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
14 changes: 9 additions & 5 deletions src/input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,13 +162,15 @@

input_t(
safe::mail_raw_t::event_t<input::touch_port_t> touch_port_event,
platf::feedback_queue_t feedback_queue
platf::feedback_queue_t feedback_queue,
std::string gamepad_override = {}
):
shortcutFlags {},
gamepads(MAX_GAMEPADS),
client_context {platf::allocate_client_input_context(platf_input)},
touch_port_event {std::move(touch_port_event)},
feedback_queue {std::move(feedback_queue)},
gamepad_override {std::move(gamepad_override)},

Check warning on line 173 in src/input.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Field "gamepad_override" is initialized before field "client_context", field "touch_port_event" and field "feedback_queue".

See more on https://sonarcloud.io/project/issues?id=LizardByte_Sunshine&issues=AZz5Ht2-vjDTsnvO4O_N&open=AZz5Ht2-vjDTsnvO4O_N&pullRequest=4863
mouse_left_button_timeout {},
touch_port {{0, 0, 0, 0}, 0, 0, 1.0f, 1.0f, 0, 0},
accumulated_vscroll_delta {},
Expand All @@ -179,6 +181,7 @@
int shortcutFlags;

std::vector<gamepad_t> gamepads;
std::string gamepad_override; // per-session override; empty = use global config
std::unique_ptr<platf::client_input_t> client_context;

safe::mail_raw_t::event_t<input::touch_port_t> touch_port_event;
Expand Down Expand Up @@ -894,7 +897,7 @@
}

// Allocate a new gamepad
if (platf::alloc_gamepad(platf_input, {id, packet->controllerNumber}, arrival, input->feedback_queue)) {
if (platf::alloc_gamepad(platf_input, {id, packet->controllerNumber}, arrival, input->feedback_queue, input->gamepad_override)) {
free_id(gamepadMask, id);
return;
}
Expand Down Expand Up @@ -1147,7 +1150,7 @@
return;
}

if (platf::alloc_gamepad(platf_input, {id, (uint8_t) packet->controllerNumber}, {}, input->feedback_queue)) {
if (platf::alloc_gamepad(platf_input, {id, (uint8_t) packet->controllerNumber}, {}, input->feedback_queue, input->gamepad_override)) {
free_id(gamepadMask, id);
return;
}
Expand Down Expand Up @@ -1681,10 +1684,11 @@
return true;
}

