openlogi_hook/pointer.rs
1//! Blocking, read-only pointer-window queries, independent of foreground caches.
2
3use crate::ForegroundApp;
4
5#[cfg(target_os = "linux")]
6use crate::linux::pointer as platform;
7#[cfg(target_os = "macos")]
8use crate::macos::pointer as platform;
9#[cfg(target_os = "windows")]
10use crate::windows::pointer as platform;
11
12/// Native window identity under the pointer, or an explicit non-window outcome.
13#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub enum PointerTarget {
15 /// A window identified by both its owner and native window-server handle.
16 Window {
17 /// Native process identifier.
18 process_id: i32,
19 /// CGWindowID, HWND, or X11 client-window XID. Not durable across restarts.
20 window_id: u64,
21 },
22 /// Positively identified desktop background or desktop icons.
23 Desktop,
24 /// A query failed, or the pointer is over an unidentifiable surface.
25 Unavailable,
26 /// This session cannot expose native pointer-window identity (e.g. Wayland).
27 Unsupported,
28}
29
30/// Application identity and native window captured by one pointer lookup.
31#[derive(Clone, Debug, PartialEq, Eq)]
32pub struct PointerContext {
33 /// Uses exactly the same profile ID namespace as the foreground app lookup.
34 /// Absent for desktop, unsupported sessions, and unavailable lookups.
35 pub app: Option<ForegroundApp>,
36 /// Native target. Missing application metadata never becomes `Desktop`.
37 pub target: PointerTarget,
38}
39
40/// Query the window currently under the pointer, without activating anything.
41///
42/// Blocking window-server / accessibility I/O: call only on a background worker,
43/// never an input hook or event-tap callback. This does not update foreground
44/// caches (including the foreground Safari PID).
45#[must_use]
46pub fn pointer_context() -> PointerContext {
47 if !pointer_context_supported() {
48 return PointerContext {
49 app: None,
50 target: PointerTarget::Unsupported,
51 };
52 }
53 #[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
54 if let Some(context) = platform::pointer_context() {
55 return context;
56 }
57 PointerContext {
58 app: None,
59 target: PointerTarget::Unavailable,
60 }
61}
62
63/// Whether this session has a pointer-window backend; does not probe permissions
64/// or connect to the window server. Unsupported Wayland never falls back to X11.
65#[must_use]
66pub fn pointer_context_supported() -> bool {
67 #[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
68 return platform::pointer_context_supported();
69 #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
70 false
71}
72
73/// Verify that this exact window, not merely another window of its application,
74/// is focused now. Returns false on any lookup failure or non-window target.
75///
76/// Like [`pointer_context`], this may block and must never run in an input hook.
77/// It only observes focus and never changes it.
78#[must_use]
79pub fn pointer_target_is_focused(target: PointerTarget) -> bool {
80 if !matches!(target, PointerTarget::Window { .. }) || !pointer_context_supported() {
81 return false;
82 }
83 #[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
84 return platform::pointer_target_is_focused(target);
85 #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
86 false
87}
88
89#[cfg(any(target_os = "macos", test))]
90pub(crate) mod hit_test;