diff --git a/src/input.cpp b/src/input.cpp index db7618bfb05..27d3a9deb52 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -162,10 +162,12 @@ namespace input { input_t( safe::mail_raw_t::event_t touch_port_event, - platf::feedback_queue_t feedback_queue + platf::feedback_queue_t feedback_queue, + std::string gamepad_override = {} ): shortcutFlags {}, gamepads(MAX_GAMEPADS), + gamepad_override {std::move(gamepad_override)}, client_context {platf::allocate_client_input_context(platf_input)}, touch_port_event {std::move(touch_port_event)}, feedback_queue {std::move(feedback_queue)}, @@ -179,6 +181,7 @@ namespace input { int shortcutFlags; std::vector gamepads; + std::string gamepad_override; // per-session override; empty = use global config std::unique_ptr client_context; safe::mail_raw_t::event_t touch_port_event; @@ -894,7 +897,7 @@ namespace input { } // 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; } @@ -1147,7 +1150,7 @@ namespace input { 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; } @@ -1681,10 +1684,11 @@ namespace input { return true; } - std::shared_ptr alloc(safe::mail_t mail) { + std::shared_ptr alloc(safe::mail_t mail, std::string_view gamepad_override) { auto input = std::make_shared( mail->event(mail::touch_port), - mail->queue(mail::gamepad_feedback) + mail->queue(mail::gamepad_feedback), + std::string {gamepad_override} ); // Workaround to ensure new frames will be captured when a client connects diff --git a/src/input.h b/src/input.h index 0e7007c9d03..71ade744d31 100644 --- a/src/input.h +++ b/src/input.h @@ -22,7 +22,7 @@ namespace input { bool probe_gamepads(); - std::shared_ptr alloc(safe::mail_t mail); + std::shared_ptr alloc(safe::mail_t mail, std::string_view gamepad_override = {}); struct touch_port_t: public platf::touch_port_t { int env_width; diff --git a/src/platform/common.h b/src/platform/common.h index 07e5af4a064..c8363ae48d1 100644 --- a/src/platform/common.h +++ b/src/platform/common.h @@ -10,6 +10,7 @@ #include #include #include +#include // lib includes #include @@ -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); /** diff --git a/src/platform/linux/input/inputtino.cpp b/src/platform/linux/input/inputtino.cpp index 3102fdd9120..e1db8923240 100644 --- a/src/platform/linux/input/inputtino.cpp +++ b/src/platform/linux/input/inputtino.cpp @@ -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) { diff --git a/src/platform/linux/input/inputtino_gamepad.cpp b/src/platform/linux/input/inputtino_gamepad.cpp index 7e782b59b21..8e1e14ba9d7 100644 --- a/src/platform/linux/input/inputtino_gamepad.cpp +++ b/src/platform/linux/input/inputtino_gamepad.cpp @@ -53,16 +53,17 @@ namespace platf::gamepad { 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) { 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) { diff --git a/src/platform/linux/input/inputtino_gamepad.h b/src/platform/linux/input/inputtino_gamepad.h index 8d26c9e1ff6..814cc945260 100644 --- a/src/platform/linux/input/inputtino_gamepad.h +++ b/src/platform/linux/input/inputtino_gamepad.h @@ -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); diff --git a/src/platform/macos/input.cpp b/src/platform/macos/input.cpp index d3bf20057ba..5724633c63d 100644 --- a/src/platform/macos/input.cpp +++ b/src/platform/macos/input.cpp @@ -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; } diff --git a/src/platform/windows/input.cpp b/src/platform/windows/input.cpp index 28fe6fa5749..3094ff0a720 100644 --- a/src/platform/windows/input.cpp +++ b/src/platform/windows/input.cpp @@ -1163,7 +1163,7 @@ 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) { @@ -1171,11 +1171,12 @@ namespace platf { } 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) { diff --git a/src/process.cpp b/src/process.cpp index 95c7bdaeaa5..94c463eefc2 100644 --- a/src/process.cpp +++ b/src/process.cpp @@ -650,6 +650,7 @@ namespace proc { auto auto_detach = app_node.get_optional("auto-detach"s); auto wait_all = app_node.get_optional("wait-all"s); auto exit_timeout = app_node.get_optional("exit-timeout"s); + auto gamepad = app_node.get_optional("gamepad"s); std::vector prep_cmds; if (!exclude_global_prep.value_or(false)) { @@ -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); diff --git a/src/process.h b/src/process.h index 0f2f5f51fb4..a5c2ccfa77a 100644 --- a/src/process.h +++ b/src/process.h @@ -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; diff --git a/src/stream.cpp b/src/stream.cpp index 8b303ef24da..b3520e97da7 100644 --- a/src/stream.cpp +++ b/src/stream.cpp @@ -4,6 +4,7 @@ */ // standard includes +#include #include #include #include @@ -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 shutdown_event; safe::signal_t controlEnd; @@ -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) { @@ -2009,6 +2011,15 @@ namespace stream { session->shutdown_event = mail->event(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; diff --git a/src_assets/common/assets/web/apps.html b/src_assets/common/assets/web/apps.html index c9d06f61f9c..3763786634a 100644 --- a/src_assets/common/assets/web/apps.html +++ b/src_assets/common/assets/web/apps.html @@ -243,6 +243,24 @@
{{ app.name }}
v-model="editForm['exit-timeout']" min="0" placeholder="5" />
{{ $t('apps.exit_timeout_desc') }}
+ +
+ + +
{{ $t('apps.gamepad_desc') }}
+
@@ -573,6 +591,7 @@ "auto-detach": true, "wait-all": true, "exit-timeout": 5, + gamepad: "", "prep-cmd": [], detached: [], "image-path": "" @@ -601,6 +620,9 @@ if (this.editForm["exit-timeout"] === undefined) { this.editForm["exit-timeout"] = 5; } + if (this.editForm.gamepad === undefined) { + this.editForm.gamepad = ""; + } this.showEditForm = true; }, showDeleteForm(id) { diff --git a/src_assets/common/assets/web/public/assets/locale/en.json b/src_assets/common/assets/web/public/assets/locale/en.json index 75b1a9c74d5..8c677490fc3 100644 --- a/src_assets/common/assets/web/public/assets/locale/en.json +++ b/src_assets/common/assets/web/public/assets/locale/en.json @@ -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",