summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar liamwhite2023-01-24 09:29:37 -0500
committerGravatar GitHub2023-01-24 09:29:37 -0500
commita68af583ea378b48e2ed5a19f519a815ba89e40f (patch)
tree2983c14a7d4bc2797259c7d97462a439bec629f3
parentMerge pull request #9555 from abouvier/catch2-update (diff)
parentcore: hid: Make use of SCOPE_EXIT and SCOPE_GUARD where applicable (diff)
downloadyuzu-a68af583ea378b48e2ed5a19f519a815ba89e40f.tar.gz
yuzu-a68af583ea378b48e2ed5a19f519a815ba89e40f.tar.xz
yuzu-a68af583ea378b48e2ed5a19f519a815ba89e40f.zip
Merge pull request #9492 from german77/joycon_release
Input_common: Implement custom joycon driver v2
Diffstat (limited to '')
-rw-r--r--src/common/input.h68
-rw-r--r--src/common/settings.h1
-rw-r--r--src/core/hid/emulated_controller.cpp298
-rw-r--r--src/core/hid/emulated_controller.h64
-rw-r--r--src/core/hid/emulated_devices.cpp46
-rw-r--r--src/core/hid/emulated_devices.h18
-rw-r--r--src/core/hid/input_converter.cpp12
-rw-r--r--src/core/hid/input_converter.h10
-rw-r--r--src/core/hle/service/hid/controllers/npad.cpp18
-rw-r--r--src/core/hle/service/hid/hidbus.cpp24
-rw-r--r--src/core/hle/service/hid/hidbus/ringcon.cpp8
-rw-r--r--src/core/hle/service/hid/hidbus/ringcon.h4
-rw-r--r--src/core/hle/service/hid/irs.cpp18
-rw-r--r--src/core/hle/service/nfc/nfc_device.cpp7
-rw-r--r--src/core/hle/service/nfp/nfp_device.cpp7
-rw-r--r--src/input_common/CMakeLists.txt21
-rw-r--r--src/input_common/drivers/camera.cpp4
-rw-r--r--src/input_common/drivers/camera.h4
-rw-r--r--src/input_common/drivers/gc_adapter.cpp6
-rw-r--r--src/input_common/drivers/gc_adapter.h2
-rw-r--r--src/input_common/drivers/joycon.cpp677
-rw-r--r--src/input_common/drivers/joycon.h111
-rw-r--r--src/input_common/drivers/sdl_driver.cpp23
-rw-r--r--src/input_common/drivers/sdl_driver.h2
-rw-r--r--src/input_common/drivers/virtual_amiibo.cpp11
-rw-r--r--src/input_common/drivers/virtual_amiibo.h2
-rw-r--r--src/input_common/helpers/joycon_driver.cpp572
-rw-r--r--src/input_common/helpers/joycon_driver.h150
-rw-r--r--src/input_common/helpers/joycon_protocol/calibration.cpp184
-rw-r--r--src/input_common/helpers/joycon_protocol/calibration.h64
-rw-r--r--src/input_common/helpers/joycon_protocol/common_protocol.cpp299
-rw-r--r--src/input_common/helpers/joycon_protocol/common_protocol.h173
-rw-r--r--src/input_common/helpers/joycon_protocol/generic_functions.cpp125
-rw-r--r--src/input_common/helpers/joycon_protocol/generic_functions.h108
-rw-r--r--src/input_common/helpers/joycon_protocol/irs.cpp298
-rw-r--r--src/input_common/helpers/joycon_protocol/irs.h63
-rw-r--r--src/input_common/helpers/joycon_protocol/joycon_types.h612
-rw-r--r--src/input_common/helpers/joycon_protocol/nfc.cpp400
-rw-r--r--src/input_common/helpers/joycon_protocol/nfc.h61
-rw-r--r--src/input_common/helpers/joycon_protocol/poller.cpp341
-rw-r--r--src/input_common/helpers/joycon_protocol/poller.h81
-rw-r--r--src/input_common/helpers/joycon_protocol/ringcon.cpp117
-rw-r--r--src/input_common/helpers/joycon_protocol/ringcon.h38
-rw-r--r--src/input_common/helpers/joycon_protocol/rumble.cpp299
-rw-r--r--src/input_common/helpers/joycon_protocol/rumble.h33
-rw-r--r--src/input_common/input_engine.cpp37
-rw-r--r--src/input_common/input_engine.h25
-rw-r--r--src/input_common/input_poller.cpp78
-rw-r--r--src/input_common/input_poller.h11
-rw-r--r--src/input_common/main.cpp14
-rw-r--r--src/yuzu/configuration/config.cpp2
-rw-r--r--src/yuzu/configuration/configure_input_advanced.cpp2
-rw-r--r--src/yuzu/configuration/configure_input_advanced.ui22
-rw-r--r--src/yuzu/configuration/configure_input_player.cpp20
-rw-r--r--src/yuzu/configuration/configure_input_player_widget.cpp10
-rw-r--r--src/yuzu/configuration/configure_ringcon.cpp105
-rw-r--r--src/yuzu/configuration/configure_ringcon.h14
-rw-r--r--src/yuzu/configuration/configure_ringcon.ui396
58 files changed, 5812 insertions, 408 deletions
diff --git a/src/common/input.h b/src/common/input.h
index d27b1d772..d61cd7ca8 100644
--- a/src/common/input.h
+++ b/src/common/input.h
@@ -51,6 +51,8 @@ enum class PollingMode {
51 NFC, 51 NFC,
52 // Enable infrared camera polling 52 // Enable infrared camera polling
53 IR, 53 IR,
54 // Enable ring controller polling
55 Ring,
54}; 56};
55 57
56enum class CameraFormat { 58enum class CameraFormat {
@@ -62,21 +64,22 @@ enum class CameraFormat {
62 None, 64 None,
63}; 65};
64 66
65// Vibration reply from the controller 67// Different results that can happen from a device request
66enum class VibrationError { 68enum class DriverResult {
67 None, 69 Success,
70 WrongReply,
71 Timeout,
72 UnsupportedControllerType,
73 HandleInUse,
74 ErrorReadingData,
75 ErrorWritingData,
76 NoDeviceDetected,
77 InvalidHandle,
68 NotSupported, 78 NotSupported,
69 Disabled, 79 Disabled,
70 Unknown, 80 Unknown,
71}; 81};
72 82
73// Polling mode reply from the controller
74enum class PollingError {
75 None,
76 NotSupported,
77 Unknown,
78};
79
80// Nfc reply from the controller 83// Nfc reply from the controller
81enum class NfcState { 84enum class NfcState {
82 Success, 85 Success,
@@ -90,13 +93,6 @@ enum class NfcState {
90 Unknown, 93 Unknown,
91}; 94};
92 95
93// Ir camera reply from the controller
94enum class CameraError {
95 None,
96 NotSupported,
97 Unknown,
98};
99
100// Hint for amplification curve to be used 96// Hint for amplification curve to be used
101enum class VibrationAmplificationType { 97enum class VibrationAmplificationType {
102 Linear, 98 Linear,
@@ -190,6 +186,8 @@ struct TouchStatus {
190struct BodyColorStatus { 186struct BodyColorStatus {
191 u32 body{}; 187 u32 body{};
192 u32 buttons{}; 188 u32 buttons{};
189 u32 left_grip{};
190 u32 right_grip{};
193}; 191};
194 192
195// HD rumble data 193// HD rumble data
@@ -228,17 +226,31 @@ enum class ButtonNames {
228 Engine, 226 Engine,
229 // This will display the button by value instead of the button name 227 // This will display the button by value instead of the button name
230 Value, 228 Value,
229
230 // Joycon button names
231 ButtonLeft, 231 ButtonLeft,
232 ButtonRight, 232 ButtonRight,
233 ButtonDown, 233 ButtonDown,
234 ButtonUp, 234 ButtonUp,
235 TriggerZ,
236 TriggerR,
237 TriggerL,
238 ButtonA, 235 ButtonA,
239 ButtonB, 236 ButtonB,
240 ButtonX, 237 ButtonX,
241 ButtonY, 238 ButtonY,
239 ButtonPlus,
240 ButtonMinus,
241 ButtonHome,
242 ButtonCapture,
243 ButtonStickL,
244 ButtonStickR,
245 TriggerL,
246 TriggerZL,
247 TriggerSL,
248 TriggerR,
249 TriggerZR,
250 TriggerSR,
251
252 // GC button names
253 TriggerZ,
242 ButtonStart, 254 ButtonStart,
243 255
244 // DS4 button names 256 // DS4 button names
@@ -316,22 +328,24 @@ class OutputDevice {
316public: 328public:
317 virtual ~OutputDevice() = default; 329 virtual ~OutputDevice() = default;
318 330
319 virtual void SetLED([[maybe_unused]] const LedStatus& led_status) {} 331 virtual DriverResult SetLED([[maybe_unused]] const LedStatus& led_status) {
332 return DriverResult::NotSupported;
333 }
320 334
321 virtual VibrationError SetVibration([[maybe_unused]] const VibrationStatus& vibration_status) { 335 virtual DriverResult SetVibration([[maybe_unused]] const VibrationStatus& vibration_status) {
322 return VibrationError::NotSupported; 336 return DriverResult::NotSupported;
323 } 337 }
324 338
325 virtual bool IsVibrationEnabled() { 339 virtual bool IsVibrationEnabled() {
326 return false; 340 return false;
327 } 341 }
328 342
329 virtual PollingError SetPollingMode([[maybe_unused]] PollingMode polling_mode) { 343 virtual DriverResult SetPollingMode([[maybe_unused]] PollingMode polling_mode) {
330 return PollingError::NotSupported; 344 return DriverResult::NotSupported;
331 } 345 }
332 346
333 virtual CameraError SetCameraFormat([[maybe_unused]] CameraFormat camera_format) { 347 virtual DriverResult SetCameraFormat([[maybe_unused]] CameraFormat camera_format) {
334 return CameraError::NotSupported; 348 return DriverResult::NotSupported;
335 } 349 }
336 350
337 virtual NfcState SupportsNfc() const { 351 virtual NfcState SupportsNfc() const {
diff --git a/src/common/settings.h b/src/common/settings.h
index 80b2eeabc..4b4da4da2 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -483,6 +483,7 @@ struct Values {
483 483
484 Setting<bool> enable_raw_input{false, "enable_raw_input"}; 484 Setting<bool> enable_raw_input{false, "enable_raw_input"};
485 Setting<bool> controller_navigation{true, "controller_navigation"}; 485 Setting<bool> controller_navigation{true, "controller_navigation"};
486 Setting<bool> enable_joycon_driver{true, "enable_joycon_driver"};
486 487
487 SwitchableSetting<bool> vibration_enabled{true, "vibration_enabled"}; 488 SwitchableSetting<bool> vibration_enabled{true, "vibration_enabled"};
488 SwitchableSetting<bool> enable_accurate_vibrations{false, "enable_accurate_vibrations"}; 489 SwitchableSetting<bool> enable_accurate_vibrations{false, "enable_accurate_vibrations"};
diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp
index a959c9db9..6e9812e6e 100644
--- a/src/core/hid/emulated_controller.cpp
+++ b/src/core/hid/emulated_controller.cpp
@@ -2,6 +2,7 @@
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include <algorithm> 4#include <algorithm>
5#include <common/scope_exit.h>
5 6
6#include "common/polyfill_ranges.h" 7#include "common/polyfill_ranges.h"
7#include "common/thread.h" 8#include "common/thread.h"
@@ -94,6 +95,7 @@ void EmulatedController::ReloadFromSettings() {
94 motion_params[index] = Common::ParamPackage(player.motions[index]); 95 motion_params[index] = Common::ParamPackage(player.motions[index]);
95 } 96 }
96 97
98 controller.color_values = {};
97 controller.colors_state.fullkey = { 99 controller.colors_state.fullkey = {
98 .body = GetNpadColor(player.body_color_left), 100 .body = GetNpadColor(player.body_color_left),
99 .button = GetNpadColor(player.button_color_left), 101 .button = GetNpadColor(player.button_color_left),
@@ -107,6 +109,8 @@ void EmulatedController::ReloadFromSettings() {
107 .button = GetNpadColor(player.button_color_right), 109 .button = GetNpadColor(player.button_color_right),
108 }; 110 };
109 111
112 ring_params[0] = Common::ParamPackage(Settings::values.ringcon_analogs);
113
110 // Other or debug controller should always be a pro controller 114 // Other or debug controller should always be a pro controller
111 if (npad_id_type != NpadIdType::Other) { 115 if (npad_id_type != NpadIdType::Other) {
112 SetNpadStyleIndex(MapSettingsTypeToNPad(player.controller_type)); 116 SetNpadStyleIndex(MapSettingsTypeToNPad(player.controller_type));
@@ -133,18 +137,28 @@ void EmulatedController::LoadDevices() {
133 trigger_params[LeftIndex] = button_params[Settings::NativeButton::ZL]; 137 trigger_params[LeftIndex] = button_params[Settings::NativeButton::ZL];
134 trigger_params[RightIndex] = button_params[Settings::NativeButton::ZR]; 138 trigger_params[RightIndex] = button_params[Settings::NativeButton::ZR];
135 139
140 color_params[LeftIndex] = left_joycon;
141 color_params[RightIndex] = right_joycon;
142 color_params[LeftIndex].Set("color", true);
143 color_params[RightIndex].Set("color", true);
144
136 battery_params[LeftIndex] = left_joycon; 145 battery_params[LeftIndex] = left_joycon;
137 battery_params[RightIndex] = right_joycon; 146 battery_params[RightIndex] = right_joycon;
138 battery_params[LeftIndex].Set("battery", true); 147 battery_params[LeftIndex].Set("battery", true);
139 battery_params[RightIndex].Set("battery", true); 148 battery_params[RightIndex].Set("battery", true);
140 149
141 camera_params = Common::ParamPackage{"engine:camera,camera:1"}; 150 camera_params[0] = right_joycon;
142 nfc_params = Common::ParamPackage{"engine:virtual_amiibo,nfc:1"}; 151 camera_params[0].Set("camera", true);
152 camera_params[1] = Common::ParamPackage{"engine:camera,camera:1"};
153 ring_params[1] = Common::ParamPackage{"engine:joycon,axis_x:100,axis_y:101"};
154 nfc_params[0] = Common::ParamPackage{"engine:virtual_amiibo,nfc:1"};
155 nfc_params[1] = right_joycon;
156 nfc_params[1].Set("nfc", true);
143 157
144 output_params[LeftIndex] = left_joycon; 158 output_params[LeftIndex] = left_joycon;
145 output_params[RightIndex] = right_joycon; 159 output_params[RightIndex] = right_joycon;
146 output_params[2] = camera_params; 160 output_params[2] = camera_params[1];
147 output_params[3] = nfc_params; 161 output_params[3] = nfc_params[0];
148 output_params[LeftIndex].Set("output", true); 162 output_params[LeftIndex].Set("output", true);
149 output_params[RightIndex].Set("output", true); 163 output_params[RightIndex].Set("output", true);
150 output_params[2].Set("output", true); 164 output_params[2].Set("output", true);
@@ -160,8 +174,11 @@ void EmulatedController::LoadDevices() {
160 Common::Input::CreateInputDevice); 174 Common::Input::CreateInputDevice);
161 std::ranges::transform(battery_params, battery_devices.begin(), 175 std::ranges::transform(battery_params, battery_devices.begin(),
162 Common::Input::CreateInputDevice); 176 Common::Input::CreateInputDevice);
163 camera_devices = Common::Input::CreateInputDevice(camera_params); 177 std::ranges::transform(color_params, color_devices.begin(), Common::Input::CreateInputDevice);
164 nfc_devices = Common::Input::CreateInputDevice(nfc_params); 178 std::ranges::transform(camera_params, camera_devices.begin(), Common::Input::CreateInputDevice);
179 std::ranges::transform(ring_params, ring_analog_devices.begin(),
180 Common::Input::CreateInputDevice);
181 std::ranges::transform(nfc_params, nfc_devices.begin(), Common::Input::CreateInputDevice);
165 std::ranges::transform(output_params, output_devices.begin(), 182 std::ranges::transform(output_params, output_devices.begin(),
166 Common::Input::CreateOutputDevice); 183 Common::Input::CreateOutputDevice);
167 184
@@ -323,6 +340,19 @@ void EmulatedController::ReloadInput() {
323 battery_devices[index]->ForceUpdate(); 340 battery_devices[index]->ForceUpdate();
324 } 341 }
325 342
343 for (std::size_t index = 0; index < color_devices.size(); ++index) {
344 if (!color_devices[index]) {
345 continue;
346 }
347 color_devices[index]->SetCallback({
348 .on_change =
349 [this, index](const Common::Input::CallbackStatus& callback) {
350 SetColors(callback, index);
351 },
352 });
353 color_devices[index]->ForceUpdate();
354 }
355
326 for (std::size_t index = 0; index < motion_devices.size(); ++index) { 356 for (std::size_t index = 0; index < motion_devices.size(); ++index) {
327 if (!motion_devices[index]) { 357 if (!motion_devices[index]) {
328 continue; 358 continue;
@@ -336,22 +366,37 @@ void EmulatedController::ReloadInput() {
336 motion_devices[index]->ForceUpdate(); 366 motion_devices[index]->ForceUpdate();
337 } 367 }
338 368
339 if (camera_devices) { 369 for (std::size_t index = 0; index < camera_devices.size(); ++index) {
340 camera_devices->SetCallback({ 370 if (!camera_devices[index]) {
371 continue;
372 }
373 camera_devices[index]->SetCallback({
341 .on_change = 374 .on_change =
342 [this](const Common::Input::CallbackStatus& callback) { SetCamera(callback); }, 375 [this](const Common::Input::CallbackStatus& callback) { SetCamera(callback); },
343 }); 376 });
344 camera_devices->ForceUpdate(); 377 camera_devices[index]->ForceUpdate();
345 } 378 }
346 379
347 if (nfc_devices) { 380 for (std::size_t index = 0; index < ring_analog_devices.size(); ++index) {
348 if (npad_id_type == NpadIdType::Handheld || npad_id_type == NpadIdType::Player1) { 381 if (!ring_analog_devices[index]) {
349 nfc_devices->SetCallback({ 382 continue;
350 .on_change =
351 [this](const Common::Input::CallbackStatus& callback) { SetNfc(callback); },
352 });
353 nfc_devices->ForceUpdate();
354 } 383 }
384 ring_analog_devices[index]->SetCallback({
385 .on_change =
386 [this](const Common::Input::CallbackStatus& callback) { SetRingAnalog(callback); },
387 });
388 ring_analog_devices[index]->ForceUpdate();
389 }
390
391 for (std::size_t index = 0; index < nfc_devices.size(); ++index) {
392 if (!nfc_devices[index]) {
393 continue;
394 }
395 nfc_devices[index]->SetCallback({
396 .on_change =
397 [this](const Common::Input::CallbackStatus& callback) { SetNfc(callback); },
398 });
399 nfc_devices[index]->ForceUpdate();
355 } 400 }
356 401
357 // Register TAS devices. No need to force update 402 // Register TAS devices. No need to force update
@@ -421,6 +466,9 @@ void EmulatedController::UnloadInput() {
421 for (auto& battery : battery_devices) { 466 for (auto& battery : battery_devices) {
422 battery.reset(); 467 battery.reset();
423 } 468 }
469 for (auto& color : color_devices) {
470 color.reset();
471 }
424 for (auto& output : output_devices) { 472 for (auto& output : output_devices) {
425 output.reset(); 473 output.reset();
426 } 474 }
@@ -436,8 +484,15 @@ void EmulatedController::UnloadInput() {
436 for (auto& stick : virtual_stick_devices) { 484 for (auto& stick : virtual_stick_devices) {
437 stick.reset(); 485 stick.reset();
438 } 486 }
439 camera_devices.reset(); 487 for (auto& camera : camera_devices) {
440 nfc_devices.reset(); 488 camera.reset();
489 }
490 for (auto& ring : ring_analog_devices) {
491 ring.reset();
492 }
493 for (auto& nfc : nfc_devices) {
494 nfc.reset();
495 }
441} 496}
442 497
443void EmulatedController::EnableConfiguration() { 498void EmulatedController::EnableConfiguration() {
@@ -449,6 +504,11 @@ void EmulatedController::EnableConfiguration() {
449void EmulatedController::DisableConfiguration() { 504void EmulatedController::DisableConfiguration() {
450 is_configuring = false; 505 is_configuring = false;
451 506
507 // Get Joycon colors before turning on the controller
508 for (const auto& color_device : color_devices) {
509 color_device->ForceUpdate();
510 }
511
452 // Apply temporary npad type to the real controller 512 // Apply temporary npad type to the real controller
453 if (tmp_npad_type != npad_type) { 513 if (tmp_npad_type != npad_type) {
454 if (is_connected) { 514 if (is_connected) {
@@ -502,6 +562,9 @@ void EmulatedController::SaveCurrentConfig() {
502 for (std::size_t index = 0; index < player.motions.size(); ++index) { 562 for (std::size_t index = 0; index < player.motions.size(); ++index) {
503 player.motions[index] = motion_params[index].Serialize(); 563 player.motions[index] = motion_params[index].Serialize();
504 } 564 }
565 if (npad_id_type == NpadIdType::Player1) {
566 Settings::values.ringcon_analogs = ring_params[0].Serialize();
567 }
505} 568}
506 569
507void EmulatedController::RestoreConfig() { 570void EmulatedController::RestoreConfig() {
@@ -773,17 +836,21 @@ void EmulatedController::SetStick(const Common::Input::CallbackStatus& callback,
773 if (index >= controller.stick_values.size()) { 836 if (index >= controller.stick_values.size()) {
774 return; 837 return;
775 } 838 }
776 std::unique_lock lock{mutex}; 839 auto trigger_guard =
840 SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Stick, !is_configuring); });
841 std::scoped_lock lock{mutex};
777 const auto stick_value = TransformToStick(callback); 842 const auto stick_value = TransformToStick(callback);
778 843
779 // Only read stick values that have the same uuid or are over the threshold to avoid flapping 844 // Only read stick values that have the same uuid or are over the threshold to avoid flapping
780 if (controller.stick_values[index].uuid != uuid) { 845 if (controller.stick_values[index].uuid != uuid) {
781 const bool is_tas = uuid == TAS_UUID; 846 const bool is_tas = uuid == TAS_UUID;
782 if (is_tas && stick_value.x.value == 0 && stick_value.y.value == 0) { 847 if (is_tas && stick_value.x.value == 0 && stick_value.y.value == 0) {
848 trigger_guard.Cancel();
783 return; 849 return;
784 } 850 }
785 if (!is_tas && !stick_value.down && !stick_value.up && !stick_value.left && 851 if (!is_tas && !stick_value.down && !stick_value.up && !stick_value.left &&
786 !stick_value.right) { 852 !stick_value.right) {
853 trigger_guard.Cancel();
787 return; 854 return;
788 } 855 }
789 } 856 }
@@ -794,8 +861,6 @@ void EmulatedController::SetStick(const Common::Input::CallbackStatus& callback,
794 if (is_configuring) { 861 if (is_configuring) {
795 controller.analog_stick_state.left = {}; 862 controller.analog_stick_state.left = {};
796 controller.analog_stick_state.right = {}; 863 controller.analog_stick_state.right = {};
797 lock.unlock();
798 TriggerOnChange(ControllerTriggerType::Stick, false);
799 return; 864 return;
800 } 865 }
801 866
@@ -827,9 +892,6 @@ void EmulatedController::SetStick(const Common::Input::CallbackStatus& callback,
827 controller.npad_button_state.stick_r_down.Assign(controller.stick_values[index].down); 892 controller.npad_button_state.stick_r_down.Assign(controller.stick_values[index].down);
828 break; 893 break;
829 } 894 }
830
831 lock.unlock();
832 TriggerOnChange(ControllerTriggerType::Stick, true);
833} 895}
834 896
835void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callback, 897void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callback,
@@ -837,7 +899,9 @@ void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callbac
837 if (index >= controller.trigger_values.size()) { 899 if (index >= controller.trigger_values.size()) {
838 return; 900 return;
839 } 901 }
840 std::unique_lock lock{mutex}; 902 auto trigger_guard =
903 SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Trigger, !is_configuring); });
904 std::scoped_lock lock{mutex};
841 const auto trigger_value = TransformToTrigger(callback); 905 const auto trigger_value = TransformToTrigger(callback);
842 906
843 // Only read trigger values that have the same uuid or are pressed once 907 // Only read trigger values that have the same uuid or are pressed once
@@ -853,13 +917,12 @@ void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callbac
853 if (is_configuring) { 917 if (is_configuring) {
854 controller.gc_trigger_state.left = 0; 918 controller.gc_trigger_state.left = 0;
855 controller.gc_trigger_state.right = 0; 919 controller.gc_trigger_state.right = 0;
856 lock.unlock();
857 TriggerOnChange(ControllerTriggerType::Trigger, false);
858 return; 920 return;
859 } 921 }
860 922
861 // Only GC controllers have analog triggers 923 // Only GC controllers have analog triggers
862 if (npad_type != NpadStyleIndex::GameCube) { 924 if (npad_type != NpadStyleIndex::GameCube) {
925 trigger_guard.Cancel();
863 return; 926 return;
864 } 927 }
865 928
@@ -876,9 +939,6 @@ void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callbac
876 controller.npad_button_state.zr.Assign(trigger.pressed.value); 939 controller.npad_button_state.zr.Assign(trigger.pressed.value);
877 break; 940 break;
878 } 941 }
879
880 lock.unlock();
881 TriggerOnChange(ControllerTriggerType::Trigger, true);
882} 942}
883 943
884void EmulatedController::SetMotion(const Common::Input::CallbackStatus& callback, 944void EmulatedController::SetMotion(const Common::Input::CallbackStatus& callback,
@@ -886,7 +946,8 @@ void EmulatedController::SetMotion(const Common::Input::CallbackStatus& callback
886 if (index >= controller.motion_values.size()) { 946 if (index >= controller.motion_values.size()) {
887 return; 947 return;
888 } 948 }
889 std::unique_lock lock{mutex}; 949 SCOPE_EXIT({ TriggerOnChange(ControllerTriggerType::Motion, !is_configuring); });
950 std::scoped_lock lock{mutex};
890 auto& raw_status = controller.motion_values[index].raw_status; 951 auto& raw_status = controller.motion_values[index].raw_status;
891 auto& emulated = controller.motion_values[index].emulated; 952 auto& emulated = controller.motion_values[index].emulated;
892 953
@@ -907,8 +968,6 @@ void EmulatedController::SetMotion(const Common::Input::CallbackStatus& callback
907 force_update_motion = raw_status.force_update; 968 force_update_motion = raw_status.force_update;
908 969
909 if (is_configuring) { 970 if (is_configuring) {
910 lock.unlock();
911 TriggerOnChange(ControllerTriggerType::Motion, false);
912 return; 971 return;
913 } 972 }
914 973
@@ -918,9 +977,56 @@ void EmulatedController::SetMotion(const Common::Input::CallbackStatus& callback
918 motion.rotation = emulated.GetRotations(); 977 motion.rotation = emulated.GetRotations();
919 motion.orientation = emulated.GetOrientation(); 978 motion.orientation = emulated.GetOrientation();
920 motion.is_at_rest = !emulated.IsMoving(motion_sensitivity); 979 motion.is_at_rest = !emulated.IsMoving(motion_sensitivity);
980}
921 981
922 lock.unlock(); 982void EmulatedController::SetColors(const Common::Input::CallbackStatus& callback,
923 TriggerOnChange(ControllerTriggerType::Motion, true); 983 std::size_t index) {
984 if (index >= controller.color_values.size()) {
985 return;
986 }
987 auto trigger_guard =
988 SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Color, !is_configuring); });
989 std::scoped_lock lock{mutex};
990 controller.color_values[index] = TransformToColor(callback);
991
992 if (is_configuring) {
993 return;
994 }
995
996 if (controller.color_values[index].body == 0) {
997 trigger_guard.Cancel();
998 return;
999 }
1000
1001 controller.colors_state.fullkey = {
1002 .body = GetNpadColor(controller.color_values[index].body),
1003 .button = GetNpadColor(controller.color_values[index].buttons),
1004 };
1005 if (npad_type == NpadStyleIndex::ProController) {
1006 controller.colors_state.left = {
1007 .body = GetNpadColor(controller.color_values[index].left_grip),
1008 .button = GetNpadColor(controller.color_values[index].buttons),
1009 };
1010 controller.colors_state.right = {
1011 .body = GetNpadColor(controller.color_values[index].right_grip),
1012 .button = GetNpadColor(controller.color_values[index].buttons),
1013 };
1014 } else {
1015 switch (index) {
1016 case LeftIndex:
1017 controller.colors_state.left = {
1018 .body = GetNpadColor(controller.color_values[index].body),
1019 .button = GetNpadColor(controller.color_values[index].buttons),
1020 };
1021 break;
1022 case RightIndex:
1023 controller.colors_state.right = {
1024 .body = GetNpadColor(controller.color_values[index].body),
1025 .button = GetNpadColor(controller.color_values[index].buttons),
1026 };
1027 break;
1028 }
1029 }
924} 1030}
925 1031
926void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callback, 1032void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callback,
@@ -928,12 +1034,11 @@ void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callbac
928 if (index >= controller.battery_values.size()) { 1034 if (index >= controller.battery_values.size()) {
929 return; 1035 return;
930 } 1036 }
931 std::unique_lock lock{mutex}; 1037 SCOPE_EXIT({ TriggerOnChange(ControllerTriggerType::Battery, !is_configuring); });
1038 std::scoped_lock lock{mutex};
932 controller.battery_values[index] = TransformToBattery(callback); 1039 controller.battery_values[index] = TransformToBattery(callback);
933 1040
934 if (is_configuring) { 1041 if (is_configuring) {
935 lock.unlock();
936 TriggerOnChange(ControllerTriggerType::Battery, false);
937 return; 1042 return;
938 } 1043 }
939 1044
@@ -989,18 +1094,14 @@ void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callbac
989 }; 1094 };
990 break; 1095 break;
991 } 1096 }
992
993 lock.unlock();
994 TriggerOnChange(ControllerTriggerType::Battery, true);
995} 1097}
996 1098
997void EmulatedController::SetCamera(const Common::Input::CallbackStatus& callback) { 1099void EmulatedController::SetCamera(const Common::Input::CallbackStatus& callback) {
998 std::unique_lock lock{mutex}; 1100 SCOPE_EXIT({ TriggerOnChange(ControllerTriggerType::IrSensor, !is_configuring); });
1101 std::scoped_lock lock{mutex};
999 controller.camera_values = TransformToCamera(callback); 1102 controller.camera_values = TransformToCamera(callback);
1000 1103
1001 if (is_configuring) { 1104 if (is_configuring) {
1002 lock.unlock();
1003 TriggerOnChange(ControllerTriggerType::IrSensor, false);
1004 return; 1105 return;
1005 } 1106 }
1006 1107
@@ -1008,18 +1109,28 @@ void EmulatedController::SetCamera(const Common::Input::CallbackStatus& callback
1008 controller.camera_state.format = 1109 controller.camera_state.format =
1009 static_cast<Core::IrSensor::ImageTransferProcessorFormat>(controller.camera_values.format); 1110 static_cast<Core::IrSensor::ImageTransferProcessorFormat>(controller.camera_values.format);
1010 controller.camera_state.data = controller.camera_values.data; 1111 controller.camera_state.data = controller.camera_values.data;
1112}
1011 1113
1012 lock.unlock(); 1114void EmulatedController::SetRingAnalog(const Common::Input::CallbackStatus& callback) {
1013 TriggerOnChange(ControllerTriggerType::IrSensor, true); 1115 SCOPE_EXIT({ TriggerOnChange(ControllerTriggerType::RingController, !is_configuring); });
1116 std::scoped_lock lock{mutex};
1117 const auto force_value = TransformToStick(callback);
1118
1119 controller.ring_analog_value = force_value.x;
1120
1121 if (is_configuring) {
1122 return;
1123 }
1124
1125 controller.ring_analog_state.force = force_value.x.value;
1014} 1126}
1015 1127
1016void EmulatedController::SetNfc(const Common::Input::CallbackStatus& callback) { 1128void EmulatedController::SetNfc(const Common::Input::CallbackStatus& callback) {
1017 std::unique_lock lock{mutex}; 1129 SCOPE_EXIT({ TriggerOnChange(ControllerTriggerType::Nfc, !is_configuring); });
1130 std::scoped_lock lock{mutex};
1018 controller.nfc_values = TransformToNfc(callback); 1131 controller.nfc_values = TransformToNfc(callback);
1019 1132
1020 if (is_configuring) { 1133 if (is_configuring) {
1021 lock.unlock();
1022 TriggerOnChange(ControllerTriggerType::Nfc, false);
1023 return; 1134 return;
1024 } 1135 }
1025 1136
@@ -1027,9 +1138,6 @@ void EmulatedController::SetNfc(const Common::Input::CallbackStatus& callback) {
1027 controller.nfc_values.state, 1138 controller.nfc_values.state,
1028 controller.nfc_values.data, 1139 controller.nfc_values.data,
1029 }; 1140 };
1030
1031 lock.unlock();
1032 TriggerOnChange(ControllerTriggerType::Nfc, true);
1033} 1141}
1034 1142
1035bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue vibration) { 1143bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue vibration) {
@@ -1061,7 +1169,7 @@ bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue v
1061 .type = type, 1169 .type = type,
1062 }; 1170 };
1063 return output_devices[device_index]->SetVibration(status) == 1171 return output_devices[device_index]->SetVibration(status) ==
1064 Common::Input::VibrationError::None; 1172 Common::Input::DriverResult::Success;
1065} 1173}
1066 1174
1067bool EmulatedController::IsVibrationEnabled(std::size_t device_index) { 1175bool EmulatedController::IsVibrationEnabled(std::size_t device_index) {
@@ -1083,16 +1191,32 @@ bool EmulatedController::IsVibrationEnabled(std::size_t device_index) {
1083 return output_devices[device_index]->IsVibrationEnabled(); 1191 return output_devices[device_index]->IsVibrationEnabled();
1084} 1192}
1085 1193
1086bool EmulatedController::SetPollingMode(Common::Input::PollingMode polling_mode) { 1194Common::Input::DriverResult EmulatedController::SetPollingMode(
1087 LOG_INFO(Service_HID, "Set polling mode {}", polling_mode); 1195 EmulatedDeviceIndex device_index, Common::Input::PollingMode polling_mode) {
1088 auto& output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)]; 1196 LOG_INFO(Service_HID, "Set polling mode {}, device_index={}", polling_mode, device_index);
1197
1198 auto& left_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Left)];
1199 auto& right_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
1089 auto& nfc_output_device = output_devices[3]; 1200 auto& nfc_output_device = output_devices[3];
1090 1201
1091 const auto virtual_nfc_result = nfc_output_device->SetPollingMode(polling_mode); 1202 if (device_index == EmulatedDeviceIndex::LeftIndex) {
1092 const auto mapped_nfc_result = output_device->SetPollingMode(polling_mode); 1203 return left_output_device->SetPollingMode(polling_mode);
1204 }
1205
1206 if (device_index == EmulatedDeviceIndex::RightIndex) {
1207 const auto virtual_nfc_result = nfc_output_device->SetPollingMode(polling_mode);
1208 const auto mapped_nfc_result = right_output_device->SetPollingMode(polling_mode);
1209
1210 if (virtual_nfc_result == Common::Input::DriverResult::Success) {
1211 return virtual_nfc_result;
1212 }
1213 return mapped_nfc_result;
1214 }
1093 1215
1094 return virtual_nfc_result == Common::Input::PollingError::None || 1216 left_output_device->SetPollingMode(polling_mode);
1095 mapped_nfc_result == Common::Input::PollingError::None; 1217 right_output_device->SetPollingMode(polling_mode);
1218 nfc_output_device->SetPollingMode(polling_mode);
1219 return Common::Input::DriverResult::Success;
1096} 1220}
1097 1221
1098bool EmulatedController::SetCameraFormat( 1222bool EmulatedController::SetCameraFormat(
@@ -1103,13 +1227,22 @@ bool EmulatedController::SetCameraFormat(
1103 auto& camera_output_device = output_devices[2]; 1227 auto& camera_output_device = output_devices[2];
1104 1228
1105 if (right_output_device->SetCameraFormat(static_cast<Common::Input::CameraFormat>( 1229 if (right_output_device->SetCameraFormat(static_cast<Common::Input::CameraFormat>(
1106 camera_format)) == Common::Input::CameraError::None) { 1230 camera_format)) == Common::Input::DriverResult::Success) {
1107 return true; 1231 return true;
1108 } 1232 }
1109 1233
1110 // Fallback to Qt camera if native device doesn't have support 1234 // Fallback to Qt camera if native device doesn't have support
1111 return camera_output_device->SetCameraFormat(static_cast<Common::Input::CameraFormat>( 1235 return camera_output_device->SetCameraFormat(static_cast<Common::Input::CameraFormat>(
1112 camera_format)) == Common::Input::CameraError::None; 1236 camera_format)) == Common::Input::DriverResult::Success;
1237}
1238
1239Common::ParamPackage EmulatedController::GetRingParam() const {
1240 return ring_params[0];
1241}
1242
1243void EmulatedController::SetRingParam(Common::ParamPackage param) {
1244 ring_params[0] = std::move(param);
1245 ReloadInput();
1113} 1246}
1114 1247
1115bool EmulatedController::HasNfc() const { 1248bool EmulatedController::HasNfc() const {
@@ -1263,39 +1396,35 @@ void EmulatedController::Connect(bool use_temporary_value) {
1263 return; 1396 return;
1264 } 1397 }
1265 1398
1266 std::unique_lock lock{mutex}; 1399 auto trigger_guard =
1400 SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Connected, !is_configuring); });
1401 std::scoped_lock lock{mutex};
1267 if (is_configuring) { 1402 if (is_configuring) {
1268 tmp_is_connected = true; 1403 tmp_is_connected = true;
1269 lock.unlock();
1270 TriggerOnChange(ControllerTriggerType::Connected, false);
1271 return; 1404 return;
1272 } 1405 }
1273 1406
1274 if (is_connected) { 1407 if (is_connected) {
1408 trigger_guard.Cancel();
1275 return; 1409 return;
1276 } 1410 }
1277 is_connected = true; 1411 is_connected = true;
1278
1279 lock.unlock();
1280 TriggerOnChange(ControllerTriggerType::Connected, true);
1281} 1412}
1282 1413
1283void EmulatedController::Disconnect() { 1414void EmulatedController::Disconnect() {
1284 std::unique_lock lock{mutex}; 1415 auto trigger_guard =
1416 SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Disconnected, !is_configuring); });
1417 std::scoped_lock lock{mutex};
1285 if (is_configuring) { 1418 if (is_configuring) {
1286 tmp_is_connected = false; 1419 tmp_is_connected = false;
1287 lock.unlock();
1288 TriggerOnChange(ControllerTriggerType::Disconnected, false);
1289 return; 1420 return;
1290 } 1421 }
1291 1422
1292 if (!is_connected) { 1423 if (!is_connected) {
1424 trigger_guard.Cancel();
1293 return; 1425 return;
1294 } 1426 }
1295 is_connected = false; 1427 is_connected = false;
1296
1297 lock.unlock();
1298 TriggerOnChange(ControllerTriggerType::Disconnected, true);
1299} 1428}
1300 1429
1301bool EmulatedController::IsConnected(bool get_temporary_value) const { 1430bool EmulatedController::IsConnected(bool get_temporary_value) const {
@@ -1320,19 +1449,21 @@ NpadStyleIndex EmulatedController::GetNpadStyleIndex(bool get_temporary_value) c
1320} 1449}
1321 1450
1322void EmulatedController::SetNpadStyleIndex(NpadStyleIndex npad_type_) { 1451void EmulatedController::SetNpadStyleIndex(NpadStyleIndex npad_type_) {
1323 std::unique_lock lock{mutex}; 1452 auto trigger_guard =
1453 SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Type, !is_configuring); });
1454 std::scoped_lock lock{mutex};
1324 1455
1325 if (is_configuring) { 1456 if (is_configuring) {
1326 if (tmp_npad_type == npad_type_) { 1457 if (tmp_npad_type == npad_type_) {
1458 trigger_guard.Cancel();
1327 return; 1459 return;
1328 } 1460 }
1329 tmp_npad_type = npad_type_; 1461 tmp_npad_type = npad_type_;
1330 lock.unlock();
1331 TriggerOnChange(ControllerTriggerType::Type, false);
1332 return; 1462 return;
1333 } 1463 }
1334 1464
1335 if (npad_type == npad_type_) { 1465 if (npad_type == npad_type_) {
1466 trigger_guard.Cancel();
1336 return; 1467 return;
1337 } 1468 }
1338 if (is_connected) { 1469 if (is_connected) {
@@ -1340,9 +1471,6 @@ void EmulatedController::SetNpadStyleIndex(NpadStyleIndex npad_type_) {
1340 NpadIdTypeToIndex(npad_id_type)); 1471 NpadIdTypeToIndex(npad_id_type));
1341 } 1472 }
1342 npad_type = npad_type_; 1473 npad_type = npad_type_;
1343
1344 lock.unlock();
1345 TriggerOnChange(ControllerTriggerType::Type, true);
1346} 1474}
1347 1475
1348LedPattern EmulatedController::GetLedPattern() const { 1476LedPattern EmulatedController::GetLedPattern() const {
@@ -1403,6 +1531,10 @@ CameraValues EmulatedController::GetCameraValues() const {
1403 return controller.camera_values; 1531 return controller.camera_values;
1404} 1532}
1405 1533
1534RingAnalogValue EmulatedController::GetRingSensorValues() const {
1535 return controller.ring_analog_value;
1536}
1537
1406HomeButtonState EmulatedController::GetHomeButtons() const { 1538HomeButtonState EmulatedController::GetHomeButtons() const {
1407 std::scoped_lock lock{mutex}; 1539 std::scoped_lock lock{mutex};
1408 if (is_configuring) { 1540 if (is_configuring) {
@@ -1436,7 +1568,7 @@ DebugPadButton EmulatedController::GetDebugPadButtons() const {
1436} 1568}
1437 1569
1438AnalogSticks EmulatedController::GetSticks() const { 1570AnalogSticks EmulatedController::GetSticks() const {
1439 std::unique_lock lock{mutex}; 1571 std::scoped_lock lock{mutex};
1440 1572
1441 if (is_configuring) { 1573 if (is_configuring) {
1442 return {}; 1574 return {};
@@ -1486,6 +1618,10 @@ const CameraState& EmulatedController::GetCamera() const {
1486 return controller.camera_state; 1618 return controller.camera_state;
1487} 1619}
1488 1620
1621RingSensorForce EmulatedController::GetRingSensorForce() const {
1622 return controller.ring_analog_state;
1623}
1624
1489const NfcState& EmulatedController::GetNfc() const { 1625const NfcState& EmulatedController::GetNfc() const {
1490 std::scoped_lock lock{mutex}; 1626 std::scoped_lock lock{mutex};
1491 return controller.nfc_state; 1627 return controller.nfc_state;
diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h
index a398543a6..3ac77b2b5 100644
--- a/src/core/hid/emulated_controller.h
+++ b/src/core/hid/emulated_controller.h
@@ -35,19 +35,27 @@ using ControllerMotionDevices =
35 std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeMotion::NumMotions>; 35 std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeMotion::NumMotions>;
36using TriggerDevices = 36using TriggerDevices =
37 std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeTrigger::NumTriggers>; 37 std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeTrigger::NumTriggers>;
38using ColorDevices =
39 std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
38using BatteryDevices = 40using BatteryDevices =
39 std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>; 41 std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
40using CameraDevices = std::unique_ptr<Common::Input::InputDevice>; 42using CameraDevices =
41using NfcDevices = std::unique_ptr<Common::Input::InputDevice>; 43 std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
44using RingAnalogDevices =
45 std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
46using NfcDevices =
47 std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
42using OutputDevices = std::array<std::unique_ptr<Common::Input::OutputDevice>, output_devices_size>; 48using OutputDevices = std::array<std::unique_ptr<Common::Input::OutputDevice>, output_devices_size>;
43 49
44using ButtonParams = std::array<Common::ParamPackage, Settings::NativeButton::NumButtons>; 50using ButtonParams = std::array<Common::ParamPackage, Settings::NativeButton::NumButtons>;
45using StickParams = std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs>; 51using StickParams = std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs>;
46using ControllerMotionParams = std::array<Common::ParamPackage, Settings::NativeMotion::NumMotions>; 52using ControllerMotionParams = std::array<Common::ParamPackage, Settings::NativeMotion::NumMotions>;
47using TriggerParams = std::array<Common::ParamPackage, Settings::NativeTrigger::NumTriggers>; 53using TriggerParams = std::array<Common::ParamPackage, Settings::NativeTrigger::NumTriggers>;
54using ColorParams = std::array<Common::ParamPackage, max_emulated_controllers>;
48using BatteryParams = std::array<Common::ParamPackage, max_emulated_controllers>; 55using BatteryParams = std::array<Common::ParamPackage, max_emulated_controllers>;
49using CameraParams = Common::ParamPackage; 56using CameraParams = std::array<Common::ParamPackage, max_emulated_controllers>;
50using NfcParams = Common::ParamPackage; 57using RingAnalogParams = std::array<Common::ParamPackage, max_emulated_controllers>;
58using NfcParams = std::array<Common::ParamPackage, max_emulated_controllers>;
51using OutputParams = std::array<Common::ParamPackage, output_devices_size>; 59using OutputParams = std::array<Common::ParamPackage, output_devices_size>;
52 60
53using ButtonValues = std::array<Common::Input::ButtonStatus, Settings::NativeButton::NumButtons>; 61using ButtonValues = std::array<Common::Input::ButtonStatus, Settings::NativeButton::NumButtons>;
@@ -58,6 +66,7 @@ using ControllerMotionValues = std::array<ControllerMotionInfo, Settings::Native
58using ColorValues = std::array<Common::Input::BodyColorStatus, max_emulated_controllers>; 66using ColorValues = std::array<Common::Input::BodyColorStatus, max_emulated_controllers>;
59using BatteryValues = std::array<Common::Input::BatteryStatus, max_emulated_controllers>; 67using BatteryValues = std::array<Common::Input::BatteryStatus, max_emulated_controllers>;
60using CameraValues = Common::Input::CameraStatus; 68using CameraValues = Common::Input::CameraStatus;
69using RingAnalogValue = Common::Input::AnalogStatus;
61using NfcValues = Common::Input::NfcStatus; 70using NfcValues = Common::Input::NfcStatus;
62using VibrationValues = std::array<Common::Input::VibrationStatus, max_emulated_controllers>; 71using VibrationValues = std::array<Common::Input::VibrationStatus, max_emulated_controllers>;
63 72
@@ -84,6 +93,10 @@ struct CameraState {
84 std::size_t sample{}; 93 std::size_t sample{};
85}; 94};
86 95
96struct RingSensorForce {
97 f32 force;
98};
99
87struct NfcState { 100struct NfcState {
88 Common::Input::NfcState state{}; 101 Common::Input::NfcState state{};
89 std::vector<u8> data{}; 102 std::vector<u8> data{};
@@ -116,6 +129,7 @@ struct ControllerStatus {
116 BatteryValues battery_values{}; 129 BatteryValues battery_values{};
117 VibrationValues vibration_values{}; 130 VibrationValues vibration_values{};
118 CameraValues camera_values{}; 131 CameraValues camera_values{};
132 RingAnalogValue ring_analog_value{};
119 NfcValues nfc_values{}; 133 NfcValues nfc_values{};
120 134
121 // Data for HID serices 135 // Data for HID serices
@@ -129,6 +143,7 @@ struct ControllerStatus {
129 ControllerColors colors_state{}; 143 ControllerColors colors_state{};
130 BatteryLevelState battery_state{}; 144 BatteryLevelState battery_state{};
131 CameraState camera_state{}; 145 CameraState camera_state{};
146 RingSensorForce ring_analog_state{};
132 NfcState nfc_state{}; 147 NfcState nfc_state{};
133}; 148};
134 149
@@ -141,6 +156,7 @@ enum class ControllerTriggerType {
141 Battery, 156 Battery,
142 Vibration, 157 Vibration,
143 IrSensor, 158 IrSensor,
159 RingController,
144 Nfc, 160 Nfc,
145 Connected, 161 Connected,
146 Disconnected, 162 Disconnected,
@@ -294,6 +310,9 @@ public:
294 /// Returns the latest camera status from the controller with parameters 310 /// Returns the latest camera status from the controller with parameters
295 CameraValues GetCameraValues() const; 311 CameraValues GetCameraValues() const;
296 312
313 /// Returns the latest status of analog input from the ring sensor with parameters
314 RingAnalogValue GetRingSensorValues() const;
315
297 /// Returns the latest status of button input for the hid::HomeButton service 316 /// Returns the latest status of button input for the hid::HomeButton service
298 HomeButtonState GetHomeButtons() const; 317 HomeButtonState GetHomeButtons() const;
299 318
@@ -324,6 +343,9 @@ public:
324 /// Returns the latest camera status from the controller 343 /// Returns the latest camera status from the controller
325 const CameraState& GetCamera() const; 344 const CameraState& GetCamera() const;
326 345
346 /// Returns the latest ringcon force sensor value
347 RingSensorForce GetRingSensorForce() const;
348
327 /// Returns the latest ntag status from the controller 349 /// Returns the latest ntag status from the controller
328 const NfcState& GetNfc() const; 350 const NfcState& GetNfc() const;
329 351
@@ -341,10 +363,12 @@ public:
341 363
342 /** 364 /**
343 * Sets the desired data to be polled from a controller 365 * Sets the desired data to be polled from a controller
366 * @param device_index index of the controller to set the polling mode
344 * @param polling_mode type of input desired buttons, gyro, nfc, ir, etc. 367 * @param polling_mode type of input desired buttons, gyro, nfc, ir, etc.
345 * @return true if SetPollingMode was successfull 368 * @return driver result from this command
346 */ 369 */
347 bool SetPollingMode(Common::Input::PollingMode polling_mode); 370 Common::Input::DriverResult SetPollingMode(EmulatedDeviceIndex device_index,
371 Common::Input::PollingMode polling_mode);
348 372
349 /** 373 /**
350 * Sets the desired camera format to be polled from a controller 374 * Sets the desired camera format to be polled from a controller
@@ -353,6 +377,15 @@ public:
353 */ 377 */
354 bool SetCameraFormat(Core::IrSensor::ImageTransferProcessorFormat camera_format); 378 bool SetCameraFormat(Core::IrSensor::ImageTransferProcessorFormat camera_format);
355 379
380 // Returns the current mapped ring device
381 Common::ParamPackage GetRingParam() const;
382
383 /**
384 * Updates the current mapped ring device
385 * @param param ParamPackage with ring sensor data to be mapped
386 */
387 void SetRingParam(Common::ParamPackage param);
388
356 /// Returns true if the device has nfc support 389 /// Returns true if the device has nfc support
357 bool HasNfc() const; 390 bool HasNfc() const;
358 391
@@ -433,9 +466,16 @@ private:
433 void SetMotion(const Common::Input::CallbackStatus& callback, std::size_t index); 466 void SetMotion(const Common::Input::CallbackStatus& callback, std::size_t index);
434 467
435 /** 468 /**
469 * Updates the color status of the controller
470 * @param callback A CallbackStatus containing the color status
471 * @param index color ID of the to be updated
472 */
473 void SetColors(const Common::Input::CallbackStatus& callback, std::size_t index);
474
475 /**
436 * Updates the battery status of the controller 476 * Updates the battery status of the controller
437 * @param callback A CallbackStatus containing the battery status 477 * @param callback A CallbackStatus containing the battery status
438 * @param index Button ID of the to be updated 478 * @param index battery ID of the to be updated
439 */ 479 */
440 void SetBattery(const Common::Input::CallbackStatus& callback, std::size_t index); 480 void SetBattery(const Common::Input::CallbackStatus& callback, std::size_t index);
441 481
@@ -446,6 +486,12 @@ private:
446 void SetCamera(const Common::Input::CallbackStatus& callback); 486 void SetCamera(const Common::Input::CallbackStatus& callback);
447 487
448 /** 488 /**
489 * Updates the ring analog sensor status of the ring controller
490 * @param callback A CallbackStatus containing the force status
491 */
492 void SetRingAnalog(const Common::Input::CallbackStatus& callback);
493
494 /**
449 * Updates the nfc status of the controller 495 * Updates the nfc status of the controller
450 * @param callback A CallbackStatus containing the nfc status 496 * @param callback A CallbackStatus containing the nfc status
451 */ 497 */
@@ -484,7 +530,9 @@ private:
484 ControllerMotionParams motion_params; 530 ControllerMotionParams motion_params;
485 TriggerParams trigger_params; 531 TriggerParams trigger_params;
486 BatteryParams battery_params; 532 BatteryParams battery_params;
533 ColorParams color_params;
487 CameraParams camera_params; 534 CameraParams camera_params;
535 RingAnalogParams ring_params;
488 NfcParams nfc_params; 536 NfcParams nfc_params;
489 OutputParams output_params; 537 OutputParams output_params;
490 538
@@ -493,7 +541,9 @@ private:
493 ControllerMotionDevices motion_devices; 541 ControllerMotionDevices motion_devices;
494 TriggerDevices trigger_devices; 542 TriggerDevices trigger_devices;
495 BatteryDevices battery_devices; 543 BatteryDevices battery_devices;
544 ColorDevices color_devices;
496 CameraDevices camera_devices; 545 CameraDevices camera_devices;
546 RingAnalogDevices ring_analog_devices;
497 NfcDevices nfc_devices; 547 NfcDevices nfc_devices;
498 OutputDevices output_devices; 548 OutputDevices output_devices;
499 549
diff --git a/src/core/hid/emulated_devices.cpp b/src/core/hid/emulated_devices.cpp
index e421828d2..836f32c0f 100644
--- a/src/core/hid/emulated_devices.cpp
+++ b/src/core/hid/emulated_devices.cpp
@@ -14,7 +14,6 @@ EmulatedDevices::EmulatedDevices() = default;
14EmulatedDevices::~EmulatedDevices() = default; 14EmulatedDevices::~EmulatedDevices() = default;
15 15
16void EmulatedDevices::ReloadFromSettings() { 16void EmulatedDevices::ReloadFromSettings() {
17 ring_params = Common::ParamPackage(Settings::values.ringcon_analogs);
18 ReloadInput(); 17 ReloadInput();
19} 18}
20 19
@@ -66,8 +65,6 @@ void EmulatedDevices::ReloadInput() {
66 key_index++; 65 key_index++;
67 } 66 }
68 67
69 ring_analog_device = Common::Input::CreateInputDevice(ring_params);
70
71 for (std::size_t index = 0; index < mouse_button_devices.size(); ++index) { 68 for (std::size_t index = 0; index < mouse_button_devices.size(); ++index) {
72 if (!mouse_button_devices[index]) { 69 if (!mouse_button_devices[index]) {
73 continue; 70 continue;
@@ -122,13 +119,6 @@ void EmulatedDevices::ReloadInput() {
122 }, 119 },
123 }); 120 });
124 } 121 }
125
126 if (ring_analog_device) {
127 ring_analog_device->SetCallback({
128 .on_change =
129 [this](const Common::Input::CallbackStatus& callback) { SetRingAnalog(callback); },
130 });
131 }
132} 122}
133 123
134void EmulatedDevices::UnloadInput() { 124void EmulatedDevices::UnloadInput() {
@@ -145,7 +135,6 @@ void EmulatedDevices::UnloadInput() {
145 for (auto& button : keyboard_modifier_devices) { 135 for (auto& button : keyboard_modifier_devices) {
146 button.reset(); 136 button.reset();
147 } 137 }
148 ring_analog_device.reset();
149} 138}
150 139
151void EmulatedDevices::EnableConfiguration() { 140void EmulatedDevices::EnableConfiguration() {
@@ -165,7 +154,6 @@ void EmulatedDevices::SaveCurrentConfig() {
165 if (!is_configuring) { 154 if (!is_configuring) {
166 return; 155 return;
167 } 156 }
168 Settings::values.ringcon_analogs = ring_params.Serialize();
169} 157}
170 158
171void EmulatedDevices::RestoreConfig() { 159void EmulatedDevices::RestoreConfig() {
@@ -175,15 +163,6 @@ void EmulatedDevices::RestoreConfig() {
175 ReloadFromSettings(); 163 ReloadFromSettings();
176} 164}
177 165
178Common::ParamPackage EmulatedDevices::GetRingParam() const {
179 return ring_params;
180}
181
182void EmulatedDevices::SetRingParam(Common::ParamPackage param) {
183 ring_params = std::move(param);
184 ReloadInput();
185}
186
187void EmulatedDevices::SetKeyboardButton(const Common::Input::CallbackStatus& callback, 166void EmulatedDevices::SetKeyboardButton(const Common::Input::CallbackStatus& callback,
188 std::size_t index) { 167 std::size_t index) {
189 if (index >= device_status.keyboard_values.size()) { 168 if (index >= device_status.keyboard_values.size()) {
@@ -430,23 +409,6 @@ void EmulatedDevices::SetMouseStick(const Common::Input::CallbackStatus& callbac
430 TriggerOnChange(DeviceTriggerType::Mouse); 409 TriggerOnChange(DeviceTriggerType::Mouse);
431} 410}
432 411
433void EmulatedDevices::SetRingAnalog(const Common::Input::CallbackStatus& callback) {
434 std::lock_guard lock{mutex};
435 const auto force_value = TransformToStick(callback);
436
437 device_status.ring_analog_value = force_value.x;
438
439 if (is_configuring) {
440 device_status.ring_analog_value = {};
441 TriggerOnChange(DeviceTriggerType::RingController);
442 return;
443 }
444
445 device_status.ring_analog_state.force = force_value.x.value;
446
447 TriggerOnChange(DeviceTriggerType::RingController);
448}
449
450KeyboardValues EmulatedDevices::GetKeyboardValues() const { 412KeyboardValues EmulatedDevices::GetKeyboardValues() const {
451 std::scoped_lock lock{mutex}; 413 std::scoped_lock lock{mutex};
452 return device_status.keyboard_values; 414 return device_status.keyboard_values;
@@ -462,10 +424,6 @@ MouseButtonValues EmulatedDevices::GetMouseButtonsValues() const {
462 return device_status.mouse_button_values; 424 return device_status.mouse_button_values;
463} 425}
464 426
465RingAnalogValue EmulatedDevices::GetRingSensorValues() const {
466 return device_status.ring_analog_value;
467}
468
469KeyboardKey EmulatedDevices::GetKeyboard() const { 427KeyboardKey EmulatedDevices::GetKeyboard() const {
470 std::scoped_lock lock{mutex}; 428 std::scoped_lock lock{mutex};
471 return device_status.keyboard_state; 429 return device_status.keyboard_state;
@@ -491,10 +449,6 @@ AnalogStickState EmulatedDevices::GetMouseWheel() const {
491 return device_status.mouse_wheel_state; 449 return device_status.mouse_wheel_state;
492} 450}
493 451
494RingSensorForce EmulatedDevices::GetRingSensorForce() const {
495 return device_status.ring_analog_state;
496}
497
498void EmulatedDevices::TriggerOnChange(DeviceTriggerType type) { 452void EmulatedDevices::TriggerOnChange(DeviceTriggerType type) {
499 std::scoped_lock lock{callback_mutex}; 453 std::scoped_lock lock{callback_mutex};
500 for (const auto& poller_pair : callback_list) { 454 for (const auto& poller_pair : callback_list) {
diff --git a/src/core/hid/emulated_devices.h b/src/core/hid/emulated_devices.h
index 4cdbf9dc6..76f9150df 100644
--- a/src/core/hid/emulated_devices.h
+++ b/src/core/hid/emulated_devices.h
@@ -26,11 +26,9 @@ using MouseButtonDevices = std::array<std::unique_ptr<Common::Input::InputDevice
26using MouseAnalogDevices = std::array<std::unique_ptr<Common::Input::InputDevice>, 26using MouseAnalogDevices = std::array<std::unique_ptr<Common::Input::InputDevice>,
27 Settings::NativeMouseWheel::NumMouseWheels>; 27 Settings::NativeMouseWheel::NumMouseWheels>;
28using MouseStickDevice = std::unique_ptr<Common::Input::InputDevice>; 28using MouseStickDevice = std::unique_ptr<Common::Input::InputDevice>;
29using RingAnalogDevice = std::unique_ptr<Common::Input::InputDevice>;
30 29
31using MouseButtonParams = 30using MouseButtonParams =
32 std::array<Common::ParamPackage, Settings::NativeMouseButton::NumMouseButtons>; 31 std::array<Common::ParamPackage, Settings::NativeMouseButton::NumMouseButtons>;
33using RingAnalogParams = Common::ParamPackage;
34 32
35using KeyboardValues = 33using KeyboardValues =
36 std::array<Common::Input::ButtonStatus, Settings::NativeKeyboard::NumKeyboardKeys>; 34 std::array<Common::Input::ButtonStatus, Settings::NativeKeyboard::NumKeyboardKeys>;
@@ -41,17 +39,12 @@ using MouseButtonValues =
41using MouseAnalogValues = 39using MouseAnalogValues =
42 std::array<Common::Input::AnalogStatus, Settings::NativeMouseWheel::NumMouseWheels>; 40 std::array<Common::Input::AnalogStatus, Settings::NativeMouseWheel::NumMouseWheels>;
43using MouseStickValue = Common::Input::TouchStatus; 41using MouseStickValue = Common::Input::TouchStatus;
44using RingAnalogValue = Common::Input::AnalogStatus;
45 42
46struct MousePosition { 43struct MousePosition {
47 f32 x; 44 f32 x;
48 f32 y; 45 f32 y;
49}; 46};
50 47
51struct RingSensorForce {
52 f32 force;
53};
54
55struct DeviceStatus { 48struct DeviceStatus {
56 // Data from input_common 49 // Data from input_common
57 KeyboardValues keyboard_values{}; 50 KeyboardValues keyboard_values{};
@@ -59,7 +52,6 @@ struct DeviceStatus {
59 MouseButtonValues mouse_button_values{}; 52 MouseButtonValues mouse_button_values{};
60 MouseAnalogValues mouse_analog_values{}; 53 MouseAnalogValues mouse_analog_values{};
61 MouseStickValue mouse_stick_value{}; 54 MouseStickValue mouse_stick_value{};
62 RingAnalogValue ring_analog_value{};
63 55
64 // Data for HID serices 56 // Data for HID serices
65 KeyboardKey keyboard_state{}; 57 KeyboardKey keyboard_state{};
@@ -67,7 +59,6 @@ struct DeviceStatus {
67 MouseButton mouse_button_state{}; 59 MouseButton mouse_button_state{};
68 MousePosition mouse_position_state{}; 60 MousePosition mouse_position_state{};
69 AnalogStickState mouse_wheel_state{}; 61 AnalogStickState mouse_wheel_state{};
70 RingSensorForce ring_analog_state{};
71}; 62};
72 63
73enum class DeviceTriggerType { 64enum class DeviceTriggerType {
@@ -138,9 +129,6 @@ public:
138 /// Returns the latest status of button input from the mouse with parameters 129 /// Returns the latest status of button input from the mouse with parameters
139 MouseButtonValues GetMouseButtonsValues() const; 130 MouseButtonValues GetMouseButtonsValues() const;
140 131
141 /// Returns the latest status of analog input from the ring sensor with parameters
142 RingAnalogValue GetRingSensorValues() const;
143
144 /// Returns the latest status of button input from the keyboard 132 /// Returns the latest status of button input from the keyboard
145 KeyboardKey GetKeyboard() const; 133 KeyboardKey GetKeyboard() const;
146 134
@@ -156,9 +144,6 @@ public:
156 /// Returns the latest mouse wheel change 144 /// Returns the latest mouse wheel change
157 AnalogStickState GetMouseWheel() const; 145 AnalogStickState GetMouseWheel() const;
158 146
159 /// Returns the latest ringcon force sensor value
160 RingSensorForce GetRingSensorForce() const;
161
162 /** 147 /**
163 * Adds a callback to the list of events 148 * Adds a callback to the list of events
164 * @param update_callback InterfaceUpdateCallback that will be triggered 149 * @param update_callback InterfaceUpdateCallback that will be triggered
@@ -224,14 +209,11 @@ private:
224 209
225 bool is_configuring{false}; 210 bool is_configuring{false};
226 211
227 RingAnalogParams ring_params;
228
229 KeyboardDevices keyboard_devices; 212 KeyboardDevices keyboard_devices;
230 KeyboardModifierDevices keyboard_modifier_devices; 213 KeyboardModifierDevices keyboard_modifier_devices;
231 MouseButtonDevices mouse_button_devices; 214 MouseButtonDevices mouse_button_devices;
232 MouseAnalogDevices mouse_analog_devices; 215 MouseAnalogDevices mouse_analog_devices;
233 MouseStickDevice mouse_stick_device; 216 MouseStickDevice mouse_stick_device;
234 RingAnalogDevice ring_analog_device;
235 217
236 mutable std::mutex mutex; 218 mutable std::mutex mutex;
237 mutable std::mutex callback_mutex; 219 mutable std::mutex callback_mutex;
diff --git a/src/core/hid/input_converter.cpp b/src/core/hid/input_converter.cpp
index 502692875..3f7b8c090 100644
--- a/src/core/hid/input_converter.cpp
+++ b/src/core/hid/input_converter.cpp
@@ -304,6 +304,18 @@ Common::Input::NfcStatus TransformToNfc(const Common::Input::CallbackStatus& cal
304 return nfc; 304 return nfc;
305} 305}
306 306
307Common::Input::BodyColorStatus TransformToColor(const Common::Input::CallbackStatus& callback) {
308 switch (callback.type) {
309 case Common::Input::InputType::Color:
310 return callback.color_status;
311 break;
312 default:
313 LOG_ERROR(Input, "Conversion from type {} to color not implemented", callback.type);
314 return {};
315 break;
316 }
317}
318
307void SanitizeAnalog(Common::Input::AnalogStatus& analog, bool clamp_value) { 319void SanitizeAnalog(Common::Input::AnalogStatus& analog, bool clamp_value) {
308 const auto& properties = analog.properties; 320 const auto& properties = analog.properties;
309 float& raw_value = analog.raw_value; 321 float& raw_value = analog.raw_value;
diff --git a/src/core/hid/input_converter.h b/src/core/hid/input_converter.h
index b7eb6e660..c51c03e57 100644
--- a/src/core/hid/input_converter.h
+++ b/src/core/hid/input_converter.h
@@ -88,11 +88,19 @@ Common::Input::CameraStatus TransformToCamera(const Common::Input::CallbackStatu
88 * Converts raw input data into a valid nfc status. 88 * Converts raw input data into a valid nfc status.
89 * 89 *
90 * @param callback Supported callbacks: Nfc. 90 * @param callback Supported callbacks: Nfc.
91 * @return A valid CameraObject object. 91 * @return A valid data tag vector.
92 */ 92 */
93Common::Input::NfcStatus TransformToNfc(const Common::Input::CallbackStatus& callback); 93Common::Input::NfcStatus TransformToNfc(const Common::Input::CallbackStatus& callback);
94 94
95/** 95/**
96 * Converts raw input data into a valid color status.
97 *
98 * @param callback Supported callbacks: Color.
99 * @return A valid Color object.
100 */
101Common::Input::BodyColorStatus TransformToColor(const Common::Input::CallbackStatus& callback);
102
103/**
96 * Converts raw analog data into a valid analog value 104 * Converts raw analog data into a valid analog value
97 * @param analog An analog object containing raw data and properties 105 * @param analog An analog object containing raw data and properties
98 * @param clamp_value determines if the value needs to be clamped between -1.0f and 1.0f. 106 * @param clamp_value determines if the value needs to be clamped between -1.0f and 1.0f.
diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp
index 2f871de31..5713f1288 100644
--- a/src/core/hle/service/hid/controllers/npad.cpp
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -272,6 +272,8 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
272 } 272 }
273 break; 273 break;
274 case Core::HID::NpadStyleIndex::JoyconLeft: 274 case Core::HID::NpadStyleIndex::JoyconLeft:
275 shared_memory->fullkey_color.attribute = ColorAttribute::Ok;
276 shared_memory->fullkey_color.fullkey = body_colors.left;
275 shared_memory->joycon_color.attribute = ColorAttribute::Ok; 277 shared_memory->joycon_color.attribute = ColorAttribute::Ok;
276 shared_memory->joycon_color.left = body_colors.left; 278 shared_memory->joycon_color.left = body_colors.left;
277 shared_memory->battery_level_dual = battery_level.left.battery_level; 279 shared_memory->battery_level_dual = battery_level.left.battery_level;
@@ -285,6 +287,8 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
285 shared_memory->sixaxis_left_properties.is_newly_assigned.Assign(1); 287 shared_memory->sixaxis_left_properties.is_newly_assigned.Assign(1);
286 break; 288 break;
287 case Core::HID::NpadStyleIndex::JoyconRight: 289 case Core::HID::NpadStyleIndex::JoyconRight:
290 shared_memory->fullkey_color.attribute = ColorAttribute::Ok;
291 shared_memory->fullkey_color.fullkey = body_colors.right;
288 shared_memory->joycon_color.attribute = ColorAttribute::Ok; 292 shared_memory->joycon_color.attribute = ColorAttribute::Ok;
289 shared_memory->joycon_color.right = body_colors.right; 293 shared_memory->joycon_color.right = body_colors.right;
290 shared_memory->battery_level_right = battery_level.right.battery_level; 294 shared_memory->battery_level_right = battery_level.right.battery_level;
@@ -332,6 +336,20 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
332 336
333 controller.is_connected = true; 337 controller.is_connected = true;
334 controller.device->Connect(); 338 controller.device->Connect();
339 controller.device->SetLedPattern();
340 if (controller_type == Core::HID::NpadStyleIndex::JoyconDual) {
341 if (controller.is_dual_left_connected) {
342 controller.device->SetPollingMode(Core::HID::EmulatedDeviceIndex::LeftIndex,
343 Common::Input::PollingMode::Active);
344 }
345 if (controller.is_dual_right_connected) {
346 controller.device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
347 Common::Input::PollingMode::Active);
348 }
349 } else {
350 controller.device->SetPollingMode(Core::HID::EmulatedDeviceIndex::AllDevices,
351 Common::Input::PollingMode::Active);
352 }
335 SignalStyleSetChangedEvent(npad_id); 353 SignalStyleSetChangedEvent(npad_id);
336 WriteEmptyEntry(controller.shared_memory); 354 WriteEmptyEntry(controller.shared_memory);
337} 355}
diff --git a/src/core/hle/service/hid/hidbus.cpp b/src/core/hle/service/hid/hidbus.cpp
index e5e50845f..17252a84a 100644
--- a/src/core/hle/service/hid/hidbus.cpp
+++ b/src/core/hle/service/hid/hidbus.cpp
@@ -297,13 +297,13 @@ void HidBus::EnableExternalDevice(Kernel::HLERequestContext& ctx) {
297 297
298 const auto parameters{rp.PopRaw<Parameters>()}; 298 const auto parameters{rp.PopRaw<Parameters>()};
299 299
300 LOG_INFO(Service_HID, 300 LOG_DEBUG(Service_HID,
301 "called, enable={}, abstracted_pad_id={}, bus_type={}, internal_index={}, " 301 "called, enable={}, abstracted_pad_id={}, bus_type={}, internal_index={}, "
302 "player_number={}, is_valid={}, inval={}, applet_resource_user_id{}", 302 "player_number={}, is_valid={}, inval={}, applet_resource_user_id{}",
303 parameters.enable, parameters.bus_handle.abstracted_pad_id, 303 parameters.enable, parameters.bus_handle.abstracted_pad_id,
304 parameters.bus_handle.bus_type, parameters.bus_handle.internal_index, 304 parameters.bus_handle.bus_type, parameters.bus_handle.internal_index,
305 parameters.bus_handle.player_number, parameters.bus_handle.is_valid, parameters.inval, 305 parameters.bus_handle.player_number, parameters.bus_handle.is_valid, parameters.inval,
306 parameters.applet_resource_user_id); 306 parameters.applet_resource_user_id);
307 307
308 const auto device_index = GetDeviceIndexFromHandle(parameters.bus_handle); 308 const auto device_index = GetDeviceIndexFromHandle(parameters.bus_handle);
309 309
@@ -326,11 +326,11 @@ void HidBus::GetExternalDeviceId(Kernel::HLERequestContext& ctx) {
326 IPC::RequestParser rp{ctx}; 326 IPC::RequestParser rp{ctx};
327 const auto bus_handle_{rp.PopRaw<BusHandle>()}; 327 const auto bus_handle_{rp.PopRaw<BusHandle>()};
328 328
329 LOG_INFO(Service_HID, 329 LOG_DEBUG(Service_HID,
330 "called, abstracted_pad_id={}, bus_type={}, internal_index={}, player_number={}, " 330 "called, abstracted_pad_id={}, bus_type={}, internal_index={}, player_number={}, "
331 "is_valid={}", 331 "is_valid={}",
332 bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index, 332 bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index,
333 bus_handle_.player_number, bus_handle_.is_valid); 333 bus_handle_.player_number, bus_handle_.is_valid);
334 334
335 const auto device_index = GetDeviceIndexFromHandle(bus_handle_); 335 const auto device_index = GetDeviceIndexFromHandle(bus_handle_);
336 336
diff --git a/src/core/hle/service/hid/hidbus/ringcon.cpp b/src/core/hle/service/hid/hidbus/ringcon.cpp
index 57f1a2a26..78ed47014 100644
--- a/src/core/hle/service/hid/hidbus/ringcon.cpp
+++ b/src/core/hle/service/hid/hidbus/ringcon.cpp
@@ -1,7 +1,7 @@
1// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "core/hid/emulated_devices.h" 4#include "core/hid/emulated_controller.h"
5#include "core/hid/hid_core.h" 5#include "core/hid/hid_core.h"
6#include "core/hle/kernel/k_event.h" 6#include "core/hle/kernel/k_event.h"
7#include "core/hle/kernel/k_readable_event.h" 7#include "core/hle/kernel/k_readable_event.h"
@@ -12,16 +12,20 @@ namespace Service::HID {
12RingController::RingController(Core::HID::HIDCore& hid_core_, 12RingController::RingController(Core::HID::HIDCore& hid_core_,
13 KernelHelpers::ServiceContext& service_context_) 13 KernelHelpers::ServiceContext& service_context_)
14 : HidbusBase(service_context_) { 14 : HidbusBase(service_context_) {
15 input = hid_core_.GetEmulatedDevices(); 15 input = hid_core_.GetEmulatedController(Core::HID::NpadIdType::Player1);
16} 16}
17 17
18RingController::~RingController() = default; 18RingController::~RingController() = default;
19 19
20void RingController::OnInit() { 20void RingController::OnInit() {
21 input->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
22 Common::Input::PollingMode::Ring);
21 return; 23 return;
22} 24}
23 25
24void RingController::OnRelease() { 26void RingController::OnRelease() {
27 input->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
28 Common::Input::PollingMode::Active);
25 return; 29 return;
26}; 30};
27 31
diff --git a/src/core/hle/service/hid/hidbus/ringcon.h b/src/core/hle/service/hid/hidbus/ringcon.h
index b37df50ac..845ce85a5 100644
--- a/src/core/hle/service/hid/hidbus/ringcon.h
+++ b/src/core/hle/service/hid/hidbus/ringcon.h
@@ -9,7 +9,7 @@
9#include "core/hle/service/hid/hidbus/hidbus_base.h" 9#include "core/hle/service/hid/hidbus/hidbus_base.h"
10 10
11namespace Core::HID { 11namespace Core::HID {
12class EmulatedDevices; 12class EmulatedController;
13} // namespace Core::HID 13} // namespace Core::HID
14 14
15namespace Service::HID { 15namespace Service::HID {
@@ -248,6 +248,6 @@ private:
248 .zero = {.value = idle_value, .crc = 225}, 248 .zero = {.value = idle_value, .crc = 225},
249 }; 249 };
250 250
251 Core::HID::EmulatedDevices* input; 251 Core::HID::EmulatedController* input;
252}; 252};
253} // namespace Service::HID 253} // namespace Service::HID
diff --git a/src/core/hle/service/hid/irs.cpp b/src/core/hle/service/hid/irs.cpp
index 6a3453457..52f402c56 100644
--- a/src/core/hle/service/hid/irs.cpp
+++ b/src/core/hle/service/hid/irs.cpp
@@ -108,6 +108,8 @@ void IRS::StopImageProcessor(Kernel::HLERequestContext& ctx) {
108 auto result = IsIrCameraHandleValid(parameters.camera_handle); 108 auto result = IsIrCameraHandleValid(parameters.camera_handle);
109 if (result.IsSuccess()) { 109 if (result.IsSuccess()) {
110 // TODO: Stop Image processor 110 // TODO: Stop Image processor
111 npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
112 Common::Input::PollingMode::Active);
111 result = ResultSuccess; 113 result = ResultSuccess;
112 } 114 }
113 115
@@ -139,6 +141,8 @@ void IRS::RunMomentProcessor(Kernel::HLERequestContext& ctx) {
139 MakeProcessor<MomentProcessor>(parameters.camera_handle, device); 141 MakeProcessor<MomentProcessor>(parameters.camera_handle, device);
140 auto& image_transfer_processor = GetProcessor<MomentProcessor>(parameters.camera_handle); 142 auto& image_transfer_processor = GetProcessor<MomentProcessor>(parameters.camera_handle);
141 image_transfer_processor.SetConfig(parameters.processor_config); 143 image_transfer_processor.SetConfig(parameters.processor_config);
144 npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
145 Common::Input::PollingMode::IR);
142 } 146 }
143 147
144 IPC::ResponseBuilder rb{ctx, 2}; 148 IPC::ResponseBuilder rb{ctx, 2};
@@ -170,6 +174,8 @@ void IRS::RunClusteringProcessor(Kernel::HLERequestContext& ctx) {
170 auto& image_transfer_processor = 174 auto& image_transfer_processor =
171 GetProcessor<ClusteringProcessor>(parameters.camera_handle); 175 GetProcessor<ClusteringProcessor>(parameters.camera_handle);
172 image_transfer_processor.SetConfig(parameters.processor_config); 176 image_transfer_processor.SetConfig(parameters.processor_config);
177 npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
178 Common::Input::PollingMode::IR);
173 } 179 }
174 180
175 IPC::ResponseBuilder rb{ctx, 2}; 181 IPC::ResponseBuilder rb{ctx, 2};
@@ -219,6 +225,8 @@ void IRS::RunImageTransferProcessor(Kernel::HLERequestContext& ctx) {
219 GetProcessor<ImageTransferProcessor>(parameters.camera_handle); 225 GetProcessor<ImageTransferProcessor>(parameters.camera_handle);
220 image_transfer_processor.SetConfig(parameters.processor_config); 226 image_transfer_processor.SetConfig(parameters.processor_config);
221 image_transfer_processor.SetTransferMemoryPointer(transfer_memory); 227 image_transfer_processor.SetTransferMemoryPointer(transfer_memory);
228 npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
229 Common::Input::PollingMode::IR);
222 } 230 }
223 231
224 IPC::ResponseBuilder rb{ctx, 2}; 232 IPC::ResponseBuilder rb{ctx, 2};
@@ -294,6 +302,8 @@ void IRS::RunTeraPluginProcessor(Kernel::HLERequestContext& ctx) {
294 auto& image_transfer_processor = 302 auto& image_transfer_processor =
295 GetProcessor<TeraPluginProcessor>(parameters.camera_handle); 303 GetProcessor<TeraPluginProcessor>(parameters.camera_handle);
296 image_transfer_processor.SetConfig(parameters.processor_config); 304 image_transfer_processor.SetConfig(parameters.processor_config);
305 npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
306 Common::Input::PollingMode::IR);
297 } 307 }
298 308
299 IPC::ResponseBuilder rb{ctx, 2}; 309 IPC::ResponseBuilder rb{ctx, 2};
@@ -343,6 +353,8 @@ void IRS::RunPointingProcessor(Kernel::HLERequestContext& ctx) {
343 MakeProcessor<PointingProcessor>(camera_handle, device); 353 MakeProcessor<PointingProcessor>(camera_handle, device);
344 auto& image_transfer_processor = GetProcessor<PointingProcessor>(camera_handle); 354 auto& image_transfer_processor = GetProcessor<PointingProcessor>(camera_handle);
345 image_transfer_processor.SetConfig(processor_config); 355 image_transfer_processor.SetConfig(processor_config);
356 npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
357 Common::Input::PollingMode::IR);
346 } 358 }
347 359
348 IPC::ResponseBuilder rb{ctx, 2}; 360 IPC::ResponseBuilder rb{ctx, 2};
@@ -453,6 +465,8 @@ void IRS::RunImageTransferExProcessor(Kernel::HLERequestContext& ctx) {
453 GetProcessor<ImageTransferProcessor>(parameters.camera_handle); 465 GetProcessor<ImageTransferProcessor>(parameters.camera_handle);
454 image_transfer_processor.SetConfig(parameters.processor_config); 466 image_transfer_processor.SetConfig(parameters.processor_config);
455 image_transfer_processor.SetTransferMemoryPointer(transfer_memory); 467 image_transfer_processor.SetTransferMemoryPointer(transfer_memory);
468 npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
469 Common::Input::PollingMode::IR);
456 } 470 }
457 471
458 IPC::ResponseBuilder rb{ctx, 2}; 472 IPC::ResponseBuilder rb{ctx, 2};
@@ -479,6 +493,8 @@ void IRS::RunIrLedProcessor(Kernel::HLERequestContext& ctx) {
479 MakeProcessor<IrLedProcessor>(camera_handle, device); 493 MakeProcessor<IrLedProcessor>(camera_handle, device);
480 auto& image_transfer_processor = GetProcessor<IrLedProcessor>(camera_handle); 494 auto& image_transfer_processor = GetProcessor<IrLedProcessor>(camera_handle);
481 image_transfer_processor.SetConfig(processor_config); 495 image_transfer_processor.SetConfig(processor_config);
496 npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
497 Common::Input::PollingMode::IR);
482 } 498 }
483 499
484 IPC::ResponseBuilder rb{ctx, 2}; 500 IPC::ResponseBuilder rb{ctx, 2};
@@ -504,6 +520,8 @@ void IRS::StopImageProcessorAsync(Kernel::HLERequestContext& ctx) {
504 auto result = IsIrCameraHandleValid(parameters.camera_handle); 520 auto result = IsIrCameraHandleValid(parameters.camera_handle);
505 if (result.IsSuccess()) { 521 if (result.IsSuccess()) {
506 // TODO: Stop image processor async 522 // TODO: Stop image processor async
523 npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
524 Common::Input::PollingMode::Active);
507 result = ResultSuccess; 525 result = ResultSuccess;
508 } 526 }
509 527
diff --git a/src/core/hle/service/nfc/nfc_device.cpp b/src/core/hle/service/nfc/nfc_device.cpp
index 78578f723..9a3234e8c 100644
--- a/src/core/hle/service/nfc/nfc_device.cpp
+++ b/src/core/hle/service/nfc/nfc_device.cpp
@@ -130,7 +130,9 @@ Result NfcDevice::StartDetection(NFP::TagProtocol allowed_protocol) {
130 return WrongDeviceState; 130 return WrongDeviceState;
131 } 131 }
132 132
133 if (!npad_device->SetPollingMode(Common::Input::PollingMode::NFC)) { 133 if (npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
134 Common::Input::PollingMode::NFC) !=
135 Common::Input::DriverResult::Success) {
134 LOG_ERROR(Service_NFC, "Nfc not supported"); 136 LOG_ERROR(Service_NFC, "Nfc not supported");
135 return NfcDisabled; 137 return NfcDisabled;
136 } 138 }
@@ -141,7 +143,8 @@ Result NfcDevice::StartDetection(NFP::TagProtocol allowed_protocol) {
141} 143}
142 144
143Result NfcDevice::StopDetection() { 145Result NfcDevice::StopDetection() {
144 npad_device->SetPollingMode(Common::Input::PollingMode::Active); 146 npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
147 Common::Input::PollingMode::Active);
145 148
146 if (device_state == NFP::DeviceState::Initialized) { 149 if (device_state == NFP::DeviceState::Initialized) {
147 return ResultSuccess; 150 return ResultSuccess;
diff --git a/src/core/hle/service/nfp/nfp_device.cpp b/src/core/hle/service/nfp/nfp_device.cpp
index c860fd1a1..e67a76f55 100644
--- a/src/core/hle/service/nfp/nfp_device.cpp
+++ b/src/core/hle/service/nfp/nfp_device.cpp
@@ -152,7 +152,9 @@ Result NfpDevice::StartDetection(TagProtocol allowed_protocol) {
152 return WrongDeviceState; 152 return WrongDeviceState;
153 } 153 }
154 154
155 if (!npad_device->SetPollingMode(Common::Input::PollingMode::NFC)) { 155 if (npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
156 Common::Input::PollingMode::NFC) !=
157 Common::Input::DriverResult::Success) {
156 LOG_ERROR(Service_NFP, "Nfc not supported"); 158 LOG_ERROR(Service_NFP, "Nfc not supported");
157 return NfcDisabled; 159 return NfcDisabled;
158 } 160 }
@@ -163,7 +165,8 @@ Result NfpDevice::StartDetection(TagProtocol allowed_protocol) {
163} 165}
164 166
165Result NfpDevice::StopDetection() { 167Result NfpDevice::StopDetection() {
166 npad_device->SetPollingMode(Common::Input::PollingMode::Active); 168 npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
169 Common::Input::PollingMode::Active);
167 170
168 if (device_state == DeviceState::Initialized) { 171 if (device_state == DeviceState::Initialized) {
169 return ResultSuccess; 172 return ResultSuccess;
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index cef2c4d52..e3b627e4f 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -51,8 +51,29 @@ endif()
51 51
52if (ENABLE_SDL2) 52if (ENABLE_SDL2)
53 target_sources(input_common PRIVATE 53 target_sources(input_common PRIVATE
54 drivers/joycon.cpp
55 drivers/joycon.h
54 drivers/sdl_driver.cpp 56 drivers/sdl_driver.cpp
55 drivers/sdl_driver.h 57 drivers/sdl_driver.h
58 helpers/joycon_driver.cpp
59 helpers/joycon_driver.h
60 helpers/joycon_protocol/calibration.cpp
61 helpers/joycon_protocol/calibration.h
62 helpers/joycon_protocol/common_protocol.cpp
63 helpers/joycon_protocol/common_protocol.h
64 helpers/joycon_protocol/generic_functions.cpp
65 helpers/joycon_protocol/generic_functions.h
66 helpers/joycon_protocol/joycon_types.h
67 helpers/joycon_protocol/irs.cpp
68 helpers/joycon_protocol/irs.h
69 helpers/joycon_protocol/nfc.cpp
70 helpers/joycon_protocol/nfc.h
71 helpers/joycon_protocol/poller.cpp
72 helpers/joycon_protocol/poller.h
73 helpers/joycon_protocol/ringcon.cpp
74 helpers/joycon_protocol/ringcon.h
75 helpers/joycon_protocol/rumble.cpp
76 helpers/joycon_protocol/rumble.h
56 ) 77 )
57 target_link_libraries(input_common PRIVATE SDL2::SDL2) 78 target_link_libraries(input_common PRIVATE SDL2::SDL2)
58 target_compile_definitions(input_common PRIVATE HAVE_SDL2) 79 target_compile_definitions(input_common PRIVATE HAVE_SDL2)
diff --git a/src/input_common/drivers/camera.cpp b/src/input_common/drivers/camera.cpp
index fad9177dc..04970f635 100644
--- a/src/input_common/drivers/camera.cpp
+++ b/src/input_common/drivers/camera.cpp
@@ -72,11 +72,11 @@ std::size_t Camera::getImageHeight() const {
72 } 72 }
73} 73}
74 74
75Common::Input::CameraError Camera::SetCameraFormat( 75Common::Input::DriverResult Camera::SetCameraFormat(
76 [[maybe_unused]] const PadIdentifier& identifier_, 76 [[maybe_unused]] const PadIdentifier& identifier_,
77 const Common::Input::CameraFormat camera_format) { 77 const Common::Input::CameraFormat camera_format) {
78 status.format = camera_format; 78 status.format = camera_format;
79 return Common::Input::CameraError::None; 79 return Common::Input::DriverResult::Success;
80} 80}
81 81
82} // namespace InputCommon 82} // namespace InputCommon
diff --git a/src/input_common/drivers/camera.h b/src/input_common/drivers/camera.h
index ead3e0fde..24b27e325 100644
--- a/src/input_common/drivers/camera.h
+++ b/src/input_common/drivers/camera.h
@@ -22,8 +22,8 @@ public:
22 std::size_t getImageWidth() const; 22 std::size_t getImageWidth() const;
23 std::size_t getImageHeight() const; 23 std::size_t getImageHeight() const;
24 24
25 Common::Input::CameraError SetCameraFormat(const PadIdentifier& identifier_, 25 Common::Input::DriverResult SetCameraFormat(const PadIdentifier& identifier_,
26 Common::Input::CameraFormat camera_format) override; 26 Common::Input::CameraFormat camera_format) override;
27 27
28private: 28private:
29 Common::Input::CameraStatus status{}; 29 Common::Input::CameraStatus status{};
diff --git a/src/input_common/drivers/gc_adapter.cpp b/src/input_common/drivers/gc_adapter.cpp
index 826fa2109..ecb3e9dc2 100644
--- a/src/input_common/drivers/gc_adapter.cpp
+++ b/src/input_common/drivers/gc_adapter.cpp
@@ -324,7 +324,7 @@ bool GCAdapter::GetGCEndpoint(libusb_device* device) {
324 return true; 324 return true;
325} 325}
326 326
327Common::Input::VibrationError GCAdapter::SetVibration( 327Common::Input::DriverResult GCAdapter::SetVibration(
328 const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) { 328 const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) {
329 const auto mean_amplitude = (vibration.low_amplitude + vibration.high_amplitude) * 0.5f; 329 const auto mean_amplitude = (vibration.low_amplitude + vibration.high_amplitude) * 0.5f;
330 const auto processed_amplitude = 330 const auto processed_amplitude =
@@ -333,9 +333,9 @@ Common::Input::VibrationError GCAdapter::SetVibration(
333 pads[identifier.port].rumble_amplitude = processed_amplitude; 333 pads[identifier.port].rumble_amplitude = processed_amplitude;
334 334
335 if (!rumble_enabled) { 335 if (!rumble_enabled) {
336 return Common::Input::VibrationError::Disabled; 336 return Common::Input::DriverResult::Disabled;
337 } 337 }
338 return Common::Input::VibrationError::None; 338 return Common::Input::DriverResult::Success;
339} 339}
340 340
341bool GCAdapter::IsVibrationEnabled([[maybe_unused]] const PadIdentifier& identifier) { 341bool GCAdapter::IsVibrationEnabled([[maybe_unused]] const PadIdentifier& identifier) {
diff --git a/src/input_common/drivers/gc_adapter.h b/src/input_common/drivers/gc_adapter.h
index b5270fd0b..3c2eb376d 100644
--- a/src/input_common/drivers/gc_adapter.h
+++ b/src/input_common/drivers/gc_adapter.h
@@ -25,7 +25,7 @@ public:
25 explicit GCAdapter(std::string input_engine_); 25 explicit GCAdapter(std::string input_engine_);
26 ~GCAdapter() override; 26 ~GCAdapter() override;
27 27
28 Common::Input::VibrationError SetVibration( 28 Common::Input::DriverResult SetVibration(
29 const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override; 29 const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override;
30 30
31 bool IsVibrationEnabled(const PadIdentifier& identifier) override; 31 bool IsVibrationEnabled(const PadIdentifier& identifier) override;
diff --git a/src/input_common/drivers/joycon.cpp b/src/input_common/drivers/joycon.cpp
new file mode 100644
index 000000000..7122093c6
--- /dev/null
+++ b/src/input_common/drivers/joycon.cpp
@@ -0,0 +1,677 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <fmt/format.h>
5
6#include "common/param_package.h"
7#include "common/settings.h"
8#include "common/thread.h"
9#include "input_common/drivers/joycon.h"
10#include "input_common/helpers/joycon_driver.h"
11#include "input_common/helpers/joycon_protocol/joycon_types.h"
12
13namespace InputCommon {
14
15Joycons::Joycons(const std::string& input_engine_) : InputEngine(input_engine_) {
16 // Avoid conflicting with SDL driver
17 if (!Settings::values.enable_joycon_driver) {
18 return;
19 }
20 LOG_INFO(Input, "Joycon driver Initialization started");
21 const int init_res = SDL_hid_init();
22 if (init_res == 0) {
23 Setup();
24 } else {
25 LOG_ERROR(Input, "Hidapi could not be initialized. failed with error = {}", init_res);
26 }
27}
28
29Joycons::~Joycons() {
30 Reset();
31}
32
33void Joycons::Reset() {
34 scan_thread = {};
35 for (const auto& device : left_joycons) {
36 if (!device) {
37 continue;
38 }
39 device->Stop();
40 }
41 for (const auto& device : right_joycons) {
42 if (!device) {
43 continue;
44 }
45 device->Stop();
46 }
47 SDL_hid_exit();
48}
49
50void Joycons::Setup() {
51 u32 port = 0;
52 PreSetController(GetIdentifier(0, Joycon::ControllerType::None));
53 for (auto& device : left_joycons) {
54 PreSetController(GetIdentifier(port, Joycon::ControllerType::Left));
55 device = std::make_shared<Joycon::JoyconDriver>(port++);
56 }
57 port = 0;
58 for (auto& device : right_joycons) {
59 PreSetController(GetIdentifier(port, Joycon::ControllerType::Right));
60 device = std::make_shared<Joycon::JoyconDriver>(port++);
61 }
62
63 scan_thread = std::jthread([this](std::stop_token stop_token) { ScanThread(stop_token); });
64}
65
66void Joycons::ScanThread(std::stop_token stop_token) {
67 constexpr u16 nintendo_vendor_id = 0x057e;
68 Common::SetCurrentThreadName("JoyconScanThread");
69 while (!stop_token.stop_requested()) {
70 SDL_hid_device_info* devs = SDL_hid_enumerate(nintendo_vendor_id, 0x0);
71 SDL_hid_device_info* cur_dev = devs;
72
73 while (cur_dev) {
74 if (IsDeviceNew(cur_dev)) {
75 LOG_DEBUG(Input, "Device Found,type : {:04X} {:04X}", cur_dev->vendor_id,
76 cur_dev->product_id);
77 RegisterNewDevice(cur_dev);
78 }
79 cur_dev = cur_dev->next;
80 }
81
82 SDL_hid_free_enumeration(devs);
83 std::this_thread::sleep_for(std::chrono::seconds(5));
84 }
85}
86
87bool Joycons::IsDeviceNew(SDL_hid_device_info* device_info) const {
88 Joycon::ControllerType type{};
89 Joycon::SerialNumber serial_number{};
90
91 const auto result = Joycon::JoyconDriver::GetDeviceType(device_info, type);
92 if (result != Joycon::DriverResult::Success) {
93 return false;
94 }
95
96 const auto result2 = Joycon::JoyconDriver::GetSerialNumber(device_info, serial_number);
97 if (result2 != Joycon::DriverResult::Success) {
98 return false;
99 }
100
101 auto is_handle_identical = [serial_number](std::shared_ptr<Joycon::JoyconDriver> device) {
102 if (!device) {
103 return false;
104 }
105 if (!device->IsConnected()) {
106 return false;
107 }
108 if (device->GetHandleSerialNumber() != serial_number) {
109 return false;
110 }
111 return true;
112 };
113
114 // Check if device already exist
115 switch (type) {
116 case Joycon::ControllerType::Left:
117 for (const auto& device : left_joycons) {
118 if (is_handle_identical(device)) {
119 return false;
120 }
121 }
122 break;
123 case Joycon::ControllerType::Right:
124 for (const auto& device : right_joycons) {
125 if (is_handle_identical(device)) {
126 return false;
127 }
128 }
129 break;
130 default:
131 return false;
132 }
133
134 return true;
135}
136
137void Joycons::RegisterNewDevice(SDL_hid_device_info* device_info) {
138 Joycon::ControllerType type{};
139 auto result = Joycon::JoyconDriver::GetDeviceType(device_info, type);
140 auto handle = GetNextFreeHandle(type);
141 if (handle == nullptr) {
142 LOG_WARNING(Input, "No free handles available");
143 return;
144 }
145 if (result == Joycon::DriverResult::Success) {
146 result = handle->RequestDeviceAccess(device_info);
147 }
148 if (result == Joycon::DriverResult::Success) {
149 LOG_WARNING(Input, "Initialize device");
150
151 const std::size_t port = handle->GetDevicePort();
152 const Joycon::JoyconCallbacks callbacks{
153 .on_battery_data = {[this, port, type](Joycon::Battery value) {
154 OnBatteryUpdate(port, type, value);
155 }},
156 .on_color_data = {[this, port, type](Joycon::Color value) {
157 OnColorUpdate(port, type, value);
158 }},
159 .on_button_data = {[this, port, type](int id, bool value) {
160 OnButtonUpdate(port, type, id, value);
161 }},
162 .on_stick_data = {[this, port, type](int id, f32 value) {
163 OnStickUpdate(port, type, id, value);
164 }},
165 .on_motion_data = {[this, port, type](int id, const Joycon::MotionData& value) {
166 OnMotionUpdate(port, type, id, value);
167 }},
168 .on_ring_data = {[this](f32 ring_data) { OnRingConUpdate(ring_data); }},
169 .on_amiibo_data = {[this, port](const std::vector<u8>& amiibo_data) {
170 OnAmiiboUpdate(port, amiibo_data);
171 }},
172 .on_camera_data = {[this, port](const std::vector<u8>& camera_data,
173 Joycon::IrsResolution format) {
174 OnCameraUpdate(port, camera_data, format);
175 }},
176 };
177
178 handle->InitializeDevice();
179 handle->SetCallbacks(callbacks);
180 }
181}
182
183std::shared_ptr<Joycon::JoyconDriver> Joycons::GetNextFreeHandle(
184 Joycon::ControllerType type) const {
185 if (type == Joycon::ControllerType::Left) {
186 const auto unconnected_device =
187 std::ranges::find_if(left_joycons, [](auto& device) { return !device->IsConnected(); });
188 if (unconnected_device != left_joycons.end()) {
189 return *unconnected_device;
190 }
191 }
192 if (type == Joycon::ControllerType::Right) {
193 const auto unconnected_device = std::ranges::find_if(
194 right_joycons, [](auto& device) { return !device->IsConnected(); });
195
196 if (unconnected_device != right_joycons.end()) {
197 return *unconnected_device;
198 }
199 }
200 return nullptr;
201}
202
203bool Joycons::IsVibrationEnabled(const PadIdentifier& identifier) {
204 const auto handle = GetHandle(identifier);
205 if (handle == nullptr) {
206 return false;
207 }
208 return handle->IsVibrationEnabled();
209}
210
211Common::Input::DriverResult Joycons::SetVibration(const PadIdentifier& identifier,
212 const Common::Input::VibrationStatus& vibration) {
213 const Joycon::VibrationValue native_vibration{
214 .low_amplitude = vibration.low_amplitude,
215 .low_frequency = vibration.low_frequency,
216 .high_amplitude = vibration.high_amplitude,
217 .high_frequency = vibration.high_frequency,
218 };
219 auto handle = GetHandle(identifier);
220 if (handle == nullptr) {
221 return Common::Input::DriverResult::InvalidHandle;
222 }
223
224 handle->SetVibration(native_vibration);
225 return Common::Input::DriverResult::Success;
226}
227
228Common::Input::DriverResult Joycons::SetLeds(const PadIdentifier& identifier,
229 const Common::Input::LedStatus& led_status) {
230 auto handle = GetHandle(identifier);
231 if (handle == nullptr) {
232 return Common::Input::DriverResult::InvalidHandle;
233 }
234 int led_config = led_status.led_1 ? 1 : 0;
235 led_config += led_status.led_2 ? 2 : 0;
236 led_config += led_status.led_3 ? 4 : 0;
237 led_config += led_status.led_4 ? 8 : 0;
238
239 return static_cast<Common::Input::DriverResult>(
240 handle->SetLedConfig(static_cast<u8>(led_config)));
241}
242
243Common::Input::DriverResult Joycons::SetCameraFormat(const PadIdentifier& identifier,
244 Common::Input::CameraFormat camera_format) {
245 auto handle = GetHandle(identifier);
246 if (handle == nullptr) {
247 return Common::Input::DriverResult::InvalidHandle;
248 }
249 return static_cast<Common::Input::DriverResult>(handle->SetIrsConfig(
250 Joycon::IrsMode::ImageTransfer, static_cast<Joycon::IrsResolution>(camera_format)));
251};
252
253Common::Input::NfcState Joycons::SupportsNfc(const PadIdentifier& identifier_) const {
254 return Common::Input::NfcState::Success;
255};
256
257Common::Input::NfcState Joycons::WriteNfcData(const PadIdentifier& identifier_,
258 const std::vector<u8>& data) {
259 return Common::Input::NfcState::NotSupported;
260};
261
262Common::Input::DriverResult Joycons::SetPollingMode(const PadIdentifier& identifier,
263 const Common::Input::PollingMode polling_mode) {
264 auto handle = GetHandle(identifier);
265 if (handle == nullptr) {
266 LOG_ERROR(Input, "Invalid handle {}", identifier.port);
267 return Common::Input::DriverResult::InvalidHandle;
268 }
269
270 switch (polling_mode) {
271 case Common::Input::PollingMode::Active:
272 return static_cast<Common::Input::DriverResult>(handle->SetActiveMode());
273 case Common::Input::PollingMode::Pasive:
274 return static_cast<Common::Input::DriverResult>(handle->SetPasiveMode());
275 case Common::Input::PollingMode::IR:
276 return static_cast<Common::Input::DriverResult>(handle->SetIrMode());
277 case Common::Input::PollingMode::NFC:
278 return static_cast<Common::Input::DriverResult>(handle->SetNfcMode());
279 case Common::Input::PollingMode::Ring:
280 return static_cast<Common::Input::DriverResult>(handle->SetRingConMode());
281 default:
282 return Common::Input::DriverResult::NotSupported;
283 }
284}
285
286void Joycons::OnBatteryUpdate(std::size_t port, Joycon::ControllerType type,
287 Joycon::Battery value) {
288 const auto identifier = GetIdentifier(port, type);
289 if (value.charging != 0) {
290 SetBattery(identifier, Common::Input::BatteryLevel::Charging);
291 return;
292 }
293
294 Common::Input::BatteryLevel battery{};
295 switch (value.status) {
296 case 0:
297 battery = Common::Input::BatteryLevel::Empty;
298 break;
299 case 1:
300 battery = Common::Input::BatteryLevel::Critical;
301 break;
302 case 2:
303 battery = Common::Input::BatteryLevel::Low;
304 break;
305 case 3:
306 battery = Common::Input::BatteryLevel::Medium;
307 break;
308 case 4:
309 default:
310 battery = Common::Input::BatteryLevel::Full;
311 break;
312 }
313 SetBattery(identifier, battery);
314}
315
316void Joycons::OnColorUpdate(std::size_t port, Joycon::ControllerType type,
317 const Joycon::Color& value) {
318 const auto identifier = GetIdentifier(port, type);
319 Common::Input::BodyColorStatus color{
320 .body = value.body,
321 .buttons = value.buttons,
322 .left_grip = value.left_grip,
323 .right_grip = value.right_grip,
324 };
325 SetColor(identifier, color);
326}
327
328void Joycons::OnButtonUpdate(std::size_t port, Joycon::ControllerType type, int id, bool value) {
329 const auto identifier = GetIdentifier(port, type);
330 SetButton(identifier, id, value);
331}
332
333void Joycons::OnStickUpdate(std::size_t port, Joycon::ControllerType type, int id, f32 value) {
334 const auto identifier = GetIdentifier(port, type);
335 SetAxis(identifier, id, value);
336}
337
338void Joycons::OnMotionUpdate(std::size_t port, Joycon::ControllerType type, int id,
339 const Joycon::MotionData& value) {
340 const auto identifier = GetIdentifier(port, type);
341 BasicMotion motion_data{
342 .gyro_x = value.gyro_x,
343 .gyro_y = value.gyro_y,
344 .gyro_z = value.gyro_z,
345 .accel_x = value.accel_x,
346 .accel_y = value.accel_y,
347 .accel_z = value.accel_z,
348 .delta_timestamp = 15000,
349 };
350 SetMotion(identifier, id, motion_data);
351}
352
353void Joycons::OnRingConUpdate(f32 ring_data) {
354 // To simplify ring detection it will always be mapped to an empty identifier for all
355 // controllers
356 constexpr PadIdentifier identifier = {
357 .guid = Common::UUID{},
358 .port = 0,
359 .pad = 0,
360 };
361 SetAxis(identifier, 100, ring_data);
362}
363
364void Joycons::OnAmiiboUpdate(std::size_t port, const std::vector<u8>& amiibo_data) {
365 const auto identifier = GetIdentifier(port, Joycon::ControllerType::Right);
366 const auto nfc_state = amiibo_data.empty() ? Common::Input::NfcState::AmiiboRemoved
367 : Common::Input::NfcState::NewAmiibo;
368 SetNfc(identifier, {nfc_state, amiibo_data});
369}
370
371void Joycons::OnCameraUpdate(std::size_t port, const std::vector<u8>& camera_data,
372 Joycon::IrsResolution format) {
373 const auto identifier = GetIdentifier(port, Joycon::ControllerType::Right);
374 SetCamera(identifier, {static_cast<Common::Input::CameraFormat>(format), camera_data});
375}
376
377std::shared_ptr<Joycon::JoyconDriver> Joycons::GetHandle(PadIdentifier identifier) const {
378 auto is_handle_active = [&](std::shared_ptr<Joycon::JoyconDriver> device) {
379 if (!device) {
380 return false;
381 }
382 if (!device->IsConnected()) {
383 return false;
384 }
385 if (device->GetDevicePort() == identifier.port) {
386 return true;
387 }
388 return false;
389 };
390 const auto type = static_cast<Joycon::ControllerType>(identifier.pad);
391
392 if (type == Joycon::ControllerType::Left) {
393 const auto matching_device = std::ranges::find_if(
394 left_joycons, [is_handle_active](auto& device) { return is_handle_active(device); });
395
396 if (matching_device != left_joycons.end()) {
397 return *matching_device;
398 }
399 }
400
401 if (type == Joycon::ControllerType::Right) {
402 const auto matching_device = std::ranges::find_if(
403 right_joycons, [is_handle_active](auto& device) { return is_handle_active(device); });
404
405 if (matching_device != right_joycons.end()) {
406 return *matching_device;
407 }
408 }
409
410 return nullptr;
411}
412
413PadIdentifier Joycons::GetIdentifier(std::size_t port, Joycon::ControllerType type) const {
414 const std::array<u8, 16> guid{0, 0, 0, 0, 0, 0, 0, 0,
415 0, 0, 0, 0, 0, 0, 0, static_cast<u8>(type)};
416 return {
417 .guid = Common::UUID{guid},
418 .port = port,
419 .pad = static_cast<std::size_t>(type),
420 };
421}
422
423Common::ParamPackage Joycons::GetParamPackage(std::size_t port, Joycon::ControllerType type) const {
424 const auto identifier = GetIdentifier(port, type);
425 return {
426 {"engine", GetEngineName()},
427 {"guid", identifier.guid.RawString()},
428 {"port", std::to_string(identifier.port)},
429 {"pad", std::to_string(identifier.pad)},
430 };
431}
432
433std::vector<Common::ParamPackage> Joycons::GetInputDevices() const {
434 std::vector<Common::ParamPackage> devices{};
435
436 auto add_entry = [&](std::shared_ptr<Joycon::JoyconDriver> device) {
437 if (!device) {
438 return;
439 }
440 if (!device->IsConnected()) {
441 return;
442 }
443 auto param = GetParamPackage(device->GetDevicePort(), device->GetHandleDeviceType());
444 std::string name = fmt::format("{} {}", JoyconName(device->GetHandleDeviceType()),
445 device->GetDevicePort() + 1);
446 param.Set("display", std::move(name));
447 devices.emplace_back(param);
448 };
449
450 for (const auto& controller : left_joycons) {
451 add_entry(controller);
452 }
453 for (const auto& controller : right_joycons) {
454 add_entry(controller);
455 }
456
457 // List dual joycon pairs
458 for (std::size_t i = 0; i < MaxSupportedControllers; i++) {
459 if (!left_joycons[i] || !right_joycons[i]) {
460 continue;
461 }
462 if (!left_joycons[i]->IsConnected() || !right_joycons[i]->IsConnected()) {
463 continue;
464 }
465 auto main_param = GetParamPackage(i, left_joycons[i]->GetHandleDeviceType());
466 const auto second_param = GetParamPackage(i, right_joycons[i]->GetHandleDeviceType());
467 const auto type = Joycon::ControllerType::Dual;
468 std::string name = fmt::format("{} {}", JoyconName(type), i + 1);
469
470 main_param.Set("display", std::move(name));
471 main_param.Set("guid2", second_param.Get("guid", ""));
472 main_param.Set("pad", std::to_string(static_cast<size_t>(type)));
473 devices.emplace_back(main_param);
474 }
475
476 return devices;
477}
478
479ButtonMapping Joycons::GetButtonMappingForDevice(const Common::ParamPackage& params) {
480 static constexpr std::array<std::tuple<Settings::NativeButton::Values, Joycon::PadButton, bool>,
481 18>
482 switch_to_joycon_button = {
483 std::tuple{Settings::NativeButton::A, Joycon::PadButton::A, true},
484 {Settings::NativeButton::B, Joycon::PadButton::B, true},
485 {Settings::NativeButton::X, Joycon::PadButton::X, true},
486 {Settings::NativeButton::Y, Joycon::PadButton::Y, true},
487 {Settings::NativeButton::DLeft, Joycon::PadButton::Left, false},
488 {Settings::NativeButton::DUp, Joycon::PadButton::Up, false},
489 {Settings::NativeButton::DRight, Joycon::PadButton::Right, false},
490 {Settings::NativeButton::DDown, Joycon::PadButton::Down, false},
491 {Settings::NativeButton::L, Joycon::PadButton::L, false},
492 {Settings::NativeButton::R, Joycon::PadButton::R, true},
493 {Settings::NativeButton::ZL, Joycon::PadButton::ZL, false},
494 {Settings::NativeButton::ZR, Joycon::PadButton::ZR, true},
495 {Settings::NativeButton::Plus, Joycon::PadButton::Plus, true},
496 {Settings::NativeButton::Minus, Joycon::PadButton::Minus, false},
497 {Settings::NativeButton::Home, Joycon::PadButton::Home, true},
498 {Settings::NativeButton::Screenshot, Joycon::PadButton::Capture, false},
499 {Settings::NativeButton::LStick, Joycon::PadButton::StickL, false},
500 {Settings::NativeButton::RStick, Joycon::PadButton::StickR, true},
501 };
502
503 if (!params.Has("port")) {
504 return {};
505 }
506
507 ButtonMapping mapping{};
508 for (const auto& [switch_button, joycon_button, side] : switch_to_joycon_button) {
509 const std::size_t port = static_cast<std::size_t>(params.Get("port", 0));
510 auto pad = static_cast<Joycon::ControllerType>(params.Get("pad", 0));
511 if (pad == Joycon::ControllerType::Dual) {
512 pad = side ? Joycon::ControllerType::Right : Joycon::ControllerType::Left;
513 }
514
515 Common::ParamPackage button_params = GetParamPackage(port, pad);
516 button_params.Set("button", static_cast<int>(joycon_button));
517 mapping.insert_or_assign(switch_button, std::move(button_params));
518 }
519
520 // Map SL and SR buttons for left joycons
521 if (params.Get("pad", 0) == static_cast<int>(Joycon::ControllerType::Left)) {
522 const std::size_t port = static_cast<std::size_t>(params.Get("port", 0));
523 Common::ParamPackage button_params = GetParamPackage(port, Joycon::ControllerType::Left);
524
525 Common::ParamPackage sl_button_params = button_params;
526 Common::ParamPackage sr_button_params = button_params;
527 sl_button_params.Set("button", static_cast<int>(Joycon::PadButton::LeftSL));
528 sr_button_params.Set("button", static_cast<int>(Joycon::PadButton::LeftSR));
529 mapping.insert_or_assign(Settings::NativeButton::SL, std::move(sl_button_params));
530 mapping.insert_or_assign(Settings::NativeButton::SR, std::move(sr_button_params));
531 }
532
533 // Map SL and SR buttons for right joycons
534 if (params.Get("pad", 0) == static_cast<int>(Joycon::ControllerType::Right)) {
535 const std::size_t port = static_cast<std::size_t>(params.Get("port", 0));
536 Common::ParamPackage button_params = GetParamPackage(port, Joycon::ControllerType::Right);
537
538 Common::ParamPackage sl_button_params = button_params;
539 Common::ParamPackage sr_button_params = button_params;
540 sl_button_params.Set("button", static_cast<int>(Joycon::PadButton::RightSL));
541 sr_button_params.Set("button", static_cast<int>(Joycon::PadButton::RightSR));
542 mapping.insert_or_assign(Settings::NativeButton::SL, std::move(sl_button_params));
543 mapping.insert_or_assign(Settings::NativeButton::SR, std::move(sr_button_params));
544 }
545
546 return mapping;
547}
548
549AnalogMapping Joycons::GetAnalogMappingForDevice(const Common::ParamPackage& params) {
550 if (!params.Has("port")) {
551 return {};
552 }
553
554 const std::size_t port = static_cast<std::size_t>(params.Get("port", 0));
555 auto pad_left = static_cast<Joycon::ControllerType>(params.Get("pad", 0));
556 auto pad_right = pad_left;
557 if (pad_left == Joycon::ControllerType::Dual) {
558 pad_left = Joycon::ControllerType::Left;
559 pad_right = Joycon::ControllerType::Right;
560 }
561
562 AnalogMapping mapping = {};
563 Common::ParamPackage left_analog_params = GetParamPackage(port, pad_left);
564 left_analog_params.Set("axis_x", static_cast<int>(Joycon::PadAxes::LeftStickX));
565 left_analog_params.Set("axis_y", static_cast<int>(Joycon::PadAxes::LeftStickY));
566 mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params));
567 Common::ParamPackage right_analog_params = GetParamPackage(port, pad_right);
568 right_analog_params.Set("axis_x", static_cast<int>(Joycon::PadAxes::RightStickX));
569 right_analog_params.Set("axis_y", static_cast<int>(Joycon::PadAxes::RightStickY));
570 mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params));
571 return mapping;
572}
573
574MotionMapping Joycons::GetMotionMappingForDevice(const Common::ParamPackage& params) {
575 if (!params.Has("port")) {
576 return {};
577 }
578
579 const std::size_t port = static_cast<std::size_t>(params.Get("port", 0));
580 auto pad_left = static_cast<Joycon::ControllerType>(params.Get("pad", 0));
581 auto pad_right = pad_left;
582 if (pad_left == Joycon::ControllerType::Dual) {
583 pad_left = Joycon::ControllerType::Left;
584 pad_right = Joycon::ControllerType::Right;
585 }
586
587 MotionMapping mapping = {};
588 Common::ParamPackage left_motion_params = GetParamPackage(port, pad_left);
589 left_motion_params.Set("motion", 0);
590 mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, std::move(left_motion_params));
591 Common::ParamPackage right_Motion_params = GetParamPackage(port, pad_right);
592 right_Motion_params.Set("motion", 1);
593 mapping.insert_or_assign(Settings::NativeMotion::MotionRight, std::move(right_Motion_params));
594 return mapping;
595}
596
597Common::Input::ButtonNames Joycons::GetUIButtonName(const Common::ParamPackage& params) const {
598 const auto button = static_cast<Joycon::PadButton>(params.Get("button", 0));
599 switch (button) {
600 case Joycon::PadButton::Left:
601 return Common::Input::ButtonNames::ButtonLeft;
602 case Joycon::PadButton::Right:
603 return Common::Input::ButtonNames::ButtonRight;
604 case Joycon::PadButton::Down:
605 return Common::Input::ButtonNames::ButtonDown;
606 case Joycon::PadButton::Up:
607 return Common::Input::ButtonNames::ButtonUp;
608 case Joycon::PadButton::LeftSL:
609 case Joycon::PadButton::RightSL:
610 return Common::Input::ButtonNames::TriggerSL;
611 case Joycon::PadButton::LeftSR:
612 case Joycon::PadButton::RightSR:
613 return Common::Input::ButtonNames::TriggerSR;
614 case Joycon::PadButton::L:
615 return Common::Input::ButtonNames::TriggerL;
616 case Joycon::PadButton::R:
617 return Common::Input::ButtonNames::TriggerR;
618 case Joycon::PadButton::ZL:
619 return Common::Input::ButtonNames::TriggerZL;
620 case Joycon::PadButton::ZR:
621 return Common::Input::ButtonNames::TriggerZR;
622 case Joycon::PadButton::A:
623 return Common::Input::ButtonNames::ButtonA;
624 case Joycon::PadButton::B:
625 return Common::Input::ButtonNames::ButtonB;
626 case Joycon::PadButton::X:
627 return Common::Input::ButtonNames::ButtonX;
628 case Joycon::PadButton::Y:
629 return Common::Input::ButtonNames::ButtonY;
630 case Joycon::PadButton::Plus:
631 return Common::Input::ButtonNames::ButtonPlus;
632 case Joycon::PadButton::Minus:
633 return Common::Input::ButtonNames::ButtonMinus;
634 case Joycon::PadButton::Home:
635 return Common::Input::ButtonNames::ButtonHome;
636 case Joycon::PadButton::Capture:
637 return Common::Input::ButtonNames::ButtonCapture;
638 case Joycon::PadButton::StickL:
639 return Common::Input::ButtonNames::ButtonStickL;
640 case Joycon::PadButton::StickR:
641 return Common::Input::ButtonNames::ButtonStickR;
642 default:
643 return Common::Input::ButtonNames::Undefined;
644 }
645}
646
647Common::Input::ButtonNames Joycons::GetUIName(const Common::ParamPackage& params) const {
648 if (params.Has("button")) {
649 return GetUIButtonName(params);
650 }
651 if (params.Has("axis")) {
652 return Common::Input::ButtonNames::Value;
653 }
654 if (params.Has("motion")) {
655 return Common::Input::ButtonNames::Engine;
656 }
657
658 return Common::Input::ButtonNames::Invalid;
659}
660
661std::string Joycons::JoyconName(Joycon::ControllerType type) const {
662 switch (type) {
663 case Joycon::ControllerType::Left:
664 return "Left Joycon";
665 case Joycon::ControllerType::Right:
666 return "Right Joycon";
667 case Joycon::ControllerType::Pro:
668 return "Pro Controller";
669 case Joycon::ControllerType::Grip:
670 return "Grip Controller";
671 case Joycon::ControllerType::Dual:
672 return "Dual Joycon";
673 default:
674 return "Unknown Joycon";
675 }
676}
677} // namespace InputCommon
diff --git a/src/input_common/drivers/joycon.h b/src/input_common/drivers/joycon.h
new file mode 100644
index 000000000..316d383d8
--- /dev/null
+++ b/src/input_common/drivers/joycon.h
@@ -0,0 +1,111 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <span>
8#include <thread>
9#include <SDL_hidapi.h>
10
11#include "input_common/input_engine.h"
12
13namespace InputCommon::Joycon {
14using SerialNumber = std::array<u8, 15>;
15struct Battery;
16struct Color;
17struct MotionData;
18enum class ControllerType;
19enum class DriverResult;
20enum class IrsResolution;
21class JoyconDriver;
22} // namespace InputCommon::Joycon
23
24namespace InputCommon {
25
26class Joycons final : public InputCommon::InputEngine {
27public:
28 explicit Joycons(const std::string& input_engine_);
29
30 ~Joycons();
31
32 bool IsVibrationEnabled(const PadIdentifier& identifier) override;
33 Common::Input::DriverResult SetVibration(
34 const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override;
35
36 Common::Input::DriverResult SetLeds(const PadIdentifier& identifier,
37 const Common::Input::LedStatus& led_status) override;
38
39 Common::Input::DriverResult SetCameraFormat(const PadIdentifier& identifier,
40 Common::Input::CameraFormat camera_format) override;
41
42 Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier_) const override;
43 Common::Input::NfcState WriteNfcData(const PadIdentifier& identifier_,
44 const std::vector<u8>& data) override;
45
46 Common::Input::DriverResult SetPollingMode(
47 const PadIdentifier& identifier, const Common::Input::PollingMode polling_mode) override;
48
49 /// Used for automapping features
50 std::vector<Common::ParamPackage> GetInputDevices() const override;
51 ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override;
52 AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override;
53 MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& params) override;
54 Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override;
55
56private:
57 static constexpr std::size_t MaxSupportedControllers = 8;
58
59 /// For shutting down, clear all data, join all threads, release usb devices
60 void Reset();
61
62 /// Registers controllers, clears all data and starts the scan thread
63 void Setup();
64
65 /// Actively searchs for new devices
66 void ScanThread(std::stop_token stop_token);
67
68 /// Returns true if device is valid and not registered
69 bool IsDeviceNew(SDL_hid_device_info* device_info) const;
70
71 /// Tries to connect to the new device
72 void RegisterNewDevice(SDL_hid_device_info* device_info);
73
74 /// Returns the next free handle
75 std::shared_ptr<Joycon::JoyconDriver> GetNextFreeHandle(Joycon::ControllerType type) const;
76
77 void OnBatteryUpdate(std::size_t port, Joycon::ControllerType type, Joycon::Battery value);
78 void OnColorUpdate(std::size_t port, Joycon::ControllerType type, const Joycon::Color& value);
79 void OnButtonUpdate(std::size_t port, Joycon::ControllerType type, int id, bool value);
80 void OnStickUpdate(std::size_t port, Joycon::ControllerType type, int id, f32 value);
81 void OnMotionUpdate(std::size_t port, Joycon::ControllerType type, int id,
82 const Joycon::MotionData& value);
83 void OnRingConUpdate(f32 ring_data);
84 void OnAmiiboUpdate(std::size_t port, const std::vector<u8>& amiibo_data);
85 void OnCameraUpdate(std::size_t port, const std::vector<u8>& camera_data,
86 Joycon::IrsResolution format);
87
88 /// Returns a JoyconHandle corresponding to a PadIdentifier
89 std::shared_ptr<Joycon::JoyconDriver> GetHandle(PadIdentifier identifier) const;
90
91 /// Returns a PadIdentifier corresponding to the port number and joycon type
92 PadIdentifier GetIdentifier(std::size_t port, Joycon::ControllerType type) const;
93
94 /// Returns a ParamPackage corresponding to the port number and joycon type
95 Common::ParamPackage GetParamPackage(std::size_t port, Joycon::ControllerType type) const;
96
97 std::string JoyconName(std::size_t port) const;
98
99 Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const;
100
101 /// Returns the name of the device in text format
102 std::string JoyconName(Joycon::ControllerType type) const;
103
104 std::jthread scan_thread;
105
106 // Joycon types are split by type to ease supporting dualjoycon configurations
107 std::array<std::shared_ptr<Joycon::JoyconDriver>, MaxSupportedControllers> left_joycons{};
108 std::array<std::shared_ptr<Joycon::JoyconDriver>, MaxSupportedControllers> right_joycons{};
109};
110
111} // namespace InputCommon
diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp
index 9835d99d2..d975eb815 100644
--- a/src/input_common/drivers/sdl_driver.cpp
+++ b/src/input_common/drivers/sdl_driver.cpp
@@ -334,6 +334,15 @@ void SDLDriver::InitJoystick(int joystick_index) {
334 334
335 const auto guid = GetGUID(sdl_joystick); 335 const auto guid = GetGUID(sdl_joystick);
336 336
337 if (Settings::values.enable_joycon_driver) {
338 if (guid.uuid[5] == 0x05 && guid.uuid[4] == 0x7e &&
339 (guid.uuid[8] == 0x06 || guid.uuid[8] == 0x07)) {
340 LOG_WARNING(Input, "Preferring joycon driver for device index {}", joystick_index);
341 SDL_JoystickClose(sdl_joystick);
342 return;
343 }
344 }
345
337 std::scoped_lock lock{joystick_map_mutex}; 346 std::scoped_lock lock{joystick_map_mutex};
338 if (joystick_map.find(guid) == joystick_map.end()) { 347 if (joystick_map.find(guid) == joystick_map.end()) {
339 auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_gamecontroller); 348 auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_gamecontroller);
@@ -456,9 +465,13 @@ SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_en
456 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1"); 465 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
457 SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); 466 SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
458 467
459 // Use hidapi driver for joycons. This will allow joycons to be detected as a GameController and 468 // Disable hidapi drivers for switch controllers when the custom joycon driver is enabled
460 // not a generic one 469 if (Settings::values.enable_joycon_driver) {
461 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1"); 470 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "0");
471 } else {
472 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1");
473 }
474 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, "1");
462 475
463 // Disable hidapi driver for xbox. Already default on Windows, this causes conflict with native 476 // Disable hidapi driver for xbox. Already default on Windows, this causes conflict with native
464 // driver on Linux. 477 // driver on Linux.
@@ -548,7 +561,7 @@ std::vector<Common::ParamPackage> SDLDriver::GetInputDevices() const {
548 return devices; 561 return devices;
549} 562}
550 563
551Common::Input::VibrationError SDLDriver::SetVibration( 564Common::Input::DriverResult SDLDriver::SetVibration(
552 const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) { 565 const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) {
553 const auto joystick = 566 const auto joystick =
554 GetSDLJoystickByGUID(identifier.guid.RawString(), static_cast<int>(identifier.port)); 567 GetSDLJoystickByGUID(identifier.guid.RawString(), static_cast<int>(identifier.port));
@@ -582,7 +595,7 @@ Common::Input::VibrationError SDLDriver::SetVibration(
582 .vibration = new_vibration, 595 .vibration = new_vibration,
583 }); 596 });
584 597
585 return Common::Input::VibrationError::None; 598 return Common::Input::DriverResult::Success;
586} 599}
587 600
588bool SDLDriver::IsVibrationEnabled(const PadIdentifier& identifier) { 601bool SDLDriver::IsVibrationEnabled(const PadIdentifier& identifier) {
diff --git a/src/input_common/drivers/sdl_driver.h b/src/input_common/drivers/sdl_driver.h
index 366bcc496..ffde169b3 100644
--- a/src/input_common/drivers/sdl_driver.h
+++ b/src/input_common/drivers/sdl_driver.h
@@ -63,7 +63,7 @@ public:
63 63
64 bool IsStickInverted(const Common::ParamPackage& params) override; 64 bool IsStickInverted(const Common::ParamPackage& params) override;
65 65
66 Common::Input::VibrationError SetVibration( 66 Common::Input::DriverResult SetVibration(
67 const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override; 67 const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override;
68 68
69 bool IsVibrationEnabled(const PadIdentifier& identifier) override; 69 bool IsVibrationEnabled(const PadIdentifier& identifier) override;
diff --git a/src/input_common/drivers/virtual_amiibo.cpp b/src/input_common/drivers/virtual_amiibo.cpp
index 63ffaca67..4a0268a4d 100644
--- a/src/input_common/drivers/virtual_amiibo.cpp
+++ b/src/input_common/drivers/virtual_amiibo.cpp
@@ -22,22 +22,23 @@ VirtualAmiibo::VirtualAmiibo(std::string input_engine_) : InputEngine(std::move(
22 22
23VirtualAmiibo::~VirtualAmiibo() = default; 23VirtualAmiibo::~VirtualAmiibo() = default;
24 24
25Common::Input::PollingError VirtualAmiibo::SetPollingMode( 25Common::Input::DriverResult VirtualAmiibo::SetPollingMode(
26 [[maybe_unused]] const PadIdentifier& identifier_, 26 [[maybe_unused]] const PadIdentifier& identifier_,
27 const Common::Input::PollingMode polling_mode_) { 27 const Common::Input::PollingMode polling_mode_) {
28 polling_mode = polling_mode_; 28 polling_mode = polling_mode_;
29 29
30 if (polling_mode == Common::Input::PollingMode::NFC) { 30 switch (polling_mode) {
31 case Common::Input::PollingMode::NFC:
31 if (state == State::Initialized) { 32 if (state == State::Initialized) {
32 state = State::WaitingForAmiibo; 33 state = State::WaitingForAmiibo;
33 } 34 }
34 } else { 35 return Common::Input::DriverResult::Success;
36 default:
35 if (state == State::AmiiboIsOpen) { 37 if (state == State::AmiiboIsOpen) {
36 CloseAmiibo(); 38 CloseAmiibo();
37 } 39 }
40 return Common::Input::DriverResult::NotSupported;
38 } 41 }
39
40 return Common::Input::PollingError::None;
41} 42}
42 43
43Common::Input::NfcState VirtualAmiibo::SupportsNfc( 44Common::Input::NfcState VirtualAmiibo::SupportsNfc(
diff --git a/src/input_common/drivers/virtual_amiibo.h b/src/input_common/drivers/virtual_amiibo.h
index 0f9dad333..13cacfc0a 100644
--- a/src/input_common/drivers/virtual_amiibo.h
+++ b/src/input_common/drivers/virtual_amiibo.h
@@ -36,7 +36,7 @@ public:
36 ~VirtualAmiibo() override; 36 ~VirtualAmiibo() override;
37 37
38 // Sets polling mode to a controller 38 // Sets polling mode to a controller
39 Common::Input::PollingError SetPollingMode( 39 Common::Input::DriverResult SetPollingMode(
40 const PadIdentifier& identifier_, const Common::Input::PollingMode polling_mode_) override; 40 const PadIdentifier& identifier_, const Common::Input::PollingMode polling_mode_) override;
41 41
42 Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier_) const override; 42 Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier_) const override;
diff --git a/src/input_common/helpers/joycon_driver.cpp b/src/input_common/helpers/joycon_driver.cpp
new file mode 100644
index 000000000..4159e5717
--- /dev/null
+++ b/src/input_common/helpers/joycon_driver.cpp
@@ -0,0 +1,572 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/logging/log.h"
5#include "common/swap.h"
6#include "common/thread.h"
7#include "input_common/helpers/joycon_driver.h"
8#include "input_common/helpers/joycon_protocol/calibration.h"
9#include "input_common/helpers/joycon_protocol/generic_functions.h"
10#include "input_common/helpers/joycon_protocol/irs.h"
11#include "input_common/helpers/joycon_protocol/nfc.h"
12#include "input_common/helpers/joycon_protocol/poller.h"
13#include "input_common/helpers/joycon_protocol/ringcon.h"
14#include "input_common/helpers/joycon_protocol/rumble.h"
15
16namespace InputCommon::Joycon {
17JoyconDriver::JoyconDriver(std::size_t port_) : port{port_} {
18 hidapi_handle = std::make_shared<JoyconHandle>();
19}
20
21JoyconDriver::~JoyconDriver() {
22 Stop();
23}
24
25void JoyconDriver::Stop() {
26 is_connected = false;
27 input_thread = {};
28}
29
30DriverResult JoyconDriver::RequestDeviceAccess(SDL_hid_device_info* device_info) {
31 std::scoped_lock lock{mutex};
32
33 handle_device_type = ControllerType::None;
34 GetDeviceType(device_info, handle_device_type);
35 if (handle_device_type == ControllerType::None) {
36 return DriverResult::UnsupportedControllerType;
37 }
38
39 hidapi_handle->handle =
40 SDL_hid_open(device_info->vendor_id, device_info->product_id, device_info->serial_number);
41 std::memcpy(&handle_serial_number, device_info->serial_number, 15);
42 if (!hidapi_handle->handle) {
43 LOG_ERROR(Input, "Yuzu can't gain access to this device: ID {:04X}:{:04X}.",
44 device_info->vendor_id, device_info->product_id);
45 return DriverResult::HandleInUse;
46 }
47 SDL_hid_set_nonblocking(hidapi_handle->handle, 1);
48 return DriverResult::Success;
49}
50
51DriverResult JoyconDriver::InitializeDevice() {
52 if (!hidapi_handle->handle) {
53 return DriverResult::InvalidHandle;
54 }
55 std::scoped_lock lock{mutex};
56 disable_input_thread = true;
57
58 // Reset Counters
59 error_counter = 0;
60 hidapi_handle->packet_counter = 0;
61
62 // Reset external device status
63 starlink_connected = false;
64 ring_connected = false;
65 amiibo_detected = false;
66
67 // Set HW default configuration
68 vibration_enabled = true;
69 motion_enabled = true;
70 hidbus_enabled = false;
71 nfc_enabled = false;
72 passive_enabled = false;
73 irs_enabled = false;
74 gyro_sensitivity = Joycon::GyroSensitivity::DPS2000;
75 gyro_performance = Joycon::GyroPerformance::HZ833;
76 accelerometer_sensitivity = Joycon::AccelerometerSensitivity::G8;
77 accelerometer_performance = Joycon::AccelerometerPerformance::HZ100;
78
79 // Initialize HW Protocols
80 calibration_protocol = std::make_unique<CalibrationProtocol>(hidapi_handle);
81 generic_protocol = std::make_unique<GenericProtocol>(hidapi_handle);
82 irs_protocol = std::make_unique<IrsProtocol>(hidapi_handle);
83 nfc_protocol = std::make_unique<NfcProtocol>(hidapi_handle);
84 ring_protocol = std::make_unique<RingConProtocol>(hidapi_handle);
85 rumble_protocol = std::make_unique<RumbleProtocol>(hidapi_handle);
86
87 // Get fixed joycon info
88 generic_protocol->GetVersionNumber(version);
89 generic_protocol->GetColor(color);
90 if (handle_device_type == ControllerType::Pro) {
91 // Some 3rd party controllers aren't pro controllers
92 generic_protocol->GetControllerType(device_type);
93 } else {
94 device_type = handle_device_type;
95 }
96 generic_protocol->GetSerialNumber(serial_number);
97 supported_features = GetSupportedFeatures();
98
99 // Get Calibration data
100 calibration_protocol->GetLeftJoyStickCalibration(left_stick_calibration);
101 calibration_protocol->GetRightJoyStickCalibration(right_stick_calibration);
102 calibration_protocol->GetImuCalibration(motion_calibration);
103
104 // Set led status
105 generic_protocol->SetLedBlinkPattern(static_cast<u8>(1 + port));
106
107 // Apply HW configuration
108 SetPollingMode();
109
110 // Initialize joycon poller
111 joycon_poller = std::make_unique<JoyconPoller>(device_type, left_stick_calibration,
112 right_stick_calibration, motion_calibration);
113
114 // Start pooling for data
115 is_connected = true;
116 if (!input_thread_running) {
117 input_thread =
118 std::jthread([this](std::stop_token stop_token) { InputThread(stop_token); });
119 }
120
121 disable_input_thread = false;
122 return DriverResult::Success;
123}
124
125void JoyconDriver::InputThread(std::stop_token stop_token) {
126 LOG_INFO(Input, "Joycon Adapter input thread started");
127 Common::SetCurrentThreadName("JoyconInput");
128 input_thread_running = true;
129
130 // Max update rate is 5ms, ensure we are always able to read a bit faster
131 constexpr int ThreadDelay = 2;
132 std::vector<u8> buffer(MaxBufferSize);
133
134 while (!stop_token.stop_requested()) {
135 int status = 0;
136
137 if (!IsInputThreadValid()) {
138 input_thread.request_stop();
139 continue;
140 }
141
142 // By disabling the input thread we can ensure custom commands will succeed as no package is
143 // skipped
144 if (!disable_input_thread) {
145 status = SDL_hid_read_timeout(hidapi_handle->handle, buffer.data(), buffer.size(),
146 ThreadDelay);
147 } else {
148 std::this_thread::sleep_for(std::chrono::milliseconds(ThreadDelay));
149 }
150
151 if (IsPayloadCorrect(status, buffer)) {
152 OnNewData(buffer);
153 }
154
155 std::this_thread::yield();
156 }
157
158 is_connected = false;
159 input_thread_running = false;
160 LOG_INFO(Input, "Joycon Adapter input thread stopped");
161}
162
163void JoyconDriver::OnNewData(std::span<u8> buffer) {
164 const auto report_mode = static_cast<InputReport>(buffer[0]);
165
166 // Packages can be a litte bit inconsistent. Average the delta time to provide a smoother motion
167 // experience
168 switch (report_mode) {
169 case InputReport::STANDARD_FULL_60HZ:
170 case InputReport::NFC_IR_MODE_60HZ:
171 case InputReport::SIMPLE_HID_MODE: {
172 const auto now = std::chrono::steady_clock::now();
173 const auto new_delta_time = static_cast<u64>(
174 std::chrono::duration_cast<std::chrono::microseconds>(now - last_update).count());
175 delta_time = ((delta_time * 8) + (new_delta_time * 2)) / 10;
176 last_update = now;
177 joycon_poller->UpdateColor(color);
178 break;
179 }
180 default:
181 break;
182 }
183
184 const MotionStatus motion_status{
185 .is_enabled = motion_enabled,
186 .delta_time = delta_time,
187 .gyro_sensitivity = gyro_sensitivity,
188 .accelerometer_sensitivity = accelerometer_sensitivity,
189 };
190
191 // TODO: Remove this when calibration is properly loaded and not calculated
192 if (ring_connected && report_mode == InputReport::STANDARD_FULL_60HZ) {
193 InputReportActive data{};
194 memcpy(&data, buffer.data(), sizeof(InputReportActive));
195 calibration_protocol->GetRingCalibration(ring_calibration, data.ring_input);
196 }
197
198 const RingStatus ring_status{
199 .is_enabled = ring_connected,
200 .default_value = ring_calibration.default_value,
201 .max_value = ring_calibration.max_value,
202 .min_value = ring_calibration.min_value,
203 };
204
205 if (irs_protocol->IsEnabled()) {
206 irs_protocol->RequestImage(buffer);
207 joycon_poller->UpdateCamera(irs_protocol->GetImage(), irs_protocol->GetIrsFormat());
208 }
209
210 if (nfc_protocol->IsEnabled()) {
211 if (amiibo_detected) {
212 if (!nfc_protocol->HasAmiibo()) {
213 joycon_poller->UpdateAmiibo({});
214 amiibo_detected = false;
215 return;
216 }
217 }
218
219 if (!amiibo_detected) {
220 std::vector<u8> data(0x21C);
221 const auto result = nfc_protocol->ScanAmiibo(data);
222 if (result == DriverResult::Success) {
223 joycon_poller->UpdateAmiibo(data);
224 amiibo_detected = true;
225 }
226 }
227 }
228
229 switch (report_mode) {
230 case InputReport::STANDARD_FULL_60HZ:
231 joycon_poller->ReadActiveMode(buffer, motion_status, ring_status);
232 break;
233 case InputReport::NFC_IR_MODE_60HZ:
234 joycon_poller->ReadNfcIRMode(buffer, motion_status);
235 break;
236 case InputReport::SIMPLE_HID_MODE:
237 joycon_poller->ReadPassiveMode(buffer);
238 break;
239 case InputReport::SUBCMD_REPLY:
240 LOG_DEBUG(Input, "Unhandled command reply");
241 break;
242 default:
243 LOG_ERROR(Input, "Report mode not Implemented {}", report_mode);
244 break;
245 }
246}
247
248DriverResult JoyconDriver::SetPollingMode() {
249 disable_input_thread = true;
250
251 rumble_protocol->EnableRumble(vibration_enabled && supported_features.vibration);
252
253 if (motion_enabled && supported_features.motion) {
254 generic_protocol->EnableImu(true);
255 generic_protocol->SetImuConfig(gyro_sensitivity, gyro_performance,
256 accelerometer_sensitivity, accelerometer_performance);
257 } else {
258 generic_protocol->EnableImu(false);
259 }
260
261 if (irs_protocol->IsEnabled()) {
262 irs_protocol->DisableIrs();
263 }
264
265 if (nfc_protocol->IsEnabled()) {
266 amiibo_detected = false;
267 nfc_protocol->DisableNfc();
268 }
269
270 if (ring_protocol->IsEnabled()) {
271 ring_connected = false;
272 ring_protocol->DisableRingCon();
273 }
274
275 if (irs_enabled && supported_features.irs) {
276 auto result = irs_protocol->EnableIrs();
277 if (result == DriverResult::Success) {
278 disable_input_thread = false;
279 return result;
280 }
281 irs_protocol->DisableIrs();
282 LOG_ERROR(Input, "Error enabling IRS");
283 }
284
285 if (nfc_enabled && supported_features.nfc) {
286 auto result = nfc_protocol->EnableNfc();
287 if (result == DriverResult::Success) {
288 result = nfc_protocol->StartNFCPollingMode();
289 }
290 if (result == DriverResult::Success) {
291 disable_input_thread = false;
292 return result;
293 }
294 nfc_protocol->DisableNfc();
295 LOG_ERROR(Input, "Error enabling NFC");
296 }
297
298 if (hidbus_enabled && supported_features.hidbus) {
299 auto result = ring_protocol->EnableRingCon();
300 if (result == DriverResult::Success) {
301 result = ring_protocol->StartRingconPolling();
302 }
303 if (result == DriverResult::Success) {
304 ring_connected = true;
305 disable_input_thread = false;
306 return result;
307 }
308 ring_connected = false;
309 ring_protocol->DisableRingCon();
310 LOG_ERROR(Input, "Error enabling Ringcon");
311 }
312
313 if (passive_enabled && supported_features.passive) {
314 const auto result = generic_protocol->EnablePassiveMode();
315 if (result == DriverResult::Success) {
316 disable_input_thread = false;
317 return result;
318 }
319 LOG_ERROR(Input, "Error enabling passive mode");
320 }
321
322 // Default Mode
323 const auto result = generic_protocol->EnableActiveMode();
324 if (result != DriverResult::Success) {
325 LOG_ERROR(Input, "Error enabling active mode");
326 }
327
328 disable_input_thread = false;
329 return result;
330}
331
332JoyconDriver::SupportedFeatures JoyconDriver::GetSupportedFeatures() {
333 SupportedFeatures features{
334 .passive = true,
335 .motion = true,
336 .vibration = true,
337 };
338
339 if (device_type == ControllerType::Right) {
340 features.nfc = true;
341 features.irs = true;
342 features.hidbus = true;
343 }
344
345 if (device_type == ControllerType::Pro) {
346 features.nfc = true;
347 }
348 return features;
349}
350
351bool JoyconDriver::IsInputThreadValid() const {
352 if (!is_connected.load()) {
353 return false;
354 }
355 if (hidapi_handle->handle == nullptr) {
356 return false;
357 }
358 // Controller is not responding. Terminate connection
359 if (error_counter > MaxErrorCount) {
360 return false;
361 }
362 return true;
363}
364
365bool JoyconDriver::IsPayloadCorrect(int status, std::span<const u8> buffer) {
366 if (status <= -1) {
367 error_counter++;
368 return false;
369 }
370 // There's no new data
371 if (status == 0) {
372 return false;
373 }
374 // No reply ever starts with zero
375 if (buffer[0] == 0x00) {
376 error_counter++;
377 return false;
378 }
379 error_counter = 0;
380 return true;
381}
382
383DriverResult JoyconDriver::SetVibration(const VibrationValue& vibration) {
384 std::scoped_lock lock{mutex};
385 if (disable_input_thread) {
386 return DriverResult::HandleInUse;
387 }
388 return rumble_protocol->SendVibration(vibration);
389}
390
391DriverResult JoyconDriver::SetLedConfig(u8 led_pattern) {
392 std::scoped_lock lock{mutex};
393 if (disable_input_thread) {
394 return DriverResult::HandleInUse;
395 }
396 return generic_protocol->SetLedPattern(led_pattern);
397}
398
399DriverResult JoyconDriver::SetIrsConfig(IrsMode mode_, IrsResolution format_) {
400 std::scoped_lock lock{mutex};
401 if (disable_input_thread) {
402 return DriverResult::HandleInUse;
403 }
404 disable_input_thread = true;
405 const auto result = irs_protocol->SetIrsConfig(mode_, format_);
406 disable_input_thread = false;
407 return result;
408}
409
410DriverResult JoyconDriver::SetPasiveMode() {
411 std::scoped_lock lock{mutex};
412 motion_enabled = false;
413 hidbus_enabled = false;
414 nfc_enabled = false;
415 passive_enabled = true;
416 irs_enabled = false;
417 return SetPollingMode();
418}
419
420DriverResult JoyconDriver::SetActiveMode() {
421 if (is_ring_disabled_by_irs) {
422 is_ring_disabled_by_irs = false;
423 SetActiveMode();
424 return SetRingConMode();
425 }
426
427 std::scoped_lock lock{mutex};
428 motion_enabled = true;
429 hidbus_enabled = false;
430 nfc_enabled = false;
431 passive_enabled = false;
432 irs_enabled = false;
433 return SetPollingMode();
434}
435
436DriverResult JoyconDriver::SetIrMode() {
437 std::scoped_lock lock{mutex};
438
439 if (!supported_features.irs) {
440 return DriverResult::NotSupported;
441 }
442
443 if (ring_connected) {
444 is_ring_disabled_by_irs = true;
445 }
446
447 motion_enabled = false;
448 hidbus_enabled = false;
449 nfc_enabled = false;
450 passive_enabled = false;
451 irs_enabled = true;
452 return SetPollingMode();
453}
454
455DriverResult JoyconDriver::SetNfcMode() {
456 std::scoped_lock lock{mutex};
457
458 if (!supported_features.nfc) {
459 return DriverResult::NotSupported;
460 }
461
462 motion_enabled = true;
463 hidbus_enabled = false;
464 nfc_enabled = true;
465 passive_enabled = false;
466 irs_enabled = false;
467 return SetPollingMode();
468}
469
470DriverResult JoyconDriver::SetRingConMode() {
471 std::scoped_lock lock{mutex};
472
473 if (!supported_features.hidbus) {
474 return DriverResult::NotSupported;
475 }
476
477 motion_enabled = true;
478 hidbus_enabled = true;
479 nfc_enabled = false;
480 passive_enabled = false;
481 irs_enabled = false;
482
483 const auto result = SetPollingMode();
484
485 if (!ring_connected) {
486 return DriverResult::NoDeviceDetected;
487 }
488
489 return result;
490}
491
492bool JoyconDriver::IsConnected() const {
493 std::scoped_lock lock{mutex};
494 return is_connected.load();
495}
496
497bool JoyconDriver::IsVibrationEnabled() const {
498 std::scoped_lock lock{mutex};
499 return vibration_enabled;
500}
501
502FirmwareVersion JoyconDriver::GetDeviceVersion() const {
503 std::scoped_lock lock{mutex};
504 return version;
505}
506
507Color JoyconDriver::GetDeviceColor() const {
508 std::scoped_lock lock{mutex};
509 return color;
510}
511
512std::size_t JoyconDriver::GetDevicePort() const {
513 std::scoped_lock lock{mutex};
514 return port;
515}
516
517ControllerType JoyconDriver::GetDeviceType() const {
518 std::scoped_lock lock{mutex};
519 return device_type;
520}
521
522ControllerType JoyconDriver::GetHandleDeviceType() const {
523 std::scoped_lock lock{mutex};
524 return handle_device_type;
525}
526
527SerialNumber JoyconDriver::GetSerialNumber() const {
528 std::scoped_lock lock{mutex};
529 return serial_number;
530}
531
532SerialNumber JoyconDriver::GetHandleSerialNumber() const {
533 std::scoped_lock lock{mutex};
534 return handle_serial_number;
535}
536
537void JoyconDriver::SetCallbacks(const JoyconCallbacks& callbacks) {
538 joycon_poller->SetCallbacks(callbacks);
539}
540
541DriverResult JoyconDriver::GetDeviceType(SDL_hid_device_info* device_info,
542 ControllerType& controller_type) {
543 static constexpr std::array<std::pair<u32, ControllerType>, 2> supported_devices{
544 std::pair<u32, ControllerType>{0x2006, ControllerType::Left},
545 {0x2007, ControllerType::Right},
546 };
547 constexpr u16 nintendo_vendor_id = 0x057e;
548
549 controller_type = ControllerType::None;
550 if (device_info->vendor_id != nintendo_vendor_id) {
551 return DriverResult::UnsupportedControllerType;
552 }
553
554 for (const auto& [product_id, type] : supported_devices) {
555 if (device_info->product_id == static_cast<u16>(product_id)) {
556 controller_type = type;
557 return Joycon::DriverResult::Success;
558 }
559 }
560 return Joycon::DriverResult::UnsupportedControllerType;
561}
562
563DriverResult JoyconDriver::GetSerialNumber(SDL_hid_device_info* device_info,
564 SerialNumber& serial_number) {
565 if (device_info->serial_number == nullptr) {
566 return DriverResult::Unknown;
567 }
568 std::memcpy(&serial_number, device_info->serial_number, 15);
569 return Joycon::DriverResult::Success;
570}
571
572} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_driver.h b/src/input_common/helpers/joycon_driver.h
new file mode 100644
index 000000000..c1e189fa5
--- /dev/null
+++ b/src/input_common/helpers/joycon_driver.h
@@ -0,0 +1,150 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <atomic>
7#include <functional>
8#include <mutex>
9#include <span>
10#include <thread>
11
12#include "input_common/helpers/joycon_protocol/joycon_types.h"
13
14namespace InputCommon::Joycon {
15class CalibrationProtocol;
16class GenericProtocol;
17class IrsProtocol;
18class NfcProtocol;
19class JoyconPoller;
20class RingConProtocol;
21class RumbleProtocol;
22
23class JoyconDriver final {
24public:
25 explicit JoyconDriver(std::size_t port_);
26
27 ~JoyconDriver();
28
29 DriverResult RequestDeviceAccess(SDL_hid_device_info* device_info);
30 DriverResult InitializeDevice();
31 void Stop();
32
33 bool IsConnected() const;
34 bool IsVibrationEnabled() const;
35
36 FirmwareVersion GetDeviceVersion() const;
37 Color GetDeviceColor() const;
38 std::size_t GetDevicePort() const;
39 ControllerType GetDeviceType() const;
40 ControllerType GetHandleDeviceType() const;
41 SerialNumber GetSerialNumber() const;
42 SerialNumber GetHandleSerialNumber() const;
43
44 DriverResult SetVibration(const VibrationValue& vibration);
45 DriverResult SetLedConfig(u8 led_pattern);
46 DriverResult SetIrsConfig(IrsMode mode_, IrsResolution format_);
47 DriverResult SetPasiveMode();
48 DriverResult SetActiveMode();
49 DriverResult SetIrMode();
50 DriverResult SetNfcMode();
51 DriverResult SetRingConMode();
52
53 void SetCallbacks(const JoyconCallbacks& callbacks);
54
55 // Returns device type from hidapi handle
56 static DriverResult GetDeviceType(SDL_hid_device_info* device_info,
57 ControllerType& controller_type);
58
59 // Returns serial number from hidapi handle
60 static DriverResult GetSerialNumber(SDL_hid_device_info* device_info,
61 SerialNumber& serial_number);
62
63private:
64 struct SupportedFeatures {
65 bool passive{};
66 bool hidbus{};
67 bool irs{};
68 bool motion{};
69 bool nfc{};
70 bool vibration{};
71 };
72
73 /// Main thread, actively request new data from the handle
74 void InputThread(std::stop_token stop_token);
75
76 /// Called everytime a valid package arrives
77 void OnNewData(std::span<u8> buffer);
78
79 /// Updates device configuration to enable or disable features
80 DriverResult SetPollingMode();
81
82 /// Returns true if input thread is valid and doesn't need to be stopped
83 bool IsInputThreadValid() const;
84
85 /// Returns true if the data should be interpreted. Otherwise the error counter is incremented
86 bool IsPayloadCorrect(int status, std::span<const u8> buffer);
87
88 /// Returns a list of supported features that can be enabled on this device
89 SupportedFeatures GetSupportedFeatures();
90
91 // Protocol Features
92 std::unique_ptr<CalibrationProtocol> calibration_protocol;
93 std::unique_ptr<GenericProtocol> generic_protocol;
94 std::unique_ptr<IrsProtocol> irs_protocol;
95 std::unique_ptr<NfcProtocol> nfc_protocol;
96 std::unique_ptr<JoyconPoller> joycon_poller;
97 std::unique_ptr<RingConProtocol> ring_protocol;
98 std::unique_ptr<RumbleProtocol> rumble_protocol;
99
100 // Connection status
101 std::atomic<bool> is_connected{};
102 u64 delta_time;
103 std::size_t error_counter{};
104 std::shared_ptr<JoyconHandle> hidapi_handle;
105 std::chrono::time_point<std::chrono::steady_clock> last_update;
106
107 // External device status
108 bool starlink_connected{};
109 bool ring_connected{};
110 bool amiibo_detected{};
111 bool is_ring_disabled_by_irs{};
112
113 // Harware configuration
114 u8 leds{};
115 ReportMode mode{};
116 bool passive_enabled{}; // Low power mode, Ideal for multiple controllers at the same time
117 bool hidbus_enabled{}; // External device support
118 bool irs_enabled{}; // Infrared camera input
119 bool motion_enabled{}; // Enables motion input
120 bool nfc_enabled{}; // Enables Amiibo detection
121 bool vibration_enabled{}; // Allows vibrations
122
123 // Calibration data
124 GyroSensitivity gyro_sensitivity{};
125 GyroPerformance gyro_performance{};
126 AccelerometerSensitivity accelerometer_sensitivity{};
127 AccelerometerPerformance accelerometer_performance{};
128 JoyStickCalibration left_stick_calibration{};
129 JoyStickCalibration right_stick_calibration{};
130 MotionCalibration motion_calibration{};
131 RingCalibration ring_calibration{};
132
133 // Fixed joycon info
134 FirmwareVersion version{};
135 Color color{};
136 std::size_t port{};
137 ControllerType device_type{}; // Device type reported by controller
138 ControllerType handle_device_type{}; // Device type reported by hidapi
139 SerialNumber serial_number{}; // Serial number reported by controller
140 SerialNumber handle_serial_number{}; // Serial number type reported by hidapi
141 SupportedFeatures supported_features{};
142
143 // Thread related
144 mutable std::mutex mutex;
145 std::jthread input_thread;
146 bool input_thread_running{};
147 bool disable_input_thread{};
148};
149
150} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/calibration.cpp b/src/input_common/helpers/joycon_protocol/calibration.cpp
new file mode 100644
index 000000000..f6e7e97d5
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/calibration.cpp
@@ -0,0 +1,184 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <cstring>
5
6#include "input_common/helpers/joycon_protocol/calibration.h"
7#include "input_common/helpers/joycon_protocol/joycon_types.h"
8
9namespace InputCommon::Joycon {
10
11CalibrationProtocol::CalibrationProtocol(std::shared_ptr<JoyconHandle> handle)
12 : JoyconCommonProtocol(std::move(handle)) {}
13
14DriverResult CalibrationProtocol::GetLeftJoyStickCalibration(JoyStickCalibration& calibration) {
15 ScopedSetBlocking sb(this);
16 std::vector<u8> buffer;
17 DriverResult result{DriverResult::Success};
18 calibration = {};
19
20 result = ReadSPI(CalAddr::USER_LEFT_MAGIC, sizeof(u16), buffer);
21
22 if (result == DriverResult::Success) {
23 const bool has_user_calibration = buffer[0] == 0xB2 && buffer[1] == 0xA1;
24 if (has_user_calibration) {
25 result = ReadSPI(CalAddr::USER_LEFT_DATA, 9, buffer);
26 } else {
27 result = ReadSPI(CalAddr::FACT_LEFT_DATA, 9, buffer);
28 }
29 }
30
31 if (result == DriverResult::Success) {
32 calibration.x.max = static_cast<u16>(((buffer[1] & 0x0F) << 8) | buffer[0]);
33 calibration.y.max = static_cast<u16>((buffer[2] << 4) | (buffer[1] >> 4));
34 calibration.x.center = static_cast<u16>(((buffer[4] & 0x0F) << 8) | buffer[3]);
35 calibration.y.center = static_cast<u16>((buffer[5] << 4) | (buffer[4] >> 4));
36 calibration.x.min = static_cast<u16>(((buffer[7] & 0x0F) << 8) | buffer[6]);
37 calibration.y.min = static_cast<u16>((buffer[8] << 4) | (buffer[7] >> 4));
38 }
39
40 // Nintendo fix for drifting stick
41 // result = ReadSPI(0x60, 0x86 ,buffer, 16);
42 // calibration.deadzone = (u16)((buffer[4] << 8) & 0xF00 | buffer[3]);
43
44 // Set a valid default calibration if data is missing
45 ValidateCalibration(calibration);
46
47 return result;
48}
49
50DriverResult CalibrationProtocol::GetRightJoyStickCalibration(JoyStickCalibration& calibration) {
51 ScopedSetBlocking sb(this);
52 std::vector<u8> buffer;
53 DriverResult result{DriverResult::Success};
54 calibration = {};
55
56 result = ReadSPI(CalAddr::USER_RIGHT_MAGIC, sizeof(u16), buffer);
57
58 if (result == DriverResult::Success) {
59 const bool has_user_calibration = buffer[0] == 0xB2 && buffer[1] == 0xA1;
60 if (has_user_calibration) {
61 result = ReadSPI(CalAddr::USER_RIGHT_DATA, 9, buffer);
62 } else {
63 result = ReadSPI(CalAddr::FACT_RIGHT_DATA, 9, buffer);
64 }
65 }
66
67 if (result == DriverResult::Success) {
68 calibration.x.center = static_cast<u16>(((buffer[1] & 0x0F) << 8) | buffer[0]);
69 calibration.y.center = static_cast<u16>((buffer[2] << 4) | (buffer[1] >> 4));
70 calibration.x.min = static_cast<u16>(((buffer[4] & 0x0F) << 8) | buffer[3]);
71 calibration.y.min = static_cast<u16>((buffer[5] << 4) | (buffer[4] >> 4));
72 calibration.x.max = static_cast<u16>(((buffer[7] & 0x0F) << 8) | buffer[6]);
73 calibration.y.max = static_cast<u16>((buffer[8] << 4) | (buffer[7] >> 4));
74 }
75
76 // Nintendo fix for drifting stick
77 // buffer = ReadSPI(0x60, 0x98 , 16);
78 // joystick.deadzone = (u16)((buffer[4] << 8) & 0xF00 | buffer[3]);
79
80 // Set a valid default calibration if data is missing
81 ValidateCalibration(calibration);
82
83 return result;
84}
85
86DriverResult CalibrationProtocol::GetImuCalibration(MotionCalibration& calibration) {
87 ScopedSetBlocking sb(this);
88 std::vector<u8> buffer;
89 DriverResult result{DriverResult::Success};
90 calibration = {};
91
92 result = ReadSPI(CalAddr::USER_IMU_MAGIC, sizeof(u16), buffer);
93
94 if (result == DriverResult::Success) {
95 const bool has_user_calibration = buffer[0] == 0xB2 && buffer[1] == 0xA1;
96 if (has_user_calibration) {
97 result = ReadSPI(CalAddr::USER_IMU_DATA, sizeof(IMUCalibration), buffer);
98 } else {
99 result = ReadSPI(CalAddr::FACT_IMU_DATA, sizeof(IMUCalibration), buffer);
100 }
101 }
102
103 if (result == DriverResult::Success) {
104 IMUCalibration device_calibration{};
105 memcpy(&device_calibration, buffer.data(), sizeof(IMUCalibration));
106 calibration.accelerometer[0].offset = device_calibration.accelerometer_offset[0];
107 calibration.accelerometer[1].offset = device_calibration.accelerometer_offset[1];
108 calibration.accelerometer[2].offset = device_calibration.accelerometer_offset[2];
109
110 calibration.accelerometer[0].scale = device_calibration.accelerometer_scale[0];
111 calibration.accelerometer[1].scale = device_calibration.accelerometer_scale[1];
112 calibration.accelerometer[2].scale = device_calibration.accelerometer_scale[2];
113
114 calibration.gyro[0].offset = device_calibration.gyroscope_offset[0];
115 calibration.gyro[1].offset = device_calibration.gyroscope_offset[1];
116 calibration.gyro[2].offset = device_calibration.gyroscope_offset[2];
117
118 calibration.gyro[0].scale = device_calibration.gyroscope_scale[0];
119 calibration.gyro[1].scale = device_calibration.gyroscope_scale[1];
120 calibration.gyro[2].scale = device_calibration.gyroscope_scale[2];
121 }
122
123 ValidateCalibration(calibration);
124
125 return result;
126}
127
128DriverResult CalibrationProtocol::GetRingCalibration(RingCalibration& calibration,
129 s16 current_value) {
130 // TODO: Get default calibration form ring itself
131 if (ring_data_max == 0 && ring_data_min == 0) {
132 ring_data_max = current_value + 800;
133 ring_data_min = current_value - 800;
134 ring_data_default = current_value;
135 }
136 ring_data_max = std::max(ring_data_max, current_value);
137 ring_data_min = std::min(ring_data_min, current_value);
138 calibration = {
139 .default_value = ring_data_default,
140 .max_value = ring_data_max,
141 .min_value = ring_data_min,
142 };
143 return DriverResult::Success;
144}
145
146void CalibrationProtocol::ValidateCalibration(JoyStickCalibration& calibration) {
147 constexpr u16 DefaultStickCenter{2048};
148 constexpr u16 DefaultStickRange{1740};
149
150 if (calibration.x.center == 0xFFF || calibration.x.center == 0) {
151 calibration.x.center = DefaultStickCenter;
152 }
153 if (calibration.x.max == 0xFFF || calibration.x.max == 0) {
154 calibration.x.max = DefaultStickRange;
155 }
156 if (calibration.x.min == 0xFFF || calibration.x.min == 0) {
157 calibration.x.min = DefaultStickRange;
158 }
159
160 if (calibration.y.center == 0xFFF || calibration.y.center == 0) {
161 calibration.y.center = DefaultStickCenter;
162 }
163 if (calibration.y.max == 0xFFF || calibration.y.max == 0) {
164 calibration.y.max = DefaultStickRange;
165 }
166 if (calibration.y.min == 0xFFF || calibration.y.min == 0) {
167 calibration.y.min = DefaultStickRange;
168 }
169}
170
171void CalibrationProtocol::ValidateCalibration(MotionCalibration& calibration) {
172 for (auto& sensor : calibration.accelerometer) {
173 if (sensor.scale == 0) {
174 sensor.scale = 0x4000;
175 }
176 }
177 for (auto& sensor : calibration.gyro) {
178 if (sensor.scale == 0) {
179 sensor.scale = 0x3be7;
180 }
181 }
182}
183
184} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/calibration.h b/src/input_common/helpers/joycon_protocol/calibration.h
new file mode 100644
index 000000000..afb52a36a
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/calibration.h
@@ -0,0 +1,64 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
5// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
6// https://github.com/CTCaer/jc_toolkit
7// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
8
9#pragma once
10
11#include <vector>
12
13#include "input_common/helpers/joycon_protocol/common_protocol.h"
14
15namespace InputCommon::Joycon {
16enum class DriverResult;
17struct JoyStickCalibration;
18struct IMUCalibration;
19struct JoyconHandle;
20} // namespace InputCommon::Joycon
21
22namespace InputCommon::Joycon {
23
24/// Driver functions related to retrieving calibration data from the device
25class CalibrationProtocol final : private JoyconCommonProtocol {
26public:
27 explicit CalibrationProtocol(std::shared_ptr<JoyconHandle> handle);
28
29 /**
30 * Sends a request to obtain the left stick calibration from memory
31 * @param is_factory_calibration if true factory values will be returned
32 * @returns JoyStickCalibration of the left joystick
33 */
34 DriverResult GetLeftJoyStickCalibration(JoyStickCalibration& calibration);
35
36 /**
37 * Sends a request to obtain the right stick calibration from memory
38 * @param is_factory_calibration if true factory values will be returned
39 * @returns JoyStickCalibration of the right joystick
40 */
41 DriverResult GetRightJoyStickCalibration(JoyStickCalibration& calibration);
42
43 /**
44 * Sends a request to obtain the motion calibration from memory
45 * @returns ImuCalibration of the motion sensor
46 */
47 DriverResult GetImuCalibration(MotionCalibration& calibration);
48
49 /**
50 * Calculates on run time the proper calibration of the ring controller
51 * @returns RingCalibration of the ring sensor
52 */
53 DriverResult GetRingCalibration(RingCalibration& calibration, s16 current_value);
54
55private:
56 void ValidateCalibration(JoyStickCalibration& calibration);
57 void ValidateCalibration(MotionCalibration& calibration);
58
59 s16 ring_data_max = 0;
60 s16 ring_data_default = 0;
61 s16 ring_data_min = 0;
62};
63
64} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/common_protocol.cpp b/src/input_common/helpers/joycon_protocol/common_protocol.cpp
new file mode 100644
index 000000000..417d0dcc5
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/common_protocol.cpp
@@ -0,0 +1,299 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/logging/log.h"
5#include "input_common/helpers/joycon_protocol/common_protocol.h"
6
7namespace InputCommon::Joycon {
8JoyconCommonProtocol::JoyconCommonProtocol(std::shared_ptr<JoyconHandle> hidapi_handle_)
9 : hidapi_handle{std::move(hidapi_handle_)} {}
10
11u8 JoyconCommonProtocol::GetCounter() {
12 hidapi_handle->packet_counter = (hidapi_handle->packet_counter + 1) & 0x0F;
13 return hidapi_handle->packet_counter;
14}
15
16void JoyconCommonProtocol::SetBlocking() {
17 SDL_hid_set_nonblocking(hidapi_handle->handle, 0);
18}
19
20void JoyconCommonProtocol::SetNonBlocking() {
21 SDL_hid_set_nonblocking(hidapi_handle->handle, 1);
22}
23
24DriverResult JoyconCommonProtocol::GetDeviceType(ControllerType& controller_type) {
25 std::vector<u8> buffer;
26 const auto result = ReadSPI(CalAddr::DEVICE_TYPE, 1, buffer);
27 controller_type = ControllerType::None;
28
29 if (result == DriverResult::Success) {
30 controller_type = static_cast<ControllerType>(buffer[0]);
31 // Fallback to 3rd party pro controllers
32 if (controller_type == ControllerType::None) {
33 controller_type = ControllerType::Pro;
34 }
35 }
36
37 return result;
38}
39
40DriverResult JoyconCommonProtocol::CheckDeviceAccess(SDL_hid_device_info* device_info) {
41 ControllerType controller_type{ControllerType::None};
42 const auto result = GetDeviceType(controller_type);
43 if (result != DriverResult::Success || controller_type == ControllerType::None) {
44 return DriverResult::UnsupportedControllerType;
45 }
46
47 hidapi_handle->handle =
48 SDL_hid_open(device_info->vendor_id, device_info->product_id, device_info->serial_number);
49
50 if (!hidapi_handle->handle) {
51 LOG_ERROR(Input, "Yuzu can't gain access to this device: ID {:04X}:{:04X}.",
52 device_info->vendor_id, device_info->product_id);
53 return DriverResult::HandleInUse;
54 }
55
56 SetNonBlocking();
57 return DriverResult::Success;
58}
59
60DriverResult JoyconCommonProtocol::SetReportMode(ReportMode report_mode) {
61 const std::array<u8, 1> buffer{static_cast<u8>(report_mode)};
62 return SendSubCommand(SubCommand::SET_REPORT_MODE, buffer);
63}
64
65DriverResult JoyconCommonProtocol::SendData(std::span<const u8> buffer) {
66 const auto result = SDL_hid_write(hidapi_handle->handle, buffer.data(), buffer.size());
67
68 if (result == -1) {
69 return DriverResult::ErrorWritingData;
70 }
71
72 return DriverResult::Success;
73}
74
75DriverResult JoyconCommonProtocol::GetSubCommandResponse(SubCommand sc, std::vector<u8>& output) {
76 constexpr int timeout_mili = 66;
77 constexpr int MaxTries = 15;
78 int tries = 0;
79 output.resize(MaxSubCommandResponseSize);
80
81 do {
82 int result = SDL_hid_read_timeout(hidapi_handle->handle, output.data(),
83 MaxSubCommandResponseSize, timeout_mili);
84
85 if (result < 1) {
86 LOG_ERROR(Input, "No response from joycon");
87 }
88 if (tries++ > MaxTries) {
89 return DriverResult::Timeout;
90 }
91 } while (output[0] != 0x21 && output[14] != static_cast<u8>(sc));
92
93 if (output[0] != 0x21 && output[14] != static_cast<u8>(sc)) {
94 return DriverResult::WrongReply;
95 }
96
97 return DriverResult::Success;
98}
99
100DriverResult JoyconCommonProtocol::SendSubCommand(SubCommand sc, std::span<const u8> buffer,
101 std::vector<u8>& output) {
102 std::vector<u8> local_buffer(MaxResponseSize);
103
104 local_buffer[0] = static_cast<u8>(OutputReport::RUMBLE_AND_SUBCMD);
105 local_buffer[1] = GetCounter();
106 local_buffer[10] = static_cast<u8>(sc);
107 for (std::size_t i = 0; i < buffer.size(); ++i) {
108 local_buffer[11 + i] = buffer[i];
109 }
110
111 auto result = SendData(local_buffer);
112
113 if (result != DriverResult::Success) {
114 return result;
115 }
116
117 result = GetSubCommandResponse(sc, output);
118
119 return DriverResult::Success;
120}
121
122DriverResult JoyconCommonProtocol::SendSubCommand(SubCommand sc, std::span<const u8> buffer) {
123 std::vector<u8> output;
124 return SendSubCommand(sc, buffer, output);
125}
126
127DriverResult JoyconCommonProtocol::SendMCUCommand(SubCommand sc, std::span<const u8> buffer) {
128 std::vector<u8> local_buffer(MaxResponseSize);
129
130 local_buffer[0] = static_cast<u8>(OutputReport::MCU_DATA);
131 local_buffer[1] = GetCounter();
132 local_buffer[10] = static_cast<u8>(sc);
133 for (std::size_t i = 0; i < buffer.size(); ++i) {
134 local_buffer[11 + i] = buffer[i];
135 }
136
137 return SendData(local_buffer);
138}
139
140DriverResult JoyconCommonProtocol::SendVibrationReport(std::span<const u8> buffer) {
141 std::vector<u8> local_buffer(MaxResponseSize);
142
143 local_buffer[0] = static_cast<u8>(Joycon::OutputReport::RUMBLE_ONLY);
144 local_buffer[1] = GetCounter();
145
146 memcpy(local_buffer.data() + 2, buffer.data(), buffer.size());
147
148 return SendData(local_buffer);
149}
150
151DriverResult JoyconCommonProtocol::ReadSPI(CalAddr addr, u8 size, std::vector<u8>& output) {
152 constexpr std::size_t MaxTries = 10;
153 std::size_t tries = 0;
154 std::array<u8, 5> buffer = {0x00, 0x00, 0x00, 0x00, size};
155 std::vector<u8> local_buffer(size + 20);
156
157 buffer[0] = static_cast<u8>(static_cast<u16>(addr) & 0x00FF);
158 buffer[1] = static_cast<u8>((static_cast<u16>(addr) & 0xFF00) >> 8);
159 do {
160 const auto result = SendSubCommand(SubCommand::SPI_FLASH_READ, buffer, local_buffer);
161 if (result != DriverResult::Success) {
162 return result;
163 }
164
165 if (tries++ > MaxTries) {
166 return DriverResult::Timeout;
167 }
168 } while (local_buffer[15] != buffer[0] || local_buffer[16] != buffer[1]);
169
170 // Remove header from output
171 output = std::vector<u8>(local_buffer.begin() + 20, local_buffer.begin() + 20 + size);
172 return DriverResult::Success;
173}
174
175DriverResult JoyconCommonProtocol::EnableMCU(bool enable) {
176 const std::array<u8, 1> mcu_state{static_cast<u8>(enable ? 1 : 0)};
177 const auto result = SendSubCommand(SubCommand::SET_MCU_STATE, mcu_state);
178
179 if (result != DriverResult::Success) {
180 LOG_ERROR(Input, "SendMCUData failed with error {}", result);
181 }
182
183 return result;
184}
185
186DriverResult JoyconCommonProtocol::ConfigureMCU(const MCUConfig& config) {
187 LOG_DEBUG(Input, "ConfigureMCU");
188 std::array<u8, sizeof(MCUConfig)> config_buffer;
189 memcpy(config_buffer.data(), &config, sizeof(MCUConfig));
190 config_buffer[37] = CalculateMCU_CRC8(config_buffer.data() + 1, 36);
191
192 const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, config_buffer);
193
194 if (result != DriverResult::Success) {
195 LOG_ERROR(Input, "Set MCU config failed with error {}", result);
196 }
197
198 return result;
199}
200
201DriverResult JoyconCommonProtocol::GetMCUDataResponse(ReportMode report_mode_,
202 std::vector<u8>& output) {
203 const int report_mode = static_cast<u8>(report_mode_);
204 constexpr int TimeoutMili = 200;
205 constexpr int MaxTries = 9;
206 int tries = 0;
207 output.resize(0x170);
208
209 do {
210 int result = SDL_hid_read_timeout(hidapi_handle->handle, output.data(), 0x170, TimeoutMili);
211
212 if (result < 1) {
213 LOG_ERROR(Input, "No response from joycon attempt {}", tries);
214 }
215 if (tries++ > MaxTries) {
216 return DriverResult::Timeout;
217 }
218 } while (output[0] != report_mode || output[49] == 0xFF);
219
220 if (output[0] != report_mode || output[49] == 0xFF) {
221 return DriverResult::WrongReply;
222 }
223
224 return DriverResult::Success;
225}
226
227DriverResult JoyconCommonProtocol::SendMCUData(ReportMode report_mode, SubCommand sc,
228 std::span<const u8> buffer,
229 std::vector<u8>& output) {
230 std::vector<u8> local_buffer(MaxResponseSize);
231
232 local_buffer[0] = static_cast<u8>(OutputReport::MCU_DATA);
233 local_buffer[1] = GetCounter();
234 local_buffer[9] = static_cast<u8>(sc);
235 for (std::size_t i = 0; i < buffer.size(); ++i) {
236 local_buffer[10 + i] = buffer[i];
237 }
238
239 auto result = SendData(local_buffer);
240
241 if (result != DriverResult::Success) {
242 return result;
243 }
244
245 result = GetMCUDataResponse(report_mode, output);
246
247 return DriverResult::Success;
248}
249
250DriverResult JoyconCommonProtocol::WaitSetMCUMode(ReportMode report_mode, MCUMode mode) {
251 std::vector<u8> output;
252 constexpr std::size_t MaxTries{8};
253 std::size_t tries{};
254
255 do {
256 const std::vector<u8> mcu_data{static_cast<u8>(MCUMode::Standby)};
257 const auto result = SendMCUData(report_mode, SubCommand::STATE, mcu_data, output);
258
259 if (result != DriverResult::Success) {
260 return result;
261 }
262
263 if (tries++ > MaxTries) {
264 return DriverResult::WrongReply;
265 }
266 } while (output[49] != 1 || output[56] != static_cast<u8>(mode));
267
268 return DriverResult::Success;
269}
270
271// crc-8-ccitt / polynomial 0x07 look up table
272constexpr std::array<u8, 256> mcu_crc8_table = {
273 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D,
274 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D,
275 0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD,
276 0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD,
277 0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA,
278 0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A,
279 0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A,
280 0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A,
281 0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4,
282 0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4,
283 0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44,
284 0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34,
285 0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63,
286 0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13,
287 0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83,
288 0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3};
289
290u8 JoyconCommonProtocol::CalculateMCU_CRC8(u8* buffer, u8 size) const {
291 u8 crc8 = 0x0;
292
293 for (int i = 0; i < size; ++i) {
294 crc8 = mcu_crc8_table[static_cast<u8>(crc8 ^ buffer[i])];
295 }
296 return crc8;
297}
298
299} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/common_protocol.h b/src/input_common/helpers/joycon_protocol/common_protocol.h
new file mode 100644
index 000000000..903bcf402
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/common_protocol.h
@@ -0,0 +1,173 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
5// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
6// https://github.com/CTCaer/jc_toolkit
7// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
8
9#pragma once
10
11#include <memory>
12#include <span>
13#include <vector>
14
15#include "common/common_types.h"
16#include "input_common/helpers/joycon_protocol/joycon_types.h"
17
18namespace InputCommon::Joycon {
19
20/// Joycon driver functions that handle low level communication
21class JoyconCommonProtocol {
22public:
23 explicit JoyconCommonProtocol(std::shared_ptr<JoyconHandle> hidapi_handle_);
24
25 /**
26 * Sets handle to blocking. In blocking mode, SDL_hid_read() will wait (block) until there is
27 * data to read before returning.
28 */
29 void SetBlocking();
30
31 /**
32 * Sets handle to non blocking. In non-blocking mode calls to SDL_hid_read() will return
33 * immediately with a value of 0 if there is no data to be read
34 */
35 void SetNonBlocking();
36
37 /**
38 * Sends a request to obtain the joycon type from device
39 * @returns controller type of the joycon
40 */
41 DriverResult GetDeviceType(ControllerType& controller_type);
42
43 /**
44 * Verifies and sets the joycon_handle if device is valid
45 * @param device info from the driver
46 * @returns success if the device is valid
47 */
48 DriverResult CheckDeviceAccess(SDL_hid_device_info* device);
49
50 /**
51 * Sends a request to set the polling mode of the joycon
52 * @param report_mode polling mode to be set
53 */
54 DriverResult SetReportMode(Joycon::ReportMode report_mode);
55
56 /**
57 * Sends data to the joycon device
58 * @param buffer data to be send
59 */
60 DriverResult SendData(std::span<const u8> buffer);
61
62 /**
63 * Waits for incoming data of the joycon device that matchs the subcommand
64 * @param sub_command type of data to be returned
65 * @returns a buffer containing the responce
66 */
67 DriverResult GetSubCommandResponse(SubCommand sub_command, std::vector<u8>& output);
68
69 /**
70 * Sends a sub command to the device and waits for it's reply
71 * @param sc sub command to be send
72 * @param buffer data to be send
73 * @returns output buffer containing the responce
74 */
75 DriverResult SendSubCommand(SubCommand sc, std::span<const u8> buffer, std::vector<u8>& output);
76
77 /**
78 * Sends a sub command to the device and waits for it's reply and ignores the output
79 * @param sc sub command to be send
80 * @param buffer data to be send
81 */
82 DriverResult SendSubCommand(SubCommand sc, std::span<const u8> buffer);
83
84 /**
85 * Sends a mcu command to the device
86 * @param sc sub command to be send
87 * @param buffer data to be send
88 */
89 DriverResult SendMCUCommand(SubCommand sc, std::span<const u8> buffer);
90
91 /**
92 * Sends vibration data to the joycon
93 * @param buffer data to be send
94 */
95 DriverResult SendVibrationReport(std::span<const u8> buffer);
96
97 /**
98 * Reads the SPI memory stored on the joycon
99 * @param Initial address location
100 * @param size in bytes to be read
101 * @returns output buffer containing the responce
102 */
103 DriverResult ReadSPI(CalAddr addr, u8 size, std::vector<u8>& output);
104
105 /**
106 * Enables MCU chip on the joycon
107 * @param enable if true the chip will be enabled
108 */
109 DriverResult EnableMCU(bool enable);
110
111 /**
112 * Configures the MCU to the correspoinding mode
113 * @param MCUConfig configuration
114 */
115 DriverResult ConfigureMCU(const MCUConfig& config);
116
117 /**
118 * Waits until there's MCU data available. On timeout returns error
119 * @param report mode of the expected reply
120 * @returns a buffer containing the responce
121 */
122 DriverResult GetMCUDataResponse(ReportMode report_mode_, std::vector<u8>& output);
123
124 /**
125 * Sends data to the MCU chip and waits for it's reply
126 * @param report mode of the expected reply
127 * @param sub command to be send
128 * @param buffer data to be send
129 * @returns output buffer containing the responce
130 */
131 DriverResult SendMCUData(ReportMode report_mode, SubCommand sc, std::span<const u8> buffer,
132 std::vector<u8>& output);
133
134 /**
135 * Wait's until the MCU chip is on the specified mode
136 * @param report mode of the expected reply
137 * @param MCUMode configuration
138 */
139 DriverResult WaitSetMCUMode(ReportMode report_mode, MCUMode mode);
140
141 /**
142 * Calculates the checksum from the MCU data
143 * @param buffer containing the data to be send
144 * @param size of the buffer in bytes
145 * @returns byte with the correct checksum
146 */
147 u8 CalculateMCU_CRC8(u8* buffer, u8 size) const;
148
149private:
150 /**
151 * Increments and returns the packet counter of the handle
152 * @param joycon_handle device to send the data
153 * @returns packet counter value
154 */
155 u8 GetCounter();
156
157 std::shared_ptr<JoyconHandle> hidapi_handle;
158};
159
160class ScopedSetBlocking {
161public:
162 explicit ScopedSetBlocking(JoyconCommonProtocol* self) : m_self{self} {
163 m_self->SetBlocking();
164 }
165
166 ~ScopedSetBlocking() {
167 m_self->SetNonBlocking();
168 }
169
170private:
171 JoyconCommonProtocol* m_self{};
172};
173} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/generic_functions.cpp b/src/input_common/helpers/joycon_protocol/generic_functions.cpp
new file mode 100644
index 000000000..52bb8b61a
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/generic_functions.cpp
@@ -0,0 +1,125 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/logging/log.h"
5#include "input_common/helpers/joycon_protocol/generic_functions.h"
6
7namespace InputCommon::Joycon {
8
9GenericProtocol::GenericProtocol(std::shared_ptr<JoyconHandle> handle)
10 : JoyconCommonProtocol(std::move(handle)) {}
11
12DriverResult GenericProtocol::EnablePassiveMode() {
13 ScopedSetBlocking sb(this);
14 return SetReportMode(ReportMode::SIMPLE_HID_MODE);
15}
16
17DriverResult GenericProtocol::EnableActiveMode() {
18 ScopedSetBlocking sb(this);
19 return SetReportMode(ReportMode::STANDARD_FULL_60HZ);
20}
21
22DriverResult GenericProtocol::GetDeviceInfo(DeviceInfo& device_info) {
23 ScopedSetBlocking sb(this);
24 std::vector<u8> output;
25
26 const auto result = SendSubCommand(SubCommand::REQ_DEV_INFO, {}, output);
27
28 device_info = {};
29 if (result == DriverResult::Success) {
30 memcpy(&device_info, output.data(), sizeof(DeviceInfo));
31 }
32
33 return result;
34}
35
36DriverResult GenericProtocol::GetControllerType(ControllerType& controller_type) {
37 return GetDeviceType(controller_type);
38}
39
40DriverResult GenericProtocol::EnableImu(bool enable) {
41 ScopedSetBlocking sb(this);
42 const std::array<u8, 1> buffer{static_cast<u8>(enable ? 1 : 0)};
43 return SendSubCommand(SubCommand::ENABLE_IMU, buffer);
44}
45
46DriverResult GenericProtocol::SetImuConfig(GyroSensitivity gsen, GyroPerformance gfrec,
47 AccelerometerSensitivity asen,
48 AccelerometerPerformance afrec) {
49 ScopedSetBlocking sb(this);
50 const std::array<u8, 4> buffer{static_cast<u8>(gsen), static_cast<u8>(asen),
51 static_cast<u8>(gfrec), static_cast<u8>(afrec)};
52 return SendSubCommand(SubCommand::SET_IMU_SENSITIVITY, buffer);
53}
54
55DriverResult GenericProtocol::GetBattery(u32& battery_level) {
56 // This function is meant to request the high resolution battery status
57 battery_level = 0;
58 return DriverResult::NotSupported;
59}
60
61DriverResult GenericProtocol::GetColor(Color& color) {
62 ScopedSetBlocking sb(this);
63 std::vector<u8> buffer;
64 const auto result = ReadSPI(CalAddr::COLOR_DATA, 12, buffer);
65
66 color = {};
67 if (result == DriverResult::Success) {
68 color.body = static_cast<u32>((buffer[0] << 16) | (buffer[1] << 8) | buffer[2]);
69 color.buttons = static_cast<u32>((buffer[3] << 16) | (buffer[4] << 8) | buffer[5]);
70 color.left_grip = static_cast<u32>((buffer[6] << 16) | (buffer[7] << 8) | buffer[8]);
71 color.right_grip = static_cast<u32>((buffer[9] << 16) | (buffer[10] << 8) | buffer[11]);
72 }
73
74 return result;
75}
76
77DriverResult GenericProtocol::GetSerialNumber(SerialNumber& serial_number) {
78 ScopedSetBlocking sb(this);
79 std::vector<u8> buffer;
80 const auto result = ReadSPI(CalAddr::SERIAL_NUMBER, 16, buffer);
81
82 serial_number = {};
83 if (result == DriverResult::Success) {
84 memcpy(serial_number.data(), buffer.data() + 1, sizeof(SerialNumber));
85 }
86
87 return result;
88}
89
90DriverResult GenericProtocol::GetTemperature(u32& temperature) {
91 // Not all devices have temperature sensor
92 temperature = 25;
93 return DriverResult::NotSupported;
94}
95
96DriverResult GenericProtocol::GetVersionNumber(FirmwareVersion& version) {
97 DeviceInfo device_info{};
98
99 const auto result = GetDeviceInfo(device_info);
100 version = device_info.firmware;
101
102 return result;
103}
104
105DriverResult GenericProtocol::SetHomeLight() {
106 ScopedSetBlocking sb(this);
107 static constexpr std::array<u8, 3> buffer{0x0f, 0xf0, 0x00};
108 return SendSubCommand(SubCommand::SET_HOME_LIGHT, buffer);
109}
110
111DriverResult GenericProtocol::SetLedBusy() {
112 return DriverResult::NotSupported;
113}
114
115DriverResult GenericProtocol::SetLedPattern(u8 leds) {
116 ScopedSetBlocking sb(this);
117 const std::array<u8, 1> buffer{leds};
118 return SendSubCommand(SubCommand::SET_PLAYER_LIGHTS, buffer);
119}
120
121DriverResult GenericProtocol::SetLedBlinkPattern(u8 leds) {
122 return SetLedPattern(static_cast<u8>(leds << 4));
123}
124
125} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/generic_functions.h b/src/input_common/helpers/joycon_protocol/generic_functions.h
new file mode 100644
index 000000000..239bb7dbf
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/generic_functions.h
@@ -0,0 +1,108 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
5// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
6// https://github.com/CTCaer/jc_toolkit
7// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
8
9#pragma once
10
11#include "input_common/helpers/joycon_protocol/common_protocol.h"
12#include "input_common/helpers/joycon_protocol/joycon_types.h"
13
14namespace InputCommon::Joycon {
15
16/// Joycon driver functions that easily implemented
17class GenericProtocol final : private JoyconCommonProtocol {
18public:
19 explicit GenericProtocol(std::shared_ptr<JoyconHandle> handle);
20
21 /// Enables passive mode. This mode only sends button data on change. Sticks will return digital
22 /// data instead of analog. Motion will be disabled
23 DriverResult EnablePassiveMode();
24
25 /// Enables active mode. This mode will return the current status every 5-15ms
26 DriverResult EnableActiveMode();
27
28 /**
29 * Sends a request to obtain the joycon firmware and mac from handle
30 * @returns controller device info
31 */
32 DriverResult GetDeviceInfo(DeviceInfo& controller_type);
33
34 /**
35 * Sends a request to obtain the joycon type from handle
36 * @returns controller type of the joycon
37 */
38 DriverResult GetControllerType(ControllerType& controller_type);
39
40 /**
41 * Enables motion input
42 * @param enable if true motion data will be enabled
43 */
44 DriverResult EnableImu(bool enable);
45
46 /**
47 * Configures the motion sensor with the specified parameters
48 * @param gsen gyroscope sensor sensitvity in degrees per second
49 * @param gfrec gyroscope sensor frequency in hertz
50 * @param asen accelerometer sensitivity in G force
51 * @param afrec accelerometer frequency in hertz
52 */
53 DriverResult SetImuConfig(GyroSensitivity gsen, GyroPerformance gfrec,
54 AccelerometerSensitivity asen, AccelerometerPerformance afrec);
55
56 /**
57 * Request battery level from the device
58 * @returns battery level
59 */
60 DriverResult GetBattery(u32& battery_level);
61
62 /**
63 * Request joycon colors from the device
64 * @returns colors of the body and buttons
65 */
66 DriverResult GetColor(Color& color);
67
68 /**
69 * Request joycon serial number from the device
70 * @returns 16 byte serial number
71 */
72 DriverResult GetSerialNumber(SerialNumber& serial_number);
73
74 /**
75 * Request joycon serial number from the device
76 * @returns 16 byte serial number
77 */
78 DriverResult GetTemperature(u32& temperature);
79
80 /**
81 * Request joycon serial number from the device
82 * @returns 16 byte serial number
83 */
84 DriverResult GetVersionNumber(FirmwareVersion& version);
85
86 /**
87 * Sets home led behaviour
88 */
89 DriverResult SetHomeLight();
90
91 /**
92 * Sets home led into a slow breathing state
93 */
94 DriverResult SetLedBusy();
95
96 /**
97 * Sets the 4 player leds on the joycon on a solid state
98 * @params bit flag containing the led state
99 */
100 DriverResult SetLedPattern(u8 leds);
101
102 /**
103 * Sets the 4 player leds on the joycon on a blinking state
104 * @returns bit flag containing the led state
105 */
106 DriverResult SetLedBlinkPattern(u8 leds);
107};
108} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/irs.cpp b/src/input_common/helpers/joycon_protocol/irs.cpp
new file mode 100644
index 000000000..09e17bc5b
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/irs.cpp
@@ -0,0 +1,298 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <thread>
5#include "common/logging/log.h"
6#include "input_common/helpers/joycon_protocol/irs.h"
7
8namespace InputCommon::Joycon {
9
10IrsProtocol::IrsProtocol(std::shared_ptr<JoyconHandle> handle)
11 : JoyconCommonProtocol(std::move(handle)) {}
12
13DriverResult IrsProtocol::EnableIrs() {
14 LOG_INFO(Input, "Enable IRS");
15 ScopedSetBlocking sb(this);
16 DriverResult result{DriverResult::Success};
17
18 if (result == DriverResult::Success) {
19 result = SetReportMode(ReportMode::NFC_IR_MODE_60HZ);
20 }
21 if (result == DriverResult::Success) {
22 result = EnableMCU(true);
23 }
24 if (result == DriverResult::Success) {
25 result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::Standby);
26 }
27 if (result == DriverResult::Success) {
28 const MCUConfig config{
29 .command = MCUCommand::ConfigureMCU,
30 .sub_command = MCUSubCommand::SetMCUMode,
31 .mode = MCUMode::IR,
32 .crc = {},
33 };
34
35 result = ConfigureMCU(config);
36 }
37 if (result == DriverResult::Success) {
38 result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::IR);
39 }
40 if (result == DriverResult::Success) {
41 result = ConfigureIrs();
42 }
43 if (result == DriverResult::Success) {
44 result = WriteRegistersStep1();
45 }
46 if (result == DriverResult::Success) {
47 result = WriteRegistersStep2();
48 }
49
50 is_enabled = true;
51
52 return result;
53}
54
55DriverResult IrsProtocol::DisableIrs() {
56 LOG_DEBUG(Input, "Disable IRS");
57 ScopedSetBlocking sb(this);
58 DriverResult result{DriverResult::Success};
59
60 if (result == DriverResult::Success) {
61 result = EnableMCU(false);
62 }
63
64 is_enabled = false;
65
66 return result;
67}
68
69DriverResult IrsProtocol::SetIrsConfig(IrsMode mode, IrsResolution format) {
70 irs_mode = mode;
71 switch (format) {
72 case IrsResolution::Size320x240:
73 resolution_code = IrsResolutionCode::Size320x240;
74 fragments = IrsFragments::Size320x240;
75 resolution = IrsResolution::Size320x240;
76 break;
77 case IrsResolution::Size160x120:
78 resolution_code = IrsResolutionCode::Size160x120;
79 fragments = IrsFragments::Size160x120;
80 resolution = IrsResolution::Size160x120;
81 break;
82 case IrsResolution::Size80x60:
83 resolution_code = IrsResolutionCode::Size80x60;
84 fragments = IrsFragments::Size80x60;
85 resolution = IrsResolution::Size80x60;
86 break;
87 case IrsResolution::Size20x15:
88 resolution_code = IrsResolutionCode::Size20x15;
89 fragments = IrsFragments::Size20x15;
90 resolution = IrsResolution::Size20x15;
91 break;
92 case IrsResolution::Size40x30:
93 default:
94 resolution_code = IrsResolutionCode::Size40x30;
95 fragments = IrsFragments::Size40x30;
96 resolution = IrsResolution::Size40x30;
97 break;
98 }
99
100 // Restart feature
101 if (is_enabled) {
102 DisableIrs();
103 return EnableIrs();
104 }
105
106 return DriverResult::Success;
107}
108
109DriverResult IrsProtocol::RequestImage(std::span<u8> buffer) {
110 const u8 next_packet_fragment =
111 static_cast<u8>((packet_fragment + 1) % (static_cast<u8>(fragments) + 1));
112
113 if (buffer[0] == 0x31 && buffer[49] == 0x03) {
114 u8 new_packet_fragment = buffer[52];
115 if (new_packet_fragment == next_packet_fragment) {
116 packet_fragment = next_packet_fragment;
117 memcpy(buf_image.data() + (300 * packet_fragment), buffer.data() + 59, 300);
118
119 return RequestFrame(packet_fragment);
120 }
121
122 if (new_packet_fragment == packet_fragment) {
123 return RequestFrame(packet_fragment);
124 }
125
126 return ResendFrame(next_packet_fragment);
127 }
128
129 return RequestFrame(packet_fragment);
130}
131
132DriverResult IrsProtocol::ConfigureIrs() {
133 LOG_DEBUG(Input, "Configure IRS");
134 constexpr std::size_t max_tries = 28;
135 std::vector<u8> output;
136 std::size_t tries = 0;
137
138 const IrsConfigure irs_configuration{
139 .command = MCUCommand::ConfigureIR,
140 .sub_command = MCUSubCommand::SetDeviceMode,
141 .irs_mode = IrsMode::ImageTransfer,
142 .number_of_fragments = fragments,
143 .mcu_major_version = 0x0500,
144 .mcu_minor_version = 0x1800,
145 .crc = {},
146 };
147 buf_image.resize((static_cast<u8>(fragments) + 1) * 300);
148
149 std::array<u8, sizeof(IrsConfigure)> request_data{};
150 memcpy(request_data.data(), &irs_configuration, sizeof(IrsConfigure));
151 request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
152 do {
153 const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, request_data, output);
154
155 if (result != DriverResult::Success) {
156 return result;
157 }
158 if (tries++ >= max_tries) {
159 return DriverResult::WrongReply;
160 }
161 } while (output[15] != 0x0b);
162
163 return DriverResult::Success;
164}
165
166DriverResult IrsProtocol::WriteRegistersStep1() {
167 LOG_DEBUG(Input, "WriteRegistersStep1");
168 DriverResult result{DriverResult::Success};
169 constexpr std::size_t max_tries = 28;
170 std::vector<u8> output;
171 std::size_t tries = 0;
172
173 const IrsWriteRegisters irs_registers{
174 .command = MCUCommand::ConfigureIR,
175 .sub_command = MCUSubCommand::WriteDeviceRegisters,
176 .number_of_registers = 0x9,
177 .registers =
178 {
179 IrsRegister{IrRegistersAddress::Resolution, static_cast<u8>(resolution_code)},
180 {IrRegistersAddress::ExposureLSB, static_cast<u8>(exposure & 0xff)},
181 {IrRegistersAddress::ExposureMSB, static_cast<u8>(exposure >> 8)},
182 {IrRegistersAddress::ExposureTime, 0x00},
183 {IrRegistersAddress::Leds, static_cast<u8>(leds)},
184 {IrRegistersAddress::DigitalGainLSB, static_cast<u8>((digital_gain & 0x0f) << 4)},
185 {IrRegistersAddress::DigitalGainMSB, static_cast<u8>((digital_gain & 0xf0) >> 4)},
186 {IrRegistersAddress::LedFilter, static_cast<u8>(led_filter)},
187 {IrRegistersAddress::WhitePixelThreshold, 0xc8},
188 },
189 .crc = {},
190 };
191
192 std::array<u8, sizeof(IrsWriteRegisters)> request_data{};
193 memcpy(request_data.data(), &irs_registers, sizeof(IrsWriteRegisters));
194 request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
195
196 std::array<u8, 38> mcu_request{0x02};
197 mcu_request[36] = CalculateMCU_CRC8(mcu_request.data(), 36);
198 mcu_request[37] = 0xFF;
199
200 if (result != DriverResult::Success) {
201 return result;
202 }
203
204 do {
205 result = SendSubCommand(SubCommand::SET_MCU_CONFIG, request_data, output);
206
207 // First time we need to set the report mode
208 if (result == DriverResult::Success && tries == 0) {
209 result = SendMCUCommand(SubCommand::SET_REPORT_MODE, mcu_request);
210 }
211 if (result == DriverResult::Success && tries == 0) {
212 GetSubCommandResponse(SubCommand::SET_MCU_CONFIG, output);
213 }
214
215 if (result != DriverResult::Success) {
216 return result;
217 }
218 if (tries++ >= max_tries) {
219 return DriverResult::WrongReply;
220 }
221 } while (!(output[15] == 0x13 && output[17] == 0x07) && output[15] != 0x23);
222
223 return DriverResult::Success;
224}
225
226DriverResult IrsProtocol::WriteRegistersStep2() {
227 LOG_DEBUG(Input, "WriteRegistersStep2");
228 constexpr std::size_t max_tries = 28;
229 std::vector<u8> output;
230 std::size_t tries = 0;
231
232 const IrsWriteRegisters irs_registers{
233 .command = MCUCommand::ConfigureIR,
234 .sub_command = MCUSubCommand::WriteDeviceRegisters,
235 .number_of_registers = 0x8,
236 .registers =
237 {
238 IrsRegister{IrRegistersAddress::LedIntensitiyMSB,
239 static_cast<u8>(led_intensity >> 8)},
240 {IrRegistersAddress::LedIntensitiyLSB, static_cast<u8>(led_intensity & 0xff)},
241 {IrRegistersAddress::ImageFlip, static_cast<u8>(image_flip)},
242 {IrRegistersAddress::DenoiseSmoothing, static_cast<u8>((denoise >> 16) & 0xff)},
243 {IrRegistersAddress::DenoiseEdge, static_cast<u8>((denoise >> 8) & 0xff)},
244 {IrRegistersAddress::DenoiseColor, static_cast<u8>(denoise & 0xff)},
245 {IrRegistersAddress::UpdateTime, 0x2d},
246 {IrRegistersAddress::FinalizeConfig, 0x01},
247 },
248 .crc = {},
249 };
250
251 std::array<u8, sizeof(IrsWriteRegisters)> request_data{};
252 memcpy(request_data.data(), &irs_registers, sizeof(IrsWriteRegisters));
253 request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
254 do {
255 const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, request_data, output);
256
257 if (result != DriverResult::Success) {
258 return result;
259 }
260 if (tries++ >= max_tries) {
261 return DriverResult::WrongReply;
262 }
263 } while (output[15] != 0x13 && output[15] != 0x23);
264
265 return DriverResult::Success;
266}
267
268DriverResult IrsProtocol::RequestFrame(u8 frame) {
269 std::array<u8, 38> mcu_request{};
270 mcu_request[3] = frame;
271 mcu_request[36] = CalculateMCU_CRC8(mcu_request.data(), 36);
272 mcu_request[37] = 0xFF;
273 return SendMCUCommand(SubCommand::SET_REPORT_MODE, mcu_request);
274}
275
276DriverResult IrsProtocol::ResendFrame(u8 frame) {
277 std::array<u8, 38> mcu_request{};
278 mcu_request[1] = 0x1;
279 mcu_request[2] = frame;
280 mcu_request[3] = 0x0;
281 mcu_request[36] = CalculateMCU_CRC8(mcu_request.data(), 36);
282 mcu_request[37] = 0xFF;
283 return SendMCUCommand(SubCommand::SET_REPORT_MODE, mcu_request);
284}
285
286std::vector<u8> IrsProtocol::GetImage() const {
287 return buf_image;
288}
289
290IrsResolution IrsProtocol::GetIrsFormat() const {
291 return resolution;
292}
293
294bool IrsProtocol::IsEnabled() const {
295 return is_enabled;
296}
297
298} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/irs.h b/src/input_common/helpers/joycon_protocol/irs.h
new file mode 100644
index 000000000..76dfa02ea
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/irs.h
@@ -0,0 +1,63 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
5// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
6// https://github.com/CTCaer/jc_toolkit
7// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
8
9#pragma once
10
11#include <vector>
12
13#include "input_common/helpers/joycon_protocol/common_protocol.h"
14#include "input_common/helpers/joycon_protocol/joycon_types.h"
15
16namespace InputCommon::Joycon {
17
18class IrsProtocol final : private JoyconCommonProtocol {
19public:
20 explicit IrsProtocol(std::shared_ptr<JoyconHandle> handle);
21
22 DriverResult EnableIrs();
23
24 DriverResult DisableIrs();
25
26 DriverResult SetIrsConfig(IrsMode mode, IrsResolution format);
27
28 DriverResult RequestImage(std::span<u8> buffer);
29
30 std::vector<u8> GetImage() const;
31
32 IrsResolution GetIrsFormat() const;
33
34 bool IsEnabled() const;
35
36private:
37 DriverResult ConfigureIrs();
38
39 DriverResult WriteRegistersStep1();
40 DriverResult WriteRegistersStep2();
41
42 DriverResult RequestFrame(u8 frame);
43 DriverResult ResendFrame(u8 frame);
44
45 IrsMode irs_mode{IrsMode::ImageTransfer};
46 IrsResolution resolution{IrsResolution::Size40x30};
47 IrsResolutionCode resolution_code{IrsResolutionCode::Size40x30};
48 IrsFragments fragments{IrsFragments::Size40x30};
49 IrLeds leds{IrLeds::BrightAndDim};
50 IrExLedFilter led_filter{IrExLedFilter::Enabled};
51 IrImageFlip image_flip{IrImageFlip::Normal};
52 u8 digital_gain{0x01};
53 u16 exposure{0x2490};
54 u16 led_intensity{0x0f10};
55 u32 denoise{0x012344};
56
57 u8 packet_fragment{};
58 std::vector<u8> buf_image; // 8bpp greyscale image.
59
60 bool is_enabled{};
61};
62
63} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/joycon_types.h b/src/input_common/helpers/joycon_protocol/joycon_types.h
new file mode 100644
index 000000000..e2d47349f
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/joycon_types.h
@@ -0,0 +1,612 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
5// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
6// https://github.com/CTCaer/jc_toolkit
7// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
8
9#pragma once
10
11#include <array>
12#include <functional>
13#include <SDL_hidapi.h>
14
15#include "common/bit_field.h"
16#include "common/common_funcs.h"
17#include "common/common_types.h"
18
19namespace InputCommon::Joycon {
20constexpr u32 MaxErrorCount = 50;
21constexpr u32 MaxBufferSize = 368;
22constexpr u32 MaxResponseSize = 49;
23constexpr u32 MaxSubCommandResponseSize = 64;
24constexpr std::array<u8, 8> DefaultVibrationBuffer{0x0, 0x1, 0x40, 0x40, 0x0, 0x1, 0x40, 0x40};
25
26using MacAddress = std::array<u8, 6>;
27using SerialNumber = std::array<u8, 15>;
28
29enum class ControllerType {
30 None,
31 Left,
32 Right,
33 Pro,
34 Grip,
35 Dual,
36};
37
38enum class PadAxes {
39 LeftStickX,
40 LeftStickY,
41 RightStickX,
42 RightStickY,
43 Undefined,
44};
45
46enum class PadMotion {
47 LeftMotion,
48 RightMotion,
49 Undefined,
50};
51
52enum class PadButton : u32 {
53 Down = 0x000001,
54 Up = 0x000002,
55 Right = 0x000004,
56 Left = 0x000008,
57 LeftSR = 0x000010,
58 LeftSL = 0x000020,
59 L = 0x000040,
60 ZL = 0x000080,
61 Y = 0x000100,
62 X = 0x000200,
63 B = 0x000400,
64 A = 0x000800,
65 RightSR = 0x001000,
66 RightSL = 0x002000,
67 R = 0x004000,
68 ZR = 0x008000,
69 Minus = 0x010000,
70 Plus = 0x020000,
71 StickR = 0x040000,
72 StickL = 0x080000,
73 Home = 0x100000,
74 Capture = 0x200000,
75};
76
77enum class PasivePadButton : u32 {
78 Down_A = 0x0001,
79 Right_X = 0x0002,
80 Left_B = 0x0004,
81 Up_Y = 0x0008,
82 SL = 0x0010,
83 SR = 0x0020,
84 Minus = 0x0100,
85 Plus = 0x0200,
86 StickL = 0x0400,
87 StickR = 0x0800,
88 Home = 0x1000,
89 Capture = 0x2000,
90 L_R = 0x4000,
91 ZL_ZR = 0x8000,
92};
93
94enum class OutputReport : u8 {
95 RUMBLE_AND_SUBCMD = 0x01,
96 FW_UPDATE_PKT = 0x03,
97 RUMBLE_ONLY = 0x10,
98 MCU_DATA = 0x11,
99 USB_CMD = 0x80,
100};
101
102enum class InputReport : u8 {
103 SUBCMD_REPLY = 0x21,
104 STANDARD_FULL_60HZ = 0x30,
105 NFC_IR_MODE_60HZ = 0x31,
106 SIMPLE_HID_MODE = 0x3F,
107 INPUT_USB_RESPONSE = 0x81,
108};
109
110enum class FeatureReport : u8 {
111 Last_SUBCMD = 0x02,
112 OTA_GW_UPGRADE = 0x70,
113 SETUP_MEM_READ = 0x71,
114 MEM_READ = 0x72,
115 ERASE_MEM_SECTOR = 0x73,
116 MEM_WRITE = 0x74,
117 LAUNCH = 0x75,
118};
119
120enum class SubCommand : u8 {
121 STATE = 0x00,
122 MANUAL_BT_PAIRING = 0x01,
123 REQ_DEV_INFO = 0x02,
124 SET_REPORT_MODE = 0x03,
125 TRIGGERS_ELAPSED = 0x04,
126 GET_PAGE_LIST_STATE = 0x05,
127 SET_HCI_STATE = 0x06,
128 RESET_PAIRING_INFO = 0x07,
129 LOW_POWER_MODE = 0x08,
130 SPI_FLASH_READ = 0x10,
131 SPI_FLASH_WRITE = 0x11,
132 RESET_MCU = 0x20,
133 SET_MCU_CONFIG = 0x21,
134 SET_MCU_STATE = 0x22,
135 SET_PLAYER_LIGHTS = 0x30,
136 GET_PLAYER_LIGHTS = 0x31,
137 SET_HOME_LIGHT = 0x38,
138 ENABLE_IMU = 0x40,
139 SET_IMU_SENSITIVITY = 0x41,
140 WRITE_IMU_REG = 0x42,
141 READ_IMU_REG = 0x43,
142 ENABLE_VIBRATION = 0x48,
143 GET_REGULATED_VOLTAGE = 0x50,
144 SET_EXTERNAL_CONFIG = 0x58,
145 UNKNOWN_RINGCON = 0x59,
146 UNKNOWN_RINGCON2 = 0x5A,
147 UNKNOWN_RINGCON3 = 0x5C,
148};
149
150enum class UsbSubCommand : u8 {
151 CONN_STATUS = 0x01,
152 HADSHAKE = 0x02,
153 BAUDRATE_3M = 0x03,
154 NO_TIMEOUT = 0x04,
155 EN_TIMEOUT = 0x05,
156 RESET = 0x06,
157 PRE_HANDSHAKE = 0x91,
158 SEND_UART = 0x92,
159};
160
161enum class CalMagic : u8 {
162 USR_MAGIC_0 = 0xB2,
163 USR_MAGIC_1 = 0xA1,
164 USRR_MAGI_SIZE = 2,
165};
166
167enum class CalAddr {
168 SERIAL_NUMBER = 0X6000,
169 DEVICE_TYPE = 0X6012,
170 COLOR_EXIST = 0X601B,
171 FACT_LEFT_DATA = 0X603d,
172 FACT_RIGHT_DATA = 0X6046,
173 COLOR_DATA = 0X6050,
174 FACT_IMU_DATA = 0X6020,
175 USER_LEFT_MAGIC = 0X8010,
176 USER_LEFT_DATA = 0X8012,
177 USER_RIGHT_MAGIC = 0X801B,
178 USER_RIGHT_DATA = 0X801D,
179 USER_IMU_MAGIC = 0X8026,
180 USER_IMU_DATA = 0X8028,
181};
182
183enum class ReportMode : u8 {
184 ACTIVE_POLLING_NFC_IR_CAMERA_DATA = 0x00,
185 ACTIVE_POLLING_NFC_IR_CAMERA_CONFIGURATION = 0x01,
186 ACTIVE_POLLING_NFC_IR_CAMERA_DATA_CONFIGURATION = 0x02,
187 ACTIVE_POLLING_IR_CAMERA_DATA = 0x03,
188 MCU_UPDATE_STATE = 0x23,
189 STANDARD_FULL_60HZ = 0x30,
190 NFC_IR_MODE_60HZ = 0x31,
191 SIMPLE_HID_MODE = 0x3F,
192};
193
194enum class GyroSensitivity : u8 {
195 DPS250,
196 DPS500,
197 DPS1000,
198 DPS2000, // Default
199};
200
201enum class AccelerometerSensitivity : u8 {
202 G8, // Default
203 G4,
204 G2,
205 G16,
206};
207
208enum class GyroPerformance : u8 {
209 HZ833,
210 HZ208, // Default
211};
212
213enum class AccelerometerPerformance : u8 {
214 HZ200,
215 HZ100, // Default
216};
217
218enum class MCUCommand : u8 {
219 ConfigureMCU = 0x21,
220 ConfigureIR = 0x23,
221};
222
223enum class MCUSubCommand : u8 {
224 SetMCUMode = 0x0,
225 SetDeviceMode = 0x1,
226 ReadDeviceMode = 0x02,
227 WriteDeviceRegisters = 0x4,
228};
229
230enum class MCUMode : u8 {
231 Suspend = 0,
232 Standby = 1,
233 Ringcon = 3,
234 NFC = 4,
235 IR = 5,
236 MaybeFWUpdate = 6,
237};
238
239enum class MCURequest : u8 {
240 GetMCUStatus = 1,
241 GetNFCData = 2,
242 GetIRData = 3,
243};
244
245enum class MCUReport : u8 {
246 Empty = 0x00,
247 StateReport = 0x01,
248 IRData = 0x03,
249 BusyInitializing = 0x0b,
250 IRStatus = 0x13,
251 IRRegisters = 0x1b,
252 NFCState = 0x2a,
253 NFCReadData = 0x3a,
254 EmptyAwaitingCmd = 0xff,
255};
256
257enum class MCUPacketFlag : u8 {
258 MorePacketsRemaining = 0x00,
259 LastCommandPacket = 0x08,
260};
261
262enum class NFCReadCommand : u8 {
263 CancelAll = 0x00,
264 StartPolling = 0x01,
265 StopPolling = 0x02,
266 StartWaitingRecieve = 0x04,
267 Ntag = 0x06,
268 Mifare = 0x0F,
269};
270
271enum class NFCTagType : u8 {
272 AllTags = 0x00,
273 Ntag215 = 0x01,
274};
275
276enum class NFCPages {
277 Block0 = 0,
278 Block45 = 45,
279 Block135 = 135,
280 Block231 = 231,
281};
282
283enum class NFCStatus : u8 {
284 LastPackage = 0x04,
285 TagLost = 0x07,
286};
287
288enum class IrsMode : u8 {
289 None = 0x02,
290 Moment = 0x03,
291 Dpd = 0x04,
292 Clustering = 0x06,
293 ImageTransfer = 0x07,
294 Silhouette = 0x08,
295 TeraImage = 0x09,
296 SilhouetteTeraImage = 0x0A,
297};
298
299enum class IrsResolution {
300 Size320x240,
301 Size160x120,
302 Size80x60,
303 Size40x30,
304 Size20x15,
305 None,
306};
307
308enum class IrsResolutionCode : u8 {
309 Size320x240 = 0x00, // Full pixel array
310 Size160x120 = 0x50, // Sensor Binning [2 X 2]
311 Size80x60 = 0x64, // Sensor Binning [4 x 2] and Skipping [1 x 2]
312 Size40x30 = 0x69, // Sensor Binning [4 x 2] and Skipping [2 x 4]
313 Size20x15 = 0x6A, // Sensor Binning [4 x 2] and Skipping [4 x 4]
314};
315
316// Size of image divided by 300
317enum class IrsFragments : u8 {
318 Size20x15 = 0x00,
319 Size40x30 = 0x03,
320 Size80x60 = 0x0f,
321 Size160x120 = 0x3f,
322 Size320x240 = 0xFF,
323};
324
325enum class IrLeds : u8 {
326 BrightAndDim = 0x00,
327 Bright = 0x20,
328 Dim = 0x10,
329 None = 0x30,
330};
331
332enum class IrExLedFilter : u8 {
333 Disabled = 0x00,
334 Enabled = 0x03,
335};
336
337enum class IrImageFlip : u8 {
338 Normal = 0x00,
339 Inverted = 0x02,
340};
341
342enum class IrRegistersAddress : u16 {
343 UpdateTime = 0x0400,
344 FinalizeConfig = 0x0700,
345 LedFilter = 0x0e00,
346 Leds = 0x1000,
347 LedIntensitiyMSB = 0x1100,
348 LedIntensitiyLSB = 0x1200,
349 ImageFlip = 0x2d00,
350 Resolution = 0x2e00,
351 DigitalGainLSB = 0x2e01,
352 DigitalGainMSB = 0x2f01,
353 ExposureLSB = 0x3001,
354 ExposureMSB = 0x3101,
355 ExposureTime = 0x3201,
356 WhitePixelThreshold = 0x4301,
357 DenoiseSmoothing = 0x6701,
358 DenoiseEdge = 0x6801,
359 DenoiseColor = 0x6901,
360};
361
362enum class DriverResult {
363 Success,
364 WrongReply,
365 Timeout,
366 UnsupportedControllerType,
367 HandleInUse,
368 ErrorReadingData,
369 ErrorWritingData,
370 NoDeviceDetected,
371 InvalidHandle,
372 NotSupported,
373 Disabled,
374 Unknown,
375};
376
377struct MotionSensorCalibration {
378 s16 offset;
379 s16 scale;
380};
381
382struct MotionCalibration {
383 std::array<MotionSensorCalibration, 3> accelerometer;
384 std::array<MotionSensorCalibration, 3> gyro;
385};
386
387// Basic motion data containing data from the sensors and a timestamp in microseconds
388struct MotionData {
389 float gyro_x{};
390 float gyro_y{};
391 float gyro_z{};
392 float accel_x{};
393 float accel_y{};
394 float accel_z{};
395 u64 delta_timestamp{};
396};
397
398struct JoyStickAxisCalibration {
399 u16 max{1};
400 u16 min{1};
401 u16 center{0};
402};
403
404struct JoyStickCalibration {
405 JoyStickAxisCalibration x;
406 JoyStickAxisCalibration y;
407};
408
409struct RingCalibration {
410 s16 default_value;
411 s16 max_value;
412 s16 min_value;
413};
414
415struct Color {
416 u32 body;
417 u32 buttons;
418 u32 left_grip;
419 u32 right_grip;
420};
421
422struct Battery {
423 union {
424 u8 raw{};
425
426 BitField<0, 4, u8> unknown;
427 BitField<4, 1, u8> charging;
428 BitField<5, 3, u8> status;
429 };
430};
431
432struct VibrationValue {
433 f32 low_amplitude;
434 f32 low_frequency;
435 f32 high_amplitude;
436 f32 high_frequency;
437};
438
439struct JoyconHandle {
440 SDL_hid_device* handle = nullptr;
441 u8 packet_counter{};
442};
443
444struct MCUConfig {
445 MCUCommand command;
446 MCUSubCommand sub_command;
447 MCUMode mode;
448 INSERT_PADDING_BYTES(0x22);
449 u8 crc;
450};
451static_assert(sizeof(MCUConfig) == 0x26, "MCUConfig is an invalid size");
452
453#pragma pack(push, 1)
454struct InputReportPassive {
455 InputReport report_mode;
456 u16 button_input;
457 u8 stick_state;
458 std::array<u8, 10> unknown_data;
459};
460static_assert(sizeof(InputReportPassive) == 0xE, "InputReportPassive is an invalid size");
461
462struct InputReportActive {
463 InputReport report_mode;
464 u8 packet_id;
465 Battery battery_status;
466 std::array<u8, 3> button_input;
467 std::array<u8, 3> left_stick_state;
468 std::array<u8, 3> right_stick_state;
469 u8 vibration_code;
470 std::array<s16, 6 * 2> motion_input;
471 INSERT_PADDING_BYTES(0x2);
472 s16 ring_input;
473};
474static_assert(sizeof(InputReportActive) == 0x29, "InputReportActive is an invalid size");
475
476struct InputReportNfcIr {
477 InputReport report_mode;
478 u8 packet_id;
479 Battery battery_status;
480 std::array<u8, 3> button_input;
481 std::array<u8, 3> left_stick_state;
482 std::array<u8, 3> right_stick_state;
483 u8 vibration_code;
484 std::array<s16, 6 * 2> motion_input;
485 INSERT_PADDING_BYTES(0x4);
486};
487static_assert(sizeof(InputReportNfcIr) == 0x29, "InputReportNfcIr is an invalid size");
488#pragma pack(pop)
489
490struct IMUCalibration {
491 std::array<s16, 3> accelerometer_offset;
492 std::array<s16, 3> accelerometer_scale;
493 std::array<s16, 3> gyroscope_offset;
494 std::array<s16, 3> gyroscope_scale;
495};
496static_assert(sizeof(IMUCalibration) == 0x18, "IMUCalibration is an invalid size");
497
498struct NFCReadBlock {
499 u8 start;
500 u8 end;
501};
502static_assert(sizeof(NFCReadBlock) == 0x2, "NFCReadBlock is an invalid size");
503
504struct NFCReadBlockCommand {
505 u8 block_count{};
506 std::array<NFCReadBlock, 4> blocks{};
507};
508static_assert(sizeof(NFCReadBlockCommand) == 0x9, "NFCReadBlockCommand is an invalid size");
509
510struct NFCReadCommandData {
511 u8 unknown;
512 u8 uuid_length;
513 u8 unknown_2;
514 std::array<u8, 6> uid;
515 NFCTagType tag_type;
516 NFCReadBlockCommand read_block;
517};
518static_assert(sizeof(NFCReadCommandData) == 0x13, "NFCReadCommandData is an invalid size");
519
520struct NFCPollingCommandData {
521 u8 enable_mifare;
522 u8 unknown_1;
523 u8 unknown_2;
524 u8 unknown_3;
525 u8 unknown_4;
526};
527static_assert(sizeof(NFCPollingCommandData) == 0x05, "NFCPollingCommandData is an invalid size");
528
529struct NFCRequestState {
530 MCUSubCommand sub_command;
531 NFCReadCommand command_argument;
532 u8 packet_id;
533 INSERT_PADDING_BYTES(0x1);
534 MCUPacketFlag packet_flag;
535 u8 data_length;
536 union {
537 std::array<u8, 0x1F> raw_data;
538 NFCReadCommandData nfc_read;
539 NFCPollingCommandData nfc_polling;
540 };
541 u8 crc;
542};
543static_assert(sizeof(NFCRequestState) == 0x26, "NFCRequestState is an invalid size");
544
545struct IrsConfigure {
546 MCUCommand command;
547 MCUSubCommand sub_command;
548 IrsMode irs_mode;
549 IrsFragments number_of_fragments;
550 u16 mcu_major_version;
551 u16 mcu_minor_version;
552 INSERT_PADDING_BYTES(0x1D);
553 u8 crc;
554};
555static_assert(sizeof(IrsConfigure) == 0x26, "IrsConfigure is an invalid size");
556
557#pragma pack(push, 1)
558struct IrsRegister {
559 IrRegistersAddress address;
560 u8 value;
561};
562static_assert(sizeof(IrsRegister) == 0x3, "IrsRegister is an invalid size");
563
564struct IrsWriteRegisters {
565 MCUCommand command;
566 MCUSubCommand sub_command;
567 u8 number_of_registers;
568 std::array<IrsRegister, 9> registers;
569 INSERT_PADDING_BYTES(0x7);
570 u8 crc;
571};
572static_assert(sizeof(IrsWriteRegisters) == 0x26, "IrsWriteRegisters is an invalid size");
573#pragma pack(pop)
574
575struct FirmwareVersion {
576 u8 major;
577 u8 minor;
578};
579static_assert(sizeof(FirmwareVersion) == 0x2, "FirmwareVersion is an invalid size");
580
581struct DeviceInfo {
582 FirmwareVersion firmware;
583 MacAddress mac_address;
584};
585static_assert(sizeof(DeviceInfo) == 0x8, "DeviceInfo is an invalid size");
586
587struct MotionStatus {
588 bool is_enabled;
589 u64 delta_time;
590 GyroSensitivity gyro_sensitivity;
591 AccelerometerSensitivity accelerometer_sensitivity;
592};
593
594struct RingStatus {
595 bool is_enabled;
596 s16 default_value;
597 s16 max_value;
598 s16 min_value;
599};
600
601struct JoyconCallbacks {
602 std::function<void(Battery)> on_battery_data;
603 std::function<void(Color)> on_color_data;
604 std::function<void(int, bool)> on_button_data;
605 std::function<void(int, f32)> on_stick_data;
606 std::function<void(int, const MotionData&)> on_motion_data;
607 std::function<void(f32)> on_ring_data;
608 std::function<void(const std::vector<u8>&)> on_amiibo_data;
609 std::function<void(const std::vector<u8>&, IrsResolution)> on_camera_data;
610};
611
612} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/nfc.cpp b/src/input_common/helpers/joycon_protocol/nfc.cpp
new file mode 100644
index 000000000..5c0f71722
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/nfc.cpp
@@ -0,0 +1,400 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <thread>
5#include "common/logging/log.h"
6#include "input_common/helpers/joycon_protocol/nfc.h"
7
8namespace InputCommon::Joycon {
9
10NfcProtocol::NfcProtocol(std::shared_ptr<JoyconHandle> handle)
11 : JoyconCommonProtocol(std::move(handle)) {}
12
13DriverResult NfcProtocol::EnableNfc() {
14 LOG_INFO(Input, "Enable NFC");
15 ScopedSetBlocking sb(this);
16 DriverResult result{DriverResult::Success};
17
18 if (result == DriverResult::Success) {
19 result = SetReportMode(ReportMode::NFC_IR_MODE_60HZ);
20 }
21 if (result == DriverResult::Success) {
22 result = EnableMCU(true);
23 }
24 if (result == DriverResult::Success) {
25 result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::Standby);
26 }
27 if (result == DriverResult::Success) {
28 const MCUConfig config{
29 .command = MCUCommand::ConfigureMCU,
30 .sub_command = MCUSubCommand::SetMCUMode,
31 .mode = MCUMode::NFC,
32 .crc = {},
33 };
34
35 result = ConfigureMCU(config);
36 }
37
38 return result;
39}
40
41DriverResult NfcProtocol::DisableNfc() {
42 LOG_DEBUG(Input, "Disable NFC");
43 ScopedSetBlocking sb(this);
44 DriverResult result{DriverResult::Success};
45
46 if (result == DriverResult::Success) {
47 result = EnableMCU(false);
48 }
49
50 is_enabled = false;
51
52 return result;
53}
54
55DriverResult NfcProtocol::StartNFCPollingMode() {
56 LOG_DEBUG(Input, "Start NFC pooling Mode");
57 ScopedSetBlocking sb(this);
58 DriverResult result{DriverResult::Success};
59 TagFoundData tag_data{};
60
61 if (result == DriverResult::Success) {
62 result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::NFC);
63 }
64 if (result == DriverResult::Success) {
65 result = WaitUntilNfcIsReady();
66 }
67 if (result == DriverResult::Success) {
68 is_enabled = true;
69 }
70
71 return result;
72}
73
74DriverResult NfcProtocol::ScanAmiibo(std::vector<u8>& data) {
75 LOG_DEBUG(Input, "Start NFC pooling Mode");
76 ScopedSetBlocking sb(this);
77 DriverResult result{DriverResult::Success};
78 TagFoundData tag_data{};
79
80 if (result == DriverResult::Success) {
81 result = StartPolling(tag_data);
82 }
83 if (result == DriverResult::Success) {
84 result = ReadTag(tag_data);
85 }
86 if (result == DriverResult::Success) {
87 result = WaitUntilNfcIsReady();
88 }
89 if (result == DriverResult::Success) {
90 result = StartPolling(tag_data);
91 }
92 if (result == DriverResult::Success) {
93 result = GetAmiiboData(data);
94 }
95
96 return result;
97}
98
99bool NfcProtocol::HasAmiibo() {
100 ScopedSetBlocking sb(this);
101 DriverResult result{DriverResult::Success};
102 TagFoundData tag_data{};
103
104 if (result == DriverResult::Success) {
105 result = StartPolling(tag_data);
106 }
107
108 return result == DriverResult::Success;
109}
110
111DriverResult NfcProtocol::WaitUntilNfcIsReady() {
112 constexpr std::size_t timeout_limit = 10;
113 std::vector<u8> output;
114 std::size_t tries = 0;
115
116 do {
117 auto result = SendStartWaitingRecieveRequest(output);
118
119 if (result != DriverResult::Success) {
120 return result;
121 }
122 if (tries++ > timeout_limit) {
123 return DriverResult::Timeout;
124 }
125 } while (output[49] != 0x2a || (output[51] << 8) + output[50] != 0x0500 || output[55] != 0x31 ||
126 output[56] != 0x00);
127
128 return DriverResult::Success;
129}
130
131DriverResult NfcProtocol::StartPolling(TagFoundData& data) {
132 LOG_DEBUG(Input, "Start Polling for tag");
133 constexpr std::size_t timeout_limit = 7;
134 std::vector<u8> output;
135 std::size_t tries = 0;
136
137 do {
138 const auto result = SendStartPollingRequest(output);
139 if (result != DriverResult::Success) {
140 return result;
141 }
142 if (tries++ > timeout_limit) {
143 return DriverResult::Timeout;
144 }
145 } while (output[49] != 0x2a || (output[51] << 8) + output[50] != 0x0500 || output[56] != 0x09);
146
147 data.type = output[62];
148 data.uuid.resize(output[64]);
149 memcpy(data.uuid.data(), output.data() + 65, data.uuid.size());
150
151 return DriverResult::Success;
152}
153
154DriverResult NfcProtocol::ReadTag(const TagFoundData& data) {
155 constexpr std::size_t timeout_limit = 10;
156 std::vector<u8> output;
157 std::size_t tries = 0;
158
159 std::string uuid_string;
160 for (auto& content : data.uuid) {
161 uuid_string += fmt::format(" {:02x}", content);
162 }
163
164 LOG_INFO(Input, "Tag detected, type={}, uuid={}", data.type, uuid_string);
165
166 tries = 0;
167 NFCPages ntag_pages = NFCPages::Block0;
168 // Read Tag data
169 while (true) {
170 auto result = SendReadAmiiboRequest(output, ntag_pages);
171 const auto mcu_report = static_cast<MCUReport>(output[49]);
172 const auto nfc_status = static_cast<NFCStatus>(output[56]);
173
174 if (result != DriverResult::Success) {
175 return result;
176 }
177
178 if ((mcu_report == MCUReport::NFCReadData || mcu_report == MCUReport::NFCState) &&
179 nfc_status == NFCStatus::TagLost) {
180 return DriverResult::ErrorReadingData;
181 }
182
183 if (mcu_report == MCUReport::NFCReadData && output[51] == 0x07 && output[52] == 0x01) {
184 if (data.type != 2) {
185 continue;
186 }
187 switch (output[74]) {
188 case 0:
189 ntag_pages = NFCPages::Block135;
190 break;
191 case 3:
192 ntag_pages = NFCPages::Block45;
193 break;
194 case 4:
195 ntag_pages = NFCPages::Block231;
196 break;
197 default:
198 return DriverResult::ErrorReadingData;
199 }
200 continue;
201 }
202
203 if (mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::LastPackage) {
204 // finished
205 SendStopPollingRequest(output);
206 return DriverResult::Success;
207 }
208
209 // Ignore other state reports
210 if (mcu_report == MCUReport::NFCState) {
211 continue;
212 }
213
214 if (tries++ > timeout_limit) {
215 return DriverResult::Timeout;
216 }
217 }
218
219 return DriverResult::Success;
220}
221
222DriverResult NfcProtocol::GetAmiiboData(std::vector<u8>& ntag_data) {
223 constexpr std::size_t timeout_limit = 10;
224 std::vector<u8> output;
225 std::size_t tries = 0;
226
227 NFCPages ntag_pages = NFCPages::Block135;
228 std::size_t ntag_buffer_pos = 0;
229 // Read Tag data
230 while (true) {
231 auto result = SendReadAmiiboRequest(output, ntag_pages);
232 const auto mcu_report = static_cast<MCUReport>(output[49]);
233 const auto nfc_status = static_cast<NFCStatus>(output[56]);
234
235 if (result != DriverResult::Success) {
236 return result;
237 }
238
239 if ((mcu_report == MCUReport::NFCReadData || mcu_report == MCUReport::NFCState) &&
240 nfc_status == NFCStatus::TagLost) {
241 return DriverResult::ErrorReadingData;
242 }
243
244 if (mcu_report == MCUReport::NFCReadData && output[51] == 0x07) {
245 std::size_t payload_size = (output[54] << 8 | output[55]) & 0x7FF;
246 if (output[52] == 0x01) {
247 memcpy(ntag_data.data() + ntag_buffer_pos, output.data() + 116, payload_size - 60);
248 ntag_buffer_pos += payload_size - 60;
249 } else {
250 memcpy(ntag_data.data() + ntag_buffer_pos, output.data() + 56, payload_size);
251 }
252 continue;
253 }
254
255 if (mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::LastPackage) {
256 LOG_INFO(Input, "Finished reading amiibo");
257 return DriverResult::Success;
258 }
259
260 // Ignore other state reports
261 if (mcu_report == MCUReport::NFCState) {
262 continue;
263 }
264
265 if (tries++ > timeout_limit) {
266 return DriverResult::Timeout;
267 }
268 }
269
270 return DriverResult::Success;
271}
272
273DriverResult NfcProtocol::SendStartPollingRequest(std::vector<u8>& output) {
274 NFCRequestState request{
275 .sub_command = MCUSubCommand::ReadDeviceMode,
276 .command_argument = NFCReadCommand::StartPolling,
277 .packet_id = 0x0,
278 .packet_flag = MCUPacketFlag::LastCommandPacket,
279 .data_length = sizeof(NFCPollingCommandData),
280 .nfc_polling =
281 {
282 .enable_mifare = 0x01,
283 .unknown_1 = 0x00,
284 .unknown_2 = 0x00,
285 .unknown_3 = 0x2c,
286 .unknown_4 = 0x01,
287 },
288 .crc = {},
289 };
290
291 std::array<u8, sizeof(NFCRequestState)> request_data{};
292 memcpy(request_data.data(), &request, sizeof(NFCRequestState));
293 request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
294 return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
295}
296
297DriverResult NfcProtocol::SendStopPollingRequest(std::vector<u8>& output) {
298 NFCRequestState request{
299 .sub_command = MCUSubCommand::ReadDeviceMode,
300 .command_argument = NFCReadCommand::StopPolling,
301 .packet_id = 0x0,
302 .packet_flag = MCUPacketFlag::LastCommandPacket,
303 .data_length = 0,
304 .raw_data = {},
305 .crc = {},
306 };
307
308 std::array<u8, sizeof(NFCRequestState)> request_data{};
309 memcpy(request_data.data(), &request, sizeof(NFCRequestState));
310 request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
311 return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
312}
313
314DriverResult NfcProtocol::SendStartWaitingRecieveRequest(std::vector<u8>& output) {
315 NFCRequestState request{
316 .sub_command = MCUSubCommand::ReadDeviceMode,
317 .command_argument = NFCReadCommand::StartWaitingRecieve,
318 .packet_id = 0x0,
319 .packet_flag = MCUPacketFlag::LastCommandPacket,
320 .data_length = 0,
321 .raw_data = {},
322 .crc = {},
323 };
324
325 std::vector<u8> request_data(sizeof(NFCRequestState));
326 memcpy(request_data.data(), &request, sizeof(NFCRequestState));
327 request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
328 return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
329}
330
331DriverResult NfcProtocol::SendReadAmiiboRequest(std::vector<u8>& output, NFCPages ntag_pages) {
332 NFCRequestState request{
333 .sub_command = MCUSubCommand::ReadDeviceMode,
334 .command_argument = NFCReadCommand::Ntag,
335 .packet_id = 0x0,
336 .packet_flag = MCUPacketFlag::LastCommandPacket,
337 .data_length = sizeof(NFCReadCommandData),
338 .nfc_read =
339 {
340 .unknown = 0xd0,
341 .uuid_length = 0x07,
342 .unknown_2 = 0x00,
343 .uid = {},
344 .tag_type = NFCTagType::AllTags,
345 .read_block = GetReadBlockCommand(ntag_pages),
346 },
347 .crc = {},
348 };
349
350 std::array<u8, sizeof(NFCRequestState)> request_data{};
351 memcpy(request_data.data(), &request, sizeof(NFCRequestState));
352 request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
353 return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
354}
355
356NFCReadBlockCommand NfcProtocol::GetReadBlockCommand(NFCPages pages) const {
357 switch (pages) {
358 case NFCPages::Block0:
359 return {
360 .block_count = 1,
361 };
362 case NFCPages::Block45:
363 return {
364 .block_count = 1,
365 .blocks =
366 {
367 NFCReadBlock{0x00, 0x2C},
368 },
369 };
370 case NFCPages::Block135:
371 return {
372 .block_count = 3,
373 .blocks =
374 {
375 NFCReadBlock{0x00, 0x3b},
376 {0x3c, 0x77},
377 {0x78, 0x86},
378 },
379 };
380 case NFCPages::Block231:
381 return {
382 .block_count = 4,
383 .blocks =
384 {
385 NFCReadBlock{0x00, 0x3b},
386 {0x3c, 0x77},
387 {0x78, 0x83},
388 {0xb4, 0xe6},
389 },
390 };
391 default:
392 return {};
393 };
394}
395
396bool NfcProtocol::IsEnabled() const {
397 return is_enabled;
398}
399
400} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/nfc.h b/src/input_common/helpers/joycon_protocol/nfc.h
new file mode 100644
index 000000000..e63665aa9
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/nfc.h
@@ -0,0 +1,61 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
5// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
6// https://github.com/CTCaer/jc_toolkit
7// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
8
9#pragma once
10
11#include <vector>
12
13#include "input_common/helpers/joycon_protocol/common_protocol.h"
14#include "input_common/helpers/joycon_protocol/joycon_types.h"
15
16namespace InputCommon::Joycon {
17
18class NfcProtocol final : private JoyconCommonProtocol {
19public:
20 explicit NfcProtocol(std::shared_ptr<JoyconHandle> handle);
21
22 DriverResult EnableNfc();
23
24 DriverResult DisableNfc();
25
26 DriverResult StartNFCPollingMode();
27
28 DriverResult ScanAmiibo(std::vector<u8>& data);
29
30 bool HasAmiibo();
31
32 bool IsEnabled() const;
33
34private:
35 struct TagFoundData {
36 u8 type;
37 std::vector<u8> uuid;
38 };
39
40 DriverResult WaitUntilNfcIsReady();
41
42 DriverResult StartPolling(TagFoundData& data);
43
44 DriverResult ReadTag(const TagFoundData& data);
45
46 DriverResult GetAmiiboData(std::vector<u8>& data);
47
48 DriverResult SendStartPollingRequest(std::vector<u8>& output);
49
50 DriverResult SendStopPollingRequest(std::vector<u8>& output);
51
52 DriverResult SendStartWaitingRecieveRequest(std::vector<u8>& output);
53
54 DriverResult SendReadAmiiboRequest(std::vector<u8>& output, NFCPages ntag_pages);
55
56 NFCReadBlockCommand GetReadBlockCommand(NFCPages pages) const;
57
58 bool is_enabled{};
59};
60
61} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/poller.cpp b/src/input_common/helpers/joycon_protocol/poller.cpp
new file mode 100644
index 000000000..7f8e093fa
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/poller.cpp
@@ -0,0 +1,341 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/logging/log.h"
5#include "input_common/helpers/joycon_protocol/poller.h"
6
7namespace InputCommon::Joycon {
8
9JoyconPoller::JoyconPoller(ControllerType device_type_, JoyStickCalibration left_stick_calibration_,
10 JoyStickCalibration right_stick_calibration_,
11 MotionCalibration motion_calibration_)
12 : device_type{device_type_}, left_stick_calibration{left_stick_calibration_},
13 right_stick_calibration{right_stick_calibration_}, motion_calibration{motion_calibration_} {}
14
15void JoyconPoller::SetCallbacks(const Joycon::JoyconCallbacks& callbacks_) {
16 callbacks = std::move(callbacks_);
17}
18
19void JoyconPoller::ReadActiveMode(std::span<u8> buffer, const MotionStatus& motion_status,
20 const RingStatus& ring_status) {
21 InputReportActive data{};
22 memcpy(&data, buffer.data(), sizeof(InputReportActive));
23
24 switch (device_type) {
25 case Joycon::ControllerType::Left:
26 UpdateActiveLeftPadInput(data, motion_status);
27 break;
28 case Joycon::ControllerType::Right:
29 UpdateActiveRightPadInput(data, motion_status);
30 break;
31 case Joycon::ControllerType::Pro:
32 UpdateActiveProPadInput(data, motion_status);
33 break;
34 case Joycon::ControllerType::Grip:
35 case Joycon::ControllerType::Dual:
36 case Joycon::ControllerType::None:
37 break;
38 }
39
40 if (ring_status.is_enabled) {
41 UpdateRing(data.ring_input, ring_status);
42 }
43
44 callbacks.on_battery_data(data.battery_status);
45}
46
47void JoyconPoller::ReadPassiveMode(std::span<u8> buffer) {
48 InputReportPassive data{};
49 memcpy(&data, buffer.data(), sizeof(InputReportPassive));
50
51 switch (device_type) {
52 case Joycon::ControllerType::Left:
53 UpdatePasiveLeftPadInput(data);
54 break;
55 case Joycon::ControllerType::Right:
56 UpdatePasiveRightPadInput(data);
57 break;
58 case Joycon::ControllerType::Pro:
59 UpdatePasiveProPadInput(data);
60 break;
61 case Joycon::ControllerType::Grip:
62 case Joycon::ControllerType::Dual:
63 case Joycon::ControllerType::None:
64 break;
65 }
66}
67
68void JoyconPoller::ReadNfcIRMode(std::span<u8> buffer, const MotionStatus& motion_status) {
69 // This mode is compatible with the active mode
70 ReadActiveMode(buffer, motion_status, {});
71}
72
73void JoyconPoller::UpdateColor(const Color& color) {
74 callbacks.on_color_data(color);
75}
76
77void JoyconPoller::UpdateAmiibo(const std::vector<u8>& amiibo_data) {
78 callbacks.on_amiibo_data(amiibo_data);
79}
80
81void JoyconPoller::UpdateCamera(const std::vector<u8>& camera_data, IrsResolution format) {
82 callbacks.on_camera_data(camera_data, format);
83}
84
85void JoyconPoller::UpdateRing(s16 value, const RingStatus& ring_status) {
86 float normalized_value = static_cast<float>(value - ring_status.default_value);
87 if (normalized_value > 0) {
88 normalized_value = normalized_value /
89 static_cast<float>(ring_status.max_value - ring_status.default_value);
90 }
91 if (normalized_value < 0) {
92 normalized_value = normalized_value /
93 static_cast<float>(ring_status.default_value - ring_status.min_value);
94 }
95 callbacks.on_ring_data(normalized_value);
96}
97
98void JoyconPoller::UpdateActiveLeftPadInput(const InputReportActive& input,
99 const MotionStatus& motion_status) {
100 static constexpr std::array<Joycon::PadButton, 11> left_buttons{
101 Joycon::PadButton::Down, Joycon::PadButton::Up, Joycon::PadButton::Right,
102 Joycon::PadButton::Left, Joycon::PadButton::LeftSL, Joycon::PadButton::LeftSR,
103 Joycon::PadButton::L, Joycon::PadButton::ZL, Joycon::PadButton::Minus,
104 Joycon::PadButton::Capture, Joycon::PadButton::StickL,
105 };
106
107 const u32 raw_button =
108 static_cast<u32>(input.button_input[2] | ((input.button_input[1] & 0b00101001) << 16));
109 for (std::size_t i = 0; i < left_buttons.size(); ++i) {
110 const bool button_status = (raw_button & static_cast<u32>(left_buttons[i])) != 0;
111 const int button = static_cast<int>(left_buttons[i]);
112 callbacks.on_button_data(button, button_status);
113 }
114
115 const u16 raw_left_axis_x =
116 static_cast<u16>(input.left_stick_state[0] | ((input.left_stick_state[1] & 0xf) << 8));
117 const u16 raw_left_axis_y =
118 static_cast<u16>((input.left_stick_state[1] >> 4) | (input.left_stick_state[2] << 4));
119 const f32 left_axis_x = GetAxisValue(raw_left_axis_x, left_stick_calibration.x);
120 const f32 left_axis_y = GetAxisValue(raw_left_axis_y, left_stick_calibration.y);
121 callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickX), left_axis_x);
122 callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickY), left_axis_y);
123
124 if (motion_status.is_enabled) {
125 auto left_motion = GetMotionInput(input, motion_status);
126 // Rotate motion axis to the correct direction
127 left_motion.accel_y = -left_motion.accel_y;
128 left_motion.accel_z = -left_motion.accel_z;
129 left_motion.gyro_x = -left_motion.gyro_x;
130 callbacks.on_motion_data(static_cast<int>(PadMotion::LeftMotion), left_motion);
131 }
132}
133
134void JoyconPoller::UpdateActiveRightPadInput(const InputReportActive& input,
135 const MotionStatus& motion_status) {
136 static constexpr std::array<Joycon::PadButton, 11> right_buttons{
137 Joycon::PadButton::Y, Joycon::PadButton::X, Joycon::PadButton::B,
138 Joycon::PadButton::A, Joycon::PadButton::RightSL, Joycon::PadButton::RightSR,
139 Joycon::PadButton::R, Joycon::PadButton::ZR, Joycon::PadButton::Plus,
140 Joycon::PadButton::Home, Joycon::PadButton::StickR,
141 };
142
143 const u32 raw_button =
144 static_cast<u32>((input.button_input[0] << 8) | (input.button_input[1] << 16));
145 for (std::size_t i = 0; i < right_buttons.size(); ++i) {
146 const bool button_status = (raw_button & static_cast<u32>(right_buttons[i])) != 0;
147 const int button = static_cast<int>(right_buttons[i]);
148 callbacks.on_button_data(button, button_status);
149 }
150
151 const u16 raw_right_axis_x =
152 static_cast<u16>(input.right_stick_state[0] | ((input.right_stick_state[1] & 0xf) << 8));
153 const u16 raw_right_axis_y =
154 static_cast<u16>((input.right_stick_state[1] >> 4) | (input.right_stick_state[2] << 4));
155 const f32 right_axis_x = GetAxisValue(raw_right_axis_x, right_stick_calibration.x);
156 const f32 right_axis_y = GetAxisValue(raw_right_axis_y, right_stick_calibration.y);
157 callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickX), right_axis_x);
158 callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickY), right_axis_y);
159
160 if (motion_status.is_enabled) {
161 auto right_motion = GetMotionInput(input, motion_status);
162 // Rotate motion axis to the correct direction
163 right_motion.accel_x = -right_motion.accel_x;
164 right_motion.accel_y = -right_motion.accel_y;
165 right_motion.gyro_z = -right_motion.gyro_z;
166 callbacks.on_motion_data(static_cast<int>(PadMotion::RightMotion), right_motion);
167 }
168}
169
170void JoyconPoller::UpdateActiveProPadInput(const InputReportActive& input,
171 const MotionStatus& motion_status) {
172 static constexpr std::array<Joycon::PadButton, 18> pro_buttons{
173 Joycon::PadButton::Down, Joycon::PadButton::Up, Joycon::PadButton::Right,
174 Joycon::PadButton::Left, Joycon::PadButton::L, Joycon::PadButton::ZL,
175 Joycon::PadButton::Minus, Joycon::PadButton::Capture, Joycon::PadButton::Y,
176 Joycon::PadButton::X, Joycon::PadButton::B, Joycon::PadButton::A,
177 Joycon::PadButton::R, Joycon::PadButton::ZR, Joycon::PadButton::Plus,
178 Joycon::PadButton::Home, Joycon::PadButton::StickL, Joycon::PadButton::StickR,
179 };
180
181 const u32 raw_button = static_cast<u32>(input.button_input[2] | (input.button_input[0] << 8) |
182 (input.button_input[1] << 16));
183 for (std::size_t i = 0; i < pro_buttons.size(); ++i) {
184 const bool button_status = (raw_button & static_cast<u32>(pro_buttons[i])) != 0;
185 const int button = static_cast<int>(pro_buttons[i]);
186 callbacks.on_button_data(button, button_status);
187 }
188
189 const u16 raw_left_axis_x =
190 static_cast<u16>(input.left_stick_state[0] | ((input.left_stick_state[1] & 0xf) << 8));
191 const u16 raw_left_axis_y =
192 static_cast<u16>((input.left_stick_state[1] >> 4) | (input.left_stick_state[2] << 4));
193 const u16 raw_right_axis_x =
194 static_cast<u16>(input.right_stick_state[0] | ((input.right_stick_state[1] & 0xf) << 8));
195 const u16 raw_right_axis_y =
196 static_cast<u16>((input.right_stick_state[1] >> 4) | (input.right_stick_state[2] << 4));
197
198 const f32 left_axis_x = GetAxisValue(raw_left_axis_x, left_stick_calibration.x);
199 const f32 left_axis_y = GetAxisValue(raw_left_axis_y, left_stick_calibration.y);
200 const f32 right_axis_x = GetAxisValue(raw_right_axis_x, right_stick_calibration.x);
201 const f32 right_axis_y = GetAxisValue(raw_right_axis_y, right_stick_calibration.y);
202 callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickX), left_axis_x);
203 callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickY), left_axis_y);
204 callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickX), right_axis_x);
205 callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickY), right_axis_y);
206
207 if (motion_status.is_enabled) {
208 auto pro_motion = GetMotionInput(input, motion_status);
209 pro_motion.gyro_x = -pro_motion.gyro_x;
210 pro_motion.accel_y = -pro_motion.accel_y;
211 pro_motion.accel_z = -pro_motion.accel_z;
212 callbacks.on_motion_data(static_cast<int>(PadMotion::LeftMotion), pro_motion);
213 callbacks.on_motion_data(static_cast<int>(PadMotion::RightMotion), pro_motion);
214 }
215}
216
217void JoyconPoller::UpdatePasiveLeftPadInput(const InputReportPassive& input) {
218 static constexpr std::array<Joycon::PasivePadButton, 11> left_buttons{
219 Joycon::PasivePadButton::Down_A, Joycon::PasivePadButton::Right_X,
220 Joycon::PasivePadButton::Left_B, Joycon::PasivePadButton::Up_Y,
221 Joycon::PasivePadButton::SL, Joycon::PasivePadButton::SR,
222 Joycon::PasivePadButton::L_R, Joycon::PasivePadButton::ZL_ZR,
223 Joycon::PasivePadButton::Minus, Joycon::PasivePadButton::Capture,
224 Joycon::PasivePadButton::StickL,
225 };
226
227 for (auto left_button : left_buttons) {
228 const bool button_status = (input.button_input & static_cast<u32>(left_button)) != 0;
229 const int button = static_cast<int>(left_button);
230 callbacks.on_button_data(button, button_status);
231 }
232}
233
234void JoyconPoller::UpdatePasiveRightPadInput(const InputReportPassive& input) {
235 static constexpr std::array<Joycon::PasivePadButton, 11> right_buttons{
236 Joycon::PasivePadButton::Down_A, Joycon::PasivePadButton::Right_X,
237 Joycon::PasivePadButton::Left_B, Joycon::PasivePadButton::Up_Y,
238 Joycon::PasivePadButton::SL, Joycon::PasivePadButton::SR,
239 Joycon::PasivePadButton::L_R, Joycon::PasivePadButton::ZL_ZR,
240 Joycon::PasivePadButton::Plus, Joycon::PasivePadButton::Home,
241 Joycon::PasivePadButton::StickR,
242 };
243
244 for (auto right_button : right_buttons) {
245 const bool button_status = (input.button_input & static_cast<u32>(right_button)) != 0;
246 const int button = static_cast<int>(right_button);
247 callbacks.on_button_data(button, button_status);
248 }
249}
250
251void JoyconPoller::UpdatePasiveProPadInput(const InputReportPassive& input) {
252 static constexpr std::array<Joycon::PasivePadButton, 14> pro_buttons{
253 Joycon::PasivePadButton::Down_A, Joycon::PasivePadButton::Right_X,
254 Joycon::PasivePadButton::Left_B, Joycon::PasivePadButton::Up_Y,
255 Joycon::PasivePadButton::SL, Joycon::PasivePadButton::SR,
256 Joycon::PasivePadButton::L_R, Joycon::PasivePadButton::ZL_ZR,
257 Joycon::PasivePadButton::Minus, Joycon::PasivePadButton::Plus,
258 Joycon::PasivePadButton::Capture, Joycon::PasivePadButton::Home,
259 Joycon::PasivePadButton::StickL, Joycon::PasivePadButton::StickR,
260 };
261
262 for (auto pro_button : pro_buttons) {
263 const bool button_status = (input.button_input & static_cast<u32>(pro_button)) != 0;
264 const int button = static_cast<int>(pro_button);
265 callbacks.on_button_data(button, button_status);
266 }
267}
268
269f32 JoyconPoller::GetAxisValue(u16 raw_value, Joycon::JoyStickAxisCalibration calibration) const {
270 const f32 value = static_cast<f32>(raw_value - calibration.center);
271 if (value > 0.0f) {
272 return value / calibration.max;
273 }
274 return value / calibration.min;
275}
276
277f32 JoyconPoller::GetAccelerometerValue(s16 raw, const MotionSensorCalibration& cal,
278 AccelerometerSensitivity sensitivity) const {
279 const f32 value = raw * (1.0f / (cal.scale - cal.offset)) * 4;
280 switch (sensitivity) {
281 case Joycon::AccelerometerSensitivity::G2:
282 return value / 4.0f;
283 case Joycon::AccelerometerSensitivity::G4:
284 return value / 2.0f;
285 case Joycon::AccelerometerSensitivity::G8:
286 return value;
287 case Joycon::AccelerometerSensitivity::G16:
288 return value * 2.0f;
289 }
290 return value;
291}
292
293f32 JoyconPoller::GetGyroValue(s16 raw, const MotionSensorCalibration& cal,
294 GyroSensitivity sensitivity) const {
295 const f32 value = (raw - cal.offset) * (936.0f / (cal.scale - cal.offset)) / 360.0f;
296 switch (sensitivity) {
297 case Joycon::GyroSensitivity::DPS250:
298 return value / 8.0f;
299 case Joycon::GyroSensitivity::DPS500:
300 return value / 4.0f;
301 case Joycon::GyroSensitivity::DPS1000:
302 return value / 2.0f;
303 case Joycon::GyroSensitivity::DPS2000:
304 return value;
305 }
306 return value;
307}
308
309s16 JoyconPoller::GetRawIMUValues(std::size_t sensor, size_t axis,
310 const InputReportActive& input) const {
311 return input.motion_input[(sensor * 3) + axis];
312}
313
314MotionData JoyconPoller::GetMotionInput(const InputReportActive& input,
315 const MotionStatus& motion_status) const {
316 MotionData motion{};
317 const auto& accel_cal = motion_calibration.accelerometer;
318 const auto& gyro_cal = motion_calibration.gyro;
319 const s16 raw_accel_x = input.motion_input[1];
320 const s16 raw_accel_y = input.motion_input[0];
321 const s16 raw_accel_z = input.motion_input[2];
322 const s16 raw_gyro_x = input.motion_input[4];
323 const s16 raw_gyro_y = input.motion_input[3];
324 const s16 raw_gyro_z = input.motion_input[5];
325
326 motion.delta_timestamp = motion_status.delta_time;
327 motion.accel_x =
328 GetAccelerometerValue(raw_accel_x, accel_cal[1], motion_status.accelerometer_sensitivity);
329 motion.accel_y =
330 GetAccelerometerValue(raw_accel_y, accel_cal[0], motion_status.accelerometer_sensitivity);
331 motion.accel_z =
332 GetAccelerometerValue(raw_accel_z, accel_cal[2], motion_status.accelerometer_sensitivity);
333 motion.gyro_x = GetGyroValue(raw_gyro_x, gyro_cal[1], motion_status.gyro_sensitivity);
334 motion.gyro_y = GetGyroValue(raw_gyro_y, gyro_cal[0], motion_status.gyro_sensitivity);
335 motion.gyro_z = GetGyroValue(raw_gyro_z, gyro_cal[2], motion_status.gyro_sensitivity);
336
337 // TODO(German77): Return all three samples data
338 return motion;
339}
340
341} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/poller.h b/src/input_common/helpers/joycon_protocol/poller.h
new file mode 100644
index 000000000..354d41dad
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/poller.h
@@ -0,0 +1,81 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
5// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
6// https://github.com/CTCaer/jc_toolkit
7// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
8
9#pragma once
10
11#include <functional>
12#include <span>
13
14#include "input_common/helpers/joycon_protocol/joycon_types.h"
15
16namespace InputCommon::Joycon {
17
18// Handles input packages and triggers the corresponding input events
19class JoyconPoller {
20public:
21 JoyconPoller(ControllerType device_type_, JoyStickCalibration left_stick_calibration_,
22 JoyStickCalibration right_stick_calibration_,
23 MotionCalibration motion_calibration_);
24
25 void SetCallbacks(const Joycon::JoyconCallbacks& callbacks_);
26
27 /// Handles data from passive packages
28 void ReadPassiveMode(std::span<u8> buffer);
29
30 /// Handles data from active packages
31 void ReadActiveMode(std::span<u8> buffer, const MotionStatus& motion_status,
32 const RingStatus& ring_status);
33
34 /// Handles data from nfc or ir packages
35 void ReadNfcIRMode(std::span<u8> buffer, const MotionStatus& motion_status);
36
37 void UpdateColor(const Color& color);
38 void UpdateRing(s16 value, const RingStatus& ring_status);
39 void UpdateAmiibo(const std::vector<u8>& amiibo_data);
40 void UpdateCamera(const std::vector<u8>& amiibo_data, IrsResolution format);
41
42private:
43 void UpdateActiveLeftPadInput(const InputReportActive& input,
44 const MotionStatus& motion_status);
45 void UpdateActiveRightPadInput(const InputReportActive& input,
46 const MotionStatus& motion_status);
47 void UpdateActiveProPadInput(const InputReportActive& input, const MotionStatus& motion_status);
48
49 void UpdatePasiveLeftPadInput(const InputReportPassive& buffer);
50 void UpdatePasiveRightPadInput(const InputReportPassive& buffer);
51 void UpdatePasiveProPadInput(const InputReportPassive& buffer);
52
53 /// Returns a calibrated joystick axis from raw axis data
54 f32 GetAxisValue(u16 raw_value, Joycon::JoyStickAxisCalibration calibration) const;
55
56 /// Returns a calibrated accelerometer axis from raw motion data
57 f32 GetAccelerometerValue(s16 raw, const MotionSensorCalibration& cal,
58 AccelerometerSensitivity sensitivity) const;
59
60 /// Returns a calibrated gyro axis from raw motion data
61 f32 GetGyroValue(s16 raw_value, const MotionSensorCalibration& cal,
62 GyroSensitivity sensitivity) const;
63
64 /// Returns a raw motion value from a buffer
65 s16 GetRawIMUValues(size_t sensor, size_t axis, const InputReportActive& input) const;
66
67 /// Returns motion data from a buffer
68 MotionData GetMotionInput(const InputReportActive& input,
69 const MotionStatus& motion_status) const;
70
71 ControllerType device_type{};
72
73 // Device calibration
74 JoyStickCalibration left_stick_calibration{};
75 JoyStickCalibration right_stick_calibration{};
76 MotionCalibration motion_calibration{};
77
78 Joycon::JoyconCallbacks callbacks{};
79};
80
81} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/ringcon.cpp b/src/input_common/helpers/joycon_protocol/ringcon.cpp
new file mode 100644
index 000000000..12f81309e
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/ringcon.cpp
@@ -0,0 +1,117 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/logging/log.h"
5#include "input_common/helpers/joycon_protocol/ringcon.h"
6
7namespace InputCommon::Joycon {
8
9RingConProtocol::RingConProtocol(std::shared_ptr<JoyconHandle> handle)
10 : JoyconCommonProtocol(std::move(handle)) {}
11
12DriverResult RingConProtocol::EnableRingCon() {
13 LOG_DEBUG(Input, "Enable Ringcon");
14 ScopedSetBlocking sb(this);
15 DriverResult result{DriverResult::Success};
16
17 if (result == DriverResult::Success) {
18 result = SetReportMode(ReportMode::STANDARD_FULL_60HZ);
19 }
20 if (result == DriverResult::Success) {
21 result = EnableMCU(true);
22 }
23 if (result == DriverResult::Success) {
24 const MCUConfig config{
25 .command = MCUCommand::ConfigureMCU,
26 .sub_command = MCUSubCommand::SetDeviceMode,
27 .mode = MCUMode::Standby,
28 .crc = {},
29 };
30 result = ConfigureMCU(config);
31 }
32
33 return result;
34}
35
36DriverResult RingConProtocol::DisableRingCon() {
37 LOG_DEBUG(Input, "Disable RingCon");
38 ScopedSetBlocking sb(this);
39 DriverResult result{DriverResult::Success};
40
41 if (result == DriverResult::Success) {
42 result = EnableMCU(false);
43 }
44
45 is_enabled = false;
46
47 return result;
48}
49
50DriverResult RingConProtocol::StartRingconPolling() {
51 LOG_DEBUG(Input, "Enable Ringcon");
52 ScopedSetBlocking sb(this);
53 DriverResult result{DriverResult::Success};
54 bool is_connected = false;
55
56 if (result == DriverResult::Success) {
57 result = IsRingConnected(is_connected);
58 }
59 if (result == DriverResult::Success && is_connected) {
60 LOG_INFO(Input, "Ringcon detected");
61 result = ConfigureRing();
62 }
63 if (result == DriverResult::Success) {
64 is_enabled = true;
65 }
66
67 return result;
68}
69
70DriverResult RingConProtocol::IsRingConnected(bool& is_connected) {
71 LOG_DEBUG(Input, "IsRingConnected");
72 constexpr std::size_t max_tries = 28;
73 constexpr u8 ring_controller_id = 0x20;
74 std::vector<u8> output;
75 std::size_t tries = 0;
76 is_connected = false;
77
78 do {
79 std::array<u8, 1> empty_data{};
80 const auto result = SendSubCommand(SubCommand::UNKNOWN_RINGCON, empty_data, output);
81
82 if (result != DriverResult::Success) {
83 return result;
84 }
85
86 if (tries++ >= max_tries) {
87 return DriverResult::NoDeviceDetected;
88 }
89 } while (output[16] != ring_controller_id);
90
91 is_connected = true;
92 return DriverResult::Success;
93}
94
95DriverResult RingConProtocol::ConfigureRing() {
96 LOG_DEBUG(Input, "ConfigureRing");
97
98 static constexpr std::array<u8, 37> ring_config{
99 0x06, 0x03, 0x25, 0x06, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x16, 0xED, 0x34, 0x36,
100 0x00, 0x00, 0x00, 0x0A, 0x64, 0x0B, 0xE6, 0xA9, 0x22, 0x00, 0x00, 0x04, 0x00,
101 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0xA8, 0xE1, 0x34, 0x36};
102
103 const DriverResult result = SendSubCommand(SubCommand::UNKNOWN_RINGCON3, ring_config);
104
105 if (result != DriverResult::Success) {
106 return result;
107 }
108
109 static constexpr std::array<u8, 4> ringcon_data{0x04, 0x01, 0x01, 0x02};
110 return SendSubCommand(SubCommand::UNKNOWN_RINGCON2, ringcon_data);
111}
112
113bool RingConProtocol::IsEnabled() const {
114 return is_enabled;
115}
116
117} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/ringcon.h b/src/input_common/helpers/joycon_protocol/ringcon.h
new file mode 100644
index 000000000..6e858f3fc
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/ringcon.h
@@ -0,0 +1,38 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
5// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
6// https://github.com/CTCaer/jc_toolkit
7// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
8
9#pragma once
10
11#include <vector>
12
13#include "input_common/helpers/joycon_protocol/common_protocol.h"
14#include "input_common/helpers/joycon_protocol/joycon_types.h"
15
16namespace InputCommon::Joycon {
17
18class RingConProtocol final : private JoyconCommonProtocol {
19public:
20 explicit RingConProtocol(std::shared_ptr<JoyconHandle> handle);
21
22 DriverResult EnableRingCon();
23
24 DriverResult DisableRingCon();
25
26 DriverResult StartRingconPolling();
27
28 bool IsEnabled() const;
29
30private:
31 DriverResult IsRingConnected(bool& is_connected);
32
33 DriverResult ConfigureRing();
34
35 bool is_enabled{};
36};
37
38} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/rumble.cpp b/src/input_common/helpers/joycon_protocol/rumble.cpp
new file mode 100644
index 000000000..63b60c946
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/rumble.cpp
@@ -0,0 +1,299 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <algorithm>
5#include <cmath>
6
7#include "common/logging/log.h"
8#include "input_common/helpers/joycon_protocol/rumble.h"
9
10namespace InputCommon::Joycon {
11
12RumbleProtocol::RumbleProtocol(std::shared_ptr<JoyconHandle> handle)
13 : JoyconCommonProtocol(std::move(handle)) {}
14
15DriverResult RumbleProtocol::EnableRumble(bool is_enabled) {
16 LOG_DEBUG(Input, "Enable Rumble");
17 ScopedSetBlocking sb(this);
18 const std::array<u8, 1> buffer{static_cast<u8>(is_enabled ? 1 : 0)};
19 return SendSubCommand(SubCommand::ENABLE_VIBRATION, buffer);
20}
21
22DriverResult RumbleProtocol::SendVibration(const VibrationValue& vibration) {
23 std::array<u8, sizeof(DefaultVibrationBuffer)> buffer{};
24
25 if (vibration.high_amplitude <= 0.0f && vibration.low_amplitude <= 0.0f) {
26 return SendVibrationReport(DefaultVibrationBuffer);
27 }
28
29 // Protect joycons from damage from strong vibrations
30 const f32 clamp_amplitude =
31 1.0f / std::max(1.0f, vibration.high_amplitude + vibration.low_amplitude);
32
33 const u16 encoded_high_frequency = EncodeHighFrequency(vibration.high_frequency);
34 const u8 encoded_high_amplitude =
35 EncodeHighAmplitude(vibration.high_amplitude * clamp_amplitude);
36 const u8 encoded_low_frequency = EncodeLowFrequency(vibration.low_frequency);
37 const u16 encoded_low_amplitude = EncodeLowAmplitude(vibration.low_amplitude * clamp_amplitude);
38
39 buffer[0] = static_cast<u8>(encoded_high_frequency & 0xFF);
40 buffer[1] = static_cast<u8>(encoded_high_amplitude | ((encoded_high_frequency >> 8) & 0x01));
41 buffer[2] = static_cast<u8>(encoded_low_frequency | ((encoded_low_amplitude >> 8) & 0x80));
42 buffer[3] = static_cast<u8>(encoded_low_amplitude & 0xFF);
43
44 // Duplicate rumble for now
45 buffer[4] = buffer[0];
46 buffer[5] = buffer[1];
47 buffer[6] = buffer[2];
48 buffer[7] = buffer[3];
49
50 return SendVibrationReport(buffer);
51}
52
53u16 RumbleProtocol::EncodeHighFrequency(f32 frequency) const {
54 const u8 new_frequency =
55 static_cast<u8>(std::clamp(std::log2(frequency / 10.0f) * 32.0f, 0.0f, 255.0f));
56 return static_cast<u16>((new_frequency - 0x60) * 4);
57}
58
59u8 RumbleProtocol::EncodeLowFrequency(f32 frequency) const {
60 const u8 new_frequency =
61 static_cast<u8>(std::clamp(std::log2(frequency / 10.0f) * 32.0f, 0.0f, 255.0f));
62 return static_cast<u8>(new_frequency - 0x40);
63}
64
65u8 RumbleProtocol::EncodeHighAmplitude(f32 amplitude) const {
66 // More information about these values can be found here:
67 // https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md
68
69 static constexpr std::array<std::pair<f32, int>, 101> high_fequency_amplitude{
70 std::pair<f32, int>{0.0f, 0x0},
71 {0.01f, 0x2},
72 {0.012f, 0x4},
73 {0.014f, 0x6},
74 {0.017f, 0x8},
75 {0.02f, 0x0a},
76 {0.024f, 0x0c},
77 {0.028f, 0x0e},
78 {0.033f, 0x10},
79 {0.04f, 0x12},
80 {0.047f, 0x14},
81 {0.056f, 0x16},
82 {0.067f, 0x18},
83 {0.08f, 0x1a},
84 {0.095f, 0x1c},
85 {0.112f, 0x1e},
86 {0.117f, 0x20},
87 {0.123f, 0x22},
88 {0.128f, 0x24},
89 {0.134f, 0x26},
90 {0.14f, 0x28},
91 {0.146f, 0x2a},
92 {0.152f, 0x2c},
93 {0.159f, 0x2e},
94 {0.166f, 0x30},
95 {0.173f, 0x32},
96 {0.181f, 0x34},
97 {0.189f, 0x36},
98 {0.198f, 0x38},
99 {0.206f, 0x3a},
100 {0.215f, 0x3c},
101 {0.225f, 0x3e},
102 {0.23f, 0x40},
103 {0.235f, 0x42},
104 {0.24f, 0x44},
105 {0.245f, 0x46},
106 {0.251f, 0x48},
107 {0.256f, 0x4a},
108 {0.262f, 0x4c},
109 {0.268f, 0x4e},
110 {0.273f, 0x50},
111 {0.279f, 0x52},
112 {0.286f, 0x54},
113 {0.292f, 0x56},
114 {0.298f, 0x58},
115 {0.305f, 0x5a},
116 {0.311f, 0x5c},
117 {0.318f, 0x5e},
118 {0.325f, 0x60},
119 {0.332f, 0x62},
120 {0.34f, 0x64},
121 {0.347f, 0x66},
122 {0.355f, 0x68},
123 {0.362f, 0x6a},
124 {0.37f, 0x6c},
125 {0.378f, 0x6e},
126 {0.387f, 0x70},
127 {0.395f, 0x72},
128 {0.404f, 0x74},
129 {0.413f, 0x76},
130 {0.422f, 0x78},
131 {0.431f, 0x7a},
132 {0.44f, 0x7c},
133 {0.45f, 0x7e},
134 {0.46f, 0x80},
135 {0.47f, 0x82},
136 {0.48f, 0x84},
137 {0.491f, 0x86},
138 {0.501f, 0x88},
139 {0.512f, 0x8a},
140 {0.524f, 0x8c},
141 {0.535f, 0x8e},
142 {0.547f, 0x90},
143 {0.559f, 0x92},
144 {0.571f, 0x94},
145 {0.584f, 0x96},
146 {0.596f, 0x98},
147 {0.609f, 0x9a},
148 {0.623f, 0x9c},
149 {0.636f, 0x9e},
150 {0.65f, 0xa0},
151 {0.665f, 0xa2},
152 {0.679f, 0xa4},
153 {0.694f, 0xa6},
154 {0.709f, 0xa8},
155 {0.725f, 0xaa},
156 {0.741f, 0xac},
157 {0.757f, 0xae},
158 {0.773f, 0xb0},
159 {0.79f, 0xb2},
160 {0.808f, 0xb4},
161 {0.825f, 0xb6},
162 {0.843f, 0xb8},
163 {0.862f, 0xba},
164 {0.881f, 0xbc},
165 {0.9f, 0xbe},
166 {0.92f, 0xc0},
167 {0.94f, 0xc2},
168 {0.96f, 0xc4},
169 {0.981f, 0xc6},
170 {1.003f, 0xc8},
171 };
172
173 for (const auto& [amplitude_value, code] : high_fequency_amplitude) {
174 if (amplitude <= amplitude_value) {
175 return static_cast<u8>(code);
176 }
177 }
178
179 return static_cast<u8>(high_fequency_amplitude[high_fequency_amplitude.size() - 1].second);
180}
181
182u16 RumbleProtocol::EncodeLowAmplitude(f32 amplitude) const {
183 // More information about these values can be found here:
184 // https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md
185
186 static constexpr std::array<std::pair<f32, int>, 101> high_fequency_amplitude{
187 std::pair<f32, int>{0.0f, 0x0040},
188 {0.01f, 0x8040},
189 {0.012f, 0x0041},
190 {0.014f, 0x8041},
191 {0.017f, 0x0042},
192 {0.02f, 0x8042},
193 {0.024f, 0x0043},
194 {0.028f, 0x8043},
195 {0.033f, 0x0044},
196 {0.04f, 0x8044},
197 {0.047f, 0x0045},
198 {0.056f, 0x8045},
199 {0.067f, 0x0046},
200 {0.08f, 0x8046},
201 {0.095f, 0x0047},
202 {0.112f, 0x8047},
203 {0.117f, 0x0048},
204 {0.123f, 0x8048},
205 {0.128f, 0x0049},
206 {0.134f, 0x8049},
207 {0.14f, 0x004a},
208 {0.146f, 0x804a},
209 {0.152f, 0x004b},
210 {0.159f, 0x804b},
211 {0.166f, 0x004c},
212 {0.173f, 0x804c},
213 {0.181f, 0x004d},
214 {0.189f, 0x804d},
215 {0.198f, 0x004e},
216 {0.206f, 0x804e},
217 {0.215f, 0x004f},
218 {0.225f, 0x804f},
219 {0.23f, 0x0050},
220 {0.235f, 0x8050},
221 {0.24f, 0x0051},
222 {0.245f, 0x8051},
223 {0.251f, 0x0052},
224 {0.256f, 0x8052},
225 {0.262f, 0x0053},
226 {0.268f, 0x8053},
227 {0.273f, 0x0054},
228 {0.279f, 0x8054},
229 {0.286f, 0x0055},
230 {0.292f, 0x8055},
231 {0.298f, 0x0056},
232 {0.305f, 0x8056},
233 {0.311f, 0x0057},
234 {0.318f, 0x8057},
235 {0.325f, 0x0058},
236 {0.332f, 0x8058},
237 {0.34f, 0x0059},
238 {0.347f, 0x8059},
239 {0.355f, 0x005a},
240 {0.362f, 0x805a},
241 {0.37f, 0x005b},
242 {0.378f, 0x805b},
243 {0.387f, 0x005c},
244 {0.395f, 0x805c},
245 {0.404f, 0x005d},
246 {0.413f, 0x805d},
247 {0.422f, 0x005e},
248 {0.431f, 0x805e},
249 {0.44f, 0x005f},
250 {0.45f, 0x805f},
251 {0.46f, 0x0060},
252 {0.47f, 0x8060},
253 {0.48f, 0x0061},
254 {0.491f, 0x8061},
255 {0.501f, 0x0062},
256 {0.512f, 0x8062},
257 {0.524f, 0x0063},
258 {0.535f, 0x8063},
259 {0.547f, 0x0064},
260 {0.559f, 0x8064},
261 {0.571f, 0x0065},
262 {0.584f, 0x8065},
263 {0.596f, 0x0066},
264 {0.609f, 0x8066},
265 {0.623f, 0x0067},
266 {0.636f, 0x8067},
267 {0.65f, 0x0068},
268 {0.665f, 0x8068},
269 {0.679f, 0x0069},
270 {0.694f, 0x8069},
271 {0.709f, 0x006a},
272 {0.725f, 0x806a},
273 {0.741f, 0x006b},
274 {0.757f, 0x806b},
275 {0.773f, 0x006c},
276 {0.79f, 0x806c},
277 {0.808f, 0x006d},
278 {0.825f, 0x806d},
279 {0.843f, 0x006e},
280 {0.862f, 0x806e},
281 {0.881f, 0x006f},
282 {0.9f, 0x806f},
283 {0.92f, 0x0070},
284 {0.94f, 0x8070},
285 {0.96f, 0x0071},
286 {0.981f, 0x8071},
287 {1.003f, 0x0072},
288 };
289
290 for (const auto& [amplitude_value, code] : high_fequency_amplitude) {
291 if (amplitude <= amplitude_value) {
292 return static_cast<u16>(code);
293 }
294 }
295
296 return static_cast<u16>(high_fequency_amplitude[high_fequency_amplitude.size() - 1].second);
297}
298
299} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/rumble.h b/src/input_common/helpers/joycon_protocol/rumble.h
new file mode 100644
index 000000000..6c12b7925
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/rumble.h
@@ -0,0 +1,33 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
5// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
6// https://github.com/CTCaer/jc_toolkit
7// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
8
9#pragma once
10
11#include <vector>
12
13#include "input_common/helpers/joycon_protocol/common_protocol.h"
14#include "input_common/helpers/joycon_protocol/joycon_types.h"
15
16namespace InputCommon::Joycon {
17
18class RumbleProtocol final : private JoyconCommonProtocol {
19public:
20 explicit RumbleProtocol(std::shared_ptr<JoyconHandle> handle);
21
22 DriverResult EnableRumble(bool is_enabled);
23
24 DriverResult SendVibration(const VibrationValue& vibration);
25
26private:
27 u16 EncodeHighFrequency(f32 frequency) const;
28 u8 EncodeLowFrequency(f32 frequency) const;
29 u8 EncodeHighAmplitude(f32 amplitude) const;
30 u16 EncodeLowAmplitude(f32 amplitude) const;
31};
32
33} // namespace InputCommon::Joycon
diff --git a/src/input_common/input_engine.cpp b/src/input_common/input_engine.cpp
index 61cfd0911..91aa96aa7 100644
--- a/src/input_common/input_engine.cpp
+++ b/src/input_common/input_engine.cpp
@@ -79,6 +79,17 @@ void InputEngine::SetBattery(const PadIdentifier& identifier, Common::Input::Bat
79 TriggerOnBatteryChange(identifier, value); 79 TriggerOnBatteryChange(identifier, value);
80} 80}
81 81
82void InputEngine::SetColor(const PadIdentifier& identifier, Common::Input::BodyColorStatus value) {
83 {
84 std::scoped_lock lock{mutex};
85 ControllerData& controller = controller_list.at(identifier);
86 if (!configuring) {
87 controller.color = value;
88 }
89 }
90 TriggerOnColorChange(identifier, value);
91}
92
82void InputEngine::SetMotion(const PadIdentifier& identifier, int motion, const BasicMotion& value) { 93void InputEngine::SetMotion(const PadIdentifier& identifier, int motion, const BasicMotion& value) {
83 { 94 {
84 std::scoped_lock lock{mutex}; 95 std::scoped_lock lock{mutex};
@@ -176,6 +187,18 @@ Common::Input::BatteryLevel InputEngine::GetBattery(const PadIdentifier& identif
176 return controller.battery; 187 return controller.battery;
177} 188}
178 189
190Common::Input::BodyColorStatus InputEngine::GetColor(const PadIdentifier& identifier) const {
191 std::scoped_lock lock{mutex};
192 const auto controller_iter = controller_list.find(identifier);
193 if (controller_iter == controller_list.cend()) {
194 LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.RawString(),
195 identifier.pad, identifier.port);
196 return {};
197 }
198 const ControllerData& controller = controller_iter->second;
199 return controller.color;
200}
201
179BasicMotion InputEngine::GetMotion(const PadIdentifier& identifier, int motion) const { 202BasicMotion InputEngine::GetMotion(const PadIdentifier& identifier, int motion) const {
180 std::scoped_lock lock{mutex}; 203 std::scoped_lock lock{mutex};
181 const auto controller_iter = controller_list.find(identifier); 204 const auto controller_iter = controller_list.find(identifier);
@@ -328,6 +351,20 @@ void InputEngine::TriggerOnBatteryChange(const PadIdentifier& identifier,
328 } 351 }
329} 352}
330 353
354void InputEngine::TriggerOnColorChange(const PadIdentifier& identifier,
355 [[maybe_unused]] Common::Input::BodyColorStatus value) {
356 std::scoped_lock lock{mutex_callback};
357 for (const auto& poller_pair : callback_list) {
358 const InputIdentifier& poller = poller_pair.second;
359 if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Color, 0)) {
360 continue;
361 }
362 if (poller.callback.on_change) {
363 poller.callback.on_change();
364 }
365 }
366}
367
331void InputEngine::TriggerOnMotionChange(const PadIdentifier& identifier, int motion, 368void InputEngine::TriggerOnMotionChange(const PadIdentifier& identifier, int motion,
332 const BasicMotion& value) { 369 const BasicMotion& value) {
333 std::scoped_lock lock{mutex_callback}; 370 std::scoped_lock lock{mutex_callback};
diff --git a/src/input_common/input_engine.h b/src/input_common/input_engine.h
index 6cbcf5207..50b5a3dc8 100644
--- a/src/input_common/input_engine.h
+++ b/src/input_common/input_engine.h
@@ -40,6 +40,7 @@ enum class EngineInputType {
40 Battery, 40 Battery,
41 Button, 41 Button,
42 Camera, 42 Camera,
43 Color,
43 HatButton, 44 HatButton,
44 Motion, 45 Motion,
45 Nfc, 46 Nfc,
@@ -104,14 +105,17 @@ public:
104 void EndConfiguration(); 105 void EndConfiguration();
105 106
106 // Sets a led pattern for a controller 107 // Sets a led pattern for a controller
107 virtual void SetLeds([[maybe_unused]] const PadIdentifier& identifier, 108 virtual Common::Input::DriverResult SetLeds(
108 [[maybe_unused]] const Common::Input::LedStatus& led_status) {} 109 [[maybe_unused]] const PadIdentifier& identifier,
110 [[maybe_unused]] const Common::Input::LedStatus& led_status) {
111 return Common::Input::DriverResult::NotSupported;
112 }
109 113
110 // Sets rumble to a controller 114 // Sets rumble to a controller
111 virtual Common::Input::VibrationError SetVibration( 115 virtual Common::Input::DriverResult SetVibration(
112 [[maybe_unused]] const PadIdentifier& identifier, 116 [[maybe_unused]] const PadIdentifier& identifier,
113 [[maybe_unused]] const Common::Input::VibrationStatus& vibration) { 117 [[maybe_unused]] const Common::Input::VibrationStatus& vibration) {
114 return Common::Input::VibrationError::NotSupported; 118 return Common::Input::DriverResult::NotSupported;
115 } 119 }
116 120
117 // Returns true if device supports vibrations 121 // Returns true if device supports vibrations
@@ -120,17 +124,17 @@ public:
120 } 124 }
121 125
122 // Sets polling mode to a controller 126 // Sets polling mode to a controller
123 virtual Common::Input::PollingError SetPollingMode( 127 virtual Common::Input::DriverResult SetPollingMode(
124 [[maybe_unused]] const PadIdentifier& identifier, 128 [[maybe_unused]] const PadIdentifier& identifier,
125 [[maybe_unused]] const Common::Input::PollingMode polling_mode) { 129 [[maybe_unused]] const Common::Input::PollingMode polling_mode) {
126 return Common::Input::PollingError::NotSupported; 130 return Common::Input::DriverResult::NotSupported;
127 } 131 }
128 132
129 // Sets camera format to a controller 133 // Sets camera format to a controller
130 virtual Common::Input::CameraError SetCameraFormat( 134 virtual Common::Input::DriverResult SetCameraFormat(
131 [[maybe_unused]] const PadIdentifier& identifier, 135 [[maybe_unused]] const PadIdentifier& identifier,
132 [[maybe_unused]] Common::Input::CameraFormat camera_format) { 136 [[maybe_unused]] Common::Input::CameraFormat camera_format) {
133 return Common::Input::CameraError::NotSupported; 137 return Common::Input::DriverResult::NotSupported;
134 } 138 }
135 139
136 // Returns success if nfc is supported 140 // Returns success if nfc is supported
@@ -199,6 +203,7 @@ public:
199 bool GetHatButton(const PadIdentifier& identifier, int button, u8 direction) const; 203 bool GetHatButton(const PadIdentifier& identifier, int button, u8 direction) const;
200 f32 GetAxis(const PadIdentifier& identifier, int axis) const; 204 f32 GetAxis(const PadIdentifier& identifier, int axis) const;
201 Common::Input::BatteryLevel GetBattery(const PadIdentifier& identifier) const; 205 Common::Input::BatteryLevel GetBattery(const PadIdentifier& identifier) const;
206 Common::Input::BodyColorStatus GetColor(const PadIdentifier& identifier) const;
202 BasicMotion GetMotion(const PadIdentifier& identifier, int motion) const; 207 BasicMotion GetMotion(const PadIdentifier& identifier, int motion) const;
203 Common::Input::CameraStatus GetCamera(const PadIdentifier& identifier) const; 208 Common::Input::CameraStatus GetCamera(const PadIdentifier& identifier) const;
204 Common::Input::NfcStatus GetNfc(const PadIdentifier& identifier) const; 209 Common::Input::NfcStatus GetNfc(const PadIdentifier& identifier) const;
@@ -212,6 +217,7 @@ protected:
212 void SetHatButton(const PadIdentifier& identifier, int button, u8 value); 217 void SetHatButton(const PadIdentifier& identifier, int button, u8 value);
213 void SetAxis(const PadIdentifier& identifier, int axis, f32 value); 218 void SetAxis(const PadIdentifier& identifier, int axis, f32 value);
214 void SetBattery(const PadIdentifier& identifier, Common::Input::BatteryLevel value); 219 void SetBattery(const PadIdentifier& identifier, Common::Input::BatteryLevel value);
220 void SetColor(const PadIdentifier& identifier, Common::Input::BodyColorStatus value);
215 void SetMotion(const PadIdentifier& identifier, int motion, const BasicMotion& value); 221 void SetMotion(const PadIdentifier& identifier, int motion, const BasicMotion& value);
216 void SetCamera(const PadIdentifier& identifier, const Common::Input::CameraStatus& value); 222 void SetCamera(const PadIdentifier& identifier, const Common::Input::CameraStatus& value);
217 void SetNfc(const PadIdentifier& identifier, const Common::Input::NfcStatus& value); 223 void SetNfc(const PadIdentifier& identifier, const Common::Input::NfcStatus& value);
@@ -227,6 +233,7 @@ private:
227 std::unordered_map<int, float> axes; 233 std::unordered_map<int, float> axes;
228 std::unordered_map<int, BasicMotion> motions; 234 std::unordered_map<int, BasicMotion> motions;
229 Common::Input::BatteryLevel battery{}; 235 Common::Input::BatteryLevel battery{};
236 Common::Input::BodyColorStatus color{};
230 Common::Input::CameraStatus camera{}; 237 Common::Input::CameraStatus camera{};
231 Common::Input::NfcStatus nfc{}; 238 Common::Input::NfcStatus nfc{};
232 }; 239 };
@@ -235,6 +242,8 @@ private:
235 void TriggerOnHatButtonChange(const PadIdentifier& identifier, int button, u8 value); 242 void TriggerOnHatButtonChange(const PadIdentifier& identifier, int button, u8 value);
236 void TriggerOnAxisChange(const PadIdentifier& identifier, int axis, f32 value); 243 void TriggerOnAxisChange(const PadIdentifier& identifier, int axis, f32 value);
237 void TriggerOnBatteryChange(const PadIdentifier& identifier, Common::Input::BatteryLevel value); 244 void TriggerOnBatteryChange(const PadIdentifier& identifier, Common::Input::BatteryLevel value);
245 void TriggerOnColorChange(const PadIdentifier& identifier,
246 Common::Input::BodyColorStatus value);
238 void TriggerOnMotionChange(const PadIdentifier& identifier, int motion, 247 void TriggerOnMotionChange(const PadIdentifier& identifier, int motion,
239 const BasicMotion& value); 248 const BasicMotion& value);
240 void TriggerOnCameraChange(const PadIdentifier& identifier, 249 void TriggerOnCameraChange(const PadIdentifier& identifier,
diff --git a/src/input_common/input_poller.cpp b/src/input_common/input_poller.cpp
index fb8be42e2..15cbf7e5f 100644
--- a/src/input_common/input_poller.cpp
+++ b/src/input_common/input_poller.cpp
@@ -498,6 +498,58 @@ private:
498 InputEngine* input_engine; 498 InputEngine* input_engine;
499}; 499};
500 500
501class InputFromColor final : public Common::Input::InputDevice {
502public:
503 explicit InputFromColor(PadIdentifier identifier_, InputEngine* input_engine_)
504 : identifier(identifier_), input_engine(input_engine_) {
505 UpdateCallback engine_callback{[this]() { OnChange(); }};
506 const InputIdentifier input_identifier{
507 .identifier = identifier,
508 .type = EngineInputType::Color,
509 .index = 0,
510 .callback = engine_callback,
511 };
512 last_color_value = {};
513 callback_key = input_engine->SetCallback(input_identifier);
514 }
515
516 ~InputFromColor() override {
517 input_engine->DeleteCallback(callback_key);
518 }
519
520 Common::Input::BodyColorStatus GetStatus() const {
521 return input_engine->GetColor(identifier);
522 }
523
524 void ForceUpdate() override {
525 const Common::Input::CallbackStatus status{
526 .type = Common::Input::InputType::Color,
527 .color_status = GetStatus(),
528 };
529
530 last_color_value = status.color_status;
531 TriggerOnChange(status);
532 }
533
534 void OnChange() {
535 const Common::Input::CallbackStatus status{
536 .type = Common::Input::InputType::Color,
537 .color_status = GetStatus(),
538 };
539
540 if (status.color_status.body != last_color_value.body) {
541 last_color_value = status.color_status;
542 TriggerOnChange(status);
543 }
544 }
545
546private:
547 const PadIdentifier identifier;
548 int callback_key;
549 Common::Input::BodyColorStatus last_color_value;
550 InputEngine* input_engine;
551};
552
501class InputFromMotion final : public Common::Input::InputDevice { 553class InputFromMotion final : public Common::Input::InputDevice {
502public: 554public:
503 explicit InputFromMotion(PadIdentifier identifier_, int motion_sensor_, float gyro_threshold_, 555 explicit InputFromMotion(PadIdentifier identifier_, int motion_sensor_, float gyro_threshold_,
@@ -754,11 +806,11 @@ public:
754 explicit OutputFromIdentifier(PadIdentifier identifier_, InputEngine* input_engine_) 806 explicit OutputFromIdentifier(PadIdentifier identifier_, InputEngine* input_engine_)
755 : identifier(identifier_), input_engine(input_engine_) {} 807 : identifier(identifier_), input_engine(input_engine_) {}
756 808
757 void SetLED(const Common::Input::LedStatus& led_status) override { 809 Common::Input::DriverResult SetLED(const Common::Input::LedStatus& led_status) override {
758 input_engine->SetLeds(identifier, led_status); 810 return input_engine->SetLeds(identifier, led_status);
759 } 811 }
760 812
761 Common::Input::VibrationError SetVibration( 813 Common::Input::DriverResult SetVibration(
762 const Common::Input::VibrationStatus& vibration_status) override { 814 const Common::Input::VibrationStatus& vibration_status) override {
763 return input_engine->SetVibration(identifier, vibration_status); 815 return input_engine->SetVibration(identifier, vibration_status);
764 } 816 }
@@ -767,11 +819,12 @@ public:
767 return input_engine->IsVibrationEnabled(identifier); 819 return input_engine->IsVibrationEnabled(identifier);
768 } 820 }
769 821
770 Common::Input::PollingError SetPollingMode(Common::Input::PollingMode polling_mode) override { 822 Common::Input::DriverResult SetPollingMode(Common::Input::PollingMode polling_mode) override {
771 return input_engine->SetPollingMode(identifier, polling_mode); 823 return input_engine->SetPollingMode(identifier, polling_mode);
772 } 824 }
773 825
774 Common::Input::CameraError SetCameraFormat(Common::Input::CameraFormat camera_format) override { 826 Common::Input::DriverResult SetCameraFormat(
827 Common::Input::CameraFormat camera_format) override {
775 return input_engine->SetCameraFormat(identifier, camera_format); 828 return input_engine->SetCameraFormat(identifier, camera_format);
776 } 829 }
777 830
@@ -966,6 +1019,18 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateBatteryDevice(
966 return std::make_unique<InputFromBattery>(identifier, input_engine.get()); 1019 return std::make_unique<InputFromBattery>(identifier, input_engine.get());
967} 1020}
968 1021
1022std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateColorDevice(
1023 const Common::ParamPackage& params) {
1024 const PadIdentifier identifier = {
1025 .guid = Common::UUID{params.Get("guid", "")},
1026 .port = static_cast<std::size_t>(params.Get("port", 0)),
1027 .pad = static_cast<std::size_t>(params.Get("pad", 0)),
1028 };
1029
1030 input_engine->PreSetController(identifier);
1031 return std::make_unique<InputFromColor>(identifier, input_engine.get());
1032}
1033
969std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateMotionDevice( 1034std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateMotionDevice(
970 Common::ParamPackage params) { 1035 Common::ParamPackage params) {
971 const PadIdentifier identifier = { 1036 const PadIdentifier identifier = {
@@ -1053,6 +1118,9 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::Create(
1053 if (params.Has("battery")) { 1118 if (params.Has("battery")) {
1054 return CreateBatteryDevice(params); 1119 return CreateBatteryDevice(params);
1055 } 1120 }
1121 if (params.Has("color")) {
1122 return CreateColorDevice(params);
1123 }
1056 if (params.Has("camera")) { 1124 if (params.Has("camera")) {
1057 return CreateCameraDevice(params); 1125 return CreateCameraDevice(params);
1058 } 1126 }
diff --git a/src/input_common/input_poller.h b/src/input_common/input_poller.h
index d7db13ce4..e097e254c 100644
--- a/src/input_common/input_poller.h
+++ b/src/input_common/input_poller.h
@@ -191,6 +191,17 @@ private:
191 const Common::ParamPackage& params); 191 const Common::ParamPackage& params);
192 192
193 /** 193 /**
194 * Creates a color device from the parameters given.
195 * @param params contains parameters for creating the device:
196 * - "guid": text string for identifying controllers
197 * - "port": port of the connected device
198 * - "pad": slot of the connected controller
199 * @returns a unique input device with the parameters specified
200 */
201 std::unique_ptr<Common::Input::InputDevice> CreateColorDevice(
202 const Common::ParamPackage& params);
203
204 /**
194 * Creates a motion device from the parameters given. 205 * Creates a motion device from the parameters given.
195 * @param params contains parameters for creating the device: 206 * @param params contains parameters for creating the device:
196 * - "axis_x": the controller horizontal axis id to bind with the input 207 * - "axis_x": the controller horizontal axis id to bind with the input
diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp
index e0b2131ed..c77fc04ee 100644
--- a/src/input_common/main.cpp
+++ b/src/input_common/main.cpp
@@ -23,6 +23,7 @@
23#include "input_common/drivers/gc_adapter.h" 23#include "input_common/drivers/gc_adapter.h"
24#endif 24#endif
25#ifdef HAVE_SDL2 25#ifdef HAVE_SDL2
26#include "input_common/drivers/joycon.h"
26#include "input_common/drivers/sdl_driver.h" 27#include "input_common/drivers/sdl_driver.h"
27#endif 28#endif
28 29
@@ -81,6 +82,7 @@ struct InputSubsystem::Impl {
81 RegisterEngine("virtual_gamepad", virtual_gamepad); 82 RegisterEngine("virtual_gamepad", virtual_gamepad);
82#ifdef HAVE_SDL2 83#ifdef HAVE_SDL2
83 RegisterEngine("sdl", sdl); 84 RegisterEngine("sdl", sdl);
85 RegisterEngine("joycon", joycon);
84#endif 86#endif
85 87
86 Common::Input::RegisterInputFactory("touch_from_button", 88 Common::Input::RegisterInputFactory("touch_from_button",
@@ -111,6 +113,7 @@ struct InputSubsystem::Impl {
111 UnregisterEngine(virtual_gamepad); 113 UnregisterEngine(virtual_gamepad);
112#ifdef HAVE_SDL2 114#ifdef HAVE_SDL2
113 UnregisterEngine(sdl); 115 UnregisterEngine(sdl);
116 UnregisterEngine(joycon);
114#endif 117#endif
115 118
116 Common::Input::UnregisterInputFactory("touch_from_button"); 119 Common::Input::UnregisterInputFactory("touch_from_button");
@@ -133,6 +136,8 @@ struct InputSubsystem::Impl {
133 auto udp_devices = udp_client->GetInputDevices(); 136 auto udp_devices = udp_client->GetInputDevices();
134 devices.insert(devices.end(), udp_devices.begin(), udp_devices.end()); 137 devices.insert(devices.end(), udp_devices.begin(), udp_devices.end());
135#ifdef HAVE_SDL2 138#ifdef HAVE_SDL2
139 auto joycon_devices = joycon->GetInputDevices();
140 devices.insert(devices.end(), joycon_devices.begin(), joycon_devices.end());
136 auto sdl_devices = sdl->GetInputDevices(); 141 auto sdl_devices = sdl->GetInputDevices();
137 devices.insert(devices.end(), sdl_devices.begin(), sdl_devices.end()); 142 devices.insert(devices.end(), sdl_devices.begin(), sdl_devices.end());
138#endif 143#endif
@@ -164,6 +169,9 @@ struct InputSubsystem::Impl {
164 if (engine == sdl->GetEngineName()) { 169 if (engine == sdl->GetEngineName()) {
165 return sdl; 170 return sdl;
166 } 171 }
172 if (engine == joycon->GetEngineName()) {
173 return joycon;
174 }
167#endif 175#endif
168 return nullptr; 176 return nullptr;
169 } 177 }
@@ -247,6 +255,9 @@ struct InputSubsystem::Impl {
247 if (engine == sdl->GetEngineName()) { 255 if (engine == sdl->GetEngineName()) {
248 return true; 256 return true;
249 } 257 }
258 if (engine == joycon->GetEngineName()) {
259 return true;
260 }
250#endif 261#endif
251 return false; 262 return false;
252 } 263 }
@@ -260,6 +271,7 @@ struct InputSubsystem::Impl {
260 udp_client->BeginConfiguration(); 271 udp_client->BeginConfiguration();
261#ifdef HAVE_SDL2 272#ifdef HAVE_SDL2
262 sdl->BeginConfiguration(); 273 sdl->BeginConfiguration();
274 joycon->BeginConfiguration();
263#endif 275#endif
264 } 276 }
265 277
@@ -272,6 +284,7 @@ struct InputSubsystem::Impl {
272 udp_client->EndConfiguration(); 284 udp_client->EndConfiguration();
273#ifdef HAVE_SDL2 285#ifdef HAVE_SDL2
274 sdl->EndConfiguration(); 286 sdl->EndConfiguration();
287 joycon->EndConfiguration();
275#endif 288#endif
276 } 289 }
277 290
@@ -304,6 +317,7 @@ struct InputSubsystem::Impl {
304 317
305#ifdef HAVE_SDL2 318#ifdef HAVE_SDL2
306 std::shared_ptr<SDLDriver> sdl; 319 std::shared_ptr<SDLDriver> sdl;
320 std::shared_ptr<Joycons> joycon;
307#endif 321#endif
308}; 322};
309 323
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index fd3bb30e1..35fef506a 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -440,6 +440,7 @@ void Config::ReadControlValues() {
440 ReadBasicSetting(Settings::values.emulate_analog_keyboard); 440 ReadBasicSetting(Settings::values.emulate_analog_keyboard);
441 Settings::values.mouse_panning = false; 441 Settings::values.mouse_panning = false;
442 ReadBasicSetting(Settings::values.mouse_panning_sensitivity); 442 ReadBasicSetting(Settings::values.mouse_panning_sensitivity);
443 ReadBasicSetting(Settings::values.enable_joycon_driver);
443 444
444 ReadBasicSetting(Settings::values.tas_enable); 445 ReadBasicSetting(Settings::values.tas_enable);
445 ReadBasicSetting(Settings::values.tas_loop); 446 ReadBasicSetting(Settings::values.tas_loop);
@@ -1139,6 +1140,7 @@ void Config::SaveControlValues() {
1139 WriteGlobalSetting(Settings::values.enable_accurate_vibrations); 1140 WriteGlobalSetting(Settings::values.enable_accurate_vibrations);
1140 WriteGlobalSetting(Settings::values.motion_enabled); 1141 WriteGlobalSetting(Settings::values.motion_enabled);
1141 WriteBasicSetting(Settings::values.enable_raw_input); 1142 WriteBasicSetting(Settings::values.enable_raw_input);
1143 WriteBasicSetting(Settings::values.enable_joycon_driver);
1142 WriteBasicSetting(Settings::values.keyboard_enabled); 1144 WriteBasicSetting(Settings::values.keyboard_enabled);
1143 WriteBasicSetting(Settings::values.emulate_analog_keyboard); 1145 WriteBasicSetting(Settings::values.emulate_analog_keyboard);
1144 WriteBasicSetting(Settings::values.mouse_panning_sensitivity); 1146 WriteBasicSetting(Settings::values.mouse_panning_sensitivity);
diff --git a/src/yuzu/configuration/configure_input_advanced.cpp b/src/yuzu/configuration/configure_input_advanced.cpp
index 235b813d9..77b976e74 100644
--- a/src/yuzu/configuration/configure_input_advanced.cpp
+++ b/src/yuzu/configuration/configure_input_advanced.cpp
@@ -138,6 +138,7 @@ void ConfigureInputAdvanced::ApplyConfiguration() {
138 Settings::values.controller_navigation = ui->controller_navigation->isChecked(); 138 Settings::values.controller_navigation = ui->controller_navigation->isChecked();
139 Settings::values.enable_ring_controller = ui->enable_ring_controller->isChecked(); 139 Settings::values.enable_ring_controller = ui->enable_ring_controller->isChecked();
140 Settings::values.enable_ir_sensor = ui->enable_ir_sensor->isChecked(); 140 Settings::values.enable_ir_sensor = ui->enable_ir_sensor->isChecked();
141 Settings::values.enable_joycon_driver = ui->enable_joycon_driver->isChecked();
141} 142}
142 143
143void ConfigureInputAdvanced::LoadConfiguration() { 144void ConfigureInputAdvanced::LoadConfiguration() {
@@ -172,6 +173,7 @@ void ConfigureInputAdvanced::LoadConfiguration() {
172 ui->controller_navigation->setChecked(Settings::values.controller_navigation.GetValue()); 173 ui->controller_navigation->setChecked(Settings::values.controller_navigation.GetValue());
173 ui->enable_ring_controller->setChecked(Settings::values.enable_ring_controller.GetValue()); 174 ui->enable_ring_controller->setChecked(Settings::values.enable_ring_controller.GetValue());
174 ui->enable_ir_sensor->setChecked(Settings::values.enable_ir_sensor.GetValue()); 175 ui->enable_ir_sensor->setChecked(Settings::values.enable_ir_sensor.GetValue());
176 ui->enable_joycon_driver->setChecked(Settings::values.enable_joycon_driver.GetValue());
175 177
176 UpdateUIEnabled(); 178 UpdateUIEnabled();
177} 179}
diff --git a/src/yuzu/configuration/configure_input_advanced.ui b/src/yuzu/configuration/configure_input_advanced.ui
index fac8cf827..75d96d3ab 100644
--- a/src/yuzu/configuration/configure_input_advanced.ui
+++ b/src/yuzu/configuration/configure_input_advanced.ui
@@ -2696,6 +2696,22 @@
2696 </widget> 2696 </widget>
2697 </item> 2697 </item>
2698 <item row="5" column="0"> 2698 <item row="5" column="0">
2699 <widget class="QCheckBox" name="enable_joycon_driver">
2700 <property name="toolTip">
2701 <string>Requires restarting yuzu</string>
2702 </property>
2703 <property name="minimumSize">
2704 <size>
2705 <width>0</width>
2706 <height>23</height>
2707 </size>
2708 </property>
2709 <property name="text">
2710 <string>Enable direct JoyCon driver</string>
2711 </property>
2712 </widget>
2713 </item>
2714 <item row="6" column="0">
2699 <widget class="QCheckBox" name="mouse_panning"> 2715 <widget class="QCheckBox" name="mouse_panning">
2700 <property name="minimumSize"> 2716 <property name="minimumSize">
2701 <size> 2717 <size>
@@ -2708,7 +2724,7 @@
2708 </property> 2724 </property>
2709 </widget> 2725 </widget>
2710 </item> 2726 </item>
2711 <item row="5" column="2"> 2727 <item row="6" column="2">
2712 <widget class="QSpinBox" name="mouse_panning_sensitivity"> 2728 <widget class="QSpinBox" name="mouse_panning_sensitivity">
2713 <property name="toolTip"> 2729 <property name="toolTip">
2714 <string>Mouse sensitivity</string> 2730 <string>Mouse sensitivity</string>
@@ -2730,14 +2746,14 @@
2730 </property> 2746 </property>
2731 </widget> 2747 </widget>
2732 </item> 2748 </item>
2733 <item row="6" column="0"> 2749 <item row="7" column="0">
2734 <widget class="QLabel" name="motion_touch"> 2750 <widget class="QLabel" name="motion_touch">
2735 <property name="text"> 2751 <property name="text">
2736 <string>Motion / Touch</string> 2752 <string>Motion / Touch</string>
2737 </property> 2753 </property>
2738 </widget> 2754 </widget>
2739 </item> 2755 </item>
2740 <item row="6" column="2"> 2756 <item row="7" column="2">
2741 <widget class="QPushButton" name="buttonMotionTouch"> 2757 <widget class="QPushButton" name="buttonMotionTouch">
2742 <property name="text"> 2758 <property name="text">
2743 <string>Configure</string> 2759 <string>Configure</string>
diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp
index c40d980c9..4b7e3b01b 100644
--- a/src/yuzu/configuration/configure_input_player.cpp
+++ b/src/yuzu/configuration/configure_input_player.cpp
@@ -66,6 +66,18 @@ QString GetButtonName(Common::Input::ButtonNames button_name) {
66 return QObject::tr("R"); 66 return QObject::tr("R");
67 case Common::Input::ButtonNames::TriggerL: 67 case Common::Input::ButtonNames::TriggerL:
68 return QObject::tr("L"); 68 return QObject::tr("L");
69 case Common::Input::ButtonNames::TriggerZR:
70 return QObject::tr("ZR");
71 case Common::Input::ButtonNames::TriggerZL:
72 return QObject::tr("ZL");
73 case Common::Input::ButtonNames::TriggerSR:
74 return QObject::tr("SR");
75 case Common::Input::ButtonNames::TriggerSL:
76 return QObject::tr("SL");
77 case Common::Input::ButtonNames::ButtonStickL:
78 return QObject::tr("Stick L");
79 case Common::Input::ButtonNames::ButtonStickR:
80 return QObject::tr("Stick R");
69 case Common::Input::ButtonNames::ButtonA: 81 case Common::Input::ButtonNames::ButtonA:
70 return QObject::tr("A"); 82 return QObject::tr("A");
71 case Common::Input::ButtonNames::ButtonB: 83 case Common::Input::ButtonNames::ButtonB:
@@ -76,6 +88,14 @@ QString GetButtonName(Common::Input::ButtonNames button_name) {
76 return QObject::tr("Y"); 88 return QObject::tr("Y");
77 case Common::Input::ButtonNames::ButtonStart: 89 case Common::Input::ButtonNames::ButtonStart:
78 return QObject::tr("Start"); 90 return QObject::tr("Start");
91 case Common::Input::ButtonNames::ButtonPlus:
92 return QObject::tr("Plus");
93 case Common::Input::ButtonNames::ButtonMinus:
94 return QObject::tr("Minus");
95 case Common::Input::ButtonNames::ButtonHome:
96 return QObject::tr("Home");
97 case Common::Input::ButtonNames::ButtonCapture:
98 return QObject::tr("Capture");
79 case Common::Input::ButtonNames::L1: 99 case Common::Input::ButtonNames::L1:
80 return QObject::tr("L1"); 100 return QObject::tr("L1");
81 case Common::Input::ButtonNames::L2: 101 case Common::Input::ButtonNames::L2:
diff --git a/src/yuzu/configuration/configure_input_player_widget.cpp b/src/yuzu/configuration/configure_input_player_widget.cpp
index 11390fec0..68af6c20c 100644
--- a/src/yuzu/configuration/configure_input_player_widget.cpp
+++ b/src/yuzu/configuration/configure_input_player_widget.cpp
@@ -103,9 +103,13 @@ void PlayerControlPreview::UpdateColors() {
103 103
104 colors.left = colors.primary; 104 colors.left = colors.primary;
105 colors.right = colors.primary; 105 colors.right = colors.primary;
106 // Possible alternative to set colors from settings 106
107 // colors.left = QColor(controller->GetColors().left.body); 107 const auto color_left = controller->GetColorsValues()[0].body;
108 // colors.right = QColor(controller->GetColors().right.body); 108 const auto color_right = controller->GetColorsValues()[1].body;
109 if (color_left != 0 && color_right != 0) {
110 colors.left = QColor(color_left);
111 colors.right = QColor(color_right);
112 }
109} 113}
110 114
111void PlayerControlPreview::ResetInputs() { 115void PlayerControlPreview::ResetInputs() {
diff --git a/src/yuzu/configuration/configure_ringcon.cpp b/src/yuzu/configuration/configure_ringcon.cpp
index 688c2dd38..1275f10c8 100644
--- a/src/yuzu/configuration/configure_ringcon.cpp
+++ b/src/yuzu/configuration/configure_ringcon.cpp
@@ -4,9 +4,11 @@
4#include <memory> 4#include <memory>
5#include <QKeyEvent> 5#include <QKeyEvent>
6#include <QMenu> 6#include <QMenu>
7#include <QMessageBox>
7#include <QTimer> 8#include <QTimer>
9#include <fmt/format.h>
8 10
9#include "core/hid/emulated_devices.h" 11#include "core/hid/emulated_controller.h"
10#include "core/hid/hid_core.h" 12#include "core/hid/hid_core.h"
11#include "input_common/drivers/keyboard.h" 13#include "input_common/drivers/keyboard.h"
12#include "input_common/drivers/mouse.h" 14#include "input_common/drivers/mouse.h"
@@ -126,9 +128,16 @@ ConfigureRingController::ConfigureRingController(QWidget* parent,
126 ui->buttonRingAnalogPush, 128 ui->buttonRingAnalogPush,
127 }; 129 };
128 130
129 emulated_device = hid_core_.GetEmulatedDevices(); 131 emulated_controller = hid_core_.GetEmulatedController(Core::HID::NpadIdType::Player1);
130 emulated_device->SaveCurrentConfig(); 132 emulated_controller->SaveCurrentConfig();
131 emulated_device->EnableConfiguration(); 133 emulated_controller->EnableConfiguration();
134
135 Core::HID::ControllerUpdateCallback engine_callback{
136 .on_change = [this](Core::HID::ControllerTriggerType type) { ControllerUpdate(type); },
137 .is_npad_service = false,
138 };
139 callback_key = emulated_controller->SetCallback(engine_callback);
140 is_controller_set = true;
132 141
133 LoadConfiguration(); 142 LoadConfiguration();
134 143
@@ -143,9 +152,9 @@ ConfigureRingController::ConfigureRingController(QWidget* parent,
143 HandleClick( 152 HandleClick(
144 analog_map_buttons[sub_button_id], 153 analog_map_buttons[sub_button_id],
145 [=, this](const Common::ParamPackage& params) { 154 [=, this](const Common::ParamPackage& params) {
146 Common::ParamPackage param = emulated_device->GetRingParam(); 155 Common::ParamPackage param = emulated_controller->GetRingParam();
147 SetAnalogParam(params, param, analog_sub_buttons[sub_button_id]); 156 SetAnalogParam(params, param, analog_sub_buttons[sub_button_id]);
148 emulated_device->SetRingParam(param); 157 emulated_controller->SetRingParam(param);
149 }, 158 },
150 InputCommon::Polling::InputType::Stick); 159 InputCommon::Polling::InputType::Stick);
151 }); 160 });
@@ -155,16 +164,16 @@ ConfigureRingController::ConfigureRingController(QWidget* parent,
155 connect(analog_button, &QPushButton::customContextMenuRequested, 164 connect(analog_button, &QPushButton::customContextMenuRequested,
156 [=, this](const QPoint& menu_location) { 165 [=, this](const QPoint& menu_location) {
157 QMenu context_menu; 166 QMenu context_menu;
158 Common::ParamPackage param = emulated_device->GetRingParam(); 167 Common::ParamPackage param = emulated_controller->GetRingParam();
159 context_menu.addAction(tr("Clear"), [&] { 168 context_menu.addAction(tr("Clear"), [&] {
160 emulated_device->SetRingParam({}); 169 emulated_controller->SetRingParam(param);
161 analog_map_buttons[sub_button_id]->setText(tr("[not set]")); 170 analog_map_buttons[sub_button_id]->setText(tr("[not set]"));
162 }); 171 });
163 context_menu.addAction(tr("Invert axis"), [&] { 172 context_menu.addAction(tr("Invert axis"), [&] {
164 const bool invert_value = param.Get("invert_x", "+") == "-"; 173 const bool invert_value = param.Get("invert_x", "+") == "-";
165 const std::string invert_str = invert_value ? "+" : "-"; 174 const std::string invert_str = invert_value ? "+" : "-";
166 param.Set("invert_x", invert_str); 175 param.Set("invert_x", invert_str);
167 emulated_device->SetRingParam(param); 176 emulated_controller->SetRingParam(param);
168 for (int sub_button_id2 = 0; sub_button_id2 < ANALOG_SUB_BUTTONS_NUM; 177 for (int sub_button_id2 = 0; sub_button_id2 < ANALOG_SUB_BUTTONS_NUM;
169 ++sub_button_id2) { 178 ++sub_button_id2) {
170 analog_map_buttons[sub_button_id2]->setText( 179 analog_map_buttons[sub_button_id2]->setText(
@@ -177,16 +186,19 @@ ConfigureRingController::ConfigureRingController(QWidget* parent,
177 } 186 }
178 187
179 connect(ui->sliderRingAnalogDeadzone, &QSlider::valueChanged, [=, this] { 188 connect(ui->sliderRingAnalogDeadzone, &QSlider::valueChanged, [=, this] {
180 Common::ParamPackage param = emulated_device->GetRingParam(); 189 Common::ParamPackage param = emulated_controller->GetRingParam();
181 const auto slider_value = ui->sliderRingAnalogDeadzone->value(); 190 const auto slider_value = ui->sliderRingAnalogDeadzone->value();
182 ui->labelRingAnalogDeadzone->setText(tr("Deadzone: %1%").arg(slider_value)); 191 ui->labelRingAnalogDeadzone->setText(tr("Deadzone: %1%").arg(slider_value));
183 param.Set("deadzone", slider_value / 100.0f); 192 param.Set("deadzone", slider_value / 100.0f);
184 emulated_device->SetRingParam(param); 193 emulated_controller->SetRingParam(param);
185 }); 194 });
186 195
187 connect(ui->restore_defaults_button, &QPushButton::clicked, this, 196 connect(ui->restore_defaults_button, &QPushButton::clicked, this,
188 &ConfigureRingController::RestoreDefaults); 197 &ConfigureRingController::RestoreDefaults);
189 198
199 connect(ui->enable_ring_controller_button, &QPushButton::clicked, this,
200 &ConfigureRingController::EnableRingController);
201
190 timeout_timer->setSingleShot(true); 202 timeout_timer->setSingleShot(true);
191 connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); }); 203 connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); });
192 204
@@ -202,7 +214,14 @@ ConfigureRingController::ConfigureRingController(QWidget* parent,
202} 214}
203 215
204ConfigureRingController::~ConfigureRingController() { 216ConfigureRingController::~ConfigureRingController() {
205 emulated_device->DisableConfiguration(); 217 emulated_controller->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
218 Common::Input::PollingMode::Active);
219 emulated_controller->DisableConfiguration();
220
221 if (is_controller_set) {
222 emulated_controller->DeleteCallback(callback_key);
223 is_controller_set = false;
224 }
206}; 225};
207 226
208void ConfigureRingController::changeEvent(QEvent* event) { 227void ConfigureRingController::changeEvent(QEvent* event) {
@@ -219,7 +238,7 @@ void ConfigureRingController::RetranslateUI() {
219 238
220void ConfigureRingController::UpdateUI() { 239void ConfigureRingController::UpdateUI() {
221 RetranslateUI(); 240 RetranslateUI();
222 const Common::ParamPackage param = emulated_device->GetRingParam(); 241 const Common::ParamPackage param = emulated_controller->GetRingParam();
223 242
224 for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) { 243 for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
225 auto* const analog_button = analog_map_buttons[sub_button_id]; 244 auto* const analog_button = analog_map_buttons[sub_button_id];
@@ -240,9 +259,9 @@ void ConfigureRingController::UpdateUI() {
240} 259}
241 260
242void ConfigureRingController::ApplyConfiguration() { 261void ConfigureRingController::ApplyConfiguration() {
243 emulated_device->DisableConfiguration(); 262 emulated_controller->DisableConfiguration();
244 emulated_device->SaveCurrentConfig(); 263 emulated_controller->SaveCurrentConfig();
245 emulated_device->EnableConfiguration(); 264 emulated_controller->EnableConfiguration();
246} 265}
247 266
248void ConfigureRingController::LoadConfiguration() { 267void ConfigureRingController::LoadConfiguration() {
@@ -252,10 +271,62 @@ void ConfigureRingController::LoadConfiguration() {
252void ConfigureRingController::RestoreDefaults() { 271void ConfigureRingController::RestoreDefaults() {
253 const std::string default_ring_string = InputCommon::GenerateAnalogParamFromKeys( 272 const std::string default_ring_string = InputCommon::GenerateAnalogParamFromKeys(
254 0, 0, Config::default_ringcon_analogs[0], Config::default_ringcon_analogs[1], 0, 0.05f); 273 0, 0, Config::default_ringcon_analogs[0], Config::default_ringcon_analogs[1], 0, 0.05f);
255 emulated_device->SetRingParam(Common::ParamPackage(default_ring_string)); 274 emulated_controller->SetRingParam(Common::ParamPackage(default_ring_string));
256 UpdateUI(); 275 UpdateUI();
257} 276}
258 277
278void ConfigureRingController::EnableRingController() {
279 const auto dialog_title = tr("Error enabling ring input");
280
281 is_ring_enabled = false;
282 ui->ring_controller_sensor_value->setText(tr("Not connected"));
283
284 if (!Settings::values.enable_joycon_driver) {
285 QMessageBox::warning(this, dialog_title, tr("Direct Joycon driver is not enabled"));
286 return;
287 }
288
289 ui->enable_ring_controller_button->setEnabled(false);
290 ui->enable_ring_controller_button->setText(tr("Configuring"));
291 // SetPollingMode is blocking. Allow to update the button status before calling the command
292 repaint();
293
294 const auto result = emulated_controller->SetPollingMode(
295 Core::HID::EmulatedDeviceIndex::RightIndex, Common::Input::PollingMode::Ring);
296 switch (result) {
297 case Common::Input::DriverResult::Success:
298 is_ring_enabled = true;
299 break;
300 case Common::Input::DriverResult::NotSupported:
301 QMessageBox::warning(this, dialog_title,
302 tr("The current mapped device doesn't support the ring controller"));
303 break;
304 case Common::Input::DriverResult::NoDeviceDetected:
305 QMessageBox::warning(this, dialog_title,
306 tr("The current mapped device doesn't have a ring attached"));
307 break;
308 default:
309 QMessageBox::warning(this, dialog_title,
310 tr("Unexpected driver result %1").arg(static_cast<int>(result)));
311 break;
312 }
313 ui->enable_ring_controller_button->setEnabled(true);
314 ui->enable_ring_controller_button->setText(tr("Enable"));
315}
316
317void ConfigureRingController::ControllerUpdate(Core::HID::ControllerTriggerType type) {
318 if (!is_ring_enabled) {
319 return;
320 }
321 if (type != Core::HID::ControllerTriggerType::RingController) {
322 return;
323 }
324
325 const auto value = emulated_controller->GetRingSensorValues();
326 const auto tex_value = QString::fromStdString(fmt::format("{:.3f}", value.raw_value));
327 ui->ring_controller_sensor_value->setText(tex_value);
328}
329
259void ConfigureRingController::HandleClick( 330void ConfigureRingController::HandleClick(
260 QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter, 331 QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter,
261 InputCommon::Polling::InputType type) { 332 InputCommon::Polling::InputType type) {
diff --git a/src/yuzu/configuration/configure_ringcon.h b/src/yuzu/configuration/configure_ringcon.h
index 38a9cb716..b23c27906 100644
--- a/src/yuzu/configuration/configure_ringcon.h
+++ b/src/yuzu/configuration/configure_ringcon.h
@@ -13,7 +13,7 @@ class InputSubsystem;
13 13
14namespace Core::HID { 14namespace Core::HID {
15class HIDCore; 15class HIDCore;
16class EmulatedDevices; 16class EmulatedController;
17} // namespace Core::HID 17} // namespace Core::HID
18 18
19namespace Ui { 19namespace Ui {
@@ -42,6 +42,12 @@ private:
42 /// Restore all buttons to their default values. 42 /// Restore all buttons to their default values.
43 void RestoreDefaults(); 43 void RestoreDefaults();
44 44
45 /// Sets current polling mode to ring input
46 void EnableRingController();
47
48 // Handles emulated controller events
49 void ControllerUpdate(Core::HID::ControllerTriggerType type);
50
45 /// Called when the button was pressed. 51 /// Called when the button was pressed.
46 void HandleClick(QPushButton* button, 52 void HandleClick(QPushButton* button,
47 std::function<void(const Common::ParamPackage&)> new_input_setter, 53 std::function<void(const Common::ParamPackage&)> new_input_setter,
@@ -78,7 +84,11 @@ private:
78 std::optional<std::function<void(const Common::ParamPackage&)>> input_setter; 84 std::optional<std::function<void(const Common::ParamPackage&)>> input_setter;
79 85
80 InputCommon::InputSubsystem* input_subsystem; 86 InputCommon::InputSubsystem* input_subsystem;
81 Core::HID::EmulatedDevices* emulated_device; 87 Core::HID::EmulatedController* emulated_controller;
88
89 bool is_ring_enabled{};
90 bool is_controller_set{};
91 int callback_key;
82 92
83 std::unique_ptr<Ui::ConfigureRingController> ui; 93 std::unique_ptr<Ui::ConfigureRingController> ui;
84}; 94};
diff --git a/src/yuzu/configuration/configure_ringcon.ui b/src/yuzu/configuration/configure_ringcon.ui
index 9ec634dd4..514dff372 100644
--- a/src/yuzu/configuration/configure_ringcon.ui
+++ b/src/yuzu/configuration/configure_ringcon.ui
@@ -6,8 +6,8 @@
6 <rect> 6 <rect>
7 <x>0</x> 7 <x>0</x>
8 <y>0</y> 8 <y>0</y>
9 <width>298</width> 9 <width>315</width>
10 <height>339</height> 10 <height>400</height>
11 </rect> 11 </rect>
12 </property> 12 </property>
13 <property name="windowTitle"> 13 <property name="windowTitle">
@@ -46,187 +46,283 @@
46 </property> 46 </property>
47 </spacer> 47 </spacer>
48 </item> 48 </item>
49 <item> 49 <item>
50 <widget class="QGroupBox" name="RingAnalog"> 50 <widget class="QGroupBox" name="RingAnalog">
51 <property name="title"> 51 <property name="title">
52 <string>Ring Sensor Parameters</string> 52 <string>Virtual Ring Sensor Parameters</string>
53 </property> 53 </property>
54 <property name="alignment"> 54 <property name="alignment">
55 <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> 55 <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
56 </property> 56 </property>
57 <layout class="QVBoxLayout" name="verticalLayout_3"> 57 <layout class="QVBoxLayout" name="verticalLayout_1">
58 <property name="spacing">
59 <number>0</number>
60 </property>
61 <property name="sizeConstraint">
62 <enum>QLayout::SetDefaultConstraint</enum>
63 </property>
64 <property name="leftMargin">
65 <number>3</number>
66 </property>
67 <property name="topMargin">
68 <number>6</number>
69 </property>
70 <property name="rightMargin">
71 <number>3</number>
72 </property>
73 <property name="bottomMargin">
74 <number>0</number>
75 </property>
76 <item>
77 <layout class="QHBoxLayout" name="buttonRingAnalogPullHorizontaLayout">
78 <property name="spacing"> 58 <property name="spacing">
79 <number>3</number> 59 <number>0</number>
80 </property> 60 </property>
81 <item alignment="Qt::AlignHCenter"> 61 <property name="sizeConstraint">
82 <widget class="QGroupBox" name="buttonRingAnalogPullGroup"> 62 <enum>QLayout::SetDefaultConstraint</enum>
83 <property name="title"> 63 </property>
84 <string>Pull</string> 64 <property name="leftMargin">
85 </property> 65 <number>3</number>
86 <property name="alignment"> 66 </property>
87 <set>Qt::AlignCenter</set> 67 <property name="topMargin">
68 <number>6</number>
69 </property>
70 <property name="rightMargin">
71 <number>3</number>
72 </property>
73 <property name="bottomMargin">
74 <number>0</number>
75 </property>
76 <item>
77 <layout class="QHBoxLayout" name="buttonRingAnalogPullHorizontaLayout">
78 <property name="spacing">
79 <number>3</number>
88 </property> 80 </property>
89 <layout class="QVBoxLayout" name="buttonRingAnalogPullVerticalLayout"> 81 <item alignment="Qt::AlignHCenter">
90 <property name="spacing"> 82 <widget class="QGroupBox" name="buttonRingAnalogPullGroup">
91 <number>3</number> 83 <property name="title">
84 <string>Pull</string>
92 </property> 85 </property>
93 <property name="leftMargin"> 86 <property name="alignment">
94 <number>3</number> 87 <set>Qt::AlignCenter</set>
95 </property>
96 <property name="topMargin">
97 <number>3</number>
98 </property> 88 </property>
99 <property name="rightMargin"> 89 <layout class="QVBoxLayout" name="buttonRingAnalogPullVerticalLayout">
100 <number>3</number> 90 <property name="spacing">
91 <number>3</number>
92 </property>
93 <property name="leftMargin">
94 <number>3</number>
95 </property>
96 <property name="topMargin">
97 <number>3</number>
98 </property>
99 <property name="rightMargin">
100 <number>3</number>
101 </property>
102 <property name="bottomMargin">
103 <number>3</number>
104 </property>
105 <item>
106 <widget class="QPushButton" name="buttonRingAnalogPull">
107 <property name="minimumSize">
108 <size>
109 <width>70</width>
110 <height>0</height>
111 </size>
112 </property>
113 <property name="maximumSize">
114 <size>
115 <width>68</width>
116 <height>16777215</height>
117 </size>
118 </property>
119 <property name="styleSheet">
120 <string notr="true">min-width: 68px;</string>
121 </property>
122 <property name="text">
123 <string>Pull</string>
124 </property>
125 </widget>
126 </item>
127 </layout>
128 </widget>
129 </item>
130 <item alignment="Qt::AlignHCenter">
131 <widget class="QGroupBox" name="buttonRingAnalogPushGroup">
132 <property name="title">
133 <string>Push</string>
101 </property> 134 </property>
102 <property name="bottomMargin"> 135 <property name="alignment">
103 <number>3</number> 136 <set>Qt::AlignCenter</set>
104 </property> 137 </property>
105 <item> 138 <layout class="QVBoxLayout" name="buttonRingAnalogPushVerticalLayout">
106 <widget class="QPushButton" name="buttonRingAnalogPull"> 139 <property name="spacing">
107 <property name="minimumSize"> 140 <number>3</number>
108 <size> 141 </property>
109 <width>68</width> 142 <property name="leftMargin">
110 <height>0</height> 143 <number>3</number>
111 </size> 144 </property>
112 </property> 145 <property name="topMargin">
113 <property name="maximumSize"> 146 <number>3</number>
114 <size> 147 </property>
115 <width>68</width> 148 <property name="rightMargin">
116 <height>16777215</height> 149 <number>3</number>
117 </size> 150 </property>
118 </property> 151 <property name="bottomMargin">
119 <property name="styleSheet"> 152 <number>3</number>
120 <string notr="true">min-width: 68px;</string> 153 </property>
121 </property> 154 <item>
122 <property name="text"> 155 <widget class="QPushButton" name="buttonRingAnalogPush">
123 <string>Pull</string> 156 <property name="minimumSize">
124 </property> 157 <size>
125 </widget> 158 <width>70</width>
126 </item> 159 <height>0</height>
127 </layout> 160 </size>
128 </widget> 161 </property>
162 <property name="maximumSize">
163 <size>
164 <width>68</width>
165 <height>16777215</height>
166 </size>
167 </property>
168 <property name="styleSheet">
169 <string notr="true">min-width: 68px;</string>
170 </property>
171 <property name="text">
172 <string>Push</string>
173 </property>
174 </widget>
175 </item>
176 </layout>
177 </widget>
178 </item>
179 </layout>
129 </item> 180 </item>
130 <item alignment="Qt::AlignHCenter"> 181 <item>
131 <widget class="QGroupBox" name="buttonRingAnalogPushGroup"> 182 <layout class="QVBoxLayout" name="sliderRingAnalogDeadzoneVerticalLayout">
132 <property name="title"> 183 <property name="spacing">
133 <string>Push</string> 184 <number>3</number>
134 </property> 185 </property>
135 <property name="alignment"> 186 <property name="sizeConstraint">
136 <set>Qt::AlignCenter</set> 187 <enum>QLayout::SetDefaultConstraint</enum>
137 </property> 188 </property>
138 <layout class="QVBoxLayout" name="buttonRingAnalogPushVerticalLayout"> 189 <property name="leftMargin">
139 <property name="spacing"> 190 <number>0</number>
140 <number>3</number> 191 </property>
141 </property> 192 <property name="topMargin">
142 <property name="leftMargin"> 193 <number>10</number>
143 <number>3</number> 194 </property>
144 </property> 195 <property name="rightMargin">
145 <property name="topMargin"> 196 <number>0</number>
146 <number>3</number> 197 </property>
147 </property> 198 <property name="bottomMargin">
148 <property name="rightMargin"> 199 <number>3</number>
149 <number>3</number> 200 </property>
150 </property> 201 <item>
151 <property name="bottomMargin"> 202 <layout class="QHBoxLayout" name="sliderRingAnalogDeadzoneHorizontalLayout">
152 <number>3</number>
153 </property>
154 <item> 203 <item>
155 <widget class="QPushButton" name="buttonRingAnalogPush"> 204 <widget class="QLabel" name="labelRingAnalogDeadzone">
156 <property name="minimumSize">
157 <size>
158 <width>68</width>
159 <height>0</height>
160 </size>
161 </property>
162 <property name="maximumSize">
163 <size>
164 <width>68</width>
165 <height>16777215</height>
166 </size>
167 </property>
168 <property name="styleSheet">
169 <string notr="true">min-width: 68px;</string>
170 </property>
171 <property name="text"> 205 <property name="text">
172 <string>Push</string> 206 <string>Deadzone: 0%</string>
207 </property>
208 <property name="alignment">
209 <set>Qt::AlignHCenter</set>
173 </property> 210 </property>
174 </widget> 211 </widget>
175 </item> 212 </item>
176 </layout> 213 </layout>
177 </widget> 214 </item>
215 <item>
216 <widget class="QSlider" name="sliderRingAnalogDeadzone">
217 <property name="maximum">
218 <number>100</number>
219 </property>
220 <property name="orientation">
221 <enum>Qt::Horizontal</enum>
222 </property>
223 </widget>
224 </item>
225 </layout>
178 </item> 226 </item>
179 </layout> 227 </layout>
180 </item> 228 </widget>
181 <item> 229 </item>
182 <layout class="QVBoxLayout" name="sliderRingAnalogDeadzoneVerticalLayout"> 230 <item>
231 <widget class="QGroupBox" name="RingDriver">
232 <property name="title">
233 <string>Direct Joycon Driver</string>
234 </property>
235 <property name="alignment">
236 <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
237 </property>
238 <layout class="QVBoxLayout" name="verticalLayout_2">
183 <property name="spacing"> 239 <property name="spacing">
184 <number>3</number> 240 <number>0</number>
185 </property> 241 </property>
186 <property name="sizeConstraint"> 242 <property name="sizeConstraint">
187 <enum>QLayout::SetDefaultConstraint</enum> 243 <enum>QLayout::SetDefaultConstraint</enum>
188 </property> 244 </property>
189 <property name="leftMargin"> 245 <property name="leftMargin">
190 <number>0</number> 246 <number>3</number>
191 </property> 247 </property>
192 <property name="topMargin"> 248 <property name="topMargin">
193 <number>10</number> 249 <number>6</number>
194 </property> 250 </property>
195 <property name="rightMargin"> 251 <property name="rightMargin">
196 <number>0</number> 252 <number>3</number>
197 </property> 253 </property>
198 <property name="bottomMargin"> 254 <property name="bottomMargin">
199 <number>3</number> 255 <number>10</number>
200 </property> 256 </property>
201 <item> 257 <item>
202 <layout class="QHBoxLayout" name="sliderRingAnalogDeadzoneHorizontalLayout"> 258 <layout class="QGridLayout" name="gridLayout">
203 <item> 259 <property name="leftMargin">
204 <widget class="QLabel" name="labelRingAnalogDeadzone"> 260 <number>10</number>
261 </property>
262 <property name="topMargin">
263 <number>6</number>
264 </property>
265 <property name="rightMargin">
266 <number>10</number>
267 </property>
268 <property name="bottomMargin">
269 <number>10</number>
270 </property>
271 <property name="verticalSpacing">
272 <number>10</number>
273 </property>
274 <item row="0" column="1">
275 <spacer name="horizontalSpacer">
276 <property name="orientation">
277 <enum>Qt::Horizontal</enum>
278 </property>
279 <property name="sizeType">
280 <enum>QSizePolicy::Fixed</enum>
281 </property>
282 <property name="sizeHint" stdset="0">
283 <size>
284 <width>76</width>
285 <height>20</height>
286 </size>
287 </property>
288 </spacer>
289 </item>
290 <item row="0" column="0">
291 <widget class="QLabel" name="enable_ring_controller_label">
292 <property name="text">
293 <string>Enable Ring Input</string>
294 </property>
295 </widget>
296 </item>
297 <item row="0" column="2">
298 <widget class="QPushButton" name="enable_ring_controller_button">
205 <property name="text"> 299 <property name="text">
206 <string>Deadzone: 0%</string> 300 <string>Enable</string>
301 </property>
302 </widget>
303 </item>
304 <item row="1" column="0">
305 <widget class="QLabel" name="ring_controller_sensor_label">
306 <property name="text">
307 <string>Ring Sensor Value</string>
308 </property>
309 </widget>
310 </item>
311 <item row="1" column="2">
312 <widget class="QLabel" name="ring_controller_sensor_value">
313 <property name="text">
314 <string>Not connected</string>
207 </property> 315 </property>
208 <property name="alignment"> 316 <property name="alignment">
209 <set>Qt::AlignHCenter</set> 317 <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
210 </property> 318 </property>
211 </widget> 319 </widget>
212 </item> 320 </item>
213 </layout> 321 </layout>
214 </item>
215 <item>
216 <widget class="QSlider" name="sliderRingAnalogDeadzone">
217 <property name="maximum">
218 <number>100</number>
219 </property>
220 <property name="orientation">
221 <enum>Qt::Horizontal</enum>
222 </property>
223 </widget>
224 </item> 322 </item>
225 </layout> 323 </layout>
226 </item> 324 </widget>
227 </layout> 325 </item>
228 </widget>
229 </item>
230 <item> 326 <item>
231 <spacer name="verticalSpacer"> 327 <spacer name="verticalSpacer">
232 <property name="orientation"> 328 <property name="orientation">
@@ -273,6 +369,6 @@
273 <signal>rejected()</signal> 369 <signal>rejected()</signal>
274 <receiver>ConfigureRingController</receiver> 370 <receiver>ConfigureRingController</receiver>
275 <slot>reject()</slot> 371 <slot>reject()</slot>
276 </connection> 372 </connection>
277 </connections> 373 </connections>
278</ui> 374</ui>