diff --git a/src/input.cpp b/src/input.cpp index 971131ae..2c68ed69 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,17 +973,36 @@ namespace input { return; } - if (input->gamepads[packet->controllerNumber].id >= 0) { - BOOST_LOG(warning) << "ControllerNumber already allocated ["sv << packet->controllerNumber << ']'; - return; - } - 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) { + const auto selection_unchanged = + gamepad.arrival->type == arrival.type && + ((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 + // 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; @@ -982,7 +1014,8 @@ namespace input { return; } - input->gamepads[packet->controllerNumber].id = id; + gamepad.id = id; + gamepad.arrival = arrival; } /** @@ -1314,6 +1347,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/common.h b/src/platform/common.h index 33d7917d..dc4bcba7 100644 --- a/src/platform/common.h +++ b/src/platform/common.h @@ -91,6 +91,19 @@ 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. + // 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; bool is_enabled; diff --git a/src/platform/windows/input.cpp b/src/platform/windows/input.cpp index 069d7567..50581eb7 100644 --- a/src/platform/windows/input.cpp +++ b/src/platform/windows/input.cpp @@ -1912,19 +1912,40 @@ 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) { + // 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) { + 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 (" - << (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; + fallback_to_ds4 = true; } - 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)); + } + 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; } - return result; } if (!raw->vigem) { @@ -1933,7 +1954,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; } diff --git a/src/process.h b/src/process.h index 5ae864a1..6612fe84 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; };