Touchpad edge gestures for Linux/Wayland.
edgepad turns touchpad edges into command zones while keeping normal pointer movement on the center of the pad. Swipe from an edge to run commands such as changing workspaces, opening a launcher, or sending desktop notifications.
The hard part is input correctness: Type-B multitouch slots, mixed edge/center contacts, SYN_DROPPED recovery, and virtual touch cleanup are covered by replay tests before they touch real hardware.
- Edge gestures on the left, right, top, and bottom touchpad zones.
- Continuous edge sliders for stepwise controls such as volume and brightness.
- Normal touchpad passthrough for unclaimed center contacts.
- Long-running user-session daemon with TOML config.
- Command actions as argv arrays, without shell re-splitting.
- Automatic touchpad discovery when exactly one readable candidate is present.
- Read-only device discovery and capture tools for debugging.
- Bounded live proxy mode for testing real hardware.
- Nix package, NixOS module, Home Manager module, release installer, and systemd user service.
The touchpad is split into four edge zones: left, right, top, and bottom.
- A gesture runs one action when the finger lifts. It can match either a directional swipe or a tap.
- A tap is a gesture with no meaningful movement.
- A slider runs repeated steps while the finger moves, which is useful for volume or brightness.
- An action is the command that edgepad starts for a gesture or slider step.
A slider and a tap gesture can share the same edge. A slider and a directional gesture cannot, because both would need to own the same movement.
curl -fsSL https://raw.githubusercontent.com/assembledev/edgepad/main/install.sh | shPreview the install plan first:
curl -fsSL https://raw.githubusercontent.com/assembledev/edgepad/main/install.sh | sh -s -- --dry-runThe release installer is the complete setup for an x86_64 Linux desktop with systemd. It downloads
a static binary, installs the udev rules, writes a default config to
~/.config/edgepad/edgepad.toml, installs and starts the user service, then runs edgepad doctor.
It asks for sudo only when installing the udev rules.
Run the same installer command again. It keeps your config, replaces the installed files, restarts the daemon, and checks that the service is healthy.
Uninstall files created by the release installer:
curl -fsSL https://raw.githubusercontent.com/assembledev/edgepad/main/install.sh | sh -s -- --uninstallThis keeps ~/.config/edgepad/edgepad.toml. To remove the config too:
curl -fsSL https://raw.githubusercontent.com/assembledev/edgepad/main/install.sh | sh -s -- --uninstall --purgeFor normal desktop use, install both the NixOS module and the Home Manager module. The NixOS module provides device access; the Home Manager module owns the config and user service. See the complete Nix setup.
The commands below only build or run the package. They do not install device rules or a user service.
Build and run from the repository:
nix build .#edgepad
./result/bin/edgepad --helpRun without installing:
nix run github:assembledev/edgepad -- --help
nix run github:assembledev/edgepad -- devicesThe config installed by the release script uses desktop notifications as safe example actions. Gestures will show what they matched, but they will not change your volume, brightness, media state, or workspace until you replace those commands.
Check that the service, config, and touchpad are ready:
edgepad statusThe service becomes active only after the virtual touchpad is created and the physical device is grabbed. Status output includes the ready daemon's PID, version, and selected device when systemd provides them.
If status reports a problem, run the full check:
edgepad doctorEdit your config:
$EDITOR ~/.config/edgepad/edgepad.tomlExample config:
device = "auto"
edge_width = 0.10
tap_min_duration_ms = 80
swipe_min_distance = 0.02
[[sliders]]
zone = "left"
up = ["notify-send", "edgepad", "volume-up"]
down = ["notify-send", "edgepad", "volume-down"]
[[sliders]]
zone = "right"
up = ["notify-send", "edgepad", "brightness-up"]
down = ["notify-send", "edgepad", "brightness-down"]
[[gestures]]
zone = "top"
direction = "tap"
action = ["notify-send", "edgepad", "play-pause"]Restart the user service after config changes:
systemctl --user restart edgepad.serviceWatch logs:
journalctl --user -u edgepad.service -fIf pointer input behaves incorrectly, stop edgepad immediately:
systemctl --user stop edgepad.serviceThe physical touchpad is ungrabbed when the daemon stops. Start it again with:
systemctl --user start edgepad.serviceFor a foreground run, edgepad daemon reads ~/.config/edgepad/edgepad.toml by default.
device can be "auto" or an explicit event node:
device = "auto"
# device = "/dev/input/event7"edge_width is the fraction of the touchpad reserved for edge zones on each side:
edge_width = 0.10tap_min_duration_ms ignores very short edge taps. It defaults to 80; set it to 0 to disable the guard.
tap_min_duration_ms = 80swipe_min_distance is the minimum normalized touchpad travel that turns an edge contact into a
directional gesture. It defaults to 0.02, or 2% of the corresponding touchpad axis. Smaller
movement remains a tap, so the same physical gesture behaves consistently across coordinate ranges.
swipe_min_distance = 0.02Each gesture binding has a zone, direction, and action:
[[gestures]]
zone = "top"
direction = "right"
action = ["notify-send", "edgepad", "top-right"]Zones:
left, right, top, bottom
Directions:
up, down, left, right, tap
Continuous controls use [[sliders]]. Side zones use vertical up/down steps; top and bottom zones use horizontal left/right steps. step is normalized touchpad travel and defaults to 0.04.
[[sliders]]
zone = "left"
step = 0.04
up = ["pamixer", "-i", "3"]
down = ["pamixer", "-d", "3"]Slider zones can share the same edge with tap gestures, but not with directional [[gestures]].
Actions are argv arrays. They are not run through a shell, so write shell logic explicitly when needed:
action = ["sh", "-c", "date >> /tmp/edgepad-actions.log"]For desktop commands, prefer running edgepad as a user service so actions inherit the user session instead of root's environment.
edgepad reads the physical touchpad, claims contacts that begin in configured edge zones, and forwards unclaimed contacts through a virtual touchpad.
For live forwarding it:
- opens the physical touchpad;
- reads the device's multitouch capabilities;
- creates a virtual touchpad through
/dev/uinput; - grabs the physical device;
- routes edge contacts to the gesture recognizer;
- emits normal contacts through the virtual device;
- releases virtual contacts and ungrabs the physical device on shutdown.
The output side does not blindly copy raw pointer-emulation events. BTN_TOUCH, BTN_TOOL_*, and legacy ABS_X/Y are synthesized from unclaimed passthrough contacts so an edge-owned finger does not leak into normal pointer movement. Physical touchpad buttons (BTN_LEFT and related pointer buttons) are passed through, and live mode preserves input properties such as INPUT_PROP_BUTTONPAD so libinput keeps clickpad behavior.
On a buttonpad/clickpad, a physical button press takes priority over edge recognition. Active edge-owned contacts are promoted to normal passthrough contacts before the button event, and remain passthrough until they lift, so physical clicks and click-drag work even inside configured edge zones. This cancels the pending edge gesture; slider steps already emitted are not rolled back. Tap-to-click does not produce a physical button event, so an edge tap keeps the normal edge-gesture behavior. Touchpads with separate buttons keep independent edge and button handling.
edgepad needs access to:
- the physical touchpad event node under
/dev/input/event*; /dev/uinputfor virtual touchpad output.
The preferred desktop setup is a user service with logind/uaccess ACLs. The NixOS module and release installer install udev rules for that mode.
Manual commands that read real input devices may need sudo, the input group, or active seat ACLs. Use edgepad doctor to see what your system is missing.
Start with these two commands:
edgepad status
edgepad doctorCommon problems:
- More than one touchpad was found: run
edgepad devices, then set an explicitdevice = "/dev/input/eventX"in the config. - The touchpad or
/dev/uinputis not accessible: use the access fix reported byedgepad doctor. If udev rules or group membership just changed, start a new login session. - The service stays in
activating: edgepad is still waiting for a readable touchpad or/dev/uinput. Checkedgepad doctorand the service log. - An action command is missing: install that program, use its full path, or replace the example action with a command available on your desktop.
- Pointer input is wrong: stop the service with
systemctl --user stop edgepad.service, then inspectjournalctl --user -u edgepad.service -b.
proxy, replay, and replay-raw use the normal edgepad config. Pass --config <file> to use
another config, or --built-in-defaults to ignore it.
Device discovery is read-only:
edgepad devices
edgepad devices --allCapture recognizer-level events from a real touchpad:
edgepad dump --device /dev/input/eventX --out bug.ev --frames 300
edgepad replay bug.evCapture raw evdev events for passthrough/output debugging:
edgepad dump --raw --device /dev/input/eventX --out bug.raw.ev --frames 300
edgepad replay-raw bug.raw.evInspect live routing without forwarding input:
edgepad proxy --device /dev/input/eventX --frames 300 --dry-runRun a bounded live virtual-touchpad proxy test:
This command grabs the physical touchpad for the duration of the test. Normal pointer input is sent through edgepad's temporary virtual touchpad until the frame limit is reached.
edgepad proxy --device /dev/input/eventX --frames 300 --uinput --grabReplace /dev/input/eventX with the touchpad node reported by edgepad devices. If the OS denies access to the event node or /dev/uinput, run edgepad doctor and use the access model it reports for your system.
edgepad devices List readable input devices and touchpad candidates
edgepad status Show a short daemon/config/device summary
edgepad doctor Check config, runtime prerequisites, actions, and service health
edgepad daemon Run the live edge-gesture proxy
edgepad dump Capture touchpad events into a replay fixture
edgepad proxy Run a bounded live proxy session for diagnostics
edgepad replay Replay a parsed fixture through the recognizer
edgepad replay-raw Replay a raw evdev capture through routing and output composition
Clone the repository and enter its development shell:
git clone https://github.com/assembledev/edgepad.git
cd edgepad
nix developBuild and check the project from there:
cargo build --locked
cargo fmt --check
cargo clippy --locked --all-targets -- -D warnings
cargo test --lockedFeedback, issues, and pull requests are welcome.
Licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT license (LICENSE-MIT)
at your option.