diff --git a/Cargo.toml b/Cargo.toml index f0c35050..da1f7d60 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ core-foundation = {version = "0.9.3"} core-foundation-sys = {version = "0.8.3"} core-graphics = {version = "0.22.3", features = ["highsierra"]} dispatch = "0.2" +foreign-types = "0.3" [target.'cfg(target_os = "linux")'.dependencies] epoll = {version = "4.1.0"} diff --git a/src/macos/common.rs b/src/macos/common.rs index 221c170f..de4915ee 100644 --- a/src/macos/common.rs +++ b/src/macos/common.rs @@ -6,6 +6,7 @@ use cocoa::base::id; use core_graphics::{ event::{CGEvent, CGEventFlags, CGEventTapLocation, CGEventType, CGKeyCode, EventField}, event_source::CGEventSourceStateID, + sys::CGEventRef, }; use lazy_static::lazy_static; use std::convert::TryInto; @@ -22,7 +23,6 @@ pub type CFRunLoopSourceRef = id; pub type CFRunLoopRef = id; pub type CFRunLoopMode = id; pub type CGEventTapProxy = id; -pub type CGEventRef = CGEvent; pub type FourCharCode = ::std::os::raw::c_uint; pub type OSType = FourCharCode; pub type PhysicalKeyboardLayoutType = OSType; @@ -94,6 +94,8 @@ extern "C" { ) -> CFRunLoopSourceRef; pub fn CFRunLoopGetCurrent() -> CFRunLoopRef; pub fn CFRunLoopAddSource(rl: CFRunLoopRef, source: CFRunLoopSourceRef, mode: CFRunLoopMode); + // Only the worker-thread regression compares against the main run loop. + #[cfg(test)] pub fn CFRunLoopGetMain() -> CFRunLoopRef; pub fn CGEventTapEnable(tap: CFMachPortRef, enable: bool); pub fn CFRunLoopRun(); diff --git a/src/macos/grab.rs b/src/macos/grab.rs index 9c763385..c1374e4c 100644 --- a/src/macos/grab.rs +++ b/src/macos/grab.rs @@ -3,10 +3,19 @@ use crate::macos::common::*; use crate::rdev::{Event, GrabError}; use cocoa::base::nil; use cocoa::foundation::NSAutoreleasePool; -use core_graphics::event::{CGEventTapLocation, CGEventType}; +use core_graphics::{ + event::{CGEvent, CGEventTapLocation, CGEventType}, + sys::CGEventRef, +}; +use foreign_types::ForeignType; +use std::cell::RefCell; +use std::mem::ManuallyDrop; use std::os::raw::c_void; -static mut GLOBAL_CALLBACK: Option Option>> = None; +thread_local! { + static GLOBAL_CALLBACK: RefCell Option>>> = + RefCell::new(None); +} unsafe extern "C" fn raw_callback( _proxy: CGEventTapProxy, @@ -14,22 +23,55 @@ unsafe extern "C" fn raw_callback( cg_event: CGEventRef, _user_info: *mut c_void, ) -> CGEventRef { - // println!("Event ref {:?}", cg_event_ptr); - // let cg_event: CGEvent = transmute_copy::<*mut c_void, CGEvent>(&cg_event_ptr); + if cg_event.is_null() { + return cg_event; + } + let cg_event_ref = ManuallyDrop::new(CGEvent::from_ptr(cg_event)); if let Ok(mut state) = KEYBOARD_STATE.lock() { if let Some(keyboard) = state.as_mut() { - if let Some(event) = convert(_type, &cg_event, keyboard) { - if let Some(callback) = &mut GLOBAL_CALLBACK { - if callback(event).is_none() { - cg_event.set_type(CGEventType::Null); + if let Some(event) = convert(_type, &cg_event_ref, keyboard) { + GLOBAL_CALLBACK.with(|slot| { + if let Ok(mut callback) = slot.try_borrow_mut() { + if let Some(callback) = callback.as_mut() { + if callback(event).is_none() { + cg_event_ref.set_type(CGEventType::Null); + } + } } - } + }); } } } cg_event } +#[cfg(test)] +mod tests { + use super::*; + use core_graphics::event::CGEvent; + use core_graphics::event_source::{CGEventSource, CGEventSourceStateID}; + + #[test] + fn callback_borrows_the_system_event() { + let _: QCallback = raw_callback; + let source = CGEventSource::new(CGEventSourceStateID::CombinedSessionState).unwrap(); + let event = CGEvent::new(source).unwrap(); + let event_ptr = event.as_ptr(); + + let returned = unsafe { + raw_callback( + nil, + CGEventType::MouseMoved, + event_ptr, + std::ptr::null_mut(), + ) + }; + + assert_eq!(returned, event_ptr); + assert!(!event.location().x.is_nan()); + } +} + static mut CUR_LOOP: CFRunLoopSourceRef = std::ptr::null_mut(); #[inline] @@ -48,7 +90,7 @@ where } unsafe { - GLOBAL_CALLBACK = Some(Box::new(callback)); + GLOBAL_CALLBACK.with(|slot| slot.replace(Some(Box::new(callback)))); let _pool = NSAutoreleasePool::new(nil); let tap = CGEventTapCreate( CGEventTapLocation::Session, // HID, Session, AnnotatedSession, diff --git a/src/macos/listen.rs b/src/macos/listen.rs index 6ca4098a..975e3bc5 100644 --- a/src/macos/listen.rs +++ b/src/macos/listen.rs @@ -3,10 +3,22 @@ use crate::macos::common::*; use crate::rdev::{Event, ListenError}; use cocoa::base::nil; use cocoa::foundation::NSAutoreleasePool; -use core_graphics::event::{CGEventTapLocation, CGEventType}; +use core_graphics::{ + event::{CGEvent, CGEventTapLocation, CGEventType}, + sys::CGEventRef, +}; +use foreign_types::ForeignType; +use std::cell::RefCell; +use std::mem::ManuallyDrop; use std::os::raw::c_void; -static mut GLOBAL_CALLBACK: Option> = None; +thread_local! { + static GLOBAL_CALLBACK: RefCell>> = RefCell::new(None); +} + +fn callback_run_loop() -> CFRunLoopRef { + unsafe { CFRunLoopGetCurrent() } +} unsafe extern "C" fn raw_callback( _proxy: CGEventTapProxy, @@ -14,19 +26,24 @@ unsafe extern "C" fn raw_callback( cg_event: CGEventRef, _user_info: *mut c_void, ) -> CGEventRef { - // println!("Event ref {:?}", cg_event_ptr); - // let cg_event: CGEvent = transmute_copy::<*mut c_void, CGEvent>(&cg_event_ptr); + if cg_event.is_null() { + return cg_event; + } + let cg_event_ref = ManuallyDrop::new(CGEvent::from_ptr(cg_event)); if let Ok(mut state) = KEYBOARD_STATE.lock() { if let Some(keyboard) = state.as_mut() { - if let Some(event) = convert(_type, &cg_event, keyboard) { - if let Some(callback) = &mut GLOBAL_CALLBACK { - callback(event); - } + if let Some(event) = convert(_type, &cg_event_ref, keyboard) { + GLOBAL_CALLBACK.with(|slot| { + if let Ok(mut callback) = slot.try_borrow_mut() { + if let Some(callback) = callback.as_mut() { + callback(event); + } + } + }); } } } // println!("Event ref END {:?}", cg_event_ptr); - // cg_event_ptr cg_event } @@ -41,7 +58,7 @@ where + (1 << CGEventType::FlagsChanged as u64); } unsafe { - GLOBAL_CALLBACK = Some(Box::new(callback)); + GLOBAL_CALLBACK.with(|slot| slot.replace(Some(Box::new(callback)))); let _pool = NSAutoreleasePool::new(nil); let tap = CGEventTapCreate( CGEventTapLocation::HID, // HID, Session, AnnotatedSession, @@ -59,7 +76,7 @@ where return Err(ListenError::LoopSourceError); } - let current_loop = CFRunLoopGetMain(); + let current_loop = callback_run_loop(); CFRunLoopAddSource(current_loop, _loop, kCFRunLoopCommonModes); CGEventTapEnable(tap, true); @@ -67,3 +84,18 @@ where } Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn worker_listener_uses_its_own_run_loop() { + let main_loop = unsafe { CFRunLoopGetMain() } as usize; + let worker_loop = std::thread::spawn(|| callback_run_loop() as usize) + .join() + .unwrap(); + + assert_ne!(worker_loop, main_loop); + } +}