teksilo_core/raw_handle.rs
1// SPDX-License-Identifier: MPL-2.0
2// SPDX-FileCopyrightText: 2026 FernTech
3
4//! Opaque platform window/display handle wrapper.
5//!
6//! `ParentHandle` carries a `(RawWindowHandle, RawDisplayHandle)` pair
7//! extracted from a winit window on the main thread. Native dialog
8//! libraries (e.g. `rfd::AsyncFileDialog::set_parent`) consume the pair
9//! to parent their OS-level UI to the Teksilo window.
10//!
11//! Lives in `teksilo-core` rather than `teksilo-platform` so the
12//! [`WindowOps`](crate::window::WindowOps) trait, which is in core, can
13//! mention it without inverting the dependency graph
14//! (`core → platform` would be a layering violation; `platform → core`
15//! is the established direction).
16//!
17//! # Thread safety
18//!
19//! `RawWindowHandle` and `RawDisplayHandle` are enums that include
20//! raw pointers (`*mut c_void` in the AppKit/Win32/Wayland variants).
21//! Rust marks raw pointers `!Send + !Sync` by default, so the enums
22//! inherit that. We need this struct to cross thread boundaries
23//! (main → async-std worker driving an `rfd::AsyncFileDialog` future),
24//! so we add `unsafe Send + Sync` impls below.
25//!
26//! Safety contract: the bytes of the handle are moved between threads,
27//! but every platform-specific dereference of the inner pointer
28//! happens inside backend glue that arranges the correct thread
29//! affinity per OS — `dispatch::Queue::main` on macOS, the D-Bus
30//! thread on Linux portal, the COM apartment on Windows. Callers
31//! must NOT dereference the inner handle off the main thread by hand.
32
33use raw_window_handle::{
34 DisplayHandle, HandleError, HasDisplayHandle, HasWindowHandle, RawDisplayHandle,
35 RawWindowHandle, WindowHandle,
36};
37
38/// Opaque pair of platform handles describing a parent window for a
39/// native OS dialog. Construct with [`ParentHandle::from_window`] on
40/// the main thread.
41///
42/// Implements [`HasWindowHandle`] and [`HasDisplayHandle`] so backend
43/// code can hand it directly to APIs like `rfd::AsyncFileDialog::set_parent`
44/// without an intermediate adapter type.
45#[derive(Clone)]
46pub struct ParentHandle {
47 window: RawWindowHandle,
48 display: RawDisplayHandle,
49}
50
51impl ParentHandle {
52 /// Extract the platform handles from a winit window.
53 ///
54 /// MUST be called on the main thread — winit's handle accessors
55 /// are documented as main-thread-only on macOS / Wayland.
56 /// Returns `None` if either handle accessor fails (rare; mostly
57 /// during teardown when the underlying surface is already gone).
58 pub fn from_window<W>(window: &W) -> Option<Self>
59 where
60 W: HasWindowHandle + HasDisplayHandle + ?Sized,
61 {
62 let window_handle = window.window_handle().ok()?.as_raw();
63 let display_handle = window.display_handle().ok()?.as_raw();
64 Some(Self {
65 window: window_handle,
66 display: display_handle,
67 })
68 }
69
70 /// Raw window handle bytes. Use when the consuming API wants the
71 /// enum directly rather than the borrowed [`WindowHandle<'_>`]
72 /// returned by [`HasWindowHandle::window_handle`].
73 pub fn raw_window_handle(&self) -> RawWindowHandle {
74 self.window
75 }
76
77 /// Raw display handle bytes. Paired with
78 /// [`Self::raw_window_handle`].
79 pub fn raw_display_handle(&self) -> RawDisplayHandle {
80 self.display
81 }
82}
83
84impl HasWindowHandle for ParentHandle {
85 fn window_handle(&self) -> Result<WindowHandle<'_>, HandleError> {
86 // SAFETY: `self.window` was extracted on the main thread
87 // from a live winit window. The originating window outlives
88 // the in-flight dialog because [`FileDialogHandle::purge_window`]
89 // drops pending callbacks before the window's tree is torn
90 // down. Backends that consume the handle (rfd's `set_parent`)
91 // copy the raw enum into their own storage; the borrow tied
92 // to `&self` never escapes the `set_parent` call.
93 Ok(unsafe { WindowHandle::borrow_raw(self.window) })
94 }
95}
96
97impl HasDisplayHandle for ParentHandle {
98 fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> {
99 // SAFETY: see HasWindowHandle::window_handle above.
100 Ok(unsafe { DisplayHandle::borrow_raw(self.display) })
101 }
102}
103
104// SAFETY: We only move handle bytes between threads. Every
105// platform-specific dereference of the inner pointer happens inside
106// backend glue that arranges the correct thread affinity per OS.
107// raw-window-handle's own design allows storage of the raw enums
108// across threads — only the borrowed `WindowHandle<'_>` /
109// `DisplayHandle<'_>` types are `!Send` because of their lifetimes.
110unsafe impl Send for ParentHandle {}
111unsafe impl Sync for ParentHandle {}
112
113impl std::fmt::Debug for ParentHandle {
114 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115 f.debug_struct("ParentHandle").finish_non_exhaustive()
116 }
117}