A tiny, free, open-source macOS menu bar app that rescues your mouse cursor when it gets trapped, hidden, or lost, including when Universal Control has carried it off to another Mac.
brew install --cask uncSoft/mousetrapped/mousetrappedOr grab the notarized build from the latest release.
On macOS, when a nearby Mac has Universal Control / mouse sharing enabled (made worse by remote-control tools like ScreenConnect), the cursor can get "eaten": some window captures pointer focus, hides the cursor, and leaves you in no-mouse limbo. Or the pointer crosses to the other Mac and something over there swallows it.
Under the hood the cursor usually isn't gone. It has been disassociated
from the mouse (CGAssociateMouseAndMouseCursorPosition(false)), hidden by a
process that never restored it, or routed to another machine. Mousetrapped
forcibly undoes all of that.
Press the hotkey (⌃⌥⌘M by default) or shake the mouse hard left-right, and Mousetrapped will:
- Re-associate the mouse with the on-screen cursor (undoes pointer capture)
- Warp the cursor to the center of your chosen display (primary by default)
- Post a synthetic mouse-move so the window server and frontmost app re-evaluate cursor visibility
- Pulse a red locator ring at the destination so you can spot the cursor
- If the pointer is on another Mac (Universal Control): restart the
UniversalControlagent, which forces macOS to bring the pointer home, then warp it to your chosen display
Remote-pointer detection compares the raw HID device stream against the local session's event counters. If the physical mouse is moving but the local session sees nothing (or the cursor is parked at a screen edge, where Universal Control leaves it), the pointer is on the other Mac.
- Rescue Cursor Now: manual trigger
- Rescue To: primary display or any connected display
- Shake Mouse to Rescue: toggle shake detection
- Shake Sensitivity: slider from hair-trigger (right: 3 reversals of light flicks) to deliberate (left: 6 big sweeping arcs). Sensitivity scales how long you must shake and how big each stroke must be, never how fast.
- Work Across Macs…: enables raw HID monitoring (see Permissions)
- Launch at Login
With Homebrew:
brew install --cask uncSoft/mousetrapped/mousetrappedOr download the notarized build from Releases.
Or build from source (requires Xcode command line tools):
git clone https://github.com/uncSoft/Mousetrapped.git
cd Mousetrapped
./build.sh # or ./build.sh --universal for arm64 + x86_64
cp -R dist/Mousetrapped.app /Applications/
open /Applications/Mousetrapped.appbuild.sh signs with your first available Apple Development / Developer ID
identity (override with CODESIGN_IDENTITY=...), falling back to ad-hoc. A
stable identity matters: macOS ties the Input Monitoring grant to the code
signature, so ad-hoc builds lose the grant on every rebuild.
Mousetrapped runs at three privilege levels, using the lowest one that works:
- No permissions: the ⌃⌥⌘M hotkey (Carbon
RegisterEventHotKey) and the menu bar button always work for local rescues. - No permissions (usually): shake detection via an NSEvent global monitor.
- Input Monitoring (opt-in via Work Across Macs…): raw HID monitoring
with
IOHIDManager. This watches the physical mouse and keyboard below the layer where Universal Control captures input, so shake and hotkey detection (and rescue) work even while the pointer is controlling another Mac. Grant it under System Settings → Privacy & Security → Input Monitoring, then relaunch the app.
The hotkey is stored in UserDefaults as a Carbon key code + modifier mask.
Example: set it to ⌃⌥⌘R (key code 15):
defaults write dev.mousetrapped.Mousetrapped hotKeyCode -int 15
defaults write dev.mousetrapped.Mousetrapped hotKeyModifiers -int 6400
killall Mousetrapped && open /Applications/Mousetrapped.appModifier mask is the sum of Carbon flags: ⌘ 256, ⇧ 512, ⌥ 2048, ⌃ 4096 (6400 = ⌘ + ⌥ + ⌃).
Debugging: the app logs events to the unified log (subsystem
dev.mousetrapped.Mousetrapped; view with log stream --predicate 'subsystem == "dev.mousetrapped.Mousetrapped"' --level info). To also
mirror to ~/Library/Logs/Mousetrapped.log, and for per-stroke shake
diagnostics:
defaults write dev.mousetrapped.Mousetrapped debugLogging -bool true
defaults write dev.mousetrapped.Mousetrapped shakeDebug -bool trueA nasty combination: while you're working in a Screen Sharing window, pushing
the pointer through a screen edge lets Universal Control seize it, and the
cursor vanishes from both Macs. Traced, the signature is unmistakable —
the cursor freezes at the desktop's outer edge, com.apple.universalcontrol
becomes the frontmost application, and the local session stops seeing mouse
events entirely.
Mousetrapped handles this, with one requirement:
- Grant Input Monitoring (Work Across Macs…). This is not optional here. Universal Control routes your keyboard to the other Mac too, so the ordinary hotkey never reaches this Mac. Raw HID monitoring reads the physical keyboard directly and is the only channel that survives.
- The hotkey (⌃⌥⌘M) is the reliable trigger in this state. Shake works too if you have a real mouse attached (its hardware deltas are still visible); a trackpad emits none, so use the hotkey there.
- The rescue detects that the pointer has left, restarts Universal Control to force it to let go, and warps the cursor home.
If nothing at all responds while trapped, check that Input Monitoring is actually granted and that the app is not App-Translocated (see below).
To record what happens around a trap, enable the cursor tracer:
defaults write dev.mousetrapped.Mousetrapped cursorTrace -bool true
defaults write dev.mousetrapped.Mousetrapped debugLogging -bool trueIt samples every 200ms into ~/Library/Logs/Mousetrapped.log: cursor
position from two independent sources, which display (if any) contains it,
the frontmost application, and how long the session and raw HID layer have
been silent. Run it on both Macs and line the timelines up by clock. Turn it
off afterwards — it writes five lines a second.
If you run Mousetrapped straight from a downloaded zip, macOS executes it
from a randomized read-only path and no permission can stick — rescues
then fail silently, including the cursor warp. Always move it to
/Applications with Finder before granting anything. The log reports
translocated=true when this is happening, and the launch line shows the
real bundle path.
- Restarting Universal Control to reclaim the pointer drops the Universal Control link for a moment; push through the screen edge to reconnect. That trade-off is the point: it's a panic button.
- If another process hid the cursor via
CGDisplayHideCursorand is truly wedged, macOS scopes that hide to the offending process; the rescue restores control and shows you where the cursor is, but killing the wedged process is the final fix. - VMs or remote sessions that grab the pointer at a lower level have to release it themselves.
PolyForm Noncommercial 1.0.0: the source is free to use, modify, and share, but not for commercial use. You can't sell it or ship it inside a paid product.