skhd for your trackpad. Define taps and swipes in particular quadrants of your trackpad and link them to hotkeys or shell scripts. Also define knocks, tap/swipe sequences, (think: up, up, down, down, left, right, left, right).
Example config:
# Two-finger tap in the top-right corner -> switch apps.
tap fingers=2 zone=top-right key=cmd+tab>return
# Three-finger tap in the bottom-right corner -> launch/open Ghostty
tap fingers=3 zone=bottom-right shell=/usr/bin/open -a Ghostty
# A short one-finger flick to the right, anywhere -> Spotlight.
swipe fingers=1 dir=right key=cmd+space
# Only while Ghostty is frontmost: flick down -> tmux prefix, then l.
swipe fingers=2 dir=down app=Ghostty key=ctrl+a>l
# A four-finger tap arms knock mode for ~0.5s, with a sound as feedback.
unlock fingers=4 shell=/usr/bin/afplay /System/Library/Sounds/Tink.aiff
# Knocks — sequences of taps/swipes, only live in that window after the unlock.
knock steps=1,2 key=cmd+space
knock steps=1@top-left,1@bottom-right shell=/usr/bin/open -a Ghostty
knock steps=1@top-left,1:down shell=/usr/bin/pmset displaysleepnow
| Thing | Location |
|---|---|
| Binary | /usr/local/bin/tapd |
| Config | ~/.config/tapd/tapd.conf |
| LaunchAgent | ~/Library/LaunchAgents/com.jhickner.tapd.plist |
| Logs | ~/Library/Logs/tapd.{out,err}.log |
make # builds ./tapd and code-signs it (see First-time setup below)Requires the Xcode command-line tools (clang). Builds a universal binary for
Apple Silicon and Intel.
-
Code-signing identity (once) — gives the binary a stable signature so the Accessibility grant survives rebuilds:
./sign/setup-signing.sh
Follow its prompts; it may point you at Keychain Access to mark the
tapd-devcert Code Signing → Always Trust (the trust API can't be driven from the CLI). -
Install:
make install # binary -> /usr/local/bin (uses sudo), config -> ~/.config # no-sudo alternative: make install PREFIX=~/.local
-
Grant permissions in System Settings → Privacy & Security:
- Accessibility → add
/usr/local/bin/tapdand enable it. Required forkey=actions (synthesizing keystrokes). Not needed forshell=actions. - Reading the trackpad needs no grant on macOS 14. If touches never register on a newer macOS, also add it under Input Monitoring.
- Accessibility → add
-
Run at login:
/usr/local/bin/tapd --install-service # writes the LaunchAgent plist /usr/local/bin/tapd --start-service # loads + launches it now
tapd --install-service # write the LaunchAgent plist
tapd --start-service # load and launch
tapd --stop-service # unload
tapd --restart-service # after replacing the binary (config reloads on its own)
tapd --uninstall-service # stop and remove the plist
tapd ~/.config/tapd/tapd.conf # foreground; Ctrl-C to quit
tapd --log # dump raw contacts (calibration)
tapd -v <config> # log every recognized gesture, matched or notMatched gestures and dispatched actions are logged to stderr (→ the err log under
launchd). Unmatched gestures are silent unless -v is given, so normal scrolling
doesn't flood the log.
One trigger per line; see tapd.conf for the full annotated syntax.
Summary:
tap fingers=2 zone=top-right key=cmd+tab
tap fingers=3 zone=bottom-right shell=/usr/bin/open -a Terminal
swipe fingers=1 dir=right key=cmd+space
knock steps=1,2 key=cmd+space
knock steps=1@top-left,1@bottom-right shell=/usr/bin/open -a Terminal
- zones (origin bottom-left, so "top" = upper edge):
top-left top-right bottom-left bottom-right top bottom left right center any - taps use
zone= - swipes use any of
dir=<left|right|up|down>(a short directional flick),from=<zone>, andto=<zone>— all optional and combinable. A baredir=rightfires on a short flick anywhere;from=top-left to=bottom-rightstill works for a full corner-to-corner drag. - knocks are a sequence of steps (≥ 1) via
steps=, comma-separated, each<fingers>[@<zone>][:<dir>]. Without:dira step is a tap (1,2@top-left); with it, a swipe (1:down,1@top-left:down). E.g.steps=1,2,steps=1@top-left,1@bottom-right, orsteps=1@top-left,1:down(tap top-left, then swipe down). Steps must occur within ~0.6s of each other. - actions:
key=<combo>(modscmd/shift/alt/ctrl/fn+ a key), or a>-separated sequence of combos for leader-key pairs — e.g.key=ctrl+a>lpresses Ctrl-A then L (a tmux prefix binding); orshell=<command>(runs to end of line via/bin/sh -c) - app gate (any trigger): add
app=<name>to fire only when that app is frontmost, matched case-insensitively against the app's name, e.g.app=Ghostty. Find the exact name withtapd --frontapp(focus the app and read what it prints). A gated knock also won't cause tap-deferral outside its app.
By default a single tap fires immediately unless it could begin a defined knock, in which case it waits up to ~0.6s to see whether the rest of the knock follows.
To keep every tap instant, define an unlock gesture:
unlock fingers=4 shell=/usr/bin/afplay /System/Library/Sounds/Tink.aiff
With an unlock set, taps never defer — knocks are matched only during a
~0.5s window after the unlock tap (like a leader key). The optional action runs
on arm as feedback (e.g. a sound). fingers=N, optional zone=<zone>, and an
optional key=/shell= action. At most one unlock line.
This pairs well with single-step knocks: knock steps=1:right ... does
nothing on its own, but after the unlock it fires on one swipe right — a gesture
that can't trigger accidentally because it only exists while armed.
Edits are picked up automatically — the daemon polls the config file and reloads
within ~1s of a save (it logs config reloaded: N trigger(s)). No restart needed
unless you replace the binary itself.
- macOS Tahoe (26.x): WindowServer may drop synthesized global shortcuts
(Cmd+Tab, Cmd+Space) from a signed-but-not-Apple binary.
shell=actions are unaffected and are the robust choice there. MultitouchSupportis a private, undocumented framework — a future macOS could change or remove it. The whole private surface is isolated insrc/MultitouchSupport.h+src/mt_input.c.- The keyboard-key input source (trapping physical keys via a CGEventTap) is not
built yet;
src/input_source.hmarks the seam for it.
MIT — see LICENSE.