Repository: obhq/obliteration (language: rust)

Issue:
Implement PlatformExt::set_modal for Wayland
https://github.com/rust-windowing/winit/issues/4068 is needed before we can implement this.


Discussion / hints:

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index 344c03ff1..85645f771 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1100,7 +1100,7 @@ version = "0.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
 dependencies = [
- "libloading 0.8.5",
+ "libloading 0.7.4",
 ]
 
 [[package]]
@@ -1112,8 +1112,7 @@ checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
 [[package]]
 name = "dpi"
 version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53"
+source = "git+https://github.com/obhq/winit.git#aed3fe997b69f5fbc12dc34a0cd7e3431c70c5be"
 
 [[package]]
 name = "drm"
@@ -4697,7 +4696,7 @@ version = "0.1.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
 dependencies = [
- "windows-sys 0.59.0",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
@@ -4995,9 +4994,8 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
 
 [[package]]
 name = "winit"
-version = "0.30.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0be9e76a1f1077e04a411f0b989cbd3c93339e1771cb41e71ac4aee95bfd2c67"
+version = "0.30.8"
+source = "git+https://github.com/obhq/winit.git#aed3fe997b69f5fbc12dc34a0cd7e3431c70c5be"
 dependencies = [
  "ahash",
  "android-activity",
diff --git a/Cargo.toml b/Cargo.toml
index 0fab6d505..7df812033 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -18,3 +18,6 @@ panic = "abort"
 [profile.release]
 panic = "abort"
 lto = true
+
+[patch.crates-io]
+winit = { git = "https://github.com/obhq/winit.git" }
diff --git a/gui/src/rt/window.rs b/gui/src/rt/window.rs
index 01c510602..61d3f91bb 100644
--- a/gui/src/rt/window.rs
+++ b/gui/src/rt/window.rs
@@ -36,4 +36,6 @@ pub trait WindowHandler {
 pub trait WinitWindow {
     fn id(&self) -> WindowId;
     fn handle(&self) -> impl HasWindowHandle + '_;
+    #[cfg(target_os = "linux")]
+    fn xdg_toplevel(&self) -> *mut std::ffi::c_void;
 }
diff --git a/gui/src/ui/backend/wayland.rs b/gui/src/ui/backend/wayland.rs
index dfc1508a3..15b3691af 100644
--- a/gui/src/ui/backend/wayland.rs
+++ b/gui/src/ui/backend/wayland.rs
@@ -9,6 +9,7 @@ use wayland_backend::sys::client::Backend;
 use wayland_client::globals::{registry_queue_init, GlobalListContents};
 use wayland_client::protocol::wl_registry::WlRegistry;
 use wayland_client::{Connection, Dispatch, EventQueue, Proxy, QueueHandle};
+use wayland_protocols::xdg::dialog::v1::client::xdg_dialog_v1::XdgDialogV1;
 use wayland_protocols::xdg::dialog::v1::client::xdg_wm_dialog_v1::XdgWmDialogV1;
 use wayland_protocols::xdg::foreign::zv2::client::zxdg_exported_v2::ZxdgExportedV2;
 use wayland_protocols::xdg::foreign::zv2::client::zxdg_exporter_v2::ZxdgExporterV2;
@@ -107,6 +108,10 @@ impl WaylandState {
     pub fn xdg_exporter(&self) -> &ZxdgExporterV2 {
         &self.xdg_exporter
     }
+
+    pub fn xdg_dialog(&self) -> &XdgWmDialogV1 {
+        &self.xdg_dialog
+    }
 }
 
 impl Drop for WaylandState {
@@ -159,6 +164,18 @@ impl Dispatch<XdgWmDialogV1, ()> for WaylandState {
     }
 }
 
+impl Dispatch<XdgDialogV1, ()> for WaylandState {
+    fn event(
+        _: &mut Self,
+        _: &XdgDialogV1,
+        _: <XdgDialogV1 as Proxy>::Event,
+        _: &(),
+        _: &Connection,
+        _: &QueueHandle<Self>,
+    ) {
+    }
+}
+
 impl Dispatch<ZxdgExporterV2, ()> for WaylandState {
     fn event(
         _: &mut Self,
diff --git a/gui/src/ui/linux/mod.rs b/gui/src/ui/linux/mod.rs
index ba5b61211..f3e9e7fb9 100644
--- a/gui/src/ui/linux/mod.rs
+++ b/gui/src/ui/linux/mod.rs
@@ -1,14 +1,21 @@
 pub use self::dialogs::*;
 
-use super::{Modal, PlatformExt, SlintBackend};
+use self::modal::Modal;
+use super::{PlatformExt, SlintBackend};
 use crate::rt::{global, WinitWindow};
 use raw_window_handle::{HasWindowHandle, RawWindowHandle};
 use thiserror::Error;
 
 mod dialogs;
+mod modal;
 mod wayland;
 
 impl<T: WinitWindow> PlatformExt for T {
+    type Modal<'a, P>
+        = Modal<'a, Self, P>
+    where
+        P: WinitWindow + 'a;
+
     fn set_center(&self) -> Result<(), PlatformError> {
         let win = self.handle();
         let win = win.window_handle().unwrap();
@@ -29,19 +36,25 @@ impl<T: WinitWindow> PlatformExt for T {
         P: WinitWindow,
         Self: Sized,
     {
-        let win = self.handle();
         let back = global::<SlintBackend>().unwrap();
-
-        if let Some(v) = back.wayland() {
-            self::wayland::set_modal(v, win, parent.handle())?;
+        let wayland = if let Some(v) = back.wayland() {
+            // SAFETY: The Modal struct we construct below force the parent to outlive the modal
+            // window.
+            unsafe { self::wayland::set_modal(v, &self, parent).map(Some)? }
         } else {
             todo!()
-        }
+        };
 
-        Ok(Modal::new(self, parent))
+        Ok(Modal::new(self, parent, wayland))
     }
 }
 
 /// Linux-specific error for [`PlatformExt`].
 #[derive(Debug, Error)]
-pub enum PlatformError {}
+pub enum PlatformError {
+    #[error("couldn't create xdg_dialog_v1")]
+    CreateXdgDialogV1(#[source] wayland_client::DispatchError),
+
+    #[error("couldn't set window modal")]
+    SetModal(#[source] wayland_client::DispatchError),
+}
diff --git a/gui/src/ui/linux/modal.rs b/gui/src/ui/linux/modal.rs
new file mode 100644
index 000000000..8f1982da4
--- /dev/null
+++ b/gui/src/ui/linux/modal.rs
@@ -0,0 +1,39 @@
+use crate::rt::{block, Blocker, WinitWindow};
+use std::ops::Deref;
+use wayland_protocols::xdg::dialog::v1::client::xdg_dialog_v1::XdgDialogV1;
+
+/// Encapsulates a modal window and its parent.
+///
+/// This struct forces the modal window to be dropped before its parent.
+pub struct Modal<'a, W, P: WinitWindow> {
+    window: W,
+    wayland: Option<XdgDialogV1>,
+    #[allow(dead_code)]
+    blocker: Blocker<'a, P>,
+}
+
+impl<'a, W, P: WinitWindow> Modal<'a, W, P> {
+    pub fn new(window: W, parent: &'a P, wayland: Option<XdgDialogV1>) -> Self {
+        Self {
+            window,
+            wayland,
+            blocker: block(parent),
+        }
+    }
+}
+
+impl<'a, W, P: WinitWindow> Drop for Modal<'a, W, P> {
+    fn drop(&mut self) {
+        if let Some(v) = self.wayland.take() {
+            v.destroy();
+        }
+    }
+}
+
+impl<'a, W, P: WinitWindow> Deref for Modal<'a, W, P> {
+    type Target = W;
+
+    fn deref(&self) -> &Self::Target {
+        &self.window
+    }
+}
diff --git a/gui/src/ui/linux/wayland.rs b/gui/src/ui/linux/wayland.rs
index 0d0772c20..d8ced4b8d 100644
--- a/gui/src/ui/linux/wayland.rs
+++ b/gui/src/ui/linux/wayland.rs
@@ -1,16 +1,48 @@
 use super::PlatformError;
+use crate::rt::WinitWindow;
 use crate::ui::Wayland;
-use raw_window_handle::HasWindowHandle;
-
-pub fn set_modal(
-    _: &Wayland,
-    _: impl HasWindowHandle,
-    _: impl HasWindowHandle,
-) -> Result<(), PlatformError> {
-    // TODO: We need xdg_toplevel from the target window to use xdg_wm_dialog_v1::get_xdg_dialog.
-    // AFAIK the only way to get it is using xdg_surface::get_toplevel. The problem is
-    // xdg_wm_base::get_xdg_surface that return xdg_surface can be called only once per wl_surface
-    // and this call already done by winit. So we need winit to expose either xdg_surface or
-    // xdg_toplevel in order for us to implement this.
-    Ok(())
+use wayland_backend::sys::client::ObjectId;
+use wayland_client::Proxy;
+use wayland_protocols::xdg::dialog::v1::client::xdg_dialog_v1::XdgDialogV1;
+use wayland_protocols::xdg::shell::client::xdg_toplevel::XdgToplevel;
+
+/// # Safety
+/// `parent` must outlive `target`.
+pub unsafe fn set_modal(
+    wayland: &Wayland,
+    target: &impl WinitWindow,
+    parent: &impl WinitWindow,
+) -> Result<XdgDialogV1, PlatformError> {
+    // Get xdg_toplevel for parent.
+    let mut queue = wayland.queue().borrow_mut();
+    let mut state = wayland.state().borrow_mut();
+    let qh = queue.handle();
+    let parent = get_xdg_toplevel(wayland, parent);
+
+    // Get xdg_dialog_v1.
+    let target = get_xdg_toplevel(wayland, target);
+    let dialog = state.xdg_dialog().get_xdg_dialog(&target, &qh, ());
+
+    queue
+        .roundtrip(&mut state)
+        .map_err(PlatformError::CreateXdgDialogV1)?;
+
+    // Set modal.
+    target.set_parent(Some(&parent));
+    dialog.set_modal();
+
+    queue
+        .roundtrip(&mut state)
+        .map_err(PlatformError::SetModal)?;
+
+    Ok(dialog)
+}
+
+/// # Safety
+/// `win` must outlive the returned [`XdgToplevel`].
+unsafe fn get_xdg_toplevel(wayland: &Wayland, win: &impl WinitWindow) -> XdgToplevel {
+    let obj = win.xdg_toplevel();
+    let obj = ObjectId::from_ptr(XdgToplevel::interface(), obj.cast()).unwrap();
+
+    XdgToplevel::from_id(wayland.connection(), obj).unwrap()
 }
diff --git a/gui/src/ui/macos/mod.rs b/gui/src/ui/macos/mod.rs
index 9b48788e8..56a63f035 100644
--- a/gui/src/ui/macos/mod.rs
+++ b/gui/src/ui/macos/mod.rs
@@ -1,7 +1,8 @@
 pub use self::dialogs::*;
 
+use self::modal::Modal;
 use self::view::with_window;
-use super::{Modal, PlatformExt};
+use super::PlatformExt;
 use crate::rt::WinitWindow;
 use block::ConcreteBlock;
 use objc::{msg_send, sel, sel_impl};
@@ -10,9 +11,15 @@ use std::ops::Deref;
 use thiserror::Error;
 
 mod dialogs;
+mod modal;
 mod view;
 
 impl<T: WinitWindow> PlatformExt for T {
+    type Modal<'a, P>
+        = Modal<'a, Self, P>
+    where
+        P: WinitWindow + 'a;
+
     fn set_center(&self) -> Result<(), PlatformError> {
         with_window::<()>(self.handle(), |win| unsafe { msg_send![win, center] });
 
diff --git a/gui/src/ui/modal.rs b/gui/src/ui/macos/modal.rs
similarity index 100%
rename from gui/src/ui/modal.rs
rename to gui/src/ui/macos/modal.rs
diff --git a/gui/src/ui/mod.rs b/gui/src/ui/mod.rs
index f09414a16..fdc0890f4 100644
--- a/gui/src/ui/mod.rs
+++ b/gui/src/ui/mod.rs
@@ -1,5 +1,4 @@
 pub use self::backend::*;
-pub use self::modal::*;
 pub use self::os::*;
 pub use self::profile::*;
 
@@ -8,10 +7,10 @@ use i_slint_core::window::WindowInner;
 use raw_window_handle::HasWindowHandle;
 use slint::{ComponentHandle, SharedString, Weak};
 use std::future::Future;
+use std::ops::Deref;
 use winit::window::WindowId;
 
 mod backend;
-mod modal;
 #[cfg_attr(target_os = "linux", path = "linux/mod.rs")]
 #[cfg_attr(target_os = "macos", path = "macos/mod.rs")]
 #[cfg_attr(target_os = "windows", path = "windows/mod.rs")]
@@ -63,16 +62,29 @@ impl<T: ComponentHandle> WinitWindow for T {
     fn handle(&self) -> impl HasWindowHandle + '_ {
         self.window().window_handle()
     }
+
+    #[cfg(target_os = "linux")]
+    fn xdg_toplevel(&self) -> *mut std::ffi::c_void {
+        use winit::platform::wayland::WindowExtWayland;
+
+        let win = WindowInner::from_pub(self.window()).window_adapter();
+
+        Window::from_adapter(win.as_ref()).winit().xdg_toplevel()
+    }
 }
 
 /// Provides platform-specific methods to operate on [`WinitWindow`].
 pub trait PlatformExt: WinitWindow {
+    type Modal<'a, P>: Deref<Target = Self>
+    where
+        P: WinitWindow + 'a;
+
     /// Center window on the screen.
     ///
     /// For [`slint::Window`] this need to call after [`slint::Window::show()`] otherwise it won't
     /// work on macOS.
     fn set_center(&self) -> Result<(), PlatformError>;
-    fn set_modal<P>(self, parent: &P) -> Result<Modal<Self, P>, PlatformError>
+    fn set_modal<P>(self, parent: &P) -> Result<Self::Modal<'_, P>, PlatformError>
     where
         P: WinitWindow,
         Self: Sized;
diff --git a/gui/src/ui/windows/mod.rs b/gui/src/ui/windows/mod.rs
index 9ab228347..58be122e9 100644
--- a/gui/src/ui/windows/mod.rs
+++ b/gui/src/ui/windows/mod.rs
@@ -1,9 +1,11 @@
 pub use self::dialogs::*;
 
-use super::{Modal, PlatformExt};
+use self::modal::Modal;
+use super::PlatformExt;
 use crate::rt::WinitWindow;
 use raw_window_handle::{HasWindowHandle, RawWindowHandle};
 use std::io::Error;
+use std::mem::zeroed;
 use thiserror::Error;
 use windows_sys::Win32::Foundation::HWND;
 use windows_sys::Win32::UI::WindowsAndMessaging::{
@@ -12,8 +14,14 @@ use windows_sys::Win32::UI::WindowsAndMessaging::{
 };
 
 mod dialogs;
+mod modal;
 
 impl<T: WinitWindow> PlatformExt for T {
+    type Modal<'a, P>
+        = Modal<'a, Self, P>
+    where
+        P: WinitWindow + 'a;
+
     fn set_center(&self) -> Result<(), PlatformError> {
         // Get HWND.
         let win = self.handle();
@@ -22,16 +30,16 @@ impl<T: WinitWindow> PlatformExt for T {
             unreachable!();
         };
 
-        unsafe {
-            let hwnd = win.hwnd.get() as HWND;
-            let mut rect = std::mem::zeroed();
-
-            let ret = GetWindowRect(hwnd, &mut rect);
+        // Get window rectangle.
+        let win = win.hwnd.get() as HWND;
+        let mut rect = unsafe { zeroed() };
+        let ret = unsafe { GetWindowRect(win, &mut rect) };
 
-            if ret == 0 {
-                return Err(PlatformError::GetWindowRect(Error::last_os_error()));
-            }
+        if ret == 0 {
+            return Err(PlatformError::GetWindowRect(Error::last_os_error()));
+        }
 
+        unsafe {
             let win_width = rect.right - rect.left;
             let win_height = rect.bottom - rect.top;
 
@@ -48,7 +56,7 @@ impl<T: WinitWindow> PlatformExt for T {
             }
 
             let ret = SetWindowPos(
-                hwnd,
+                win,
                 HWND_TOP,
                 (screen_width - win_width) / 2,
                 (screen_height - win_height) / 2,
diff --git a/gui/src/ui/windows/modal.rs b/gui/src/ui/windows/modal.rs
new file mode 100644
index 000000000..c6d43a6ab
--- /dev/null
+++ b/gui/src/ui/windows/modal.rs
@@ -0,0 +1,28 @@
+use crate::rt::{block, Blocker, WinitWindow};
+use std::ops::Deref;
+
+/// Encapsulates a modal window and its parent.
+///
+/// This struct forces the modal window to be dropped before its parent.
+pub struct Modal<'a, W, P: WinitWindow> {
+    window: W,
+    #[allow(dead_code)]
+    blocker: Blocker<'a, P>,
+}
+
+impl<'a, W, P: WinitWindow> Modal<'a, W, P> {
+    pub(super) fn new(window: W, parent: &'a P) -> Self {
+        Self {
+            window,
+            blocker: block(parent),
+        }
+    }
+}
+
+impl<'a, W, P: WinitWindow> Deref for Modal<'a, W, P> {
+    type Target = W;
+
+    fn deref(&self) -> &Self::Target {
+        &self.window
+    }
+}


Test patch: