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
48 changes: 41 additions & 7 deletions src/input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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]() {
Expand All @@ -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<platf::gamepad_arrival_t> 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
Expand Down Expand Up @@ -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;
Expand All @@ -982,7 +1014,8 @@ namespace input {
return;
}

input->gamepads[packet->controllerNumber].id = id;
gamepad.id = id;
gamepad.arrival = arrival;
}

/**
Expand Down Expand Up @@ -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;
Expand Down
13 changes: 13 additions & 0 deletions src/platform/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
45 changes: 35 additions & 10 deletions src/platform/windows/input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
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) {
Expand All @@ -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;
}
Expand Down
2 changes: 1 addition & 1 deletion src/process.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};

Expand Down
Loading