From 1e5f09d82508e69bbf05da021173da1ef97038d4 Mon Sep 17 00:00:00 2001 From: John Nunley Date: Thu, 30 Mar 2023 01:24:50 +0200 Subject: [PATCH] Change the "Active" struct to be sound (#116) * Use a new strategy for ensuring window handles are active * Make sure android requires std * Second look at HasWindowHandle trait * Export the error type * Make DisplayHandle more consistent with WindowHandle * Fix active handle resemblance to refcounted version * fmt * Mark `set_inactive` as unsafe * Add more docs * Fix doc links * tait -> trait * Win32 handles can't be arbitrarily deleted * fmt --- Cargo.toml | 1 + src/borrowed.rs | 669 ++++++++++++++++++++++++++++++++---------------- src/lib.rs | 8 +- 3 files changed, 451 insertions(+), 227 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1ee078b..1aa39c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ rust-version = "1.64" [features] alloc = [] +std = ["alloc"] [package.metadata.docs.rs] all-features = true diff --git a/src/borrowed.rs b/src/borrowed.rs index 823c120..b38d3d8 100644 --- a/src/borrowed.rs +++ b/src/borrowed.rs @@ -1,197 +1,267 @@ //! Borrowable window handles based on the ones in this crate. //! -//! These should be 100% safe to pass around and use, no possibility of dangling or -//! invalidity. +//! These should be 100% safe to pass around and use, no possibility of dangling or invalidity. + +#[cfg(all(not(feature = "std"), target_os = "android"))] +compile_error!("Using borrowed handles on Android requires the `std` feature to be enabled."); use core::fmt; +use core::hash::{Hash, Hasher}; use core::marker::PhantomData; use crate::{HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle}; -/// The application is currently active. +/// Keeps track of whether the application is currently active. /// -/// This structure is a token that indicates that the application is -/// not presently suspended. It is used to ensure that the window handle -/// is only used when the application is active. +/// On certain platforms (e.g. Android), it is possible for the application to enter a "suspended" +/// state. While in this state, all previously valid window handles become invalid. Therefore, in +/// order for window handles to be valid, the application must be active. /// -/// It is safe to create this token on platforms where the application -/// is guaranteed to be active, such as on desktop platforms. On Android -/// platforms, the application may be suspended, so this token must be -/// either created with `unsafe` or derived from a `HasDisplayHandle` -/// type. -pub struct Active<'a> { - _marker: PhantomData<&'a *const ()>, +/// On platforms where the graphical user interface is always active, this type is a ZST and all +/// of its methods are noops. On Android, this type acts as a reference counter that keeps track +/// of all currently active window handles. Before the application enters the suspended state, it +/// blocks until all of the currently active window handles are dropped. +/// +/// ## Explanation +/// +/// On Android, there is an [Activity]-global [`ANativeWindow`] object that is used for drawing. This +/// handle is used [within the `RawWindowHandle` type] for Android NDK, since it is necessary for GFX +/// APIs to draw to the screen. +/// +/// However, the [`ANativeWindow`] type can be arbitrarily invalidated by the underlying Android runtime. +/// The reasoning for this is complicated, but this idea is exposed to native code through the +/// [`onNativeWindowCreated`] and [`onNativeWindowDestroyed`] callbacks. To save you a click, the +/// conditions associated with these callbacks are: +/// +/// - [`onNativeWindowCreated`] provides a valid [`ANativeWindow`] pointer that can be used for drawing. +/// - [`onNativeWindowDestroyed`] indicates that the previous [`ANativeWindow`] pointer is no longer +/// valid. The documentation clarifies that, *once the function returns*, the [`ANativeWindow`] pointer +/// can no longer be used for drawing without resulting in undefined behavior. +/// +/// In [`winit`], these are exposed via the [`Resumed`] and [`Suspended`] events, respectively. Therefore, +/// between the last [`Suspended`] event and the next [`Resumed`] event, it is undefined behavior to use +/// the raw window handle. This condition makes it tricky to define an API that safely wraps the raw +/// window handles, since an existing window handle can be made invalid at any time. +/// +/// The Android docs specifies that the [`ANativeWindow`] pointer is still valid while the application +/// is still in the [`onNativeWindowDestroyed`] block, and suggests that synchronization needs to take +/// place to ensure that the pointer has been invalidated before the function returns. `Active` aims +/// to be the solution to this problem. It keeps track of all currently active window handles, and +/// blocks until all of them are dropped before allowing the application to enter the suspended state. +/// +/// [Activity]: https://developer.android.com/reference/android/app/Activity +/// [`ANativeWindow`]: https://developer.android.com/ndk/reference/group/a-native-window +/// [within the `RawWindowHandle` type]: struct.AndroidNdkWindowHandle.html#structfield.a_native_window +/// [`onNativeWindowCreated`]: https://developer.android.com/ndk/reference/struct/a-native-activity-callbacks#onnativewindowcreated +/// [`onNativeWindowDestroyed`]: https://developer.android.com/ndk/reference/struct/a-native-activity-callbacks#onnativewindowdestroyed +/// [`Resumed`]: https://docs.rs/winit/latest/winit/event/enum.Event.html#variant.Resumed +/// [`Suspended`]: https://docs.rs/winit/latest/winit/event/enum.Event.html#variant.Suspended +/// [`sdl2`]: https://crates.io/crates/sdl2 +/// [`RawWindowHandle`]: https://docs.rs/raw-window-handle/latest/raw_window_handle/enum.RawWindowHandle.html +/// [`HasRawWindowHandle`]: https://docs.rs/raw-window-handle/latest/raw_window_handle/trait.HasRawWindowHandle.html +/// [`winit`]: https://crates.io/crates/winit +pub struct Active(imp::Active); + +impl fmt::Debug for Active { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Active { .. }") + } } -impl fmt::Debug for Active<'_> { +/// Represents a live window handle. +/// +/// This is carried around by the [`Active`] type, and is used to ensure that the application doesn't +/// enter the suspended state while there are still live window handles. See documentation on the +/// [`Active`] type for more information. +/// +/// On non-Android platforms, this is a ZST. On Android, this is a reference counted handle that +/// keeps the application active while it is alive. +#[derive(Clone)] +pub struct ActiveHandle<'a>(imp::ActiveHandle<'a>); + +impl<'a> fmt::Debug for ActiveHandle<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("Active { .. }") + f.write_str("ActiveHandle { .. }") } } -impl<'a> Active<'a> { - /// Create a new active token. +impl Active { + /// Create a new `Active` tracker. /// - /// # Safety + /// Only one of these should exist per display connection. /// - /// The application must not be `Suspend`ed. + /// # Example /// - /// # Examples + /// ``` + /// use raw_window_handle::Active; + /// let active = Active::new(); + /// ``` + pub const fn new() -> Self { + Self(imp::Active::new()) + } + + /// Get a live window handle. + /// + /// This function returns an active handle if the application is active, and `None` otherwise. + /// + /// # Example /// /// ``` /// use raw_window_handle::Active; /// - /// // SAFETY: The application is active. - /// let active = unsafe { Active::new_unchecked() }; + /// // Set the application to be active. + /// let active = Active::new(); + /// unsafe { active.set_active() }; + /// + /// // Get a live window handle. + /// let handle = active.handle(); + /// + /// // Drop it and set the application to be inactive. + /// drop(handle); + /// active.set_inactive(); /// ``` - pub unsafe fn new_unchecked() -> Self { - Self { - _marker: PhantomData, - } + pub fn handle(&self) -> Option> { + self.0.handle().map(ActiveHandle) } - /// Create a new active token on a system where the application is - /// guaranteed to be active. + /// Set the application to be inactive. + /// + /// This function may block until there are no more active handles. /// - /// On most platforms, there is no event where the application is - /// suspended, so there are no cases where this function is unsafe. + /// # Example /// /// ``` /// use raw_window_handle::Active; /// - /// let with_active = |active: Active<'_>| { - /// /* ... */ - /// }; + /// // Set the application to be active. + /// let active = Active::new(); + /// unsafe { active.set_active() }; + /// + /// // Set the application to be inactive. + /// active.set_inactive(); + /// ``` + pub fn set_inactive(&self) { + self.0.set_inactive() + } + + /// Set the application to be active. + /// + /// # Safety /// - /// // Only use this code-path on non-android platforms. - /// #[cfg(not(target_os = "android"))] - /// { - /// let active = Active::new(); - /// with_active(active); - /// } + /// The application must actually be active. Setting to active when the application is not active + /// will result in undefined behavior. /// - /// // Only use this code-path on android platforms. - /// #[cfg(target_os = "android")] - /// { - /// if application_is_active() { - /// let active = unsafe { Active::new_unchecked() }; - /// with_active(active); - /// } - /// } - /// # fn application_is_active() -> bool { false } - /// ``` - #[cfg(not(target_os = "android"))] - #[cfg_attr(docsrs, doc(cfg(not(target_os = "android"))))] - pub fn new() -> Self { - // SAFETY: The application is guaranteed to be active. - unsafe { Self::new_unchecked() } + /// # Example + /// + /// ``` + /// use raw_window_handle::Active; + /// + /// // Set the application to be active. + /// let active = Active::new(); + /// unsafe { active.set_active() }; + /// + /// // Set the application to be inactive. + /// active.set_inactive(); + /// ``` + pub unsafe fn set_active(&self) { + self.0.set_active() + } +} + +impl ActiveHandle<'_> { + /// Create a new freestanding active handle. + /// + /// This function acts as an "escape hatch" to allow the user to create a live window handle + /// without having to go through the [`Active`] type. This is useful if the user *knows* that the + /// application is active, and wants to create a live window handle without having to go through + /// the [`Active`] type. + /// + /// # Safety + /// + /// The application must actually be active. + /// + /// # Example + /// + /// ``` + /// use raw_window_handle::ActiveHandle; + /// + /// // Create a freestanding active handle. + /// // SAFETY: The application must actually be active. + /// let handle = unsafe { ActiveHandle::new_unchecked() }; + /// ``` + pub unsafe fn new_unchecked() -> Self { + Self(imp::ActiveHandle::new_unchecked()) } } /// A display that acts as a wrapper around a display handle. /// +/// Objects that implement this trait should be able to return a [`DisplayHandle`] for the display +/// that they are associated with. This handle should last for the lifetime of the object, and should +/// return an error if the application is inactive. +/// +/// Implementors of this trait will be windowing systems, like [`winit`] and [`sdl2`]. These windowing +/// systems should implement this trait on types that already implement [`HasRawDisplayHandle`]. It +/// should be implemented by tying the lifetime of the [`DisplayHandle`] to the lifetime of the +/// display object. +/// +/// Users of this trait will include graphics libraries, like [`wgpu`] and [`glutin`]. These APIs +/// should be generic over a type that implements `HasDisplayHandle`, and should use the +/// [`DisplayHandle`] type to access the display handle. +/// /// # Safety /// -/// The safety requirements of [`HasRawDisplayHandle`] apply here as -/// well. To clarify, the [`DisplayHandle`] must contain a valid window -/// handle for its lifetime. In addition, the handle must be consistent -/// between multiple calls barring platform-specific events. +/// The safety requirements of [`HasRawDisplayHandle`] apply here as well. To reiterate, the +/// [`DisplayHandle`] must contain a valid window handle for its lifetime. /// -/// In addition, the `active` function must only return an `Active` -/// token if the application is active. +/// It is not possible to invalidate a [`DisplayHandle`] on any platform without additional unsafe code. /// -/// Note that these requirements are not enforced on `HasDisplayHandle`, -/// rather, they are enforced on the constructors of [`Active`] and -/// [`DisplayHandle`]. This is because the `HasDisplayHandle` trait is -/// safe to implement. +/// Note that these requirements are not enforced on `HasDisplayHandle`, rather, they are enforced on the +/// constructors of [`DisplayHandle`]. This is because the `HasDisplayHandle` trait is safe to implement. /// /// [`HasRawDisplayHandle`]: crate::HasRawDisplayHandle +/// [`winit`]: https://crates.io/crates/winit +/// [`sdl2`]: https://crates.io/crates/sdl2 +/// [`wgpu`]: https://crates.io/crates/wgpu +/// [`glutin`]: https://crates.io/crates/glutin pub trait HasDisplayHandle { - /// Get a token indicating whether the application is active. - fn active(&self) -> Option>; - /// Get a handle to the display controller of the windowing system. - fn display_handle<'this, 'active>( - &'this self, - active: &'active Active<'_>, - ) -> DisplayHandle<'this> - where - 'active: 'this; + fn display_handle(&self) -> Result, HandleError>; } -impl HasDisplayHandle for &T { - fn active(&self) -> Option> { - (**self).active() - } - - fn display_handle<'this, 'active>( - &'this self, - active: &'active Active<'_>, - ) -> DisplayHandle<'this> - where - 'active: 'this, - { - (**self).display_handle(active) +impl HasDisplayHandle for &H { + fn display_handle(&self) -> Result, HandleError> { + (**self).display_handle() } } #[cfg(feature = "alloc")] -#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] -impl HasDisplayHandle for alloc::boxed::Box { - fn active(&self) -> Option> { - (**self).active() - } - - fn display_handle<'this, 'active>( - &'this self, - active: &'active Active<'_>, - ) -> DisplayHandle<'this> - where - 'active: 'this, - { - (**self).display_handle(active) +impl HasDisplayHandle for alloc::boxed::Box { + fn display_handle(&self) -> Result, HandleError> { + (**self).display_handle() } } #[cfg(feature = "alloc")] -#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] -impl HasDisplayHandle for alloc::rc::Rc { - fn active(&self) -> Option> { - (**self).active() - } - - fn display_handle<'this, 'active>( - &'this self, - active: &'active Active<'_>, - ) -> DisplayHandle<'this> - where - 'active: 'this, - { - (**self).display_handle(active) +impl HasDisplayHandle for alloc::rc::Rc { + fn display_handle(&self) -> Result, HandleError> { + (**self).display_handle() } } #[cfg(feature = "alloc")] -#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] -impl HasDisplayHandle for alloc::sync::Arc { - fn active(&self) -> Option> { - (**self).active() - } - - fn display_handle<'this, 'active>( - &'this self, - active: &'active Active<'_>, - ) -> DisplayHandle<'this> - where - 'active: 'this, - { - (**self).display_handle(active) +impl HasDisplayHandle for alloc::sync::Arc { + fn display_handle(&self) -> Result, HandleError> { + (**self).display_handle() } } /// The handle to the display controller of the windowing system. /// -/// Get the underlying raw display handle with the `HasRawDisplayHandle` trait. +/// This is the primary return type of the [`HasDisplayHandle`] trait. It is guaranteed to contain +/// a valid platform-specific display handle for its lifetime. +/// +/// Get the underlying raw display handle with the [`HasRawDisplayHandle`] trait. #[repr(transparent)] #[derive(PartialEq, Eq, Hash)] pub struct DisplayHandle<'a> { @@ -205,23 +275,22 @@ impl fmt::Debug for DisplayHandle<'_> { } } -impl<'a> Copy for DisplayHandle<'a> {} impl<'a> Clone for DisplayHandle<'a> { fn clone(&self) -> Self { - *self + Self { + raw: self.raw, + _marker: PhantomData, + } } } impl<'a> DisplayHandle<'a> { - /// Borrow a `DisplayHandle` from a `RawDisplayHandle`. + /// Create a `DisplayHandle` from a [`RawDisplayHandle`]. /// /// # Safety /// - /// The `RawDisplayHandle` must be valid for the lifetime and the - /// application must be `Active`. See the requirements on the - /// [`HasDisplayHandle`] trait for more information. - pub unsafe fn borrow_raw(raw: RawDisplayHandle, active: &Active<'a>) -> Self { - let _ = active; + /// The `RawDisplayHandle` must be valid for the lifetime. + pub unsafe fn borrow_raw(raw: RawDisplayHandle) -> Self { Self { raw, _marker: PhantomData, @@ -236,108 +305,102 @@ unsafe impl HasRawDisplayHandle for DisplayHandle<'_> { } impl<'a> HasDisplayHandle for DisplayHandle<'a> { - fn active(&self) -> Option> { - // SAFETY: The fact that this handle was created means that the - // application is active. - Some(unsafe { Active::new_unchecked() }) - } - - fn display_handle<'this, 'active>( - &'this self, - _active: &'active Active<'_>, - ) -> DisplayHandle<'this> - where - 'active: 'this, - { - *self + fn display_handle(&self) -> Result, HandleError> { + Ok(self.clone()) } } /// A handle to a window. /// +/// Objects that implement this trait should be able to return a [`WindowHandle`] for the window +/// that they are associated with. This handle should last for the lifetime of the object, and should +/// return an error if the application is inactive. +/// +/// Implementors of this trait will be windowing systems, like [`winit`] and [`sdl2`]. These windowing +/// systems should implement this trait on types that already implement [`HasRawWindowHandle`]. First, +/// it should be made sure that the display type contains a unique [`Active`] ref-counted handle. +/// To create a [`WindowHandle`], the [`Active`] should be used to create an [`ActiveHandle`] that is +/// then used to create a [`WindowHandle`]. Finally, the raw window handle should be retrieved from +/// the type and used to create a [`WindowHandle`]. +/// +/// Users of this trait will include graphics libraries, like [`wgpu`] and [`glutin`]. These APIs +/// should be generic over a type that implements `HasWindowHandle`, and should use the +/// [`WindowHandle`] type to access the window handle. The window handle should be acquired and held +/// while the window is being used, in order to ensure that the window is not deleted while it is in +/// use. +/// /// # Safety /// -/// The safety requirements of [`HasRawWindowHandle`] apply here as -/// well. To clarify, the [`WindowHandle`] must contain a valid window -/// handle for its lifetime. In addition, the handle must be consistent -/// between multiple calls barring platform-specific events. +/// All pointers within the resulting [`WindowHandle`] must be valid and not dangling for the lifetime of +/// the handle. +/// +/// Note that this guarantee only applies to *pointers*, and not any window ID types in the handle. +/// This includes Window IDs (XIDs) from X11 and the window ID for web platforms. There is no way for +/// Rust to enforce any kind of invariant on these types, since: +/// +/// - For all three listed platforms, it is possible for safe code in the same process to delete +/// the window. +/// - For X11, it is possible for code in a different process to delete the window. In fact, it is +/// possible for code on a different *machine* to delete the window. +/// +/// It is *also* possible for the window to be replaced with another, valid-but-different window. User +/// code should be aware of this possibility, and should be ready to soundly handle the possible error +/// conditions that can arise from this. +/// +/// In addition, the window handle must not be invalidated for the duration of the [`ActiveHandle`] token. +/// +/// Note that these requirements are not enforced on `HasWindowHandle`, rather, they are enforced on the +/// constructors of [`WindowHandle`]. This is because the `HasWindowHandle` trait is safe to implement. /// -/// Note that these requirements are not enforced on `HasWindowHandle`, -/// rather, they are enforced on the constructors of -/// [`WindowHandle`]. This is because the `HasWindowHandle` trait is -/// safe to implement. +/// [`winit`]: https://crates.io/crates/winit +/// [`sdl2`]: https://crates.io/crates/sdl2 +/// [`wgpu`]: https://crates.io/crates/wgpu +/// [`glutin`]: https://crates.io/crates/glutin pub trait HasWindowHandle { /// Get a handle to the window. - fn window_handle<'this, 'active>( - &'this self, - active: &'active Active<'_>, - ) -> WindowHandle<'this> - where - 'active: 'this; + fn window_handle(&self) -> Result, HandleError>; } -impl HasWindowHandle for &T { - fn window_handle<'this, 'active>( - &'this self, - active: &'active Active<'_>, - ) -> WindowHandle<'this> - where - 'active: 'this, - { - (**self).window_handle(active) +impl HasWindowHandle for &H { + fn window_handle(&self) -> Result, HandleError> { + (**self).window_handle() } } #[cfg(feature = "alloc")] -#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] -impl HasWindowHandle for alloc::boxed::Box { - fn window_handle<'this, 'active>( - &'this self, - active: &'active Active<'_>, - ) -> WindowHandle<'this> - where - 'active: 'this, - { - (**self).window_handle(active) +impl HasWindowHandle for alloc::boxed::Box { + fn window_handle(&self) -> Result, HandleError> { + (**self).window_handle() } } #[cfg(feature = "alloc")] -#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] -impl HasWindowHandle for alloc::rc::Rc { - fn window_handle<'this, 'active>( - &'this self, - active: &'active Active<'_>, - ) -> WindowHandle<'this> - where - 'active: 'this, - { - (**self).window_handle(active) +impl HasWindowHandle for alloc::rc::Rc { + fn window_handle(&self) -> Result, HandleError> { + (**self).window_handle() } } #[cfg(feature = "alloc")] -#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] -impl HasWindowHandle for alloc::sync::Arc { - fn window_handle<'this, 'active>( - &'this self, - active: &'active Active<'_>, - ) -> WindowHandle<'this> - where - 'active: 'this, - { - (**self).window_handle(active) +impl HasWindowHandle for alloc::sync::Arc { + fn window_handle(&self) -> Result, HandleError> { + (**self).window_handle() } } /// The handle to a window. /// -/// This handle is guaranteed to be safe and valid. Get the underlying -/// raw window handle with the `HasRawWindowHandle` trait. -#[repr(transparent)] -#[derive(PartialEq, Eq, Hash)] +/// This is the primary return type of the [`HasWindowHandle`] trait. All *pointers* within this type +/// are guaranteed to be valid and not dangling for the lifetime of the handle. This excludes window IDs +/// like XIDs and the window ID for web platforms. See the documentation on the [`HasWindowHandle`] +/// trait for more information about these safety requirements. +/// +/// This handle is guaranteed to be safe and valid. Get the underlying raw window handle with the +/// [`HasRawWindowHandle`] trait. +#[derive(Clone)] pub struct WindowHandle<'a> { raw: RawWindowHandle, + _active: ActiveHandle<'a>, _marker: PhantomData<&'a *const ()>, } @@ -347,24 +410,32 @@ impl fmt::Debug for WindowHandle<'_> { } } -impl<'a> Copy for WindowHandle<'a> {} -impl<'a> Clone for WindowHandle<'a> { - fn clone(&self) -> Self { - *self +impl PartialEq for WindowHandle<'_> { + fn eq(&self, other: &Self) -> bool { + self.raw == other.raw + } +} + +impl Eq for WindowHandle<'_> {} + +impl Hash for WindowHandle<'_> { + fn hash(&self, state: &mut H) { + self.raw.hash(state); } } impl<'a> WindowHandle<'a> { - /// Borrow a `WindowHandle` from a `RawWindowHandle`. + /// Borrow a `WindowHandle` from a [`RawWindowHandle`]. /// /// # Safety /// - /// The `RawWindowHandle` must be valid for the lifetime and the - /// application must be `Active`. - pub unsafe fn borrow_raw(raw: RawWindowHandle, active: &Active<'a>) -> Self { - let _ = active; + /// The [`RawWindowHandle`] must be valid for the lifetime and the application must not be + /// suspended. The [`Active`] object that the [`ActiveHandle`] was created from must be + /// associated directly with the display that the window handle is associated with. + pub unsafe fn borrow_raw(raw: RawWindowHandle, active: ActiveHandle<'a>) -> Self { Self { raw, + _active: active, _marker: PhantomData, } } @@ -376,18 +447,33 @@ unsafe impl HasRawWindowHandle for WindowHandle<'_> { } } -impl<'a> HasWindowHandle for WindowHandle<'a> { - fn window_handle<'this, 'active>( - &'this self, - _active: &'active Active<'_>, - ) -> WindowHandle<'this> - where - 'active: 'this, - { - *self +impl HasWindowHandle for WindowHandle<'_> { + fn window_handle(&self) -> Result { + Ok(self.clone()) + } +} + +/// The error type returned when a handle cannot be obtained. +#[derive(Debug)] +#[non_exhaustive] +pub enum HandleError { + /// The handle is not currently active. + /// + /// See documentation on [`Active`] for more information. + Inactive, +} + +impl fmt::Display for HandleError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Inactive => write!(f, "the handle is not currently active"), + } } } +#[cfg(feature = "std")] +impl std::error::Error for HandleError {} + /// ```compile_fail /// use raw_window_handle::{Active, DisplayHandle, WindowHandle}; /// fn _assert() {} @@ -396,3 +482,134 @@ impl<'a> HasWindowHandle for WindowHandle<'a> { /// _assert::>(); /// ``` fn _not_send_or_sync() {} + +#[cfg(not(any(target_os = "android", raw_window_handle_force_refcount)))] +#[cfg_attr(docsrs, doc(cfg(not(target_os = "android"))))] +mod imp { + //! We don't need to refcount the handles, so we can just use no-ops. + + use core::cell::UnsafeCell; + use core::marker::PhantomData; + + pub(super) struct Active; + + #[derive(Clone)] + pub(super) struct ActiveHandle<'a> { + _marker: PhantomData<&'a UnsafeCell<()>>, + } + + impl Active { + pub(super) const fn new() -> Self { + Self + } + + pub(super) fn handle(&self) -> Option> { + // SAFETY: The handle is always active. + Some(unsafe { ActiveHandle::new_unchecked() }) + } + + pub(super) unsafe fn set_active(&self) {} + + pub(super) fn set_inactive(&self) {} + } + + impl ActiveHandle<'_> { + pub(super) unsafe fn new_unchecked() -> Self { + Self { + _marker: PhantomData, + } + } + } + + impl Drop for ActiveHandle<'_> { + fn drop(&mut self) { + // Done for consistency with the refcounted version. + } + } + + impl super::ActiveHandle<'_> { + /// Create a new `ActiveHandle`. + /// + /// This is safe because the handle is always active. + /// + /// # Example + /// + /// ``` + /// use raw_window_handle::ActiveHandle; + /// let handle = ActiveHandle::new(); + /// ``` + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + // SAFETY: The handle is always active. + unsafe { super::ActiveHandle::new_unchecked() } + } + } +} + +#[cfg(any(target_os = "android", raw_window_handle_force_refcount))] +#[cfg_attr(docsrs, doc(cfg(any(target_os = "android"))))] +mod imp { + //! We need to refcount the handles, so we use an `RwLock` to do so. + + use std::sync::{RwLock, RwLockReadGuard}; + + pub(super) struct Active { + active: RwLock, + } + + pub(super) struct ActiveHandle<'a> { + inner: Option>, + } + + struct Inner<'a> { + _read_guard: RwLockReadGuard<'a, bool>, + active: &'a Active, + } + + impl Clone for ActiveHandle<'_> { + fn clone(&self) -> Self { + Self { + inner: self.inner.as_ref().map(|inner| Inner { + _read_guard: inner.active.active.read().unwrap(), + active: inner.active, + }), + } + } + } + + impl Active { + pub(super) const fn new() -> Self { + Self { + active: RwLock::new(false), + } + } + + pub(super) fn handle(&self) -> Option> { + let active = self.active.read().ok()?; + if !*active { + return None; + } + + Some(ActiveHandle { + inner: Some(Inner { + _read_guard: active, + active: self, + }), + }) + } + + pub(super) unsafe fn set_active(&self) { + *self.active.write().unwrap() = true; + } + + pub(super) fn set_inactive(&self) { + *self.active.write().unwrap() = false; + } + } + + impl ActiveHandle<'_> { + pub(super) unsafe fn new_unchecked() -> Self { + Self { inner: None } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index f299a09..522e49e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,6 +28,9 @@ #[cfg(feature = "alloc")] extern crate alloc; +#[cfg(feature = "std")] +extern crate std; + mod android; mod appkit; mod borrowed; @@ -40,7 +43,10 @@ mod windows; pub use android::{AndroidDisplayHandle, AndroidNdkWindowHandle}; pub use appkit::{AppKitDisplayHandle, AppKitWindowHandle}; -pub use borrowed::{Active, DisplayHandle, HasDisplayHandle, HasWindowHandle, WindowHandle}; +pub use borrowed::{ + Active, ActiveHandle, DisplayHandle, HandleError, HasDisplayHandle, HasWindowHandle, + WindowHandle, +}; pub use haiku::{HaikuDisplayHandle, HaikuWindowHandle}; pub use redox::{OrbitalDisplayHandle, OrbitalWindowHandle}; pub use uikit::{UiKitDisplayHandle, UiKitWindowHandle};