Skip to main content

automata_windows/
mouse_hook.rs

1// ── Non-Windows stub ──────────────────────────────────────────────────────────
2
3#[cfg(not(target_os = "windows"))]
4use crate::Result;
5
6#[cfg(not(target_os = "windows"))]
7pub struct MouseHook;
8
9#[cfg(not(target_os = "windows"))]
10impl MouseHook {
11    pub fn start() -> Result<Self> {
12        anyhow::bail!("Windows only")
13    }
14    pub fn receiver(&self) -> &std::sync::mpsc::Receiver<(i32, i32)> {
15        unimplemented!()
16    }
17}
18
19// ── Windows implementation ────────────────────────────────────────────────────
20
21#[cfg(target_os = "windows")]
22pub use win::MouseHook;
23
24#[cfg(target_os = "windows")]
25mod win {
26    use std::cell::RefCell;
27    use std::sync::mpsc;
28    use std::thread;
29
30    use windows::Win32::Foundation::{LPARAM, LRESULT, WPARAM};
31    use windows::Win32::UI::WindowsAndMessaging::{
32        CallNextHookEx, DispatchMessageW, GetCursorPos, GetMessageW, HC_ACTION, HHOOK, MSG,
33        SetWindowsHookExW, UnhookWindowsHookEx, WH_MOUSE_LL, WM_MOUSEMOVE,
34    };
35
36    use crate::Result;
37
38    thread_local! {
39        static HOOK_SENDER: RefCell<Option<mpsc::SyncSender<(i32, i32)>>> =
40            RefCell::new(None);
41    }
42
43    unsafe extern "system" fn hook_proc(code: i32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
44        if code == HC_ACTION as i32 && wparam.0 as u32 == WM_MOUSEMOVE {
45            let mut pt = windows::Win32::Foundation::POINT::default();
46            if unsafe { GetCursorPos(&mut pt) }.is_ok() {
47                HOOK_SENDER.with(|cell| {
48                    if let Some(tx) = cell.borrow().as_ref() {
49                        let _ = tx.try_send((pt.x, pt.y));
50                    }
51                });
52            }
53        }
54        unsafe { CallNextHookEx(Some(HHOOK::default()), code, wparam, lparam) }
55    }
56
57    /// Installs a low-level mouse hook on a background thread.
58    /// Sends cursor `(x, y)` on each mouse move; bounded to 1 so the consumer always
59    /// sees the latest position.
60    pub struct MouseHook {
61        receiver: mpsc::Receiver<(i32, i32)>,
62        _thread: thread::JoinHandle<()>,
63    }
64
65    impl MouseHook {
66        pub fn start() -> Result<Self> {
67            let (tx, rx) = mpsc::sync_channel::<(i32, i32)>(1);
68
69            let handle = thread::spawn(move || unsafe {
70                HOOK_SENDER.with(|cell| *cell.borrow_mut() = Some(tx));
71
72                let hook = SetWindowsHookExW(WH_MOUSE_LL, Some(hook_proc), None, 0)
73                    .expect("SetWindowsHookExW");
74
75                let mut msg = MSG::default();
76                while GetMessageW(&mut msg, None, 0, 0).as_bool() {
77                    let _ = DispatchMessageW(&msg);
78                }
79                UnhookWindowsHookEx(hook).ok();
80            });
81
82            Ok(Self {
83                receiver: rx,
84                _thread: handle,
85            })
86        }
87
88        /// Iterate with `.receiver().iter()` to receive `(x, y)` on each mouse move.
89        pub fn receiver(&self) -> &mpsc::Receiver<(i32, i32)> {
90            &self.receiver
91        }
92    }
93}