Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions docs/getting_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,48 @@ After adding yourself to the group, log out and log back in for the changes to t

### Linux

#### Virtual Input Devices
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously this wasn't needed unless in a headless environment. So this is documented in the troubleshooting section instead.


Sunshine uses virtual input devices (via uinput) to inject keyboard, mouse, and gamepad events. Access to `/dev/uinput` is typically restricted to the `input` group.

Add your user to the group:

```bash
sudo usermod -aG input $USER
```

> Log out and back in after running this.
The required udev rules are bundled with Sunshine and installed automatically. No manual udev configuration is needed for single-seat setups.

---

#### Multi-seat

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 (suffixed with ` (absolute)`).
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
```

#### Services

**Start once**
Expand Down
24 changes: 20 additions & 4 deletions src/platform/linux/input/inputtino_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,28 @@
#include "src/config.h"
#include "src/logging.h"
#include "src/platform/common.h"
#include "src/platform/linux/input/inputtino_seat.h"
#include "src/utility.h"

using namespace std::literals;

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>;

struct joypad_state {
Expand All @@ -30,13 +46,13 @@ namespace platf {
struct input_raw_t {
input_raw_t():
mouse(inputtino::Mouse::create({
.name = "Mouse passthrough",
.name = inputtino_name_for_seat("Mouse passthrough"sv),
.vendor_id = 0xBEEF,
.product_id = 0xDEAD,
.version = 0x111,
})),
keyboard(inputtino::Keyboard::create({
.name = "Keyboard passthrough",
.name = inputtino_name_for_seat("Keyboard passthrough"sv),
.vendor_id = 0xBEEF,
.product_id = 0xDEAD,
.version = 0x111,
Expand Down Expand Up @@ -66,13 +82,13 @@ namespace platf {
struct client_input_raw_t: public client_input_t {
client_input_raw_t(input_t &input):
touch(inputtino::TouchScreen::create({
.name = "Touch passthrough",
.name = inputtino_name_for_seat("Touch passthrough"sv),
.vendor_id = 0xBEEF,
.product_id = 0xDEAD,
.version = 0x111,
})),
pen(inputtino::PenTablet::create({
.name = "Pen passthrough",
.name = inputtino_name_for_seat("Pen passthrough"sv),
.vendor_id = 0xBEEF,
.product_id = 0xDEAD,
.version = 0x111,
Expand Down
7 changes: 4 additions & 3 deletions src/platform/linux/input/inputtino_gamepad.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
// local includes
#include "inputtino_common.h"
#include "inputtino_gamepad.h"
#include "inputtino_seat.h"
#include "src/config.h"
#include "src/logging.h"
#include "src/platform/common.h"
Expand All @@ -27,15 +28,15 @@ namespace platf::gamepad {
};

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
.vendor_id = 0x045E,
.product_id = 0x02EA,
.version = 0x0408});
}

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
.vendor_id = 0x057e,
.product_id = 0x2009,
Expand All @@ -50,7 +51,7 @@ namespace platf::gamepad {
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) {
Expand Down
22 changes: 22 additions & 0 deletions src/platform/linux/input/inputtino_seat.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* @file src/platform/linux/input/inputtino_seat.cpp
* @brief Implementation for multi-seat naming (udev-only).
*/

#include "inputtino_seat.h"

#include <cstdlib>

namespace platf::inputtino_seat {

std::string get_target_seat() {
if (const char *seat = std::getenv("XDG_SEAT")) {

Check warning on line 13 in src/platform/linux/input/inputtino_seat.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'getenv' is deprecated: This function or variable may be unsafe. Consider using _dupenv_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.

See more on https://sonarcloud.io/project/issues?id=LizardByte_Sunshine&issues=AZ1jE-QkrvBJ5ASyU7Rq&open=AZ1jE-QkrvBJ5ASyU7Rq&pullRequest=4954
if (seat[0] != '\0') {
return seat;
}
}

return {};
}

} // namespace platf::inputtino_seat
17 changes: 17 additions & 0 deletions src/platform/linux/input/inputtino_seat.h
Original file line number Diff line number Diff line change
@@ -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
17 changes: 8 additions & 9 deletions src_assets/linux/misc/60-sunshine.rules
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For contributors who don't live in Linux land, like me... can you leave the comments in place? As well as the previous ordering?

Also, remove alignment as it makes reading the diffs difficult.

Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
# Allows Sunshine to acces /dev/uinput
# Allows Sunshine to access /dev/uinput and /dev/uhid
KERNEL=="uinput", SUBSYSTEM=="misc", OPTIONS+="static_node=uinput", GROUP="input", MODE="0660", TAG+="uaccess"
KERNEL=="uhid", GROUP="input", MODE="0660", TAG+="uaccess"

# Allows Sunshine to access /dev/uhid
KERNEL=="uhid", GROUP="input", MODE="0660", TAG+="uaccess"

# Joypads
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 gamepad (virtual) motion sensors", GROUP="input", MODE="0660", TAG+="uaccess"
SUBSYSTEMS=="input", ATTRS{name}=="Sunshine Nintendo (virtual) pad", GROUP="input", MODE="0660", TAG+="uaccess"
# Virtual joypads (seat suffix e.g. "... (seat1)" is appended on multi-seat systems)
KERNEL=="hidraw*", ATTRS{name}=="Sunshine PS5 (virtual) pad*", GROUP="input", MODE="0660", TAG+="uaccess"
SUBSYSTEMS=="input", 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 Nintendo (virtual) pad*", GROUP="input", MODE="0660", TAG+="uaccess"
SUBSYSTEMS=="input", ATTRS{name}=="Sunshine gamepad (virtual) motion sensors*", GROUP="input", MODE="0660", TAG+="uaccess"