diff --git a/compositor/src/shell_control.rs b/compositor/src/shell_control.rs index 5be79d9..ab89ec2 100644 --- a/compositor/src/shell_control.rs +++ b/compositor/src/shell_control.rs @@ -1,3 +1,4 @@ +use ferese_core::{OutputId, WorkspaceId}; use ferese_layout::WindowId; use ferese_protocols::shell::v1::server::{ ferese_shell_manager_v1::{self, FereseShellManagerV1}, @@ -91,6 +92,20 @@ impl Dispatch for Ferese { join_id(window_hi, window_lo), ShellWindowAction::Close, ), + ferese_shell_v1::Request::ActivateWorkspace { + workspace_hi, + workspace_lo, + } => { + let workspace = WorkspaceId(join_u64(workspace_hi, workspace_lo)); + + if !state.activate_managed_workspace(workspace) { + send_request_failed( + shell, + ferese_shell_v1::FailedRequest::ActivateWorkspace, + workspace.0, + ); + } + } ferese_shell_v1::Request::EnterOverview => state.set_overview_active(true), ferese_shell_v1::Request::ExitOverview => state.set_overview_active(false), ferese_shell_v1::Request::SelectOverviewWindow { @@ -103,7 +118,7 @@ impl Dispatch for Ferese { send_request_failed( shell, ferese_shell_v1::FailedRequest::SelectOverviewWindow, - id, + id.0, ); } } @@ -128,9 +143,40 @@ impl Ferese { fn send_shell_snapshot(&mut self, shell: &FereseShellV1) { self.shell_snapshot_serial = self.shell_snapshot_serial.wrapping_add(1); let serial = self.shell_snapshot_serial; + let outputs = self.output_snapshots(); + let workspaces = self.workspace_snapshots(); let snapshots = self.managed_window_snapshots(); shell.snapshot_begin(serial); + for output in outputs { + let (output_hi, output_lo) = split_id(output.id.0); + let (workspace_hi, workspace_lo) = split_id(output.active_workspace.0); + + shell.output( + output_hi, + output_lo, + output.name, + workspace_hi, + workspace_lo, + u32::from(output.focused), + ); + } + for workspace in workspaces { + let (workspace_hi, workspace_lo) = split_id(workspace.id.0); + let (output_hi, output_lo) = workspace + .output + .map(|output| split_id(output.0)) + .unwrap_or_default(); + + shell.workspace( + workspace_hi, + workspace_lo, + output_hi, + output_lo, + workspace.name, + u32::from(workspace.active), + ); + } for snapshot in snapshots { let (window_hi, window_lo) = split_id(snapshot.id.0); let (workspace_hi, workspace_lo) = split_id(snapshot.workspace); @@ -148,6 +194,48 @@ impl Ferese { shell.snapshot_end(serial); } + fn output_snapshots(&self) -> Vec { + let focused = self.output_workspaces.focused_output(); + let mut outputs = self + .output_ids + .iter() + .filter_map(|(output, id)| { + Some(OutputSnapshot { + id: *id, + name: output.name(), + active_workspace: self.output_workspaces.active_workspace(*id)?, + focused: focused == Some(*id), + }) + }) + .collect::>(); + + outputs.sort_by_key(|output| output.id.0); + outputs + } + + fn workspace_snapshots(&self) -> Vec { + let mut workspaces = self + .workspaces + .iter() + .map(|workspace| { + let output = self.output_workspaces.output_for_workspace(workspace.id); + let active = output.is_some_and(|output| { + self.output_workspaces.active_workspace(output) == Some(workspace.id) + }); + + WorkspaceSnapshot { + id: workspace.id, + output, + name: workspace.name.clone(), + active, + } + }) + .collect::>(); + + workspaces.sort_by_key(|workspace| workspace.id.0); + workspaces + } + fn managed_window_snapshots(&self) -> Vec { let mut snapshots = self .window_ids @@ -212,7 +300,7 @@ impl Ferese { }; if !succeeded { - send_request_failed(shell, request, id); + send_request_failed(shell, request, id.0); } } @@ -242,26 +330,40 @@ struct ManagedWindowSnapshot { state: ferese_shell_v1::WindowState, } +struct OutputSnapshot { + id: OutputId, + name: String, + active_workspace: WorkspaceId, + focused: bool, +} + +struct WorkspaceSnapshot { + id: WorkspaceId, + output: Option, + name: String, + active: bool, +} + fn split_id(id: u64) -> (u32, u32) { ((id >> 32) as u32, id as u32) } fn join_id(hi: u32, lo: u32) -> WindowId { - WindowId((u64::from(hi) << 32) | u64::from(lo)) + WindowId(join_u64(hi, lo)) } -fn send_request_failed( - shell: &FereseShellV1, - request: ferese_shell_v1::FailedRequest, - id: WindowId, -) { - let (window_hi, window_lo) = split_id(id.0); +fn join_u64(hi: u32, lo: u32) -> u64 { + (u64::from(hi) << 32) | u64::from(lo) +} + +fn send_request_failed(shell: &FereseShellV1, request: ferese_shell_v1::FailedRequest, id: u64) { + let (object_hi, object_lo) = split_id(id); shell.request_failed( request, - window_hi, - window_lo, - "unknown or unavailable managed window".to_owned(), + object_hi, + object_lo, + "unknown or unavailable shell object".to_owned(), ); } diff --git a/compositor/src/state.rs b/compositor/src/state.rs index 21c38ca..bc03d9c 100644 --- a/compositor/src/state.rs +++ b/compositor/src/state.rs @@ -114,7 +114,7 @@ pub struct Ferese { pub space: Space, pub workspaces: WorkspaceSet, pub output_workspaces: OutputWorkspaceMap, - output_ids: HashMap, + pub(crate) output_ids: HashMap, output_identity_ids: HashMap, pub window_ids: HashMap, pub window_geometry: HashMap, @@ -239,18 +239,20 @@ impl Ferese { geometry.size.h, ); - match self + let registration = self .output_workspaces - .connect(output_id, geometry, fallback_workspace) - { + .connect(output_id, geometry, fallback_workspace); + + match registration { Ok(workspace) => { self.output_ids.insert(output.clone(), output_id); if self.output_workspaces.focused_output() == Some(output_id) { self.activate_output_workspace(output_id, workspace); } + self.send_shell_snapshots(); } Err(error) => { - tracing::error!(%error, output = %output.name(), "failed to register output") + tracing::error!(%error, output = %output.name(), "failed to register output"); } } } @@ -1665,6 +1667,37 @@ impl Ferese { true } + pub(crate) fn activate_managed_workspace(&mut self, workspace: WorkspaceId) -> bool { + if self.workspaces.workspace(workspace).is_none() { + return false; + } + + let output = self + .output_workspaces + .output_for_workspace(workspace) + .or_else(|| self.output_workspaces.focused_output()); + let Some(output) = output else { + return false; + }; + + if self + .output_workspaces + .output_for_workspace(workspace) + .is_none() + && self + .output_workspaces + .assign_workspace(output, workspace) + .is_err() + { + return false; + } + + self.activate_output_workspace(output, workspace); + self.relayout(); + self.restore_keyboard_focus(); + true + } + pub(crate) fn closing_visual(&self, id: WindowId) -> (f64, f32) { let Some(animation) = self.closing_windows.get(&id) else { return (1.0, 1.0); diff --git a/crates/ferese-protocols/build.rs b/crates/ferese-protocols/build.rs new file mode 100644 index 0000000..da36b21 --- /dev/null +++ b/crates/ferese-protocols/build.rs @@ -0,0 +1,4 @@ +fn main() { + println!("cargo::rerun-if-changed=../../protocols/ferese-effects-v1.xml"); + println!("cargo::rerun-if-changed=../../protocols/ferese-shell-v1.xml"); +} diff --git a/protocols/ferese-shell-v1.xml b/protocols/ferese-shell-v1.xml index 354aa80..36bf7b6 100644 --- a/protocols/ferese-shell-v1.xml +++ b/protocols/ferese-shell-v1.xml @@ -37,9 +37,9 @@ - Window and workspace identifiers are unsigned 64-bit values encoded as - high and low 32-bit words. Each snapshot atomically replaces the prior - managed-window view after snapshot_end. + Window, workspace, and output identifiers are unsigned 64-bit values + encoded as high and low 32-bit words. Each snapshot atomically replaces + the prior shell view after snapshot_end. @@ -52,6 +52,11 @@ + + + + + @@ -73,6 +78,7 @@ + @@ -89,6 +95,24 @@ + + + + + + + + + + + + + + + + + + @@ -99,8 +123,8 @@ - - + + diff --git a/tools/ferese-shell-control-probe/src/main.rs b/tools/ferese-shell-control-probe/src/main.rs index ce1c6c3..f9f26e9 100644 --- a/tools/ferese-shell-control-probe/src/main.rs +++ b/tools/ferese-shell-control-probe/src/main.rs @@ -53,8 +53,10 @@ fn main() -> Result<(), Box> { } println!( - "PASS shell-control snapshot={} windows={} overview round-trip and stale-id rejection", + "PASS shell-control snapshot={} outputs={} workspaces={} windows={} overview round-trip and stale-id rejection", state.snapshot_serial.unwrap_or_default(), + state.output_count, + state.workspace_count, state.window_count ); Ok(()) @@ -68,6 +70,8 @@ struct ProbeState { snapshot_serial: Option, snapshot_complete: bool, window_count: usize, + output_count: usize, + workspace_count: usize, overview_active: Option, overview_events: usize, invalid_window_rejected: bool, @@ -77,6 +81,8 @@ impl ProbeState { fn initialized(&self) -> bool { self.capabilities == Some(1) && self.snapshot_complete + && self.output_count > 0 + && self.workspace_count > 0 && self.overview_active == Some(false) } } @@ -122,10 +128,18 @@ impl Dispatch for ProbeState { state.snapshot_serial = Some(serial); state.snapshot_complete = false; state.window_count = 0; + state.output_count = 0; + state.workspace_count = 0; } ferese_shell_v1::Event::Window { .. } => { state.window_count += 1; } + ferese_shell_v1::Event::Output { .. } => { + state.output_count += 1; + } + ferese_shell_v1::Event::Workspace { .. } => { + state.workspace_count += 1; + } ferese_shell_v1::Event::SnapshotEnd { serial } => { state.snapshot_complete = state.snapshot_serial == Some(serial); } @@ -135,14 +149,14 @@ impl Dispatch for ProbeState { } ferese_shell_v1::Event::RequestFailed { request, - window_hi, - window_lo, + object_hi, + object_lo, .. } => { state.invalid_window_rejected = request == WEnum::Value(ferese_shell_v1::FailedRequest::ActivateWindow) - && window_hi == u32::MAX - && window_lo == u32::MAX; + && object_hi == u32::MAX + && object_lo == u32::MAX; } _ => {} }