use std::ptr::NonNull;
use std::sync::LazyLock;
use std::sync::mpsc;
use std::time::Duration;
use dispatch2::DispatchQueue;
use objc2::MainThreadMarker;
use objc2_app_kit::{NSEvent, NSRunningApplication, NSWindow, NSWorkspace};
use objc2_application_services::{AXError, AXUIElement};
use objc2_core_foundation::{CFArray, CFDictionary, CFNumber, CFRetained, CFString, CFType};
use objc2_core_graphics::{
CGWindowLevelForKey, CGWindowLevelKey, CGWindowListCopyWindowInfo, CGWindowListOption,
kCGWindowLayer, kCGWindowNumber, kCGWindowOwnerPID,
};
use super::foreground::foreground_app_from_running_application;
use crate::{ForegroundApp, PointerContext, PointerTarget};
type Dictionary = CFDictionary<CFString, CFType>;
const MAIN_THREAD_HIT_TEST_TIMEOUT: Duration = Duration::from_millis(100);
pub(crate) fn pointer_context_supported() -> bool {
true
}
fn dictionary(value: CFRetained<CFType>) -> Option<CFRetained<Dictionary>> {
let value = value.downcast::<CFDictionary>().ok()?;
Some(unsafe { CFRetained::cast_unchecked::<Dictionary>(value) })
}
fn number(info: &Dictionary, key: &CFString) -> Option<CFRetained<CFNumber>> {
info.get(key)?.downcast::<CFNumber>().ok()
}
const SYSTEM_OVERLAY_OWNERS: [&str; 2] = ["com.apple.dock", "com.apple.WindowManager"];
fn target(info: &Dictionary, owner: Option<&ForegroundApp>) -> Option<PointerTarget> {
let layer = number(info, unsafe { kCGWindowLayer })?.as_i32()?;
let window_id = number(info, unsafe { kCGWindowNumber }).and_then(|n| n.as_i64());
classify(layer, owner, owner_pid(info), window_id)
}
fn classify(
layer: i32,
owner: Option<&ForegroundApp>,
process_id: Option<i32>,
window_id: Option<i64>,
) -> Option<PointerTarget> {
if layer == CGWindowLevelForKey(CGWindowLevelKey::DesktopWindowLevelKey)
|| layer == CGWindowLevelForKey(CGWindowLevelKey::DesktopIconWindowLevelKey)
{
return Some(PointerTarget::Desktop);
}
if layer != CGWindowLevelForKey(CGWindowLevelKey::NormalWindowLevelKey) {
let system = owner.is_some_and(|app| SYSTEM_OVERLAY_OWNERS.contains(&app.id.as_str()));
return Some(if system {
PointerTarget::Desktop
} else {
PointerTarget::Unavailable
});
}
Some(PointerTarget::Window {
process_id: process_id?,
window_id: u64::try_from(window_id?).ok().filter(|id| *id > 0)?,
})
}
fn owner_pid(info: &Dictionary) -> Option<i32> {
let pid = number(info, unsafe { kCGWindowOwnerPID })?.as_i32()?;
(pid > 0).then_some(pid)
}
fn owner_app(process_id: i32) -> Option<ForegroundApp> {
objc2::rc::autoreleasepool(|pool| {
let app = NSRunningApplication::runningApplicationWithProcessIdentifier(process_id)?;
foreground_app_from_running_application(&app, pool)
})
}
fn window_number_under_pointer() -> Option<isize> {
let (tx, rx) = mpsc::sync_channel(1);
DispatchQueue::main().exec_async(move || {
let Some(mtm) = MainThreadMarker::new() else {
return;
};
let _ = tx.send(NSWindow::windowNumberAtPoint_belowWindowWithWindowNumber(
NSEvent::mouseLocation(),
0,
mtm,
));
});
rx.recv_timeout(MAIN_THREAD_HIT_TEST_TIMEOUT).ok()
}
pub(crate) fn pointer_context() -> Option<PointerContext> {
let window_number = window_number_under_pointer()?;
let window_id = u32::try_from(window_number).ok().filter(|id| *id != 0)?;
let windows = CGWindowListCopyWindowInfo(CGWindowListOption::OptionIncludingWindow, window_id)?;
let windows = unsafe { CFRetained::cast_unchecked::<CFArray<CFType>>(windows) };
let info = dictionary(windows.into_iter().next()?)?;
let owner = owner_pid(&info).and_then(owner_app);
let target = target(&info, owner.as_ref())?;
let app = match target {
PointerTarget::Window { .. } => Some(owner?),
_ => None,
};
Some(PointerContext { app, target })
}
type WindowIdFn = unsafe extern "C" fn(*const AXUIElement, *mut u32) -> AXError;
static WINDOW_ID: LazyLock<Option<WindowIdFn>> = LazyLock::new(|| {
let symbol = unsafe { libc::dlsym(libc::RTLD_DEFAULT, c"_AXUIElementGetWindow".as_ptr()) };
if symbol.is_null() {
return None;
}
Some(unsafe { std::mem::transmute::<*mut libc::c_void, WindowIdFn>(symbol) })
});
pub(crate) fn pointer_target_is_focused(target: PointerTarget) -> bool {
let PointerTarget::Window {
process_id,
window_id,
} = target
else {
return false;
};
focused_window_id(process_id) == Some(window_id)
}
fn focused_window_id(pid: i32) -> Option<u64> {
let get_window_id = (*WINDOW_ID)?;
objc2::rc::autoreleasepool(|_| {
let workspace = NSWorkspace::sharedWorkspace();
if workspace.frontmostApplication()?.processIdentifier() != pid {
return None;
}
let app = unsafe { AXUIElement::new_application(pid) };
if unsafe { app.set_messaging_timeout(0.1) } != AXError::Success {
return None;
}
let mut value = std::ptr::null();
let attr = CFString::from_static_str("AXFocusedWindow");
if unsafe { app.copy_attribute_value(&attr, NonNull::from(&mut value)) } != AXError::Success
{
return None;
}
let value = NonNull::new(value.cast_mut())?;
let value = unsafe { CFRetained::from_raw(value) };
let window = value.downcast::<AXUIElement>().ok()?;
if unsafe { window.set_messaging_timeout(0.1) } != AXError::Success {
return None;
}
let mut id = 0;
if unsafe { get_window_id(&raw const *window, &raw mut id) } != AXError::Success || id == 0
{
return None;
}
if workspace.frontmostApplication()?.processIdentifier() != pid {
return None;
}
Some(u64::from(id))
})
}
#[cfg(test)]
mod tests {
use super::*;
fn app(id: &str) -> ForegroundApp {
ForegroundApp {
id: id.into(),
display_name: id.into(),
}
}
fn level(key: CGWindowLevelKey) -> i32 {
CGWindowLevelForKey(key)
}
#[test]
fn desktop_layers_are_desktop() {
for key in [
CGWindowLevelKey::DesktopWindowLevelKey,
CGWindowLevelKey::DesktopIconWindowLevelKey,
] {
assert_eq!(
classify(level(key), None, None, None),
Some(PointerTarget::Desktop)
);
}
}
#[test]
fn normal_layer_is_the_owning_window() {
assert_eq!(
classify(
level(CGWindowLevelKey::NormalWindowLevelKey),
None,
Some(41),
Some(7)
),
Some(PointerTarget::Window {
process_id: 41,
window_id: 7
})
);
}
#[test]
fn normal_layer_with_missing_or_invalid_ids_fails_closed() {
let normal = level(CGWindowLevelKey::NormalWindowLevelKey);
for (pid, window) in [
(None, Some(7)),
(Some(41), None),
(Some(41), Some(0)),
(Some(41), Some(-3)),
] {
assert_eq!(classify(normal, None, pid, window), None);
}
}
#[test]
fn mission_control_overlays_count_as_desktop() {
let dock = level(CGWindowLevelKey::DockWindowLevelKey);
for owner in SYSTEM_OVERLAY_OWNERS {
assert_eq!(
classify(dock, Some(&app(owner)), Some(41), Some(7)),
Some(PointerTarget::Desktop)
);
}
}
#[test]
fn other_overlays_stay_unavailable() {
let floating = level(CGWindowLevelKey::FloatingWindowLevelKey);
assert_eq!(
classify(
floating,
Some(&app("com.apple.notificationcenterui")),
Some(41),
Some(7)
),
Some(PointerTarget::Unavailable)
);
assert_eq!(
classify(floating, None, None, None),
Some(PointerTarget::Unavailable)
);
}
}