summaryrefslogtreecommitdiff
path: root/src/core/hid
diff options
context:
space:
mode:
authorGravatar Feng Chen2021-12-18 13:57:14 +0800
committerGravatar GitHub2021-12-18 13:57:14 +0800
commite49184e6069a9d791d2df3c1958f5c4b1187e124 (patch)
treeb776caf722e0be0e680f67b0ad0842628162ef1c /src/core/hid
parentImplement convert legacy to generic (diff)
parentMerge pull request #7570 from ameerj/favorites-expanded (diff)
downloadyuzu-e49184e6069a9d791d2df3c1958f5c4b1187e124.tar.gz
yuzu-e49184e6069a9d791d2df3c1958f5c4b1187e124.tar.xz
yuzu-e49184e6069a9d791d2df3c1958f5c4b1187e124.zip
Merge branch 'yuzu-emu:master' into convert_legacy
Diffstat (limited to 'src/core/hid')
-rw-r--r--src/core/hid/emulated_console.cpp232
-rw-r--r--src/core/hid/emulated_console.h190
-rw-r--r--src/core/hid/emulated_controller.cpp1139
-rw-r--r--src/core/hid/emulated_controller.h411
-rw-r--r--src/core/hid/emulated_devices.cpp459
-rw-r--r--src/core/hid/emulated_devices.h210
-rw-r--r--src/core/hid/hid_core.cpp214
-rw-r--r--src/core/hid/hid_core.h82
-rw-r--r--src/core/hid/hid_types.h635
-rw-r--r--src/core/hid/input_converter.cpp383
-rw-r--r--src/core/hid/input_converter.h96
-rw-r--r--src/core/hid/input_interpreter.cpp61
-rw-r--r--src/core/hid/input_interpreter.h112
-rw-r--r--src/core/hid/motion_input.cpp280
-rw-r--r--src/core/hid/motion_input.h87
15 files changed, 4591 insertions, 0 deletions
diff --git a/src/core/hid/emulated_console.cpp b/src/core/hid/emulated_console.cpp
new file mode 100644
index 000000000..685ec080c
--- /dev/null
+++ b/src/core/hid/emulated_console.cpp
@@ -0,0 +1,232 @@
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/settings.h"
6#include "core/hid/emulated_console.h"
7#include "core/hid/input_converter.h"
8
9namespace Core::HID {
10EmulatedConsole::EmulatedConsole() = default;
11
12EmulatedConsole::~EmulatedConsole() = default;
13
14void EmulatedConsole::ReloadFromSettings() {
15 // Using first motion device from player 1. No need to assign any unique config at the moment
16 const auto& player = Settings::values.players.GetValue()[0];
17 motion_params = Common::ParamPackage(player.motions[0]);
18
19 ReloadInput();
20}
21
22void EmulatedConsole::SetTouchParams() {
23 // TODO(german77): Support any number of fingers
24 std::size_t index = 0;
25
26 // Hardcode mouse, touchscreen and cemuhook parameters
27 if (!Settings::values.mouse_enabled) {
28 // We can't use mouse as touch if native mouse is enabled
29 touch_params[index++] = Common::ParamPackage{"engine:mouse,axis_x:10,axis_y:11,button:0"};
30 }
31 touch_params[index++] = Common::ParamPackage{"engine:touch,axis_x:0,axis_y:1,button:0"};
32 touch_params[index++] = Common::ParamPackage{"engine:touch,axis_x:2,axis_y:3,button:1"};
33 touch_params[index++] =
34 Common::ParamPackage{"engine:cemuhookudp,axis_x:17,axis_y:18,button:65536"};
35 touch_params[index++] =
36 Common::ParamPackage{"engine:cemuhookudp,axis_x:19,axis_y:20,button:131072"};
37
38 const auto button_index =
39 static_cast<u64>(Settings::values.touch_from_button_map_index.GetValue());
40 const auto& touch_buttons = Settings::values.touch_from_button_maps[button_index].buttons;
41
42 // Map the rest of the fingers from touch from button configuration
43 for (const auto& config_entry : touch_buttons) {
44 if (index >= touch_params.size()) {
45 continue;
46 }
47 Common::ParamPackage params{config_entry};
48 Common::ParamPackage touch_button_params;
49 const int x = params.Get("x", 0);
50 const int y = params.Get("y", 0);
51 params.Erase("x");
52 params.Erase("y");
53 touch_button_params.Set("engine", "touch_from_button");
54 touch_button_params.Set("button", params.Serialize());
55 touch_button_params.Set("x", x);
56 touch_button_params.Set("y", y);
57 touch_button_params.Set("touch_id", static_cast<int>(index));
58 touch_params[index] = touch_button_params;
59 index++;
60 }
61}
62
63void EmulatedConsole::ReloadInput() {
64 // If you load any device here add the equivalent to the UnloadInput() function
65 SetTouchParams();
66
67 motion_devices = Common::Input::CreateDevice<Common::Input::InputDevice>(motion_params);
68 if (motion_devices) {
69 motion_devices->SetCallback({
70 .on_change =
71 [this](const Common::Input::CallbackStatus& callback) { SetMotion(callback); },
72 });
73 }
74
75 // Unique index for identifying touch device source
76 std::size_t index = 0;
77 for (auto& touch_device : touch_devices) {
78 touch_device = Common::Input::CreateDevice<Common::Input::InputDevice>(touch_params[index]);
79 if (!touch_device) {
80 continue;
81 }
82 touch_device->SetCallback({
83 .on_change =
84 [this, index](const Common::Input::CallbackStatus& callback) {
85 SetTouch(callback, index);
86 },
87 });
88 index++;
89 }
90}
91
92void EmulatedConsole::UnloadInput() {
93 motion_devices.reset();
94 for (auto& touch : touch_devices) {
95 touch.reset();
96 }
97}
98
99void EmulatedConsole::EnableConfiguration() {
100 is_configuring = true;
101 SaveCurrentConfig();
102}
103
104void EmulatedConsole::DisableConfiguration() {
105 is_configuring = false;
106}
107
108bool EmulatedConsole::IsConfiguring() const {
109 return is_configuring;
110}
111
112void EmulatedConsole::SaveCurrentConfig() {
113 if (!is_configuring) {
114 return;
115 }
116}
117
118void EmulatedConsole::RestoreConfig() {
119 if (!is_configuring) {
120 return;
121 }
122 ReloadFromSettings();
123}
124
125Common::ParamPackage EmulatedConsole::GetMotionParam() const {
126 return motion_params;
127}
128
129void EmulatedConsole::SetMotionParam(Common::ParamPackage param) {
130 motion_params = param;
131 ReloadInput();
132}
133
134void EmulatedConsole::SetMotion(const Common::Input::CallbackStatus& callback) {
135 std::lock_guard lock{mutex};
136 auto& raw_status = console.motion_values.raw_status;
137 auto& emulated = console.motion_values.emulated;
138
139 raw_status = TransformToMotion(callback);
140 emulated.SetAcceleration(Common::Vec3f{
141 raw_status.accel.x.value,
142 raw_status.accel.y.value,
143 raw_status.accel.z.value,
144 });
145 emulated.SetGyroscope(Common::Vec3f{
146 raw_status.gyro.x.value,
147 raw_status.gyro.y.value,
148 raw_status.gyro.z.value,
149 });
150 emulated.UpdateRotation(raw_status.delta_timestamp);
151 emulated.UpdateOrientation(raw_status.delta_timestamp);
152
153 if (is_configuring) {
154 TriggerOnChange(ConsoleTriggerType::Motion);
155 return;
156 }
157
158 auto& motion = console.motion_state;
159 motion.accel = emulated.GetAcceleration();
160 motion.gyro = emulated.GetGyroscope();
161 motion.rotation = emulated.GetGyroscope();
162 motion.orientation = emulated.GetOrientation();
163 motion.quaternion = emulated.GetQuaternion();
164 motion.is_at_rest = !emulated.IsMoving(motion_sensitivity);
165
166 TriggerOnChange(ConsoleTriggerType::Motion);
167}
168
169void EmulatedConsole::SetTouch(const Common::Input::CallbackStatus& callback, std::size_t index) {
170 if (index >= console.touch_values.size()) {
171 return;
172 }
173 std::lock_guard lock{mutex};
174
175 console.touch_values[index] = TransformToTouch(callback);
176
177 if (is_configuring) {
178 TriggerOnChange(ConsoleTriggerType::Touch);
179 return;
180 }
181
182 // TODO(german77): Remap touch id in sequential order
183 console.touch_state[index] = {
184 .position = {console.touch_values[index].x.value, console.touch_values[index].y.value},
185 .id = static_cast<u32>(console.touch_values[index].id),
186 .pressed = console.touch_values[index].pressed.value,
187 };
188
189 TriggerOnChange(ConsoleTriggerType::Touch);
190}
191
192ConsoleMotionValues EmulatedConsole::GetMotionValues() const {
193 return console.motion_values;
194}
195
196TouchValues EmulatedConsole::GetTouchValues() const {
197 return console.touch_values;
198}
199
200ConsoleMotion EmulatedConsole::GetMotion() const {
201 return console.motion_state;
202}
203
204TouchFingerState EmulatedConsole::GetTouch() const {
205 return console.touch_state;
206}
207
208void EmulatedConsole::TriggerOnChange(ConsoleTriggerType type) {
209 for (const auto& poller_pair : callback_list) {
210 const ConsoleUpdateCallback& poller = poller_pair.second;
211 if (poller.on_change) {
212 poller.on_change(type);
213 }
214 }
215}
216
217int EmulatedConsole::SetCallback(ConsoleUpdateCallback update_callback) {
218 std::lock_guard lock{mutex};
219 callback_list.insert_or_assign(last_callback_key, update_callback);
220 return last_callback_key++;
221}
222
223void EmulatedConsole::DeleteCallback(int key) {
224 std::lock_guard lock{mutex};
225 const auto& iterator = callback_list.find(key);
226 if (iterator == callback_list.end()) {
227 LOG_ERROR(Input, "Tried to delete non-existent callback {}", key);
228 return;
229 }
230 callback_list.erase(iterator);
231}
232} // namespace Core::HID
diff --git a/src/core/hid/emulated_console.h b/src/core/hid/emulated_console.h
new file mode 100644
index 000000000..3afd284d5
--- /dev/null
+++ b/src/core/hid/emulated_console.h
@@ -0,0 +1,190 @@
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 <functional>
9#include <memory>
10#include <mutex>
11#include <unordered_map>
12
13#include "common/common_types.h"
14#include "common/input.h"
15#include "common/param_package.h"
16#include "common/point.h"
17#include "common/quaternion.h"
18#include "common/vector_math.h"
19#include "core/hid/hid_types.h"
20#include "core/hid/motion_input.h"
21
22namespace Core::HID {
23
24struct ConsoleMotionInfo {
25 Common::Input::MotionStatus raw_status{};
26 MotionInput emulated{};
27};
28
29using ConsoleMotionDevices = std::unique_ptr<Common::Input::InputDevice>;
30using TouchDevices = std::array<std::unique_ptr<Common::Input::InputDevice>, 16>;
31
32using ConsoleMotionParams = Common::ParamPackage;
33using TouchParams = std::array<Common::ParamPackage, 16>;
34
35using ConsoleMotionValues = ConsoleMotionInfo;
36using TouchValues = std::array<Common::Input::TouchStatus, 16>;
37
38struct TouchFinger {
39 u64 last_touch{};
40 Common::Point<float> position{};
41 u32 id{};
42 TouchAttribute attribute{};
43 bool pressed{};
44};
45
46// Contains all motion related data that is used on the services
47struct ConsoleMotion {
48 Common::Vec3f accel{};
49 Common::Vec3f gyro{};
50 Common::Vec3f rotation{};
51 std::array<Common::Vec3f, 3> orientation{};
52 Common::Quaternion<f32> quaternion{};
53 bool is_at_rest{};
54};
55
56using TouchFingerState = std::array<TouchFinger, 16>;
57
58struct ConsoleStatus {
59 // Data from input_common
60 ConsoleMotionValues motion_values{};
61 TouchValues touch_values{};
62
63 // Data for HID services
64 ConsoleMotion motion_state{};
65 TouchFingerState touch_state{};
66};
67
68enum class ConsoleTriggerType {
69 Motion,
70 Touch,
71 All,
72};
73
74struct ConsoleUpdateCallback {
75 std::function<void(ConsoleTriggerType)> on_change;
76};
77
78class EmulatedConsole {
79public:
80 /**
81 * Contains all input data within the emulated switch console tablet such as touch and motion
82 */
83 explicit EmulatedConsole();
84 ~EmulatedConsole();
85
86 YUZU_NON_COPYABLE(EmulatedConsole);
87 YUZU_NON_MOVEABLE(EmulatedConsole);
88
89 /// Removes all callbacks created from input devices
90 void UnloadInput();
91
92 /**
93 * Sets the emulated console into configuring mode
94 * This prevents the modification of the HID state of the emulated console by input commands
95 */
96 void EnableConfiguration();
97
98 /// Returns the emulated console into normal mode, allowing the modification of the HID state
99 void DisableConfiguration();
100
101 /// Returns true if the emulated console is in configuring mode
102 bool IsConfiguring() const;
103
104 /// Reload all input devices
105 void ReloadInput();
106
107 /// Overrides current mapped devices with the stored configuration and reloads all input devices
108 void ReloadFromSettings();
109
110 /// Saves the current mapped configuration
111 void SaveCurrentConfig();
112
113 /// Reverts any mapped changes made that weren't saved
114 void RestoreConfig();
115
116 // Returns the current mapped motion device
117 Common::ParamPackage GetMotionParam() const;
118
119 /**
120 * Updates the current mapped motion device
121 * @param param ParamPackage with controller data to be mapped
122 */
123 void SetMotionParam(Common::ParamPackage param);
124
125 /// Returns the latest status of motion input from the console with parameters
126 ConsoleMotionValues GetMotionValues() const;
127
128 /// Returns the latest status of touch input from the console with parameters
129 TouchValues GetTouchValues() const;
130
131 /// Returns the latest status of motion input from the console
132 ConsoleMotion GetMotion() const;
133
134 /// Returns the latest status of touch input from the console
135 TouchFingerState GetTouch() const;
136
137 /**
138 * Adds a callback to the list of events
139 * @param update_callback A ConsoleUpdateCallback that will be triggered
140 * @return an unique key corresponding to the callback index in the list
141 */
142 int SetCallback(ConsoleUpdateCallback update_callback);
143
144 /**
145 * Removes a callback from the list stopping any future events to this object
146 * @param key Key corresponding to the callback index in the list
147 */
148 void DeleteCallback(int key);
149
150private:
151 /// Creates and stores the touch params
152 void SetTouchParams();
153
154 /**
155 * Updates the motion status of the console
156 * @param callback A CallbackStatus containing gyro and accelerometer data
157 */
158 void SetMotion(const Common::Input::CallbackStatus& callback);
159
160 /**
161 * Updates the touch status of the console
162 * @param callback A CallbackStatus containing the touch position
163 * @param index Finger ID to be updated
164 */
165 void SetTouch(const Common::Input::CallbackStatus& callback, std::size_t index);
166
167 /**
168 * Triggers a callback that something has changed on the console status
169 * @param type Input type of the event to trigger
170 */
171 void TriggerOnChange(ConsoleTriggerType type);
172
173 bool is_configuring{false};
174 f32 motion_sensitivity{0.01f};
175
176 ConsoleMotionParams motion_params;
177 TouchParams touch_params;
178
179 ConsoleMotionDevices motion_devices;
180 TouchDevices touch_devices;
181
182 mutable std::mutex mutex;
183 std::unordered_map<int, ConsoleUpdateCallback> callback_list;
184 int last_callback_key = 0;
185
186 // Stores the current status of all console input
187 ConsoleStatus console;
188};
189
190} // namespace Core::HID
diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp
new file mode 100644
index 000000000..93372445b
--- /dev/null
+++ b/src/core/hid/emulated_controller.cpp
@@ -0,0 +1,1139 @@
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/input_converter.h"
7
8namespace Core::HID {
9constexpr s32 HID_JOYSTICK_MAX = 0x7fff;
10constexpr s32 HID_TRIGGER_MAX = 0x7fff;
11
12EmulatedController::EmulatedController(NpadIdType npad_id_type_) : npad_id_type(npad_id_type_) {}
13
14EmulatedController::~EmulatedController() = default;
15
16NpadStyleIndex EmulatedController::MapSettingsTypeToNPad(Settings::ControllerType type) {
17 switch (type) {
18 case Settings::ControllerType::ProController:
19 return NpadStyleIndex::ProController;
20 case Settings::ControllerType::DualJoyconDetached:
21 return NpadStyleIndex::JoyconDual;
22 case Settings::ControllerType::LeftJoycon:
23 return NpadStyleIndex::JoyconLeft;
24 case Settings::ControllerType::RightJoycon:
25 return NpadStyleIndex::JoyconRight;
26 case Settings::ControllerType::Handheld:
27 return NpadStyleIndex::Handheld;
28 case Settings::ControllerType::GameCube:
29 return NpadStyleIndex::GameCube;
30 case Settings::ControllerType::Pokeball:
31 return NpadStyleIndex::Pokeball;
32 case Settings::ControllerType::NES:
33 return NpadStyleIndex::NES;
34 case Settings::ControllerType::SNES:
35 return NpadStyleIndex::SNES;
36 case Settings::ControllerType::N64:
37 return NpadStyleIndex::N64;
38 case Settings::ControllerType::SegaGenesis:
39 return NpadStyleIndex::SegaGenesis;
40 default:
41 return NpadStyleIndex::ProController;
42 }
43}
44
45Settings::ControllerType EmulatedController::MapNPadToSettingsType(NpadStyleIndex type) {
46 switch (type) {
47 case NpadStyleIndex::ProController:
48 return Settings::ControllerType::ProController;
49 case NpadStyleIndex::JoyconDual:
50 return Settings::ControllerType::DualJoyconDetached;
51 case NpadStyleIndex::JoyconLeft:
52 return Settings::ControllerType::LeftJoycon;
53 case NpadStyleIndex::JoyconRight:
54 return Settings::ControllerType::RightJoycon;
55 case NpadStyleIndex::Handheld:
56 return Settings::ControllerType::Handheld;
57 case NpadStyleIndex::GameCube:
58 return Settings::ControllerType::GameCube;
59 case NpadStyleIndex::Pokeball:
60 return Settings::ControllerType::Pokeball;
61 case NpadStyleIndex::NES:
62 return Settings::ControllerType::NES;
63 case NpadStyleIndex::SNES:
64 return Settings::ControllerType::SNES;
65 case NpadStyleIndex::N64:
66 return Settings::ControllerType::N64;
67 case NpadStyleIndex::SegaGenesis:
68 return Settings::ControllerType::SegaGenesis;
69 default:
70 return Settings::ControllerType::ProController;
71 }
72}
73
74void EmulatedController::ReloadFromSettings() {
75 const auto player_index = NpadIdTypeToIndex(npad_id_type);
76 const auto& player = Settings::values.players.GetValue()[player_index];
77
78 for (std::size_t index = 0; index < player.buttons.size(); ++index) {
79 button_params[index] = Common::ParamPackage(player.buttons[index]);
80 }
81 for (std::size_t index = 0; index < player.analogs.size(); ++index) {
82 stick_params[index] = Common::ParamPackage(player.analogs[index]);
83 }
84 for (std::size_t index = 0; index < player.motions.size(); ++index) {
85 motion_params[index] = Common::ParamPackage(player.motions[index]);
86 }
87
88 controller.colors_state.left = {
89 .body = player.body_color_left,
90 .button = player.button_color_left,
91 };
92
93 controller.colors_state.right = {
94 .body = player.body_color_right,
95 .button = player.button_color_right,
96 };
97
98 controller.colors_state.fullkey = controller.colors_state.left;
99
100 // Other or debug controller should always be a pro controller
101 if (npad_id_type != NpadIdType::Other) {
102 SetNpadStyleIndex(MapSettingsTypeToNPad(player.controller_type));
103 } else {
104 SetNpadStyleIndex(NpadStyleIndex::ProController);
105 }
106
107 if (player.connected) {
108 Connect();
109 } else {
110 Disconnect();
111 }
112
113 ReloadInput();
114}
115
116void EmulatedController::LoadDevices() {
117 // TODO(german77): Use more buttons to detect the correct device
118 const auto left_joycon = button_params[Settings::NativeButton::DRight];
119 const auto right_joycon = button_params[Settings::NativeButton::A];
120
121 // Triggers for GC controllers
122 trigger_params[LeftIndex] = button_params[Settings::NativeButton::ZL];
123 trigger_params[RightIndex] = button_params[Settings::NativeButton::ZR];
124
125 battery_params[LeftIndex] = left_joycon;
126 battery_params[RightIndex] = right_joycon;
127 battery_params[LeftIndex].Set("battery", true);
128 battery_params[RightIndex].Set("battery", true);
129
130 output_params[LeftIndex] = left_joycon;
131 output_params[RightIndex] = right_joycon;
132 output_params[LeftIndex].Set("output", true);
133 output_params[RightIndex].Set("output", true);
134
135 LoadTASParams();
136
137 std::transform(button_params.begin() + Settings::NativeButton::BUTTON_HID_BEGIN,
138 button_params.begin() + Settings::NativeButton::BUTTON_NS_END,
139 button_devices.begin(), Common::Input::CreateDevice<Common::Input::InputDevice>);
140 std::transform(stick_params.begin() + Settings::NativeAnalog::STICK_HID_BEGIN,
141 stick_params.begin() + Settings::NativeAnalog::STICK_HID_END,
142 stick_devices.begin(), Common::Input::CreateDevice<Common::Input::InputDevice>);
143 std::transform(motion_params.begin() + Settings::NativeMotion::MOTION_HID_BEGIN,
144 motion_params.begin() + Settings::NativeMotion::MOTION_HID_END,
145 motion_devices.begin(), Common::Input::CreateDevice<Common::Input::InputDevice>);
146 std::transform(trigger_params.begin(), trigger_params.end(), trigger_devices.begin(),
147 Common::Input::CreateDevice<Common::Input::InputDevice>);
148 std::transform(battery_params.begin(), battery_params.begin(), battery_devices.end(),
149 Common::Input::CreateDevice<Common::Input::InputDevice>);
150 std::transform(output_params.begin(), output_params.end(), output_devices.begin(),
151 Common::Input::CreateDevice<Common::Input::OutputDevice>);
152
153 // Initialize TAS devices
154 std::transform(tas_button_params.begin(), tas_button_params.end(), tas_button_devices.begin(),
155 Common::Input::CreateDevice<Common::Input::InputDevice>);
156 std::transform(tas_stick_params.begin(), tas_stick_params.end(), tas_stick_devices.begin(),
157 Common::Input::CreateDevice<Common::Input::InputDevice>);
158}
159
160void EmulatedController::LoadTASParams() {
161 const auto player_index = NpadIdTypeToIndex(npad_id_type);
162 Common::ParamPackage common_params{};
163 common_params.Set("engine", "tas");
164 common_params.Set("port", static_cast<int>(player_index));
165 for (auto& param : tas_button_params) {
166 param = common_params;
167 }
168 for (auto& param : tas_stick_params) {
169 param = common_params;
170 }
171
172 // TODO(german77): Replace this with an input profile or something better
173 tas_button_params[Settings::NativeButton::A].Set("button", 0);
174 tas_button_params[Settings::NativeButton::B].Set("button", 1);
175 tas_button_params[Settings::NativeButton::X].Set("button", 2);
176 tas_button_params[Settings::NativeButton::Y].Set("button", 3);
177 tas_button_params[Settings::NativeButton::LStick].Set("button", 4);
178 tas_button_params[Settings::NativeButton::RStick].Set("button", 5);
179 tas_button_params[Settings::NativeButton::L].Set("button", 6);
180 tas_button_params[Settings::NativeButton::R].Set("button", 7);
181 tas_button_params[Settings::NativeButton::ZL].Set("button", 8);
182 tas_button_params[Settings::NativeButton::ZR].Set("button", 9);
183 tas_button_params[Settings::NativeButton::Plus].Set("button", 10);
184 tas_button_params[Settings::NativeButton::Minus].Set("button", 11);
185 tas_button_params[Settings::NativeButton::DLeft].Set("button", 12);
186 tas_button_params[Settings::NativeButton::DUp].Set("button", 13);
187 tas_button_params[Settings::NativeButton::DRight].Set("button", 14);
188 tas_button_params[Settings::NativeButton::DDown].Set("button", 15);
189 tas_button_params[Settings::NativeButton::SL].Set("button", 16);
190 tas_button_params[Settings::NativeButton::SR].Set("button", 17);
191 tas_button_params[Settings::NativeButton::Home].Set("button", 18);
192 tas_button_params[Settings::NativeButton::Screenshot].Set("button", 19);
193
194 tas_stick_params[Settings::NativeAnalog::LStick].Set("axis_x", 0);
195 tas_stick_params[Settings::NativeAnalog::LStick].Set("axis_y", 1);
196 tas_stick_params[Settings::NativeAnalog::RStick].Set("axis_x", 2);
197 tas_stick_params[Settings::NativeAnalog::RStick].Set("axis_y", 3);
198}
199
200void EmulatedController::ReloadInput() {
201 // If you load any device here add the equivalent to the UnloadInput() function
202 LoadDevices();
203 for (std::size_t index = 0; index < button_devices.size(); ++index) {
204 if (!button_devices[index]) {
205 continue;
206 }
207 const auto uuid = Common::UUID{button_params[index].Get("guid", "")};
208 button_devices[index]->SetCallback({
209 .on_change =
210 [this, index, uuid](const Common::Input::CallbackStatus& callback) {
211 SetButton(callback, index, uuid);
212 },
213 });
214 button_devices[index]->ForceUpdate();
215 }
216
217 for (std::size_t index = 0; index < stick_devices.size(); ++index) {
218 if (!stick_devices[index]) {
219 continue;
220 }
221 const auto uuid = Common::UUID{stick_params[index].Get("guid", "")};
222 stick_devices[index]->SetCallback({
223 .on_change =
224 [this, index, uuid](const Common::Input::CallbackStatus& callback) {
225 SetStick(callback, index, uuid);
226 },
227 });
228 stick_devices[index]->ForceUpdate();
229 }
230
231 for (std::size_t index = 0; index < trigger_devices.size(); ++index) {
232 if (!trigger_devices[index]) {
233 continue;
234 }
235 const auto uuid = Common::UUID{trigger_params[index].Get("guid", "")};
236 trigger_devices[index]->SetCallback({
237 .on_change =
238 [this, index, uuid](const Common::Input::CallbackStatus& callback) {
239 SetTrigger(callback, index, uuid);
240 },
241 });
242 trigger_devices[index]->ForceUpdate();
243 }
244
245 for (std::size_t index = 0; index < battery_devices.size(); ++index) {
246 if (!battery_devices[index]) {
247 continue;
248 }
249 battery_devices[index]->SetCallback({
250 .on_change =
251 [this, index](const Common::Input::CallbackStatus& callback) {
252 SetBattery(callback, index);
253 },
254 });
255 battery_devices[index]->ForceUpdate();
256 }
257
258 for (std::size_t index = 0; index < motion_devices.size(); ++index) {
259 if (!motion_devices[index]) {
260 continue;
261 }
262 motion_devices[index]->SetCallback({
263 .on_change =
264 [this, index](const Common::Input::CallbackStatus& callback) {
265 SetMotion(callback, index);
266 },
267 });
268 motion_devices[index]->ForceUpdate();
269 }
270
271 // Use a common UUID for TAS
272 const auto tas_uuid = Common::UUID{0x0, 0x7A5};
273
274 // Register TAS devices. No need to force update
275 for (std::size_t index = 0; index < tas_button_devices.size(); ++index) {
276 if (!tas_button_devices[index]) {
277 continue;
278 }
279 tas_button_devices[index]->SetCallback({
280 .on_change =
281 [this, index, tas_uuid](const Common::Input::CallbackStatus& callback) {
282 SetButton(callback, index, tas_uuid);
283 },
284 });
285 }
286
287 for (std::size_t index = 0; index < tas_stick_devices.size(); ++index) {
288 if (!tas_stick_devices[index]) {
289 continue;
290 }
291 tas_stick_devices[index]->SetCallback({
292 .on_change =
293 [this, index, tas_uuid](const Common::Input::CallbackStatus& callback) {
294 SetStick(callback, index, tas_uuid);
295 },
296 });
297 }
298}
299
300void EmulatedController::UnloadInput() {
301 for (auto& button : button_devices) {
302 button.reset();
303 }
304 for (auto& stick : stick_devices) {
305 stick.reset();
306 }
307 for (auto& motion : motion_devices) {
308 motion.reset();
309 }
310 for (auto& trigger : trigger_devices) {
311 trigger.reset();
312 }
313 for (auto& battery : battery_devices) {
314 battery.reset();
315 }
316 for (auto& output : output_devices) {
317 output.reset();
318 }
319 for (auto& button : tas_button_devices) {
320 button.reset();
321 }
322 for (auto& stick : tas_stick_devices) {
323 stick.reset();
324 }
325}
326
327void EmulatedController::EnableConfiguration() {
328 is_configuring = true;
329 tmp_is_connected = is_connected;
330 tmp_npad_type = npad_type;
331}
332
333void EmulatedController::DisableConfiguration() {
334 is_configuring = false;
335
336 // Apply temporary npad type to the real controller
337 if (tmp_npad_type != npad_type) {
338 if (is_connected) {
339 Disconnect();
340 }
341 SetNpadStyleIndex(tmp_npad_type);
342 }
343
344 // Apply temporary connected status to the real controller
345 if (tmp_is_connected != is_connected) {
346 if (tmp_is_connected) {
347 Connect();
348 return;
349 }
350 Disconnect();
351 }
352}
353
354bool EmulatedController::IsConfiguring() const {
355 return is_configuring;
356}
357
358void EmulatedController::SaveCurrentConfig() {
359 const auto player_index = NpadIdTypeToIndex(npad_id_type);
360 auto& player = Settings::values.players.GetValue()[player_index];
361 player.connected = is_connected;
362 player.controller_type = MapNPadToSettingsType(npad_type);
363 for (std::size_t index = 0; index < player.buttons.size(); ++index) {
364 player.buttons[index] = button_params[index].Serialize();
365 }
366 for (std::size_t index = 0; index < player.analogs.size(); ++index) {
367 player.analogs[index] = stick_params[index].Serialize();
368 }
369 for (std::size_t index = 0; index < player.motions.size(); ++index) {
370 player.motions[index] = motion_params[index].Serialize();
371 }
372}
373
374void EmulatedController::RestoreConfig() {
375 if (!is_configuring) {
376 return;
377 }
378 ReloadFromSettings();
379}
380
381std::vector<Common::ParamPackage> EmulatedController::GetMappedDevices(
382 EmulatedDeviceIndex device_index) const {
383 std::vector<Common::ParamPackage> devices;
384 for (const auto& param : button_params) {
385 if (!param.Has("engine")) {
386 continue;
387 }
388 const auto devices_it = std::find_if(
389 devices.begin(), devices.end(), [param](const Common::ParamPackage param_) {
390 return param.Get("engine", "") == param_.Get("engine", "") &&
391 param.Get("guid", "") == param_.Get("guid", "") &&
392 param.Get("port", 0) == param_.Get("port", 0);
393 });
394 if (devices_it != devices.end()) {
395 continue;
396 }
397 Common::ParamPackage device{};
398 device.Set("engine", param.Get("engine", ""));
399 device.Set("guid", param.Get("guid", ""));
400 device.Set("port", param.Get("port", 0));
401 devices.push_back(device);
402 }
403
404 for (const auto& param : stick_params) {
405 if (!param.Has("engine")) {
406 continue;
407 }
408 if (param.Get("engine", "") == "analog_from_button") {
409 continue;
410 }
411 const auto devices_it = std::find_if(
412 devices.begin(), devices.end(), [param](const Common::ParamPackage param_) {
413 return param.Get("engine", "") == param_.Get("engine", "") &&
414 param.Get("guid", "") == param_.Get("guid", "") &&
415 param.Get("port", 0) == param_.Get("port", 0);
416 });
417 if (devices_it != devices.end()) {
418 continue;
419 }
420 Common::ParamPackage device{};
421 device.Set("engine", param.Get("engine", ""));
422 device.Set("guid", param.Get("guid", ""));
423 device.Set("port", param.Get("port", 0));
424 devices.push_back(device);
425 }
426 return devices;
427}
428
429Common::ParamPackage EmulatedController::GetButtonParam(std::size_t index) const {
430 if (index >= button_params.size()) {
431 return {};
432 }
433 return button_params[index];
434}
435
436Common::ParamPackage EmulatedController::GetStickParam(std::size_t index) const {
437 if (index >= stick_params.size()) {
438 return {};
439 }
440 return stick_params[index];
441}
442
443Common::ParamPackage EmulatedController::GetMotionParam(std::size_t index) const {
444 if (index >= motion_params.size()) {
445 return {};
446 }
447 return motion_params[index];
448}
449
450void EmulatedController::SetButtonParam(std::size_t index, Common::ParamPackage param) {
451 if (index >= button_params.size()) {
452 return;
453 }
454 button_params[index] = std::move(param);
455 ReloadInput();
456}
457
458void EmulatedController::SetStickParam(std::size_t index, Common::ParamPackage param) {
459 if (index >= stick_params.size()) {
460 return;
461 }
462 stick_params[index] = std::move(param);
463 ReloadInput();
464}
465
466void EmulatedController::SetMotionParam(std::size_t index, Common::ParamPackage param) {
467 if (index >= motion_params.size()) {
468 return;
469 }
470 motion_params[index] = std::move(param);
471 ReloadInput();
472}
473
474void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback, std::size_t index,
475 Common::UUID uuid) {
476 if (index >= controller.button_values.size()) {
477 return;
478 }
479 {
480 std::lock_guard lock{mutex};
481 bool value_changed = false;
482 const auto new_status = TransformToButton(callback);
483 auto& current_status = controller.button_values[index];
484
485 // Only read button values that have the same uuid or are pressed once
486 if (current_status.uuid != uuid) {
487 if (!new_status.value) {
488 return;
489 }
490 }
491
492 current_status.toggle = new_status.toggle;
493 current_status.uuid = uuid;
494
495 // Update button status with current
496 if (!current_status.toggle) {
497 current_status.locked = false;
498 if (current_status.value != new_status.value) {
499 current_status.value = new_status.value;
500 value_changed = true;
501 }
502 } else {
503 // Toggle button and lock status
504 if (new_status.value && !current_status.locked) {
505 current_status.locked = true;
506 current_status.value = !current_status.value;
507 value_changed = true;
508 }
509
510 // Unlock button ready for next press
511 if (!new_status.value && current_status.locked) {
512 current_status.locked = false;
513 }
514 }
515
516 if (!value_changed) {
517 return;
518 }
519
520 if (is_configuring) {
521 controller.npad_button_state.raw = NpadButton::None;
522 controller.debug_pad_button_state.raw = 0;
523 TriggerOnChange(ControllerTriggerType::Button, false);
524 return;
525 }
526
527 switch (index) {
528 case Settings::NativeButton::A:
529 controller.npad_button_state.a.Assign(current_status.value);
530 controller.debug_pad_button_state.a.Assign(current_status.value);
531 break;
532 case Settings::NativeButton::B:
533 controller.npad_button_state.b.Assign(current_status.value);
534 controller.debug_pad_button_state.b.Assign(current_status.value);
535 break;
536 case Settings::NativeButton::X:
537 controller.npad_button_state.x.Assign(current_status.value);
538 controller.debug_pad_button_state.x.Assign(current_status.value);
539 break;
540 case Settings::NativeButton::Y:
541 controller.npad_button_state.y.Assign(current_status.value);
542 controller.debug_pad_button_state.y.Assign(current_status.value);
543 break;
544 case Settings::NativeButton::LStick:
545 controller.npad_button_state.stick_l.Assign(current_status.value);
546 break;
547 case Settings::NativeButton::RStick:
548 controller.npad_button_state.stick_r.Assign(current_status.value);
549 break;
550 case Settings::NativeButton::L:
551 controller.npad_button_state.l.Assign(current_status.value);
552 controller.debug_pad_button_state.l.Assign(current_status.value);
553 break;
554 case Settings::NativeButton::R:
555 controller.npad_button_state.r.Assign(current_status.value);
556 controller.debug_pad_button_state.r.Assign(current_status.value);
557 break;
558 case Settings::NativeButton::ZL:
559 controller.npad_button_state.zl.Assign(current_status.value);
560 controller.debug_pad_button_state.zl.Assign(current_status.value);
561 break;
562 case Settings::NativeButton::ZR:
563 controller.npad_button_state.zr.Assign(current_status.value);
564 controller.debug_pad_button_state.zr.Assign(current_status.value);
565 break;
566 case Settings::NativeButton::Plus:
567 controller.npad_button_state.plus.Assign(current_status.value);
568 controller.debug_pad_button_state.plus.Assign(current_status.value);
569 break;
570 case Settings::NativeButton::Minus:
571 controller.npad_button_state.minus.Assign(current_status.value);
572 controller.debug_pad_button_state.minus.Assign(current_status.value);
573 break;
574 case Settings::NativeButton::DLeft:
575 controller.npad_button_state.left.Assign(current_status.value);
576 controller.debug_pad_button_state.d_left.Assign(current_status.value);
577 break;
578 case Settings::NativeButton::DUp:
579 controller.npad_button_state.up.Assign(current_status.value);
580 controller.debug_pad_button_state.d_up.Assign(current_status.value);
581 break;
582 case Settings::NativeButton::DRight:
583 controller.npad_button_state.right.Assign(current_status.value);
584 controller.debug_pad_button_state.d_right.Assign(current_status.value);
585 break;
586 case Settings::NativeButton::DDown:
587 controller.npad_button_state.down.Assign(current_status.value);
588 controller.debug_pad_button_state.d_down.Assign(current_status.value);
589 break;
590 case Settings::NativeButton::SL:
591 controller.npad_button_state.left_sl.Assign(current_status.value);
592 controller.npad_button_state.right_sl.Assign(current_status.value);
593 break;
594 case Settings::NativeButton::SR:
595 controller.npad_button_state.left_sr.Assign(current_status.value);
596 controller.npad_button_state.right_sr.Assign(current_status.value);
597 break;
598 case Settings::NativeButton::Home:
599 case Settings::NativeButton::Screenshot:
600 break;
601 }
602 }
603 if (!is_connected) {
604 if (npad_id_type == NpadIdType::Player1 && npad_type != NpadStyleIndex::Handheld) {
605 Connect();
606 }
607 if (npad_id_type == NpadIdType::Handheld && npad_type == NpadStyleIndex::Handheld) {
608 Connect();
609 }
610 }
611 TriggerOnChange(ControllerTriggerType::Button, true);
612}
613
614void EmulatedController::SetStick(const Common::Input::CallbackStatus& callback, std::size_t index,
615 Common::UUID uuid) {
616 if (index >= controller.stick_values.size()) {
617 return;
618 }
619 std::lock_guard lock{mutex};
620 const auto stick_value = TransformToStick(callback);
621
622 // Only read stick values that have the same uuid or are over the threshold to avoid flapping
623 if (controller.stick_values[index].uuid != uuid) {
624 if (!stick_value.down && !stick_value.up && !stick_value.left && !stick_value.right) {
625 return;
626 }
627 }
628
629 controller.stick_values[index] = stick_value;
630 controller.stick_values[index].uuid = uuid;
631
632 if (is_configuring) {
633 controller.analog_stick_state.left = {};
634 controller.analog_stick_state.right = {};
635 TriggerOnChange(ControllerTriggerType::Stick, false);
636 return;
637 }
638
639 const AnalogStickState stick{
640 .x = static_cast<s32>(controller.stick_values[index].x.value * HID_JOYSTICK_MAX),
641 .y = static_cast<s32>(controller.stick_values[index].y.value * HID_JOYSTICK_MAX),
642 };
643
644 switch (index) {
645 case Settings::NativeAnalog::LStick:
646 controller.analog_stick_state.left = stick;
647 controller.npad_button_state.stick_l_left.Assign(controller.stick_values[index].left);
648 controller.npad_button_state.stick_l_up.Assign(controller.stick_values[index].up);
649 controller.npad_button_state.stick_l_right.Assign(controller.stick_values[index].right);
650 controller.npad_button_state.stick_l_down.Assign(controller.stick_values[index].down);
651 break;
652 case Settings::NativeAnalog::RStick:
653 controller.analog_stick_state.right = stick;
654 controller.npad_button_state.stick_r_left.Assign(controller.stick_values[index].left);
655 controller.npad_button_state.stick_r_up.Assign(controller.stick_values[index].up);
656 controller.npad_button_state.stick_r_right.Assign(controller.stick_values[index].right);
657 controller.npad_button_state.stick_r_down.Assign(controller.stick_values[index].down);
658 break;
659 }
660
661 TriggerOnChange(ControllerTriggerType::Stick, true);
662}
663
664void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callback,
665 std::size_t index, Common::UUID uuid) {
666 if (index >= controller.trigger_values.size()) {
667 return;
668 }
669 std::lock_guard lock{mutex};
670 const auto trigger_value = TransformToTrigger(callback);
671
672 // Only read trigger values that have the same uuid or are pressed once
673 if (controller.trigger_values[index].uuid != uuid) {
674 if (!trigger_value.pressed.value) {
675 return;
676 }
677 }
678
679 controller.trigger_values[index] = trigger_value;
680 controller.trigger_values[index].uuid = uuid;
681
682 if (is_configuring) {
683 controller.gc_trigger_state.left = 0;
684 controller.gc_trigger_state.right = 0;
685 TriggerOnChange(ControllerTriggerType::Trigger, false);
686 return;
687 }
688
689 const auto& trigger = controller.trigger_values[index];
690
691 switch (index) {
692 case Settings::NativeTrigger::LTrigger:
693 controller.gc_trigger_state.left = static_cast<s32>(trigger.analog.value * HID_TRIGGER_MAX);
694 controller.npad_button_state.zl.Assign(trigger.pressed.value);
695 break;
696 case Settings::NativeTrigger::RTrigger:
697 controller.gc_trigger_state.right =
698 static_cast<s32>(trigger.analog.value * HID_TRIGGER_MAX);
699 controller.npad_button_state.zr.Assign(trigger.pressed.value);
700 break;
701 }
702
703 TriggerOnChange(ControllerTriggerType::Trigger, true);
704}
705
706void EmulatedController::SetMotion(const Common::Input::CallbackStatus& callback,
707 std::size_t index) {
708 if (index >= controller.motion_values.size()) {
709 return;
710 }
711 std::lock_guard lock{mutex};
712 auto& raw_status = controller.motion_values[index].raw_status;
713 auto& emulated = controller.motion_values[index].emulated;
714
715 raw_status = TransformToMotion(callback);
716 emulated.SetAcceleration(Common::Vec3f{
717 raw_status.accel.x.value,
718 raw_status.accel.y.value,
719 raw_status.accel.z.value,
720 });
721 emulated.SetGyroscope(Common::Vec3f{
722 raw_status.gyro.x.value,
723 raw_status.gyro.y.value,
724 raw_status.gyro.z.value,
725 });
726 emulated.UpdateRotation(raw_status.delta_timestamp);
727 emulated.UpdateOrientation(raw_status.delta_timestamp);
728 force_update_motion = raw_status.force_update;
729
730 if (is_configuring) {
731 TriggerOnChange(ControllerTriggerType::Motion, false);
732 return;
733 }
734
735 auto& motion = controller.motion_state[index];
736 motion.accel = emulated.GetAcceleration();
737 motion.gyro = emulated.GetGyroscope();
738 motion.rotation = emulated.GetRotations();
739 motion.orientation = emulated.GetOrientation();
740 motion.is_at_rest = !emulated.IsMoving(motion_sensitivity);
741
742 TriggerOnChange(ControllerTriggerType::Motion, true);
743}
744
745void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callback,
746 std::size_t index) {
747 if (index >= controller.battery_values.size()) {
748 return;
749 }
750 std::lock_guard lock{mutex};
751 controller.battery_values[index] = TransformToBattery(callback);
752
753 if (is_configuring) {
754 TriggerOnChange(ControllerTriggerType::Battery, false);
755 return;
756 }
757
758 bool is_charging = false;
759 bool is_powered = false;
760 NpadBatteryLevel battery_level = 0;
761 switch (controller.battery_values[index]) {
762 case Common::Input::BatteryLevel::Charging:
763 is_charging = true;
764 is_powered = true;
765 battery_level = 6;
766 break;
767 case Common::Input::BatteryLevel::Medium:
768 battery_level = 6;
769 break;
770 case Common::Input::BatteryLevel::Low:
771 battery_level = 4;
772 break;
773 case Common::Input::BatteryLevel::Critical:
774 battery_level = 2;
775 break;
776 case Common::Input::BatteryLevel::Empty:
777 battery_level = 0;
778 break;
779 case Common::Input::BatteryLevel::None:
780 case Common::Input::BatteryLevel::Full:
781 default:
782 is_powered = true;
783 battery_level = 8;
784 break;
785 }
786
787 switch (index) {
788 case LeftIndex:
789 controller.battery_state.left = {
790 .is_powered = is_powered,
791 .is_charging = is_charging,
792 .battery_level = battery_level,
793 };
794 break;
795 case RightIndex:
796 controller.battery_state.right = {
797 .is_powered = is_powered,
798 .is_charging = is_charging,
799 .battery_level = battery_level,
800 };
801 break;
802 case DualIndex:
803 controller.battery_state.dual = {
804 .is_powered = is_powered,
805 .is_charging = is_charging,
806 .battery_level = battery_level,
807 };
808 break;
809 }
810 TriggerOnChange(ControllerTriggerType::Battery, true);
811}
812
813bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue vibration) {
814 if (device_index >= output_devices.size()) {
815 return false;
816 }
817 if (!output_devices[device_index]) {
818 return false;
819 }
820 const auto player_index = NpadIdTypeToIndex(npad_id_type);
821 const auto& player = Settings::values.players.GetValue()[player_index];
822 const f32 strength = static_cast<f32>(player.vibration_strength) / 100.0f;
823
824 if (!player.vibration_enabled) {
825 return false;
826 }
827
828 // Exponential amplification is too strong at low amplitudes. Switch to a linear
829 // amplification if strength is set below 0.7f
830 const Common::Input::VibrationAmplificationType type =
831 strength > 0.7f ? Common::Input::VibrationAmplificationType::Exponential
832 : Common::Input::VibrationAmplificationType::Linear;
833
834 const Common::Input::VibrationStatus status = {
835 .low_amplitude = std::min(vibration.low_amplitude * strength, 1.0f),
836 .low_frequency = vibration.low_frequency,
837 .high_amplitude = std::min(vibration.high_amplitude * strength, 1.0f),
838 .high_frequency = vibration.high_frequency,
839 .type = type,
840 };
841 return output_devices[device_index]->SetVibration(status) ==
842 Common::Input::VibrationError::None;
843}
844
845bool EmulatedController::TestVibration(std::size_t device_index) {
846 if (device_index >= output_devices.size()) {
847 return false;
848 }
849 if (!output_devices[device_index]) {
850 return false;
851 }
852
853 // Send a slight vibration to test for rumble support
854 constexpr Common::Input::VibrationStatus status = {
855 .low_amplitude = 0.001f,
856 .low_frequency = 160.0f,
857 .high_amplitude = 0.001f,
858 .high_frequency = 320.0f,
859 .type = Common::Input::VibrationAmplificationType::Linear,
860 };
861 return output_devices[device_index]->SetVibration(status) ==
862 Common::Input::VibrationError::None;
863}
864
865void EmulatedController::SetLedPattern() {
866 for (auto& device : output_devices) {
867 if (!device) {
868 continue;
869 }
870
871 const LedPattern pattern = GetLedPattern();
872 const Common::Input::LedStatus status = {
873 .led_1 = pattern.position1 != 0,
874 .led_2 = pattern.position2 != 0,
875 .led_3 = pattern.position3 != 0,
876 .led_4 = pattern.position4 != 0,
877 };
878 device->SetLED(status);
879 }
880}
881
882void EmulatedController::SetSupportedNpadStyleTag(NpadStyleTag supported_styles) {
883 supported_style_tag = supported_styles;
884 if (!is_connected) {
885 return;
886 }
887 if (!IsControllerSupported()) {
888 LOG_ERROR(Service_HID, "Controller type {} is not supported. Disconnecting controller",
889 npad_type);
890 Disconnect();
891 }
892}
893
894bool EmulatedController::IsControllerSupported() const {
895 switch (npad_type) {
896 case NpadStyleIndex::ProController:
897 return supported_style_tag.fullkey;
898 case NpadStyleIndex::Handheld:
899 return supported_style_tag.handheld;
900 case NpadStyleIndex::JoyconDual:
901 return supported_style_tag.joycon_dual;
902 case NpadStyleIndex::JoyconLeft:
903 return supported_style_tag.joycon_left;
904 case NpadStyleIndex::JoyconRight:
905 return supported_style_tag.joycon_right;
906 case NpadStyleIndex::GameCube:
907 return supported_style_tag.gamecube;
908 case NpadStyleIndex::Pokeball:
909 return supported_style_tag.palma;
910 case NpadStyleIndex::NES:
911 return supported_style_tag.lark;
912 case NpadStyleIndex::SNES:
913 return supported_style_tag.lucia;
914 case NpadStyleIndex::N64:
915 return supported_style_tag.lagoon;
916 case NpadStyleIndex::SegaGenesis:
917 return supported_style_tag.lager;
918 default:
919 return false;
920 }
921}
922
923void EmulatedController::Connect() {
924 if (!IsControllerSupported()) {
925 LOG_ERROR(Service_HID, "Controller type {} is not supported", npad_type);
926 return;
927 }
928 {
929 std::lock_guard lock{mutex};
930 if (is_configuring) {
931 tmp_is_connected = true;
932 TriggerOnChange(ControllerTriggerType::Connected, false);
933 return;
934 }
935
936 if (is_connected) {
937 return;
938 }
939 is_connected = true;
940 }
941 TriggerOnChange(ControllerTriggerType::Connected, true);
942}
943
944void EmulatedController::Disconnect() {
945 {
946 std::lock_guard lock{mutex};
947 if (is_configuring) {
948 tmp_is_connected = false;
949 TriggerOnChange(ControllerTriggerType::Disconnected, false);
950 return;
951 }
952
953 if (!is_connected) {
954 return;
955 }
956 is_connected = false;
957 }
958 TriggerOnChange(ControllerTriggerType::Disconnected, true);
959}
960
961bool EmulatedController::IsConnected(bool get_temporary_value) const {
962 if (get_temporary_value && is_configuring) {
963 return tmp_is_connected;
964 }
965 return is_connected;
966}
967
968bool EmulatedController::IsVibrationEnabled() const {
969 const auto player_index = NpadIdTypeToIndex(npad_id_type);
970 const auto& player = Settings::values.players.GetValue()[player_index];
971 return player.vibration_enabled;
972}
973
974NpadIdType EmulatedController::GetNpadIdType() const {
975 return npad_id_type;
976}
977
978NpadStyleIndex EmulatedController::GetNpadStyleIndex(bool get_temporary_value) const {
979 if (get_temporary_value && is_configuring) {
980 return tmp_npad_type;
981 }
982 return npad_type;
983}
984
985void EmulatedController::SetNpadStyleIndex(NpadStyleIndex npad_type_) {
986 {
987 std::lock_guard lock{mutex};
988
989 if (is_configuring) {
990 if (tmp_npad_type == npad_type_) {
991 return;
992 }
993 tmp_npad_type = npad_type_;
994 TriggerOnChange(ControllerTriggerType::Type, false);
995 return;
996 }
997
998 if (npad_type == npad_type_) {
999 return;
1000 }
1001 if (is_connected) {
1002 LOG_WARNING(Service_HID, "Controller {} type changed while it's connected",
1003 NpadIdTypeToIndex(npad_id_type));
1004 }
1005 npad_type = npad_type_;
1006 }
1007 TriggerOnChange(ControllerTriggerType::Type, true);
1008}
1009
1010LedPattern EmulatedController::GetLedPattern() const {
1011 switch (npad_id_type) {
1012 case NpadIdType::Player1:
1013 return LedPattern{1, 0, 0, 0};
1014 case NpadIdType::Player2:
1015 return LedPattern{1, 1, 0, 0};
1016 case NpadIdType::Player3:
1017 return LedPattern{1, 1, 1, 0};
1018 case NpadIdType::Player4:
1019 return LedPattern{1, 1, 1, 1};
1020 case NpadIdType::Player5:
1021 return LedPattern{1, 0, 0, 1};
1022 case NpadIdType::Player6:
1023 return LedPattern{1, 0, 1, 0};
1024 case NpadIdType::Player7:
1025 return LedPattern{1, 0, 1, 1};
1026 case NpadIdType::Player8:
1027 return LedPattern{0, 1, 1, 0};
1028 default:
1029 return LedPattern{0, 0, 0, 0};
1030 }
1031}
1032
1033ButtonValues EmulatedController::GetButtonsValues() const {
1034 return controller.button_values;
1035}
1036
1037SticksValues EmulatedController::GetSticksValues() const {
1038 return controller.stick_values;
1039}
1040
1041TriggerValues EmulatedController::GetTriggersValues() const {
1042 return controller.trigger_values;
1043}
1044
1045ControllerMotionValues EmulatedController::GetMotionValues() const {
1046 return controller.motion_values;
1047}
1048
1049ColorValues EmulatedController::GetColorsValues() const {
1050 return controller.color_values;
1051}
1052
1053BatteryValues EmulatedController::GetBatteryValues() const {
1054 return controller.battery_values;
1055}
1056
1057NpadButtonState EmulatedController::GetNpadButtons() const {
1058 if (is_configuring) {
1059 return {};
1060 }
1061 return controller.npad_button_state;
1062}
1063
1064DebugPadButton EmulatedController::GetDebugPadButtons() const {
1065 if (is_configuring) {
1066 return {};
1067 }
1068 return controller.debug_pad_button_state;
1069}
1070
1071AnalogSticks EmulatedController::GetSticks() const {
1072 if (is_configuring) {
1073 return {};
1074 }
1075 // Some drivers like stick from buttons need constant refreshing
1076 for (auto& device : stick_devices) {
1077 if (!device) {
1078 continue;
1079 }
1080 device->SoftUpdate();
1081 }
1082 return controller.analog_stick_state;
1083}
1084
1085NpadGcTriggerState EmulatedController::GetTriggers() const {
1086 if (is_configuring) {
1087 return {};
1088 }
1089 return controller.gc_trigger_state;
1090}
1091
1092MotionState EmulatedController::GetMotions() const {
1093 if (force_update_motion) {
1094 for (auto& device : motion_devices) {
1095 if (!device) {
1096 continue;
1097 }
1098 device->ForceUpdate();
1099 }
1100 }
1101 return controller.motion_state;
1102}
1103
1104ControllerColors EmulatedController::GetColors() const {
1105 return controller.colors_state;
1106}
1107
1108BatteryLevelState EmulatedController::GetBattery() const {
1109 return controller.battery_state;
1110}
1111
1112void EmulatedController::TriggerOnChange(ControllerTriggerType type, bool is_npad_service_update) {
1113 for (const auto& poller_pair : callback_list) {
1114 const ControllerUpdateCallback& poller = poller_pair.second;
1115 if (!is_npad_service_update && poller.is_npad_service) {
1116 continue;
1117 }
1118 if (poller.on_change) {
1119 poller.on_change(type);
1120 }
1121 }
1122}
1123
1124int EmulatedController::SetCallback(ControllerUpdateCallback update_callback) {
1125 std::lock_guard lock{mutex};
1126 callback_list.insert_or_assign(last_callback_key, std::move(update_callback));
1127 return last_callback_key++;
1128}
1129
1130void EmulatedController::DeleteCallback(int key) {
1131 std::lock_guard lock{mutex};
1132 const auto& iterator = callback_list.find(key);
1133 if (iterator == callback_list.end()) {
1134 LOG_ERROR(Input, "Tried to delete non-existent callback {}", key);
1135 return;
1136 }
1137 callback_list.erase(iterator);
1138}
1139} // namespace Core::HID
diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h
new file mode 100644
index 000000000..e42aafebc
--- /dev/null
+++ b/src/core/hid/emulated_controller.h
@@ -0,0 +1,411 @@
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 <functional>
9#include <memory>
10#include <mutex>
11#include <unordered_map>
12
13#include "common/common_types.h"
14#include "common/input.h"
15#include "common/param_package.h"
16#include "common/point.h"
17#include "common/quaternion.h"
18#include "common/settings.h"
19#include "common/vector_math.h"
20#include "core/hid/hid_types.h"
21#include "core/hid/motion_input.h"
22
23namespace Core::HID {
24const std::size_t max_emulated_controllers = 2;
25struct ControllerMotionInfo {
26 Common::Input::MotionStatus raw_status{};
27 MotionInput emulated{};
28};
29
30using ButtonDevices =
31 std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeButton::NumButtons>;
32using StickDevices =
33 std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeAnalog::NumAnalogs>;
34using ControllerMotionDevices =
35 std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeMotion::NumMotions>;
36using TriggerDevices =
37 std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeTrigger::NumTriggers>;
38using BatteryDevices =
39 std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
40using OutputDevices =
41 std::array<std::unique_ptr<Common::Input::OutputDevice>, max_emulated_controllers>;
42
43using ButtonParams = std::array<Common::ParamPackage, Settings::NativeButton::NumButtons>;
44using StickParams = std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs>;
45using ControllerMotionParams = std::array<Common::ParamPackage, Settings::NativeMotion::NumMotions>;
46using TriggerParams = std::array<Common::ParamPackage, Settings::NativeTrigger::NumTriggers>;
47using BatteryParams = std::array<Common::ParamPackage, max_emulated_controllers>;
48using OutputParams = std::array<Common::ParamPackage, max_emulated_controllers>;
49
50using ButtonValues = std::array<Common::Input::ButtonStatus, Settings::NativeButton::NumButtons>;
51using SticksValues = std::array<Common::Input::StickStatus, Settings::NativeAnalog::NumAnalogs>;
52using TriggerValues =
53 std::array<Common::Input::TriggerStatus, Settings::NativeTrigger::NumTriggers>;
54using ControllerMotionValues = std::array<ControllerMotionInfo, Settings::NativeMotion::NumMotions>;
55using ColorValues = std::array<Common::Input::BodyColorStatus, max_emulated_controllers>;
56using BatteryValues = std::array<Common::Input::BatteryStatus, max_emulated_controllers>;
57using VibrationValues = std::array<Common::Input::VibrationStatus, max_emulated_controllers>;
58
59struct AnalogSticks {
60 AnalogStickState left{};
61 AnalogStickState right{};
62};
63
64struct ControllerColors {
65 NpadControllerColor fullkey{};
66 NpadControllerColor left{};
67 NpadControllerColor right{};
68};
69
70struct BatteryLevelState {
71 NpadPowerInfo dual{};
72 NpadPowerInfo left{};
73 NpadPowerInfo right{};
74};
75
76struct ControllerMotion {
77 Common::Vec3f accel{};
78 Common::Vec3f gyro{};
79 Common::Vec3f rotation{};
80 std::array<Common::Vec3f, 3> orientation{};
81 bool is_at_rest{};
82};
83
84enum EmulatedDeviceIndex : u8 {
85 LeftIndex,
86 RightIndex,
87 DualIndex,
88 AllDevices,
89};
90
91using MotionState = std::array<ControllerMotion, 2>;
92
93struct ControllerStatus {
94 // Data from input_common
95 ButtonValues button_values{};
96 SticksValues stick_values{};
97 ControllerMotionValues motion_values{};
98 TriggerValues trigger_values{};
99 ColorValues color_values{};
100 BatteryValues battery_values{};
101 VibrationValues vibration_values{};
102
103 // Data for HID serices
104 NpadButtonState npad_button_state{};
105 DebugPadButton debug_pad_button_state{};
106 AnalogSticks analog_stick_state{};
107 MotionState motion_state{};
108 NpadGcTriggerState gc_trigger_state{};
109 ControllerColors colors_state{};
110 BatteryLevelState battery_state{};
111};
112
113enum class ControllerTriggerType {
114 Button,
115 Stick,
116 Trigger,
117 Motion,
118 Color,
119 Battery,
120 Vibration,
121 Connected,
122 Disconnected,
123 Type,
124 All,
125};
126
127struct ControllerUpdateCallback {
128 std::function<void(ControllerTriggerType)> on_change;
129 bool is_npad_service;
130};
131
132class EmulatedController {
133public:
134 /**
135 * Contains all input data (buttons, joysticks, vibration, and motion) within this controller.
136 * @param npad_id_type npad id type for this specific controller
137 */
138 explicit EmulatedController(NpadIdType npad_id_type_);
139 ~EmulatedController();
140
141 YUZU_NON_COPYABLE(EmulatedController);
142 YUZU_NON_MOVEABLE(EmulatedController);
143
144 /// Converts the controller type from settings to npad type
145 static NpadStyleIndex MapSettingsTypeToNPad(Settings::ControllerType type);
146
147 /// Converts npad type to the equivalent of controller type from settings
148 static Settings::ControllerType MapNPadToSettingsType(NpadStyleIndex type);
149
150 /// Gets the NpadIdType for this controller
151 NpadIdType GetNpadIdType() const;
152
153 /// Sets the NpadStyleIndex for this controller
154 void SetNpadStyleIndex(NpadStyleIndex npad_type_);
155
156 /**
157 * Gets the NpadStyleIndex for this controller
158 * @param get_temporary_value If true tmp_npad_type will be returned
159 * @return NpadStyleIndex set on the controller
160 */
161 NpadStyleIndex GetNpadStyleIndex(bool get_temporary_value = false) const;
162
163 /**
164 * Sets the supported controller types. Disconnects the controller if current type is not
165 * supported
166 * @param supported_styles bitflag with supported types
167 */
168 void SetSupportedNpadStyleTag(NpadStyleTag supported_styles);
169
170 /// Sets the connected status to true
171 void Connect();
172
173 /// Sets the connected status to false
174 void Disconnect();
175
176 /**
177 * Is the emulated connected
178 * @param get_temporary_value If true tmp_is_connected will be returned
179 * @return true if the controller has the connected status
180 */
181 bool IsConnected(bool get_temporary_value = false) const;
182
183 /// Returns true if vibration is enabled
184 bool IsVibrationEnabled() const;
185
186 /// Removes all callbacks created from input devices
187 void UnloadInput();
188
189 /**
190 * Sets the emulated controller into configuring mode
191 * This prevents the modification of the HID state of the emulated controller by input commands
192 */
193 void EnableConfiguration();
194
195 /// Returns the emulated controller into normal mode, allowing the modification of the HID state
196 void DisableConfiguration();
197
198 /// Returns true if the emulated controller is in configuring mode
199 bool IsConfiguring() const;
200
201 /// Reload all input devices
202 void ReloadInput();
203
204 /// Overrides current mapped devices with the stored configuration and reloads all input devices
205 void ReloadFromSettings();
206
207 /// Saves the current mapped configuration
208 void SaveCurrentConfig();
209
210 /// Reverts any mapped changes made that weren't saved
211 void RestoreConfig();
212
213 /// Returns a vector of mapped devices from the mapped button and stick parameters
214 std::vector<Common::ParamPackage> GetMappedDevices(EmulatedDeviceIndex device_index) const;
215
216 // Returns the current mapped button device
217 Common::ParamPackage GetButtonParam(std::size_t index) const;
218
219 // Returns the current mapped stick device
220 Common::ParamPackage GetStickParam(std::size_t index) const;
221
222 // Returns the current mapped motion device
223 Common::ParamPackage GetMotionParam(std::size_t index) const;
224
225 /**
226 * Updates the current mapped button device
227 * @param param ParamPackage with controller data to be mapped
228 */
229 void SetButtonParam(std::size_t index, Common::ParamPackage param);
230
231 /**
232 * Updates the current mapped stick device
233 * @param param ParamPackage with controller data to be mapped
234 */
235 void SetStickParam(std::size_t index, Common::ParamPackage param);
236
237 /**
238 * Updates the current mapped motion device
239 * @param param ParamPackage with controller data to be mapped
240 */
241 void SetMotionParam(std::size_t index, Common::ParamPackage param);
242
243 /// Returns the latest button status from the controller with parameters
244 ButtonValues GetButtonsValues() const;
245
246 /// Returns the latest analog stick status from the controller with parameters
247 SticksValues GetSticksValues() const;
248
249 /// Returns the latest trigger status from the controller with parameters
250 TriggerValues GetTriggersValues() const;
251
252 /// Returns the latest motion status from the controller with parameters
253 ControllerMotionValues GetMotionValues() const;
254
255 /// Returns the latest color status from the controller with parameters
256 ColorValues GetColorsValues() const;
257
258 /// Returns the latest battery status from the controller with parameters
259 BatteryValues GetBatteryValues() const;
260
261 /// Returns the latest status of button input for the npad service
262 NpadButtonState GetNpadButtons() const;
263
264 /// Returns the latest status of button input for the debug pad service
265 DebugPadButton GetDebugPadButtons() const;
266
267 /// Returns the latest status of stick input from the mouse
268 AnalogSticks GetSticks() const;
269
270 /// Returns the latest status of trigger input from the mouse
271 NpadGcTriggerState GetTriggers() const;
272
273 /// Returns the latest status of motion input from the mouse
274 MotionState GetMotions() const;
275
276 /// Returns the latest color value from the controller
277 ControllerColors GetColors() const;
278
279 /// Returns the latest battery status from the controller
280 BatteryLevelState GetBattery() const;
281
282 /**
283 * Sends a specific vibration to the output device
284 * @return returns true if vibration had no errors
285 */
286 bool SetVibration(std::size_t device_index, VibrationValue vibration);
287
288 /**
289 * Sends a small vibration to the output device
290 * @return returns true if SetVibration was successfull
291 */
292 bool TestVibration(std::size_t device_index);
293
294 /// Returns the led pattern corresponding to this emulated controller
295 LedPattern GetLedPattern() const;
296
297 /// Asks the output device to change the player led pattern
298 void SetLedPattern();
299
300 /**
301 * Adds a callback to the list of events
302 * @param update_callback A ConsoleUpdateCallback that will be triggered
303 * @return an unique key corresponding to the callback index in the list
304 */
305 int SetCallback(ControllerUpdateCallback update_callback);
306
307 /**
308 * Removes a callback from the list stopping any future events to this object
309 * @param key Key corresponding to the callback index in the list
310 */
311 void DeleteCallback(int key);
312
313private:
314 /// creates input devices from params
315 void LoadDevices();
316
317 /// Set the params for TAS devices
318 void LoadTASParams();
319
320 /**
321 * Checks the current controller type against the supported_style_tag
322 * @return true if the controller is supported
323 */
324 bool IsControllerSupported() const;
325
326 /**
327 * Updates the button status of the controller
328 * @param callback A CallbackStatus containing the button status
329 * @param index Button ID of the to be updated
330 */
331 void SetButton(const Common::Input::CallbackStatus& callback, std::size_t index,
332 Common::UUID uuid);
333
334 /**
335 * Updates the analog stick status of the controller
336 * @param callback A CallbackStatus containing the analog stick status
337 * @param index stick ID of the to be updated
338 */
339 void SetStick(const Common::Input::CallbackStatus& callback, std::size_t index,
340 Common::UUID uuid);
341
342 /**
343 * Updates the trigger status of the controller
344 * @param callback A CallbackStatus containing the trigger status
345 * @param index trigger ID of the to be updated
346 */
347 void SetTrigger(const Common::Input::CallbackStatus& callback, std::size_t index,
348 Common::UUID uuid);
349
350 /**
351 * Updates the motion status of the controller
352 * @param callback A CallbackStatus containing gyro and accelerometer data
353 * @param index motion ID of the to be updated
354 */
355 void SetMotion(const Common::Input::CallbackStatus& callback, std::size_t index);
356
357 /**
358 * Updates the battery status of the controller
359 * @param callback A CallbackStatus containing the battery status
360 * @param index Button ID of the to be updated
361 */
362 void SetBattery(const Common::Input::CallbackStatus& callback, std::size_t index);
363
364 /**
365 * Triggers a callback that something has changed on the controller status
366 * @param type Input type of the event to trigger
367 * @param is_service_update indicates if this event should only be sent to HID services
368 */
369 void TriggerOnChange(ControllerTriggerType type, bool is_service_update);
370
371 NpadIdType npad_id_type;
372 NpadStyleIndex npad_type{NpadStyleIndex::None};
373 NpadStyleTag supported_style_tag{NpadStyleSet::All};
374 bool is_connected{false};
375 bool is_configuring{false};
376 f32 motion_sensitivity{0.01f};
377 bool force_update_motion{false};
378
379 // Temporary values to avoid doing changes while the controller is in configuring mode
380 NpadStyleIndex tmp_npad_type{NpadStyleIndex::None};
381 bool tmp_is_connected{false};
382
383 ButtonParams button_params;
384 StickParams stick_params;
385 ControllerMotionParams motion_params;
386 TriggerParams trigger_params;
387 BatteryParams battery_params;
388 OutputParams output_params;
389
390 ButtonDevices button_devices;
391 StickDevices stick_devices;
392 ControllerMotionDevices motion_devices;
393 TriggerDevices trigger_devices;
394 BatteryDevices battery_devices;
395 OutputDevices output_devices;
396
397 // TAS related variables
398 ButtonParams tas_button_params;
399 StickParams tas_stick_params;
400 ButtonDevices tas_button_devices;
401 StickDevices tas_stick_devices;
402
403 mutable std::mutex mutex;
404 std::unordered_map<int, ControllerUpdateCallback> callback_list;
405 int last_callback_key = 0;
406
407 // Stores the current status of all controller input
408 ControllerStatus controller;
409};
410
411} // namespace Core::HID
diff --git a/src/core/hid/emulated_devices.cpp b/src/core/hid/emulated_devices.cpp
new file mode 100644
index 000000000..708480f2d
--- /dev/null
+++ b/src/core/hid/emulated_devices.cpp
@@ -0,0 +1,459 @@
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 <algorithm>
6#include <fmt/format.h>
7
8#include "core/hid/emulated_devices.h"
9#include "core/hid/input_converter.h"
10
11namespace Core::HID {
12
13EmulatedDevices::EmulatedDevices() = default;
14
15EmulatedDevices::~EmulatedDevices() = default;
16
17void EmulatedDevices::ReloadFromSettings() {
18 ReloadInput();
19}
20
21void EmulatedDevices::ReloadInput() {
22 // If you load any device here add the equivalent to the UnloadInput() function
23 std::size_t key_index = 0;
24 for (auto& mouse_device : mouse_button_devices) {
25 Common::ParamPackage mouse_params;
26 mouse_params.Set("engine", "mouse");
27 mouse_params.Set("button", static_cast<int>(key_index));
28 mouse_device = Common::Input::CreateDevice<Common::Input::InputDevice>(mouse_params);
29 key_index++;
30 }
31
32 mouse_stick_device = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>(
33 "engine:mouse,axis_x:0,axis_y:1");
34
35 // First two axis are reserved for mouse position
36 key_index = 2;
37 for (auto& mouse_device : mouse_analog_devices) {
38 Common::ParamPackage mouse_params;
39 mouse_params.Set("engine", "mouse");
40 mouse_params.Set("axis", static_cast<int>(key_index));
41 mouse_device = Common::Input::CreateDevice<Common::Input::InputDevice>(mouse_params);
42 key_index++;
43 }
44
45 key_index = 0;
46 for (auto& keyboard_device : keyboard_devices) {
47 // Keyboard keys are only mapped on port 1, pad 0
48 Common::ParamPackage keyboard_params;
49 keyboard_params.Set("engine", "keyboard");
50 keyboard_params.Set("button", static_cast<int>(key_index));
51 keyboard_params.Set("port", 1);
52 keyboard_params.Set("pad", 0);
53 keyboard_device = Common::Input::CreateDevice<Common::Input::InputDevice>(keyboard_params);
54 key_index++;
55 }
56
57 key_index = 0;
58 for (auto& keyboard_device : keyboard_modifier_devices) {
59 // Keyboard moddifiers are only mapped on port 1, pad 1
60 Common::ParamPackage keyboard_params;
61 keyboard_params.Set("engine", "keyboard");
62 keyboard_params.Set("button", static_cast<int>(key_index));
63 keyboard_params.Set("port", 1);
64 keyboard_params.Set("pad", 1);
65 keyboard_device = Common::Input::CreateDevice<Common::Input::InputDevice>(keyboard_params);
66 key_index++;
67 }
68
69 for (std::size_t index = 0; index < mouse_button_devices.size(); ++index) {
70 if (!mouse_button_devices[index]) {
71 continue;
72 }
73 mouse_button_devices[index]->SetCallback({
74 .on_change =
75 [this, index](const Common::Input::CallbackStatus& callback) {
76 SetMouseButton(callback, index);
77 },
78 });
79 }
80
81 for (std::size_t index = 0; index < mouse_analog_devices.size(); ++index) {
82 if (!mouse_analog_devices[index]) {
83 continue;
84 }
85 mouse_analog_devices[index]->SetCallback({
86 .on_change =
87 [this, index](const Common::Input::CallbackStatus& callback) {
88 SetMouseAnalog(callback, index);
89 },
90 });
91 }
92
93 if (mouse_stick_device) {
94 mouse_stick_device->SetCallback({
95 .on_change =
96 [this](const Common::Input::CallbackStatus& callback) { SetMouseStick(callback); },
97 });
98 }
99
100 for (std::size_t index = 0; index < keyboard_devices.size(); ++index) {
101 if (!keyboard_devices[index]) {
102 continue;
103 }
104 keyboard_devices[index]->SetCallback({
105 .on_change =
106 [this, index](const Common::Input::CallbackStatus& callback) {
107 SetKeyboardButton(callback, index);
108 },
109 });
110 }
111
112 for (std::size_t index = 0; index < keyboard_modifier_devices.size(); ++index) {
113 if (!keyboard_modifier_devices[index]) {
114 continue;
115 }
116 keyboard_modifier_devices[index]->SetCallback({
117 .on_change =
118 [this, index](const Common::Input::CallbackStatus& callback) {
119 SetKeyboardModifier(callback, index);
120 },
121 });
122 }
123}
124
125void EmulatedDevices::UnloadInput() {
126 for (auto& button : mouse_button_devices) {
127 button.reset();
128 }
129 for (auto& analog : mouse_analog_devices) {
130 analog.reset();
131 }
132 mouse_stick_device.reset();
133 for (auto& button : keyboard_devices) {
134 button.reset();
135 }
136 for (auto& button : keyboard_modifier_devices) {
137 button.reset();
138 }
139}
140
141void EmulatedDevices::EnableConfiguration() {
142 is_configuring = true;
143 SaveCurrentConfig();
144}
145
146void EmulatedDevices::DisableConfiguration() {
147 is_configuring = false;
148}
149
150bool EmulatedDevices::IsConfiguring() const {
151 return is_configuring;
152}
153
154void EmulatedDevices::SaveCurrentConfig() {
155 if (!is_configuring) {
156 return;
157 }
158}
159
160void EmulatedDevices::RestoreConfig() {
161 if (!is_configuring) {
162 return;
163 }
164 ReloadFromSettings();
165}
166
167void EmulatedDevices::SetKeyboardButton(const Common::Input::CallbackStatus& callback,
168 std::size_t index) {
169 if (index >= device_status.keyboard_values.size()) {
170 return;
171 }
172 std::lock_guard lock{mutex};
173 bool value_changed = false;
174 const auto new_status = TransformToButton(callback);
175 auto& current_status = device_status.keyboard_values[index];
176 current_status.toggle = new_status.toggle;
177
178 // Update button status with current status
179 if (!current_status.toggle) {
180 current_status.locked = false;
181 if (current_status.value != new_status.value) {
182 current_status.value = new_status.value;
183 value_changed = true;
184 }
185 } else {
186 // Toggle button and lock status
187 if (new_status.value && !current_status.locked) {
188 current_status.locked = true;
189 current_status.value = !current_status.value;
190 value_changed = true;
191 }
192
193 // Unlock button, ready for next press
194 if (!new_status.value && current_status.locked) {
195 current_status.locked = false;
196 }
197 }
198
199 if (!value_changed) {
200 return;
201 }
202
203 if (is_configuring) {
204 TriggerOnChange(DeviceTriggerType::Keyboard);
205 return;
206 }
207
208 // Index should be converted from NativeKeyboard to KeyboardKeyIndex
209 UpdateKey(index, current_status.value);
210
211 TriggerOnChange(DeviceTriggerType::Keyboard);
212}
213
214void EmulatedDevices::UpdateKey(std::size_t key_index, bool status) {
215 constexpr std::size_t KEYS_PER_BYTE = 8;
216 auto& entry = device_status.keyboard_state.key[key_index / KEYS_PER_BYTE];
217 const u8 mask = static_cast<u8>(1 << (key_index % KEYS_PER_BYTE));
218 if (status) {
219 entry = entry | mask;
220 } else {
221 entry = static_cast<u8>(entry & ~mask);
222 }
223}
224
225void EmulatedDevices::SetKeyboardModifier(const Common::Input::CallbackStatus& callback,
226 std::size_t index) {
227 if (index >= device_status.keyboard_moddifier_values.size()) {
228 return;
229 }
230 std::lock_guard lock{mutex};
231 bool value_changed = false;
232 const auto new_status = TransformToButton(callback);
233 auto& current_status = device_status.keyboard_moddifier_values[index];
234 current_status.toggle = new_status.toggle;
235
236 // Update button status with current
237 if (!current_status.toggle) {
238 current_status.locked = false;
239 if (current_status.value != new_status.value) {
240 current_status.value = new_status.value;
241 value_changed = true;
242 }
243 } else {
244 // Toggle button and lock status
245 if (new_status.value && !current_status.locked) {
246 current_status.locked = true;
247 current_status.value = !current_status.value;
248 value_changed = true;
249 }
250
251 // Unlock button ready for next press
252 if (!new_status.value && current_status.locked) {
253 current_status.locked = false;
254 }
255 }
256
257 if (!value_changed) {
258 return;
259 }
260
261 if (is_configuring) {
262 TriggerOnChange(DeviceTriggerType::KeyboardModdifier);
263 return;
264 }
265
266 switch (index) {
267 case Settings::NativeKeyboard::LeftControl:
268 case Settings::NativeKeyboard::RightControl:
269 device_status.keyboard_moddifier_state.control.Assign(current_status.value);
270 break;
271 case Settings::NativeKeyboard::LeftShift:
272 case Settings::NativeKeyboard::RightShift:
273 device_status.keyboard_moddifier_state.shift.Assign(current_status.value);
274 break;
275 case Settings::NativeKeyboard::LeftAlt:
276 device_status.keyboard_moddifier_state.left_alt.Assign(current_status.value);
277 break;
278 case Settings::NativeKeyboard::RightAlt:
279 device_status.keyboard_moddifier_state.right_alt.Assign(current_status.value);
280 break;
281 case Settings::NativeKeyboard::CapsLock:
282 device_status.keyboard_moddifier_state.caps_lock.Assign(current_status.value);
283 break;
284 case Settings::NativeKeyboard::ScrollLock:
285 device_status.keyboard_moddifier_state.scroll_lock.Assign(current_status.value);
286 break;
287 case Settings::NativeKeyboard::NumLock:
288 device_status.keyboard_moddifier_state.num_lock.Assign(current_status.value);
289 break;
290 }
291
292 TriggerOnChange(DeviceTriggerType::KeyboardModdifier);
293}
294
295void EmulatedDevices::SetMouseButton(const Common::Input::CallbackStatus& callback,
296 std::size_t index) {
297 if (index >= device_status.mouse_button_values.size()) {
298 return;
299 }
300 std::lock_guard lock{mutex};
301 bool value_changed = false;
302 const auto new_status = TransformToButton(callback);
303 auto& current_status = device_status.mouse_button_values[index];
304 current_status.toggle = new_status.toggle;
305
306 // Update button status with current
307 if (!current_status.toggle) {
308 current_status.locked = false;
309 if (current_status.value != new_status.value) {
310 current_status.value = new_status.value;
311 value_changed = true;
312 }
313 } else {
314 // Toggle button and lock status
315 if (new_status.value && !current_status.locked) {
316 current_status.locked = true;
317 current_status.value = !current_status.value;
318 value_changed = true;
319 }
320
321 // Unlock button ready for next press
322 if (!new_status.value && current_status.locked) {
323 current_status.locked = false;
324 }
325 }
326
327 if (!value_changed) {
328 return;
329 }
330
331 if (is_configuring) {
332 TriggerOnChange(DeviceTriggerType::Mouse);
333 return;
334 }
335
336 switch (index) {
337 case Settings::NativeMouseButton::Left:
338 device_status.mouse_button_state.left.Assign(current_status.value);
339 break;
340 case Settings::NativeMouseButton::Right:
341 device_status.mouse_button_state.right.Assign(current_status.value);
342 break;
343 case Settings::NativeMouseButton::Middle:
344 device_status.mouse_button_state.middle.Assign(current_status.value);
345 break;
346 case Settings::NativeMouseButton::Forward:
347 device_status.mouse_button_state.forward.Assign(current_status.value);
348 break;
349 case Settings::NativeMouseButton::Back:
350 device_status.mouse_button_state.back.Assign(current_status.value);
351 break;
352 }
353
354 TriggerOnChange(DeviceTriggerType::Mouse);
355}
356
357void EmulatedDevices::SetMouseAnalog(const Common::Input::CallbackStatus& callback,
358 std::size_t index) {
359 if (index >= device_status.mouse_analog_values.size()) {
360 return;
361 }
362 std::lock_guard lock{mutex};
363 const auto analog_value = TransformToAnalog(callback);
364
365 device_status.mouse_analog_values[index] = analog_value;
366
367 if (is_configuring) {
368 device_status.mouse_position_state = {};
369 TriggerOnChange(DeviceTriggerType::Mouse);
370 return;
371 }
372
373 switch (index) {
374 case Settings::NativeMouseWheel::X:
375 device_status.mouse_wheel_state.x = static_cast<s32>(analog_value.value);
376 break;
377 case Settings::NativeMouseWheel::Y:
378 device_status.mouse_wheel_state.y = static_cast<s32>(analog_value.value);
379 break;
380 }
381
382 TriggerOnChange(DeviceTriggerType::Mouse);
383}
384
385void EmulatedDevices::SetMouseStick(const Common::Input::CallbackStatus& callback) {
386 std::lock_guard lock{mutex};
387 const auto touch_value = TransformToTouch(callback);
388
389 device_status.mouse_stick_value = touch_value;
390
391 if (is_configuring) {
392 device_status.mouse_position_state = {};
393 TriggerOnChange(DeviceTriggerType::Mouse);
394 return;
395 }
396
397 device_status.mouse_position_state.x = touch_value.x.value;
398 device_status.mouse_position_state.y = touch_value.y.value;
399
400 TriggerOnChange(DeviceTriggerType::Mouse);
401}
402
403KeyboardValues EmulatedDevices::GetKeyboardValues() const {
404 return device_status.keyboard_values;
405}
406
407KeyboardModifierValues EmulatedDevices::GetKeyboardModdifierValues() const {
408 return device_status.keyboard_moddifier_values;
409}
410
411MouseButtonValues EmulatedDevices::GetMouseButtonsValues() const {
412 return device_status.mouse_button_values;
413}
414
415KeyboardKey EmulatedDevices::GetKeyboard() const {
416 return device_status.keyboard_state;
417}
418
419KeyboardModifier EmulatedDevices::GetKeyboardModifier() const {
420 return device_status.keyboard_moddifier_state;
421}
422
423MouseButton EmulatedDevices::GetMouseButtons() const {
424 return device_status.mouse_button_state;
425}
426
427MousePosition EmulatedDevices::GetMousePosition() const {
428 return device_status.mouse_position_state;
429}
430
431AnalogStickState EmulatedDevices::GetMouseWheel() const {
432 return device_status.mouse_wheel_state;
433}
434
435void EmulatedDevices::TriggerOnChange(DeviceTriggerType type) {
436 for (const auto& poller_pair : callback_list) {
437 const InterfaceUpdateCallback& poller = poller_pair.second;
438 if (poller.on_change) {
439 poller.on_change(type);
440 }
441 }
442}
443
444int EmulatedDevices::SetCallback(InterfaceUpdateCallback update_callback) {
445 std::lock_guard lock{mutex};
446 callback_list.insert_or_assign(last_callback_key, std::move(update_callback));
447 return last_callback_key++;
448}
449
450void EmulatedDevices::DeleteCallback(int key) {
451 std::lock_guard lock{mutex};
452 const auto& iterator = callback_list.find(key);
453 if (iterator == callback_list.end()) {
454 LOG_ERROR(Input, "Tried to delete non-existent callback {}", key);
455 return;
456 }
457 callback_list.erase(iterator);
458}
459} // namespace Core::HID
diff --git a/src/core/hid/emulated_devices.h b/src/core/hid/emulated_devices.h
new file mode 100644
index 000000000..790d3b411
--- /dev/null
+++ b/src/core/hid/emulated_devices.h
@@ -0,0 +1,210 @@
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 <functional>
9#include <memory>
10#include <mutex>
11#include <unordered_map>
12
13#include "common/common_types.h"
14#include "common/input.h"
15#include "common/param_package.h"
16#include "common/settings.h"
17#include "core/hid/hid_types.h"
18
19namespace Core::HID {
20using KeyboardDevices = std::array<std::unique_ptr<Common::Input::InputDevice>,
21 Settings::NativeKeyboard::NumKeyboardKeys>;
22using KeyboardModifierDevices = std::array<std::unique_ptr<Common::Input::InputDevice>,
23 Settings::NativeKeyboard::NumKeyboardMods>;
24using MouseButtonDevices = std::array<std::unique_ptr<Common::Input::InputDevice>,
25 Settings::NativeMouseButton::NumMouseButtons>;
26using MouseAnalogDevices = std::array<std::unique_ptr<Common::Input::InputDevice>,
27 Settings::NativeMouseWheel::NumMouseWheels>;
28using MouseStickDevice = std::unique_ptr<Common::Input::InputDevice>;
29
30using MouseButtonParams =
31 std::array<Common::ParamPackage, Settings::NativeMouseButton::NumMouseButtons>;
32
33using KeyboardValues =
34 std::array<Common::Input::ButtonStatus, Settings::NativeKeyboard::NumKeyboardKeys>;
35using KeyboardModifierValues =
36 std::array<Common::Input::ButtonStatus, Settings::NativeKeyboard::NumKeyboardMods>;
37using MouseButtonValues =
38 std::array<Common::Input::ButtonStatus, Settings::NativeMouseButton::NumMouseButtons>;
39using MouseAnalogValues =
40 std::array<Common::Input::AnalogStatus, Settings::NativeMouseWheel::NumMouseWheels>;
41using MouseStickValue = Common::Input::TouchStatus;
42
43struct MousePosition {
44 f32 x;
45 f32 y;
46};
47
48struct DeviceStatus {
49 // Data from input_common
50 KeyboardValues keyboard_values{};
51 KeyboardModifierValues keyboard_moddifier_values{};
52 MouseButtonValues mouse_button_values{};
53 MouseAnalogValues mouse_analog_values{};
54 MouseStickValue mouse_stick_value{};
55
56 // Data for HID serices
57 KeyboardKey keyboard_state{};
58 KeyboardModifier keyboard_moddifier_state{};
59 MouseButton mouse_button_state{};
60 MousePosition mouse_position_state{};
61 AnalogStickState mouse_wheel_state{};
62};
63
64enum class DeviceTriggerType {
65 Keyboard,
66 KeyboardModdifier,
67 Mouse,
68};
69
70struct InterfaceUpdateCallback {
71 std::function<void(DeviceTriggerType)> on_change;
72};
73
74class EmulatedDevices {
75public:
76 /**
77 * Contains all input data related to external devices that aren't necesarily a controller
78 * This includes devices such as the keyboard or mouse
79 */
80 explicit EmulatedDevices();
81 ~EmulatedDevices();
82
83 YUZU_NON_COPYABLE(EmulatedDevices);
84 YUZU_NON_MOVEABLE(EmulatedDevices);
85
86 /// Removes all callbacks created from input devices
87 void UnloadInput();
88
89 /**
90 * Sets the emulated devices into configuring mode
91 * This prevents the modification of the HID state of the emulated devices by input commands
92 */
93 void EnableConfiguration();
94
95 /// Returns the emulated devices into normal mode, allowing the modification of the HID state
96 void DisableConfiguration();
97
98 /// Returns true if the emulated device is in configuring mode
99 bool IsConfiguring() const;
100
101 /// Reload all input devices
102 void ReloadInput();
103
104 /// Overrides current mapped devices with the stored configuration and reloads all input devices
105 void ReloadFromSettings();
106
107 /// Saves the current mapped configuration
108 void SaveCurrentConfig();
109
110 /// Reverts any mapped changes made that weren't saved
111 void RestoreConfig();
112
113 /// Returns the latest status of button input from the keyboard with parameters
114 KeyboardValues GetKeyboardValues() const;
115
116 /// Returns the latest status of button input from the keyboard modifiers with parameters
117 KeyboardModifierValues GetKeyboardModdifierValues() const;
118
119 /// Returns the latest status of button input from the mouse with parameters
120 MouseButtonValues GetMouseButtonsValues() const;
121
122 /// Returns the latest status of button input from the keyboard
123 KeyboardKey GetKeyboard() const;
124
125 /// Returns the latest status of button input from the keyboard modifiers
126 KeyboardModifier GetKeyboardModifier() const;
127
128 /// Returns the latest status of button input from the mouse
129 MouseButton GetMouseButtons() const;
130
131 /// Returns the latest mouse coordinates
132 MousePosition GetMousePosition() const;
133
134 /// Returns the latest mouse wheel change
135 AnalogStickState GetMouseWheel() const;
136
137 /**
138 * Adds a callback to the list of events
139 * @param update_callback InterfaceUpdateCallback that will be triggered
140 * @return an unique key corresponding to the callback index in the list
141 */
142 int SetCallback(InterfaceUpdateCallback update_callback);
143
144 /**
145 * Removes a callback from the list stopping any future events to this object
146 * @param key Key corresponding to the callback index in the list
147 */
148 void DeleteCallback(int key);
149
150private:
151 /// Helps assigning a value to keyboard_state
152 void UpdateKey(std::size_t key_index, bool status);
153
154 /**
155 * Updates the touch status of the keyboard device
156 * @param callback A CallbackStatus containing the key status
157 * @param index key ID to be updated
158 */
159 void SetKeyboardButton(const Common::Input::CallbackStatus& callback, std::size_t index);
160
161 /**
162 * Updates the keyboard status of the keyboard device
163 * @param callback A CallbackStatus containing the modifier key status
164 * @param index modifier key ID to be updated
165 */
166 void SetKeyboardModifier(const Common::Input::CallbackStatus& callback, std::size_t index);
167
168 /**
169 * Updates the mouse button status of the mouse device
170 * @param callback A CallbackStatus containing the button status
171 * @param index Button ID to be updated
172 */
173 void SetMouseButton(const Common::Input::CallbackStatus& callback, std::size_t index);
174
175 /**
176 * Updates the mouse wheel status of the mouse device
177 * @param callback A CallbackStatus containing the wheel status
178 * @param index wheel ID to be updated
179 */
180 void SetMouseAnalog(const Common::Input::CallbackStatus& callback, std::size_t index);
181
182 /**
183 * Updates the mouse position status of the mouse device
184 * @param callback A CallbackStatus containing the position status
185 */
186 void SetMouseStick(const Common::Input::CallbackStatus& callback);
187
188 /**
189 * Triggers a callback that something has changed on the device status
190 * @param type Input type of the event to trigger
191 */
192 void TriggerOnChange(DeviceTriggerType type);
193
194 bool is_configuring{false};
195
196 KeyboardDevices keyboard_devices;
197 KeyboardModifierDevices keyboard_modifier_devices;
198 MouseButtonDevices mouse_button_devices;
199 MouseAnalogDevices mouse_analog_devices;
200 MouseStickDevice mouse_stick_device;
201
202 mutable std::mutex mutex;
203 std::unordered_map<int, InterfaceUpdateCallback> callback_list;
204 int last_callback_key = 0;
205
206 // Stores the current status of all external device input
207 DeviceStatus device_status;
208};
209
210} // namespace Core::HID
diff --git a/src/core/hid/hid_core.cpp b/src/core/hid/hid_core.cpp
new file mode 100644
index 000000000..a1c3bbb57
--- /dev/null
+++ b/src/core/hid/hid_core.cpp
@@ -0,0 +1,214 @@
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/assert.h"
6#include "core/hid/emulated_console.h"
7#include "core/hid/emulated_controller.h"
8#include "core/hid/emulated_devices.h"
9#include "core/hid/hid_core.h"
10
11namespace Core::HID {
12
13HIDCore::HIDCore()
14 : player_1{std::make_unique<EmulatedController>(NpadIdType::Player1)},
15 player_2{std::make_unique<EmulatedController>(NpadIdType::Player2)},
16 player_3{std::make_unique<EmulatedController>(NpadIdType::Player3)},
17 player_4{std::make_unique<EmulatedController>(NpadIdType::Player4)},
18 player_5{std::make_unique<EmulatedController>(NpadIdType::Player5)},
19 player_6{std::make_unique<EmulatedController>(NpadIdType::Player6)},
20 player_7{std::make_unique<EmulatedController>(NpadIdType::Player7)},
21 player_8{std::make_unique<EmulatedController>(NpadIdType::Player8)},
22 other{std::make_unique<EmulatedController>(NpadIdType::Other)},
23 handheld{std::make_unique<EmulatedController>(NpadIdType::Handheld)},
24 console{std::make_unique<EmulatedConsole>()}, devices{std::make_unique<EmulatedDevices>()} {}
25
26HIDCore::~HIDCore() = default;
27
28EmulatedController* HIDCore::GetEmulatedController(NpadIdType npad_id_type) {
29 switch (npad_id_type) {
30 case NpadIdType::Player1:
31 return player_1.get();
32 case NpadIdType::Player2:
33 return player_2.get();
34 case NpadIdType::Player3:
35 return player_3.get();
36 case NpadIdType::Player4:
37 return player_4.get();
38 case NpadIdType::Player5:
39 return player_5.get();
40 case NpadIdType::Player6:
41 return player_6.get();
42 case NpadIdType::Player7:
43 return player_7.get();
44 case NpadIdType::Player8:
45 return player_8.get();
46 case NpadIdType::Other:
47 return other.get();
48 case NpadIdType::Handheld:
49 return handheld.get();
50 case NpadIdType::Invalid:
51 default:
52 UNREACHABLE_MSG("Invalid NpadIdType={}", npad_id_type);
53 return nullptr;
54 }
55}
56
57const EmulatedController* HIDCore::GetEmulatedController(NpadIdType npad_id_type) const {
58 switch (npad_id_type) {
59 case NpadIdType::Player1:
60 return player_1.get();
61 case NpadIdType::Player2:
62 return player_2.get();
63 case NpadIdType::Player3:
64 return player_3.get();
65 case NpadIdType::Player4:
66 return player_4.get();
67 case NpadIdType::Player5:
68 return player_5.get();
69 case NpadIdType::Player6:
70 return player_6.get();
71 case NpadIdType::Player7:
72 return player_7.get();
73 case NpadIdType::Player8:
74 return player_8.get();
75 case NpadIdType::Other:
76 return other.get();
77 case NpadIdType::Handheld:
78 return handheld.get();
79 case NpadIdType::Invalid:
80 default:
81 UNREACHABLE_MSG("Invalid NpadIdType={}", npad_id_type);
82 return nullptr;
83 }
84}
85EmulatedConsole* HIDCore::GetEmulatedConsole() {
86 return console.get();
87}
88
89const EmulatedConsole* HIDCore::GetEmulatedConsole() const {
90 return console.get();
91}
92
93EmulatedDevices* HIDCore::GetEmulatedDevices() {
94 return devices.get();
95}
96
97const EmulatedDevices* HIDCore::GetEmulatedDevices() const {
98 return devices.get();
99}
100
101EmulatedController* HIDCore::GetEmulatedControllerByIndex(std::size_t index) {
102 return GetEmulatedController(IndexToNpadIdType(index));
103}
104
105const EmulatedController* HIDCore::GetEmulatedControllerByIndex(std::size_t index) const {
106 return GetEmulatedController(IndexToNpadIdType(index));
107}
108
109void HIDCore::SetSupportedStyleTag(NpadStyleTag style_tag) {
110 supported_style_tag.raw = style_tag.raw;
111 player_1->SetSupportedNpadStyleTag(supported_style_tag);
112 player_2->SetSupportedNpadStyleTag(supported_style_tag);
113 player_3->SetSupportedNpadStyleTag(supported_style_tag);
114 player_4->SetSupportedNpadStyleTag(supported_style_tag);
115 player_5->SetSupportedNpadStyleTag(supported_style_tag);
116 player_6->SetSupportedNpadStyleTag(supported_style_tag);
117 player_7->SetSupportedNpadStyleTag(supported_style_tag);
118 player_8->SetSupportedNpadStyleTag(supported_style_tag);
119 other->SetSupportedNpadStyleTag(supported_style_tag);
120 handheld->SetSupportedNpadStyleTag(supported_style_tag);
121}
122
123NpadStyleTag HIDCore::GetSupportedStyleTag() const {
124 return supported_style_tag;
125}
126
127s8 HIDCore::GetPlayerCount() const {
128 s8 active_players = 0;
129 for (std::size_t player_index = 0; player_index < available_controllers - 2; ++player_index) {
130 const auto* const controller = GetEmulatedControllerByIndex(player_index);
131 if (controller->IsConnected()) {
132 active_players++;
133 }
134 }
135 return active_players;
136}
137
138NpadIdType HIDCore::GetFirstNpadId() const {
139 for (std::size_t player_index = 0; player_index < available_controllers; ++player_index) {
140 const auto* const controller = GetEmulatedControllerByIndex(player_index);
141 if (controller->IsConnected()) {
142 return controller->GetNpadIdType();
143 }
144 }
145 return NpadIdType::Player1;
146}
147
148NpadIdType HIDCore::GetFirstDisconnectedNpadId() const {
149 for (std::size_t player_index = 0; player_index < available_controllers; ++player_index) {
150 const auto* const controller = GetEmulatedControllerByIndex(player_index);
151 if (!controller->IsConnected()) {
152 return controller->GetNpadIdType();
153 }
154 }
155 return NpadIdType::Player1;
156}
157
158void HIDCore::EnableAllControllerConfiguration() {
159 player_1->EnableConfiguration();
160 player_2->EnableConfiguration();
161 player_3->EnableConfiguration();
162 player_4->EnableConfiguration();
163 player_5->EnableConfiguration();
164 player_6->EnableConfiguration();
165 player_7->EnableConfiguration();
166 player_8->EnableConfiguration();
167 other->EnableConfiguration();
168 handheld->EnableConfiguration();
169}
170
171void HIDCore::DisableAllControllerConfiguration() {
172 player_1->DisableConfiguration();
173 player_2->DisableConfiguration();
174 player_3->DisableConfiguration();
175 player_4->DisableConfiguration();
176 player_5->DisableConfiguration();
177 player_6->DisableConfiguration();
178 player_7->DisableConfiguration();
179 player_8->DisableConfiguration();
180 other->DisableConfiguration();
181 handheld->DisableConfiguration();
182}
183
184void HIDCore::ReloadInputDevices() {
185 player_1->ReloadFromSettings();
186 player_2->ReloadFromSettings();
187 player_3->ReloadFromSettings();
188 player_4->ReloadFromSettings();
189 player_5->ReloadFromSettings();
190 player_6->ReloadFromSettings();
191 player_7->ReloadFromSettings();
192 player_8->ReloadFromSettings();
193 other->ReloadFromSettings();
194 handheld->ReloadFromSettings();
195 console->ReloadFromSettings();
196 devices->ReloadFromSettings();
197}
198
199void HIDCore::UnloadInputDevices() {
200 player_1->UnloadInput();
201 player_2->UnloadInput();
202 player_3->UnloadInput();
203 player_4->UnloadInput();
204 player_5->UnloadInput();
205 player_6->UnloadInput();
206 player_7->UnloadInput();
207 player_8->UnloadInput();
208 other->UnloadInput();
209 handheld->UnloadInput();
210 console->UnloadInput();
211 devices->UnloadInput();
212}
213
214} // namespace Core::HID
diff --git a/src/core/hid/hid_core.h b/src/core/hid/hid_core.h
new file mode 100644
index 000000000..837f7de49
--- /dev/null
+++ b/src/core/hid/hid_core.h
@@ -0,0 +1,82 @@
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 <memory>
8
9#include "core/hid/hid_types.h"
10
11namespace Core::HID {
12class EmulatedConsole;
13class EmulatedController;
14class EmulatedDevices;
15} // namespace Core::HID
16
17namespace Core::HID {
18
19class HIDCore {
20public:
21 explicit HIDCore();
22 ~HIDCore();
23
24 YUZU_NON_COPYABLE(HIDCore);
25 YUZU_NON_MOVEABLE(HIDCore);
26
27 EmulatedController* GetEmulatedController(NpadIdType npad_id_type);
28 const EmulatedController* GetEmulatedController(NpadIdType npad_id_type) const;
29
30 EmulatedController* GetEmulatedControllerByIndex(std::size_t index);
31 const EmulatedController* GetEmulatedControllerByIndex(std::size_t index) const;
32
33 EmulatedConsole* GetEmulatedConsole();
34 const EmulatedConsole* GetEmulatedConsole() const;
35
36 EmulatedDevices* GetEmulatedDevices();
37 const EmulatedDevices* GetEmulatedDevices() const;
38
39 void SetSupportedStyleTag(NpadStyleTag style_tag);
40 NpadStyleTag GetSupportedStyleTag() const;
41
42 /// Counts the connected players from P1-P8
43 s8 GetPlayerCount() const;
44
45 /// Returns the first connected npad id
46 NpadIdType GetFirstNpadId() const;
47
48 /// Returns the first disconnected npad id
49 NpadIdType GetFirstDisconnectedNpadId() const;
50
51 /// Sets all emulated controllers into configuring mode.
52 void EnableAllControllerConfiguration();
53
54 /// Sets all emulated controllers into normal mode.
55 void DisableAllControllerConfiguration();
56
57 /// Reloads all input devices from settings
58 void ReloadInputDevices();
59
60 /// Removes all callbacks from input common
61 void UnloadInputDevices();
62
63 /// Number of emulated controllers
64 static constexpr std::size_t available_controllers{10};
65
66private:
67 std::unique_ptr<EmulatedController> player_1;
68 std::unique_ptr<EmulatedController> player_2;
69 std::unique_ptr<EmulatedController> player_3;
70 std::unique_ptr<EmulatedController> player_4;
71 std::unique_ptr<EmulatedController> player_5;
72 std::unique_ptr<EmulatedController> player_6;
73 std::unique_ptr<EmulatedController> player_7;
74 std::unique_ptr<EmulatedController> player_8;
75 std::unique_ptr<EmulatedController> other;
76 std::unique_ptr<EmulatedController> handheld;
77 std::unique_ptr<EmulatedConsole> console;
78 std::unique_ptr<EmulatedDevices> devices;
79 NpadStyleTag supported_style_tag{NpadStyleSet::All};
80};
81
82} // namespace Core::HID
diff --git a/src/core/hid/hid_types.h b/src/core/hid/hid_types.h
new file mode 100644
index 000000000..7c12f01fc
--- /dev/null
+++ b/src/core/hid/hid_types.h
@@ -0,0 +1,635 @@
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/bit_field.h"
8#include "common/common_funcs.h"
9#include "common/common_types.h"
10#include "common/point.h"
11#include "common/uuid.h"
12
13namespace Core::HID {
14
15enum class DeviceIndex : u8 {
16 Left = 0,
17 Right = 1,
18 None = 2,
19 MaxDeviceIndex = 3,
20};
21
22// This is nn::hid::NpadButton
23enum class NpadButton : u64 {
24 None = 0,
25 A = 1U << 0,
26 B = 1U << 1,
27 X = 1U << 2,
28 Y = 1U << 3,
29 StickL = 1U << 4,
30 StickR = 1U << 5,
31 L = 1U << 6,
32 R = 1U << 7,
33 ZL = 1U << 8,
34 ZR = 1U << 9,
35 Plus = 1U << 10,
36 Minus = 1U << 11,
37
38 Left = 1U << 12,
39 Up = 1U << 13,
40 Right = 1U << 14,
41 Down = 1U << 15,
42
43 StickLLeft = 1U << 16,
44 StickLUp = 1U << 17,
45 StickLRight = 1U << 18,
46 StickLDown = 1U << 19,
47
48 StickRLeft = 1U << 20,
49 StickRUp = 1U << 21,
50 StickRRight = 1U << 22,
51 StickRDown = 1U << 23,
52
53 LeftSL = 1U << 24,
54 LeftSR = 1U << 25,
55
56 RightSL = 1U << 26,
57 RightSR = 1U << 27,
58
59 Palma = 1U << 28,
60 Verification = 1U << 29,
61 HandheldLeftB = 1U << 30,
62 LagonCLeft = 1U << 31,
63 LagonCUp = 1ULL << 32,
64 LagonCRight = 1ULL << 33,
65 LagonCDown = 1ULL << 34,
66
67 All = 0xFFFFFFFFFFFFFFFFULL,
68};
69DECLARE_ENUM_FLAG_OPERATORS(NpadButton);
70
71enum class KeyboardKeyIndex : u32 {
72 A = 4,
73 B = 5,
74 C = 6,
75 D = 7,
76 E = 8,
77 F = 9,
78 G = 10,
79 H = 11,
80 I = 12,
81 J = 13,
82 K = 14,
83 L = 15,
84 M = 16,
85 N = 17,
86 O = 18,
87 P = 19,
88 Q = 20,
89 R = 21,
90 S = 22,
91 T = 23,
92 U = 24,
93 V = 25,
94 W = 26,
95 X = 27,
96 Y = 28,
97 Z = 29,
98 D1 = 30,
99 D2 = 31,
100 D3 = 32,
101 D4 = 33,
102 D5 = 34,
103 D6 = 35,
104 D7 = 36,
105 D8 = 37,
106 D9 = 38,
107 D0 = 39,
108 Return = 40,
109 Escape = 41,
110 Backspace = 42,
111 Tab = 43,
112 Space = 44,
113 Minus = 45,
114 Plus = 46,
115 OpenBracket = 47,
116 CloseBracket = 48,
117 Pipe = 49,
118 Tilde = 50,
119 Semicolon = 51,
120 Quote = 52,
121 Backquote = 53,
122 Comma = 54,
123 Period = 55,
124 Slash = 56,
125 CapsLock = 57,
126 F1 = 58,
127 F2 = 59,
128 F3 = 60,
129 F4 = 61,
130 F5 = 62,
131 F6 = 63,
132 F7 = 64,
133 F8 = 65,
134 F9 = 66,
135 F10 = 67,
136 F11 = 68,
137 F12 = 69,
138 PrintScreen = 70,
139 ScrollLock = 71,
140 Pause = 72,
141 Insert = 73,
142 Home = 74,
143 PageUp = 75,
144 Delete = 76,
145 End = 77,
146 PageDown = 78,
147 RightArrow = 79,
148 LeftArrow = 80,
149 DownArrow = 81,
150 UpArrow = 82,
151 NumLock = 83,
152 NumPadDivide = 84,
153 NumPadMultiply = 85,
154 NumPadSubtract = 86,
155 NumPadAdd = 87,
156 NumPadEnter = 88,
157 NumPad1 = 89,
158 NumPad2 = 90,
159 NumPad3 = 91,
160 NumPad4 = 92,
161 NumPad5 = 93,
162 NumPad6 = 94,
163 NumPad7 = 95,
164 NumPad8 = 96,
165 NumPad9 = 97,
166 NumPad0 = 98,
167 NumPadDot = 99,
168 Backslash = 100,
169 Application = 101,
170 Power = 102,
171 NumPadEquals = 103,
172 F13 = 104,
173 F14 = 105,
174 F15 = 106,
175 F16 = 107,
176 F17 = 108,
177 F18 = 109,
178 F19 = 110,
179 F20 = 111,
180 F21 = 112,
181 F22 = 113,
182 F23 = 114,
183 F24 = 115,
184 NumPadComma = 133,
185 Ro = 135,
186 KatakanaHiragana = 136,
187 Yen = 137,
188 Henkan = 138,
189 Muhenkan = 139,
190 NumPadCommaPc98 = 140,
191 HangulEnglish = 144,
192 Hanja = 145,
193 Katakana = 146,
194 Hiragana = 147,
195 ZenkakuHankaku = 148,
196 LeftControl = 224,
197 LeftShift = 225,
198 LeftAlt = 226,
199 LeftGui = 227,
200 RightControl = 228,
201 RightShift = 229,
202 RightAlt = 230,
203 RightGui = 231,
204};
205
206// This is nn::hid::NpadIdType
207enum class NpadIdType : u32 {
208 Player1 = 0x0,
209 Player2 = 0x1,
210 Player3 = 0x2,
211 Player4 = 0x3,
212 Player5 = 0x4,
213 Player6 = 0x5,
214 Player7 = 0x6,
215 Player8 = 0x7,
216 Other = 0x10,
217 Handheld = 0x20,
218
219 Invalid = 0xFFFFFFFF,
220};
221
222// This is nn::hid::NpadStyleIndex
223enum class NpadStyleIndex : u8 {
224 None = 0,
225 ProController = 3,
226 Handheld = 4,
227 HandheldNES = 4,
228 JoyconDual = 5,
229 JoyconLeft = 6,
230 JoyconRight = 7,
231 GameCube = 8,
232 Pokeball = 9,
233 NES = 10,
234 SNES = 12,
235 N64 = 13,
236 SegaGenesis = 14,
237 SystemExt = 32,
238 System = 33,
239 MaxNpadType = 34,
240};
241
242// This is nn::hid::NpadStyleSet
243enum class NpadStyleSet : u32 {
244 None = 0,
245 Fullkey = 1U << 0,
246 Handheld = 1U << 1,
247 JoyDual = 1U << 2,
248 JoyLeft = 1U << 3,
249 JoyRight = 1U << 4,
250 Gc = 1U << 5,
251 Palma = 1U << 6,
252 Lark = 1U << 7,
253 HandheldLark = 1U << 8,
254 Lucia = 1U << 9,
255 Lagoon = 1U << 10,
256 Lager = 1U << 11,
257 SystemExt = 1U << 29,
258 System = 1U << 30,
259
260 All = 0xFFFFFFFFU,
261};
262static_assert(sizeof(NpadStyleSet) == 4, "NpadStyleSet is an invalid size");
263
264// This is nn::hid::VibrationDevicePosition
265enum class VibrationDevicePosition : u32 {
266 None = 0,
267 Left = 1,
268 Right = 2,
269};
270
271// This is nn::hid::VibrationDeviceType
272enum class VibrationDeviceType : u32 {
273 Unknown = 0,
274 LinearResonantActuator = 1,
275 GcErm = 2,
276};
277
278// This is nn::hid::VibrationGcErmCommand
279enum class VibrationGcErmCommand : u64 {
280 Stop = 0,
281 Start = 1,
282 StopHard = 2,
283};
284
285// This is nn::hid::NpadStyleTag
286struct NpadStyleTag {
287 union {
288 NpadStyleSet raw{};
289
290 BitField<0, 1, u32> fullkey;
291 BitField<1, 1, u32> handheld;
292 BitField<2, 1, u32> joycon_dual;
293 BitField<3, 1, u32> joycon_left;
294 BitField<4, 1, u32> joycon_right;
295 BitField<5, 1, u32> gamecube;
296 BitField<6, 1, u32> palma;
297 BitField<7, 1, u32> lark;
298 BitField<8, 1, u32> handheld_lark;
299 BitField<9, 1, u32> lucia;
300 BitField<10, 1, u32> lagoon;
301 BitField<11, 1, u32> lager;
302 BitField<29, 1, u32> system_ext;
303 BitField<30, 1, u32> system;
304 };
305};
306static_assert(sizeof(NpadStyleTag) == 4, "NpadStyleTag is an invalid size");
307
308// This is nn::hid::TouchAttribute
309struct TouchAttribute {
310 union {
311 u32 raw{};
312 BitField<0, 1, u32> start_touch;
313 BitField<1, 1, u32> end_touch;
314 };
315};
316static_assert(sizeof(TouchAttribute) == 0x4, "TouchAttribute is an invalid size");
317
318// This is nn::hid::TouchState
319struct TouchState {
320 u64 delta_time;
321 TouchAttribute attribute;
322 u32 finger;
323 Common::Point<u32> position;
324 u32 diameter_x;
325 u32 diameter_y;
326 u32 rotation_angle;
327};
328static_assert(sizeof(TouchState) == 0x28, "Touchstate is an invalid size");
329
330// This is nn::hid::NpadControllerColor
331struct NpadControllerColor {
332 u32 body;
333 u32 button;
334};
335static_assert(sizeof(NpadControllerColor) == 8, "NpadControllerColor is an invalid size");
336
337// This is nn::hid::AnalogStickState
338struct AnalogStickState {
339 s32 x;
340 s32 y;
341};
342static_assert(sizeof(AnalogStickState) == 8, "AnalogStickState is an invalid size");
343
344// This is nn::hid::server::NpadGcTriggerState
345struct NpadGcTriggerState {
346 s64 sampling_number{};
347 s32 left{};
348 s32 right{};
349};
350static_assert(sizeof(NpadGcTriggerState) == 0x10, "NpadGcTriggerState is an invalid size");
351
352// This is nn::hid::system::NpadBatteryLevel
353using NpadBatteryLevel = u32;
354static_assert(sizeof(NpadBatteryLevel) == 0x4, "NpadBatteryLevel is an invalid size");
355
356// This is nn::hid::system::NpadPowerInfo
357struct NpadPowerInfo {
358 bool is_powered;
359 bool is_charging;
360 INSERT_PADDING_BYTES(0x6);
361 NpadBatteryLevel battery_level;
362};
363static_assert(sizeof(NpadPowerInfo) == 0xC, "NpadPowerInfo is an invalid size");
364
365struct LedPattern {
366 explicit LedPattern(u64 light1, u64 light2, u64 light3, u64 light4) {
367 position1.Assign(light1);
368 position2.Assign(light2);
369 position3.Assign(light3);
370 position4.Assign(light4);
371 }
372 union {
373 u64 raw{};
374 BitField<0, 1, u64> position1;
375 BitField<1, 1, u64> position2;
376 BitField<2, 1, u64> position3;
377 BitField<3, 1, u64> position4;
378 };
379};
380
381struct NpadButtonState {
382 union {
383 NpadButton raw{};
384
385 // Buttons
386 BitField<0, 1, u64> a;
387 BitField<1, 1, u64> b;
388 BitField<2, 1, u64> x;
389 BitField<3, 1, u64> y;
390 BitField<4, 1, u64> stick_l;
391 BitField<5, 1, u64> stick_r;
392 BitField<6, 1, u64> l;
393 BitField<7, 1, u64> r;
394 BitField<8, 1, u64> zl;
395 BitField<9, 1, u64> zr;
396 BitField<10, 1, u64> plus;
397 BitField<11, 1, u64> minus;
398
399 // D-Pad
400 BitField<12, 1, u64> left;
401 BitField<13, 1, u64> up;
402 BitField<14, 1, u64> right;
403 BitField<15, 1, u64> down;
404
405 // Left JoyStick
406 BitField<16, 1, u64> stick_l_left;
407 BitField<17, 1, u64> stick_l_up;
408 BitField<18, 1, u64> stick_l_right;
409 BitField<19, 1, u64> stick_l_down;
410
411 // Right JoyStick
412 BitField<20, 1, u64> stick_r_left;
413 BitField<21, 1, u64> stick_r_up;
414 BitField<22, 1, u64> stick_r_right;
415 BitField<23, 1, u64> stick_r_down;
416
417 BitField<24, 1, u64> left_sl;
418 BitField<25, 1, u64> left_sr;
419
420 BitField<26, 1, u64> right_sl;
421 BitField<27, 1, u64> right_sr;
422
423 BitField<28, 1, u64> palma;
424 BitField<29, 1, u64> verification;
425 BitField<30, 1, u64> handheld_left_b;
426 BitField<31, 1, u64> lagon_c_left;
427 BitField<32, 1, u64> lagon_c_up;
428 BitField<33, 1, u64> lagon_c_right;
429 BitField<34, 1, u64> lagon_c_down;
430 };
431};
432static_assert(sizeof(NpadButtonState) == 0x8, "NpadButtonState has incorrect size.");
433
434// This is nn::hid::DebugPadButton
435struct DebugPadButton {
436 union {
437 u32 raw{};
438 BitField<0, 1, u32> a;
439 BitField<1, 1, u32> b;
440 BitField<2, 1, u32> x;
441 BitField<3, 1, u32> y;
442 BitField<4, 1, u32> l;
443 BitField<5, 1, u32> r;
444 BitField<6, 1, u32> zl;
445 BitField<7, 1, u32> zr;
446 BitField<8, 1, u32> plus;
447 BitField<9, 1, u32> minus;
448 BitField<10, 1, u32> d_left;
449 BitField<11, 1, u32> d_up;
450 BitField<12, 1, u32> d_right;
451 BitField<13, 1, u32> d_down;
452 };
453};
454static_assert(sizeof(DebugPadButton) == 0x4, "DebugPadButton is an invalid size");
455
456// This is nn::hid::ConsoleSixAxisSensorHandle
457struct ConsoleSixAxisSensorHandle {
458 u8 unknown_1;
459 u8 unknown_2;
460 INSERT_PADDING_BYTES_NOINIT(2);
461};
462static_assert(sizeof(ConsoleSixAxisSensorHandle) == 4,
463 "ConsoleSixAxisSensorHandle is an invalid size");
464
465// This is nn::hid::SixAxisSensorHandle
466struct SixAxisSensorHandle {
467 NpadStyleIndex npad_type;
468 u8 npad_id;
469 DeviceIndex device_index;
470 INSERT_PADDING_BYTES_NOINIT(1);
471};
472static_assert(sizeof(SixAxisSensorHandle) == 4, "SixAxisSensorHandle is an invalid size");
473
474struct SixAxisSensorFusionParameters {
475 f32 parameter1;
476 f32 parameter2;
477};
478static_assert(sizeof(SixAxisSensorFusionParameters) == 8,
479 "SixAxisSensorFusionParameters is an invalid size");
480
481// This is nn::hid::VibrationDeviceHandle
482struct VibrationDeviceHandle {
483 NpadStyleIndex npad_type;
484 u8 npad_id;
485 DeviceIndex device_index;
486 INSERT_PADDING_BYTES_NOINIT(1);
487};
488static_assert(sizeof(VibrationDeviceHandle) == 4, "SixAxisSensorHandle is an invalid size");
489
490// This is nn::hid::VibrationValue
491struct VibrationValue {
492 f32 low_amplitude;
493 f32 low_frequency;
494 f32 high_amplitude;
495 f32 high_frequency;
496};
497static_assert(sizeof(VibrationValue) == 0x10, "VibrationValue has incorrect size.");
498
499// This is nn::hid::VibrationDeviceInfo
500struct VibrationDeviceInfo {
501 VibrationDeviceType type{};
502 VibrationDevicePosition position{};
503};
504static_assert(sizeof(VibrationDeviceInfo) == 0x8, "VibrationDeviceInfo has incorrect size.");
505
506// This is nn::hid::KeyboardModifier
507struct KeyboardModifier {
508 union {
509 u32 raw{};
510 BitField<0, 1, u32> control;
511 BitField<1, 1, u32> shift;
512 BitField<2, 1, u32> left_alt;
513 BitField<3, 1, u32> right_alt;
514 BitField<4, 1, u32> gui;
515 BitField<8, 1, u32> caps_lock;
516 BitField<9, 1, u32> scroll_lock;
517 BitField<10, 1, u32> num_lock;
518 BitField<11, 1, u32> katakana;
519 BitField<12, 1, u32> hiragana;
520 };
521};
522
523static_assert(sizeof(KeyboardModifier) == 0x4, "KeyboardModifier is an invalid size");
524
525// This is nn::hid::KeyboardAttribute
526struct KeyboardAttribute {
527 union {
528 u32 raw{};
529 BitField<0, 1, u32> is_connected;
530 };
531};
532static_assert(sizeof(KeyboardAttribute) == 0x4, "KeyboardAttribute is an invalid size");
533
534// This is nn::hid::KeyboardKey
535struct KeyboardKey {
536 // This should be a 256 bit flag
537 std::array<u8, 32> key;
538};
539static_assert(sizeof(KeyboardKey) == 0x20, "KeyboardKey is an invalid size");
540
541// This is nn::hid::MouseButton
542struct MouseButton {
543 union {
544 u32_le raw{};
545 BitField<0, 1, u32> left;
546 BitField<1, 1, u32> right;
547 BitField<2, 1, u32> middle;
548 BitField<3, 1, u32> forward;
549 BitField<4, 1, u32> back;
550 };
551};
552static_assert(sizeof(MouseButton) == 0x4, "MouseButton is an invalid size");
553
554// This is nn::hid::MouseAttribute
555struct MouseAttribute {
556 union {
557 u32 raw{};
558 BitField<0, 1, u32> transferable;
559 BitField<1, 1, u32> is_connected;
560 };
561};
562static_assert(sizeof(MouseAttribute) == 0x4, "MouseAttribute is an invalid size");
563
564// This is nn::hid::detail::MouseState
565struct MouseState {
566 s64 sampling_number;
567 s32 x;
568 s32 y;
569 s32 delta_x;
570 s32 delta_y;
571 // Axis Order in HW is switched for the wheel
572 s32 delta_wheel_y;
573 s32 delta_wheel_x;
574 MouseButton button;
575 MouseAttribute attribute;
576};
577static_assert(sizeof(MouseState) == 0x28, "MouseState is an invalid size");
578
579/// Converts a NpadIdType to an array index.
580constexpr size_t NpadIdTypeToIndex(NpadIdType npad_id_type) {
581 switch (npad_id_type) {
582 case NpadIdType::Player1:
583 return 0;
584 case NpadIdType::Player2:
585 return 1;
586 case NpadIdType::Player3:
587 return 2;
588 case NpadIdType::Player4:
589 return 3;
590 case NpadIdType::Player5:
591 return 4;
592 case NpadIdType::Player6:
593 return 5;
594 case NpadIdType::Player7:
595 return 6;
596 case NpadIdType::Player8:
597 return 7;
598 case NpadIdType::Handheld:
599 return 8;
600 case NpadIdType::Other:
601 return 9;
602 default:
603 return 0;
604 }
605}
606
607/// Converts an array index to a NpadIdType
608constexpr NpadIdType IndexToNpadIdType(size_t index) {
609 switch (index) {
610 case 0:
611 return NpadIdType::Player1;
612 case 1:
613 return NpadIdType::Player2;
614 case 2:
615 return NpadIdType::Player3;
616 case 3:
617 return NpadIdType::Player4;
618 case 4:
619 return NpadIdType::Player5;
620 case 5:
621 return NpadIdType::Player6;
622 case 6:
623 return NpadIdType::Player7;
624 case 7:
625 return NpadIdType::Player8;
626 case 8:
627 return NpadIdType::Handheld;
628 case 9:
629 return NpadIdType::Other;
630 default:
631 return NpadIdType::Invalid;
632 }
633}
634
635} // namespace Core::HID
diff --git a/src/core/hid/input_converter.cpp b/src/core/hid/input_converter.cpp
new file mode 100644
index 000000000..f5acff6e0
--- /dev/null
+++ b/src/core/hid/input_converter.cpp
@@ -0,0 +1,383 @@
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 <random>
6
7#include "common/input.h"
8#include "core/hid/input_converter.h"
9
10namespace Core::HID {
11
12Common::Input::BatteryStatus TransformToBattery(const Common::Input::CallbackStatus& callback) {
13 Common::Input::BatteryStatus battery{Common::Input::BatteryStatus::None};
14 switch (callback.type) {
15 case Common::Input::InputType::Analog:
16 case Common::Input::InputType::Trigger: {
17 const auto value = TransformToTrigger(callback).analog.value;
18 battery = Common::Input::BatteryLevel::Empty;
19 if (value > 0.2f) {
20 battery = Common::Input::BatteryLevel::Critical;
21 }
22 if (value > 0.4f) {
23 battery = Common::Input::BatteryLevel::Low;
24 }
25 if (value > 0.6f) {
26 battery = Common::Input::BatteryLevel::Medium;
27 }
28 if (value > 0.8f) {
29 battery = Common::Input::BatteryLevel::Full;
30 }
31 if (value >= 1.0f) {
32 battery = Common::Input::BatteryLevel::Charging;
33 }
34 break;
35 }
36 case Common::Input::InputType::Button:
37 battery = callback.button_status.value ? Common::Input::BatteryLevel::Charging
38 : Common::Input::BatteryLevel::Critical;
39 break;
40 case Common::Input::InputType::Battery:
41 battery = callback.battery_status;
42 break;
43 default:
44 LOG_ERROR(Input, "Conversion from type {} to battery not implemented", callback.type);
45 break;
46 }
47
48 return battery;
49}
50
51Common::Input::ButtonStatus TransformToButton(const Common::Input::CallbackStatus& callback) {
52 Common::Input::ButtonStatus status{};
53 switch (callback.type) {
54 case Common::Input::InputType::Analog:
55 case Common::Input::InputType::Trigger:
56 status.value = TransformToTrigger(callback).pressed.value;
57 break;
58 case Common::Input::InputType::Button:
59 status = callback.button_status;
60 break;
61 default:
62 LOG_ERROR(Input, "Conversion from type {} to button not implemented", callback.type);
63 break;
64 }
65
66 if (status.inverted) {
67 status.value = !status.value;
68 }
69
70 return status;
71}
72
73Common::Input::MotionStatus TransformToMotion(const Common::Input::CallbackStatus& callback) {
74 Common::Input::MotionStatus status{};
75 switch (callback.type) {
76 case Common::Input::InputType::Button: {
77 Common::Input::AnalogProperties properties{
78 .deadzone = 0.0f,
79 .range = 1.0f,
80 .offset = 0.0f,
81 };
82 status.delta_timestamp = 5000;
83 status.force_update = true;
84 status.accel.x = {
85 .value = 0.0f,
86 .raw_value = 0.0f,
87 .properties = properties,
88 };
89 status.accel.y = {
90 .value = 0.0f,
91 .raw_value = 0.0f,
92 .properties = properties,
93 };
94 status.accel.z = {
95 .value = 0.0f,
96 .raw_value = -1.0f,
97 .properties = properties,
98 };
99 status.gyro.x = {
100 .value = 0.0f,
101 .raw_value = 0.0f,
102 .properties = properties,
103 };
104 status.gyro.y = {
105 .value = 0.0f,
106 .raw_value = 0.0f,
107 .properties = properties,
108 };
109 status.gyro.z = {
110 .value = 0.0f,
111 .raw_value = 0.0f,
112 .properties = properties,
113 };
114 if (TransformToButton(callback).value) {
115 std::random_device device;
116 std::mt19937 gen(device());
117 std::uniform_int_distribution<s16> distribution(-1000, 1000);
118 status.accel.x.raw_value = static_cast<f32>(distribution(gen)) * 0.001f;
119 status.accel.y.raw_value = static_cast<f32>(distribution(gen)) * 0.001f;
120 status.accel.z.raw_value = static_cast<f32>(distribution(gen)) * 0.001f;
121 status.gyro.x.raw_value = static_cast<f32>(distribution(gen)) * 0.001f;
122 status.gyro.y.raw_value = static_cast<f32>(distribution(gen)) * 0.001f;
123 status.gyro.z.raw_value = static_cast<f32>(distribution(gen)) * 0.001f;
124 }
125 break;
126 }
127 case Common::Input::InputType::Motion:
128 status = callback.motion_status;
129 break;
130 default:
131 LOG_ERROR(Input, "Conversion from type {} to motion not implemented", callback.type);
132 break;
133 }
134 SanitizeAnalog(status.accel.x, false);
135 SanitizeAnalog(status.accel.y, false);
136 SanitizeAnalog(status.accel.z, false);
137 SanitizeAnalog(status.gyro.x, false);
138 SanitizeAnalog(status.gyro.y, false);
139 SanitizeAnalog(status.gyro.z, false);
140
141 return status;
142}
143
144Common::Input::StickStatus TransformToStick(const Common::Input::CallbackStatus& callback) {
145 Common::Input::StickStatus status{};
146
147 switch (callback.type) {
148 case Common::Input::InputType::Stick:
149 status = callback.stick_status;
150 break;
151 default:
152 LOG_ERROR(Input, "Conversion from type {} to stick not implemented", callback.type);
153 break;
154 }
155
156 SanitizeStick(status.x, status.y, true);
157 const auto& properties_x = status.x.properties;
158 const auto& properties_y = status.y.properties;
159 const float x = status.x.value;
160 const float y = status.y.value;
161
162 // Set directional buttons
163 status.right = x > properties_x.threshold;
164 status.left = x < -properties_x.threshold;
165 status.up = y > properties_y.threshold;
166 status.down = y < -properties_y.threshold;
167
168 return status;
169}
170
171Common::Input::TouchStatus TransformToTouch(const Common::Input::CallbackStatus& callback) {
172 Common::Input::TouchStatus status{};
173
174 switch (callback.type) {
175 case Common::Input::InputType::Touch:
176 status = callback.touch_status;
177 break;
178 case Common::Input::InputType::Stick:
179 status.x = callback.stick_status.x;
180 status.y = callback.stick_status.y;
181 break;
182 default:
183 LOG_ERROR(Input, "Conversion from type {} to touch not implemented", callback.type);
184 break;
185 }
186
187 SanitizeAnalog(status.x, true);
188 SanitizeAnalog(status.y, true);
189 float& x = status.x.value;
190 float& y = status.y.value;
191
192 // Adjust if value is inverted
193 x = status.x.properties.inverted ? 1.0f + x : x;
194 y = status.y.properties.inverted ? 1.0f + y : y;
195
196 // clamp value
197 x = std::clamp(x, 0.0f, 1.0f);
198 y = std::clamp(y, 0.0f, 1.0f);
199
200 if (status.pressed.inverted) {
201 status.pressed.value = !status.pressed.value;
202 }
203
204 return status;
205}
206
207Common::Input::TriggerStatus TransformToTrigger(const Common::Input::CallbackStatus& callback) {
208 Common::Input::TriggerStatus status{};
209 float& raw_value = status.analog.raw_value;
210 bool calculate_button_value = true;
211
212 switch (callback.type) {
213 case Common::Input::InputType::Analog:
214 status.analog.properties = callback.analog_status.properties;
215 raw_value = callback.analog_status.raw_value;
216 break;
217 case Common::Input::InputType::Button:
218 status.analog.properties.range = 1.0f;
219 status.analog.properties.inverted = callback.button_status.inverted;
220 raw_value = callback.button_status.value ? 1.0f : 0.0f;
221 break;
222 case Common::Input::InputType::Trigger:
223 status = callback.trigger_status;
224 calculate_button_value = false;
225 break;
226 default:
227 LOG_ERROR(Input, "Conversion from type {} to trigger not implemented", callback.type);
228 break;
229 }
230
231 SanitizeAnalog(status.analog, true);
232 const auto& properties = status.analog.properties;
233 float& value = status.analog.value;
234
235 // Set button status
236 if (calculate_button_value) {
237 status.pressed.value = value > properties.threshold;
238 }
239
240 // Adjust if value is inverted
241 value = properties.inverted ? 1.0f + value : value;
242
243 // clamp value
244 value = std::clamp(value, 0.0f, 1.0f);
245
246 return status;
247}
248
249Common::Input::AnalogStatus TransformToAnalog(const Common::Input::CallbackStatus& callback) {
250 Common::Input::AnalogStatus status{};
251
252 switch (callback.type) {
253 case Common::Input::InputType::Analog:
254 status.properties = callback.analog_status.properties;
255 status.raw_value = callback.analog_status.raw_value;
256 break;
257 default:
258 LOG_ERROR(Input, "Conversion from type {} to analog not implemented", callback.type);
259 break;
260 }
261
262 SanitizeAnalog(status, false);
263
264 // Adjust if value is inverted
265 status.value = status.properties.inverted ? -status.value : status.value;
266
267 return status;
268}
269
270void SanitizeAnalog(Common::Input::AnalogStatus& analog, bool clamp_value) {
271 const auto& properties = analog.properties;
272 float& raw_value = analog.raw_value;
273 float& value = analog.value;
274
275 if (!std::isnormal(raw_value)) {
276 raw_value = 0;
277 }
278
279 // Apply center offset
280 raw_value -= properties.offset;
281
282 // Set initial values to be formated
283 value = raw_value;
284
285 // Calculate vector size
286 const float r = std::abs(value);
287
288 // Return zero if value is smaller than the deadzone
289 if (r <= properties.deadzone || properties.deadzone == 1.0f) {
290 analog.value = 0;
291 return;
292 }
293
294 // Adjust range of value
295 const float deadzone_factor =
296 1.0f / r * (r - properties.deadzone) / (1.0f - properties.deadzone);
297 value = value * deadzone_factor / properties.range;
298
299 // Invert direction if needed
300 if (properties.inverted) {
301 value = -value;
302 }
303
304 // Clamp value
305 if (clamp_value) {
306 value = std::clamp(value, -1.0f, 1.0f);
307 }
308}
309
310void SanitizeStick(Common::Input::AnalogStatus& analog_x, Common::Input::AnalogStatus& analog_y,
311 bool clamp_value) {
312 const auto& properties_x = analog_x.properties;
313 const auto& properties_y = analog_y.properties;
314 float& raw_x = analog_x.raw_value;
315 float& raw_y = analog_y.raw_value;
316 float& x = analog_x.value;
317 float& y = analog_y.value;
318
319 if (!std::isnormal(raw_x)) {
320 raw_x = 0;
321 }
322 if (!std::isnormal(raw_y)) {
323 raw_y = 0;
324 }
325
326 // Apply center offset
327 raw_x += properties_x.offset;
328 raw_y += properties_y.offset;
329
330 // Apply X scale correction from offset
331 if (std::abs(properties_x.offset) < 0.5f) {
332 if (raw_x > 0) {
333 raw_x /= 1 + properties_x.offset;
334 } else {
335 raw_x /= 1 - properties_x.offset;
336 }
337 }
338
339 // Apply Y scale correction from offset
340 if (std::abs(properties_y.offset) < 0.5f) {
341 if (raw_y > 0) {
342 raw_y /= 1 + properties_y.offset;
343 } else {
344 raw_y /= 1 - properties_y.offset;
345 }
346 }
347
348 // Invert direction if needed
349 raw_x = properties_x.inverted ? -raw_x : raw_x;
350 raw_y = properties_y.inverted ? -raw_y : raw_y;
351
352 // Set initial values to be formated
353 x = raw_x;
354 y = raw_y;
355
356 // Calculate vector size
357 float r = x * x + y * y;
358 r = std::sqrt(r);
359
360 // TODO(German77): Use deadzone and range of both axis
361
362 // Return zero if values are smaller than the deadzone
363 if (r <= properties_x.deadzone || properties_x.deadzone >= 1.0f) {
364 x = 0;
365 y = 0;
366 return;
367 }
368
369 // Adjust range of joystick
370 const float deadzone_factor =
371 1.0f / r * (r - properties_x.deadzone) / (1.0f - properties_x.deadzone);
372 x = x * deadzone_factor / properties_x.range;
373 y = y * deadzone_factor / properties_x.range;
374 r = r * deadzone_factor / properties_x.range;
375
376 // Normalize joystick
377 if (clamp_value && r > 1.0f) {
378 x /= r;
379 y /= r;
380 }
381}
382
383} // namespace Core::HID
diff --git a/src/core/hid/input_converter.h b/src/core/hid/input_converter.h
new file mode 100644
index 000000000..d24582226
--- /dev/null
+++ b/src/core/hid/input_converter.h
@@ -0,0 +1,96 @@
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
7namespace Common::Input {
8struct CallbackStatus;
9enum class BatteryLevel : u32;
10using BatteryStatus = BatteryLevel;
11struct AnalogStatus;
12struct ButtonStatus;
13struct MotionStatus;
14struct StickStatus;
15struct TouchStatus;
16struct TriggerStatus;
17}; // namespace Common::Input
18
19namespace Core::HID {
20
21/**
22 * Converts raw input data into a valid battery status.
23 *
24 * @param callback Supported callbacks: Analog, Battery, Trigger.
25 * @return A valid BatteryStatus object.
26 */
27Common::Input::BatteryStatus TransformToBattery(const Common::Input::CallbackStatus& callback);
28
29/**
30 * Converts raw input data into a valid button status. Applies invert properties to the output.
31 *
32 * @param callback Supported callbacks: Analog, Button, Trigger.
33 * @return A valid TouchStatus object.
34 */
35Common::Input::ButtonStatus TransformToButton(const Common::Input::CallbackStatus& callback);
36
37/**
38 * Converts raw input data into a valid motion status.
39 *
40 * @param callback Supported callbacks: Motion.
41 * @return A valid TouchStatus object.
42 */
43Common::Input::MotionStatus TransformToMotion(const Common::Input::CallbackStatus& callback);
44
45/**
46 * Converts raw input data into a valid stick status. Applies offset, deadzone, range and invert
47 * properties to the output.
48 *
49 * @param callback Supported callbacks: Stick.
50 * @return A valid StickStatus object.
51 */
52Common::Input::StickStatus TransformToStick(const Common::Input::CallbackStatus& callback);
53
54/**
55 * Converts raw input data into a valid touch status.
56 *
57 * @param callback Supported callbacks: Touch.
58 * @return A valid TouchStatus object.
59 */
60Common::Input::TouchStatus TransformToTouch(const Common::Input::CallbackStatus& callback);
61
62/**
63 * Converts raw input data into a valid trigger status. Applies offset, deadzone, range and
64 * invert properties to the output. Button status uses the threshold property if necessary.
65 *
66 * @param callback Supported callbacks: Analog, Button, Trigger.
67 * @return A valid TriggerStatus object.
68 */
69Common::Input::TriggerStatus TransformToTrigger(const Common::Input::CallbackStatus& callback);
70
71/**
72 * Converts raw input data into a valid analog status. Applies offset, deadzone, range and
73 * invert properties to the output.
74 *
75 * @param callback Supported callbacks: Analog.
76 * @return A valid AnalogStatus object.
77 */
78Common::Input::AnalogStatus TransformToAnalog(const Common::Input::CallbackStatus& callback);
79
80/**
81 * Converts raw analog data into a valid analog value
82 * @param analog An analog object containing raw data and properties
83 * @param clamp_value determines if the value needs to be clamped between -1.0f and 1.0f.
84 */
85void SanitizeAnalog(Common::Input::AnalogStatus& analog, bool clamp_value);
86
87/**
88 * Converts raw stick data into a valid stick value
89 * @param analog_x raw analog data and properties for the x-axis
90 * @param analog_y raw analog data and properties for the y-axis
91 * @param clamp_value bool that determines if the value needs to be clamped into the unit circle.
92 */
93void SanitizeStick(Common::Input::AnalogStatus& analog_x, Common::Input::AnalogStatus& analog_y,
94 bool clamp_value);
95
96} // namespace Core::HID
diff --git a/src/core/hid/input_interpreter.cpp b/src/core/hid/input_interpreter.cpp
new file mode 100644
index 000000000..2dbda8814
--- /dev/null
+++ b/src/core/hid/input_interpreter.cpp
@@ -0,0 +1,61 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "core/core.h"
6#include "core/hid/hid_types.h"
7#include "core/hid/input_interpreter.h"
8#include "core/hle/service/hid/controllers/npad.h"
9#include "core/hle/service/hid/hid.h"
10#include "core/hle/service/sm/sm.h"
11
12InputInterpreter::InputInterpreter(Core::System& system)
13 : npad{system.ServiceManager()
14 .GetService<Service::HID::Hid>("hid")
15 ->GetAppletResource()
16 ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad)} {
17 ResetButtonStates();
18}
19
20InputInterpreter::~InputInterpreter() = default;
21
22void InputInterpreter::PollInput() {
23 const auto button_state = npad.GetAndResetPressState();
24
25 previous_index = current_index;
26 current_index = (current_index + 1) % button_states.size();
27
28 button_states[current_index] = button_state;
29}
30
31void InputInterpreter::ResetButtonStates() {
32 previous_index = 0;
33 current_index = 0;
34
35 button_states[0] = Core::HID::NpadButton::All;
36
37 for (std::size_t i = 1; i < button_states.size(); ++i) {
38 button_states[i] = Core::HID::NpadButton::None;
39 }
40}
41
42bool InputInterpreter::IsButtonPressed(Core::HID::NpadButton button) const {
43 return True(button_states[current_index] & button);
44}
45
46bool InputInterpreter::IsButtonPressedOnce(Core::HID::NpadButton button) const {
47 const bool current_press = True(button_states[current_index] & button);
48 const bool previous_press = True(button_states[previous_index] & button);
49
50 return current_press && !previous_press;
51}
52
53bool InputInterpreter::IsButtonHeld(Core::HID::NpadButton button) const {
54 Core::HID::NpadButton held_buttons{button_states[0]};
55
56 for (std::size_t i = 1; i < button_states.size(); ++i) {
57 held_buttons &= button_states[i];
58 }
59
60 return True(held_buttons & button);
61}
diff --git a/src/core/hid/input_interpreter.h b/src/core/hid/input_interpreter.h
new file mode 100644
index 000000000..70c34d474
--- /dev/null
+++ b/src/core/hid/input_interpreter.h
@@ -0,0 +1,112 @@
1// Copyright 2020 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
11namespace Core {
12class System;
13}
14
15namespace Core::HID {
16enum class NpadButton : u64;
17}
18
19namespace Service::HID {
20class Controller_NPad;
21}
22
23/**
24 * The InputInterpreter class interfaces with HID to retrieve button press states.
25 * Input is intended to be polled every 50ms so that a button is considered to be
26 * held down after 400ms has elapsed since the initial button press and subsequent
27 * repeated presses occur every 50ms.
28 */
29class InputInterpreter {
30public:
31 explicit InputInterpreter(Core::System& system);
32 virtual ~InputInterpreter();
33
34 /// Gets a button state from HID and inserts it into the array of button states.
35 void PollInput();
36
37 /// Resets all the button states to their defaults.
38 void ResetButtonStates();
39
40 /**
41 * Checks whether the button is pressed.
42 *
43 * @param button The button to check.
44 *
45 * @returns True when the button is pressed.
46 */
47 [[nodiscard]] bool IsButtonPressed(Core::HID::NpadButton button) const;
48
49 /**
50 * Checks whether any of the buttons in the parameter list is pressed.
51 *
52 * @tparam HIDButton The buttons to check.
53 *
54 * @returns True when at least one of the buttons is pressed.
55 */
56 template <Core::HID::NpadButton... T>
57 [[nodiscard]] bool IsAnyButtonPressed() {
58 return (IsButtonPressed(T) || ...);
59 }
60
61 /**
62 * The specified button is considered to be pressed once
63 * if it is currently pressed and not pressed previously.
64 *
65 * @param button The button to check.
66 *
67 * @returns True when the button is pressed once.
68 */
69 [[nodiscard]] bool IsButtonPressedOnce(Core::HID::NpadButton button) const;
70
71 /**
72 * Checks whether any of the buttons in the parameter list is pressed once.
73 *
74 * @tparam T The buttons to check.
75 *
76 * @returns True when at least one of the buttons is pressed once.
77 */
78 template <Core::HID::NpadButton... T>
79 [[nodiscard]] bool IsAnyButtonPressedOnce() const {
80 return (IsButtonPressedOnce(T) || ...);
81 }
82
83 /**
84 * The specified button is considered to be held down if it is pressed in all 9 button states.
85 *
86 * @param button The button to check.
87 *
88 * @returns True when the button is held down.
89 */
90 [[nodiscard]] bool IsButtonHeld(Core::HID::NpadButton button) const;
91
92 /**
93 * Checks whether any of the buttons in the parameter list is held down.
94 *
95 * @tparam T The buttons to check.
96 *
97 * @returns True when at least one of the buttons is held down.
98 */
99 template <Core::HID::NpadButton... T>
100 [[nodiscard]] bool IsAnyButtonHeld() const {
101 return (IsButtonHeld(T) || ...);
102 }
103
104private:
105 Service::HID::Controller_NPad& npad;
106
107 /// Stores 9 consecutive button states polled from HID.
108 std::array<Core::HID::NpadButton, 9> button_states{};
109
110 std::size_t previous_index{};
111 std::size_t current_index{};
112};
diff --git a/src/core/hid/motion_input.cpp b/src/core/hid/motion_input.cpp
new file mode 100644
index 000000000..c25fea966
--- /dev/null
+++ b/src/core/hid/motion_input.cpp
@@ -0,0 +1,280 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included
4
5#include "common/math_util.h"
6#include "core/hid/motion_input.h"
7
8namespace Core::HID {
9
10MotionInput::MotionInput() {
11 // Initialize PID constants with default values
12 SetPID(0.3f, 0.005f, 0.0f);
13}
14
15void MotionInput::SetPID(f32 new_kp, f32 new_ki, f32 new_kd) {
16 kp = new_kp;
17 ki = new_ki;
18 kd = new_kd;
19}
20
21void MotionInput::SetAcceleration(const Common::Vec3f& acceleration) {
22 accel = acceleration;
23}
24
25void MotionInput::SetGyroscope(const Common::Vec3f& gyroscope) {
26 gyro = gyroscope - gyro_drift;
27
28 // Auto adjust drift to minimize drift
29 if (!IsMoving(0.1f)) {
30 gyro_drift = (gyro_drift * 0.9999f) + (gyroscope * 0.0001f);
31 }
32
33 if (gyro.Length2() < gyro_threshold) {
34 gyro = {};
35 } else {
36 only_accelerometer = false;
37 }
38}
39
40void MotionInput::SetQuaternion(const Common::Quaternion<f32>& quaternion) {
41 quat = quaternion;
42}
43
44void MotionInput::SetGyroDrift(const Common::Vec3f& drift) {
45 gyro_drift = drift;
46}
47
48void MotionInput::SetGyroThreshold(f32 threshold) {
49 gyro_threshold = threshold;
50}
51
52void MotionInput::EnableReset(bool reset) {
53 reset_enabled = reset;
54}
55
56void MotionInput::ResetRotations() {
57 rotations = {};
58}
59
60bool MotionInput::IsMoving(f32 sensitivity) const {
61 return gyro.Length() >= sensitivity || accel.Length() <= 0.9f || accel.Length() >= 1.1f;
62}
63
64bool MotionInput::IsCalibrated(f32 sensitivity) const {
65 return real_error.Length() < sensitivity;
66}
67
68void MotionInput::UpdateRotation(u64 elapsed_time) {
69 const auto sample_period = static_cast<f32>(elapsed_time) / 1000000.0f;
70 if (sample_period > 0.1f) {
71 return;
72 }
73 rotations += gyro * sample_period;
74}
75
76// Based on Madgwick's implementation of Mayhony's AHRS algorithm.
77// https://github.com/xioTechnologies/Open-Source-AHRS-With-x-IMU/blob/master/x-IMU%20IMU%20and%20AHRS%20Algorithms/x-IMU%20IMU%20and%20AHRS%20Algorithms/AHRS/MahonyAHRS.cs
78void MotionInput::UpdateOrientation(u64 elapsed_time) {
79 if (!IsCalibrated(0.1f)) {
80 ResetOrientation();
81 }
82 // Short name local variable for readability
83 f32 q1 = quat.w;
84 f32 q2 = quat.xyz[0];
85 f32 q3 = quat.xyz[1];
86 f32 q4 = quat.xyz[2];
87 const auto sample_period = static_cast<f32>(elapsed_time) / 1000000.0f;
88
89 // Ignore invalid elapsed time
90 if (sample_period > 0.1f) {
91 return;
92 }
93
94 const auto normal_accel = accel.Normalized();
95 auto rad_gyro = gyro * Common::PI * 2;
96 const f32 swap = rad_gyro.x;
97 rad_gyro.x = rad_gyro.y;
98 rad_gyro.y = -swap;
99 rad_gyro.z = -rad_gyro.z;
100
101 // Clear gyro values if there is no gyro present
102 if (only_accelerometer) {
103 rad_gyro.x = 0;
104 rad_gyro.y = 0;
105 rad_gyro.z = 0;
106 }
107
108 // Ignore drift correction if acceleration is not reliable
109 if (accel.Length() >= 0.75f && accel.Length() <= 1.25f) {
110 const f32 ax = -normal_accel.x;
111 const f32 ay = normal_accel.y;
112 const f32 az = -normal_accel.z;
113
114 // Estimated direction of gravity
115 const f32 vx = 2.0f * (q2 * q4 - q1 * q3);
116 const f32 vy = 2.0f * (q1 * q2 + q3 * q4);
117 const f32 vz = q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4;
118
119 // Error is cross product between estimated direction and measured direction of gravity
120 const Common::Vec3f new_real_error = {
121 az * vx - ax * vz,
122 ay * vz - az * vy,
123 ax * vy - ay * vx,
124 };
125
126 derivative_error = new_real_error - real_error;
127 real_error = new_real_error;
128
129 // Prevent integral windup
130 if (ki != 0.0f && !IsCalibrated(0.05f)) {
131 integral_error += real_error;
132 } else {
133 integral_error = {};
134 }
135
136 // Apply feedback terms
137 if (!only_accelerometer) {
138 rad_gyro += kp * real_error;
139 rad_gyro += ki * integral_error;
140 rad_gyro += kd * derivative_error;
141 } else {
142 // Give more weight to accelerometer values to compensate for the lack of gyro
143 rad_gyro += 35.0f * kp * real_error;
144 rad_gyro += 10.0f * ki * integral_error;
145 rad_gyro += 10.0f * kd * derivative_error;
146
147 // Emulate gyro values for games that need them
148 gyro.x = -rad_gyro.y;
149 gyro.y = rad_gyro.x;
150 gyro.z = -rad_gyro.z;
151 UpdateRotation(elapsed_time);
152 }
153 }
154
155 const f32 gx = rad_gyro.y;
156 const f32 gy = rad_gyro.x;
157 const f32 gz = rad_gyro.z;
158
159 // Integrate rate of change of quaternion
160 const f32 pa = q2;
161 const f32 pb = q3;
162 const f32 pc = q4;
163 q1 = q1 + (-q2 * gx - q3 * gy - q4 * gz) * (0.5f * sample_period);
164 q2 = pa + (q1 * gx + pb * gz - pc * gy) * (0.5f * sample_period);
165 q3 = pb + (q1 * gy - pa * gz + pc * gx) * (0.5f * sample_period);
166 q4 = pc + (q1 * gz + pa * gy - pb * gx) * (0.5f * sample_period);
167
168 quat.w = q1;
169 quat.xyz[0] = q2;
170 quat.xyz[1] = q3;
171 quat.xyz[2] = q4;
172 quat = quat.Normalized();
173}
174
175std::array<Common::Vec3f, 3> MotionInput::GetOrientation() const {
176 const Common::Quaternion<float> quad{
177 .xyz = {-quat.xyz[1], -quat.xyz[0], -quat.w},
178 .w = -quat.xyz[2],
179 };
180 const std::array<float, 16> matrix4x4 = quad.ToMatrix();
181
182 return {Common::Vec3f(matrix4x4[0], matrix4x4[1], -matrix4x4[2]),
183 Common::Vec3f(matrix4x4[4], matrix4x4[5], -matrix4x4[6]),
184 Common::Vec3f(-matrix4x4[8], -matrix4x4[9], matrix4x4[10])};
185}
186
187Common::Vec3f MotionInput::GetAcceleration() const {
188 return accel;
189}
190
191Common::Vec3f MotionInput::GetGyroscope() const {
192 return gyro;
193}
194
195Common::Quaternion<f32> MotionInput::GetQuaternion() const {
196 return quat;
197}
198
199Common::Vec3f MotionInput::GetRotations() const {
200 return rotations;
201}
202
203void MotionInput::ResetOrientation() {
204 if (!reset_enabled || only_accelerometer) {
205 return;
206 }
207 if (!IsMoving(0.5f) && accel.z <= -0.9f) {
208 ++reset_counter;
209 if (reset_counter > 900) {
210 quat.w = 0;
211 quat.xyz[0] = 0;
212 quat.xyz[1] = 0;
213 quat.xyz[2] = -1;
214 SetOrientationFromAccelerometer();
215 integral_error = {};
216 reset_counter = 0;
217 }
218 } else {
219 reset_counter = 0;
220 }
221}
222
223void MotionInput::SetOrientationFromAccelerometer() {
224 int iterations = 0;
225 const f32 sample_period = 0.015f;
226
227 const auto normal_accel = accel.Normalized();
228
229 while (!IsCalibrated(0.01f) && ++iterations < 100) {
230 // Short name local variable for readability
231 f32 q1 = quat.w;
232 f32 q2 = quat.xyz[0];
233 f32 q3 = quat.xyz[1];
234 f32 q4 = quat.xyz[2];
235
236 Common::Vec3f rad_gyro;
237 const f32 ax = -normal_accel.x;
238 const f32 ay = normal_accel.y;
239 const f32 az = -normal_accel.z;
240
241 // Estimated direction of gravity
242 const f32 vx = 2.0f * (q2 * q4 - q1 * q3);
243 const f32 vy = 2.0f * (q1 * q2 + q3 * q4);
244 const f32 vz = q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4;
245
246 // Error is cross product between estimated direction and measured direction of gravity
247 const Common::Vec3f new_real_error = {
248 az * vx - ax * vz,
249 ay * vz - az * vy,
250 ax * vy - ay * vx,
251 };
252
253 derivative_error = new_real_error - real_error;
254 real_error = new_real_error;
255
256 rad_gyro += 10.0f * kp * real_error;
257 rad_gyro += 5.0f * ki * integral_error;
258 rad_gyro += 10.0f * kd * derivative_error;
259
260 const f32 gx = rad_gyro.y;
261 const f32 gy = rad_gyro.x;
262 const f32 gz = rad_gyro.z;
263
264 // Integrate rate of change of quaternion
265 const f32 pa = q2;
266 const f32 pb = q3;
267 const f32 pc = q4;
268 q1 = q1 + (-q2 * gx - q3 * gy - q4 * gz) * (0.5f * sample_period);
269 q2 = pa + (q1 * gx + pb * gz - pc * gy) * (0.5f * sample_period);
270 q3 = pb + (q1 * gy - pa * gz + pc * gx) * (0.5f * sample_period);
271 q4 = pc + (q1 * gz + pa * gy - pb * gx) * (0.5f * sample_period);
272
273 quat.w = q1;
274 quat.xyz[0] = q2;
275 quat.xyz[1] = q3;
276 quat.xyz[2] = q4;
277 quat = quat.Normalized();
278 }
279}
280} // namespace Core::HID
diff --git a/src/core/hid/motion_input.h b/src/core/hid/motion_input.h
new file mode 100644
index 000000000..5b5b420bb
--- /dev/null
+++ b/src/core/hid/motion_input.h
@@ -0,0 +1,87 @@
1// Copyright 2020 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 "common/quaternion.h"
9#include "common/vector_math.h"
10
11namespace Core::HID {
12
13class MotionInput {
14public:
15 explicit MotionInput();
16
17 MotionInput(const MotionInput&) = default;
18 MotionInput& operator=(const MotionInput&) = default;
19
20 MotionInput(MotionInput&&) = default;
21 MotionInput& operator=(MotionInput&&) = default;
22
23 void SetPID(f32 new_kp, f32 new_ki, f32 new_kd);
24 void SetAcceleration(const Common::Vec3f& acceleration);
25 void SetGyroscope(const Common::Vec3f& gyroscope);
26 void SetQuaternion(const Common::Quaternion<f32>& quaternion);
27 void SetGyroDrift(const Common::Vec3f& drift);
28 void SetGyroThreshold(f32 threshold);
29
30 void EnableReset(bool reset);
31 void ResetRotations();
32
33 void UpdateRotation(u64 elapsed_time);
34 void UpdateOrientation(u64 elapsed_time);
35
36 [[nodiscard]] std::array<Common::Vec3f, 3> GetOrientation() const;
37 [[nodiscard]] Common::Vec3f GetAcceleration() const;
38 [[nodiscard]] Common::Vec3f GetGyroscope() const;
39 [[nodiscard]] Common::Vec3f GetRotations() const;
40 [[nodiscard]] Common::Quaternion<f32> GetQuaternion() const;
41
42 [[nodiscard]] bool IsMoving(f32 sensitivity) const;
43 [[nodiscard]] bool IsCalibrated(f32 sensitivity) const;
44
45private:
46 void ResetOrientation();
47 void SetOrientationFromAccelerometer();
48
49 // PID constants
50 f32 kp;
51 f32 ki;
52 f32 kd;
53
54 // PID errors
55 Common::Vec3f real_error;
56 Common::Vec3f integral_error;
57 Common::Vec3f derivative_error;
58
59 // Quaternion containing the device orientation
60 Common::Quaternion<f32> quat{{0.0f, 0.0f, -1.0f}, 0.0f};
61
62 // Number of full rotations in each axis
63 Common::Vec3f rotations;
64
65 // Acceleration vector measurement in G force
66 Common::Vec3f accel;
67
68 // Gyroscope vector measurement in radians/s.
69 Common::Vec3f gyro;
70
71 // Vector to be substracted from gyro measurements
72 Common::Vec3f gyro_drift;
73
74 // Minimum gyro amplitude to detect if the device is moving
75 f32 gyro_threshold = 0.0f;
76
77 // Number of invalid sequential data
78 u32 reset_counter = 0;
79
80 // If the provided data is invalid the device will be autocalibrated
81 bool reset_enabled = true;
82
83 // Use accelerometer values to calculate position
84 bool only_accelerometer = true;
85};
86
87} // namespace Core::HID