Skip to content

Repository files navigation

Bezel

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 Demo


Installation

Bezel is Linux-only (Wayland required).

Prebuilt binary

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

From source

cargo install --git https://github.com/indra55/bezel

Arch Linux

Normally 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!

NixOS

You can try out Bezel using this command:

nix run github:indra55/bezel

For 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:

  1. install the Bezel package on your system;
  2. create and enable the Bezel service for all users;
  3. configure udev rules. NOTE: you'll still have to add yourself to the input and uinput groups
{ ... }: {
  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;
}

Setup

Add yourself to the input group (required on all distros):

sudo usermod -aG input $USER
# reboot your computer after this

NixOS 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

Configuration

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-osd

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

Autostart

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/bezel

For Sway (~/.config/sway/config):

exec ~/.local/bin/bezel

For Niri (~/.config/niri/config.kdl):

spawn-at-startup "~/.local/bin/bezel"

Web Visualizer (Demo)

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:

  1. Update your ~/.config/bezel/config.toml to use the pipe OSD backend:
    [osd]
    enabled = true
    backend = "pipe"
  2. Restart Bezel (systemctl --user restart bezel.service or restart manually).
  3. Start the visualization server from the root of this repository:
    python3 viz/server.py
  4. Open http://localhost:8080 in 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.

Troubleshooting

Compatibility with libinput-gestures

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 Trackpad

Then restart libinput-gestures (libinput-gestures-setup restart).

OSD Notifications Not Showing

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_ADDRESS

Sway:

exec systemctl --user import-environment WAYLAND_DISPLAY SWAYSOCK DBUS_SESSION_BUS_ADDRESS

(Niri does this automatically).

Debugging Logs

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 bezel

If 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

About

A lightweight Rust daemon for trackpad gestures on Wayland compositor

Topics

Resources

Stars

152 stars

Watchers

2 watching

Forks

Releases

Packages

Contributors

Languages