summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/common/settings.h3
-rw-r--r--src/common/settings_input.h1
-rw-r--r--src/core/hid/emulated_devices.cpp45
-rw-r--r--src/core/hid/emulated_devices.h34
-rw-r--r--src/core/hle/service/hid/hidbus.cpp4
-rw-r--r--src/core/hle/service/hid/hidbus/hidbus_base.cpp4
-rw-r--r--src/core/hle/service/hid/hidbus/hidbus_base.h3
-rw-r--r--src/core/hle/service/hid/hidbus/ringcon.cpp78
-rw-r--r--src/core/hle/service/hid/hidbus/ringcon.h29
-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_ringcon.cpp424
-rw-r--r--src/yuzu/configuration/configure_ringcon.h85
-rw-r--r--src/yuzu/configuration/configure_ringcon.ui278
19 files changed, 992 insertions, 65 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/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/service/hid/hidbus.cpp b/src/core/hle/service/hid/hidbus.cpp
index db2864277..af7662a15 100644
--- a/src/core/hle/service/hid/hidbus.cpp
+++ b/src/core/hle/service/hid/hidbus.cpp
@@ -3,6 +3,7 @@
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include "common/logging/log.h" 5#include "common/logging/log.h"
6#include "common/settings.h"
6#include "core/core.h" 7#include "core/core.h"
7#include "core/core_timing.h" 8#include "core/core_timing.h"
8#include "core/core_timing_util.h" 9#include "core/core_timing_util.h"
@@ -190,6 +191,7 @@ void HidBus::IsExternalDeviceConnected(Kernel::HLERequestContext& ctx) {
190 IPC::ResponseBuilder rb{ctx, 3}; 191 IPC::ResponseBuilder rb{ctx, 3};
191 rb.Push(ResultSuccess); 192 rb.Push(ResultSuccess);
192 rb.Push(is_attached); 193 rb.Push(is_attached);
194 return;
193 } 195 }
194 196
195 LOG_ERROR(Service_HID, "Invalid handle"); 197 LOG_ERROR(Service_HID, "Invalid handle");
@@ -217,7 +219,7 @@ void HidBus::Initialize(Kernel::HLERequestContext& ctx) {
217 const auto entry_index = devices[device_index.value()].handle.internal_index; 219 const auto entry_index = devices[device_index.value()].handle.internal_index;
218 auto& cur_entry = hidbus_status.entries[entry_index]; 220 auto& cur_entry = hidbus_status.entries[entry_index];
219 221
220 if (bus_handle_.internal_index == 0) { 222 if (bus_handle_.internal_index == 0 && Settings::values.enable_ring_controller) {
221 MakeDevice<RingController>(bus_handle_); 223 MakeDevice<RingController>(bus_handle_);
222 devices[device_index.value()].is_device_initializated = true; 224 devices[device_index.value()].is_device_initializated = true;
223 devices[device_index.value()].device->ActivateDevice(); 225 devices[device_index.value()].device->ActivateDevice();
diff --git a/src/core/hle/service/hid/hidbus/hidbus_base.cpp b/src/core/hle/service/hid/hidbus/hidbus_base.cpp
index 9cac0be80..09bff10e5 100644
--- a/src/core/hle/service/hid/hidbus/hidbus_base.cpp
+++ b/src/core/hle/service/hid/hidbus/hidbus_base.cpp
@@ -12,7 +12,7 @@ namespace Service::HID {
12 12
13HidbusBase::HidbusBase(KernelHelpers::ServiceContext& service_context_) 13HidbusBase::HidbusBase(KernelHelpers::ServiceContext& service_context_)
14 : service_context(service_context_) { 14 : service_context(service_context_) {
15 send_command_asyc_event = service_context.CreateEvent("hidbus:SendCommandAsycEvent"); 15 send_command_async_event = service_context.CreateEvent("hidbus:SendCommandAsyncEvent");
16} 16}
17HidbusBase::~HidbusBase() = default; 17HidbusBase::~HidbusBase() = default;
18 18
@@ -66,7 +66,7 @@ void HidbusBase::SetTransferMemoryPointer(u8* t_mem) {
66} 66}
67 67
68Kernel::KReadableEvent& HidbusBase::GetSendCommandAsycEvent() const { 68Kernel::KReadableEvent& HidbusBase::GetSendCommandAsycEvent() const {
69 return send_command_asyc_event->GetReadableEvent(); 69 return send_command_async_event->GetReadableEvent();
70} 70}
71 71
72} // namespace Service::HID 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
index 41e571998..13d073a3d 100644
--- a/src/core/hle/service/hid/hidbus/hidbus_base.h
+++ b/src/core/hle/service/hid/hidbus/hidbus_base.h
@@ -165,6 +165,7 @@ protected:
165 bool device_enabled{}; 165 bool device_enabled{};
166 bool polling_mode_enabled{}; 166 bool polling_mode_enabled{};
167 JoyPollingMode polling_mode = {}; 167 JoyPollingMode polling_mode = {};
168 // TODO(German77): All data accessors need to be replaced with a ring lifo object
168 JoyDisableSixAxisDataAccessor disable_sixaxis_data{}; 169 JoyDisableSixAxisDataAccessor disable_sixaxis_data{};
169 JoyEnableSixAxisDataAccessor enable_sixaxis_data{}; 170 JoyEnableSixAxisDataAccessor enable_sixaxis_data{};
170 ButtonOnlyPollingDataAccessor button_only_data{}; 171 ButtonOnlyPollingDataAccessor button_only_data{};
@@ -172,7 +173,7 @@ protected:
172 u8* transfer_memory{nullptr}; 173 u8* transfer_memory{nullptr};
173 bool is_transfer_memory_set{}; 174 bool is_transfer_memory_set{};
174 175
175 Kernel::KEvent* send_command_asyc_event; 176 Kernel::KEvent* send_command_async_event;
176 KernelHelpers::ServiceContext& service_context; 177 KernelHelpers::ServiceContext& service_context;
177}; 178};
178} // namespace Service::HID 179} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hidbus/ringcon.cpp b/src/core/hle/service/hid/hidbus/ringcon.cpp
index 6fe68081f..5ec3cc83c 100644
--- a/src/core/hle/service/hid/hidbus/ringcon.cpp
+++ b/src/core/hle/service/hid/hidbus/ringcon.cpp
@@ -2,7 +2,7 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include "core/hid/emulated_controller.h" 5#include "core/hid/emulated_devices.h"
6#include "core/hid/hid_core.h" 6#include "core/hid/hid_core.h"
7#include "core/hle/kernel/k_event.h" 7#include "core/hle/kernel/k_event.h"
8#include "core/hle/kernel/k_readable_event.h" 8#include "core/hle/kernel/k_readable_event.h"
@@ -13,9 +13,7 @@ namespace Service::HID {
13RingController::RingController(Core::HID::HIDCore& hid_core_, 13RingController::RingController(Core::HID::HIDCore& hid_core_,
14 KernelHelpers::ServiceContext& service_context_) 14 KernelHelpers::ServiceContext& service_context_)
15 : HidbusBase(service_context_) { 15 : HidbusBase(service_context_) {
16 // Use the horizontal axis of left stick for emulating input 16 input = hid_core_.GetEmulatedDevices();
17 // There is no point on adding a frontend implementation since Ring Fit Adventure doesn't work
18 input = hid_core_.GetEmulatedController(Core::HID::NpadIdType::Player1);
19} 17}
20 18
21RingController::~RingController() = default; 19RingController::~RingController() = default;
@@ -41,6 +39,8 @@ void RingController::OnUpdate() {
41 return; 39 return;
42 } 40 }
43 41
42 // TODO: Increment multitasking counters from motion and sensor data
43
44 switch (polling_mode) { 44 switch (polling_mode) {
45 case JoyPollingMode::SixAxisSensorEnable: { 45 case JoyPollingMode::SixAxisSensorEnable: {
46 enable_sixaxis_data.header.total_entries = 10; 46 enable_sixaxis_data.header.total_entries = 10;
@@ -74,9 +74,8 @@ RingController::RingConData RingController::GetSensorValue() const {
74 .data = 0, 74 .data = 0,
75 }; 75 };
76 76
77 const f32 stick_value = static_cast<f32>(input->GetSticks().left.x) / 32767.0f; 77 const f32 force_value = input->GetRingSensorForce().force * range;
78 78 ringcon_sensor_value.data = static_cast<s16>(force_value) + idle_value;
79 ringcon_sensor_value.data = static_cast<s16>(stick_value * range) + idle_value;
80 79
81 return ringcon_sensor_value; 80 return ringcon_sensor_value;
82} 81}
@@ -105,6 +104,8 @@ std::vector<u8> RingController::GetReply() const {
105 return GetReadRepCountReply(); 104 return GetReadRepCountReply();
106 case RingConCommands::ReadTotalPushCount: 105 case RingConCommands::ReadTotalPushCount:
107 return GetReadTotalPushCountReply(); 106 return GetReadTotalPushCountReply();
107 case RingConCommands::ResetRepCount:
108 return GetResetRepCountReply();
108 case RingConCommands::SaveCalData: 109 case RingConCommands::SaveCalData:
109 return GetSaveDataReply(); 110 return GetSaveDataReply();
110 default: 111 default:
@@ -119,36 +120,9 @@ bool RingController::SetCommand(const std::vector<u8>& data) {
119 return false; 120 return false;
120 } 121 }
121 122
122 // There must be a better way to do this 123 std::memcpy(&command, data.data(), sizeof(RingConCommands));
123 const u32 command_id =
124 u32{data[0]} + (u32{data[1]} << 8) + (u32{data[2]} << 16) + (u32{data[3]} << 24);
125 static constexpr std::array supported_commands = {
126 RingConCommands::GetFirmwareVersion,
127 RingConCommands::ReadId,
128 RingConCommands::c20105,
129 RingConCommands::ReadUnkCal,
130 RingConCommands::ReadFactoryCal,
131 RingConCommands::ReadUserCal,
132 RingConCommands::ReadRepCount,
133 RingConCommands::ReadTotalPushCount,
134 RingConCommands::SaveCalData,
135 };
136
137 for (RingConCommands cmd : supported_commands) {
138 if (command_id == static_cast<u32>(cmd)) {
139 return ExcecuteCommand(cmd, data);
140 }
141 }
142
143 LOG_ERROR(Service_HID, "Command not implemented {}", command_id);
144 command = RingConCommands::Error;
145 // Signal a reply to avoid softlocking
146 send_command_asyc_event->GetWritableEvent().Signal();
147 return false;
148}
149 124
150bool RingController::ExcecuteCommand(RingConCommands cmd, const std::vector<u8>& data) { 125 switch (command) {
151 switch (cmd) {
152 case RingConCommands::GetFirmwareVersion: 126 case RingConCommands::GetFirmwareVersion:
153 case RingConCommands::ReadId: 127 case RingConCommands::ReadId:
154 case RingConCommands::c20105: 128 case RingConCommands::c20105:
@@ -158,23 +132,27 @@ bool RingController::ExcecuteCommand(RingConCommands cmd, const std::vector<u8>&
158 case RingConCommands::ReadRepCount: 132 case RingConCommands::ReadRepCount:
159 case RingConCommands::ReadTotalPushCount: 133 case RingConCommands::ReadTotalPushCount:
160 ASSERT_MSG(data.size() == 0x4, "data.size is not 0x4 bytes"); 134 ASSERT_MSG(data.size() == 0x4, "data.size is not 0x4 bytes");
161 command = cmd; 135 send_command_async_event->GetWritableEvent().Signal();
162 send_command_asyc_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();
163 return true; 141 return true;
164 case RingConCommands::SaveCalData: { 142 case RingConCommands::SaveCalData: {
165 ASSERT_MSG(data.size() == 0x14, "data.size is not 0x14 bytes"); 143 ASSERT_MSG(data.size() == 0x14, "data.size is not 0x14 bytes");
166 144
167 SaveCalData save_info{}; 145 SaveCalData save_info{};
168 std::memcpy(&save_info, &data, sizeof(SaveCalData)); 146 std::memcpy(&save_info, data.data(), sizeof(SaveCalData));
169 user_calibration = save_info.calibration; 147 user_calibration = save_info.calibration;
170 148 send_command_async_event->GetWritableEvent().Signal();
171 command = cmd;
172 send_command_asyc_event->GetWritableEvent().Signal();
173 return true; 149 return true;
174 } 150 }
175 default: 151 default:
176 LOG_ERROR(Service_HID, "Command not implemented {}", cmd); 152 LOG_ERROR(Service_HID, "Command not implemented {}", command);
177 command = RingConCommands::Error; 153 command = RingConCommands::Error;
154 // Signal a reply to avoid softlocking the game
155 send_command_async_event->GetWritableEvent().Signal();
178 return false; 156 return false;
179 } 157 }
180} 158}
@@ -240,27 +218,29 @@ std::vector<u8> RingController::GetReadUserCalReply() const {
240} 218}
241 219
242std::vector<u8> RingController::GetReadRepCountReply() const { 220std::vector<u8> RingController::GetReadRepCountReply() const {
243 // The values are hardcoded from a real joycon
244 const GetThreeByteReply reply{ 221 const GetThreeByteReply reply{
245 .status = DataValid::Valid, 222 .status = DataValid::Valid,
246 .data = {30, 0, 0}, 223 .data = {total_rep_count, 0, 0},
247 .crc = GetCrcValue({30, 0, 0, 0}), 224 .crc = GetCrcValue({total_rep_count, 0, 0, 0}),
248 }; 225 };
249 226
250 return GetDataVector(reply); 227 return GetDataVector(reply);
251} 228}
252 229
253std::vector<u8> RingController::GetReadTotalPushCountReply() const { 230std::vector<u8> RingController::GetReadTotalPushCountReply() const {
254 // The values are hardcoded from a real joycon
255 const GetThreeByteReply reply{ 231 const GetThreeByteReply reply{
256 .status = DataValid::Valid, 232 .status = DataValid::Valid,
257 .data = {30, 0, 0}, 233 .data = {total_push_count, 0, 0},
258 .crc = GetCrcValue({30, 0, 0, 0}), 234 .crc = GetCrcValue({total_push_count, 0, 0, 0}),
259 }; 235 };
260 236
261 return GetDataVector(reply); 237 return GetDataVector(reply);
262} 238}
263 239
240std::vector<u8> RingController::GetResetRepCountReply() const {
241 return GetReadRepCountReply();
242}
243
264std::vector<u8> RingController::GetSaveDataReply() const { 244std::vector<u8> RingController::GetSaveDataReply() const {
265 const StatusReply reply{ 245 const StatusReply reply{
266 .status = DataValid::Valid, 246 .status = DataValid::Valid,
diff --git a/src/core/hle/service/hid/hidbus/ringcon.h b/src/core/hle/service/hid/hidbus/ringcon.h
index e8b3d8254..2dbc6150e 100644
--- a/src/core/hle/service/hid/hidbus/ringcon.h
+++ b/src/core/hle/service/hid/hidbus/ringcon.h
@@ -10,7 +10,7 @@
10#include "core/hle/service/hid/hidbus/hidbus_base.h" 10#include "core/hle/service/hid/hidbus/hidbus_base.h"
11 11
12namespace Core::HID { 12namespace Core::HID {
13class EmulatedController; 13class EmulatedDevices;
14} // namespace Core::HID 14} // namespace Core::HID
15 15
16namespace Service::HID { 16namespace Service::HID {
@@ -43,6 +43,7 @@ private:
43 static constexpr s16 idle_deadzone = 120; 43 static constexpr s16 idle_deadzone = 120;
44 static constexpr s16 range = 2500; 44 static constexpr s16 range = 2500;
45 45
46 // Most missing command names are leftovers from other firmware versions
46 enum class RingConCommands : u32 { 47 enum class RingConCommands : u32 {
47 GetFirmwareVersion = 0x00020000, 48 GetFirmwareVersion = 0x00020000,
48 ReadId = 0x00020100, 49 ReadId = 0x00020100,
@@ -60,10 +61,10 @@ private:
60 ReadUserCal = 0x00021A04, 61 ReadUserCal = 0x00021A04,
61 ReadRepCount = 0x00023104, 62 ReadRepCount = 0x00023104,
62 ReadTotalPushCount = 0x00023204, 63 ReadTotalPushCount = 0x00023204,
63 Unknown9 = 0x04013104, 64 ResetRepCount = 0x04013104,
64 Unknown10 = 0x04011104, 65 Unknown8 = 0x04011104,
65 Unknown11 = 0x04011204, 66 Unknown9 = 0x04011204,
66 Unknown12 = 0x04011304, 67 Unknown10 = 0x04011304,
67 SaveCalData = 0x10011A04, 68 SaveCalData = 0x10011A04,
68 Error = 0xFFFFFFFF, 69 Error = 0xFFFFFFFF,
69 }; 70 };
@@ -180,9 +181,6 @@ private:
180 }; 181 };
181 static_assert(sizeof(RingConData) == 0x8, "RingConData is an invalid size"); 182 static_assert(sizeof(RingConData) == 0x8, "RingConData is an invalid size");
182 183
183 // Executes the command requested
184 bool ExcecuteCommand(RingConCommands cmd, const std::vector<u8>& data);
185
186 // Returns RingConData struct with pressure sensor values 184 // Returns RingConData struct with pressure sensor values
187 RingConData GetSensorValue() const; 185 RingConData GetSensorValue() const;
188 186
@@ -204,12 +202,15 @@ private:
204 // Returns 20 byte reply with user calibration values 202 // Returns 20 byte reply with user calibration values
205 std::vector<u8> GetReadUserCalReply() const; 203 std::vector<u8> GetReadUserCalReply() const;
206 204
207 // (STUBBED) Returns 8 byte reply 205 // Returns 8 byte reply
208 std::vector<u8> GetReadRepCountReply() const; 206 std::vector<u8> GetReadRepCountReply() const;
209 207
210 // (STUBBED) Returns 8 byte reply 208 // Returns 8 byte reply
211 std::vector<u8> GetReadTotalPushCountReply() const; 209 std::vector<u8> GetReadTotalPushCountReply() const;
212 210
211 // Returns 8 byte reply
212 std::vector<u8> GetResetRepCountReply() const;
213
213 // Returns 4 byte save data reply 214 // Returns 4 byte save data reply
214 std::vector<u8> GetSaveDataReply() const; 215 std::vector<u8> GetSaveDataReply() const;
215 216
@@ -225,6 +226,12 @@ private:
225 226
226 RingConCommands command{RingConCommands::Error}; 227 RingConCommands command{RingConCommands::Error};
227 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
228 const u8 device_id = 0x20; 235 const u8 device_id = 0x20;
229 const FirmwareVersion version = { 236 const FirmwareVersion version = {
230 .sub = 0x0, 237 .sub = 0x0,
@@ -242,6 +249,6 @@ private:
242 .zero = {.value = idle_value, .crc = 225}, 249 .zero = {.value = idle_value, .crc = 225},
243 }; 250 };
244 251
245 Core::HID::EmulatedController* input; 252 Core::HID::EmulatedDevices* input;
246}; 253};
247} // namespace Service::HID 254} // 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_ringcon.cpp b/src/yuzu/configuration/configure_ringcon.cpp
new file mode 100644
index 000000000..144e2b83f
--- /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 event->ignore();
309 if (!input_setter || !event) {
310 return;
311 }
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>