Skip to content

CyrusZhang23/TrackpadBridge

Repository files navigation

TrackpadBridge

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.


Features

  • 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-gestures shows raw gesture data and Bundle IDs without injecting anything
  • No Xcode required — built entirely with Swift Package Manager

Supported Gestures

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)

Available Actions

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)

Scroll Filtering Modes

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.

Requirements

  • macOS 13 Ventura or later
  • Xcode Command Line Tools (xcode-select --install)
  • Accessibility permission (System Settings → Privacy & Security → Accessibility)

Installation

# Clone
git clone https://github.com/CyrusZhang23/TrackpadBridge.git
cd TrackpadBridge

# Build and install to /usr/local/bin/trackpad-bridge
scripts/install.sh

On first run, macOS will prompt you to grant Accessibility permission. Grant it, then re-run.


Usage

1 — Configure apps interactively

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.

2 — Discover Bundle IDs (debug mode)

If you don't know an app's Bundle ID, run:

trackpad-bridge --list-gestures

Switch 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.

3 — Start the daemon

trackpad-bridge

Runs in the foreground. Use a process manager or Login Items to run at startup (see below).

4 — Dry-run (verify without injecting)

trackpad-bridge --dry-run

Configuration File

Located 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.0

You 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.


Auto-start at Login

Option A — Login Items (macOS 13+)

System Settings → General → Login Items → add trackpad-bridge.

Option B — launchd plist

scripts/install-launch-agent.sh

To remove the LaunchAgent and installed binary:

scripts/uninstall.sh

How It Works

macOS 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.


Project Structure

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

Distribution Roadmap

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.

Development Checks

scripts/check.sh

scripts/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.


License

MIT — see LICENSE.

About

Per-app macOS trackpad gesture translator for X11/XQuartz compatibility

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors