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). On macOS the lookup runs on the
45/// main queue, so the process must be running its AppKit loop; otherwise every
46/// call returns [`PointerTarget::Unavailable`] after a short timeout.
47#[must_use]
48pub fn pointer_context() -> PointerContext {
49 if !pointer_context_supported() {
50 return PointerContext {
51 app: None,
52 target: PointerTarget::Unsupported,
53 };
54 }
55 #[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
56 if let Some(context) = platform::pointer_context() {
57 return context;
58 }
59 PointerContext {
60 app: None,
61 target: PointerTarget::Unavailable,
62 }
63}
64
65/// Whether this session has a pointer-window backend; does not probe permissions
66/// or connect to the window server. Unsupported Wayland never falls back to X11.
67#[must_use]
68pub fn pointer_context_supported() -> bool {
69 #[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
70 return platform::pointer_context_supported();
71 #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
72 false
73}
74
75/// Verify that this exact window, not merely another window of its application,
76/// is focused now. Returns false on any lookup failure or non-window target.
77///
78/// Like [`pointer_context`], this may block and must never run in an input hook.
79/// It only observes focus and never changes it.
80#[must_use]
81pub fn pointer_target_is_focused(target: PointerTarget) -> bool {
82 if !matches!(target, PointerTarget::Window { .. }) || !pointer_context_supported() {
83 return false;
84 }
85 #[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
86 return platform::pointer_target_is_focused(target);
87 #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
88 false
89}