1use std::{
2 collections::{HashMap, HashSet},
3 sync::OnceLock,
4};
5
6use cosmic_protocols::toplevel_info::v1::client::{
7 zcosmic_toplevel_handle_v1, zcosmic_toplevel_info_v1,
8};
9use sctk::registry::RegistryState;
10use wayland_client::{Connection, Dispatch, Proxy, QueueHandle, Weak, protocol::wl_output};
11use wayland_protocols::ext::{
12 foreign_toplevel_list::v1::client::{
13 ext_foreign_toplevel_handle_v1, ext_foreign_toplevel_list_v1,
14 },
15 workspace::v1::client::ext_workspace_handle_v1,
16};
17
18use crate::GlobalData;
19
20#[derive(Clone, Debug, Default)]
21pub struct ToplevelGeometry {
22 pub x: i32,
23 pub y: i32,
24 pub width: i32,
25 pub height: i32,
26}
27
28#[derive(Clone, Debug)]
29pub struct ToplevelInfo {
30 pub title: String,
31 pub app_id: String,
32 pub identifier: String,
33 pub state: HashSet<zcosmic_toplevel_handle_v1::State>,
35 pub output: HashSet<wl_output::WlOutput>,
37 pub geometry: HashMap<wl_output::WlOutput, ToplevelGeometry>,
39 pub workspace: HashSet<ext_workspace_handle_v1::ExtWorkspaceHandleV1>,
41 pub cosmic_toplevel: Option<zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1>,
43 pub foreign_toplevel: ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1,
44}
45
46#[derive(Debug)]
47struct ToplevelData {
48 current_info: Option<ToplevelInfo>,
49 pending_info: ToplevelInfo,
50 has_cosmic_info: bool,
51}
52
53impl ToplevelData {
54 fn new(foreign_toplevel: ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1) -> Self {
55 let pending_info = ToplevelInfo {
56 title: String::new(),
57 app_id: String::new(),
58 identifier: String::new(),
59 state: HashSet::new(),
60 output: HashSet::new(),
61 geometry: HashMap::new(),
62 workspace: HashSet::new(),
63 cosmic_toplevel: None,
64 foreign_toplevel,
65 };
66 Self {
67 current_info: None,
68 pending_info,
69 has_cosmic_info: false,
70 }
71 }
72
73 fn cosmic_toplevel(&self) -> Option<&zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1> {
74 self.pending_info.cosmic_toplevel.as_ref()
75 }
76
77 fn foreign_toplevel(&self) -> &ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1 {
78 &self.pending_info.foreign_toplevel
79 }
80}
81
82#[doc(hidden)]
83#[derive(Default)]
84pub struct ToplevelUserData {
85 cosmic_toplevel: OnceLock<Option<Weak<zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1>>>,
86}
87
88#[derive(Debug)]
91pub struct ToplevelInfoState {
92 pub foreign_toplevel_list: ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1,
93 pub cosmic_toplevel_info: Option<zcosmic_toplevel_info_v1::ZcosmicToplevelInfoV1>,
94 toplevels: Vec<ToplevelData>,
95}
96
97impl ToplevelInfoState {
98 pub fn try_new<D>(registry: &RegistryState, qh: &QueueHandle<D>) -> Option<Self>
99 where
100 D: Dispatch<zcosmic_toplevel_info_v1::ZcosmicToplevelInfoV1, GlobalData>
101 + Dispatch<ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1, GlobalData>
102 + 'static,
103 {
104 let foreign_toplevel_list = registry
105 .bind_one::<ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1, _, _>(
106 qh,
107 1..=1,
108 GlobalData,
109 )
110 .ok()?;
111 let cosmic_toplevel_info = registry
112 .bind_one::<zcosmic_toplevel_info_v1::ZcosmicToplevelInfoV1, _, _>(
113 qh,
114 2..=3,
115 GlobalData,
116 )
117 .ok();
118
119 Some(Self {
120 foreign_toplevel_list,
121 cosmic_toplevel_info,
122 toplevels: Vec::new(),
123 })
124 }
125
126 pub fn new<D>(registry: &RegistryState, qh: &QueueHandle<D>) -> Self
127 where
128 D: Dispatch<zcosmic_toplevel_info_v1::ZcosmicToplevelInfoV1, GlobalData>
129 + Dispatch<ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1, GlobalData>
130 + 'static,
131 {
132 Self::try_new(registry, qh).unwrap()
133 }
134
135 pub fn info(
136 &self,
137 toplevel: &ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1,
138 ) -> Option<&ToplevelInfo> {
139 self.toplevels
140 .iter()
141 .find(|data| data.foreign_toplevel() == toplevel)?
142 .current_info
143 .as_ref()
144 }
145
146 pub fn toplevels(&self) -> impl Iterator<Item = &ToplevelInfo> {
147 self.toplevels
148 .iter()
149 .filter_map(|data| data.current_info.as_ref())
150 }
151}
152
153pub trait ToplevelInfoHandler: Sized {
154 fn toplevel_info_state(&mut self) -> &mut ToplevelInfoState;
155
156 fn new_toplevel(
157 &mut self,
158 conn: &Connection,
159 qh: &QueueHandle<Self>,
160 toplevel: &ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1,
161 );
162
163 fn update_toplevel(
164 &mut self,
165 conn: &Connection,
166 qh: &QueueHandle<Self>,
167 toplevel: &ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1,
168 );
169
170 fn toplevel_closed(
171 &mut self,
172 conn: &Connection,
173 qh: &QueueHandle<Self>,
174 toplevel: &ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1,
175 );
176
177 fn info_done(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>) {}
178
179 fn finished(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>) {}
180}
181
182impl<D> Dispatch<zcosmic_toplevel_info_v1::ZcosmicToplevelInfoV1, GlobalData, D>
183 for ToplevelInfoState
184where
185 D: Dispatch<zcosmic_toplevel_info_v1::ZcosmicToplevelInfoV1, GlobalData>
186 + Dispatch<zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1, GlobalData>
187 + ToplevelInfoHandler
188 + 'static,
189{
190 fn event(
191 state: &mut D,
192 _proxy: &zcosmic_toplevel_info_v1::ZcosmicToplevelInfoV1,
193 event: zcosmic_toplevel_info_v1::Event,
194 _: &GlobalData,
195 conn: &Connection,
196 qh: &QueueHandle<D>,
197 ) {
198 match event {
199 zcosmic_toplevel_info_v1::Event::Done => {
200 state.info_done(conn, qh);
201 }
202 zcosmic_toplevel_info_v1::Event::Toplevel { .. }
204 | zcosmic_toplevel_info_v1::Event::Finished => {}
205 _ => unreachable!(),
206 }
207 }
208
209 wayland_client::event_created_child!(D, zcosmic_toplevel_info_v1::ZcosmicToplevelInfoV1, [
210 zcosmic_toplevel_info_v1::EVT_TOPLEVEL_OPCODE => (zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1, GlobalData)
211 ]);
212}
213
214impl<D> Dispatch<zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1, GlobalData, D>
215 for ToplevelInfoState
216where
217 D: Dispatch<zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1, GlobalData>
218 + ToplevelInfoHandler
219 + 'static,
220{
221 fn event(
222 state: &mut D,
223 toplevel: &zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1,
224 event: zcosmic_toplevel_handle_v1::Event,
225 _: &GlobalData,
226 _conn: &Connection,
227 _qh: &QueueHandle<D>,
228 ) {
229 let data = &mut state
230 .toplevel_info_state()
231 .toplevels
232 .iter_mut()
233 .find(|data| data.cosmic_toplevel() == Some(toplevel))
234 .expect("Received event for dead toplevel");
235 match event {
236 zcosmic_toplevel_handle_v1::Event::OutputEnter { output } => {
237 data.pending_info.output.insert(output);
238 }
239 zcosmic_toplevel_handle_v1::Event::OutputLeave { output } => {
240 data.pending_info.output.remove(&output);
241 data.pending_info.geometry.remove(&output);
242 }
243 zcosmic_toplevel_handle_v1::Event::WorkspaceEnter { .. }
245 | zcosmic_toplevel_handle_v1::Event::WorkspaceLeave { .. } => {}
246 zcosmic_toplevel_handle_v1::Event::ExtWorkspaceEnter { workspace } => {
247 data.pending_info.workspace.insert(workspace);
248 }
249 zcosmic_toplevel_handle_v1::Event::ExtWorkspaceLeave { workspace } => {
250 data.pending_info.workspace.remove(&workspace);
251 }
252 zcosmic_toplevel_handle_v1::Event::State { state } => {
253 data.has_cosmic_info = true;
254 data.pending_info.state.clear();
255 for value in state.chunks_exact(4) {
256 if let Ok(state) = zcosmic_toplevel_handle_v1::State::try_from(
257 u32::from_ne_bytes(value[0..4].try_into().unwrap()),
258 ) {
259 data.pending_info.state.insert(state);
260 }
261 }
262 }
263 zcosmic_toplevel_handle_v1::Event::Geometry {
264 output,
265 x,
266 y,
267 width,
268 height,
269 } => {
270 data.pending_info.geometry.insert(
271 output,
272 ToplevelGeometry {
273 x,
274 y,
275 width,
276 height,
277 },
278 );
279 }
280 zcosmic_toplevel_handle_v1::Event::AppId { .. }
282 | zcosmic_toplevel_handle_v1::Event::Title { .. }
283 | zcosmic_toplevel_handle_v1::Event::Done { .. }
284 | zcosmic_toplevel_handle_v1::Event::Closed { .. } => {}
285 _ => unreachable!(),
286 }
287 }
288}
289
290impl<D> Dispatch<ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1, GlobalData, D>
291 for ToplevelInfoState
292where
293 D: Dispatch<ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1, GlobalData>
294 + Dispatch<ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1, ToplevelUserData>
295 + Dispatch<zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1, GlobalData>
296 + ToplevelInfoHandler
297 + 'static,
298{
299 fn event(
300 state: &mut D,
301 proxy: &ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1,
302 event: ext_foreign_toplevel_list_v1::Event,
303 _: &GlobalData,
304 conn: &Connection,
305 qh: &QueueHandle<D>,
306 ) {
307 match event {
308 ext_foreign_toplevel_list_v1::Event::Toplevel { toplevel } => {
309 let info_state = state.toplevel_info_state();
310 let mut toplevel_data = ToplevelData::new(toplevel.clone());
311 let cosmic_toplevel =
312 info_state
313 .cosmic_toplevel_info
314 .as_ref()
315 .map(|cosmic_toplevel_info| {
316 cosmic_toplevel_info.get_cosmic_toplevel(&toplevel, qh, GlobalData)
317 });
318 toplevel
319 .data::<ToplevelUserData>()
320 .unwrap()
321 .cosmic_toplevel
322 .set(cosmic_toplevel.as_ref().map(|t| t.downgrade()))
323 .unwrap();
324 toplevel_data.pending_info.cosmic_toplevel = cosmic_toplevel;
325 info_state.toplevels.push(toplevel_data);
326 }
327 ext_foreign_toplevel_list_v1::Event::Finished => {
328 state.finished(conn, qh);
329 proxy.destroy();
330 }
331 _ => unreachable!(),
332 }
333 }
334
335 wayland_client::event_created_child!(D, ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1, [
336 ext_foreign_toplevel_list_v1::EVT_TOPLEVEL_OPCODE => (ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1, Default::default())
337 ]);
338}
339
340impl<D> Dispatch<ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1, ToplevelUserData, D>
341 for ToplevelInfoState
342where
343 D: Dispatch<ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1, ToplevelUserData>
344 + ToplevelInfoHandler,
345{
346 fn event(
347 state: &mut D,
348 handle: &ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1,
349 event: ext_foreign_toplevel_handle_v1::Event,
350 _data: &ToplevelUserData,
351 conn: &Connection,
352 qh: &QueueHandle<D>,
353 ) {
354 let data = &mut state
355 .toplevel_info_state()
356 .toplevels
357 .iter_mut()
358 .find(|data| data.foreign_toplevel() == handle)
359 .expect("Received event for dead toplevel");
360 match event {
361 ext_foreign_toplevel_handle_v1::Event::Closed => {
362 state.toplevel_closed(conn, qh, handle);
363
364 let toplevels = &mut state.toplevel_info_state().toplevels;
365 if let Some(idx) = toplevels
366 .iter()
367 .position(|data| data.foreign_toplevel() == handle)
368 {
369 toplevels.remove(idx);
370 }
371 }
372 ext_foreign_toplevel_handle_v1::Event::Done => {
373 if data.cosmic_toplevel().is_some() && !data.has_cosmic_info {
374 return;
377 }
378
379 let is_new = data.current_info.is_none();
380 data.current_info = Some(data.pending_info.clone());
381 if is_new {
382 state.new_toplevel(conn, qh, handle);
383 } else {
384 state.update_toplevel(conn, qh, handle);
385 }
386 }
387 ext_foreign_toplevel_handle_v1::Event::Title { title } => {
388 data.pending_info.title = title;
389 }
390 ext_foreign_toplevel_handle_v1::Event::AppId { app_id } => {
391 data.pending_info.app_id = app_id;
392 }
393 ext_foreign_toplevel_handle_v1::Event::Identifier { identifier } => {
394 data.pending_info.identifier = identifier;
395 }
396 _ => unreachable!(),
397 }
398 }
399}
400
401#[macro_export]
402macro_rules! delegate_toplevel_info {
403 ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
404 $crate::wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
405 $crate::cosmic_protocols::toplevel_info::v1::client::zcosmic_toplevel_info_v1::ZcosmicToplevelInfoV1: $crate::GlobalData
406 ] => $crate::toplevel_info::ToplevelInfoState);
407 $crate::wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
408 $crate::cosmic_protocols::toplevel_info::v1::client::zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1: $crate::GlobalData
409 ] => $crate::toplevel_info::ToplevelInfoState);
410 $crate::wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
411 $crate::wayland_protocols::ext::foreign_toplevel_list::v1::client::ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1: $crate::GlobalData
412 ] => $crate::toplevel_info::ToplevelInfoState);
413 $crate::wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
414 $crate::wayland_protocols::ext::foreign_toplevel_list::v1::client::ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1: $crate::toplevel_info::ToplevelUserData
415 ] => $crate::toplevel_info::ToplevelInfoState);
416 };
417}