ctrlassist 0.4.0

Controller Assist for gaming on Linux
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
![Banner](docs/artwork/banner.png)

**CtrlAssist** brings "controller assist" functionality to Linux gaming by allowing multiple physical controllers to operate as a single virtual input device. This enables collaborative play and customizable gamepad setups, making it easier for players of all ages and abilities to enjoy games together. While similar features exist on modern game consoles, CtrlAssist is an open source project that enhances accessibility for PC gaming, offering additional quality-of-life improvements through virtual input devices on Linux.

[![CI Pipeline](https://github.com/ruffsl/CtrlAssist/actions/workflows/ci.yml/badge.svg)](https://github.com/ruffsl/CtrlAssist/actions/workflows/ci.yml)
[![Crates.io Version](https://img.shields.io/crates/v/ctrlassist)](https://crates.io/crates/ctrlassist)
[![Crates.io Dependencies](https://img.shields.io/deps-rs/ctrlassist/latest)](https://crates.io/crates/ctrlassist/dependencies)
![Crates.io MSRV](https://img.shields.io/crates/msrv/ctrlassist)
![Crates.io Total Downloads](https://img.shields.io/crates/d/ctrlassist)
[![Crates.io License](https://img.shields.io/crates/l/ctrlassist)](https://choosealicense.com/licenses/apache-2.0)

# โœจ Features

- ๐ŸŽฎ Combine physical controllers into one virtual gamepad
  - Assign controllers as either Primary or Assist
- ๐ŸŽ›๏ธ Customizable multiplexing modes for buttons and axes
  - Logically merging or preempting events is flexible
- ๐Ÿ™ˆ Hide physical controllers for improved game compatibility
  - Multiple hiding strategies for avoiding interference
- ๐Ÿ•น๏ธ Spoof gamepad vendor for in-game layout recognition
  - Mimic either Primary or Assist controller hardware
- ๐Ÿซจ Rumble pass-through from virtual to physical devices
  - Forward force feedback to either or both controllers
- ๐Ÿ–ฑ๏ธ System tray interface for graphical desktop environments
  - Configure controllers and mux options via the taskbar
  - Start/stop/alter muxing with live status notifications
  - Persistent user settings across session restarts

![System Tray Screenshot](docs/screenshots/system_tray.png)

## ๐ŸŽ›๏ธ Modes

### ๐Ÿ”€ mux

Combine Primary and Assist controllers into one virtual gamepad.

- ๐Ÿ‘‘ **Priority** (default): Assist controller overrides when active
  - Axes: Prioritize Assist when active (exceeds deadzone)
    - Buttons: Prioritize Assist when button released
    - Triggers: Prioritize largest value from either
  - Ideal for partial and asynchronous assistance
    - E.g. Assist for movement while Primary for actions
- โš–๏ธ **Average**: Blend weighted inputs from both controllers
  - Axes: Averaged when both are active (exceed deadzone)
    - Buttons: logically OR'ed between pressed controllers
    - Triggers: Averaged when both are active (exceed deadzone)
  - Ideal for cooperative input and subtle corrections
    - E.g. For counter steer/brake assist in racing games
- ๐Ÿ”ƒ **Toggle**: Switch Active controller on demand
  - All inputs forwarded from currently active controller
    - Toggle Active controller via reserved Mode button on Assist
    - Immediately synchronizes input to current Active state
  - Ideal when fine-grain conflict-free control is needed
    - E.g. Game menu navigation or precise interventions

[Screencast_20251230_070245.webm](https://github.com/user-attachments/assets/40f72091-cfeb-461b-a4fb-5b4198604e9d)

### ๐Ÿ” demux

Split one Primary controller into multiple virtual gamepads.

- ๐Ÿ”‚ **Unicast** (default): route Primary to Active gamepad
  - All inputs forwarded to currently active virtual gamepad
    - Cycle Active gamepad via reserved Mode button on Primary
    - Force feedback synchronized with Active gamepad
  - Ideal for switching between multiple virtual gamepads
    - E.g. Assist multiple players across multiple mux instances
- ๐Ÿ“ข **Multicast**: route Primary to all virtual gamepads
  - All inputs forwarded to all virtual gamepads simultaneously
    - Including Mode button events from Primary controller
    - Force feedback synchronized with all virtual gamepads
  - Ideal for replicating Primary input across multiple devices
    - E.g. For more advanced input multiplexing pipelines

[Screencast_20260108_173024.webm](https://github.com/user-attachments/assets/67b4ec7f-c667-4dfc-b9e3-a5965d20c0d1)

# โฌ‡๏ธ Install

The following installation methods are available:

- ๐Ÿฆ€ [Cargo](#cargo) (Rust package manager)
  - Ideal for customization and unsandboxed use
  - Suitable for development and contributing
  - E.g. fork custom features and upstream fixes
- ๐Ÿ“ฆ [Flatpak](#flatpak) (Linux application sandbox)
  - Ideal for easy install on SteamOS, Bazzite, etc.
  - Suitable for immutable Linux distributions
  - E.g. where installing build tools is a hassle

## ๐Ÿฆ€ Cargo

- Build dependencies:
  - [libudev-dev](https://pkgs.org/search/?q=libudev-dev)
  - [pkg-config](https://pkgs.org/search/?q=pkg-config)
- Rust toolchain:
  - https://rust-lang.org/tools/install/
  - configure `PATH` per Notes linked above

Install or upgrade to the latest version:

```sh
cargo install ctrlassist --force
```

## ๐Ÿ“ฆ Flatpak

- Runtime dependency:
  - [Flatpak](https://flatpak.org/setup/) (likely already installed)

Download latest bundle from [releases page](https://github.com/ruffsl/ctrlassist/releases) and install:

```sh
export VERSION=v0.4.0
wget https://github.com/ruffsl/ctrlassist/releases/download/$VERSION/ctrlassist.flatpak
flatpak install --user ctrlassist.flatpak
```

Run and test via Flatpak using the application ID:

```sh
flatpak run io.github.ruffsl.ctrlassist --help
```

Or launch the system tray via the installed desktop icon.

# ๐Ÿ“– Usage

Use the `--help` flag for information on each CLI subcommand:

```sh
$ ctrlassist --help
Controller Assist for gaming on Linux

Usage: ctrlassist <COMMAND>

Commands:
  list   List all detected controllers and respective IDs
  mux    Multiplex connected controllers into virtual gamepad
  demux  Demultiplex one controller to multiple virtual gamepads
  tray   Launch system tray app for graphical control
  help   Print this message or the help of the given subcommand(s)

Options:
  -h, --help     Print help
  -V, --version  Print version
```

## ๐Ÿ–ฑ๏ธ tray

Launch the system tray app for graphical control:

```sh
$ ctrlassist tray
CtrlAssist system tray started
Configure and control the mux from your system tray
Press Ctrl+C to exit
```

The system tray provides:
- **Controller selection** menus for Primary and Assist
- **Configuration options** for mux mode, hiding, spoofing, and rumble
- **Start/Stop buttons** with visual feedback
- **Live status indicator** in the tray icon
- **Desktop notifications** for status changes
- **Persistent settings** saved to disk on use

Device invariant options can be altered while the mux is running; all other options are disabled (greyed out) until the mux is stopped.

## ๐Ÿงพ list

List all detected controllers and respective IDs:

```sh
$ ctrlassist list
(0) Microsoft Xbox One
(1) PS4 Controller
```

## ๐Ÿ”€ mux

Multiplex first two detected controllers by default:

```sh
$ ctrlassist mux
Primary: (0) Microsoft Xbox One
Assist:  (1) PS4 Controller
...
Mux Active. Press Ctrl+C to exit.
```

### ๐ŸŽฎ Primary Assist Mapping

Manually specify Primary and Assist controllers via IDs:

```sh
$ ctrlassist mux --primary 1 --assist 0
Primary: (1) PS4 Controller
Assist:  (0) Microsoft Xbox One
...
```

### ๐ŸŽ›๏ธ Mux Mode Selection

Manually specify mode for merging controllers:

```sh
$ ctrlassist mux --mode priority
```

### ๐Ÿ•น๏ธ Spoof Virtual Device

Mimic controller hardware for in-game layout recognition:

```sh
$ ctrlassist mux --spoof primary
Primary: (0) Microsoft Xbox One
Assist:  (1) PS4 Controller
Virtual: (2) Microsoft X-Box One pad (Firmware 2015) [#]
```

> [!WARNING]
> Combining spoofing with some hiding strategies may also hide the virtual device.

### ๐Ÿซจ Rumble Pass-Through

Target force feedback to either, none, or both physical controllers:

```sh
$ ctrlassist mux --rumble both
```

Some modes also support dynamically targeting active controllers:

```sh
$ ctrlassist mux --mode toggle --rumble active
```

### ๐Ÿ™ˆ Hide Physical Devices

Multiple hiding strategies are available to avoid input conflicts:

| Strategy   | Access/Compatibility         | Granularity         | Restart Required   |
|------------|-----------------------------|---------------------|--------------------|
| **Steam**  | No root, Flatpak compatible | Vendor/Product ID   | Steam only         |
| **System** | Root required, no Flatpak   | Per-device          | Game/Launcher      |

Use **Steam** hiding when running CtrlAssist via Flatpak. For 2v1 scenarios, where a third player not using CtrlAssist shares the same controller make and model, use **System** to avoid hiding the third player's gamepad.

#### Steam Input

Automatically configure Steam's controller blacklist:

```sh
ctrlassist mux --hide steam
```

> [!NOTE]
> Restart Steam for blacklist to take effect; CtrlAssist reverts config on exit.

> [!WARNING]
> Combining this hiding strategy with spoofing may also hide the virtual device.

#### System Level

Restrict device tree permissions system-wide:

```sh
sudo ctrlassist mux --hide system
```

> [!NOTE]
> Restart game/launcher to force rediscovery; CtrlAssist reverts change on exit.

> [!IMPORTANT]
> Not possible via Flatpak sandbox for security. Use `--hide steam` instead.

## ๐Ÿ” demux

Demultiplex via unicast between two virtual gamepads by default:

```sh
$ ctrlassist demux
Primary: (0) Microsoft Xbox One
Virtual: (1) CtrlAssist Virtual Gamepad [0]
Virtual: (2) CtrlAssist Virtual Gamepad [1]
...
Demux Active. Press Ctrl+C to exit.
```

<details><summary>More options</summary>

### ๐ŸŽฎ Primary Virtual Mapping

Specify Primary via ID and number of virtual controllers:

```sh
$ ctrlassist demux --primary 1 --virtuals 3
Primary: (1) PS4 Controller
Virtual: (2) CtrlAssist Virtual Gamepad [0]
Virtual: (3) CtrlAssist Virtual Gamepad [1]
Virtual: (4) CtrlAssist Virtual Gamepad [2]
...
```

### ๐ŸŽ›๏ธ Demux Mode Selection

Manually specify mode for virtual controller:

```sh
$ ctrlassist demux --mode multicast
```

### ๐Ÿ•น๏ธ Spoof Virtual Device

Mimic controller hardware for in-game layout recognition:

```sh
$ ctrlassist demux --spoof primary
Primary: (0) Microsoft Xbox One
Virtual: (1) Microsoft X-Box One pad (Firmware 2015) [0]
Virtual: (2) Microsoft X-Box One pad (Firmware 2015) [1]
```

> [!WARNING]
> Combining spoofing with some hiding strategies may also hide the virtual device.

> [!NOTE]
> Hiding virtual devices used by other mux instances of CtrlAssist may be desirable.

### ๐Ÿซจ Rumble Pass-Through

Forward force feedback from either `none`, or `active` virtual gamepad:

```sh
$ ctrlassist demux --rumble active
```

### ๐Ÿ™ˆ Hide Physical Devices

Same as mux command; see other for details.

</details>

# โš™๏ธ Configuration

The system tray saves settings to `$XDG_CONFIG_HOME/ctrlassist/config.toml`:

```toml
# Mux configuration
[mux]
primary_name = "Microsoft Xbox One"
assist_name = "PS4 Controller"
mode = "Priority"
hide = "Steam"
spoof = "None"
rumble = "Both"

# Demux configuration
[demux]
primary_name = "Microsoft Xbox One"
virtuals = 2
mode = "Unicast"
hide = "Steam"
spoof = "None"
rumble = "Active"
```

Settings are loaded on startup and saved when using the mux. Controllers are matched by name (best-effort) if IDs change between sessions.

# โš ๏ธ Limitations

- Hiding must be done before starting games or launchers
  - Processes with open file handles may retain device access
- Reconnecting a hidden controller may revert its visibility
  - Steam hiding persists across reconnects while CtrlAssist is running
  - System hiding: custom udev rules needed for persistent permissions
- Toggle mode requires pressing all buttons and axes after startup
  - gilrs lazily initializes gamepad state used for synchronization

# โ“ FAQ

Frequently Asked Questions about the project.

### **Who is CtrlAssist for?**

CtrlAssist is designed for anyone who wants to combine multiple controllers into one, enabling collaborative play, real-time assistance, or better gamepad ergonomics. Ideal for accessibility and partial asynchronous input, i.e. offloading camera angle management, movements requiring speed and precision, or on standby backup during difficult combat encounters. CtrlAssist is especially useful for:
- Players with disabilities or motor skill challenges
- Beginners still developing muscle memory for controller gameplay
- Experienced gamers helping newcomers through challenging games
- Families with young children or older relatives who want to play together
- Racing or flight sim enthusiasts using a wheel or HOTAS (Hands On Throttle-And-Stick)
- Anyone interested in collaborative co-op for single or multiplayer games

### **Why not pass around a single controller?**

While playing "hot potato" with a gamepad may be sufficient for some scenarios, like turn-based games, divvying up level progression, menu navigation, the approach falls short for many reasons:

- Broken immersion
  - Context switching in real life pulls players out of the story
- Added friction
  - Awkward handoffs and delays interfere with real-time gameplay
- Reduced agency
  - Waiting for returned control can kill spontaneity and focus
- Deprived learning
  - No haptic feedback hinders recognizing cues like attack telegraphs
- Marginal assistance
  - Inhibited intervention may only compound unnecessary frustration

Accessibility features such as control assist address these issues by enabling simultaneous/partial input from multiple controllers, resulting in more fluid and engaging gameplay.

### **Why was CtrlAssist developed?**

CtrlAssist was first created out of personal necessity. After migrating household gaming to Linux, including the family living room, the lack of controller assist features found on older consoles like Xbox and PlayStation became clear. CtrlAssist was developed as an open source solution to make group gaming sessions on PC more inclusive and accessible for players of all ages and abilities.

Following its initial release and personal household success, as well as the broader trend of Linux adoption, CtrlAssist evolved from a simple CLI tool into a desktop-friendly utility. This category of accessibility features has significantly enhanced family gaming time, transforming passive spectators into active participants. From helping grandparents experience new immersive and interactive single player stories, to leveling age gaps across nieces and nephews in multiplayer PvPs, to rescuing friends from critical damage and finally overcoming a challenging boss, assistive players may expect as much enjoyment as primary players.

<details><summary>More QA</summary>

### **What games are compatible?**

CtrlAssist works with most Linux games that support standard gamepad input. Some games or launchers may require restarting after changing controller visibility or virtual device settings. Note that many games have no explicit setting for controller selection, thus the motivation for various hiding strategies to avoid input conflicts between physical and virtual devices. For best compatibility, use the appropriate hiding strategy as described above.

Even in games that natively support multiple controllers, simultaneous input from multiple devices is often not handled. Most games prioritize one controller at a time, only switching after a period of inactivity. CtrlAssist overcomes this limitation by merging inputs into a single virtual device and providing advanced multiplexing modes for input events, going beyond simple first-come, first-served behavior.

### **Which controllers are supported?**

CtrlAssist supports most standard gamepads, such as those with a conventional Xbox or PlayStation layout, including those with strong and weak force feedback (rumble) capabilities. Under the hood, the [`gilrs`](https://crates.io/crates/gilrs) crate is used for gamepad input detection and event handling, requiring that controllers [have at least 1 button and 2 axes](https://docs.rs/crate/gilrs-core/0.6.6/source/src/platform/linux/gamepad.rs#625).

However, specialized controller features such as tactile triggers, gyroscopic and accelerometer motion tracking, or more exotic force feedback waveforms are not yet supported. If you have device driver expertise and would like to contribute support for additional controller features, please consider opening a pull request!

### **Are mice or keyboards supported?**

Not directly, as CtrlAssist is focused on gamepad input multiplexing. However, it is possible to combine CtrlAssist with more advanced utilities such as [InputPlumber](https://github.com/ShadowBlip/InputPlumber) to route keyboard and mouse events to virtual gamepads and into CtrlAssist, or vice versa taking virtual gamepads from CtrlAssist to keyboard and mouse events.

Note that mouse and keyboard inputs are typically handled differently from gamepad inputs, as they are core interfaces for operating systems and display managers. Merging events from multiple mice and keyboards is often managed by the OS already, negating the need for simpler multiplexing software.

### **Is running multiple instances possible?**

Yes! For scenarios where multiple primary players would like assistance, such as true split-screen multiplayer, multiple instances of CtrlAssist can be run simultaneously. Each instance will create its own virtual gamepad device, with the tray command also creating multiple separate system tray icons and menus.

Additionally, each instance can use different hiding strategies, spoofing options, and rumble targets to suit the needs of each player. Just be mindful that selected hiding strategies do not conflict between instances, causing one virtual device to be hidden by another instance.

</details>

# ๐Ÿง‘โ€๐Ÿณ Cookbook

Other basic examples of how else CtrlAssist can be used include:

- Dual wielding one for each hand, like split Nintendo Switch Joy-Cons
- Combining a standard gamepad with an accessible Xbox Adaptive Controller

However, because running multiple instances is possible, more complex setups can be achieved by chaining multiple mux and demux commands together.

## Couch Co-Op Swap

Two players can take turns assisting each other using toggle mode:

```sh
$ ctrlassist list
(0) PS4 Controller
(1) PS5 Controller
```

```bash
#!/usr/bin/env bash
ctrlassist mux --primary 0 --assist 1 --mode toggle --hide steam &
sleep 1 # wait to ensure virtual devices are discoverable
ctrlassist mux --primary 1 --assist 0 --mode toggle --hide steam &
wait
```

```mermaid
flowchart LR
    A[Assist 1 <br> Controller] --> C[Mux <br> Toggle]
    B[Assist 2 <br> Controller] --> C
    A --> D[Mux <br> Toggle]
    B --> D
    C --> E[Virtual 1 <br> Gamepad]
    D --> F[Virtual 2 <br> Gamepad]
```

Or specify a single assist controller by toggling once before duplicating first mux.

## Double Agent Tag Team

Assist multiple Primary players using demux outputs as mux Assist inputs:

```sh
$ ctrlassist list
(0) PS4 Controller
(1) PS5 Controller
(2) Xbox One Controller
```

```bash
#!/usr/bin/env bash
ctrlassist demux --primary 2 --virtuals 2 --mode unicast \
  --hide steam --spoof primary & # spoof to not also hide virtual 1 & 2
sleep 1 # wait to ensure virtual devices are discoverable
ctrlassist mux --primary 0 --assist 3 --mode priority --hide steam &
sleep 1 # wait to ensure virtual devices are discoverable
ctrlassist mux --primary 1 --assist 4 --mode priority --hide steam &
wait
```

```mermaid
flowchart LR
    A[Assist <br> Controller] --> B[Demux <br> Unicast]
    B --> C[Assist 1]
    B --> D[Assist 2]
    G[Primary 1 <br> Controller] --> E[Mux <br> Priority]
    C[Assist 1 <br> Virtual] --> E
    D[Assist 2 <br> Virtual] --> F[Mux <br> Priority]
    H[Primary 2 <br> Controller] --> F
    E --> I[Virtual 1 <br> Gamepad]
    F --> J[Virtual 2 <br> Gamepad]
```

Simply scale with number of Primary players by adjusting the `--virtuals` count.

# ๐Ÿ“š Background

- [Controller Assist on Xbox and Windows](https://support.xbox.com/en-US/help/account-profile/accessibility/copilot)
- [Second Controller Assistance on PlayStation](https://www.playstation.com/en-us/support/hardware/second-controller-assistance/)
- [InputPlumber: Open source input router and remapper daemon for Linux](https://github.com/ShadowBlip/InputPlumber)