diff --git a/src/platform/common.h b/src/platform/common.h index dcb126878b9..9e1fc707eeb 100644 --- a/src/platform/common.h +++ b/src/platform/common.h @@ -117,6 +117,8 @@ namespace platf { set_motion_event_state, ///< Set motion event state set_rgb_led, ///< Set RGB LED set_adaptive_triggers, ///< Set adaptive triggers + set_player_led, ///< Set player-indicator LEDs (DualSense) + set_mic_led, ///< Set mic-mute LED (DualSense) }; /** @@ -208,6 +210,36 @@ namespace platf { return msg; } + /** + * @brief Create a player-LED (player-indicator) feedback command (DualSense). + * + * @param id Identifier for the controller. + * @param ledValue Raw 5-bit player-indicator bitmask (forwarded verbatim from the game's report). + * @return Constructed player-LED object. + */ + static gamepad_feedback_msg_t make_player_led(std::uint16_t id, std::uint8_t ledValue) { + gamepad_feedback_msg_t msg; + msg.type = gamepad_feedback_e::set_player_led; + msg.id = id; + msg.data.player_led = {ledValue}; + return msg; + } + + /** + * @brief Create a mic-mute-LED feedback command (DualSense). + * + * @param id Identifier for the controller. + * @param ledState Mic-LED state (0 = off, 1 = on, 2 = pulse). + * @return Constructed mic-LED object. + */ + static gamepad_feedback_msg_t make_mic_led(std::uint16_t id, std::uint8_t ledState) { + gamepad_feedback_msg_t msg; + msg.type = gamepad_feedback_e::set_mic_led; + msg.id = id; + msg.data.mic_led = {ledState}; + return msg; + } + gamepad_feedback_e type; ///< Feedback command type stored in the union payload. std::uint16_t id; ///< Controller identifier associated with this message. @@ -233,6 +265,14 @@ namespace platf { std::uint8_t b; } rgb_led; + struct { + std::uint8_t value; ///< Raw 5-bit player-indicator bitmask. + } player_led; + + struct { + std::uint8_t state; ///< 0 = off, 1 = on, 2 = pulse. + } mic_led; + struct { uint16_t controllerNumber; uint8_t event_flags; diff --git a/src/platform/virtualhid_input.cpp b/src/platform/virtualhid_input.cpp index b7aee98299a..f054536a06c 100644 --- a/src/platform/virtualhid_input.cpp +++ b/src/platform/virtualhid_input.cpp @@ -45,6 +45,10 @@ namespace platf::virtualhid { std::uint8_t last_red = 0; ///< Last red LED value. std::uint8_t last_green = 0; ///< Last green LED value. std::uint8_t last_blue = 0; ///< Last blue LED value. + bool has_last_player_led = false; ///< Whether the last player-LED value is valid. + std::uint8_t last_player_led = 0; ///< Last player-indicator LED bitmask. + bool has_last_mic_led = false; ///< Whether the last mic-LED state is valid. + std::uint8_t last_mic_led = 0; ///< Last mic-mute LED state. }; namespace { @@ -390,6 +394,22 @@ namespace platf::virtualhid { gamepad->last_blue = output.blue; raise_feedback_unlocked(gamepad, gamepad_feedback_msg_t::make_rgb_led(gamepad->client_relative_index, output.red, output.green, output.blue)); break; + case lvh::GamepadOutputKind::player_led: + if (gamepad->has_last_player_led && gamepad->last_player_led == output.player_led) { + return; + } + gamepad->has_last_player_led = true; + gamepad->last_player_led = output.player_led; + raise_feedback_unlocked(gamepad, gamepad_feedback_msg_t::make_player_led(gamepad->client_relative_index, output.player_led)); + break; + case lvh::GamepadOutputKind::mic_led: + if (gamepad->has_last_mic_led && gamepad->last_mic_led == output.mic_led) { + return; + } + gamepad->has_last_mic_led = true; + gamepad->last_mic_led = output.mic_led; + raise_feedback_unlocked(gamepad, gamepad_feedback_msg_t::make_mic_led(gamepad->client_relative_index, output.mic_led)); + break; case lvh::GamepadOutputKind::adaptive_triggers: raise_feedback_unlocked(gamepad, gamepad_feedback_msg_t::make_adaptive_triggers(gamepad->client_relative_index, output.adaptive_trigger_flags, output.left_trigger_effect_type, output.right_trigger_effect_type, output.left_trigger_effect, output.right_trigger_effect)); break; diff --git a/src/stream.cpp b/src/stream.cpp index 5d31a55534a..8c428b08053 100644 --- a/src/stream.cpp +++ b/src/stream.cpp @@ -49,6 +49,8 @@ constexpr int IDX_RUMBLE_TRIGGER_DATA = 12; ///< Control-stream message index f constexpr int IDX_SET_MOTION_EVENT = 13; ///< Control-stream message index for set motion event. constexpr int IDX_SET_RGB_LED = 14; ///< Control-stream message index for set rgb led. constexpr int IDX_SET_ADAPTIVE_TRIGGERS = 15; ///< Control-stream message index for set adaptive triggers. +constexpr int IDX_SET_PLAYER_LED = 16; ///< Control-stream message index for set player LED (DualSense). +constexpr int IDX_SET_MIC_LED = 17; ///< Control-stream message index for set mic LED (DualSense). static const short packetTypes[] = { 0x0305, // Start A @@ -67,6 +69,8 @@ static const short packetTypes[] = { 0x5501, // Set motion event (Sunshine protocol extension) 0x5502, // Set RGB LED (Sunshine protocol extension) 0x5503, // Set Adaptive triggers (Sunshine protocol extension) + 0x5504, // Set Player LED (Sunshine protocol extension, DualSense) + 0x5505, // Set Mic LED (Sunshine protocol extension, DualSense) }; namespace asio = boost::asio; @@ -239,6 +243,26 @@ namespace stream { std::uint8_t b; ///< Blue LED channel. }; + /** + * @brief Control payload that sets the DualSense player-indicator LEDs. + */ + struct control_set_player_led_t { + control_header_v2 header; ///< Control message header preceding this payload. + + std::uint16_t id; ///< Controller identifier associated with this message. + std::uint8_t value; ///< Raw 5-bit player-indicator bitmask. + }; + + /** + * @brief Control payload that sets the DualSense mic-mute LED. + */ + struct control_set_mic_led_t { + control_header_v2 header; ///< Control message header preceding this payload. + + std::uint16_t id; ///< Controller identifier associated with this message. + std::uint8_t state; ///< Mic-LED state (0 = off, 1 = on, 2 = pulse). + }; + /** * @brief Control payload that configures DualSense adaptive triggers. */ @@ -1047,6 +1071,32 @@ namespace stream { plaintext.type_right = msg.data.adaptive_triggers.type_right; std::ranges::copy(msg.data.adaptive_triggers.right, plaintext.right); + std::array + encrypted_payload; + + payload = encode_control(session, util::view(plaintext), encrypted_payload); + } else if (msg.type == platf::gamepad_feedback_e::set_player_led) { + control_set_player_led_t plaintext; + plaintext.header.type = packetTypes[IDX_SET_PLAYER_LED]; + plaintext.header.payloadLength = sizeof(plaintext) - sizeof(control_header_v2); + + plaintext.id = util::endian::little(msg.id); + plaintext.value = msg.data.player_led.value; + + BOOST_LOG(verbose) << "Player LED: "sv << msg.id << " :: "sv << util::hex(msg.data.player_led.value).to_string_view(); + std::array + encrypted_payload; + + payload = encode_control(session, util::view(plaintext), encrypted_payload); + } else if (msg.type == platf::gamepad_feedback_e::set_mic_led) { + control_set_mic_led_t plaintext; + plaintext.header.type = packetTypes[IDX_SET_MIC_LED]; + plaintext.header.payloadLength = sizeof(plaintext) - sizeof(control_header_v2); + + plaintext.id = util::endian::little(msg.id); + plaintext.state = msg.data.mic_led.state; + + BOOST_LOG(verbose) << "Mic LED: "sv << msg.id << " :: "sv << util::hex(msg.data.mic_led.state).to_string_view(); std::array encrypted_payload; diff --git a/third-party/libvirtualhid b/third-party/libvirtualhid index 15a37d34ac6..623b214b87a 160000 --- a/third-party/libvirtualhid +++ b/third-party/libvirtualhid @@ -1 +1 @@ -Subproject commit 15a37d34ac6705f23cee7ad4f42695e8000d68cb +Subproject commit 623b214b87a4c06a5b196ec94e199898d8c28665