summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Fernando S2022-04-16 15:51:14 +0200
committerGravatar GitHub2022-04-16 15:51:14 +0200
commitfd49b186fa13c74a61cdf70844486c75cfc0c930 (patch)
treeaff4d01b4a955e7928ed97cb979d80b596d013f3
parentMerge pull request #8188 from merryhime/jit-race-page-table-changed (diff)
parentyuzu: Call ignore event after ensuring it's initialized (diff)
downloadyuzu-fd49b186fa13c74a61cdf70844486c75cfc0c930.tar.gz
yuzu-fd49b186fa13c74a61cdf70844486c75cfc0c930.tar.xz
yuzu-fd49b186fa13c74a61cdf70844486c75cfc0c930.zip
Merge pull request #6558 from german77/ringcon2
hidbus: Implement hidbus and ringcon
Diffstat (limited to '')
-rw-r--r--src/common/settings.h3
-rw-r--r--src/common/settings_input.h1
-rw-r--r--src/core/CMakeLists.txt10
-rw-r--r--src/core/hid/emulated_devices.cpp45
-rw-r--r--src/core/hid/emulated_devices.h34
-rw-r--r--src/core/hle/kernel/kernel.cpp18
-rw-r--r--src/core/hle/kernel/kernel.h6
-rw-r--r--src/core/hle/service/hid/hid.cpp27
-rw-r--r--src/core/hle/service/hid/hidbus.cpp531
-rw-r--r--src/core/hle/service/hid/hidbus.h131
-rw-r--r--src/core/hle/service/hid/hidbus/hidbus_base.cpp72
-rw-r--r--src/core/hle/service/hid/hidbus/hidbus_base.h179
-rw-r--r--src/core/hle/service/hid/hidbus/ringcon.cpp286
-rw-r--r--src/core/hle/service/hid/hidbus/ringcon.h254
-rw-r--r--src/core/hle/service/hid/hidbus/starlink.cpp51
-rw-r--r--src/core/hle/service/hid/hidbus/starlink.h39
-rw-r--r--src/core/hle/service/hid/hidbus/stubbed.cpp52
-rw-r--r--src/core/hle/service/hid/hidbus/stubbed.h39
-rw-r--r--src/yuzu/CMakeLists.txt3
-rw-r--r--src/yuzu/configuration/config.cpp34
-rw-r--r--src/yuzu/configuration/config.h3
-rw-r--r--src/yuzu/configuration/configure_input.cpp5
-rw-r--r--src/yuzu/configuration/configure_input_advanced.cpp9
-rw-r--r--src/yuzu/configuration/configure_input_advanced.h1
-rw-r--r--src/yuzu/configuration/configure_input_advanced.ui14
-rw-r--r--src/yuzu/configuration/configure_input_player.cpp2
-rw-r--r--src/yuzu/configuration/configure_ringcon.cpp424
-rw-r--r--src/yuzu/configuration/configure_ringcon.h85
-rw-r--r--src/yuzu/configuration/configure_ringcon.ui278
29 files changed, 2608 insertions, 28 deletions
diff --git a/src/common/settings.h b/src/common/settings.h
index 86e0fa140..3b7be63b3 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -590,6 +590,9 @@ struct Values {
590 BasicSetting<int> touch_from_button_map_index{0, "touch_from_button_map"}; 590 BasicSetting<int> touch_from_button_map_index{0, "touch_from_button_map"};
591 std::vector<TouchFromButtonMap> touch_from_button_maps; 591 std::vector<TouchFromButtonMap> touch_from_button_maps;
592 592
593 BasicSetting<bool> enable_ring_controller{true, "enable_ring_controller"};
594 RingconRaw ringcon_analogs;
595
593 // Data Storage 596 // Data Storage
594 BasicSetting<bool> use_virtual_sd{true, "use_virtual_sd"}; 597 BasicSetting<bool> use_virtual_sd{true, "use_virtual_sd"};
595 BasicSetting<bool> gamecard_inserted{false, "gamecard_inserted"}; 598 BasicSetting<bool> gamecard_inserted{false, "gamecard_inserted"};
diff --git a/src/common/settings_input.h b/src/common/settings_input.h
index 4ff37e186..6f42346bc 100644
--- a/src/common/settings_input.h
+++ b/src/common/settings_input.h
@@ -357,6 +357,7 @@ constexpr int NUM_KEYBOARD_MODS_HID = NumKeyboardMods;
357using AnalogsRaw = std::array<std::string, NativeAnalog::NumAnalogs>; 357using AnalogsRaw = std::array<std::string, NativeAnalog::NumAnalogs>;
358using ButtonsRaw = std::array<std::string, NativeButton::NumButtons>; 358using ButtonsRaw = std::array<std::string, NativeButton::NumButtons>;
359using MotionsRaw = std::array<std::string, NativeMotion::NumMotions>; 359using MotionsRaw = std::array<std::string, NativeMotion::NumMotions>;
360using RingconRaw = std::string;
360 361
361constexpr u32 JOYCON_BODY_NEON_RED = 0xFF3C28; 362constexpr u32 JOYCON_BODY_NEON_RED = 0xFF3C28;
362constexpr u32 JOYCON_BUTTONS_NEON_RED = 0x1E0A0A; 363constexpr u32 JOYCON_BUTTONS_NEON_RED = 0x1E0A0A;
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index b681d21a7..62230bae0 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -434,6 +434,8 @@ add_library(core STATIC
434 hle/service/grc/grc.h 434 hle/service/grc/grc.h
435 hle/service/hid/hid.cpp 435 hle/service/hid/hid.cpp
436 hle/service/hid/hid.h 436 hle/service/hid/hid.h
437 hle/service/hid/hidbus.cpp
438 hle/service/hid/hidbus.h
437 hle/service/hid/irs.cpp 439 hle/service/hid/irs.cpp
438 hle/service/hid/irs.h 440 hle/service/hid/irs.h
439 hle/service/hid/ring_lifo.h 441 hle/service/hid/ring_lifo.h
@@ -460,6 +462,14 @@ add_library(core STATIC
460 hle/service/hid/controllers/touchscreen.h 462 hle/service/hid/controllers/touchscreen.h
461 hle/service/hid/controllers/xpad.cpp 463 hle/service/hid/controllers/xpad.cpp
462 hle/service/hid/controllers/xpad.h 464 hle/service/hid/controllers/xpad.h
465 hle/service/hid/hidbus/hidbus_base.cpp
466 hle/service/hid/hidbus/hidbus_base.h
467 hle/service/hid/hidbus/ringcon.cpp
468 hle/service/hid/hidbus/ringcon.h
469 hle/service/hid/hidbus/starlink.cpp
470 hle/service/hid/hidbus/starlink.h
471 hle/service/hid/hidbus/stubbed.cpp
472 hle/service/hid/hidbus/stubbed.h
463 hle/service/jit/jit_context.cpp 473 hle/service/jit/jit_context.cpp
464 hle/service/jit/jit_context.h 474 hle/service/jit/jit_context.h
465 hle/service/jit/jit.cpp 475 hle/service/jit/jit.cpp
diff --git a/src/core/hid/emulated_devices.cpp b/src/core/hid/emulated_devices.cpp
index cc0dcd931..2f84d2b52 100644
--- a/src/core/hid/emulated_devices.cpp
+++ b/src/core/hid/emulated_devices.cpp
@@ -15,6 +15,7 @@ EmulatedDevices::EmulatedDevices() = default;
15EmulatedDevices::~EmulatedDevices() = default; 15EmulatedDevices::~EmulatedDevices() = default;
16 16
17void EmulatedDevices::ReloadFromSettings() { 17void EmulatedDevices::ReloadFromSettings() {
18 ring_params = Common::ParamPackage(Settings::values.ringcon_analogs);
18 ReloadInput(); 19 ReloadInput();
19} 20}
20 21
@@ -66,6 +67,8 @@ void EmulatedDevices::ReloadInput() {
66 key_index++; 67 key_index++;
67 } 68 }
68 69
70 ring_analog_device = Common::Input::CreateDevice<Common::Input::InputDevice>(ring_params);
71
69 for (std::size_t index = 0; index < mouse_button_devices.size(); ++index) { 72 for (std::size_t index = 0; index < mouse_button_devices.size(); ++index) {
70 if (!mouse_button_devices[index]) { 73 if (!mouse_button_devices[index]) {
71 continue; 74 continue;
@@ -120,6 +123,13 @@ void EmulatedDevices::ReloadInput() {
120 }, 123 },
121 }); 124 });
122 } 125 }
126
127 if (ring_analog_device) {
128 ring_analog_device->SetCallback({
129 .on_change =
130 [this](const Common::Input::CallbackStatus& callback) { SetRingAnalog(callback); },
131 });
132 }
123} 133}
124 134
125void EmulatedDevices::UnloadInput() { 135void EmulatedDevices::UnloadInput() {
@@ -155,6 +165,7 @@ void EmulatedDevices::SaveCurrentConfig() {
155 if (!is_configuring) { 165 if (!is_configuring) {
156 return; 166 return;
157 } 167 }
168 Settings::values.ringcon_analogs = ring_params.Serialize();
158} 169}
159 170
160void EmulatedDevices::RestoreConfig() { 171void EmulatedDevices::RestoreConfig() {
@@ -164,6 +175,15 @@ void EmulatedDevices::RestoreConfig() {
164 ReloadFromSettings(); 175 ReloadFromSettings();
165} 176}
166 177
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
167void EmulatedDevices::SetKeyboardButton(const Common::Input::CallbackStatus& callback, 187void EmulatedDevices::SetKeyboardButton(const Common::Input::CallbackStatus& callback,
168 std::size_t index) { 188 std::size_t index) {
169 if (index >= device_status.keyboard_values.size()) { 189 if (index >= device_status.keyboard_values.size()) {
@@ -410,6 +430,23 @@ void EmulatedDevices::SetMouseStick(const Common::Input::CallbackStatus& callbac
410 TriggerOnChange(DeviceTriggerType::Mouse); 430 TriggerOnChange(DeviceTriggerType::Mouse);
411} 431}
412 432
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
413KeyboardValues EmulatedDevices::GetKeyboardValues() const { 450KeyboardValues EmulatedDevices::GetKeyboardValues() const {
414 std::scoped_lock lock{mutex}; 451 std::scoped_lock lock{mutex};
415 return device_status.keyboard_values; 452 return device_status.keyboard_values;
@@ -425,6 +462,10 @@ MouseButtonValues EmulatedDevices::GetMouseButtonsValues() const {
425 return device_status.mouse_button_values; 462 return device_status.mouse_button_values;
426} 463}
427 464
465RingAnalogValue EmulatedDevices::GetRingSensorValues() const {
466 return device_status.ring_analog_value;
467}
468
428KeyboardKey EmulatedDevices::GetKeyboard() const { 469KeyboardKey EmulatedDevices::GetKeyboard() const {
429 std::scoped_lock lock{mutex}; 470 std::scoped_lock lock{mutex};
430 return device_status.keyboard_state; 471 return device_status.keyboard_state;
@@ -450,6 +491,10 @@ AnalogStickState EmulatedDevices::GetMouseWheel() const {
450 return device_status.mouse_wheel_state; 491 return device_status.mouse_wheel_state;
451} 492}
452 493
494RingSensorForce EmulatedDevices::GetRingSensorForce() const {
495 return device_status.ring_analog_state;
496}
497
453void EmulatedDevices::TriggerOnChange(DeviceTriggerType type) { 498void EmulatedDevices::TriggerOnChange(DeviceTriggerType type) {
454 std::scoped_lock lock{callback_mutex}; 499 std::scoped_lock lock{callback_mutex};
455 for (const auto& poller_pair : callback_list) { 500 for (const auto& poller_pair : callback_list) {
diff --git a/src/core/hid/emulated_devices.h b/src/core/hid/emulated_devices.h
index 73e9f0293..fb6451e7a 100644
--- a/src/core/hid/emulated_devices.h
+++ b/src/core/hid/emulated_devices.h
@@ -26,9 +26,11 @@ 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>;
29 30
30using MouseButtonParams = 31using MouseButtonParams =
31 std::array<Common::ParamPackage, Settings::NativeMouseButton::NumMouseButtons>; 32 std::array<Common::ParamPackage, Settings::NativeMouseButton::NumMouseButtons>;
33using RingAnalogParams = Common::ParamPackage;
32 34
33using KeyboardValues = 35using KeyboardValues =
34 std::array<Common::Input::ButtonStatus, Settings::NativeKeyboard::NumKeyboardKeys>; 36 std::array<Common::Input::ButtonStatus, Settings::NativeKeyboard::NumKeyboardKeys>;
@@ -39,12 +41,17 @@ using MouseButtonValues =
39using MouseAnalogValues = 41using MouseAnalogValues =
40 std::array<Common::Input::AnalogStatus, Settings::NativeMouseWheel::NumMouseWheels>; 42 std::array<Common::Input::AnalogStatus, Settings::NativeMouseWheel::NumMouseWheels>;
41using MouseStickValue = Common::Input::TouchStatus; 43using MouseStickValue = Common::Input::TouchStatus;
44using RingAnalogValue = Common::Input::AnalogStatus;
42 45
43struct MousePosition { 46struct MousePosition {
44 f32 x; 47 f32 x;
45 f32 y; 48 f32 y;
46}; 49};
47 50
51struct RingSensorForce {
52 f32 force;
53};
54
48struct DeviceStatus { 55struct DeviceStatus {
49 // Data from input_common 56 // Data from input_common
50 KeyboardValues keyboard_values{}; 57 KeyboardValues keyboard_values{};
@@ -52,6 +59,7 @@ struct DeviceStatus {
52 MouseButtonValues mouse_button_values{}; 59 MouseButtonValues mouse_button_values{};
53 MouseAnalogValues mouse_analog_values{}; 60 MouseAnalogValues mouse_analog_values{};
54 MouseStickValue mouse_stick_value{}; 61 MouseStickValue mouse_stick_value{};
62 RingAnalogValue ring_analog_value{};
55 63
56 // Data for HID serices 64 // Data for HID serices
57 KeyboardKey keyboard_state{}; 65 KeyboardKey keyboard_state{};
@@ -59,12 +67,14 @@ struct DeviceStatus {
59 MouseButton mouse_button_state{}; 67 MouseButton mouse_button_state{};
60 MousePosition mouse_position_state{}; 68 MousePosition mouse_position_state{};
61 AnalogStickState mouse_wheel_state{}; 69 AnalogStickState mouse_wheel_state{};
70 RingSensorForce ring_analog_state{};
62}; 71};
63 72
64enum class DeviceTriggerType { 73enum class DeviceTriggerType {
65 Keyboard, 74 Keyboard,
66 KeyboardModdifier, 75 KeyboardModdifier,
67 Mouse, 76 Mouse,
77 RingController,
68}; 78};
69 79
70struct InterfaceUpdateCallback { 80struct InterfaceUpdateCallback {
@@ -110,6 +120,15 @@ public:
110 /// Reverts any mapped changes made that weren't saved 120 /// Reverts any mapped changes made that weren't saved
111 void RestoreConfig(); 121 void RestoreConfig();
112 122
123 // Returns the current mapped ring device
124 Common::ParamPackage GetRingParam() const;
125
126 /**
127 * Updates the current mapped ring device
128 * @param param ParamPackage with ring sensor data to be mapped
129 */
130 void SetRingParam(Common::ParamPackage param);
131
113 /// Returns the latest status of button input from the keyboard with parameters 132 /// Returns the latest status of button input from the keyboard with parameters
114 KeyboardValues GetKeyboardValues() const; 133 KeyboardValues GetKeyboardValues() const;
115 134
@@ -119,6 +138,9 @@ public:
119 /// Returns the latest status of button input from the mouse with parameters 138 /// Returns the latest status of button input from the mouse with parameters
120 MouseButtonValues GetMouseButtonsValues() const; 139 MouseButtonValues GetMouseButtonsValues() const;
121 140
141 /// Returns the latest status of analog input from the ring sensor with parameters
142 RingAnalogValue GetRingSensorValues() const;
143
122 /// Returns the latest status of button input from the keyboard 144 /// Returns the latest status of button input from the keyboard
123 KeyboardKey GetKeyboard() const; 145 KeyboardKey GetKeyboard() const;
124 146
@@ -134,6 +156,9 @@ public:
134 /// Returns the latest mouse wheel change 156 /// Returns the latest mouse wheel change
135 AnalogStickState GetMouseWheel() const; 157 AnalogStickState GetMouseWheel() const;
136 158
159 /// Returns the latest ringcon force sensor value
160 RingSensorForce GetRingSensorForce() const;
161
137 /** 162 /**
138 * Adds a callback to the list of events 163 * Adds a callback to the list of events
139 * @param update_callback InterfaceUpdateCallback that will be triggered 164 * @param update_callback InterfaceUpdateCallback that will be triggered
@@ -186,6 +211,12 @@ private:
186 void SetMouseStick(const Common::Input::CallbackStatus& callback); 211 void SetMouseStick(const Common::Input::CallbackStatus& callback);
187 212
188 /** 213 /**
214 * Updates the ring analog sensor status of the ring controller
215 * @param callback A CallbackStatus containing the force status
216 */
217 void SetRingAnalog(const Common::Input::CallbackStatus& callback);
218
219 /**
189 * Triggers a callback that something has changed on the device status 220 * Triggers a callback that something has changed on the device status
190 * @param type Input type of the event to trigger 221 * @param type Input type of the event to trigger
191 */ 222 */
@@ -193,11 +224,14 @@ private:
193 224
194 bool is_configuring{false}; 225 bool is_configuring{false};
195 226
227 RingAnalogParams ring_params;
228
196 KeyboardDevices keyboard_devices; 229 KeyboardDevices keyboard_devices;
197 KeyboardModifierDevices keyboard_modifier_devices; 230 KeyboardModifierDevices keyboard_modifier_devices;
198 MouseButtonDevices mouse_button_devices; 231 MouseButtonDevices mouse_button_devices;
199 MouseAnalogDevices mouse_analog_devices; 232 MouseAnalogDevices mouse_analog_devices;
200 MouseStickDevice mouse_stick_device; 233 MouseStickDevice mouse_stick_device;
234 RingAnalogDevice ring_analog_device;
201 235
202 mutable std::mutex mutex; 236 mutable std::mutex mutex;
203 mutable std::mutex callback_mutex; 237 mutable std::mutex callback_mutex;
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index d840d44e6..5984afd7e 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -140,6 +140,7 @@ struct KernelCore::Impl {
140 CleanupObject(font_shared_mem); 140 CleanupObject(font_shared_mem);
141 CleanupObject(irs_shared_mem); 141 CleanupObject(irs_shared_mem);
142 CleanupObject(time_shared_mem); 142 CleanupObject(time_shared_mem);
143 CleanupObject(hidbus_shared_mem);
143 CleanupObject(system_resource_limit); 144 CleanupObject(system_resource_limit);
144 145
145 for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) { 146 for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
@@ -622,16 +623,20 @@ struct KernelCore::Impl {
622 constexpr std::size_t font_size{0x1100000}; 623 constexpr std::size_t font_size{0x1100000};
623 constexpr std::size_t irs_size{0x8000}; 624 constexpr std::size_t irs_size{0x8000};
624 constexpr std::size_t time_size{0x1000}; 625 constexpr std::size_t time_size{0x1000};
626 constexpr std::size_t hidbus_size{0x1000};
625 627
626 const PAddr hid_phys_addr{system_pool.GetAddress()}; 628 const PAddr hid_phys_addr{system_pool.GetAddress()};
627 const PAddr font_phys_addr{system_pool.GetAddress() + hid_size}; 629 const PAddr font_phys_addr{system_pool.GetAddress() + hid_size};
628 const PAddr irs_phys_addr{system_pool.GetAddress() + hid_size + font_size}; 630 const PAddr irs_phys_addr{system_pool.GetAddress() + hid_size + font_size};
629 const PAddr time_phys_addr{system_pool.GetAddress() + hid_size + font_size + irs_size}; 631 const PAddr time_phys_addr{system_pool.GetAddress() + hid_size + font_size + irs_size};
632 const PAddr hidbus_phys_addr{system_pool.GetAddress() + hid_size + font_size + irs_size +
633 time_size};
630 634
631 hid_shared_mem = KSharedMemory::Create(system.Kernel()); 635 hid_shared_mem = KSharedMemory::Create(system.Kernel());
632 font_shared_mem = KSharedMemory::Create(system.Kernel()); 636 font_shared_mem = KSharedMemory::Create(system.Kernel());
633 irs_shared_mem = KSharedMemory::Create(system.Kernel()); 637 irs_shared_mem = KSharedMemory::Create(system.Kernel());
634 time_shared_mem = KSharedMemory::Create(system.Kernel()); 638 time_shared_mem = KSharedMemory::Create(system.Kernel());
639 hidbus_shared_mem = KSharedMemory::Create(system.Kernel());
635 640
636 hid_shared_mem->Initialize(system.DeviceMemory(), nullptr, 641 hid_shared_mem->Initialize(system.DeviceMemory(), nullptr,
637 {hid_phys_addr, hid_size / PageSize}, 642 {hid_phys_addr, hid_size / PageSize},
@@ -649,6 +654,10 @@ struct KernelCore::Impl {
649 {time_phys_addr, time_size / PageSize}, 654 {time_phys_addr, time_size / PageSize},
650 Svc::MemoryPermission::None, Svc::MemoryPermission::Read, 655 Svc::MemoryPermission::None, Svc::MemoryPermission::Read,
651 time_phys_addr, time_size, "Time:SharedMemory"); 656 time_phys_addr, time_size, "Time:SharedMemory");
657 hidbus_shared_mem->Initialize(system.DeviceMemory(), nullptr,
658 {hidbus_phys_addr, hidbus_size / PageSize},
659 Svc::MemoryPermission::None, Svc::MemoryPermission::Read,
660 hidbus_phys_addr, hidbus_size, "HidBus:SharedMemory");
652 } 661 }
653 662
654 KClientPort* CreateNamedServicePort(std::string name) { 663 KClientPort* CreateNamedServicePort(std::string name) {
@@ -748,6 +757,7 @@ struct KernelCore::Impl {
748 Kernel::KSharedMemory* font_shared_mem{}; 757 Kernel::KSharedMemory* font_shared_mem{};
749 Kernel::KSharedMemory* irs_shared_mem{}; 758 Kernel::KSharedMemory* irs_shared_mem{};
750 Kernel::KSharedMemory* time_shared_mem{}; 759 Kernel::KSharedMemory* time_shared_mem{};
760 Kernel::KSharedMemory* hidbus_shared_mem{};
751 761
752 // Memory layout 762 // Memory layout
753 std::unique_ptr<KMemoryLayout> memory_layout; 763 std::unique_ptr<KMemoryLayout> memory_layout;
@@ -1047,6 +1057,14 @@ const Kernel::KSharedMemory& KernelCore::GetTimeSharedMem() const {
1047 return *impl->time_shared_mem; 1057 return *impl->time_shared_mem;
1048} 1058}
1049 1059
1060Kernel::KSharedMemory& KernelCore::GetHidBusSharedMem() {
1061 return *impl->hidbus_shared_mem;
1062}
1063
1064const Kernel::KSharedMemory& KernelCore::GetHidBusSharedMem() const {
1065 return *impl->hidbus_shared_mem;
1066}
1067
1050void KernelCore::Suspend(bool in_suspention) { 1068void KernelCore::Suspend(bool in_suspention) {
1051 const bool should_suspend = exception_exited || in_suspention; 1069 const bool should_suspend = exception_exited || in_suspention;
1052 { 1070 {
diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h
index d709c368b..12e44b8a5 100644
--- a/src/core/hle/kernel/kernel.h
+++ b/src/core/hle/kernel/kernel.h
@@ -264,6 +264,12 @@ public:
264 /// Gets the shared memory object for Time services. 264 /// Gets the shared memory object for Time services.
265 const Kernel::KSharedMemory& GetTimeSharedMem() const; 265 const Kernel::KSharedMemory& GetTimeSharedMem() const;
266 266
267 /// Gets the shared memory object for HIDBus services.
268 Kernel::KSharedMemory& GetHidBusSharedMem();
269
270 /// Gets the shared memory object for HIDBus services.
271 const Kernel::KSharedMemory& GetHidBusSharedMem() const;
272
267 /// Suspend/unsuspend the OS. 273 /// Suspend/unsuspend the OS.
268 void Suspend(bool in_suspention); 274 void Suspend(bool in_suspention);
269 275
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index b2cec2253..9d3e0a658 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -16,6 +16,7 @@
16#include "core/hle/kernel/kernel.h" 16#include "core/hle/kernel/kernel.h"
17#include "core/hle/service/hid/errors.h" 17#include "core/hle/service/hid/errors.h"
18#include "core/hle/service/hid/hid.h" 18#include "core/hle/service/hid/hid.h"
19#include "core/hle/service/hid/hidbus.h"
19#include "core/hle/service/hid/irs.h" 20#include "core/hle/service/hid/irs.h"
20#include "core/hle/service/hid/xcd.h" 21#include "core/hle/service/hid/xcd.h"
21#include "core/memory.h" 22#include "core/memory.h"
@@ -2128,32 +2129,6 @@ public:
2128 } 2129 }
2129}; 2130};
2130 2131
2131class HidBus final : public ServiceFramework<HidBus> {
2132public:
2133 explicit HidBus(Core::System& system_) : ServiceFramework{system_, "hidbus"} {
2134 // clang-format off
2135 static const FunctionInfo functions[] = {
2136 {1, nullptr, "GetBusHandle"},
2137 {2, nullptr, "IsExternalDeviceConnected"},
2138 {3, nullptr, "Initialize"},
2139 {4, nullptr, "Finalize"},
2140 {5, nullptr, "EnableExternalDevice"},
2141 {6, nullptr, "GetExternalDeviceId"},
2142 {7, nullptr, "SendCommandAsync"},
2143 {8, nullptr, "GetSendCommandAsynceResult"},
2144 {9, nullptr, "SetEventForSendCommandAsycResult"},
2145 {10, nullptr, "GetSharedMemoryHandle"},
2146 {11, nullptr, "EnableJoyPollingReceiveMode"},
2147 {12, nullptr, "DisableJoyPollingReceiveMode"},
2148 {13, nullptr, "GetPollingData"},
2149 {14, nullptr, "SetStatusManagerType"},
2150 };
2151 // clang-format on
2152
2153 RegisterHandlers(functions);
2154 }
2155};
2156
2157void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { 2132void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
2158 std::make_shared<Hid>(system)->InstallAsService(service_manager); 2133 std::make_shared<Hid>(system)->InstallAsService(service_manager);
2159 std::make_shared<HidBus>(system)->InstallAsService(service_manager); 2134 std::make_shared<HidBus>(system)->InstallAsService(service_manager);
diff --git a/src/core/hle/service/hid/hidbus.cpp b/src/core/hle/service/hid/hidbus.cpp
new file mode 100644
index 000000000..af7662a15
--- /dev/null
+++ b/src/core/hle/service/hid/hidbus.cpp
@@ -0,0 +1,531 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "common/logging/log.h"
6#include "common/settings.h"
7#include "core/core.h"
8#include "core/core_timing.h"
9#include "core/core_timing_util.h"
10#include "core/hid/hid_types.h"
11#include "core/hle/ipc_helpers.h"
12#include "core/hle/kernel/k_event.h"
13#include "core/hle/kernel/k_readable_event.h"
14#include "core/hle/kernel/k_shared_memory.h"
15#include "core/hle/kernel/k_transfer_memory.h"
16#include "core/hle/service/hid/hidbus.h"
17#include "core/hle/service/hid/hidbus/ringcon.h"
18#include "core/hle/service/hid/hidbus/starlink.h"
19#include "core/hle/service/hid/hidbus/stubbed.h"
20#include "core/hle/service/service.h"
21#include "core/memory.h"
22
23namespace Service::HID {
24// (15ms, 66Hz)
25constexpr auto hidbus_update_ns = std::chrono::nanoseconds{15 * 1000 * 1000};
26
27HidBus::HidBus(Core::System& system_)
28 : ServiceFramework{system_, "hidbus"}, service_context{system_, service_name} {
29
30 // clang-format off
31 static const FunctionInfo functions[] = {
32 {1, &HidBus::GetBusHandle, "GetBusHandle"},
33 {2, &HidBus::IsExternalDeviceConnected, "IsExternalDeviceConnected"},
34 {3, &HidBus::Initialize, "Initialize"},
35 {4, &HidBus::Finalize, "Finalize"},
36 {5, &HidBus::EnableExternalDevice, "EnableExternalDevice"},
37 {6, &HidBus::GetExternalDeviceId, "GetExternalDeviceId"},
38 {7, &HidBus::SendCommandAsync, "SendCommandAsync"},
39 {8, &HidBus::GetSendCommandAsynceResult, "GetSendCommandAsynceResult"},
40 {9, &HidBus::SetEventForSendCommandAsycResult, "SetEventForSendCommandAsycResult"},
41 {10, &HidBus::GetSharedMemoryHandle, "GetSharedMemoryHandle"},
42 {11, &HidBus::EnableJoyPollingReceiveMode, "EnableJoyPollingReceiveMode"},
43 {12, &HidBus::DisableJoyPollingReceiveMode, "DisableJoyPollingReceiveMode"},
44 {13, nullptr, "GetPollingData"},
45 {14, &HidBus::SetStatusManagerType, "SetStatusManagerType"},
46 };
47 // clang-format on
48
49 RegisterHandlers(functions);
50
51 // Register update callbacks
52 hidbus_update_event = Core::Timing::CreateEvent(
53 "Hidbus::UpdateCallback",
54 [this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
55 const auto guard = LockService();
56 UpdateHidbus(user_data, ns_late);
57 });
58
59 system_.CoreTiming().ScheduleEvent(hidbus_update_ns, hidbus_update_event);
60}
61
62HidBus::~HidBus() {
63 system.CoreTiming().UnscheduleEvent(hidbus_update_event, 0);
64}
65
66void HidBus::UpdateHidbus(std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
67 auto& core_timing = system.CoreTiming();
68
69 if (is_hidbus_enabled) {
70 for (std::size_t i = 0; i < devices.size(); ++i) {
71 if (!devices[i].is_device_initializated) {
72 continue;
73 }
74 auto& device = devices[i].device;
75 device->OnUpdate();
76 auto& cur_entry = hidbus_status.entries[devices[i].handle.internal_index];
77 cur_entry.is_polling_mode = device->IsPollingMode();
78 cur_entry.polling_mode = device->GetPollingMode();
79 cur_entry.is_enabled = device->IsEnabled();
80
81 u8* shared_memory = system.Kernel().GetHidBusSharedMem().GetPointer();
82 std::memcpy(shared_memory + (i * sizeof(HidbusStatusManagerEntry)), &hidbus_status,
83 sizeof(HidbusStatusManagerEntry));
84 }
85 }
86
87 // If ns_late is higher than the update rate ignore the delay
88 if (ns_late > hidbus_update_ns) {
89 ns_late = {};
90 }
91
92 core_timing.ScheduleEvent(hidbus_update_ns - ns_late, hidbus_update_event);
93}
94
95std::optional<std::size_t> HidBus::GetDeviceIndexFromHandle(BusHandle handle) const {
96 for (std::size_t i = 0; i < devices.size(); ++i) {
97 const auto& device_handle = devices[i].handle;
98 if (handle.abstracted_pad_id == device_handle.abstracted_pad_id &&
99 handle.internal_index == device_handle.internal_index &&
100 handle.player_number == device_handle.player_number &&
101 handle.bus_type == device_handle.bus_type &&
102 handle.is_valid == device_handle.is_valid) {
103 return i;
104 }
105 }
106 return std::nullopt;
107}
108
109void HidBus::GetBusHandle(Kernel::HLERequestContext& ctx) {
110 IPC::RequestParser rp{ctx};
111 struct Parameters {
112 Core::HID::NpadIdType npad_id;
113 INSERT_PADDING_WORDS_NOINIT(1);
114 BusType bus_type;
115 u64 applet_resource_user_id;
116 };
117 static_assert(sizeof(Parameters) == 0x18, "Parameters has incorrect size.");
118
119 const auto parameters{rp.PopRaw<Parameters>()};
120
121 LOG_INFO(Service_HID, "called, npad_id={}, bus_type={}, applet_resource_user_id={}",
122 parameters.npad_id, parameters.bus_type, parameters.applet_resource_user_id);
123
124 bool is_handle_found = 0;
125 std::size_t handle_index = 0;
126
127 for (std::size_t i = 0; i < devices.size(); i++) {
128 const auto& handle = devices[i].handle;
129 if (!handle.is_valid) {
130 continue;
131 }
132 if (static_cast<Core::HID::NpadIdType>(handle.player_number) == parameters.npad_id &&
133 handle.bus_type == parameters.bus_type) {
134 is_handle_found = true;
135 handle_index = i;
136 break;
137 }
138 }
139
140 // Handle not found. Create a new one
141 if (!is_handle_found) {
142 for (std::size_t i = 0; i < devices.size(); i++) {
143 if (devices[i].handle.is_valid) {
144 continue;
145 }
146 devices[i].handle = {
147 .abstracted_pad_id = static_cast<u8>(i),
148 .internal_index = static_cast<u8>(i),
149 .player_number = static_cast<u8>(parameters.npad_id),
150 .bus_type = parameters.bus_type,
151 .is_valid = true,
152 };
153 handle_index = i;
154 break;
155 }
156 }
157
158 struct OutData {
159 bool is_valid;
160 INSERT_PADDING_BYTES(7);
161 BusHandle handle;
162 };
163 static_assert(sizeof(OutData) == 0x10, "OutData has incorrect size.");
164
165 const OutData out_data{
166 .is_valid = true,
167 .handle = devices[handle_index].handle,
168 };
169
170 IPC::ResponseBuilder rb{ctx, 6};
171 rb.Push(ResultSuccess);
172 rb.PushRaw(out_data);
173}
174
175void HidBus::IsExternalDeviceConnected(Kernel::HLERequestContext& ctx) {
176 IPC::RequestParser rp{ctx};
177 const auto bus_handle_{rp.PopRaw<BusHandle>()};
178
179 LOG_INFO(Service_HID,
180 "Called, abstracted_pad_id={}, bus_type={}, internal_index={}, "
181 "player_number={}, is_valid={}",
182 bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index,
183 bus_handle_.player_number, bus_handle_.is_valid);
184
185 const auto device_index = GetDeviceIndexFromHandle(bus_handle_);
186
187 if (device_index) {
188 const auto& device = devices[device_index.value()].device;
189 const bool is_attached = device->IsDeviceActivated();
190
191 IPC::ResponseBuilder rb{ctx, 3};
192 rb.Push(ResultSuccess);
193 rb.Push(is_attached);
194 return;
195 }
196
197 LOG_ERROR(Service_HID, "Invalid handle");
198 IPC::ResponseBuilder rb{ctx, 2};
199 rb.Push(ResultUnknown);
200 return;
201}
202
203void HidBus::Initialize(Kernel::HLERequestContext& ctx) {
204 IPC::RequestParser rp{ctx};
205 const auto bus_handle_{rp.PopRaw<BusHandle>()};
206 const auto applet_resource_user_id{rp.Pop<u64>()};
207
208 LOG_INFO(Service_HID,
209 "called, abstracted_pad_id={} bus_type={} internal_index={} "
210 "player_number={} is_valid={}, applet_resource_user_id={}",
211 bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index,
212 bus_handle_.player_number, bus_handle_.is_valid, applet_resource_user_id);
213
214 is_hidbus_enabled = true;
215
216 const auto device_index = GetDeviceIndexFromHandle(bus_handle_);
217
218 if (device_index) {
219 const auto entry_index = devices[device_index.value()].handle.internal_index;
220 auto& cur_entry = hidbus_status.entries[entry_index];
221
222 if (bus_handle_.internal_index == 0 && Settings::values.enable_ring_controller) {
223 MakeDevice<RingController>(bus_handle_);
224 devices[device_index.value()].is_device_initializated = true;
225 devices[device_index.value()].device->ActivateDevice();
226 cur_entry.is_in_focus = true;
227 cur_entry.is_connected = true;
228 cur_entry.is_connected_result = ResultSuccess;
229 cur_entry.is_enabled = false;
230 cur_entry.is_polling_mode = false;
231 } else {
232 MakeDevice<HidbusStubbed>(bus_handle_);
233 devices[device_index.value()].is_device_initializated = true;
234 cur_entry.is_in_focus = true;
235 cur_entry.is_connected = false;
236 cur_entry.is_connected_result = ResultSuccess;
237 cur_entry.is_enabled = false;
238 cur_entry.is_polling_mode = false;
239 }
240
241 std::memcpy(system.Kernel().GetHidBusSharedMem().GetPointer(), &hidbus_status,
242 sizeof(hidbus_status));
243
244 IPC::ResponseBuilder rb{ctx, 2};
245 rb.Push(ResultSuccess);
246 return;
247 }
248
249 LOG_ERROR(Service_HID, "Invalid handle");
250 IPC::ResponseBuilder rb{ctx, 2};
251 rb.Push(ResultUnknown);
252 return;
253}
254
255void HidBus::Finalize(Kernel::HLERequestContext& ctx) {
256 IPC::RequestParser rp{ctx};
257 const auto bus_handle_{rp.PopRaw<BusHandle>()};
258 const auto applet_resource_user_id{rp.Pop<u64>()};
259
260 LOG_INFO(Service_HID,
261 "called, abstracted_pad_id={}, bus_type={}, internal_index={}, "
262 "player_number={}, is_valid={}, applet_resource_user_id={}",
263 bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index,
264 bus_handle_.player_number, bus_handle_.is_valid, applet_resource_user_id);
265
266 const auto device_index = GetDeviceIndexFromHandle(bus_handle_);
267
268 if (device_index) {
269 const auto entry_index = devices[device_index.value()].handle.internal_index;
270 auto& cur_entry = hidbus_status.entries[entry_index];
271 auto& device = devices[device_index.value()].device;
272 devices[device_index.value()].is_device_initializated = false;
273 device->DeactivateDevice();
274
275 cur_entry.is_in_focus = true;
276 cur_entry.is_connected = false;
277 cur_entry.is_connected_result = ResultSuccess;
278 cur_entry.is_enabled = false;
279 cur_entry.is_polling_mode = false;
280 std::memcpy(system.Kernel().GetHidBusSharedMem().GetPointer(), &hidbus_status,
281 sizeof(hidbus_status));
282
283 IPC::ResponseBuilder rb{ctx, 2};
284 rb.Push(ResultSuccess);
285 return;
286 }
287
288 LOG_ERROR(Service_HID, "Invalid handle");
289 IPC::ResponseBuilder rb{ctx, 2};
290 rb.Push(ResultUnknown);
291 return;
292}
293
294void HidBus::EnableExternalDevice(Kernel::HLERequestContext& ctx) {
295 IPC::RequestParser rp{ctx};
296 struct Parameters {
297 bool enable;
298 INSERT_PADDING_BYTES_NOINIT(7);
299 BusHandle bus_handle;
300 u64 inval;
301 u64 applet_resource_user_id;
302 };
303 static_assert(sizeof(Parameters) == 0x20, "Parameters has incorrect size.");
304
305 const auto parameters{rp.PopRaw<Parameters>()};
306
307 LOG_INFO(Service_HID,
308 "called, enable={}, abstracted_pad_id={}, bus_type={}, internal_index={}, "
309 "player_number={}, is_valid={}, inval={}, applet_resource_user_id{}",
310 parameters.enable, parameters.bus_handle.abstracted_pad_id,
311 parameters.bus_handle.bus_type, parameters.bus_handle.internal_index,
312 parameters.bus_handle.player_number, parameters.bus_handle.is_valid, parameters.inval,
313 parameters.applet_resource_user_id);
314
315 const auto device_index = GetDeviceIndexFromHandle(parameters.bus_handle);
316
317 if (device_index) {
318 auto& device = devices[device_index.value()].device;
319 device->Enable(parameters.enable);
320
321 IPC::ResponseBuilder rb{ctx, 2};
322 rb.Push(ResultSuccess);
323 return;
324 }
325
326 LOG_ERROR(Service_HID, "Invalid handle");
327 IPC::ResponseBuilder rb{ctx, 2};
328 rb.Push(ResultUnknown);
329 return;
330}
331
332void HidBus::GetExternalDeviceId(Kernel::HLERequestContext& ctx) {
333 IPC::RequestParser rp{ctx};
334 const auto bus_handle_{rp.PopRaw<BusHandle>()};
335
336 LOG_INFO(Service_HID,
337 "called, abstracted_pad_id={}, bus_type={}, internal_index={}, player_number={}, "
338 "is_valid={}",
339 bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index,
340 bus_handle_.player_number, bus_handle_.is_valid);
341
342 const auto device_index = GetDeviceIndexFromHandle(bus_handle_);
343
344 if (device_index) {
345 const auto& device = devices[device_index.value()].device;
346 u32 device_id = device->GetDeviceId();
347 IPC::ResponseBuilder rb{ctx, 3};
348 rb.Push(ResultSuccess);
349 rb.Push<u32>(device_id);
350 return;
351 }
352
353 LOG_ERROR(Service_HID, "Invalid handle");
354 IPC::ResponseBuilder rb{ctx, 2};
355 rb.Push(ResultUnknown);
356 return;
357}
358
359void HidBus::SendCommandAsync(Kernel::HLERequestContext& ctx) {
360 IPC::RequestParser rp{ctx};
361 const auto data = ctx.ReadBuffer();
362 const auto bus_handle_{rp.PopRaw<BusHandle>()};
363
364 LOG_DEBUG(Service_HID,
365 "called, data_size={}, abstracted_pad_id={}, bus_type={}, internal_index={}, "
366 "player_number={}, is_valid={}",
367 data.size(), bus_handle_.abstracted_pad_id, bus_handle_.bus_type,
368 bus_handle_.internal_index, bus_handle_.player_number, bus_handle_.is_valid);
369
370 const auto device_index = GetDeviceIndexFromHandle(bus_handle_);
371
372 if (device_index) {
373 auto& device = devices[device_index.value()].device;
374 device->SetCommand(data);
375
376 IPC::ResponseBuilder rb{ctx, 2};
377 rb.Push(ResultSuccess);
378 return;
379 }
380
381 LOG_ERROR(Service_HID, "Invalid handle");
382 IPC::ResponseBuilder rb{ctx, 2};
383 rb.Push(ResultUnknown);
384 return;
385};
386
387void HidBus::GetSendCommandAsynceResult(Kernel::HLERequestContext& ctx) {
388 IPC::RequestParser rp{ctx};
389 const auto bus_handle_{rp.PopRaw<BusHandle>()};
390
391 LOG_DEBUG(Service_HID,
392 "called, abstracted_pad_id={}, bus_type={}, internal_index={}, player_number={}, "
393 "is_valid={}",
394 bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index,
395 bus_handle_.player_number, bus_handle_.is_valid);
396
397 const auto device_index = GetDeviceIndexFromHandle(bus_handle_);
398
399 if (device_index) {
400 const auto& device = devices[device_index.value()].device;
401 const std::vector<u8> data = device->GetReply();
402 const u64 data_size = ctx.WriteBuffer(data);
403
404 IPC::ResponseBuilder rb{ctx, 4};
405 rb.Push(ResultSuccess);
406 rb.Push<u64>(data_size);
407 return;
408 }
409
410 LOG_ERROR(Service_HID, "Invalid handle");
411 IPC::ResponseBuilder rb{ctx, 2};
412 rb.Push(ResultUnknown);
413 return;
414};
415
416void HidBus::SetEventForSendCommandAsycResult(Kernel::HLERequestContext& ctx) {
417 IPC::RequestParser rp{ctx};
418 const auto bus_handle_{rp.PopRaw<BusHandle>()};
419
420 LOG_INFO(Service_HID,
421 "called, abstracted_pad_id={}, bus_type={}, internal_index={}, player_number={}, "
422 "is_valid={}",
423 bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index,
424 bus_handle_.player_number, bus_handle_.is_valid);
425
426 const auto device_index = GetDeviceIndexFromHandle(bus_handle_);
427
428 if (device_index) {
429 const auto& device = devices[device_index.value()].device;
430 IPC::ResponseBuilder rb{ctx, 2, 1};
431 rb.Push(ResultSuccess);
432 rb.PushCopyObjects(device->GetSendCommandAsycEvent());
433 return;
434 }
435
436 LOG_ERROR(Service_HID, "Invalid handle");
437 IPC::ResponseBuilder rb{ctx, 2};
438 rb.Push(ResultUnknown);
439 return;
440};
441
442void HidBus::GetSharedMemoryHandle(Kernel::HLERequestContext& ctx) {
443 LOG_DEBUG(Service_HID, "called");
444
445 IPC::ResponseBuilder rb{ctx, 2, 1};
446 rb.Push(ResultSuccess);
447 rb.PushCopyObjects(&system.Kernel().GetHidBusSharedMem());
448}
449
450void HidBus::EnableJoyPollingReceiveMode(Kernel::HLERequestContext& ctx) {
451 IPC::RequestParser rp{ctx};
452 const auto t_mem_size{rp.Pop<u32>()};
453 const auto t_mem_handle{ctx.GetCopyHandle(0)};
454 const auto polling_mode_{rp.PopEnum<JoyPollingMode>()};
455 const auto bus_handle_{rp.PopRaw<BusHandle>()};
456
457 ASSERT_MSG(t_mem_size == 0x1000, "t_mem_size is not 0x1000 bytes");
458
459 auto t_mem =
460 system.CurrentProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(t_mem_handle);
461
462 if (t_mem.IsNull()) {
463 LOG_ERROR(Service_HID, "t_mem is a nullptr for handle=0x{:08X}", t_mem_handle);
464 IPC::ResponseBuilder rb{ctx, 2};
465 rb.Push(ResultUnknown);
466 return;
467 }
468
469 ASSERT_MSG(t_mem->GetSize() == 0x1000, "t_mem has incorrect size");
470
471 LOG_INFO(Service_HID,
472 "called, t_mem_handle=0x{:08X}, polling_mode={}, abstracted_pad_id={}, bus_type={}, "
473 "internal_index={}, player_number={}, is_valid={}",
474 t_mem_handle, polling_mode_, bus_handle_.abstracted_pad_id, bus_handle_.bus_type,
475 bus_handle_.internal_index, bus_handle_.player_number, bus_handle_.is_valid);
476
477 const auto device_index = GetDeviceIndexFromHandle(bus_handle_);
478
479 if (device_index) {
480 auto& device = devices[device_index.value()].device;
481 device->SetPollingMode(polling_mode_);
482 device->SetTransferMemoryPointer(system.Memory().GetPointer(t_mem->GetSourceAddress()));
483
484 IPC::ResponseBuilder rb{ctx, 2};
485 rb.Push(ResultSuccess);
486 return;
487 }
488
489 LOG_ERROR(Service_HID, "Invalid handle");
490 IPC::ResponseBuilder rb{ctx, 2};
491 rb.Push(ResultUnknown);
492 return;
493}
494
495void HidBus::DisableJoyPollingReceiveMode(Kernel::HLERequestContext& ctx) {
496 IPC::RequestParser rp{ctx};
497 const auto bus_handle_{rp.PopRaw<BusHandle>()};
498
499 LOG_INFO(Service_HID,
500 "called, abstracted_pad_id={}, bus_type={}, internal_index={}, player_number={}, "
501 "is_valid={}",
502 bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index,
503 bus_handle_.player_number, bus_handle_.is_valid);
504
505 const auto device_index = GetDeviceIndexFromHandle(bus_handle_);
506
507 if (device_index) {
508 auto& device = devices[device_index.value()].device;
509 device->DisablePollingMode();
510
511 IPC::ResponseBuilder rb{ctx, 2};
512 rb.Push(ResultSuccess);
513 return;
514 }
515
516 LOG_ERROR(Service_HID, "Invalid handle");
517 IPC::ResponseBuilder rb{ctx, 2};
518 rb.Push(ResultUnknown);
519 return;
520}
521
522void HidBus::SetStatusManagerType(Kernel::HLERequestContext& ctx) {
523 IPC::RequestParser rp{ctx};
524 const auto manager_type{rp.PopEnum<StatusManagerType>()};
525
526 LOG_WARNING(Service_HID, "(STUBBED) called, manager_type={}", manager_type);
527
528 IPC::ResponseBuilder rb{ctx, 2};
529 rb.Push(ResultSuccess);
530};
531} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hidbus.h b/src/core/hle/service/hid/hidbus.h
new file mode 100644
index 000000000..b10d5156a
--- /dev/null
+++ b/src/core/hle/service/hid/hidbus.h
@@ -0,0 +1,131 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <functional>
8
9#include "core/hle/service/hid/hidbus/hidbus_base.h"
10#include "core/hle/service/kernel_helpers.h"
11#include "core/hle/service/service.h"
12
13namespace Core::Timing {
14struct EventType;
15} // namespace Core::Timing
16
17namespace Core {
18class System;
19} // namespace Core
20
21namespace Service::HID {
22
23class HidBus final : public ServiceFramework<HidBus> {
24public:
25 explicit HidBus(Core::System& system_);
26 ~HidBus() override;
27
28private:
29 static const std::size_t max_number_of_handles = 0x13;
30
31 enum class HidBusDeviceId : std::size_t {
32 RingController = 0x20,
33 FamicomRight = 0x21,
34 Starlink = 0x28,
35 };
36
37 // This is nn::hidbus::detail::StatusManagerType
38 enum class StatusManagerType : u32 {
39 None,
40 Type16,
41 Type32,
42 };
43
44 // This is nn::hidbus::BusType
45 enum class BusType : u8 {
46 LeftJoyRail,
47 RightJoyRail,
48 InternalBus, // Lark microphone
49
50 MaxBusType,
51 };
52
53 // This is nn::hidbus::BusHandle
54 struct BusHandle {
55 u32 abstracted_pad_id;
56 u8 internal_index;
57 u8 player_number;
58 BusType bus_type;
59 bool is_valid;
60 };
61 static_assert(sizeof(BusHandle) == 0x8, "BusHandle is an invalid size");
62
63 // This is nn::hidbus::JoyPollingReceivedData
64 struct JoyPollingReceivedData {
65 std::array<u8, 0x30> data;
66 u64 out_size;
67 u64 sampling_number;
68 };
69 static_assert(sizeof(JoyPollingReceivedData) == 0x40,
70 "JoyPollingReceivedData is an invalid size");
71
72 struct HidbusStatusManagerEntry {
73 u8 is_connected{};
74 INSERT_PADDING_BYTES(0x3);
75 ResultCode is_connected_result{0};
76 u8 is_enabled{};
77 u8 is_in_focus{};
78 u8 is_polling_mode{};
79 u8 reserved{};
80 JoyPollingMode polling_mode{};
81 INSERT_PADDING_BYTES(0x70); // Unknown
82 };
83 static_assert(sizeof(HidbusStatusManagerEntry) == 0x80,
84 "HidbusStatusManagerEntry is an invalid size");
85
86 struct HidbusStatusManager {
87 std::array<HidbusStatusManagerEntry, max_number_of_handles> entries{};
88 INSERT_PADDING_BYTES(0x680); // Unused
89 };
90 static_assert(sizeof(HidbusStatusManager) <= 0x1000, "HidbusStatusManager is an invalid size");
91
92 struct HidbusDevice {
93 bool is_device_initializated{};
94 BusHandle handle{};
95 std::unique_ptr<HidbusBase> device{nullptr};
96 };
97
98 void GetBusHandle(Kernel::HLERequestContext& ctx);
99 void IsExternalDeviceConnected(Kernel::HLERequestContext& ctx);
100 void Initialize(Kernel::HLERequestContext& ctx);
101 void Finalize(Kernel::HLERequestContext& ctx);
102 void EnableExternalDevice(Kernel::HLERequestContext& ctx);
103 void GetExternalDeviceId(Kernel::HLERequestContext& ctx);
104 void SendCommandAsync(Kernel::HLERequestContext& ctx);
105 void GetSendCommandAsynceResult(Kernel::HLERequestContext& ctx);
106 void SetEventForSendCommandAsycResult(Kernel::HLERequestContext& ctx);
107 void GetSharedMemoryHandle(Kernel::HLERequestContext& ctx);
108 void EnableJoyPollingReceiveMode(Kernel::HLERequestContext& ctx);
109 void DisableJoyPollingReceiveMode(Kernel::HLERequestContext& ctx);
110 void SetStatusManagerType(Kernel::HLERequestContext& ctx);
111
112 void UpdateHidbus(std::uintptr_t user_data, std::chrono::nanoseconds ns_late);
113 std::optional<std::size_t> GetDeviceIndexFromHandle(BusHandle handle) const;
114
115 template <typename T>
116 void MakeDevice(BusHandle handle) {
117 const auto device_index = GetDeviceIndexFromHandle(handle);
118 if (device_index) {
119 devices[device_index.value()].device =
120 std::make_unique<T>(system.HIDCore(), service_context);
121 }
122 }
123
124 bool is_hidbus_enabled{false};
125 HidbusStatusManager hidbus_status{};
126 std::array<HidbusDevice, max_number_of_handles> devices{};
127 std::shared_ptr<Core::Timing::EventType> hidbus_update_event;
128 KernelHelpers::ServiceContext service_context;
129};
130
131} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hidbus/hidbus_base.cpp b/src/core/hle/service/hid/hidbus/hidbus_base.cpp
new file mode 100644
index 000000000..09bff10e5
--- /dev/null
+++ b/src/core/hle/service/hid/hidbus/hidbus_base.cpp
@@ -0,0 +1,72 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "core/hid/hid_core.h"
6#include "core/hle/kernel/k_event.h"
7#include "core/hle/kernel/k_readable_event.h"
8#include "core/hle/service/hid/hidbus/hidbus_base.h"
9#include "core/hle/service/kernel_helpers.h"
10
11namespace Service::HID {
12
13HidbusBase::HidbusBase(KernelHelpers::ServiceContext& service_context_)
14 : service_context(service_context_) {
15 send_command_async_event = service_context.CreateEvent("hidbus:SendCommandAsyncEvent");
16}
17HidbusBase::~HidbusBase() = default;
18
19void HidbusBase::ActivateDevice() {
20 if (is_activated) {
21 return;
22 }
23 is_activated = true;
24 OnInit();
25}
26
27void HidbusBase::DeactivateDevice() {
28 if (is_activated) {
29 OnRelease();
30 }
31 is_activated = false;
32}
33
34bool HidbusBase::IsDeviceActivated() const {
35 return is_activated;
36}
37
38void HidbusBase::Enable(bool enable) {
39 device_enabled = enable;
40}
41
42bool HidbusBase::IsEnabled() const {
43 return device_enabled;
44}
45
46bool HidbusBase::IsPollingMode() const {
47 return polling_mode_enabled;
48}
49
50JoyPollingMode HidbusBase::GetPollingMode() const {
51 return polling_mode;
52}
53
54void HidbusBase::SetPollingMode(JoyPollingMode mode) {
55 polling_mode = mode;
56 polling_mode_enabled = true;
57}
58
59void HidbusBase::DisablePollingMode() {
60 polling_mode_enabled = false;
61}
62
63void HidbusBase::SetTransferMemoryPointer(u8* t_mem) {
64 is_transfer_memory_set = true;
65 transfer_memory = t_mem;
66}
67
68Kernel::KReadableEvent& HidbusBase::GetSendCommandAsycEvent() const {
69 return send_command_async_event->GetReadableEvent();
70}
71
72} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hidbus/hidbus_base.h b/src/core/hle/service/hid/hidbus/hidbus_base.h
new file mode 100644
index 000000000..13d073a3d
--- /dev/null
+++ b/src/core/hle/service/hid/hidbus/hidbus_base.h
@@ -0,0 +1,179 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8#include "common/common_types.h"
9#include "core/hle/result.h"
10
11namespace Kernel {
12class KEvent;
13class KReadableEvent;
14} // namespace Kernel
15
16namespace Service::KernelHelpers {
17class ServiceContext;
18}
19
20namespace Service::HID {
21
22// This is nn::hidbus::JoyPollingMode
23enum class JoyPollingMode : u32 {
24 SixAxisSensorDisable,
25 SixAxisSensorEnable,
26 ButtonOnly,
27};
28
29struct DataAccessorHeader {
30 ResultCode result{ResultUnknown};
31 INSERT_PADDING_WORDS(0x1);
32 std::array<u8, 0x18> unused{};
33 u64 latest_entry{};
34 u64 total_entries{};
35};
36static_assert(sizeof(DataAccessorHeader) == 0x30, "DataAccessorHeader is an invalid size");
37
38struct JoyDisableSixAxisPollingData {
39 std::array<u8, 0x26> data;
40 u8 out_size;
41 INSERT_PADDING_BYTES(0x1);
42 u64 sampling_number;
43};
44static_assert(sizeof(JoyDisableSixAxisPollingData) == 0x30,
45 "JoyDisableSixAxisPollingData is an invalid size");
46
47struct JoyEnableSixAxisPollingData {
48 std::array<u8, 0x8> data;
49 u8 out_size;
50 INSERT_PADDING_BYTES(0x7);
51 u64 sampling_number;
52};
53static_assert(sizeof(JoyEnableSixAxisPollingData) == 0x18,
54 "JoyEnableSixAxisPollingData is an invalid size");
55
56struct JoyButtonOnlyPollingData {
57 std::array<u8, 0x2c> data;
58 u8 out_size;
59 INSERT_PADDING_BYTES(0x3);
60 u64 sampling_number;
61};
62static_assert(sizeof(JoyButtonOnlyPollingData) == 0x38,
63 "JoyButtonOnlyPollingData is an invalid size");
64
65struct JoyDisableSixAxisPollingEntry {
66 u64 sampling_number;
67 JoyDisableSixAxisPollingData polling_data;
68};
69static_assert(sizeof(JoyDisableSixAxisPollingEntry) == 0x38,
70 "JoyDisableSixAxisPollingEntry is an invalid size");
71
72struct JoyEnableSixAxisPollingEntry {
73 u64 sampling_number;
74 JoyEnableSixAxisPollingData polling_data;
75};
76static_assert(sizeof(JoyEnableSixAxisPollingEntry) == 0x20,
77 "JoyEnableSixAxisPollingEntry is an invalid size");
78
79struct JoyButtonOnlyPollingEntry {
80 u64 sampling_number;
81 JoyButtonOnlyPollingData polling_data;
82};
83static_assert(sizeof(JoyButtonOnlyPollingEntry) == 0x40,
84 "JoyButtonOnlyPollingEntry is an invalid size");
85
86struct JoyDisableSixAxisDataAccessor {
87 DataAccessorHeader header{};
88 std::array<JoyDisableSixAxisPollingEntry, 0xb> entries{};
89};
90static_assert(sizeof(JoyDisableSixAxisDataAccessor) == 0x298,
91 "JoyDisableSixAxisDataAccessor is an invalid size");
92
93struct JoyEnableSixAxisDataAccessor {
94 DataAccessorHeader header{};
95 std::array<JoyEnableSixAxisPollingEntry, 0xb> entries{};
96};
97static_assert(sizeof(JoyEnableSixAxisDataAccessor) == 0x190,
98 "JoyEnableSixAxisDataAccessor is an invalid size");
99
100struct ButtonOnlyPollingDataAccessor {
101 DataAccessorHeader header;
102 std::array<JoyButtonOnlyPollingEntry, 0xb> entries;
103};
104static_assert(sizeof(ButtonOnlyPollingDataAccessor) == 0x2F0,
105 "ButtonOnlyPollingDataAccessor is an invalid size");
106
107class HidbusBase {
108public:
109 explicit HidbusBase(KernelHelpers::ServiceContext& service_context_);
110 virtual ~HidbusBase();
111
112 void ActivateDevice();
113
114 void DeactivateDevice();
115
116 bool IsDeviceActivated() const;
117
118 // Enables/disables the device
119 void Enable(bool enable);
120
121 // returns true if device is enabled
122 bool IsEnabled() const;
123
124 // returns true if polling mode is enabled
125 bool IsPollingMode() const;
126
127 // returns polling mode
128 JoyPollingMode GetPollingMode() const;
129
130 // Sets and enables JoyPollingMode
131 void SetPollingMode(JoyPollingMode mode);
132
133 // Disables JoyPollingMode
134 void DisablePollingMode();
135
136 // Called on EnableJoyPollingReceiveMode
137 void SetTransferMemoryPointer(u8* t_mem);
138
139 Kernel::KReadableEvent& GetSendCommandAsycEvent() const;
140
141 virtual void OnInit() {}
142
143 virtual void OnRelease() {}
144
145 // Updates device transfer memory
146 virtual void OnUpdate() {}
147
148 // Returns the device ID of the joycon
149 virtual u8 GetDeviceId() const {
150 return {};
151 }
152
153 // Assigns a command from data
154 virtual bool SetCommand(const std::vector<u8>& data) {
155 return {};
156 }
157
158 // Returns a reply from a command
159 virtual std::vector<u8> GetReply() const {
160 return {};
161 }
162
163protected:
164 bool is_activated{};
165 bool device_enabled{};
166 bool polling_mode_enabled{};
167 JoyPollingMode polling_mode = {};
168 // TODO(German77): All data accessors need to be replaced with a ring lifo object
169 JoyDisableSixAxisDataAccessor disable_sixaxis_data{};
170 JoyEnableSixAxisDataAccessor enable_sixaxis_data{};
171 ButtonOnlyPollingDataAccessor button_only_data{};
172
173 u8* transfer_memory{nullptr};
174 bool is_transfer_memory_set{};
175
176 Kernel::KEvent* send_command_async_event;
177 KernelHelpers::ServiceContext& service_context;
178};
179} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hidbus/ringcon.cpp b/src/core/hle/service/hid/hidbus/ringcon.cpp
new file mode 100644
index 000000000..5ec3cc83c
--- /dev/null
+++ b/src/core/hle/service/hid/hidbus/ringcon.cpp
@@ -0,0 +1,286 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "core/hid/emulated_devices.h"
6#include "core/hid/hid_core.h"
7#include "core/hle/kernel/k_event.h"
8#include "core/hle/kernel/k_readable_event.h"
9#include "core/hle/service/hid/hidbus/ringcon.h"
10
11namespace Service::HID {
12
13RingController::RingController(Core::HID::HIDCore& hid_core_,
14 KernelHelpers::ServiceContext& service_context_)
15 : HidbusBase(service_context_) {
16 input = hid_core_.GetEmulatedDevices();
17}
18
19RingController::~RingController() = default;
20
21void RingController::OnInit() {
22 return;
23}
24
25void RingController::OnRelease() {
26 return;
27};
28
29void RingController::OnUpdate() {
30 if (!is_activated) {
31 return;
32 }
33
34 if (!device_enabled) {
35 return;
36 }
37
38 if (!polling_mode_enabled || !is_transfer_memory_set) {
39 return;
40 }
41
42 // TODO: Increment multitasking counters from motion and sensor data
43
44 switch (polling_mode) {
45 case JoyPollingMode::SixAxisSensorEnable: {
46 enable_sixaxis_data.header.total_entries = 10;
47 enable_sixaxis_data.header.result = ResultSuccess;
48 const auto& last_entry =
49 enable_sixaxis_data.entries[enable_sixaxis_data.header.latest_entry];
50
51 enable_sixaxis_data.header.latest_entry =
52 (enable_sixaxis_data.header.latest_entry + 1) % 10;
53 auto& curr_entry = enable_sixaxis_data.entries[enable_sixaxis_data.header.latest_entry];
54
55 curr_entry.sampling_number = last_entry.sampling_number + 1;
56 curr_entry.polling_data.sampling_number = curr_entry.sampling_number;
57
58 const RingConData ringcon_value = GetSensorValue();
59 curr_entry.polling_data.out_size = sizeof(ringcon_value);
60 std::memcpy(curr_entry.polling_data.data.data(), &ringcon_value, sizeof(ringcon_value));
61
62 std::memcpy(transfer_memory, &enable_sixaxis_data, sizeof(enable_sixaxis_data));
63 break;
64 }
65 default:
66 LOG_ERROR(Service_HID, "Polling mode not supported {}", polling_mode);
67 break;
68 }
69}
70
71RingController::RingConData RingController::GetSensorValue() const {
72 RingConData ringcon_sensor_value{
73 .status = DataValid::Valid,
74 .data = 0,
75 };
76
77 const f32 force_value = input->GetRingSensorForce().force * range;
78 ringcon_sensor_value.data = static_cast<s16>(force_value) + idle_value;
79
80 return ringcon_sensor_value;
81}
82
83u8 RingController::GetDeviceId() const {
84 return device_id;
85}
86
87std::vector<u8> RingController::GetReply() const {
88 const RingConCommands current_command = command;
89
90 switch (current_command) {
91 case RingConCommands::GetFirmwareVersion:
92 return GetFirmwareVersionReply();
93 case RingConCommands::ReadId:
94 return GetReadIdReply();
95 case RingConCommands::c20105:
96 return GetC020105Reply();
97 case RingConCommands::ReadUnkCal:
98 return GetReadUnkCalReply();
99 case RingConCommands::ReadFactoryCal:
100 return GetReadFactoryCalReply();
101 case RingConCommands::ReadUserCal:
102 return GetReadUserCalReply();
103 case RingConCommands::ReadRepCount:
104 return GetReadRepCountReply();
105 case RingConCommands::ReadTotalPushCount:
106 return GetReadTotalPushCountReply();
107 case RingConCommands::ResetRepCount:
108 return GetResetRepCountReply();
109 case RingConCommands::SaveCalData:
110 return GetSaveDataReply();
111 default:
112 return GetErrorReply();
113 }
114}
115
116bool RingController::SetCommand(const std::vector<u8>& data) {
117 if (data.size() < 4) {
118 LOG_ERROR(Service_HID, "Command size not supported {}", data.size());
119 command = RingConCommands::Error;
120 return false;
121 }
122
123 std::memcpy(&command, data.data(), sizeof(RingConCommands));
124
125 switch (command) {
126 case RingConCommands::GetFirmwareVersion:
127 case RingConCommands::ReadId:
128 case RingConCommands::c20105:
129 case RingConCommands::ReadUnkCal:
130 case RingConCommands::ReadFactoryCal:
131 case RingConCommands::ReadUserCal:
132 case RingConCommands::ReadRepCount:
133 case RingConCommands::ReadTotalPushCount:
134 ASSERT_MSG(data.size() == 0x4, "data.size is not 0x4 bytes");
135 send_command_async_event->GetWritableEvent().Signal();
136 return true;
137 case RingConCommands::ResetRepCount:
138 ASSERT_MSG(data.size() == 0x4, "data.size is not 0x4 bytes");
139 total_rep_count = 0;
140 send_command_async_event->GetWritableEvent().Signal();
141 return true;
142 case RingConCommands::SaveCalData: {
143 ASSERT_MSG(data.size() == 0x14, "data.size is not 0x14 bytes");
144
145 SaveCalData save_info{};
146 std::memcpy(&save_info, data.data(), sizeof(SaveCalData));
147 user_calibration = save_info.calibration;
148 send_command_async_event->GetWritableEvent().Signal();
149 return true;
150 }
151 default:
152 LOG_ERROR(Service_HID, "Command not implemented {}", command);
153 command = RingConCommands::Error;
154 // Signal a reply to avoid softlocking the game
155 send_command_async_event->GetWritableEvent().Signal();
156 return false;
157 }
158}
159
160std::vector<u8> RingController::GetFirmwareVersionReply() const {
161 const FirmwareVersionReply reply{
162 .status = DataValid::Valid,
163 .firmware = version,
164 };
165
166 return GetDataVector(reply);
167}
168
169std::vector<u8> RingController::GetReadIdReply() const {
170 // The values are hardcoded from a real joycon
171 const ReadIdReply reply{
172 .status = DataValid::Valid,
173 .id_l_x0 = 8,
174 .id_l_x0_2 = 41,
175 .id_l_x4 = 22294,
176 .id_h_x0 = 19777,
177 .id_h_x0_2 = 13621,
178 .id_h_x4 = 8245,
179 };
180
181 return GetDataVector(reply);
182}
183
184std::vector<u8> RingController::GetC020105Reply() const {
185 const Cmd020105Reply reply{
186 .status = DataValid::Valid,
187 .data = 1,
188 };
189
190 return GetDataVector(reply);
191}
192
193std::vector<u8> RingController::GetReadUnkCalReply() const {
194 const ReadUnkCalReply reply{
195 .status = DataValid::Valid,
196 .data = 0,
197 };
198
199 return GetDataVector(reply);
200}
201
202std::vector<u8> RingController::GetReadFactoryCalReply() const {
203 const ReadFactoryCalReply reply{
204 .status = DataValid::Valid,
205 .calibration = factory_calibration,
206 };
207
208 return GetDataVector(reply);
209}
210
211std::vector<u8> RingController::GetReadUserCalReply() const {
212 const ReadUserCalReply reply{
213 .status = DataValid::Valid,
214 .calibration = user_calibration,
215 };
216
217 return GetDataVector(reply);
218}
219
220std::vector<u8> RingController::GetReadRepCountReply() const {
221 const GetThreeByteReply reply{
222 .status = DataValid::Valid,
223 .data = {total_rep_count, 0, 0},
224 .crc = GetCrcValue({total_rep_count, 0, 0, 0}),
225 };
226
227 return GetDataVector(reply);
228}
229
230std::vector<u8> RingController::GetReadTotalPushCountReply() const {
231 const GetThreeByteReply reply{
232 .status = DataValid::Valid,
233 .data = {total_push_count, 0, 0},
234 .crc = GetCrcValue({total_push_count, 0, 0, 0}),
235 };
236
237 return GetDataVector(reply);
238}
239
240std::vector<u8> RingController::GetResetRepCountReply() const {
241 return GetReadRepCountReply();
242}
243
244std::vector<u8> RingController::GetSaveDataReply() const {
245 const StatusReply reply{
246 .status = DataValid::Valid,
247 };
248
249 return GetDataVector(reply);
250}
251
252std::vector<u8> RingController::GetErrorReply() const {
253 const ErrorReply reply{
254 .status = DataValid::BadCRC,
255 };
256
257 return GetDataVector(reply);
258}
259
260u8 RingController::GetCrcValue(const std::vector<u8>& data) const {
261 u8 crc = 0;
262 for (std::size_t index = 0; index < data.size(); index++) {
263 for (u8 i = 0x80; i > 0; i >>= 1) {
264 bool bit = (crc & 0x80) != 0;
265 if ((data[index] & i) != 0) {
266 bit = !bit;
267 }
268 crc <<= 1;
269 if (bit) {
270 crc ^= 0x8d;
271 }
272 }
273 }
274 return crc;
275}
276
277template <typename T>
278std::vector<u8> RingController::GetDataVector(const T& reply) const {
279 static_assert(std::is_trivially_copyable_v<T>);
280 std::vector<u8> data;
281 data.resize(sizeof(reply));
282 std::memcpy(data.data(), &reply, sizeof(reply));
283 return data;
284}
285
286} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hidbus/ringcon.h b/src/core/hle/service/hid/hidbus/ringcon.h
new file mode 100644
index 000000000..2dbc6150e
--- /dev/null
+++ b/src/core/hle/service/hid/hidbus/ringcon.h
@@ -0,0 +1,254 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8
9#include "common/common_types.h"
10#include "core/hle/service/hid/hidbus/hidbus_base.h"
11
12namespace Core::HID {
13class EmulatedDevices;
14} // namespace Core::HID
15
16namespace Service::HID {
17
18class RingController final : public HidbusBase {
19public:
20 explicit RingController(Core::HID::HIDCore& hid_core_,
21 KernelHelpers::ServiceContext& service_context_);
22 ~RingController() override;
23
24 void OnInit() override;
25
26 void OnRelease() override;
27
28 // Updates ringcon transfer memory
29 void OnUpdate() override;
30
31 // Returns the device ID of the joycon
32 u8 GetDeviceId() const override;
33
34 // Assigns a command from data
35 bool SetCommand(const std::vector<u8>& data) override;
36
37 // Returns a reply from a command
38 std::vector<u8> GetReply() const override;
39
40private:
41 // These values are obtained from a real ring controller
42 static constexpr s16 idle_value = 2280;
43 static constexpr s16 idle_deadzone = 120;
44 static constexpr s16 range = 2500;
45
46 // Most missing command names are leftovers from other firmware versions
47 enum class RingConCommands : u32 {
48 GetFirmwareVersion = 0x00020000,
49 ReadId = 0x00020100,
50 JoyPolling = 0x00020101,
51 Unknown1 = 0x00020104,
52 c20105 = 0x00020105,
53 Unknown2 = 0x00020204,
54 Unknown3 = 0x00020304,
55 Unknown4 = 0x00020404,
56 ReadUnkCal = 0x00020504,
57 ReadFactoryCal = 0x00020A04,
58 Unknown5 = 0x00021104,
59 Unknown6 = 0x00021204,
60 Unknown7 = 0x00021304,
61 ReadUserCal = 0x00021A04,
62 ReadRepCount = 0x00023104,
63 ReadTotalPushCount = 0x00023204,
64 ResetRepCount = 0x04013104,
65 Unknown8 = 0x04011104,
66 Unknown9 = 0x04011204,
67 Unknown10 = 0x04011304,
68 SaveCalData = 0x10011A04,
69 Error = 0xFFFFFFFF,
70 };
71
72 enum class DataValid : u32 {
73 Valid,
74 BadCRC,
75 Cal,
76 };
77
78 struct FirmwareVersion {
79 u8 sub;
80 u8 main;
81 };
82 static_assert(sizeof(FirmwareVersion) == 0x2, "FirmwareVersion is an invalid size");
83
84 struct FactoryCalibration {
85 s32_le os_max;
86 s32_le hk_max;
87 s32_le zero_min;
88 s32_le zero_max;
89 };
90 static_assert(sizeof(FactoryCalibration) == 0x10, "FactoryCalibration is an invalid size");
91
92 struct CalibrationValue {
93 s16 value;
94 u16 crc;
95 };
96 static_assert(sizeof(CalibrationValue) == 0x4, "CalibrationValue is an invalid size");
97
98 struct UserCalibration {
99 CalibrationValue os_max;
100 CalibrationValue hk_max;
101 CalibrationValue zero;
102 };
103 static_assert(sizeof(UserCalibration) == 0xC, "UserCalibration is an invalid size");
104
105 struct SaveCalData {
106 RingConCommands command;
107 UserCalibration calibration;
108 INSERT_PADDING_BYTES_NOINIT(4);
109 };
110 static_assert(sizeof(SaveCalData) == 0x14, "SaveCalData is an invalid size");
111 static_assert(std::is_trivially_copyable_v<SaveCalData>,
112 "SaveCalData must be trivially copyable");
113
114 struct FirmwareVersionReply {
115 DataValid status;
116 FirmwareVersion firmware;
117 INSERT_PADDING_BYTES(0x2);
118 };
119 static_assert(sizeof(FirmwareVersionReply) == 0x8, "FirmwareVersionReply is an invalid size");
120
121 struct Cmd020105Reply {
122 DataValid status;
123 u8 data;
124 INSERT_PADDING_BYTES(0x3);
125 };
126 static_assert(sizeof(Cmd020105Reply) == 0x8, "Cmd020105Reply is an invalid size");
127
128 struct StatusReply {
129 DataValid status;
130 };
131 static_assert(sizeof(StatusReply) == 0x4, "StatusReply is an invalid size");
132
133 struct GetThreeByteReply {
134 DataValid status;
135 std::array<u8, 3> data;
136 u8 crc;
137 };
138 static_assert(sizeof(GetThreeByteReply) == 0x8, "GetThreeByteReply is an invalid size");
139
140 struct ReadUnkCalReply {
141 DataValid status;
142 u16 data;
143 INSERT_PADDING_BYTES(0x2);
144 };
145 static_assert(sizeof(ReadUnkCalReply) == 0x8, "ReadUnkCalReply is an invalid size");
146
147 struct ReadFactoryCalReply {
148 DataValid status;
149 FactoryCalibration calibration;
150 };
151 static_assert(sizeof(ReadFactoryCalReply) == 0x14, "ReadFactoryCalReply is an invalid size");
152
153 struct ReadUserCalReply {
154 DataValid status;
155 UserCalibration calibration;
156 INSERT_PADDING_BYTES(0x4);
157 };
158 static_assert(sizeof(ReadUserCalReply) == 0x14, "ReadUserCalReply is an invalid size");
159
160 struct ReadIdReply {
161 DataValid status;
162 u16 id_l_x0;
163 u16 id_l_x0_2;
164 u16 id_l_x4;
165 u16 id_h_x0;
166 u16 id_h_x0_2;
167 u16 id_h_x4;
168 };
169 static_assert(sizeof(ReadIdReply) == 0x10, "ReadIdReply is an invalid size");
170
171 struct ErrorReply {
172 DataValid status;
173 INSERT_PADDING_BYTES(0x3);
174 };
175 static_assert(sizeof(ErrorReply) == 0x8, "ErrorReply is an invalid size");
176
177 struct RingConData {
178 DataValid status;
179 s16_le data;
180 INSERT_PADDING_BYTES(0x2);
181 };
182 static_assert(sizeof(RingConData) == 0x8, "RingConData is an invalid size");
183
184 // Returns RingConData struct with pressure sensor values
185 RingConData GetSensorValue() const;
186
187 // Returns 8 byte reply with firmware version
188 std::vector<u8> GetFirmwareVersionReply() const;
189
190 // Returns 16 byte reply with ID values
191 std::vector<u8> GetReadIdReply() const;
192
193 // (STUBBED) Returns 8 byte reply
194 std::vector<u8> GetC020105Reply() const;
195
196 // (STUBBED) Returns 8 byte empty reply
197 std::vector<u8> GetReadUnkCalReply() const;
198
199 // Returns 20 byte reply with factory calibration values
200 std::vector<u8> GetReadFactoryCalReply() const;
201
202 // Returns 20 byte reply with user calibration values
203 std::vector<u8> GetReadUserCalReply() const;
204
205 // Returns 8 byte reply
206 std::vector<u8> GetReadRepCountReply() const;
207
208 // Returns 8 byte reply
209 std::vector<u8> GetReadTotalPushCountReply() const;
210
211 // Returns 8 byte reply
212 std::vector<u8> GetResetRepCountReply() const;
213
214 // Returns 4 byte save data reply
215 std::vector<u8> GetSaveDataReply() const;
216
217 // Returns 8 byte error reply
218 std::vector<u8> GetErrorReply() const;
219
220 // Returns 8 bit redundancy check from provided data
221 u8 GetCrcValue(const std::vector<u8>& data) const;
222
223 // Converts structs to an u8 vector equivalent
224 template <typename T>
225 std::vector<u8> GetDataVector(const T& reply) const;
226
227 RingConCommands command{RingConCommands::Error};
228
229 // These counters are used in multitasking mode while the switch is sleeping
230 // Total steps taken
231 u8 total_rep_count = 0;
232 // Total times the ring was pushed
233 u8 total_push_count = 0;
234
235 const u8 device_id = 0x20;
236 const FirmwareVersion version = {
237 .sub = 0x0,
238 .main = 0x2c,
239 };
240 const FactoryCalibration factory_calibration = {
241 .os_max = idle_value + range + idle_deadzone,
242 .hk_max = idle_value - range - idle_deadzone,
243 .zero_min = idle_value - idle_deadzone,
244 .zero_max = idle_value + idle_deadzone,
245 };
246 UserCalibration user_calibration = {
247 .os_max = {.value = range, .crc = 228},
248 .hk_max = {.value = -range, .crc = 239},
249 .zero = {.value = idle_value, .crc = 225},
250 };
251
252 Core::HID::EmulatedDevices* input;
253};
254} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hidbus/starlink.cpp b/src/core/hle/service/hid/hidbus/starlink.cpp
new file mode 100644
index 000000000..3175c48da
--- /dev/null
+++ b/src/core/hle/service/hid/hidbus/starlink.cpp
@@ -0,0 +1,51 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "core/hid/emulated_controller.h"
6#include "core/hid/hid_core.h"
7#include "core/hle/service/hid/hidbus/starlink.h"
8
9namespace Service::HID {
10constexpr u8 DEVICE_ID = 0x28;
11
12Starlink::Starlink(Core::HID::HIDCore& hid_core_, KernelHelpers::ServiceContext& service_context_)
13 : HidbusBase(service_context_) {}
14Starlink::~Starlink() = default;
15
16void Starlink::OnInit() {
17 return;
18}
19
20void Starlink::OnRelease() {
21 return;
22};
23
24void Starlink::OnUpdate() {
25 if (!is_activated) {
26 return;
27 }
28 if (!device_enabled) {
29 return;
30 }
31 if (!polling_mode_enabled || !is_transfer_memory_set) {
32 return;
33 }
34
35 LOG_ERROR(Service_HID, "Polling mode not supported {}", polling_mode);
36}
37
38u8 Starlink::GetDeviceId() const {
39 return DEVICE_ID;
40}
41
42std::vector<u8> Starlink::GetReply() const {
43 return {};
44}
45
46bool Starlink::SetCommand(const std::vector<u8>& data) {
47 LOG_ERROR(Service_HID, "Command not implemented");
48 return false;
49}
50
51} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hidbus/starlink.h b/src/core/hle/service/hid/hidbus/starlink.h
new file mode 100644
index 000000000..79770b68e
--- /dev/null
+++ b/src/core/hle/service/hid/hidbus/starlink.h
@@ -0,0 +1,39 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include "common/common_types.h"
8#include "core/hle/service/hid/hidbus/hidbus_base.h"
9
10namespace Core::HID {
11class EmulatedController;
12} // namespace Core::HID
13
14namespace Service::HID {
15
16class Starlink final : public HidbusBase {
17public:
18 explicit Starlink(Core::HID::HIDCore& hid_core_,
19 KernelHelpers::ServiceContext& service_context_);
20 ~Starlink() override;
21
22 void OnInit() override;
23
24 void OnRelease() override;
25
26 // Updates ringcon transfer memory
27 void OnUpdate() override;
28
29 // Returns the device ID of the joycon
30 u8 GetDeviceId() const override;
31
32 // Assigns a command from data
33 bool SetCommand(const std::vector<u8>& data) override;
34
35 // Returns a reply from a command
36 std::vector<u8> GetReply() const override;
37};
38
39} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hidbus/stubbed.cpp b/src/core/hle/service/hid/hidbus/stubbed.cpp
new file mode 100644
index 000000000..5474657be
--- /dev/null
+++ b/src/core/hle/service/hid/hidbus/stubbed.cpp
@@ -0,0 +1,52 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "core/hid/emulated_controller.h"
6#include "core/hid/hid_core.h"
7#include "core/hle/service/hid/hidbus/stubbed.h"
8
9namespace Service::HID {
10constexpr u8 DEVICE_ID = 0xFF;
11
12HidbusStubbed::HidbusStubbed(Core::HID::HIDCore& hid_core_,
13 KernelHelpers::ServiceContext& service_context_)
14 : HidbusBase(service_context_) {}
15HidbusStubbed::~HidbusStubbed() = default;
16
17void HidbusStubbed::OnInit() {
18 return;
19}
20
21void HidbusStubbed::OnRelease() {
22 return;
23};
24
25void HidbusStubbed::OnUpdate() {
26 if (!is_activated) {
27 return;
28 }
29 if (!device_enabled) {
30 return;
31 }
32 if (!polling_mode_enabled || !is_transfer_memory_set) {
33 return;
34 }
35
36 LOG_ERROR(Service_HID, "Polling mode not supported {}", polling_mode);
37}
38
39u8 HidbusStubbed::GetDeviceId() const {
40 return DEVICE_ID;
41}
42
43std::vector<u8> HidbusStubbed::GetReply() const {
44 return {};
45}
46
47bool HidbusStubbed::SetCommand(const std::vector<u8>& data) {
48 LOG_ERROR(Service_HID, "Command not implemented");
49 return false;
50}
51
52} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hidbus/stubbed.h b/src/core/hle/service/hid/hidbus/stubbed.h
new file mode 100644
index 000000000..40acdfe8f
--- /dev/null
+++ b/src/core/hle/service/hid/hidbus/stubbed.h
@@ -0,0 +1,39 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include "common/common_types.h"
8#include "core/hle/service/hid/hidbus/hidbus_base.h"
9
10namespace Core::HID {
11class EmulatedController;
12} // namespace Core::HID
13
14namespace Service::HID {
15
16class HidbusStubbed final : public HidbusBase {
17public:
18 explicit HidbusStubbed(Core::HID::HIDCore& hid_core_,
19 KernelHelpers::ServiceContext& service_context_);
20 ~HidbusStubbed() override;
21
22 void OnInit() override;
23
24 void OnRelease() override;
25
26 // Updates ringcon transfer memory
27 void OnUpdate() override;
28
29 // Returns the device ID of the joycon
30 u8 GetDeviceId() const override;
31
32 // Assigns a command from data
33 bool SetCommand(const std::vector<u8>& data) override;
34
35 // Returns a reply from a command
36 std::vector<u8> GetReply() const override;
37};
38
39} // namespace Service::HID
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index b1467d016..2ee21f751 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -99,6 +99,9 @@ add_executable(yuzu
99 configuration/configure_profile_manager.cpp 99 configuration/configure_profile_manager.cpp
100 configuration/configure_profile_manager.h 100 configuration/configure_profile_manager.h
101 configuration/configure_profile_manager.ui 101 configuration/configure_profile_manager.ui
102 configuration/configure_ringcon.cpp
103 configuration/configure_ringcon.h
104 configuration/configure_ringcon.ui
102 configuration/configure_network.cpp 105 configuration/configure_network.cpp
103 configuration/configure_network.h 106 configuration/configure_network.h
104 configuration/configure_network.ui 107 configuration/configure_network.ui
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index d2e735f48..ac26b885b 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -60,6 +60,11 @@ const std::array<int, 2> Config::default_stick_mod = {
60 0, 60 0,
61}; 61};
62 62
63const std::array<int, 2> Config::default_ringcon_analogs{{
64 Qt::Key_A,
65 Qt::Key_D,
66}};
67
63// This shouldn't have anything except static initializers (no functions). So 68// This shouldn't have anything except static initializers (no functions). So
64// QKeySequence(...).toString() is NOT ALLOWED HERE. 69// QKeySequence(...).toString() is NOT ALLOWED HERE.
65// This must be in alphabetical order according to action name as it must have the same order as 70// This must be in alphabetical order according to action name as it must have the same order as
@@ -346,6 +351,23 @@ void Config::ReadTouchscreenValues() {
346 ReadSetting(QStringLiteral("touchscreen_diameter_y"), 15).toUInt(); 351 ReadSetting(QStringLiteral("touchscreen_diameter_y"), 15).toUInt();
347} 352}
348 353
354void Config::ReadHidbusValues() {
355 Settings::values.enable_ring_controller =
356 ReadSetting(QStringLiteral("enable_ring_controller"), true).toBool();
357
358 const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
359 0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f);
360 auto& ringcon_analogs = Settings::values.ringcon_analogs;
361
362 ringcon_analogs =
363 qt_config->value(QStringLiteral("ring_controller"), QString::fromStdString(default_param))
364 .toString()
365 .toStdString();
366 if (ringcon_analogs.empty()) {
367 ringcon_analogs = default_param;
368 }
369}
370
349void Config::ReadAudioValues() { 371void Config::ReadAudioValues() {
350 qt_config->beginGroup(QStringLiteral("Audio")); 372 qt_config->beginGroup(QStringLiteral("Audio"));
351 373
@@ -369,6 +391,7 @@ void Config::ReadControlValues() {
369 ReadMouseValues(); 391 ReadMouseValues();
370 ReadTouchscreenValues(); 392 ReadTouchscreenValues();
371 ReadMotionTouchValues(); 393 ReadMotionTouchValues();
394 ReadHidbusValues();
372 395
373#ifdef _WIN32 396#ifdef _WIN32
374 ReadBasicSetting(Settings::values.enable_raw_input); 397 ReadBasicSetting(Settings::values.enable_raw_input);
@@ -962,6 +985,16 @@ void Config::SaveMotionTouchValues() {
962 qt_config->endArray(); 985 qt_config->endArray();
963} 986}
964 987
988void Config::SaveHidbusValues() {
989 WriteBasicSetting(Settings::values.enable_ring_controller);
990
991 const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
992 0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f);
993 WriteSetting(QStringLiteral("ring_controller"),
994 QString::fromStdString(Settings::values.ringcon_analogs),
995 QString::fromStdString(default_param));
996}
997
965void Config::SaveValues() { 998void Config::SaveValues() {
966 if (global) { 999 if (global) {
967 SaveControlValues(); 1000 SaveControlValues();
@@ -1002,6 +1035,7 @@ void Config::SaveControlValues() {
1002 SaveMouseValues(); 1035 SaveMouseValues();
1003 SaveTouchscreenValues(); 1036 SaveTouchscreenValues();
1004 SaveMotionTouchValues(); 1037 SaveMotionTouchValues();
1038 SaveHidbusValues();
1005 1039
1006 WriteGlobalSetting(Settings::values.use_docked_mode); 1040 WriteGlobalSetting(Settings::values.use_docked_mode);
1007 WriteGlobalSetting(Settings::values.vibration_enabled); 1041 WriteGlobalSetting(Settings::values.vibration_enabled);
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h
index ae3e36a11..f0ab6bdaa 100644
--- a/src/yuzu/configuration/config.h
+++ b/src/yuzu/configuration/config.h
@@ -42,6 +42,7 @@ public:
42 static const std::array<int, Settings::NativeMotion::NumMotions> default_motions; 42 static const std::array<int, Settings::NativeMotion::NumMotions> default_motions;
43 static const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> default_analogs; 43 static const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> default_analogs;
44 static const std::array<int, 2> default_stick_mod; 44 static const std::array<int, 2> default_stick_mod;
45 static const std::array<int, 2> default_ringcon_analogs;
45 static const std::array<int, Settings::NativeMouseButton::NumMouseButtons> 46 static const std::array<int, Settings::NativeMouseButton::NumMouseButtons>
46 default_mouse_buttons; 47 default_mouse_buttons;
47 static const std::array<int, Settings::NativeKeyboard::NumKeyboardKeys> default_keyboard_keys; 48 static const std::array<int, Settings::NativeKeyboard::NumKeyboardKeys> default_keyboard_keys;
@@ -66,6 +67,7 @@ private:
66 void ReadMouseValues(); 67 void ReadMouseValues();
67 void ReadTouchscreenValues(); 68 void ReadTouchscreenValues();
68 void ReadMotionTouchValues(); 69 void ReadMotionTouchValues();
70 void ReadHidbusValues();
69 71
70 // Read functions bases off the respective config section names. 72 // Read functions bases off the respective config section names.
71 void ReadAudioValues(); 73 void ReadAudioValues();
@@ -93,6 +95,7 @@ private:
93 void SaveMouseValues(); 95 void SaveMouseValues();
94 void SaveTouchscreenValues(); 96 void SaveTouchscreenValues();
95 void SaveMotionTouchValues(); 97 void SaveMotionTouchValues();
98 void SaveHidbusValues();
96 99
97 // Save functions based off the respective config section names. 100 // Save functions based off the respective config section names.
98 void SaveAudioValues(); 101 void SaveAudioValues();
diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp
index 4ca74a5f7..73d7ba24b 100644
--- a/src/yuzu/configuration/configure_input.cpp
+++ b/src/yuzu/configuration/configure_input.cpp
@@ -20,6 +20,7 @@
20#include "yuzu/configuration/configure_input_advanced.h" 20#include "yuzu/configuration/configure_input_advanced.h"
21#include "yuzu/configuration/configure_input_player.h" 21#include "yuzu/configuration/configure_input_player.h"
22#include "yuzu/configuration/configure_motion_touch.h" 22#include "yuzu/configuration/configure_motion_touch.h"
23#include "yuzu/configuration/configure_ringcon.h"
23#include "yuzu/configuration/configure_touchscreen_advanced.h" 24#include "yuzu/configuration/configure_touchscreen_advanced.h"
24#include "yuzu/configuration/configure_vibration.h" 25#include "yuzu/configuration/configure_vibration.h"
25#include "yuzu/configuration/input_profiles.h" 26#include "yuzu/configuration/input_profiles.h"
@@ -158,6 +159,10 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
158 [this, input_subsystem] { 159 [this, input_subsystem] {
159 CallConfigureDialog<ConfigureMotionTouch>(*this, input_subsystem); 160 CallConfigureDialog<ConfigureMotionTouch>(*this, input_subsystem);
160 }); 161 });
162 connect(advanced, &ConfigureInputAdvanced::CallRingControllerDialog,
163 [this, input_subsystem, &hid_core] {
164 CallConfigureDialog<ConfigureRingController>(*this, input_subsystem, hid_core);
165 });
161 166
162 connect(ui->vibrationButton, &QPushButton::clicked, 167 connect(ui->vibrationButton, &QPushButton::clicked,
163 [this, &hid_core] { CallConfigureDialog<ConfigureVibration>(*this, hid_core); }); 168 [this, &hid_core] { CallConfigureDialog<ConfigureVibration>(*this, hid_core); });
diff --git a/src/yuzu/configuration/configure_input_advanced.cpp b/src/yuzu/configuration/configure_input_advanced.cpp
index 20fc2599d..8fd1f4a38 100644
--- a/src/yuzu/configuration/configure_input_advanced.cpp
+++ b/src/yuzu/configuration/configure_input_advanced.cpp
@@ -79,13 +79,17 @@ ConfigureInputAdvanced::ConfigureInputAdvanced(QWidget* parent)
79 &ConfigureInputAdvanced::UpdateUIEnabled); 79 &ConfigureInputAdvanced::UpdateUIEnabled);
80 connect(ui->touchscreen_enabled, &QCheckBox::stateChanged, this, 80 connect(ui->touchscreen_enabled, &QCheckBox::stateChanged, this,
81 &ConfigureInputAdvanced::UpdateUIEnabled); 81 &ConfigureInputAdvanced::UpdateUIEnabled);
82 connect(ui->enable_ring_controller, &QCheckBox::stateChanged, this,
83 &ConfigureInputAdvanced::UpdateUIEnabled);
82 84
83 connect(ui->debug_configure, &QPushButton::clicked, this, 85 connect(ui->debug_configure, &QPushButton::clicked, this,
84 [this] { CallDebugControllerDialog(); }); 86 [this] { CallDebugControllerDialog(); });
85 connect(ui->touchscreen_advanced, &QPushButton::clicked, this, 87 connect(ui->touchscreen_advanced, &QPushButton::clicked, this,
86 [this] { CallTouchscreenConfigDialog(); }); 88 [this] { CallTouchscreenConfigDialog(); });
87 connect(ui->buttonMotionTouch, &QPushButton::clicked, this, 89 connect(ui->buttonMotionTouch, &QPushButton::clicked, this,
88 &ConfigureInputAdvanced::CallMotionTouchConfigDialog); 90 [this] { CallMotionTouchConfigDialog(); });
91 connect(ui->ring_controller_configure, &QPushButton::clicked, this,
92 [this] { CallRingControllerDialog(); });
89 93
90#ifndef _WIN32 94#ifndef _WIN32
91 ui->enable_raw_input->setVisible(false); 95 ui->enable_raw_input->setVisible(false);
@@ -132,6 +136,7 @@ void ConfigureInputAdvanced::ApplyConfiguration() {
132 Settings::values.enable_raw_input = ui->enable_raw_input->isChecked(); 136 Settings::values.enable_raw_input = ui->enable_raw_input->isChecked();
133 Settings::values.enable_udp_controller = ui->enable_udp_controller->isChecked(); 137 Settings::values.enable_udp_controller = ui->enable_udp_controller->isChecked();
134 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();
135} 140}
136 141
137void ConfigureInputAdvanced::LoadConfiguration() { 142void ConfigureInputAdvanced::LoadConfiguration() {
@@ -164,6 +169,7 @@ void ConfigureInputAdvanced::LoadConfiguration() {
164 ui->enable_raw_input->setChecked(Settings::values.enable_raw_input.GetValue()); 169 ui->enable_raw_input->setChecked(Settings::values.enable_raw_input.GetValue());
165 ui->enable_udp_controller->setChecked(Settings::values.enable_udp_controller.GetValue()); 170 ui->enable_udp_controller->setChecked(Settings::values.enable_udp_controller.GetValue());
166 ui->controller_navigation->setChecked(Settings::values.controller_navigation.GetValue()); 171 ui->controller_navigation->setChecked(Settings::values.controller_navigation.GetValue());
172 ui->enable_ring_controller->setChecked(Settings::values.enable_ring_controller.GetValue());
167 173
168 UpdateUIEnabled(); 174 UpdateUIEnabled();
169} 175}
@@ -185,4 +191,5 @@ void ConfigureInputAdvanced::UpdateUIEnabled() {
185 ui->touchscreen_advanced->setEnabled(ui->touchscreen_enabled->isChecked()); 191 ui->touchscreen_advanced->setEnabled(ui->touchscreen_enabled->isChecked());
186 ui->mouse_panning->setEnabled(!ui->mouse_enabled->isChecked()); 192 ui->mouse_panning->setEnabled(!ui->mouse_enabled->isChecked());
187 ui->mouse_panning_sensitivity->setEnabled(!ui->mouse_enabled->isChecked()); 193 ui->mouse_panning_sensitivity->setEnabled(!ui->mouse_enabled->isChecked());
194 ui->ring_controller_configure->setEnabled(ui->enable_ring_controller->isChecked());
188} 195}
diff --git a/src/yuzu/configuration/configure_input_advanced.h b/src/yuzu/configuration/configure_input_advanced.h
index 3083d55c1..4472cb846 100644
--- a/src/yuzu/configuration/configure_input_advanced.h
+++ b/src/yuzu/configuration/configure_input_advanced.h
@@ -29,6 +29,7 @@ signals:
29 void CallMouseConfigDialog(); 29 void CallMouseConfigDialog();
30 void CallTouchscreenConfigDialog(); 30 void CallTouchscreenConfigDialog();
31 void CallMotionTouchConfigDialog(); 31 void CallMotionTouchConfigDialog();
32 void CallRingControllerDialog();
32 33
33private: 34private:
34 void changeEvent(QEvent* event) override; 35 void changeEvent(QEvent* event) override;
diff --git a/src/yuzu/configuration/configure_input_advanced.ui b/src/yuzu/configuration/configure_input_advanced.ui
index 66f2075f2..14403cb10 100644
--- a/src/yuzu/configuration/configure_input_advanced.ui
+++ b/src/yuzu/configuration/configure_input_advanced.ui
@@ -2603,6 +2603,20 @@
2603 </property> 2603 </property>
2604 </widget> 2604 </widget>
2605 </item> 2605 </item>
2606 <item row="4" column="0">
2607 <widget class="QCheckBox" name="enable_ring_controller">
2608 <property name="text">
2609 <string>Ring Controller</string>
2610 </property>
2611 </widget>
2612 </item>
2613 <item row="4" column="2">
2614 <widget class="QPushButton" name="ring_controller_configure">
2615 <property name="text">
2616 <string>Configure</string>
2617 </property>
2618 </widget>
2619 </item>
2606 </layout> 2620 </layout>
2607 </widget> 2621 </widget>
2608 </item> 2622 </item>
diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp
index 8ef3596dd..291e37acf 100644
--- a/src/yuzu/configuration/configure_input_player.cpp
+++ b/src/yuzu/configuration/configure_input_player.cpp
@@ -1407,10 +1407,10 @@ void ConfigureInputPlayer::mousePressEvent(QMouseEvent* event) {
1407} 1407}
1408 1408
1409void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) { 1409void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) {
1410 event->ignore();
1411 if (!input_setter || !event) { 1410 if (!input_setter || !event) {
1412 return; 1411 return;
1413 } 1412 }
1413 event->ignore();
1414 if (event->key() != Qt::Key_Escape) { 1414 if (event->key() != Qt::Key_Escape) {
1415 input_subsystem->GetKeyboard()->PressKey(event->key()); 1415 input_subsystem->GetKeyboard()->PressKey(event->key());
1416 } 1416 }
diff --git a/src/yuzu/configuration/configure_ringcon.cpp b/src/yuzu/configuration/configure_ringcon.cpp
new file mode 100644
index 000000000..f1cdd4fed
--- /dev/null
+++ b/src/yuzu/configuration/configure_ringcon.cpp
@@ -0,0 +1,424 @@
1// Copyright 2022 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <memory>
6#include <QKeyEvent>
7#include <QMenu>
8#include <QTimer>
9
10#include "core/hid/emulated_devices.h"
11#include "core/hid/hid_core.h"
12#include "input_common/drivers/keyboard.h"
13#include "input_common/drivers/mouse.h"
14#include "input_common/main.h"
15#include "ui_configure_ringcon.h"
16#include "yuzu/bootmanager.h"
17#include "yuzu/configuration/config.h"
18#include "yuzu/configuration/configure_ringcon.h"
19
20const std::array<std::string, ConfigureRingController::ANALOG_SUB_BUTTONS_NUM>
21 ConfigureRingController::analog_sub_buttons{{
22 "left",
23 "right",
24 }};
25
26namespace {
27
28QString GetKeyName(int key_code) {
29 switch (key_code) {
30 case Qt::Key_Shift:
31 return QObject::tr("Shift");
32 case Qt::Key_Control:
33 return QObject::tr("Ctrl");
34 case Qt::Key_Alt:
35 return QObject::tr("Alt");
36 case Qt::Key_Meta:
37 return {};
38 default:
39 return QKeySequence(key_code).toString();
40 }
41}
42
43QString GetButtonName(Common::Input::ButtonNames button_name) {
44 switch (button_name) {
45 case Common::Input::ButtonNames::ButtonLeft:
46 return QObject::tr("Left");
47 case Common::Input::ButtonNames::ButtonRight:
48 return QObject::tr("Right");
49 case Common::Input::ButtonNames::ButtonDown:
50 return QObject::tr("Down");
51 case Common::Input::ButtonNames::ButtonUp:
52 return QObject::tr("Up");
53 case Common::Input::ButtonNames::TriggerZ:
54 return QObject::tr("Z");
55 case Common::Input::ButtonNames::TriggerR:
56 return QObject::tr("R");
57 case Common::Input::ButtonNames::TriggerL:
58 return QObject::tr("L");
59 case Common::Input::ButtonNames::ButtonA:
60 return QObject::tr("A");
61 case Common::Input::ButtonNames::ButtonB:
62 return QObject::tr("B");
63 case Common::Input::ButtonNames::ButtonX:
64 return QObject::tr("X");
65 case Common::Input::ButtonNames::ButtonY:
66 return QObject::tr("Y");
67 case Common::Input::ButtonNames::ButtonStart:
68 return QObject::tr("Start");
69 case Common::Input::ButtonNames::L1:
70 return QObject::tr("L1");
71 case Common::Input::ButtonNames::L2:
72 return QObject::tr("L2");
73 case Common::Input::ButtonNames::L3:
74 return QObject::tr("L3");
75 case Common::Input::ButtonNames::R1:
76 return QObject::tr("R1");
77 case Common::Input::ButtonNames::R2:
78 return QObject::tr("R2");
79 case Common::Input::ButtonNames::R3:
80 return QObject::tr("R3");
81 case Common::Input::ButtonNames::Circle:
82 return QObject::tr("Circle");
83 case Common::Input::ButtonNames::Cross:
84 return QObject::tr("Cross");
85 case Common::Input::ButtonNames::Square:
86 return QObject::tr("Square");
87 case Common::Input::ButtonNames::Triangle:
88 return QObject::tr("Triangle");
89 case Common::Input::ButtonNames::Share:
90 return QObject::tr("Share");
91 case Common::Input::ButtonNames::Options:
92 return QObject::tr("Options");
93 default:
94 return QObject::tr("[undefined]");
95 }
96}
97
98void SetAnalogParam(const Common::ParamPackage& input_param, Common::ParamPackage& analog_param,
99 const std::string& button_name) {
100 // The poller returned a complete axis, so set all the buttons
101 if (input_param.Has("axis_x") && input_param.Has("axis_y")) {
102 analog_param = input_param;
103 return;
104 }
105 // Check if the current configuration has either no engine or an axis binding.
106 // Clears out the old binding and adds one with analog_from_button.
107 if (!analog_param.Has("engine") || analog_param.Has("axis_x") || analog_param.Has("axis_y")) {
108 analog_param = {
109 {"engine", "analog_from_button"},
110 };
111 }
112 analog_param.Set(button_name, input_param.Serialize());
113}
114} // namespace
115
116ConfigureRingController::ConfigureRingController(QWidget* parent,
117 InputCommon::InputSubsystem* input_subsystem_,
118 Core::HID::HIDCore& hid_core_)
119 : QDialog(parent), timeout_timer(std::make_unique<QTimer>()),
120 poll_timer(std::make_unique<QTimer>()), input_subsystem{input_subsystem_},
121
122 ui(std::make_unique<Ui::ConfigureRingController>()) {
123 ui->setupUi(this);
124
125 analog_map_buttons = {
126 ui->buttonRingAnalogPull,
127 ui->buttonRingAnalogPush,
128 };
129
130 emulated_device = hid_core_.GetEmulatedDevices();
131 emulated_device->SaveCurrentConfig();
132 emulated_device->EnableConfiguration();
133
134 LoadConfiguration();
135
136 for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
137 auto* const analog_button = analog_map_buttons[sub_button_id];
138
139 if (analog_button == nullptr) {
140 continue;
141 }
142
143 connect(analog_button, &QPushButton::clicked, [=, this] {
144 HandleClick(
145 analog_map_buttons[sub_button_id],
146 [=, this](const Common::ParamPackage& params) {
147 Common::ParamPackage param = emulated_device->GetRingParam();
148 SetAnalogParam(params, param, analog_sub_buttons[sub_button_id]);
149 emulated_device->SetRingParam(param);
150 },
151 InputCommon::Polling::InputType::Stick);
152 });
153
154 analog_button->setContextMenuPolicy(Qt::CustomContextMenu);
155
156 connect(analog_button, &QPushButton::customContextMenuRequested,
157 [=, this](const QPoint& menu_location) {
158 QMenu context_menu;
159 Common::ParamPackage param = emulated_device->GetRingParam();
160 context_menu.addAction(tr("Clear"), [&] {
161 emulated_device->SetRingParam({});
162 analog_map_buttons[sub_button_id]->setText(tr("[not set]"));
163 });
164 context_menu.addAction(tr("Invert axis"), [&] {
165 const bool invert_value = param.Get("invert_x", "+") == "-";
166 const std::string invert_str = invert_value ? "+" : "-";
167 param.Set("invert_x", invert_str);
168 emulated_device->SetRingParam(param);
169 for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM;
170 ++sub_button_id) {
171 analog_map_buttons[sub_button_id]->setText(
172 AnalogToText(param, analog_sub_buttons[sub_button_id]));
173 }
174 });
175 context_menu.exec(
176 analog_map_buttons[sub_button_id]->mapToGlobal(menu_location));
177 });
178 }
179
180 connect(ui->sliderRingAnalogDeadzone, &QSlider::valueChanged, [=, this] {
181 Common::ParamPackage param = emulated_device->GetRingParam();
182 const auto slider_value = ui->sliderRingAnalogDeadzone->value();
183 ui->labelRingAnalogDeadzone->setText(tr("Deadzone: %1%").arg(slider_value));
184 param.Set("deadzone", slider_value / 100.0f);
185 emulated_device->SetRingParam(param);
186 });
187
188 connect(ui->restore_defaults_button, &QPushButton::clicked, this,
189 &ConfigureRingController::RestoreDefaults);
190
191 timeout_timer->setSingleShot(true);
192 connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); });
193
194 connect(poll_timer.get(), &QTimer::timeout, [this] {
195 const auto& params = input_subsystem->GetNextInput();
196 if (params.Has("engine") && IsInputAcceptable(params)) {
197 SetPollingResult(params, false);
198 return;
199 }
200 });
201
202 resize(0, 0);
203}
204
205ConfigureRingController::~ConfigureRingController() {
206 emulated_device->DisableConfiguration();
207};
208
209void ConfigureRingController::changeEvent(QEvent* event) {
210 if (event->type() == QEvent::LanguageChange) {
211 RetranslateUI();
212 }
213
214 QDialog::changeEvent(event);
215}
216
217void ConfigureRingController::RetranslateUI() {
218 ui->retranslateUi(this);
219}
220
221void ConfigureRingController::UpdateUI() {
222 RetranslateUI();
223 const Common::ParamPackage param = emulated_device->GetRingParam();
224
225 for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
226 auto* const analog_button = analog_map_buttons[sub_button_id];
227
228 if (analog_button == nullptr) {
229 continue;
230 }
231
232 analog_button->setText(AnalogToText(param, analog_sub_buttons[sub_button_id]));
233 }
234
235 const auto deadzone_label = ui->labelRingAnalogDeadzone;
236 const auto deadzone_slider = ui->sliderRingAnalogDeadzone;
237
238 int slider_value = static_cast<int>(param.Get("deadzone", 0.15f) * 100);
239 deadzone_label->setText(tr("Deadzone: %1%").arg(slider_value));
240 deadzone_slider->setValue(slider_value);
241}
242
243void ConfigureRingController::ApplyConfiguration() {
244 emulated_device->DisableConfiguration();
245 emulated_device->SaveCurrentConfig();
246 emulated_device->EnableConfiguration();
247}
248
249void ConfigureRingController::LoadConfiguration() {
250 UpdateUI();
251}
252
253void ConfigureRingController::RestoreDefaults() {
254 const std::string default_ring_string = InputCommon::GenerateAnalogParamFromKeys(
255 0, 0, Config::default_ringcon_analogs[0], Config::default_ringcon_analogs[1], 0, 0.05f);
256 emulated_device->SetRingParam(Common::ParamPackage(default_ring_string));
257 UpdateUI();
258}
259
260void ConfigureRingController::HandleClick(
261 QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter,
262 InputCommon::Polling::InputType type) {
263 button->setText(tr("[waiting]"));
264 button->setFocus();
265
266 input_setter = new_input_setter;
267
268 input_subsystem->BeginMapping(type);
269
270 QWidget::grabMouse();
271 QWidget::grabKeyboard();
272
273 timeout_timer->start(2500); // Cancel after 2.5 seconds
274 poll_timer->start(25); // Check for new inputs every 25ms
275}
276
277void ConfigureRingController::SetPollingResult(const Common::ParamPackage& params, bool abort) {
278 timeout_timer->stop();
279 poll_timer->stop();
280 input_subsystem->StopMapping();
281
282 QWidget::releaseMouse();
283 QWidget::releaseKeyboard();
284
285 if (!abort) {
286 (*input_setter)(params);
287 }
288
289 UpdateUI();
290
291 input_setter = std::nullopt;
292}
293
294bool ConfigureRingController::IsInputAcceptable(const Common::ParamPackage& params) const {
295 return true;
296}
297
298void ConfigureRingController::mousePressEvent(QMouseEvent* event) {
299 if (!input_setter || !event) {
300 return;
301 }
302
303 const auto button = GRenderWindow::QtButtonToMouseButton(event->button());
304 input_subsystem->GetMouse()->PressButton(0, 0, 0, 0, button);
305}
306
307void ConfigureRingController::keyPressEvent(QKeyEvent* event) {
308 if (!input_setter || !event) {
309 return;
310 }
311 event->ignore();
312 if (event->key() != Qt::Key_Escape) {
313 input_subsystem->GetKeyboard()->PressKey(event->key());
314 }
315}
316
317QString ConfigureRingController::ButtonToText(const Common::ParamPackage& param) {
318 if (!param.Has("engine")) {
319 return QObject::tr("[not set]");
320 }
321
322 const QString toggle = QString::fromStdString(param.Get("toggle", false) ? "~" : "");
323 const QString inverted = QString::fromStdString(param.Get("inverted", false) ? "!" : "");
324 const auto common_button_name = input_subsystem->GetButtonName(param);
325
326 // Retrieve the names from Qt
327 if (param.Get("engine", "") == "keyboard") {
328 const QString button_str = GetKeyName(param.Get("code", 0));
329 return QObject::tr("%1%2").arg(toggle, button_str);
330 }
331
332 if (common_button_name == Common::Input::ButtonNames::Invalid) {
333 return QObject::tr("[invalid]");
334 }
335
336 if (common_button_name == Common::Input::ButtonNames::Engine) {
337 return QString::fromStdString(param.Get("engine", ""));
338 }
339
340 if (common_button_name == Common::Input::ButtonNames::Value) {
341 if (param.Has("hat")) {
342 const QString hat = QString::fromStdString(param.Get("direction", ""));
343 return QObject::tr("%1%2Hat %3").arg(toggle, inverted, hat);
344 }
345 if (param.Has("axis")) {
346 const QString axis = QString::fromStdString(param.Get("axis", ""));
347 return QObject::tr("%1%2Axis %3").arg(toggle, inverted, axis);
348 }
349 if (param.Has("axis_x") && param.Has("axis_y") && param.Has("axis_z")) {
350 const QString axis_x = QString::fromStdString(param.Get("axis_x", ""));
351 const QString axis_y = QString::fromStdString(param.Get("axis_y", ""));
352 const QString axis_z = QString::fromStdString(param.Get("axis_z", ""));
353 return QObject::tr("%1%2Axis %3,%4,%5").arg(toggle, inverted, axis_x, axis_y, axis_z);
354 }
355 if (param.Has("motion")) {
356 const QString motion = QString::fromStdString(param.Get("motion", ""));
357 return QObject::tr("%1%2Motion %3").arg(toggle, inverted, motion);
358 }
359 if (param.Has("button")) {
360 const QString button = QString::fromStdString(param.Get("button", ""));
361 return QObject::tr("%1%2Button %3").arg(toggle, inverted, button);
362 }
363 }
364
365 QString button_name = GetButtonName(common_button_name);
366 if (param.Has("hat")) {
367 return QObject::tr("%1%2Hat %3").arg(toggle, inverted, button_name);
368 }
369 if (param.Has("axis")) {
370 return QObject::tr("%1%2Axis %3").arg(toggle, inverted, button_name);
371 }
372 if (param.Has("motion")) {
373 return QObject::tr("%1%2Axis %3").arg(toggle, inverted, button_name);
374 }
375 if (param.Has("button")) {
376 return QObject::tr("%1%2Button %3").arg(toggle, inverted, button_name);
377 }
378
379 return QObject::tr("[unknown]");
380}
381
382QString ConfigureRingController::AnalogToText(const Common::ParamPackage& param,
383 const std::string& dir) {
384 if (!param.Has("engine")) {
385 return QObject::tr("[not set]");
386 }
387
388 if (param.Get("engine", "") == "analog_from_button") {
389 return ButtonToText(Common::ParamPackage{param.Get(dir, "")});
390 }
391
392 if (!param.Has("axis_x") || !param.Has("axis_y")) {
393 return QObject::tr("[unknown]");
394 }
395
396 const auto engine_str = param.Get("engine", "");
397 const QString axis_x_str = QString::fromStdString(param.Get("axis_x", ""));
398 const QString axis_y_str = QString::fromStdString(param.Get("axis_y", ""));
399 const bool invert_x = param.Get("invert_x", "+") == "-";
400 const bool invert_y = param.Get("invert_y", "+") == "-";
401
402 if (dir == "modifier") {
403 return QObject::tr("[unused]");
404 }
405
406 if (dir == "left") {
407 const QString invert_x_str = QString::fromStdString(invert_x ? "+" : "-");
408 return QObject::tr("Axis %1%2").arg(axis_x_str, invert_x_str);
409 }
410 if (dir == "right") {
411 const QString invert_x_str = QString::fromStdString(invert_x ? "-" : "+");
412 return QObject::tr("Axis %1%2").arg(axis_x_str, invert_x_str);
413 }
414 if (dir == "up") {
415 const QString invert_y_str = QString::fromStdString(invert_y ? "-" : "+");
416 return QObject::tr("Axis %1%2").arg(axis_y_str, invert_y_str);
417 }
418 if (dir == "down") {
419 const QString invert_y_str = QString::fromStdString(invert_y ? "+" : "-");
420 return QObject::tr("Axis %1%2").arg(axis_y_str, invert_y_str);
421 }
422
423 return QObject::tr("[unknown]");
424} \ No newline at end of file
diff --git a/src/yuzu/configuration/configure_ringcon.h b/src/yuzu/configuration/configure_ringcon.h
new file mode 100644
index 000000000..cf9e54f09
--- /dev/null
+++ b/src/yuzu/configuration/configure_ringcon.h
@@ -0,0 +1,85 @@
1// Copyright 2022 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <functional>
8#include <memory>
9#include <QDialog>
10
11namespace InputCommon {
12class InputSubsystem;
13} // namespace InputCommon
14
15namespace Core::HID {
16class HIDCore;
17class EmulatedDevices;
18} // namespace Core::HID
19
20namespace Ui {
21class ConfigureRingController;
22} // namespace Ui
23
24class ConfigureRingController : public QDialog {
25 Q_OBJECT
26
27public:
28 explicit ConfigureRingController(QWidget* parent, InputCommon::InputSubsystem* input_subsystem_,
29 Core::HID::HIDCore& hid_core_);
30 ~ConfigureRingController() override;
31
32 void ApplyConfiguration();
33
34private:
35 void changeEvent(QEvent* event) override;
36 void RetranslateUI();
37
38 void UpdateUI();
39
40 /// Load configuration settings.
41 void LoadConfiguration();
42
43 /// Restore all buttons to their default values.
44 void RestoreDefaults();
45
46 /// Called when the button was pressed.
47 void HandleClick(QPushButton* button,
48 std::function<void(const Common::ParamPackage&)> new_input_setter,
49 InputCommon::Polling::InputType type);
50
51 /// Finish polling and configure input using the input_setter.
52 void SetPollingResult(const Common::ParamPackage& params, bool abort);
53
54 /// Checks whether a given input can be accepted.
55 bool IsInputAcceptable(const Common::ParamPackage& params) const;
56
57 /// Handle mouse button press events.
58 void mousePressEvent(QMouseEvent* event) override;
59
60 /// Handle key press events.
61 void keyPressEvent(QKeyEvent* event) override;
62
63 QString ButtonToText(const Common::ParamPackage& param);
64
65 QString AnalogToText(const Common::ParamPackage& param, const std::string& dir);
66
67 static constexpr int ANALOG_SUB_BUTTONS_NUM = 2;
68
69 // A group of four QPushButtons represent one analog input. The buttons each represent left,
70 // right, respectively.
71 std::array<QPushButton*, ANALOG_SUB_BUTTONS_NUM> analog_map_buttons;
72
73 static const std::array<std::string, ANALOG_SUB_BUTTONS_NUM> analog_sub_buttons;
74
75 std::unique_ptr<QTimer> timeout_timer;
76 std::unique_ptr<QTimer> poll_timer;
77
78 /// This will be the the setting function when an input is awaiting configuration.
79 std::optional<std::function<void(const Common::ParamPackage&)>> input_setter;
80
81 InputCommon::InputSubsystem* input_subsystem;
82 Core::HID::EmulatedDevices* emulated_device;
83
84 std::unique_ptr<Ui::ConfigureRingController> ui;
85};
diff --git a/src/yuzu/configuration/configure_ringcon.ui b/src/yuzu/configuration/configure_ringcon.ui
new file mode 100644
index 000000000..9ec634dd4
--- /dev/null
+++ b/src/yuzu/configuration/configure_ringcon.ui
@@ -0,0 +1,278 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<ui version="4.0">
3 <class>ConfigureRingController</class>
4 <widget class="QDialog" name="ConfigureRingController">
5 <property name="geometry">
6 <rect>
7 <x>0</x>
8 <y>0</y>
9 <width>298</width>
10 <height>339</height>
11 </rect>
12 </property>
13 <property name="windowTitle">
14 <string>Configure Ring Controller</string>
15 </property>
16 <layout class="QVBoxLayout" name="verticalLayout">
17 <item>
18 <widget class="QLabel" name="label_2">
19 <property name="minimumSize">
20 <size>
21 <width>280</width>
22 <height>0</height>
23 </size>
24 </property>
25 <property name="text">
26 <string>If you want to use this controller configure player 1 as right controller and player 2 as dual joycon before starting the game to allow this controller to be detected properly.</string>
27 </property>
28 <property name="wordWrap">
29 <bool>true</bool>
30 </property>
31 </widget>
32 </item>
33 <item>
34 <spacer name="verticalSpacer_2">
35 <property name="orientation">
36 <enum>Qt::Vertical</enum>
37 </property>
38 <property name="sizeType">
39 <enum>QSizePolicy::Fixed</enum>
40 </property>
41 <property name="sizeHint" stdset="0">
42 <size>
43 <width>20</width>
44 <height>10</height>
45 </size>
46 </property>
47 </spacer>
48 </item>
49 <item>
50 <widget class="QGroupBox" name="RingAnalog">
51 <property name="title">
52 <string>Ring Sensor Parameters</string>
53 </property>
54 <property name="alignment">
55 <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
56 </property>
57 <layout class="QVBoxLayout" name="verticalLayout_3">
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">
79 <number>3</number>
80 </property>
81 <item alignment="Qt::AlignHCenter">
82 <widget class="QGroupBox" name="buttonRingAnalogPullGroup">
83 <property name="title">
84 <string>Pull</string>
85 </property>
86 <property name="alignment">
87 <set>Qt::AlignCenter</set>
88 </property>
89 <layout class="QVBoxLayout" name="buttonRingAnalogPullVerticalLayout">
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>68</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>
134 </property>
135 <property name="alignment">
136 <set>Qt::AlignCenter</set>
137 </property>
138 <layout class="QVBoxLayout" name="buttonRingAnalogPushVerticalLayout">
139 <property name="spacing">
140 <number>3</number>
141 </property>
142 <property name="leftMargin">
143 <number>3</number>
144 </property>
145 <property name="topMargin">
146 <number>3</number>
147 </property>
148 <property name="rightMargin">
149 <number>3</number>
150 </property>
151 <property name="bottomMargin">
152 <number>3</number>
153 </property>
154 <item>
155 <widget class="QPushButton" name="buttonRingAnalogPush">
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">
172 <string>Push</string>
173 </property>
174 </widget>
175 </item>
176 </layout>
177 </widget>
178 </item>
179 </layout>
180 </item>
181 <item>
182 <layout class="QVBoxLayout" name="sliderRingAnalogDeadzoneVerticalLayout">
183 <property name="spacing">
184 <number>3</number>
185 </property>
186 <property name="sizeConstraint">
187 <enum>QLayout::SetDefaultConstraint</enum>
188 </property>
189 <property name="leftMargin">
190 <number>0</number>
191 </property>
192 <property name="topMargin">
193 <number>10</number>
194 </property>
195 <property name="rightMargin">
196 <number>0</number>
197 </property>
198 <property name="bottomMargin">
199 <number>3</number>
200 </property>
201 <item>
202 <layout class="QHBoxLayout" name="sliderRingAnalogDeadzoneHorizontalLayout">
203 <item>
204 <widget class="QLabel" name="labelRingAnalogDeadzone">
205 <property name="text">
206 <string>Deadzone: 0%</string>
207 </property>
208 <property name="alignment">
209 <set>Qt::AlignHCenter</set>
210 </property>
211 </widget>
212 </item>
213 </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>
225 </layout>
226 </item>
227 </layout>
228 </widget>
229 </item>
230 <item>
231 <spacer name="verticalSpacer">
232 <property name="orientation">
233 <enum>Qt::Vertical</enum>
234 </property>
235 <property name="sizeHint" stdset="0">
236 <size>
237 <width>20</width>
238 <height>40</height>
239 </size>
240 </property>
241 </spacer>
242 </item>
243 <item>
244 <layout class="QHBoxLayout" name="horizontalLayout">
245 <item>
246 <widget class="QPushButton" name="restore_defaults_button">
247 <property name="text">
248 <string>Restore Defaults</string>
249 </property>
250 </widget>
251 </item>
252 <item>
253 <widget class="QDialogButtonBox" name="buttonBox">
254 <property name="standardButtons">
255 <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
256 </property>
257 </widget>
258 </item>
259 </layout>
260 </item>
261 </layout>
262 </widget>
263 <resources/>
264 <connections>
265 <connection>
266 <sender>buttonBox</sender>
267 <signal>accepted()</signal>
268 <receiver>ConfigureRingController</receiver>
269 <slot>accept()</slot>
270 </connection>
271 <connection>
272 <sender>buttonBox</sender>
273 <signal>rejected()</signal>
274 <receiver>ConfigureRingController</receiver>
275 <slot>reject()</slot>
276 </connection>
277 </connections>
278</ui>