From c3f54ff2329d79bdbb273678b5123cf0b1cd090c Mon Sep 17 00:00:00 2001 From: german77 Date: Mon, 20 Sep 2021 19:43:16 -0500 Subject: [PATCH] core/hid: Add emulated controllers --- src/core/CMakeLists.txt | 8 + src/core/hid/emulated_console.cpp | 208 ++++++++ src/core/hid/emulated_console.h | 142 +++++ src/core/hid/emulated_controller.cpp | 745 +++++++++++++++++++++++++++ src/core/hid/emulated_controller.h | 232 +++++++++ src/core/hid/emulated_devices.cpp | 349 +++++++++++++ src/core/hid/emulated_devices.h | 137 +++++ src/core/hid/hid_core.cpp | 144 ++++++ src/core/hid/hid_core.h | 60 +++ 9 files changed, 2025 insertions(+) create mode 100644 src/core/hid/emulated_console.cpp create mode 100644 src/core/hid/emulated_console.h create mode 100644 src/core/hid/emulated_controller.cpp create mode 100644 src/core/hid/emulated_controller.h create mode 100644 src/core/hid/emulated_devices.cpp create mode 100644 src/core/hid/emulated_devices.h create mode 100644 src/core/hid/hid_core.cpp create mode 100644 src/core/hid/hid_core.h diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index eddf455c08..09163fab97 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 0000000000..c65d050411 --- /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 + +#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(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(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(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(index)); + LOG_ERROR(Common, "{} ", touch_button_params.Serialize()); + touch_devices[index] = + Input::CreateDeviceFromString(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 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 0000000000..d9e275042c --- /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 +#include +#include + +#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; +using TouchDevices = std::array, 16>; + +using ConsoleMotionParams = Common::ParamPackage; +using TouchParams = std::array; + +using ConsoleMotionValues = ConsoleMotionInfo; +using TouchValues = std::array; + +struct TouchFinger { + u64_le last_touch{}; + Common::Point 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 orientation{}; + Common::Quaternion quaternion{}; +}; + +using TouchFingerState = std::array; + +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 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 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 0000000000..4eb5d99bc3 --- /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 + +#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); + std::transform(stick_params.begin() + Settings::NativeAnalog::STICK_HID_BEGIN, + stick_params.begin() + Settings::NativeAnalog::STICK_HID_END, + stick_devices.begin(), Input::CreateDevice); + std::transform(motion_params.begin() + Settings::NativeMotion::MOTION_HID_BEGIN, + motion_params.begin() + Settings::NativeMotion::MOTION_HID_END, + motion_devices.begin(), Input::CreateDevice); + + trigger_devices[0] = + Input::CreateDevice(button_params[Settings::NativeButton::ZL]); + trigger_devices[1] = + Input::CreateDevice(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(left_side); + battery_devices[1] = Input::CreateDevice(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 EmulatedController::GetMappedDevices() const { + std::vector 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(controller.stick_values[index].x.value * HID_JOYSTICK_MAX), + .y = static_cast(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(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(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 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 0000000000..94db9b00be --- /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 +#include +#include + +#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, Settings::NativeButton::NumButtons>; +using StickDevices = + std::array, Settings::NativeAnalog::NumAnalogs>; +using ControllerMotionDevices = + std::array, Settings::NativeMotion::NumMotions>; +using TriggerDevices = + std::array, Settings::NativeTrigger::NumTriggers>; +using BatteryDevices = std::array, 2>; + +using ButtonParams = std::array; +using StickParams = std::array; +using ControllerMotionParams = std::array; +using TriggerParams = std::array; +using BatteryParams = std::array; + +using ButtonValues = std::array; +using SticksValues = std::array; +using TriggerValues = std::array; +using ControllerMotionValues = std::array; +using ColorValues = std::array; +using BatteryValues = std::array; +using VibrationValues = std::array; + +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 orientation{}; +}; + +using MotionState = std::array; + +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 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 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 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 0000000000..3caf90714a --- /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 + +#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); + + std::transform(Settings::values.keyboard_keys.begin(), Settings::values.keyboard_keys.end(), + keyboard_devices.begin(), Input::CreateDeviceFromString); + + std::transform(Settings::values.keyboard_mods.begin(), Settings::values.keyboard_mods.end(), + keyboard_modifier_devices.begin(), + Input::CreateDeviceFromString); + + 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 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 0000000000..6f728eff55 --- /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 +#include +#include + +#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, Settings::NativeKeyboard::NumKeyboardKeys>; +using KeyboardModifierDevices = + std::array, Settings::NativeKeyboard::NumKeyboardMods>; +using MouseButtonDevices = + std::array, Settings::NativeMouseButton::NumMouseButtons>; + +using MouseButtonParams = + std::array; + +using KeyboardValues = std::array; +using KeyboardModifierValues = + std::array; +using MouseButtonValues = + std::array; + +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 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 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 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 0000000000..ee76db1106 --- /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(NpadIdType::Player1)}, + player_2{std::make_unique(NpadIdType::Player2)}, + player_3{std::make_unique(NpadIdType::Player3)}, + player_4{std::make_unique(NpadIdType::Player4)}, + player_5{std::make_unique(NpadIdType::Player5)}, + player_6{std::make_unique(NpadIdType::Player6)}, + player_7{std::make_unique(NpadIdType::Player7)}, + player_8{std::make_unique(NpadIdType::Player8)}, + other{std::make_unique(NpadIdType::Other)}, + handheld{std::make_unique(NpadIdType::Handheld)}, + console{std::make_unique()}, devices{std::make_unique()} {} + +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 0000000000..f11f48b617 --- /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 + +#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 player_1; + std::unique_ptr player_2; + std::unique_ptr player_3; + std::unique_ptr player_4; + std::unique_ptr player_5; + std::unique_ptr player_6; + std::unique_ptr player_7; + std::unique_ptr player_8; + std::unique_ptr other; + std::unique_ptr handheld; + std::unique_ptr console; + std::unique_ptr devices; + NpadStyleTag supported_style_tag; +}; + +} // namespace Core::HID