Skip to main content

window_observer/platform_impl/macos/
window.rs

1use accessibility::{AXUIElement, AXUIElementAttributes};
2use core_foundation::base::TCFType;
3use objc2_core_foundation::{CGPoint, CGSize};
4
5use super::{
6    binding_ax_function::{ax_ui_element_copy_attribute_value, ax_value_get_value},
7    error::MacOSError,
8};
9use crate::window::{Position, Size};
10
11impl From<CGSize> for Size {
12    fn from(size: CGSize) -> Self {
13        Size {
14            width: size.width as _,
15            height: size.height as _,
16        }
17    }
18}
19
20impl From<CGPoint> for Position {
21    fn from(point: CGPoint) -> Self {
22        Position {
23            x: point.x as _,
24            y: point.y as _,
25        }
26    }
27}
28
29/// Represents a macOS window and provides methods to interact with it.
30/// This is the wrapper of [`AXUIElement`] which represents a window.
31#[derive(Debug, Clone, PartialEq, Eq)]
32pub struct WindowUIElement(AXUIElement);
33
34unsafe impl Send for WindowUIElement {}
35unsafe impl Sync for WindowUIElement {}
36
37impl WindowUIElement {
38    /// Creates a new [`WindowUIElement`] instance from an [`AXUIElement`].
39    ///
40    /// # Warning
41    /// You need to ensure that the provided `AXUIElement` is indeed a window.
42    /// This means that the role of the element must be [`kAXWindowRole`][accessibility_sys::kAXWindowRole].
43    ///
44    /// If the role is not a window, the methods will always return an error.
45    pub fn new(element: AXUIElement) -> Self {
46        Self(element)
47    }
48
49    /// Retrieves the underlying [`AXUIElement`].
50    pub fn ax_ui_element(&self) -> &AXUIElement {
51        &self.0
52    }
53
54    /// Retrieves a specific attribute of the window via [`AXUIElement`].
55    fn get<T>(
56        &self,
57        attribute: &str,
58        r#type: accessibility_sys::AXValueType,
59    ) -> Result<T, MacOSError> {
60        let ax_value =
61            ax_ui_element_copy_attribute_value(&self.0, attribute).map_err(MacOSError::Ax)?;
62        Ok(unsafe {
63            ax_value_get_value::<T>(ax_value.as_CFTypeRef() as _, r#type)
64                .expect("AXValue is not this type")
65        })
66    }
67
68    /// Retrieves the title of the window.
69    pub fn title(&self) -> Result<String, MacOSError> {
70        Ok(self.0.title()?.to_string())
71    }
72
73    /// Retrieves the size of the window.
74    pub fn size(&self) -> Result<Size, MacOSError> {
75        self.get::<CGSize>(
76            accessibility_sys::kAXSizeAttribute,
77            accessibility_sys::kAXValueTypeCGSize,
78        )
79        .map(|v| v.into())
80    }
81
82    /// Retrieves the position of the window.
83    pub fn position(&self) -> Result<Position, MacOSError> {
84        self.get::<CGPoint>(
85            accessibility_sys::kAXPositionAttribute,
86            accessibility_sys::kAXValueTypeCGPoint,
87        )
88        .map(|v| v.into())
89    }
90
91    /// Checks if the window is currently active.
92    pub fn is_focused(&self) -> Result<bool, MacOSError> {
93        Ok(self.0.focused()?.into())
94    }
95
96    /// Retrieves the id of the window. The value is [`CGWindowID`][window_id].
97    ///
98    /// # Warning
99    /// This function will call private API `_AXUIElementGetWindow` of macOS.
100    /// This is because the [`AXUIElement`][element] does not provide a public method to get the window id.
101    ///
102    /// [window_id]: https://developer.apple.com/documentation/coregraphics/cgwindowid?language=objc
103    /// [element]: https://developer.apple.com/documentation/applicationservices/axuielement_h?language=objc
104    #[cfg(feature = "macos-private-api")]
105    #[cfg_attr(docsrs, doc(cfg(feature = "macos-private-api")))]
106    pub fn id(&self) -> Result<u32, MacOSError> {
107        super::binding_ax_function::ax_ui_element_get_window_id(&self.0)
108    }
109}