Skip to content

Releases: BANANASJIM/padctl

v0.1.4

29 Apr 13:33
940e5b7

Choose a tag to compare

padctl v0.1.4

Released: 2026-04-29
Tag base: v0.1.2 (9ceab0c)

44 commits, 35 PRs, 84 files changed (+16,345 / −972). v0.1.3 was drafted but never published; the scope was rolled forward into v0.1.4.


🚨 Bug Fixes

User-visible fixes (with issue references):

PR Issue Description
#151 #79 timerfd scheduling driftnow_ns snapshot captured before fd dispatch; timerfd handlers moved after gamepad loop
#152 #99 Macro cannot trigger gamepad/mouse buttons — engine now correctly dispatches .gamepad_button / .mouse_button targets
#171 #72 Macro timerfd vs layer timerfd collision — separated into independent slots so macro delays are no longer clobbered
#172 #136 padctl switch with no argument — reads [[device]].default_mapping and switches; clear error if unset
#165 #162 Hotplug + xpad shadow — proactive xpad unbind + broader udev ACTION coverage
#164 #163 LT/RT remap silently ineffective — validate now warns when remap targets LT/RT without trigger_threshold

systemd / install fixes:

PR Issue Description
#134 #131-A Zombie uinput on suspend — supervisor suspend_grace_sec window
#133 #131-B Obsolete resume servicepadctl-resume.service removed
#141 #139 Missing XDG parent dirsensureUserXdgDirs now creates ~/.local/state/padctl parents
#146 #139 v2 systemd v254+ symlink residue — legacy state-dir symlink replaced with real directory
#148 #139 v3 sudo_hop bypassinstallWillStartUserService covers the sudo path; latent fchown BADF crash fixed
#145 #143 Uninstall residue — both /lib/systemd/user/padctl.service and /etc/systemd/user/padctl.service are removed
#144 #142 padctl switch did not refresh auxcommitSwitchTarget calls rebuildAuxIfChanged
#149 --no-user-service flag + completion hint

Device compat / reconnect:

PR Issue Description
#129 #93 Reconnect after dedup hit — re-attach correctly when backing fds are dead

🛡 Hardening (Audit V1 / V4 / V5 + X / Y series)

Audit findings closed (6-agent parallel audit, 2026-04-23):

PR Audit Description
#168 V1 Device-name control-character injectionwriteBinding TOML-escapes device.name and rejects control chars
#169 V4 Per-frame remap string lookup — pre-resolve remap tables at init; eliminates hot-path string ops
#170 V5 Silent unknown-field swallowing — post-parse schema linter warns on unknown TOML fields (closes #163 class)
#140 UHID 6 HIGH findings — descriptor-driven encoder + input-group permission fallback + modules-load.d

Defensive validation:

PR Description
#175 macro fd must not promote PENDING layer — separated timer-expiry handlers
#176 layer.tap = "macro:..." — rejected at validate
#177 button_group source.size > 8 — rejected at compile time, prevents bit-op overflow
#178 VID/PID extract section-aware + parseStringArray leak
#180 AtomicWriter preserves all sections[supervisor] / [diagnostics] no longer lost on rewrite
#181 Supervisor startFromDirs test uses fake dev_root — no longer depends on real /dev

✨ Features

UHID output backend (Phase 13 Wave 1–3)

SDL-visible main controller + IMU pairing:

PR Wave Description
#127 W1 UHID output backend — simulator harness + UHID device creation primitives
#132 W2 HID descriptor builder — declarative DSL → HID descriptor bytes
#159 W3 supervisor → UhidDevice routing — IMU emit on UHID (closes #81)
#160 W3 udev rule + canary test90-padctl.rules for SDL detection (closes #81)

Diagnostics

PR Issue Description
#130 #65 padctl dump CLI — togglable diagnostic dump with --json / --raw

📚 Documentation

PR Description
#150 trigger_threshold reference — RT/LT macro/remap usage
#158 Macro LT/RT working examples — stale "Known limitation" removed
#173 glibc 2.43+ R_X86_64_PC64 build failure — workaround documented (issue #147)
#174 README + docs/src cleanup — audit-2604 consolidation
#184 User-facing docs drift audit (2026-04-28)[output.imu] schema, default_mapping fallback, schema-linter, etc.

🔧 Internal / CI

Refactors:

