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#[derive(Type)]
92#[zvariant(signature = "s")]
93#[doc(alias = "XdpParent")]
94#[non_exhaustive]
95pub enum WindowIdentifier {
96 #[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 #[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 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 pub fn from_xid(xid: std::os::raw::c_ulong) -> Self {
200 Self::Raw(WindowIdentifierType::X11(xid))
201 }
202
203 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 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 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 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 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#[derive(Debug, Clone, PartialEq, Eq, Type)]
281#[zvariant(signature = "s")]
282pub enum WindowIdentifierType {
283 X11(std::os::raw::c_ulong),
285 #[allow(dead_code)]
286 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 #[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 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 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}