Bezel is a Linux daemon that provides customizable trackpad edge gestures. It intercepts raw trackpad inputs via evdev and dispatches shell commands based on directional swipes or taps along the edges (zones) of your trackpad.
Bezel is Linux-only (Wayland required).
curl -sSfL https://raw.githubusercontent.com/indra55/bezel/main/install.sh | bash(To update to a newer version, simply run this exact same command again. It will safely back up your old binary and seamlessly restart the service.)
cargo install --git https://github.com/indra55/bezelNormally we'd just tell you to yay -S bezel, but the AUR is currently experiencing a massive malware apocalypse. Someone adopted 1,500 orphaned packages and turned them into malware, so Arch had to disable new account registrations. We have our PKGBUILD ready to go, but until they put out the fire, you'll have to use the prebuilt binary or build from source like a normal person. Stay safe out there!
You can try out Bezel using this command:
nix run github:indra55/bezelFor a permanent installation, first add Bezel to your flake inputs:
{
inputs = {
# ... other inputs
bezel = {
url = "github:Indra55/bezel";
inputs.nixpkgs.follows = "nixpkgs";
};
};
# ... rest of your flake
}Also make sure that you have extraSpecialArgs = { inherit inputs; }; in your flake outputs.
Bezel provides a NixOS module. To enable it:
{ inputs, ... }: {
imports = [
inputs.bezel.nixosModules.default
];
}You can now enable the Bezel service. This snippet will:
- install the Bezel package on your system;
- create and enable the Bezel service for all users;
- configure udev rules.
NOTE: you'll still have to add yourself to the
inputanduinputgroups
{ ... }: {
services.bezel.enable = true;
}If you prefer to enable Bezel per-user instead, you can do so using Home Manager.
{ inputs, ... }: {
imports = [
inputs.bezel.homeModules.default
];
services.bezel.enable = true;
}homeManagerModules.default remains available for existing configurations.
NOTE: If you use the Home Manager module, you'll have to enable uinput separately in your NixOS config, as Home Manager doesn't have access to them:
{ ... }: {
hardware.uinput.enable = true;
}Add yourself to the input group (required on all distros):
sudo usermod -aG input $USER
# reboot your computer after thisNixOS Users: Add "input" and "uinput" to your users.users.<name>.extraGroups instead of using usermod.
If you still get Permission denied (os error 13) after rebooting, you may need custom udev rules for your physical and virtual trackpads. Create /etc/udev/rules.d/99-bezel.rules:
SUBSYSTEM=="input", KERNEL=="event*", ENV{ID_INPUT_TOUCHPAD}=="1", GROUP="input", MODE="0640"
KERNEL=="uinput", MODE="0660", GROUP="input", OPTIONS+="static_node=uinput"
Then reload udev rules with sudo udevadm control --reload-rules && sudo udevadm trigger.
NixOS Users: set either services.bezel.enable = true or hardware.uinput.enable = true
Bezel looks for its configuration at ~/.config/bezel/config.toml.
On first install, install.sh launches a terminal onboarding wizard. It detects your Wayland desktop, offers numbered choices for Hyprland, Niri, Sway, Plasma, GNOME, or other Wayland desktops, and shows each trackpad edge as you configure it. Each edge supports up, down, left, right, and tap, with 16 available actions on Hyprland, 15 on Niri and Sway, and 11 on other desktops. Press Enter to keep suggested bindings, or customize any of the 20 edge/gesture slots. Hyprland, Niri, and Sway get their own workspace commands; Plasma, GNOME, and unknown desktops omit workspace actions. On upgrades, the installer asks whether to run onboarding again when a config exists. Choosing no keeps the config. Choosing yes opens the wizard, where Apply now backs up and replaces it, while Save preview only leaves it unchanged.
The active trackpad edge uses a static warm-orange halo in color terminals. If your shell sets NO_COLOR, run bash onboard.sh --color to preview the accent anyway; --no-color forces the monochrome version.
On NixOS, the installer prints a Home Manager snippet instead of creating an unmanaged TOML file or systemd service. Import the Bezel Home Manager module, add the snippet, and configure hardware.uinput plus the input and uinput groups in your NixOS configuration. The snippet includes the gesture command tools (wpctl, brightnessctl, playerctl).
For Arch, Debian/Ubuntu, Fedora, and openSUSE, the same installer uses systemd user services and udev; you need Rust only when building from source or when a prebuilt binary is unavailable. The command tools above must be installed separately.
To define a gesture, specify the zone and direction, and the command to run: This example uses Hyprland 0.55+ syntax; older Hyprland releases use different dispatch commands.
[gestures.top.left]
action = "command"
cmd = "hyprctl dispatch 'hl.dsp.focus({ workspace = \"e-1\" })'"(See config.toml.example in this repo for a basic template.)
To configure OSD labels, add the following:
[osd]
enabled = true
backend = "notify-send" # Valid options: "notify-send", "swayosd", "pipe"
canonical_hints = false # Set to true only if using mako or notify-osdNixOS Users: After importing the Home Manager module you can also customize Bezel using the services.bezel.config option. See config.nix.example for a complete template.
Warning
If you used the NixOS/HM module or install.sh script, Bezel is already running as a systemd service! Do not add these autostart commands, or you will run two instances simultaneously and they will crash.
Start Bezel when your Wayland compositor starts. Void Linux / non-systemd users should use this method instead of a background service. If bezel is not in your system $PATH, use the absolute path ~/.local/bin/bezel (or ~/.cargo/bin/bezel if built with cargo).
For Hyprland 0.55+ (~/.config/hypr/hyprland.lua):
hl.on("hyprland.start", function()
hl.exec_cmd("~/.local/bin/bezel")
end)For older Hyprland (~/.config/hypr/hyprland.conf):
exec-once = ~/.local/bin/bezelFor Sway (~/.config/sway/config):
exec ~/.local/bin/bezelFor Niri (~/.config/niri/config.kdl):
spawn-at-startup "~/.local/bin/bezel"Bezel includes a web-based visualizer that shows a graphical representation of your trackpad and animates when you perform gestures. It's useful for testing, debugging, or recording demos.
To use the visualizer:
- Update your
~/.config/bezel/config.tomlto use thepipeOSD backend:[osd] enabled = true backend = "pipe"
- Restart Bezel (
systemctl --user restart bezel.serviceor restart manually). - Start the visualization server from the root of this repository:
python3 viz/server.py
- Open
http://localhost:8080in your web browser. When you perform gestures, the server will read them from the pipe (/tmp/bezel-osd) and broadcast them to the webpage in real-time.
If you are running both Bezel and libinput-gestures, you may find that libinput-gestures stops working when Bezel is active. This happens because Bezel grabs the physical trackpad exclusively, and libinput-gestures' auto-detection heuristic prefers devices with "touchpad" in the name over "trackpad". It will bind to the silenced hardware device instead of the virtual one created by Bezel.
To fix this, you must explicitly tell libinput-gestures to use Bezel's virtual device by adding the following line to your /etc/libinput-gestures.conf (or ~/.config/libinput-gestures.conf if you're using a per-user config):
device Bezel Virtual TrackpadThen restart libinput-gestures (libinput-gestures-setup restart).
If OSD notifications (notify-send) fail or don't show up when Bezel is run as a systemd service, your compositor might not be exporting the D-Bus environment properly. You can check the logs to see if notify-send is exiting with an error.
To fix this, add the following to your compositor's startup config to import the session variables:
Hyprland 0.55+ (hyprland.lua):
hl.on("hyprland.start", function()
hl.exec_cmd("systemctl --user import-environment WAYLAND_DISPLAY XDG_CURRENT_DESKTOP DBUS_SESSION_BUS_ADDRESS")
end)Older Hyprland (hyprland.conf):
exec-once = systemctl --user import-environment WAYLAND_DISPLAY XDG_CURRENT_DESKTOP DBUS_SESSION_BUS_ADDRESSSway:
exec systemctl --user import-environment WAYLAND_DISPLAY SWAYSOCK DBUS_SESSION_BUS_ADDRESS(Niri does this automatically).
To enable more detailed logging, you can set the BEZEL_LOG environment variable. Valid log levels are error, warn, info, debug, and trace. For example:
BEZEL_LOG=debug bezelIf you are running Bezel as a systemd service, you can run systemctl --user edit bezel.service and add the following to the override file:
[Service]
Environment="BEZEL_LOG=debug"Then restart the service.
If your trackpad stops responding, restart the service:
systemctl --user restart bezel.service