diff options
| author | 2021-12-18 13:57:14 +0800 | |
|---|---|---|
| committer | 2021-12-18 13:57:14 +0800 | |
| commit | e49184e6069a9d791d2df3c1958f5c4b1187e124 (patch) | |
| tree | b776caf722e0be0e680f67b0ad0842628162ef1c /src/core/hid | |
| parent | Implement convert legacy to generic (diff) | |
| parent | Merge pull request #7570 from ameerj/favorites-expanded (diff) | |
| download | yuzu-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.cpp | 232 | ||||
| -rw-r--r-- | src/core/hid/emulated_console.h | 190 | ||||
| -rw-r--r-- | src/core/hid/emulated_controller.cpp | 1139 | ||||
| -rw-r--r-- | src/core/hid/emulated_controller.h | 411 | ||||
| -rw-r--r-- | src/core/hid/emulated_devices.cpp | 459 | ||||
| -rw-r--r-- | src/core/hid/emulated_devices.h | 210 | ||||
| -rw-r--r-- | src/core/hid/hid_core.cpp | 214 | ||||
| -rw-r--r-- | src/core/hid/hid_core.h | 82 | ||||
| -rw-r--r-- | src/core/hid/hid_types.h | 635 | ||||
| -rw-r--r-- | src/core/hid/input_converter.cpp | 383 | ||||
| -rw-r--r-- | src/core/hid/input_converter.h | 96 | ||||
| -rw-r--r-- | src/core/hid/input_interpreter.cpp | 61 | ||||
| -rw-r--r-- | src/core/hid/input_interpreter.h | 112 | ||||
| -rw-r--r-- | src/core/hid/motion_input.cpp | 280 | ||||
| -rw-r--r-- | src/core/hid/motion_input.h | 87 |
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 | |||
| 9 | namespace Core::HID { | ||
| 10 | EmulatedConsole::EmulatedConsole() = default; | ||
| 11 | |||
| 12 | EmulatedConsole::~EmulatedConsole() = default; | ||
| 13 | |||
| 14 | void 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 | |||
| 22 | void 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 | |||
| 63 | void 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 | |||
| 92 | void EmulatedConsole::UnloadInput() { | ||
| 93 | motion_devices.reset(); | ||
| 94 | for (auto& touch : touch_devices) { | ||
| 95 | touch.reset(); | ||
| 96 | } | ||
| 97 | } | ||
| 98 | |||
| 99 | void EmulatedConsole::EnableConfiguration() { | ||
| 100 | is_configuring = true; | ||
| 101 | SaveCurrentConfig(); | ||
| 102 | } | ||
| 103 | |||
| 104 | void EmulatedConsole::DisableConfiguration() { | ||
| 105 | is_configuring = false; | ||
| 106 | } | ||
| 107 | |||
| 108 | bool EmulatedConsole::IsConfiguring() const { | ||
| 109 | return is_configuring; | ||
| 110 | } | ||
| 111 | |||
| 112 | void EmulatedConsole::SaveCurrentConfig() { | ||
| 113 | if (!is_configuring) { | ||
| 114 | return; | ||
| 115 | } | ||
| 116 | } | ||
| 117 | |||
| 118 | void EmulatedConsole::RestoreConfig() { | ||
| 119 | if (!is_configuring) { | ||
| 120 | return; | ||
| 121 | } | ||
| 122 | ReloadFromSettings(); | ||
| 123 | } | ||
| 124 | |||
| 125 | Common::ParamPackage EmulatedConsole::GetMotionParam() const { | ||
| 126 | return motion_params; | ||
| 127 | } | ||
| 128 | |||
| 129 | void EmulatedConsole::SetMotionParam(Common::ParamPackage param) { | ||
| 130 | motion_params = param; | ||
| 131 | ReloadInput(); | ||
| 132 | } | ||
| 133 | |||
| 134 | void 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 | |||
| 169 | void 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 | |||
| 192 | ConsoleMotionValues EmulatedConsole::GetMotionValues() const { | ||
| 193 | return console.motion_values; | ||
| 194 | } | ||
| 195 | |||
| 196 | TouchValues EmulatedConsole::GetTouchValues() const { | ||
| 197 | return console.touch_values; | ||
| 198 | } | ||
| 199 | |||
| 200 | ConsoleMotion EmulatedConsole::GetMotion() const { | ||
| 201 | return console.motion_state; | ||
| 202 | } | ||
| 203 | |||
| 204 | TouchFingerState EmulatedConsole::GetTouch() const { | ||
| 205 | return console.touch_state; | ||
| 206 | } | ||
| 207 | |||
| 208 | void 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 | |||
| 217 | int 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 | |||
| 223 | void 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 | |||
| 22 | namespace Core::HID { | ||
| 23 | |||
| 24 | struct ConsoleMotionInfo { | ||
| 25 | Common::Input::MotionStatus raw_status{}; | ||
| 26 | MotionInput emulated{}; | ||
| 27 | }; | ||
| 28 | |||
| 29 | using ConsoleMotionDevices = std::unique_ptr<Common::Input::InputDevice>; | ||
| 30 | using TouchDevices = std::array<std::unique_ptr<Common::Input::InputDevice>, 16>; | ||
| 31 | |||
| 32 | using ConsoleMotionParams = Common::ParamPackage; | ||
| 33 | using TouchParams = std::array<Common::ParamPackage, 16>; | ||
| 34 | |||
| 35 | using ConsoleMotionValues = ConsoleMotionInfo; | ||
| 36 | using TouchValues = std::array<Common::Input::TouchStatus, 16>; | ||
| 37 | |||
| 38 | struct 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 | ||
| 47 | struct 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 | |||
| 56 | using TouchFingerState = std::array<TouchFinger, 16>; | ||
| 57 | |||
| 58 | struct 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 | |||
| 68 | enum class ConsoleTriggerType { | ||
| 69 | Motion, | ||
| 70 | Touch, | ||
| 71 | All, | ||
| 72 | }; | ||
| 73 | |||
| 74 | struct ConsoleUpdateCallback { | ||
| 75 | std::function<void(ConsoleTriggerType)> on_change; | ||
| 76 | }; | ||
| 77 | |||
| 78 | class EmulatedConsole { | ||
| 79 | public: | ||
| 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 | |||
| 150 | private: | ||
| 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 | |||
| 8 | namespace Core::HID { | ||
| 9 | constexpr s32 HID_JOYSTICK_MAX = 0x7fff; | ||
| 10 | constexpr s32 HID_TRIGGER_MAX = 0x7fff; | ||
| 11 | |||
| 12 | EmulatedController::EmulatedController(NpadIdType npad_id_type_) : npad_id_type(npad_id_type_) {} | ||
| 13 | |||
| 14 | EmulatedController::~EmulatedController() = default; | ||
| 15 | |||
| 16 | NpadStyleIndex 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 | |||
| 45 | Settings::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 | |||
| 74 | void 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 | |||
| 116 | void 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 | |||
| 160 | void 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 | |||
| 200 | void 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 | |||
| 300 | void 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 | |||
| 327 | void EmulatedController::EnableConfiguration() { | ||
| 328 | is_configuring = true; | ||
| 329 | tmp_is_connected = is_connected; | ||
| 330 | tmp_npad_type = npad_type; | ||
| 331 | } | ||
| 332 | |||
| 333 | void 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 | |||
| 354 | bool EmulatedController::IsConfiguring() const { | ||
| 355 | return is_configuring; | ||
| 356 | } | ||
| 357 | |||
| 358 | void 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 | |||
| 374 | void EmulatedController::RestoreConfig() { | ||
| 375 | if (!is_configuring) { | ||
| 376 | return; | ||
| 377 | } | ||
| 378 | ReloadFromSettings(); | ||
| 379 | } | ||
| 380 | |||
| 381 | std::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 | |||
| 429 | Common::ParamPackage EmulatedController::GetButtonParam(std::size_t index) const { | ||
| 430 | if (index >= button_params.size()) { | ||
| 431 | return {}; | ||
| 432 | } | ||
| 433 | return button_params[index]; | ||
| 434 | } | ||
| 435 | |||
| 436 | Common::ParamPackage EmulatedController::GetStickParam(std::size_t index) const { | ||
| 437 | if (index >= stick_params.size()) { | ||
| 438 | return {}; | ||
| 439 | } | ||
| 440 | return stick_params[index]; | ||
| 441 | } | ||
| 442 | |||
| 443 | Common::ParamPackage EmulatedController::GetMotionParam(std::size_t index) const { | ||
| 444 | if (index >= motion_params.size()) { | ||
| 445 | return {}; | ||
| 446 | } | ||
| 447 | return motion_params[index]; | ||
| 448 | } | ||
| 449 | |||
| 450 | void 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 | |||
| 458 | void 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 | |||
| 466 | void 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 | |||
| 474 | void 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 | |||
| 614 | void 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 | |||
| 664 | void 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 | |||
| 706 | void 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 | |||
| 745 | void 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 | |||
| 813 | bool 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 | |||
| 845 | bool 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 | |||
| 865 | void 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 | |||
| 882 | void 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 | |||
| 894 | bool 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 | |||
| 923 | void 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 | |||
| 944 | void 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 | |||
| 961 | bool 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 | |||
| 968 | bool 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 | |||
| 974 | NpadIdType EmulatedController::GetNpadIdType() const { | ||
| 975 | return npad_id_type; | ||
| 976 | } | ||
| 977 | |||
| 978 | NpadStyleIndex 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 | |||
| 985 | void 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 | |||
| 1010 | LedPattern 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 | |||
| 1033 | ButtonValues EmulatedController::GetButtonsValues() const { | ||
| 1034 | return controller.button_values; | ||
| 1035 | } | ||
| 1036 | |||
| 1037 | SticksValues EmulatedController::GetSticksValues() const { | ||
| 1038 | return controller.stick_values; | ||
| 1039 | } | ||
| 1040 | |||
| 1041 | TriggerValues EmulatedController::GetTriggersValues() const { | ||
| 1042 | return controller.trigger_values; | ||
| 1043 | } | ||
| 1044 | |||
| 1045 | ControllerMotionValues EmulatedController::GetMotionValues() const { | ||
| 1046 | return controller.motion_values; | ||
| 1047 | } | ||
| 1048 | |||
| 1049 | ColorValues EmulatedController::GetColorsValues() const { | ||
| 1050 | return controller.color_values; | ||
| 1051 | } | ||
| 1052 | |||
| 1053 | BatteryValues EmulatedController::GetBatteryValues() const { | ||
| 1054 | return controller.battery_values; | ||
| 1055 | } | ||
| 1056 | |||
| 1057 | NpadButtonState EmulatedController::GetNpadButtons() const { | ||
| 1058 | if (is_configuring) { | ||
| 1059 | return {}; | ||
| 1060 | } | ||
| 1061 | return controller.npad_button_state; | ||
| 1062 | } | ||
| 1063 | |||
| 1064 | DebugPadButton EmulatedController::GetDebugPadButtons() const { | ||
| 1065 | if (is_configuring) { | ||
| 1066 | return {}; | ||
| 1067 | } | ||
| 1068 | return controller.debug_pad_button_state; | ||
| 1069 | } | ||
| 1070 | |||
| 1071 | AnalogSticks 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 | |||
| 1085 | NpadGcTriggerState EmulatedController::GetTriggers() const { | ||
| 1086 | if (is_configuring) { | ||
| 1087 | return {}; | ||
| 1088 | } | ||
| 1089 | return controller.gc_trigger_state; | ||
| 1090 | } | ||
| 1091 | |||
| 1092 | MotionState 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 | |||
| 1104 | ControllerColors EmulatedController::GetColors() const { | ||
| 1105 | return controller.colors_state; | ||
| 1106 | } | ||
| 1107 | |||
| 1108 | BatteryLevelState EmulatedController::GetBattery() const { | ||
| 1109 | return controller.battery_state; | ||
| 1110 | } | ||
| 1111 | |||
| 1112 | void 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 | |||
| 1124 | int 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 | |||
| 1130 | void 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 | |||
| 23 | namespace Core::HID { | ||
| 24 | const std::size_t max_emulated_controllers = 2; | ||
| 25 | struct ControllerMotionInfo { | ||
| 26 | Common::Input::MotionStatus raw_status{}; | ||
| 27 | MotionInput emulated{}; | ||
| 28 | }; | ||
| 29 | |||
| 30 | using ButtonDevices = | ||
| 31 | std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeButton::NumButtons>; | ||
| 32 | using StickDevices = | ||
| 33 | std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeAnalog::NumAnalogs>; | ||
| 34 | using ControllerMotionDevices = | ||
| 35 | std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeMotion::NumMotions>; | ||
| 36 | using TriggerDevices = | ||
| 37 | std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeTrigger::NumTriggers>; | ||
| 38 | using BatteryDevices = | ||
| 39 | std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>; | ||
| 40 | using OutputDevices = | ||
| 41 | std::array<std::unique_ptr<Common::Input::OutputDevice>, max_emulated_controllers>; | ||
| 42 | |||
| 43 | using ButtonParams = std::array<Common::ParamPackage, Settings::NativeButton::NumButtons>; | ||
| 44 | using StickParams = std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs>; | ||
| 45 | using ControllerMotionParams = std::array<Common::ParamPackage, Settings::NativeMotion::NumMotions>; | ||
| 46 | using TriggerParams = std::array<Common::ParamPackage, Settings::NativeTrigger::NumTriggers>; | ||
| 47 | using BatteryParams = std::array<Common::ParamPackage, max_emulated_controllers>; | ||
| 48 | using OutputParams = std::array<Common::ParamPackage, max_emulated_controllers>; | ||
| 49 | |||
| 50 | using ButtonValues = std::array<Common::Input::ButtonStatus, Settings::NativeButton::NumButtons>; | ||
| 51 | using SticksValues = std::array<Common::Input::StickStatus, Settings::NativeAnalog::NumAnalogs>; | ||
| 52 | using TriggerValues = | ||
| 53 | std::array<Common::Input::TriggerStatus, Settings::NativeTrigger::NumTriggers>; | ||
| 54 | using ControllerMotionValues = std::array<ControllerMotionInfo, Settings::NativeMotion::NumMotions>; | ||
| 55 | using ColorValues = std::array<Common::Input::BodyColorStatus, max_emulated_controllers>; | ||
| 56 | using BatteryValues = std::array<Common::Input::BatteryStatus, max_emulated_controllers>; | ||
| 57 | using VibrationValues = std::array<Common::Input::VibrationStatus, max_emulated_controllers>; | ||
| 58 | |||
| 59 | struct AnalogSticks { | ||
| 60 | AnalogStickState left{}; | ||
| 61 | AnalogStickState right{}; | ||
| 62 | }; | ||
| 63 | |||
| 64 | struct ControllerColors { | ||
| 65 | NpadControllerColor fullkey{}; | ||
| 66 | NpadControllerColor left{}; | ||
| 67 | NpadControllerColor right{}; | ||
| 68 | }; | ||
| 69 | |||
| 70 | struct BatteryLevelState { | ||
| 71 | NpadPowerInfo dual{}; | ||
| 72 | NpadPowerInfo left{}; | ||
| 73 | NpadPowerInfo right{}; | ||
| 74 | }; | ||
| 75 | |||
| 76 | struct 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 | |||
| 84 | enum EmulatedDeviceIndex : u8 { | ||
| 85 | LeftIndex, | ||
| 86 | RightIndex, | ||
| 87 | DualIndex, | ||
| 88 | AllDevices, | ||
| 89 | }; | ||
| 90 | |||
| 91 | using MotionState = std::array<ControllerMotion, 2>; | ||
| 92 | |||
| 93 | struct 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 | |||
| 113 | enum 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 | |||
| 127 | struct ControllerUpdateCallback { | ||
| 128 | std::function<void(ControllerTriggerType)> on_change; | ||
| 129 | bool is_npad_service; | ||
| 130 | }; | ||
| 131 | |||
| 132 | class EmulatedController { | ||
| 133 | public: | ||
| 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 | |||
| 313 | private: | ||
| 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 | |||
| 11 | namespace Core::HID { | ||
| 12 | |||
| 13 | EmulatedDevices::EmulatedDevices() = default; | ||
| 14 | |||
| 15 | EmulatedDevices::~EmulatedDevices() = default; | ||
| 16 | |||
| 17 | void EmulatedDevices::ReloadFromSettings() { | ||
| 18 | ReloadInput(); | ||
| 19 | } | ||
| 20 | |||
| 21 | void 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 | |||
| 125 | void 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 | |||
| 141 | void EmulatedDevices::EnableConfiguration() { | ||
| 142 | is_configuring = true; | ||
| 143 | SaveCurrentConfig(); | ||
| 144 | } | ||
| 145 | |||
| 146 | void EmulatedDevices::DisableConfiguration() { | ||
| 147 | is_configuring = false; | ||
| 148 | } | ||
| 149 | |||
| 150 | bool EmulatedDevices::IsConfiguring() const { | ||
| 151 | return is_configuring; | ||
| 152 | } | ||
| 153 | |||
| 154 | void EmulatedDevices::SaveCurrentConfig() { | ||
| 155 | if (!is_configuring) { | ||
| 156 | return; | ||
| 157 | } | ||
| 158 | } | ||
| 159 | |||
| 160 | void EmulatedDevices::RestoreConfig() { | ||
| 161 | if (!is_configuring) { | ||
| 162 | return; | ||
| 163 | } | ||
| 164 | ReloadFromSettings(); | ||
| 165 | } | ||
| 166 | |||
| 167 | void 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 | |||
| 214 | void 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 | |||
| 225 | void 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 | |||
| 295 | void 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 | |||
| 357 | void 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 | |||
| 385 | void 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 | |||
| 403 | KeyboardValues EmulatedDevices::GetKeyboardValues() const { | ||
| 404 | return device_status.keyboard_values; | ||
| 405 | } | ||
| 406 | |||
| 407 | KeyboardModifierValues EmulatedDevices::GetKeyboardModdifierValues() const { | ||
| 408 | return device_status.keyboard_moddifier_values; | ||
| 409 | } | ||
| 410 | |||
| 411 | MouseButtonValues EmulatedDevices::GetMouseButtonsValues() const { | ||
| 412 | return device_status.mouse_button_values; | ||
| 413 | } | ||
| 414 | |||
| 415 | KeyboardKey EmulatedDevices::GetKeyboard() const { | ||
| 416 | return device_status.keyboard_state; | ||
| 417 | } | ||
| 418 | |||
| 419 | KeyboardModifier EmulatedDevices::GetKeyboardModifier() const { | ||
| 420 | return device_status.keyboard_moddifier_state; | ||
| 421 | } | ||
| 422 | |||
| 423 | MouseButton EmulatedDevices::GetMouseButtons() const { | ||
| 424 | return device_status.mouse_button_state; | ||
| 425 | } | ||
| 426 | |||
| 427 | MousePosition EmulatedDevices::GetMousePosition() const { | ||
| 428 | return device_status.mouse_position_state; | ||
| 429 | } | ||
| 430 | |||
| 431 | AnalogStickState EmulatedDevices::GetMouseWheel() const { | ||
| 432 | return device_status.mouse_wheel_state; | ||
| 433 | } | ||
| 434 | |||
| 435 | void 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 | |||
| 444 | int 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 | |||
| 450 | void 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 | |||
| 19 | namespace Core::HID { | ||
| 20 | using KeyboardDevices = std::array<std::unique_ptr<Common::Input::InputDevice>, | ||
| 21 | Settings::NativeKeyboard::NumKeyboardKeys>; | ||
| 22 | using KeyboardModifierDevices = std::array<std::unique_ptr<Common::Input::InputDevice>, | ||
| 23 | Settings::NativeKeyboard::NumKeyboardMods>; | ||
| 24 | using MouseButtonDevices = std::array<std::unique_ptr<Common::Input::InputDevice>, | ||
| 25 | Settings::NativeMouseButton::NumMouseButtons>; | ||
| 26 | using MouseAnalogDevices = std::array<std::unique_ptr<Common::Input::InputDevice>, | ||
| 27 | Settings::NativeMouseWheel::NumMouseWheels>; | ||
| 28 | using MouseStickDevice = std::unique_ptr<Common::Input::InputDevice>; | ||
| 29 | |||
| 30 | using MouseButtonParams = | ||
| 31 | std::array<Common::ParamPackage, Settings::NativeMouseButton::NumMouseButtons>; | ||
| 32 | |||
| 33 | using KeyboardValues = | ||
| 34 | std::array<Common::Input::ButtonStatus, Settings::NativeKeyboard::NumKeyboardKeys>; | ||
| 35 | using KeyboardModifierValues = | ||
| 36 | std::array<Common::Input::ButtonStatus, Settings::NativeKeyboard::NumKeyboardMods>; | ||
| 37 | using MouseButtonValues = | ||
| 38 | std::array<Common::Input::ButtonStatus, Settings::NativeMouseButton::NumMouseButtons>; | ||
| 39 | using MouseAnalogValues = | ||
| 40 | std::array<Common::Input::AnalogStatus, Settings::NativeMouseWheel::NumMouseWheels>; | ||
| 41 | using MouseStickValue = Common::Input::TouchStatus; | ||
| 42 | |||
| 43 | struct MousePosition { | ||
| 44 | f32 x; | ||
| 45 | f32 y; | ||
| 46 | }; | ||
| 47 | |||
| 48 | struct 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 | |||
| 64 | enum class DeviceTriggerType { | ||
| 65 | Keyboard, | ||
| 66 | KeyboardModdifier, | ||
| 67 | Mouse, | ||
| 68 | }; | ||
| 69 | |||
| 70 | struct InterfaceUpdateCallback { | ||
| 71 | std::function<void(DeviceTriggerType)> on_change; | ||
| 72 | }; | ||
| 73 | |||
| 74 | class EmulatedDevices { | ||
| 75 | public: | ||
| 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 | |||
| 150 | private: | ||
| 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 | |||
| 11 | namespace Core::HID { | ||
| 12 | |||
| 13 | HIDCore::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 | |||
| 26 | HIDCore::~HIDCore() = default; | ||
| 27 | |||
| 28 | EmulatedController* 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 | |||
| 57 | const 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 | } | ||
| 85 | EmulatedConsole* HIDCore::GetEmulatedConsole() { | ||
| 86 | return console.get(); | ||
| 87 | } | ||
| 88 | |||
| 89 | const EmulatedConsole* HIDCore::GetEmulatedConsole() const { | ||
| 90 | return console.get(); | ||
| 91 | } | ||
| 92 | |||
| 93 | EmulatedDevices* HIDCore::GetEmulatedDevices() { | ||
| 94 | return devices.get(); | ||
| 95 | } | ||
| 96 | |||
| 97 | const EmulatedDevices* HIDCore::GetEmulatedDevices() const { | ||
| 98 | return devices.get(); | ||
| 99 | } | ||
| 100 | |||
| 101 | EmulatedController* HIDCore::GetEmulatedControllerByIndex(std::size_t index) { | ||
| 102 | return GetEmulatedController(IndexToNpadIdType(index)); | ||
| 103 | } | ||
| 104 | |||
| 105 | const EmulatedController* HIDCore::GetEmulatedControllerByIndex(std::size_t index) const { | ||
| 106 | return GetEmulatedController(IndexToNpadIdType(index)); | ||
| 107 | } | ||
| 108 | |||
| 109 | void 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 | |||
| 123 | NpadStyleTag HIDCore::GetSupportedStyleTag() const { | ||
| 124 | return supported_style_tag; | ||
| 125 | } | ||
| 126 | |||
| 127 | s8 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 | |||
| 138 | NpadIdType 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 | |||
| 148 | NpadIdType 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 | |||
| 158 | void 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 | |||
| 171 | void 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 | |||
| 184 | void 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 | |||
| 199 | void 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 | |||
| 11 | namespace Core::HID { | ||
| 12 | class EmulatedConsole; | ||
| 13 | class EmulatedController; | ||
| 14 | class EmulatedDevices; | ||
| 15 | } // namespace Core::HID | ||
| 16 | |||
| 17 | namespace Core::HID { | ||
| 18 | |||
| 19 | class HIDCore { | ||
| 20 | public: | ||
| 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 | |||
| 66 | private: | ||
| 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 | |||
| 13 | namespace Core::HID { | ||
| 14 | |||
| 15 | enum class DeviceIndex : u8 { | ||
| 16 | Left = 0, | ||
| 17 | Right = 1, | ||
| 18 | None = 2, | ||
| 19 | MaxDeviceIndex = 3, | ||
| 20 | }; | ||
| 21 | |||
| 22 | // This is nn::hid::NpadButton | ||
| 23 | enum 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 | }; | ||
| 69 | DECLARE_ENUM_FLAG_OPERATORS(NpadButton); | ||
| 70 | |||
| 71 | enum 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 | ||
| 207 | enum 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 | ||
| 223 | enum 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 | ||
| 243 | enum 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 | }; | ||
| 262 | static_assert(sizeof(NpadStyleSet) == 4, "NpadStyleSet is an invalid size"); | ||
| 263 | |||
| 264 | // This is nn::hid::VibrationDevicePosition | ||
| 265 | enum class VibrationDevicePosition : u32 { | ||
| 266 | None = 0, | ||
| 267 | Left = 1, | ||
| 268 | Right = 2, | ||
| 269 | }; | ||
| 270 | |||
| 271 | // This is nn::hid::VibrationDeviceType | ||
| 272 | enum class VibrationDeviceType : u32 { | ||
| 273 | Unknown = 0, | ||
| 274 | LinearResonantActuator = 1, | ||
| 275 | GcErm = 2, | ||
| 276 | }; | ||
| 277 | |||
| 278 | // This is nn::hid::VibrationGcErmCommand | ||
| 279 | enum class VibrationGcErmCommand : u64 { | ||
| 280 | Stop = 0, | ||
| 281 | Start = 1, | ||
| 282 | StopHard = 2, | ||
| 283 | }; | ||
| 284 | |||
| 285 | // This is nn::hid::NpadStyleTag | ||
| 286 | struct 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 | }; | ||
| 306 | static_assert(sizeof(NpadStyleTag) == 4, "NpadStyleTag is an invalid size"); | ||
| 307 | |||
| 308 | // This is nn::hid::TouchAttribute | ||
| 309 | struct TouchAttribute { | ||
| 310 | union { | ||
| 311 | u32 raw{}; | ||
| 312 | BitField<0, 1, u32> start_touch; | ||
| 313 | BitField<1, 1, u32> end_touch; | ||
| 314 | }; | ||
| 315 | }; | ||
| 316 | static_assert(sizeof(TouchAttribute) == 0x4, "TouchAttribute is an invalid size"); | ||
| 317 | |||
| 318 | // This is nn::hid::TouchState | ||
| 319 | struct 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 | }; | ||
| 328 | static_assert(sizeof(TouchState) == 0x28, "Touchstate is an invalid size"); | ||
| 329 | |||
| 330 | // This is nn::hid::NpadControllerColor | ||
| 331 | struct NpadControllerColor { | ||
| 332 | u32 body; | ||
| 333 | u32 button; | ||
| 334 | }; | ||
| 335 | static_assert(sizeof(NpadControllerColor) == 8, "NpadControllerColor is an invalid size"); | ||
| 336 | |||
| 337 | // This is nn::hid::AnalogStickState | ||
| 338 | struct AnalogStickState { | ||
| 339 | s32 x; | ||
| 340 | s32 y; | ||
| 341 | }; | ||
| 342 | static_assert(sizeof(AnalogStickState) == 8, "AnalogStickState is an invalid size"); | ||
| 343 | |||
| 344 | // This is nn::hid::server::NpadGcTriggerState | ||
| 345 | struct NpadGcTriggerState { | ||
| 346 | s64 sampling_number{}; | ||
| 347 | s32 left{}; | ||
| 348 | s32 right{}; | ||
| 349 | }; | ||
| 350 | static_assert(sizeof(NpadGcTriggerState) == 0x10, "NpadGcTriggerState is an invalid size"); | ||
| 351 | |||
| 352 | // This is nn::hid::system::NpadBatteryLevel | ||
| 353 | using NpadBatteryLevel = u32; | ||
| 354 | static_assert(sizeof(NpadBatteryLevel) == 0x4, "NpadBatteryLevel is an invalid size"); | ||
| 355 | |||
| 356 | // This is nn::hid::system::NpadPowerInfo | ||
| 357 | struct NpadPowerInfo { | ||
| 358 | bool is_powered; | ||
| 359 | bool is_charging; | ||
| 360 | INSERT_PADDING_BYTES(0x6); | ||
| 361 | NpadBatteryLevel battery_level; | ||
| 362 | }; | ||
| 363 | static_assert(sizeof(NpadPowerInfo) == 0xC, "NpadPowerInfo is an invalid size"); | ||
| 364 | |||
| 365 | struct 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 | |||
| 381 | struct 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 | }; | ||
| 432 | static_assert(sizeof(NpadButtonState) == 0x8, "NpadButtonState has incorrect size."); | ||
| 433 | |||
| 434 | // This is nn::hid::DebugPadButton | ||
| 435 | struct 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 | }; | ||
| 454 | static_assert(sizeof(DebugPadButton) == 0x4, "DebugPadButton is an invalid size"); | ||
| 455 | |||
| 456 | // This is nn::hid::ConsoleSixAxisSensorHandle | ||
| 457 | struct ConsoleSixAxisSensorHandle { | ||
| 458 | u8 unknown_1; | ||
| 459 | u8 unknown_2; | ||
| 460 | INSERT_PADDING_BYTES_NOINIT(2); | ||
| 461 | }; | ||
| 462 | static_assert(sizeof(ConsoleSixAxisSensorHandle) == 4, | ||
| 463 | "ConsoleSixAxisSensorHandle is an invalid size"); | ||
| 464 | |||
| 465 | // This is nn::hid::SixAxisSensorHandle | ||
| 466 | struct SixAxisSensorHandle { | ||
| 467 | NpadStyleIndex npad_type; | ||
| 468 | u8 npad_id; | ||
| 469 | DeviceIndex device_index; | ||
| 470 | INSERT_PADDING_BYTES_NOINIT(1); | ||
| 471 | }; | ||
| 472 | static_assert(sizeof(SixAxisSensorHandle) == 4, "SixAxisSensorHandle is an invalid size"); | ||
| 473 | |||
| 474 | struct SixAxisSensorFusionParameters { | ||
| 475 | f32 parameter1; | ||
| 476 | f32 parameter2; | ||
| 477 | }; | ||
| 478 | static_assert(sizeof(SixAxisSensorFusionParameters) == 8, | ||
| 479 | "SixAxisSensorFusionParameters is an invalid size"); | ||
| 480 | |||
| 481 | // This is nn::hid::VibrationDeviceHandle | ||
| 482 | struct VibrationDeviceHandle { | ||
| 483 | NpadStyleIndex npad_type; | ||
| 484 | u8 npad_id; | ||
| 485 | DeviceIndex device_index; | ||
| 486 | INSERT_PADDING_BYTES_NOINIT(1); | ||
| 487 | }; | ||
| 488 | static_assert(sizeof(VibrationDeviceHandle) == 4, "SixAxisSensorHandle is an invalid size"); | ||
| 489 | |||
| 490 | // This is nn::hid::VibrationValue | ||
| 491 | struct VibrationValue { | ||
| 492 | f32 low_amplitude; | ||
| 493 | f32 low_frequency; | ||
| 494 | f32 high_amplitude; | ||
| 495 | f32 high_frequency; | ||
| 496 | }; | ||
| 497 | static_assert(sizeof(VibrationValue) == 0x10, "VibrationValue has incorrect size."); | ||
| 498 | |||
| 499 | // This is nn::hid::VibrationDeviceInfo | ||
| 500 | struct VibrationDeviceInfo { | ||
| 501 | VibrationDeviceType type{}; | ||
| 502 | VibrationDevicePosition position{}; | ||
| 503 | }; | ||
| 504 | static_assert(sizeof(VibrationDeviceInfo) == 0x8, "VibrationDeviceInfo has incorrect size."); | ||
| 505 | |||
| 506 | // This is nn::hid::KeyboardModifier | ||
| 507 | struct 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 | |||
| 523 | static_assert(sizeof(KeyboardModifier) == 0x4, "KeyboardModifier is an invalid size"); | ||
| 524 | |||
| 525 | // This is nn::hid::KeyboardAttribute | ||
| 526 | struct KeyboardAttribute { | ||
| 527 | union { | ||
| 528 | u32 raw{}; | ||
| 529 | BitField<0, 1, u32> is_connected; | ||
| 530 | }; | ||
| 531 | }; | ||
| 532 | static_assert(sizeof(KeyboardAttribute) == 0x4, "KeyboardAttribute is an invalid size"); | ||
| 533 | |||
| 534 | // This is nn::hid::KeyboardKey | ||
| 535 | struct KeyboardKey { | ||
| 536 | // This should be a 256 bit flag | ||
| 537 | std::array<u8, 32> key; | ||
| 538 | }; | ||
| 539 | static_assert(sizeof(KeyboardKey) == 0x20, "KeyboardKey is an invalid size"); | ||
| 540 | |||
| 541 | // This is nn::hid::MouseButton | ||
| 542 | struct 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 | }; | ||
| 552 | static_assert(sizeof(MouseButton) == 0x4, "MouseButton is an invalid size"); | ||
| 553 | |||
| 554 | // This is nn::hid::MouseAttribute | ||
| 555 | struct MouseAttribute { | ||
| 556 | union { | ||
| 557 | u32 raw{}; | ||
| 558 | BitField<0, 1, u32> transferable; | ||
| 559 | BitField<1, 1, u32> is_connected; | ||
| 560 | }; | ||
| 561 | }; | ||
| 562 | static_assert(sizeof(MouseAttribute) == 0x4, "MouseAttribute is an invalid size"); | ||
| 563 | |||
| 564 | // This is nn::hid::detail::MouseState | ||
| 565 | struct 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 | }; | ||
| 577 | static_assert(sizeof(MouseState) == 0x28, "MouseState is an invalid size"); | ||
| 578 | |||
| 579 | /// Converts a NpadIdType to an array index. | ||
| 580 | constexpr 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 | ||
| 608 | constexpr 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 | |||
| 10 | namespace Core::HID { | ||
| 11 | |||
| 12 | Common::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 | |||
| 51 | Common::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 | |||
| 73 | Common::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 | |||
| 144 | Common::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 | |||
| 171 | Common::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 | |||
| 207 | Common::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 | |||
| 249 | Common::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 | |||
| 270 | void 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 | |||
| 310 | void 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 | |||
| 7 | namespace Common::Input { | ||
| 8 | struct CallbackStatus; | ||
| 9 | enum class BatteryLevel : u32; | ||
| 10 | using BatteryStatus = BatteryLevel; | ||
| 11 | struct AnalogStatus; | ||
| 12 | struct ButtonStatus; | ||
| 13 | struct MotionStatus; | ||
| 14 | struct StickStatus; | ||
| 15 | struct TouchStatus; | ||
| 16 | struct TriggerStatus; | ||
| 17 | }; // namespace Common::Input | ||
| 18 | |||
| 19 | namespace 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 | */ | ||
| 27 | Common::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 | */ | ||
| 35 | Common::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 | */ | ||
| 43 | Common::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 | */ | ||
| 52 | Common::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 | */ | ||
| 60 | Common::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 | */ | ||
| 69 | Common::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 | */ | ||
| 78 | Common::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 | */ | ||
| 85 | void 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 | */ | ||
| 93 | void 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 | |||
| 12 | InputInterpreter::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 | |||
| 20 | InputInterpreter::~InputInterpreter() = default; | ||
| 21 | |||
| 22 | void 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 | |||
| 31 | void 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 | |||
| 42 | bool InputInterpreter::IsButtonPressed(Core::HID::NpadButton button) const { | ||
| 43 | return True(button_states[current_index] & button); | ||
| 44 | } | ||
| 45 | |||
| 46 | bool 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 | |||
| 53 | bool 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 | |||
| 11 | namespace Core { | ||
| 12 | class System; | ||
| 13 | } | ||
| 14 | |||
| 15 | namespace Core::HID { | ||
| 16 | enum class NpadButton : u64; | ||
| 17 | } | ||
| 18 | |||
| 19 | namespace Service::HID { | ||
| 20 | class 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 | */ | ||
| 29 | class InputInterpreter { | ||
| 30 | public: | ||
| 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 | |||
| 104 | private: | ||
| 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 | |||
| 8 | namespace Core::HID { | ||
| 9 | |||
| 10 | MotionInput::MotionInput() { | ||
| 11 | // Initialize PID constants with default values | ||
| 12 | SetPID(0.3f, 0.005f, 0.0f); | ||
| 13 | } | ||
| 14 | |||
| 15 | void 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 | |||
| 21 | void MotionInput::SetAcceleration(const Common::Vec3f& acceleration) { | ||
| 22 | accel = acceleration; | ||
| 23 | } | ||
| 24 | |||
| 25 | void 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 | |||
| 40 | void MotionInput::SetQuaternion(const Common::Quaternion<f32>& quaternion) { | ||
| 41 | quat = quaternion; | ||
| 42 | } | ||
| 43 | |||
| 44 | void MotionInput::SetGyroDrift(const Common::Vec3f& drift) { | ||
| 45 | gyro_drift = drift; | ||
| 46 | } | ||
| 47 | |||
| 48 | void MotionInput::SetGyroThreshold(f32 threshold) { | ||
| 49 | gyro_threshold = threshold; | ||
| 50 | } | ||
| 51 | |||
| 52 | void MotionInput::EnableReset(bool reset) { | ||
| 53 | reset_enabled = reset; | ||
| 54 | } | ||
| 55 | |||
| 56 | void MotionInput::ResetRotations() { | ||
| 57 | rotations = {}; | ||
| 58 | } | ||
| 59 | |||
| 60 | bool MotionInput::IsMoving(f32 sensitivity) const { | ||
| 61 | return gyro.Length() >= sensitivity || accel.Length() <= 0.9f || accel.Length() >= 1.1f; | ||
| 62 | } | ||
| 63 | |||
| 64 | bool MotionInput::IsCalibrated(f32 sensitivity) const { | ||
| 65 | return real_error.Length() < sensitivity; | ||
| 66 | } | ||
| 67 | |||
| 68 | void 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 | ||
| 78 | void 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 | |||
| 175 | std::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 | |||
| 187 | Common::Vec3f MotionInput::GetAcceleration() const { | ||
| 188 | return accel; | ||
| 189 | } | ||
| 190 | |||
| 191 | Common::Vec3f MotionInput::GetGyroscope() const { | ||
| 192 | return gyro; | ||
| 193 | } | ||
| 194 | |||
| 195 | Common::Quaternion<f32> MotionInput::GetQuaternion() const { | ||
| 196 | return quat; | ||
| 197 | } | ||
| 198 | |||
| 199 | Common::Vec3f MotionInput::GetRotations() const { | ||
| 200 | return rotations; | ||
| 201 | } | ||
| 202 | |||
| 203 | void 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 | |||
| 223 | void 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 | |||
| 11 | namespace Core::HID { | ||
| 12 | |||
| 13 | class MotionInput { | ||
| 14 | public: | ||
| 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 | |||
| 45 | private: | ||
| 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 | ||