Skip to content

Commit 51a869e

Browse files
committed
feat(input): forward DS5 player/mic-LED from libvirtualhid to the client
Route the new libvirtualhid player_led / mic_led output kinds to the client via two control-stream messages (0x5504 player LED, 0x5505 mic LED), mirroring the existing rgb_led passthrough with per-value dedup. Bumps the libvirtualhid submodule to the commit that surfaces those output kinds. Signed-off-by: Joel Coelho <joelfernandocscoelho@gmail.com>
1 parent e04318f commit 51a869e

4 files changed

Lines changed: 111 additions & 1 deletion

File tree

src/platform/common.h

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ namespace platf {
117117
set_motion_event_state, ///< Set motion event state
118118
set_rgb_led, ///< Set RGB LED
119119
set_adaptive_triggers, ///< Set adaptive triggers
120+
set_player_led, ///< Set player-indicator LEDs (DualSense)
121+
set_mic_led, ///< Set mic-mute LED (DualSense)
120122
};
121123

122124
/**
@@ -208,6 +210,36 @@ namespace platf {
208210
return msg;
209211
}
210212

213+
/**
214+
* @brief Create a player-LED (player-indicator) feedback command (DualSense).
215+
*
216+
* @param id Identifier for the controller.
217+
* @param ledValue Raw 5-bit player-indicator bitmask (forwarded verbatim from the game's report).
218+
* @return Constructed player-LED object.
219+
*/
220+
static gamepad_feedback_msg_t make_player_led(std::uint16_t id, std::uint8_t ledValue) {
221+
gamepad_feedback_msg_t msg;
222+
msg.type = gamepad_feedback_e::set_player_led;
223+
msg.id = id;
224+
msg.data.player_led = {ledValue};
225+
return msg;
226+
}
227+
228+
/**
229+
* @brief Create a mic-mute-LED feedback command (DualSense).
230+
*
231+
* @param id Identifier for the controller.
232+
* @param ledState Mic-LED state (0 = off, 1 = on, 2 = pulse).
233+
* @return Constructed mic-LED object.
234+
*/
235+
static gamepad_feedback_msg_t make_mic_led(std::uint16_t id, std::uint8_t ledState) {
236+
gamepad_feedback_msg_t msg;
237+
msg.type = gamepad_feedback_e::set_mic_led;
238+
msg.id = id;
239+
msg.data.mic_led = {ledState};
240+
return msg;
241+
}
242+
211243
gamepad_feedback_e type; ///< Feedback command type stored in the union payload.
212244
std::uint16_t id; ///< Controller identifier associated with this message.
213245

@@ -233,6 +265,14 @@ namespace platf {
233265
std::uint8_t b;
234266
} rgb_led;
235267

