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=falseto 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.sframesections tocrt1.ostartup objects, which Zig 0.15.x's linker does not yet handle. This is an upstream Zig limitation, not a padctl bug. UseDockerfile.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 viablock_kernel_driversin device TOML configs. When run as root,padctl installalso walks/sys/bus/usb/drivers/<driver>/unbindfor 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):
~/.config/padctl/config.toml— user overrides (highest priority)/etc/padctl/config.toml— system-wide defaults (written bypadctl 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.