summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/core/CMakeLists.txt8
-rw-r--r--src/core/hid/emulated_console.cpp208
-rw-r--r--src/core/hid/emulated_console.h142
-rw-r--r--src/core/hid/emulated_controller.cpp745
-rw-r--r--src/core/hid/emulated_controller.h232
-rw-r--r--src/core/hid/emulated_devices.cpp349
-rw-r--r--src/core/hid/emulated_devices.h137
-rw-r--r--src/core/hid/hid_core.cpp144
-rw-r--r--src/core/hid/hid_core.h60
9 files changed, 2025 insertions, 0 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index eddf455c0..09163fab9 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -135,6 +135,14 @@ add_library(core STATIC
frontend/input.h
hardware_interrupt_manager.cpp
hardware_interrupt_manager.h
+ hid/emulated_console.cpp
+ hid/emulated_console.h
+ hid/emulated_controller.cpp
+ hid/emulated_controller.h
+ hid/emulated_devices.cpp
+ hid/emulated_devices.h
+ hid/hid_core.cpp
+ hid/hid_core.h
hid/hid_types.h
hid/input_converter.cpp
hid/input_converter.h
diff --git a/src/core/hid/emulated_console.cpp b/src/core/hid/emulated_console.cpp
new file mode 100644
index 000000000..c65d05041
--- /dev/null
+++ b/src/core/hid/emulated_console.cpp
@@ -0,0 +1,208 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included
+
+#include <fmt/format.h>
+
+#include "core/hid/emulated_console.h"
+#include "core/hid/input_converter.h"
+
+namespace Core::HID {
+EmulatedConsole::EmulatedConsole() {}
+
+EmulatedConsole::~EmulatedConsole() = default;
+
+void EmulatedConsole::ReloadFromSettings() {
+ // Using first motion device from player 1. No need to assign a special config at the moment
+ const auto& player = Settings::values.players.GetValue()[0];
+ motion_params = Common::ParamPackage(player.motions[0]);
+
+ ReloadInput();
+}
+
+void EmulatedConsole::ReloadInput() {
+ motion_devices = Input::CreateDevice<Input::InputDevice>(motion_params);
+ if (motion_devices) {
+ Input::InputCallback motion_callback{
+ [this](Input::CallbackStatus callback) { SetMotion(callback); }};
+ motion_devices->SetCallback(motion_callback);
+ }
+
+ // TODO: Fix this mess
+ std::size_t index = 0;
+ const std::string mouse_device_string =
+ fmt::format("engine:mouse,axis_x:10,axis_y:11,button:{}", index);
+ touch_devices[index] = Input::CreateDeviceFromString<Input::InputDevice>(mouse_device_string);
+ Input::InputCallback trigger_callbackk{
+ [this, index](Input::CallbackStatus callback) { SetTouch(callback, index); }};
+ touch_devices[index]->SetCallback(trigger_callbackk);
+
+ index++;
+ const auto button_index =
+ static_cast<u64>(Settings::values.touch_from_button_map_index.GetValue());
+ const auto& touch_buttons = Settings::values.touch_from_button_maps[button_index].buttons;
+ for (const auto& config_entry : touch_buttons) {
+ Common::ParamPackage params{config_entry};
+ Common::ParamPackage touch_button_params;
+ const int x = params.Get("x", 0);
+ const int y = params.Get("y", 0);
+ params.Erase("x");
+ params.Erase("y");
+ touch_button_params.Set("engine", "touch_from_button");
+ touch_button_params.Set("button", params.Serialize());
+ touch_button_params.Set("x", x);
+ touch_button_params.Set("y", y);
+ touch_button_params.Set("touch_id", static_cast<int>(index));
+ LOG_ERROR(Common, "{} ", touch_button_params.Serialize());
+ touch_devices[index] =
+ Input::CreateDeviceFromString<Input::InputDevice>(touch_button_params.Serialize());
+ if (!touch_devices[index]) {
+ continue;
+ }
+
+ Input::InputCallback trigger_callback{
+ [this, index](Input::CallbackStatus callback) { SetTouch(callback, index); }};
+ touch_devices[index]->SetCallback(trigger_callback);
+ index++;
+ }
+}
+
+void EmulatedConsole::UnloadInput() {
+ motion_devices.reset();
+ for (auto& touch : touch_devices) {
+ touch.reset();
+ }
+}
+
+void EmulatedConsole::EnableConfiguration() {
+ is_configuring = true;
+ SaveCurrentConfig();
+}
+
+void EmulatedConsole::DisableConfiguration() {
+ is_configuring = false;
+}
+
+bool EmulatedConsole::IsConfiguring() const {
+ return is_configuring;
+}
+
+void EmulatedConsole::SaveCurrentConfig() {
+ if (!is_configuring) {
+ return;
+ }
+}
+
+void EmulatedConsole::RestoreConfig() {
+ if (!is_configuring) {
+ return;
+ }
+ ReloadFromSettings();
+}
+
+Common::ParamPackage EmulatedConsole::GetMotionParam() const {
+ return motion_params;
+}
+
+void EmulatedConsole::SetMotionParam(Common::ParamPackage param) {
+ motion_params = param;
+ ReloadInput();
+}
+
+void EmulatedConsole::SetMotion(Input::CallbackStatus callback) {
+ std::lock_guard lock{mutex};
+ auto& raw_status = console.motion_values.raw_status;
+ auto& emulated = console.motion_values.emulated;
+
+ raw_status = TransformToMotion(callback);
+ emulated.SetAcceleration(Common::Vec3f{
+ raw_status.accel.x.value,
+ raw_status.accel.y.value,
+ raw_status.accel.z.value,
+ });
+ emulated.SetGyroscope(Common::Vec3f{
+ raw_status.gyro.x.value,
+ raw_status.gyro.y.value,
+ raw_status.gyro.z.value,
+ });
+ emulated.UpdateRotation(raw_status.delta_timestamp);
+ emulated.UpdateOrientation(raw_status.delta_timestamp);
+
+ if (is_configuring) {
+ TriggerOnChange(ConsoleTriggerType::Motion);
+ return;
+ }
+
+ auto& motion = console.motion_state;
+ motion.accel = emulated.GetAcceleration();
+ motion.gyro = emulated.GetGyroscope();
+ motion.rotation = emulated.GetGyroscope();
+ motion.orientation = emulated.GetOrientation();
+ motion.quaternion = emulated.GetQuaternion();
+ motion.is_at_rest = emulated.IsMoving(motion_sensitivity);
+
+ TriggerOnChange(ConsoleTriggerType::Motion);
+}
+
+void EmulatedConsole::SetTouch(Input::CallbackStatus callback, [[maybe_unused]] std::size_t index) {
+ if (index >= console.touch_values.size()) {
+ return;
+ }
+ std::lock_guard lock{mutex};
+
+ console.touch_values[index] = TransformToTouch(callback);
+
+ if (is_configuring) {
+ TriggerOnChange(ConsoleTriggerType::Touch);
+ return;
+ }
+
+ console.touch_state[index] = {
+ .position = {console.touch_values[index].x.value, console.touch_values[index].y.value},
+ .id = console.touch_values[index].id,
+ .pressed = console.touch_values[index].pressed.value,
+ };
+
+ TriggerOnChange(ConsoleTriggerType::Touch);
+}
+
+ConsoleMotionValues EmulatedConsole::GetMotionValues() const {
+ return console.motion_values;
+}
+
+TouchValues EmulatedConsole::GetTouchValues() const {
+ return console.touch_values;
+}
+
+ConsoleMotion EmulatedConsole::GetMotion() const {
+ return console.motion_state;
+}
+
+TouchFingerState EmulatedConsole::GetTouch() const {
+ return console.touch_state;
+}
+
+void EmulatedConsole::TriggerOnChange(ConsoleTriggerType type) {
+ for (const std::pair<int, ConsoleUpdateCallback> poller_pair : callback_list) {
+ const ConsoleUpdateCallback& poller = poller_pair.second;
+ if (poller.on_change) {
+ poller.on_change(type);
+ }
+ }
+}
+
+int EmulatedConsole::SetCallback(ConsoleUpdateCallback update_callback) {
+ std::lock_guard lock{mutex};
+ callback_list.insert_or_assign(last_callback_key, update_callback);
+ return last_callback_key++;
+}
+
+void EmulatedConsole::DeleteCallback(int key) {
+ std::lock_guard lock{mutex};
+ if (!callback_list.contains(key)) {
+ LOG_ERROR(Input, "Tried to delete non-existent callback {}", key);
+ return;
+ }
+ callback_list.erase(key);
+}
+} // 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..d9e275042
--- /dev/null
+++ b/src/core/hid/emulated_console.h
@@ -0,0 +1,142 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <functional>
+#include <mutex>
+#include <unordered_map>
+
+#include "common/input.h"
+#include "common/param_package.h"
+#include "common/point.h"
+#include "common/quaternion.h"
+#include "common/settings.h"
+#include "common/vector_math.h"
+#include "core/hid/hid_types.h"
+#include "core/hid/motion_input.h"
+
+namespace Core::HID {
+
+struct ConsoleMotionInfo {
+ Input::MotionStatus raw_status;
+ MotionInput emulated{};
+};
+
+using ConsoleMotionDevices = std::unique_ptr<Input::InputDevice>;
+using TouchDevices = std::array<std::unique_ptr<Input::InputDevice>, 16>;
+
+using ConsoleMotionParams = Common::ParamPackage;
+using TouchParams = std::array<Common::ParamPackage, 16>;
+
+using ConsoleMotionValues = ConsoleMotionInfo;
+using TouchValues = std::array<Input::TouchStatus, 16>;
+
+struct TouchFinger {
+ u64_le last_touch{};
+ Common::Point<float> position{};
+ u32_le id{};
+ bool pressed{};
+ Core::HID::TouchAttribute attribute{};
+};
+
+struct ConsoleMotion {
+ bool is_at_rest{};
+ Common::Vec3f accel{};
+ Common::Vec3f gyro{};
+ Common::Vec3f rotation{};
+ std::array<Common::Vec3f, 3> orientation{};
+ Common::Quaternion<f32> quaternion{};
+};
+
+using TouchFingerState = std::array<TouchFinger, 16>;
+
+struct ConsoleStatus {
+ // Data from input_common
+ ConsoleMotionValues motion_values{};
+ TouchValues touch_values{};
+
+ // Data for Nintendo devices;
+ ConsoleMotion motion_state{};
+ TouchFingerState touch_state{};
+};
+
+enum class ConsoleTriggerType {
+ Motion,
+ Touch,
+ All,
+};
+
+struct ConsoleUpdateCallback {
+ std::function<void(ConsoleTriggerType)> on_change;
+};
+
+class EmulatedConsole {
+public:
+ /**
+ * TODO: Write description
+ *
+ * @param npad_id_type
+ */
+ explicit EmulatedConsole();
+ ~EmulatedConsole();
+
+ YUZU_NON_COPYABLE(EmulatedConsole);
+ YUZU_NON_MOVEABLE(EmulatedConsole);
+
+ void ReloadFromSettings();
+ void ReloadInput();
+ void UnloadInput();
+
+ void EnableConfiguration();
+ void DisableConfiguration();
+ bool IsConfiguring() const;
+ void SaveCurrentConfig();
+ void RestoreConfig();
+
+ Common::ParamPackage GetMotionParam() const;
+
+ void SetMotionParam(Common::ParamPackage param);
+
+ ConsoleMotionValues GetMotionValues() const;
+ TouchValues GetTouchValues() const;
+
+ ConsoleMotion GetMotion() const;
+ TouchFingerState GetTouch() const;
+
+ int SetCallback(ConsoleUpdateCallback update_callback);
+ void DeleteCallback(int key);
+
+private:
+ /**
+ * Sets the status of a button. Applies toggle properties to the output.
+ *
+ * @param A CallbackStatus and a button index number
+ */
+ void SetMotion(Input::CallbackStatus callback);
+ void SetTouch(Input::CallbackStatus callback, std::size_t index);
+
+ /**
+ * Triggers a callback that something has changed
+ *
+ * @param Input type of the trigger
+ */
+ void TriggerOnChange(ConsoleTriggerType type);
+
+ bool is_configuring{false};
+ f32 motion_sensitivity{0.01f};
+
+ ConsoleMotionParams motion_params;
+ TouchParams touch_params;
+
+ ConsoleMotionDevices motion_devices;
+ TouchDevices touch_devices;
+
+ mutable std::mutex mutex;
+ std::unordered_map<int, ConsoleUpdateCallback> callback_list;
+ int last_callback_key = 0;
+ ConsoleStatus console;
+};
+
+} // 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..4eb5d99bc
--- /dev/null
+++ b/src/core/hid/emulated_controller.cpp
@@ -0,0 +1,745 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included
+
+#include <fmt/format.h>
+
+#include "core/hid/emulated_controller.h"
+#include "core/hid/input_converter.h"
+
+namespace Core::HID {
+constexpr s32 HID_JOYSTICK_MAX = 0x7fff;
+constexpr s32 HID_TRIGGER_MAX = 0x7fff;
+
+EmulatedController::EmulatedController(NpadIdType npad_id_type_) : npad_id_type(npad_id_type_) {}
+
+EmulatedController::~EmulatedController() = default;
+
+NpadType EmulatedController::MapSettingsTypeToNPad(Settings::ControllerType type) {
+ switch (type) {
+ case Settings::ControllerType::ProController:
+ return NpadType::ProController;
+ case Settings::ControllerType::DualJoyconDetached:
+ return NpadType::JoyconDual;
+ case Settings::ControllerType::LeftJoycon:
+ return NpadType::JoyconLeft;
+ case Settings::ControllerType::RightJoycon:
+ return NpadType::JoyconRight;
+ case Settings::ControllerType::Handheld:
+ return NpadType::Handheld;
+ case Settings::ControllerType::GameCube:
+ return NpadType::GameCube;
+ default:
+ return NpadType::ProController;
+ }
+}
+
+Settings::ControllerType EmulatedController::MapNPadToSettingsType(NpadType type) {
+ switch (type) {
+ case NpadType::ProController:
+ return Settings::ControllerType::ProController;
+ case NpadType::JoyconDual:
+ return Settings::ControllerType::DualJoyconDetached;
+ case NpadType::JoyconLeft:
+ return Settings::ControllerType::LeftJoycon;
+ case NpadType::JoyconRight:
+ return Settings::ControllerType::RightJoycon;
+ case NpadType::Handheld:
+ return Settings::ControllerType::Handheld;
+ case NpadType::GameCube:
+ return Settings::ControllerType::GameCube;
+ default:
+ return Settings::ControllerType::ProController;
+ }
+}
+
+void EmulatedController::ReloadFromSettings() {
+ const auto player_index = NpadIdTypeToIndex(npad_id_type);
+ const auto& player = Settings::values.players.GetValue()[player_index];
+
+ for (std::size_t index = 0; index < player.buttons.size(); ++index) {
+ button_params[index] = Common::ParamPackage(player.buttons[index]);
+ }
+ for (std::size_t index = 0; index < player.analogs.size(); ++index) {
+ stick_params[index] = Common::ParamPackage(player.analogs[index]);
+ }
+ for (std::size_t index = 0; index < player.motions.size(); ++index) {
+ motion_params[index] = Common::ParamPackage(player.motions[index]);
+ }
+ ReloadInput();
+}
+
+void EmulatedController::ReloadInput() {
+ const auto player_index = NpadIdTypeToIndex(npad_id_type);
+ const auto& player = Settings::values.players.GetValue()[player_index];
+ const auto left_side = button_params[Settings::NativeButton::ZL];
+ const auto right_side = button_params[Settings::NativeButton::ZR];
+
+ std::transform(button_params.begin() + Settings::NativeButton::BUTTON_HID_BEGIN,
+ button_params.begin() + Settings::NativeButton::BUTTON_NS_END,
+ button_devices.begin(), Input::CreateDevice<Input::InputDevice>);
+ std::transform(stick_params.begin() + Settings::NativeAnalog::STICK_HID_BEGIN,
+ stick_params.begin() + Settings::NativeAnalog::STICK_HID_END,
+ stick_devices.begin(), Input::CreateDevice<Input::InputDevice>);
+ std::transform(motion_params.begin() + Settings::NativeMotion::MOTION_HID_BEGIN,
+ motion_params.begin() + Settings::NativeMotion::MOTION_HID_END,
+ motion_devices.begin(), Input::CreateDevice<Input::InputDevice>);
+
+ trigger_devices[0] =
+ Input::CreateDevice<Input::InputDevice>(button_params[Settings::NativeButton::ZL]);
+ trigger_devices[1] =
+ Input::CreateDevice<Input::InputDevice>(button_params[Settings::NativeButton::ZR]);
+
+ controller.colors_state.left = {
+ .body = player.body_color_left,
+ .button = player.button_color_left,
+ };
+
+ controller.colors_state.right = {
+ .body = player.body_color_right,
+ .button = player.button_color_right,
+ };
+
+ controller.colors_state.fullkey = controller.colors_state.left;
+
+ battery_devices[0] = Input::CreateDevice<Input::InputDevice>(left_side);
+ battery_devices[1] = Input::CreateDevice<Input::InputDevice>(right_side);
+
+ for (std::size_t index = 0; index < button_devices.size(); ++index) {
+ if (!button_devices[index]) {
+ continue;
+ }
+ Input::InputCallback button_callback{
+ [this, index](Input::CallbackStatus callback) { SetButton(callback, index); }};
+ button_devices[index]->SetCallback(button_callback);
+ }
+
+ for (std::size_t index = 0; index < stick_devices.size(); ++index) {
+ if (!stick_devices[index]) {
+ continue;
+ }
+ Input::InputCallback stick_callback{
+ [this, index](Input::CallbackStatus callback) { SetStick(callback, index); }};
+ stick_devices[index]->SetCallback(stick_callback);
+ }
+
+ for (std::size_t index = 0; index < trigger_devices.size(); ++index) {
+ if (!trigger_devices[index]) {
+ continue;
+ }
+ Input::InputCallback trigger_callback{
+ [this, index](Input::CallbackStatus callback) { SetTrigger(callback, index); }};
+ trigger_devices[index]->SetCallback(trigger_callback);
+ }
+
+ for (std::size_t index = 0; index < battery_devices.size(); ++index) {
+ if (!battery_devices[index]) {
+ continue;
+ }
+ Input::InputCallback battery_callback{
+ [this, index](Input::CallbackStatus callback) { SetBattery(callback, index); }};
+ battery_devices[index]->SetCallback(battery_callback);
+ }
+
+ for (std::size_t index = 0; index < motion_devices.size(); ++index) {
+ if (!motion_devices[index]) {
+ continue;
+ }
+ Input::InputCallback motion_callback{
+ [this, index](Input::CallbackStatus callback) { SetMotion(callback, index); }};
+ motion_devices[index]->SetCallback(motion_callback);
+ }
+
+ SetNpadType(MapSettingsTypeToNPad(player.controller_type));
+
+ if (player.connected) {
+ Connect();
+ } else {
+ Disconnect();
+ }
+}
+
+void EmulatedController::UnloadInput() {
+ for (auto& button : button_devices) {
+ button.reset();
+ }
+ for (auto& stick : stick_devices) {
+ stick.reset();
+ }
+ for (auto& motion : motion_devices) {
+ motion.reset();
+ }
+ for (auto& trigger : trigger_devices) {
+ trigger.reset();
+ }
+ for (auto& battery : battery_devices) {
+ battery.reset();
+ }
+}
+
+void EmulatedController::EnableConfiguration() {
+ is_configuring = true;
+ SaveCurrentConfig();
+}
+
+void EmulatedController::DisableConfiguration() {
+ is_configuring = false;
+}
+
+bool EmulatedController::IsConfiguring() const {
+ return is_configuring;
+}
+
+void EmulatedController::SaveCurrentConfig() {
+ if (!is_configuring) {
+ return;
+ }
+
+ const auto player_index = NpadIdTypeToIndex(npad_id_type);
+ auto& player = Settings::values.players.GetValue()[player_index];
+
+ for (std::size_t index = 0; index < player.buttons.size(); ++index) {
+ player.buttons[index] = button_params[index].Serialize();
+ }
+ for (std::size_t index = 0; index < player.analogs.size(); ++index) {
+ player.analogs[index] = stick_params[index].Serialize();
+ }
+ for (std::size_t index = 0; index < player.motions.size(); ++index) {
+ player.motions[index] = motion_params[index].Serialize();
+ }
+}
+
+void EmulatedController::RestoreConfig() {
+ if (!is_configuring) {
+ return;
+ }
+ ReloadFromSettings();
+}
+
+std::vector<Common::ParamPackage> EmulatedController::GetMappedDevices() const {
+ std::vector<Common::ParamPackage> devices;
+ for (const auto& param : button_params) {
+ if (!param.Has("engine")) {
+ continue;
+ }
+ const auto devices_it = std::find_if(
+ devices.begin(), devices.end(), [param](const Common::ParamPackage param_) {
+ return param.Get("engine", "") == param_.Get("engine", "") &&
+ param.Get("guid", "") == param_.Get("guid", "") &&
+ param.Get("port", "") == param_.Get("port", "");
+ });
+ if (devices_it != devices.end()) {
+ continue;
+ }
+ Common::ParamPackage device{};
+ device.Set("engine", param.Get("engine", ""));
+ device.Set("guid", param.Get("guid", ""));
+ device.Set("port", param.Get("port", ""));
+ devices.push_back(device);
+ }
+
+ for (const auto& param : stick_params) {
+ if (!param.Has("engine")) {
+ continue;
+ }
+ if (param.Get("engine", "") == "analog_from_button") {
+ continue;
+ }
+ const auto devices_it = std::find_if(
+ devices.begin(), devices.end(), [param](const Common::ParamPackage param_) {
+ return param.Get("engine", "") == param_.Get("engine", "") &&
+ param.Get("guid", "") == param_.Get("guid", "") &&
+ param.Get("port", "") == param_.Get("port", "");
+ });
+ if (devices_it != devices.end()) {
+ continue;
+ }
+ Common::ParamPackage device{};
+ device.Set("engine", param.Get("engine", ""));
+ device.Set("guid", param.Get("guid", ""));
+ device.Set("port", param.Get("port", ""));
+ devices.push_back(device);
+ }
+ return devices;
+}
+
+Common::ParamPackage EmulatedController::GetButtonParam(std::size_t index) const {
+ if (index >= button_params.size()) {
+ return {};
+ }
+ return button_params[index];
+}
+
+Common::ParamPackage EmulatedController::GetStickParam(std::size_t index) const {
+ if (index >= stick_params.size()) {
+ return {};
+ }
+ return stick_params[index];
+}
+
+Common::ParamPackage EmulatedController::GetMotionParam(std::size_t index) const {
+ if (index >= motion_params.size()) {
+ return {};
+ }
+ return motion_params[index];
+}
+
+void EmulatedController::SetButtonParam(std::size_t index, Common::ParamPackage param) {
+ if (index >= button_params.size()) {
+ return;
+ }
+ button_params[index] = param;
+ ReloadInput();
+}
+
+void EmulatedController::SetStickParam(std::size_t index, Common::ParamPackage param) {
+ if (index >= stick_params.size()) {
+ return;
+ }
+ stick_params[index] = param;
+ ReloadInput();
+}
+
+void EmulatedController::SetMotionParam(std::size_t index, Common::ParamPackage param) {
+ if (index >= motion_params.size()) {
+ return;
+ }
+ motion_params[index] = param;
+ ReloadInput();
+}
+
+void EmulatedController::SetButton(Input::CallbackStatus callback, std::size_t index) {
+ if (index >= controller.button_values.size()) {
+ return;
+ }
+ std::lock_guard lock{mutex};
+ bool value_changed = false;
+ const auto new_status = TransformToButton(callback);
+ auto& current_status = controller.button_values[index];
+ current_status.toggle = new_status.toggle;
+
+ // Update button status with current
+ if (!current_status.toggle) {
+ current_status.locked = false;
+ if (current_status.value != new_status.value) {
+ current_status.value = new_status.value;
+ value_changed = true;
+ }
+ } else {
+ // Toggle button and lock status
+ if (new_status.value && !current_status.locked) {
+ current_status.locked = true;
+ current_status.value = !current_status.value;
+ value_changed = true;
+ }
+
+ // Unlock button ready for next press
+ if (!new_status.value && current_status.locked) {
+ current_status.locked = false;
+ }
+ }
+
+ if (!value_changed) {
+ return;
+ }
+
+ if (is_configuring) {
+ controller.npad_button_state.raw = NpadButton::None;
+ controller.debug_pad_button_state.raw = 0;
+ TriggerOnChange(ControllerTriggerType::Button);
+ return;
+ }
+
+ switch (index) {
+ case Settings::NativeButton::A:
+ controller.npad_button_state.a.Assign(current_status.value);
+ controller.debug_pad_button_state.a.Assign(current_status.value);
+ break;
+ case Settings::NativeButton::B:
+ controller.npad_button_state.b.Assign(current_status.value);
+ controller.debug_pad_button_state.b.Assign(current_status.value);
+ break;
+ case Settings::NativeButton::X:
+ controller.npad_button_state.x.Assign(current_status.value);
+ controller.debug_pad_button_state.x.Assign(current_status.value);
+ break;
+ case Settings::NativeButton::Y:
+ controller.npad_button_state.y.Assign(current_status.value);
+ controller.debug_pad_button_state.y.Assign(current_status.value);
+ break;
+ case Settings::NativeButton::LStick:
+ controller.npad_button_state.stick_l.Assign(current_status.value);
+ break;
+ case Settings::NativeButton::RStick:
+ controller.npad_button_state.stick_r.Assign(current_status.value);
+ break;
+ case Settings::NativeButton::L:
+ controller.npad_button_state.l.Assign(current_status.value);
+ controller.debug_pad_button_state.l.Assign(current_status.value);
+ break;
+ case Settings::NativeButton::R:
+ controller.npad_button_state.r.Assign(current_status.value);
+ controller.debug_pad_button_state.r.Assign(current_status.value);
+ break;
+ case Settings::NativeButton::ZL:
+ controller.npad_button_state.zl.Assign(current_status.value);
+ controller.debug_pad_button_state.zl.Assign(current_status.value);
+ break;
+ case Settings::NativeButton::ZR:
+ controller.npad_button_state.zr.Assign(current_status.value);
+ controller.debug_pad_button_state.zr.Assign(current_status.value);
+ break;
+ case Settings::NativeButton::Plus:
+ controller.npad_button_state.plus.Assign(current_status.value);
+ controller.debug_pad_button_state.plus.Assign(current_status.value);
+ break;
+ case Settings::NativeButton::Minus:
+ controller.npad_button_state.minus.Assign(current_status.value);
+ controller.debug_pad_button_state.minus.Assign(current_status.value);
+ break;
+ case Settings::NativeButton::DLeft:
+ controller.npad_button_state.left.Assign(current_status.value);
+ controller.debug_pad_button_state.d_left.Assign(current_status.value);
+ break;
+ case Settings::NativeButton::DUp:
+ controller.npad_button_state.up.Assign(current_status.value);
+ controller.debug_pad_button_state.d_up.Assign(current_status.value);
+ break;
+ case Settings::NativeButton::DRight:
+ controller.npad_button_state.right.Assign(current_status.value);
+ controller.debug_pad_button_state.d_right.Assign(current_status.value);
+ break;
+ case Settings::NativeButton::DDown:
+ controller.npad_button_state.down.Assign(current_status.value);
+ controller.debug_pad_button_state.d_down.Assign(current_status.value);
+ break;
+ case Settings::NativeButton::SL:
+ controller.npad_button_state.left_sl.Assign(current_status.value);
+ controller.npad_button_state.right_sl.Assign(current_status.value);
+ break;
+ case Settings::NativeButton::SR:
+ controller.npad_button_state.left_sr.Assign(current_status.value);
+ controller.npad_button_state.right_sr.Assign(current_status.value);
+ break;
+ case Settings::NativeButton::Home:
+ case Settings::NativeButton::Screenshot:
+ break;
+ }
+ TriggerOnChange(ControllerTriggerType::Button);
+}
+
+void EmulatedController::SetStick(Input::CallbackStatus callback, std::size_t index) {
+ if (index >= controller.stick_values.size()) {
+ return;
+ }
+ std::lock_guard lock{mutex};
+ controller.stick_values[index] = TransformToStick(callback);
+
+ if (is_configuring) {
+ controller.analog_stick_state.left = {};
+ controller.analog_stick_state.right = {};
+ TriggerOnChange(ControllerTriggerType::Stick);
+ return;
+ }
+
+ const AnalogStickState stick{
+ .x = static_cast<s32>(controller.stick_values[index].x.value * HID_JOYSTICK_MAX),
+ .y = static_cast<s32>(controller.stick_values[index].y.value * HID_JOYSTICK_MAX),
+ };
+
+ switch (index) {
+ case Settings::NativeAnalog::LStick:
+ controller.analog_stick_state.left = stick;
+ controller.npad_button_state.stick_l_left.Assign(controller.stick_values[index].left);
+ controller.npad_button_state.stick_l_up.Assign(controller.stick_values[index].up);
+ controller.npad_button_state.stick_l_right.Assign(controller.stick_values[index].right);
+ controller.npad_button_state.stick_l_down.Assign(controller.stick_values[index].down);
+ break;
+ case Settings::NativeAnalog::RStick:
+ controller.analog_stick_state.right = stick;
+ controller.npad_button_state.stick_r_left.Assign(controller.stick_values[index].left);
+ controller.npad_button_state.stick_r_up.Assign(controller.stick_values[index].up);
+ controller.npad_button_state.stick_r_right.Assign(controller.stick_values[index].right);
+ controller.npad_button_state.stick_r_down.Assign(controller.stick_values[index].down);
+ break;
+ }
+
+ TriggerOnChange(ControllerTriggerType::Stick);
+}
+
+void EmulatedController::SetTrigger(Input::CallbackStatus callback, std::size_t index) {
+ if (index >= controller.trigger_values.size()) {
+ return;
+ }
+ std::lock_guard lock{mutex};
+ controller.trigger_values[index] = TransformToTrigger(callback);
+
+ if (is_configuring) {
+ controller.gc_trigger_state.left = 0;
+ controller.gc_trigger_state.right = 0;
+ TriggerOnChange(ControllerTriggerType::Trigger);
+ return;
+ }
+
+ const auto trigger = controller.trigger_values[index];
+
+ switch (index) {
+ case Settings::NativeTrigger::LTrigger:
+ controller.gc_trigger_state.left = static_cast<s32>(trigger.analog.value * HID_TRIGGER_MAX);
+ controller.npad_button_state.zl.Assign(trigger.pressed);
+ break;
+ case Settings::NativeTrigger::RTrigger:
+ controller.gc_trigger_state.right =
+ static_cast<s32>(trigger.analog.value * HID_TRIGGER_MAX);
+ controller.npad_button_state.zr.Assign(trigger.pressed);
+ break;
+ }
+
+ TriggerOnChange(ControllerTriggerType::Trigger);
+}
+
+void EmulatedController::SetMotion(Input::CallbackStatus callback, std::size_t index) {
+ if (index >= controller.motion_values.size()) {
+ return;
+ }
+ std::lock_guard lock{mutex};
+ auto& raw_status = controller.motion_values[index].raw_status;
+ auto& emulated = controller.motion_values[index].emulated;
+
+ raw_status = TransformToMotion(callback);
+ emulated.SetAcceleration(Common::Vec3f{
+ raw_status.accel.x.value,
+ raw_status.accel.y.value,
+ raw_status.accel.z.value,
+ });
+ emulated.SetGyroscope(Common::Vec3f{
+ raw_status.gyro.x.value,
+ raw_status.gyro.y.value,
+ raw_status.gyro.z.value,
+ });
+ emulated.UpdateRotation(raw_status.delta_timestamp);
+ emulated.UpdateOrientation(raw_status.delta_timestamp);
+
+ if (is_configuring) {
+ TriggerOnChange(ControllerTriggerType::Motion);
+ return;
+ }
+
+ auto& motion = controller.motion_state[index];
+ motion.accel = emulated.GetAcceleration();
+ motion.gyro = emulated.GetGyroscope();
+ motion.rotation = emulated.GetGyroscope();
+ motion.orientation = emulated.GetOrientation();
+ motion.is_at_rest = emulated.IsMoving(motion_sensitivity);
+
+ TriggerOnChange(ControllerTriggerType::Motion);
+}
+
+void EmulatedController::SetBattery(Input::CallbackStatus callback, std::size_t index) {
+ if (index >= controller.battery_values.size()) {
+ return;
+ }
+ std::lock_guard lock{mutex};
+ controller.battery_values[index] = TransformToBattery(callback);
+
+ if (is_configuring) {
+ TriggerOnChange(ControllerTriggerType::Battery);
+ return;
+ }
+
+ bool is_charging = false;
+ bool is_powered = false;
+ BatteryLevel battery_level = 0;
+ switch (controller.battery_values[index]) {
+ case Input::BatteryLevel::Charging:
+ is_charging = true;
+ is_powered = true;
+ battery_level = 6;
+ break;
+ case Input::BatteryLevel::Medium:
+ battery_level = 6;
+ break;
+ case Input::BatteryLevel::Low:
+ battery_level = 4;
+ break;
+ case Input::BatteryLevel::Critical:
+ battery_level = 2;
+ break;
+ case Input::BatteryLevel::Empty:
+ battery_level = 0;
+ break;
+ case Input::BatteryLevel::Full:
+ default:
+ is_powered = true;
+ battery_level = 8;
+ break;
+ }
+
+ switch (index) {
+ case 0:
+ controller.battery_state.left = {
+ .is_powered = is_powered,
+ .is_charging = is_charging,
+ .battery_level = battery_level,
+ };
+ break;
+ case 1:
+ controller.battery_state.right = {
+ .is_powered = is_powered,
+ .is_charging = is_charging,
+ .battery_level = battery_level,
+ };
+ break;
+ case 2:
+ controller.battery_state.dual = {
+ .is_powered = is_powered,
+ .is_charging = is_charging,
+ .battery_level = battery_level,
+ };
+ break;
+ }
+ TriggerOnChange(ControllerTriggerType::Battery);
+}
+
+bool EmulatedController::SetVibration([[maybe_unused]] std::size_t device_index,
+ [[maybe_unused]] VibrationValue vibration) {
+ return false;
+}
+
+int EmulatedController::TestVibration(std::size_t device_index) {
+ return 1;
+}
+
+void EmulatedController::Connect() {
+ std::lock_guard lock{mutex};
+ if (is_connected) {
+ LOG_WARNING(Service_HID, "Tried to turn on a connected controller {}", npad_id_type);
+ return;
+ }
+ is_connected = true;
+ TriggerOnChange(ControllerTriggerType::Connected);
+}
+
+void EmulatedController::Disconnect() {
+ std::lock_guard lock{mutex};
+ if (!is_connected) {
+ LOG_WARNING(Service_HID, "Tried to turn off a disconnected controller {}", npad_id_type);
+ return;
+ }
+ is_connected = false;
+ TriggerOnChange(ControllerTriggerType::Disconnected);
+}
+
+bool EmulatedController::IsConnected() const {
+ return is_connected;
+}
+
+bool EmulatedController::IsVibrationEnabled() const {
+ return is_vibration_enabled;
+}
+
+NpadIdType EmulatedController::GetNpadIdType() const {
+ return npad_id_type;
+}
+
+NpadType EmulatedController::GetNpadType() const {
+ return npad_type;
+}
+
+void EmulatedController::SetNpadType(NpadType npad_type_) {
+ std::lock_guard lock{mutex};
+ if (npad_type == npad_type_) {
+ return;
+ }
+ npad_type = npad_type_;
+ TriggerOnChange(ControllerTriggerType::Type);
+}
+
+ButtonValues EmulatedController::GetButtonsValues() const {
+ return controller.button_values;
+}
+
+SticksValues EmulatedController::GetSticksValues() const {
+ return controller.stick_values;
+}
+
+TriggerValues EmulatedController::GetTriggersValues() const {
+ return controller.trigger_values;
+}
+
+ControllerMotionValues EmulatedController::GetMotionValues() const {
+ return controller.motion_values;
+}
+
+ColorValues EmulatedController::GetColorsValues() const {
+ return controller.color_values;
+}
+
+BatteryValues EmulatedController::GetBatteryValues() const {
+ return controller.battery_values;
+}
+
+NpadButtonState EmulatedController::GetNpadButtons() const {
+ if (is_configuring) {
+ return {};
+ }
+ return controller.npad_button_state;
+}
+
+DebugPadButton EmulatedController::GetDebugPadButtons() const {
+ if (is_configuring) {
+ return {};
+ }
+ return controller.debug_pad_button_state;
+}
+
+AnalogSticks EmulatedController::GetSticks() const {
+ if (is_configuring) {
+ return {};
+ }
+ return controller.analog_stick_state;
+}
+
+NpadGcTriggerState EmulatedController::GetTriggers() const {
+ if (is_configuring) {
+ return {};
+ }
+ return controller.gc_trigger_state;
+}
+
+MotionState EmulatedController::GetMotions() const {
+ return controller.motion_state;
+}
+
+ControllerColors EmulatedController::GetColors() const {
+ return controller.colors_state;
+}
+
+BatteryLevelState EmulatedController::GetBattery() const {
+ return controller.battery_state;
+}
+
+void EmulatedController::TriggerOnChange(ControllerTriggerType type) {
+ for (const std::pair<int, ControllerUpdateCallback> poller_pair : callback_list) {
+ const ControllerUpdateCallback& poller = poller_pair.second;
+ if (poller.on_change) {
+ poller.on_change(type);
+ }
+ }
+}
+
+int EmulatedController::SetCallback(ControllerUpdateCallback update_callback) {
+ std::lock_guard lock{mutex};
+ callback_list.insert_or_assign(last_callback_key, update_callback);
+ return last_callback_key++;
+}
+
+void EmulatedController::DeleteCallback(int key) {
+ std::lock_guard lock{mutex};
+ if (!callback_list.contains(key)) {
+ LOG_ERROR(Input, "Tried to delete non-existent callback {}", key);
+ return;
+ }
+ callback_list.erase(key);
+}
+} // 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..94db9b00b
--- /dev/null
+++ b/src/core/hid/emulated_controller.h
@@ -0,0 +1,232 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <functional>
+#include <mutex>
+#include <unordered_map>
+
+#include "common/input.h"
+#include "common/param_package.h"
+#include "common/point.h"
+#include "common/quaternion.h"
+#include "common/settings.h"
+#include "common/vector_math.h"
+#include "core/hid/hid_types.h"
+#include "core/hid/motion_input.h"
+
+namespace Core::HID {
+
+struct ControllerMotionInfo {
+ Input::MotionStatus raw_status;
+ MotionInput emulated{};
+};
+
+using ButtonDevices =
+ std::array<std::unique_ptr<Input::InputDevice>, Settings::NativeButton::NumButtons>;
+using StickDevices =
+ std::array<std::unique_ptr<Input::InputDevice>, Settings::NativeAnalog::NumAnalogs>;
+using ControllerMotionDevices =
+ std::array<std::unique_ptr<Input::InputDevice>, Settings::NativeMotion::NumMotions>;
+using TriggerDevices =
+ std::array<std::unique_ptr<Input::InputDevice>, Settings::NativeTrigger::NumTriggers>;
+using BatteryDevices = std::array<std::unique_ptr<Input::InputDevice>, 2>;
+
+using ButtonParams = std::array<Common::ParamPackage, Settings::NativeButton::NumButtons>;
+using StickParams = std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs>;
+using ControllerMotionParams = std::array<Common::ParamPackage, Settings::NativeMotion::NumMotions>;
+using TriggerParams = std::array<Common::ParamPackage, Settings::NativeTrigger::NumTriggers>;
+using BatteryParams = std::array<Common::ParamPackage, 2>;
+
+using ButtonValues = std::array<Input::ButtonStatus, Settings::NativeButton::NumButtons>;
+using SticksValues = std::array<Input::StickStatus, Settings::NativeAnalog::NumAnalogs>;
+using TriggerValues = std::array<Input::TriggerStatus, Settings::NativeTrigger::NumTriggers>;
+using ControllerMotionValues = std::array<ControllerMotionInfo, Settings::NativeMotion::NumMotions>;
+using ColorValues = std::array<Input::BodyColorStatus, 3>;
+using BatteryValues = std::array<Input::BatteryStatus, 3>;
+using VibrationValues = std::array<Input::VibrationStatus, 2>;
+
+struct AnalogSticks {
+ AnalogStickState left;
+ AnalogStickState right;
+};
+
+struct ControllerColors {
+ NpadControllerColor fullkey;
+ NpadControllerColor left;
+ NpadControllerColor right;
+};
+
+struct BatteryLevelState {
+ NpadPowerInfo dual;
+ NpadPowerInfo left;
+ NpadPowerInfo right;
+};
+
+struct ControllerMotion {
+ bool is_at_rest;
+ Common::Vec3f accel{};
+ Common::Vec3f gyro{};
+ Common::Vec3f rotation{};
+ std::array<Common::Vec3f, 3> orientation{};
+};
+
+using MotionState = std::array<ControllerMotion, 2>;
+
+struct ControllerStatus {
+ // Data from input_common
+ ButtonValues button_values{};
+ SticksValues stick_values{};
+ ControllerMotionValues motion_values{};
+ TriggerValues trigger_values{};
+ ColorValues color_values{};
+ BatteryValues battery_values{};
+ VibrationValues vibration_values{};
+
+ // Data for Nintendo devices
+ NpadButtonState npad_button_state{};
+ DebugPadButton debug_pad_button_state{};
+ AnalogSticks analog_stick_state{};
+ MotionState motion_state{};
+ NpadGcTriggerState gc_trigger_state{};
+ ControllerColors colors_state{};
+ BatteryLevelState battery_state{};
+};
+enum class ControllerTriggerType {
+ Button,
+ Stick,
+ Trigger,
+ Motion,
+ Color,
+ Battery,
+ Vibration,
+ Connected,
+ Disconnected,
+ Type,
+ All,
+};
+
+struct ControllerUpdateCallback {
+ std::function<void(ControllerTriggerType)> on_change;
+};
+
+class EmulatedController {
+public:
+ /**
+ * TODO: Write description
+ *
+ * @param npad_id_type
+ */
+ explicit EmulatedController(NpadIdType npad_id_type_);
+ ~EmulatedController();
+
+ YUZU_NON_COPYABLE(EmulatedController);
+ YUZU_NON_MOVEABLE(EmulatedController);
+
+ static NpadType MapSettingsTypeToNPad(Settings::ControllerType type);
+ static Settings::ControllerType MapNPadToSettingsType(NpadType type);
+
+ /// Gets the NpadIdType for this controller.
+ NpadIdType GetNpadIdType() const;
+
+ /// Sets the NpadType for this controller.
+ void SetNpadType(NpadType npad_type_);
+
+ /// Gets the NpadType for this controller.
+ NpadType GetNpadType() const;
+
+ void Connect();
+ void Disconnect();
+
+ bool IsConnected() const;
+ bool IsVibrationEnabled() const;
+
+ void ReloadFromSettings();
+ void ReloadInput();
+ void UnloadInput();
+
+ void EnableConfiguration();
+ void DisableConfiguration();
+ bool IsConfiguring() const;
+ void SaveCurrentConfig();
+ void RestoreConfig();
+
+ std::vector<Common::ParamPackage> GetMappedDevices() const;
+
+ Common::ParamPackage GetButtonParam(std::size_t index) const;
+ Common::ParamPackage GetStickParam(std::size_t index) const;
+ Common::ParamPackage GetMotionParam(std::size_t index) const;
+
+ void SetButtonParam(std::size_t index, Common::ParamPackage param);
+ void SetStickParam(std::size_t index, Common::ParamPackage param);
+ void SetMotionParam(std::size_t index, Common::ParamPackage param);
+
+ ButtonValues GetButtonsValues() const;
+ SticksValues GetSticksValues() const;
+ TriggerValues GetTriggersValues() const;
+ ControllerMotionValues GetMotionValues() const;
+ ColorValues GetColorsValues() const;
+ BatteryValues GetBatteryValues() const;
+
+ NpadButtonState GetNpadButtons() const;
+ DebugPadButton GetDebugPadButtons() const;
+ AnalogSticks GetSticks() const;
+ NpadGcTriggerState GetTriggers() const;
+ MotionState GetMotions() const;
+ ControllerColors GetColors() const;
+ BatteryLevelState GetBattery() const;
+
+ bool SetVibration(std::size_t device_index, VibrationValue vibration);
+ int TestVibration(std::size_t device_index);
+
+ int SetCallback(ControllerUpdateCallback update_callback);
+ void DeleteCallback(int key);
+
+private:
+ /**
+ * Sets the status of a button. Applies toggle properties to the output.
+ *
+ * @param A CallbackStatus and a button index number
+ */
+ void SetButton(Input::CallbackStatus callback, std::size_t index);
+ void SetStick(Input::CallbackStatus callback, std::size_t index);
+ void SetTrigger(Input::CallbackStatus callback, std::size_t index);
+ void SetMotion(Input::CallbackStatus callback, std::size_t index);
+ void SetBattery(Input::CallbackStatus callback, std::size_t index);
+
+ /**
+ * Triggers a callback that something has changed
+ *
+ * @param Input type of the trigger
+ */
+ void TriggerOnChange(ControllerTriggerType type);
+
+ NpadIdType npad_id_type;
+ NpadType npad_type{NpadType::None};
+ bool is_connected{false};
+ bool is_configuring{false};
+ bool is_vibration_enabled{true};
+ f32 motion_sensitivity{0.01f};
+
+ ButtonParams button_params;
+ StickParams stick_params;
+ ControllerMotionParams motion_params;
+ TriggerParams trigger_params;
+ BatteryParams battery_params;
+
+ ButtonDevices button_devices;
+ StickDevices stick_devices;
+ ControllerMotionDevices motion_devices;
+ TriggerDevices trigger_devices;
+ BatteryDevices battery_devices;
+ // VibrationDevices vibration_devices;
+
+ mutable std::mutex mutex;
+ std::unordered_map<int, ControllerUpdateCallback> callback_list;
+ int last_callback_key = 0;
+ ControllerStatus controller;
+};
+
+} // 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..3caf90714
--- /dev/null
+++ b/src/core/hid/emulated_devices.cpp
@@ -0,0 +1,349 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included
+
+#include <fmt/format.h>
+
+#include "core/hid/emulated_devices.h"
+#include "core/hid/input_converter.h"
+
+namespace Core::HID {
+
+EmulatedDevices::EmulatedDevices() {}
+
+EmulatedDevices::~EmulatedDevices() = default;
+
+void EmulatedDevices::ReloadFromSettings() {
+ const auto& mouse = Settings::values.mouse_buttons;
+
+ for (std::size_t index = 0; index < mouse.size(); ++index) {
+ mouse_button_params[index] = Common::ParamPackage(mouse[index]);
+ }
+ ReloadInput();
+}
+
+void EmulatedDevices::ReloadInput() {
+ std::transform(mouse_button_params.begin() + Settings::NativeMouseButton::MOUSE_HID_BEGIN,
+ mouse_button_params.begin() + Settings::NativeMouseButton::MOUSE_HID_END,
+ mouse_button_devices.begin(), Input::CreateDevice<Input::InputDevice>);
+
+ std::transform(Settings::values.keyboard_keys.begin(), Settings::values.keyboard_keys.end(),
+ keyboard_devices.begin(), Input::CreateDeviceFromString<Input::InputDevice>);
+
+ std::transform(Settings::values.keyboard_mods.begin(), Settings::values.keyboard_mods.end(),
+ keyboard_modifier_devices.begin(),
+ Input::CreateDeviceFromString<Input::InputDevice>);
+
+ for (std::size_t index = 0; index < mouse_button_devices.size(); ++index) {
+ if (!mouse_button_devices[index]) {
+ continue;
+ }
+ Input::InputCallback button_callback{
+ [this, index](Input::CallbackStatus callback) { SetMouseButton(callback, index); }};
+ mouse_button_devices[index]->SetCallback(button_callback);
+ }
+
+ for (std::size_t index = 0; index < keyboard_devices.size(); ++index) {
+ if (!keyboard_devices[index]) {
+ continue;
+ }
+ Input::InputCallback button_callback{
+ [this, index](Input::CallbackStatus callback) { SetKeyboardButton(callback, index); }};
+ keyboard_devices[index]->SetCallback(button_callback);
+ }
+
+ for (std::size_t index = 0; index < keyboard_modifier_devices.size(); ++index) {
+ if (!keyboard_modifier_devices[index]) {
+ continue;
+ }
+ Input::InputCallback button_callback{[this, index](Input::CallbackStatus callback) {
+ SetKeyboardModifier(callback, index);
+ }};
+ keyboard_modifier_devices[index]->SetCallback(button_callback);
+ }
+}
+
+void EmulatedDevices::UnloadInput() {
+ for (auto& button : mouse_button_devices) {
+ button.reset();
+ }
+ for (auto& button : keyboard_devices) {
+ button.reset();
+ }
+ for (auto& button : keyboard_modifier_devices) {
+ button.reset();
+ }
+}
+
+void EmulatedDevices::EnableConfiguration() {
+ is_configuring = true;
+ SaveCurrentConfig();
+}
+
+void EmulatedDevices::DisableConfiguration() {
+ is_configuring = false;
+}
+
+bool EmulatedDevices::IsConfiguring() const {
+ return is_configuring;
+}
+
+void EmulatedDevices::SaveCurrentConfig() {
+ if (!is_configuring) {
+ return;
+ }
+
+ auto& mouse = Settings::values.mouse_buttons;
+
+ for (std::size_t index = 0; index < mouse.size(); ++index) {
+ mouse[index] = mouse_button_params[index].Serialize();
+ }
+}
+
+void EmulatedDevices::RestoreConfig() {
+ if (!is_configuring) {
+ return;
+ }
+ ReloadFromSettings();
+}
+
+Common::ParamPackage EmulatedDevices::GetMouseButtonParam(std::size_t index) const {
+ if (index >= mouse_button_params.size()) {
+ return {};
+ }
+ return mouse_button_params[index];
+}
+
+void EmulatedDevices::SetButtonParam(std::size_t index, Common::ParamPackage param) {
+ if (index >= mouse_button_params.size()) {
+ return;
+ }
+ mouse_button_params[index] = param;
+ ReloadInput();
+}
+
+void EmulatedDevices::SetKeyboardButton(Input::CallbackStatus callback, std::size_t index) {
+ if (index >= device_status.keyboard_values.size()) {
+ return;
+ }
+ std::lock_guard lock{mutex};
+ bool value_changed = false;
+ const auto new_status = TransformToButton(callback);
+ auto& current_status = device_status.keyboard_values[index];
+ current_status.toggle = new_status.toggle;
+
+ // Update button status with current
+ if (!current_status.toggle) {
+ current_status.locked = false;
+ if (current_status.value != new_status.value) {
+ current_status.value = new_status.value;
+ value_changed = true;
+ }
+ } else {
+ // Toggle button and lock status
+ if (new_status.value && !current_status.locked) {
+ current_status.locked = true;
+ current_status.value = !current_status.value;
+ value_changed = true;
+ }
+
+ // Unlock button ready for next press
+ if (!new_status.value && current_status.locked) {
+ current_status.locked = false;
+ }
+ }
+
+ if (!value_changed) {
+ return;
+ }
+
+ if (is_configuring) {
+ TriggerOnChange(DeviceTriggerType::Keyboard);
+ return;
+ }
+
+ // TODO(german77): Do this properly
+ // switch (index) {
+ // case Settings::NativeKeyboard::A:
+ // interface_status.keyboard_state.a.Assign(current_status.value);
+ // break;
+ // ....
+ //}
+
+ TriggerOnChange(DeviceTriggerType::Keyboard);
+}
+
+void EmulatedDevices::SetKeyboardModifier(Input::CallbackStatus callback, std::size_t index) {
+ if (index >= device_status.keyboard_moddifier_values.size()) {
+ return;
+ }
+ std::lock_guard lock{mutex};
+ bool value_changed = false;
+ const auto new_status = TransformToButton(callback);
+ auto& current_status = device_status.keyboard_moddifier_values[index];
+ current_status.toggle = new_status.toggle;
+
+ // Update button status with current
+ if (!current_status.toggle) {
+ current_status.locked = false;
+ if (current_status.value != new_status.value) {
+ current_status.value = new_status.value;
+ value_changed = true;
+ }
+ } else {
+ // Toggle button and lock status
+ if (new_status.value && !current_status.locked) {
+ current_status.locked = true;
+ current_status.value = !current_status.value;
+ value_changed = true;
+ }
+
+ // Unlock button ready for next press
+ if (!new_status.value && current_status.locked) {
+ current_status.locked = false;
+ }
+ }
+
+ if (!value_changed) {
+ return;
+ }
+
+ if (is_configuring) {
+ TriggerOnChange(DeviceTriggerType::KeyboardModdifier);
+ return;
+ }
+
+ switch (index) {
+ case Settings::NativeKeyboard::LeftControl:
+ case Settings::NativeKeyboard::RightControl:
+ device_status.keyboard_moddifier_state.control.Assign(current_status.value);
+ break;
+ case Settings::NativeKeyboard::LeftShift:
+ case Settings::NativeKeyboard::RightShift:
+ device_status.keyboard_moddifier_state.shift.Assign(current_status.value);
+ break;
+ case Settings::NativeKeyboard::LeftAlt:
+ device_status.keyboard_moddifier_state.left_alt.Assign(current_status.value);
+ break;
+ case Settings::NativeKeyboard::RightAlt:
+ device_status.keyboard_moddifier_state.right_alt.Assign(current_status.value);
+ break;
+ case Settings::NativeKeyboard::CapsLock:
+ device_status.keyboard_moddifier_state.caps_lock.Assign(current_status.value);
+ break;
+ case Settings::NativeKeyboard::ScrollLock:
+ device_status.keyboard_moddifier_state.scroll_lock.Assign(current_status.value);
+ break;
+ case Settings::NativeKeyboard::NumLock:
+ device_status.keyboard_moddifier_state.num_lock.Assign(current_status.value);
+ break;
+ }
+
+ TriggerOnChange(DeviceTriggerType::KeyboardModdifier);
+}
+
+void EmulatedDevices::SetMouseButton(Input::CallbackStatus callback, std::size_t index) {
+ if (index >= device_status.mouse_button_values.size()) {
+ return;
+ }
+ std::lock_guard lock{mutex};
+ bool value_changed = false;
+ const auto new_status = TransformToButton(callback);
+ auto& current_status = device_status.mouse_button_values[index];
+ current_status.toggle = new_status.toggle;
+
+ // Update button status with current
+ if (!current_status.toggle) {
+ current_status.locked = false;
+ if (current_status.value != new_status.value) {
+ current_status.value = new_status.value;
+ value_changed = true;
+ }
+ } else {
+ // Toggle button and lock status
+ if (new_status.value && !current_status.locked) {
+ current_status.locked = true;
+ current_status.value = !current_status.value;
+ value_changed = true;
+ }
+
+ // Unlock button ready for next press
+ if (!new_status.value && current_status.locked) {
+ current_status.locked = false;
+ }
+ }
+
+ if (!value_changed) {
+ return;
+ }
+
+ if (is_configuring) {
+ TriggerOnChange(DeviceTriggerType::Mouse);
+ return;
+ }
+
+ switch (index) {
+ case Settings::NativeMouseButton::Left:
+ device_status.mouse_button_state.left.Assign(current_status.value);
+ break;
+ case Settings::NativeMouseButton::Right:
+ device_status.mouse_button_state.right.Assign(current_status.value);
+ break;
+ case Settings::NativeMouseButton::Middle:
+ device_status.mouse_button_state.middle.Assign(current_status.value);
+ break;
+ case Settings::NativeMouseButton::Forward:
+ device_status.mouse_button_state.forward.Assign(current_status.value);
+ break;
+ case Settings::NativeMouseButton::Back:
+ device_status.mouse_button_state.back.Assign(current_status.value);
+ break;
+ }
+
+ TriggerOnChange(DeviceTriggerType::Mouse);
+}
+
+MouseButtonValues EmulatedDevices::GetMouseButtonsValues() const {
+ return device_status.mouse_button_values;
+}
+
+KeyboardKey EmulatedDevices::GetKeyboard() const {
+ return device_status.keyboard_state;
+}
+
+KeyboardModifier EmulatedDevices::GetKeyboardModifier() const {
+ return device_status.keyboard_moddifier_state;
+}
+
+MouseButton EmulatedDevices::GetMouseButtons() const {
+ return device_status.mouse_button_state;
+}
+
+MousePosition EmulatedDevices::GetMousePosition() const {
+ return device_status.mouse_position_state;
+}
+
+void EmulatedDevices::TriggerOnChange(DeviceTriggerType type) {
+ for (const std::pair<int, InterfaceUpdateCallback> poller_pair : callback_list) {
+ const InterfaceUpdateCallback& poller = poller_pair.second;
+ if (poller.on_change) {
+ poller.on_change(type);
+ }
+ }
+}
+
+int EmulatedDevices::SetCallback(InterfaceUpdateCallback update_callback) {
+ std::lock_guard lock{mutex};
+ callback_list.insert_or_assign(last_callback_key, update_callback);
+ return last_callback_key++;
+}
+
+void EmulatedDevices::DeleteCallback(int key) {
+ std::lock_guard lock{mutex};
+ if (!callback_list.contains(key)) {
+ LOG_ERROR(Input, "Tried to delete non-existent callback {}", key);
+ return;
+ }
+ callback_list.erase(key);
+}
+} // 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..6f728eff5
--- /dev/null
+++ b/src/core/hid/emulated_devices.h
@@ -0,0 +1,137 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <functional>
+#include <mutex>
+#include <unordered_map>
+
+#include "common/input.h"
+#include "common/param_package.h"
+#include "common/settings.h"
+#include "core/hid/hid_types.h"
+#include "core/hid/motion_input.h"
+
+namespace Core::HID {
+
+using KeyboardDevices =
+ std::array<std::unique_ptr<Input::InputDevice>, Settings::NativeKeyboard::NumKeyboardKeys>;
+using KeyboardModifierDevices =
+ std::array<std::unique_ptr<Input::InputDevice>, Settings::NativeKeyboard::NumKeyboardMods>;
+using MouseButtonDevices =
+ std::array<std::unique_ptr<Input::InputDevice>, Settings::NativeMouseButton::NumMouseButtons>;
+
+using MouseButtonParams =
+ std::array<Common::ParamPackage, Settings::NativeMouseButton::NumMouseButtons>;
+
+using KeyboardValues = std::array<Input::ButtonStatus, Settings::NativeKeyboard::NumKeyboardKeys>;
+using KeyboardModifierValues =
+ std::array<Input::ButtonStatus, Settings::NativeKeyboard::NumKeyboardMods>;
+using MouseButtonValues =
+ std::array<Input::ButtonStatus, Settings::NativeMouseButton::NumMouseButtons>;
+
+struct MousePosition {
+ s32 x;
+ s32 y;
+ s32 delta_wheel_x;
+ s32 delta_wheel_y;
+};
+
+struct DeviceStatus {
+ // Data from input_common
+ KeyboardValues keyboard_values{};
+ KeyboardModifierValues keyboard_moddifier_values{};
+ MouseButtonValues mouse_button_values{};
+
+ // Data for Nintendo devices
+ KeyboardKey keyboard_state{};
+ KeyboardModifier keyboard_moddifier_state{};
+ MouseButton mouse_button_state{};
+ MousePosition mouse_position_state{};
+};
+
+enum class DeviceTriggerType {
+ Keyboard,
+ KeyboardModdifier,
+ Mouse,
+};
+
+struct InterfaceUpdateCallback {
+ std::function<void(DeviceTriggerType)> on_change;
+};
+
+class EmulatedDevices {
+public:
+ /**
+ * TODO: Write description
+ *
+ * @param npad_id_type
+ */
+ explicit EmulatedDevices();
+ ~EmulatedDevices();
+
+ YUZU_NON_COPYABLE(EmulatedDevices);
+ YUZU_NON_MOVEABLE(EmulatedDevices);
+
+ void ReloadFromSettings();
+ void ReloadInput();
+ void UnloadInput();
+
+ void EnableConfiguration();
+ void DisableConfiguration();
+ bool IsConfiguring() const;
+ void SaveCurrentConfig();
+ void RestoreConfig();
+
+ std::vector<Common::ParamPackage> GetMappedDevices() const;
+
+ Common::ParamPackage GetMouseButtonParam(std::size_t index) const;
+
+ void SetButtonParam(std::size_t index, Common::ParamPackage param);
+
+ KeyboardValues GetKeyboardValues() const;
+ KeyboardModifierValues GetKeyboardModdifierValues() const;
+ MouseButtonValues GetMouseButtonsValues() const;
+
+ KeyboardKey GetKeyboard() const;
+ KeyboardModifier GetKeyboardModifier() const;
+ MouseButton GetMouseButtons() const;
+ MousePosition GetMousePosition() const;
+
+ int SetCallback(InterfaceUpdateCallback update_callback);
+ void DeleteCallback(int key);
+
+private:
+ /**
+ * Sets the status of a button. Applies toggle properties to the output.
+ *
+ * @param A CallbackStatus and a button index number
+ */
+ void SetKeyboardButton(Input::CallbackStatus callback, std::size_t index);
+ void SetKeyboardModifier(Input::CallbackStatus callback, std::size_t index);
+ void SetMouseButton(Input::CallbackStatus callback, std::size_t index);
+
+ /**
+ * Triggers a callback that something has changed
+ *
+ * @param Input type of the trigger
+ */
+ void TriggerOnChange(DeviceTriggerType type);
+
+ bool is_configuring{false};
+
+ MouseButtonParams mouse_button_params;
+
+ KeyboardDevices keyboard_devices;
+ KeyboardModifierDevices keyboard_modifier_devices;
+ MouseButtonDevices mouse_button_devices;
+
+ mutable std::mutex mutex;
+ std::unordered_map<int, InterfaceUpdateCallback> callback_list;
+ int last_callback_key = 0;
+ DeviceStatus device_status;
+};
+
+} // 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..ee76db110
--- /dev/null
+++ b/src/core/hid/hid_core.cpp
@@ -0,0 +1,144 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/assert.h"
+#include "core/hid/hid_core.h"
+
+namespace Core::HID {
+
+HIDCore::HIDCore()
+ : player_1{std::make_unique<EmulatedController>(NpadIdType::Player1)},
+ player_2{std::make_unique<EmulatedController>(NpadIdType::Player2)},
+ player_3{std::make_unique<EmulatedController>(NpadIdType::Player3)},
+ player_4{std::make_unique<EmulatedController>(NpadIdType::Player4)},
+ player_5{std::make_unique<EmulatedController>(NpadIdType::Player5)},
+ player_6{std::make_unique<EmulatedController>(NpadIdType::Player6)},
+ player_7{std::make_unique<EmulatedController>(NpadIdType::Player7)},
+ player_8{std::make_unique<EmulatedController>(NpadIdType::Player8)},
+ other{std::make_unique<EmulatedController>(NpadIdType::Other)},
+ handheld{std::make_unique<EmulatedController>(NpadIdType::Handheld)},
+ console{std::make_unique<EmulatedConsole>()}, devices{std::make_unique<EmulatedDevices>()} {}
+
+HIDCore::~HIDCore() = default;
+
+EmulatedController* HIDCore::GetEmulatedController(NpadIdType npad_id_type) {
+ switch (npad_id_type) {
+ case NpadIdType::Player1:
+ return player_1.get();
+ case NpadIdType::Player2:
+ return player_2.get();
+ case NpadIdType::Player3:
+ return player_3.get();
+ case NpadIdType::Player4:
+ return player_4.get();
+ case NpadIdType::Player5:
+ return player_5.get();
+ case NpadIdType::Player6:
+ return player_6.get();
+ case NpadIdType::Player7:
+ return player_7.get();
+ case NpadIdType::Player8:
+ return player_8.get();
+ case NpadIdType::Other:
+ return other.get();
+ case NpadIdType::Handheld:
+ return handheld.get();
+ case NpadIdType::Invalid:
+ default:
+ UNREACHABLE_MSG("Invalid NpadIdType={}", npad_id_type);
+ return nullptr;
+ }
+}
+
+const EmulatedController* HIDCore::GetEmulatedController(NpadIdType npad_id_type) const {
+ switch (npad_id_type) {
+ case NpadIdType::Player1:
+ return player_1.get();
+ case NpadIdType::Player2:
+ return player_2.get();
+ case NpadIdType::Player3:
+ return player_3.get();
+ case NpadIdType::Player4:
+ return player_4.get();
+ case NpadIdType::Player5:
+ return player_5.get();
+ case NpadIdType::Player6:
+ return player_6.get();
+ case NpadIdType::Player7:
+ return player_7.get();
+ case NpadIdType::Player8:
+ return player_8.get();
+ case NpadIdType::Other:
+ return other.get();
+ case NpadIdType::Handheld:
+ return handheld.get();
+ case NpadIdType::Invalid:
+ default:
+ UNREACHABLE_MSG("Invalid NpadIdType={}", npad_id_type);
+ return nullptr;
+ }
+}
+EmulatedConsole* HIDCore::GetEmulatedConsole() {
+ return console.get();
+}
+
+const EmulatedConsole* HIDCore::GetEmulatedConsole() const {
+ return console.get();
+}
+
+EmulatedDevices* HIDCore::GetEmulatedDevices() {
+ return devices.get();
+}
+
+const EmulatedDevices* HIDCore::GetEmulatedDevices() const {
+ return devices.get();
+}
+
+EmulatedController* HIDCore::GetEmulatedControllerByIndex(std::size_t index) {
+ return GetEmulatedController(IndexToNpadIdType(index));
+}
+
+const EmulatedController* HIDCore::GetEmulatedControllerByIndex(std::size_t index) const {
+ return GetEmulatedController(IndexToNpadIdType(index));
+}
+
+void HIDCore::SetSupportedStyleTag(NpadStyleTag style_tag) {
+ supported_style_tag.raw = style_tag.raw;
+}
+
+NpadStyleTag HIDCore::GetSupportedStyleTag() const {
+ return supported_style_tag;
+}
+
+void HIDCore::ReloadInputDevices() {
+ player_1->ReloadFromSettings();
+ player_2->ReloadFromSettings();
+ player_3->ReloadFromSettings();
+ player_4->ReloadFromSettings();
+ player_5->ReloadFromSettings();
+ player_6->ReloadFromSettings();
+ player_7->ReloadFromSettings();
+ player_8->ReloadFromSettings();
+ other->ReloadFromSettings();
+ handheld->ReloadFromSettings();
+ console->ReloadFromSettings();
+ devices->ReloadFromSettings();
+}
+
+void HIDCore::UnloadInputDevices() {
+ player_1->UnloadInput();
+ player_2->UnloadInput();
+ player_3->UnloadInput();
+ player_4->UnloadInput();
+ player_5->UnloadInput();
+ player_6->UnloadInput();
+ player_7->UnloadInput();
+ player_8->UnloadInput();
+ other->UnloadInput();
+ handheld->UnloadInput();
+ console->UnloadInput();
+ devices->UnloadInput();
+}
+
+} // 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..f11f48b61
--- /dev/null
+++ b/src/core/hid/hid_core.h
@@ -0,0 +1,60 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+
+#include "core/hid/emulated_console.h"
+#include "core/hid/emulated_controller.h"
+#include "core/hid/emulated_devices.h"
+
+namespace Core::HID {
+
+class HIDCore {
+public:
+ explicit HIDCore();
+ ~HIDCore();
+
+ YUZU_NON_COPYABLE(HIDCore);
+ YUZU_NON_MOVEABLE(HIDCore);
+
+ EmulatedController* GetEmulatedController(NpadIdType npad_id_type);
+ const EmulatedController* GetEmulatedController(NpadIdType npad_id_type) const;
+
+ EmulatedController* GetEmulatedControllerByIndex(std::size_t index);
+ const EmulatedController* GetEmulatedControllerByIndex(std::size_t index) const;
+
+ EmulatedConsole* GetEmulatedConsole();
+ const EmulatedConsole* GetEmulatedConsole() const;
+
+ EmulatedDevices* GetEmulatedDevices();
+ const EmulatedDevices* GetEmulatedDevices() const;
+
+ void SetSupportedStyleTag(NpadStyleTag style_tag);
+ NpadStyleTag GetSupportedStyleTag() const;
+
+ // Reloads all input devices from settings
+ void ReloadInputDevices();
+
+ // Removes all callbacks from input common
+ void UnloadInputDevices();
+
+private:
+ std::unique_ptr<EmulatedController> player_1;
+ std::unique_ptr<EmulatedController> player_2;
+ std::unique_ptr<EmulatedController> player_3;
+ std::unique_ptr<EmulatedController> player_4;
+ std::unique_ptr<EmulatedController> player_5;
+ std::unique_ptr<EmulatedController> player_6;
+ std::unique_ptr<EmulatedController> player_7;
+ std::unique_ptr<EmulatedController> player_8;
+ std::unique_ptr<EmulatedController> other;
+ std::unique_ptr<EmulatedController> handheld;
+ std::unique_ptr<EmulatedConsole> console;
+ std::unique_ptr<EmulatedDevices> devices;
+ NpadStyleTag supported_style_tag;
+};
+
+} // namespace Core::HID