Control macOS with a Nintendo Switch Pro Controller. Designed for Claude Code — navigate spaces, trigger dictation, and drive the terminal, all from your gamepad.
make && ./gamepad-cli- macOS 14+ (Sonoma)
- Accessibility permission: System Settings → Privacy & Security → Accessibility → enable your terminal
- Nintendo Switch Pro Controller paired via Bluetooth
./gamepad-cli # English (default)
./gamepad-cli --lang zh # 中文
./gamepad-cli --lang en # English
./gamepad-cli --quiet # Suppress button logs
./gamepad-cli -q # Same as --quietLanguage auto-detected from LANG:
LANG=zh_CN.UTF-8 ./gamepad-cli # 中文
LANG=en_US.UTF-8 ./gamepad-cli # EnglishPress Ctrl+C to quit.
ZL L R ZR ← Shoulders/Triggers
┌─────────────────────────────────┐
│ ┌───┐ ┌───┐ │
│ │十字│ HOME │ X │ │ Face: X(top) A(right)
│ │D-pad│ │Y A│ │ Y(left) B(bottom)
│ └───┘ │ B │ │
│ └───┘ │
│ L3(按下) R3(按下) │ Stick clicks
│ [左摇杆] [右摇杆] │ Analog sticks
└─────────────────────────────────┘
- +
GCController mapping: L = leftShoulder, R = rightShoulder, ZL = leftTrigger, ZR = rightTrigger.
| Control | Action | Implementation |
|---|---|---|
| A (B button) | Enter | Keyboard event |
| B (A button) | Escape | Keyboard event |
| X (X button) | Interrupt (Ctrl+C) | Keyboard event |
| Y (Y button) | Paste (Cmd+V) | Keyboard event |
| D-pad ↑ | Arrow Up | Keyboard event |
| D-pad ↓ | Arrow Down | Keyboard event |
| D-pad ← | Arrow Left | Keyboard event |
| D-pad → | Arrow Right | Keyboard event |
| ZL (left trigger) | Voice dictation | Fn+Option (hold to speak) |
| ZR (right trigger) | Enter | Keyboard event |
| L3 (left stick click) | Left click | Mouse event |
| R3 (right stick click) | Right click | Mouse event |
| + (Menu) | Type "/" (slash cmd) | Keyboard event |
| - (Options) | Type "/clear" | Keyboard event |
| Left stick | Scroll wheel | Scroll event |
| Right stick | Mouse cursor | Mouse move event |
| Combo | Action | Key |
|---|---|---|
| L + D-pad ← | Previous Tab | Cmd+Shift+[ |
| L + D-pad → | Next Tab | Cmd+Shift+] |
| L + D-pad ↑ | Close Tab | Cmd+W |
| L + D-pad ↓ | New Tab | Cmd+T |
| L + Y | Copy | Cmd+C |
| L + B | Delete | Delete |
| L + L3 | Left Click | Mouse |
| L + R3 | Right Click | Mouse |
| L + Left stick ←/→ | Previous/Next App | Cmd+Shift+Tab / Cmd+Tab |
| Combo | Action | Implementation |
|---|---|---|
| R + D-pad ↑ | Mission Control | /usr/bin/open -a "Mission Control" |
| R + D-pad ↓ | App Exposé | open -a "Mission Control" --args -frontmost |
| R + D-pad ← | Previous Space | CGS private API |
| R + D-pad → | Next Space | CGS private API |
Hold ZL to start macOS dictation (Fn+Option), release to commit recognized text. This triggers the native macOS dictation service — a short microphone popover appears while speaking, then text is inserted on release.
The implementation uses kCGEventFlagMaskSecondaryFn (the Fn key) combined with
Option, matching the physical keyboard shortcut for Mac dictation.
Space switching uses the private CGS (CoreGraphics Services / SkyLight) API rather than simulated keyboard events. macOS WindowServer filters synthetic Ctrl+arrow events for Mission Control gestures, so keyboard injection doesn't work.
The CGS API (CGSManagedDisplaySetCurrentSpace) is the same approach used by
window managers like AeroSpace and yabai.
Mission Control and App Exposé are triggered via /usr/bin/open, which is the
only reliable way to invoke these UIs programmatically on macOS 14+.
make # compile (auto code-signs for stable permissions)
make run # compile and run
make clean # remove binaryBuilds with clang, links against GameController, CoreGraphics, Foundation, and ApplicationServices frameworks.
- No entitlement required: Uses
GCController.shouldMonitorBackgroundEventsfor background controller input — works without special signing. - Ad-hoc code signing: The Makefile runs
codesign --sign -after compilation to give the binary a stable identity for TCC (Accessibility) permissions. - 60Hz polling: Analog sticks and triggers are polled at 60fps via
NSTimerwith radial deadzone processing (0.3 threshold). - Auto-repeat: Held buttons (D-pad arrows, L-layer D-pad, L-layer Delete) repeat with 400ms initial delay and 50ms repeat rate.
Keyboard/mouse events not working: Ensure your terminal has Accessibility permission. Open System Settings → Privacy & Security → Accessibility and add/enable your terminal app.
Voice dictation not working: Verify macOS dictation is enabled: System Settings → Keyboard → Dictation → toggle ON. The shortcut is Fn+Option (handled automatically by the tool).
Space switching not working: The CGS API requires no additional permissions beyond Accessibility. If spaces don't switch, verify Mission Control spaces are enabled: System Settings → Desktop & Dock → Mission Control.
Controller not detected: Ensure Bluetooth is on and the controller is paired. The tool auto-discovers wireless controllers on startup and handles hot-plug.