From 59e58166b95b5b0bd3b79fabcf005bcb84e5d4cf Mon Sep 17 00:00:00 2001 From: chanderlud Date: Wed, 15 Oct 2025 13:10:45 -0700 Subject: [PATCH] adds stop_grab and stop_listen functions for windows targets --- Cargo.toml | 2 +- src/lib.rs | 20 ++++++++++++++++++-- src/windows/grab.rs | 28 +++++++++++++++++++++++++--- src/windows/listen.rs | 25 ++++++++++++++++++++++--- src/windows/mod.rs | 4 ++-- tests/grab.rs | 16 +++++++++++++++- 6 files changed, 83 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 569998e8..ed55965f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rdev" -version = "0.6.0" +version = "0.6.1" authors = ["Nicolas Patry "] edition = "2024" diff --git a/src/lib.rs b/src/lib.rs index 9394e152..6d7fb5d6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -242,7 +242,10 @@ mod windows; #[cfg(target_os = "windows")] pub use crate::windows::Keyboard; #[cfg(target_os = "windows")] -use crate::windows::{display_size as _display_size, listen as _listen, simulate as _simulate}; +use crate::windows::{ + display_size as _display_size, listen as _listen, simulate as _simulate, + stop_listen as _stop_listen, +}; /// Listening to global events. Caveat: On MacOS, you require the listen /// loop needs to be the primary app (no fork before) and need to have accessibility @@ -272,6 +275,12 @@ where _listen(callback) } +#[cfg(target_os = "windows")] +/// Stops the current listen +pub fn stop_listen() { + _stop_listen(); +} + /// Sending some events /// /// ```no_run @@ -330,7 +339,7 @@ pub use crate::linux::grab as _grab; pub use crate::macos::grab as _grab; #[cfg(feature = "unstable_grab")] #[cfg(target_os = "windows")] -pub use crate::windows::grab as _grab; +pub use crate::windows::{grab as _grab, stop_grab as _stop_grab}; #[cfg(feature = "unstable_grab")] /// Grabbing global events. In the callback, returning None ignores the event /// and returning the event let's it pass. There is no modification of the event @@ -364,6 +373,13 @@ where _grab(callback) } +#[cfg(feature = "unstable_grab")] +#[cfg(target_os = "windows")] +/// Stops the current grab +pub fn stop_grab() { + _stop_grab(); +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/windows/grab.rs b/src/windows/grab.rs index 45a5664e..0a4cc796 100644 --- a/src/windows/grab.rs +++ b/src/windows/grab.rs @@ -1,10 +1,16 @@ use crate::rdev::{Event, EventType, GrabError}; use crate::windows::common::{HOOK, HookError, KEYBOARD, convert, set_key_hook, set_mouse_hook}; -use std::ptr::null_mut; +use std::mem::zeroed; +use std::sync::atomic::{AtomicU32, Ordering}; use std::time::SystemTime; -use winapi::um::winuser::{CallNextHookEx, GetMessageA, HC_ACTION}; +use winapi::um::processthreadsapi::GetCurrentThreadId; +use winapi::um::winuser::{ + CallNextHookEx, DispatchMessageA, GetMessageA, HC_ACTION, MSG, PostThreadMessageA, + TranslateMessage, WM_QUIT, +}; static mut GLOBAL_CALLBACK: Option Option>> = None; +static HOOK_THREAD_ID: AtomicU32 = AtomicU32::new(0); unsafe extern "system" fn raw_callback(code: i32, param: usize, lpdata: isize) -> isize { unsafe { @@ -56,7 +62,23 @@ where set_key_hook(raw_callback)?; set_mouse_hook(raw_callback)?; - GetMessageA(null_mut(), null_mut(), 0, 0); + let tid = GetCurrentThreadId(); + HOOK_THREAD_ID.store(tid, Ordering::SeqCst); + + let mut msg: MSG = zeroed(); + while GetMessageA(&mut msg, Default::default(), 0, 0) == 1 { + TranslateMessage(&msg); + DispatchMessageA(&msg); + } } Ok(()) } + +pub fn stop_grab() { + let tid = HOOK_THREAD_ID.load(Ordering::SeqCst); + if tid != 0 { + unsafe { + PostThreadMessageA(tid, WM_QUIT, 0, 0); + } + } +} diff --git a/src/windows/listen.rs b/src/windows/listen.rs index f5c2e733..e7fe220e 100644 --- a/src/windows/listen.rs +++ b/src/windows/listen.rs @@ -1,12 +1,15 @@ +use std::mem::zeroed; use crate::rdev::{Event, EventType, ListenError}; use crate::windows::common::{HOOK, HookError, KEYBOARD, convert, set_key_hook, set_mouse_hook}; use std::os::raw::c_int; -use std::ptr::null_mut; +use std::sync::atomic::{AtomicU32, Ordering}; use std::time::SystemTime; use winapi::shared::minwindef::{LPARAM, LRESULT, WPARAM}; -use winapi::um::winuser::{CallNextHookEx, GetMessageA, HC_ACTION}; +use winapi::um::processthreadsapi::GetCurrentThreadId; +use winapi::um::winuser::{CallNextHookEx, GetMessageA, HC_ACTION, PostThreadMessageA, WM_QUIT, MSG, TranslateMessage, DispatchMessageA}; static mut GLOBAL_CALLBACK: Option> = None; +static HOOK_THREAD_ID: AtomicU32 = AtomicU32::new(0); impl From for ListenError { fn from(error: HookError) -> Self { @@ -53,7 +56,23 @@ where set_key_hook(raw_callback)?; set_mouse_hook(raw_callback)?; - GetMessageA(null_mut(), null_mut(), 0, 0); + let tid = GetCurrentThreadId(); + HOOK_THREAD_ID.store(tid, Ordering::SeqCst); + + let mut msg: MSG = zeroed(); + while GetMessageA(&mut msg, Default::default(), 0, 0) == 1 { + TranslateMessage(&msg); + DispatchMessageA(&msg); + } } Ok(()) } + +pub fn stop_listen() { + let tid = HOOK_THREAD_ID.load(Ordering::SeqCst); + if tid != 0 { + unsafe { + PostThreadMessageA(tid, WM_QUIT, 0, 0); + } + } +} diff --git a/src/windows/mod.rs b/src/windows/mod.rs index 8ef404f3..6d7f56c2 100644 --- a/src/windows/mod.rs +++ b/src/windows/mod.rs @@ -11,7 +11,7 @@ mod simulate; pub use crate::windows::display::display_size; #[cfg(feature = "unstable_grab")] -pub use crate::windows::grab::grab; +pub use crate::windows::grab::{grab, stop_grab}; pub use crate::windows::keyboard::Keyboard; -pub use crate::windows::listen::listen; +pub use crate::windows::listen::{listen, stop_listen}; pub use crate::windows::simulate::simulate; diff --git a/tests/grab.rs b/tests/grab.rs index 826963d9..4ac7bb2e 100644 --- a/tests/grab.rs +++ b/tests/grab.rs @@ -1,10 +1,11 @@ use lazy_static::lazy_static; -use rdev::{Event, EventType, Key, grab, listen, simulate}; +use rdev::{Event, EventType, Key, grab, listen, simulate, stop_grab}; use serial_test::serial; use std::error::Error; use std::sync::Mutex; use std::sync::mpsc::{Receiver, RecvTimeoutError, Sender, channel}; use std::thread; +use std::thread::sleep; use std::time::Duration; lazy_static! { @@ -70,3 +71,16 @@ fn test_grab() -> Result<(), Box> { }; Ok(()) } + +#[test] +fn test_stop_grab() { + let grab = thread::spawn(move || { + grab(grab_tab).expect("Could not grab"); + }); + + sleep(Duration::from_millis(100)); + + stop_grab(); + + grab.join().expect("Could not stop grab"); +}