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
6 changes: 6 additions & 0 deletions src/core/gamepad_adapter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ namespace lvh {
support.supports_rumble = profile.capabilities.supports_rumble;
support.supports_rgb_led = profile.capabilities.supports_rgb_led;
support.supports_adaptive_triggers = profile.capabilities.supports_adaptive_triggers;
support.supports_player_led = profile.capabilities.supports_player_led;
support.supports_mic_led = profile.capabilities.supports_mic_led;
support.supports_motion = profile.capabilities.supports_motion;
support.supports_touchpad = profile.capabilities.supports_touchpad;
support.supports_battery = profile.capabilities.supports_battery;
Expand Down Expand Up @@ -144,6 +146,10 @@ namespace lvh {
return support.supports_adaptive_triggers;
case raw_report:
return profile.output_report_size > 0U;
case player_led:
return support.supports_player_led;
case mic_led:
return support.supports_mic_led;
}

return false;
Expand Down
2 changes: 2 additions & 0 deletions src/core/profiles.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2012,6 +2012,8 @@ namespace lvh::profiles {
.supports_rgb_led = true,
.supports_battery = true,
.supports_adaptive_triggers = true,
.supports_player_led = true,
.supports_mic_led = true,
};
profile.report_descriptor =
bus_type == BusType::bluetooth ? make_dualsense_bluetooth_report_descriptor() : make_dualsense_usb_report_descriptor();
Expand Down
18 changes: 18 additions & 0 deletions src/core/report.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ namespace lvh::reports {

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

constexpr auto dualsense_flag1_mic_mute_led = std::byte {0x01};
constexpr auto dualsense_flag1_lightbar = std::byte {0x04};
constexpr auto dualsense_flag1_player_indicator = std::byte {0x10};

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

Expand Down Expand Up @@ -936,6 +938,22 @@ namespace lvh::reports {
outputs.push_back(std::move(output));
}

if (has_flag(valid_flag1, dualsense_flag1_player_indicator)) {
GamepadOutput output;
output.kind = GamepadOutputKind::player_led;
output.player_led = raw_report[offset + 43U];
output.raw_report = raw_report;
outputs.push_back(std::move(output));
}

if (has_flag(valid_flag1, dualsense_flag1_mic_mute_led)) {
GamepadOutput output;
output.kind = GamepadOutputKind::mic_led;
output.mic_led = raw_report[offset + 8U];
output.raw_report = raw_report;
outputs.push_back(std::move(output));
}

const auto trigger_flags = valid_flag0 & (dualsense_flag0_left_trigger | dualsense_flag0_right_trigger);
if (trigger_flags != zero_byte) {
GamepadOutput output;
Expand Down
10 changes: 10 additions & 0 deletions src/include/libvirtualhid/gamepad_adapter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,16 @@ namespace lvh {
*/
bool supports_adaptive_triggers = false;

/**
* @brief Whether the profile supports player-indicator LED output.
*/
bool supports_player_led = false;

/**
* @brief Whether the profile supports mic-mute LED output.
*/
bool supports_mic_led = false;

/**
* @brief Whether the profile exposes motion sensor input.
*/
Expand Down
22 changes: 22 additions & 0 deletions src/include/libvirtualhid/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,16 @@ namespace lvh {
* @brief Whether the profile supports adaptive trigger output.
*/
bool supports_adaptive_triggers = false;

/**
* @brief Whether the profile supports player-indicator LED output.
*/
bool supports_player_led = false;

/**
* @brief Whether the profile supports mic-mute LED output.
*/
bool supports_mic_led = false;
};

/**
Expand Down Expand Up @@ -998,6 +1008,8 @@ namespace lvh {
adaptive_triggers, ///< Adaptive trigger output.
raw_report, ///< Raw output report bytes.
trigger_rumble, ///< Independent trigger rumble output.
player_led, ///< Player-indicator LED output.
mic_led, ///< Mic-mute LED output.
};

/**
Expand Down Expand Up @@ -1044,6 +1056,16 @@ namespace lvh {
*/
std::uint8_t blue = 0;

/**
* @brief Player-indicator LED bitmask.
*/
std::uint8_t player_led = 0;

/**
* @brief Mic-mute LED state (0 = off, 1 = on, 2 = pulse).
*/
std::uint8_t mic_led = 0;

/**
* @brief Adaptive trigger event flags from a profile-specific output report.
*/
Expand Down
4 changes: 4 additions & 0 deletions tests/unit/test_gamepad_adapter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ TEST(GamepadAdapterTest, ReportsProfileSupport) {
EXPECT_TRUE(dualsense_support.supports_rumble);
EXPECT_TRUE(dualsense_support.supports_rgb_led);
EXPECT_TRUE(dualsense_support.supports_adaptive_triggers);
EXPECT_TRUE(dualsense_support.supports_player_led);
EXPECT_TRUE(dualsense_support.supports_mic_led);
EXPECT_TRUE(dualsense_support.supports_motion);
EXPECT_TRUE(dualsense_support.supports_touchpad);
EXPECT_TRUE(dualsense_support.supports_battery);
Expand Down Expand Up @@ -96,6 +98,8 @@ TEST(GamepadAdapterTest, ChecksButtonsAndOutputsByProfile) {
EXPECT_FALSE(lvh::supports_gamepad_output(dualshock4, lvh::GamepadOutputKind::trigger_rumble));
EXPECT_TRUE(lvh::supports_gamepad_output(dualshock4, lvh::GamepadOutputKind::raw_report));
EXPECT_TRUE(lvh::supports_gamepad_output(dualsense, lvh::GamepadOutputKind::adaptive_triggers));
EXPECT_TRUE(lvh::supports_gamepad_output(dualsense, lvh::GamepadOutputKind::player_led));
EXPECT_TRUE(lvh::supports_gamepad_output(dualsense, lvh::GamepadOutputKind::mic_led));
EXPECT_TRUE(lvh::supports_gamepad_output(switch_pro, lvh::GamepadOutputKind::rumble));
EXPECT_TRUE(lvh::supports_gamepad_output(switch_pro, lvh::GamepadOutputKind::raw_report));
EXPECT_TRUE(lvh::supports_gamepad_output(generic, lvh::GamepadOutputKind::raw_report));
Expand Down
2 changes: 2 additions & 0 deletions tests/unit/test_profiles.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,8 @@ TEST(ProfileTest, StreamingControllerProfilesArePresent) {
EXPECT_TRUE(dualsense.capabilities.supports_touchpad);
EXPECT_TRUE(dualsense.capabilities.supports_rgb_led);
EXPECT_TRUE(dualsense.capabilities.supports_adaptive_triggers);
EXPECT_TRUE(dualsense.capabilities.supports_player_led);
EXPECT_TRUE(dualsense.capabilities.supports_mic_led);
EXPECT_GT(dualsense.input_report_size, 14U);
EXPECT_GT(dualsense.output_report_size, 5U);
EXPECT_EQ(dualsense.manufacturer, "Sony Interactive Entertainment");
Expand Down
39 changes: 39 additions & 0 deletions tests/unit/test_report.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,45 @@ TEST(ReportTest, ParsesDualSenseBluetoothOutputReportEvents) {
EXPECT_EQ(outputs[2].kind, lvh::GamepadOutputKind::adaptive_triggers);
}

TEST(ReportTest, ParsesDualSensePlayerAndMicLedOutputReportEvents) {
const auto profile = lvh::profiles::dualsense_usb();
std::vector<std::uint8_t> report(profile.output_report_size, 0);
report[0] = 0x02;
report[2] = 0x11; // valid_flag1: player-indicator (0x10) | mic-mute LED (0x01)
report[9] = 0x02; // mic-mute LED state (offset + 8): pulse
report[44] = 0x1F; // player-indicator LEDs (offset + 43): all five lit

const auto outputs = lvh::reports::parse_output_reports(profile, report);

ASSERT_EQ(outputs.size(), 2U);
EXPECT_EQ(outputs[0].kind, lvh::GamepadOutputKind::player_led);
EXPECT_EQ(outputs[0].player_led, 0x1F);
EXPECT_EQ(outputs[1].kind, lvh::GamepadOutputKind::mic_led);
EXPECT_EQ(outputs[1].mic_led, 0x02);
}

TEST(ReportTest, OmitsDualSensePlayerAndMicLedWhenFlagsClear) {
const auto profile = lvh::profiles::dualsense_usb();
std::vector<std::uint8_t> report(profile.output_report_size, 0);
report[0] = 0x02;
report[1] = 0x01; // valid_flag0: rumble only
report[2] = 0x04; // valid_flag1: lightbar only (player/mic flags clear)
report[3] = 0x80;
report[4] = 0x40;
report[9] = 0x02; // mic-mute LED byte populated, but its valid flag is clear
report[44] = 0x1F; // player-indicator byte populated, but its valid flag is clear
report[45] = 0x11;
report[46] = 0x22;
report[47] = 0x33;

const auto outputs = lvh::reports::parse_output_reports(profile, report);

for (const auto &output : outputs) {
EXPECT_NE(output.kind, lvh::GamepadOutputKind::player_led);
EXPECT_NE(output.kind, lvh::GamepadOutputKind::mic_led);
}
}

TEST(ReportTest, ParsesDualShock4OutputReportEvents) {
const auto profile = lvh::profiles::dualshock4_usb();
std::vector<std::uint8_t> report(profile.output_report_size, 0);
Expand Down
26 changes: 25 additions & 1 deletion tests/unit/test_virtualhid_control_model.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ TEST(VirtualHidControlModelTest, NamesKnownAndFallbackEnumValues) {
EXPECT_EQ(control::output_kind_name(lvh::GamepadOutputKind::adaptive_triggers), L"adaptive triggers");
EXPECT_EQ(control::output_kind_name(lvh::GamepadOutputKind::raw_report), L"raw report");
EXPECT_EQ(control::output_kind_name(lvh::GamepadOutputKind::trigger_rumble), L"trigger rumble");
EXPECT_EQ(control::output_kind_name(lvh::GamepadOutputKind::player_led), L"player led");
EXPECT_EQ(control::output_kind_name(lvh::GamepadOutputKind::mic_led), L"mic led");
EXPECT_EQ(control::output_kind_name(static_cast<lvh::GamepadOutputKind>(255)), L"raw report");

EXPECT_EQ(control::battery_state_name(lvh::GamepadBatteryState::unknown), L"unknown");
Expand Down Expand Up @@ -196,12 +198,16 @@ TEST(VirtualHidControlModelTest, SummarizesOutputState) {
state.latest_rgb_led->red = 1;
state.latest_rgb_led->green = 2;
state.latest_rgb_led->blue = 3;
state.latest_player_led = output(player_led);
state.latest_player_led->player_led = 31;
state.latest_mic_led = output(mic_led);
state.latest_mic_led->mic_led = 2;
state.latest_adaptive_triggers = output(adaptive_triggers);
state.latest_adaptive_triggers->adaptive_trigger_flags = 4;

EXPECT_EQ(
control::output_summary(state, dualsense),
L"Output: rumble low=10 high=20 | trigger rumble L=30 R=40 | RGB 1,2,3 | adaptive flags=4"
L"Output: rumble low=10 high=20 | trigger rumble L=30 R=40 | RGB 1,2,3 | player LEDs=31 | mic LED=2 | adaptive flags=4"
);
}

Expand Down Expand Up @@ -249,3 +255,21 @@ TEST(VirtualHidControlModelTest, RecordsOutputsAndMaintainsLatestSummaryFields)
ASSERT_TRUE(state.latest_raw_report.has_value());
EXPECT_EQ(state.latest_raw_report->raw_report, (std::vector<std::uint8_t> {0x12, 0x34}));
}

TEST(VirtualHidControlModelTest, RecordsPlayerAndMicLedOutputs) {
control::OutputState state;
auto next_sequence = std::uint64_t {1};

auto player = output(lvh::GamepadOutputKind::player_led);
player.player_led = 0x1F;
control::record_output(state, player, next_sequence, 8);

auto mic = output(lvh::GamepadOutputKind::mic_led);
mic.mic_led = 2;
control::record_output(state, mic, next_sequence, 8);

ASSERT_TRUE(state.latest_player_led.has_value());
EXPECT_EQ(state.latest_player_led->player_led, 0x1F);
ASSERT_TRUE(state.latest_mic_led.has_value());
EXPECT_EQ(state.latest_mic_led->mic_led, 2);
}
20 changes: 20 additions & 0 deletions tools/virtualhid_control_model.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ namespace lvh::tools::virtualhid_control {
return L"raw report";
case trigger_rumble:
return L"trigger rumble";
case player_led:
return L"player led";
case mic_led:
return L"mic led";
}
return L"raw report";
}
Expand Down Expand Up @@ -211,6 +215,16 @@ namespace lvh::tools::virtualhid_control {
<< static_cast<unsigned>(state.latest_rgb_led->blue);
wrote = true;
}
if (state.latest_player_led) {
append_summary_separator(stream, wrote);
stream << L"player LEDs=" << static_cast<unsigned>(state.latest_player_led->player_led);
wrote = true;
}
if (state.latest_mic_led) {
append_summary_separator(stream, wrote);
stream << L"mic LED=" << static_cast<unsigned>(state.latest_mic_led->mic_led);
wrote = true;
}
if (state.latest_adaptive_triggers) {
append_summary_separator(stream, wrote);
stream << L"adaptive flags=" << static_cast<unsigned>(state.latest_adaptive_triggers->adaptive_trigger_flags);
Expand Down Expand Up @@ -280,6 +294,12 @@ namespace lvh::tools::virtualhid_control {
case rgb_led:
state.latest_rgb_led = output;
break;
case player_led:
state.latest_player_led = output;
break;
case mic_led:
state.latest_mic_led = output;
break;
case adaptive_triggers:
state.latest_adaptive_triggers = output;
break;
Expand Down
2 changes: 2 additions & 0 deletions tools/virtualhid_control_model.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ namespace lvh::tools::virtualhid_control {
std::optional<GamepadOutput> latest_rumble;
std::optional<GamepadOutput> latest_trigger_rumble;
std::optional<GamepadOutput> latest_rgb_led;
std::optional<GamepadOutput> latest_player_led;
std::optional<GamepadOutput> latest_mic_led;
std::optional<GamepadOutput> latest_adaptive_triggers;
std::optional<GamepadOutput> latest_raw_report;
};
Expand Down
Loading