← Back to Wiki
Linux Gaming / Hardware

Your RGB Controller Is Pretending to Be a Gamepad

The camera in a strategy game slides in one direction forever. A menu scrolls on its own. Your character strafes without you touching anything. You unplug your controller and it keeps happening. On Linux there's a specific, unglamorous cause that gets misdiagnosed as a game bug constantly: a device that is not a gamepad is being enumerated as one, and it's reporting its axes jammed at full deflection. Motherboard RGB controllers are the classic offender. Here's how to prove it in about two minutes, and the one-line udev rule that ends it.

Share on X

Why a light controller looks like a joystick

USB devices describe themselves with an HID report descriptor — a list of the inputs and outputs they have. A gamepad declares absolute axes (a stick has a position, not a direction of travel). So does a mouse wheel, a graphics tablet, a VR tracker, a fan controller reporting RPM, and — the one that bites people — a lot of motherboard RGB/LED controllers, which pipe their channel values through the same generic HID plumbing.

Linux doesn't judge intent. The kernel's joydev driver attaches to anything that looks joystick-shaped and creates a /dev/input/js* node. udev then tags the device ID_INPUT_JOYSTICK=1. From that point on, every game library that asks "what gamepads are attached?" — SDL, Unity's libudev backend, Unreal, Steam Input — is told about your light controller.

That alone would be harmless if the device reported neutral values. It usually doesn't. An LED controller has no reason to centre anything, so its "axes" sit wherever its firmware leaves them, which is frequently the numeric extreme. A game reading a stick pegged at -32767 does exactly what you'd expect if you were holding a real stick all the way over: it moves, forever.

Unity makes this especially likely, because its default input mappings feed joystick axes straight into the same Horizontal/Vertical axes as your keyboard. You don't have to have configured a controller. You don't have to own a controller.

Step 1: find the impostor

List every input device the kernel knows about and look at what carries a js handler:

cat /proc/bus/input/devices

You're hunting for a stanza whose Name is obviously not a game controller but which has a js* handler and an ABS= line. A real example from an ASRock board:

I: Bus=0003 Vendor=26ce Product=01a2 Version=0110
N: Name="ASRock LED Controller"
H: Handlers=kbd event2 js0
B: ABS=100000301ff

An RGB controller with twelve absolute axes and a joystick node. For a quick shortlist:

ls -l /dev/input/js* /dev/input/by-id/*joystick* 2>/dev/null

If the only js0 on your machine belongs to something that lights up your case, you've found it. Confirm what udev is advertising:

udevadm info /dev/input/event2 | grep -E 'ID_INPUT|ID_VENDOR_ID|ID_MODEL'

Step 2: prove the axes are stuck

Don't stop at "it's enumerated." Read what it's actually reporting, because that's the difference between a harmless phantom device and the thing ruining your game. The joystick API helpfully dumps the full current state the moment you open the node, so a few seconds of reading is enough:

python3 - <<'PY'
import struct, os, time
f = os.open("/dev/input/js0", os.O_RDONLY | os.O_NONBLOCK)
fmt, axes = "IhBB", {}
sz = struct.calcsize(fmt)
t0 = time.time()
while time.time() - t0 < 3:
    try:
        data = os.read(f, sz)
    except BlockingIOError:
        time.sleep(0.02); continue
    if not data or len(data) < sz: continue
    _, val, typ, num = struct.unpack(fmt, data)
    if typ & 0x02: axes[num] = val
os.close(f)
for n, v in sorted(axes.items()):
    print(f"axis {n}: {v}{'   <-- OFF-CENTRE' if v else ''}")
PY

A device that is genuinely idle reports 0 across the board. The ASRock controller above reported ten of its twelve axes pinned at -32767 — hard against the stop, on every single one. That is a game being told you are shoving a stick into the corner and holding it there.

evtest does the same job interactively if you'd rather watch events scroll by; it needs root and a device number.

Cross-check from the game's side. Unity titles log their input backend on startup — look for Using libudev for joystick management and Importing game controller configs in the game's Player.log (usually under ~/.config/unity3d/<Company>/<Game>/). That confirms the game is enumerating joysticks at all, which is the link between "a phantom device exists" and "my camera won't stop moving."

Step 3: untag it with udev

The fix is to stop udev advertising the device as a joystick. Every consumer — SDL, Unity, Steam Input — discovers gamepads by that tag, so removing it makes the device invisible to games while leaving it completely functional for whatever it actually does.

Create /etc/udev/rules.d/99-not-a-joystick.rules, substituting your own vendor and product IDs from step 1:

SUBSYSTEM=="input", ATTRS{idVendor}=="26ce", ATTRS{idProduct}=="01a2", \
  ENV{ID_INPUT_JOYSTICK}="", ENV{ID_INPUT}=""

Reload and re-apply without rebooting:

sudo udevadm control --reload-rules
sudo udevadm trigger --subsystem-match=input

Verify the tag is gone:

udevadm info /dev/input/event2 | grep ID_INPUT   # should print nothing
The /dev/input/js0 node may still exist — that's fine. It's created by the kernel's joydev driver based on the device's capabilities, and a udev rule doesn't change what the hardware claims to be. What the rule changes is discovery: SDL and Unity enumerate tagged devices, so an untagged one never gets opened. If you want the node gone as well you'd have to unbind or blacklist joydev, which also kills your real controllers — not worth it. Judge success by the missing ID_INPUT_JOYSTICK tag and by the game behaving, not by the node disappearing.

This does not affect RGB control. Lighting software talks to the device over USB HID directly (that's how OpenRGB works); the input-subsystem tag is a separate concern entirely.

On immutable distros

If you're on Bazzite, Silverblue, Aurora or another rpm-ostree system, this needs no special handling — /etc is writable and persists across updates, so drop the rule in /etc/udev/rules.d/ exactly as above. No layered packages, no rpm-ostree, no reboot.

Alternatives, and when to reach for them

How to tell this isn't your problem

Be honest with the evidence before you go rule-writing. Two checks:

The reason this one is worth knowing is that it's invisible from inside the game and survives every obvious troubleshooting step. You can reinstall, verify files, swap drivers, and unplug every peripheral you own, and a light controller bolted to your motherboard will keep quietly holding the stick down.