Skip to main content

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}