use objc2_core_foundation::{CFArray, CFDictionary, CFRetained, CFString, CFType};
use objc2_core_graphics::{CGWindowListCopyWindowInfo, CGWindowListOption, kCGNullWindowID};
use crate::{Error, Window};
pub use error::MacOSError;
pub use window::MacOSWindow;
pub use window_info::{WindowInfo, WindowInfoDict};
pub type MacOSBounds = objc2_core_foundation::CGRect;
pub type MacOSWindowId = objc2_core_graphics::CGWindowID;
pub fn get_window(id: MacOSWindowId) -> Result<Option<Window>, Error> {
let list: CFRetained<CFArray<CFDictionary<CFString, CFType>>> = unsafe {
let list = CGWindowListCopyWindowInfo(CGWindowListOption::all(), id);
let Some(list) = list else {
return Err(Error::NoWindowEnvironment);
};
CFRetained::cast_unchecked(list)
};
for dict in list.iter() {
let window = MacOSWindow::new(WindowInfo::new(dict));
if window.id() == id {
return Ok(Some(Window(window)));
}
}
Ok(None)
}
pub fn get_windows() -> Result<Vec<Window>, Error> {
let list: CFRetained<CFArray<CFDictionary<CFString, CFType>>> = unsafe {
let list = CGWindowListCopyWindowInfo(CGWindowListOption::all(), kCGNullWindowID);
let Some(list) = list else {
return Err(Error::NoWindowEnvironment);
};
CFRetained::cast_unchecked(list)
};
let windows = list
.iter()
.map(|dict| Window(MacOSWindow::new(WindowInfo::new(dict))))
.collect();
Ok(windows)
}
pub mod window {
use std::mem::MaybeUninit;
use objc2_core_foundation::CGRect;
use objc2_core_graphics::CGRectMakeWithDictionaryRepresentation;
use crate::Bounds;
use super::{MacOSError, WindowInfo};
#[derive(Clone, Debug)]
pub struct MacOSWindow(pub(crate) WindowInfo);
impl MacOSWindow {
pub fn new(window_info: WindowInfo) -> Self {
Self(window_info)
}
pub fn window_info(&self) -> &WindowInfo {
&self.0
}
pub fn into_window_info(self) -> WindowInfo {
self.0
}
pub fn id(&self) -> u32 {
self.0
.number()
.as_i64()
.expect("invalid window number value") as _
}
pub fn title(&self) -> Option<String> {
self.0.name().map(|name| name.to_string())
}
pub fn bounds(&self) -> Result<Bounds, MacOSError> {
let bounds = self.0.bounds();
let mut rect = MaybeUninit::<CGRect>::uninit();
unsafe {
let result =
CGRectMakeWithDictionaryRepresentation(Some(&bounds), rect.as_mut_ptr());
if result {
Ok(rect.assume_init().into())
} else {
Err(MacOSError::InvalidWindowBounds)
}
}
}
pub fn owner_pid(&self) -> i32 {
self.0
.owner_pid()
.as_i32()
.expect("invalid owner PID value")
}
pub fn owner_name(&self) -> Option<String> {
self.0.owner_name().map(|name| name.to_string())
}
}
}
pub mod window_info {
use objc2_core_foundation::{CFBoolean, CFDictionary, CFNumber, CFRetained, CFString, CFType};
macro_rules! impl_window_info_getters {
($(($name:ident, $return_type:ty, $key:ident)),*) => {
$(
pub fn $name(&self) -> CFRetained<$return_type> {
let object = self
.0
.get(unsafe { objc2_core_graphics::$key })
.expect(concat!("`", stringify!($key), "` should always be present"));
const EXPECT: &str = concat!(
"Expected a value `",
stringify!($return_type),
"` for the key `",
stringify!($key),
"`"
);
CFRetained::downcast(object).expect(EXPECT)
}
)*
};
}
macro_rules! impl_window_info_optional_getters {
($(($name:ident, $return_type:ty, $key:ident)),*) => {
$(
pub fn $name(&self) -> Option<CFRetained<$return_type>> {
let object = self
.0
.get(unsafe { objc2_core_graphics::$key })?;
const EXPECT: &str = concat!(
"Expected a value `",
stringify!($return_type),
"` for the key `",
stringify!($key),
"`"
);
Some(CFRetained::downcast(object).expect(EXPECT))
}
)*
};
}
pub type WindowInfoDict = CFRetained<CFDictionary<CFString, CFType>>;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct WindowInfo(WindowInfoDict);
unsafe impl Send for WindowInfo {}
unsafe impl Sync for WindowInfo {}
impl WindowInfo {
pub fn new(dict: WindowInfoDict) -> Self {
Self(dict)
}
pub fn window_info_dict(&self) -> &WindowInfoDict {
&self.0
}
pub fn into_window_info_dict(self) -> WindowInfoDict {
self.0
}
impl_window_info_getters!(
(number, CFNumber, kCGWindowNumber),
(store_type, CFNumber, kCGWindowStoreType),
(layer, CFNumber, kCGWindowLayer),
(bounds, CFDictionary, kCGWindowBounds),
(sharing_state, CFNumber, kCGWindowSharingState),
(alpha, CFNumber, kCGWindowAlpha),
(owner_pid, CFNumber, kCGWindowOwnerPID),
(memory_usage, CFNumber, kCGWindowMemoryUsage)
);
impl_window_info_optional_getters!(
(owner_name, CFString, kCGWindowOwnerName),
(name, CFString, kCGWindowName),
(is_on_screen, CFBoolean, kCGWindowIsOnscreen),
(
backing_location_video_memory,
CFBoolean,
kCGWindowBackingLocationVideoMemory
)
);
}
}
pub mod permission {
pub fn request_screen_capture_access() -> bool {
objc2_core_graphics::CGRequestScreenCaptureAccess()
}
pub fn has_screen_capture_access() -> bool {
objc2_core_graphics::CGPreflightScreenCaptureAccess()
}
}
pub mod error {
#[derive(Debug, thiserror::Error)]
pub enum MacOSError {
#[error("Failed to make window `CGRect` from dictionary representation.")]
InvalidWindowBounds,
}
impl From<MacOSError> for crate::Error {
fn from(error: MacOSError) -> Self {
Self::PlatformSpecificError(error)
}
}
}