From bbdf6ff1f39d8b19d263888d289ad78c9ba60ec7 Mon Sep 17 00:00:00 2001 From: Nicolas Patry Date: Mon, 23 Jun 2025 12:25:04 +0200 Subject: [PATCH 1/4] Moving to objc2 ? --- Cargo.toml | 8 ++- src/macos/common.rs | 160 +++++++++++++----------------------------- src/macos/display.rs | 16 ++++- src/macos/keyboard.rs | 6 +- src/macos/keycodes.rs | 3 +- src/macos/listen.rs | 60 ++++++++-------- src/macos/mod.rs | 8 +-- src/macos/simulate.rs | 99 ++++++++++++++------------ 8 files changed, 163 insertions(+), 197 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ddfd670c..569998e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,11 +26,15 @@ wayland = ["input", "input-linux", "xkbcommon"] x11 = ["dep:x11"] [target.'cfg(target_os = "macos")'.dependencies] -cocoa = "0.26" -core-graphics = {version = "0.24.0", features = ["highsierra"]} +# cocoa = "0.26" +# core-graphics = {version = "0.24.0", features = ["highsierra"]} core-foundation = {version = "0.10"} core-foundation-sys = {version = "0.8"} dispatch = "0.2" +objc2-foundation = "0.3.1" +objc2-core-graphics = "0.3.1" +objc2-core-foundation = "0.3.1" +objc2 = "0.6.1" [target.'cfg(all(target_family = "unix", not(target_os = "macos")))'.dependencies] diff --git a/src/macos/common.rs b/src/macos/common.rs index c53e8863..d15ad270 100644 --- a/src/macos/common.rs +++ b/src/macos/common.rs @@ -1,103 +1,27 @@ #![allow(clippy::upper_case_acronyms)] use crate::macos::keyboard::Keyboard; use crate::rdev::{Button, Event, EventType}; -use cocoa::base::id; -use core_graphics::event::{CGEvent, CGEventFlags, CGEventTapLocation, CGEventType, EventField}; +use core::ptr::NonNull; use lazy_static::lazy_static; +use objc2_core_graphics::{CGEvent, CGEventField, CGEventFlags, CGEventType}; use std::convert::TryInto; -use std::os::raw::c_void; use std::sync::Mutex; use std::time::SystemTime; use crate::macos::keycodes::key_from_code; -pub type CFMachPortRef = *const c_void; -pub type CFIndex = u64; -pub type CFAllocatorRef = id; -pub type CFRunLoopSourceRef = id; -pub type CFRunLoopRef = id; -pub type CFRunLoopMode = id; -pub type CGEventTapProxy = id; -pub type CGEventRef = CGEvent; - -// https://developer.apple.com/documentation/coregraphics/cgeventtapplacement?language=objc -pub type CGEventTapPlacement = u32; -#[allow(non_upper_case_globals)] -pub const kCGHeadInsertEventTap: u32 = 0; - -// https://developer.apple.com/documentation/coregraphics/cgeventtapoptions?language=objc -#[allow(non_upper_case_globals)] -#[repr(u32)] -pub enum CGEventTapOption { - #[cfg(feature = "unstable_grab")] - Default = 0, - ListenOnly = 1, -} - lazy_static! { - pub static ref LAST_FLAGS: Mutex = Mutex::new(CGEventFlags::CGEventFlagNull); + pub static ref LAST_FLAGS: Mutex = Mutex::new(CGEventFlags(0)); pub static ref KEYBOARD_STATE: Mutex = Mutex::new(Keyboard::new().unwrap()); } -// https://developer.apple.com/documentation/coregraphics/cgeventmask?language=objc -pub type CGEventMask = u64; -#[allow(non_upper_case_globals)] -pub const kCGEventMaskForAllEvents: u64 = (1 << CGEventType::LeftMouseDown as u64) - + (1 << CGEventType::LeftMouseUp as u64) - + (1 << CGEventType::RightMouseDown as u64) - + (1 << CGEventType::RightMouseUp as u64) - + (1 << CGEventType::MouseMoved as u64) - + (1 << CGEventType::LeftMouseDragged as u64) - + (1 << CGEventType::RightMouseDragged as u64) - + (1 << CGEventType::KeyDown as u64) - + (1 << CGEventType::KeyUp as u64) - + (1 << CGEventType::FlagsChanged as u64) - + (1 << CGEventType::ScrollWheel as u64); - -#[cfg(target_os = "macos")] -#[link(name = "Cocoa", kind = "framework")] -unsafe extern "C" { - #[allow(improper_ctypes)] - pub fn CGEventTapCreate( - tap: CGEventTapLocation, - place: CGEventTapPlacement, - options: CGEventTapOption, - eventsOfInterest: CGEventMask, - callback: QCallback, - user_info: id, - ) -> CFMachPortRef; - pub fn CFMachPortCreateRunLoopSource( - allocator: CFAllocatorRef, - tap: CFMachPortRef, - order: CFIndex, - ) -> CFRunLoopSourceRef; - pub fn CFRunLoopAddSource(rl: CFRunLoopRef, source: CFRunLoopSourceRef, mode: CFRunLoopMode); - pub fn CFRunLoopGetCurrent() -> CFRunLoopRef; - pub fn CGEventTapEnable(tap: CFMachPortRef, enable: bool); - pub fn CFRunLoopRun(); - - pub static kCFRunLoopCommonModes: CFRunLoopMode; - -} - -// TODO Remove this, this was added as the coded -// existed and worked, but clippy is complaining. -// There's probably a better fix. -#[allow(improper_ctypes_definitions)] -pub type QCallback = unsafe extern "C" fn( - proxy: CGEventTapProxy, - _type: CGEventType, - cg_event: CGEventRef, - user_info: *mut c_void, -) -> CGEventRef; - pub fn set_is_main_thread(b: bool) { KEYBOARD_STATE.lock().unwrap().set_is_main_thread(b); } pub unsafe fn convert( _type: CGEventType, - cg_event: &CGEvent, + cg_event: NonNull, keyboard_state: &mut Keyboard, ) -> Option { unsafe { @@ -107,81 +31,91 @@ pub unsafe fn convert( CGEventType::RightMouseDown => Some(EventType::ButtonPress(Button::Right)), CGEventType::RightMouseUp => Some(EventType::ButtonRelease(Button::Right)), CGEventType::MouseMoved => { - let point = cg_event.location(); + let point = CGEvent::location(Some(cg_event.as_ref())); + // let point = cg_event.location(); Some(EventType::MouseMove { x: point.x, y: point.y, }) } CGEventType::LeftMouseDragged => { - let point = cg_event.location(); + let point = CGEvent::location(Some(cg_event.as_ref())); Some(EventType::MouseMove { x: point.x, y: point.y, }) } CGEventType::RightMouseDragged => { - let point = cg_event.location(); + let point = CGEvent::location(Some(cg_event.as_ref())); Some(EventType::MouseMove { x: point.x, y: point.y, }) } CGEventType::KeyDown => { - let code = cg_event.get_integer_value_field(EventField::KEYBOARD_EVENT_KEYCODE); + let code = CGEvent::integer_value_field( + Some(cg_event.as_ref()), + CGEventField::KeyboardEventKeycode, + ); let key = key_from_code(code.try_into().ok()?); Some(EventType::KeyPress(key)) } CGEventType::KeyUp => { - let code = cg_event.get_integer_value_field(EventField::KEYBOARD_EVENT_KEYCODE); + let code = CGEvent::integer_value_field( + Some(cg_event.as_ref()), + CGEventField::KeyboardEventKeycode, + ); let key = key_from_code(code.try_into().ok()?); Some(EventType::KeyRelease(key)) } CGEventType::FlagsChanged => { - let code = cg_event.get_integer_value_field(EventField::KEYBOARD_EVENT_KEYCODE); + let code = CGEvent::integer_value_field( + Some(cg_event.as_ref()), + CGEventField::KeyboardEventKeycode, + ); let code = code.try_into().ok()?; - let flags = cg_event.get_flags(); + let flags = CGEvent::flags(Some(cg_event.as_ref())); let key = key_from_code(code); // Determine if this is a press or release based on flag changes let mut global_flags = LAST_FLAGS.lock().unwrap(); - if flags.contains(CGEventFlags::CGEventFlagShift) - && !global_flags.contains(CGEventFlags::CGEventFlagShift) + if flags.contains(CGEventFlags::MaskShift) + && !global_flags.contains(CGEventFlags::MaskShift) { *global_flags = flags; Some(EventType::KeyPress(key)) - } else if !flags.contains(CGEventFlags::CGEventFlagShift) - && global_flags.contains(CGEventFlags::CGEventFlagShift) + } else if !flags.contains(CGEventFlags::MaskShift) + && global_flags.contains(CGEventFlags::MaskShift) { *global_flags = flags; Some(EventType::KeyRelease(key)) - } else if flags.contains(CGEventFlags::CGEventFlagControl) - && !global_flags.contains(CGEventFlags::CGEventFlagControl) + } else if flags.contains(CGEventFlags::MaskControl) + && !global_flags.contains(CGEventFlags::MaskControl) { *global_flags = flags; Some(EventType::KeyPress(key)) - } else if !flags.contains(CGEventFlags::CGEventFlagControl) - && global_flags.contains(CGEventFlags::CGEventFlagControl) + } else if !flags.contains(CGEventFlags::MaskControl) + && global_flags.contains(CGEventFlags::MaskControl) { *global_flags = flags; Some(EventType::KeyRelease(key)) - } else if flags.contains(CGEventFlags::CGEventFlagAlternate) - && !global_flags.contains(CGEventFlags::CGEventFlagAlternate) + } else if flags.contains(CGEventFlags::MaskAlternate) + && !global_flags.contains(CGEventFlags::MaskAlternate) { *global_flags = flags; Some(EventType::KeyPress(key)) - } else if !flags.contains(CGEventFlags::CGEventFlagAlternate) - && global_flags.contains(CGEventFlags::CGEventFlagAlternate) + } else if !flags.contains(CGEventFlags::MaskAlternate) + && global_flags.contains(CGEventFlags::MaskAlternate) { *global_flags = flags; Some(EventType::KeyRelease(key)) - } else if flags.contains(CGEventFlags::CGEventFlagCommand) - && !global_flags.contains(CGEventFlags::CGEventFlagCommand) + } else if flags.contains(CGEventFlags::MaskCommand) + && !global_flags.contains(CGEventFlags::MaskCommand) { *global_flags = flags; Some(EventType::KeyPress(key)) - } else if !flags.contains(CGEventFlags::CGEventFlagCommand) - && global_flags.contains(CGEventFlags::CGEventFlagCommand) + } else if !flags.contains(CGEventFlags::MaskCommand) + && global_flags.contains(CGEventFlags::MaskCommand) { *global_flags = flags; Some(EventType::KeyRelease(key)) @@ -190,10 +124,14 @@ pub unsafe fn convert( } } CGEventType::ScrollWheel => { - let delta_y = cg_event - .get_integer_value_field(EventField::SCROLL_WHEEL_EVENT_POINT_DELTA_AXIS_1); - let delta_x = cg_event - .get_integer_value_field(EventField::SCROLL_WHEEL_EVENT_POINT_DELTA_AXIS_2); + let delta_y = CGEvent::integer_value_field( + Some(cg_event.as_ref()), + CGEventField::ScrollWheelEventDeltaAxis1, + ); + let delta_x = CGEvent::integer_value_field( + Some(cg_event.as_ref()), + CGEventField::ScrollWheelEventDeltaAxis2, + ); Some(EventType::Wheel { delta_x, delta_y }) } _ => None, @@ -201,9 +139,11 @@ pub unsafe fn convert( if let Some(event_type) = option_type { let name = match event_type { EventType::KeyPress(_) => { - let code = - cg_event.get_integer_value_field(EventField::KEYBOARD_EVENT_KEYCODE) as u32; - let flags = cg_event.get_flags(); + let code = CGEvent::integer_value_field( + Some(cg_event.as_ref()), + CGEventField::KeyboardEventKeycode, + ); + let flags = CGEvent::flags(Some(cg_event.as_ref())); keyboard_state.create_string_for_key(code, flags) } _ => None, diff --git a/src/macos/display.rs b/src/macos/display.rs index 00dc66c4..b412c86d 100644 --- a/src/macos/display.rs +++ b/src/macos/display.rs @@ -1,7 +1,17 @@ +use objc2_core_graphics::{CGDisplayPixelsHigh, CGDisplayPixelsWide, CGMainDisplayID}; + use crate::rdev::DisplayError; -use core_graphics::display::CGDisplay; pub fn display_size() -> Result<(u64, u64), DisplayError> { - let main = CGDisplay::main(); - Ok((main.pixels_wide(), main.pixels_high())) + unsafe { + let main = CGMainDisplayID(); + Ok(( + CGDisplayPixelsWide(main) + .try_into() + .map_err(|_| DisplayError::ConversionError)?, + CGDisplayPixelsHigh(main) + .try_into() + .map_err(|_| DisplayError::ConversionError)?, + )) + } } diff --git a/src/macos/keyboard.rs b/src/macos/keyboard.rs index 7faadefe..2c205c0a 100644 --- a/src/macos/keyboard.rs +++ b/src/macos/keyboard.rs @@ -4,7 +4,7 @@ use crate::rdev::{EventType, Key, KeyboardState}; use core_foundation::base::{CFRelease, OSStatus}; use core_foundation::string::UniChar; use core_foundation_sys::data::{CFDataGetBytePtr, CFDataRef}; -use core_graphics::event::CGEventFlags; +use objc2_core_graphics::CGEventFlags; use std::convert::TryInto; use std::ffi::c_void; use std::os::raw::c_uint; @@ -86,7 +86,7 @@ impl Keyboard { pub(crate) unsafe fn create_string_for_key( &mut self, - code: u32, + code: i64, flags: CGEventFlags, ) -> Option { unsafe { @@ -105,7 +105,7 @@ impl Keyboard { pub(crate) unsafe fn string_from_code( &mut self, - code: u32, + code: i64, modifier_state: ModifierState, ) -> Option { unsafe { diff --git a/src/macos/keycodes.rs b/src/macos/keycodes.rs index eeb6f98e..95769eb6 100644 --- a/src/macos/keycodes.rs +++ b/src/macos/keycodes.rs @@ -1,5 +1,6 @@ +use objc2_core_graphics::CGKeyCode; + use crate::rdev::Key; -use core_graphics::event::CGKeyCode; use std::convert::TryInto; /// Option diff --git a/src/macos/listen.rs b/src/macos/listen.rs index 217a23f3..0c87e0bc 100644 --- a/src/macos/listen.rs +++ b/src/macos/listen.rs @@ -1,26 +1,31 @@ #![allow(improper_ctypes_definitions)] use crate::macos::common::*; use crate::rdev::{Event, ListenError}; -use cocoa::base::nil; -use cocoa::foundation::NSAutoreleasePool; -use core_graphics::event::{CGEventTapLocation, CGEventType}; -use std::os::raw::c_void; +use core::ptr::NonNull; +use objc2_core_foundation::{CFMachPort, CFRunLoop, kCFRunLoopCommonModes}; +use objc2_core_graphics::{ + CGEvent, CGEventTapCallBack, CGEventTapLocation, CGEventTapOptions, CGEventTapPlacement, + CGEventTapProxy, CGEventType, kCGEventMaskForAllEvents, +}; +use objc2_foundation::NSAutoreleasePool; +use std::ffi::c_void; +use std::ptr::null_mut; static mut GLOBAL_CALLBACK: Option> = None; #[link(name = "Cocoa", kind = "framework")] unsafe extern "C" {} -unsafe extern "C" fn raw_callback( +unsafe extern "C-unwind" fn raw_callback( _proxy: CGEventTapProxy, event_type: CGEventType, - cg_event: CGEventRef, + cg_event: NonNull, _user_info: *mut c_void, -) -> CGEventRef { +) -> *mut CGEvent { let opt = KEYBOARD_STATE.lock(); if let Ok(mut keyboard) = opt { unsafe { - if let Some(event) = convert(event_type, &cg_event, &mut keyboard) { + if let Some(event) = convert(event_type, cg_event, &mut keyboard) { // Reborrowing the global callback pointer. // Totally UB. but not sure there's a great alternative. let ptr = &raw mut GLOBAL_CALLBACK; @@ -30,7 +35,7 @@ unsafe extern "C" fn raw_callback( } } } - cg_event + cg_event.as_ptr() } pub fn listen(callback: T) -> Result<(), ListenError> @@ -39,28 +44,25 @@ where { unsafe { GLOBAL_CALLBACK = Some(Box::new(callback)); - let _pool = NSAutoreleasePool::new(nil); - let tap = CGEventTapCreate( - CGEventTapLocation::HID, // HID, Session, AnnotatedSession, - kCGHeadInsertEventTap, - CGEventTapOption::ListenOnly, - kCGEventMaskForAllEvents, - raw_callback, - nil, - ); - if tap.is_null() { - return Err(ListenError::EventTapError); - } - let _loop = CFMachPortCreateRunLoopSource(nil, tap, 0); - if _loop.is_null() { - return Err(ListenError::LoopSourceError); - } + let _pool = NSAutoreleasePool::new(); + let callback: CGEventTapCallBack = Some(raw_callback); + let tap = CGEvent::tap_create( + CGEventTapLocation::HIDEventTap, // HID, Session, AnnotatedSession, + CGEventTapPlacement::HeadInsertEventTap, + CGEventTapOptions::ListenOnly, + kCGEventMaskForAllEvents.into(), + callback, + null_mut(), + ) + .ok_or(ListenError::EventTapError)?; + let loop_ = CFMachPort::new_run_loop_source(None, Some(&tap), 0) + .ok_or(ListenError::LoopSourceError)?; - let current_loop = CFRunLoopGetCurrent(); - CFRunLoopAddSource(current_loop, _loop, kCFRunLoopCommonModes); + let current_loop = CFRunLoop::current().unwrap(); + current_loop.add_source(Some(&loop_), kCFRunLoopCommonModes); - CGEventTapEnable(tap, true); - CFRunLoopRun(); + CGEvent::tap_enable(&tap, true); + CFRunLoop::run(); } Ok(()) } diff --git a/src/macos/mod.rs b/src/macos/mod.rs index 87326541..d0a84d92 100644 --- a/src/macos/mod.rs +++ b/src/macos/mod.rs @@ -1,7 +1,7 @@ mod common; mod display; -#[cfg(feature = "unstable_grab")] -mod grab; +// #[cfg(feature = "unstable_grab")] +// mod grab; mod keyboard; mod keycodes; mod listen; @@ -9,8 +9,8 @@ mod simulate; pub use crate::macos::common::set_is_main_thread; pub use crate::macos::display::display_size; -#[cfg(feature = "unstable_grab")] -pub use crate::macos::grab::grab; +// #[cfg(feature = "unstable_grab")] +// pub use crate::macos::grab::grab; pub use crate::macos::keyboard::Keyboard; pub use crate::macos::listen::listen; pub use crate::macos::simulate::simulate; diff --git a/src/macos/simulate.rs b/src/macos/simulate.rs index d9df76a9..2b1beb77 100644 --- a/src/macos/simulate.rs +++ b/src/macos/simulate.rs @@ -1,11 +1,11 @@ +use objc2_core_foundation::{CFRetained, CGPoint}; +use objc2_core_graphics::{ + CGEvent, CGEventField, CGEventFlags, CGEventSource, CGEventSourceStateID, CGEventTapLocation, + CGEventType, CGMouseButton, CGScrollEventUnit, +}; + use crate::Key; use crate::rdev::{Button, EventType, SimulateError}; -use core_graphics::event::{ - CGEvent, CGEventFlags, CGEventTapLocation, CGEventType, CGMouseButton, EventField, - ScrollEventUnit, -}; -use core_graphics::event_source::{CGEventSource, CGEventSourceStateID}; -use core_graphics::geometry::CGPoint; use std::convert::TryInto; use crate::macos::common::LAST_FLAGS; @@ -13,41 +13,46 @@ use crate::macos::keycodes::code_from_key; unsafe fn convert_native_with_source( event_type: &EventType, - source: CGEventSource, -) -> Option { + source: CFRetained, +) -> Option> { unsafe { match event_type { EventType::KeyPress(key) => { let code = code_from_key(*key)?; // For modifier keys, we need to use FlagsChanged event type if is_modifier_key(*key) { - let event = CGEvent::new(source).ok()?; - event.set_type(CGEventType::FlagsChanged); - event.set_integer_value_field(EventField::KEYBOARD_EVENT_KEYCODE, code as i64); + let event = CGEvent::new(Some(&source))?; + CGEvent::set_type(Some(&event), CGEventType::FlagsChanged); + CGEvent::set_integer_value_field( + Some(&event), + CGEventField::KeyboardEventKeycode, + code as i64, + ); // Get current flags and update them let mut new_flags = LAST_FLAGS.lock().unwrap(); match key { Key::ShiftLeft | Key::ShiftRight => { - new_flags.insert(CGEventFlags::CGEventFlagShift); + new_flags.insert(CGEventFlags::MaskShift); } Key::ControlLeft | Key::ControlRight => { - new_flags.insert(CGEventFlags::CGEventFlagControl); + new_flags.insert(CGEventFlags::MaskControl); } Key::AltGr | Key::Alt => { - new_flags.insert(CGEventFlags::CGEventFlagAlternate); + new_flags.insert(CGEventFlags::MaskAlternate); } Key::MetaLeft | Key::MetaRight => { - new_flags.insert(CGEventFlags::CGEventFlagCommand); + new_flags.insert(CGEventFlags::MaskCommand); } _ => {} } - event.set_flags(*new_flags); + CGEvent::set_flags(Some(&event), *new_flags); + // event.set_flags(*new_flags); Some(event) } else { // For non-modifier keys, use regular key events - let event = CGEvent::new_keyboard_event(source, code, true).ok()?; - event.set_flags(*LAST_FLAGS.lock().unwrap()); + let event = CGEvent::new_keyboard_event(Some(&source), code, true)?; + CGEvent::set_flags(Some(&event), *LAST_FLAGS.lock().unwrap()); Some(event) } } @@ -55,33 +60,37 @@ unsafe fn convert_native_with_source( let code = code_from_key(*key)?; // For modifier keys, we need to use FlagsChanged event type if is_modifier_key(*key) { - let event = CGEvent::new(source).ok()?; - event.set_type(CGEventType::FlagsChanged); - event.set_integer_value_field(EventField::KEYBOARD_EVENT_KEYCODE, code as i64); + let event = CGEvent::new(Some(&source))?; + CGEvent::set_type(Some(&event), CGEventType::FlagsChanged); + CGEvent::set_integer_value_field( + Some(&event), + CGEventField::KeyboardEventKeycode, + code as i64, + ); // Get current flags and update them let mut new_flags = LAST_FLAGS.lock().unwrap(); match key { Key::ShiftLeft | Key::ShiftRight => { - new_flags.remove(CGEventFlags::CGEventFlagShift); + new_flags.remove(CGEventFlags::MaskShift); } Key::ControlLeft | Key::ControlRight => { - new_flags.remove(CGEventFlags::CGEventFlagControl); + new_flags.remove(CGEventFlags::MaskControl); } Key::AltGr | Key::Alt => { - new_flags.remove(CGEventFlags::CGEventFlagAlternate); + new_flags.remove(CGEventFlags::MaskAlternate); } Key::MetaLeft | Key::MetaRight => { - new_flags.remove(CGEventFlags::CGEventFlagCommand); + new_flags.remove(CGEventFlags::MaskCommand); } _ => {} } - event.set_flags(*new_flags); + CGEvent::set_flags(Some(&event), *new_flags); Some(event) } else { // For non-modifier keys, use regular key events - let event = CGEvent::new_keyboard_event(source, code, false).ok()?; - event.set_flags(*LAST_FLAGS.lock().unwrap()); + let event = CGEvent::new_keyboard_event(Some(&source), code, false)?; + CGEvent::set_flags(Some(&event), *LAST_FLAGS.lock().unwrap()); Some(event) } } @@ -93,12 +102,11 @@ unsafe fn convert_native_with_source( _ => return None, }; CGEvent::new_mouse_event( - source, + Some(&source), event, point, CGMouseButton::Left, // ignored because we don't use OtherMouse EventType ) - .ok() } EventType::ButtonRelease(button) => { let point = get_current_mouse_location()?; @@ -108,48 +116,49 @@ unsafe fn convert_native_with_source( _ => return None, }; CGEvent::new_mouse_event( - source, + Some(&source), event, point, CGMouseButton::Left, // ignored because we don't use OtherMouse EventType ) - .ok() } EventType::MouseMove { x, y } => { let point = CGPoint { x: (*x), y: (*y) }; CGEvent::new_mouse_event( - source, + Some(&source), CGEventType::MouseMoved, point, CGMouseButton::Left, ) - .ok() } EventType::Wheel { delta_x, delta_y } => { let wheel_count = 2; - CGEvent::new_scroll_event( - source, - ScrollEventUnit::PIXEL, + CGEvent::new_scroll_wheel_event2( + Some(&source), + CGScrollEventUnit::Pixel, wheel_count, (*delta_y).try_into().ok()?, (*delta_x).try_into().ok()?, 0, ) - .ok() } } } } -unsafe fn convert_native(event_type: &EventType) -> Option { - let source = CGEventSource::new(CGEventSourceStateID::HIDSystemState).ok()?; - unsafe { convert_native_with_source(event_type, source) } +unsafe fn convert_native(event_type: &EventType) -> Option> { + unsafe { + let source = CGEventSource::new(CGEventSourceStateID::HIDSystemState)?; + convert_native_with_source(event_type, source) + } } unsafe fn get_current_mouse_location() -> Option { - let source = CGEventSource::new(CGEventSourceStateID::HIDSystemState).ok()?; - let event = CGEvent::new(source).ok()?; - Some(event.location()) + unsafe { + let source = CGEventSource::new(CGEventSourceStateID::HIDSystemState)?; + let event = CGEvent::new(Some(&source))?; + Some(CGEvent::location(Some(&event))) + } } fn is_modifier_key(key: Key) -> bool { @@ -172,7 +181,7 @@ unsafe extern "C" {} pub fn simulate(event_type: &EventType) -> Result<(), SimulateError> { unsafe { if let Some(cg_event) = convert_native(event_type) { - cg_event.post(CGEventTapLocation::HID); + CGEvent::post(CGEventTapLocation::HIDEventTap, Some(&cg_event)); Ok(()) } else { Err(SimulateError) From 2b796531d7b151b850bfe538951dea927a4caa8f Mon Sep 17 00:00:00 2001 From: Nicolas Patry Date: Mon, 23 Jun 2025 12:36:12 +0200 Subject: [PATCH 2/4] Fixing the listen_and_simulate test. --- tests/listen_and_simulate.rs | 41 +++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/tests/listen_and_simulate.rs b/tests/listen_and_simulate.rs index 6a587dab..03b9809d 100644 --- a/tests/listen_and_simulate.rs +++ b/tests/listen_and_simulate.rs @@ -1,26 +1,42 @@ -use rdev::{Button, EventType, Key, listen, simulate}; +use lazy_static::lazy_static; +use rdev::{listen, simulate, Button, Event, EventType, Key}; use serial_test::serial; use std::error::Error; use std::iter::Iterator; -use std::sync::mpsc::channel; +use std::sync::mpsc::{channel, Receiver, Sender}; +use std::sync::Mutex; use std::thread; use std::time::Duration; +lazy_static! { + static ref EVENT_CHANNEL: (Mutex>, Mutex>) = { + let (send, recv) = channel(); + (Mutex::new(send), Mutex::new(recv)) + }; +} + +fn send_event(event: Event) { + EVENT_CHANNEL + .0 + .lock() + .expect("Failed to unlock Mutex") + .send(event) + .expect("Receiving end of EVENT_CHANNEL was closed"); +} + fn sim_then_listen(events: &mut dyn Iterator) -> Result<(), Box> { - let (send, recv) = channel(); // spawn new thread because listen blocks let _listener = thread::spawn(move || { - listen(move |event| send.send(event).unwrap()).expect("Could not listen"); + listen(send_event).expect("Could not listen"); }); let second = Duration::from_millis(1000); thread::sleep(second); + let recv = EVENT_CHANNEL.1.lock()?; for event in events { simulate(&event)?; - let received_event = recv - .recv_timeout(second) - .unwrap_or_else(|_| panic!("{}", "No events to receive {event:?}")); - assert_eq!(received_event.event_type, event); + let recieved_event = recv.recv_timeout(second).expect("No events to recieve"); + assert_eq!(recieved_event.event_type, event); } Ok(()) } @@ -31,15 +47,10 @@ fn test_listen_and_simulate() -> Result<(), Box> { // wait for user input from keyboard to stop // (i.e. the return/enter keypress to run test command) thread::sleep(Duration::from_millis(50)); - // On wayland we need to open libuinput and the hooks take - // some time to install, this forces the handle to get installed - simulate(&EventType::MouseMove { x: 0.0, y: 0.0 })?; - thread::sleep(Duration::from_millis(50)); let events = vec![ //TODO: fix sending shift keypress events on linux - EventType::KeyPress(Key::ShiftLeft), - EventType::KeyRelease(Key::ShiftLeft), + //EventType::KeyPress(Key::ShiftLeft), EventType::KeyPress(Key::KeyS), EventType::KeyRelease(Key::KeyS), EventType::ButtonPress(Button::Right), @@ -54,7 +65,7 @@ fn test_listen_and_simulate() -> Result<(), Box> { }, ] .into_iter(); - let click_events = (1..480).map(|pixel| EventType::MouseMove { + let click_events = (0..480).map(|pixel| EventType::MouseMove { x: pixel as f64, y: pixel as f64, }); From 3f8bdc0ae4500396d63eb289757447f94272accc Mon Sep 17 00:00:00 2001 From: Nicolas Patry Date: Mon, 23 Jun 2025 12:37:01 +0200 Subject: [PATCH 3/4] Fmt. --- tests/listen_and_simulate.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/listen_and_simulate.rs b/tests/listen_and_simulate.rs index 03b9809d..3d5dbf80 100644 --- a/tests/listen_and_simulate.rs +++ b/tests/listen_and_simulate.rs @@ -1,10 +1,10 @@ use lazy_static::lazy_static; -use rdev::{listen, simulate, Button, Event, EventType, Key}; +use rdev::{Button, Event, EventType, Key, listen, simulate}; use serial_test::serial; use std::error::Error; use std::iter::Iterator; -use std::sync::mpsc::{channel, Receiver, Sender}; use std::sync::Mutex; +use std::sync::mpsc::{Receiver, Sender, channel}; use std::thread; use std::time::Duration; From 2d7b1114a37d6ed869022094630b8698b539736f Mon Sep 17 00:00:00 2001 From: Nicolas Patry Date: Mon, 23 Jun 2025 12:47:40 +0200 Subject: [PATCH 4/4] Forgot grab. --- src/macos/grab.rs | 59 ++++++++++++++++++++++++----------------------- src/macos/mod.rs | 8 +++---- 2 files changed, 34 insertions(+), 33 deletions(-) diff --git a/src/macos/grab.rs b/src/macos/grab.rs index 316b5dd2..f0a9154a 100644 --- a/src/macos/grab.rs +++ b/src/macos/grab.rs @@ -1,38 +1,42 @@ #![allow(improper_ctypes_definitions)] use crate::macos::common::*; use crate::rdev::{Event, GrabError}; -use cocoa::base::nil; -use cocoa::foundation::NSAutoreleasePool; -use core_graphics::event::{CGEventTapLocation, CGEventType}; +use objc2_core_foundation::{CFMachPort, CFRunLoop, kCFRunLoopCommonModes}; +use objc2_core_graphics::{ + CGEvent, CGEventTapCallBack, CGEventTapLocation, CGEventTapOptions, CGEventTapPlacement, + CGEventTapProxy, CGEventType, kCGEventMaskForAllEvents, +}; +use objc2_foundation::NSAutoreleasePool; use std::os::raw::c_void; +use std::ptr::{NonNull, null_mut}; static mut GLOBAL_CALLBACK: Option Option>> = None; #[link(name = "Cocoa", kind = "framework")] unsafe extern "C" {} -unsafe extern "C" fn raw_callback( +unsafe extern "C-unwind" fn raw_callback( _proxy: CGEventTapProxy, _type: CGEventType, - cg_event: CGEventRef, + cg_event: NonNull, _user_info: *mut c_void, -) -> CGEventRef { +) -> *mut CGEvent { let opt = KEYBOARD_STATE.lock(); if let Ok(mut keyboard) = opt { unsafe { - if let Some(event) = convert(_type, &cg_event, &mut keyboard) { + if let Some(event) = convert(_type, cg_event, &mut keyboard) { // Reborrowing the global callback pointer. // Totally UB. but not sure there's a great alternative. let ptr = &raw mut GLOBAL_CALLBACK; if let Some(callback) = &mut *ptr { if callback(event).is_none() { - cg_event.set_type(CGEventType::Null); + CGEvent::set_type(Some(cg_event.as_ref()), CGEventType::Null) } } } } } - cg_event + cg_event.as_ptr() } pub fn grab(callback: T) -> Result<(), GrabError> @@ -41,28 +45,25 @@ where { unsafe { GLOBAL_CALLBACK = Some(Box::new(callback)); - let _pool = NSAutoreleasePool::new(nil); - let tap = CGEventTapCreate( - CGEventTapLocation::HID, // HID, Session, AnnotatedSession, - kCGHeadInsertEventTap, - CGEventTapOption::Default, - kCGEventMaskForAllEvents, - raw_callback, - nil, - ); - if tap.is_null() { - return Err(GrabError::EventTapError); - } - let _loop = CFMachPortCreateRunLoopSource(nil, tap, 0); - if _loop.is_null() { - return Err(GrabError::LoopSourceError); - } + let _pool = NSAutoreleasePool::new(); + let callback: CGEventTapCallBack = Some(raw_callback); + let tap = CGEvent::tap_create( + CGEventTapLocation::HIDEventTap, // HID, Session, AnnotatedSession, + CGEventTapPlacement::HeadInsertEventTap, + CGEventTapOptions::Default, + kCGEventMaskForAllEvents.into(), + callback, + null_mut(), + ) + .ok_or(GrabError::EventTapError)?; + let loop_ = CFMachPort::new_run_loop_source(None, Some(&tap), 0) + .ok_or(GrabError::LoopSourceError)?; - let current_loop = CFRunLoopGetCurrent(); - CFRunLoopAddSource(current_loop, _loop, kCFRunLoopCommonModes); + let current_loop = CFRunLoop::current().unwrap(); + current_loop.add_source(Some(&loop_), kCFRunLoopCommonModes); - CGEventTapEnable(tap, true); - CFRunLoopRun(); + CGEvent::tap_enable(&tap, true); + CFRunLoop::run(); } Ok(()) } diff --git a/src/macos/mod.rs b/src/macos/mod.rs index d0a84d92..87326541 100644 --- a/src/macos/mod.rs +++ b/src/macos/mod.rs @@ -1,7 +1,7 @@ mod common; mod display; -// #[cfg(feature = "unstable_grab")] -// mod grab; +#[cfg(feature = "unstable_grab")] +mod grab; mod keyboard; mod keycodes; mod listen; @@ -9,8 +9,8 @@ mod simulate; pub use crate::macos::common::set_is_main_thread; pub use crate::macos::display::display_size; -// #[cfg(feature = "unstable_grab")] -// pub use crate::macos::grab::grab; +#[cfg(feature = "unstable_grab")] +pub use crate::macos::grab::grab; pub use crate::macos::keyboard::Keyboard; pub use crate::macos::listen::listen; pub use crate::macos::simulate::simulate;