No description
  • C 77.6%
  • Shell 22.4%
Find a file
gabrielgad 58a0188a4b
All checks were successful
CI / build (push) Successful in 2s
ci: build evjoyd on every push
No CI before this. evjoyd is in the input path for every splitux seat — the
CUSE daemon that synthesizes /dev/input/js* from the evdev pads, including the
splitux-together virtual pads it allowlists by name — so a build break here is
a launch that comes up with no working controller.

Builds with the exact command lines validate.sh uses, so CI and the on-hardware
script cannot drift into compiling different things. -Wextra runs
non-blocking: these sources have never been compiled under it and reporting
that debt is more useful than gating on it today.

validate.sh is deliberately not run here. It modprobes cuse, creates a uinput
pad and reads the synthesized js node, so it needs root and a kernel with
cuse.ko. CI proves this builds; validate.sh proves it works, and only hardware
can do that.
2026-08-08 14:18:56 -04:00
.github/workflows ci: build evjoyd on every push 2026-08-08 14:18:56 -04:00
.gitignore evjoyd v0.1.0: userspace joydev replacement (CUSE evdev -> js*) 2026-06-24 06:27:30 -04:00
evjoyd-supervisor evjoyd v0.1.0: userspace joydev replacement (CUSE evdev -> js*) 2026-06-24 06:27:30 -04:00
evjoyd.c evjoyd v0.1.0: userspace joydev replacement (CUSE evdev -> js*) 2026-06-24 06:27:30 -04:00
jsprobe.c evjoyd v0.1.0: userspace joydev replacement (CUSE evdev -> js*) 2026-06-24 06:27:30 -04:00
mkpad.c evjoyd v0.1.0: userspace joydev replacement (CUSE evdev -> js*) 2026-06-24 06:27:30 -04:00
openlog.c evjoyd v0.1.0: userspace joydev replacement (CUSE evdev -> js*) 2026-06-24 06:27:30 -04:00
README.md evjoyd v0.1.0: userspace joydev replacement (CUSE evdev -> js*) 2026-06-24 06:27:30 -04:00
validate.sh evjoyd v0.1.0: userspace joydev replacement (CUSE evdev -> js*) 2026-06-24 06:27:30 -04:00

evjoyd — userspace joydev replacement

A CUSE daemon that translates an evdev device (/dev/input/eventN) into a legacy joystick node (/dev/input/jsN), faithfully reproducing joydev's axis/button map, value correction, and js_event/JSIOC* protocol — so consumers that only speak the old /dev/input/js* API see the controller.

Why

This host removed/blacklisted the in-kernel joydev (it mis-enumerated four identical 8BitDo pads as one controller). That left no js* nodes at all, which breaks path-scanning js* consumers — notably the Chromium Gamepad API (browsers read /dev/input/js* directly). evjoyd restores correct, 1:1-unique js* nodes without bringing joydev back.

Scope note: udev-based discovery (SDL2, Unity's modern path) filters SUBSYSTEM=="input", and CUSE nodes are SUBSYSTEM=="cuse", so those consumers never see an evjoyd node regardless of tagging. evjoyd serves the path-scanning consumers (Chromium); evdev-native games are unaffected. For Unity/InControl games that crash on virtual pads, see the sibling project xpad-gadget.

Design

One process owns one js node (crash isolation, no shared state). A supervisor spawns one per matched controller and assigns unique js numbers. joydev itself stays unloaded — this is its replacement, not a companion.

Build

gcc -O2 -Wall evjoyd.c -o evjoyd $(pkg-config --cflags --libs fuse3)
gcc -O2 -Wall jsprobe.c -o jsprobe      # inspect a js node (debug)
gcc -O2 -Wall mkpad.c   -o mkpad        # synthesize a test uinput pad
gcc -O2 -Wall -shared -fPIC openlog.c -o openlog.so   # open-trace shim (debug)

Requires /dev/cuse (CONFIG_CUSE) and libfuse3.

Run

sudo ./evjoyd --evdev /dev/input/event8 --js 0 [--name "..."] [-f]
# or let the supervisor match an allowlist and spawn one per controller:
./evjoyd-supervisor

validate.sh smoke-tests a node against the joydev protocol.

Files

  • evjoyd.c — the CUSE daemon (one evdev → one js node).
  • evjoyd-supervisor — spawns and assigns one evjoyd per allowlisted controller.
  • jsprobe.c, mkpad.c, openlog.c — debugging helpers.
  • validate.sh — protocol smoke test.