mirror of
https://github.com/joleuger/vuinputd.git
synced 2026-11-05 01:17:20 +00:00
[GH-ISSUE #16] Offer: multi-seat Incus test rig, plus findings on the Incus strategy and uhid #15
Labels
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
joleuger/vuinputd#15
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @superuser404notfound on GitHub (Jul 28, 2026).
Original GitHub issue: https://github.com/joleuger/vuinputd/issues/16
Following up from https://github.com/LizardByte/Sunshine/issues/3768, where you
mentioned uhid is the missing piece. I read through the codebase and the open
issues first, so this is partly an offer of help and partly two concrete
findings that might be useful regardless of whether I end up contributing code.
Where I am coming from. I run a multi-seat setup on Incus: two seats, each
an Incus system container with headless sway, its own Sunshine instance and its
own Steam account, streaming to separate Moonlight clients. Both run in parallel
today with keyboard, mouse and gamepad reaching exactly one seat each. My input
isolation is the naive one, host udev rules plus a broker that injects nodes
into the right container by matching the seat tag Sunshine writes into the
device name. It works, but attribution by name only protects against accidents,
not against a compromised seat, which is exactly the axis where mediating
/dev/uinputis better. Code and measurement logs:https://github.com/superuser404notfound/Polyseat
1. The Incus strategy cannot do multi-container today (relates to #11)
Every generic strategy derives its target from
requesting_processand acts inthat process's namespaces, so multiple containers work by construction.
impl InjectionStrategy for Incusdoes the opposite: it marks the parameter_requesting_processas unused and takes the container name fromget_scope(), bailing out onScope::Multi. So Incus effectively requires onevuinputd instance per container, and a setup like mine cannot be served by a
single daemon.
The container name is derivable from the requesting process. On a live two-seat
host here, reading the caller's cgroup gives it directly:
That is Incus 7.2 on a CachyOS host, where the prefix is
lxc.payload.becauseIncus still uses LXC underneath; other versions may write
incus.payload., soit would want matching on both. Either way it would let the Incus strategy
resolve the target the same way the generic ones do and drop the
Scope::Singlerestriction.
While reading that function I also noticed
mknod_device_nodebuildshostpathandincuspathas the samepath=...string and passes both, neversetting
source=. Incus tolerates it becausepathdoubles as the host pathwhen
sourceis absent, so this is cosmetic rather than broken, but it readslike one of the two was meant to be
source=.2. What I measured about uhid
Sunshine creates gamepads through inputtino as HID devices via
/dev/uhid, notthrough uinput, and it does so for the default emulated model as well, not
only for a DualSense. So you do not need to buy a controller to reproduce it:
any Moonlight client with an on-screen controller overlay is enough. Without
uhid passed through, Sunshine still logs
Gamepad 0 will be Xbox One controllerwhile no device ever appears inside the container, which is what made it look
like a client-side problem to me for quite a while.
A uhid device also produces a
hidrawnode alongside the input event node, andSteam Input uses hidraw. So whatever handles uhid has to inject hidraw too. That
is an open gap on my side as well.
On the protocol: uinput is essentially write-only from the application, while
uhid is bidirectional and stateful. The application writes
UHID_CREATE2andUHID_INPUT2but has to readUHID_START,UHID_OPEN,UHID_OUTPUTandUHID_GET_REPORTback. Having seenvuinput_read.rsandvuinput_poll.rs, therelaying shape clearly already exists, so my question is not whether it fits but
how you would want it cut: a second CUSE device for
/dev/uhidnext to theexisting one, or something more shared?
Worth noting that this overlaps with #10:
UHID_OUTPUTis how rumble arrivesfor HID devices, so uhid support and the vibration work are partly the same
problem.
3. What I can actually offer
A real two-seat Incus rig with Sunshine, gamepads and the habit of writing down
what each experiment measured. I can test branches against it, and I can
reproduce #11, #10 and #7. #7 in particular looks familiar: Sunshine's "Mouse
passthrough (absolute)" reports
EV_ABSrather thanEV_REL, and classifying itwrongly made sway treat it as a keyboard in my setup, so the pointer stayed dead
without an error appearing anywhere.
What I am not offering is a promise to land uhid support on my own in a codebase
I have read but never written in. If you want it from outside, tell me how you
would want it cut and I will start with a narrow piece. If you would rather build
it yourself, testing and diagnosis are probably where I am more useful anyway.
@superuser404notfound commented on GitHub (Jul 28, 2026):
Correction to what I wrote above about uhid. I had two seats streaming with a
controller each today and measured it properly, and my earlier claim was wrong.
Which interface a gamepad uses depends on the emulated model, not on Sunshine
as such:
The log names the choice and where it came from:
So I was wrong to say the default model goes through uhid too. It does not. Only
DualSense emulation does, plus whatever else Sunshine implements as a real HID
descriptor.
My conclusion still holds, but for a different reason. You do not need to buy
a controller to exercise uhid: Sunshine has a
gamepadoption and the logdistinguishes "manual selection" from "auto-selected by client-reported type", so
setting it to force DS5 emulation should reach the uhid path from any client.
One observation I cannot fully explain, which may matter for your side. With
/dev/uhidnot passed into the container, no gamepad appeared at all, whilethe log still announced an Xbox One pad, and that is the model we now know uses
uinput. So uhid seems to be required for gamepad support to come up at all, even
when the pad that gets created does not end up using it. If that holds, a
container with a mediated
/dev/uinputbut no uhid would silently have nogamepads regardless of the emulated model.
And a detail for whoever implements the uhid side: one DualSense produces
three input devices from a single HID device, the pad itself plus "Motion
Sensors" and "Touchpad". So the injection has to handle a one-to-many mapping,
not one node per creation.