feat(input): implement multiseat support and virtual device naming (#4954)
This commit is contained in:
@@ -159,6 +159,33 @@ If the input is still not working, you may need to add your user to the `input`
|
|||||||
sudo usermod -aG input $USER
|
sudo usermod -aG input $USER
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Multiseat
|
||||||
|
|
||||||
|
If you run multiple concurrent Wayland sessions on separate logind seats (e.g. `seat0`, `seat1`),
|
||||||
|
your compositor may ignore injected input unless Sunshine's virtual devices are assigned to the correct seat.
|
||||||
|
|
||||||
|
Sunshine determines its target seat from `XDG_SEAT`, which is typically set automatically by your display manager.
|
||||||
|
If needed, you can override it manually in your systemd service file or shell environment before starting Sunshine.
|
||||||
|
|
||||||
|
When the seat is not `seat0`, Sunshine appends the seat name to its virtual device names, for example:
|
||||||
|
|
||||||
|
- Keyboard passthrough (seat1)
|
||||||
|
- Sunshine PS5 (virtual) pad (seat1)
|
||||||
|
|
||||||
|
Sunshine creates two mouse devices: a relative one and an absolute one.
|
||||||
|
|
||||||
|
To assign Sunshine's virtual devices to the correct seat, create this udev rules file
|
||||||
|
(/etc/udev/rules.d/72-sunshine-virtual-seat.rules):
|
||||||
|
```udev
|
||||||
|
SUBSYSTEM=="input", KERNEL=="input*", ATTR{name}=="*(seat1)*", TAG+="seat", ENV{ID_SEAT}="seat1"
|
||||||
|
```
|
||||||
|
|
||||||
|
Then reload udev:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo udevadm control --reload-rules && sudo udevadm trigger -s input
|
||||||
|
```
|
||||||
|
|
||||||
### KMS Streaming fails
|
### KMS Streaming fails
|
||||||
KMS screencasting requires elevated privileges which are not allowed for Flatpak or AppImage packages.
|
KMS screencasting requires elevated privileges which are not allowed for Flatpak or AppImage packages.
|
||||||
This means that you must install Sunshine using the native package format of your distribution, if available.
|
This means that you must install Sunshine using the native package format of your distribution, if available.
|
||||||
|
|||||||
@@ -13,12 +13,28 @@
|
|||||||
#include "src/config.h"
|
#include "src/config.h"
|
||||||
#include "src/logging.h"
|
#include "src/logging.h"
|
||||||
#include "src/platform/common.h"
|
#include "src/platform/common.h"
|
||||||
|
#include "src/platform/linux/input/inputtino_seat.h"
|
||||||
#include "src/utility.h"
|
#include "src/utility.h"
|
||||||
|
|
||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
|
|
||||||
namespace platf {
|
namespace platf {
|
||||||
|
|
||||||
|
inline std::string inputtino_name_for_seat(std::string_view base_name) {
|
||||||
|
auto seat_id = inputtino_seat::get_target_seat();
|
||||||
|
if (seat_id.empty() || seat_id == "seat0") {
|
||||||
|
return std::string(base_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string name;
|
||||||
|
name.reserve(base_name.size() + seat_id.size() + 3);
|
||||||
|
name.append(base_name);
|
||||||
|
name.append(" (");
|
||||||
|
name.append(seat_id);
|
||||||
|
name.push_back(')');
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
using joypads_t = std::variant<inputtino::XboxOneJoypad, inputtino::SwitchJoypad, inputtino::PS5Joypad>;
|
using joypads_t = std::variant<inputtino::XboxOneJoypad, inputtino::SwitchJoypad, inputtino::PS5Joypad>;
|
||||||
|
|
||||||
struct joypad_state {
|
struct joypad_state {
|
||||||
@@ -30,13 +46,13 @@ namespace platf {
|
|||||||
struct input_raw_t {
|
struct input_raw_t {
|
||||||
input_raw_t():
|
input_raw_t():
|
||||||
mouse(inputtino::Mouse::create({
|
mouse(inputtino::Mouse::create({
|
||||||
.name = "Mouse passthrough",
|
.name = inputtino_name_for_seat("Mouse passthrough"sv),
|
||||||
.vendor_id = 0xBEEF,
|
.vendor_id = 0xBEEF,
|
||||||
.product_id = 0xDEAD,
|
.product_id = 0xDEAD,
|
||||||
.version = 0x111,
|
.version = 0x111,
|
||||||
})),
|
})),
|
||||||
keyboard(inputtino::Keyboard::create({
|
keyboard(inputtino::Keyboard::create({
|
||||||
.name = "Keyboard passthrough",
|
.name = inputtino_name_for_seat("Keyboard passthrough"sv),
|
||||||
.vendor_id = 0xBEEF,
|
.vendor_id = 0xBEEF,
|
||||||
.product_id = 0xDEAD,
|
.product_id = 0xDEAD,
|
||||||
.version = 0x111,
|
.version = 0x111,
|
||||||
@@ -66,13 +82,13 @@ namespace platf {
|
|||||||
struct client_input_raw_t: public client_input_t {
|
struct client_input_raw_t: public client_input_t {
|
||||||
client_input_raw_t(input_t &input):
|
client_input_raw_t(input_t &input):
|
||||||
touch(inputtino::TouchScreen::create({
|
touch(inputtino::TouchScreen::create({
|
||||||
.name = "Touch passthrough",
|
.name = inputtino_name_for_seat("Touch passthrough"sv),
|
||||||
.vendor_id = 0xBEEF,
|
.vendor_id = 0xBEEF,
|
||||||
.product_id = 0xDEAD,
|
.product_id = 0xDEAD,
|
||||||
.version = 0x111,
|
.version = 0x111,
|
||||||
})),
|
})),
|
||||||
pen(inputtino::PenTablet::create({
|
pen(inputtino::PenTablet::create({
|
||||||
.name = "Pen passthrough",
|
.name = inputtino_name_for_seat("Pen passthrough"sv),
|
||||||
.vendor_id = 0xBEEF,
|
.vendor_id = 0xBEEF,
|
||||||
.product_id = 0xDEAD,
|
.product_id = 0xDEAD,
|
||||||
.version = 0x111,
|
.version = 0x111,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
// local includes
|
// local includes
|
||||||
#include "inputtino_common.h"
|
#include "inputtino_common.h"
|
||||||
#include "inputtino_gamepad.h"
|
#include "inputtino_gamepad.h"
|
||||||
|
#include "inputtino_seat.h"
|
||||||
#include "src/config.h"
|
#include "src/config.h"
|
||||||
#include "src/logging.h"
|
#include "src/logging.h"
|
||||||
#include "src/platform/common.h"
|
#include "src/platform/common.h"
|
||||||
@@ -27,7 +28,7 @@ namespace platf::gamepad {
|
|||||||
};
|
};
|
||||||
|
|
||||||
auto create_xbox_one() {
|
auto create_xbox_one() {
|
||||||
return inputtino::XboxOneJoypad::create({.name = "Sunshine X-Box One (virtual) pad",
|
return inputtino::XboxOneJoypad::create({.name = inputtino_name_for_seat("Sunshine X-Box One (virtual) pad"sv),
|
||||||
// https://github.com/torvalds/linux/blob/master/drivers/input/joystick/xpad.c#L147
|
// https://github.com/torvalds/linux/blob/master/drivers/input/joystick/xpad.c#L147
|
||||||
.vendor_id = 0x045E,
|
.vendor_id = 0x045E,
|
||||||
.product_id = 0x02EA,
|
.product_id = 0x02EA,
|
||||||
@@ -35,7 +36,7 @@ namespace platf::gamepad {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto create_switch() {
|
auto create_switch() {
|
||||||
return inputtino::SwitchJoypad::create({.name = "Sunshine Nintendo (virtual) pad",
|
return inputtino::SwitchJoypad::create({.name = inputtino_name_for_seat("Sunshine Nintendo (virtual) pad"sv),
|
||||||
// https://github.com/torvalds/linux/blob/master/drivers/hid/hid-ids.h#L981
|
// https://github.com/torvalds/linux/blob/master/drivers/hid/hid-ids.h#L981
|
||||||
.vendor_id = 0x057e,
|
.vendor_id = 0x057e,
|
||||||
.product_id = 0x2009,
|
.product_id = 0x2009,
|
||||||
@@ -50,7 +51,7 @@ namespace platf::gamepad {
|
|||||||
device_mac = std::format("02:00:00:00:00:{:02x}", globalIndex);
|
device_mac = std::format("02:00:00:00:00:{:02x}", globalIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
return inputtino::PS5Joypad::create({.name = "Sunshine PS5 (virtual) pad", .vendor_id = 0x054C, .product_id = 0x0CE6, .version = 0x8111, .device_phys = device_mac, .device_uniq = device_mac});
|
return inputtino::PS5Joypad::create({.name = inputtino_name_for_seat("Sunshine PS5 (virtual) pad"sv), .vendor_id = 0x054C, .product_id = 0x0CE6, .version = 0x8111, .device_phys = device_mac, .device_uniq = device_mac});
|
||||||
}
|
}
|
||||||
|
|
||||||
int alloc(input_raw_t *raw, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue) {
|
int alloc(input_raw_t *raw, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue) {
|
||||||
|
|||||||
23
src/platform/linux/input/inputtino_seat.cpp
Normal file
23
src/platform/linux/input/inputtino_seat.cpp
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* @file src/platform/linux/input/inputtino_seat.cpp
|
||||||
|
* @brief Implementation for multi-seat naming (udev-only).
|
||||||
|
*/
|
||||||
|
// standard includes
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
// local includes
|
||||||
|
#include "inputtino_seat.h"
|
||||||
|
|
||||||
|
namespace platf::inputtino_seat {
|
||||||
|
|
||||||
|
std::string get_target_seat() {
|
||||||
|
if (const char *seat = std::getenv("XDG_SEAT")) {
|
||||||
|
if (seat[0] != '\0') {
|
||||||
|
return seat;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace platf::inputtino_seat
|
||||||
17
src/platform/linux/input/inputtino_seat.h
Normal file
17
src/platform/linux/input/inputtino_seat.h
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* @file src/platform/linux/input/inputtino_seat.h
|
||||||
|
* @brief Helpers for multi-seat naming (udev-only).
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace platf::inputtino_seat {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the target seat for the current Sunshine instance.
|
||||||
|
* Returns empty string if no seat could be determined.
|
||||||
|
*/
|
||||||
|
std::string get_target_seat();
|
||||||
|
|
||||||
|
} // namespace platf::inputtino_seat
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
# Allows Sunshine to acces /dev/uinput
|
# Allows Sunshine to access /dev/uinput
|
||||||
KERNEL=="uinput", SUBSYSTEM=="misc", OPTIONS+="static_node=uinput", GROUP="input", MODE="0660", TAG+="uaccess"
|
KERNEL=="uinput", SUBSYSTEM=="misc", OPTIONS+="static_node=uinput", GROUP="input", MODE="0660", TAG+="uaccess"
|
||||||
|
|
||||||
# Allows Sunshine to access /dev/uhid
|
# Allows Sunshine to access /dev/uhid
|
||||||
KERNEL=="uhid", GROUP="input", MODE="0660", TAG+="uaccess"
|
KERNEL=="uhid", GROUP="input", MODE="0660", TAG+="uaccess"
|
||||||
|
|
||||||
# Joypads
|
# Joypads
|
||||||
KERNEL=="hidraw*", ATTRS{name}=="Sunshine PS5 (virtual) pad", GROUP="input", MODE="0660", TAG+="uaccess"
|
KERNEL=="hidraw*", ATTRS{name}=="Sunshine PS5 (virtual) pad*", GROUP="input", MODE="0660", TAG+="uaccess"
|
||||||
SUBSYSTEMS=="input", ATTRS{name}=="Sunshine X-Box One (virtual) pad", GROUP="input", MODE="0660", TAG+="uaccess"
|
SUBSYSTEMS=="input", ATTRS{name}=="Sunshine X-Box One (virtual) pad*", GROUP="input", MODE="0660", TAG+="uaccess"
|
||||||
SUBSYSTEMS=="input", ATTRS{name}=="Sunshine gamepad (virtual) motion sensors", GROUP="input", MODE="0660", TAG+="uaccess"
|
SUBSYSTEMS=="input", ATTRS{name}=="Sunshine gamepad (virtual) motion sensors*", GROUP="input", MODE="0660", TAG+="uaccess"
|
||||||
SUBSYSTEMS=="input", ATTRS{name}=="Sunshine Nintendo (virtual) pad", GROUP="input", MODE="0660", TAG+="uaccess"
|
SUBSYSTEMS=="input", ATTRS{name}=="Sunshine Nintendo (virtual) pad*", GROUP="input", MODE="0660", TAG+="uaccess"
|
||||||
|
SUBSYSTEMS=="input", ATTRS{name}=="Sunshine PS5 (virtual) pad*", GROUP="input", MODE="0660", TAG+="uaccess"
|
||||||
|
|||||||
Reference in New Issue
Block a user