Getting Started

Install via Package Manager

Arch Linux (AUR)

yay -S padctl-git

A prebuilt binary package (padctl-bin) is also available in the AUR.

Debian / Ubuntu

curl -fLO https://github.com/BANANASJIM/padctl/releases/latest/download/padctl_amd64.deb
sudo dpkg -i padctl_amd64.deb

For arm64:

curl -fLO https://github.com/BANANASJIM/padctl/releases/latest/download/padctl_arm64.deb
sudo dpkg -i padctl_arm64.deb

Prerequisites

  • Zig 0.15+ (build from source)
  • Linux kernel ≥ 5.10 (uinput + hidraw support)
  • libusb-1.0 (system package, optional — pass -Dlibusb=false to build without)
  • A HID gamepad accessible via /dev/hidraw*

Build from Source

git clone https://github.com/BANANASJIM/padctl
cd padctl
zig build -Doptimize=ReleaseSafe

Optional build flags:

  • -Dlibusb=false — disable libusb linkage (uses hidraw-only path)
  • -Dwasm=false — disable WASM plugin runtime

GCC 15 build failure (issue #147): Arch Linux and similar distros with glibc 2.43+ may hit error: relocation R_X86_64_PC64 in .sframe section is unsupported — glibc 2.43 adds .sframe sections to crt1.o startup objects, which Zig 0.15.x's linker does not yet handle. This is an upstream Zig limitation, not a padctl bug. Use Dockerfile.wave5 (Debian bookworm + Zig 0.15.2 tarball, glibc 2.36) or install Zig 0.15.2 from the official tarball on a system with glibc ≤ 2.41 (Debian 12, Ubuntu 22.04/24.04 all work; Arch with glibc 2.43+ does NOT). Upstream fix: ziglang/zig#31272.

Install

sudo ./zig-out/bin/padctl install

This copies the binary, systemd service, device configs, and udev rules into /usr. It also runs systemctl daemon-reload and udevadm trigger automatically, and removes any legacy udev rules left by previous installs.

Custom prefix (e.g. for packaging):

sudo ./zig-out/bin/padctl install --prefix /usr --destdir "$DESTDIR"

Additional Services

padctl install also sets up the following on all systems:

  • padctl-reconnect — A hotplug script triggered by udev when a controller is plugged in. It starts the daemon if not running, restarts it if failed, and re-applies the active mapping. After suspend/resume the kernel re-emits udev events for re-enumerated devices, so the same hook handles post-wake reconnect — no separate resume unit is needed.
  • Driver conflict rules — Auto-generated udev rules that unbind conflicting kernel drivers (e.g., xpad) from devices that padctl manages. Configured per-device via block_kernel_drivers in device TOML configs. When run as root, padctl install also walks /sys/bus/usb/drivers/<driver>/unbind for matching VID:PID pairs immediately, so already-bound devices are evicted without waiting for replug (issue #162).

Install a Mapping

To install a mapping config to /etc/padctl/mappings/ during install:

sudo ./zig-out/bin/padctl install --mapping vader5

The --mapping flag is repeatable. Use --force-mapping to overwrite existing mapping files.

When --mapping is given, the installer also writes a device-to-mapping binding in /etc/padctl/config.toml so the daemon auto-applies the mapping on every boot. Use --force-binding to overwrite an existing binding for the same device.

Bazzite / immutable distros: See the Bazzite / Immutable Distros guide for special installation steps.

Install problems? See Troubleshooting for the devices/ warning, systemd 257+ status=218/CAPABILITIES, and the Arch glibc 2.43 build failure.

Verify

padctl scan

Lists all connected HID devices and shows whether a matching device config was found for each.

Run as Service

If you built from source, run the installer first — zig build alone does not install the service file:

zig build
sudo ./zig-out/bin/padctl install    # installs binary, service, device configs, and udev rules

padctl install automatically runs daemon-reload, enables, and starts padctl.service via sudo -u $SUDO_USER systemctl --user. The systemctl --user enable --now padctl.service line is only needed if you used --no-enable or --no-start.

To auto-start at boot without an active login session (headless setups, Steam Deck game mode):

sudo loginctl enable-linger $USER

The service runs padctl in daemon mode, scanning all config directories (user, system, and builtin) with automatic hotplug support. udev rules grant access via uaccess — no sudo needed for the logged-in user.

Check the daemon is running:

$ padctl status
STATUS device=Flydigi Vader 5 Pro state=active mapping=fps

Each managed device prints one space-separated triple: device=<name>, state=<active|suspended>, mapping=<active mapping name|(none)>. Multiple devices appear on the same line. Exit code is 0 when the daemon answered and 1 when the response begins with ERR or the socket is unreachable.

Run Manually

Bare invocation — padctl auto-discovers configs via XDG paths:

padctl

Or target specific configs:

# Single config
padctl --config /usr/share/padctl/devices/sony/dualsense.toml

# All configs in a directory
padctl --config-dir /usr/share/padctl/devices/

Validate a Config

padctl --validate devices/sony/dualsense.toml      # device config
padctl --validate ~/.config/padctl/mappings/fps.toml  # mapping config

--validate auto-detects which schema to apply by scanning for a [device] section header — files containing [device], [device.*], or [[device.*]] are validated as device configs; everything else (including bare name = ... mapping files) is validated against the mapping schema.

Exit 0 = valid. Exit 1 = validation errors printed to stderr. Exit 2 = file not found or parse failure.

The flag is repeatable: padctl --validate a.toml --validate b.toml validates both files and exits with the worst code seen.

Generate Device Docs

padctl --doc-gen --config devices/sony/dualsense.toml

User Config

padctl reads a config file to set per-device default mappings. The loader checks these paths in order (first found wins):

  1. ~/.config/padctl/config.toml — user overrides (highest priority)
  2. /etc/padctl/config.toml — system-wide defaults (written by padctl install --mapping)
version = 1

[[device]]
name = "Flydigi Vader 5 Pro"
default_mapping = "fps"

On daemon start, padctl matches the connected device name (case-insensitive) and loads the named mapping profile automatically. The system path is the fallback for environments where HOME is not set (e.g. systemd services).

padctl switch <name> automatically updates the user config, so the choice is remembered for bare padctl switch (re-apply without a name). Bare padctl switch (no argument) reads default_mapping from the connected device's entry in config.toml; if no entry exists, it prints error: no default_mapping in config.toml for device "<name>" and exits. To make the choice survive reboots, use padctl switch <name> --persist which copies the mapping and config to /etc/padctl/ via sudo.

CLI Reference

padctl switch [name] [--device <id>]       # switch mapping; omit name to fall back to default_mapping from config.toml
padctl switch <name> --persist             # switch + copy to /etc/padctl/ for reboot persistence (sudo)
padctl status [--socket <path>]            # show daemon status
padctl devices [--socket <path>]           # list connected devices
padctl list-mappings [--config-dir <dir>]  # list available mapping profiles
padctl reload [--pid <pid>]                # send SIGHUP to reload configs
padctl config list                         # show XDG config search paths
padctl config init [--device] [--preset]   # interactive mapping creator
padctl config edit [name]                  # open mapping in $VISUAL/$EDITOR
padctl config test [--config] [--mapping]  # live input preview
padctl dump enable|disable                 # toggle diagnostic logging (persists)
padctl dump status                         # show dump state, log path, size, time span
padctl dump export --period Nm|Nh|Nd [-o file]  # export filtered log window
padctl dump clear                          # delete all log files

See the Diagnostic Logging guide for the full padctl dump workflow, log paths, and the [diagnostics] config section.

udev Permissions

padctl needs access to /dev/hidraw*, /dev/uinput, and /dev/uhid. The first two are standard for HID gamepad daemons; /dev/uhid is required for the SDL3-visible IMU pairing path (per ADR-015) — padctl install writes the necessary udev rule (60-padctl.rules) and a DeviceAllow=/dev/uhid rw entry in the systemd unit automatically.

The padctl install command generates and installs udev rules automatically from device configs.

If you need to regenerate rules after adding custom device configs:

sudo padctl install

The udev rules use TAG+="uaccess" to grant the logged-in user access to supported devices without requiring root.