Mouser now supports macOS alongside Windows. This document covers macOS-specific setup and known differences.
- macOS 12 (Monterey) or later recommended
- Python 3.11+ (via Homebrew or python.org)
- Accessibility permission — required for CGEventTap to intercept mouse events
pip install -r requirements.txtOn macOS, this will also install:
pyobjc-framework-Quartz— for CGEventTap (mouse hooking) and CGEvent (key simulation)pyobjc-framework-Cocoa— for NSWorkspace (app detection) and NSEvent (media keys)
Mouser uses a CGEventTap to intercept and suppress mouse button events. macOS requires Accessibility permission for this:
- Open System Settings → Privacy & Security → Accessibility
- Click the + button
- Add either:
- Terminal.app / iTerm2 (if running from terminal)
- The Python binary (e.g.
/usr/local/bin/python3) - The built
.appbundle (if packaged)
- Ensure the checkbox is enabled
- Restart Mouser if it was already running
If Accessibility is not granted, Mouser will print:
[MouseHook] ERROR: Failed to create CGEventTap!
| Feature | Windows | macOS |
|---|---|---|
| Mouse hook | SetWindowsHookExW (LL hook) | CGEventTap |
| Key simulation | SendInput (VK codes) | CGEvent (CGKeyCodes) |
| Media keys | VK_MEDIA_* constants | NSEvent (NX key IDs) |
| App detection | GetForegroundWindow | NSWorkspace.frontmostApplication |
| Gesture button | Raw Input + HID++ | HID++ only |
| Scroll inversion | Coalesced SendInput | CGEventCreateScrollWheelEvent |
| Modifier key | Ctrl | Cmd (⌘) |
| Config location | %APPDATA%\Mouser |
~/Library/Application Support/Mouser |
| Auto-reconnect | Device change notification | HID++ reconnect loop |
Actions that use Ctrl on Windows automatically use Cmd (⌘) on macOS:
- Copy → Cmd+C
- Paste → Cmd+V
- Cut → Cmd+X
- Undo → Cmd+Z
- etc.
Alt+Tab becomes Cmd+Tab, Win+D becomes Ctrl+Up (Mission Control).
On macOS, the HID gesture listener uses non-exclusive access (hid_darwin_set_open_exclusive(0))
so the mouse continues to function normally while Mouser reads HID++ reports.
python main_qml.pySend SIGUSR1 to dump all thread stack traces:
kill -USR1 $(pgrep -f main_qml.py)