A small system-wide daemon that remaps game controllers between protocols on Linux, controlled from a tray icon. It presents a physical controller to applications under a different identity — for example, exposing a Sony DualSense as an Xbox 360 pad so that applications which only speak the XInput protocol see a controller they understand.
Many games and applications support only one controller protocol. A title that expects XInput (the Xbox protocol) will not recognise a controller that registers itself as a PlayStation device, even though both are ordinary gamepads. The usual workarounds are per-application and fragile.
Controller Manager solves this once, at the system level: it grabs the physical device on the kernel's input layer and re-emits its events through a virtual controller of the target type, before any application enumerates devices. The remap is selected per controller from a tray menu and applies uniformly to every launcher and application — there is no per-application configuration.
This repository is a self-contained tool and a worked example of a Linux input pipeline:
evdev device grabbing, uinput virtual devices, raw HID access control, and a
StatusNotifierItem tray served directly over D-Bus.
- Per-controller remapping — each connected controller has its own mode (native or remapped), chosen from the tray.
- Multiple controllers at once — two identical pads are tracked independently via a stable per-device identity, so one can be native while the other is remapped.
- Launcher-agnostic — works at the device layer, so every application sees the result regardless of how it was started.
- Raw HID gating — closes the double-input gap that an
evdevgrab alone leaves open, including against processes that opened the pad before the gate (Steam Input) (see the hidraw gate decision). - Lightbar status colours (DualSense) — the pad shows its active mode at a glance: blue = native, green = Xbox emulation. Games may take the lightbar over while they run; the resting colour returns when they exit (see Steam coexistence).
- Hotplug aware — controllers may be connected and disconnected at any time; the tray menu updates automatically.
- No daemon dependencies beyond the standard desktop stack —
python-evdev,dbus-python, andPyGObject.
Physical controller (evdev /dev/input/eventX)
│
├── native mode ──────────► device passes through unchanged
│
└── remap mode
├── evdev grab (EVIOCGRAB) exclusive kernel access to the source
├── uinput virtual device the target-protocol controller
├── event loop forward EV_KEY / EV_ABS / EV_REL
└── hidraw gate hide the source's raw HID node from applications
A StatusNotifierItem tray icon (served over D-Bus, no toolkit dependency) exposes the
per-controller mode menu. See docs/architecture/overview.md
for the full design.
-
A modern Linux desktop with a user systemd session.
-
A tray host that implements the StatusNotifierItem specification (most desktops do, some require an extension).
-
Write access to
/dev/uinput(granted by group membership on most distributions). -
Runtime packages:
Package (typical name) Purpose python3-evdevread input devices, create uinputvirtual devicespython3-dbus(dbus-python)serve the tray item and menu over D-Bus python3-gi(PyGObject)GLib main loop
git clone https://github.com/NicolasPogorzelski/controller-manager.git
cd controller-manager
./install.shinstall.sh deploys the user-space daemon and service, then installs the root-owned
hidraw gate helper, its udev rule and its sudoers rule (this step asks for a password).
For the full procedure with verification and rollback, follow the
installation runbook.
If you use Steam: set Settings → Controller → PlayStation controller support to "Enabled in Games w/o Support" (not "Enabled", not off). The Steam client then holds the pad permanently, but the daemon classifies game-vs-idle and restores the resting lightbar colour whenever no game runs; titles keep their native DualSense features. Also set Xbox controller support off. Turning PlayStation support off entirely is the one broken middle ground — Steam still opens the pad to identify it and latches the lightbar for no benefit. Details and trade-offs: Steam coexistence.
Enable autostart with the desktop session:
systemctl --user enable --now controller-manager.serviceOpen the tray icon to see every connected controller and its available modes as radio items. Selecting a mode applies it immediately and persists it.
| Controller family | Modes offered |
|---|---|
| PlayStation (DualSense) | Native · Output as Xbox |
| Xbox | Native |
The reverse direction (output a PlayStation identity from an Xbox pad) is intentionally not offered; see output protocol constraints for why.
Modes are persisted per device in ~/.config/controller-modes.json. The key is the
controller's stable identity (its uniq, e.g. the Bluetooth MAC) so that two identical
controllers keep separate settings:
{
"ac:36:1b:70:70:e8": "ps5-xbox",
"48:18:8d:53:37:6e": "ps5-native"
}The file is managed by the daemon; it is normally not edited by hand. A stored mode that is no longer offered for a controller family falls back to that family's native default.
Controllers are recognised by vendor plus gamepad capability, not a fixed product-ID list, so unlisted models of a known vendor still work. Product IDs are used only for nicer display names. Tested families: Sony DualSense and Microsoft Xbox One / Series pads, over USB and Bluetooth.
- Architecture Overview
- Design decisions:
- Troubleshooting / known issues
- Runbooks:
Contributions are welcome — see CONTRIBUTING.md for the commit format, validation script, and documentation conventions.