monio-rs
A pure Rust cross-platform input hook library with proper drag detection.
Features
- Cross-platform: macOS, Windows, and Linux (X11/evdev) support
- Proper drag detection: Distinguishes
MouseDraggedfromMouseMovedevents - Event grabbing: Block events from reaching other applications (global hotkeys)
- Async/Channel support: Non-blocking event receiving with std or tokio channels
- Event recording & playback: Record and replay macros (requires
recorderfeature) - Input statistics: Analyze typing speed, mouse distance, etc. (requires
statisticsfeature) - Display queries: Get monitor info, DPI scale, system settings (multi-monitor support)
- Pure Rust: No C dependencies (uses native Rust bindings)
- Event simulation: Programmatically generate keyboard and mouse events
- Thread-safe: Atomic state tracking for reliable button/modifier detection
The Problem This Solves
Most input hooking libraries report all mouse movement as MouseMoved, even when buttons are held down. This makes implementing drag-and-drop, drawing applications, or gesture recognition difficult.
monio-rs tracks button state globally and emits MouseDragged events when movement occurs while any mouse button is pressed:
Button Down → Move → Move → Button Up
↓ ↓ ↓ ↓
Pressed Dragged Dragged Released
Installation
Add to your Cargo.toml:
[]
= "0.1"
Feature Flags
# Default (X11 on Linux)
= "0.1"
# Async channel support with Tokio
= { = "0.1", = ["tokio"] }
# Event recording and playback (macro scripts)
= { = "0.1", = ["recorder"] }
# Input statistics collection
= { = "0.1", = ["statistics"] }
# All features
= { = "0.1", = ["tokio", "recorder", "statistics"] }
# Linux: evdev support (works on X11 AND Wayland)
= { = "0.1", = ["evdev"], = false }
Quick Start
Listening for Events
use ;
Grabbing Events (Block Keys/Mouse)
Use grab() to intercept events and optionally prevent them from reaching other applications.
Return None to consume an event, or Some(event) to pass it through.
use ;
Platform Support for Grabbing:
| Platform | Grab Support | Notes |
|---|---|---|
| macOS | ✅ Full | Via CGEventTap |
| Windows | ✅ Full | Via low-level hooks |
| Linux/X11 | ⚠️ Limited | Falls back to listen mode (XRecord cannot grab) |
| Linux/Wayland | ⚠️ Limited | See Wayland Limitation below |
Channel-Based Listening (Non-Blocking)
For background processing, use channels instead of callbacks:
use listen_channel;
use EventType;
use Duration;
With Tokio (requires tokio feature):
use listen_async_channel;
async
Simulating Events
use ;
Using the Hook Struct (Non-blocking)
use ;
use thread;
use Duration;
Display & System Properties
Query display information and system settings:
use ;
Recording & Playback (Macros)
Record user actions and replay them later (requires recorder feature):
use ;
use Duration;
Input Statistics
Collect and analyze input patterns (requires statistics feature):
use StatisticsCollector;
use Duration;
Event Types
| Event Type | Description |
|---|---|
HookEnabled |
Hook started successfully |
HookDisabled |
Hook stopped |
KeyPressed |
Key pressed down |
KeyReleased |
Key released |
KeyTyped |
Character typed (after dead key processing) |
MousePressed |
Mouse button pressed |
MouseReleased |
Mouse button released |
MouseClicked |
Button press + release without movement |
MouseMoved |
Mouse moved (no buttons held) |
MouseDragged |
Mouse moved while button held |
MouseWheel |
Scroll wheel rotated |
Platform Notes
macOS
Requires Accessibility permissions. The app will prompt for permission on first run, or you can grant it manually in System Preferences → Security & Privacy → Privacy → Accessibility.
Windows
No special permissions required for hooking. Simulation may require the app to be running as Administrator in some contexts.
Linux
Two backends are available:
X11 (default): Uses XRecord for event capture and XTest for simulation. Works only on X11.
evdev: Reads directly from /dev/input/event* devices. Works on both X11 and Wayland!
# Use evdev backend (for Wayland support)
evdev permissions: Requires membership in the input group:
# Log out and back in for changes to take effect
Wayland Limitation
On Wayland, the grab() function has a fundamental limitation due to how Wayland compositors handle input:
- ✅ Blocking events works: Events you choose to consume (return
None) are properly blocked - ❌ Pass-through events fail: Events you want to pass through (return
Some(event)) may not reach other applications
Why this happens: Wayland compositors use libinput, which takes exclusive access to physical input devices. When we grab via evdev, we intercept events before libinput sees them. When we re-inject events via uinput (virtual device), libinput typically ignores them for security reasons.
Workarounds:
- Use X11 instead of Wayland for full grab support
- Use grab only for consuming/blocking events, not for selective pass-through
- For global hotkeys on Wayland, consider using your compositor's native hotkey system
This limitation affects all input libraries using evdev+uinput on Wayland, not just monio.
Examples
# Basic event logging
# Drag detection demo
# Event simulation
# Event grabbing (block specific keys)
# Display information
# Channel-based (sync)
# Channel-based (async with tokio)
# Record and playback macros (requires recorder feature)
# Input statistics (requires statistics feature)