← Back to Wiki
Linux Gaming / Hardware

Phantom Gamepad Input in Games: an RGB Controller Posing as a Joystick

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 is 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 reports its axes jammed at full deflection. Motherboard RGB controllers are the classic offender. Here is 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, because a stick has a position rather than a direction of travel. So does a mouse wheel. So does a graphics tablet, a VR tracker, a fan controller reporting RPM. And so do a lot of motherboard RGB and LED controllers, which pipe their channel values through the same generic HID plumbing. That last one is what bites people.

Linux does not 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?" is told about your light controller. SDL, Unity's libudev backend, Unreal, Steam Input.

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

Unity makes this especially likely. Its default input mappings feed joystick axes straight into the same Horizontal and Vertical axes as your keyboard. You do not have to have configured a controller. You do not 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 want 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 have 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

Do not stop at "it is enumerated". Read what it reports. That is the difference between a harmless phantom device and the thing ruining your game. The joystick API 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 genuinely idle device reports 0 across the board. The ASRock controller above reported ten of its twelve axes pinned at -32767. Hard against the stop, on every 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 would 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 enumerates joysticks at all, which is the link between "a phantom device exists" and "my camera will not stop moving".

Step 3: untag it with udev

The fix is to stop udev advertising the device as a joystick. Every consumer discovers gamepads by that tag. SDL, Unity, Steam Input. Remove the tag and the device goes invisible to games while staying 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
BE WARNED: the /dev/input/js0 node may still exist. That is fine. The kernel's joydev driver creates it from the device's capabilities, and a udev rule does not change what the hardware claims to be. The rule changes discovery. SDL and Unity enumerate tagged devices, so an untagged one never gets opened. Want the node gone too and you have to unbind or blacklist joydev, which kills your real controllers as well. 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, which is how OpenRGB works. The input-subsystem tag is a separate concern.

On immutable distros

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:

This one is worth knowing because it is invisible from inside the game and survives every obvious troubleshooting step. Reinstall, verify files, swap drivers, unplug every peripheral you own. A light controller bolted to your motherboard keeps quietly holding the stick down.