A macOS CLI daemon that translates trackpad gestures into keyboard + scroll wheel events per application, designed to make modern trackpad gestures compatible with legacy X11-forwarded software (XQuartz, remote GUI apps over SSH, etc.).
Only explicitly configured apps are affected. All other apps pass through untouched.
- Per-app mapping — each application has its own gesture→action table; unconfigured apps are never touched
- Interactive config CLI — browse frontmost/running/recent apps, pick gestures, assign actions, auto-save
- X11/XQuartz compatible — translates pinch/swipe/rotate into Ctrl+scroll, Shift+scroll, PageUp/Down, middle click, etc.
- Scroll shaping — converts high-frequency trackpad scroll deltas into low-frequency discrete wheel notches
- Two scroll models — accumulator mode for exact notches, physics mode for water-dam style damping
- Debug mode —
--list-gesturesshows raw gesture data and Bundle IDs without injecting anything - No Xcode required — built entirely with Swift Package Manager
| Gesture | Description |
|---|---|
pinch_in |
Two-finger pinch (zoom out) |
pinch_out |
Two-finger spread (zoom in) |
swipe_left |
Two-finger swipe left |
swipe_right |
Two-finger swipe right |
rotate_cw |
Two-finger clockwise rotation |
rotate_ccw |
Two-finger counter-clockwise rotation |
smart_magnify |
Two-finger double-tap (smart zoom) |
| Action key | Injects |
|---|---|
none |
Disable this gesture |
middle_click |
Middle mouse click |
ctrl_scroll_up |
Ctrl + Scroll Up (zoom in) |
ctrl_scroll_down |
Ctrl + Scroll Down (zoom out) |
shift_scroll_up |
Shift + Scroll Up (horizontal left in many X11 apps) |
shift_scroll_down |
Shift + Scroll Down (horizontal right in many X11 apps) |
page_up |
PageUp |
page_down |
PageDown |
left_bracket |
[ key |
right_bracket |
] key |
ctrl_zero |
Ctrl + 0 (reset zoom) |
ctrl_equal |
Ctrl + = (zoom in) |
ctrl_minus |
Ctrl + - (zoom out) |
Trackpads emit very frequent, very small scroll deltas. Legacy X11 and remote GUI software often treats each incoming wheel event as a full discrete wheel notch, which makes direct forwarding scroll far too fast. TrackpadBridge always consumes raw trackpad scroll events for configured apps and emits model-shaped discrete wheel notches instead.
| Mode | Config key | Purpose |
|---|---|---|
| Accumulator | scroll_mode: accumulator |
Adds small deltas until scroll_notch_size is reached, then emits one or more wheel notches. Predictable and precise. |
| Physics | scroll_mode: physics |
Feeds deltas into a water-dam style velocity pool, then releases notches with viscous and Coulomb damping. Tunable for inertial feel. |
- macOS 13 Ventura or later
- Xcode Command Line Tools (
xcode-select --install) - Accessibility permission (System Settings → Privacy & Security → Accessibility)
# Clone
git clone https://github.com/CyrusZhang23/TrackpadBridge.git
cd TrackpadBridge
# Build and install to /usr/local/bin/trackpad-bridge
scripts/install.shOn first run, macOS will prompt you to grant Accessibility permission. Grant it, then re-run.
trackpad-bridge config
Launches an interactive menu:
════════════════════════════════════════════════════════════
TrackpadBridge 应用手势配置
────────────────────────────────────────────────────────────
应用列表(序号选择 · q 退出)
1. XQuartz org.xquartz.X11 [✓已配置·运行中]
2. iTerm2 com.googlecode.iterm2 [运行中]
3. Terminal com.apple.Terminal [运行中]
选择应用 > 2
── 🤏 捏合缩小
1. 🚫 禁用此手势
2. ⊕ Ctrl + 滚轮↑ (放大)
3. ⊖ Ctrl + 滚轮↓ (缩小) ← 选这个
...
Changes are saved to ~/.trackpad-bridge.yml immediately.
The app list prioritizes the current frontmost app, then recently active or recently launched apps, then configured apps.
If you don't know an app's Bundle ID, run:
trackpad-bridge --list-gesturesSwitch to the target app and perform gestures. Output:
🤏 Pinch delta=-0.0431 phase=changed app=XQuartz (org.xquartz.X11)
👆 Swipe dx=-0.48 dy=0.01 phase=ended app=XQuartz (org.xquartz.X11)
Copy the Bundle ID into trackpad-bridge config.
trackpad-bridgeRuns in the foreground. Use a process manager or Login Items to run at startup (see below).
trackpad-bridge --dry-runLocated at ~/.trackpad-bridge.yml (auto-created on first run).
# Only apps listed here are translated; everything else passes through.
apps:
"org.xquartz.X11":
pinch_in: ctrl_scroll_down
pinch_out: ctrl_scroll_up
swipe_left: shift_scroll_down
swipe_right: shift_scroll_up
rotate_cw: right_bracket
rotate_ccw: left_bracket
smart_magnify: ctrl_zero
scroll_mode: accumulator
scroll_notch_size: 3.0
"com.apple.Terminal":
pinch_in: ctrl_minus
pinch_out: ctrl_equal
scroll_mode: physics
scroll_gain: 5.0
scroll_viscous: 4.0
scroll_coulomb: 2.0
scroll_max_velocity: 60.0You can also edit this file directly — it will be picked up on the next daemon start.
Newly added apps start with all gesture actions set to none; the user explicitly chooses every mapping.
System Settings → General → Login Items → add trackpad-bridge.
scripts/install-launch-agent.shTo remove the LaunchAgent and installed binary:
scripts/uninstall.shmacOS Trackpad
│
▼
CGEventTap ←── TrackpadBridge (this daemon)
│
├── App not in config? ──► pass through unchanged
│
└── App in config? ──► GestureState (accumulate delta)
│
▼
Translator (gesture → KeyCombo)
│
▼
CGEvent inject (keyboard + scroll)
│
▼
Target Application
(XQuartz / X11 / etc.)
The daemon intercepts raw CGEvent gesture events via a session-level event tap. Gestures are accumulated in stateful accumulators (to handle the began/changed/ended phase cycle), then translated and re-injected as standard keyboard+scroll events that X11 programs understand natively.
For configured apps, raw high-frequency trackpad scroll events are not forwarded directly. They are converted by either the accumulator model or the physics model into discrete wheel notches before injection.
Sources/TrackpadBridge/
├── main.swift CLI entry point, argument parsing, subcommand routing
├── BridgeConfig.swift Types: KeyCombo, ActionPreset, GestureSlot,
│ AppGestureMap, MultiAppConfig (load/save)
├── AppWatcher.swift Frontmost-app tracker + recent-app persistence
├── GestureState.swift Stateful accumulators (pinch / rotate / swipe)
├── EventBridge.swift CGEventTap core: intercept → translate → inject
├── Injector.swift CGEvent synthesis and posting
├── ConfigCLI.swift Interactive configuration menu
└── ScrollDebugger.swift Real-time physics scroll debugger
The current package builds with Swift Package Manager. A future release should add tagged release artifacts and a Homebrew formula once on-device gesture validation is complete.
scripts/check.shscripts/cold-test.sh is a framework-free cold test runner for the pure logic that can be verified without live trackpad input. On-device gesture and scroll feel still need manual validation after installing the daemon; use docs/MANUAL_TESTING.md for that pass.
MIT — see LICENSE.