Skip to main content

ashpd/window_identifier/
mod.rs

1use std::{fmt, str::FromStr};
2
3#[cfg(all(
4    any(feature = "gtk4_wayland", feature = "gtk4_x11"),
5    feature = "backend"
6))]
7use ::gtk4 as gtk;
8#[cfg(all(feature = "raw_handle", feature = "gtk4"))]
9use raw_window_handle::{
10    DisplayHandle, HandleError, HasDisplayHandle, HasWindowHandle, WindowHandle,
11};
12#[cfg(feature = "raw_handle")]
13use raw_window_handle::{RawDisplayHandle, RawWindowHandle};
14use serde::{Deserialize, Serialize, ser::Serializer};
15use zbus::zvariant::Type;
16/// Most portals interact with the user by showing dialogs.
17///
18/// These dialogs should generally be placed on top of the application window
19/// that triggered them. To arrange this, the compositor needs to know about the
20/// application window. Many portal requests expect a [`WindowIdentifier`] for
21/// this reason.
22///
23/// Under X11, the [`WindowIdentifier`] should have the form `x11:XID`, where
24/// XID is the XID of the application window in hexadecimal. Under Wayland, it
25/// should have the form `wayland:HANDLE`, where HANDLE is a surface handle
26/// obtained with the [xdg-foreign](https://gitlab.freedesktop.org/wayland/wayland-protocols/-/blob/main/unstable/xdg-foreign/xdg-foreign-unstable-v2.xml) protocol.
27///
28/// See also [Parent window identifiers](https://flatpak.github.io/xdg-desktop-portal/docs/window-identifiers.html).
29///
30/// # Usage
31///
32/// ## From an X11 XID
33///
34/// ```rust,ignore
35/// let identifier = WindowIdentifier::from_xid(212321);
36///
37/// /// Open some portals
38/// ```
39///
40/// ## From a Wayland Surface
41///
42/// The `wayland` feature must be enabled. The exported surface handle will be
43/// unexported on `Drop`.
44///
45/// ```text
46/// // let wl_surface = some_surface;
47/// // let identifier = WindowIdentifier::from_wayland(wl_surface).await;
48///
49/// /// Open some portals
50/// ```
51///
52/// Or using a raw `wl_surface` pointer
53///
54/// ```text
55/// // let wl_surface_ptr = some_surface;
56/// // let wl_display_ptr = corresponding_display;
57/// // let identifier = WindowIdentifier::from_wayland_raw(wl_surface_ptr, wl_display_ptr).await;
58///
59/// /// Open some portals
60/// ```
61///
62/// ## With GTK 4
63///
64/// The feature `gtk4` must be enabled. You can get a
65/// [`WindowIdentifier`] from a [`IsA<gtk4::Native>`](https://gtk-rs.org/gtk4-rs/stable/latest/docs/gtk4/struct.Native.html) using `WindowIdentifier::from_native`
66///
67/// ```rust, ignore
68/// let widget = gtk4::Button::new();
69///
70/// let ctx = glib::MainContext::default();
71/// ctx.spawn_async(async move {
72///     let identifier = WindowIdentifier::from_native(&widget.native().unwrap()).await;
73///
74///     /// Open some portals
75/// });
76/// ```
77/// The constructor should return a valid identifier under both X11 and Wayland
78/// and fallback to the [`Default`] implementation otherwise.
79///
80/// ## Other Toolkits
81///
82/// If you have access to `RawWindowHandle` you can convert it to a
83/// [`WindowIdentifier`] with
84///
85/// ```rust, ignore
86/// let handle = RawWindowHandle::Xlib(XlibHandle::empty());
87/// let identifier = WindowIdentifier::from_raw_handle(handle, None);
88///
89/// /// Open some portals
90/// ```
91#[derive(Type)]
92#[zvariant(signature = "s")]
93#[doc(alias = "XdpParent")]
94#[non_exhaustive]
95pub enum WindowIdentifier {
96    /// Gtk 4 Window Identifier
97    #[cfg(any(feature = "gtk4_wayland", feature = "gtk4_x11"))]
98    #[doc(hidden)]
99    Gtk4(Gtk4WindowIdentifier),
100    #[cfg(feature = "wayland")]
101    #[doc(hidden)]
102    Wayland(WaylandWindowIdentifier),
103    #[doc(hidden)]
104    Raw(WindowIdentifierType),
105}
106
107impl zbus::zvariant::NoneValue for WindowIdentifier {
108    type NoneType = String;
109
110    fn null_value() -> Self::NoneType {
111        String::default()
112    }
113}
114
115impl zbus::zvariant::NoneValue for &WindowIdentifier {
116    type NoneType = String;
117
118    fn null_value() -> Self::NoneType {
119        String::default()
120    }
121}
122unsafe impl Send for WindowIdentifier {}
123unsafe impl Sync for WindowIdentifier {}
124
125impl Serialize for WindowIdentifier {
126    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
127    where
128        S: Serializer,
129    {
130        serializer.serialize_str(&self.to_string())
131    }
132}
133
134impl std::fmt::Display for WindowIdentifier {
135    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
136        match self {
137            #[cfg(any(feature = "gtk4_wayland", feature = "gtk4_x11"))]
138            Self::Gtk4(identifier) => identifier.fmt(f),
139            #[cfg(feature = "wayland")]
140            Self::Wayland(identifier) => identifier.fmt(f),
141            Self::Raw(identifier) => identifier.fmt(f),
142        }
143    }
144}
145
146impl std::fmt::Debug for WindowIdentifier {
147    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148        f.debug_tuple("WindowIdentifier")
149            .field(&format_args!("{self}"))
150            .finish()
151    }
152}
153
154impl WindowIdentifier {
155    #[cfg(any(feature = "gtk4_wayland", feature = "gtk4_x11"))]
156    #[cfg_attr(docsrs, doc(cfg(any(feature = "gtk4_wayland", feature = "gtk4_x11"))))]
157    /// Creates a [`WindowIdentifier`] from a [`gtk4::Native`](https://docs.gtk.org/gtk4/class.Native.html).
158    ///
159    /// The constructor returns a valid handle under both Wayland & x11.
160    ///
161    /// **Note** the function has to be async as the Wayland handle retrieval
162    /// API is async as well.
163    #[doc(alias = "xdp_parent_new_gtk")]
164    pub async fn from_native(native: &impl ::gtk4::prelude::IsA<::gtk4::Native>) -> Option<Self> {
165        Gtk4WindowIdentifier::new(native).await.map(Self::Gtk4)
166    }
167
168    #[cfg(feature = "raw_handle")]
169    #[cfg_attr(docsrs, doc(cfg(feature = "raw_handle")))]
170    /// Create an instance of [`WindowIdentifier`] from a
171    /// [`RawWindowHandle`](raw_window_handle::RawWindowHandle).
172    ///
173    /// The constructor returns a valid handle under both Wayland & X11.
174    ///
175    /// This method is only async and requires a `RawDisplayHandle` only for
176    /// Wayland handles.
177    pub async fn from_raw_handle(
178        window_handle: &RawWindowHandle,
179        display_handle: Option<&RawDisplayHandle>,
180    ) -> Option<Self> {
181        use raw_window_handle::RawWindowHandle::{Xcb, Xlib};
182        #[cfg(feature = "wayland")]
183        use raw_window_handle::{
184            RawDisplayHandle::Wayland as DisplayHandle, RawWindowHandle::Wayland,
185        };
186        match (window_handle, display_handle) {
187            #[cfg(feature = "wayland")]
188            (Wayland(wl_handle), Some(DisplayHandle(wl_display))) => unsafe {
189                Self::from_wayland_raw(wl_handle.surface.as_ptr(), wl_display.display.as_ptr())
190                    .await
191            },
192            (Xlib(x_handle), _) => Some(Self::from_xid(x_handle.window)),
193            (Xcb(x_handle), _) => Some(Self::from_xid(x_handle.window.get().into())),
194            _ => None,
195        }
196    }
197
198    /// Create an instance of [`WindowIdentifier`] from an X11 window's XID.
199    pub fn from_xid(xid: std::os::raw::c_ulong) -> Self {
200        Self::Raw(WindowIdentifierType::X11(xid))
201    }
202
203    /// Create an instance of [`WindowIdentifier`] from a Wayland exported
204    /// handle.
205    ///
206    /// The developer takes responsibility of keeping the handle around till the
207    /// interaction is over before un-exporting the handle.
208    pub fn from_xdg_foreign_exported(handle: String) -> Self {
209        Self::Raw(WindowIdentifierType::Wayland(handle))
210    }
211
212    #[cfg(feature = "wayland")]
213    #[cfg_attr(docsrs, doc(cfg(feature = "wayland")))]
214    /// Create an instance of [`WindowIdentifier`] from a Wayland surface.
215    ///
216    /// # Safety
217    ///
218    /// Both surface and display pointers have to be valid . You must
219    /// ensure the `display_ptr` lives longer than the returned
220    /// `WindowIdentifier`.
221    pub async unsafe fn from_wayland_raw(
222        surface_ptr: *mut std::ffi::c_void,
223        display_ptr: *mut std::ffi::c_void,
224    ) -> Option<Self> {
225        unsafe { WaylandWindowIdentifier::from_raw(surface_ptr, display_ptr).await }
226            .map(Self::Wayland)
227    }
228
229    #[cfg(feature = "wayland")]
230    #[cfg_attr(docsrs, doc(cfg(feature = "wayland")))]
231    /// Create an instance of [`WindowIdentifier`] from a Wayland surface.
232    pub async fn from_wayland(
233        surface: &wayland_client::protocol::wl_surface::WlSurface,
234    ) -> Option<Self> {
235        WaylandWindowIdentifier::new(surface)
236            .await
237            .map(Self::Wayland)
238    }
239}
240
241#[cfg(all(feature = "raw_handle", feature = "gtk4"))]
242impl HasDisplayHandle for WindowIdentifier {
243    /// Convert a [`WindowIdentifier`] to
244    /// [`RawDisplayHandle`](raw_window_handle::RawDisplayHandle`).
245    ///
246    /// # Panics
247    ///
248    /// If you attempt to convert a [`WindowIdentifier`] created from a
249    /// [`RawDisplayHandle`](raw_window_handle::RawDisplayHandle`) instead of
250    /// the gtk4 constructors.
251    fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> {
252        match self {
253            #[cfg(feature = "gtk4")]
254            Self::Gtk4(identifier) => Ok(identifier.as_raw_display_handle()),
255            _ => unreachable!(),
256        }
257    }
258}
259
260#[cfg(all(feature = "raw_handle", feature = "gtk4"))]
261impl HasWindowHandle for WindowIdentifier {
262    /// Convert a [`WindowIdentifier`] to
263    /// [`RawWindowHandle`](raw_window_handle::RawWindowHandle`).
264    ///
265    /// # Panics
266    ///
267    /// If you attempt to convert a [`WindowIdentifier`] created from a
268    /// [`RawWindowHandle`](raw_window_handle::RawWindowHandle`) instead of
269    /// the gtk4 constructors.
270    fn window_handle(&self) -> Result<WindowHandle<'_>, HandleError> {
271        match self {
272            #[cfg(feature = "gtk4")]
273            Self::Gtk4(identifier) => Ok(identifier.as_raw_window_handle()),
274            _ => unreachable!(),
275        }
276    }
277}
278
279/// Supported WindowIdentifier kinds
280#[derive(Debug, Clone, PartialEq, Eq, Type)]
281#[zvariant(signature = "s")]
282pub enum WindowIdentifierType {
283    /// X11.
284    X11(std::os::raw::c_ulong),
285    #[allow(dead_code)]
286    /// Wayland.
287    Wayland(String),
288}
289
290impl From<WindowIdentifierType> for WindowIdentifier {
291    fn from(value: WindowIdentifierType) -> Self {
292        Self::Raw(value)
293    }
294}
295
296impl zbus::zvariant::NoneValue for WindowIdentifierType {
297    type NoneType = String;
298
299    fn null_value() -> String {
300        String::default()
301    }
302}
303
304impl fmt::Display for WindowIdentifierType {
305    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
306        match self {
307            Self::X11(xid) => {
308                f.write_str("x11:")?;
309                write!(f, "{xid:x}")
310            }
311            Self::Wayland(handle) => {
312                f.write_str("wayland:")?;
313                f.write_str(handle)
314            }
315        }
316    }
317}
318
319impl FromStr for WindowIdentifierType {
320    type Err = PortalError;
321    fn from_str(s: &str) -> Result<Self, Self::Err> {
322        let (kind, handle) = s
323            .split_once(':')
324            .ok_or_else(|| PortalError::InvalidArgument("Invalid Window Identifier".to_owned()))?;
325        match kind {
326            "x11" => {
327                let handle = handle.trim_start_matches("0x");
328                Ok(Self::X11(
329                    std::os::raw::c_ulong::from_str_radix(handle, 16)
330                        .map_err(|_| PortalError::InvalidArgument(format!("Wrong XID {handle}")))?,
331                ))
332            }
333            "wayland" => Ok(Self::Wayland(handle.to_owned())),
334            t => Err(PortalError::InvalidArgument(format!(
335                "Invalid Window Identifier type {t}",
336            ))),
337        }
338    }
339}
340
341impl TryFrom<String> for WindowIdentifierType {
342    type Error = PortalError;
343
344    fn try_from(value: String) -> Result<Self, Self::Error> {
345        Self::from_str(&value)
346    }
347}
348
349impl<'de> Deserialize<'de> for WindowIdentifierType {
350    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
351    where
352        D: serde::Deserializer<'de>,
353    {
354        let handle = String::deserialize(deserializer)?;
355        Self::from_str(&handle)
356            .map_err(|e| serde::de::Error::custom(format!("Invalid Window identifier {e}")))
357    }
358}
359
360impl Serialize for WindowIdentifierType {
361    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
362    where
363        S: Serializer,
364    {
365        self.to_string().serialize(serializer)
366    }
367}
368
369impl WindowIdentifierType {
370    /// Sets the given window as a modal child of the window represented by
371    /// self.
372    ///
373    /// The different combinations of window types and their support is as per
374    /// follows.
375    /// - Wayland child window, Wayland parent window - supported.
376    /// - Wayland child window, X11 parent window - unsupported.
377    /// - X11 child window, Wayland parent window - unsupported.
378    /// - X11 child window, X11 parent window - supported.
379    ///
380    /// This is useful in backend implementations as the portal dialogs have to
381    /// be modal and grouped together with the application that launched the
382    /// request.
383    ///
384    /// Realizes the window if it is not realized yet.
385    ///
386    /// Returns `true` on success.
387    #[cfg(all(
388        any(feature = "gtk4_wayland", feature = "gtk4_x11"),
389        feature = "backend"
390    ))]
391    #[cfg_attr(
392        docsrs,
393        doc(cfg(all(
394            any(feature = "gtk4_wayland", feature = "gtk4_x11"),
395            feature = "backend"
396        )))
397    )]
398    pub fn set_parent_of(&self, window: &impl gtk::prelude::IsA<gtk::Window>) -> bool {
399        use gtk::prelude::*;
400
401        let window = window.as_ref();
402
403        let surface = match window.surface() {
404            Some(surface) => surface,
405            None => {
406                WidgetExt::realize(window);
407                window.surface().unwrap()
408            }
409        };
410
411        window.set_modal(true);
412
413        match self {
414            #[cfg(feature = "gtk4_x11")]
415            WindowIdentifierType::X11(xid) => {
416                use gdk4x11::{X11Display, X11Surface, x11::xlib};
417
418                let display = match WidgetExt::display(window).dynamic_cast::<X11Display>() {
419                    Ok(display) => display,
420                    Err(_) => {
421                        #[cfg(feature = "tracing")]
422                        tracing::warn!("Failed to get X11 display");
423                        return false;
424                    }
425                };
426                let surface = match surface.dynamic_cast::<X11Surface>() {
427                    Ok(surface) => surface,
428                    Err(_) => {
429                        #[cfg(feature = "tracing")]
430                        tracing::warn!("Failed to get X11 surface");
431                        return false;
432                    }
433                };
434                unsafe {
435                    // Based on GNOME's libgxdp -
436                    // https://gitlab.gnome.org/GNOME/libgxdp/-/blob/e6c11f2812cad0a43e847ec97bfc1c67bf50be52/src/gxdp-external-window-x11.c#L90-105
437                    let xdisplay = display.xdisplay();
438                    let xlib_handle = xlib::Xlib::open().unwrap();
439                    (xlib_handle.XSetTransientForHint)(xdisplay, surface.xid(), *xid);
440                    let net_wm_window_type_atom =
441                        gdk4x11::x11_get_xatom_by_name_for_display(&display, "_NET_WM_WINDOW_TYPE");
442                    let net_wm_window_type_dialog_atom = gdk4x11::x11_get_xatom_by_name_for_display(
443                        &display,
444                        "_NET_WM_WINDOW_DIALOG_TYPE",
445                    );
446                    let data: *const u8 = &(net_wm_window_type_dialog_atom as u8);
447                    (xlib_handle.XChangeProperty)(
448                        xdisplay,
449                        surface.xid(),
450                        net_wm_window_type_atom,
451                        xlib::XA_ATOM,
452                        32,
453                        xlib::PropModeReplace,
454                        data,
455                        1,
456                    );
457                    true
458                }
459            }
460            #[cfg(feature = "gtk4_wayland")]
461            WindowIdentifierType::Wayland(handle) => {
462                use gdk4wayland::WaylandToplevel;
463
464                let toplevel = match surface.dynamic_cast::<WaylandToplevel>() {
465                    Ok(toplevel) => toplevel,
466                    Err(_) => {
467                        #[cfg(feature = "tracing")]
468                        tracing::warn!("Failed to get toplevel from surface");
469                        return false;
470                    }
471                };
472                toplevel.set_transient_for_exported(handle)
473            }
474            #[cfg(not(all(feature = "gtk4_x11", feature = "gtk4_wayland")))]
475            _ => false,
476        }
477    }
478}
479
480#[cfg(any(feature = "gtk4_wayland", feature = "gtk4_x11"))]
481mod gtk4;
482
483#[cfg(any(feature = "gtk4_wayland", feature = "gtk4_x11"))]
484pub use self::gtk4::Gtk4WindowIdentifier;
485use crate::PortalError;
486
487#[cfg(feature = "wayland")]
488mod wayland;
489
490#[cfg(feature = "wayland")]
491pub use self::wayland::WaylandWindowIdentifier;
492
493#[cfg(test)]
494mod tests {
495    use std::str::FromStr;
496
497    use super::WindowIdentifier;
498    use crate::window_identifier::WindowIdentifierType;
499
500    #[test]
501    fn test_serialize() {
502        let x11 = WindowIdentifier::from_xid(1024);
503        assert_eq!(x11.to_string(), "x11:400");
504
505        assert_eq!(
506            WindowIdentifierType::from_str("x11:11432").unwrap(),
507            WindowIdentifierType::X11(70706)
508        );
509
510        // A valid x11 window identifier shouldn't be prefixed with 0x, this is kept for
511        // backwards compatibility and compatibility with backends which
512        // implicitly strip the prefix with e.g. `strtol`
513        assert_eq!(
514            WindowIdentifierType::from_str("x11:0x502a").unwrap(),
515            WindowIdentifierType::X11(20522)
516        );
517
518        assert_eq!(
519            WindowIdentifierType::from_str("wayland:Somerandomchars").unwrap(),
520            WindowIdentifierType::Wayland("Somerandomchars".to_owned())
521        );
522        assert!(WindowIdentifierType::from_str("some_handle").is_err());
523        assert!(WindowIdentifierType::from_str("some_type:some_handle").is_err());
524    }
525}