Skip to content

BANANASJIM/padctl

Repository files navigation

padctl

Universal Linux gamepad compatibility layer

This project is very much a work in progress. Feedback, bug reports, and feature requests are welcome — please open an issue!

CI License

What is padctl

padctl is a userspace daemon that maps vendor-specific USB/HID gamepad reports to standard Linux input events via uinput. Device support is driven entirely by declarative TOML configs — no kernel patches, no custom drivers.

Features

  • Declarative device configs — add new devices with a .toml file, no recompilation
  • Layer system — hold/toggle/tap-hold layers with independent remaps, gyro, and stick modes
  • Gyro mouse — gyro-to-mouse with sensitivity, deadzone, smoothing, and curve controls
  • Stick mouse/scroll — left or right stick as mouse or scroll wheel
  • Macros — named key sequences bound to any button
  • Exclusive device grab — grabs the hidraw/evdev node so the original device is hidden from other processes while padctl is running
  • Multi-device + hotplug — automatic device detection and per-device threads via netlink
  • Hot-reloadSIGHUP re-reads configs without restart, diffed per physical device
  • Force feedback — FF_RUMBLE passthrough from uinput to physical device with userspace auto-stop timer (compensates for uinput not using the kernel's ff-memless driver)
  • Runtime mapping switchpadctl switch <name> changes profiles without restart
  • Persistent mappingpadctl install --mapping <name> writes a device binding to /etc/padctl/config.toml that auto-applies on every boot
  • User config~/.config/padctl/config.toml for per-device default mappings (system fallback: /etc/padctl/config.toml)
  • Opt-in diagnostic loggingpadctl dump enable turns on a general-purpose, togglable file logger so users can produce a structured log for any class of bug report (force-feedback, input, mapping, hotplug, …). Today it is wired deepest into the rumble/HID path; more subsystems will be instrumented over time. Rotated, bounded on disk, and zero overhead when disabled (default)
  • CLI toolspadctl status, padctl devices, padctl list-mappings, padctl config init/edit/test, padctl dump enable/disable/status/export/clear

Architecture

+----------------------------+
| Physical Device (USB / BT) |
+----------------------------+
              |
      +-------+-------+
      |               |
      v               v
+----------------+  +-------------------+
| HID / hidraw   |  | Vendor / libusb   |
| io/hidraw.zig  |  | io/usbraw.zig     |
+----------------+  +-------------------+
       \              /
        \            /
         v          v
      +--------------------+
      | DeviceIO (unified) |
      +--------------------+
                |
                v
      +--------------------+
      | main loop (ppoll)  |
      +--------------------+
                |
      +---------+---------+
      |                   |
      v                   v
+------------------+  +------------------+
| config/device.zig|  | io/hotplug.zig   |
| devices/*.toml   |  | udev monitor     |
+------------------+  +------------------+
          |
          v
+-----------------------------------------+
| [input rules] -> interpreter -> state   |
| [output]     -> OutputConfig            |
+-----------------------------------------+
                      |
                      v
           +----------------------+
           | mapper (layer/remap) |
           +----------------------+
                |            |
                v            v
      +----------------+  +------------------+
      | gamepad output |  | generic output   |
      | uinput + aux   |  | generic + touch  |
      +----------------+  +------------------+

Supported Devices

Ships with production configs for 12 supported device families across 8 vendors. devices/ also contains a Flydigi DInput/2.4G compatibility variant plus example/test fixtures, so the raw TOML file count is higher:

Sony (3) · Nintendo (1) · Microsoft (1) · Valve (1) · 8BitDo (1) · Flydigi (2) · HORI (1) · Lenovo (2)

Full device list with feature matrix →

Installation

Arch Linux (AUR)

yay -S padctl-bin   # prebuilt binary
yay -S padctl-git   # build from source

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

After installing via a package manager, enable the user service from your normal login session:

systemctl --user daemon-reload
systemctl --user enable --now padctl.service

From Source

See Quick Start below. For other distros, see CONTRIBUTING.md.

Quick Start

zig build                                             # build from source
sudo zig-out/bin/padctl install                       # install binary, udev rules, service; starts user service when run via sudo
padctl status                                         # check daemon and detected devices
padctl config init                                    # create a mapping in ~/.config/padctl/mappings/ interactively
padctl list-mappings                                  # show generated and installed mapping profiles
padctl switch <name>                                  # switch mapping profile without restart

padctl runs as a systemd user service. The default root install writes the system-wide user unit under /usr/lib/systemd/user/ and enables/starts it for the invoking user via systemctl --user; immutable and custom-prefix installs may use /etc/systemd/user/. The binary and udev rules still require root to install, but the daemon runs as your own user. Run systemctl --user enable --now padctl.service manually only if install was run with --no-enable, --no-start, or without a discoverable SUDO_USER.

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

sudo loginctl enable-linger $USER

Bazzite / Steam Deck: linger behavior depends on the desktop session configuration. Auto-start at boot without login is not verified on these platforms.

See the getting started guide for detailed setup.

CLI Reference

Command Description
padctl status Show daemon state and active devices
padctl devices List detected HID/USB devices
padctl list-mappings Show available mapping profiles
padctl switch <name> Switch to a named mapping profile
padctl config init [--preset <name>] Interactively create a new mapping file in ~/.config/padctl/mappings/. Valid --preset values: xbox-360, xbox-elite2, dualsense, switch-pro.
padctl config edit <mapping> Open mapping in $VISUAL or $EDITOR
padctl config test <mapping> Live input preview against the mapping (no apply)
padctl scan Re-scan for connected devices
padctl dump enable|disable Toggle opt-in diagnostic logging (persists across reboots)
padctl dump status Show logging state, log path, size, and time span
padctl dump export --period <N>m|<N>h|<N>d [-o file] Export recent log window for bug reports
padctl dump clear Delete all log files

Build

Requirements: Zig 0.15+, libusb-1.0

zig build              # build all binaries
zig build test         # run unit tests
zig build check-all    # all checks (test + safe + fmt)
Flag Default Effect
-Dlibusb=false true Disable libusb linkage (hidraw-only)
-Dwasm=false true Disable WASM plugin runtime

Known build issues

GCC 15 — R_X86_64_PC64 in .sframe linker error (issue #147)

glibc 2.43+ (shipped on Arch, Artix, and similar bleeding-edge distros) adds .sframe sections to crt1.o and related startup objects. Zig 0.15.x's linker does not yet handle the R_X86_64_PC64 relocation type used there, producing:

error: relocation R_X86_64_PC64 in .sframe section is unsupported

This is an upstream Zig limitation, not a padctl bug. Workarounds:

  1. Use the canonical Docker image (recommended)./scripts/padctl-docker build builds inside the Debian bookworm image (glibc 2.36) with the Zig version pinned by .zigversion, which is the supported CI build environment. See Build with Docker below.
  2. Install Zig 0.15.2 from the official tarball (https://ziglang.org/download/) on a system with glibc ≤ 2.41 (Debian 12 = glibc 2.36, Ubuntu 22.04 = glibc 2.35, Ubuntu 24.04 = glibc 2.39 all work; Arch with glibc 2.43+ does NOT).
  3. Track upstream fix progress at ziglang/zig#31272.

Build with Docker

If you cannot install Zig locally — or hit the glibc 2.43+ linker error above — build padctl inside the canonical Docker image instead. It pins the exact Zig version from .zigversion against Debian bookworm (glibc 2.36), so it matches the CI build environment.

./scripts/padctl-docker build      # zig build inside the image
./scripts/padctl-docker test       # zig build test inside the image
./scripts/padctl-docker shell      # interactive shell for debugging

The first invocation builds the image (padctl-build:<zig-version>); later runs reuse it. The repository is bind-mounted at /src, so build output lands in your working tree as usual. Requires only Docker — no local Zig toolchain.

Bazzite / Immutable Distros

On immutable distributions (Bazzite, Fedora Atomic, etc.) where /usr is read-only, use the bootstrap script for a complete one-command setup:

curl -fsSL https://raw.githubusercontent.com/BANANASJIM/padctl/main/scripts/bazzite-setup.sh \
  | bash -s -- --mapping vader5

Replace vader5 with the mapping for your controller, or omit --mapping to install without a mapping. When run locally (bash scripts/bazzite-setup.sh), the script prompts for mapping selection interactively.

See the Bazzite / Immutable Distros guide for full details on what the install does, the --immutable flag, security notes, and mapping management.

Tested on: Bazzite (Fedora Atomic / ostree). Other immutable distros may work but are untested.

V2 note: Bazzite and Steam Deck default linger state has not been verified with the user-service install. loginctl enable-linger is required for auto-start without an active session but its interaction with game-mode auto-login is unconfirmed.

Documentation

Full documentation: bananasjim.github.io/padctl

Contributing

See CONTRIBUTING.md for guidelines on adding device configs or contributing code.

License

LGPL-2.1-or-later — see LICENSE.

About

HID gamepad daemon with declarative TOML device config

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors