From ce9f4bfe824cd517d54c45aaea48c54fd42ff7f4 Mon Sep 17 00:00:00 2001 From: qiin Date: Mon, 17 Aug 2026 09:50:23 +0800 Subject: [PATCH 1/4] feat(input): honor client DualSense preference --- src/input.cpp | 8 ++++++-- src/platform/common.h | 4 ++++ src/platform/windows/input.cpp | 26 +++++++++++++++++--------- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/input.cpp b/src/input.cpp index 971131aed..e0bef5600 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -961,8 +961,12 @@ namespace input { } if (input->gamepads[packet->controllerNumber].id >= 0) { - BOOST_LOG(warning) << "ControllerNumber already allocated ["sv << packet->controllerNumber << ']'; - return; + // A client may intentionally re-declare a controller to change its emulated type or + // capabilities at runtime. Recreate it so the new arrival metadata takes effect. + BOOST_LOG(info) << "Reallocating ControllerNumber with updated metadata ["sv + << packet->controllerNumber << ']'; + free_gamepad(platf_input, input->gamepads[packet->controllerNumber].id); + input->gamepads[packet->controllerNumber].id = -1; } platf::gamepad_arrival_t arrival { diff --git a/src/platform/common.h b/src/platform/common.h index 33d7917df..830b2d5ea 100644 --- a/src/platform/common.h +++ b/src/platform/common.h @@ -91,6 +91,10 @@ namespace platf { constexpr std::uint32_t TOUCHPAD_BUTTON = 0x100000; constexpr std::uint32_t MISC_BUTTON = 0x200000; + // Foundation client extension carried in SS_CONTROLLER_ARRIVAL_PACKET::capabilities. + // Requests a native DualSense device rather than the generic PS/DS4 fallback. + constexpr std::uint16_t GAMEPAD_CAP_PREFER_DS5 = 0x0100; + struct supported_gamepad_t { std::string name; bool is_enabled; diff --git a/src/platform/windows/input.cpp b/src/platform/windows/input.cpp index 069d7567b..3bf197a29 100644 --- a/src/platform/windows/input.cpp +++ b/src/platform/windows/input.cpp @@ -1912,19 +1912,27 @@ namespace platf { const auto gamepad_mode = effective_gamepad_mode(); const auto per_app_override = current_gamepad_mode.load(std::memory_order_relaxed) != 0; - if (gamepad_mode == 4) { + const auto client_prefers_ds5 = gamepad_mode == 1 && + (metadata.capabilities & GAMEPAD_CAP_PREFER_DS5); + if (gamepad_mode == 4 || client_prefers_ds5) { BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be DualSense controller (" - << (per_app_override ? "per-app selection" : "global selection") << ')'; + << (client_prefers_ds5 ? "requested by client" : + per_app_override ? "per-app selection" : "global selection") << ')'; if (!raw->ds5_sidecar || !raw->ds5_sidecar->configured()) { - BOOST_LOG(error) << "DualSense emulation is selected but its optional sidecar component is unavailable"sv; - return -1; + if (!client_prefers_ds5) { + BOOST_LOG(error) << "DualSense emulation is selected but its optional sidecar component is unavailable"sv; + return -1; + } + BOOST_LOG(warning) << "Client requested DualSense emulation, but its optional sidecar component is unavailable; falling back to DualShock 4"sv; } - const auto result = raw->ds5_sidecar->alloc(id, feedback_queue, config::input.ds5_audio_haptics); - if (result == 0) { - feedback_queue->raise(gamepad_feedback_msg_t::make_motion_event_state(id.clientRelativeIndex, LI_MOTION_TYPE_ACCEL, 100)); - feedback_queue->raise(gamepad_feedback_msg_t::make_motion_event_state(id.clientRelativeIndex, LI_MOTION_TYPE_GYRO, 100)); + else { + const auto result = raw->ds5_sidecar->alloc(id, feedback_queue, config::input.ds5_audio_haptics); + if (result == 0) { + feedback_queue->raise(gamepad_feedback_msg_t::make_motion_event_state(id.clientRelativeIndex, LI_MOTION_TYPE_ACCEL, 100)); + feedback_queue->raise(gamepad_feedback_msg_t::make_motion_event_state(id.clientRelativeIndex, LI_MOTION_TYPE_GYRO, 100)); + } + return result; } - return result; } if (!raw->vigem) { From 83dbacdfaa44718c268f46b5377f613379a787a0 Mon Sep 17 00:00:00 2001 From: qiin Date: Mon, 17 Aug 2026 10:29:04 +0800 Subject: [PATCH 2/4] fix(input): make controller reallocation safe --- src/input.cpp | 46 ++++++++++++++++++++++++++-------- src/platform/windows/input.cpp | 14 +++++++++-- 2 files changed, 47 insertions(+), 13 deletions(-) diff --git a/src/input.cpp b/src/input.cpp index e0bef5600..1abf6e2a9 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -129,7 +129,7 @@ namespace input { } struct gamepad_t { gamepad_t(): - gamepad_state {}, back_timeout_id {}, id { -1 }, back_button_state { button_state_e::NONE } {} + gamepad_state {}, back_timeout_id {}, id { -1 }, back_button_state { button_state_e::NONE }, arrival {} {} ~gamepad_t() { if (id >= 0) { task_pool.push([id = this->id]() { @@ -150,8 +150,21 @@ namespace input { // Sunshine forces the button to be in a specific state until the gamepad state matches that of // Moonlight once more. button_state_e back_button_state; + + std::optional arrival; }; + void + reset_gamepad_runtime_state(gamepad_t &gamepad) { + if (gamepad.back_timeout_id) { + task_pool.cancel(gamepad.back_timeout_id); + gamepad.back_timeout_id = nullptr; + } + gamepad.gamepad_state = {}; + gamepad.back_button_state = button_state_e::NONE; + gamepad.arrival.reset(); + } + struct input_t { enum shortkey_e { CTRL = 0x1, ///< Control key @@ -960,21 +973,30 @@ namespace input { return; } - if (input->gamepads[packet->controllerNumber].id >= 0) { - // A client may intentionally re-declare a controller to change its emulated type or - // capabilities at runtime. Recreate it so the new arrival metadata takes effect. - BOOST_LOG(info) << "Reallocating ControllerNumber with updated metadata ["sv - << packet->controllerNumber << ']'; - free_gamepad(platf_input, input->gamepads[packet->controllerNumber].id); - input->gamepads[packet->controllerNumber].id = -1; - } - platf::gamepad_arrival_t arrival { packet->type, util::endian::little(packet->capabilities), util::endian::little(packet->supportedButtonFlags), }; + auto &gamepad = input->gamepads[packet->controllerNumber]; + if (gamepad.id >= 0) { + if (gamepad.arrival && + gamepad.arrival->type == arrival.type && + gamepad.arrival->capabilities == arrival.capabilities && + gamepad.arrival->supportedButtons == arrival.supportedButtons) { + return; + } + + // A client may intentionally re-declare a controller to change its emulated type or + // capabilities at runtime. Recreate it so the new arrival metadata takes effect. + BOOST_LOG(info) << "Reallocating ControllerNumber with updated metadata ["sv + << packet->controllerNumber << ']'; + reset_gamepad_runtime_state(gamepad); + free_gamepad(platf_input, gamepad.id); + gamepad.id = -1; + } + auto id = alloc_id(gamepadMask); if (id < 0) { return; @@ -986,7 +1008,8 @@ namespace input { return; } - input->gamepads[packet->controllerNumber].id = id; + gamepad.id = id; + gamepad.arrival = arrival; } /** @@ -1318,6 +1341,7 @@ namespace input { } else if (!(packet->activeGamepadMask & (1 << packet->controllerNumber)) && gamepad.id >= 0) { // If this is the final event for a gamepad being removed, free the gamepad and return. + reset_gamepad_runtime_state(gamepad); free_gamepad(platf_input, gamepad.id); gamepad.id = -1; return; diff --git a/src/platform/windows/input.cpp b/src/platform/windows/input.cpp index 3bf197a29..b03d5173f 100644 --- a/src/platform/windows/input.cpp +++ b/src/platform/windows/input.cpp @@ -1914,6 +1914,7 @@ namespace platf { const auto client_prefers_ds5 = gamepad_mode == 1 && (metadata.capabilities & GAMEPAD_CAP_PREFER_DS5); + auto fallback_to_ds4 = false; if (gamepad_mode == 4 || client_prefers_ds5) { BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be DualSense controller (" << (client_prefers_ds5 ? "requested by client" : @@ -1924,6 +1925,7 @@ namespace platf { return -1; } BOOST_LOG(warning) << "Client requested DualSense emulation, but its optional sidecar component is unavailable; falling back to DualShock 4"sv; + fallback_to_ds4 = true; } else { const auto result = raw->ds5_sidecar->alloc(id, feedback_queue, config::input.ds5_audio_haptics); @@ -1931,7 +1933,11 @@ namespace platf { feedback_queue->raise(gamepad_feedback_msg_t::make_motion_event_state(id.clientRelativeIndex, LI_MOTION_TYPE_ACCEL, 100)); feedback_queue->raise(gamepad_feedback_msg_t::make_motion_event_state(id.clientRelativeIndex, LI_MOTION_TYPE_GYRO, 100)); } - return result; + if (result == 0 || !client_prefers_ds5) { + return result; + } + BOOST_LOG(warning) << "Client-requested DualSense allocation failed; falling back to DualShock 4"sv; + fallback_to_ds4 = true; } } @@ -1941,7 +1947,11 @@ namespace platf { VIGEM_TARGET_TYPE selectedGamepadType; - if (gamepad_mode == 2) { + if (fallback_to_ds4) { + BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be DualShock 4 controller (DualSense fallback)"sv; + selectedGamepadType = DualShock4Wired; + } + else if (gamepad_mode == 2) { BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Xbox 360 controller ("sv << (per_app_override ? "per-app selection" : "global selection") << ')'; selectedGamepadType = Xbox360Wired; } From 8c4beb6fd51a48337a57d848ee4b25847a5355b1 Mon Sep 17 00:00:00 2001 From: qiin Date: Mon, 17 Aug 2026 11:34:44 +0800 Subject: [PATCH 3/4] fix(input): honor client DualSense request over global mode --- src/platform/windows/input.cpp | 5 ++++- src/process.h | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/platform/windows/input.cpp b/src/platform/windows/input.cpp index b03d5173f..130fab990 100644 --- a/src/platform/windows/input.cpp +++ b/src/platform/windows/input.cpp @@ -1912,7 +1912,10 @@ namespace platf { const auto gamepad_mode = effective_gamepad_mode(); const auto per_app_override = current_gamepad_mode.load(std::memory_order_relaxed) != 0; - const auto client_prefers_ds5 = gamepad_mode == 1 && + // A client that needs DualSense-only input (such as a screen-backed + // touchpad) must be able to override the global controller default. Keep + // an explicit per-app controller selection authoritative. + const auto client_prefers_ds5 = !per_app_override && (metadata.capabilities & GAMEPAD_CAP_PREFER_DS5); auto fallback_to_ds4 = false; if (gamepad_mode == 4 || client_prefers_ds5) { diff --git a/src/process.h b/src/process.h index 5ae864a10..6612fe842 100644 --- a/src/process.h +++ b/src/process.h @@ -70,7 +70,7 @@ namespace proc { bool auto_detach; bool wait_all; int mouse_mode; ///< 0=auto (use global config), 1=force virtual mouse, 2=force SendInput - int gamepad_mode; ///< 0=inherit global, 1=auto, 2=Xbox 360, 3=DualShock 4 + int gamepad_mode; ///< 0=inherit global, 1=auto, 2=Xbox 360, 3=DualShock 4, 4=DualSense std::chrono::seconds exit_timeout; }; From 15fd347d894d9f49db0a73e791dbe2cdebac3e7c Mon Sep 17 00:00:00 2001 From: qiin <414382190@qq.com> Date: Mon, 17 Aug 2026 14:03:58 +0800 Subject: [PATCH 4/4] =?UTF-8?q?fix(input):=20Foundation=20=E5=81=8F?= =?UTF-8?q?=E5=A5=BD=E4=BD=8D=E6=8C=AA=E8=87=B3=E8=83=BD=E5=8A=9B=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=E9=AB=98=E4=BD=8D=E5=B9=B6=E6=94=B6=E6=95=9B=E9=87=8D?= =?UTF-8?q?=E5=88=86=E9=85=8D=E6=9D=A1=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - GAMEPAD_CAP_PREFER_DS5 0x0100 -> 0x8000,避开上游 moonlight-common-c 继续分配低位能力位的冲突;偏好为尽力而为,sidecar 不可用仍回退 DS4 - arrival 更新仅在类型相关字段(控制器类型、触摸板/加速度/陀螺仪 能力、偏好位)变化时才重建虚拟设备,其余元数据变化只刷新记录, 消除串流中因能力位抖动导致的设备销毁重建 - 客户端偏好覆盖非 auto 的主机全局模式时补 warning 日志 --- src/input.cpp | 18 ++++++++++++------ src/platform/common.h | 13 +++++++++++-- src/platform/windows/input.cpp | 4 ++++ 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/input.cpp b/src/input.cpp index 1abf6e2a9..2c68ed691 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -981,15 +981,21 @@ namespace input { auto &gamepad = input->gamepads[packet->controllerNumber]; if (gamepad.id >= 0) { - if (gamepad.arrival && + if (gamepad.arrival) { + const auto selection_unchanged = gamepad.arrival->type == arrival.type && - gamepad.arrival->capabilities == arrival.capabilities && - gamepad.arrival->supportedButtons == arrival.supportedButtons) { - return; + ((gamepad.arrival->capabilities ^ arrival.capabilities) & + platf::GAMEPAD_TYPE_SELECTION_CAPS) == 0; + if (selection_unchanged) { + // The update cannot change the emulated device type; remember the + // refreshed metadata without disturbing the active virtual device. + gamepad.arrival = arrival; + return; + } } - // A client may intentionally re-declare a controller to change its emulated type or - // capabilities at runtime. Recreate it so the new arrival metadata takes effect. + // A client may intentionally re-declare a controller to change its emulated type + // at runtime. Recreate it so the new arrival metadata takes effect. BOOST_LOG(info) << "Reallocating ControllerNumber with updated metadata ["sv << packet->controllerNumber << ']'; reset_gamepad_runtime_state(gamepad); diff --git a/src/platform/common.h b/src/platform/common.h index 830b2d5ea..dc4bcba7b 100644 --- a/src/platform/common.h +++ b/src/platform/common.h @@ -92,8 +92,17 @@ namespace platf { constexpr std::uint32_t MISC_BUTTON = 0x200000; // Foundation client extension carried in SS_CONTROLLER_ARRIVAL_PACKET::capabilities. - // Requests a native DualSense device rather than the generic PS/DS4 fallback. - constexpr std::uint16_t GAMEPAD_CAP_PREFER_DS5 = 0x0100; + // Best-effort preference for a native DualSense device; the host may still fall + // back to DualShock 4 when the optional sidecar is unavailable. Upstream + // moonlight-common-c owns the low capability bits (currently 0x00FF), so + // Foundation extensions claim the top bit. + constexpr std::uint16_t GAMEPAD_CAP_PREFER_DS5 = 0x8000; + + // Arrival fields that can change which emulated device type a platform selects + // (LI_CCAP_TOUCHPAD | LI_CCAP_ACCEL | LI_CCAP_GYRO plus the Foundation + // preference bit). Arrival updates touching only other fields are recorded + // without reallocating the active virtual device. + constexpr std::uint16_t GAMEPAD_TYPE_SELECTION_CAPS = 0x0038 | GAMEPAD_CAP_PREFER_DS5; struct supported_gamepad_t { std::string name; diff --git a/src/platform/windows/input.cpp b/src/platform/windows/input.cpp index 130fab990..50581eb7b 100644 --- a/src/platform/windows/input.cpp +++ b/src/platform/windows/input.cpp @@ -1919,6 +1919,10 @@ namespace platf { (metadata.capabilities & GAMEPAD_CAP_PREFER_DS5); auto fallback_to_ds4 = false; if (gamepad_mode == 4 || client_prefers_ds5) { + if (client_prefers_ds5 && gamepad_mode != 1) { + BOOST_LOG(warning) << "Client DualSense preference overrides the host gamepad mode for gamepad "sv + << id.globalIndex; + } BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be DualSense controller (" << (client_prefers_ds5 ? "requested by client" : per_app_override ? "per-app selection" : "global selection") << ')';