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). 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}