ashpd/window_identifier/
mod.rs

1use std::{fmt, str::FromStr};
2
3#[cfg(all(feature = "raw_handle", feature = "gtk4"))]
4use raw_window_handle::{
5    DisplayHandle, HandleError, HasDisplayHandle, HasWindowHandle, WindowHandle,
6};
7#[cfg(feature = "raw_handle")]
8use raw_window_handle::{RawDisplayHandle, RawWindowHandle};
9use serde::{ser::Serializer, Deserialize, Serialize};
10use zbus::zvariant::Type;
11/// Most portals interact with the user by showing dialogs.
12///
13/// These dialogs should generally be placed on top of the application window
14/// that triggered them. To arrange this, the compositor needs to know about the
15/// application window. Many portal requests expect a [`WindowIdentifier`] for
16/// this reason.
17///
18/// Under X11, the [`WindowIdentifier`] should have the form `x11:XID`, where
19/// XID is the XID of the application window in hexadecimal. Under Wayland, it
20/// should have the form `wayland:HANDLE`, where HANDLE is a surface handle
21/// obtained with the [xdg-foreign](https://gitlab.freedesktop.org/wayland/wayland-protocols/-/blob/main/unstable/xdg-foreign/xdg-foreign-unstable-v2.xml) protocol.
22///
23/// See also [Parent window identifiers](https://flatpak.github.io/xdg-desktop-portal/docs/window-identifiers.html).
24///
25/// # Usage
26///
27/// ## From an X11 XID
28///
29/// ```rust,ignore
30/// let identifier = WindowIdentifier::from_xid(212321);
31///
32/// /// Open some portals
33/// ```
34///
35/// ## From a Wayland Surface
36///
37/// The `wayland` feature must be enabled. The exported surface handle will be
38/// unexported on `Drop`.
39///
40/// ```text
41/// // let wl_surface = some_surface;
42/// // let identifier = WindowIdentifier::from_wayland(wl_surface).await;
43///
44/// /// Open some portals
45/// ```
46///
47/// Or using a raw `wl_surface` pointer
48///
49/// ```text
50/// // let wl_surface_ptr = some_surface;
51/// // let wl_display_ptr = corresponding_display;
52/// // let identifier = WindowIdentifier::from_wayland_raw(wl_surface_ptr, wl_display_ptr).await;
53///
54/// /// Open some portals
55/// ```
56///
57/// ## With GTK 4
58///
59/// The feature `gtk4` must be enabled. You can get a
60/// [`WindowIdentifier`] from a [`IsA<gtk4::Native>`](https://gtk-rs.org/gtk4-rs/stable/latest/docs/gtk4/struct.Native.html) using `WindowIdentifier::from_native`
61///
62/// ```rust, ignore
63/// let widget = gtk4::Button::new();
64///
65/// let ctx = glib::MainContext::default();
66/// ctx.spawn_async(async move {
67///     let identifier = WindowIdentifier::from_native(&widget.native().unwrap()).await;
68///
69///     /// Open some portals
70/// });
71/// ```
72/// The constructor should return a valid identifier under both X11 and Wayland
73/// and fallback to the [`Default`] implementation otherwise.
74///
75/// ## Other Toolkits
76///
77/// If you have access to `RawWindowHandle` you can convert it to a
78/// [`WindowIdentifier`] with
79///
80/// ```rust, ignore
81/// let handle = RawWindowHandle::Xlib(XlibHandle::empty());
82/// let identifier = WindowIdentifier::from_raw_handle(handle, None);
83///
84/// /// Open some portals
85/// ```
86#[derive(Type)]
87#[zvariant(signature = "s")]
88#[doc(alias = "XdpParent")]
89#[non_exhaustive]
90pub enum WindowIdentifier {
91    /// Gtk 4 Window Identifier
92    #[cfg(any(feature = "gtk4_wayland", feature = "gtk4_x11"))]
93    #[doc(hidden)]
94    Gtk4(Gtk4WindowIdentifier),
95    #[cfg(feature = "wayland")]
96    #[doc(hidden)]
97    Wayland(WaylandWindowIdentifier),
98    #[doc(hidden)]
99    X11(WindowIdentifierType),
100}
101
102unsafe impl Send for WindowIdentifier {}
103unsafe impl Sync for WindowIdentifier {}
104
105impl Serialize for WindowIdentifier {
106    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
107    where
108        S: Serializer,
109    {
110        serializer.serialize_str(&self.to_string())
111    }
112}
113
114impl std::fmt::Display for WindowIdentifier {
115    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116        match self {
117            #[cfg(any(feature = "gtk4_wayland", feature = "gtk4_x11"))]
118            Self::Gtk4(identifier) => f.write_str(&format!("{identifier}")),
119            #[cfg(feature = "wayland")]
120            Self::Wayland(identifier) => f.write_str(&format!("{identifier}")),
121            Self::X11(identifier) => f.write_str(&format!("{identifier}")),
122        }
123    }
124}
125
126impl std::fmt::Debug for WindowIdentifier {
127    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
128        f.debug_tuple("WindowIdentifier")
129            .field(&format!("{self}"))
130            .finish()
131    }
132}
133
134impl WindowIdentifier {
135    #[cfg(any(feature = "gtk4_wayland", feature = "gtk4_x11"))]
136    #[cfg_attr(docsrs, doc(cfg(any(feature = "gtk4_wayland", feature = "gtk4_x11"))))]
137    /// Creates a [`WindowIdentifier`] from a [`gtk4::Native`](https://docs.gtk.org/gtk4/class.Native.html).
138    ///
139    /// The constructor returns a valid handle under both Wayland & x11.
140    ///
141    /// **Note** the function has to be async as the Wayland handle retrieval
142    /// API is async as well.
143    #[doc(alias = "xdp_parent_new_gtk")]
144    pub async fn from_native(native: &impl ::gtk4::prelude::IsA<::gtk4::Native>) -> Option<Self> {
145        Gtk4WindowIdentifier::new(native).await.map(Self::Gtk4)
146    }
147
148    #[cfg(feature = "raw_handle")]
149    #[cfg_attr(docsrs, doc(cfg(feature = "raw_handle")))]
150    /// Create an instance of [`WindowIdentifier`] from a
151    /// [`RawWindowHandle`](raw_window_handle::RawWindowHandle).
152    ///
153    /// The constructor returns a valid handle under both Wayland & X11.
154    ///
155    /// This method is only async and requires a `RawDisplayHandle` only for
156    /// Wayland handles.
157    pub async fn from_raw_handle(
158        window_handle: &RawWindowHandle,
159        display_handle: Option<&RawDisplayHandle>,
160    ) -> Option<Self> {
161        use raw_window_handle::RawWindowHandle::{Xcb, Xlib};
162        #[cfg(feature = "wayland")]
163        use raw_window_handle::{
164            RawDisplayHandle::Wayland as DisplayHandle, RawWindowHandle::Wayland,
165        };
166        match (window_handle, display_handle) {
167            #[cfg(feature = "wayland")]
168            (Wayland(wl_handle), Some(DisplayHandle(wl_display))) => unsafe {
169                Self::from_wayland_raw(wl_handle.surface.as_ptr(), wl_display.display.as_ptr())
170                    .await
171            },
172            (Xlib(x_handle), _) => Some(Self::from_xid(x_handle.window)),
173            (Xcb(x_handle), _) => Some(Self::from_xid(x_handle.window.get().into())),
174            _ => None,
175        }
176    }
177
178    /// Create an instance of [`WindowIdentifier`] from an X11 window's XID.
179    pub fn from_xid(xid: std::os::raw::c_ulong) -> Self {
180        Self::X11(WindowIdentifierType::X11(xid))
181    }
182
183    #[cfg(feature = "wayland")]
184    #[cfg_attr(docsrs, doc(cfg(feature = "wayland")))]
185    /// Create an instance of [`WindowIdentifier`] from a Wayland surface.
186    ///
187    /// # Safety
188    ///
189    /// Both surface and display pointers have to be valid . You must
190    /// ensure the `display_ptr` lives longer than the returned
191    /// `WindowIdentifier`.
192    pub async unsafe fn from_wayland_raw(
193        surface_ptr: *mut std::ffi::c_void,
194        display_ptr: *mut std::ffi::c_void,
195    ) -> Option<Self> {
196        WaylandWindowIdentifier::from_raw(surface_ptr, display_ptr)
197            .await
198            .map(Self::Wayland)
199    }
200
201    #[cfg(feature = "wayland")]
202    #[cfg_attr(docsrs, doc(cfg(feature = "wayland")))]
203    /// Create an instance of [`WindowIdentifier`] from a Wayland surface.
204    pub async fn from_wayland(
205        surface: &wayland_client::protocol::wl_surface::WlSurface,
206    ) -> Option<Self> {
207        WaylandWindowIdentifier::new(surface)
208            .await
209            .map(Self::Wayland)
210    }
211}
212
213#[cfg(all(feature = "raw_handle", feature = "gtk4"))]
214impl HasDisplayHandle for WindowIdentifier {
215    /// Convert a [`WindowIdentifier`] to
216    /// [`RawDisplayHandle`](raw_window_handle::RawDisplayHandle`).
217    ///
218    /// # Panics
219    ///
220    /// If you attempt to convert a [`WindowIdentifier`] created from a
221    /// [`RawDisplayHandle`](raw_window_handle::RawDisplayHandle`) instead of
222    /// the gtk4 constructors.
223    fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> {
224        match self {
225            #[cfg(feature = "gtk4")]
226            Self::Gtk4(identifier) => Ok(identifier.as_raw_display_handle()),
227            _ => unreachable!(),
228        }
229    }
230}
231
232#[cfg(all(feature = "raw_handle", feature = "gtk4"))]
233impl HasWindowHandle for WindowIdentifier {
234    /// Convert a [`WindowIdentifier`] to
235    /// [`RawWindowHandle`](raw_window_handle::RawWindowHandle`).
236    ///
237    /// # Panics
238    ///
239    /// If you attempt to convert a [`WindowIdentifier`] created from a
240    /// [`RawWindowHandle`](raw_window_handle::RawWindowHandle`) instead of
241    /// the gtk4 constructors.
242    fn window_handle(&self) -> Result<WindowHandle<'_>, HandleError> {
243        match self {
244            #[cfg(feature = "gtk4")]
245            Self::Gtk4(identifier) => Ok(identifier.as_raw_window_handle()),
246            _ => unreachable!(),
247        }
248    }
249}
250
251/// Supported WindowIdentifier kinds
252#[derive(Debug, Clone, PartialEq, Eq, Type)]
253#[zvariant(signature = "s")]
254pub enum WindowIdentifierType {
255    /// X11.
256    X11(std::os::raw::c_ulong),
257    #[allow(dead_code)]
258    /// Wayland.
259    Wayland(String),
260}
261
262impl fmt::Display for WindowIdentifierType {
263    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
264        match self {
265            Self::X11(xid) => f.write_str(&format!("x11:0x{xid:x}")),
266            Self::Wayland(handle) => f.write_str(&format!("wayland:{handle}")),
267        }
268    }
269}
270
271impl FromStr for WindowIdentifierType {
272    type Err = PortalError;
273    fn from_str(s: &str) -> Result<Self, Self::Err> {
274        let (kind, handle) = s
275            .split_once(':')
276            .ok_or_else(|| PortalError::InvalidArgument("Invalid Window Identifier".to_owned()))?;
277        match kind {
278            "x11" => {
279                let handle = handle.trim_start_matches("0x");
280                Ok(Self::X11(
281                    std::os::raw::c_ulong::from_str_radix(handle, 16)
282                        .map_err(|_| PortalError::InvalidArgument(format!("Wrong XID {handle}")))?,
283                ))
284            }
285            "wayland" => Ok(Self::Wayland(handle.to_owned())),
286            t => Err(PortalError::InvalidArgument(format!(
287                "Invalid Window Identifier type {t}",
288            ))),
289        }
290    }
291}
292
293impl<'de> Deserialize<'de> for WindowIdentifierType {
294    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
295    where
296        D: serde::Deserializer<'de>,
297    {
298        let handle = String::deserialize(deserializer)?;
299        Self::from_str(&handle)
300            .map_err(|e| serde::de::Error::custom(format!("Invalid Window identifier {e}")))
301    }
302}
303
304impl Serialize for WindowIdentifierType {
305    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
306    where
307        S: Serializer,
308    {
309        self.to_string().serialize(serializer)
310    }
311}
312
313#[cfg(any(feature = "gtk4_wayland", feature = "gtk4_x11"))]
314mod gtk4;
315
316#[cfg(any(feature = "gtk4_wayland", feature = "gtk4_x11"))]
317pub use self::gtk4::Gtk4WindowIdentifier;
318use crate::PortalError;
319
320#[cfg(feature = "wayland")]
321mod wayland;
322
323#[cfg(feature = "wayland")]
324pub use self::wayland::WaylandWindowIdentifier;
325
326#[cfg(test)]
327mod tests {
328    use std::str::FromStr;
329
330    use super::WindowIdentifier;
331    use crate::window_identifier::WindowIdentifierType;
332
333    #[test]
334    fn test_serialize() {
335        let x11 = WindowIdentifier::from_xid(1024);
336        assert_eq!(x11.to_string(), "x11:0x400");
337
338        assert_eq!(
339            WindowIdentifierType::from_str("x11:0x11432").unwrap(),
340            WindowIdentifierType::X11(70706)
341        );
342
343        assert_eq!(
344            WindowIdentifierType::from_str("wayland:Somerandomchars").unwrap(),
345            WindowIdentifierType::Wayland("Somerandomchars".to_owned())
346        );
347        assert!(WindowIdentifierType::from_str("some_handle").is_err());
348        assert!(WindowIdentifierType::from_str("some_type:some_handle").is_err());
349    }
350}