@@ -75,6 +75,30 @@ namespace lvh::reports {
7575
7676 constexpr std::size_t switch_rumble_output_report_size = 10 ;
7777
78+ constexpr std::uint8_t switch_set_player_lights_subcommand = 0x30 ;
79+
80+ constexpr std::uint8_t switch_set_home_light_subcommand = 0x38 ;
81+
82+ constexpr float switch_acceleration_scale = 4096 .0F / 9 .80665F ;
83+
84+ constexpr float switch_gyroscope_scale = 14 .2842F ;
85+
86+ std::uint8_t decode_switch_home_light_intensity (std::byte encoded_intensity) {
87+ const auto intensity = std::to_integer<std::uint8_t >(encoded_intensity >> 4U );
88+ if (intensity == 0U ) {
89+ return 0U ;
90+ }
91+ if (intensity <= 6U ) {
92+ return static_cast <std::uint8_t >(std::lround (static_cast <float >(intensity) * 25 .5F ));
93+ }
94+ if (intensity == 15U ) {
95+ return 255U ;
96+ }
97+
98+ const auto normalized = (static_cast <float >(intensity) - 0 .5F ) / 15 .0F ;
99+ return static_cast <std::uint8_t >(std::lround (std::pow (normalized, 1 .0F / 2 .13F ) * 255 .0F ));
100+ }
101+
78102 // SDL maps 16-bit rumble strengths to Nintendo's shared 101-step amplitude
79103 // scale. This is the inverse table for the packed high- and low-band values:
80104 // https://github.com/libsdl-org/SDL/blob/main/src/joystick/hidapi/SDL_hidapi_switch.c
@@ -950,6 +974,47 @@ namespace lvh::reports {
950974 }
951975 }
952976
977+ void append_switch_pro_outputs (
978+ const std::vector<std::uint8_t > &report,
979+ std::vector<GamepadOutput> &outputs
980+ ) {
981+ if (const auto rumble = decode_switch_rumble_report (report); rumble.has_value ()) {
982+ GamepadOutput output;
983+ output.kind = GamepadOutputKind::rumble;
984+ output.low_frequency_rumble = rumble->low_frequency ;
985+ output.high_frequency_rumble = rumble->high_frequency ;
986+ output.raw_report = report;
987+ outputs.push_back (std::move (output));
988+ }
989+ if (
990+ report.size () >= 12U && report[0 ] == switch_rumble_and_subcommand_output_report_id &&
991+ report[10 ] == switch_set_player_lights_subcommand
992+ ) {
993+ GamepadOutput output;
994+ output.kind = GamepadOutputKind::player_leds;
995+ const auto player_lights = std::byte {report[11 ]};
996+ for (std::size_t index = 0 ; index < output.player_leds .size (); ++index) {
997+ output.player_leds [index] = (player_lights & (std::byte {1 } << index)) != zero_byte;
998+ output.flashing_player_leds [index] =
999+ (player_lights & (std::byte {1 } << (index + 4U ))) != zero_byte;
1000+ }
1001+ output.raw_report = report;
1002+ outputs.push_back (std::move (output));
1003+ }
1004+ if (
1005+ report.size () >= 15U && report[0 ] == switch_rumble_and_subcommand_output_report_id &&
1006+ report[10 ] == switch_set_home_light_subcommand
1007+ ) {
1008+ GamepadOutput output;
1009+ output.kind = GamepadOutputKind::rgb_led;
1010+ output.red = decode_switch_home_light_intensity (std::byte {report[12 ]});
1011+ output.green = output.red ;
1012+ output.blue = output.red ;
1013+ output.raw_report = report;
1014+ outputs.push_back (std::move (output));
1015+ }
1016+ }
1017+
9531018 } // namespace
9541019
9551020 float clamp_axis (float value) {
@@ -1061,6 +1126,22 @@ namespace lvh::reports {
10611126 report[offset + 2U ] = to_byte ((y >> 4U ) & 0xFFU );
10621127 }
10631128
1129+ void write_switch_imu_sample (
1130+ ByteReport &report,
1131+ std::size_t offset,
1132+ const Vector3 &acceleration,
1133+ const Vector3 &gyroscope
1134+ ) {
1135+ // Nintendo's native coordinate system differs from the portable
1136+ // PlayStation-style coordinate system exposed by GamepadState.
1137+ write_i16 (report, offset, scale_i16 (-acceleration.z , switch_acceleration_scale));
1138+ write_i16 (report, offset + 2U , scale_i16 (-acceleration.x , switch_acceleration_scale));
1139+ write_i16 (report, offset + 4U , scale_i16 (acceleration.y , switch_acceleration_scale));
1140+ write_i16 (report, offset + 6U , scale_i16 (-gyroscope.z , switch_gyroscope_scale));
1141+ write_i16 (report, offset + 8U , scale_i16 (-gyroscope.x , switch_gyroscope_scale));
1142+ write_i16 (report, offset + 10U , scale_i16 (gyroscope.y , switch_gyroscope_scale));
1143+ }
1144+
10641145 std::byte switch_battery_and_connection (const std::optional<GamepadBattery> &battery) {
10651146 constexpr auto usb_connection = std::byte {0x01 };
10661147 if (!battery.has_value ()) {
@@ -1222,6 +1303,11 @@ namespace lvh::reports {
12221303 normalize_switch_stick_axis (normalized.right_stick .x ),
12231304 normalize_switch_stick_axis (normalized.right_stick .y )
12241305 );
1306+ const auto acceleration = normalized.acceleration .value_or (Vector3 {.y = 9 .80665F });
1307+ const auto gyroscope = normalized.gyroscope .value_or (Vector3 {});
1308+ for (const auto offset : {13U , 25U , 37U }) {
1309+ write_switch_imu_sample (report, offset, acceleration, gyroscope);
1310+ }
12251311 return to_uint8_report (report);
12261312 }
12271313
@@ -1302,13 +1388,8 @@ namespace lvh::reports {
13021388 }
13031389
13041390 if (profile.gamepad_kind == GamepadProfileKind::switch_pro) {
1305- if (const auto rumble = decode_switch_rumble_report (report); rumble.has_value ()) {
1306- GamepadOutput output;
1307- output.kind = GamepadOutputKind::rumble;
1308- output.low_frequency_rumble = rumble->low_frequency ;
1309- output.high_frequency_rumble = rumble->high_frequency ;
1310- output.raw_report = report;
1311- outputs.push_back (std::move (output));
1391+ append_switch_pro_outputs (report, outputs);
1392+ if (!outputs.empty ()) {
13121393 return outputs;
13131394 }
13141395 }
0 commit comments