Repository: obhq/obliteration (language: rust)

Issue:
Fix soundness hole in PlatformExt::set_modal
Our current implementation does not prevent the parent from destroyed before the modal window.


Discussion / hints:

Fix patch:
diff --git a/gui/Cargo.toml b/gui/Cargo.toml
index 0d356f267..deb5f8f01 100644
--- a/gui/Cargo.toml
+++ b/gui/Cargo.toml
@@ -89,7 +89,7 @@ block = "0.1.6"
 core-foundation = "0.10.0"
 core-foundation-sys = "0.8.7"
 core-graphics-types = "0.1.3"
-i-slint-renderer-skia = { version = "=1.9.0" }
+i-slint-renderer-skia = "=1.9.0"
 metal = "0.29.0"
 objc = "0.2.7"
 
diff --git a/gui/src/graphics/metal/window.rs b/gui/src/graphics/metal/window.rs
index 6255ea094..15a38091e 100644
--- a/gui/src/graphics/metal/window.rs
+++ b/gui/src/graphics/metal/window.rs
@@ -1,11 +1,8 @@
 use super::Metal;
-use crate::rt::{Hook, RuntimeWindow};
+use crate::rt::{Hook, WindowHandler};
 use metal::{CAMetalLayer, MetalLayer};
 use objc::runtime::{Object, NO, YES};
 use objc::{msg_send, sel, sel_impl};
-use raw_window_handle::{
-    DisplayHandle, HandleError, HasDisplayHandle, HasWindowHandle, WindowHandle,
-};
 use rwh05::{HasRawWindowHandle, RawWindowHandle};
 use std::error::Error;
 use std::ptr::null_mut;
