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 running/recent apps, pick gestures, assign actions, auto-save
  • X11/XQuartz compatible — translates pinch/swipe/rotate into Ctrl+scroll, Alt+arrow, PageUp/Down, etc.
  • 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
ctrl_scroll_up Ctrl + Scroll Up (zoom in)
ctrl_scroll_down Ctrl + Scroll Down (zoom out)
alt_left Alt + ← (back / prev page)
alt_right Alt + → (forward / next page)
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)
none Disable this gesture

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 (release)
swift build -c release

# Install to /usr/local/bin
sudo cp .build/release/TrackpadBridge /usr/local/bin/trackpad-bridge

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.

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: alt_left
    swipe_right: alt_right
    swipe_up: page_up
    swipe_down: page_down
    rotate_cw: right_bracket
    rotate_ccw: left_bracket
    smart_magnify: ctrl_zero

  "com.apple.Terminal":
    pinch_in: ctrl_minus
    pinch_out: ctrl_equal
    swipe_up: page_up
    swipe_down: page_down

You can also edit this file directly — it will be picked up on the next daemon start.


Auto-start at Login

Option A — Login Items (macOS 13+)

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

Option B — launchd plist

cat > ~/Library/LaunchAgents/com.cyruszhang.trackpad-bridge.plist << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>             <string>com.cyruszhang.trackpad-bridge</string>
  <key>ProgramArguments</key>  <array><string>/usr/local/bin/trackpad-bridge</string></array>
  <key>RunAtLoad</key>         <true/>
  <key>KeepAlive</key>         <true/>
  <key>StandardOutPath</key>   <string>/tmp/trackpad-bridge.log</string>
  <key>StandardErrorPath</key> <string>/tmp/trackpad-bridge.err</string>
</dict>
</plist>
EOF

launchctl load ~/Library/LaunchAgents/com.cyruszhang.trackpad-bridge.plist

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.


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

License

MIT — see LICENSE.

About

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

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages