diff --git a/src/lib.rs b/src/lib.rs index 4aee56d0..fdc1c2e5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -228,7 +228,10 @@ mod macos; #[cfg(target_os = "macos")] pub use crate::macos::Keyboard; #[cfg(target_os = "macos")] -use crate::macos::{display_size as _display_size, listen as _listen, simulate as _simulate}; +use crate::macos::{ + display_size as _display_size, listen as _listen, listen_non_blocking as _listen_non_blocking, + simulate as _simulate, +}; #[cfg(target_os = "linux")] mod linux; @@ -272,6 +275,59 @@ where _listen(callback) } +/// 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 +/// settings enabled. +/// +/// ```no_run +/// use rdev::{listen_non_blocking, Event}; +/// use cacao::{ +/// macos::{ +/// menu::{Menu, MenuItem}, +/// window::Window, +/// App, AppDelegate, +/// }, +/// notification_center::Dispatcher, +/// view::View, +/// }; +/// +/// #[derive(Default)] +/// pub struct BaseApp { +/// pub window: Window, +/// } +/// +/// fn callback(event: Event) { +/// println!("My callback {:?}", event); +/// match event.name{ +/// Some(string) => println!("User wrote {:?}", string), +/// None => () +/// } +/// } +/// +/// impl AppDelegate for BaseApp { +/// fn did_finish_launching(&self) { +/// self.window.set_title("RDev"); +/// self.window.show(); +/// +/// // This will not block +/// if let Err(error) = listen(callback) { +/// println!("Error: {:?}", error) +/// } +/// } +/// +/// fn should_terminate_after_last_window_closed(&self) -> bool { +/// false +/// } +/// } +/// ``` +#[cfg(target_os = "macos")] +pub fn listen_non_blocking(callback: T) -> Result<(), ListenError> +where + T: FnMut(Event) + 'static, +{ + _listen_non_blocking(callback) +} + /// Sending some events /// /// ```no_run diff --git a/src/macos/listen.rs b/src/macos/listen.rs index 3da80f44..a5253821 100644 --- a/src/macos/listen.rs +++ b/src/macos/listen.rs @@ -61,3 +61,36 @@ where } Ok(()) } + + +#[link(name = "Cocoa", kind = "framework")] +pub fn listen_non_blocking(callback: T) -> Result<(), ListenError> +where + T: FnMut(Event) + 'static, +{ + 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 current_loop = CFRunLoopGetCurrent(); + CFRunLoopAddSource(current_loop, _loop, kCFRunLoopCommonModes); + + CGEventTapEnable(tap, true); + } + Ok(()) +} diff --git a/src/macos/mod.rs b/src/macos/mod.rs index 43ce23fb..c177a9e7 100644 --- a/src/macos/mod.rs +++ b/src/macos/mod.rs @@ -12,4 +12,5 @@ pub use crate::macos::display::display_size; pub use crate::macos::grab::grab; pub use crate::macos::keyboard::Keyboard; pub use crate::macos::listen::listen; +pub use crate::macos::listen::listen_non_blocking; pub use crate::macos::simulate::simulate;