@@ -16,7 +13,7 @@ use winit::event::{DeviceId, ElementState, InnerSizeWriter, MouseButton, StartCa
 use winit::event_loop::ControlFlow;
 use winit::window::{Window, WindowId};
 
-/// Implementation of [`RuntimeWindow`] and [`Hook`] for Metal.
+/// Implementation of [`WindowHandler`] and [`Hook`] for Metal.
 pub struct MetalWindow {
     view: *mut Object,
     layer: MetalLayer,
@@ -55,8 +52,8 @@ impl Drop for MetalWindow {
     }
 }
 
-impl RuntimeWindow for MetalWindow {
-    fn id(&self) -> WindowId {
+impl WindowHandler for MetalWindow {
+    fn window_id(&self) -> WindowId {
         self.window.id()
     }
 
@@ -106,18 +103,6 @@ impl RuntimeWindow for MetalWindow {
     }
 }
 
-impl HasDisplayHandle for MetalWindow {
-    fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> {
-        self.window.display_handle()
-    }
-}
-
-impl HasWindowHandle for MetalWindow {
-    fn window_handle(&self) -> Result<WindowHandle<'_>, HandleError> {
-        self.window.window_handle()
-    }
-}
-
 impl Hook for MetalWindow {
     fn new_events(&self, cause: &StartCause) -> Result<(), Box<dyn Error + Send + Sync>> {
         todo!()
diff --git a/gui/src/graphics/vulkan/mod.rs b/gui/src/graphics/vulkan/mod.rs
index 043ba0ba4..efe2b1bb1 100644
--- a/gui/src/graphics/vulkan/mod.rs
+++ b/gui/src/graphics/vulkan/mod.rs
@@ -3,13 +3,14 @@ use self::engine::Vulkan;
 use self::window::VulkanWindow;
 use super::EngineBuilder;
 use crate::profile::Profile;
-use crate::rt::{create_window, raw_display_handle, RuntimeError};
+use crate::rt::{create_window, raw_display_handle};
 use ash::extensions::khr::Surface;
 use ash::vk::{ApplicationInfo, InstanceCreateInfo, QueueFlags, API_VERSION_1_3};
 use ash::{Entry, Instance};
 use ash_window::enumerate_required_extensions;
 use std::ffi::CStr;
 use std::mem::ManuallyDrop;
+use std::rc::Rc;
 use std::sync::atomic::AtomicBool;
 use std::sync::Arc;
 use thiserror::Error;
@@ -120,9 +121,10 @@ impl EngineBuilder for VulkanBuilder {
         shutdown: &Arc<AtomicBool>,
     ) -> Result<Arc<Self::Engine>, GraphicsError> {
         let engine = Vulkan::new(self, profile).map(Arc::new)?;
-        let window = create_window(screen, |w| VulkanWindow::new(&engine, w, shutdown))
-            .map_err(GraphicsError::CreateWindow)?;
+        let window = create_window(screen).map_err(GraphicsError::CreateWindow)?;
+        let window = VulkanWindow::new(&engine, window, shutdown).map(Rc::new)?;
 
+        crate::rt::register_window(&window);
         crate::rt::push_hook(window);
 
         Ok(engine)
@@ -173,5 +175,5 @@ pub enum GraphicsError {
     CreateSurface(#[source] ash::vk::Result),
 
     #[error("couldn't create window")]
-    CreateWindow(#[source] RuntimeError),
+    CreateWindow(#[source] winit::error::OsError),
 }
diff --git a/gui/src/graphics/vulkan/window.rs b/gui/src/graphics/vulkan/window.rs
index 3a03c1a78..7b43526ac 100644
--- a/gui/src/graphics/vulkan/window.rs
+++ b/gui/src/graphics/vulkan/window.rs
@@ -1,12 +1,8 @@
 use super::engine::Vulkan;
 use super::GraphicsError;
-use crate::rt::{Hook, RuntimeWindow};
+use crate::rt::{Hook, WindowHandler};
 use ash::vk::SurfaceKHR;
-use raw_window_handle::{
-    DisplayHandle, HandleError, HasDisplayHandle, HasWindowHandle, WindowHandle,
-};
 use std::error::Error;
-use std::rc::Rc;
 use std::sync::atomic::{AtomicBool, Ordering};
 use std::sync::Arc;
 use winit::dpi::{PhysicalPosition, PhysicalSize};
@@ -14,7 +10,7 @@ use winit::event::{DeviceId, ElementState, InnerSizeWriter, MouseButton, StartCa
 use winit::event_loop::ControlFlow;
 use winit::window::{Window, WindowId};
 
-/// Implementation of [`RuntimeWindow`] and [`Hook`] for Vulkan.
+/// Implementation of [`WindowHandler`] and [`Hook`] for Vulkan.
 ///
 /// Fields in this struct must be dropped in a correct order.
 pub struct VulkanWindow {
@@ -29,17 +25,17 @@ impl VulkanWindow {
         engine: &Arc<Vulkan>,
         window: Window,
         shutdown: &Arc<AtomicBool>,
-    ) -> Result<Rc<Self>, Box<dyn Error + Send + Sync>> {
+    ) -> Result<Self, GraphicsError> {
         // Create VkSurfaceKHR.
         let surface =
             unsafe { engine.create_surface(&window) }.map_err(GraphicsError::CreateSurface)?;
 
-        Ok(Rc::new(Self {
+        Ok(Self {
             surface,
             window,
             engine: engine.clone(),
             shutdown: shutdown.clone(),
-        }))
+        })
     }
 }
 
@@ -49,8 +45,8 @@ impl Drop for VulkanWindow {
     }
 }
 
-impl RuntimeWindow for VulkanWindow {
-    fn id(&self) -> WindowId {
+impl WindowHandler for VulkanWindow {
+    fn window_id(&self) -> WindowId {
         self.window.id()
     }
 
@@ -103,18 +99,6 @@ impl RuntimeWindow for VulkanWindow {
     }
 }
 
-impl HasDisplayHandle for VulkanWindow {
-    fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> {
-        self.window.display_handle()
-    }
-}
-
-impl HasWindowHandle for VulkanWindow {
-    fn window_handle(&self) -> Result<WindowHandle<'_>, HandleError> {
-        self.window.window_handle()
-    }
-}
-
 impl Hook for VulkanWindow {
     fn new_events(&self, _: &StartCause) -> Result<(), Box<dyn Error + Send + Sync>> {
         Ok(())
diff --git a/gui/src/main.rs b/gui/src/main.rs
index e2fe993fd..da29d450c 100644
--- a/gui/src/main.rs
+++ b/gui/src/main.rs
@@ -107,7 +107,7 @@ fn main() -> ExitCode {
         // Show error window.
         let msg = format!("An unexpected error has occurred: {}.", e.display());
 
-        self::ui::error(msg).await;
+        self::ui::error::<MainWindow>(None, msg).await;
 
         ExitCode::FAILURE
     };
diff --git a/gui/src/rt/context.rs b/gui/src/rt/context.rs
index 4c04e7fe1..a4edb5b54 100644
--- a/gui/src/rt/context.rs
+++ b/gui/src/rt/context.rs
@@ -1,5 +1,5 @@
 use super::task::TaskList;
-use super::{Event, Hook, RuntimeWindow};
+use super::{Event, Hook, WindowHandler};
 use rustc_hash::FxHashMap;
 use std::any::{Any, TypeId};
 use std::cell::Cell;
@@ -16,8 +16,7 @@ pub struct Context<'a> {
     pub tasks: &'a mut TaskList,
     pub objects: &'a mut FxHashMap<TypeId, Rc<dyn Any>>,
     pub hooks: Option<&'a mut Vec<Rc<dyn Hook>>>,
-    pub windows: &'a mut FxHashMap<WindowId, Weak<dyn RuntimeWindow>>,
-    pub active: Option<&'a WindowId>,
+    pub windows: &'a mut FxHashMap<WindowId, Weak<dyn WindowHandler>>,
 }
 
 impl<'a> Context<'a> {
diff --git a/gui/src/rt/mod.rs b/gui/src/rt/mod.rs
index 5b26d0a68..d4d65679a 100644
--- a/gui/src/rt/mod.rs
+++ b/gui/src/rt/mod.rs
@@ -31,6 +31,8 @@ mod window;
 /// [`std::task::Waker`]. Any pending futures that need to wakeup by an external event like I/O need
 /// a dedicated thread to invoke [`std::task::Waker::wake()`] when the I/O is ready. That mean our
 /// async executor will not work with Tokio by default.
+///
+/// To create a window, call [`create_window()`] from `main` future.
 pub fn run<T: 'static>(main: impl Future<Output = T> + 'static) -> Result<T, RuntimeError> {
     // Setup winit event loop.
     let mut el = EventLoop::<Event>::with_user_event();
@@ -57,7 +59,6 @@ pub fn run<T: 'static>(main: impl Future<Output = T> + 'static) -> Result<T, Run
         objects: HashMap::default(),
         hooks: Vec::new(),
         windows: HashMap::default(),
-        active: None,
         exit,
     };
 
@@ -86,39 +87,26 @@ pub fn raw_display_handle() -> RawDisplayHandle {
     Context::with(|cx| cx.el.raw_display_handle())
 }
 
-/// Note that the runtime do **not** hold a strong reference to the [`RuntimeWindow`] returned from
-/// `f`.
+/// You need to call [`register_window()`] after this to receive events for the created window and
+/// you should do it before the first `await` otherwise you may missed some initial events.
 ///
 /// # Panics
-/// - If called from the other thread than main thread.
-/// - If called from `f`.
-pub fn create_window<T: RuntimeWindow + 'static>(
-    attrs: WindowAttributes,
-    f: impl FnOnce(Window) -> Result<Rc<T>, Box<dyn Error + Send + Sync>>,
-) -> Result<Rc<T>, RuntimeError> {
-    Context::with(move |cx| {
-        let win = cx
-            .el
-            .create_window(attrs)
-            .map_err(RuntimeError::CreateWinitWindow)?;
-        let id = win.id();
-        let win = f(win).map_err(RuntimeError::CreateRuntimeWindow)?;
-        let weak = Rc::downgrade(&win);
-
-        assert!(cx.windows.insert(id, weak).is_none());
-
-        Ok(win)
-    })
+/// If called from the other thread than main thread.
+pub fn create_window(attrs: WindowAttributes) -> Result<Window, OsError> {
+    Context::with(move |cx| cx.el.create_window(attrs))
 }
 
+/// Note that the runtime **do not** hold a strong reference to `win` and it will automatically
+/// removed when the underlying winit window destroyed.
+///
 /// # Panics
-/// If called from the other thread than main thread.
-pub fn active_window() -> Option<Rc<dyn RuntimeWindow>> {
-    Context::with(|cx| {
-        cx.active
-            .and_then(|i| cx.windows.get(i))
-            .and_then(|w| w.upgrade())
-    })
+/// - If called from the other thread than main thread.
+/// - If the underlying winit window on `win` already registered.
+pub fn register_window<T: WindowHandler + 'static>(win: &Rc<T>) {
+    let id = win.window_id();
+    let win = Rc::downgrade(win);
+
+    Context::with(move |cx| assert!(cx.windows.insert(id, win).is_none()));
 }
 
 /// All objects will be destroyed before event loop exit.
@@ -126,7 +114,7 @@ pub fn active_window() -> Option<Rc<dyn RuntimeWindow>> {
 /// # Panics
 /// - If called from the other thread than main thread.
 /// - If object with the same type as `obj` already registered.
-pub fn register(obj: Rc<dyn Any>) {
+pub fn register_global(obj: Rc<dyn Any>) {
     let id = obj.as_ref().type_id();
 
     Context::with(move |cx| assert!(cx.objects.insert(id, obj).is_none()))
@@ -159,8 +147,7 @@ struct Runtime<T> {
     main: u64,
     objects: FxHashMap<TypeId, Rc<dyn Any>>,
     hooks: Vec<Rc<dyn Hook>>,
-    windows: FxHashMap<WindowId, Weak<dyn RuntimeWindow>>,
-    active: Option<WindowId>,
+    windows: FxHashMap<WindowId, Weak<dyn WindowHandler>>,
     exit: Rc<Cell<Option<Result<T, RuntimeError>>>>,
 }
 
@@ -188,7 +175,6 @@ impl<T> Runtime<T> {
                 None
             },
             windows: &mut self.windows,
-            active: self.active.as_ref(),
         };
 
         // Poll the task.
@@ -222,7 +208,6 @@ impl<T> ApplicationHandler<Event> for Runtime<T> {
             objects: &mut self.objects,
             hooks: None,
             windows: &mut self.windows,
-            active: self.active.as_ref(),
         };
 
         if let Err(e) = cx.run(|| {
@@ -265,7 +250,6 @@ impl<T> ApplicationHandler<Event> for Runtime<T> {
             objects: &mut self.objects,
             hooks: None,
             windows: &mut self.windows,
-            active: self.active.as_ref(),
         };
 
         if let Err(e) = cx.run(|| {
@@ -314,17 +298,7 @@ impl<T> ApplicationHandler<Event> for Runtime<T> {
                 r
             }
             WindowEvent::Focused(v) => {
-                let r = dispatch!(w => { w.on_focused(v).map_err(RuntimeError::Focused) });
-
-                // TODO: Is it possible for the lost focus to deliver to the previous window after
-                // the new window got focused?
-                if v {
-                    self.active = Some(id);
-                } else {
-                    self.active = None;
-                }
-
-                r
+                dispatch!(w => { w.on_focused(v).map_err(RuntimeError::Focused) })
             }
             WindowEvent::CursorMoved {
                 device_id: dev,
@@ -365,7 +339,6 @@ impl<T> ApplicationHandler<Event> for Runtime<T> {
             objects: &mut self.objects,
             hooks: None,
             windows: &mut self.windows,
-            active: self.active.as_ref(),
         };
 
         if let Err(e) = cx.run(|| {
@@ -394,7 +367,6 @@ impl<T> ApplicationHandler<Event> for Runtime<T> {
             objects: &mut self.objects,
             hooks: None,
             windows: &mut self.windows,
-            active: self.active.as_ref(),
         };
 
         if let Err(e) = cx.run(|| {
@@ -453,12 +425,6 @@ pub enum RuntimeError {
     #[error("couldn't run event loop")]
     RunEventLoop(#[source] EventLoopError),
 
-    #[error("couldn't create winit window")]
-    CreateWinitWindow(#[source] OsError),
-
-    #[error("couldn't create runtime window")]
-    CreateRuntimeWindow(#[source] Box<dyn Error + Send + Sync>),
-
     #[error("couldn't handle event loop wakeup event")]
     NewEvents(#[source] Box<dyn Error + Send + Sync>),
 
diff --git a/gui/src/rt/window.rs b/gui/src/rt/window.rs
index e6d7a75f4..632bbb3f4 100644
--- a/gui/src/rt/window.rs
+++ b/gui/src/rt/window.rs
@@ -1,4 +1,3 @@
-use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
 use std::error::Error;
 use winit::dpi::{PhysicalPosition, PhysicalSize};
 use winit::event::{DeviceId, ElementState, InnerSizeWriter, MouseButton};
@@ -7,8 +6,8 @@ use winit::window::WindowId;
 /// Encapsulates winit window with window-specific logic.
 ///
 /// The event loop will exit immediately if any method return an error.
-pub trait RuntimeWindow: HasDisplayHandle + HasWindowHandle + 'static {
-    fn id(&self) -> WindowId;
+pub trait WindowHandler {
+    fn window_id(&self) -> WindowId;
     fn on_resized(&self, new: PhysicalSize<u32>) -> Result<(), Box<dyn Error + Send + Sync>>;
     fn on_close_requested(&self) -> Result<(), Box<dyn Error + Send + Sync>>;
     fn on_focused(&self, gained: bool) -> Result<(), Box<dyn Error + Send + Sync>>;
diff --git a/gui/src/setup/mod.rs b/gui/src/setup/mod.rs
index b4181f011..48beb40ca 100644
--- a/gui/src/setup/mod.rs
+++ b/gui/src/setup/mod.rs
@@ -147,7 +147,7 @@ fn set_data_root(win: SetupWizard) {
 
     if input.is_empty() {
         let msg = SharedString::from("You need to choose where to store data before proceed.");
-        crate::rt::spawn(error(msg));
+        crate::rt::spawn(async move { error(Some(&win), msg).await });
         return;
     }
 
diff --git a/gui/src/ui/backend/mod.rs b/gui/src/ui/backend/mod.rs
index 4d46bbcc3..bdfadfab5 100644
--- a/gui/src/ui/backend/mod.rs
+++ b/gui/src/ui/backend/mod.rs
@@ -2,7 +2,7 @@
 pub(super) use self::wayland::*;
 pub(super) use self::window::Window;
 
-use crate::rt::{create_window, raw_display_handle, RuntimeWindow};
+use crate::rt::{create_window, raw_display_handle, WindowHandler};
 use i_slint_core::graphics::RequestedGraphicsAPI;
 use i_slint_renderer_skia::SkiaRenderer;
 use rustc_hash::FxHashMap;
@@ -62,7 +62,7 @@ impl SlintBackend {
         let b = Rc::new(self);
 
         slint::platform::set_platform(Box::new(Platform(Rc::downgrade(&b))))?;
-        crate::rt::register(b.clone());
+        crate::rt::register_global(b.clone());
         crate::rt::push_hook(b.clone());
 
         #[cfg(target_os = "linux")]
@@ -133,10 +133,12 @@ impl crate::rt::Hook for SlintBackend {
 /// Implementation of [`slint::platform::Platform`] for [`SlintBackend`].
 struct Platform(Weak<SlintBackend>);
 
-impl Platform {
-    fn create_window(
-        win: winit::window::Window,
-    ) -> Result<Rc<Window>, Box<dyn Error + Send + Sync>> {
+impl slint::platform::Platform for Platform {
+    fn create_window_adapter(&self) -> Result<Rc<dyn WindowAdapter>, PlatformError> {
+        // Create winit window.
+        let attrs = winit::window::Window::default_attributes().with_visible(false);
+        let win = create_window(attrs).map_err(|e| PlatformError::OtherError(Box::new(e)))?;
+
         // Create renderer.
         let win = Rc::new(win);
         let size = win.inner_size();
@@ -155,29 +157,22 @@ impl Platform {
             move || win.pre_present_notify()
         })));
 
-        // Create WindowAdapter.
-        Ok(Rc::<Window>::new_cyclic(move |weak| {
+        // Just panic if people try to create the window when the event loop already exited.
+        let win = Rc::<Window>::new_cyclic(move |weak| {
             Window::new(win, slint::Window::new(weak.clone()), renderer)
-        }))
-    }
-}
+        });
 
-impl slint::platform::Platform for Platform {
-    fn create_window_adapter(&self) -> Result<Rc<dyn WindowAdapter>, PlatformError> {
-        let attrs = winit::window::Window::default_attributes().with_visible(false);
-        let win = create_window(attrs, Self::create_window)
-            .map_err(|e| PlatformError::OtherError(Box::new(e)))?;
-
-        // Just panic if people try to create the window when the event loop already exited.
         assert!(self
             .0
             .upgrade()
             .unwrap()
             .windows
             .borrow_mut()
-            .insert(win.id(), Rc::downgrade(&win))
+            .insert(win.window_id(), Rc::downgrade(&win))
             .is_none());
 
+        crate::rt::register_window(&win);
+
         Ok(win)
     }
 }
diff --git a/gui/src/ui/backend/window.rs b/gui/src/ui/backend/window.rs
index e1d4870d0..858f20ca8 100644
--- a/gui/src/ui/backend/window.rs
+++ b/gui/src/ui/backend/window.rs
@@ -1,4 +1,4 @@
-use crate::rt::{RuntimeWindow, Signal};
+use crate::rt::{Signal, WindowHandler};
 use i_slint_core::window::WindowAdapterInternal;
 use i_slint_core::InternalToken;
 use i_slint_renderer_skia::SkiaRenderer;
@@ -53,8 +53,8 @@ impl Window {
     }
 }
 
-impl RuntimeWindow for Window {
-    fn id(&self) -> WindowId {
+impl WindowHandler for Window {
+    fn window_id(&self) -> WindowId {
         self.winit.id()
     }
 
@@ -158,18 +158,6 @@ impl RuntimeWindow for Window {
     }
 }
 
-impl HasDisplayHandle for Window {
-    fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> {
-        self.winit.display_handle()
-    }
-}
-
-impl HasWindowHandle for Window {
-    fn window_handle(&self) -> Result<WindowHandle<'_>, HandleError> {
-        self.winit.window_handle()
-    }
-}
-
 impl WindowAdapter for Window {
     fn window(&self) -> &slint::Window {
         &self.slint
@@ -181,18 +169,22 @@ impl WindowAdapter for Window {
 
             self.winit.set_visible(true);
 
-            // Render initial frame. Without this the modal on macOS will show a blank window until
-            // show animation is complete.
-            let scale_factor = self.winit.scale_factor() as f32;
-            let size = self.winit.inner_size();
-            let size = PhysicalSize::new(size.width, size.height);
-            let size = LogicalSize::from_physical(size, scale_factor);
-
-            self.slint
-                .dispatch_event(WindowEvent::ScaleFactorChanged { scale_factor });
-            self.slint.dispatch_event(WindowEvent::Resized { size });
-
-            self.renderer.render()?;
+            // Render initial frame on macOS. Without this the modal will show a blank window until
+            // show animation is complete. On Wayland there are some problems when another window is
+            // showing so we need to to disable it.
+            #[cfg(target_os = "macos")]
+            {
+                let scale_factor = self.winit.scale_factor() as f32;
+                let size = self.winit.inner_size();
+                let size = PhysicalSize::new(size.width, size.height);
+                let size = LogicalSize::from_physical(size, scale_factor);
+
+                self.slint
+                    .dispatch_event(WindowEvent::ScaleFactorChanged { scale_factor });
+                self.slint.dispatch_event(WindowEvent::Resized { size });
+
+                self.renderer.render()?;
+            }
 
             self.visible.set(Some(true));
         } else if self.visible.get().is_some_and(|v| v) {
diff --git a/gui/src/ui/linux/mod.rs b/gui/src/ui/linux/mod.rs
index f4a43cd85..e636fcaf5 100644
--- a/gui/src/ui/linux/mod.rs
+++ b/gui/src/ui/linux/mod.rs
@@ -1,14 +1,13 @@
-use super::{PlatformExt, SlintBackend};
-use crate::rt::{global, RuntimeWindow};
+use super::{Modal, PlatformExt, PlatformWindow, SlintBackend};
+use crate::rt::global;
 use raw_window_handle::{HasWindowHandle, RawWindowHandle};
-use slint::ComponentHandle;
 use thiserror::Error;
 
 mod wayland;
 
-impl<T: ComponentHandle> PlatformExt for T {
+impl<T: PlatformWindow> PlatformExt for T {
     fn set_center(&self) -> Result<(), PlatformError> {
-        let win = self.window().window_handle();
+        let win = self.handle();
         let win = win.window_handle().unwrap();
 
         match win.as_ref() {
@@ -22,18 +21,21 @@ impl<T: ComponentHandle> PlatformExt for T {
         Ok(())
     }
 
-    fn set_modal<P>(&self, parent: &P) -> Result<(), PlatformError>
+    fn set_modal<P>(self, parent: &P) -> Result<Modal<Self, P>, PlatformError>
     where
-        P: RuntimeWindow + ?Sized,
+        P: PlatformWindow,
+        Self: Sized,
     {
-        let win = self.window().window_handle();
+        let win = self.handle();
         let back = global::<SlintBackend>().unwrap();
 
         if let Some(v) = back.wayland() {
-            self::wayland::set_modal(v, &win, parent)
+            self::wayland::set_modal(v, win, parent.handle())?;
         } else {
             todo!()
         }
+
+        Ok(Modal::new(self, parent))
     }
 }
 
diff --git a/gui/src/ui/linux/wayland.rs b/gui/src/ui/linux/wayland.rs
index 213a50974..0d0772c20 100644
--- a/gui/src/ui/linux/wayland.rs
+++ b/gui/src/ui/linux/wayland.rs
@@ -1,12 +1,12 @@
 use super::PlatformError;
-use crate::rt::RuntimeWindow;
 use crate::ui::Wayland;
-use slint::WindowHandle;
+use raw_window_handle::HasWindowHandle;
 
-pub fn set_modal<P>(_: &Wayland, _: &WindowHandle, _: &P) -> Result<(), PlatformError>
-where
-    P: RuntimeWindow + ?Sized,
-{
+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
diff --git a/gui/src/ui/macos/mod.rs b/gui/src/ui/macos/mod.rs
index 24e0c4dc7..18664ba0a 100644
--- a/gui/src/ui/macos/mod.rs
+++ b/gui/src/ui/macos/mod.rs
@@ -1,39 +1,36 @@
 use self::view::with_window;
-use super::PlatformExt;
-use crate::rt::RuntimeWindow;
+use super::{Modal, PlatformExt, PlatformWindow};
 use block::ConcreteBlock;
 use objc::{msg_send, sel, sel_impl};
-use slint::ComponentHandle;
 use std::ffi::c_long;
 use std::ops::Deref;
 use thiserror::Error;
 
 mod view;
 
-impl<T: ComponentHandle> PlatformExt for T {
+impl<T: PlatformWindow> PlatformExt for T {
     fn set_center(&self) -> Result<(), PlatformError> {
-        with_window::<()>(&self.window().window_handle(), |win| unsafe {
-            msg_send![win, center]
-        });
+        with_window::<()>(self.handle(), |win| unsafe { msg_send![win, center] });
 
         Ok(())
     }
 
-    fn set_modal<P>(&self, parent: &P) -> Result<(), PlatformError>
+    fn set_modal<P>(self, parent: &P) -> Result<Modal<Self, P>, PlatformError>
     where
-        P: RuntimeWindow + ?Sized,
+        P: PlatformWindow,
+        Self: Sized,
     {
         // Setup completionHandler.
         let cb = ConcreteBlock::new(move |_: c_long| {}).copy();
 
         // Show the sheet.
-        let win = self.window().window_handle();
-        let win = with_window(&win, |w| w);
-        let _: () = with_window(parent, |w| unsafe {
+        let win = self.handle();
+        let win = with_window(win, |w| w);
+        let _: () = with_window(parent.handle(), |w| unsafe {
             msg_send![w, beginSheet:win completionHandler:cb.deref()]
         });
 
-        Ok(())
+        Ok(Modal::new(self, parent))
     }
 }
 
diff --git a/gui/src/ui/mod.rs b/gui/src/ui/mod.rs
index 00b6fd287..3532e4ea0 100644
--- a/gui/src/ui/mod.rs
+++ b/gui/src/ui/mod.rs
@@ -1,21 +1,22 @@
 pub use self::backend::*;
+pub use self::modal::*;
 pub use self::os::PlatformError;
 pub use self::profile::*;
 
-use crate::rt::{active_window, RuntimeWindow};
 use i_slint_core::window::WindowInner;
 use i_slint_core::InternalToken;
+use raw_window_handle::HasWindowHandle;
 use slint::{ComponentHandle, SharedString};
 
 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")]
 mod os;
 mod profile;
 
-pub async fn error(msg: impl Into<SharedString>) {
-    let parent = active_window();
+pub async fn error<P: PlatformWindow>(parent: Option<&P>, msg: impl Into<SharedString>) {
     let win = ErrorWindow::new().unwrap();
 
     win.set_message(msg.into());
@@ -28,21 +29,36 @@ pub async fn error(msg: impl Into<SharedString>) {
     win.show().unwrap();
 
     match parent {
-        Some(p) => win.set_modal(p.as_ref()).unwrap(),
-        None => win.set_center().unwrap(),
+        Some(p) => win.set_modal(p).unwrap().wait().await,
+        None => {
+            win.set_center().unwrap();
+            win.wait().await;
+        }
     }
+}
+
+/// Provides method to return [`HasWindowHandle`].
+pub trait PlatformWindow {
+    fn handle(&self) -> impl HasWindowHandle + '_;
+}
 
-    win.wait().await;
+impl<T: ComponentHandle> PlatformWindow for T {
+    fn handle(&self) -> impl HasWindowHandle + '_ {
+        self.window().window_handle()
+    }
 }
 
-/// Provides platform-specific methods for [`ComponentHandle`].
-pub trait PlatformExt: ComponentHandle {
-    /// Center window on the screen. This need to call after [`ComponentHandle::show()`] otherwise
-    /// it won't work on macOS.
+/// Provides methods to operate on [`PlatformWindow`].
+pub trait PlatformExt: PlatformWindow {
+    /// 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<(), PlatformError>
+    fn set_modal<P>(self, parent: &P) -> Result<Modal<Self, P>, PlatformError>
     where
-        P: RuntimeWindow + ?Sized;
+        P: PlatformWindow,
+        Self: Sized;
 }
 
 /// Provides methods for [`ComponentHandle`] to work with our async runtime.
diff --git a/gui/src/ui/modal.rs b/gui/src/ui/modal.rs
new file mode 100644
index 000000000..19623b243
--- /dev/null
+++ b/gui/src/ui/modal.rs
@@ -0,0 +1,23 @@
+use std::ops::Deref;
+
+/// Encapsulates a modal window and its parent.
+///
+/// This struct force the modal window to be dropped before its parent.
+pub struct Modal<'a, W, P> {
+    window: W,
+    parent: &'a P,
+}
+
+impl<'a, W, P> Modal<'a, W, P> {
+    pub(super) fn new(window: W, parent: &'a P) -> Self {
+        Self { window, parent }
+    }
+}
+
+impl<'a, W, P> Deref for Modal<'a, W, P> {
+    type Target = W;
+
+    fn deref(&self) -> &Self::Target {
+        &self.window
+    }
+}
diff --git a/gui/src/ui/windows/mod.rs b/gui/src/ui/windows/mod.rs
index 3c3ad2b2c..4338cf25c 100644
--- a/gui/src/ui/windows/mod.rs
+++ b/gui/src/ui/windows/mod.rs
@@ -1,19 +1,18 @@
-use super::PlatformExt;
-use crate::rt::RuntimeWindow;
+use super::{Modal, PlatformExt, PlatformWindow};
+use crate::rt::WindowHandler;
 use raw_window_handle::{HasWindowHandle, RawWindowHandle};
 use slint::ComponentHandle;
+use std::io::Error;
 use thiserror::Error;
-use windows_sys::Win32::{
-    Foundation::HWND,
-    UI::WindowsAndMessaging::{
-        GetSystemMetrics, GetWindowRect, SetWindowPos, HWND_TOP, SM_CXSCREEN, SM_CYSCREEN,
-        SWP_NOSIZE, SWP_NOZORDER,
-    },
+use windows_sys::Win32::Foundation::HWND;
+use windows_sys::Win32::UI::WindowsAndMessaging::{
+    GetSystemMetrics, GetWindowRect, SetWindowPos, HWND_TOP, SM_CXSCREEN, SM_CYSCREEN, SWP_NOSIZE,
+    SWP_NOZORDER,
 };
 
-impl<T: ComponentHandle> PlatformExt for T {
+impl<T: PlatformWindow> PlatformExt for T {
     fn set_center(&self) -> Result<(), PlatformError> {
-        let win = self.window().window_handle();
+        let win = self.handle();
         let raw_handle = win.window_handle().unwrap();
 
         let RawWindowHandle::Win32(h) = raw_handle.as_ref() else {
@@ -27,9 +26,7 @@ impl<T: ComponentHandle> PlatformExt for T {
             let ret = GetWindowRect(hwnd, &mut rect);
 
             if ret == 0 {
-                return Err(PlatformError::GetWindowRectFailed(
-                    std::io::Error::last_os_error(),
-                ));
+                return Err(PlatformError::GetWindowRect(Error::last_os_error()));
             }
 
             let win_width = rect.right - rect.left;
@@ -38,17 +35,13 @@ impl<T: ComponentHandle> PlatformExt for T {
             let screen_width = GetSystemMetrics(SM_CXSCREEN);
 
             if screen_width == 0 {
-                return Err(PlatformError::GetScreenWidthFailed(
-                    std::io::Error::last_os_error(),
-                ));
+                return Err(PlatformError::GetScreenWidth(Error::last_os_error()));
             }
 
             let screen_height = GetSystemMetrics(SM_CYSCREEN);
 
             if screen_height == 0 {
-                return Err(PlatformError::GetScreenHeightFailed(
-                    std::io::Error::last_os_error(),
-                ));
+                return Err(PlatformError::GetScreenHeight(Error::last_os_error()));
             }
 
             let ret = SetWindowPos(
@@ -62,34 +55,34 @@ impl<T: ComponentHandle> PlatformExt for T {
             );
 
             if ret == 0 {
-                return Err(PlatformError::SetWindowPosFailed(
-                    std::io::Error::last_os_error(),
-                ));
+                return Err(PlatformError::SetWindowPos(Error::last_os_error()));
             }
         }
 
         Ok(())
     }
 
-    fn set_modal<P>(&self, parent: &P) -> Result<(), PlatformError>
+    fn set_modal<P>(self, parent: &P) -> Result<Modal<Self, P>, PlatformError>
     where
-        P: RuntimeWindow + ?Sized,
+        P: PlatformWindow,
+        Self: Sized,
     {
         todo!()
     }
 }
 
+/// Windows-specific error for [`PlatformExt`].
 #[derive(Debug, Error)]
 pub enum PlatformError {
-    #[error("failed to get window rect")]
-    GetWindowRectFailed(#[source] std::io::Error),
+    #[error("couldn't get window rectangle")]
+    GetWindowRect(#[source] Error),
 
-    #[error("failed to get screen width")]
-    GetScreenWidthFailed(#[source] std::io::Error),
+    #[error("couldn't get screen width")]
+    GetScreenWidth(#[source] Error),
 
-    #[error("failed to get screen height")]
-    GetScreenHeightFailed(#[source] std::io::Error),
+    #[error("couldn't get screen height")]
+    GetScreenHeight(#[source] Error),
 
-    #[error("failed to set window position")]
-    SetWindowPosFailed(#[source] std::io::Error),
+    #[error("couldn't set window position")]
+    SetWindowPos(#[source] Error),
 }


Test patch: