Skip to main content

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;