mirror of
https://github.com/CloudNebulaProject/wayray.git
synced 2026-04-10 13:10:41 +00:00
Wire WM protocol requests to compositor and add keybinding support
Complete the Phase 2.5 protocol wiring so external WMs can actually control the compositor: - Add compositor action methods to WmProtocolHandler: configure_window, focus_window, close_window, set_fullscreen, set_decoration - Wire manage-phase requests (propose_dimensions, set_focus, close, fullscreen grant/deny, decorations) through to Smithay ToplevelSurface - Trigger manage_start on new toplevel, render_start each frame, and notify_window_closed on toplevel destruction when external WM connected - Apply external WM render commands in headless render loop - Keybinding dispatch: bind_key/unbind_key storage, mode support, binding_pressed/released events sent to WM seat - Built-in floating WM: Alt+F4 close, Alt+Tab focus cycling with tests
This commit is contained in:
parent
f2aebe04a6
commit
f648a8af39
6 changed files with 392 additions and 42 deletions
|
|
@ -238,7 +238,36 @@ fn render_headless_frame(data: &mut CalloopData) {
|
||||||
data.state.space.refresh();
|
data.state.space.refresh();
|
||||||
|
|
||||||
// Apply WM render phase — positions/z-order before frame capture.
|
// Apply WM render phase — positions/z-order before frame capture.
|
||||||
data.state.apply_wm_render_commands();
|
// If an external WM is connected, trigger the render phase protocol
|
||||||
|
// and apply its commands instead of the built-in WM's.
|
||||||
|
if let Some(proto) = &mut data.state.wm_state.protocol {
|
||||||
|
if proto.is_wm_connected() {
|
||||||
|
proto.start_render_phase();
|
||||||
|
// Note: The external WM responds via protocol dispatch in the
|
||||||
|
// next display.dispatch_clients() call. For this frame, apply
|
||||||
|
// any commands accumulated from previous dispatches.
|
||||||
|
let commands = proto.take_render_commands();
|
||||||
|
for cmd in commands {
|
||||||
|
if let Some(window) = data
|
||||||
|
.state
|
||||||
|
.window_ids
|
||||||
|
.iter()
|
||||||
|
.find(|(id, _)| *id == cmd.id)
|
||||||
|
.map(|(_, w)| w.clone())
|
||||||
|
{
|
||||||
|
if cmd.visible {
|
||||||
|
data.state.space.map_element(window, cmd.position, false);
|
||||||
|
} else {
|
||||||
|
data.state.space.unmap_elem(&window);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
data.state.apply_wm_render_commands();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
data.state.apply_wm_render_commands();
|
||||||
|
}
|
||||||
|
|
||||||
let custom_elements: &[TextureRenderElement<PixmanTexture>] = &[];
|
let custom_elements: &[TextureRenderElement<PixmanTexture>] = &[];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,15 @@
|
||||||
//!
|
//!
|
||||||
//! Connects the generated protocol types to our WmProtocolState implementation.
|
//! Connects the generated protocol types to our WmProtocolState implementation.
|
||||||
|
|
||||||
use smithay::reexports::wayland_server::{
|
use smithay::reexports::{
|
||||||
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, backend::ClientId,
|
wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode as SmithayDecorationMode,
|
||||||
|
wayland_protocols::xdg::shell::server::xdg_toplevel,
|
||||||
|
wayland_server::{
|
||||||
|
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, backend::ClientId,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
use smithay::utils::SERIAL_COUNTER;
|
||||||
|
use tracing::warn;
|
||||||
use wayray_wm_protocol::server::{
|
use wayray_wm_protocol::server::{
|
||||||
wayray_wm_manager_v1::WayrayWmManagerV1, wayray_wm_seat_v1::WayrayWmSeatV1,
|
wayray_wm_manager_v1::WayrayWmManagerV1, wayray_wm_seat_v1::WayrayWmSeatV1,
|
||||||
wayray_wm_window_v1::WayrayWmWindowV1, wayray_wm_workspace_v1::WayrayWmWorkspaceV1,
|
wayray_wm_window_v1::WayrayWmWindowV1, wayray_wm_workspace_v1::WayrayWmWorkspaceV1,
|
||||||
|
|
@ -12,6 +18,7 @@ use wayray_wm_protocol::server::{
|
||||||
|
|
||||||
use crate::state::WayRay;
|
use crate::state::WayRay;
|
||||||
use crate::wm::protocol::{WmGlobalData, WmProtocolHandler, WmProtocolState, WmWindowData};
|
use crate::wm::protocol::{WmGlobalData, WmProtocolHandler, WmProtocolState, WmWindowData};
|
||||||
|
use crate::wm::types::DecorationMode;
|
||||||
|
|
||||||
impl WmProtocolHandler for WayRay {
|
impl WmProtocolHandler for WayRay {
|
||||||
fn wm_protocol_state(&mut self) -> &mut WmProtocolState {
|
fn wm_protocol_state(&mut self) -> &mut WmProtocolState {
|
||||||
|
|
@ -31,6 +38,75 @@ impl WmProtocolHandler for WayRay {
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn configure_window(&mut self, id: crate::wm::types::WindowId, width: i32, height: i32) {
|
||||||
|
let Some(window) = self.window_for_id(id).cloned() else {
|
||||||
|
warn!(?id, "configure_window: unknown window");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if let Some(toplevel) = window.toplevel() {
|
||||||
|
toplevel.with_pending_state(|state| {
|
||||||
|
state.size = Some((width, height).into());
|
||||||
|
});
|
||||||
|
toplevel.send_pending_configure();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn focus_window(&mut self, id: crate::wm::types::WindowId) {
|
||||||
|
let Some(window) = self.window_for_id(id).cloned() else {
|
||||||
|
warn!(?id, "focus_window: unknown window");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
self.space.raise_element(&window, true);
|
||||||
|
let serial = SERIAL_COUNTER.next_serial();
|
||||||
|
let keyboard = self.seat.get_keyboard().unwrap();
|
||||||
|
let wl_surface = window.toplevel().map(|t| t.wl_surface().clone());
|
||||||
|
keyboard.set_focus(self, wl_surface, serial);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn close_window(&mut self, id: crate::wm::types::WindowId) {
|
||||||
|
let Some(window) = self.window_for_id(id).cloned() else {
|
||||||
|
warn!(?id, "close_window: unknown window");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if let Some(toplevel) = window.toplevel() {
|
||||||
|
toplevel.send_close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_fullscreen(&mut self, id: crate::wm::types::WindowId, granted: bool) {
|
||||||
|
let Some(window) = self.window_for_id(id).cloned() else {
|
||||||
|
warn!(?id, "set_fullscreen: unknown window");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if let Some(toplevel) = window.toplevel() {
|
||||||
|
toplevel.with_pending_state(|state| {
|
||||||
|
if granted {
|
||||||
|
state.states.set(xdg_toplevel::State::Fullscreen);
|
||||||
|
} else {
|
||||||
|
state.states.unset(xdg_toplevel::State::Fullscreen);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
toplevel.send_pending_configure();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_decoration(&mut self, id: crate::wm::types::WindowId, mode: DecorationMode) {
|
||||||
|
let Some(window) = self.window_for_id(id).cloned() else {
|
||||||
|
warn!(?id, "set_decoration: unknown window");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if let Some(toplevel) = window.toplevel() {
|
||||||
|
let smithay_mode = match mode {
|
||||||
|
DecorationMode::ServerSide => SmithayDecorationMode::ServerSide,
|
||||||
|
DecorationMode::ClientSide => SmithayDecorationMode::ClientSide,
|
||||||
|
};
|
||||||
|
toplevel.with_pending_state(|state| {
|
||||||
|
state.decoration_mode = Some(smithay_mode);
|
||||||
|
});
|
||||||
|
toplevel.send_pending_configure();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delegate GlobalDispatch for the manager global.
|
// Delegate GlobalDispatch for the manager global.
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,37 @@ impl XdgShellHandler for WayRay {
|
||||||
pos = ?response.position,
|
pos = ?response.position,
|
||||||
"new toplevel mapped via WM"
|
"new toplevel mapped via WM"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Notify external WM if connected, triggering a manage phase.
|
||||||
|
if let Some(proto) = &mut self.wm_state.protocol
|
||||||
|
&& proto.is_wm_connected()
|
||||||
|
{
|
||||||
|
proto.notify_new_window::<WayRay>(id, None, None, response.size.0, response.size.1);
|
||||||
|
proto.start_manage_phase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn toplevel_destroyed(&mut self, surface: ToplevelSurface) {
|
||||||
|
// Find and remove the window from our tracking.
|
||||||
|
let wl_surface = surface.wl_surface();
|
||||||
|
if let Some(pos) = self.window_ids.iter().position(|(_, w)| {
|
||||||
|
w.toplevel()
|
||||||
|
.map(|t| t.wl_surface() == wl_surface)
|
||||||
|
.unwrap_or(false)
|
||||||
|
}) {
|
||||||
|
let (id, window) = self.window_ids.remove(pos);
|
||||||
|
self.wm_state.active_wm().on_close_toplevel(id);
|
||||||
|
self.space.unmap_elem(&window);
|
||||||
|
|
||||||
|
// Notify external WM if connected.
|
||||||
|
if let Some(proto) = &mut self.wm_state.protocol
|
||||||
|
&& proto.is_wm_connected()
|
||||||
|
{
|
||||||
|
proto.notify_window_closed(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
info!(?id, "toplevel destroyed");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_popup(&mut self, surface: PopupSurface, _positioner: PositionerState) {
|
fn new_popup(&mut self, surface: PopupSurface, _positioner: PositionerState) {
|
||||||
|
|
|
||||||
|
|
@ -283,6 +283,39 @@ impl WayRay {
|
||||||
pub fn inject_network_input(&mut self, msg: InputMessage) {
|
pub fn inject_network_input(&mut self, msg: InputMessage) {
|
||||||
match msg {
|
match msg {
|
||||||
InputMessage::Keyboard(ev) => {
|
InputMessage::Keyboard(ev) => {
|
||||||
|
let pressed = matches!(ev.state, wayray_protocol::messages::KeyState::Pressed);
|
||||||
|
|
||||||
|
// Check if an external WM wants this key.
|
||||||
|
if let Some(proto) = &self.wm_state.protocol
|
||||||
|
&& proto.check_key_binding(ev.keycode, 0, pressed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the built-in WM wants this key (only on press).
|
||||||
|
if pressed && self.wm_state.active_wm().on_key_binding(ev.keycode, 0) {
|
||||||
|
// Alt+F4: close the focused window.
|
||||||
|
if ev.keycode == crate::wm::floating::KEY_F4
|
||||||
|
&& let Some(focused) = self.wm_state.builtin.focused()
|
||||||
|
&& let Some(window) = self.window_for_id(focused).cloned()
|
||||||
|
&& let Some(toplevel) = window.toplevel()
|
||||||
|
{
|
||||||
|
toplevel.send_close();
|
||||||
|
}
|
||||||
|
// Alt+Tab: focus the next window.
|
||||||
|
if ev.keycode == crate::wm::floating::KEY_TAB
|
||||||
|
&& let Some(new_focus) = self.wm_state.builtin.focused()
|
||||||
|
&& let Some(window) = self.window_for_id(new_focus).cloned()
|
||||||
|
{
|
||||||
|
self.space.raise_element(&window, true);
|
||||||
|
let serial = SERIAL_COUNTER.next_serial();
|
||||||
|
let keyboard = self.seat.get_keyboard().unwrap();
|
||||||
|
let wl_surface = window.toplevel().map(|t| t.wl_surface().clone());
|
||||||
|
keyboard.set_focus(self, wl_surface, serial);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let serial = SERIAL_COUNTER.next_serial();
|
let serial = SERIAL_COUNTER.next_serial();
|
||||||
let keyboard = self.seat.get_keyboard().unwrap();
|
let keyboard = self.seat.get_keyboard().unwrap();
|
||||||
let state = match ev.state {
|
let state = match ev.state {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,14 @@ use std::collections::HashMap;
|
||||||
use super::WindowManager;
|
use super::WindowManager;
|
||||||
use super::types::{DecorationMode, ManageResponse, RenderCommand, WindowId, WindowInfo, ZOrder};
|
use super::types::{DecorationMode, ManageResponse, RenderCommand, WindowId, WindowInfo, ZOrder};
|
||||||
|
|
||||||
|
/// Modifier bitmask for Alt (Mod1 in X11/XKB terms).
|
||||||
|
pub const MOD_ALT: u32 = 0x08;
|
||||||
|
|
||||||
|
/// Linux evdev keycode for F4.
|
||||||
|
pub const KEY_F4: u32 = 62;
|
||||||
|
/// Linux evdev keycode for Tab.
|
||||||
|
pub const KEY_TAB: u32 = 15;
|
||||||
|
|
||||||
/// State for a single managed window in the floating WM.
|
/// State for a single managed window in the floating WM.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct WindowState {
|
struct WindowState {
|
||||||
|
|
@ -39,6 +47,32 @@ impl BuiltinFloatingWm {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the currently focused window (top of focus stack).
|
||||||
|
pub fn focused(&self) -> Option<WindowId> {
|
||||||
|
self.focus_stack.last().copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cycle focus to the next window in the stack.
|
||||||
|
/// Returns the newly focused window id, if any.
|
||||||
|
pub fn focus_next(&mut self) -> Option<WindowId> {
|
||||||
|
if self.focus_stack.len() < 2 {
|
||||||
|
return self.focus_stack.last().copied();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move the top (focused) window to the bottom of the stack.
|
||||||
|
let top = self.focus_stack.pop().unwrap();
|
||||||
|
self.focus_stack.insert(0, top);
|
||||||
|
|
||||||
|
// The new top is now focused — raise it.
|
||||||
|
let new_focus = *self.focus_stack.last().unwrap();
|
||||||
|
if let Some(state) = self.windows.get_mut(&new_focus) {
|
||||||
|
state.z_index = self.next_z;
|
||||||
|
self.next_z += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(new_focus)
|
||||||
|
}
|
||||||
|
|
||||||
/// Move window to the top of the focus stack.
|
/// Move window to the top of the focus stack.
|
||||||
fn raise_to_top(&mut self, id: WindowId) {
|
fn raise_to_top(&mut self, id: WindowId) {
|
||||||
self.focus_stack.retain(|&w| w != id);
|
self.focus_stack.retain(|&w| w != id);
|
||||||
|
|
@ -125,9 +159,25 @@ impl WindowManager for BuiltinFloatingWm {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_key_binding(&mut self, _key: u32, _modifiers: u32) -> bool {
|
fn on_key_binding(&mut self, key: u32, modifiers: u32) -> bool {
|
||||||
// TODO: Alt+F4 close, Alt+Tab cycle
|
if modifiers & MOD_ALT == 0 {
|
||||||
false
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
match key {
|
||||||
|
KEY_F4 => {
|
||||||
|
// Alt+F4: signal that the focused window should close.
|
||||||
|
// The actual close is handled by the compositor via
|
||||||
|
// the returned `true` + checking focused().
|
||||||
|
true
|
||||||
|
}
|
||||||
|
KEY_TAB => {
|
||||||
|
// Alt+Tab: cycle focus to the next window.
|
||||||
|
self.focus_next();
|
||||||
|
true
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_pointer_focus(&mut self, id: WindowId) -> Option<WindowId> {
|
fn on_pointer_focus(&mut self, id: WindowId) -> Option<WindowId> {
|
||||||
|
|
@ -228,4 +278,54 @@ mod tests {
|
||||||
assert_eq!(wm.windows[&id].position, (0, 0));
|
assert_eq!(wm.windows[&id].position, (0, 0));
|
||||||
assert_eq!(wm.windows[&id].size, (1280, 720));
|
assert_eq!(wm.windows[&id].size, (1280, 720));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn alt_f4_signals_close() {
|
||||||
|
let mut wm = BuiltinFloatingWm::new(1280, 720);
|
||||||
|
let id = WindowId::from_raw(1);
|
||||||
|
wm.on_new_toplevel(id, make_info());
|
||||||
|
|
||||||
|
// Alt+F4 should be consumed.
|
||||||
|
assert!(wm.on_key_binding(KEY_F4, MOD_ALT));
|
||||||
|
// The focused window should still exist (compositor handles actual close).
|
||||||
|
assert_eq!(wm.focused(), Some(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn alt_tab_cycles_focus() {
|
||||||
|
let mut wm = BuiltinFloatingWm::new(1280, 720);
|
||||||
|
let id1 = WindowId::from_raw(1);
|
||||||
|
let id2 = WindowId::from_raw(2);
|
||||||
|
let id3 = WindowId::from_raw(3);
|
||||||
|
|
||||||
|
wm.on_new_toplevel(id1, make_info());
|
||||||
|
wm.on_new_toplevel(id2, make_info());
|
||||||
|
wm.on_new_toplevel(id3, make_info());
|
||||||
|
|
||||||
|
assert_eq!(wm.focused(), Some(id3));
|
||||||
|
|
||||||
|
// Alt+Tab should cycle to id2.
|
||||||
|
assert!(wm.on_key_binding(KEY_TAB, MOD_ALT));
|
||||||
|
assert_eq!(wm.focused(), Some(id2));
|
||||||
|
|
||||||
|
// Again -> id1.
|
||||||
|
assert!(wm.on_key_binding(KEY_TAB, MOD_ALT));
|
||||||
|
assert_eq!(wm.focused(), Some(id1));
|
||||||
|
|
||||||
|
// Again -> back to id3.
|
||||||
|
assert!(wm.on_key_binding(KEY_TAB, MOD_ALT));
|
||||||
|
assert_eq!(wm.focused(), Some(id3));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn non_alt_keys_not_consumed() {
|
||||||
|
let mut wm = BuiltinFloatingWm::new(1280, 720);
|
||||||
|
let id = WindowId::from_raw(1);
|
||||||
|
wm.on_new_toplevel(id, make_info());
|
||||||
|
|
||||||
|
// F4 without Alt should not be consumed.
|
||||||
|
assert!(!wm.on_key_binding(KEY_F4, 0));
|
||||||
|
// Random key with Alt should not be consumed.
|
||||||
|
assert!(!wm.on_key_binding(42, MOD_ALT));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
//! Implements `GlobalDispatch` and `Dispatch` for the four custom WM interfaces,
|
//! Implements `GlobalDispatch` and `Dispatch` for the four custom WM interfaces,
|
||||||
//! allowing external WM clients to connect and control window layout.
|
//! allowing external WM clients to connect and control window layout.
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
use smithay::reexports::wayland_server::{
|
use smithay::reexports::wayland_server::{
|
||||||
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
|
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
|
||||||
|
|
@ -17,7 +17,7 @@ use wayray_wm_protocol::server::{
|
||||||
wayray_wm_workspace_v1::{self, WayrayWmWorkspaceV1},
|
wayray_wm_workspace_v1::{self, WayrayWmWorkspaceV1},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::types::{RenderCommand, WindowId, ZOrder};
|
use super::types::{DecorationMode, RenderCommand, WindowId, ZOrder};
|
||||||
|
|
||||||
/// Window info tuple for sending to a newly connected WM.
|
/// Window info tuple for sending to a newly connected WM.
|
||||||
/// (window_id, title, app_id, width, height)
|
/// (window_id, title, app_id, width, height)
|
||||||
|
|
@ -51,6 +51,14 @@ pub struct WmProtocolState {
|
||||||
render_phase_active: bool,
|
render_phase_active: bool,
|
||||||
/// Display handle for creating resources.
|
/// Display handle for creating resources.
|
||||||
dh: DisplayHandle,
|
dh: DisplayHandle,
|
||||||
|
/// Registered keybindings: (key, modifiers, mode) -> active.
|
||||||
|
keybindings: HashSet<(u32, u32, String)>,
|
||||||
|
/// Available binding modes.
|
||||||
|
binding_modes: HashSet<String>,
|
||||||
|
/// Currently active binding mode (empty string = default).
|
||||||
|
active_mode: String,
|
||||||
|
/// The WM's seat object for sending binding events.
|
||||||
|
wm_seat: Option<WayrayWmSeatV1>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for WmProtocolState {
|
impl std::fmt::Debug for WmProtocolState {
|
||||||
|
|
@ -84,6 +92,10 @@ impl WmProtocolState {
|
||||||
manage_phase_active: false,
|
manage_phase_active: false,
|
||||||
render_phase_active: false,
|
render_phase_active: false,
|
||||||
dh: dh.clone(),
|
dh: dh.clone(),
|
||||||
|
keybindings: HashSet::new(),
|
||||||
|
binding_modes: HashSet::new(),
|
||||||
|
active_mode: String::new(),
|
||||||
|
wm_seat: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -169,6 +181,32 @@ impl WmProtocolState {
|
||||||
std::mem::take(&mut self.pending_render_commands)
|
std::mem::take(&mut self.pending_render_commands)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if a key+modifiers combination is registered as a WM binding.
|
||||||
|
/// If so, send the binding_pressed event to the WM and return true.
|
||||||
|
pub fn check_key_binding(&self, key: u32, modifiers: u32, pressed: bool) -> bool {
|
||||||
|
// Check default mode bindings and active mode bindings.
|
||||||
|
let default_match = self.keybindings.contains(&(key, modifiers, String::new()));
|
||||||
|
let mode_match = !self.active_mode.is_empty()
|
||||||
|
&& self
|
||||||
|
.keybindings
|
||||||
|
.contains(&(key, modifiers, self.active_mode.clone()));
|
||||||
|
|
||||||
|
if !default_match && !mode_match {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send binding event to the WM's seat object.
|
||||||
|
if let Some(seat) = &self.wm_seat {
|
||||||
|
if pressed {
|
||||||
|
seat.binding_pressed(key, modifiers);
|
||||||
|
} else {
|
||||||
|
seat.binding_released(key, modifiers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
/// Send the full window list to a newly connected WM.
|
/// Send the full window list to a newly connected WM.
|
||||||
fn send_full_window_list<D>(
|
fn send_full_window_list<D>(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|
@ -234,6 +272,23 @@ pub trait WmProtocolHandler:
|
||||||
/// Return the list of existing windows for sending to a newly connected WM.
|
/// Return the list of existing windows for sending to a newly connected WM.
|
||||||
/// Each tuple is (window_id, title, app_id, width, height).
|
/// Each tuple is (window_id, title, app_id, width, height).
|
||||||
fn existing_windows(&self) -> Vec<WindowSnapshot>;
|
fn existing_windows(&self) -> Vec<WindowSnapshot>;
|
||||||
|
|
||||||
|
// -- Compositor actions: let protocol dispatch reach back into Smithay --
|
||||||
|
|
||||||
|
/// Send a configure with proposed dimensions to the window's toplevel.
|
||||||
|
fn configure_window(&mut self, id: WindowId, width: i32, height: i32);
|
||||||
|
|
||||||
|
/// Move keyboard focus to the specified window.
|
||||||
|
fn focus_window(&mut self, id: WindowId);
|
||||||
|
|
||||||
|
/// Ask the client to close the specified window.
|
||||||
|
fn close_window(&mut self, id: WindowId);
|
||||||
|
|
||||||
|
/// Set or unset fullscreen state on a window.
|
||||||
|
fn set_fullscreen(&mut self, id: WindowId, granted: bool);
|
||||||
|
|
||||||
|
/// Set the decoration mode (server-side or client-side) for a window.
|
||||||
|
fn set_decoration(&mut self, id: WindowId, mode: DecorationMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Manager ---
|
// --- Manager ---
|
||||||
|
|
@ -314,6 +369,9 @@ impl<D: WmProtocolHandler> Dispatch<WayrayWmManagerV1, (), D> for WmProtocolStat
|
||||||
proto.window_objects.clear();
|
proto.window_objects.clear();
|
||||||
proto.manage_phase_active = false;
|
proto.manage_phase_active = false;
|
||||||
proto.render_phase_active = false;
|
proto.render_phase_active = false;
|
||||||
|
proto.keybindings.clear();
|
||||||
|
proto.wm_seat = None;
|
||||||
|
proto.active_mode.clear();
|
||||||
info!("external WM disconnected");
|
info!("external WM disconnected");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -331,34 +389,46 @@ impl<D: WmProtocolHandler> Dispatch<WayrayWmWindowV1, WmWindowData, D> for WmPro
|
||||||
_dh: &DisplayHandle,
|
_dh: &DisplayHandle,
|
||||||
_data_init: &mut DataInit<'_, D>,
|
_data_init: &mut DataInit<'_, D>,
|
||||||
) {
|
) {
|
||||||
let proto = state.wm_protocol_state();
|
|
||||||
let window_id = data.window_id;
|
let window_id = data.window_id;
|
||||||
|
|
||||||
|
// Manage-phase requests: call compositor actions directly on state.
|
||||||
|
// These need &mut access to the full compositor, not just the protocol state.
|
||||||
match request {
|
match request {
|
||||||
wayray_wm_window_v1::Request::ProposeDimensions {
|
wayray_wm_window_v1::Request::ProposeDimensions { width, height } => {
|
||||||
width: _,
|
state.configure_window(window_id, width, height);
|
||||||
height: _,
|
return;
|
||||||
} => {
|
|
||||||
// TODO: implement dimension proposal tracking
|
|
||||||
}
|
}
|
||||||
wayray_wm_window_v1::Request::SetFocus => {
|
wayray_wm_window_v1::Request::SetFocus => {
|
||||||
// TODO: queue focus change
|
state.focus_window(window_id);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
wayray_wm_window_v1::Request::UseSsd => {
|
wayray_wm_window_v1::Request::UseSsd => {
|
||||||
// TODO: set decoration mode
|
state.set_decoration(window_id, DecorationMode::ServerSide);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
wayray_wm_window_v1::Request::UseCsd => {
|
wayray_wm_window_v1::Request::UseCsd => {
|
||||||
// TODO: set decoration mode
|
state.set_decoration(window_id, DecorationMode::ClientSide);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
wayray_wm_window_v1::Request::GrantFullscreen => {
|
wayray_wm_window_v1::Request::GrantFullscreen => {
|
||||||
// TODO: grant fullscreen
|
state.set_fullscreen(window_id, true);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
wayray_wm_window_v1::Request::DenyFullscreen => {
|
wayray_wm_window_v1::Request::DenyFullscreen => {
|
||||||
// TODO: deny fullscreen
|
state.set_fullscreen(window_id, false);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
wayray_wm_window_v1::Request::Close => {
|
wayray_wm_window_v1::Request::Close => {
|
||||||
// TODO: send close to toplevel
|
state.close_window(window_id);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render-phase requests: accumulate into pending render commands.
|
||||||
|
let proto = state.wm_protocol_state();
|
||||||
|
|
||||||
|
match request {
|
||||||
wayray_wm_window_v1::Request::SetPosition { x, y } => {
|
wayray_wm_window_v1::Request::SetPosition { x, y } => {
|
||||||
proto.pending_render_commands.push(RenderCommand {
|
proto.pending_render_commands.push(RenderCommand {
|
||||||
id: window_id,
|
id: window_id,
|
||||||
|
|
@ -368,7 +438,6 @@ impl<D: WmProtocolHandler> Dispatch<WayrayWmWindowV1, WmWindowData, D> for WmPro
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
wayray_wm_window_v1::Request::SetZTop => {
|
wayray_wm_window_v1::Request::SetZTop => {
|
||||||
// Update the last command for this window, or add a new one.
|
|
||||||
if let Some(cmd) = proto
|
if let Some(cmd) = proto
|
||||||
.pending_render_commands
|
.pending_render_commands
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
|
|
@ -378,7 +447,7 @@ impl<D: WmProtocolHandler> Dispatch<WayrayWmWindowV1, WmWindowData, D> for WmPro
|
||||||
} else {
|
} else {
|
||||||
proto.pending_render_commands.push(RenderCommand {
|
proto.pending_render_commands.push(RenderCommand {
|
||||||
id: window_id,
|
id: window_id,
|
||||||
position: (0, 0), // Will use existing position
|
position: (0, 0),
|
||||||
z_order: ZOrder::Top,
|
z_order: ZOrder::Top,
|
||||||
visible: true,
|
visible: true,
|
||||||
});
|
});
|
||||||
|
|
@ -400,15 +469,6 @@ impl<D: WmProtocolHandler> Dispatch<WayrayWmWindowV1, WmWindowData, D> for WmPro
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
wayray_wm_window_v1::Request::SetZAbove { .. } => {
|
|
||||||
// TODO: relative z-ordering
|
|
||||||
}
|
|
||||||
wayray_wm_window_v1::Request::SetZBelow { .. } => {
|
|
||||||
// TODO: relative z-ordering
|
|
||||||
}
|
|
||||||
wayray_wm_window_v1::Request::SetBorders { .. } => {
|
|
||||||
// TODO: border rendering
|
|
||||||
}
|
|
||||||
wayray_wm_window_v1::Request::Show => {
|
wayray_wm_window_v1::Request::Show => {
|
||||||
if let Some(cmd) = proto
|
if let Some(cmd) = proto
|
||||||
.pending_render_commands
|
.pending_render_commands
|
||||||
|
|
@ -441,8 +501,15 @@ impl<D: WmProtocolHandler> Dispatch<WayrayWmWindowV1, WmWindowData, D> for WmPro
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
wayray_wm_window_v1::Request::SetZAbove { .. }
|
||||||
|
| wayray_wm_window_v1::Request::SetZBelow { .. } => {
|
||||||
|
// TODO: relative z-ordering (needs sibling window lookup)
|
||||||
|
}
|
||||||
|
wayray_wm_window_v1::Request::SetBorders { .. } => {
|
||||||
|
// TODO: border rendering
|
||||||
|
}
|
||||||
wayray_wm_window_v1::Request::SetOutput { .. } => {
|
wayray_wm_window_v1::Request::SetOutput { .. } => {
|
||||||
// TODO: multi-output
|
// TODO: multi-output support
|
||||||
}
|
}
|
||||||
wayray_wm_window_v1::Request::Destroy => {}
|
wayray_wm_window_v1::Request::Destroy => {}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|
@ -466,31 +533,42 @@ impl<D: WmProtocolHandler> Dispatch<WayrayWmWindowV1, WmWindowData, D> for WmPro
|
||||||
|
|
||||||
impl<D: WmProtocolHandler> Dispatch<WayrayWmSeatV1, (), D> for WmProtocolState {
|
impl<D: WmProtocolHandler> Dispatch<WayrayWmSeatV1, (), D> for WmProtocolState {
|
||||||
fn request(
|
fn request(
|
||||||
_state: &mut D,
|
state: &mut D,
|
||||||
_client: &Client,
|
_client: &Client,
|
||||||
_resource: &WayrayWmSeatV1,
|
resource: &WayrayWmSeatV1,
|
||||||
request: wayray_wm_seat_v1::Request,
|
request: wayray_wm_seat_v1::Request,
|
||||||
_data: &(),
|
_data: &(),
|
||||||
_dh: &DisplayHandle,
|
_dh: &DisplayHandle,
|
||||||
_data_init: &mut DataInit<'_, D>,
|
_data_init: &mut DataInit<'_, D>,
|
||||||
) {
|
) {
|
||||||
|
let proto = state.wm_protocol_state();
|
||||||
|
|
||||||
|
// Store the seat object for sending binding events later.
|
||||||
|
if proto.wm_seat.is_none() {
|
||||||
|
proto.wm_seat = Some(resource.clone());
|
||||||
|
}
|
||||||
|
|
||||||
match request {
|
match request {
|
||||||
wayray_wm_seat_v1::Request::BindKey {
|
wayray_wm_seat_v1::Request::BindKey {
|
||||||
key,
|
key,
|
||||||
modifiers,
|
modifiers,
|
||||||
mode,
|
mode,
|
||||||
} => {
|
} => {
|
||||||
// TODO: register keybinding
|
proto.keybindings.insert((key, modifiers, mode.clone()));
|
||||||
info!(key, modifiers, mode, "WM registered keybinding");
|
info!(key, modifiers, mode, "WM registered keybinding");
|
||||||
}
|
}
|
||||||
wayray_wm_seat_v1::Request::UnbindKey { .. } => {
|
wayray_wm_seat_v1::Request::UnbindKey {
|
||||||
// TODO: unregister keybinding
|
key,
|
||||||
|
modifiers,
|
||||||
|
mode,
|
||||||
|
} => {
|
||||||
|
proto.keybindings.remove(&(key, modifiers, mode));
|
||||||
}
|
}
|
||||||
wayray_wm_seat_v1::Request::CreateMode { .. } => {
|
wayray_wm_seat_v1::Request::CreateMode { name } => {
|
||||||
// TODO: create binding mode
|
proto.binding_modes.insert(name);
|
||||||
}
|
}
|
||||||
wayray_wm_seat_v1::Request::ActivateMode { .. } => {
|
wayray_wm_seat_v1::Request::ActivateMode { name } => {
|
||||||
// TODO: activate binding mode
|
proto.active_mode = name;
|
||||||
}
|
}
|
||||||
wayray_wm_seat_v1::Request::StartMove { .. } => {
|
wayray_wm_seat_v1::Request::StartMove { .. } => {
|
||||||
// TODO: interactive move
|
// TODO: interactive move
|
||||||
|
|
@ -498,7 +576,10 @@ impl<D: WmProtocolHandler> Dispatch<WayrayWmSeatV1, (), D> for WmProtocolState {
|
||||||
wayray_wm_seat_v1::Request::StartResize { .. } => {
|
wayray_wm_seat_v1::Request::StartResize { .. } => {
|
||||||
// TODO: interactive resize
|
// TODO: interactive resize
|
||||||
}
|
}
|
||||||
wayray_wm_seat_v1::Request::Destroy => {}
|
wayray_wm_seat_v1::Request::Destroy => {
|
||||||
|
proto.wm_seat = None;
|
||||||
|
proto.keybindings.clear();
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue