Commit a1a05e799a71
tarball9 files changed+676-0
@@ -398,6 +398,7 @@ dependencies = [
398398
"monolith-config",399399
"monolith-ipc",400400
"monolith-layout",401
+ "monolith-protocols",401402
"monolith-render-vk",402403
"serde_json",403404
"slotmap",@@ -454,6 +455,8 @@ name = "monolith-protocols"
454455
version = "0.0.0"455456
dependencies = [456457
"smithay",458
+ "wayland-client",459
+ "wayland-protocols",457460
]458461
459462
[[package]]@@ -16,6 +16,7 @@ calloop-wayland-source = "0.4"
1616
monolith-config.workspace = true1717
monolith-ipc.workspace = true1818
monolith-layout.workspace = true19
+monolith-protocols.workspace = true1920
monolith-render-vk.workspace = true2021
serde_json.workspace = true2122
slotmap.workspace = true@@ -27,6 +27,7 @@ use smithay::wayland::shell::xdg::decoration::XdgDecorationHandler;
2727
use smithay::wayland::shell::xdg::{2828
PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState,2929
};30
+use monolith_protocols::workspace::{Action, WorkspaceHandler, WorkspaceState};3031
use smithay::wayland::shm::{ShmHandler, ShmState};3132
3233
impl CompositorHandler for State {@@ -245,3 +246,38 @@ impl State {
245246
}246247
}247248
}249
+250
+impl WorkspaceHandler for State {251
+ fn workspace_state(&mut self) -> &mut WorkspaceState {252
+ &mut self.workspaces253
+ }254
+255
+ fn workspace_actions(&mut self, actions: Vec<Action>) {256
+ for action in actions {257
+ let (group, tag) = match &action {258
+ Action::Activate { group, workspace } | Action::Deactivate { group, workspace } => {259
+ (group, workspace)260
+ }261
+ Action::Create { group, name } => (group, name),262
+ };263
+ let Some(o) = self264
+ .layout265
+ .outputs()266
+ .find(|(_, out)| out.name == *group)267
+ .map(|(o, _)| o)268
+ else {269
+ continue;270
+ };271
+ let t = self.layout.tag(tag);272
+ match action {273
+ Action::Activate { .. } | Action::Create { .. } => self.layout.view(o, t),274
+ Action::Deactivate { .. } => {275
+ if self.layout.is_viewed(o, t) {276
+ self.layout.toggle_view(o, t);277
+ }278
+ }279
+ }280
+ }281
+ self.apply_layout();282
+ }283
+}@@ -1,9 +1,12 @@
11
//! Host input forwarded into monolith's seat.22
33
use crate::state::State;4
+use monolith_config::Modifier;5
+use monolith_layout::{Rect, WindowId};46
use smithay::backend::input::{Axis, ButtonState, InputTime, KeyState};57
use smithay::input::keyboard::{FilterResult, Keycode};68
use smithay::input::pointer::{AxisFrame, ButtonEvent, MotionEvent};9
+use smithay::utils::Logical;710
use smithay::utils::{Point, SERIAL_COUNTER};811
use wayland_client::protocol::{wl_keyboard, wl_pointer, wl_seat};912
use wayland_client::{Connection, Dispatch, QueueHandle, WEnum};@@ -59,6 +62,9 @@ impl Dispatch<wl_pointer::WlPointer, ()> for State {
5962
if let Some(host) = state.host.as_mut() {6063
host.pointer_pos = pos;6164
}65
+ if drag_to(state, pos) {66
+ return;67
+ }6268
let under = state.surface_under(pos);6369
pointer.motion(6470
state,@@ -80,8 +86,14 @@ impl Dispatch<wl_pointer::WlPointer, ()> for State {
8086
wl_pointer::ButtonState::Pressed => ButtonState::Pressed,8187
_ => ButtonState::Released,8288
};89
+ if button_state == ButtonState::Released && state.drag.take().is_some() {90
+ return;91
+ }8392
if button_state == ButtonState::Pressed {8493
focus_under_pointer(state);94
+ if start_drag(state, button) {95
+ return;96
+ }8597
}8698
pointer.button(8799
state,@@ -184,3 +196,71 @@ fn focus_under_pointer(state: &mut State) {
184196
state.apply_layout();185197
}186198
}199
+200
+const BTN_LEFT: u32 = 0x110;201
+const BTN_RIGHT: u32 = 0x111;202
+203
+/// A floating window being moved or resized with Mod and a mouse button.204
+pub struct Drag {205
+ window: WindowId,206
+ from: Point<f64, Logical>,207
+ rect: Rect,208
+ resize: bool,209
+}210
+211
+fn start_drag(state: &mut State, button: u32) -> bool {212
+ if button != BTN_LEFT && button != BTN_RIGHT {213
+ return false;214
+ }215
+ let held = state216
+ .seat217
+ .get_keyboard()218
+ .map(|k| k.modifier_state())219
+ .is_some_and(|m| match state.config.general.mod_key {220
+ Modifier::Super => m.logo,221
+ Modifier::Alt => m.alt,222
+ Modifier::Ctrl => m.ctrl,223
+ Modifier::Shift => m.shift,224
+ });225
+ let Some(from) = state.host.as_ref().map(|h| h.pointer_pos) else {226
+ return false;227
+ };228
+ let Some(id) = state.layout.focused() else {229
+ return false;230
+ };231
+ let Some(rect) = state232
+ .layout233
+ .window(id)234
+ .filter(|w| w.floating && !w.fullscreen)235
+ .and_then(|w| w.float_rect)236
+ else {237
+ return false;238
+ };239
+ if !held {240
+ return false;241
+ }242
+ state.drag = Some(Drag {243
+ window: id,244
+ from,245
+ rect,246
+ resize: button == BTN_RIGHT,247
+ });248
+ true249
+}250
+251
+fn drag_to(state: &mut State, pos: Point<f64, Logical>) -> bool {252
+ let Some(d) = &state.drag else {253
+ return false;254
+ };255
+ let (dx, dy) = (pos.x - d.from.x, pos.y - d.from.y);256
+ let r = d.rect;257
+ let rect = if d.resize {258
+ Rect::new(r.x, r.y, (r.w + dx).max(32.0), (r.h + dy).max(32.0))259
+ } else {260
+ Rect::new(r.x + dx, r.y + dy, r.w, r.h)261
+ };262
+ let id = d.window;263
+ state.layout.set_float_rect(id, rect);264
+ state.apply_layout();265
+ true266
+}@@ -2,6 +2,7 @@ use crate::commands::Binds;
22
use crate::nested::Host;33
use monolith_config::Config;44
use monolith_layout::{Layout, OutputId, Rect, WindowId};5
+use monolith_protocols::workspace::{Group, Snapshot, Workspace, WorkspaceState};56
use monolith_render_vk::VulkanRenderer;67
use smithay::backend::renderer::ImportDma;78
use smithay::desktop::{PopupManager, Space, Window, WindowSurfaceType};@@ -49,8 +50,10 @@ pub struct State {
4950
pub config_path: Option<PathBuf>,5051
pub binds: Binds,5152
pub suppressed: Vec<Keycode>,53
+ pub drag: Option<crate::input::Drag>,5254
pub ipc: crate::ipc::Server,5355
pub launches: crate::launch::Launches,56
+ pub workspaces: WorkspaceState,5457
5558
pub compositor_state: CompositorState,5659
pub xdg_shell_state: XdgShellState,@@ -124,8 +127,10 @@ impl State {
124127
config_path,125128
binds,126129
suppressed: Vec::new(),130
+ drag: None,127131
ipc: crate::ipc::Server::default(),128132
launches: Default::default(),133
+ workspaces: WorkspaceState::new::<State>(&dh),129134
compositor_state: CompositorState::new::<State>(&dh),130135
xdg_shell_state: XdgShellState::new::<State>(&dh),131136
_xdg_decoration_state: XdgDecorationState::new::<State>(&dh),@@ -146,6 +151,7 @@ impl State {
146151
self.popups.cleanup();147152
crate::nested::render(self);148153
crate::ipc::broadcast(self);154
+ self.publish_workspaces();149155
let _ = self.display.flush_clients();150156
}151157
@@ -286,6 +292,39 @@ impl State {
286292
}287293
}288294
295
+ fn publish_workspaces(&mut self) {296
+ if !self.workspaces.has_clients() {297
+ return;298
+ }299
+ let l = &self.layout;300
+ let groups = l301
+ .outputs()302
+ .filter_map(|(o, _)| {303
+ let output = self304
+ .host305
+ .as_ref()306
+ .filter(|_| Some(o) == self.output_id)?307
+ .output308
+ .clone();309
+ let workspaces = l310
+ .tags()311
+ .enumerate()312
+ .map(|(i, (t, tag))| Workspace {313
+ key: tag.name.clone(),314
+ name: tag.name.clone(),315
+ coordinates: vec![i as u32],316
+ active: l.is_viewed(o, t),317
+ urgent: tag.urgent,318
+ hidden: false,319
+ })320
+ .collect();321
+ Some(Group { output, workspaces })322
+ })323
+ .collect();324
+ let dh = self.display.clone();325
+ self.workspaces.update::<State>(&dh, Snapshot { groups });326
+ }327
+289328
pub fn reconfigure_keyboard(&mut self) {290329
let kb = self.config.keyboard.clone();291330
if let Some(keyboard) = self.seat.get_keyboard() {@@ -10,3 +10,7 @@ workspace = true
1010
1111
[dependencies]1212
smithay.workspace = true13
+14
+[dev-dependencies]15
+wayland-client = "0.31"16
+wayland-protocols = { version = "0.32", features = ["client", "staging"] }@@ -0,0 +1,114 @@
1
+//! Prints ext-workspace events from the compositor on $WAYLAND_DISPLAY.2
+//! `workspaces activate <name>` activates a workspace and exits.3
+4
+use wayland_client::globals::{GlobalListContents, registry_queue_init};5
+use wayland_client::protocol::{wl_output, wl_registry};6
+use wayland_client::{Connection, Dispatch, Proxy, QueueHandle, event_created_child};7
+use wayland_protocols::ext::workspace::v1::client::{8
+ ext_workspace_group_handle_v1 as group, ext_workspace_handle_v1 as ws,9
+ ext_workspace_manager_v1 as manager,10
+};11
+12
+#[derive(Default)]13
+struct App {14
+ names: Vec<(ws::ExtWorkspaceHandleV1, String)>,15
+}16
+17
+fn main() {18
+ let target = std::env::args().nth(2);19
+ let conn = Connection::connect_to_env().expect("no compositor");20
+ let (globals, mut queue) = registry_queue_init::<App>(&conn).unwrap();21
+ let qh = queue.handle();22
+ let _output: wl_output::WlOutput = globals.bind(&qh, 1..=4, ()).unwrap();23
+ let m: manager::ExtWorkspaceManagerV1 = globals.bind(&qh, 1..=1, ()).unwrap();24
+ let mut app = App::default();25
+ queue.roundtrip(&mut app).unwrap();26
+ queue.roundtrip(&mut app).unwrap();27
+ if let Some(name) = target {28
+ if let Some((h, _)) = app.names.iter().find(|(_, n)| *n == name) {29
+ h.activate();30
+ m.commit();31
+ queue.roundtrip(&mut app).unwrap();32
+ queue.roundtrip(&mut app).unwrap();33
+ }34
+ return;35
+ }36
+ loop {37
+ queue.blocking_dispatch(&mut app).unwrap();38
+ }39
+}40
+41
+impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for App {42
+ fn event(43
+ _: &mut Self,44
+ _: &wl_registry::WlRegistry,45
+ _: wl_registry::Event,46
+ _: &GlobalListContents,47
+ _: &Connection,48
+ _: &QueueHandle<Self>,49
+ ) {50
+ }51
+}52
+53
+impl Dispatch<wl_output::WlOutput, ()> for App {54
+ fn event(55
+ _: &mut Self,56
+ _: &wl_output::WlOutput,57
+ _: wl_output::Event,58
+ _: &(),59
+ _: &Connection,60
+ _: &QueueHandle<Self>,61
+ ) {62
+ }63
+}64
+65
+impl Dispatch<manager::ExtWorkspaceManagerV1, ()> for App {66
+ fn event(67
+ _: &mut Self,68
+ _: &manager::ExtWorkspaceManagerV1,69
+ event: manager::Event,70
+ _: &(),71
+ _: &Connection,72
+ _: &QueueHandle<Self>,73
+ ) {74
+ match event {75
+ manager::Event::WorkspaceGroup { .. } => println!("group"),76
+ manager::Event::Workspace { .. } => println!("workspace"),77
+ e => println!("manager {e:?}"),78
+ }79
+ }80
+81
+ event_created_child!(App, manager::ExtWorkspaceManagerV1, [82
+ manager::EVT_WORKSPACE_GROUP_OPCODE => (group::ExtWorkspaceGroupHandleV1, ()),83
+ manager::EVT_WORKSPACE_OPCODE => (ws::ExtWorkspaceHandleV1, ()),84
+ ]);85
+}86
+87
+impl Dispatch<group::ExtWorkspaceGroupHandleV1, ()> for App {88
+ fn event(89
+ _: &mut Self,90
+ _: &group::ExtWorkspaceGroupHandleV1,91
+ event: group::Event,92
+ _: &(),93
+ _: &Connection,94
+ _: &QueueHandle<Self>,95
+ ) {96
+ println!(" group {event:?}");97
+ }98
+}99
+100
+impl Dispatch<ws::ExtWorkspaceHandleV1, ()> for App {101
+ fn event(102
+ app: &mut Self,103
+ h: &ws::ExtWorkspaceHandleV1,104
+ event: ws::Event,105
+ _: &(),106
+ _: &Connection,107
+ _: &QueueHandle<Self>,108
+ ) {109
+ if let ws::Event::Name { name } = &event {110
+ app.names.push((h.clone(), name.clone()));111
+ }112
+ println!(" workspace {:?} {event:?}", h.id().protocol_id());113
+ }114
+}@@ -1 +1,3 @@
1
+//! Wayland protocols monolith implements itself.12
3
+pub mod workspace;@@ -0,0 +1,397 @@
1
+//! ext-workspace-v1. The compositor publishes a snapshot of groups and2
+//! workspaces, and this module sends each client the difference from what3
+//! it has already seen. Client requests are queued until `commit` and then4
+//! handed over as one batch.5
+6
+use smithay::output::Output;7
+use smithay::reexports::wayland_protocols::ext::workspace::v1::server::{8
+ ext_workspace_group_handle_v1::{self, ExtWorkspaceGroupHandleV1, GroupCapabilities},9
+ ext_workspace_handle_v1::{self, ExtWorkspaceHandleV1, WorkspaceCapabilities},10
+ ext_workspace_manager_v1::{self, ExtWorkspaceManagerV1},11
+};12
+use smithay::reexports::wayland_server::backend::{ClientId, GlobalId};13
+use smithay::reexports::wayland_server::protocol::wl_output::WlOutput;14
+use smithay::reexports::wayland_server::{15
+ Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,16
+};17
+use smithay::wayland::{Dispatch2, GlobalDispatch2};18
+use std::sync::Mutex;19
+20
+#[derive(Clone, Debug, Default, PartialEq)]21
+pub struct Snapshot {22
+ pub groups: Vec<Group>,23
+}24
+25
+#[derive(Clone, Debug, PartialEq)]26
+pub struct Group {27
+ pub output: Output,28
+ pub workspaces: Vec<Workspace>,29
+}30
+31
+#[derive(Clone, Debug, PartialEq, Eq)]32
+pub struct Workspace {33
+ /// Unique within its group, and reported to clients as the stable id.34
+ pub key: String,35
+ pub name: String,36
+ pub coordinates: Vec<u32>,37
+ pub active: bool,38
+ pub urgent: bool,39
+ pub hidden: bool,40
+}41
+42
+#[derive(Clone, Debug, PartialEq, Eq)]43
+pub enum Action {44
+ Activate { group: String, workspace: String },45
+ Deactivate { group: String, workspace: String },46
+ Create { group: String, name: String },47
+}48
+49
+pub trait WorkspaceHandler {50
+ fn workspace_state(&mut self) -> &mut WorkspaceState;51
+ fn workspace_actions(&mut self, actions: Vec<Action>);52
+}53
+54
+struct SentGroup {55
+ name: String,56
+ handle: ExtWorkspaceGroupHandleV1,57
+ outputs: Vec<WlOutput>,58
+}59
+60
+struct SentWorkspace {61
+ group: String,62
+ handle: ExtWorkspaceHandleV1,63
+ last: Workspace,64
+}65
+66
+struct Manager {67
+ handle: ExtWorkspaceManagerV1,68
+ groups: Vec<SentGroup>,69
+ workspaces: Vec<SentWorkspace>,70
+}71
+72
+pub struct WorkspaceState {73
+ global: GlobalId,74
+ managers: Vec<Manager>,75
+ snapshot: Snapshot,76
+}77
+78
+pub struct ManagerGlobal;79
+80
+#[derive(Default)]81
+pub struct ManagerData {82
+ pending: Mutex<Vec<Action>>,83
+}84
+85
+pub struct GroupData {86
+ manager: ExtWorkspaceManagerV1,87
+ group: String,88
+}89
+90
+pub struct WorkspaceData {91
+ manager: ExtWorkspaceManagerV1,92
+ group: String,93
+ key: String,94
+}95
+96
+impl WorkspaceState {97
+ pub fn new<D>(dh: &DisplayHandle) -> Self98
+ where99
+ D: GlobalDispatch<ExtWorkspaceManagerV1, ManagerGlobal> + 'static,100
+ {101
+ Self {102
+ global: dh.create_global::<D, ExtWorkspaceManagerV1, _>(1, ManagerGlobal),103
+ managers: Vec::new(),104
+ snapshot: Snapshot::default(),105
+ }106
+ }107
+108
+ pub fn global(&self) -> GlobalId {109
+ self.global.clone()110
+ }111
+112
+ pub fn has_clients(&self) -> bool {113
+ !self.managers.is_empty()114
+ }115
+116
+ /// Publishes a snapshot. Clients only hear about differences, including117
+ /// wl_outputs they bound after the manager.118
+ pub fn update<D>(&mut self, dh: &DisplayHandle, snapshot: Snapshot)119
+ where120
+ D: Dispatch<ExtWorkspaceGroupHandleV1, GroupData>121
+ + Dispatch<ExtWorkspaceHandleV1, WorkspaceData>122
+ + 'static,123
+ {124
+ self.snapshot = snapshot;125
+ self.managers.retain(|m| m.handle.is_alive());126
+ for m in &mut self.managers {127
+ sync::<D>(dh, m, &self.snapshot);128
+ }129
+ }130
+}131
+132
+fn sync<D>(dh: &DisplayHandle, m: &mut Manager, snap: &Snapshot)133
+where134
+ D: Dispatch<ExtWorkspaceGroupHandleV1, GroupData>135
+ + Dispatch<ExtWorkspaceHandleV1, WorkspaceData>136
+ + 'static,137
+{138
+ let Ok(client) = dh.get_client(m.handle.id()) else {139
+ return;140
+ };141
+ let mut changed = false;142
+143
+ let gone: Vec<String> = m144
+ .groups145
+ .iter()146
+ .filter(|g| !snap.groups.iter().any(|s| s.output.name() == g.name))147
+ .map(|g| g.name.clone())148
+ .collect();149
+ m.workspaces.retain(|w| {150
+ let in_snap = snap151
+ .groups152
+ .iter()153
+ .find(|g| g.output.name() == w.group)154
+ .is_some_and(|g| g.workspaces.iter().any(|s| s.key == w.last.key));155
+ if !in_snap {156
+ if let Some(g) = m.groups.iter().find(|g| g.name == w.group) {157
+ g.handle.workspace_leave(&w.handle);158
+ }159
+ w.handle.removed();160
+ changed = true;161
+ }162
+ in_snap163
+ });164
+ m.groups.retain(|g| {165
+ let keep = !gone.contains(&g.name);166
+ if !keep {167
+ g.handle.removed();168
+ changed = true;169
+ }170
+ keep171
+ });172
+173
+ for group in &snap.groups {174
+ let name = group.output.name();175
+ let idx = match m.groups.iter().position(|g| g.name == name) {176
+ Some(i) => i,177
+ None => {178
+ let Ok(handle) = client.create_resource::<ExtWorkspaceGroupHandleV1, _, D>(179
+ dh,180
+ m.handle.version(),181
+ GroupData {182
+ manager: m.handle.clone(),183
+ group: name.clone(),184
+ },185
+ ) else {186
+ continue;187
+ };188
+ m.handle.workspace_group(&handle);189
+ handle.capabilities(GroupCapabilities::CreateWorkspace);190
+ m.groups.push(SentGroup {191
+ name: name.clone(),192
+ handle,193
+ outputs: Vec::new(),194
+ });195
+ changed = true;196
+ m.groups.len() - 1197
+ }198
+ };199
+ let sent = &mut m.groups[idx];200
+ for wl in group.output.client_outputs(&client) {201
+ if !sent.outputs.contains(&wl) {202
+ sent.handle.output_enter(&wl);203
+ sent.outputs.push(wl);204
+ changed = true;205
+ }206
+ }207
+ sent.outputs.retain(|o| o.is_alive());208
+209
+ for ws in &group.workspaces {210
+ match m211
+ .workspaces212
+ .iter_mut()213
+ .find(|w| w.group == name && w.last.key == ws.key)214
+ {215
+ Some(w) => {216
+ if w.last != *ws {217
+ send_props(&w.handle, Some(&w.last), ws);218
+ w.last = ws.clone();219
+ changed = true;220
+ }221
+ }222
+ None => {223
+ let Ok(handle) = client.create_resource::<ExtWorkspaceHandleV1, _, D>(224
+ dh,225
+ m.handle.version(),226
+ WorkspaceData {227
+ manager: m.handle.clone(),228
+ group: name.clone(),229
+ key: ws.key.clone(),230
+ },231
+ ) else {232
+ continue;233
+ };234
+ m.handle.workspace(&handle);235
+ handle.id(ws.key.clone());236
+ handle.capabilities(237
+ WorkspaceCapabilities::Activate | WorkspaceCapabilities::Deactivate,238
+ );239
+ send_props(&handle, None, ws);240
+ m.groups[idx].handle.workspace_enter(&handle);241
+ m.workspaces.push(SentWorkspace {242
+ group: name.clone(),243
+ handle,244
+ last: ws.clone(),245
+ });246
+ changed = true;247
+ }248
+ }249
+ }250
+ }251
+ if changed {252
+ m.handle.done();253
+ }254
+}255
+256
+fn send_props(h: &ExtWorkspaceHandleV1, old: Option<&Workspace>, new: &Workspace) {257
+ if old.is_none_or(|o| o.name != new.name) {258
+ h.name(new.name.clone());259
+ }260
+ if old.is_none_or(|o| o.coordinates != new.coordinates) {261
+ let bytes = new262
+ .coordinates263
+ .iter()264
+ .flat_map(|c| c.to_ne_bytes())265
+ .collect();266
+ h.coordinates(bytes);267
+ }268
+ let state = |w: &Workspace| {269
+ let mut s = ext_workspace_handle_v1::State::empty();270
+ s.set(ext_workspace_handle_v1::State::Active, w.active);271
+ s.set(ext_workspace_handle_v1::State::Urgent, w.urgent);272
+ s.set(ext_workspace_handle_v1::State::Hidden, w.hidden);273
+ s274
+ };275
+ if old.is_none_or(|o| state(o) != state(new)) {276
+ h.state(state(new));277
+ }278
+}279
+280
+fn queue(manager: &ExtWorkspaceManagerV1, action: Action) {281
+ if let Some(data) = manager.data::<ManagerData>() {282
+ data.pending283
+ .lock()284
+ .unwrap_or_else(|e| e.into_inner())285
+ .push(action);286
+ }287
+}288
+289
+impl<D> GlobalDispatch2<ExtWorkspaceManagerV1, D> for ManagerGlobal290
+where291
+ D: WorkspaceHandler292
+ + Dispatch<ExtWorkspaceManagerV1, ManagerData>293
+ + Dispatch<ExtWorkspaceGroupHandleV1, GroupData>294
+ + Dispatch<ExtWorkspaceHandleV1, WorkspaceData>295
+ + 'static,296
+{297
+ fn bind(298
+ &self,299
+ state: &mut D,300
+ dh: &DisplayHandle,301
+ _client: &Client,302
+ resource: New<ExtWorkspaceManagerV1>,303
+ data_init: &mut DataInit<'_, D>,304
+ ) {305
+ let handle = data_init.init(resource, ManagerData::default());306
+ let ws = state.workspace_state();307
+ let mut m = Manager {308
+ handle,309
+ groups: Vec::new(),310
+ workspaces: Vec::new(),311
+ };312
+ sync::<D>(dh, &mut m, &ws.snapshot);313
+ ws.managers.push(m);314
+ }315
+}316
+317
+impl<D: WorkspaceHandler> Dispatch2<ExtWorkspaceManagerV1, D> for ManagerData {318
+ fn request(319
+ &self,320
+ state: &mut D,321
+ _client: &Client,322
+ resource: &ExtWorkspaceManagerV1,323
+ request: ext_workspace_manager_v1::Request,324
+ _dh: &DisplayHandle,325
+ _data_init: &mut DataInit<'_, D>,326
+ ) {327
+ match request {328
+ ext_workspace_manager_v1::Request::Commit => {329
+ let actions =330
+ std::mem::take(&mut *self.pending.lock().unwrap_or_else(|e| e.into_inner()));331
+ if !actions.is_empty() {332
+ state.workspace_actions(actions);333
+ }334
+ }335
+ ext_workspace_manager_v1::Request::Stop => {336
+ resource.finished();337
+ state338
+ .workspace_state()339
+ .managers340
+ .retain(|m| m.handle != *resource);341
+ }342
+ _ => {}343
+ }344
+ }345
+346
+ fn destroyed(&self, state: &mut D, _client: ClientId, resource: &ExtWorkspaceManagerV1) {347
+ state348
+ .workspace_state()349
+ .managers350
+ .retain(|m| m.handle != *resource);351
+ }352
+}353
+354
+impl<D: WorkspaceHandler> Dispatch2<ExtWorkspaceGroupHandleV1, D> for GroupData {355
+ fn request(356
+ &self,357
+ _state: &mut D,358
+ _client: &Client,359
+ _resource: &ExtWorkspaceGroupHandleV1,360
+ request: ext_workspace_group_handle_v1::Request,361
+ _dh: &DisplayHandle,362
+ _data_init: &mut DataInit<'_, D>,363
+ ) {364
+ if let ext_workspace_group_handle_v1::Request::CreateWorkspace { workspace } = request {365
+ queue(366
+ &self.manager,367
+ Action::Create {368
+ group: self.group.clone(),369
+ name: workspace,370
+ },371
+ );372
+ }373
+ }374
+}375
+376
+impl<D: WorkspaceHandler> Dispatch2<ExtWorkspaceHandleV1, D> for WorkspaceData {377
+ fn request(378
+ &self,379
+ _state: &mut D,380
+ _client: &Client,381
+ _resource: &ExtWorkspaceHandleV1,382
+ request: ext_workspace_handle_v1::Request,383
+ _dh: &DisplayHandle,384
+ _data_init: &mut DataInit<'_, D>,385
+ ) {386
+ let (group, workspace) = (self.group.clone(), self.key.clone());387
+ match request {388
+ ext_workspace_handle_v1::Request::Activate => {389
+ queue(&self.manager, Action::Activate { group, workspace });390
+ }391
+ ext_workspace_handle_v1::Request::Deactivate => {392
+ queue(&self.manager, Action::Deactivate { group, workspace });393
+ }394
+ _ => {}395
+ }396
+ }397
+}