global-mousemove 0.1.1

A minimal library to listen for global mousemove events.
Documentation
//! A minimal library to listen for global mousemove events.
//!
//! Supports Linux (X11), macOS, and Windows. See [`listen`] for details.

use std::cell::RefCell;

#[cfg(target_os = "linux")]
mod linux;
#[cfg(target_os = "macos")]
mod macos;
#[cfg(target_os = "windows")]
mod windows;

/// Errors that can occur when trying to listen for mousemove events.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum ListenError {
    #[cfg(target_os = "linux")]
    #[error("Failed to connect to X display server")]
    XOpenDisplay,
    #[cfg(target_os = "linux")]
    #[error("X Record extension does not exist")]
    XRecordExtensionMissing,
    #[cfg(target_os = "linux")]
    #[error("Failed to allocate X RecordRange structure")]
    XRecordAllocRange,
    #[cfg(target_os = "linux")]
    #[error("Failed to create X Record context")]
    XRecordCreateContext,
    #[cfg(target_os = "linux")]
    #[error("Failed to enable X Record context")]
    XRecordEnableContext,
    #[cfg(target_os = "macos")]
    #[error("Failed to create CGEvent tap")]
    CGEventTap,
    #[cfg(target_os = "macos")]
    #[error("Failed to create CFRunLoop source")]
    CFRunLoopSource,
    #[cfg(target_os = "windows")]
    #[error("Failed to install WH_MOUSE_LL hook, error code: {0}")]
    WHMouseHook(u32),
}

/// A mousemove event.
#[derive(Debug)]
pub struct MouseMoveEvent {
    /// The x-coordinate of the mouse pointer in physical pixels.
    pub x: f64,
    /// The y-coordinate of the mouse pointer in physical pixels.
    pub y: f64,
}

type MouseMoveCallback = Box<dyn FnMut(MouseMoveEvent)>;

thread_local! {
    pub(crate) static GLOBAL_CALLBACK: RefCell<Option<MouseMoveCallback>> = RefCell::new(None);
}

/// Listen for global mousemove events.
///
/// ### OS Caveats
///
/// - **Linux**: Only X11 supported with the X Record extension.
/// - **macOS**: The application may require "Input Monitoring" permissions to
///   capture global mouse events. Calling this function may automatically
///   prompt the user to grant these permissions.
/// - **Windows**: No special requirements.
///
/// ### Examples
///
/// ```no_run
/// global_mousemove::listen(|event| {
///     println!("Mouse moved: ({}, {})", event.x, event.y);
/// });
/// ```
///
/// The above example listens for global mousemove events and prints the
/// coordinates of the mouse pointer each time it moves. Note that this blocks
/// the current thread. To run it in the background, consider spawning a
/// separate thread and use a channel. For example:
///
/// ```no_run
/// use std::sync::mpsc;
///
/// let (tx, rx) = mpsc::channel();
///
/// std::thread::spawn(move || {
///     let _ = global_mousemove::listen(move |event| {
///         let _ = tx.send(event);
///     });
/// });
///
/// while let Ok(event) = rx.recv() {
///     println!("Mouse moved: ({}, {})", event.x, event.y);
/// }
/// ```
///
/// The listener will be stopped when the thread is terminated. There is
/// currently no way to stop the listener gracefully. However, you can
/// effectively stop it by having a very cheap check that short-circuits the
/// callback. For example, we can use an atomic boolean:
///
/// ```no_run
/// use std::sync::atomic::{AtomicBool, Ordering};
/// use std::sync::Arc;
///
/// let listening = Arc::new(AtomicBool::new(true));
/// let listening_cloned = Arc::clone(&listening);
///
/// std::thread::spawn(move || {
///     let _ = global_mousemove::listen(move |event| {
///         if !listening_cloned.load(Ordering::Relaxed) {
///             return;
///         }
///         println!("Mouse moved: ({}, {})", event.x, event.y);
///     });
/// });
///
/// // To stop listening:
/// // listening.store(false, Ordering::Relaxed);
/// ```
pub fn listen<T>(callback: T) -> Result<(), ListenError>
where
    T: FnMut(MouseMoveEvent) + 'static,
{
    #[cfg(target_os = "linux")]
    crate::linux::listen(callback)?;
    #[cfg(target_os = "macos")]
    crate::macos::listen(callback)?;
    #[cfg(target_os = "windows")]
    crate::windows::listen(callback)?;

    Ok(())
}