PR Description
#156 InstallPlan struct — install logic 500 → 95 LoC
#157 now_ns threading + applyTarget dispatch — dedup monotonicNs, unified timer callbacks

Tests:

PR Description
#153 docs concurrency, #79 assertion tightening, regression corpus notes
#154 UHID gate probePADCTL_TEST_REQUIRE_UHID=1 when /dev/uhid accessible; rootless-Docker install-flow sandbox
#155 B3 coverage — DualSense adaptive trigger × 5 + UHID sendCreate uniq byte-pin × 2

CI:

PR Description
#182 timeout 600 + --summary all — hang protection + diagnostics
#185 tee log for SIGTERM survivabilitycheck-matrix + distro-check outputs survive 10-min timeout

Research:

PR Description
#161 Wave 6 PIDFF probe — kernel 6.18 BUS_USB UHID bustype validated (Wave 6 deferred from this release)

⚠️ Known Issues

  • Wave 6 HID PID FFB (issue #82) — racing-wheel force-feedback work in spec/phase-13-wave-6-pidff (PR #166); landing target v0.1.5 / v0.2.0 after real-hardware matrix
  • issue #65 rumble stuck — padctl logs are clean; reporter needs usbmon to isolate USB / firmware layer
  • issue #147 glibc 2.43+ — Arch rolling users must pin Zig version or wait for upstream

⚙️ Upgrade Notes

1. Device-name control-character hard-reject (#168)

Before: control chars in device.name were normalized.
Now: validate hard-rejects them.

Impact: malformed configs only; normal configs unaffected.

2. Unknown TOML field warnings (#170)

Schema linter emits warnings for unknown fields in mapping TOML at startup.

Impact: previously-silent typos (e.g. triiger_threshold) become visible; check logs after upgrade.

3. padctl switch no-arg behavior (#172)

Before: error "missing argument".
Now: reads [[device]].default_mapping and switches.

Impact: positive change; explicit error if default_mapping is missing.

4. systemd v254+ state directory (#146 / #148)

~/.local/state/padctl is now a real directory, not a symlink.

Impact: new installs only; existing installs migrate on next padctl install.

5. macro timerfd separation (#171)

Behavioral fix; no operator action required.


Provenance

git fetch origin main v0.1.2
git log v0.1.2..origin/main --oneline      # 44 commits
git diff --shortstat v0.1.2..origin/main   # 84 files, +16345 / −972
gh pr list --state merged --base main --search 'merged:>2026-04-22'

Issues referenced: #65, #72, #79, #81, #93, #99, #131, #136, #139, #142, #143, #147, #162, #163.
Issues closed by merge: #81 (PR #159 Wave 3 routing), #163 class (PR #170 schema linter).


v0.1.2

21 Apr 19:27
9ceab0c

Choose a tag to compare

Changelog — v0.1.2

9ceab0c chore(release): bump version to 0.1.2 (#128)
7773910 fix: Steam Deck HID layout + widen button_group bit_idx to u6 (#122)
7ef185c log(supervisor): add hotplug diagnostic logs for issue #93 (#125)
82c563b fix(install,config-list): sudo user-scope bridge + daemon probe + config UX (#124)
f9402e9 fix(rumble): prevent EV_FF coalescing in pollFf drain loop + make immutable install reliable (#65) (#120)
6c4a5af fix(mapper,mapping): wire pause_for_release + scan macro KEY_ targets (#72) (#118)
4034241 fix(scripts): add brew opt PATH for versioned zig formula (#108) (#117)
ce4d472 fix(install): remove SupplementaryGroups=input from user service (#115) (#116)
6a447fb fix(supervisor): keep uinput alive across device sleep/wake (#114)
5eb7bd7 fix(scripts): pin zig@0.15 in bazzite-setup, reject 0.16+ (#108) (#113)
d41e7b6 fix(install): add SupplementaryGroups=input to systemd user service (#112)
1e7c1c1 feat(mapper): synthesize digital buttons from analog triggers via threshold (#99) (#111)
7954ecd fix(release): run makepkg as non-root user in Docker

v0.1.1

18 Apr 09:36

Choose a tag to compare

Changelog — v0.1.1

b9cbe7f fix(release): run padctl install as root for system-wide unit path
dad5915 fix(release,aur): update systemd paths from system to user scope
9affe8e fix(scripts): add Zig version check to bazzite-setup.sh (#108) (#110)
857a46c fix(install): use systemctl --user for user service units (#107) (#109)
bd65053 feat(gyro): add target field for left/right stick selection (#80) (#106)
daa9416 fix(layer): use press timestamp to resolve tap/hold race (#104)
05c38be fix(config): print next-step guidance after config init (#89) (#105)
4c68227 fix(cli): distinguish "no devices" from "no default_mapping" in padctl switch (#98) (#102)
258f1c0 fix(uinput): make AuxDevice EV_REL registration conditional (#100) (#101)
18c1a93 fix(config): create parent dirs recursively in config init (#88) (#96)
5c94a63 fix(cli): return XDG socket path unconditionally for non-root user (#86) (#95)
3765a25 fix(install): atomically rename binaries to avoid ETXTBSY (#97)
25eef25 fix(scan,install): quieter scan warnings + lock XDG service template (#84)
1619c35 fix(capture): escape TOML strings in toml_gen emitToml (#83)
834cbda feat(install): switch to systemd --user service (Path B, ADR-014) (#77)
49fea1d fix(config): escape TOML string values in writeConfigToml (#75)
e129a7f fix(rumble,config): resolve stuck vibration motor and post-reboot mapping loss (#65) (#69)
638182d refactor: minimize fix to essentials, remove pending_play overengineering (#68)
f5b0a57 fix: case-insensitive device name lookup + diagnostics for missing mapping (#65) (#67)
94c32f1 fix: retry hotplug open() on transient errors beyond EACCES (#64) (#66)
7bf039e fix: use defer for socket cleanup in test to prevent fd leak
6d37d13 fix: detect and reject duplicate daemon instances
b759e14 docs: add Debian install option, clarify service install steps
3f7c88c README: add Debian/Ubuntu and padctl-bin install options
314b7e0 release: auto-build and upload .deb packages
60e0828 fix: add --repo flag to checksums gh release upload

v0.1.0

05 Apr 06:41

Choose a tag to compare

Changelog — v0.1.0

439d50e fix: use absolute destdir path in release workflow
5bccacc fix: build gen-install-artifacts with musl target
201a36c fix: use statFile instead of accessAbsolute in detectImmutableOs
e4a719d fix: handle unexpected errno in detectImmutableOs
74f9d22 release: auto-update padctl-bin AUR package after release
aafaf1f add DEB packaging and update COPR spec (#62)
438ac5e update AUR PKGBUILDs for current install flow (#61)
1e80795 warn when --mapping is used in daemon mode (#60)
924005b feat(install): add immutable OS support, Bazzite bootstrap, and Vader 5 fixes (#59)
cbed4f6 fix: evdev grab permissions for non-root + warn on grab failure (#58)
0dac9eb fix: non-root socket path + sudo user config discovery (#56)
3b6a669 fix: hotplug sysfs retry on readInterfaceId null (#55)
7570a48 fix hotplug: interface filter in attachWithRoot, defensive detach, debug logs
07ad755 fix(netlink): bind group 1 only — group 2 is libudev internal, not kernel multicast
439a232 fix drainHotplugRetry: only retry AccessDenied, drop other errors immediately
5b8cd14 fix: prefer udev netlink group 2 to eliminate hotplug race condition
2414f1b fix: retain device config in configs when no hidraw found at startup
07f0224 fix dpad arrows layer edge miss and scroll Y direction inversion
e432871 fix: refresh user_cfg in doReloadFromDir and doReloadFromDirs
2241597 fix: log skipped and scanned config dirs in startFromDirs
1f61f76 fix: transfer default mapping ParseResult ownership to ManagedInstance to prevent UAF, add warn logging for config errors, fix invalid remap target suppressing button, add BTN_FORWARD/BTN_BACK to debug TUI
5f6a615 fix: hidraw sys_root propagation, USB port boundary match, and evdev grab test
766940f fix: remapping overhaul — mapping loading, gyro axes, AuxDevice lifecycle (#54)
ae2f544 fix: capture init bounds check and prefix cast safety
0246c11 docs: add mapping configuration guide
c3fe0bd feat: padctl-capture --config option for device init sequence
795ad1a docs: add comprehensive mapping config example
9737c59 fix: config init validate mapping correctly, remove hardcoded --config-dir
09bd506 fix: daemon install and runtime issues (#51) (#52)
2e63efd fix: extractVidPid scoped to [device] section, scan all config dirs for udev rules (#50)
438d715 L3 e2e: 33/33 configs OK, zero skip, zero fail (#48)
8143b7e fix L3 e2e test failures: axis tolerance, memory leak, event node discovery (#46)
2a8688d fix all 13 generator coverage gaps (#45)
3baa36b Replace Zig DRT oracle with Lean subprocess — proven correct oracle (#44)
e2f442d Add L3 aux events DRT + DualSense BT mode test (#41)
70f630b feat: integrate Lean oracle CSV test vectors into Zig DRT (#42)
c641345 Add Lean 4 formal verification spec (24 theorems, zero sorry) (#39)
bffe341 test: add 5 L1 tests covering REL event codes, gyro sign, scroll direction, and hot-reload (#38)
60ff531 fix: L3 e2e test real assertions — inverse scale signed types, coverage gates, skip counters (#37)
5d51757 fix: BTN_TOUCH missing, inverse transforms, button bits not cleared in DRT (#36)
9d16146 fix: L3 e2e — setupTestUdev for hidraw permissions, makeGenericRd return length, GamepadState type fix (#35)
c1d7628 fix L3 e2e generative test: 3 BLOCKING + 5 IMPORTANT + 3 MINOR issues
df3594e L3 full e2e: all 15 real configs + 20 random configs × mapping × DRT (#34)
02baef9 test: replace silent catch-continue with try for device config parsing
00d0c49 test: replace catch-continue with try in generative property tests
55c21ce fix: generative test infrastructure — edge-only key emit, dpad cancel, shrink guard
20e3f44 wire device_specific_props into testing_support in main.zig
4dcbffa fix audit items: replace fragile sleeps with poll-based waits; add shrink tests
6e161ca fix: remaining audit items — Thread.sleep reduction, big-endian config, shrink tests, device-specific tests, zig fmt
359b936 Integrate real device configs into generative framework + fix audit findings (#33)
9a7abb4 test: Phase G4 — failure minimization + regression corpus (#32)
37e8991 Add generative mapper DRT harness with transition coverage (G3) (#31)
2c022ec Add mapper reference oracle for generative DRT (G2) (#30)
a59b168 Add generative test config and sequence generators (G1) (#29)
7850ed5 fix: XDG_RUNTIME_DIR fallback for control socket path (#27)
b3b56bc Add UHID config-driven simulation tests for all device configs (#28)
28cfcf7 Fix DRT: inject checksums for Sony devices + add dpad coverage (#26)
ebd9647 fix: address 4 test framework audit issues (C1/C2/I3/I4) (#25)
375472c T5: add negative test corpus for vader5 interpreter (#24)
ffd0af9 test: rename inline tests to follow TP6 "module: description" convention (#23)
8453b60 Add Layer 3 UHID→uinput e2e pipeline tests (#22)
c6fde30 Document scale signed-type asymmetry and threadlocal test slot rationale (#21)
f88b8b7 refactor: deduplicate exe_mod/src_mod in build.zig (#20)
feca694 Add DRT reference interpreter and supervisor SM coverage (T4) (#19)
3db72f2 test: fix MR5/MR6 metamorphic tests and add poll_ff contract (#18)
70394b4 Add mutation audit tests and assertion density improvements (T2) (#17)
235c265 refactor: reorganize tests — move hidraw props, rename T-number prefixes, remove dead skips (#16)
27dfff1 Add PBT and E2E pipeline tests (#15)
2e67297 Reject transform chains exceeding MAX_TRANSFORMS at validation (#14)
50194c4 Replace hardcoded REL_* event codes with kernel constants (#13)
f991769 Fix scan interface filtering for multi-interface devices (#12)
f060e45 ci: remove last ubuntu-24.04 reference from distro-check
1bcff6e Add Vader 4 Pro 04b4:2412 config variant (#9)
3051988 fix: code quality — enum sync, comment matching, dead code, CI (#11)
ce1904d fix: usbraw resource leak on error paths and pipe drain underflow (#10)
1086b81 docs(readme): switch to ascii architecture diagram
8ca3ca3 docs(readme): add simplified architecture diagram
c8e09dd ci: strengthen matrix checks, cache strategy, and tsan gating (#7)
3d518dd fix(supervisor): atomic switch + reload edge cases + IPC error handling (#6)
b7e0a60 fix: avoid scan double free and enumerate readonly hidraw nodes (#5)
8cbe0fb fix: discover device configs when installing from zig-out (#4)
041f8fd fix: rate-limit rumble writes to 10ms minimum interval
8c8bc89 ci: build kcov from source — not available as apt package
e2ff861 docs: optimize README — concise, link to docs site, fix paths
2715106 optimize README for public open-source presentation
7c0c439 ci: fix coverage and docs workflows
cd71aba ci: exclude test-tsan from check-all — SIGSEGV on Ubuntu CI runners
8c2ef76 ci: remove submodules flag — worktrees are not submodules
67e48bd ci: trigger workflow after public visibility change
d33fa96 chore: pin flake.nix to Zig 0.15.2, matching all other version refs
f8e17f6 chore: unify Zig version to 0.15.2 across all workflows, mark PKGBUILD as templates
dcd3f7e docs: fix factual errors — bits syntax, sum8 algo, exit codes, device count
4032250 docs: split Contributing into chapters, remove InputPlumber guide
cbfd818 docs: remove InputPlumber-specific guide, keep generic RE guide only
92f3d35 docs: add HID reverse engineering guide + contributing index
86f4954 docs: generate device reference pages for all 12 supported devices
e30f4d3 docs: comprehensive documentation update for public release
16355e7 fix: RingBuffer TSan false positive — use pthread_mutex under sanitizer
9cc6a73 fix: skip root check when --destdir is set (package build staging)
2b10e64 test: PBT properties + uinput/touchpad/serve coverage
3b03975 test: add CI, fuzz step, L2 UHID integration tests
32900b8 fix: socket connect, memory leaks, config validation
9db6511 fix: critical safety bugs + test coverage gaps
d81987a fix: free-literal UB, systemd RuntimeDirectory, scan OOM leak, EOF handling
e6b519e build: add check-all step for CI — runs test + tsan + safe + fmt
fba6a92 build: add C sanitizer, ReleaseSafe tests, format check step
0da36cf fix: install bugs + add uninstall command
23d0f5d chore: switch license from MIT to LGPL-2.1-or-later
a04de8b fix: config test VID/PID matching, systemd hardening, docs cleanup
574cd8c fix: pre-release usability — pid-file, udev uaccess, scan XDG, config list recursion
8fc0a77 chore: untrack internal planning docs and hooks
0d1dd11 fix: CLI tests hang — inject writer instead of hardcoding STDOUT/STDERR fd writes
590865e fix: negate transform saturates instead of truncating on i16 min
edc2821 fix: arena UAF race — stop-swap-restart on mapping reload
0276a9a fix: handleDevices bounds check, symlink discovery, padctl-debug safety guards
6d7a7c8 fix: update test data for checksum validation — crc32 needs u32le, add sum8 to valid algos
68aca3e fix: socket addr family init + sendCommand blocking read
5dd3777 fix: deinit() ensures threads stopped before teardown
e31ff8b fix: warn when report_size exceeds init buffer capacity
7bc67ec fix: validate checksum expect_offset bounds for crc32/sum8/xor
92302db fix: add comptime assert for emit() events array capacity
17ec790 fix: armTimer/disarmTimer return void, remove redundant stop_pipe drain
8887ef2 fix: validate button_group bit_idx bounds to prevent @intcast panic
78dc124 fix: cap transform chain length at MAX_TRANSFORMS to prevent OOB
d66871e fix: mapping discovery — swap seen/list order to prevent dangling pointer on OOM
800de0b fix: move docstring to correct function (serve, not doReloadFromDir)
0062046 fix: padctl-debug — log raw mode error, fix device leak, log poll failure
1aea606 fix: serve() now handles SIGHUP and inotify config reload
47aef9c fix: dangling pointer in attachWithRoot — dupe phys before DeviceInstance.init
cf60717 fix: warn when init command exceeds 64-byte buffer
0a41994 fix: padctl-debug 100% CPU — handle POLLHUP/POLLERR on stdin fd
292d362 fix: update 3 failing tests — socketpair error type, vader5 interface count, renderStats assertion
b89aca0 fix: event loop premature running=false
08826c4 fix: use std.c.socketpair for Z...

Read more