global-mousemove 0.1.1

A minimal library to listen for global mousemove events.
Documentation
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(())
}