smithay/wayland/foreign_toplevel_list/
mod.rs

1//! Foreign toplevel list
2//!
3//! The purpose of this protocol is to provide protocol object handles for toplevels.
4//!
5//! ```no_run
6//! use smithay::wayland::foreign_toplevel_list::{ForeignToplevelListState, ForeignToplevelListHandler};
7//!
8//! pub struct State {
9//!     foreign_toplevel_list: ForeignToplevelListState,
10//! }
11//!
12//! smithay::delegate_foreign_toplevel_list!(State);
13//!
14//! impl ForeignToplevelListHandler for State {
15//!     fn foreign_toplevel_list_state(&mut self) -> &mut ForeignToplevelListState {
16//!         &mut self.foreign_toplevel_list
17//!     }
18//! }
19//!
20//! # let mut display = wayland_server::Display::<State>::new().unwrap();
21//! # let display_handle = display.handle();
22//! let mut state = State {
23//!     foreign_toplevel_list: ForeignToplevelListState::new::<State>(&display_handle),
24//! };
25//!
26//! let handle = state.foreign_toplevel_list.new_toplevel::<State>("Window Title", "com.example");
27//!
28//! // Handle can be used to update title and app_id
29//! handle.send_title("Window title has changed");
30//! handle.send_done();
31//!
32//! // Handle can also be used to close the window, after this call the handle will become inert,
33//! // and the handle will no longer be announced to clients
34//! handle.send_closed();
35//! ```
36
37use std::sync::{Arc, Mutex};
38
39use rand::distributions::{Alphanumeric, DistString};
40use wayland_protocols::ext::foreign_toplevel_list::v1::server::{
41    ext_foreign_toplevel_handle_v1::{self, ExtForeignToplevelHandleV1},
42    ext_foreign_toplevel_list_v1::{self, ExtForeignToplevelListV1},
43};
44use wayland_server::{
45    backend::{ClientId, GlobalId},
46    Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, Weak,
47};
48
49use crate::utils::user_data::UserDataMap;
50
51/// Handler for foreign toplevel list protocol
52pub trait ForeignToplevelListHandler:
53    GlobalDispatch<ExtForeignToplevelListV1, ForeignToplevelListGlobalData>
54    + Dispatch<ExtForeignToplevelListV1, ()>
55    + Dispatch<ExtForeignToplevelHandleV1, ForeignToplevelHandle>
56    + 'static
57{
58    /// [ForeignToplevelListState] getter
59    fn foreign_toplevel_list_state(&mut self) -> &mut ForeignToplevelListState;
60}
61
62#[derive(Debug)]
63struct ForeignToplevelHandleInner {
64    identifier: String,
65    title: String,
66    app_id: String,
67    // Each ExtForeignToplevelHandleV1 contains the handle in it's user data,
68    // so this ref has to be weak
69    instances: Vec<Weak<ExtForeignToplevelHandleV1>>,
70    closed: bool,
71}
72
73impl ForeignToplevelHandleInner {
74    /// The toplevel has been closed
75    fn send_closed(&mut self) {
76        if self.closed {
77            return;
78        }
79
80        self.closed = true;
81        // drain to prevent any events from being sent to closed handles
82        for toplevel in self.instances.drain(..) {
83            if let Ok(toplevel) = toplevel.upgrade() {
84                toplevel.closed();
85            }
86        }
87    }
88}
89
90impl Drop for ForeignToplevelHandleInner {
91    fn drop(&mut self) {
92        self.send_closed()
93    }
94}
95
96/// Weak version of [ForeignToplevelHandle]
97#[derive(Debug, Clone)]
98pub struct ForeignToplevelWeakHandle {
99    inner: std::sync::Weak<(Mutex<ForeignToplevelHandleInner>, UserDataMap)>,
100}
101
102impl ForeignToplevelWeakHandle {
103    /// Upgrade weak [ForeignToplevelWeakHandle] to strong [ForeignToplevelHandle]
104    pub fn upgrade(&self) -> Option<ForeignToplevelHandle> {
105        Some(ForeignToplevelHandle {
106            inner: self.inner.upgrade()?,
107        })
108    }
109}
110
111/// Handle of a toplevel, used to update title or app_id, after initial handle creation
112///
113/// eg.
114/// ```no_run
115/// use smithay::wayland::foreign_toplevel_list::ForeignToplevelHandle;
116/// let handle: ForeignToplevelHandle = todo!();
117///
118/// // Handle can be used to update title and app_id
119/// handle.send_title("abc");
120/// handle.send_app_id("com.example");
121/// handle.send_done();
122///
123/// // Handle can also be used to close the window, after this call the handle will become inert,
124/// // and the handle will no longer be announced to clients
125/// handle.send_closed();
126/// ```
127#[derive(Debug, Clone)]
128pub struct ForeignToplevelHandle {
129    inner: Arc<(Mutex<ForeignToplevelHandleInner>, UserDataMap)>,
130}
131
132impl ForeignToplevelHandle {
133    fn new(
134        identifier: String,
135        title: String,
136        app_id: String,
137        instances: Vec<Weak<ExtForeignToplevelHandleV1>>,
138    ) -> Self {
139        Self {
140            inner: Arc::new((
141                Mutex::new(ForeignToplevelHandleInner {
142                    identifier,
143                    title,
144                    app_id,
145                    instances,
146                    closed: false,
147                }),
148                UserDataMap::new(),
149            )),
150        }
151    }
152
153    /// Downgrade strong [ForeignToplevelHandle] to weak [ForeignToplevelWeakHandle]
154    pub fn downgrade(&self) -> ForeignToplevelWeakHandle {
155        ForeignToplevelWeakHandle {
156            inner: Arc::downgrade(&self.inner),
157        }
158    }
159
160    /// Attempt to retrieve [ForeignToplevelHandle] from an existing resource
161    pub fn from_resource(resource: &ExtForeignToplevelHandleV1) -> Option<Self> {
162        resource.data::<Self>().cloned()
163    }
164
165    /// Retrieve [`ExtForeignToplevelHandleV1`]
166    /// instances for this handle.
167    pub fn resources(&self) -> Vec<ExtForeignToplevelHandleV1> {
168        let inner = self.inner.0.lock().unwrap();
169        inner
170            .instances
171            .iter()
172            .filter_map(|weak| weak.upgrade().ok())
173            .collect()
174    }
175
176    /// Retrieve [`ExtForeignToplevelHandleV1`]
177    /// instances for this handle of a given [`Client`].
178    pub fn resources_for_client(&self, client: &Client) -> Vec<ExtForeignToplevelHandleV1> {
179        self.resources()
180            .into_iter()
181            .filter(|handle| handle.client().as_ref().is_some_and(|c| c == client))
182            .collect()
183    }
184
185    /// Access the [UserDataMap] associated with this [ForeignToplevelHandle]
186    pub fn user_data(&self) -> &UserDataMap {
187        &self.inner.1
188    }
189
190    /// The title of the toplevel has changed.
191    ///
192    /// [Self::send_done] has to be called to finalize the update
193    pub fn send_title(&self, title: &str) {
194        let mut inner = self.inner.0.lock().unwrap();
195        if inner.title == title {
196            return;
197        }
198
199        inner.title = title.to_string();
200
201        for toplevel in inner.instances.iter() {
202            if let Ok(toplevel) = toplevel.upgrade() {
203                toplevel.title(title.to_string());
204            }
205        }
206    }
207
208    /// The app_id of the toplevel has changed.
209    ///
210    /// [Self::send_done] has to be called to finalize the update
211    pub fn send_app_id(&self, app_id: &str) {
212        let mut inner = self.inner.0.lock().unwrap();
213        if inner.app_id == app_id {
214            return;
215        }
216
217        inner.app_id = app_id.to_string();
218
219        for toplevel in inner.instances.iter() {
220            if let Ok(toplevel) = toplevel.upgrade() {
221                toplevel.app_id(app_id.to_string());
222            }
223        }
224    }
225
226    /// This event is should be sent after all changes in the toplevel state have been sent.
227    pub fn send_done(&self) {
228        let inner = self.inner.0.lock().unwrap();
229        for toplevel in inner.instances.iter() {
230            if let Ok(toplevel) = toplevel.upgrade() {
231                toplevel.done();
232            }
233        }
234    }
235
236    /// The toplevel has been closed
237    pub fn send_closed(&self) {
238        self.inner.0.lock().unwrap().send_closed();
239    }
240
241    /// A stable identifier for a toplevel
242    pub fn identifier(&self) -> String {
243        self.inner.0.lock().unwrap().identifier.clone()
244    }
245
246    /// The title of the toplevel
247    pub fn title(&self) -> String {
248        self.inner.0.lock().unwrap().title.clone()
249    }
250
251    /// The app id of the toplevel
252    pub fn app_id(&self) -> String {
253        self.inner.0.lock().unwrap().app_id.clone()
254    }
255
256    /// The toplevel has been closed
257    pub fn is_closed(&self) -> bool {
258        self.inner.0.lock().unwrap().closed
259    }
260
261    fn init_new_instance(&self, toplevel: ExtForeignToplevelHandleV1) {
262        debug_assert!(
263            !self.is_closed(),
264            "No handles should ever be created for closed toplevel"
265        );
266
267        toplevel.identifier(self.identifier());
268        toplevel.title(self.title());
269        toplevel.app_id(self.app_id());
270        toplevel.done();
271
272        self.inner.0.lock().unwrap().instances.push(toplevel.downgrade());
273    }
274
275    fn remove_instance(&self, instance: &ExtForeignToplevelHandleV1) {
276        let mut inner = self.inner.0.lock().unwrap();
277        if let Some(pos) = inner.instances.iter().position(|i| i == instance) {
278            inner.instances.remove(pos);
279        }
280    }
281}
282
283/// State of the [ExtForeignToplevelListV1] global
284#[derive(Debug)]
285pub struct ForeignToplevelListState {
286    global: GlobalId,
287    toplevels: Vec<ForeignToplevelWeakHandle>,
288    list_instances: Vec<ExtForeignToplevelListV1>,
289    dh: DisplayHandle,
290}
291
292impl ForeignToplevelListState {
293    /// Register new [ExtForeignToplevelListV1] global
294    pub fn new<D: ForeignToplevelListHandler>(dh: &DisplayHandle) -> Self {
295        Self::new_with_filter::<D>(dh, |_| true)
296    }
297
298    /// Register new [ExtForeignToplevelListV1] global with filter
299    pub fn new_with_filter<D: ForeignToplevelListHandler>(
300        dh: &DisplayHandle,
301        can_view: impl Fn(&Client) -> bool + Send + Sync + 'static,
302    ) -> Self {
303        let global = dh.create_global::<D, ExtForeignToplevelListV1, _>(
304            1,
305            ForeignToplevelListGlobalData {
306                filter: Box::new(can_view),
307            },
308        );
309
310        Self {
311            global,
312            toplevels: Vec::new(),
313            list_instances: Vec::new(),
314            dh: dh.clone(),
315        }
316    }
317
318    /// [ExtForeignToplevelListV1] GlobalId getter
319    pub fn global(&self) -> GlobalId {
320        self.global.clone()
321    }
322
323    /// This event is emitted whenever a new toplevel window is created.
324    /// It is emitted for all toplevels, regardless of the app that has created them.
325    pub fn new_toplevel<D: ForeignToplevelListHandler>(
326        &mut self,
327        title: impl Into<String>,
328        app_id: impl Into<String>,
329    ) -> ForeignToplevelHandle {
330        let handle = ForeignToplevelHandle::new(
331            Alphanumeric.sample_string(&mut rand::thread_rng(), 32),
332            title.into(),
333            app_id.into(),
334            Vec::with_capacity(self.list_instances.len()),
335        );
336
337        for instance in &self.list_instances {
338            let Ok(client) = self.dh.get_client(instance.id()) else {
339                continue;
340            };
341
342            let Ok(toplevel) = client.create_resource::<ExtForeignToplevelHandleV1, _, D>(
343                &self.dh,
344                instance.version(),
345                handle.clone(),
346            ) else {
347                continue;
348            };
349
350            instance.toplevel(&toplevel);
351            handle.init_new_instance(toplevel);
352        }
353
354        self.toplevels.push(handle.downgrade());
355
356        handle
357    }
358
359    /// Remove the toplevel, and send closed event if needed
360    ///
361    /// Alternatively, you can just call [ForeignToplevelHandle::send_closed] and the handle will be
362    /// lazely cleaned up, either by [Self::cleanup_closed_handles], or during next global bind
363    pub fn remove_toplevel(&mut self, handle: &ForeignToplevelHandle) {
364        handle.send_closed();
365
366        if let Some(pos) = self
367            .toplevels
368            .iter()
369            .filter_map(|h| h.upgrade())
370            .position(|h| Arc::ptr_eq(&h.inner, &handle.inner))
371        {
372            self.toplevels.remove(pos);
373        }
374    }
375
376    /// Auto cleanup closed handles
377    ///
378    /// This is not needed if you already manually remove each handle with [Self::remove_toplevel]
379    pub fn cleanup_closed_handles(&mut self) {
380        self.toplevels.retain(|handle| {
381            let Some(handle) = handle.upgrade() else {
382                return false;
383            };
384            !handle.is_closed()
385        });
386    }
387}
388
389/// Glabal data of [ExtForeignToplevelListV1]
390pub struct ForeignToplevelListGlobalData {
391    filter: Box<dyn Fn(&Client) -> bool + Send + Sync>,
392}
393
394impl std::fmt::Debug for ForeignToplevelListGlobalData {
395    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
396        f.debug_struct("ForeignToplevelListGlobalData")
397            .finish_non_exhaustive()
398    }
399}
400
401impl<D: ForeignToplevelListHandler> GlobalDispatch<ExtForeignToplevelListV1, ForeignToplevelListGlobalData, D>
402    for ForeignToplevelListState
403{
404    fn bind(
405        state: &mut D,
406        dh: &DisplayHandle,
407        client: &Client,
408        resource: New<ExtForeignToplevelListV1>,
409        _global_data: &ForeignToplevelListGlobalData,
410        data_init: &mut DataInit<'_, D>,
411    ) {
412        let instance = data_init.init(resource, ());
413
414        let state = state.foreign_toplevel_list_state();
415
416        state.toplevels.retain(|handle| {
417            let Some(handle) = handle.upgrade() else {
418                // Cleanup dead handles
419                return false;
420            };
421
422            if handle.is_closed() {
423                // Cleanup closed handles
424                return false;
425            }
426
427            if let Ok(toplevel) = client.create_resource::<ExtForeignToplevelHandleV1, _, D>(
428                dh,
429                instance.version(),
430                handle.clone(),
431            ) {
432                instance.toplevel(&toplevel);
433                handle.init_new_instance(toplevel);
434            }
435
436            true
437        });
438
439        state.list_instances.push(instance);
440    }
441
442    fn can_view(client: Client, global_data: &ForeignToplevelListGlobalData) -> bool {
443        (global_data.filter)(&client)
444    }
445}
446
447impl<D: ForeignToplevelListHandler> Dispatch<ExtForeignToplevelListV1, (), D> for ForeignToplevelListState {
448    fn request(
449        state: &mut D,
450        client: &wayland_server::Client,
451        manager: &ExtForeignToplevelListV1,
452        request: ext_foreign_toplevel_list_v1::Request,
453        data: &(),
454        _dh: &DisplayHandle,
455        _data_init: &mut wayland_server::DataInit<'_, D>,
456    ) {
457        match request {
458            ext_foreign_toplevel_list_v1::Request::Stop => {
459                Self::destroyed(state, client.id(), manager, data);
460                manager.finished();
461            }
462            ext_foreign_toplevel_list_v1::Request::Destroy => {}
463            _ => unreachable!(),
464        }
465    }
466
467    fn destroyed(state: &mut D, _client: ClientId, resource: &ExtForeignToplevelListV1, _data: &()) {
468        state
469            .foreign_toplevel_list_state()
470            .list_instances
471            .retain(|i| i != resource);
472    }
473}
474
475impl<D: ForeignToplevelListHandler> Dispatch<ExtForeignToplevelHandleV1, ForeignToplevelHandle, D>
476    for ForeignToplevelListState
477{
478    fn request(
479        _state: &mut D,
480        _client: &wayland_server::Client,
481        _context: &ExtForeignToplevelHandleV1,
482        request: ext_foreign_toplevel_handle_v1::Request,
483        _data: &ForeignToplevelHandle,
484        _dh: &DisplayHandle,
485        _data_init: &mut wayland_server::DataInit<'_, D>,
486    ) {
487        match request {
488            ext_foreign_toplevel_handle_v1::Request::Destroy => {}
489            _ => unreachable!(),
490        }
491    }
492
493    fn destroyed(
494        _state: &mut D,
495        _client: ClientId,
496        resource: &ExtForeignToplevelHandleV1,
497        handle: &ForeignToplevelHandle,
498    ) {
499        handle.remove_instance(resource);
500    }
501}
502
503/// Macro to delegate implementation of the xdg toplevel icon to [ForeignToplevelListState].
504///
505/// You must also implement [ForeignToplevelListHandler] to use this.
506#[macro_export]
507macro_rules! delegate_foreign_toplevel_list {
508    ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
509        $crate::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
510            $crate::reexports::wayland_protocols::ext::foreign_toplevel_list::v1::server::ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1: $crate::wayland::foreign_toplevel_list::ForeignToplevelListGlobalData
511        ] => $crate::wayland::foreign_toplevel_list::ForeignToplevelListState);
512        $crate::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
513            $crate::reexports::wayland_protocols::ext::foreign_toplevel_list::v1::server::ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1: ()
514        ] => $crate::wayland::foreign_toplevel_list::ForeignToplevelListState);
515        $crate::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
516            $crate::reexports::wayland_protocols::ext::foreign_toplevel_list::v1::server::ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1: $crate::wayland::foreign_toplevel_list::ForeignToplevelHandle
517        ] => $crate::wayland::foreign_toplevel_list::ForeignToplevelListState);
518    };
519}