Turn a $10 Bluetooth camera-shutter remote into a programmable macro controller for your Mac — switch desktops, summon AI assistants, trigger any shortcut or script, all from a remote in your hand.
Echo Remote is a small Swift HID bridge for the VN11 BLE remote (a cheap camera shutter sold under many brand names). macOS normally treats these remotes as a half-broken keyboard that mashes the volume keys; Echo Remote seizes the raw HID stream instead and turns every button, ring, and gesture into an action you define in a plain-text config file.
ok = key:enter
left = key:cmd+w
up = oskey:ctrl+right # next desktop
down_double = app:Terminal
shutter = run:~/bin/my_script.sh
zoom_out = url:https://claude.ai
Edit the file, press a button — the new mapping is live. No rebuild, no restart, no re-granting permissions.
The VN11 sends nonstandard consumer/digitizer reports that Karabiner cannot reliably distinguish (several buttons arrive as identical volume events, and the direction pad arrives as digitizer coordinates). Echo Remote reads the raw reports, reconstructs each physical button and gesture, and suppresses the system volume-HUD side effects by opening the device in seize mode.
- 13 mappable inputs from one remote: OK, Left, Right, Up, Down (each with optional double-press), Shutter, Zoom In, Zoom Out, Function (single and double-press).
- Five action types: synthetic keystrokes (
key:), System Events keystrokes for Spaces switching (oskey:), app launch (app:), URLs (url:), and arbitrary shell commands (run:). - A menu-bar app with a live green/red health indicator; clicking it when red runs a full reconnect sequence (Bluetooth poke, bridge restart, and a notification telling you exactly what to fix if that wasn't enough).
- Self-healing: a LaunchAgent starts the bridge at login, a supervisor restarts it if it dies, and reconnect handles the remote's sleep cycles.
Requirements: macOS 13+, Xcode Command Line Tools (xcode-select --install),
and a VN11-compatible remote — for example
this one on Amazon (~$10; sold under
many brand names, see Using a different remote).
git clone https://github.com/kkw-21/echo-remote.git
cd echo-remote
./install.shThen two one-time permission grants:
- Input Monitoring — System Settings → Privacy & Security → Input
Monitoring → enable EchoRemoteRuntime. (macOS prompts the first time
the bridge tries to seize the remote; until granted, the menu icon stays
red with
seize_open_failed.) - Accessibility / Automation — only needed if you use
oskey:or scripted actions; macOS prompts on first use.
Pair the remote in Bluetooth Settings, press a button, and watch the menu-bar dot turn green.
Everything lives in ~/.config/echo-remote/actions.conf (the menu-bar app has
an Edit Button Actions shortcut). The config is re-read on every press, so
edits apply instantly.
| Button | Config keys |
|---|---|
| OK (center) | ok |
| Direction pad | left, right, up, down + *_double variants |
| Shutter | shutter |
| Zoom ring | zoom_in, zoom_out |
| Function | function, function_double |
Action syntax:
| Action | Example | Notes |
|---|---|---|
key: |
key:cmd+shift+4 |
Modifiers: cmd, ctrl, opt, shift. Keys: letters, digits, enter, space, tab, esc, delete, arrows, f1–f12, and more. |
oskey: |
oskey:ctrl+right |
Same combos via System Events — required for Spaces switching and some system shortcuts. |
app: |
app:Terminal |
Opens or activates an app. |
url: |
url:https://claude.ai |
Opens in the default handler. |
run: |
run:~/bin/toggle_mic.sh |
Any shell command, via zsh -c. |
none |
Disable a button. Leaving *_double as none makes single presses fire with zero delay. |
See examples/ai-workflow.conf for a complete "AI command deck" layout, and examples/scripts/focus_app_click_input.sh for focusing a chat app and clicking straight into its input box.
Many BLE shutter remotes share the VN11's chipset. Point Echo Remote at yours
in ~/.config/echo-remote/device.conf:
vendor_id = 1452
product_id = 544
product_name = VN11
Find your device's values with
ioreg -r -c IOHIDDevice -l | grep -E 'Product|VendorID'. If the direction
pad or OK button misbehave, your firmware's report frames may differ — launch
the runtime with ECHO_REMOTE_LOG_RAW=1 to capture them and open an issue
with the log.
If Karabiner-Elements is installed, tell it to ignore the remote so the bridge
is the only mapper. In ~/.config/karabiner/karabiner.json, add ignore: true
device entries for vendor 1452 / product 544 (both the consumer and
keyboard services).
- Menu shows red — click it; the reconnect flow fixes the common cases and posts a notification when it needs you (usually Input Monitoring or a sleeping remote).
seize_open_failedin the log — Input Monitoring isn't granted forEchoRemoteRuntime. Toggle it in Privacy & Security, then click the menu icon.- Remote paired but silent — BLE shutters nap aggressively. Press any button to wake it; the bridge reattaches automatically.
- Logs —
~/Library/Application Support/EchoRemote/echo_remote.log(or Open Log in the menu). - Status —
cat "$HOME/Library/Application Support/EchoRemote/echo_remote.status"
./uninstall.sh # keeps ~/.config/echo-remote
./uninstall.sh --purge # removes config tooEchoRemoteRuntime opens the remote through IOHIDManager in seize mode
(exclusive access — this is what silences the volume HUD), classifies each
input report into a logical button or gesture (the direction pad is actually a
digitizer surface; direction is recovered from coordinate deltas, and the zoom
ring from a separate gesture mode), then dispatches the action mapped in
actions.conf. Keeping mappings out of the binary matters on macOS: Input
Monitoring authorization is tied to the signed binary, so a config-only change
never costs you a re-grant.