std::shared_ptr<input_t> alloc(safe::mail_t mail) {
std::shared_ptr<input_t> alloc(safe::mail_t mail, std::string_view gamepad_override) {
auto input = std::make_shared<input_t>(
mail->event<input::touch_port_t>(mail::touch_port),
mail->queue<platf::gamepad_feedback_msg_t>(mail::gamepad_feedback)
mail->queue<platf::gamepad_feedback_msg_t>(mail::gamepad_feedback),
std::string {gamepad_override}
);

// Workaround to ensure new frames will be captured when a client connects
Expand Down
2 changes: 1 addition & 1 deletion src/input.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ namespace input {

bool probe_gamepads();

std::shared_ptr<input_t> alloc(safe::mail_t mail);
std::shared_ptr<input_t> alloc(safe::mail_t mail, std::string_view gamepad_override = {});

struct touch_port_t: public platf::touch_port_t {
int env_width;
Expand Down
4 changes: 3 additions & 1 deletion src/platform/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <functional>
#include <mutex>
#include <string>
#include <string_view>

// lib includes
#include <boost/core/noncopyable.hpp>
Expand Down Expand Up @@ -834,9 +835,10 @@ namespace platf {
* @param id The gamepad ID.
* @param metadata Controller metadata from client (empty if none provided).
* @param feedback_queue The queue for posting messages back to the client.
* @param gamepad_override Per-session gamepad type override (e.g. "xone", "ds4"); empty = use global config.
* @return 0 on success.
*/
int alloc_gamepad(input_t &input, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue);
int alloc_gamepad(input_t &input, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue, std::string_view gamepad_override = {});
void free_gamepad(input_t &input, int nr);

/**
Expand Down
4 changes: 2 additions & 2 deletions src/platform/linux/input/inputtino.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,9 @@ namespace platf {
platf::pen::update(raw, touch_port, pen);
}

int alloc_gamepad(input_t &input, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue) {
int alloc_gamepad(input_t &input, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue, std::string_view gamepad_override) {
auto raw = (input_raw_t *) input.get();
return platf::gamepad::alloc(raw, id, metadata, feedback_queue);
return platf::gamepad::alloc(raw, id, metadata, feedback_queue, gamepad_override);
}

void free_gamepad(input_t &input, int nr) {
Expand Down
9 changes: 5 additions & 4 deletions src/platform/linux/input/inputtino_gamepad.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,17 @@
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});
}

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, std::string_view gamepad_override) {

Check failure on line 56 in src/platform/linux/input/inputtino_gamepad.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 43 to the 25 allowed.

See more on https://sonarcloud.io/project/issues?id=LizardByte_Sunshine&issues=AZz5HtwDvjDTsnvO4O_M&open=AZz5HtwDvjDTsnvO4O_M&pullRequest=4863
ControllerType selectedGamepadType;
const std::string_view effective_gamepad = gamepad_override.empty() ? std::string_view(config::input.gamepad) : gamepad_override;

if (config::input.gamepad == "xone"sv) {
if (effective_gamepad == "xone"sv) {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Xbox One controller (manual selection)"sv;
selectedGamepadType = XboxOneWired;
} else if (config::input.gamepad == "ds5"sv) {
} else if (effective_gamepad == "ds5"sv) {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be DualSense 5 controller (manual selection)"sv;
selectedGamepadType = DualSenseWired;
} else if (config::input.gamepad == "switch"sv) {
} else if (effective_gamepad == "switch"sv) {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Nintendo Pro controller (manual selection)"sv;
selectedGamepadType = SwitchProWired;
} else if (metadata.type == LI_CTYPE_XBOX) {
Expand Down
2 changes: 1 addition & 1 deletion src/platform/linux/input/inputtino_gamepad.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ namespace platf::gamepad {
SwitchProWired ///< Switch Pro Wired Controller
};

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, std::string_view gamepad_override = {});

void free(input_raw_t *raw, int nr);

Expand Down
2 changes: 1 addition & 1 deletion src/platform/macos/input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ const KeyCodeMap kKeyCodesMap[] = {
BOOST_LOG(info) << "unicode: Unicode input not yet implemented for MacOS."sv;
}

int alloc_gamepad(input_t &input, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue) {
int alloc_gamepad(input_t &input, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue, std::string_view gamepad_override) {
BOOST_LOG(info) << "alloc_gamepad: Gamepad not yet implemented for MacOS."sv;
return -1;
}
Expand Down
7 changes: 4 additions & 3 deletions src/platform/windows/input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1163,19 +1163,20 @@ namespace platf {
}
}

int alloc_gamepad(input_t &input, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue) {
int alloc_gamepad(input_t &input, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue, std::string_view gamepad_override) {
auto raw = (input_raw_t *) input.get();

if (!raw->vigem) {
return 0;
}

VIGEM_TARGET_TYPE selectedGamepadType;
const std::string_view effective_gamepad = gamepad_override.empty() ? std::string_view(config::input.gamepad) : gamepad_override;

if (config::input.gamepad == "x360"sv) {
if (effective_gamepad == "x360"sv) {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Xbox 360 controller (manual selection)"sv;
selectedGamepadType = Xbox360Wired;
} else if (config::input.gamepad == "ds4"sv) {
} else if (effective_gamepad == "ds4"sv) {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be DualShock 4 controller (manual selection)"sv;
selectedGamepadType = DualShock4Wired;
} else if (metadata.type == LI_CTYPE_PS) {
Expand Down
5 changes: 5 additions & 0 deletions src/process.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,7 @@ namespace proc {
auto auto_detach = app_node.get_optional<bool>("auto-detach"s);
auto wait_all = app_node.get_optional<bool>("wait-all"s);
auto exit_timeout = app_node.get_optional<int>("exit-timeout"s);
auto gamepad = app_node.get_optional<std::string>("gamepad"s);

std::vector<proc::cmd_t> prep_cmds;
if (!exclude_global_prep.value_or(false)) {
Expand Down Expand Up @@ -715,6 +716,10 @@ namespace proc {
ctx.image_path = parse_env_val(this_env, *image_path);
}

if (gamepad) {
ctx.gamepad = parse_env_val(this_env, *gamepad);
}

ctx.elevated = elevated.value_or(false);
ctx.auto_detach = auto_detach.value_or(true);
ctx.wait_all = wait_all.value_or(true);
Expand Down
1 change: 1 addition & 0 deletions src/process.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ namespace proc {
std::string output;
std::string image_path;
std::string id;
std::string gamepad; // optional: "xone", "ds5", "switch", "x360", "ds4", or empty/"auto" = use global config
bool elevated;
bool auto_detach;
bool wait_all;
Expand Down
13 changes: 12 additions & 1 deletion src/stream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/

// standard includes
#include <algorithm>
#include <fstream>
#include <future>
#include <queue>
Expand Down Expand Up @@ -405,6 +406,7 @@ namespace stream {
} control;

std::uint32_t launch_session_id;
std::string gamepad_override; // per-app gamepad type when streaming this session; empty = use global config

safe::mail_raw_t::event_t<bool> shutdown_event;
safe::signal_t controlEnd;
Expand Down Expand Up @@ -1961,7 +1963,7 @@ namespace stream {
}

int start(session_t &session, const std::string &addr_string) {
session.input = input::alloc(session.mail);
session.input = input::alloc(session.mail, session.gamepad_override);

session.broadcast_ref = broadcast.ref();
if (!session.broadcast_ref) {
Expand Down Expand Up @@ -2009,6 +2011,15 @@ namespace stream {

session->shutdown_event = mail->event<bool>(mail::shutdown);
session->launch_session_id = launch_session.id;
{
const auto &apps = proc::proc.get_apps();
auto it = std::find_if(apps.begin(), apps.end(), [&](const proc::ctx_t &app) {
return app.id == std::to_string(launch_session.appid);
});
if (it != apps.end() && !it->gamepad.empty() && it->gamepad != "auto") {
session->gamepad_override = it->gamepad;
}
}

session->config = config;

Expand Down
22 changes: 22 additions & 0 deletions src_assets/common/assets/web/apps.html
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,24 @@ <h5 class="card-title mb-2">{{ app.name }}</h5>
v-model="editForm['exit-timeout']" min="0" placeholder="5" />
<div id="exitTimeoutHelp" class="form-text">{{ $t('apps.exit_timeout_desc') }}</div>
</div>
<!-- per-app gamepad type (when not macos) -->
<div class="mb-3" v-if="platform !== 'macos'">
<label for="appGamepad" class="form-label">{{ $t('apps.gamepad') }}</label>
<select id="appGamepad" class="form-select" v-model="editForm.gamepad">
<option value="">{{ $t('apps.gamepad_default') }}</option>
<option value="auto">{{ $t('_common.auto') }}</option>
<template v-if="platform === 'windows'">
<option value="x360">{{ $t('config.gamepad_x360') }}</option>
<option value="ds4">{{ $t('config.gamepad_ds4') }}</option>
</template>
<template v-if="platform === 'linux' || platform === 'freebsd'">
<option value="xone">{{ $t('config.gamepad_xone') }}</option>
<option value="ds5">{{ $t('config.gamepad_ds5') }}</option>
<option value="switch">{{ $t('config.gamepad_switch') }}</option>
</template>
</select>
<div class="form-text">{{ $t('apps.gamepad_desc') }}</div>
</div>
<div class="mb-3">
<label for="appImagePath" class="form-label">{{ $t('apps.image') }}</label>
<div class="input-group">
Expand Down Expand Up @@ -573,6 +591,7 @@ <h5 class="modal-title">{{ fileBrowserTitle || $t('file_browser.title') }}</h5>
"auto-detach": true,
"wait-all": true,
"exit-timeout": 5,
gamepad: "",
"prep-cmd": [],
detached: [],
"image-path": ""
Expand Down Expand Up @@ -601,6 +620,9 @@ <h5 class="modal-title">{{ fileBrowserTitle || $t('file_browser.title') }}</h5>
if (this.editForm["exit-timeout"] === undefined) {
this.editForm["exit-timeout"] = 5;
}
if (this.editForm.gamepad === undefined) {
this.editForm.gamepad = "";
}
this.showEditForm = true;
},
showDeleteForm(id) {
Expand Down
3 changes: 3 additions & 0 deletions src_assets/common/assets/web/public/assets/locale/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@
"env_xrandr_example": "Example - Xrandr for Resolution Automation:",
"exit_timeout": "Exit Timeout",
"exit_timeout_desc": "Number of seconds to wait for all app processes to gracefully exit when requested to quit. If unset, the default is to wait up to 5 seconds. If set to 0, the app will be immediately terminated.",
"gamepad": "Controller Type",
"gamepad_default": "Use default (from Input settings)",
"gamepad_desc": "Override the emulated controller type for this app. When streaming this app, the host will use this type instead of the global setting.",
"find_cover": "Find Cover",
"global_prep_desc": "Enable/Disable the execution of Global Prep Commands for this application.",
"global_prep_name": "Global Prep Commands",
Expand Down