diff options
| author | Ivan Molodetskikh <yalterz@gmail.com> | 2025-06-14 16:17:43 +0300 |
|---|---|---|
| committer | Ivan Molodetskikh <yalterz@gmail.com> | 2025-07-13 11:43:59 +0300 |
| commit | ce9ba00d54104f250f0074a54f14d1a5e4ff427f (patch) | |
| tree | d0a8b4be303b9698357a5da6ff78b0c70c6eb5d7 /src | |
| parent | 37458d94b288945f6cfbd3c5c233f634d59f246c (diff) | |
| download | niri-ce9ba00d54104f250f0074a54f14d1a5e4ff427f.tar.gz niri-ce9ba00d54104f250f0074a54f14d1a5e4ff427f.tar.bz2 niri-ce9ba00d54104f250f0074a54f14d1a5e4ff427f.zip | |
Implement ext-workspace
Diffstat (limited to 'src')
| -rw-r--r-- | src/handlers/mod.rs | 44 | ||||
| -rw-r--r-- | src/niri.rs | 6 | ||||
| -rw-r--r-- | src/protocols/ext_workspace.rs | 715 | ||||
| -rw-r--r-- | src/protocols/mod.rs | 1 |
4 files changed, 764 insertions, 2 deletions
diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index 8353cd09..5955bcec 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -76,8 +76,10 @@ use smithay::{ }; pub use crate::handlers::xdg_shell::KdeDecorationsModeState; +use crate::layout::workspace::WorkspaceId; use crate::layout::ActivateWindow; use crate::niri::{DndIcon, NewClient, State}; +use crate::protocols::ext_workspace::{self, ExtWorkspaceHandler, ExtWorkspaceManagerState}; use crate::protocols::foreign_toplevel::{ self, ForeignToplevelHandler, ForeignToplevelManagerState, }; @@ -92,8 +94,9 @@ use crate::protocols::virtual_pointer::{ }; use crate::utils::{output_size, send_scale_transform, with_toplevel_role}; use crate::{ - delegate_foreign_toplevel, delegate_gamma_control, delegate_mutter_x11_interop, - delegate_output_management, delegate_screencopy, delegate_virtual_pointer, + delegate_ext_workspace, delegate_foreign_toplevel, delegate_gamma_control, + delegate_mutter_x11_interop, delegate_output_management, delegate_screencopy, + delegate_virtual_pointer, }; pub const XDG_ACTIVATION_TOKEN_TIMEOUT: Duration = Duration::from_secs(10); @@ -414,6 +417,7 @@ delegate_ext_data_control!(State); impl OutputHandler for State { fn output_bound(&mut self, output: Output, wl_output: WlOutput) { foreign_toplevel::on_output_bound(self, &output, &wl_output); + ext_workspace::on_output_bound(self, &output, &wl_output); } } delegate_output!(State); @@ -574,6 +578,42 @@ impl ForeignToplevelHandler for State { } delegate_foreign_toplevel!(State); +impl ExtWorkspaceHandler for State { + fn ext_workspace_manager_state(&mut self) -> &mut ExtWorkspaceManagerState { + &mut self.niri.ext_workspace_state + } + + fn activate_workspace(&mut self, id: WorkspaceId) { + let reference = niri_config::WorkspaceReference::Id(id.get()); + if let Some((mut output, index)) = self.niri.find_output_and_workspace_index(reference) { + if let Some(active) = self.niri.layout.active_output() { + if output.as_ref() == Some(active) { + output = None; + } + } + + if let Some(output) = output { + self.niri.layout.focus_output(&output); + } + self.niri.layout.switch_workspace(index); + // No mouse warp: assuming the layer-shell bar workspaces use-case. + + // FIXME: granular + self.niri.queue_redraw_all(); + } + } + + fn assign_workspace(&mut self, ws_id: WorkspaceId, output: Output) { + let reference = niri_config::WorkspaceReference::Id(ws_id.get()); + if let Some((old_output, old_idx)) = self.niri.find_output_and_workspace_index(reference) { + self.niri + .layout + .move_workspace_to_output_by_id(old_idx, old_output, output); + } + } +} +delegate_ext_workspace!(State); + impl ScreencopyHandler for State { fn frame(&mut self, manager: &ZwlrScreencopyManagerV1, screencopy: Screencopy) { // If with_damage then push it onto the queue for redraw of the output, diff --git a/src/niri.rs b/src/niri.rs index fd4112e6..05db2fd0 100644 --- a/src/niri.rs +++ b/src/niri.rs @@ -136,6 +136,7 @@ use crate::layout::tile::TileRenderElement; use crate::layout::workspace::{Workspace, WorkspaceId}; use crate::layout::{HitType, Layout, LayoutElement as _, MonitorRenderElement}; use crate::niri_render_elements; +use crate::protocols::ext_workspace::{self, ExtWorkspaceManagerState}; use crate::protocols::foreign_toplevel::{self, ForeignToplevelManagerState}; use crate::protocols::gamma_control::GammaControlManagerState; use crate::protocols::mutter_x11_interop::MutterX11InteropManagerState; @@ -259,6 +260,7 @@ pub struct Niri { pub layer_shell_state: WlrLayerShellState, pub session_lock_state: SessionLockManagerState, pub foreign_toplevel_state: ForeignToplevelManagerState, + pub ext_workspace_state: ExtWorkspaceManagerState, pub screencopy_state: ScreencopyManagerState, pub output_management_state: OutputManagementManagerState, pub viewporter_state: ViewporterState, @@ -719,6 +721,7 @@ impl State { self.niri.refresh_idle_inhibit(); self.refresh_pointer_contents(); foreign_toplevel::refresh(self); + ext_workspace::refresh(self); #[cfg(feature = "xdp-gnome-screencast")] self.niri.refresh_mapped_cast_outputs(); @@ -2325,6 +2328,8 @@ impl Niri { VirtualPointerManagerState::new::<State, _>(&display_handle, client_is_unrestricted); let foreign_toplevel_state = ForeignToplevelManagerState::new::<State, _>(&display_handle, client_is_unrestricted); + let ext_workspace_state = + ExtWorkspaceManagerState::new::<State, _>(&display_handle, client_is_unrestricted); let mut output_management_state = OutputManagementManagerState::new::<State, _>(&display_handle, client_is_unrestricted); output_management_state.on_config_changed(config_.outputs.clone()); @@ -2519,6 +2524,7 @@ impl Niri { layer_shell_state, session_lock_state, foreign_toplevel_state, + ext_workspace_state, output_management_state, screencopy_state, viewporter_state, diff --git a/src/protocols/ext_workspace.rs b/src/protocols/ext_workspace.rs new file mode 100644 index 00000000..3d97cc4f --- /dev/null +++ b/src/protocols/ext_workspace.rs @@ -0,0 +1,715 @@ +//! ext-workspace protocol implementation. +//! +//! This is how we map the protocol concepts to the niri concepts: +//! +//! - Workspace groups are outputs. +//! - Workspace coordinates: X = 0, Y = workspace index. They need to be two-dimensional because 1D +//! coordinates are defined to be a plain list without a geometric interpretation, while we do +//! order workspaces in a vertical line. +//! - Workspace id: name for named workspaces, unset for unnamed. Because ids in this protocol are +//! expected to be stable across sessions. +//! - Workspace name: name for named workspaces, index for unnamed. + +use std::collections::hash_map::Entry; +use std::collections::HashMap; +use std::mem; + +use arrayvec::ArrayVec; +use ext_workspace_group_handle_v1::ExtWorkspaceGroupHandleV1; +use ext_workspace_handle_v1::ExtWorkspaceHandleV1; +use ext_workspace_manager_v1::ExtWorkspaceManagerV1; +use smithay::output::{Output, WeakOutput}; +use smithay::reexports::wayland_protocols::ext::workspace::v1::server::{ + ext_workspace_group_handle_v1, ext_workspace_handle_v1, ext_workspace_manager_v1, +}; +use smithay::reexports::wayland_server::protocol::wl_output::WlOutput; +use smithay::reexports::wayland_server::{ + Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, +}; +use wayland_backend::server::ClientId; + +use crate::layout::monitor::Monitor; +use crate::layout::workspace::{Workspace, WorkspaceId}; +use crate::niri::State; +use crate::window::Mapped; + +const VERSION: u32 = 1; + +pub trait ExtWorkspaceHandler { + fn ext_workspace_manager_state(&mut self) -> &mut ExtWorkspaceManagerState; + fn activate_workspace(&mut self, id: WorkspaceId); + fn assign_workspace(&mut self, ws_id: WorkspaceId, output: Output); +} + +enum Action { + Assign(WorkspaceId, WeakOutput), + Activate(WorkspaceId), +} + +impl Action { + fn order(&self) -> u8 { + // First assign everything (move across outputs), then activate. + match self { + Action::Assign(_, _) => 0, + Action::Activate(_) => 1, + } + } +} + +pub struct ExtWorkspaceManagerState { + display: DisplayHandle, + instances: HashMap<ExtWorkspaceManagerV1, Vec<Action>>, + workspace_groups: HashMap<Output, ExtWorkspaceGroupData>, + workspaces: HashMap<WorkspaceId, ExtWorkspaceData>, +} + +struct ExtWorkspaceGroupData { + instances: Vec<ExtWorkspaceGroupHandleV1>, +} + +struct ExtWorkspaceData { + // id cannot change once set. + id: Option<String>, + name: String, + coordinates: ArrayVec<u32, 2>, + state: ext_workspace_handle_v1::State, + instances: Vec<ExtWorkspaceHandleV1>, + output: Option<Output>, +} + +pub struct ExtWorkspaceGlobalData { + filter: Box<dyn for<'c> Fn(&'c Client) -> bool + Send + Sync>, +} + +pub fn refresh(state: &mut State) { + let _span = tracy_client::span!("ext_workspace::refresh"); + + let protocol_state = &mut state.niri.ext_workspace_state; + + let mut changed = false; + + // Remove workspaces that no longer exist (sending workspace_leave to workspace groups). + let mut seen_workspaces = HashMap::new(); + for (mon, _, ws) in state.niri.layout.workspaces() { + let output = mon.map(|mon| mon.output()); + seen_workspaces.insert(ws.id(), output); + } + + protocol_state.workspaces.retain(|id, workspace| { + if seen_workspaces.contains_key(id) { + return true; + } + + remove_workspace_instances(&protocol_state.workspace_groups, workspace); + changed = true; + false + }); + + // Remove workspace groups for outputs that no longer exist. + protocol_state.workspace_groups.retain(|output, data| { + if state.niri.sorted_outputs.contains(output) { + return true; + } + + for group in &data.instances { + // Send workspace_leave for all workspaces in this group with matching manager. + let manager: &ExtWorkspaceManagerV1 = group.data().unwrap(); + for ws in protocol_state.workspaces.values() { + if ws.output.as_ref() == Some(output) { + for workspace in &ws.instances { + if workspace.data() == Some(manager) { + group.workspace_leave(workspace); + } + } + } + } + + group.removed(); + } + + changed = true; + false + }); + + // Update existing workspaces and create new ones. + for (mon, ws_idx, ws) in state.niri.layout.workspaces() { + changed |= refresh_workspace(protocol_state, mon, ws_idx, ws); + } + + // Update workspace groups and create new ones, sending workspace_enter events as needed. + for output in &state.niri.sorted_outputs { + changed |= refresh_workspace_group(protocol_state, output); + } + + if changed { + for manager in protocol_state.instances.keys() { + manager.done(); + } + } +} + +pub fn on_output_bound(state: &mut State, output: &Output, wl_output: &WlOutput) { + let Some(client) = wl_output.client() else { + return; + }; + + let mut sent = false; + + let protocol_state = &mut state.niri.ext_workspace_state; + if let Some(data) = protocol_state.workspace_groups.get_mut(output) { + for group in &mut data.instances { + if group.client().as_ref() != Some(&client) { + continue; + } + + group.output_enter(wl_output); + sent = true; + } + } + + if !sent { + return; + } + + for manager in protocol_state.instances.keys() { + if manager.client().as_ref() == Some(&client) { + manager.done(); + } + } +} + +fn refresh_workspace_group(protocol_state: &mut ExtWorkspaceManagerState, output: &Output) -> bool { + if protocol_state.workspace_groups.contains_key(output) { + // Existing workspace group. Nothing can actually change since our workspace groups are tied + // to an output. + return false; + } + + // New workspace group, start tracking it. + let mut data = ExtWorkspaceGroupData { + instances: Vec::new(), + }; + + // Create workspace group handle for each manager instance. + for manager in protocol_state.instances.keys() { + if let Some(client) = manager.client() { + data.add_instance::<State>(&protocol_state.display, &client, manager, output); + } + } + + // Send workspace_enter for all existing workspaces on this output. + for group in &data.instances { + let manager: &ExtWorkspaceManagerV1 = group.data().unwrap(); + for (_, ws) in protocol_state.workspaces.iter() { + if ws.output.as_ref() != Some(output) { + continue; + } + for workspace in &ws.instances { + if workspace.data() == Some(manager) { + group.workspace_enter(workspace); + } + } + } + } + + protocol_state.workspace_groups.insert(output.clone(), data); + true +} + +fn send_workspace_enter_leave( + workspace_groups: &HashMap<Output, ExtWorkspaceGroupData>, + data: &ExtWorkspaceData, + enter: bool, +) { + if let Some(output) = &data.output { + if let Some(group_data) = workspace_groups.get(output) { + for group in &group_data.instances { + let manager: &ExtWorkspaceManagerV1 = group.data().unwrap(); + for workspace in &data.instances { + if workspace.data() == Some(manager) { + if enter { + group.workspace_enter(workspace); + } else { + group.workspace_leave(workspace); + } + } + } + } + } + } +} + +fn remove_workspace_instances( + workspace_groups: &HashMap<Output, ExtWorkspaceGroupData>, + data: &ExtWorkspaceData, +) { + send_workspace_enter_leave(workspace_groups, data, false); + + for workspace in &data.instances { + workspace.removed(); + } +} + +fn build_name(ws: &Workspace<Mapped>, ws_idx: usize) -> String { + ws.name().cloned().unwrap_or_else(|| { + // Add 1 since this is a human-readable name, and our action indexing is 1-based. + (ws_idx + 1).to_string() + }) +} + +fn refresh_workspace( + protocol_state: &mut ExtWorkspaceManagerState, + mon: Option<&Monitor<Mapped>>, + ws_idx: usize, + ws: &Workspace<Mapped>, +) -> bool { + let mut state = ext_workspace_handle_v1::State::empty(); + if mon.is_some_and(|mon| mon.active_workspace_idx() == ws_idx) { + state |= ext_workspace_handle_v1::State::Active; + } + if ws.is_urgent() { + state |= ext_workspace_handle_v1::State::Urgent; + } + + let output = mon.map(|mon| mon.output()); + + match protocol_state.workspaces.entry(ws.id()) { + Entry::Occupied(entry) => { + // Existing workspace, check if anything changed. + let data = entry.into_mut(); + + let mut id_set = false; + let mut recreate = false; + let id = ws.name(); + if data.id.as_ref() != id { + if data.id.is_some() { + recreate = true; + } else { + id_set = true; + } + data.id = id.cloned(); + } + + let mut coordinates_changed = false; + if data.coordinates[1] != ws_idx as u32 { + data.coordinates[1] = ws_idx as u32; + coordinates_changed = true; + } + + let mut state_changed = false; + if data.state != state { + data.state = state; + state_changed = true; + } + + // Recreate means name got changed or unset (meaning data.name is back to ws_idx). + let check = recreate + || if data.id.is_some() { + // True means workspace got named, going from ws_idx to name. + id_set + } else { + // The workspace is unnamed, check if ws_idx changed. + coordinates_changed + }; + let mut name_changed = false; + if check { + let new_name = build_name(ws, ws_idx); + // This will likely be true, except if the workspace got named its index. + if data.name != new_name { + data.name = new_name; + name_changed = true; + } + } + + let mut output_changed = false; + if data.output.as_ref() != output { + send_workspace_enter_leave(&protocol_state.workspace_groups, data, false); + data.output = output.cloned(); + output_changed = true; + } + + if recreate { + remove_workspace_instances(&protocol_state.workspace_groups, data); + data.instances.clear(); + + for manager in protocol_state.instances.keys() { + if let Some(client) = manager.client() { + data.add_instance::<State>(&protocol_state.display, &client, manager); + } + } + + send_workspace_enter_leave(&protocol_state.workspace_groups, data, true); + return true; + } + + if output_changed { + // Send workspace_enter to the new output's group. If the group doesn't exist yet + // (new groups are created after refreshing workspaces), then workspace_enter() will + // be sent when the group is created. + send_workspace_enter_leave(&protocol_state.workspace_groups, data, true); + } + + let something_changed = id_set || name_changed || coordinates_changed || state_changed; + if something_changed { + for instance in &data.instances { + if id_set { + instance.id(data.id.clone().unwrap()); + } + if name_changed { + instance.name(data.name.clone()); + } + if coordinates_changed { + instance.coordinates( + data.coordinates + .iter() + .flat_map(|x| x.to_ne_bytes()) + .collect(), + ); + } + if state_changed { + instance.state(data.state); + } + } + } + + output_changed || something_changed + } + Entry::Vacant(entry) => { + // New workspace, start tracking it. + let mut data = ExtWorkspaceData { + id: ws.name().cloned(), + name: build_name(ws, ws_idx), + coordinates: ArrayVec::from([0, ws_idx as u32]), + state, + instances: Vec::new(), + output: output.cloned(), + }; + + for manager in protocol_state.instances.keys() { + if let Some(client) = manager.client() { + data.add_instance::<State>(&protocol_state.display, &client, manager); + } + } + + send_workspace_enter_leave(&protocol_state.workspace_groups, &data, true); + entry.insert(data); + true + } + } +} + +impl ExtWorkspaceGroupData { + fn add_instance<D>( + &mut self, + handle: &DisplayHandle, + client: &Client, + manager: &ExtWorkspaceManagerV1, + output: &Output, + ) -> &ExtWorkspaceGroupHandleV1 + where + D: Dispatch<ExtWorkspaceGroupHandleV1, ExtWorkspaceManagerV1>, + D: 'static, + { + let group = client + .create_resource::<ExtWorkspaceGroupHandleV1, _, D>( + handle, + manager.version(), + manager.clone(), + ) + .unwrap(); + manager.workspace_group(&group); + + group.capabilities(ext_workspace_group_handle_v1::GroupCapabilities::empty()); + + for wl_output in output.client_outputs(client) { + group.output_enter(&wl_output); + } + + self.instances.push(group); + self.instances.last().unwrap() + } +} + +impl ExtWorkspaceData { + fn add_instance<D>( + &mut self, + handle: &DisplayHandle, + client: &Client, + manager: &ExtWorkspaceManagerV1, + ) -> &ExtWorkspaceHandleV1 + where + D: Dispatch<ExtWorkspaceHandleV1, ExtWorkspaceManagerV1>, + D: 'static, + { + let workspace = client + .create_resource::<ExtWorkspaceHandleV1, _, D>( + handle, + manager.version(), + manager.clone(), + ) + .unwrap(); + manager.workspace(&workspace); + + if let Some(id) = self.id.clone() { + workspace.id(id); + } + + workspace.name(self.name.clone()); + workspace.coordinates( + self.coordinates + .iter() + .flat_map(|x| x.to_ne_bytes()) + .collect(), + ); + workspace.state(self.state); + workspace.capabilities( + ext_workspace_handle_v1::WorkspaceCapabilities::Activate + | ext_workspace_handle_v1::WorkspaceCapabilities::Assign, + ); + + self.instances.push(workspace); + self.instances.last().unwrap() + } +} + +impl ExtWorkspaceManagerState { + pub fn new<D, F>(display: &DisplayHandle, filter: F) -> Self + where + D: GlobalDispatch<ExtWorkspaceManagerV1, ExtWorkspaceGlobalData>, + D: Dispatch<ExtWorkspaceManagerV1, ()>, + D: 'static, + F: for<'c> Fn(&'c Client) -> bool + Send + Sync + 'static, + { + let global_data = ExtWorkspaceGlobalData { + filter: Box::new(filter), + }; + display.create_global::<D, ExtWorkspaceManagerV1, _>(VERSION, global_data); + Self { + display: display.clone(), + instances: HashMap::new(), + workspace_groups: HashMap::new(), + workspaces: HashMap::new(), + } + } +} + +impl<D> GlobalDispatch<ExtWorkspaceManagerV1, ExtWorkspaceGlobalData, D> + for ExtWorkspaceManagerState +where + D: GlobalDispatch<ExtWorkspaceManagerV1, ExtWorkspaceGlobalData>, + D: Dispatch<ExtWorkspaceManagerV1, ()>, + D: Dispatch<ExtWorkspaceHandleV1, ExtWorkspaceManagerV1>, + D: ExtWorkspaceHandler, +{ + fn bind( + state: &mut D, + handle: &DisplayHandle, + client: &Client, + resource: New<ExtWorkspaceManagerV1>, + _global_data: &ExtWorkspaceGlobalData, + data_init: &mut DataInit<'_, D>, + ) { + let manager = data_init.init(resource, ()); + + let state = state.ext_workspace_manager_state(); + + // Send existing workspaces to the new client. + let mut new_workspaces: HashMap<_, Vec<_>> = HashMap::new(); + for data in state.workspaces.values_mut() { + let output = data.output.clone(); + let workspace = data.add_instance::<State>(handle, client, &manager); + + if let Some(output) = output { + new_workspaces.entry(output).or_default().push(workspace); + } + } + + // Create workspace groups for all outputs. + for (output, group_data) in &mut state.workspace_groups { + let group = group_data.add_instance::<State>(handle, client, &manager, output); + + for workspace in new_workspaces.get(output).into_iter().flatten() { + group.workspace_enter(workspace); + } + } + + manager.done(); + state.instances.insert(manager, Vec::new()); + } + + fn can_view(client: Client, global_data: &ExtWorkspaceGlobalData) -> bool { + (global_data.filter)(&client) + } +} + +impl<D> Dispatch<ExtWorkspaceManagerV1, (), D> for ExtWorkspaceManagerState +where + D: Dispatch<ExtWorkspaceManagerV1, ()>, + D: ExtWorkspaceHandler, +{ + fn request( + state: &mut D, + _client: &Client, + resource: &ExtWorkspaceManagerV1, + request: <ExtWorkspaceManagerV1 as Resource>::Request, + _data: &(), + _dhandle: &DisplayHandle, + _data_init: &mut DataInit<'_, D>, + ) { + match request { + ext_workspace_manager_v1::Request::Commit => { + let protocol_state = state.ext_workspace_manager_state(); + let actions = protocol_state.instances.get_mut(resource).unwrap(); + let mut actions = mem::take(actions); + + actions.sort_by_key(Action::order); + + for action in actions { + match action { + Action::Assign(ws_id, output) => { + if let Some(output) = output.upgrade() { + state.assign_workspace(ws_id, output); + } + } + Action::Activate(id) => state.activate_workspace(id), + } + } + } + ext_workspace_manager_v1::Request::Stop => { + resource.finished(); + + let state = state.ext_workspace_manager_state(); + state.instances.retain(|x, _| x != resource); + + for data in state.workspace_groups.values_mut() { + data.instances + .retain(|instance| instance.data() != Some(resource)); + } + + for data in state.workspaces.values_mut() { + data.instances + .retain(|instance| instance.data() != Some(resource)); + } + } + _ => unreachable!(), + } + } + + fn destroyed(state: &mut D, _client: ClientId, resource: &ExtWorkspaceManagerV1, _data: &()) { + let state = state.ext_workspace_manager_state(); + state.instances.retain(|x, _| x != resource); + } +} + +impl<D> Dispatch<ExtWorkspaceHandleV1, ExtWorkspaceManagerV1, D> for ExtWorkspaceManagerState +where + D: Dispatch<ExtWorkspaceHandleV1, ExtWorkspaceManagerV1>, + D: ExtWorkspaceHandler, +{ + fn request( + state: &mut D, + _client: &Client, + resource: &ExtWorkspaceHandleV1, + request: <ExtWorkspaceHandleV1 as Resource>::Request, + data: &ExtWorkspaceManagerV1, + _dhandle: &DisplayHandle, + _data_init: &mut DataInit<'_, D>, + ) { + let protocol_state = state.ext_workspace_manager_state(); + + let Some((workspace, _)) = protocol_state + .workspaces + .iter() + .find(|(_, data)| data.instances.contains(resource)) + else { + return; + }; + let workspace = *workspace; + + match request { + ext_workspace_handle_v1::Request::Activate => { + let actions = protocol_state.instances.get_mut(data).unwrap(); + actions.push(Action::Activate(workspace)); + } + ext_workspace_handle_v1::Request::Deactivate => (), + ext_workspace_handle_v1::Request::Assign { workspace_group } => { + if let Some(output) = protocol_state + .workspace_groups + .iter() + .find(|(_, data)| data.instances.contains(&workspace_group)) + .map(|(output, _)| output.clone()) + { + let actions = protocol_state.instances.get_mut(data).unwrap(); + actions.push(Action::Assign(workspace, output.downgrade())); + } + } + ext_workspace_handle_v1::Request::Remove => (), + ext_workspace_handle_v1::Request::Destroy => (), + _ => unreachable!(), + } + } + + fn destroyed( + state: &mut D, + _client: ClientId, + resource: &ExtWorkspaceHandleV1, + _data: &ExtWorkspaceManagerV1, + ) { + let state = state.ext_workspace_manager_state(); + for data in state.workspaces.values_mut() { + data.instances.retain(|instance| instance != resource); + } + } +} + +impl<D> Dispatch<ExtWorkspaceGroupHandleV1, ExtWorkspaceManagerV1, D> for ExtWorkspaceManagerState +where + D: Dispatch<ExtWorkspaceGroupHandleV1, ExtWorkspaceManagerV1>, + D: ExtWorkspaceHandler, +{ + fn request( + _state: &mut D, + _client: &Client, + _resource: &ExtWorkspaceGroupHandleV1, + request: <ExtWorkspaceGroupHandleV1 as Resource>::Request, + _data: &ExtWorkspaceManagerV1, + _dhandle: &DisplayHandle, + _data_init: &mut DataInit<'_, D>, + ) { + match request { + ext_workspace_group_handle_v1::Request::CreateWorkspace { .. } => (), + ext_workspace_group_handle_v1::Request::Destroy => (), + _ => unreachable!(), + } + } + + fn destroyed( + state: &mut D, + _client: ClientId, + resource: &ExtWorkspaceGroupHandleV1, + _data: &ExtWorkspaceManagerV1, + ) { + let state = state.ext_workspace_manager_state(); + for data in state.workspace_groups.values_mut() { + data.instances.retain(|instance| instance != resource); + } + } +} + +#[macro_export] +macro_rules! delegate_ext_workspace { + ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { + smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + smithay::reexports::wayland_protocols::ext::workspace::v1::server::ext_workspace_manager_v1::ExtWorkspaceManagerV1: $crate::protocols::ext_workspace::ExtWorkspaceGlobalData + ] => $crate::protocols::ext_workspace::ExtWorkspaceManagerState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + smithay::reexports::wayland_protocols::ext::workspace::v1::server::ext_workspace_manager_v1::ExtWorkspaceManagerV1: () + ] => $crate::protocols::ext_workspace::ExtWorkspaceManagerState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + smithay::reexports::wayland_protocols::ext::workspace::v1::server::ext_workspace_handle_v1::ExtWorkspaceHandleV1: smithay::reexports::wayland_protocols::ext::workspace::v1::server::ext_workspace_manager_v1::ExtWorkspaceManagerV1 + ] => $crate::protocols::ext_workspace::ExtWorkspaceManagerState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + smithay::reexports::wayland_protocols::ext::workspace::v1::server::ext_workspace_group_handle_v1::ExtWorkspaceGroupHandleV1: smithay::reexports::wayland_protocols::ext::workspace::v1::server::ext_workspace_manager_v1::ExtWorkspaceManagerV1 + ] => $crate::protocols::ext_workspace::ExtWorkspaceManagerState); + }; +} diff --git a/src/protocols/mod.rs b/src/protocols/mod.rs index 476f24eb..b150cb7f 100644 --- a/src/protocols/mod.rs +++ b/src/protocols/mod.rs @@ -1,3 +1,4 @@ +pub mod ext_workspace; pub mod foreign_toplevel; pub mod gamma_control; pub mod mutter_x11_interop; |
