Skip to content

Commit b8e56dc

Browse files
joelfcscoelhoReenigneArcher
authored andcommitted
feat: expose DS5 player-indicator and mic-mute LED outputs
Add player_led and mic_led to GamepadOutputKind and GamepadOutput, and parse them from the DualSense output report in append_dualsense_outputs (player indicator gated by valid_flag1 bit 0x10 at offset+43; mic-mute LED gated by bit 0x01 at offset+8), mirroring the existing rgb_led handling. The DS5 profile advertises both via new supports_player_led / supports_mic_led capability flags. Signed-off-by: Joel Coelho <joelfernandocscoelho@gmail.com>
1 parent 15a37d3 commit b8e56dc

10 files changed

Lines changed: 109 additions & 0 deletions

src/core/gamepad_adapter.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ namespace lvh {
8383
support.supports_rumble = profile.capabilities.supports_rumble;
8484
support.supports_rgb_led = profile.capabilities.supports_rgb_led;
8585
support.supports_adaptive_triggers = profile.capabilities.supports_adaptive_triggers;
86+
support.supports_player_led = profile.capabilities.supports_player_led;
87+
support.supports_mic_led = profile.capabilities.supports_mic_led;
8688
support.supports_motion = profile.capabilities.supports_motion;
8789
support.supports_touchpad = profile.capabilities.supports_touchpad;
8890
support.supports_battery = profile.capabilities.supports_battery;
@@ -144,6 +146,10 @@ namespace lvh {
144146
return support.supports_adaptive_triggers;
145147
case raw_report:
146148
return profile.output_report_size > 0U;
149+
case player_led:
150+
return support.supports_player_led;
151+
case mic_led:
152+
return support.supports_mic_led;
147153
}
148154

149155
return false;

src/core/profiles.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2012,6 +2012,8 @@ namespace lvh::profiles {
20122012
.supports_rgb_led = true,
20132013
.supports_battery = true,
20142014
.supports_adaptive_triggers = true,
2015+
.supports_player_led = true,
2016+
.supports_mic_led = true,
20152017
};
20162018
profile.report_descriptor =
20172019
bus_type == BusType::bluetooth ? make_dualsense_bluetooth_report_descriptor() : make_dualsense_usb_report_descriptor();

src/core/report.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ namespace lvh::reports {
6161

6262
constexpr auto dualsense_flag0_left_trigger = std::byte {0x08};
6363

64+
constexpr auto dualsense_flag1_mic_mute_led = std::byte {0x01};
6465
constexpr auto dualsense_flag1_lightbar = std::byte {0x04};
66+
constexpr auto dualsense_flag1_player_indicator = std::byte {0x10};
6567

6668
constexpr auto dualsense_flag2_compatible_vibration = std::byte {0x04};
6769

@@ -936,6 +938,22 @@ namespace lvh::reports {
936938
outputs.push_back(std::move(output));
937939
}
938940

941+
if (has_flag(valid_flag1, dualsense_flag1_player_indicator)) {
942+
GamepadOutput output;
943+
output.kind = GamepadOutputKind::player_led;
944+
output.player_led = raw_report[offset + 43U];
945+
output.raw_report = raw_report;
946+
outputs.push_back(std::move(output));
947+
}
948+
949+
if (has_flag(valid_flag1, dualsense_flag1_mic_mute_led)) {
950+
GamepadOutput output;
951+
output.kind = GamepadOutputKind::mic_led;
952+
output.mic_led = raw_report[offset + 8U];
953+
output.raw_report = raw_report;
954+
outputs.push_back(std::move(output));
955+
}
956+
939957
const auto trigger_flags = valid_flag0 & (dualsense_flag0_left_trigger | dualsense_flag0_right_trigger);
940958
if (trigger_flags != zero_byte) {
941959
GamepadOutput output;

src/include/libvirtualhid/gamepad_adapter.hpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,16 @@ namespace lvh {
4040
*/
4141
bool supports_adaptive_triggers = false;
4242

43+
/**
44+
* @brief Whether the profile supports player-indicator LED output.
45+
*/
46+
bool supports_player_led = false;
47+
48+
/**
49+
* @brief Whether the profile supports mic-mute LED output.
50+
*/
51+
bool supports_mic_led = false;
52+
4353
/**
4454
* @brief Whether the profile exposes motion sensor input.
4555
*/

src/include/libvirtualhid/types.hpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,16 @@ namespace lvh {
270270
* @brief Whether the profile supports adaptive trigger output.
271271
*/
272272
bool supports_adaptive_triggers = false;
273+
274+
/**
275+
* @brief Whether the profile supports player-indicator LED output.
276+
*/
277+
bool supports_player_led = false;
278+
279+
/**
280+
* @brief Whether the profile supports mic-mute LED output.
281+
*/
282+
bool supports_mic_led = false;
273283
};
274284

275285
/**
@@ -998,6 +1008,8 @@ namespace lvh {
9981008
adaptive_triggers, ///< Adaptive trigger output.
9991009
raw_report, ///< Raw output report bytes.
10001010
trigger_rumble, ///< Independent trigger rumble output.
1011+
player_led, ///< Player-indicator LED output.
1012+
mic_led, ///< Mic-mute LED output.
10011013
};
10021014

10031015
/**
@@ -1044,6 +1056,16 @@ namespace lvh {
10441056
*/
10451057
std::uint8_t blue = 0;
10461058

1059+
/**
1060+
* @brief Player-indicator LED bitmask.
1061+
*/
1062+
std::uint8_t player_led = 0;
1063+
1064+
/**
1065+
* @brief Mic-mute LED state (0 = off, 1 = on, 2 = pulse).
1066+
*/
1067+
std::uint8_t mic_led = 0;
1068+
10471069
/**
10481070
* @brief Adaptive trigger event flags from a profile-specific output report.
10491071
*/

tests/unit/test_gamepad_adapter.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ TEST(GamepadAdapterTest, ReportsProfileSupport) {
4444
EXPECT_TRUE(dualsense_support.supports_rumble);
4545
EXPECT_TRUE(dualsense_support.supports_rgb_led);
4646
EXPECT_TRUE(dualsense_support.supports_adaptive_triggers);
47+
EXPECT_TRUE(dualsense_support.supports_player_led);
48+
EXPECT_TRUE(dualsense_support.supports_mic_led);
4749
EXPECT_TRUE(dualsense_support.supports_motion);
4850
EXPECT_TRUE(dualsense_support.supports_touchpad);
4951
EXPECT_TRUE(dualsense_support.supports_battery);
@@ -96,6 +98,8 @@ TEST(GamepadAdapterTest, ChecksButtonsAndOutputsByProfile) {
9698
EXPECT_FALSE(lvh::supports_gamepad_output(dualshock4, lvh::GamepadOutputKind::trigger_rumble));
9799
EXPECT_TRUE(lvh::supports_gamepad_output(dualshock4, lvh::GamepadOutputKind::raw_report));
98100
EXPECT_TRUE(lvh::supports_gamepad_output(dualsense, lvh::GamepadOutputKind::adaptive_triggers));
101+
EXPECT_TRUE(lvh::supports_gamepad_output(dualsense, lvh::GamepadOutputKind::player_led));
102+
EXPECT_TRUE(lvh::supports_gamepad_output(dualsense, lvh::GamepadOutputKind::mic_led));
99103
EXPECT_TRUE(lvh::supports_gamepad_output(switch_pro, lvh::GamepadOutputKind::rumble));
100104
EXPECT_TRUE(lvh::supports_gamepad_output(switch_pro, lvh::GamepadOutputKind::raw_report));
101105
EXPECT_TRUE(lvh::supports_gamepad_output(generic, lvh::GamepadOutputKind::raw_report));

tests/unit/test_profiles.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,8 @@ TEST(ProfileTest, StreamingControllerProfilesArePresent) {
213213
EXPECT_TRUE(dualsense.capabilities.supports_touchpad);
214214
EXPECT_TRUE(dualsense.capabilities.supports_rgb_led);
215215
EXPECT_TRUE(dualsense.capabilities.supports_adaptive_triggers);
216+
EXPECT_TRUE(dualsense.capabilities.supports_player_led);
217+
EXPECT_TRUE(dualsense.capabilities.supports_mic_led);
216218
EXPECT_GT(dualsense.input_report_size, 14U);
217219
EXPECT_GT(dualsense.output_report_size, 5U);
218220
EXPECT_EQ(dualsense.manufacturer, "Sony Interactive Entertainment");

tests/unit/test_report.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,45 @@ TEST(ReportTest, ParsesDualSenseBluetoothOutputReportEvents) {
525525
EXPECT_EQ(outputs[2].kind, lvh::GamepadOutputKind::adaptive_triggers);
526526
}
527527

528+
TEST(ReportTest, ParsesDualSensePlayerAndMicLedOutputReportEvents) {
529+
const auto profile = lvh::profiles::dualsense_usb();
530+
std::vector<std::uint8_t> report(profile.output_report_size, 0);
531+
report[0] = 0x02;
532+
report[2] = 0x11; // valid_flag1: player-indicator (0x10) | mic-mute LED (0x01)
533+
report[9] = 0x02; // mic-mute LED state (offset + 8): pulse
534+
report[44] = 0x1F; // player-indicator LEDs (offset + 43): all five lit
535+
536+
const auto outputs = lvh::reports::parse_output_reports(profile, report);
537+
538+
ASSERT_EQ(outputs.size(), 2U);
539+
EXPECT_EQ(outputs[0].kind, lvh::GamepadOutputKind::player_led);
540+
EXPECT_EQ(outputs[0].player_led, 0x1F);
541+
EXPECT_EQ(outputs[1].kind, lvh::GamepadOutputKind::mic_led);
542+
EXPECT_EQ(outputs[1].mic_led, 0x02);
543+
}
544+
545+
TEST(ReportTest, OmitsDualSensePlayerAndMicLedWhenFlagsClear) {
546+
const auto profile = lvh::profiles::dualsense_usb();
547+
std::vector<std::uint8_t> report(profile.output_report_size, 0);
548+
report[0] = 0x02;
549+
report[1] = 0x01; // valid_flag0: rumble only
550+
report[2] = 0x04; // valid_flag1: lightbar only (player/mic flags clear)
551+
report[3] = 0x80;
552+
report[4] = 0x40;
553+
report[9] = 0x02; // mic-mute LED byte populated, but its valid flag is clear
554+
report[44] = 0x1F; // player-indicator byte populated, but its valid flag is clear
555+
report[45] = 0x11;
556+
report[46] = 0x22;
557+
report[47] = 0x33;
558+
559+
const auto outputs = lvh::reports::parse_output_reports(profile, report);
560+
561+
for (const auto &output : outputs) {
562+
EXPECT_NE(output.kind, lvh::GamepadOutputKind::player_led);
563+
EXPECT_NE(output.kind, lvh::GamepadOutputKind::mic_led);
564+
}
565+
}
566+
528567
TEST(ReportTest, ParsesDualShock4OutputReportEvents) {
529568
const auto profile = lvh::profiles::dualshock4_usb();
530569
std::vector<std::uint8_t> report(profile.output_report_size, 0);

tests/unit/test_virtualhid_control_model.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ TEST(VirtualHidControlModelTest, NamesKnownAndFallbackEnumValues) {
5858
EXPECT_EQ(control::output_kind_name(lvh::GamepadOutputKind::adaptive_triggers), L"adaptive triggers");
5959
EXPECT_EQ(control::output_kind_name(lvh::GamepadOutputKind::raw_report), L"raw report");
6060
EXPECT_EQ(control::output_kind_name(lvh::GamepadOutputKind::trigger_rumble), L"trigger rumble");
61+
EXPECT_EQ(control::output_kind_name(lvh::GamepadOutputKind::player_led), L"player led");
62+
EXPECT_EQ(control::output_kind_name(lvh::GamepadOutputKind::mic_led), L"mic led");
6163
EXPECT_EQ(control::output_kind_name(static_cast<lvh::GamepadOutputKind>(255)), L"raw report");
6264

6365
EXPECT_EQ(control::battery_state_name(lvh::GamepadBatteryState::unknown), L"unknown");

tools/virtualhid_control_model.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ namespace lvh::tools::virtualhid_control {
7777
return L"raw report";
7878
case trigger_rumble:
7979
return L"trigger rumble";
80+
case player_led:
81+
return L"player led";
82+
case mic_led:
83+
return L"mic led";
8084
}
8185
return L"raw report";
8286
}

0 commit comments

Comments
 (0)