use core::ptr::NonNull;
use std::ffi::c_void;
use std::ptr::null_mut;
use objc2_core_foundation::{CFMachPort, CFRunLoop, kCFRunLoopCommonModes};
use objc2_core_graphics::{
CGEvent, CGEventTapCallBack, CGEventTapLocation, CGEventTapOptions, CGEventTapPlacement,
CGEventTapProxy, CGEventType, kCGEventMaskForAllEvents,
};
use objc2_foundation::NSAutoreleasePool;
use crate::{GLOBAL_CALLBACK, ListenError, MouseMoveEvent};
unsafe extern "C-unwind" fn raw_callback(
_proxy: CGEventTapProxy,
event_type: CGEventType,
cg_event: NonNull<CGEvent>,
_user_info: *mut c_void,
) -> *mut CGEvent {
match event_type {
CGEventType::MouseMoved
| CGEventType::LeftMouseDragged
| CGEventType::RightMouseDragged => {
let point = CGEvent::location(Some(unsafe { cg_event.as_ref() }));
let event = MouseMoveEvent {
x: point.x,
y: point.y,
};
GLOBAL_CALLBACK.with(|cell| {
if let Some(callback) = cell.borrow_mut().as_mut() {
callback(event);
}
});
},
_ => {},
};
cg_event.as_ptr()
}
pub fn listen<T>(callback: T) -> Result<(), ListenError>
where
T: FnMut(MouseMoveEvent) + 'static,
{
GLOBAL_CALLBACK.with(|cell| {
*cell.borrow_mut() = Some(Box::new(callback));
});
unsafe {
let _pool = NSAutoreleasePool::new();
let callback: CGEventTapCallBack = Some(raw_callback);
let tap = CGEvent::tap_create(
CGEventTapLocation::HIDEventTap,
CGEventTapPlacement::HeadInsertEventTap,
CGEventTapOptions::ListenOnly,
kCGEventMaskForAllEvents.into(),
callback,
null_mut(),
)
.ok_or(ListenError::CGEventTap)?;
let source = CFMachPort::new_run_loop_source(None, Some(&tap), 0)
.ok_or(ListenError::CFRunLoopSource)?;
let rl = CFRunLoop::current().unwrap();
rl.add_source(Some(&source), kCFRunLoopCommonModes);
CGEvent::tap_enable(&tap, true);
CFRunLoop::run();
}
Ok(())
}