268+
struct {
269+
std::uint8_t value; ///< Raw 5-bit player-indicator bitmask.
270+
} player_led;
271+
272+
struct {
273+
std::uint8_t state; ///< 0 = off, 1 = on, 2 = pulse.
274+
} mic_led;
275+
236276
struct {
237277
uint16_t controllerNumber;
238278
uint8_t event_flags;

src/platform/virtualhid_input.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ namespace platf::virtualhid {
4545
std::uint8_t last_red = 0; ///< Last red LED value.
4646
std::uint8_t last_green = 0; ///< Last green LED value.
4747
std::uint8_t last_blue = 0; ///< Last blue LED value.
48+
bool has_last_player_led = false; ///< Whether the last player-LED value is valid.
49+
std::uint8_t last_player_led = 0; ///< Last player-indicator LED bitmask.
50+
bool has_last_mic_led = false; ///< Whether the last mic-LED state is valid.
51+
std::uint8_t last_mic_led = 0; ///< Last mic-mute LED state.
4852
};
4953

5054
namespace {
@@ -390,6 +394,22 @@ namespace platf::virtualhid {
390394
gamepad->last_blue = output.blue;
391395
raise_feedback_unlocked(gamepad, gamepad_feedback_msg_t::make_rgb_led(gamepad->client_relative_index, output.red, output.green, output.blue));
392396
break;
397+
case lvh::GamepadOutputKind::player_led:
398+
if (gamepad->has_last_player_led && gamepad->last_player_led == output.player_led) {
399+
return;
400+
}
401+
gamepad->has_last_player_led = true;
402+
gamepad->last_player_led = output.player_led;
403+
raise_feedback_unlocked(gamepad, gamepad_feedback_msg_t::make_player_led(gamepad->client_relative_index, output.player_led));
404+
break;
405+
case lvh::GamepadOutputKind::mic_led:
406+
if (gamepad->has_last_mic_led && gamepad->last_mic_led == output.mic_led) {
407+
return;
408+
}
409+
gamepad->has_last_mic_led = true;
410+
gamepad->last_mic_led = output.mic_led;
411+
raise_feedback_unlocked(gamepad, gamepad_feedback_msg_t::make_mic_led(gamepad->client_relative_index, output.mic_led));
412+
break;
393413
case lvh::GamepadOutputKind::adaptive_triggers:
394414
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));
395415
break;

src/stream.cpp

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ constexpr int IDX_RUMBLE_TRIGGER_DATA = 12; ///< Control-stream message index f
4949
constexpr int IDX_SET_MOTION_EVENT = 13; ///< Control-stream message index for set motion event.
5050
constexpr int IDX_SET_RGB_LED = 14; ///< Control-stream message index for set rgb led.
5151
constexpr int IDX_SET_ADAPTIVE_TRIGGERS = 15; ///< Control-stream message index for set adaptive triggers.
52+
constexpr int IDX_SET_PLAYER_LED = 16; ///< Control-stream message index for set player LED (DualSense).
53+
constexpr int IDX_SET_MIC_LED = 17; ///< Control-stream message index for set mic LED (DualSense).
5254

5355
static const short packetTypes[] = {
5456
0x0305, // Start A
@@ -67,6 +69,8 @@ static const short packetTypes[] = {
6769
0x5501, // Set motion event (Sunshine protocol extension)
6870
0x5502, // Set RGB LED (Sunshine protocol extension)
6971
0x5503, // Set Adaptive triggers (Sunshine protocol extension)
72+
0x5504, // Set Player LED (Sunshine protocol extension, DualSense)
73+
0x5505, // Set Mic LED (Sunshine protocol extension, DualSense)
7074
};
7175

7276
namespace asio = boost::asio;
@@ -239,6 +243,26 @@ namespace stream {
239243
std::uint8_t b; ///< Blue LED channel.
240244
};
241245

246+
/**
247+
* @brief Control payload that sets the DualSense player-indicator LEDs.
248+
*/
249+
struct control_set_player_led_t {
250+
control_header_v2 header; ///< Control message header preceding this payload.
251+
252+
std::uint16_t id; ///< Controller identifier associated with this message.
253+
std::uint8_t value; ///< Raw 5-bit player-indicator bitmask.
254+
};
255+
256+
/**
257+
* @brief Control payload that sets the DualSense mic-mute LED.
258+
*/
259+
struct control_set_mic_led_t {
260+
control_header_v2 header; ///< Control message header preceding this payload.
261+
262+
std::uint16_t id; ///< Controller identifier associated with this message.
263+
std::uint8_t state; ///< Mic-LED state (0 = off, 1 = on, 2 = pulse).
264+
};
265+
242266
/**
243267
* @brief Control payload that configures DualSense adaptive triggers.
244268
*/
@@ -1047,6 +1071,32 @@ namespace stream {
10471071
plaintext.type_right = msg.data.adaptive_triggers.type_right;
10481072
std::ranges::copy(msg.data.adaptive_triggers.right, plaintext.right);
10491073

1074+
std::array<std::uint8_t, sizeof(control_encrypted_t) + crypto::cipher::round_to_pkcs7_padded(sizeof(plaintext)) + crypto::cipher::tag_size>
1075+
encrypted_payload;
1076+
1077+
payload = encode_control(session, util::view(plaintext), encrypted_payload);
1078+
} else if (msg.type == platf::gamepad_feedback_e::set_player_led) {
1079+
control_set_player_led_t plaintext;
1080+
plaintext.header.type = packetTypes[IDX_SET_PLAYER_LED];
1081+
plaintext.header.payloadLength = sizeof(plaintext) - sizeof(control_header_v2);
1082+
1083+
plaintext.id = util::endian::little(msg.id);
1084+
plaintext.value = msg.data.player_led.value;
1085+
1086+
BOOST_LOG(verbose) << "Player LED: "sv << msg.id << " :: "sv << util::hex(msg.data.player_led.value).to_string_view();
1087+
std::array<std::uint8_t, sizeof(control_encrypted_t) + crypto::cipher::round_to_pkcs7_padded(sizeof(plaintext)) + crypto::cipher::tag_size>
1088+
encrypted_payload;
1089+
1090+
payload = encode_control(session, util::view(plaintext), encrypted_payload);
1091+
} else if (msg.type == platf::gamepad_feedback_e::set_mic_led) {
1092+
control_set_mic_led_t plaintext;
1093+
plaintext.header.type = packetTypes[IDX_SET_MIC_LED];
1094+
plaintext.header.payloadLength = sizeof(plaintext) - sizeof(control_header_v2);
1095+
1096+
plaintext.id = util::endian::little(msg.id);
1097+
plaintext.state = msg.data.mic_led.state;
1098+
1099+
BOOST_LOG(verbose) << "Mic LED: "sv << msg.id << " :: "sv << util::hex(msg.data.mic_led.state).to_string_view();
10501100
std::array<std::uint8_t, sizeof(control_encrypted_t) + crypto::cipher::round_to_pkcs7_padded(sizeof(plaintext)) + crypto::cipher::tag_size>
10511101
encrypted_payload;
10521102

0 commit comments

Comments
 (0)