1use cosmic_protocols::workspace::v2::client::{
2 zcosmic_workspace_handle_v2, zcosmic_workspace_manager_v2,
3};
4use sctk::registry::{GlobalProxy, RegistryState};
5use std::collections::HashSet;
6use wayland_client::{Connection, Dispatch, QueueHandle, WEnum, protocol::wl_output};
7use wayland_protocols::ext::workspace::v1::client::{
8 ext_workspace_group_handle_v1, ext_workspace_handle_v1, ext_workspace_manager_v1,
9};
10
11use crate::GlobalData;
12
13#[derive(Clone, Debug)]
14pub struct WorkspaceGroup {
15 pub handle: ext_workspace_group_handle_v1::ExtWorkspaceGroupHandleV1,
16 pub capabilities: ext_workspace_group_handle_v1::GroupCapabilities,
17 pub outputs: Vec<wl_output::WlOutput>,
18 pub workspaces: HashSet<ext_workspace_handle_v1::ExtWorkspaceHandleV1>,
19}
20
21#[derive(Debug)]
22struct WorkspaceGroupData {
23 handle: ext_workspace_group_handle_v1::ExtWorkspaceGroupHandleV1,
24 current: Option<WorkspaceGroup>,
25 pending: Option<WorkspaceGroup>,
26}
27
28impl WorkspaceGroupData {
29 fn pending(&mut self) -> &mut WorkspaceGroup {
30 if self.pending.is_none() {
31 self.pending = Some(self.current.clone().unwrap_or(WorkspaceGroup {
32 handle: self.handle.clone(),
33 capabilities: ext_workspace_group_handle_v1::GroupCapabilities::empty(),
34 outputs: Vec::new(),
35 workspaces: HashSet::new(),
36 }));
37 }
38 self.pending.as_mut().unwrap()
39 }
40
41 fn commit_pending(&mut self) {
42 if let Some(pending) = self.pending.take() {
43 self.current = Some(pending);
44 }
45 }
46}
47
48#[derive(Clone, Debug)]
49pub struct Workspace {
50 pub handle: ext_workspace_handle_v1::ExtWorkspaceHandleV1,
51 pub cosmic_handle: Option<zcosmic_workspace_handle_v2::ZcosmicWorkspaceHandleV2>,
52 pub name: String,
53 pub coordinates: Vec<u32>,
54 pub state: ext_workspace_handle_v1::State,
55 pub cosmic_state: zcosmic_workspace_handle_v2::State,
56 pub capabilities: ext_workspace_handle_v1::WorkspaceCapabilities,
57 pub cosmic_capabilities: zcosmic_workspace_handle_v2::WorkspaceCapabilities,
58 pub tiling: Option<WEnum<zcosmic_workspace_handle_v2::TilingState>>,
59 pub id: Option<String>,
60}
61
62#[derive(Debug)]
63struct WorkspaceData {
64 handle: ext_workspace_handle_v1::ExtWorkspaceHandleV1,
65 cosmic_handle: Option<zcosmic_workspace_handle_v2::ZcosmicWorkspaceHandleV2>,
66 current: Option<Workspace>,
67 pending: Option<Workspace>,
68 has_cosmic_info: bool,
69}
70
71impl WorkspaceData {
72 fn pending(&mut self) -> &mut Workspace {
73 if self.pending.is_none() {
74 self.pending = Some(self.current.clone().unwrap_or(Workspace {
75 handle: self.handle.clone(),
76 cosmic_handle: self.cosmic_handle.clone(),
77 name: String::new(),
78 coordinates: Vec::new(),
79 state: ext_workspace_handle_v1::State::empty(),
80 cosmic_state: zcosmic_workspace_handle_v2::State::empty(),
81 capabilities: ext_workspace_handle_v1::WorkspaceCapabilities::empty(),
82 cosmic_capabilities: zcosmic_workspace_handle_v2::WorkspaceCapabilities::empty(),
83 tiling: None,
84 id: None,
85 }));
86 }
87 self.pending.as_mut().unwrap()
88 }
89
90 fn commit_pending(&mut self) {
91 if let Some(pending) = self.pending.take() {
92 self.current = Some(pending);
93 }
94 }
95}
96
97#[derive(Debug)]
98pub struct WorkspaceState {
99 workspace_groups: Vec<WorkspaceGroupData>,
100 workspaces: Vec<WorkspaceData>,
101 manager: GlobalProxy<ext_workspace_manager_v1::ExtWorkspaceManagerV1>,
102 cosmic_manager: GlobalProxy<zcosmic_workspace_manager_v2::ZcosmicWorkspaceManagerV2>,
103}
104
105impl WorkspaceState {
106 pub fn new<D>(registry: &RegistryState, qh: &QueueHandle<D>) -> Self
107 where
108 D: Dispatch<ext_workspace_manager_v1::ExtWorkspaceManagerV1, GlobalData>
109 + Dispatch<zcosmic_workspace_manager_v2::ZcosmicWorkspaceManagerV2, GlobalData>
110 + 'static,
111 {
112 Self {
113 workspace_groups: Vec::new(),
114 workspaces: Vec::new(),
115 manager: GlobalProxy::from(registry.bind_one(qh, 1..=1, GlobalData)),
116 cosmic_manager: GlobalProxy::from(registry.bind_one(qh, 1..=2, GlobalData)),
117 }
118 }
119
120 pub fn workspace_manager(
121 &self,
122 ) -> &GlobalProxy<ext_workspace_manager_v1::ExtWorkspaceManagerV1> {
123 &self.manager
124 }
125
126 pub fn workspace_groups(&self) -> impl Iterator<Item = &WorkspaceGroup> {
127 self.workspace_groups
128 .iter()
129 .filter_map(|data| data.current.as_ref())
130 }
131
132 pub fn workspace_group_info(
133 &self,
134 handle: &ext_workspace_group_handle_v1::ExtWorkspaceGroupHandleV1,
135 ) -> Option<&WorkspaceGroup> {
136 self.workspace_groups
137 .iter()
138 .find(|g| g.handle == *handle)?
139 .current
140 .as_ref()
141 }
142
143 pub fn workspaces(&self) -> impl Iterator<Item = &Workspace> {
144 self.workspaces
145 .iter()
146 .filter_map(|data| data.current.as_ref())
147 }
148
149 pub fn workspace_info(
150 &self,
151 handle: &ext_workspace_handle_v1::ExtWorkspaceHandleV1,
152 ) -> Option<&Workspace> {
153 self.workspaces
154 .iter()
155 .find(|g| g.handle == *handle)?
156 .current
157 .as_ref()
158 }
159}
160
161pub trait WorkspaceHandler {
162 fn workspace_state(&mut self) -> &mut WorkspaceState;
163
164 fn done(&mut self);
166}
167
168impl<D> Dispatch<ext_workspace_manager_v1::ExtWorkspaceManagerV1, GlobalData, D> for WorkspaceState
169where
170 D: Dispatch<ext_workspace_manager_v1::ExtWorkspaceManagerV1, GlobalData>
171 + Dispatch<ext_workspace_group_handle_v1::ExtWorkspaceGroupHandleV1, GlobalData>
172 + Dispatch<ext_workspace_handle_v1::ExtWorkspaceHandleV1, GlobalData>
173 + Dispatch<zcosmic_workspace_handle_v2::ZcosmicWorkspaceHandleV2, GlobalData>
174 + WorkspaceHandler
175 + 'static,
176{
177 fn event(
178 state: &mut D,
179 _: &ext_workspace_manager_v1::ExtWorkspaceManagerV1,
180 event: ext_workspace_manager_v1::Event,
181 _: &GlobalData,
182 _: &Connection,
183 qh: &QueueHandle<D>,
184 ) {
185 match event {
186 ext_workspace_manager_v1::Event::WorkspaceGroup { workspace_group } => {
187 state
188 .workspace_state()
189 .workspace_groups
190 .push(WorkspaceGroupData {
191 handle: workspace_group,
192 current: None,
193 pending: None,
194 });
195 }
196 ext_workspace_manager_v1::Event::Workspace { workspace } => {
197 let cosmic_handle =
198 state
199 .workspace_state()
200 .cosmic_manager
201 .get()
202 .ok()
203 .map(|cosmic_manager| {
204 cosmic_manager.get_cosmic_workspace(&workspace, qh, GlobalData)
205 });
206 state.workspace_state().workspaces.push(WorkspaceData {
207 handle: workspace,
208 cosmic_handle,
209 current: None,
210 pending: None,
211 has_cosmic_info: false,
212 });
213 }
214 ext_workspace_manager_v1::Event::Done => {
215 if state.workspace_state().cosmic_manager.get().is_ok()
219 && state
220 .workspace_state()
221 .workspaces
222 .iter()
223 .any(|w| !w.has_cosmic_info)
224 {
225 return;
226 }
227 for data in &mut state.workspace_state().workspace_groups {
228 data.commit_pending();
229 }
230 for data in &mut state.workspace_state().workspaces {
231 data.commit_pending();
232 }
233 state.done();
234 }
235 ext_workspace_manager_v1::Event::Finished => {}
236 _ => unreachable!(),
237 }
238 }
239
240 wayland_client::event_created_child!(D, ext_workspace_manager_v1::ExtWorkspaceManagerV1, [
241 ext_workspace_manager_v1::EVT_WORKSPACE_GROUP_OPCODE => (ext_workspace_group_handle_v1::ExtWorkspaceGroupHandleV1, GlobalData),
242 ext_workspace_manager_v1::EVT_WORKSPACE_OPCODE => (ext_workspace_handle_v1::ExtWorkspaceHandleV1, GlobalData)
243 ]);
244}
245
246impl<D> Dispatch<ext_workspace_group_handle_v1::ExtWorkspaceGroupHandleV1, GlobalData, D>
247 for WorkspaceState
248where
249 D: Dispatch<ext_workspace_group_handle_v1::ExtWorkspaceGroupHandleV1, GlobalData>
250 + Dispatch<ext_workspace_handle_v1::ExtWorkspaceHandleV1, GlobalData>
251 + WorkspaceHandler
252 + 'static,
253{
254 fn event(
255 state: &mut D,
256 handle: &ext_workspace_group_handle_v1::ExtWorkspaceGroupHandleV1,
257 event: ext_workspace_group_handle_v1::Event,
258 _: &GlobalData,
259 _: &Connection,
260 _: &QueueHandle<D>,
261 ) {
262 let group = &mut state
263 .workspace_state()
264 .workspace_groups
265 .iter_mut()
266 .find(|group| &group.handle == handle)
267 .unwrap();
268 match event {
269 ext_workspace_group_handle_v1::Event::Capabilities { capabilities } => {
270 group.pending().capabilities = bitflags_retained(capabilities);
271 }
272 ext_workspace_group_handle_v1::Event::OutputEnter { output } => {
273 group.pending().outputs.push(output);
274 }
275 ext_workspace_group_handle_v1::Event::OutputLeave { output } => {
276 let pending = group.pending();
277 if let Some(idx) = pending.outputs.iter().position(|x| x == &output) {
278 pending.outputs.remove(idx);
279 }
280 }
281 ext_workspace_group_handle_v1::Event::WorkspaceEnter { workspace } => {
282 group.pending().workspaces.insert(workspace);
283 }
284 ext_workspace_group_handle_v1::Event::WorkspaceLeave { workspace } => {
285 group.pending().workspaces.remove(&workspace);
286 }
287 ext_workspace_group_handle_v1::Event::Removed => {
288 if let Some(idx) = state
289 .workspace_state()
290 .workspace_groups
291 .iter()
292 .position(|group| &group.handle == handle)
293 {
294 state.workspace_state().workspace_groups.remove(idx);
295 }
296 }
297 _ => unreachable!(),
298 }
299 }
300}
301
302impl<D> Dispatch<ext_workspace_handle_v1::ExtWorkspaceHandleV1, GlobalData, D> for WorkspaceState
303where
304 D: Dispatch<ext_workspace_handle_v1::ExtWorkspaceHandleV1, GlobalData> + WorkspaceHandler,
305{
306 fn event(
307 state: &mut D,
308 handle: &ext_workspace_handle_v1::ExtWorkspaceHandleV1,
309 event: ext_workspace_handle_v1::Event,
310 _: &GlobalData,
311 _: &Connection,
312 _: &QueueHandle<D>,
313 ) {
314 let workspace = state
315 .workspace_state()
316 .workspaces
317 .iter_mut()
318 .find(|w| &w.handle == handle)
319 .unwrap();
320 match event {
321 ext_workspace_handle_v1::Event::Name { name } => {
322 workspace.pending().name = name;
323 }
324 ext_workspace_handle_v1::Event::Coordinates { coordinates } => {
325 workspace.pending().coordinates = coordinates
326 .chunks(4)
327 .map(|chunk| u32::from_ne_bytes(chunk.try_into().unwrap()))
328 .collect();
329 }
330 ext_workspace_handle_v1::Event::State { state } => {
331 workspace.pending().state = bitflags_retained(state);
332 }
333 ext_workspace_handle_v1::Event::Capabilities { capabilities } => {
334 workspace.pending().capabilities = bitflags_retained(capabilities);
335 }
336 ext_workspace_handle_v1::Event::Id { id } => {
337 workspace.pending().id = Some(id);
338 }
339 ext_workspace_handle_v1::Event::Removed => {
340 if let Some(idx) = state
344 .workspace_state()
345 .workspaces
346 .iter()
347 .position(|w| &w.handle == handle)
348 {
349 state.workspace_state().workspaces.remove(idx);
350 }
351 }
352 _ => unreachable!(),
353 }
354 }
355}
356
357impl<D> Dispatch<zcosmic_workspace_manager_v2::ZcosmicWorkspaceManagerV2, GlobalData, D>
358 for WorkspaceState
359where
360 D: Dispatch<zcosmic_workspace_manager_v2::ZcosmicWorkspaceManagerV2, GlobalData>
361 + WorkspaceHandler
362 + 'static,
363{
364 fn event(
365 _: &mut D,
366 _: &zcosmic_workspace_manager_v2::ZcosmicWorkspaceManagerV2,
367 _: zcosmic_workspace_manager_v2::Event,
368 _: &GlobalData,
369 _: &Connection,
370 _: &QueueHandle<D>,
371 ) {
372 unreachable!()
373 }
374}
375
376impl<D> Dispatch<zcosmic_workspace_handle_v2::ZcosmicWorkspaceHandleV2, GlobalData, D>
377 for WorkspaceState
378where
379 D: Dispatch<zcosmic_workspace_handle_v2::ZcosmicWorkspaceHandleV2, GlobalData>
380 + WorkspaceHandler
381 + 'static,
382{
383 fn event(
384 state: &mut D,
385 handle: &zcosmic_workspace_handle_v2::ZcosmicWorkspaceHandleV2,
386 event: zcosmic_workspace_handle_v2::Event,
387 _: &GlobalData,
388 _: &Connection,
389 _: &QueueHandle<D>,
390 ) {
391 let workspace = state
392 .workspace_state()
393 .workspaces
394 .iter_mut()
395 .find(|w| w.cosmic_handle.as_ref() == Some(&handle))
396 .unwrap();
397 match event {
398 zcosmic_workspace_handle_v2::Event::Capabilities { capabilities } => {
399 workspace.pending().cosmic_capabilities = bitflags_retained(capabilities);
400 workspace.has_cosmic_info = true;
401 }
402 zcosmic_workspace_handle_v2::Event::TilingState { state } => {
403 workspace.pending().tiling = Some(state);
404 }
405 zcosmic_workspace_handle_v2::Event::State { state } => {
406 workspace.pending().cosmic_state = bitflags_retained(state);
407 }
408 _ => unreachable!(),
409 }
410 }
411}
412
413fn bitflags_retained<T: bitflags::Flags<Bits = u32>>(flags: WEnum<T>) -> T {
415 match flags {
416 WEnum::Value(value) => value,
417 WEnum::Unknown(value) => T::from_bits_retain(value),
418 }
419}
420
421#[macro_export]
422macro_rules! delegate_workspace {
423 ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
424 $crate::wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
425 $crate::wayland_protocols::ext::workspace::v1::client::ext_workspace_manager_v1::ExtWorkspaceManagerV1: $crate::GlobalData
426 ] => $crate::workspace::WorkspaceState);
427 $crate::wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
428 $crate::wayland_protocols::ext::workspace::v1::client::ext_workspace_group_handle_v1::ExtWorkspaceGroupHandleV1: $crate::GlobalData
429 ] => $crate::workspace::WorkspaceState);
430 $crate::wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
431 $crate::wayland_protocols::ext::workspace::v1::client::ext_workspace_handle_v1::ExtWorkspaceHandleV1: $crate::GlobalData
432 ] => $crate::workspace::WorkspaceState);
433
434 $crate::wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
435 $crate::cosmic_protocols::workspace::v2::client::zcosmic_workspace_manager_v2::ZcosmicWorkspaceManagerV2: $crate::GlobalData
436 ] => $crate::workspace::WorkspaceState);
437 $crate::wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
438 $crate::cosmic_protocols::workspace::v2::client::zcosmic_workspace_handle_v2::ZcosmicWorkspaceHandleV2: $crate::GlobalData
439 ] => $crate::workspace::WorkspaceState);
440 };
441}