diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 23d43a394e..919da4a53e 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -73,6 +73,7 @@ add_library(common STATIC hex_util.h host_memory.cpp host_memory.h + input.h intrusive_red_black_tree.h literals.h logging/backend.cpp diff --git a/src/common/input.h b/src/common/input.h new file mode 100644 index 0000000000..eaee0bdeaf --- /dev/null +++ b/src/common/input.h @@ -0,0 +1,372 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include +#include "common/logging/log.h" +#include "common/param_package.h" +#include "common/uuid.h" + +namespace Common::Input { + +// Type of data that is expected to recieve or send +enum class InputType { + None, + Battery, + Button, + Stick, + Analog, + Trigger, + Motion, + Touch, + Color, + Vibration, + Nfc, + Ir, +}; + +// Internal battery charge level +enum class BatteryLevel : u32 { + None, + Empty, + Critical, + Low, + Medium, + Full, + Charging, +}; + +enum class PollingMode { + // Constant polling of buttons, analogs and motion data + Active, + // Only update on button change, digital analogs + Pasive, + // Enable near field communication polling + NFC, + // Enable infrared camera polling + IR, +}; + +// Vibration reply from the controller +enum class VibrationError { + None, + NotSupported, + Disabled, + Unknown, +}; + +// Polling mode reply from the controller +enum class PollingError { + None, + NotSupported, + Unknown, +}; + +// Hint for amplification curve to be used +enum class VibrationAmplificationType { + Linear, + Exponential, +}; + +// Analog properties for calibration +struct AnalogProperties { + // Anything below this value will be detected as zero + float deadzone{}; + // Anyting above this values will be detected as one + float range{1.0f}; + // Minimum value to be detected as active + float threshold{0.5f}; + // Drift correction applied to the raw data + float offset{}; + // Invert direction of the sensor data + bool inverted{}; +}; + +// Single analog sensor data +struct AnalogStatus { + float value{}; + float raw_value{}; + AnalogProperties properties{}; +}; + +// Button data +struct ButtonStatus { + Common::UUID uuid{}; + bool value{}; + bool inverted{}; + bool toggle{}; + bool locked{}; +}; + +// Internal battery data +using BatteryStatus = BatteryLevel; + +// Analog and digital joystick data +struct StickStatus { + Common::UUID uuid{}; + AnalogStatus x{}; + AnalogStatus y{}; + bool left{}; + bool right{}; + bool up{}; + bool down{}; +}; + +// Analog and digital trigger data +struct TriggerStatus { + Common::UUID uuid{}; + AnalogStatus analog{}; + ButtonStatus pressed{}; +}; + +// 3D vector representing motion input +struct MotionSensor { + AnalogStatus x{}; + AnalogStatus y{}; + AnalogStatus z{}; +}; + +// Motion data used to calculate controller orientation +struct MotionStatus { + // Gyroscope vector measurement in radians/s. + MotionSensor gyro{}; + // Acceleration vector measurement in G force + MotionSensor accel{}; + // Time since last measurement in microseconds + u64 delta_timestamp{}; + // Request to update after reading the value + bool force_update{}; +}; + +// Data of a single point on a touch screen +struct TouchStatus { + ButtonStatus pressed{}; + AnalogStatus x{}; + AnalogStatus y{}; + int id{}; +}; + +// Physical controller color in RGB format +struct BodyColorStatus { + u32 body{}; + u32 buttons{}; +}; + +// HD rumble data +struct VibrationStatus { + f32 low_amplitude{}; + f32 low_frequency{}; + f32 high_amplitude{}; + f32 high_frequency{}; + VibrationAmplificationType type; +}; + +// Physical controller LED pattern +struct LedStatus { + bool led_1{}; + bool led_2{}; + bool led_3{}; + bool led_4{}; +}; + +// List of buttons to be passed to Qt that can be translated +enum class ButtonNames { + Undefined, + Invalid, + // This will display the engine name instead of the button name + Engine, + // This will display the button by value instead of the button name + Value, + ButtonLeft, + ButtonRight, + ButtonDown, + ButtonUp, + TriggerZ, + TriggerR, + TriggerL, + ButtonA, + ButtonB, + ButtonX, + ButtonY, + ButtonStart, + + // DS4 button names + L1, + L2, + L3, + R1, + R2, + R3, + Circle, + Cross, + Square, + Triangle, + Share, + Options, +}; + +// Callback data consisting of an input type and the equivalent data status +struct CallbackStatus { + InputType type{InputType::None}; + ButtonStatus button_status{}; + StickStatus stick_status{}; + AnalogStatus analog_status{}; + TriggerStatus trigger_status{}; + MotionStatus motion_status{}; + TouchStatus touch_status{}; + BodyColorStatus color_status{}; + BatteryStatus battery_status{}; + VibrationStatus vibration_status{}; +}; + +// Triggered once every input change +struct InputCallback { + std::function on_change; +}; + +/// An abstract class template for an input device (a button, an analog input, etc.). +class InputDevice { +public: + virtual ~InputDevice() = default; + + // Request input device to update if necessary + virtual void SoftUpdate() { + return; + } + + // Force input device to update data regardless of the current state + virtual void ForceUpdate() { + return; + } + + // Sets the function to be triggered when input changes + void SetCallback(InputCallback callback_) { + callback = std::move(callback_); + } + + // Triggers the function set in the callback + void TriggerOnChange(CallbackStatus status) { + if (callback.on_change) { + callback.on_change(status); + } + } + +private: + InputCallback callback; +}; + +/// An abstract class template for an output device (rumble, LED pattern, polling mode). +class OutputDevice { +public: + virtual ~OutputDevice() = default; + + virtual void SetLED([[maybe_unused]] LedStatus led_status) { + return; + } + + virtual VibrationError SetVibration([[maybe_unused]] VibrationStatus vibration_status) { + return VibrationError::NotSupported; + } + + virtual PollingError SetPollingMode([[maybe_unused]] PollingMode polling_mode) { + return PollingError::NotSupported; + } +}; + +/// An abstract class template for a factory that can create input devices. +template +class Factory { +public: + virtual ~Factory() = default; + virtual std::unique_ptr Create(const Common::ParamPackage&) = 0; +}; + +namespace Impl { + +template +using FactoryListType = std::unordered_map>>; + +template +struct FactoryList { + static FactoryListType list; +}; + +template +FactoryListType FactoryList::list; + +} // namespace Impl + +/** + * Registers an input device factory. + * @tparam InputDeviceType the type of input devices the factory can create + * @param name the name of the factory. Will be used to match the "engine" parameter when creating + * a device + * @param factory the factory object to register + */ +template +void RegisterFactory(const std::string& name, std::shared_ptr> factory) { + auto pair = std::make_pair(name, std::move(factory)); + if (!Impl::FactoryList::list.insert(std::move(pair)).second) { + LOG_ERROR(Input, "Factory '{}' already registered", name); + } +} + +/** + * Unregisters an input device factory. + * @tparam InputDeviceType the type of input devices the factory can create + * @param name the name of the factory to unregister + */ +template +void UnregisterFactory(const std::string& name) { + if (Impl::FactoryList::list.erase(name) == 0) { + LOG_ERROR(Input, "Factory '{}' not registered", name); + } +} + +/** + * Create an input device from given paramters. + * @tparam InputDeviceType the type of input devices to create + * @param params a serialized ParamPackage string that contains all parameters for creating the + * device + */ +template +std::unique_ptr CreateDeviceFromString(const std::string& params) { + const Common::ParamPackage package(params); + const std::string engine = package.Get("engine", "null"); + const auto& factory_list = Impl::FactoryList::list; + const auto pair = factory_list.find(engine); + if (pair == factory_list.end()) { + if (engine != "null") { + LOG_ERROR(Input, "Unknown engine name: {}", engine); + } + return std::make_unique(); + } + return pair->second->Create(package); +} + +/** + * Create an input device from given paramters. + * @tparam InputDeviceType the type of input devices to create + * @param A ParamPackage that contains all parameters for creating the device + */ +template +std::unique_ptr CreateDevice(const Common::ParamPackage package) { + const std::string engine = package.Get("engine", "null"); + const auto& factory_list = Impl::FactoryList::list; + const auto pair = factory_list.find(engine); + if (pair == factory_list.end()) { + if (engine != "null") { + LOG_ERROR(Input, "Unknown engine name: {}", engine); + } + return std::make_unique(); + } + return pair->second->Create(package); +} + +} // namespace Common::Input diff --git a/src/common/settings.h b/src/common/settings.h index fa4aa87476..e4e049f670 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -6,7 +6,6 @@ #include #include -#include #include #include #include @@ -560,25 +559,19 @@ struct Values { Setting enable_accurate_vibrations{false, "enable_accurate_vibrations"}; Setting motion_enabled{true, "motion_enabled"}; - BasicSetting motion_device{"engine:motion_emu,update_period:100,sensitivity:0.01", - "motion_device"}; BasicSetting udp_input_servers{"127.0.0.1:26760", "udp_input_servers"}; + BasicSetting enable_udp_controller{false, "enable_udp_controller"}; BasicSetting pause_tas_on_load{true, "pause_tas_on_load"}; BasicSetting tas_enable{false, "tas_enable"}; BasicSetting tas_loop{false, "tas_loop"}; - BasicSetting tas_swap_controllers{true, "tas_swap_controllers"}; BasicSetting mouse_panning{false, "mouse_panning"}; BasicRangedSetting mouse_panning_sensitivity{10, 1, 100, "mouse_panning_sensitivity"}; BasicSetting mouse_enabled{false, "mouse_enabled"}; - std::string mouse_device; - MouseButtonsRaw mouse_buttons; BasicSetting emulate_analog_keyboard{false, "emulate_analog_keyboard"}; BasicSetting keyboard_enabled{false, "keyboard_enabled"}; - KeyboardKeysRaw keyboard_keys; - KeyboardModsRaw keyboard_mods; BasicSetting debug_pad_enabled{false, "debug_pad_enabled"}; ButtonsRaw debug_pad_buttons; @@ -586,14 +579,11 @@ struct Values { TouchscreenInput touchscreen; - BasicSetting use_touch_from_button{false, "use_touch_from_button"}; BasicSetting touch_device{"min_x:100,min_y:50,max_x:1800,max_y:850", "touch_device"}; BasicSetting touch_from_button_map_index{0, "touch_from_button_map"}; std::vector touch_from_button_maps; - std::atomic_bool is_device_reload_pending{true}; - // Data Storage BasicSetting use_virtual_sd{true, "use_virtual_sd"}; BasicSetting gamecard_inserted{false, "gamecard_inserted"}; diff --git a/src/common/settings_input.h b/src/common/settings_input.h index 6096005822..9e3df73763 100644 --- a/src/common/settings_input.h +++ b/src/common/settings_input.h @@ -62,11 +62,22 @@ enum Values : int { constexpr int STICK_HID_BEGIN = LStick; constexpr int STICK_HID_END = NumAnalogs; -constexpr int NUM_STICKS_HID = NumAnalogs; extern const std::array mapping; } // namespace NativeAnalog +namespace NativeTrigger { +enum Values : int { + LTrigger, + RTrigger, + + NumTriggers, +}; + +constexpr int TRIGGER_HID_BEGIN = LTrigger; +constexpr int TRIGGER_HID_END = NumTriggers; +} // namespace NativeTrigger + namespace NativeVibration { enum Values : int { LeftVibrationDevice, @@ -115,10 +126,20 @@ constexpr int NUM_MOUSE_HID = NumMouseButtons; extern const std::array mapping; } // namespace NativeMouseButton +namespace NativeMouseWheel { +enum Values { + X, + Y, + + NumMouseWheels, +}; + +extern const std::array mapping; +} // namespace NativeMouseWheel + namespace NativeKeyboard { enum Keys { None, - Error, A = 4, B, @@ -156,22 +177,22 @@ enum Keys { N8, N9, N0, - Enter, + Return, Escape, Backspace, Tab, Space, Minus, - Equal, - LeftBrace, - RightBrace, - Backslash, + Plus, + OpenBracket, + CloseBracket, + Pipe, Tilde, Semicolon, - Apostrophe, - Grave, + Quote, + Backquote, Comma, - Dot, + Period, Slash, CapsLockKey, @@ -188,7 +209,7 @@ enum Keys { F11, F12, - SystemRequest, + PrintScreen, ScrollLockKey, Pause, Insert, @@ -257,8 +278,18 @@ enum Keys { ScrollLockActive, KPComma, - KPLeftParenthesis, - KPRightParenthesis, + Ro = 0x87, + KatakanaHiragana, + Yen, + Henkan, + Muhenkan, + NumPadCommaPc98, + + HangulEnglish = 0x90, + Hanja, + KatakanaKey, + HiraganaKey, + ZenkakuHankaku, LeftControlKey = 0xE0, LeftShiftKey, @@ -307,6 +338,8 @@ enum Modifiers { CapsLock, ScrollLock, NumLock, + Katakana, + Hiragana, NumKeyboardMods, }; @@ -324,11 +357,6 @@ constexpr int NUM_KEYBOARD_MODS_HID = NumKeyboardMods; using AnalogsRaw = std::array; using ButtonsRaw = std::array; using MotionsRaw = std::array; -using VibrationsRaw = std::array; - -using MouseButtonsRaw = std::array; -using KeyboardKeysRaw = std::array; -using KeyboardModsRaw = std::array; constexpr u32 JOYCON_BODY_NEON_RED = 0xFF3C28; constexpr u32 JOYCON_BUTTONS_NEON_RED = 0x1E0A0A; @@ -349,7 +377,6 @@ struct PlayerInput { ControllerType controller_type; ButtonsRaw buttons; AnalogsRaw analogs; - VibrationsRaw vibrations; MotionsRaw motions; bool vibration_enabled; diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 9f0fbba2d9..582c15f7e7 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -132,11 +132,23 @@ add_library(core STATIC frontend/emu_window.h frontend/framebuffer_layout.cpp frontend/framebuffer_layout.h - frontend/input_interpreter.cpp - frontend/input_interpreter.h - 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 + hid/input_interpreter.cpp + hid/input_interpreter.h + hid/motion_input.cpp + hid/motion_input.h hle/api_version.h hle/ipc.h hle/ipc_helpers.h @@ -402,6 +414,7 @@ add_library(core STATIC hle/service/hid/hid.h hle/service/hid/irs.cpp hle/service/hid/irs.h + hle/service/hid/ring_lifo.h hle/service/hid/xcd.cpp hle/service/hid/xcd.h hle/service/hid/errors.h diff --git a/src/core/core.cpp b/src/core/core.cpp index 07448fd295..473ab9f817 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -27,6 +27,7 @@ #include "core/file_sys/vfs_concat.h" #include "core/file_sys/vfs_real.h" #include "core/hardware_interrupt_manager.h" +#include "core/hid/hid_core.h" #include "core/hle/kernel/k_process.h" #include "core/hle/kernel/k_scheduler.h" #include "core/hle/kernel/kernel.h" @@ -126,7 +127,7 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs, struct System::Impl { explicit Impl(System& system) - : kernel{system}, fs_controller{system}, memory{system}, + : kernel{system}, fs_controller{system}, memory{system}, hid_core{}, cpu_manager{system}, reporter{system}, applet_manager{system}, time_manager{system} {} SystemResultStatus Run() { @@ -391,6 +392,7 @@ struct System::Impl { std::unique_ptr interrupt_manager; std::unique_ptr device_memory; Core::Memory::Memory memory; + Core::HID::HIDCore hid_core; CpuManager cpu_manager; std::atomic_bool is_powered_on{}; bool exit_lock = false; @@ -615,6 +617,14 @@ const Kernel::KernelCore& System::Kernel() const { return impl->kernel; } +HID::HIDCore& System::HIDCore() { + return impl->hid_core; +} + +const HID::HIDCore& System::HIDCore() const { + return impl->hid_core; +} + Timing::CoreTiming& System::CoreTiming() { return impl->core_timing; } @@ -825,8 +835,6 @@ void System::ApplySettings() { if (IsPoweredOn()) { Renderer().RefreshBaseSettings(); } - - Service::HID::ReloadInputDevices(); } } // namespace Core diff --git a/src/core/core.h b/src/core/core.h index 01bc0a2c77..645e5c241c 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -89,6 +89,10 @@ namespace Core::Hardware { class InterruptManager; } +namespace Core::HID { +class HIDCore; +} + namespace Core { class ARM_Interface; @@ -285,6 +289,12 @@ public: /// Provides a constant reference to the kernel instance. [[nodiscard]] const Kernel::KernelCore& Kernel() const; + /// Gets a mutable reference to the HID interface. + [[nodiscard]] HID::HIDCore& HIDCore(); + + /// Gets an immutable reference to the HID interface. + [[nodiscard]] const HID::HIDCore& HIDCore() const; + /// Provides a reference to the internal PerfStats instance. [[nodiscard]] Core::PerfStats& GetPerfStats(); diff --git a/src/core/frontend/applets/controller.cpp b/src/core/frontend/applets/controller.cpp index 03bbedf8b8..6dbd38ffae 100644 --- a/src/core/frontend/applets/controller.cpp +++ b/src/core/frontend/applets/controller.cpp @@ -5,16 +5,15 @@ #include "common/assert.h" #include "common/logging/log.h" #include "core/frontend/applets/controller.h" -#include "core/hle/service/hid/controllers/npad.h" -#include "core/hle/service/hid/hid.h" -#include "core/hle/service/sm/sm.h" +#include "core/hid/emulated_controller.h" +#include "core/hid/hid_core.h" +#include "core/hid/hid_types.h" namespace Core::Frontend { ControllerApplet::~ControllerApplet() = default; -DefaultControllerApplet::DefaultControllerApplet(Service::SM::ServiceManager& service_manager_) - : service_manager{service_manager_} {} +DefaultControllerApplet::DefaultControllerApplet(HID::HIDCore& hid_core_) : hid_core{hid_core_} {} DefaultControllerApplet::~DefaultControllerApplet() = default; @@ -22,24 +21,20 @@ void DefaultControllerApplet::ReconfigureControllers(std::function callb const ControllerParameters& parameters) const { LOG_INFO(Service_HID, "called, deducing the best configuration based on the given parameters!"); - auto& npad = - service_manager.GetService("hid") - ->GetAppletResource() - ->GetController(Service::HID::HidController::NPad); - - auto& players = Settings::values.players.GetValue(); - const std::size_t min_supported_players = parameters.enable_single_mode ? 1 : parameters.min_players; // Disconnect Handheld first. - npad.DisconnectNpadAtIndex(8); + auto* handheld = hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld); + handheld->Disconnect(); // Deduce the best configuration based on the input parameters. - for (std::size_t index = 0; index < players.size() - 2; ++index) { + for (std::size_t index = 0; index < hid_core.available_controllers - 2; ++index) { + auto* controller = hid_core.GetEmulatedControllerByIndex(index); + // First, disconnect all controllers regardless of the value of keep_controllers_connected. // This makes it easy to connect the desired controllers. - npad.DisconnectNpadAtIndex(index); + controller->Disconnect(); // Only connect the minimum number of required players. if (index >= min_supported_players) { @@ -49,27 +44,27 @@ void DefaultControllerApplet::ReconfigureControllers(std::function callb // Connect controllers based on the following priority list from highest to lowest priority: // Pro Controller -> Dual Joycons -> Left Joycon/Right Joycon -> Handheld if (parameters.allow_pro_controller) { - npad.AddNewControllerAt( - npad.MapSettingsTypeToNPad(Settings::ControllerType::ProController), index); + controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController); + controller->Connect(); } else if (parameters.allow_dual_joycons) { - npad.AddNewControllerAt( - npad.MapSettingsTypeToNPad(Settings::ControllerType::DualJoyconDetached), index); + controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::JoyconDual); + controller->Connect(); } else if (parameters.allow_left_joycon && parameters.allow_right_joycon) { // Assign left joycons to even player indices and right joycons to odd player indices. // We do this since Captain Toad Treasure Tracker expects a left joycon for Player 1 and // a right Joycon for Player 2 in 2 Player Assist mode. if (index % 2 == 0) { - npad.AddNewControllerAt( - npad.MapSettingsTypeToNPad(Settings::ControllerType::LeftJoycon), index); + controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::JoyconLeft); + controller->Connect(); } else { - npad.AddNewControllerAt( - npad.MapSettingsTypeToNPad(Settings::ControllerType::RightJoycon), index); + controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::JoyconRight); + controller->Connect(); } } else if (index == 0 && parameters.enable_single_mode && parameters.allow_handheld && !Settings::values.use_docked_mode.GetValue()) { // We should *never* reach here under any normal circumstances. - npad.AddNewControllerAt(npad.MapSettingsTypeToNPad(Settings::ControllerType::Handheld), - index); + controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld); + controller->Connect(); } else { UNREACHABLE_MSG("Unable to add a new controller based on the given parameters!"); } diff --git a/src/core/frontend/applets/controller.h b/src/core/frontend/applets/controller.h index b0626a0f9c..014bc89011 100644 --- a/src/core/frontend/applets/controller.h +++ b/src/core/frontend/applets/controller.h @@ -8,8 +8,8 @@ #include "common/common_types.h" -namespace Service::SM { -class ServiceManager; +namespace Core::HID { +class HIDCore; } namespace Core::Frontend { @@ -44,14 +44,14 @@ public: class DefaultControllerApplet final : public ControllerApplet { public: - explicit DefaultControllerApplet(Service::SM::ServiceManager& service_manager_); + explicit DefaultControllerApplet(HID::HIDCore& hid_core_); ~DefaultControllerApplet() override; void ReconfigureControllers(std::function callback, const ControllerParameters& parameters) const override; private: - Service::SM::ServiceManager& service_manager; + HID::HIDCore& hid_core; }; } // namespace Core::Frontend diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp index e1f7e5886b..57c6ffc43b 100644 --- a/src/core/frontend/emu_window.cpp +++ b/src/core/frontend/emu_window.cpp @@ -3,66 +3,31 @@ // Refer to the license.txt file included. #include -#include "common/settings.h" #include "core/frontend/emu_window.h" -#include "core/frontend/input.h" namespace Core::Frontend { GraphicsContext::~GraphicsContext() = default; -class EmuWindow::TouchState : public Input::Factory, - public std::enable_shared_from_this { -public: - std::unique_ptr Create(const Common::ParamPackage&) override { - return std::make_unique(shared_from_this()); - } - - std::mutex mutex; - - Input::TouchStatus status; - -private: - class Device : public Input::TouchDevice { - public: - explicit Device(std::weak_ptr&& touch_state_) : touch_state(touch_state_) {} - Input::TouchStatus GetStatus() const override { - if (auto state = touch_state.lock()) { - std::lock_guard guard{state->mutex}; - return state->status; - } - return {}; - } - - private: - std::weak_ptr touch_state; - }; -}; - EmuWindow::EmuWindow() { // TODO: Find a better place to set this. config.min_client_area_size = std::make_pair(Layout::MinimumSize::Width, Layout::MinimumSize::Height); active_config = config; - touch_state = std::make_shared(); - Input::RegisterFactory("emu_window", touch_state); } -EmuWindow::~EmuWindow() { - Input::UnregisterFactory("emu_window"); -} +EmuWindow::~EmuWindow() {} -/** - * Check if the given x/y coordinates are within the touchpad specified by the framebuffer layout - * @param layout FramebufferLayout object describing the framebuffer size and screen positions - * @param framebuffer_x Framebuffer x-coordinate to check - * @param framebuffer_y Framebuffer y-coordinate to check - * @return True if the coordinates are within the touchpad, otherwise false - */ -static bool IsWithinTouchscreen(const Layout::FramebufferLayout& layout, u32 framebuffer_x, - u32 framebuffer_y) { - return (framebuffer_y >= layout.screen.top && framebuffer_y < layout.screen.bottom && - framebuffer_x >= layout.screen.left && framebuffer_x < layout.screen.right); +std::pair EmuWindow::MapToTouchScreen(u32 framebuffer_x, u32 framebuffer_y) const { + std::tie(framebuffer_x, framebuffer_y) = ClipToTouchScreen(framebuffer_x, framebuffer_y); + const float x = + static_cast(framebuffer_x - framebuffer_layout.screen.left) / + static_cast(framebuffer_layout.screen.right - framebuffer_layout.screen.left); + const float y = + static_cast(framebuffer_y - framebuffer_layout.screen.top) / + static_cast(framebuffer_layout.screen.bottom - framebuffer_layout.screen.top); + + return std::make_pair(x, y); } std::pair EmuWindow::ClipToTouchScreen(u32 new_x, u32 new_y) const { @@ -75,49 +40,6 @@ std::pair EmuWindow::ClipToTouchScreen(u32 new_x, u32 new_y) const { return std::make_pair(new_x, new_y); } -void EmuWindow::TouchPressed(u32 framebuffer_x, u32 framebuffer_y, size_t id) { - if (!IsWithinTouchscreen(framebuffer_layout, framebuffer_x, framebuffer_y)) { - return; - } - if (id >= touch_state->status.size()) { - return; - } - - std::lock_guard guard{touch_state->mutex}; - const float x = - static_cast(framebuffer_x - framebuffer_layout.screen.left) / - static_cast(framebuffer_layout.screen.right - framebuffer_layout.screen.left); - const float y = - static_cast(framebuffer_y - framebuffer_layout.screen.top) / - static_cast(framebuffer_layout.screen.bottom - framebuffer_layout.screen.top); - - touch_state->status[id] = std::make_tuple(x, y, true); -} - -void EmuWindow::TouchReleased(size_t id) { - if (id >= touch_state->status.size()) { - return; - } - std::lock_guard guard{touch_state->mutex}; - touch_state->status[id] = std::make_tuple(0.0f, 0.0f, false); -} - -void EmuWindow::TouchMoved(u32 framebuffer_x, u32 framebuffer_y, size_t id) { - if (id >= touch_state->status.size()) { - return; - } - - if (!std::get<2>(touch_state->status[id])) { - return; - } - - if (!IsWithinTouchscreen(framebuffer_layout, framebuffer_x, framebuffer_y)) { - std::tie(framebuffer_x, framebuffer_y) = ClipToTouchScreen(framebuffer_x, framebuffer_y); - } - - TouchPressed(framebuffer_x, framebuffer_y, id); -} - void EmuWindow::UpdateCurrentFramebufferLayout(u32 width, u32 height) { NotifyFramebufferLayoutChanged(Layout::DefaultFrameLayout(width, height)); } diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h index 8a86a1d276..e413a520ab 100644 --- a/src/core/frontend/emu_window.h +++ b/src/core/frontend/emu_window.h @@ -112,28 +112,6 @@ public: /// Returns if window is shown (not minimized) virtual bool IsShown() const = 0; - /** - * Signal that a touch pressed event has occurred (e.g. mouse click pressed) - * @param framebuffer_x Framebuffer x-coordinate that was pressed - * @param framebuffer_y Framebuffer y-coordinate that was pressed - * @param id Touch event ID - */ - void TouchPressed(u32 framebuffer_x, u32 framebuffer_y, size_t id); - - /** - * Signal that a touch released event has occurred (e.g. mouse click released) - * @param id Touch event ID - */ - void TouchReleased(size_t id); - - /** - * Signal that a touch movement event has occurred (e.g. mouse was moved over the emu window) - * @param framebuffer_x Framebuffer x-coordinate - * @param framebuffer_y Framebuffer y-coordinate - * @param id Touch event ID - */ - void TouchMoved(u32 framebuffer_x, u32 framebuffer_y, size_t id); - /** * Returns currently active configuration. * @note Accesses to the returned object need not be consistent because it may be modified in @@ -212,6 +190,11 @@ protected: client_area_height = size.second; } + /** + * Converts a screen postion into the equivalent touchscreen position. + */ + std::pair MapToTouchScreen(u32 framebuffer_x, u32 framebuffer_y) const; + WindowSystemInfo window_info; private: @@ -237,9 +220,6 @@ private: WindowConfig config; ///< Internal configuration (changes pending for being applied in /// ProcessConfigurationChanges) WindowConfig active_config; ///< Internal active configuration - - class TouchState; - std::shared_ptr touch_state; }; } // namespace Core::Frontend diff --git a/src/core/frontend/input.h b/src/core/frontend/input.h deleted file mode 100644 index f1747c5b23..0000000000 --- a/src/core/frontend/input.h +++ /dev/null @@ -1,217 +0,0 @@ -// Copyright 2017 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include -#include -#include -#include -#include "common/logging/log.h" -#include "common/param_package.h" -#include "common/quaternion.h" -#include "common/vector_math.h" - -namespace Input { - -enum class AnalogDirection : u8 { - RIGHT, - LEFT, - UP, - DOWN, -}; -struct AnalogProperties { - float deadzone; - float range; - float threshold; -}; -template -struct InputCallback { - std::function on_change; -}; - -/// An abstract class template for an input device (a button, an analog input, etc.). -template -class InputDevice { -public: - virtual ~InputDevice() = default; - virtual StatusType GetStatus() const { - return {}; - } - virtual StatusType GetRawStatus() const { - return GetStatus(); - } - virtual AnalogProperties GetAnalogProperties() const { - return {}; - } - virtual bool GetAnalogDirectionStatus([[maybe_unused]] AnalogDirection direction) const { - return {}; - } - virtual bool SetRumblePlay([[maybe_unused]] f32 amp_low, [[maybe_unused]] f32 freq_low, - [[maybe_unused]] f32 amp_high, - [[maybe_unused]] f32 freq_high) const { - return {}; - } - void SetCallback(InputCallback callback_) { - callback = std::move(callback_); - } - void TriggerOnChange() { - if (callback.on_change) { - callback.on_change(GetStatus()); - } - } - -private: - InputCallback callback; -}; - -/// An abstract class template for a factory that can create input devices. -template -class Factory { -public: - virtual ~Factory() = default; - virtual std::unique_ptr Create(const Common::ParamPackage&) = 0; -}; - -namespace Impl { - -template -using FactoryListType = std::unordered_map>>; - -template -struct FactoryList { - static FactoryListType list; -}; - -template -FactoryListType FactoryList::list; - -} // namespace Impl - -/** - * Registers an input device factory. - * @tparam InputDeviceType the type of input devices the factory can create - * @param name the name of the factory. Will be used to match the "engine" parameter when creating - * a device - * @param factory the factory object to register - */ -template -void RegisterFactory(const std::string& name, std::shared_ptr> factory) { - auto pair = std::make_pair(name, std::move(factory)); - if (!Impl::FactoryList::list.insert(std::move(pair)).second) { - LOG_ERROR(Input, "Factory '{}' already registered", name); - } -} - -/** - * Unregisters an input device factory. - * @tparam InputDeviceType the type of input devices the factory can create - * @param name the name of the factory to unregister - */ -template -void UnregisterFactory(const std::string& name) { - if (Impl::FactoryList::list.erase(name) == 0) { - LOG_ERROR(Input, "Factory '{}' not registered", name); - } -} - -/** - * Create an input device from given paramters. - * @tparam InputDeviceType the type of input devices to create - * @param params a serialized ParamPackage string contains all parameters for creating the device - */ -template -std::unique_ptr CreateDevice(const std::string& params) { - const Common::ParamPackage package(params); - const std::string engine = package.Get("engine", "null"); - const auto& factory_list = Impl::FactoryList::list; - const auto pair = factory_list.find(engine); - if (pair == factory_list.end()) { - if (engine != "null") { - LOG_ERROR(Input, "Unknown engine name: {}", engine); - } - return std::make_unique(); - } - return pair->second->Create(package); -} - -/** - * A button device is an input device that returns bool as status. - * true for pressed; false for released. - */ -using ButtonDevice = InputDevice; - -/** - * An analog device is an input device that returns a tuple of x and y coordinates as status. The - * coordinates are within the unit circle. x+ is defined as right direction, and y+ is defined as up - * direction - */ -using AnalogDevice = InputDevice>; - -/** - * A vibration device is an input device that returns an unsigned byte as status. - * It represents whether the vibration device supports vibration or not. - * If the status returns 1, it supports vibration. Otherwise, it does not support vibration. - */ -using VibrationDevice = InputDevice; - -/** - * A motion status is an object that returns a tuple of accelerometer state vector, - * gyroscope state vector, rotation state vector, orientation state matrix and quaterion state - * vector. - * - * For both 3D vectors: - * x+ is the same direction as RIGHT on D-pad. - * y+ is normal to the touch screen, pointing outward. - * z+ is the same direction as UP on D-pad. - * - * For accelerometer state vector - * Units: g (gravitational acceleration) - * - * For gyroscope state vector: - * Orientation is determined by right-hand rule. - * Units: deg/sec - * - * For rotation state vector - * Units: rotations - * - * For orientation state matrix - * x vector - * y vector - * z vector - * - * For quaternion state vector - * xyz vector - * w float - */ -using MotionStatus = std::tuple, Common::Vec3, Common::Vec3, - std::array, Common::Quaternion>; - -/** - * A motion device is an input device that returns a motion status object - */ -using MotionDevice = InputDevice; - -/** - * A touch status is an object that returns an array of 16 tuple elements of two floats and a bool. - * The floats are x and y coordinates in the range 0.0 - 1.0, and the bool indicates whether it is - * pressed. - */ -using TouchStatus = std::array, 16>; - -/** - * A touch device is an input device that returns a touch status object - */ -using TouchDevice = InputDevice; - -/** - * A mouse device is an input device that returns a tuple of two floats and four ints. - * The first two floats are X and Y device coordinates of the mouse (from 0-1). - * The s32s are the mouse wheel. - */ -using MouseDevice = InputDevice>; - -} // namespace Input diff --git a/src/core/hid/emulated_console.cpp b/src/core/hid/emulated_console.cpp new file mode 100644 index 0000000000..80db8e9c6d --- /dev/null +++ b/src/core/hid/emulated_console.cpp @@ -0,0 +1,229 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included + +#include "common/settings.h" +#include "core/hid/emulated_console.h" +#include "core/hid/input_converter.h" + +namespace Core::HID { +EmulatedConsole::EmulatedConsole() = default; + +EmulatedConsole::~EmulatedConsole() = default; + +void EmulatedConsole::ReloadFromSettings() { + // Using first motion device from player 1. No need to assign any unique config at the moment + const auto& player = Settings::values.players.GetValue()[0]; + motion_params = Common::ParamPackage(player.motions[0]); + + ReloadInput(); +} + +void EmulatedConsole::SetTouchParams() { + // TODO(german77): Support any number of fingers + std::size_t index = 0; + + // Hardcode mouse, touchscreen and cemuhook parameters + if (!Settings::values.mouse_enabled) { + // We can't use mouse as touch if native mouse is enabled + touch_params[index++] = Common::ParamPackage{"engine:mouse,axis_x:10,axis_y:11,button:0"}; + } + touch_params[index++] = Common::ParamPackage{"engine:touch,axis_x:0,axis_y:1,button:0"}; + touch_params[index++] = Common::ParamPackage{"engine:touch,axis_x:2,axis_y:3,button:1"}; + touch_params[index++] = + Common::ParamPackage{"engine:cemuhookudp,axis_x:17,axis_y:18,button:65536"}; + touch_params[index++] = + Common::ParamPackage{"engine:cemuhookudp,axis_x:19,axis_y:20,button:131072"}; + + 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; + + // Map the rest of the fingers from touch from button configuration + for (const auto& config_entry : touch_buttons) { + if (index >= touch_params.size()) { + continue; + } + 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)); + touch_params[index] = touch_button_params; + index++; + } +} + +void EmulatedConsole::ReloadInput() { + // If you load any device here add the equivalent to the UnloadInput() function + SetTouchParams(); + + motion_devices = Common::Input::CreateDevice(motion_params); + if (motion_devices) { + Common::Input::InputCallback motion_callback{ + [this](Common::Input::CallbackStatus callback) { SetMotion(callback); }}; + motion_devices->SetCallback(motion_callback); + } + + // Unique index for identifying touch device source + std::size_t index = 0; + for (auto& touch_device : touch_devices) { + touch_device = Common::Input::CreateDevice(touch_params[index]); + if (!touch_device) { + continue; + } + Common::Input::InputCallback touch_callback{ + [this, index](Common::Input::CallbackStatus callback) { SetTouch(callback, index); }}; + touch_device->SetCallback(touch_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(Common::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(Common::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; + } + + // TODO(german77): Remap touch id in sequential order + console.touch_state[index] = { + .position = {console.touch_values[index].x.value, console.touch_values[index].y.value}, + .id = static_cast(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 auto& 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}; + const auto& iterator = callback_list.find(key); + if (iterator == callback_list.end()) { + LOG_ERROR(Input, "Tried to delete non-existent callback {}", key); + return; + } + callback_list.erase(iterator); +} +} // 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..25c183eee5 --- /dev/null +++ b/src/core/hid/emulated_console.h @@ -0,0 +1,188 @@ +// 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 +#include + +#include "common/common_types.h" +#include "common/input.h" +#include "common/param_package.h" +#include "common/point.h" +#include "common/quaternion.h" +#include "common/vector_math.h" +#include "core/hid/hid_types.h" +#include "core/hid/motion_input.h" + +namespace Core::HID { + +struct ConsoleMotionInfo { + Common::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 last_touch{}; + Common::Point position{}; + u32 id{}; + TouchAttribute attribute{}; + bool pressed{}; +}; + +// Contains all motion related data that is used on the services +struct ConsoleMotion { + Common::Vec3f accel{}; + Common::Vec3f gyro{}; + Common::Vec3f rotation{}; + std::array orientation{}; + Common::Quaternion quaternion{}; + bool is_at_rest{}; +}; + +using TouchFingerState = std::array; + +struct ConsoleStatus { + // Data from input_common + ConsoleMotionValues motion_values{}; + TouchValues touch_values{}; + + // Data for HID services + ConsoleMotion motion_state{}; + TouchFingerState touch_state{}; +}; + +enum class ConsoleTriggerType { + Motion, + Touch, + All, +}; + +struct ConsoleUpdateCallback { + std::function on_change; +}; + +class EmulatedConsole { +public: + /** + * Contains all input data related to the console like motion and touch input + */ + EmulatedConsole(); + ~EmulatedConsole(); + + YUZU_NON_COPYABLE(EmulatedConsole); + YUZU_NON_MOVEABLE(EmulatedConsole); + + /// Removes all callbacks created from input devices + void UnloadInput(); + + /// Sets the emulated console into configuring mode. Locking all HID service events from being + /// moddified + void EnableConfiguration(); + + /// Returns the emulated console to the normal behaivour + void DisableConfiguration(); + + /// Returns true if the emulated console is on configuring mode + bool IsConfiguring() const; + + /// Reload all input devices + void ReloadInput(); + + /// Overrides current mapped devices with the stored configuration and reloads all input devices + void ReloadFromSettings(); + + /// Saves the current mapped configuration + void SaveCurrentConfig(); + + /// Reverts any mapped changes made that weren't saved + void RestoreConfig(); + + // Returns the current mapped motion device + Common::ParamPackage GetMotionParam() const; + + /** + * Updates the current mapped motion device + * @param ParamPackage with controller data to be mapped + */ + void SetMotionParam(Common::ParamPackage param); + + /// Returns the latest status of motion input from the console with parameters + ConsoleMotionValues GetMotionValues() const; + + /// Returns the latest status of touch input from the console with parameters + TouchValues GetTouchValues() const; + + /// Returns the latest status of motion input from the console + ConsoleMotion GetMotion() const; + + /// Returns the latest status of touch input from the console + TouchFingerState GetTouch() const; + + /** + * Adds a callback to the list of events + * @param ConsoleUpdateCallback that will be triggered + * @return an unique key corresponding to the callback index in the list + */ + int SetCallback(ConsoleUpdateCallback update_callback); + + /** + * Removes a callback from the list stopping any future events to this object + * @param Key corresponding to the callback index in the list + */ + void DeleteCallback(int key); + +private: + /// Creates and stores the touch params + void SetTouchParams(); + + /** + * Updates the motion status of the console + * @param A CallbackStatus containing gyro and accelerometer data + */ + void SetMotion(Common::Input::CallbackStatus callback); + + /** + * Updates the touch status of the console + * @param callback: A CallbackStatus containing the touch position + * @param index: Finger ID to be updated + */ + void SetTouch(Common::Input::CallbackStatus callback, std::size_t index); + + /** + * Triggers a callback that something has changed on the console status + * @param Input type of the event to 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; + + // Stores the current status of all console input + 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..06ae41c3eb --- /dev/null +++ b/src/core/hid/emulated_controller.cpp @@ -0,0 +1,1061 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included + +#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; + +NpadStyleIndex EmulatedController::MapSettingsTypeToNPad(Settings::ControllerType type) { + switch (type) { + case Settings::ControllerType::ProController: + return NpadStyleIndex::ProController; + case Settings::ControllerType::DualJoyconDetached: + return NpadStyleIndex::JoyconDual; + case Settings::ControllerType::LeftJoycon: + return NpadStyleIndex::JoyconLeft; + case Settings::ControllerType::RightJoycon: + return NpadStyleIndex::JoyconRight; + case Settings::ControllerType::Handheld: + return NpadStyleIndex::Handheld; + case Settings::ControllerType::GameCube: + return NpadStyleIndex::GameCube; + default: + return NpadStyleIndex::ProController; + } +} + +Settings::ControllerType EmulatedController::MapNPadToSettingsType(NpadStyleIndex type) { + switch (type) { + case NpadStyleIndex::ProController: + return Settings::ControllerType::ProController; + case NpadStyleIndex::JoyconDual: + return Settings::ControllerType::DualJoyconDetached; + case NpadStyleIndex::JoyconLeft: + return Settings::ControllerType::LeftJoycon; + case NpadStyleIndex::JoyconRight: + return Settings::ControllerType::RightJoycon; + case NpadStyleIndex::Handheld: + return Settings::ControllerType::Handheld; + case NpadStyleIndex::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]); + } + + 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; + + // Other or debug controller should always be a pro controller + if (npad_id_type != NpadIdType::Other) { + SetNpadStyleIndex(MapSettingsTypeToNPad(player.controller_type)); + } else { + SetNpadStyleIndex(NpadStyleIndex::ProController); + } + + if (player.connected) { + Connect(); + } else { + Disconnect(); + } + + ReloadInput(); +} + +void EmulatedController::LoadDevices() { + // TODO(german77): Use more buttons to detect the correct device + const auto left_joycon = button_params[Settings::NativeButton::DRight]; + const auto right_joycon = button_params[Settings::NativeButton::A]; + + // Triggers for GC controllers + trigger_params[LeftIndex] = button_params[Settings::NativeButton::ZL]; + trigger_params[RightIndex] = button_params[Settings::NativeButton::ZR]; + + battery_params[LeftIndex] = left_joycon; + battery_params[RightIndex] = right_joycon; + battery_params[LeftIndex].Set("battery", true); + battery_params[RightIndex].Set("battery", true); + + output_params[LeftIndex] = left_joycon; + output_params[RightIndex] = right_joycon; + output_params[LeftIndex].Set("output", true); + output_params[RightIndex].Set("output", true); + + LoadTASParams(); + + std::transform(button_params.begin() + Settings::NativeButton::BUTTON_HID_BEGIN, + button_params.begin() + Settings::NativeButton::BUTTON_NS_END, + button_devices.begin(), Common::Input::CreateDevice); + std::transform(stick_params.begin() + Settings::NativeAnalog::STICK_HID_BEGIN, + stick_params.begin() + Settings::NativeAnalog::STICK_HID_END, + stick_devices.begin(), Common::Input::CreateDevice); + std::transform(motion_params.begin() + Settings::NativeMotion::MOTION_HID_BEGIN, + motion_params.begin() + Settings::NativeMotion::MOTION_HID_END, + motion_devices.begin(), Common::Input::CreateDevice); + std::transform(trigger_params.begin(), trigger_params.end(), trigger_devices.begin(), + Common::Input::CreateDevice); + std::transform(battery_params.begin(), battery_params.begin(), battery_devices.end(), + Common::Input::CreateDevice); + std::transform(output_params.begin(), output_params.end(), output_devices.begin(), + Common::Input::CreateDevice); + + // Initialize TAS devices + std::transform(tas_button_params.begin(), tas_button_params.end(), tas_button_devices.begin(), + Common::Input::CreateDevice); + std::transform(tas_stick_params.begin(), tas_stick_params.end(), tas_stick_devices.begin(), + Common::Input::CreateDevice); +} + +void EmulatedController::LoadTASParams() { + const auto player_index = NpadIdTypeToIndex(npad_id_type); + Common::ParamPackage common_params{}; + common_params.Set("engine", "tas"); + common_params.Set("port", static_cast(player_index)); + for (auto& param : tas_button_params) { + param = common_params; + } + for (auto& param : tas_stick_params) { + param = common_params; + } + + // TODO(german77): Replace this with an input profile or something better + tas_button_params[Settings::NativeButton::A].Set("button", 0); + tas_button_params[Settings::NativeButton::B].Set("button", 1); + tas_button_params[Settings::NativeButton::X].Set("button", 2); + tas_button_params[Settings::NativeButton::Y].Set("button", 3); + tas_button_params[Settings::NativeButton::LStick].Set("button", 4); + tas_button_params[Settings::NativeButton::RStick].Set("button", 5); + tas_button_params[Settings::NativeButton::L].Set("button", 6); + tas_button_params[Settings::NativeButton::R].Set("button", 7); + tas_button_params[Settings::NativeButton::ZL].Set("button", 8); + tas_button_params[Settings::NativeButton::ZR].Set("button", 9); + tas_button_params[Settings::NativeButton::Plus].Set("button", 10); + tas_button_params[Settings::NativeButton::Minus].Set("button", 11); + tas_button_params[Settings::NativeButton::DLeft].Set("button", 12); + tas_button_params[Settings::NativeButton::DUp].Set("button", 13); + tas_button_params[Settings::NativeButton::DRight].Set("button", 14); + tas_button_params[Settings::NativeButton::DDown].Set("button", 15); + tas_button_params[Settings::NativeButton::SL].Set("button", 16); + tas_button_params[Settings::NativeButton::SR].Set("button", 17); + tas_button_params[Settings::NativeButton::Home].Set("button", 18); + tas_button_params[Settings::NativeButton::Screenshot].Set("button", 19); + + tas_stick_params[Settings::NativeAnalog::LStick].Set("axis_x", 0); + tas_stick_params[Settings::NativeAnalog::LStick].Set("axis_y", 1); + tas_stick_params[Settings::NativeAnalog::RStick].Set("axis_x", 2); + tas_stick_params[Settings::NativeAnalog::RStick].Set("axis_y", 3); +} + +void EmulatedController::ReloadInput() { + // If you load any device here add the equivalent to the UnloadInput() function + LoadDevices(); + for (std::size_t index = 0; index < button_devices.size(); ++index) { + if (!button_devices[index]) { + continue; + } + const auto uuid = Common::UUID{button_params[index].Get("guid", "")}; + Common::Input::InputCallback button_callback{ + [this, index, uuid](Common::Input::CallbackStatus callback) { + SetButton(callback, index, uuid); + }}; + button_devices[index]->SetCallback(button_callback); + button_devices[index]->ForceUpdate(); + } + + for (std::size_t index = 0; index < stick_devices.size(); ++index) { + if (!stick_devices[index]) { + continue; + } + const auto uuid = Common::UUID{stick_params[index].Get("guid", "")}; + Common::Input::InputCallback stick_callback{ + [this, index, uuid](Common::Input::CallbackStatus callback) { + SetStick(callback, index, uuid); + }}; + stick_devices[index]->SetCallback(stick_callback); + stick_devices[index]->ForceUpdate(); + } + + for (std::size_t index = 0; index < trigger_devices.size(); ++index) { + if (!trigger_devices[index]) { + continue; + } + const auto uuid = Common::UUID{trigger_params[index].Get("guid", "")}; + Common::Input::InputCallback trigger_callback{ + [this, index, uuid](Common::Input::CallbackStatus callback) { + SetTrigger(callback, index, uuid); + }}; + trigger_devices[index]->SetCallback(trigger_callback); + trigger_devices[index]->ForceUpdate(); + } + + for (std::size_t index = 0; index < battery_devices.size(); ++index) { + if (!battery_devices[index]) { + continue; + } + Common::Input::InputCallback battery_callback{ + [this, index](Common::Input::CallbackStatus callback) { SetBattery(callback, index); }}; + battery_devices[index]->SetCallback(battery_callback); + battery_devices[index]->ForceUpdate(); + } + + for (std::size_t index = 0; index < motion_devices.size(); ++index) { + if (!motion_devices[index]) { + continue; + } + Common::Input::InputCallback motion_callback{ + [this, index](Common::Input::CallbackStatus callback) { SetMotion(callback, index); }}; + motion_devices[index]->SetCallback(motion_callback); + motion_devices[index]->ForceUpdate(); + } + + // Use a common UUID for TAS + const auto tas_uuid = Common::UUID{0x0, 0x7A5}; + + // Register TAS devices. No need to force update + for (std::size_t index = 0; index < tas_button_devices.size(); ++index) { + if (!tas_button_devices[index]) { + continue; + } + Common::Input::InputCallback button_callback{ + [this, index, tas_uuid](Common::Input::CallbackStatus callback) { + SetButton(callback, index, tas_uuid); + }}; + tas_button_devices[index]->SetCallback(button_callback); + } + + for (std::size_t index = 0; index < tas_stick_devices.size(); ++index) { + if (!tas_stick_devices[index]) { + continue; + } + Common::Input::InputCallback stick_callback{ + [this, index, tas_uuid](Common::Input::CallbackStatus callback) { + SetStick(callback, index, tas_uuid); + }}; + tas_stick_devices[index]->SetCallback(stick_callback); + } +} + +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(); + } + for (auto& output : output_devices) { + output.reset(); + } + for (auto& button : tas_button_devices) { + button.reset(); + } + for (auto& stick : tas_stick_devices) { + stick.reset(); + } +} + +void EmulatedController::EnableConfiguration() { + is_configuring = true; + tmp_is_connected = is_connected; + tmp_npad_type = npad_type; +} + +void EmulatedController::DisableConfiguration() { + is_configuring = false; + + // Apply temporary npad type to the real controller + if (tmp_npad_type != npad_type) { + if (is_connected) { + Disconnect(); + } + SetNpadStyleIndex(tmp_npad_type); + } + + // Apply temporary connected status to the real controller + if (tmp_is_connected != is_connected) { + if (tmp_is_connected) { + Connect(); + return; + } + Disconnect(); + } +} + +bool EmulatedController::IsConfiguring() const { + return is_configuring; +} + +void EmulatedController::SaveCurrentConfig() { + const auto player_index = NpadIdTypeToIndex(npad_id_type); + auto& player = Settings::values.players.GetValue()[player_index]; + player.connected = is_connected; + player.controller_type = MapNPadToSettingsType(npad_type); + 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( + EmulatedDeviceIndex device_index) 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", 0) == param_.Get("port", 0); + }); + 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", 0)); + 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", 0) == param_.Get("port", 0); + }); + 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", 0)); + 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(Common::Input::CallbackStatus callback, std::size_t index, + Common::UUID uuid) { + 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]; + + // Only read button values that have the same uuid or are pressed once + if (current_status.uuid != uuid) { + if (!new_status.value) { + return; + } + } + + current_status.toggle = new_status.toggle; + current_status.uuid = uuid; + + // 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, false); + 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; + } + } + if (!is_connected) { + if (npad_id_type == NpadIdType::Player1 && npad_type != NpadStyleIndex::Handheld) { + Connect(); + } + if (npad_id_type == NpadIdType::Handheld && npad_type == NpadStyleIndex::Handheld) { + Connect(); + } + } + TriggerOnChange(ControllerTriggerType::Button, true); +} + +void EmulatedController::SetStick(Common::Input::CallbackStatus callback, std::size_t index, + Common::UUID uuid) { + if (index >= controller.stick_values.size()) { + return; + } + std::lock_guard lock{mutex}; + const auto stick_value = TransformToStick(callback); + + // Only read stick values that have the same uuid or are over the threshold to avoid flapping + if (controller.stick_values[index].uuid != uuid) { + if (!stick_value.down && !stick_value.up && !stick_value.left && !stick_value.right) { + return; + } + } + + controller.stick_values[index] = stick_value; + controller.stick_values[index].uuid = uuid; + + if (is_configuring) { + controller.analog_stick_state.left = {}; + controller.analog_stick_state.right = {}; + TriggerOnChange(ControllerTriggerType::Stick, false); + 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, true); +} + +void EmulatedController::SetTrigger(Common::Input::CallbackStatus callback, std::size_t index, + Common::UUID uuid) { + if (index >= controller.trigger_values.size()) { + return; + } + std::lock_guard lock{mutex}; + const auto trigger_value = TransformToTrigger(callback); + + // Only read trigger values that have the same uuid or are pressed once + if (controller.stick_values[index].uuid != uuid) { + if (!trigger_value.pressed.value) { + return; + } + } + + controller.trigger_values[index] = trigger_value; + controller.trigger_values[index].uuid = uuid; + + if (is_configuring) { + controller.gc_trigger_state.left = 0; + controller.gc_trigger_state.right = 0; + TriggerOnChange(ControllerTriggerType::Trigger, false); + 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.value); + 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.value); + break; + } + + TriggerOnChange(ControllerTriggerType::Trigger, true); +} + +void EmulatedController::SetMotion(Common::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); + force_update_motion = raw_status.force_update; + + if (is_configuring) { + TriggerOnChange(ControllerTriggerType::Motion, false); + return; + } + + auto& motion = controller.motion_state[index]; + motion.accel = emulated.GetAcceleration(); + motion.gyro = emulated.GetGyroscope(); + motion.rotation = emulated.GetRotations(); + motion.orientation = emulated.GetOrientation(); + motion.is_at_rest = !emulated.IsMoving(motion_sensitivity); + + TriggerOnChange(ControllerTriggerType::Motion, true); +} + +void EmulatedController::SetBattery(Common::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, false); + return; + } + + bool is_charging = false; + bool is_powered = false; + NpadBatteryLevel battery_level = 0; + switch (controller.battery_values[index]) { + case Common::Input::BatteryLevel::Charging: + is_charging = true; + is_powered = true; + battery_level = 6; + break; + case Common::Input::BatteryLevel::Medium: + battery_level = 6; + break; + case Common::Input::BatteryLevel::Low: + battery_level = 4; + break; + case Common::Input::BatteryLevel::Critical: + battery_level = 2; + break; + case Common::Input::BatteryLevel::Empty: + battery_level = 0; + break; + case Common::Input::BatteryLevel::None: + case Common::Input::BatteryLevel::Full: + default: + is_powered = true; + battery_level = 8; + break; + } + + switch (index) { + case LeftIndex: + controller.battery_state.left = { + .is_powered = is_powered, + .is_charging = is_charging, + .battery_level = battery_level, + }; + break; + case RightIndex: + controller.battery_state.right = { + .is_powered = is_powered, + .is_charging = is_charging, + .battery_level = battery_level, + }; + break; + case DualIndex: + controller.battery_state.dual = { + .is_powered = is_powered, + .is_charging = is_charging, + .battery_level = battery_level, + }; + break; + } + TriggerOnChange(ControllerTriggerType::Battery, true); +} + +bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue vibration) { + if (device_index >= output_devices.size()) { + return false; + } + if (!output_devices[device_index]) { + return false; + } + const auto player_index = NpadIdTypeToIndex(npad_id_type); + const auto& player = Settings::values.players.GetValue()[player_index]; + const f32 strength = static_cast(player.vibration_strength) / 100.0f; + + if (!player.vibration_enabled) { + return false; + } + + // Exponential amplification is too strong at low amplitudes. Switch to a linear + // amplification if strength is set below 0.7f + const Common::Input::VibrationAmplificationType type = + strength > 0.7f ? Common::Input::VibrationAmplificationType::Exponential + : Common::Input::VibrationAmplificationType::Linear; + + const Common::Input::VibrationStatus status = { + .low_amplitude = std::min(vibration.low_amplitude * strength, 1.0f), + .low_frequency = vibration.low_frequency, + .high_amplitude = std::min(vibration.high_amplitude * strength, 1.0f), + .high_frequency = vibration.high_frequency, + .type = type, + }; + return output_devices[device_index]->SetVibration(status) == + Common::Input::VibrationError::None; +} + +bool EmulatedController::TestVibration(std::size_t device_index) { + if (device_index >= output_devices.size()) { + return false; + } + if (!output_devices[device_index]) { + return false; + } + + // Send a slight vibration to test for rumble support + constexpr Common::Input::VibrationStatus status = { + .low_amplitude = 0.001f, + .low_frequency = 160.0f, + .high_amplitude = 0.001f, + .high_frequency = 320.0f, + .type = Common::Input::VibrationAmplificationType::Linear, + }; + return output_devices[device_index]->SetVibration(status) == + Common::Input::VibrationError::None; +} + +void EmulatedController::SetLedPattern() { + for (auto& device : output_devices) { + if (!device) { + continue; + } + + const LedPattern pattern = GetLedPattern(); + const Common::Input::LedStatus status = { + .led_1 = pattern.position1 != 0, + .led_2 = pattern.position2 != 0, + .led_3 = pattern.position3 != 0, + .led_4 = pattern.position4 != 0, + }; + device->SetLED(status); + } +} + +void EmulatedController::Connect() { + { + std::lock_guard lock{mutex}; + if (is_configuring) { + tmp_is_connected = true; + TriggerOnChange(ControllerTriggerType::Connected, false); + return; + } + + if (is_connected) { + return; + } + is_connected = true; + } + TriggerOnChange(ControllerTriggerType::Connected, true); +} + +void EmulatedController::Disconnect() { + { + std::lock_guard lock{mutex}; + if (is_configuring) { + tmp_is_connected = false; + TriggerOnChange(ControllerTriggerType::Disconnected, false); + return; + } + + if (!is_connected) { + return; + } + is_connected = false; + } + TriggerOnChange(ControllerTriggerType::Disconnected, true); +} + +bool EmulatedController::IsConnected(bool get_temporary_value) const { + if (get_temporary_value && is_configuring) { + return tmp_is_connected; + } + return is_connected; +} + +bool EmulatedController::IsVibrationEnabled() const { + const auto player_index = NpadIdTypeToIndex(npad_id_type); + const auto& player = Settings::values.players.GetValue()[player_index]; + return player.vibration_enabled; +} + +NpadIdType EmulatedController::GetNpadIdType() const { + return npad_id_type; +} + +NpadStyleIndex EmulatedController::GetNpadStyleIndex(bool get_temporary_value) const { + if (get_temporary_value && is_configuring) { + return tmp_npad_type; + } + return npad_type; +} + +void EmulatedController::SetNpadStyleIndex(NpadStyleIndex npad_type_) { + { + std::lock_guard lock{mutex}; + + if (is_configuring) { + if (tmp_npad_type == npad_type_) { + return; + } + tmp_npad_type = npad_type_; + TriggerOnChange(ControllerTriggerType::Type, false); + return; + } + + if (npad_type == npad_type_) { + return; + } + if (is_connected) { + LOG_WARNING(Service_HID, "Controller {} type changed while it's connected", + NpadIdTypeToIndex(npad_id_type)); + } + npad_type = npad_type_; + } + TriggerOnChange(ControllerTriggerType::Type, true); +} + +LedPattern EmulatedController::GetLedPattern() const { + switch (npad_id_type) { + case NpadIdType::Player1: + return LedPattern{1, 0, 0, 0}; + case NpadIdType::Player2: + return LedPattern{1, 1, 0, 0}; + case NpadIdType::Player3: + return LedPattern{1, 1, 1, 0}; + case NpadIdType::Player4: + return LedPattern{1, 1, 1, 1}; + case NpadIdType::Player5: + return LedPattern{1, 0, 0, 1}; + case NpadIdType::Player6: + return LedPattern{1, 0, 1, 0}; + case NpadIdType::Player7: + return LedPattern{1, 0, 1, 1}; + case NpadIdType::Player8: + return LedPattern{0, 1, 1, 0}; + default: + return LedPattern{0, 0, 0, 0}; + } +} + +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 {}; + } + // Some drivers like stick from buttons need constant refreshing + for (auto& device : stick_devices) { + if (!device) { + continue; + } + device->SoftUpdate(); + } + return controller.analog_stick_state; +} + +NpadGcTriggerState EmulatedController::GetTriggers() const { + if (is_configuring) { + return {}; + } + return controller.gc_trigger_state; +} + +MotionState EmulatedController::GetMotions() const { + if (force_update_motion) { + for (auto& device : motion_devices) { + if (!device) { + continue; + } + device->ForceUpdate(); + } + } + 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, bool is_npad_service_update) { + for (const auto& poller_pair : callback_list) { + const ControllerUpdateCallback& poller = poller_pair.second; + if (!is_npad_service_update && poller.is_npad_service) { + continue; + } + 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}; + const auto& iterator = callback_list.find(key); + if (iterator == callback_list.end()) { + LOG_ERROR(Input, "Tried to delete non-existent callback {}", key); + return; + } + callback_list.erase(iterator); +} +} // 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..2c5d51bc8f --- /dev/null +++ b/src/core/hid/emulated_controller.h @@ -0,0 +1,392 @@ +// 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 +#include + +#include "common/common_types.h" +#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 { +const std::size_t max_emulated_controllers = 2; +struct ControllerMotionInfo { + Common::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, max_emulated_controllers>; +using OutputDevices = + std::array, max_emulated_controllers>; + +using ButtonParams = std::array; +using StickParams = std::array; +using ControllerMotionParams = std::array; +using TriggerParams = std::array; +using BatteryParams = std::array; +using OutputParams = 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 { + Common::Vec3f accel{}; + Common::Vec3f gyro{}; + Common::Vec3f rotation{}; + std::array orientation{}; + bool is_at_rest{}; +}; + +enum EmulatedDeviceIndex : u8 { + LeftIndex, + RightIndex, + DualIndex, + AllDevices, +}; + +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 HID serices + 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; + bool is_npad_service; +}; + +class EmulatedController { +public: + /** + * Contains all input data related to this controller. Like buttons, joysticks, motion. + * @param Npad id type for this specific controller + */ + explicit EmulatedController(NpadIdType npad_id_type_); + ~EmulatedController(); + + YUZU_NON_COPYABLE(EmulatedController); + YUZU_NON_MOVEABLE(EmulatedController); + + /// Converts the controller type from settings to npad type + static NpadStyleIndex MapSettingsTypeToNPad(Settings::ControllerType type); + + /// Converts npad type to the equivalent of controller type from settings + static Settings::ControllerType MapNPadToSettingsType(NpadStyleIndex type); + + /// Gets the NpadIdType for this controller + NpadIdType GetNpadIdType() const; + + /// Sets the NpadStyleIndex for this controller + void SetNpadStyleIndex(NpadStyleIndex npad_type_); + + /** + * Gets the NpadStyleIndex for this controller + * @param If true tmp_npad_type will be returned + * @return NpadStyleIndex set on the controller + */ + NpadStyleIndex GetNpadStyleIndex(bool get_temporary_value = false) const; + + /// Sets the connected status to true + void Connect(); + + /// Sets the connected status to false + void Disconnect(); + + /** + * Is the emulated connected + * @param If true tmp_is_connected will be returned + * @return true if the controller has the connected status + */ + bool IsConnected(bool get_temporary_value = false) const; + + /// Returns true if vibration is enabled + bool IsVibrationEnabled() const; + + /// Removes all callbacks created from input devices + void UnloadInput(); + + /// Sets the emulated console into configuring mode. Locking all HID service events from being + /// moddified + void EnableConfiguration(); + + /// Returns the emulated console to the normal behaivour + void DisableConfiguration(); + + /// Returns true if the emulated device is on configuring mode + bool IsConfiguring() const; + + /// Reload all input devices + void ReloadInput(); + + /// Overrides current mapped devices with the stored configuration and reloads all input devices + void ReloadFromSettings(); + + /// Saves the current mapped configuration + void SaveCurrentConfig(); + + /// Reverts any mapped changes made that weren't saved + void RestoreConfig(); + + /// Returns a vector of mapped devices from the mapped button and stick parameters + std::vector GetMappedDevices(EmulatedDeviceIndex device_index) const; + + // Returns the current mapped button device + Common::ParamPackage GetButtonParam(std::size_t index) const; + + // Returns the current mapped stick device + Common::ParamPackage GetStickParam(std::size_t index) const; + + // Returns the current mapped motion device + Common::ParamPackage GetMotionParam(std::size_t index) const; + + /** + * Updates the current mapped button device + * @param ParamPackage with controller data to be mapped + */ + void SetButtonParam(std::size_t index, Common::ParamPackage param); + + /** + * Updates the current mapped stick device + * @param ParamPackage with controller data to be mapped + */ + void SetStickParam(std::size_t index, Common::ParamPackage param); + + /** + * Updates the current mapped motion device + * @param ParamPackage with controller data to be mapped + */ + void SetMotionParam(std::size_t index, Common::ParamPackage param); + + /// Returns the latest button status from the controller with parameters + ButtonValues GetButtonsValues() const; + + /// Returns the latest analog stick status from the controller with parameters + SticksValues GetSticksValues() const; + + /// Returns the latest trigger status from the controller with parameters + TriggerValues GetTriggersValues() const; + + /// Returns the latest motion status from the controller with parameters + ControllerMotionValues GetMotionValues() const; + + /// Returns the latest color status from the controller with parameters + ColorValues GetColorsValues() const; + + /// Returns the latest battery status from the controller with parameters + BatteryValues GetBatteryValues() const; + + /// Returns the latest status of button input for the npad service + NpadButtonState GetNpadButtons() const; + + /// Returns the latest status of button input for the debug pad service + DebugPadButton GetDebugPadButtons() const; + + /// Returns the latest status of stick input from the mouse + AnalogSticks GetSticks() const; + + /// Returns the latest status of trigger input from the mouse + NpadGcTriggerState GetTriggers() const; + + /// Returns the latest status of motion input from the mouse + MotionState GetMotions() const; + + /// Returns the latest color value from the controller + ControllerColors GetColors() const; + + /// Returns the latest battery status from the controller + BatteryLevelState GetBattery() const; + + /* + * Sends a specific vibration to the output device + * @return returns true if vibration had no errors + */ + bool SetVibration(std::size_t device_index, VibrationValue vibration); + + /* + * Sends a small vibration to the output device + * @return returns true if SetVibration was successfull + */ + bool TestVibration(std::size_t device_index); + + /// Returns the led pattern corresponding to this emulated controller + LedPattern GetLedPattern() const; + + /// Asks the output device to change the player led pattern + void SetLedPattern(); + + /** + * Adds a callback to the list of events + * @param ConsoleUpdateCallback that will be triggered + * @return an unique key corresponding to the callback index in the list + */ + int SetCallback(ControllerUpdateCallback update_callback); + + /** + * Removes a callback from the list stopping any future events to this object + * @param Key corresponding to the callback index in the list + */ + void DeleteCallback(int key); + +private: + /// creates input devices from params + void LoadDevices(); + + /// Set the params for TAS devices + void LoadTASParams(); + + /** + * Updates the button status of the controller + * @param callback: A CallbackStatus containing the button status + * @param index: Button ID of the to be updated + */ + void SetButton(Common::Input::CallbackStatus callback, std::size_t index, Common::UUID uuid); + + /** + * Updates the analog stick status of the controller + * @param callback: A CallbackStatus containing the analog stick status + * @param index: stick ID of the to be updated + */ + void SetStick(Common::Input::CallbackStatus callback, std::size_t index, Common::UUID uuid); + + /** + * Updates the trigger status of the controller + * @param callback: A CallbackStatus containing the trigger status + * @param index: trigger ID of the to be updated + */ + void SetTrigger(Common::Input::CallbackStatus callback, std::size_t index, Common::UUID uuid); + + /** + * Updates the motion status of the controller + * @param callback: A CallbackStatus containing gyro and accelerometer data + * @param index: motion ID of the to be updated + */ + void SetMotion(Common::Input::CallbackStatus callback, std::size_t index); + + /** + * Updates the battery status of the controller + * @param callback: A CallbackStatus containing the battery status + * @param index: Button ID of the to be updated + */ + void SetBattery(Common::Input::CallbackStatus callback, std::size_t index); + + /** + * Triggers a callback that something has changed on the controller status + * @param type: Input type of the event to trigger + * @param is_service_update: indicates if this event should be sended to only services + */ + void TriggerOnChange(ControllerTriggerType type, bool is_service_update); + + NpadIdType npad_id_type; + NpadStyleIndex npad_type{NpadStyleIndex::None}; + bool is_connected{false}; + bool is_configuring{false}; + f32 motion_sensitivity{0.01f}; + bool force_update_motion{false}; + + // Temporary values to avoid doing changes while the controller is on configuration mode + NpadStyleIndex tmp_npad_type{NpadStyleIndex::None}; + bool tmp_is_connected{false}; + + ButtonParams button_params; + StickParams stick_params; + ControllerMotionParams motion_params; + TriggerParams trigger_params; + BatteryParams battery_params; + OutputParams output_params; + + ButtonDevices button_devices; + StickDevices stick_devices; + ControllerMotionDevices motion_devices; + TriggerDevices trigger_devices; + BatteryDevices battery_devices; + OutputDevices output_devices; + + // TAS related variables + ButtonParams tas_button_params; + StickParams tas_stick_params; + ButtonDevices tas_button_devices; + StickDevices tas_stick_devices; + + mutable std::mutex mutex; + std::unordered_map callback_list; + int last_callback_key = 0; + + // Stores the current status of all controller input + 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..874780ec28 --- /dev/null +++ b/src/core/hid/emulated_devices.cpp @@ -0,0 +1,451 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included + +#include +#include + +#include "core/hid/emulated_devices.h" +#include "core/hid/input_converter.h" + +namespace Core::HID { + +EmulatedDevices::EmulatedDevices() = default; + +EmulatedDevices::~EmulatedDevices() = default; + +void EmulatedDevices::ReloadFromSettings() { + ReloadInput(); +} + +void EmulatedDevices::ReloadInput() { + // If you load any device here add the equivalent to the UnloadInput() function + std::size_t key_index = 0; + for (auto& mouse_device : mouse_button_devices) { + Common::ParamPackage mouse_params; + mouse_params.Set("engine", "mouse"); + mouse_params.Set("button", static_cast(key_index)); + mouse_device = Common::Input::CreateDevice(mouse_params); + key_index++; + } + + mouse_stick_device = Common::Input::CreateDeviceFromString( + "engine:mouse,axis_x:0,axis_y:1"); + + // First two axis are reserved for mouse position + key_index = 2; + for (auto& mouse_device : mouse_analog_devices) { + Common::ParamPackage mouse_params; + mouse_params.Set("engine", "mouse"); + mouse_params.Set("axis", static_cast(key_index)); + mouse_device = Common::Input::CreateDevice(mouse_params); + key_index++; + } + + key_index = 0; + for (auto& keyboard_device : keyboard_devices) { + // Keyboard keys are only mapped on port 1, pad 0 + Common::ParamPackage keyboard_params; + keyboard_params.Set("engine", "keyboard"); + keyboard_params.Set("button", static_cast(key_index)); + keyboard_params.Set("port", 1); + keyboard_params.Set("pad", 0); + keyboard_device = Common::Input::CreateDevice(keyboard_params); + key_index++; + } + + key_index = 0; + for (auto& keyboard_device : keyboard_modifier_devices) { + // Keyboard moddifiers are only mapped on port 1, pad 1 + Common::ParamPackage keyboard_params; + keyboard_params.Set("engine", "keyboard"); + keyboard_params.Set("button", static_cast(key_index)); + keyboard_params.Set("port", 1); + keyboard_params.Set("pad", 1); + keyboard_device = Common::Input::CreateDevice(keyboard_params); + key_index++; + } + + for (std::size_t index = 0; index < mouse_button_devices.size(); ++index) { + if (!mouse_button_devices[index]) { + continue; + } + Common::Input::InputCallback button_callback{ + [this, index](Common::Input::CallbackStatus callback) { + SetMouseButton(callback, index); + }}; + mouse_button_devices[index]->SetCallback(button_callback); + } + + for (std::size_t index = 0; index < mouse_analog_devices.size(); ++index) { + if (!mouse_analog_devices[index]) { + continue; + } + Common::Input::InputCallback button_callback{ + [this, index](Common::Input::CallbackStatus callback) { + SetMouseAnalog(callback, index); + }}; + mouse_analog_devices[index]->SetCallback(button_callback); + } + + if (mouse_stick_device) { + Common::Input::InputCallback button_callback{ + [this](Common::Input::CallbackStatus callback) { SetMouseStick(callback); }}; + mouse_stick_device->SetCallback(button_callback); + } + + for (std::size_t index = 0; index < keyboard_devices.size(); ++index) { + if (!keyboard_devices[index]) { + continue; + } + Common::Input::InputCallback button_callback{ + [this, index](Common::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; + } + Common::Input::InputCallback button_callback{ + [this, index](Common::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& analog : mouse_analog_devices) { + analog.reset(); + } + mouse_stick_device.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; + } +} + +void EmulatedDevices::RestoreConfig() { + if (!is_configuring) { + return; + } + ReloadFromSettings(); +} + +void EmulatedDevices::SetKeyboardButton(Common::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 status + 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; + } + + // Index should be converted from NativeKeyboard to KeyboardKeyIndex + UpdateKey(index, current_status.value); + + TriggerOnChange(DeviceTriggerType::Keyboard); +} + +void EmulatedDevices::UpdateKey(std::size_t key_index, bool status) { + constexpr std::size_t KEYS_PER_BYTE = 8; + auto& entry = device_status.keyboard_state.key[key_index / KEYS_PER_BYTE]; + const u8 mask = static_cast(1 << (key_index % KEYS_PER_BYTE)); + if (status) { + entry = entry | mask; + } else { + entry = static_cast(entry & ~mask); + } +} + +void EmulatedDevices::SetKeyboardModifier(Common::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(Common::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); +} + +void EmulatedDevices::SetMouseAnalog(Common::Input::CallbackStatus callback, std::size_t index) { + if (index >= device_status.mouse_analog_values.size()) { + return; + } + std::lock_guard lock{mutex}; + const auto analog_value = TransformToAnalog(callback); + + device_status.mouse_analog_values[index] = analog_value; + + if (is_configuring) { + device_status.mouse_position_state = {}; + TriggerOnChange(DeviceTriggerType::Mouse); + return; + } + + switch (index) { + case Settings::NativeMouseWheel::X: + device_status.mouse_wheel_state.x = static_cast(analog_value.value); + break; + case Settings::NativeMouseWheel::Y: + device_status.mouse_wheel_state.y = static_cast(analog_value.value); + break; + } + + TriggerOnChange(DeviceTriggerType::Mouse); +} + +void EmulatedDevices::SetMouseStick(Common::Input::CallbackStatus callback) { + std::lock_guard lock{mutex}; + const auto touch_value = TransformToTouch(callback); + + device_status.mouse_stick_value = touch_value; + + if (is_configuring) { + device_status.mouse_position_state = {}; + TriggerOnChange(DeviceTriggerType::Mouse); + return; + } + + device_status.mouse_position_state.x = touch_value.x.value; + device_status.mouse_position_state.y = touch_value.y.value; + + TriggerOnChange(DeviceTriggerType::Mouse); +} + +KeyboardValues EmulatedDevices::GetKeyboardValues() const { + return device_status.keyboard_values; +} + +KeyboardModifierValues EmulatedDevices::GetKeyboardModdifierValues() const { + return device_status.keyboard_moddifier_values; +} + +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; +} + +AnalogStickState EmulatedDevices::GetMouseWheel() const { + return device_status.mouse_wheel_state; +} + +void EmulatedDevices::TriggerOnChange(DeviceTriggerType type) { + for (const auto& 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}; + const auto& iterator = callback_list.find(key); + if (iterator == callback_list.end()) { + LOG_ERROR(Input, "Tried to delete non-existent callback {}", key); + return; + } + callback_list.erase(iterator); +} +} // 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..05a945d083 --- /dev/null +++ b/src/core/hid/emulated_devices.h @@ -0,0 +1,209 @@ +// 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 +#include + +#include "common/common_types.h" +#include "common/input.h" +#include "common/param_package.h" +#include "common/settings.h" +#include "core/hid/hid_types.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 MouseAnalogDevices = std::array, + Settings::NativeMouseWheel::NumMouseWheels>; +using MouseStickDevice = std::unique_ptr; + +using MouseButtonParams = + std::array; + +using KeyboardValues = + std::array; +using KeyboardModifierValues = + std::array; +using MouseButtonValues = + std::array; +using MouseAnalogValues = + std::array; +using MouseStickValue = Common::Input::TouchStatus; + +struct MousePosition { + f32 x; + f32 y; +}; + +struct DeviceStatus { + // Data from input_common + KeyboardValues keyboard_values{}; + KeyboardModifierValues keyboard_moddifier_values{}; + MouseButtonValues mouse_button_values{}; + MouseAnalogValues mouse_analog_values{}; + MouseStickValue mouse_stick_value{}; + + // Data for HID serices + KeyboardKey keyboard_state{}; + KeyboardModifier keyboard_moddifier_state{}; + MouseButton mouse_button_state{}; + MousePosition mouse_position_state{}; + AnalogStickState mouse_wheel_state{}; +}; + +enum class DeviceTriggerType { + Keyboard, + KeyboardModdifier, + Mouse, +}; + +struct InterfaceUpdateCallback { + std::function on_change; +}; + +class EmulatedDevices { +public: + /** + * Contains all input data related to external devices that aren't necesarily a controller + * like keyboard and mouse + */ + EmulatedDevices(); + ~EmulatedDevices(); + + YUZU_NON_COPYABLE(EmulatedDevices); + YUZU_NON_MOVEABLE(EmulatedDevices); + + /// Removes all callbacks created from input devices + void UnloadInput(); + + /// Sets the emulated console into configuring mode. Locking all HID service events from being + /// moddified + void EnableConfiguration(); + + /// Returns the emulated console to the normal behaivour + void DisableConfiguration(); + + /// Returns true if the emulated device is on configuring mode + bool IsConfiguring() const; + + /// Reload all input devices + void ReloadInput(); + + /// Overrides current mapped devices with the stored configuration and reloads all input devices + void ReloadFromSettings(); + + /// Saves the current mapped configuration + void SaveCurrentConfig(); + + /// Reverts any mapped changes made that weren't saved + void RestoreConfig(); + + /// Returns the latest status of button input from the keyboard with parameters + KeyboardValues GetKeyboardValues() const; + + /// Returns the latest status of button input from the keyboard modifiers with parameters + KeyboardModifierValues GetKeyboardModdifierValues() const; + + /// Returns the latest status of button input from the mouse with parameters + MouseButtonValues GetMouseButtonsValues() const; + + /// Returns the latest status of button input from the keyboard + KeyboardKey GetKeyboard() const; + + /// Returns the latest status of button input from the keyboard modifiers + KeyboardModifier GetKeyboardModifier() const; + + /// Returns the latest status of button input from the mouse + MouseButton GetMouseButtons() const; + + /// Returns the latest mouse coordinates + MousePosition GetMousePosition() const; + + /// Returns the latest mouse wheel change + AnalogStickState GetMouseWheel() const; + + /** + * Adds a callback to the list of events + * @param InterfaceUpdateCallback that will be triggered + * @return an unique key corresponding to the callback index in the list + */ + int SetCallback(InterfaceUpdateCallback update_callback); + + /** + * Removes a callback from the list stopping any future events to this object + * @param Key corresponding to the callback index in the list + */ + void DeleteCallback(int key); + +private: + /// Helps assigning a value to keyboard_state + void UpdateKey(std::size_t key_index, bool status); + + /** + * Updates the touch status of the keyboard device + * @param callback: A CallbackStatus containing the key status + * @param index: key ID to be updated + */ + void SetKeyboardButton(Common::Input::CallbackStatus callback, std::size_t index); + + /** + * Updates the keyboard status of the keyboard device + * @param callback: A CallbackStatus containing the modifier key status + * @param index: modifier key ID to be updated + */ + void SetKeyboardModifier(Common::Input::CallbackStatus callback, std::size_t index); + + /** + * Updates the mouse button status of the mouse device + * @param callback: A CallbackStatus containing the button status + * @param index: Button ID to be updated + */ + void SetMouseButton(Common::Input::CallbackStatus callback, std::size_t index); + + /** + * Updates the mouse wheel status of the mouse device + * @param callback: A CallbackStatus containing the wheel status + * @param index: wheel ID to be updated + */ + void SetMouseAnalog(Common::Input::CallbackStatus callback, std::size_t index); + + /** + * Updates the mouse position status of the mouse device + * @param callback: A CallbackStatus containing the position status + * @param index: stick ID to be updated + */ + void SetMouseStick(Common::Input::CallbackStatus callback); + + /** + * Triggers a callback that something has changed on the device status + * @param Input type of the event to trigger + */ + void TriggerOnChange(DeviceTriggerType type); + + bool is_configuring{false}; + + KeyboardDevices keyboard_devices; + KeyboardModifierDevices keyboard_modifier_devices; + MouseButtonDevices mouse_button_devices; + MouseAnalogDevices mouse_analog_devices; + MouseStickDevice mouse_stick_device; + + mutable std::mutex mutex; + std::unordered_map callback_list; + int last_callback_key = 0; + + // Stores the current status of all external device input + 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..741a69c3cc --- /dev/null +++ b/src/core/hid/hid_core.cpp @@ -0,0 +1,168 @@ +// 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/emulated_console.h" +#include "core/hid/emulated_controller.h" +#include "core/hid/emulated_devices.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; +} + +s8 HIDCore::GetPlayerCount() const { + s8 active_players = 0; + for (std::size_t player_index = 0; player_index < available_controllers - 2; ++player_index) { + const auto* const controller = GetEmulatedControllerByIndex(player_index); + if (controller->IsConnected()) { + active_players++; + } + } + return active_players; +} + +NpadIdType HIDCore::GetFirstNpadId() const { + for (std::size_t player_index = 0; player_index < available_controllers; ++player_index) { + const auto* const controller = GetEmulatedControllerByIndex(player_index); + if (controller->IsConnected()) { + return controller->GetNpadIdType(); + } + } + return NpadIdType::Player1; +} + +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..609f40f3b9 --- /dev/null +++ b/src/core/hid/hid_core.h @@ -0,0 +1,73 @@ +// 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/hid_types.h" + +namespace Core::HID { +class EmulatedConsole; +class EmulatedController; +class EmulatedDevices; +} // namespace Core::HID + +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; + + /// Counts the connected players from P1-P8 + s8 GetPlayerCount() const; + + /// Returns the first connected npad id + NpadIdType GetFirstNpadId() const; + + /// Reloads all input devices from settings + void ReloadInputDevices(); + + /// Removes all callbacks from input common + void UnloadInputDevices(); + + /// Number of emulated controllers + static constexpr std::size_t available_controllers{10}; + +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 diff --git a/src/core/hid/hid_types.h b/src/core/hid/hid_types.h new file mode 100644 index 0000000000..acf54e233a --- /dev/null +++ b/src/core/hid/hid_types.h @@ -0,0 +1,631 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/bit_field.h" +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/point.h" +#include "common/uuid.h" + +namespace Core::HID { + +enum class DeviceIndex : u8 { + Left = 0, + Right = 1, + None = 2, + MaxDeviceIndex = 3, +}; + +// This is nn::hid::NpadButton +enum class NpadButton : u64 { + None = 0, + A = 1U << 0, + B = 1U << 1, + X = 1U << 2, + Y = 1U << 3, + StickL = 1U << 4, + StickR = 1U << 5, + L = 1U << 6, + R = 1U << 7, + ZL = 1U << 8, + ZR = 1U << 9, + Plus = 1U << 10, + Minus = 1U << 11, + + Left = 1U << 12, + Up = 1U << 13, + Right = 1U << 14, + Down = 1U << 15, + + StickLLeft = 1U << 16, + StickLUp = 1U << 17, + StickLRight = 1U << 18, + StickLDown = 1U << 19, + + StickRLeft = 1U << 20, + StickRUp = 1U << 21, + StickRRight = 1U << 22, + StickRDown = 1U << 23, + + LeftSL = 1U << 24, + LeftSR = 1U << 25, + + RightSL = 1U << 26, + RightSR = 1U << 27, + + Palma = 1U << 28, + Verification = 1U << 29, + HandheldLeftB = 1U << 30, + LagonCLeft = 1U << 31, + LagonCUp = 1ULL << 32, + LagonCRight = 1ULL << 33, + LagonCDown = 1ULL << 34, +}; +DECLARE_ENUM_FLAG_OPERATORS(NpadButton); + +enum class KeyboardKeyIndex : u32 { + A = 4, + B = 5, + C = 6, + D = 7, + E = 8, + F = 9, + G = 10, + H = 11, + I = 12, + J = 13, + K = 14, + L = 15, + M = 16, + N = 17, + O = 18, + P = 19, + Q = 20, + R = 21, + S = 22, + T = 23, + U = 24, + V = 25, + W = 26, + X = 27, + Y = 28, + Z = 29, + D1 = 30, + D2 = 31, + D3 = 32, + D4 = 33, + D5 = 34, + D6 = 35, + D7 = 36, + D8 = 37, + D9 = 38, + D0 = 39, + Return = 40, + Escape = 41, + Backspace = 42, + Tab = 43, + Space = 44, + Minus = 45, + Plus = 46, + OpenBracket = 47, + CloseBracket = 48, + Pipe = 49, + Tilde = 50, + Semicolon = 51, + Quote = 52, + Backquote = 53, + Comma = 54, + Period = 55, + Slash = 56, + CapsLock = 57, + F1 = 58, + F2 = 59, + F3 = 60, + F4 = 61, + F5 = 62, + F6 = 63, + F7 = 64, + F8 = 65, + F9 = 66, + F10 = 67, + F11 = 68, + F12 = 69, + PrintScreen = 70, + ScrollLock = 71, + Pause = 72, + Insert = 73, + Home = 74, + PageUp = 75, + Delete = 76, + End = 77, + PageDown = 78, + RightArrow = 79, + LeftArrow = 80, + DownArrow = 81, + UpArrow = 82, + NumLock = 83, + NumPadDivide = 84, + NumPadMultiply = 85, + NumPadSubtract = 86, + NumPadAdd = 87, + NumPadEnter = 88, + NumPad1 = 89, + NumPad2 = 90, + NumPad3 = 91, + NumPad4 = 92, + NumPad5 = 93, + NumPad6 = 94, + NumPad7 = 95, + NumPad8 = 96, + NumPad9 = 97, + NumPad0 = 98, + NumPadDot = 99, + Backslash = 100, + Application = 101, + Power = 102, + NumPadEquals = 103, + F13 = 104, + F14 = 105, + F15 = 106, + F16 = 107, + F17 = 108, + F18 = 109, + F19 = 110, + F20 = 111, + F21 = 112, + F22 = 113, + F23 = 114, + F24 = 115, + NumPadComma = 133, + Ro = 135, + KatakanaHiragana = 136, + Yen = 137, + Henkan = 138, + Muhenkan = 139, + NumPadCommaPc98 = 140, + HangulEnglish = 144, + Hanja = 145, + Katakana = 146, + Hiragana = 147, + ZenkakuHankaku = 148, + LeftControl = 224, + LeftShift = 225, + LeftAlt = 226, + LeftGui = 227, + RightControl = 228, + RightShift = 229, + RightAlt = 230, + RightGui = 231, +}; + +// This is nn::hid::NpadIdType +enum class NpadIdType : u32 { + Player1 = 0x0, + Player2 = 0x1, + Player3 = 0x2, + Player4 = 0x3, + Player5 = 0x4, + Player6 = 0x5, + Player7 = 0x6, + Player8 = 0x7, + Other = 0x10, + Handheld = 0x20, + + Invalid = 0xFFFFFFFF, +}; + +// This is nn::hid::NpadStyleIndex +enum class NpadStyleIndex : u8 { + None = 0, + ProController = 3, + Handheld = 4, + HandheldNES = 4, + JoyconDual = 5, + JoyconLeft = 6, + JoyconRight = 7, + GameCube = 8, + Pokeball = 9, + NES = 10, + SNES = 12, + N64 = 13, + SegaGenesis = 14, + SystemExt = 32, + System = 33, + MaxNpadType = 34, +}; + +// This is nn::hid::NpadStyleSet +enum class NpadStyleSet : u32 { + None = 0, + Fullkey = 1U << 0, + Handheld = 1U << 1, + JoyDual = 1U << 2, + JoyLeft = 1U << 3, + JoyRight = 1U << 4, + Gc = 1U << 5, + Palma = 1U << 6, + Lark = 1U << 7, + HandheldLark = 1U << 8, + Lucia = 1U << 9, + Lagoon = 1U << 10, + Lager = 1U << 11, + SystemExt = 1U << 29, + System = 1U << 30, +}; +static_assert(sizeof(NpadStyleSet) == 4, "NpadStyleSet is an invalid size"); + +// This is nn::hid::VibrationDevicePosition +enum class VibrationDevicePosition : u32 { + None = 0, + Left = 1, + Right = 2, +}; + +// This is nn::hid::VibrationDeviceType +enum class VibrationDeviceType : u32 { + Unknown = 0, + LinearResonantActuator = 1, + GcErm = 2, +}; + +// This is nn::hid::VibrationGcErmCommand +enum class VibrationGcErmCommand : u64 { + Stop = 0, + Start = 1, + StopHard = 2, +}; + +// This is nn::hid::NpadStyleTag +struct NpadStyleTag { + union { + NpadStyleSet raw{}; + + BitField<0, 1, u32> fullkey; + BitField<1, 1, u32> handheld; + BitField<2, 1, u32> joycon_dual; + BitField<3, 1, u32> joycon_left; + BitField<4, 1, u32> joycon_right; + BitField<5, 1, u32> gamecube; + BitField<6, 1, u32> palma; + BitField<7, 1, u32> lark; + BitField<8, 1, u32> handheld_lark; + BitField<9, 1, u32> lucia; + BitField<10, 1, u32> lagoon; + BitField<11, 1, u32> lager; + BitField<29, 1, u32> system_ext; + BitField<30, 1, u32> system; + }; +}; +static_assert(sizeof(NpadStyleTag) == 4, "NpadStyleTag is an invalid size"); + +// This is nn::hid::TouchAttribute +struct TouchAttribute { + union { + u32 raw{}; + BitField<0, 1, u32> start_touch; + BitField<1, 1, u32> end_touch; + }; +}; +static_assert(sizeof(TouchAttribute) == 0x4, "TouchAttribute is an invalid size"); + +// This is nn::hid::TouchState +struct TouchState { + u64 delta_time; + TouchAttribute attribute; + u32 finger; + Common::Point position; + u32 diameter_x; + u32 diameter_y; + u32 rotation_angle; +}; +static_assert(sizeof(TouchState) == 0x28, "Touchstate is an invalid size"); + +// This is nn::hid::NpadControllerColor +struct NpadControllerColor { + u32 body; + u32 button; +}; +static_assert(sizeof(NpadControllerColor) == 8, "NpadControllerColor is an invalid size"); + +// This is nn::hid::AnalogStickState +struct AnalogStickState { + s32 x; + s32 y; +}; +static_assert(sizeof(AnalogStickState) == 8, "AnalogStickState is an invalid size"); + +// This is nn::hid::server::NpadGcTriggerState +struct NpadGcTriggerState { + s64 sampling_number{}; + s32 left{}; + s32 right{}; +}; +static_assert(sizeof(NpadGcTriggerState) == 0x10, "NpadGcTriggerState is an invalid size"); + +// This is nn::hid::system::NpadBatteryLevel +using NpadBatteryLevel = u32; +static_assert(sizeof(NpadBatteryLevel) == 0x4, "NpadBatteryLevel is an invalid size"); + +// This is nn::hid::system::NpadPowerInfo +struct NpadPowerInfo { + bool is_powered; + bool is_charging; + INSERT_PADDING_BYTES(0x6); + NpadBatteryLevel battery_level; +}; +static_assert(sizeof(NpadPowerInfo) == 0xC, "NpadPowerInfo is an invalid size"); + +struct LedPattern { + explicit LedPattern(u64 light1, u64 light2, u64 light3, u64 light4) { + position1.Assign(light1); + position2.Assign(light2); + position3.Assign(light3); + position4.Assign(light4); + } + union { + u64 raw{}; + BitField<0, 1, u64> position1; + BitField<1, 1, u64> position2; + BitField<2, 1, u64> position3; + BitField<3, 1, u64> position4; + }; +}; + +struct NpadButtonState { + union { + NpadButton raw{}; + + // Buttons + BitField<0, 1, u64> a; + BitField<1, 1, u64> b; + BitField<2, 1, u64> x; + BitField<3, 1, u64> y; + BitField<4, 1, u64> stick_l; + BitField<5, 1, u64> stick_r; + BitField<6, 1, u64> l; + BitField<7, 1, u64> r; + BitField<8, 1, u64> zl; + BitField<9, 1, u64> zr; + BitField<10, 1, u64> plus; + BitField<11, 1, u64> minus; + + // D-Pad + BitField<12, 1, u64> left; + BitField<13, 1, u64> up; + BitField<14, 1, u64> right; + BitField<15, 1, u64> down; + + // Left JoyStick + BitField<16, 1, u64> stick_l_left; + BitField<17, 1, u64> stick_l_up; + BitField<18, 1, u64> stick_l_right; + BitField<19, 1, u64> stick_l_down; + + // Right JoyStick + BitField<20, 1, u64> stick_r_left; + BitField<21, 1, u64> stick_r_up; + BitField<22, 1, u64> stick_r_right; + BitField<23, 1, u64> stick_r_down; + + BitField<24, 1, u64> left_sl; + BitField<25, 1, u64> left_sr; + + BitField<26, 1, u64> right_sl; + BitField<27, 1, u64> right_sr; + + BitField<28, 1, u64> palma; + BitField<29, 1, u64> verification; + BitField<30, 1, u64> handheld_left_b; + BitField<31, 1, u64> lagon_c_left; + BitField<32, 1, u64> lagon_c_up; + BitField<33, 1, u64> lagon_c_right; + BitField<34, 1, u64> lagon_c_down; + }; +}; +static_assert(sizeof(NpadButtonState) == 0x8, "NpadButtonState has incorrect size."); + +// This is nn::hid::DebugPadButton +struct DebugPadButton { + union { + u32 raw{}; + BitField<0, 1, u32> a; + BitField<1, 1, u32> b; + BitField<2, 1, u32> x; + BitField<3, 1, u32> y; + BitField<4, 1, u32> l; + BitField<5, 1, u32> r; + BitField<6, 1, u32> zl; + BitField<7, 1, u32> zr; + BitField<8, 1, u32> plus; + BitField<9, 1, u32> minus; + BitField<10, 1, u32> d_left; + BitField<11, 1, u32> d_up; + BitField<12, 1, u32> d_right; + BitField<13, 1, u32> d_down; + }; +}; +static_assert(sizeof(DebugPadButton) == 0x4, "DebugPadButton is an invalid size"); + +// This is nn::hid::ConsoleSixAxisSensorHandle +struct ConsoleSixAxisSensorHandle { + u8 unknown_1; + u8 unknown_2; + INSERT_PADDING_BYTES_NOINIT(2); +}; +static_assert(sizeof(ConsoleSixAxisSensorHandle) == 4, + "ConsoleSixAxisSensorHandle is an invalid size"); + +// This is nn::hid::SixAxisSensorHandle +struct SixAxisSensorHandle { + NpadStyleIndex npad_type; + u8 npad_id; + DeviceIndex device_index; + INSERT_PADDING_BYTES_NOINIT(1); +}; +static_assert(sizeof(SixAxisSensorHandle) == 4, "SixAxisSensorHandle is an invalid size"); + +struct SixAxisSensorFusionParameters { + f32 parameter1; + f32 parameter2; +}; +static_assert(sizeof(SixAxisSensorFusionParameters) == 8, + "SixAxisSensorFusionParameters is an invalid size"); + +// This is nn::hid::VibrationDeviceHandle +struct VibrationDeviceHandle { + NpadStyleIndex npad_type; + u8 npad_id; + DeviceIndex device_index; + INSERT_PADDING_BYTES_NOINIT(1); +}; +static_assert(sizeof(VibrationDeviceHandle) == 4, "SixAxisSensorHandle is an invalid size"); + +// This is nn::hid::VibrationValue +struct VibrationValue { + f32 low_amplitude; + f32 low_frequency; + f32 high_amplitude; + f32 high_frequency; +}; +static_assert(sizeof(VibrationValue) == 0x10, "VibrationValue has incorrect size."); + +// This is nn::hid::VibrationDeviceInfo +struct VibrationDeviceInfo { + VibrationDeviceType type{}; + VibrationDevicePosition position{}; +}; +static_assert(sizeof(VibrationDeviceInfo) == 0x8, "VibrationDeviceInfo has incorrect size."); + +// This is nn::hid::KeyboardModifier +struct KeyboardModifier { + union { + u32 raw{}; + BitField<0, 1, u32> control; + BitField<1, 1, u32> shift; + BitField<2, 1, u32> left_alt; + BitField<3, 1, u32> right_alt; + BitField<4, 1, u32> gui; + BitField<8, 1, u32> caps_lock; + BitField<9, 1, u32> scroll_lock; + BitField<10, 1, u32> num_lock; + BitField<11, 1, u32> katakana; + BitField<12, 1, u32> hiragana; + }; +}; + +static_assert(sizeof(KeyboardModifier) == 0x4, "KeyboardModifier is an invalid size"); + +// This is nn::hid::KeyboardAttribute +struct KeyboardAttribute { + union { + u32 raw{}; + BitField<0, 1, u32> is_connected; + }; +}; +static_assert(sizeof(KeyboardAttribute) == 0x4, "KeyboardAttribute is an invalid size"); + +// This is nn::hid::KeyboardKey +struct KeyboardKey { + // This should be a 256 bit flag + std::array key; +}; +static_assert(sizeof(KeyboardKey) == 0x20, "KeyboardKey is an invalid size"); + +// This is nn::hid::MouseButton +struct MouseButton { + union { + u32_le raw{}; + BitField<0, 1, u32> left; + BitField<1, 1, u32> right; + BitField<2, 1, u32> middle; + BitField<3, 1, u32> forward; + BitField<4, 1, u32> back; + }; +}; +static_assert(sizeof(MouseButton) == 0x4, "MouseButton is an invalid size"); + +// This is nn::hid::MouseAttribute +struct MouseAttribute { + union { + u32 raw{}; + BitField<0, 1, u32> transferable; + BitField<1, 1, u32> is_connected; + }; +}; +static_assert(sizeof(MouseAttribute) == 0x4, "MouseAttribute is an invalid size"); + +// This is nn::hid::detail::MouseState +struct MouseState { + s64 sampling_number; + s32 x; + s32 y; + s32 delta_x; + s32 delta_y; + // Axis Order in HW is switched for the wheel + s32 delta_wheel_y; + s32 delta_wheel_x; + MouseButton button; + MouseAttribute attribute; +}; +static_assert(sizeof(MouseState) == 0x28, "MouseState is an invalid size"); + +/// Converts a NpadIdType to an array index. +constexpr size_t NpadIdTypeToIndex(NpadIdType npad_id_type) { + switch (npad_id_type) { + case NpadIdType::Player1: + return 0; + case NpadIdType::Player2: + return 1; + case NpadIdType::Player3: + return 2; + case NpadIdType::Player4: + return 3; + case NpadIdType::Player5: + return 4; + case NpadIdType::Player6: + return 5; + case NpadIdType::Player7: + return 6; + case NpadIdType::Player8: + return 7; + case NpadIdType::Handheld: + return 8; + case NpadIdType::Other: + return 9; + default: + return 0; + } +} + +/// Converts an array index to a NpadIdType +constexpr NpadIdType IndexToNpadIdType(size_t index) { + switch (index) { + case 0: + return NpadIdType::Player1; + case 1: + return NpadIdType::Player2; + case 2: + return NpadIdType::Player3; + case 3: + return NpadIdType::Player4; + case 4: + return NpadIdType::Player5; + case 5: + return NpadIdType::Player6; + case 6: + return NpadIdType::Player7; + case 7: + return NpadIdType::Player8; + case 8: + return NpadIdType::Handheld; + case 9: + return NpadIdType::Other; + default: + return NpadIdType::Invalid; + } +} + +} // namespace Core::HID diff --git a/src/core/hid/input_converter.cpp b/src/core/hid/input_converter.cpp new file mode 100644 index 0000000000..f5acff6e0f --- /dev/null +++ b/src/core/hid/input_converter.cpp @@ -0,0 +1,383 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included + +#include + +#include "common/input.h" +#include "core/hid/input_converter.h" + +namespace Core::HID { + +Common::Input::BatteryStatus TransformToBattery(const Common::Input::CallbackStatus& callback) { + Common::Input::BatteryStatus battery{Common::Input::BatteryStatus::None}; + switch (callback.type) { + case Common::Input::InputType::Analog: + case Common::Input::InputType::Trigger: { + const auto value = TransformToTrigger(callback).analog.value; + battery = Common::Input::BatteryLevel::Empty; + if (value > 0.2f) { + battery = Common::Input::BatteryLevel::Critical; + } + if (value > 0.4f) { + battery = Common::Input::BatteryLevel::Low; + } + if (value > 0.6f) { + battery = Common::Input::BatteryLevel::Medium; + } + if (value > 0.8f) { + battery = Common::Input::BatteryLevel::Full; + } + if (value >= 1.0f) { + battery = Common::Input::BatteryLevel::Charging; + } + break; + } + case Common::Input::InputType::Button: + battery = callback.button_status.value ? Common::Input::BatteryLevel::Charging + : Common::Input::BatteryLevel::Critical; + break; + case Common::Input::InputType::Battery: + battery = callback.battery_status; + break; + default: + LOG_ERROR(Input, "Conversion from type {} to battery not implemented", callback.type); + break; + } + + return battery; +} + +Common::Input::ButtonStatus TransformToButton(const Common::Input::CallbackStatus& callback) { + Common::Input::ButtonStatus status{}; + switch (callback.type) { + case Common::Input::InputType::Analog: + case Common::Input::InputType::Trigger: + status.value = TransformToTrigger(callback).pressed.value; + break; + case Common::Input::InputType::Button: + status = callback.button_status; + break; + default: + LOG_ERROR(Input, "Conversion from type {} to button not implemented", callback.type); + break; + } + + if (status.inverted) { + status.value = !status.value; + } + + return status; +} + +Common::Input::MotionStatus TransformToMotion(const Common::Input::CallbackStatus& callback) { + Common::Input::MotionStatus status{}; + switch (callback.type) { + case Common::Input::InputType::Button: { + Common::Input::AnalogProperties properties{ + .deadzone = 0.0f, + .range = 1.0f, + .offset = 0.0f, + }; + status.delta_timestamp = 5000; + status.force_update = true; + status.accel.x = { + .value = 0.0f, + .raw_value = 0.0f, + .properties = properties, + }; + status.accel.y = { + .value = 0.0f, + .raw_value = 0.0f, + .properties = properties, + }; + status.accel.z = { + .value = 0.0f, + .raw_value = -1.0f, + .properties = properties, + }; + status.gyro.x = { + .value = 0.0f, + .raw_value = 0.0f, + .properties = properties, + }; + status.gyro.y = { + .value = 0.0f, + .raw_value = 0.0f, + .properties = properties, + }; + status.gyro.z = { + .value = 0.0f, + .raw_value = 0.0f, + .properties = properties, + }; + if (TransformToButton(callback).value) { + std::random_device device; + std::mt19937 gen(device()); + std::uniform_int_distribution distribution(-1000, 1000); + status.accel.x.raw_value = static_cast(distribution(gen)) * 0.001f; + status.accel.y.raw_value = static_cast(distribution(gen)) * 0.001f; + status.accel.z.raw_value = static_cast(distribution(gen)) * 0.001f; + status.gyro.x.raw_value = static_cast(distribution(gen)) * 0.001f; + status.gyro.y.raw_value = static_cast(distribution(gen)) * 0.001f; + status.gyro.z.raw_value = static_cast(distribution(gen)) * 0.001f; + } + break; + } + case Common::Input::InputType::Motion: + status = callback.motion_status; + break; + default: + LOG_ERROR(Input, "Conversion from type {} to motion not implemented", callback.type); + break; + } + SanitizeAnalog(status.accel.x, false); + SanitizeAnalog(status.accel.y, false); + SanitizeAnalog(status.accel.z, false); + SanitizeAnalog(status.gyro.x, false); + SanitizeAnalog(status.gyro.y, false); + SanitizeAnalog(status.gyro.z, false); + + return status; +} + +Common::Input::StickStatus TransformToStick(const Common::Input::CallbackStatus& callback) { + Common::Input::StickStatus status{}; + + switch (callback.type) { + case Common::Input::InputType::Stick: + status = callback.stick_status; + break; + default: + LOG_ERROR(Input, "Conversion from type {} to stick not implemented", callback.type); + break; + } + + SanitizeStick(status.x, status.y, true); + const auto& properties_x = status.x.properties; + const auto& properties_y = status.y.properties; + const float x = status.x.value; + const float y = status.y.value; + + // Set directional buttons + status.right = x > properties_x.threshold; + status.left = x < -properties_x.threshold; + status.up = y > properties_y.threshold; + status.down = y < -properties_y.threshold; + + return status; +} + +Common::Input::TouchStatus TransformToTouch(const Common::Input::CallbackStatus& callback) { + Common::Input::TouchStatus status{}; + + switch (callback.type) { + case Common::Input::InputType::Touch: + status = callback.touch_status; + break; + case Common::Input::InputType::Stick: + status.x = callback.stick_status.x; + status.y = callback.stick_status.y; + break; + default: + LOG_ERROR(Input, "Conversion from type {} to touch not implemented", callback.type); + break; + } + + SanitizeAnalog(status.x, true); + SanitizeAnalog(status.y, true); + float& x = status.x.value; + float& y = status.y.value; + + // Adjust if value is inverted + x = status.x.properties.inverted ? 1.0f + x : x; + y = status.y.properties.inverted ? 1.0f + y : y; + + // clamp value + x = std::clamp(x, 0.0f, 1.0f); + y = std::clamp(y, 0.0f, 1.0f); + + if (status.pressed.inverted) { + status.pressed.value = !status.pressed.value; + } + + return status; +} + +Common::Input::TriggerStatus TransformToTrigger(const Common::Input::CallbackStatus& callback) { + Common::Input::TriggerStatus status{}; + float& raw_value = status.analog.raw_value; + bool calculate_button_value = true; + + switch (callback.type) { + case Common::Input::InputType::Analog: + status.analog.properties = callback.analog_status.properties; + raw_value = callback.analog_status.raw_value; + break; + case Common::Input::InputType::Button: + status.analog.properties.range = 1.0f; + status.analog.properties.inverted = callback.button_status.inverted; + raw_value = callback.button_status.value ? 1.0f : 0.0f; + break; + case Common::Input::InputType::Trigger: + status = callback.trigger_status; + calculate_button_value = false; + break; + default: + LOG_ERROR(Input, "Conversion from type {} to trigger not implemented", callback.type); + break; + } + + SanitizeAnalog(status.analog, true); + const auto& properties = status.analog.properties; + float& value = status.analog.value; + + // Set button status + if (calculate_button_value) { + status.pressed.value = value > properties.threshold; + } + + // Adjust if value is inverted + value = properties.inverted ? 1.0f + value : value; + + // clamp value + value = std::clamp(value, 0.0f, 1.0f); + + return status; +} + +Common::Input::AnalogStatus TransformToAnalog(const Common::Input::CallbackStatus& callback) { + Common::Input::AnalogStatus status{}; + + switch (callback.type) { + case Common::Input::InputType::Analog: + status.properties = callback.analog_status.properties; + status.raw_value = callback.analog_status.raw_value; + break; + default: + LOG_ERROR(Input, "Conversion from type {} to analog not implemented", callback.type); + break; + } + + SanitizeAnalog(status, false); + + // Adjust if value is inverted + status.value = status.properties.inverted ? -status.value : status.value; + + return status; +} + +void SanitizeAnalog(Common::Input::AnalogStatus& analog, bool clamp_value) { + const auto& properties = analog.properties; + float& raw_value = analog.raw_value; + float& value = analog.value; + + if (!std::isnormal(raw_value)) { + raw_value = 0; + } + + // Apply center offset + raw_value -= properties.offset; + + // Set initial values to be formated + value = raw_value; + + // Calculate vector size + const float r = std::abs(value); + + // Return zero if value is smaller than the deadzone + if (r <= properties.deadzone || properties.deadzone == 1.0f) { + analog.value = 0; + return; + } + + // Adjust range of value + const float deadzone_factor = + 1.0f / r * (r - properties.deadzone) / (1.0f - properties.deadzone); + value = value * deadzone_factor / properties.range; + + // Invert direction if needed + if (properties.inverted) { + value = -value; + } + + // Clamp value + if (clamp_value) { + value = std::clamp(value, -1.0f, 1.0f); + } +} + +void SanitizeStick(Common::Input::AnalogStatus& analog_x, Common::Input::AnalogStatus& analog_y, + bool clamp_value) { + const auto& properties_x = analog_x.properties; + const auto& properties_y = analog_y.properties; + float& raw_x = analog_x.raw_value; + float& raw_y = analog_y.raw_value; + float& x = analog_x.value; + float& y = analog_y.value; + + if (!std::isnormal(raw_x)) { + raw_x = 0; + } + if (!std::isnormal(raw_y)) { + raw_y = 0; + } + + // Apply center offset + raw_x += properties_x.offset; + raw_y += properties_y.offset; + + // Apply X scale correction from offset + if (std::abs(properties_x.offset) < 0.5f) { + if (raw_x > 0) { + raw_x /= 1 + properties_x.offset; + } else { + raw_x /= 1 - properties_x.offset; + } + } + + // Apply Y scale correction from offset + if (std::abs(properties_y.offset) < 0.5f) { + if (raw_y > 0) { + raw_y /= 1 + properties_y.offset; + } else { + raw_y /= 1 - properties_y.offset; + } + } + + // Invert direction if needed + raw_x = properties_x.inverted ? -raw_x : raw_x; + raw_y = properties_y.inverted ? -raw_y : raw_y; + + // Set initial values to be formated + x = raw_x; + y = raw_y; + + // Calculate vector size + float r = x * x + y * y; + r = std::sqrt(r); + + // TODO(German77): Use deadzone and range of both axis + + // Return zero if values are smaller than the deadzone + if (r <= properties_x.deadzone || properties_x.deadzone >= 1.0f) { + x = 0; + y = 0; + return; + } + + // Adjust range of joystick + const float deadzone_factor = + 1.0f / r * (r - properties_x.deadzone) / (1.0f - properties_x.deadzone); + x = x * deadzone_factor / properties_x.range; + y = y * deadzone_factor / properties_x.range; + r = r * deadzone_factor / properties_x.range; + + // Normalize joystick + if (clamp_value && r > 1.0f) { + x /= r; + y /= r; + } +} + +} // namespace Core::HID diff --git a/src/core/hid/input_converter.h b/src/core/hid/input_converter.h new file mode 100644 index 0000000000..1492489d73 --- /dev/null +++ b/src/core/hid/input_converter.h @@ -0,0 +1,95 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included + +#pragma once + +namespace Common::Input { +struct CallbackStatus; +enum class BatteryLevel : u32; +using BatteryStatus = BatteryLevel; +struct AnalogStatus; +struct ButtonStatus; +struct MotionStatus; +struct StickStatus; +struct TouchStatus; +struct TriggerStatus; +}; // namespace Common::Input + +namespace Core::HID { + +/** + * Converts raw input data into a valid battery status. + * + * @param Supported callbacks: Analog, Battery, Trigger. + * @return A valid BatteryStatus object. + */ +Common::Input::BatteryStatus TransformToBattery(const Common::Input::CallbackStatus& callback); + +/** + * Converts raw input data into a valid button status. Applies invert properties to the output. + * + * @param Supported callbacks: Analog, Button, Trigger. + * @return A valid TouchStatus object. + */ +Common::Input::ButtonStatus TransformToButton(const Common::Input::CallbackStatus& callback); + +/** + * Converts raw input data into a valid motion status. + * + * @param Supported callbacks: Motion. + * @return A valid TouchStatus object. + */ +Common::Input::MotionStatus TransformToMotion(const Common::Input::CallbackStatus& callback); + +/** + * Converts raw input data into a valid stick status. Applies offset, deadzone, range and invert + * properties to the output. + * + * @param Supported callbacks: Stick. + * @return A valid StickStatus object. + */ +Common::Input::StickStatus TransformToStick(const Common::Input::CallbackStatus& callback); + +/** + * Converts raw input data into a valid touch status. + * + * @param Supported callbacks: Touch. + * @return A valid TouchStatus object. + */ +Common::Input::TouchStatus TransformToTouch(const Common::Input::CallbackStatus& callback); + +/** + * Converts raw input data into a valid trigger status. Applies offset, deadzone, range and + * invert properties to the output. Button status uses the threshold property if necessary. + * + * @param Supported callbacks: Analog, Button, Trigger. + * @return A valid TriggerStatus object. + */ +Common::Input::TriggerStatus TransformToTrigger(const Common::Input::CallbackStatus& callback); + +/** + * Converts raw input data into a valid analog status. Applies offset, deadzone, range and + * invert properties to the output. + * + * @param Supported callbacks: Analog. + * @return A valid AnalogStatus object. + */ +Common::Input::AnalogStatus TransformToAnalog(const Common::Input::CallbackStatus& callback); + +/** + * Converts raw analog data into a valid analog value + * @param An analog object containing raw data and properties, bool that determines if the value + * needs to be clamped between -1.0f and 1.0f. + */ +void SanitizeAnalog(Common::Input::AnalogStatus& analog, bool clamp_value); + +/** + * Converts raw stick data into a valid stick value + * @param Two analog objects containing raw data and properties, bool that determines if the value + * needs to be clamped into the unit circle. + */ +void SanitizeStick(Common::Input::AnalogStatus& analog_x, Common::Input::AnalogStatus& analog_y, + bool clamp_value); + +} // namespace Core::HID diff --git a/src/core/frontend/input_interpreter.cpp b/src/core/hid/input_interpreter.cpp similarity index 60% rename from src/core/frontend/input_interpreter.cpp rename to src/core/hid/input_interpreter.cpp index 9f6a90e8f1..870422d820 100644 --- a/src/core/frontend/input_interpreter.cpp +++ b/src/core/hid/input_interpreter.cpp @@ -3,7 +3,8 @@ // Refer to the license.txt file included. #include "core/core.h" -#include "core/frontend/input_interpreter.h" +#include "core/hid/hid_types.h" +#include "core/hid/input_interpreter.h" #include "core/hle/service/hid/controllers/npad.h" #include "core/hle/service/hid/hid.h" #include "core/hle/service/sm/sm.h" @@ -19,7 +20,7 @@ InputInterpreter::InputInterpreter(Core::System& system) InputInterpreter::~InputInterpreter() = default; void InputInterpreter::PollInput() { - const u32 button_state = npad.GetAndResetPressState(); + const u64 button_state = npad.GetAndResetPressState(); previous_index = current_index; current_index = (current_index + 1) % button_states.size(); @@ -31,32 +32,30 @@ void InputInterpreter::ResetButtonStates() { previous_index = 0; current_index = 0; - button_states[0] = 0xFFFFFFFF; + button_states[0] = 0xFFFFFFFFFFFFFFFF; for (std::size_t i = 1; i < button_states.size(); ++i) { button_states[i] = 0; } } -bool InputInterpreter::IsButtonPressed(HIDButton button) const { - return (button_states[current_index] & (1U << static_cast(button))) != 0; +bool InputInterpreter::IsButtonPressed(Core::HID::NpadButton button) const { + return (button_states[current_index] & static_cast(button)) != 0; } -bool InputInterpreter::IsButtonPressedOnce(HIDButton button) const { - const bool current_press = - (button_states[current_index] & (1U << static_cast(button))) != 0; - const bool previous_press = - (button_states[previous_index] & (1U << static_cast(button))) != 0; +bool InputInterpreter::IsButtonPressedOnce(Core::HID::NpadButton button) const { + const bool current_press = (button_states[current_index] & static_cast(button)) != 0; + const bool previous_press = (button_states[previous_index] & static_cast(button)) != 0; return current_press && !previous_press; } -bool InputInterpreter::IsButtonHeld(HIDButton button) const { - u32 held_buttons{button_states[0]}; +bool InputInterpreter::IsButtonHeld(Core::HID::NpadButton button) const { + u64 held_buttons{button_states[0]}; for (std::size_t i = 1; i < button_states.size(); ++i) { held_buttons &= button_states[i]; } - return (held_buttons & (1U << static_cast(button))) != 0; + return (held_buttons & static_cast(button)) != 0; } diff --git a/src/core/frontend/input_interpreter.h b/src/core/hid/input_interpreter.h similarity index 79% rename from src/core/frontend/input_interpreter.h rename to src/core/hid/input_interpreter.h index 9495e3daf0..1c2e02142d 100644 --- a/src/core/frontend/input_interpreter.h +++ b/src/core/hid/input_interpreter.h @@ -12,46 +12,14 @@ namespace Core { class System; } +namespace Core::HID { +enum class NpadButton : u64; +} + namespace Service::HID { class Controller_NPad; } -enum class HIDButton : u8 { - A, - B, - X, - Y, - LStick, - RStick, - L, - R, - ZL, - ZR, - Plus, - Minus, - - DLeft, - DUp, - DRight, - DDown, - - LStickLeft, - LStickUp, - LStickRight, - LStickDown, - - RStickLeft, - RStickUp, - RStickRight, - RStickDown, - - LeftSL, - LeftSR, - - RightSL, - RightSR, -}; - /** * The InputInterpreter class interfaces with HID to retrieve button press states. * Input is intended to be polled every 50ms so that a button is considered to be @@ -76,7 +44,7 @@ public: * * @returns True when the button is pressed. */ - [[nodiscard]] bool IsButtonPressed(HIDButton button) const; + [[nodiscard]] bool IsButtonPressed(Core::HID::NpadButton button) const; /** * Checks whether any of the buttons in the parameter list is pressed. @@ -85,7 +53,7 @@ public: * * @returns True when at least one of the buttons is pressed. */ - template + template [[nodiscard]] bool IsAnyButtonPressed() { return (IsButtonPressed(T) || ...); } @@ -98,7 +66,7 @@ public: * * @returns True when the button is pressed once. */ - [[nodiscard]] bool IsButtonPressedOnce(HIDButton button) const; + [[nodiscard]] bool IsButtonPressedOnce(Core::HID::NpadButton button) const; /** * Checks whether any of the buttons in the parameter list is pressed once. @@ -107,7 +75,7 @@ public: * * @returns True when at least one of the buttons is pressed once. */ - template + template [[nodiscard]] bool IsAnyButtonPressedOnce() const { return (IsButtonPressedOnce(T) || ...); } @@ -119,7 +87,7 @@ public: * * @returns True when the button is held down. */ - [[nodiscard]] bool IsButtonHeld(HIDButton button) const; + [[nodiscard]] bool IsButtonHeld(Core::HID::NpadButton button) const; /** * Checks whether any of the buttons in the parameter list is held down. @@ -128,7 +96,7 @@ public: * * @returns True when at least one of the buttons is held down. */ - template + template [[nodiscard]] bool IsAnyButtonHeld() const { return (IsButtonHeld(T) || ...); } @@ -137,7 +105,7 @@ private: Service::HID::Controller_NPad& npad; /// Stores 9 consecutive button states polled from HID. - std::array button_states{}; + std::array button_states{}; std::size_t previous_index{}; std::size_t current_index{}; diff --git a/src/input_common/motion_input.cpp b/src/core/hid/motion_input.cpp similarity index 82% rename from src/input_common/motion_input.cpp rename to src/core/hid/motion_input.cpp index 1c9d561c0b..c25fea966b 100644 --- a/src/input_common/motion_input.cpp +++ b/src/core/hid/motion_input.cpp @@ -2,13 +2,21 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included -#include #include "common/math_util.h" -#include "input_common/motion_input.h" +#include "core/hid/motion_input.h" -namespace InputCommon { +namespace Core::HID { -MotionInput::MotionInput(f32 new_kp, f32 new_ki, f32 new_kd) : kp(new_kp), ki(new_ki), kd(new_kd) {} +MotionInput::MotionInput() { + // Initialize PID constants with default values + SetPID(0.3f, 0.005f, 0.0f); +} + +void MotionInput::SetPID(f32 new_kp, f32 new_ki, f32 new_kd) { + kp = new_kp; + ki = new_ki; + kd = new_kd; +} void MotionInput::SetAcceleration(const Common::Vec3f& acceleration) { accel = acceleration; @@ -65,6 +73,8 @@ void MotionInput::UpdateRotation(u64 elapsed_time) { rotations += gyro * sample_period; } +// Based on Madgwick's implementation of Mayhony's AHRS algorithm. +// https://github.com/xioTechnologies/Open-Source-AHRS-With-x-IMU/blob/master/x-IMU%20IMU%20and%20AHRS%20Algorithms/x-IMU%20IMU%20and%20AHRS%20Algorithms/AHRS/MahonyAHRS.cs void MotionInput::UpdateOrientation(u64 elapsed_time) { if (!IsCalibrated(0.1f)) { ResetOrientation(); @@ -190,43 +200,6 @@ Common::Vec3f MotionInput::GetRotations() const { return rotations; } -Input::MotionStatus MotionInput::GetMotion() const { - const Common::Vec3f gyroscope = GetGyroscope(); - const Common::Vec3f accelerometer = GetAcceleration(); - const Common::Vec3f rotation = GetRotations(); - const std::array orientation = GetOrientation(); - const Common::Quaternion quaternion = GetQuaternion(); - return {accelerometer, gyroscope, rotation, orientation, quaternion}; -} - -Input::MotionStatus MotionInput::GetRandomMotion(int accel_magnitude, int gyro_magnitude) const { - std::random_device device; - std::mt19937 gen(device()); - std::uniform_int_distribution distribution(-1000, 1000); - const Common::Vec3f gyroscope{ - static_cast(distribution(gen)) * 0.001f, - static_cast(distribution(gen)) * 0.001f, - static_cast(distribution(gen)) * 0.001f, - }; - const Common::Vec3f accelerometer{ - static_cast(distribution(gen)) * 0.001f, - static_cast(distribution(gen)) * 0.001f, - static_cast(distribution(gen)) * 0.001f, - }; - constexpr Common::Vec3f rotation; - constexpr std::array orientation{ - Common::Vec3f{1.0f, 0.0f, 0.0f}, - Common::Vec3f{0.0f, 1.0f, 0.0f}, - Common::Vec3f{0.0f, 0.0f, 1.0f}, - }; - constexpr Common::Quaternion quaternion{ - {0.0f, 0.0f, 0.0f}, - 1.0f, - }; - return {accelerometer * accel_magnitude, gyroscope * gyro_magnitude, rotation, orientation, - quaternion}; -} - void MotionInput::ResetOrientation() { if (!reset_enabled || only_accelerometer) { return; @@ -304,4 +277,4 @@ void MotionInput::SetOrientationFromAccelerometer() { quat = quat.Normalized(); } } -} // namespace InputCommon +} // namespace Core::HID diff --git a/src/input_common/motion_input.h b/src/core/hid/motion_input.h similarity index 75% rename from src/input_common/motion_input.h rename to src/core/hid/motion_input.h index efe74cf19a..5b5b420bba 100644 --- a/src/input_common/motion_input.h +++ b/src/core/hid/motion_input.h @@ -7,13 +7,12 @@ #include "common/common_types.h" #include "common/quaternion.h" #include "common/vector_math.h" -#include "core/frontend/input.h" -namespace InputCommon { +namespace Core::HID { class MotionInput { public: - explicit MotionInput(f32 new_kp, f32 new_ki, f32 new_kd); + explicit MotionInput(); MotionInput(const MotionInput&) = default; MotionInput& operator=(const MotionInput&) = default; @@ -21,6 +20,7 @@ public: MotionInput(MotionInput&&) = default; MotionInput& operator=(MotionInput&&) = default; + void SetPID(f32 new_kp, f32 new_ki, f32 new_kd); void SetAcceleration(const Common::Vec3f& acceleration); void SetGyroscope(const Common::Vec3f& gyroscope); void SetQuaternion(const Common::Quaternion& quaternion); @@ -38,9 +38,6 @@ public: [[nodiscard]] Common::Vec3f GetGyroscope() const; [[nodiscard]] Common::Vec3f GetRotations() const; [[nodiscard]] Common::Quaternion GetQuaternion() const; - [[nodiscard]] Input::MotionStatus GetMotion() const; - [[nodiscard]] Input::MotionStatus GetRandomMotion(int accel_magnitude, - int gyro_magnitude) const; [[nodiscard]] bool IsMoving(f32 sensitivity) const; [[nodiscard]] bool IsCalibrated(f32 sensitivity) const; @@ -59,16 +56,32 @@ private: Common::Vec3f integral_error; Common::Vec3f derivative_error; + // Quaternion containing the device orientation Common::Quaternion quat{{0.0f, 0.0f, -1.0f}, 0.0f}; + + // Number of full rotations in each axis Common::Vec3f rotations; + + // Acceleration vector measurement in G force Common::Vec3f accel; + + // Gyroscope vector measurement in radians/s. Common::Vec3f gyro; + + // Vector to be substracted from gyro measurements Common::Vec3f gyro_drift; + // Minimum gyro amplitude to detect if the device is moving f32 gyro_threshold = 0.0f; + + // Number of invalid sequential data u32 reset_counter = 0; + + // If the provided data is invalid the device will be autocalibrated bool reset_enabled = true; + + // Use accelerometer values to calculate position bool only_accelerometer = true; }; -} // namespace InputCommon +} // namespace Core::HID diff --git a/src/core/hle/service/am/applets/applet_controller.cpp b/src/core/hle/service/am/applets/applet_controller.cpp index 2721679c10..d073f22108 100644 --- a/src/core/hle/service/am/applets/applet_controller.cpp +++ b/src/core/hle/service/am/applets/applet_controller.cpp @@ -10,6 +10,9 @@ #include "common/string_util.h" #include "core/core.h" #include "core/frontend/applets/controller.h" +#include "core/hid/emulated_controller.h" +#include "core/hid/hid_core.h" +#include "core/hid/hid_types.h" #include "core/hle/result.h" #include "core/hle/service/am/am.h" #include "core/hle/service/am/applets/applet_controller.h" @@ -25,7 +28,7 @@ namespace Service::AM::Applets { static Core::Frontend::ControllerParameters ConvertToFrontendParameters( ControllerSupportArgPrivate private_arg, ControllerSupportArgHeader header, bool enable_text, std::vector identification_colors, std::vector text) { - HID::Controller_NPad::NpadStyleSet npad_style_set; + Core::HID::NpadStyleTag npad_style_set; npad_style_set.raw = private_arg.style_set; return { @@ -243,19 +246,11 @@ void Controller::Execute() { void Controller::ConfigurationComplete() { ControllerSupportResultInfo result_info{}; - const auto& players = Settings::values.players.GetValue(); - // If enable_single_mode is enabled, player_count is 1 regardless of any other parameters. // Otherwise, only count connected players from P1-P8. - result_info.player_count = - is_single_mode - ? 1 - : static_cast(std::count_if(players.begin(), players.end() - 2, - [](const auto& player) { return player.connected; })); + result_info.player_count = is_single_mode ? 1 : system.HIDCore().GetPlayerCount(); - result_info.selected_id = HID::Controller_NPad::IndexToNPad(std::distance( - players.begin(), std::find_if(players.begin(), players.end(), - [](const auto& player) { return player.connected; }))); + result_info.selected_id = static_cast(system.HIDCore().GetFirstNpadId()); result_info.result = 0; diff --git a/src/core/hle/service/am/applets/applet_controller.h b/src/core/hle/service/am/applets/applet_controller.h index 0a34c4fc0c..1a832505eb 100644 --- a/src/core/hle/service/am/applets/applet_controller.h +++ b/src/core/hle/service/am/applets/applet_controller.h @@ -16,6 +16,10 @@ namespace Core { class System; } +namespace Core::HID { +enum class NpadStyleSet : u32; +} + namespace Service::AM::Applets { using IdentificationColor = std::array; @@ -52,7 +56,7 @@ struct ControllerSupportArgPrivate { bool flag_1{}; ControllerSupportMode mode{}; ControllerSupportCaller caller{}; - u32 style_set{}; + Core::HID::NpadStyleSet style_set{}; u32 joy_hold_type{}; }; static_assert(sizeof(ControllerSupportArgPrivate) == 0x14, diff --git a/src/core/hle/service/am/applets/applets.cpp b/src/core/hle/service/am/applets/applets.cpp index 7320b1c0fe..134ac1ee2c 100644 --- a/src/core/hle/service/am/applets/applets.cpp +++ b/src/core/hle/service/am/applets/applets.cpp @@ -231,7 +231,7 @@ void AppletManager::SetDefaultAppletFrontendSet() { void AppletManager::SetDefaultAppletsIfMissing() { if (frontend.controller == nullptr) { frontend.controller = - std::make_unique(system.ServiceManager()); + std::make_unique(system.HIDCore()); } if (frontend.error == nullptr) { diff --git a/src/core/hle/service/hid/controllers/console_sixaxis.cpp b/src/core/hle/service/hid/controllers/console_sixaxis.cpp index bda6e2557a..f0f3105dc5 100644 --- a/src/core/hle/service/hid/controllers/console_sixaxis.cpp +++ b/src/core/hle/service/hid/controllers/console_sixaxis.cpp @@ -4,13 +4,18 @@ #include "common/settings.h" #include "core/core_timing.h" +#include "core/hid/emulated_console.h" +#include "core/hid/hid_core.h" #include "core/hle/service/hid/controllers/console_sixaxis.h" namespace Service::HID { constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3C200; -Controller_ConsoleSixAxis::Controller_ConsoleSixAxis(Core::System& system_) - : ControllerBase{system_} {} +Controller_ConsoleSixAxis::Controller_ConsoleSixAxis(Core::HID::HIDCore& hid_core_) + : ControllerBase{hid_core_} { + console = hid_core.GetEmulatedConsole(); +} + Controller_ConsoleSixAxis::~Controller_ConsoleSixAxis() = default; void Controller_ConsoleSixAxis::OnInit() {} @@ -19,44 +24,31 @@ void Controller_ConsoleSixAxis::OnRelease() {} void Controller_ConsoleSixAxis::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) { - seven_six_axis.header.timestamp = core_timing.GetCPUTicks(); - seven_six_axis.header.total_entry_count = 17; - if (!IsControllerActivated() || !is_transfer_memory_set) { - seven_six_axis.header.entry_count = 0; - seven_six_axis.header.last_entry_index = 0; + seven_sixaxis_lifo.buffer_count = 0; + seven_sixaxis_lifo.buffer_tail = 0; return; } - seven_six_axis.header.entry_count = 16; - const auto& last_entry = - seven_six_axis.sevensixaxis_states[seven_six_axis.header.last_entry_index]; - seven_six_axis.header.last_entry_index = (seven_six_axis.header.last_entry_index + 1) % 17; - auto& cur_entry = seven_six_axis.sevensixaxis_states[seven_six_axis.header.last_entry_index]; - - cur_entry.sampling_number = last_entry.sampling_number + 1; - cur_entry.sampling_number2 = cur_entry.sampling_number; + const auto& last_entry = seven_sixaxis_lifo.ReadCurrentEntry().state; + next_seven_sixaxis_state.sampling_number = last_entry.sampling_number + 1; // Try to read sixaxis sensor states - MotionDevice motion_device{}; - const auto& device = motions[0]; - if (device) { - std::tie(motion_device.accel, motion_device.gyro, motion_device.rotation, - motion_device.orientation, motion_device.quaternion) = device->GetStatus(); - console_six_axis.is_seven_six_axis_sensor_at_rest = motion_device.gyro.Length2() < 0.0001f; - } + const auto motion_status = console->GetMotion(); - cur_entry.accel = motion_device.accel; + console_six_axis.is_seven_six_axis_sensor_at_rest = motion_status.is_at_rest; + + next_seven_sixaxis_state.accel = motion_status.accel; // Zero gyro values as they just mess up with the camera // Note: Probably a correct sensivity setting must be set - cur_entry.gyro = {}; - cur_entry.quaternion = { + next_seven_sixaxis_state.gyro = {}; + next_seven_sixaxis_state.quaternion = { { - motion_device.quaternion.xyz.y, - motion_device.quaternion.xyz.x, - -motion_device.quaternion.w, + motion_status.quaternion.xyz.y, + motion_status.quaternion.xyz.x, + -motion_status.quaternion.w, }, - -motion_device.quaternion.xyz.z, + -motion_status.quaternion.xyz.z, }; console_six_axis.sampling_number++; @@ -67,14 +59,8 @@ void Controller_ConsoleSixAxis::OnUpdate(const Core::Timing::CoreTiming& core_ti // Update console six axis shared memory std::memcpy(data + SHARED_MEMORY_OFFSET, &console_six_axis, sizeof(console_six_axis)); // Update seven six axis transfer memory - std::memcpy(transfer_memory, &seven_six_axis, sizeof(seven_six_axis)); -} - -void Controller_ConsoleSixAxis::OnLoadInputDevices() { - const auto player = Settings::values.players.GetValue()[0]; - std::transform(player.motions.begin() + Settings::NativeMotion::MOTION_HID_BEGIN, - player.motions.begin() + Settings::NativeMotion::MOTION_HID_END, motions.begin(), - Input::CreateDevice); + seven_sixaxis_lifo.WriteNextEntry(next_seven_sixaxis_state); + std::memcpy(transfer_memory, &seven_sixaxis_lifo, sizeof(seven_sixaxis_lifo)); } void Controller_ConsoleSixAxis::SetTransferMemoryPointer(u8* t_mem) { @@ -83,8 +69,7 @@ void Controller_ConsoleSixAxis::SetTransferMemoryPointer(u8* t_mem) { } void Controller_ConsoleSixAxis::ResetTimestamp() { - auto& cur_entry = seven_six_axis.sevensixaxis_states[seven_six_axis.header.last_entry_index]; - cur_entry.sampling_number = 0; - cur_entry.sampling_number2 = 0; + seven_sixaxis_lifo.buffer_count = 0; + seven_sixaxis_lifo.buffer_tail = 0; } } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/console_sixaxis.h b/src/core/hle/service/hid/controllers/console_sixaxis.h index fd8a427afa..2792418588 100644 --- a/src/core/hle/service/hid/controllers/console_sixaxis.h +++ b/src/core/hle/service/hid/controllers/console_sixaxis.h @@ -5,16 +5,21 @@ #pragma once #include -#include "common/bit_field.h" + #include "common/common_types.h" #include "common/quaternion.h" -#include "core/frontend/input.h" +#include "core/hid/hid_types.h" #include "core/hle/service/hid/controllers/controller_base.h" +#include "core/hle/service/hid/ring_lifo.h" + +namespace Core::HID { +class EmulatedConsole; +} // namespace Core::HID namespace Service::HID { class Controller_ConsoleSixAxis final : public ControllerBase { public: - explicit Controller_ConsoleSixAxis(Core::System& system_); + explicit Controller_ConsoleSixAxis(Core::HID::HIDCore& hid_core_); ~Controller_ConsoleSixAxis() override; // Called when the controller is initialized @@ -26,9 +31,6 @@ public: // When the controller is requesting an update for the shared memory void OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, size_t size) override; - // Called when input devices should be loaded - void OnLoadInputDevices() override; - // Called on InitializeSevenSixAxisSensor void SetTransferMemoryPointer(u8* t_mem); @@ -38,43 +40,31 @@ public: private: struct SevenSixAxisState { INSERT_PADDING_WORDS(4); // unused - s64_le sampling_number{}; - s64_le sampling_number2{}; + s64 sampling_number{}; u64 unknown{}; Common::Vec3f accel{}; Common::Vec3f gyro{}; Common::Quaternion quaternion{}; }; - static_assert(sizeof(SevenSixAxisState) == 0x50, "SevenSixAxisState is an invalid size"); - - struct SevenSixAxisMemory { - CommonHeader header{}; - std::array sevensixaxis_states{}; - }; - static_assert(sizeof(SevenSixAxisMemory) == 0xA70, "SevenSixAxisMemory is an invalid size"); + static_assert(sizeof(SevenSixAxisState) == 0x48, "SevenSixAxisState is an invalid size"); + // This is nn::hid::detail::ConsoleSixAxisSensorSharedMemoryFormat struct ConsoleSharedMemory { - u64_le sampling_number{}; + u64 sampling_number{}; bool is_seven_six_axis_sensor_at_rest{}; + INSERT_PADDING_BYTES(4); // padding f32 verticalization_error{}; Common::Vec3f gyro_bias{}; }; static_assert(sizeof(ConsoleSharedMemory) == 0x20, "ConsoleSharedMemory is an invalid size"); - struct MotionDevice { - Common::Vec3f accel; - Common::Vec3f gyro; - Common::Vec3f rotation; - std::array orientation; - Common::Quaternion quaternion; - }; + Lifo seven_sixaxis_lifo{}; + static_assert(sizeof(seven_sixaxis_lifo) == 0xA70, "SevenSixAxisState is an invalid size"); - using MotionArray = - std::array, Settings::NativeMotion::NUM_MOTIONS_HID>; - MotionArray motions; + Core::HID::EmulatedConsole* console; u8* transfer_memory = nullptr; bool is_transfer_memory_set = false; ConsoleSharedMemory console_six_axis{}; - SevenSixAxisMemory seven_six_axis{}; + SevenSixAxisState next_seven_sixaxis_state{}; }; } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/controller_base.cpp b/src/core/hle/service/hid/controllers/controller_base.cpp index 9d1e6db6a2..788ae9ae7a 100644 --- a/src/core/hle/service/hid/controllers/controller_base.cpp +++ b/src/core/hle/service/hid/controllers/controller_base.cpp @@ -6,12 +6,12 @@ namespace Service::HID { -ControllerBase::ControllerBase(Core::System& system_) : system(system_) {} +ControllerBase::ControllerBase(Core::HID::HIDCore& hid_core_) : hid_core(hid_core_) {} ControllerBase::~ControllerBase() = default; void ControllerBase::ActivateController() { if (is_activated) { - OnRelease(); + return; } is_activated = true; OnInit(); diff --git a/src/core/hle/service/hid/controllers/controller_base.h b/src/core/hle/service/hid/controllers/controller_base.h index 1556fb08e4..7450eb20a7 100644 --- a/src/core/hle/service/hid/controllers/controller_base.h +++ b/src/core/hle/service/hid/controllers/controller_base.h @@ -11,14 +11,14 @@ namespace Core::Timing { class CoreTiming; } -namespace Core { -class System; +namespace Core::HID { +class HIDCore; } namespace Service::HID { class ControllerBase { public: - explicit ControllerBase(Core::System& system_); + explicit ControllerBase(Core::HID::HIDCore& hid_core_); virtual ~ControllerBase(); // Called when the controller is initialized @@ -35,26 +35,17 @@ public: virtual void OnMotionUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) {} - // Called when input devices should be loaded - virtual void OnLoadInputDevices() = 0; - void ActivateController(); void DeactivateController(); bool IsControllerActivated() const; + static const std::size_t hid_entry_count = 17; + protected: bool is_activated{false}; - struct CommonHeader { - s64_le timestamp; - s64_le total_entry_count; - s64_le last_entry_index; - s64_le entry_count; - }; - static_assert(sizeof(CommonHeader) == 0x20, "CommonHeader is an invalid size"); - - Core::System& system; + Core::HID::HIDCore& hid_core; }; } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/debug_pad.cpp b/src/core/hle/service/hid/controllers/debug_pad.cpp index d439b8fb0e..6a6fb9cab3 100644 --- a/src/core/hle/service/hid/controllers/debug_pad.cpp +++ b/src/core/hle/service/hid/controllers/debug_pad.cpp @@ -6,15 +6,19 @@ #include "common/common_types.h" #include "common/settings.h" #include "core/core_timing.h" +#include "core/hid/emulated_controller.h" +#include "core/hid/hid_core.h" +#include "core/hid/hid_types.h" #include "core/hle/service/hid/controllers/debug_pad.h" namespace Service::HID { +constexpr std::size_t SHARED_MEMORY_OFFSET = 0x00000; -constexpr s32 HID_JOYSTICK_MAX = 0x7fff; -[[maybe_unused]] constexpr s32 HID_JOYSTICK_MIN = -0x7fff; -enum class JoystickId : std::size_t { Joystick_Left, Joystick_Right }; +Controller_DebugPad::Controller_DebugPad(Core::HID::HIDCore& hid_core_) + : ControllerBase{hid_core_} { + controller = hid_core.GetEmulatedController(Core::HID::NpadIdType::Other); +} -Controller_DebugPad::Controller_DebugPad(Core::System& system_) : ControllerBase{system_} {} Controller_DebugPad::~Controller_DebugPad() = default; void Controller_DebugPad::OnInit() {} @@ -23,63 +27,29 @@ void Controller_DebugPad::OnRelease() {} void Controller_DebugPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) { - shared_memory.header.timestamp = core_timing.GetCPUTicks(); - shared_memory.header.total_entry_count = 17; - if (!IsControllerActivated()) { - shared_memory.header.entry_count = 0; - shared_memory.header.last_entry_index = 0; + debug_pad_lifo.buffer_count = 0; + debug_pad_lifo.buffer_tail = 0; + std::memcpy(data + SHARED_MEMORY_OFFSET, &debug_pad_lifo, sizeof(debug_pad_lifo)); return; } - shared_memory.header.entry_count = 16; - const auto& last_entry = shared_memory.pad_states[shared_memory.header.last_entry_index]; - shared_memory.header.last_entry_index = (shared_memory.header.last_entry_index + 1) % 17; - auto& cur_entry = shared_memory.pad_states[shared_memory.header.last_entry_index]; - - cur_entry.sampling_number = last_entry.sampling_number + 1; - cur_entry.sampling_number2 = cur_entry.sampling_number; + const auto& last_entry = debug_pad_lifo.ReadCurrentEntry().state; + next_state.sampling_number = last_entry.sampling_number + 1; if (Settings::values.debug_pad_enabled) { - cur_entry.attribute.connected.Assign(1); - auto& pad = cur_entry.pad_state; + next_state.attribute.connected.Assign(1); - using namespace Settings::NativeButton; - pad.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus()); - pad.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus()); - pad.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus()); - pad.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus()); - pad.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus()); - pad.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus()); - pad.zl.Assign(buttons[ZL - BUTTON_HID_BEGIN]->GetStatus()); - pad.zr.Assign(buttons[ZR - BUTTON_HID_BEGIN]->GetStatus()); - pad.plus.Assign(buttons[Plus - BUTTON_HID_BEGIN]->GetStatus()); - pad.minus.Assign(buttons[Minus - BUTTON_HID_BEGIN]->GetStatus()); - pad.d_left.Assign(buttons[DLeft - BUTTON_HID_BEGIN]->GetStatus()); - pad.d_up.Assign(buttons[DUp - BUTTON_HID_BEGIN]->GetStatus()); - pad.d_right.Assign(buttons[DRight - BUTTON_HID_BEGIN]->GetStatus()); - pad.d_down.Assign(buttons[DDown - BUTTON_HID_BEGIN]->GetStatus()); + const auto& button_state = controller->GetDebugPadButtons(); + const auto& stick_state = controller->GetSticks(); - const auto [stick_l_x_f, stick_l_y_f] = - analogs[static_cast(JoystickId::Joystick_Left)]->GetStatus(); - const auto [stick_r_x_f, stick_r_y_f] = - analogs[static_cast(JoystickId::Joystick_Right)]->GetStatus(); - cur_entry.l_stick.x = static_cast(stick_l_x_f * HID_JOYSTICK_MAX); - cur_entry.l_stick.y = static_cast(stick_l_y_f * HID_JOYSTICK_MAX); - cur_entry.r_stick.x = static_cast(stick_r_x_f * HID_JOYSTICK_MAX); - cur_entry.r_stick.y = static_cast(stick_r_y_f * HID_JOYSTICK_MAX); + next_state.pad_state = button_state; + next_state.l_stick = stick_state.left; + next_state.r_stick = stick_state.right; } - std::memcpy(data, &shared_memory, sizeof(SharedMemory)); + debug_pad_lifo.WriteNextEntry(next_state); + std::memcpy(data + SHARED_MEMORY_OFFSET, &debug_pad_lifo, sizeof(debug_pad_lifo)); } -void Controller_DebugPad::OnLoadInputDevices() { - std::transform(Settings::values.debug_pad_buttons.begin(), - Settings::values.debug_pad_buttons.begin() + - Settings::NativeButton::NUM_BUTTONS_HID, - buttons.begin(), Input::CreateDevice); - std::transform(Settings::values.debug_pad_analogs.begin(), - Settings::values.debug_pad_analogs.end(), analogs.begin(), - Input::CreateDevice); -} } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/debug_pad.h b/src/core/hle/service/hid/controllers/debug_pad.h index 1b1645184d..afe374fc26 100644 --- a/src/core/hle/service/hid/controllers/debug_pad.h +++ b/src/core/hle/service/hid/controllers/debug_pad.h @@ -8,15 +8,20 @@ #include "common/bit_field.h" #include "common/common_funcs.h" #include "common/common_types.h" -#include "common/settings.h" #include "common/swap.h" -#include "core/frontend/input.h" #include "core/hle/service/hid/controllers/controller_base.h" +#include "core/hle/service/hid/ring_lifo.h" + +namespace Core::HID { +class EmulatedController; +struct DebugPadButton; +struct AnalogStickState; +} // namespace Core::HID namespace Service::HID { class Controller_DebugPad final : public ControllerBase { public: - explicit Controller_DebugPad(Core::System& system_); + explicit Controller_DebugPad(Core::HID::HIDCore& hid_core_); ~Controller_DebugPad() override; // Called when the controller is initialized @@ -28,66 +33,31 @@ public: // When the controller is requesting an update for the shared memory void OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) override; - // Called when input devices should be loaded - void OnLoadInputDevices() override; - private: - struct AnalogStick { - s32_le x; - s32_le y; - }; - static_assert(sizeof(AnalogStick) == 0x8); - - struct PadState { + // This is nn::hid::DebugPadAttribute + struct DebugPadAttribute { union { - u32_le raw{}; - BitField<0, 1, u32> a; - BitField<1, 1, u32> b; - BitField<2, 1, u32> x; - BitField<3, 1, u32> y; - BitField<4, 1, u32> l; - BitField<5, 1, u32> r; - BitField<6, 1, u32> zl; - BitField<7, 1, u32> zr; - BitField<8, 1, u32> plus; - BitField<9, 1, u32> minus; - BitField<10, 1, u32> d_left; - BitField<11, 1, u32> d_up; - BitField<12, 1, u32> d_right; - BitField<13, 1, u32> d_down; - }; - }; - static_assert(sizeof(PadState) == 0x4, "PadState is an invalid size"); - - struct Attributes { - union { - u32_le raw{}; + u32 raw{}; BitField<0, 1, u32> connected; }; }; - static_assert(sizeof(Attributes) == 0x4, "Attributes is an invalid size"); + static_assert(sizeof(DebugPadAttribute) == 0x4, "DebugPadAttribute is an invalid size"); - struct PadStates { - s64_le sampling_number; - s64_le sampling_number2; - Attributes attribute; - PadState pad_state; - AnalogStick r_stick; - AnalogStick l_stick; + // This is nn::hid::DebugPadState + struct DebugPadState { + s64 sampling_number; + DebugPadAttribute attribute; + Core::HID::DebugPadButton pad_state; + Core::HID::AnalogStickState r_stick; + Core::HID::AnalogStickState l_stick; }; - static_assert(sizeof(PadStates) == 0x28, "PadStates is an invalid state"); + static_assert(sizeof(DebugPadState) == 0x20, "DebugPadState is an invalid state"); - struct SharedMemory { - CommonHeader header; - std::array pad_states; - INSERT_PADDING_BYTES(0x138); - }; - static_assert(sizeof(SharedMemory) == 0x400, "SharedMemory is an invalid size"); - SharedMemory shared_memory{}; + // This is nn::hid::detail::DebugPadLifo + Lifo debug_pad_lifo{}; + static_assert(sizeof(debug_pad_lifo) == 0x2C8, "debug_pad_lifo is an invalid size"); + DebugPadState next_state{}; - std::array, Settings::NativeButton::NUM_BUTTONS_HID> - buttons; - std::array, Settings::NativeAnalog::NUM_STICKS_HID> - analogs; + Core::HID::EmulatedController* controller; }; } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/gesture.cpp b/src/core/hle/service/hid/controllers/gesture.cpp index 764abb5b62..fe895c4f67 100644 --- a/src/core/hle/service/hid/controllers/gesture.cpp +++ b/src/core/hle/service/hid/controllers/gesture.cpp @@ -7,6 +7,7 @@ #include "common/settings.h" #include "core/core_timing.h" #include "core/frontend/emu_window.h" +#include "core/hid/hid_core.h" #include "core/hle/service/hid/controllers/gesture.h" namespace Service::HID { @@ -23,16 +24,14 @@ constexpr f32 Square(s32 num) { return static_cast(num * num); } -Controller_Gesture::Controller_Gesture(Core::System& system_) : ControllerBase(system_) {} +Controller_Gesture::Controller_Gesture(Core::HID::HIDCore& hid_core_) : ControllerBase(hid_core_) { + console = hid_core.GetEmulatedConsole(); +} Controller_Gesture::~Controller_Gesture() = default; void Controller_Gesture::OnInit() { - for (std::size_t id = 0; id < MAX_FINGERS; ++id) { - mouse_finger_id[id] = MAX_POINTS; - keyboard_finger_id[id] = MAX_POINTS; - udp_finger_id[id] = MAX_POINTS; - } - shared_memory.header.entry_count = 0; + gesture_lifo.buffer_count = 0; + gesture_lifo.buffer_tail = 0; force_update = true; } @@ -40,50 +39,38 @@ void Controller_Gesture::OnRelease() {} void Controller_Gesture::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) { - shared_memory.header.timestamp = core_timing.GetCPUTicks(); - shared_memory.header.total_entry_count = 17; - if (!IsControllerActivated()) { - shared_memory.header.entry_count = 0; - shared_memory.header.last_entry_index = 0; + gesture_lifo.buffer_count = 0; + gesture_lifo.buffer_tail = 0; + std::memcpy(data + SHARED_MEMORY_OFFSET, &gesture_lifo, sizeof(gesture_lifo)); return; } ReadTouchInput(); GestureProperties gesture = GetGestureProperties(); - f32 time_difference = static_cast(shared_memory.header.timestamp - last_update_timestamp) / - (1000 * 1000 * 1000); + f32 time_difference = + static_cast(gesture_lifo.timestamp - last_update_timestamp) / (1000 * 1000 * 1000); // Only update if necesary if (!ShouldUpdateGesture(gesture, time_difference)) { return; } - last_update_timestamp = shared_memory.header.timestamp; + last_update_timestamp = gesture_lifo.timestamp; UpdateGestureSharedMemory(data, size, gesture, time_difference); } void Controller_Gesture::ReadTouchInput() { - const Input::TouchStatus& mouse_status = touch_mouse_device->GetStatus(); - const Input::TouchStatus& udp_status = touch_udp_device->GetStatus(); - for (std::size_t id = 0; id < mouse_status.size(); ++id) { - mouse_finger_id[id] = UpdateTouchInputEvent(mouse_status[id], mouse_finger_id[id]); - udp_finger_id[id] = UpdateTouchInputEvent(udp_status[id], udp_finger_id[id]); - } - - if (Settings::values.use_touch_from_button) { - const Input::TouchStatus& keyboard_status = touch_btn_device->GetStatus(); - for (std::size_t id = 0; id < mouse_status.size(); ++id) { - keyboard_finger_id[id] = - UpdateTouchInputEvent(keyboard_status[id], keyboard_finger_id[id]); - } + const auto touch_status = console->GetTouch(); + for (std::size_t id = 0; id < fingers.size(); ++id) { + fingers[id] = touch_status[id]; } } bool Controller_Gesture::ShouldUpdateGesture(const GestureProperties& gesture, f32 time_difference) { - const auto& last_entry = shared_memory.gesture_states[shared_memory.header.last_entry_index]; + const auto& last_entry = GetLastGestureEntry(); if (force_update) { force_update = false; return true; @@ -97,7 +84,7 @@ bool Controller_Gesture::ShouldUpdateGesture(const GestureProperties& gesture, } // Update on press and hold event after 0.5 seconds - if (last_entry.type == TouchType::Touch && last_entry.point_count == 1 && + if (last_entry.type == GestureType::Touch && last_entry.point_count == 1 && time_difference > press_delay) { return enable_press_and_tap; } @@ -108,27 +95,19 @@ bool Controller_Gesture::ShouldUpdateGesture(const GestureProperties& gesture, void Controller_Gesture::UpdateGestureSharedMemory(u8* data, std::size_t size, GestureProperties& gesture, f32 time_difference) { - TouchType type = TouchType::Idle; - Attribute attributes{}; + GestureType type = GestureType::Idle; + GestureAttribute attributes{}; - const auto& last_entry = shared_memory.gesture_states[shared_memory.header.last_entry_index]; - shared_memory.header.last_entry_index = (shared_memory.header.last_entry_index + 1) % 17; - auto& cur_entry = shared_memory.gesture_states[shared_memory.header.last_entry_index]; + const auto& last_entry = gesture_lifo.ReadCurrentEntry().state; - if (shared_memory.header.entry_count < 16) { - shared_memory.header.entry_count++; - } - - cur_entry.sampling_number = last_entry.sampling_number + 1; - cur_entry.sampling_number2 = cur_entry.sampling_number; - - // Reset values to default - cur_entry.delta = {}; - cur_entry.vel_x = 0; - cur_entry.vel_y = 0; - cur_entry.direction = Direction::None; - cur_entry.rotation_angle = 0; - cur_entry.scale = 0; + // Reset next state to default + next_state.sampling_number = last_entry.sampling_number + 1; + next_state.delta = {}; + next_state.vel_x = 0; + next_state.vel_y = 0; + next_state.direction = GestureDirection::None; + next_state.rotation_angle = 0; + next_state.scale = 0; if (gesture.active_points > 0) { if (last_gesture.active_points == 0) { @@ -141,46 +120,47 @@ void Controller_Gesture::UpdateGestureSharedMemory(u8* data, std::size_t size, } // Apply attributes - cur_entry.detection_count = gesture.detection_count; - cur_entry.type = type; - cur_entry.attributes = attributes; - cur_entry.pos = gesture.mid_point; - cur_entry.point_count = static_cast(gesture.active_points); - cur_entry.points = gesture.points; + next_state.detection_count = gesture.detection_count; + next_state.type = type; + next_state.attributes = attributes; + next_state.pos = gesture.mid_point; + next_state.point_count = static_cast(gesture.active_points); + next_state.points = gesture.points; last_gesture = gesture; - std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(SharedMemory)); + gesture_lifo.WriteNextEntry(next_state); + std::memcpy(data + SHARED_MEMORY_OFFSET, &gesture_lifo, sizeof(gesture_lifo)); } -void Controller_Gesture::NewGesture(GestureProperties& gesture, TouchType& type, - Attribute& attributes) { +void Controller_Gesture::NewGesture(GestureProperties& gesture, GestureType& type, + GestureAttribute& attributes) { const auto& last_entry = GetLastGestureEntry(); gesture.detection_count++; - type = TouchType::Touch; + type = GestureType::Touch; // New touch after cancel is not considered new - if (last_entry.type != TouchType::Cancel) { + if (last_entry.type != GestureType::Cancel) { attributes.is_new_touch.Assign(1); enable_press_and_tap = true; } } -void Controller_Gesture::UpdateExistingGesture(GestureProperties& gesture, TouchType& type, +void Controller_Gesture::UpdateExistingGesture(GestureProperties& gesture, GestureType& type, f32 time_difference) { const auto& last_entry = GetLastGestureEntry(); // Promote to pan type if touch moved for (size_t id = 0; id < MAX_POINTS; id++) { if (gesture.points[id] != last_gesture.points[id]) { - type = TouchType::Pan; + type = GestureType::Pan; break; } } // Number of fingers changed cancel the last event and clear data if (gesture.active_points != last_gesture.active_points) { - type = TouchType::Cancel; + type = GestureType::Cancel; enable_press_and_tap = false; gesture.active_points = 0; gesture.mid_point = {}; @@ -189,41 +169,41 @@ void Controller_Gesture::UpdateExistingGesture(GestureProperties& gesture, Touch } // Calculate extra parameters of panning - if (type == TouchType::Pan) { + if (type == GestureType::Pan) { UpdatePanEvent(gesture, last_gesture, type, time_difference); return; } // Promote to press type - if (last_entry.type == TouchType::Touch) { - type = TouchType::Press; + if (last_entry.type == GestureType::Touch) { + type = GestureType::Press; } } void Controller_Gesture::EndGesture(GestureProperties& gesture, - GestureProperties& last_gesture_props, TouchType& type, - Attribute& attributes, f32 time_difference) { + GestureProperties& last_gesture_props, GestureType& type, + GestureAttribute& attributes, f32 time_difference) { const auto& last_entry = GetLastGestureEntry(); if (last_gesture_props.active_points != 0) { switch (last_entry.type) { - case TouchType::Touch: + case GestureType::Touch: if (enable_press_and_tap) { SetTapEvent(gesture, last_gesture_props, type, attributes); return; } - type = TouchType::Cancel; + type = GestureType::Cancel; force_update = true; break; - case TouchType::Press: - case TouchType::Tap: - case TouchType::Swipe: - case TouchType::Pinch: - case TouchType::Rotate: - type = TouchType::Complete; + case GestureType::Press: + case GestureType::Tap: + case GestureType::Swipe: + case GestureType::Pinch: + case GestureType::Rotate: + type = GestureType::Complete; force_update = true; break; - case TouchType::Pan: + case GestureType::Pan: EndPanEvent(gesture, last_gesture_props, type, time_difference); break; default: @@ -231,15 +211,15 @@ void Controller_Gesture::EndGesture(GestureProperties& gesture, } return; } - if (last_entry.type == TouchType::Complete || last_entry.type == TouchType::Cancel) { + if (last_entry.type == GestureType::Complete || last_entry.type == GestureType::Cancel) { gesture.detection_count++; } } void Controller_Gesture::SetTapEvent(GestureProperties& gesture, - GestureProperties& last_gesture_props, TouchType& type, - Attribute& attributes) { - type = TouchType::Tap; + GestureProperties& last_gesture_props, GestureType& type, + GestureAttribute& attributes) { + type = GestureType::Tap; gesture = last_gesture_props; force_update = true; f32 tap_time_difference = @@ -251,44 +231,42 @@ void Controller_Gesture::SetTapEvent(GestureProperties& gesture, } void Controller_Gesture::UpdatePanEvent(GestureProperties& gesture, - GestureProperties& last_gesture_props, TouchType& type, + GestureProperties& last_gesture_props, GestureType& type, f32 time_difference) { - auto& cur_entry = shared_memory.gesture_states[shared_memory.header.last_entry_index]; const auto& last_entry = GetLastGestureEntry(); - cur_entry.delta = gesture.mid_point - last_entry.pos; - cur_entry.vel_x = static_cast(cur_entry.delta.x) / time_difference; - cur_entry.vel_y = static_cast(cur_entry.delta.y) / time_difference; + next_state.delta = gesture.mid_point - last_entry.pos; + next_state.vel_x = static_cast(next_state.delta.x) / time_difference; + next_state.vel_y = static_cast(next_state.delta.y) / time_difference; last_pan_time_difference = time_difference; // Promote to pinch type if (std::abs(gesture.average_distance - last_gesture_props.average_distance) > pinch_threshold) { - type = TouchType::Pinch; - cur_entry.scale = gesture.average_distance / last_gesture_props.average_distance; + type = GestureType::Pinch; + next_state.scale = gesture.average_distance / last_gesture_props.average_distance; } const f32 angle_between_two_lines = std::atan((gesture.angle - last_gesture_props.angle) / (1 + (gesture.angle * last_gesture_props.angle))); // Promote to rotate type if (std::abs(angle_between_two_lines) > angle_threshold) { - type = TouchType::Rotate; - cur_entry.scale = 0; - cur_entry.rotation_angle = angle_between_two_lines * 180.0f / Common::PI; + type = GestureType::Rotate; + next_state.scale = 0; + next_state.rotation_angle = angle_between_two_lines * 180.0f / Common::PI; } } void Controller_Gesture::EndPanEvent(GestureProperties& gesture, - GestureProperties& last_gesture_props, TouchType& type, + GestureProperties& last_gesture_props, GestureType& type, f32 time_difference) { - auto& cur_entry = shared_memory.gesture_states[shared_memory.header.last_entry_index]; const auto& last_entry = GetLastGestureEntry(); - cur_entry.vel_x = + next_state.vel_x = static_cast(last_entry.delta.x) / (last_pan_time_difference + time_difference); - cur_entry.vel_y = + next_state.vel_y = static_cast(last_entry.delta.y) / (last_pan_time_difference + time_difference); const f32 curr_vel = - std::sqrt((cur_entry.vel_x * cur_entry.vel_x) + (cur_entry.vel_y * cur_entry.vel_y)); + std::sqrt((next_state.vel_x * next_state.vel_x) + (next_state.vel_y * next_state.vel_y)); // Set swipe event with parameters if (curr_vel > swipe_threshold) { @@ -297,105 +275,50 @@ void Controller_Gesture::EndPanEvent(GestureProperties& gesture, } // End panning without swipe - type = TouchType::Complete; - cur_entry.vel_x = 0; - cur_entry.vel_y = 0; + type = GestureType::Complete; + next_state.vel_x = 0; + next_state.vel_y = 0; force_update = true; } void Controller_Gesture::SetSwipeEvent(GestureProperties& gesture, - GestureProperties& last_gesture_props, TouchType& type) { - auto& cur_entry = shared_memory.gesture_states[shared_memory.header.last_entry_index]; + GestureProperties& last_gesture_props, GestureType& type) { const auto& last_entry = GetLastGestureEntry(); - type = TouchType::Swipe; + type = GestureType::Swipe; gesture = last_gesture_props; force_update = true; - cur_entry.delta = last_entry.delta; + next_state.delta = last_entry.delta; - if (std::abs(cur_entry.delta.x) > std::abs(cur_entry.delta.y)) { - if (cur_entry.delta.x > 0) { - cur_entry.direction = Direction::Right; + if (std::abs(next_state.delta.x) > std::abs(next_state.delta.y)) { + if (next_state.delta.x > 0) { + next_state.direction = GestureDirection::Right; return; } - cur_entry.direction = Direction::Left; + next_state.direction = GestureDirection::Left; return; } - if (cur_entry.delta.y > 0) { - cur_entry.direction = Direction::Down; + if (next_state.delta.y > 0) { + next_state.direction = GestureDirection::Down; return; } - cur_entry.direction = Direction::Up; -} - -void Controller_Gesture::OnLoadInputDevices() { - touch_mouse_device = Input::CreateDevice("engine:emu_window"); - touch_udp_device = Input::CreateDevice("engine:cemuhookudp"); - touch_btn_device = Input::CreateDevice("engine:touch_from_button"); -} - -std::optional Controller_Gesture::GetUnusedFingerID() const { - // Dont assign any touch input to a point if disabled - if (!Settings::values.touchscreen.enabled) { - return std::nullopt; - } - std::size_t first_free_id = 0; - while (first_free_id < MAX_POINTS) { - if (!fingers[first_free_id].pressed) { - return first_free_id; - } else { - first_free_id++; - } - } - return std::nullopt; -} - -Controller_Gesture::GestureState& Controller_Gesture::GetLastGestureEntry() { - return shared_memory.gesture_states[(shared_memory.header.last_entry_index + 16) % 17]; + next_state.direction = GestureDirection::Up; } const Controller_Gesture::GestureState& Controller_Gesture::GetLastGestureEntry() const { - return shared_memory.gesture_states[(shared_memory.header.last_entry_index + 16) % 17]; -} - -std::size_t Controller_Gesture::UpdateTouchInputEvent( - const std::tuple& touch_input, std::size_t finger_id) { - const auto& [x, y, pressed] = touch_input; - if (finger_id > MAX_POINTS) { - LOG_ERROR(Service_HID, "Invalid finger id {}", finger_id); - return MAX_POINTS; - } - if (pressed) { - if (finger_id == MAX_POINTS) { - const auto first_free_id = GetUnusedFingerID(); - if (!first_free_id) { - // Invalid finger id do nothing - return MAX_POINTS; - } - finger_id = first_free_id.value(); - fingers[finger_id].pressed = true; - } - fingers[finger_id].pos = {x, y}; - return finger_id; - } - - if (finger_id != MAX_POINTS) { - fingers[finger_id].pressed = false; - } - - return MAX_POINTS; + return gesture_lifo.ReadCurrentEntry().state; } Controller_Gesture::GestureProperties Controller_Gesture::GetGestureProperties() { GestureProperties gesture; - std::array active_fingers; + std::array active_fingers; const auto end_iter = std::copy_if(fingers.begin(), fingers.end(), active_fingers.begin(), [](const auto& finger) { return finger.pressed; }); gesture.active_points = static_cast(std::distance(active_fingers.begin(), end_iter)); for (size_t id = 0; id < gesture.active_points; ++id) { - const auto& [active_x, active_y] = active_fingers[id].pos; + const auto& [active_x, active_y] = active_fingers[id].position; gesture.points[id] = { .x = static_cast(active_x * Layout::ScreenUndocked::Width), .y = static_cast(active_y * Layout::ScreenUndocked::Height), diff --git a/src/core/hle/service/hid/controllers/gesture.h b/src/core/hle/service/hid/controllers/gesture.h index 7e7ae66259..0936a3fa37 100644 --- a/src/core/hle/service/hid/controllers/gesture.h +++ b/src/core/hle/service/hid/controllers/gesture.h @@ -8,13 +8,14 @@ #include "common/bit_field.h" #include "common/common_types.h" #include "common/point.h" -#include "core/frontend/input.h" +#include "core/hid/emulated_console.h" #include "core/hle/service/hid/controllers/controller_base.h" +#include "core/hle/service/hid/ring_lifo.h" namespace Service::HID { class Controller_Gesture final : public ControllerBase { public: - explicit Controller_Gesture(Core::System& system_); + explicit Controller_Gesture(Core::HID::HIDCore& hid_core_); ~Controller_Gesture() override; // Called when the controller is initialized @@ -26,14 +27,12 @@ public: // When the controller is requesting an update for the shared memory void OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, size_t size) override; - // Called when input devices should be loaded - void OnLoadInputDevices() override; - private: static constexpr size_t MAX_FINGERS = 16; static constexpr size_t MAX_POINTS = 4; - enum class TouchType : u32 { + // This is nn::hid::GestureType + enum class GestureType : u32 { Idle, // Nothing touching the screen Complete, // Set at the end of a touch event Cancel, // Set when the number of fingers change @@ -46,7 +45,8 @@ private: Rotate, // All points rotating from the midpoint }; - enum class Direction : u32 { + // This is nn::hid::GestureDirection + enum class GestureDirection : u32 { None, Left, Up, @@ -54,51 +54,41 @@ private: Down, }; - struct Attribute { + // This is nn::hid::GestureAttribute + struct GestureAttribute { union { - u32_le raw{}; + u32 raw{}; BitField<4, 1, u32> is_new_touch; BitField<8, 1, u32> is_double_tap; }; }; - static_assert(sizeof(Attribute) == 4, "Attribute is an invalid size"); + static_assert(sizeof(GestureAttribute) == 4, "GestureAttribute is an invalid size"); + // This is nn::hid::GestureState struct GestureState { - s64_le sampling_number; - s64_le sampling_number2; - s64_le detection_count; - TouchType type; - Direction direction; - Common::Point pos; - Common::Point delta; + s64 sampling_number; + s64 detection_count; + GestureType type; + GestureDirection direction; + Common::Point pos; + Common::Point delta; f32 vel_x; f32 vel_y; - Attribute attributes; + GestureAttribute attributes; f32 scale; f32 rotation_angle; - s32_le point_count; - std::array, 4> points; - }; - static_assert(sizeof(GestureState) == 0x68, "GestureState is an invalid size"); - - struct SharedMemory { - CommonHeader header; - std::array gesture_states; - }; - static_assert(sizeof(SharedMemory) == 0x708, "SharedMemory is an invalid size"); - - struct Finger { - Common::Point pos{}; - bool pressed{}; + s32 point_count; + std::array, 4> points; }; + static_assert(sizeof(GestureState) == 0x60, "GestureState is an invalid size"); struct GestureProperties { - std::array, MAX_POINTS> points{}; + std::array, MAX_POINTS> points{}; std::size_t active_points{}; - Common::Point mid_point{}; - s64_le detection_count{}; - u64_le delta_time{}; + Common::Point mid_point{}; + s64 detection_count{}; + u64 delta_time{}; f32 average_distance{}; f32 angle{}; }; @@ -114,61 +104,48 @@ private: f32 time_difference); // Initializes new gesture - void NewGesture(GestureProperties& gesture, TouchType& type, Attribute& attributes); + void NewGesture(GestureProperties& gesture, GestureType& type, GestureAttribute& attributes); // Updates existing gesture state - void UpdateExistingGesture(GestureProperties& gesture, TouchType& type, f32 time_difference); + void UpdateExistingGesture(GestureProperties& gesture, GestureType& type, f32 time_difference); // Terminates exiting gesture void EndGesture(GestureProperties& gesture, GestureProperties& last_gesture_props, - TouchType& type, Attribute& attributes, f32 time_difference); + GestureType& type, GestureAttribute& attributes, f32 time_difference); // Set current event to a tap event void SetTapEvent(GestureProperties& gesture, GestureProperties& last_gesture_props, - TouchType& type, Attribute& attributes); + GestureType& type, GestureAttribute& attributes); // Calculates and set the extra parameters related to a pan event void UpdatePanEvent(GestureProperties& gesture, GestureProperties& last_gesture_props, - TouchType& type, f32 time_difference); + GestureType& type, f32 time_difference); // Terminates the pan event void EndPanEvent(GestureProperties& gesture, GestureProperties& last_gesture_props, - TouchType& type, f32 time_difference); + GestureType& type, f32 time_difference); // Set current event to a swipe event void SetSwipeEvent(GestureProperties& gesture, GestureProperties& last_gesture_props, - TouchType& type); - - // Returns an unused finger id, if there is no fingers available std::nullopt is returned. - [[nodiscard]] std::optional GetUnusedFingerID() const; + GestureType& type); // Retrieves the last gesture entry, as indicated by shared memory indices. - [[nodiscard]] GestureState& GetLastGestureEntry(); [[nodiscard]] const GestureState& GetLastGestureEntry() const; - /** - * If the touch is new it tries to assign a new finger id, if there is no fingers available no - * changes will be made. Updates the coordinates if the finger id it's already set. If the touch - * ends delays the output by one frame to set the end_touch flag before finally freeing the - * finger id - */ - size_t UpdateTouchInputEvent(const std::tuple& touch_input, - size_t finger_id); - // Returns the average distance, angle and middle point of the active fingers GestureProperties GetGestureProperties(); - SharedMemory shared_memory{}; - std::unique_ptr touch_mouse_device; - std::unique_ptr touch_udp_device; - std::unique_ptr touch_btn_device; - std::array mouse_finger_id{}; - std::array keyboard_finger_id{}; - std::array udp_finger_id{}; - std::array fingers{}; + // This is nn::hid::detail::GestureLifo + Lifo gesture_lifo{}; + static_assert(sizeof(gesture_lifo) == 0x708, "gesture_lifo is an invalid size"); + GestureState next_state{}; + + Core::HID::EmulatedConsole* console; + + std::array fingers{}; GestureProperties last_gesture{}; - s64_le last_update_timestamp{}; - s64_le last_tap_timestamp{}; + s64 last_update_timestamp{}; + s64 last_tap_timestamp{}; f32 last_pan_time_difference{}; bool force_update{false}; bool enable_press_and_tap{false}; diff --git a/src/core/hle/service/hid/controllers/keyboard.cpp b/src/core/hle/service/hid/controllers/keyboard.cpp index c6c6200087..9588a69101 100644 --- a/src/core/hle/service/hid/controllers/keyboard.cpp +++ b/src/core/hle/service/hid/controllers/keyboard.cpp @@ -6,13 +6,18 @@ #include "common/common_types.h" #include "common/settings.h" #include "core/core_timing.h" +#include "core/hid/emulated_devices.h" +#include "core/hid/hid_core.h" #include "core/hle/service/hid/controllers/keyboard.h" namespace Service::HID { constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3800; -constexpr u8 KEYS_PER_BYTE = 8; -Controller_Keyboard::Controller_Keyboard(Core::System& system_) : ControllerBase{system_} {} +Controller_Keyboard::Controller_Keyboard(Core::HID::HIDCore& hid_core_) + : ControllerBase{hid_core_} { + emulated_devices = hid_core.GetEmulatedDevices(); +} + Controller_Keyboard::~Controller_Keyboard() = default; void Controller_Keyboard::OnInit() {} @@ -21,51 +26,27 @@ void Controller_Keyboard::OnRelease() {} void Controller_Keyboard::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) { - shared_memory.header.timestamp = core_timing.GetCPUTicks(); - shared_memory.header.total_entry_count = 17; - if (!IsControllerActivated()) { - shared_memory.header.entry_count = 0; - shared_memory.header.last_entry_index = 0; + keyboard_lifo.buffer_count = 0; + keyboard_lifo.buffer_tail = 0; + std::memcpy(data + SHARED_MEMORY_OFFSET, &keyboard_lifo, sizeof(keyboard_lifo)); return; } - shared_memory.header.entry_count = 16; - const auto& last_entry = shared_memory.pad_states[shared_memory.header.last_entry_index]; - shared_memory.header.last_entry_index = (shared_memory.header.last_entry_index + 1) % 17; - auto& cur_entry = shared_memory.pad_states[shared_memory.header.last_entry_index]; + const auto& last_entry = keyboard_lifo.ReadCurrentEntry().state; + next_state.sampling_number = last_entry.sampling_number + 1; - cur_entry.sampling_number = last_entry.sampling_number + 1; - cur_entry.sampling_number2 = cur_entry.sampling_number; - - cur_entry.key.fill(0); if (Settings::values.keyboard_enabled) { - for (std::size_t i = 0; i < keyboard_keys.size(); ++i) { - auto& entry = cur_entry.key[i / KEYS_PER_BYTE]; - entry = static_cast(entry | (keyboard_keys[i]->GetStatus() << (i % KEYS_PER_BYTE))); - } + const auto& keyboard_state = emulated_devices->GetKeyboard(); + const auto& keyboard_modifier_state = emulated_devices->GetKeyboardModifier(); - using namespace Settings::NativeKeyboard; - - // TODO: Assign the correct key to all modifiers - cur_entry.modifier.control.Assign(keyboard_mods[LeftControl]->GetStatus()); - cur_entry.modifier.shift.Assign(keyboard_mods[LeftShift]->GetStatus()); - cur_entry.modifier.left_alt.Assign(keyboard_mods[LeftAlt]->GetStatus()); - cur_entry.modifier.right_alt.Assign(keyboard_mods[RightAlt]->GetStatus()); - cur_entry.modifier.gui.Assign(0); - cur_entry.modifier.caps_lock.Assign(keyboard_mods[CapsLock]->GetStatus()); - cur_entry.modifier.scroll_lock.Assign(keyboard_mods[ScrollLock]->GetStatus()); - cur_entry.modifier.num_lock.Assign(keyboard_mods[NumLock]->GetStatus()); - cur_entry.modifier.katakana.Assign(0); - cur_entry.modifier.hiragana.Assign(0); + next_state.key = keyboard_state; + next_state.modifier = keyboard_modifier_state; + next_state.attribute.is_connected.Assign(1); } - std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(SharedMemory)); + + keyboard_lifo.WriteNextEntry(next_state); + std::memcpy(data + SHARED_MEMORY_OFFSET, &keyboard_lifo, sizeof(keyboard_lifo)); } -void Controller_Keyboard::OnLoadInputDevices() { - std::transform(Settings::values.keyboard_keys.begin(), Settings::values.keyboard_keys.end(), - keyboard_keys.begin(), Input::CreateDevice); - std::transform(Settings::values.keyboard_mods.begin(), Settings::values.keyboard_mods.end(), - keyboard_mods.begin(), Input::CreateDevice); -} } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/keyboard.h b/src/core/hle/service/hid/controllers/keyboard.h index 172a80e9c2..cf62d38961 100644 --- a/src/core/hle/service/hid/controllers/keyboard.h +++ b/src/core/hle/service/hid/controllers/keyboard.h @@ -8,15 +8,20 @@ #include "common/bit_field.h" #include "common/common_funcs.h" #include "common/common_types.h" -#include "common/settings.h" #include "common/swap.h" -#include "core/frontend/input.h" #include "core/hle/service/hid/controllers/controller_base.h" +#include "core/hle/service/hid/ring_lifo.h" + +namespace Core::HID { +class EmulatedDevices; +struct KeyboardModifier; +struct KeyboardKey; +} // namespace Core::HID namespace Service::HID { class Controller_Keyboard final : public ControllerBase { public: - explicit Controller_Keyboard(Core::System& system_); + explicit Controller_Keyboard(Core::HID::HIDCore& hid_core_); ~Controller_Keyboard() override; // Called when the controller is initialized @@ -28,47 +33,21 @@ public: // When the controller is requesting an update for the shared memory void OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) override; - // Called when input devices should be loaded - void OnLoadInputDevices() override; - private: - struct Modifiers { - union { - u32_le raw{}; - BitField<0, 1, u32> control; - BitField<1, 1, u32> shift; - BitField<2, 1, u32> left_alt; - BitField<3, 1, u32> right_alt; - BitField<4, 1, u32> gui; - BitField<8, 1, u32> caps_lock; - BitField<9, 1, u32> scroll_lock; - BitField<10, 1, u32> num_lock; - BitField<11, 1, u32> katakana; - BitField<12, 1, u32> hiragana; - }; - }; - static_assert(sizeof(Modifiers) == 0x4, "Modifiers is an invalid size"); - + // This is nn::hid::detail::KeyboardState struct KeyboardState { - s64_le sampling_number; - s64_le sampling_number2; - - Modifiers modifier; - std::array key; + s64 sampling_number; + Core::HID::KeyboardModifier modifier; + Core::HID::KeyboardAttribute attribute; + Core::HID::KeyboardKey key; }; - static_assert(sizeof(KeyboardState) == 0x38, "KeyboardState is an invalid size"); + static_assert(sizeof(KeyboardState) == 0x30, "KeyboardState is an invalid size"); - struct SharedMemory { - CommonHeader header; - std::array pad_states; - INSERT_PADDING_BYTES(0x28); - }; - static_assert(sizeof(SharedMemory) == 0x400, "SharedMemory is an invalid size"); - SharedMemory shared_memory{}; + // This is nn::hid::detail::KeyboardLifo + Lifo keyboard_lifo{}; + static_assert(sizeof(keyboard_lifo) == 0x3D8, "keyboard_lifo is an invalid size"); + KeyboardState next_state{}; - std::array, Settings::NativeKeyboard::NumKeyboardKeys> - keyboard_keys; - std::array, Settings::NativeKeyboard::NumKeyboardMods> - keyboard_mods; + Core::HID::EmulatedDevices* emulated_devices; }; } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/mouse.cpp b/src/core/hle/service/hid/controllers/mouse.cpp index 544a719481..ba79888ae0 100644 --- a/src/core/hle/service/hid/controllers/mouse.cpp +++ b/src/core/hle/service/hid/controllers/mouse.cpp @@ -6,12 +6,17 @@ #include "common/common_types.h" #include "core/core_timing.h" #include "core/frontend/emu_window.h" +#include "core/hid/emulated_devices.h" +#include "core/hid/hid_core.h" #include "core/hle/service/hid/controllers/mouse.h" namespace Service::HID { constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3400; -Controller_Mouse::Controller_Mouse(Core::System& system_) : ControllerBase{system_} {} +Controller_Mouse::Controller_Mouse(Core::HID::HIDCore& hid_core_) : ControllerBase{hid_core_} { + emulated_devices = hid_core.GetEmulatedDevices(); +} + Controller_Mouse::~Controller_Mouse() = default; void Controller_Mouse::OnInit() {} @@ -19,50 +24,35 @@ void Controller_Mouse::OnRelease() {} void Controller_Mouse::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) { - shared_memory.header.timestamp = core_timing.GetCPUTicks(); - shared_memory.header.total_entry_count = 17; - if (!IsControllerActivated()) { - shared_memory.header.entry_count = 0; - shared_memory.header.last_entry_index = 0; + mouse_lifo.buffer_count = 0; + mouse_lifo.buffer_tail = 0; + std::memcpy(data + SHARED_MEMORY_OFFSET, &mouse_lifo, sizeof(mouse_lifo)); return; } - shared_memory.header.entry_count = 16; - auto& last_entry = shared_memory.mouse_states[shared_memory.header.last_entry_index]; - shared_memory.header.last_entry_index = (shared_memory.header.last_entry_index + 1) % 17; - auto& cur_entry = shared_memory.mouse_states[shared_memory.header.last_entry_index]; + const auto& last_entry = mouse_lifo.ReadCurrentEntry().state; + next_state.sampling_number = last_entry.sampling_number + 1; - cur_entry.sampling_number = last_entry.sampling_number + 1; - cur_entry.sampling_number2 = cur_entry.sampling_number; - - cur_entry.attribute.raw = 0; + next_state.attribute.raw = 0; if (Settings::values.mouse_enabled) { - const auto [px, py, sx, sy] = mouse_device->GetStatus(); - const auto x = static_cast(px * Layout::ScreenUndocked::Width); - const auto y = static_cast(py * Layout::ScreenUndocked::Height); - cur_entry.x = x; - cur_entry.y = y; - cur_entry.delta_x = x - last_entry.x; - cur_entry.delta_y = y - last_entry.y; - cur_entry.mouse_wheel_x = sx; - cur_entry.mouse_wheel_y = sy; - cur_entry.attribute.is_connected.Assign(1); + const auto& mouse_button_state = emulated_devices->GetMouseButtons(); + const auto& mouse_position_state = emulated_devices->GetMousePosition(); + const auto& mouse_wheel_state = emulated_devices->GetMouseWheel(); + next_state.attribute.is_connected.Assign(1); + next_state.x = static_cast(mouse_position_state.x * Layout::ScreenUndocked::Width); + next_state.y = static_cast(mouse_position_state.y * Layout::ScreenUndocked::Height); + next_state.delta_x = next_state.x - last_entry.x; + next_state.delta_y = next_state.y - last_entry.y; + next_state.delta_wheel_x = mouse_wheel_state.x - last_mouse_wheel_state.x; + next_state.delta_wheel_y = mouse_wheel_state.y - last_mouse_wheel_state.y; - using namespace Settings::NativeMouseButton; - cur_entry.button.left.Assign(mouse_button_devices[Left]->GetStatus()); - cur_entry.button.right.Assign(mouse_button_devices[Right]->GetStatus()); - cur_entry.button.middle.Assign(mouse_button_devices[Middle]->GetStatus()); - cur_entry.button.forward.Assign(mouse_button_devices[Forward]->GetStatus()); - cur_entry.button.back.Assign(mouse_button_devices[Back]->GetStatus()); + last_mouse_wheel_state = mouse_wheel_state; + next_state.button = mouse_button_state; } - std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(SharedMemory)); + mouse_lifo.WriteNextEntry(next_state); + std::memcpy(data + SHARED_MEMORY_OFFSET, &mouse_lifo, sizeof(mouse_lifo)); } -void Controller_Mouse::OnLoadInputDevices() { - mouse_device = Input::CreateDevice(Settings::values.mouse_device); - std::transform(Settings::values.mouse_buttons.begin(), Settings::values.mouse_buttons.end(), - mouse_button_devices.begin(), Input::CreateDevice); -} } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/mouse.h b/src/core/hle/service/hid/controllers/mouse.h index 3d391a798b..7559fc78d2 100644 --- a/src/core/hle/service/hid/controllers/mouse.h +++ b/src/core/hle/service/hid/controllers/mouse.h @@ -7,15 +7,20 @@ #include #include "common/bit_field.h" #include "common/common_types.h" -#include "common/settings.h" #include "common/swap.h" -#include "core/frontend/input.h" #include "core/hle/service/hid/controllers/controller_base.h" +#include "core/hle/service/hid/ring_lifo.h" + +namespace Core::HID { +class EmulatedDevices; +struct MouseState; +struct AnalogStickState; +} // namespace Core::HID namespace Service::HID { class Controller_Mouse final : public ControllerBase { public: - explicit Controller_Mouse(Core::System& system_); + explicit Controller_Mouse(Core::HID::HIDCore& hid_core_); ~Controller_Mouse() override; // Called when the controller is initialized @@ -27,53 +32,13 @@ public: // When the controller is requesting an update for the shared memory void OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) override; - // Called when input devices should be loaded - void OnLoadInputDevices() override; - private: - struct Buttons { - union { - u32_le raw{}; - BitField<0, 1, u32> left; - BitField<1, 1, u32> right; - BitField<2, 1, u32> middle; - BitField<3, 1, u32> forward; - BitField<4, 1, u32> back; - }; - }; - static_assert(sizeof(Buttons) == 0x4, "Buttons is an invalid size"); + // This is nn::hid::detail::MouseLifo + Lifo mouse_lifo{}; + static_assert(sizeof(mouse_lifo) == 0x350, "mouse_lifo is an invalid size"); + Core::HID::MouseState next_state{}; - struct Attributes { - union { - u32_le raw{}; - BitField<0, 1, u32> transferable; - BitField<1, 1, u32> is_connected; - }; - }; - static_assert(sizeof(Attributes) == 0x4, "Attributes is an invalid size"); - - struct MouseState { - s64_le sampling_number; - s64_le sampling_number2; - s32_le x; - s32_le y; - s32_le delta_x; - s32_le delta_y; - s32_le mouse_wheel_x; - s32_le mouse_wheel_y; - Buttons button; - Attributes attribute; - }; - static_assert(sizeof(MouseState) == 0x30, "MouseState is an invalid size"); - - struct SharedMemory { - CommonHeader header; - std::array mouse_states; - }; - SharedMemory shared_memory{}; - - std::unique_ptr mouse_device; - std::array, Settings::NativeMouseButton::NumMouseButtons> - mouse_button_devices; + Core::HID::AnalogStickState last_mouse_wheel_state; + Core::HID::EmulatedDevices* emulated_devices; }; } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index 196876810a..dd4d954aac 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp @@ -10,9 +10,9 @@ #include "common/common_types.h" #include "common/logging/log.h" #include "common/settings.h" -#include "core/core.h" #include "core/core_timing.h" -#include "core/frontend/input.h" +#include "core/hid/emulated_controller.h" +#include "core/hid/hid_core.h" #include "core/hle/kernel/k_event.h" #include "core/hle/kernel/k_readable_event.h" #include "core/hle/kernel/k_writable_event.h" @@ -20,120 +20,26 @@ #include "core/hle/service/kernel_helpers.h" namespace Service::HID { -constexpr s32 HID_JOYSTICK_MAX = 0x7fff; -constexpr s32 HID_TRIGGER_MAX = 0x7fff; -[[maybe_unused]] constexpr s32 HID_JOYSTICK_MIN = -0x7fff; constexpr std::size_t NPAD_OFFSET = 0x9A00; -constexpr u32 BATTERY_FULL = 2; -constexpr u32 MAX_NPAD_ID = 7; -constexpr std::size_t HANDHELD_INDEX = 8; -constexpr std::array npad_id_list{ - 0, 1, 2, 3, 4, 5, 6, 7, NPAD_HANDHELD, NPAD_UNKNOWN, +constexpr std::array npad_id_list{ + Core::HID::NpadIdType::Player1, Core::HID::NpadIdType::Player2, Core::HID::NpadIdType::Player3, + Core::HID::NpadIdType::Player4, Core::HID::NpadIdType::Player5, Core::HID::NpadIdType::Player6, + Core::HID::NpadIdType::Player7, Core::HID::NpadIdType::Player8, Core::HID::NpadIdType::Other, + Core::HID::NpadIdType::Handheld, }; -enum class JoystickId : std::size_t { - Joystick_Left, - Joystick_Right, -}; - -Controller_NPad::NPadControllerType Controller_NPad::MapSettingsTypeToNPad( - Settings::ControllerType type) { - switch (type) { - case Settings::ControllerType::ProController: - return NPadControllerType::ProController; - case Settings::ControllerType::DualJoyconDetached: - return NPadControllerType::JoyDual; - case Settings::ControllerType::LeftJoycon: - return NPadControllerType::JoyLeft; - case Settings::ControllerType::RightJoycon: - return NPadControllerType::JoyRight; - case Settings::ControllerType::Handheld: - return NPadControllerType::Handheld; - case Settings::ControllerType::GameCube: - return NPadControllerType::GameCube; - default: - UNREACHABLE(); - return NPadControllerType::ProController; - } -} - -Settings::ControllerType Controller_NPad::MapNPadToSettingsType( - Controller_NPad::NPadControllerType type) { - switch (type) { - case NPadControllerType::ProController: - return Settings::ControllerType::ProController; - case NPadControllerType::JoyDual: - return Settings::ControllerType::DualJoyconDetached; - case NPadControllerType::JoyLeft: - return Settings::ControllerType::LeftJoycon; - case NPadControllerType::JoyRight: - return Settings::ControllerType::RightJoycon; - case NPadControllerType::Handheld: - return Settings::ControllerType::Handheld; - case NPadControllerType::GameCube: - return Settings::ControllerType::GameCube; - default: - UNREACHABLE(); - return Settings::ControllerType::ProController; - } -} - -std::size_t Controller_NPad::NPadIdToIndex(u32 npad_id) { +bool Controller_NPad::IsNpadIdValid(Core::HID::NpadIdType npad_id) { switch (npad_id) { - case 0: - case 1: - case 2: - case 3: - case 4: - case 5: - case 6: - case 7: - return npad_id; - case HANDHELD_INDEX: - case NPAD_HANDHELD: - return HANDHELD_INDEX; - case 9: - case NPAD_UNKNOWN: - return 9; - default: - UNIMPLEMENTED_MSG("Unknown npad id {}", npad_id); - return 0; - } -} - -u32 Controller_NPad::IndexToNPad(std::size_t index) { - switch (index) { - case 0: - case 1: - case 2: - case 3: - case 4: - case 5: - case 6: - case 7: - return static_cast(index); - case HANDHELD_INDEX: - return NPAD_HANDHELD; - case 9: - return NPAD_UNKNOWN; - default: - UNIMPLEMENTED_MSG("Unknown npad index {}", index); - return 0; - } -} - -bool Controller_NPad::IsNpadIdValid(u32 npad_id) { - switch (npad_id) { - case 0: - case 1: - case 2: - case 3: - case 4: - case 5: - case 6: - case 7: - case NPAD_UNKNOWN: - case NPAD_HANDHELD: + case Core::HID::NpadIdType::Player1: + case Core::HID::NpadIdType::Player2: + case Core::HID::NpadIdType::Player3: + case Core::HID::NpadIdType::Player4: + case Core::HID::NpadIdType::Player5: + case Core::HID::NpadIdType::Player6: + case Core::HID::NpadIdType::Player7: + case Core::HID::NpadIdType::Player8: + case Core::HID::NpadIdType::Other: + case Core::HID::NpadIdType::Handheld: return true; default: LOG_ERROR(Service_HID, "Invalid npad id {}", npad_id); @@ -141,131 +47,215 @@ bool Controller_NPad::IsNpadIdValid(u32 npad_id) { } } -bool Controller_NPad::IsDeviceHandleValid(const DeviceHandle& device_handle) { - return IsNpadIdValid(device_handle.npad_id) && - device_handle.npad_type < NpadType::MaxNpadType && - device_handle.device_index < DeviceIndex::MaxDeviceIndex; +bool Controller_NPad::IsDeviceHandleValid(const Core::HID::VibrationDeviceHandle& device_handle) { + return IsNpadIdValid(static_cast(device_handle.npad_id)) && + device_handle.npad_type < Core::HID::NpadStyleIndex::MaxNpadType && + device_handle.device_index < Core::HID::DeviceIndex::MaxDeviceIndex; } -Controller_NPad::Controller_NPad(Core::System& system_, +bool Controller_NPad::IsDeviceHandleValid(const Core::HID::SixAxisSensorHandle& device_handle) { + return IsNpadIdValid(static_cast(device_handle.npad_id)) && + device_handle.npad_type < Core::HID::NpadStyleIndex::MaxNpadType && + device_handle.device_index < Core::HID::DeviceIndex::MaxDeviceIndex; +} + +Controller_NPad::Controller_NPad(Core::HID::HIDCore& hid_core_, KernelHelpers::ServiceContext& service_context_) - : ControllerBase{system_}, service_context{service_context_} { - latest_vibration_values.fill({DEFAULT_VIBRATION_VALUE, DEFAULT_VIBRATION_VALUE}); + : ControllerBase{hid_core_}, service_context{service_context_} { + for (std::size_t i = 0; i < controller_data.size(); ++i) { + auto& controller = controller_data[i]; + controller.device = hid_core.GetEmulatedControllerByIndex(i); + controller.vibration[Core::HID::EmulatedDeviceIndex::LeftIndex].latest_vibration_value = + DEFAULT_VIBRATION_VALUE; + controller.vibration[Core::HID::EmulatedDeviceIndex::RightIndex].latest_vibration_value = + DEFAULT_VIBRATION_VALUE; + Core::HID::ControllerUpdateCallback engine_callback{ + .on_change = [this, + i](Core::HID::ControllerTriggerType type) { ControllerUpdate(type, i); }, + .is_npad_service = true, + }; + controller.callback_key = controller.device->SetCallback(engine_callback); + } } Controller_NPad::~Controller_NPad() { + for (std::size_t i = 0; i < controller_data.size(); ++i) { + auto& controller = controller_data[i]; + controller.device->DeleteCallback(controller.callback_key); + } OnRelease(); } -void Controller_NPad::InitNewlyAddedController(std::size_t controller_idx) { - const auto controller_type = connected_controllers[controller_idx].type; - auto& controller = shared_memory_entries[controller_idx]; - if (controller_type == NPadControllerType::None) { - styleset_changed_events[controller_idx]->GetWritableEvent().Signal(); +void Controller_NPad::ControllerUpdate(Core::HID::ControllerTriggerType type, + std::size_t controller_idx) { + if (type == Core::HID::ControllerTriggerType::All) { + ControllerUpdate(Core::HID::ControllerTriggerType::Connected, controller_idx); + ControllerUpdate(Core::HID::ControllerTriggerType::Battery, controller_idx); return; } - controller.style_set.raw = 0; // Zero out - controller.device_type.raw = 0; - controller.system_properties.raw = 0; + if (controller_idx >= controller_data.size()) { + return; + } + + auto& controller = controller_data[controller_idx]; + const auto is_connected = controller.device->IsConnected(); + const auto npad_type = controller.device->GetNpadStyleIndex(); + const auto npad_id = controller.device->GetNpadIdType(); + switch (type) { + case Core::HID::ControllerTriggerType::Connected: + case Core::HID::ControllerTriggerType::Disconnected: + if (is_connected == controller.is_connected) { + return; + } + UpdateControllerAt(npad_type, npad_id, is_connected); + break; + case Core::HID::ControllerTriggerType::Battery: { + if (!controller.is_connected) { + return; + } + auto& shared_memory = controller.shared_memory_entry; + const auto& battery_level = controller.device->GetBattery(); + shared_memory.battery_level_dual = battery_level.dual.battery_level; + shared_memory.battery_level_left = battery_level.left.battery_level; + shared_memory.battery_level_right = battery_level.right.battery_level; + break; + } + default: + break; + } +} + +void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { + LOG_DEBUG(Service_HID, "Npad connected {}", npad_id); + auto& controller = GetControllerFromNpadIdType(npad_id); + const auto controller_type = controller.device->GetNpadStyleIndex(); + auto& shared_memory = controller.shared_memory_entry; + if (controller_type == Core::HID::NpadStyleIndex::None) { + controller.styleset_changed_event->GetWritableEvent().Signal(); + return; + } + shared_memory.style_tag.raw = Core::HID::NpadStyleSet::None; + shared_memory.device_type.raw = 0; + shared_memory.system_properties.raw = 0; switch (controller_type) { - case NPadControllerType::None: + case Core::HID::NpadStyleIndex::None: UNREACHABLE(); break; - case NPadControllerType::ProController: - controller.style_set.fullkey.Assign(1); - controller.device_type.fullkey.Assign(1); - controller.system_properties.is_vertical.Assign(1); - controller.system_properties.use_plus.Assign(1); - controller.system_properties.use_minus.Assign(1); - controller.assignment_mode = NpadAssignments::Single; - controller.footer_type = AppletFooterUiType::SwitchProController; + case Core::HID::NpadStyleIndex::ProController: + shared_memory.style_tag.fullkey.Assign(1); + shared_memory.device_type.fullkey.Assign(1); + shared_memory.system_properties.is_vertical.Assign(1); + shared_memory.system_properties.use_plus.Assign(1); + shared_memory.system_properties.use_minus.Assign(1); + shared_memory.assignment_mode = NpadJoyAssignmentMode::Single; + shared_memory.applet_footer.type = AppletFooterUiType::SwitchProController; break; - case NPadControllerType::Handheld: - controller.style_set.handheld.Assign(1); - controller.device_type.handheld_left.Assign(1); - controller.device_type.handheld_right.Assign(1); - controller.system_properties.is_vertical.Assign(1); - controller.system_properties.use_plus.Assign(1); - controller.system_properties.use_minus.Assign(1); - controller.assignment_mode = NpadAssignments::Dual; - controller.footer_type = AppletFooterUiType::HandheldJoyConLeftJoyConRight; + case Core::HID::NpadStyleIndex::Handheld: + shared_memory.style_tag.handheld.Assign(1); + shared_memory.device_type.handheld_left.Assign(1); + shared_memory.device_type.handheld_right.Assign(1); + shared_memory.system_properties.is_vertical.Assign(1); + shared_memory.system_properties.use_plus.Assign(1); + shared_memory.system_properties.use_minus.Assign(1); + shared_memory.system_properties.use_directional_buttons.Assign(1); + shared_memory.assignment_mode = NpadJoyAssignmentMode::Dual; + shared_memory.applet_footer.type = AppletFooterUiType::HandheldJoyConLeftJoyConRight; break; - case NPadControllerType::JoyDual: - controller.style_set.joycon_dual.Assign(1); - controller.device_type.joycon_left.Assign(1); - controller.device_type.joycon_right.Assign(1); - controller.system_properties.is_vertical.Assign(1); - controller.system_properties.use_plus.Assign(1); - controller.system_properties.use_minus.Assign(1); - controller.assignment_mode = NpadAssignments::Dual; - controller.footer_type = AppletFooterUiType::JoyDual; + case Core::HID::NpadStyleIndex::JoyconDual: + shared_memory.style_tag.joycon_dual.Assign(1); + shared_memory.device_type.joycon_left.Assign(1); + shared_memory.device_type.joycon_right.Assign(1); + shared_memory.system_properties.is_vertical.Assign(1); + shared_memory.system_properties.use_plus.Assign(1); + shared_memory.system_properties.use_minus.Assign(1); + shared_memory.system_properties.use_directional_buttons.Assign(1); + shared_memory.assignment_mode = NpadJoyAssignmentMode::Dual; + shared_memory.applet_footer.type = AppletFooterUiType::JoyDual; break; - case NPadControllerType::JoyLeft: - controller.style_set.joycon_left.Assign(1); - controller.device_type.joycon_left.Assign(1); - controller.system_properties.is_horizontal.Assign(1); - controller.system_properties.use_minus.Assign(1); - controller.assignment_mode = NpadAssignments::Single; - controller.footer_type = AppletFooterUiType::JoyLeftHorizontal; + case Core::HID::NpadStyleIndex::JoyconLeft: + shared_memory.style_tag.joycon_left.Assign(1); + shared_memory.device_type.joycon_left.Assign(1); + shared_memory.system_properties.is_horizontal.Assign(1); + shared_memory.system_properties.use_minus.Assign(1); + shared_memory.assignment_mode = NpadJoyAssignmentMode::Single; + shared_memory.applet_footer.type = AppletFooterUiType::JoyLeftHorizontal; break; - case NPadControllerType::JoyRight: - controller.style_set.joycon_right.Assign(1); - controller.device_type.joycon_right.Assign(1); - controller.system_properties.is_horizontal.Assign(1); - controller.system_properties.use_plus.Assign(1); - controller.assignment_mode = NpadAssignments::Single; - controller.footer_type = AppletFooterUiType::JoyRightHorizontal; + case Core::HID::NpadStyleIndex::JoyconRight: + shared_memory.style_tag.joycon_right.Assign(1); + shared_memory.device_type.joycon_right.Assign(1); + shared_memory.system_properties.is_horizontal.Assign(1); + shared_memory.system_properties.use_plus.Assign(1); + shared_memory.assignment_mode = NpadJoyAssignmentMode::Single; + shared_memory.applet_footer.type = AppletFooterUiType::JoyRightHorizontal; break; - case NPadControllerType::GameCube: - controller.style_set.gamecube.Assign(1); - // The GC Controller behaves like a wired Pro Controller - controller.device_type.fullkey.Assign(1); - controller.system_properties.is_vertical.Assign(1); - controller.system_properties.use_plus.Assign(1); + case Core::HID::NpadStyleIndex::GameCube: + shared_memory.style_tag.gamecube.Assign(1); + shared_memory.device_type.fullkey.Assign(1); + shared_memory.system_properties.is_vertical.Assign(1); + shared_memory.system_properties.use_plus.Assign(1); break; - case NPadControllerType::Pokeball: - controller.style_set.palma.Assign(1); - controller.device_type.palma.Assign(1); - controller.assignment_mode = NpadAssignments::Single; + case Core::HID::NpadStyleIndex::Pokeball: + shared_memory.style_tag.palma.Assign(1); + shared_memory.device_type.palma.Assign(1); + shared_memory.assignment_mode = NpadJoyAssignmentMode::Single; + break; + case Core::HID::NpadStyleIndex::NES: + shared_memory.style_tag.lark.Assign(1); + shared_memory.device_type.fullkey.Assign(1); + break; + case Core::HID::NpadStyleIndex::SNES: + shared_memory.style_tag.lucia.Assign(1); + shared_memory.device_type.fullkey.Assign(1); + shared_memory.applet_footer.type = AppletFooterUiType::Lucia; + break; + case Core::HID::NpadStyleIndex::N64: + shared_memory.style_tag.lagoon.Assign(1); + shared_memory.device_type.fullkey.Assign(1); + shared_memory.applet_footer.type = AppletFooterUiType::Lagon; + break; + case Core::HID::NpadStyleIndex::SegaGenesis: + shared_memory.style_tag.lager.Assign(1); + shared_memory.device_type.fullkey.Assign(1); + break; + default: break; } - controller.fullkey_color.attribute = ColorAttributes::Ok; - controller.fullkey_color.fullkey.body = 0; - controller.fullkey_color.fullkey.button = 0; + const auto& body_colors = controller.device->GetColors(); - controller.joycon_color.attribute = ColorAttributes::Ok; - controller.joycon_color.left.body = - Settings::values.players.GetValue()[controller_idx].body_color_left; - controller.joycon_color.left.button = - Settings::values.players.GetValue()[controller_idx].button_color_left; - controller.joycon_color.right.body = - Settings::values.players.GetValue()[controller_idx].body_color_right; - controller.joycon_color.right.button = - Settings::values.players.GetValue()[controller_idx].button_color_right; + shared_memory.fullkey_color.attribute = ColorAttribute::Ok; + shared_memory.fullkey_color.fullkey = body_colors.fullkey; + + shared_memory.joycon_color.attribute = ColorAttribute::Ok; + shared_memory.joycon_color.left = body_colors.left; + shared_memory.joycon_color.right = body_colors.right; // TODO: Investigate when we should report all batery types - controller.battery_level_dual = BATTERY_FULL; - controller.battery_level_left = BATTERY_FULL; - controller.battery_level_right = BATTERY_FULL; + const auto& battery_level = controller.device->GetBattery(); + shared_memory.battery_level_dual = battery_level.dual.battery_level; + shared_memory.battery_level_left = battery_level.left.battery_level; + shared_memory.battery_level_right = battery_level.right.battery_level; - SignalStyleSetChangedEvent(IndexToNPad(controller_idx)); + controller.is_connected = true; + controller.device->Connect(); + SignalStyleSetChangedEvent(npad_id); + WriteEmptyEntry(controller.shared_memory_entry); } void Controller_NPad::OnInit() { - for (std::size_t i = 0; i < styleset_changed_events.size(); ++i) { - styleset_changed_events[i] = - service_context.CreateEvent(fmt::format("npad:NpadStyleSetChanged_{}", i)); - } - if (!IsControllerActivated()) { return; } - OnLoadInputDevices(); + for (std::size_t i = 0; i < controller_data.size(); ++i) { + auto& controller = controller_data[i]; + controller.styleset_changed_event = + service_context.CreateEvent(fmt::format("npad:NpadStyleSetChanged_{}", i)); + } - if (style.raw == 0) { + if (hid_core.GetSupportedStyleTag().raw == Core::HID::NpadStyleSet::None) { // We want to support all controllers + Core::HID::NpadStyleTag style{}; style.handheld.Assign(1); style.joycon_left.Assign(1); style.joycon_right.Assign(1); @@ -273,173 +263,120 @@ void Controller_NPad::OnInit() { style.fullkey.Assign(1); style.gamecube.Assign(1); style.palma.Assign(1); - } - - std::transform(Settings::values.players.GetValue().begin(), - Settings::values.players.GetValue().end(), connected_controllers.begin(), - [](const Settings::PlayerInput& player) { - return ControllerHolder{MapSettingsTypeToNPad(player.controller_type), - player.connected}; - }); - - // Connect the Player 1 or Handheld controller if none are connected. - if (std::none_of(connected_controllers.begin(), connected_controllers.end(), - [](const ControllerHolder& controller) { return controller.is_connected; })) { - const auto controller = - MapSettingsTypeToNPad(Settings::values.players.GetValue()[0].controller_type); - if (controller == NPadControllerType::Handheld) { - Settings::values.players.GetValue()[HANDHELD_INDEX].connected = true; - connected_controllers[HANDHELD_INDEX] = {controller, true}; - } else { - Settings::values.players.GetValue()[0].connected = true; - connected_controllers[0] = {controller, true}; - } - } - - // Account for handheld - if (connected_controllers[HANDHELD_INDEX].is_connected) { - connected_controllers[HANDHELD_INDEX].type = NPadControllerType::Handheld; + hid_core.SetSupportedStyleTag(style); } supported_npad_id_types.resize(npad_id_list.size()); std::memcpy(supported_npad_id_types.data(), npad_id_list.data(), - npad_id_list.size() * sizeof(u32)); + npad_id_list.size() * sizeof(Core::HID::NpadIdType)); - for (std::size_t i = 0; i < connected_controllers.size(); ++i) { - const auto& controller = connected_controllers[i]; - if (controller.is_connected) { - AddNewControllerAt(controller.type, i); + // Prefill controller buffers + for (auto& controller : controller_data) { + auto& npad = controller.shared_memory_entry; + npad.fullkey_color = { + .attribute = ColorAttribute::NoController, + .fullkey = {}, + }; + npad.joycon_color = { + .attribute = ColorAttribute::NoController, + .left = {}, + .right = {}, + }; + // HW seems to initialize the first 19 entries + for (std::size_t i = 0; i < 19; ++i) { + WriteEmptyEntry(npad); + } + } + + // Connect controllers + for (auto& controller : controller_data) { + const auto& device = controller.device; + if (device->IsConnected()) { + AddNewControllerAt(device->GetNpadStyleIndex(), device->GetNpadIdType()); } } } -void Controller_NPad::OnLoadInputDevices() { - const auto& players = Settings::values.players.GetValue(); - - std::lock_guard lock{mutex}; - for (std::size_t i = 0; i < players.size(); ++i) { - std::transform(players[i].buttons.begin() + Settings::NativeButton::BUTTON_HID_BEGIN, - players[i].buttons.begin() + Settings::NativeButton::BUTTON_HID_END, - buttons[i].begin(), Input::CreateDevice); - std::transform(players[i].analogs.begin() + Settings::NativeAnalog::STICK_HID_BEGIN, - players[i].analogs.begin() + Settings::NativeAnalog::STICK_HID_END, - sticks[i].begin(), Input::CreateDevice); - std::transform(players[i].vibrations.begin() + - Settings::NativeVibration::VIBRATION_HID_BEGIN, - players[i].vibrations.begin() + Settings::NativeVibration::VIBRATION_HID_END, - vibrations[i].begin(), Input::CreateDevice); - std::transform(players[i].motions.begin() + Settings::NativeMotion::MOTION_HID_BEGIN, - players[i].motions.begin() + Settings::NativeMotion::MOTION_HID_END, - motions[i].begin(), Input::CreateDevice); - for (std::size_t device_idx = 0; device_idx < vibrations[i].size(); ++device_idx) { - InitializeVibrationDeviceAtIndex(i, device_idx); - } - } +void Controller_NPad::WriteEmptyEntry(NpadInternalState& npad) { + NPadGenericState dummy_pad_state{}; + NpadGcTriggerState dummy_gc_state{}; + dummy_pad_state.sampling_number = npad.fullkey_lifo.ReadCurrentEntry().sampling_number + 1; + npad.fullkey_lifo.WriteNextEntry(dummy_pad_state); + dummy_pad_state.sampling_number = npad.handheld_lifo.ReadCurrentEntry().sampling_number + 1; + npad.handheld_lifo.WriteNextEntry(dummy_pad_state); + dummy_pad_state.sampling_number = npad.joy_dual_lifo.ReadCurrentEntry().sampling_number + 1; + npad.joy_dual_lifo.WriteNextEntry(dummy_pad_state); + dummy_pad_state.sampling_number = npad.joy_left_lifo.ReadCurrentEntry().sampling_number + 1; + npad.joy_left_lifo.WriteNextEntry(dummy_pad_state); + dummy_pad_state.sampling_number = npad.joy_right_lifo.ReadCurrentEntry().sampling_number + 1; + npad.joy_right_lifo.WriteNextEntry(dummy_pad_state); + dummy_pad_state.sampling_number = npad.palma_lifo.ReadCurrentEntry().sampling_number + 1; + npad.palma_lifo.WriteNextEntry(dummy_pad_state); + dummy_pad_state.sampling_number = npad.system_ext_lifo.ReadCurrentEntry().sampling_number + 1; + npad.system_ext_lifo.WriteNextEntry(dummy_pad_state); + dummy_gc_state.sampling_number = npad.gc_trigger_lifo.ReadCurrentEntry().sampling_number + 1; + npad.gc_trigger_lifo.WriteNextEntry(dummy_gc_state); } void Controller_NPad::OnRelease() { - for (std::size_t npad_idx = 0; npad_idx < vibrations.size(); ++npad_idx) { - for (std::size_t device_idx = 0; device_idx < vibrations[npad_idx].size(); ++device_idx) { - VibrateControllerAtIndex(npad_idx, device_idx, {}); + for (std::size_t i = 0; i < controller_data.size(); ++i) { + auto& controller = controller_data[i]; + service_context.CloseEvent(controller.styleset_changed_event); + for (std::size_t device_idx = 0; device_idx < controller.vibration.size(); ++device_idx) { + VibrateControllerAtIndex(controller.device->GetNpadIdType(), device_idx, {}); } } - - for (std::size_t i = 0; i < styleset_changed_events.size(); ++i) { - service_context.CloseEvent(styleset_changed_events[i]); - } } -void Controller_NPad::RequestPadStateUpdate(u32 npad_id) { +void Controller_NPad::RequestPadStateUpdate(Core::HID::NpadIdType npad_id) { std::lock_guard lock{mutex}; - - const auto controller_idx = NPadIdToIndex(npad_id); - const auto controller_type = connected_controllers[controller_idx].type; - if (!connected_controllers[controller_idx].is_connected) { + auto& controller = GetControllerFromNpadIdType(npad_id); + const auto controller_type = controller.device->GetNpadStyleIndex(); + if (!controller.device->IsConnected()) { return; } - auto& pad_state = npad_pad_states[controller_idx].pad_states; - auto& lstick_entry = npad_pad_states[controller_idx].l_stick; - auto& rstick_entry = npad_pad_states[controller_idx].r_stick; - auto& trigger_entry = npad_trigger_states[controller_idx]; - const auto& button_state = buttons[controller_idx]; - const auto& analog_state = sticks[controller_idx]; - const auto [stick_l_x_f, stick_l_y_f] = - analog_state[static_cast(JoystickId::Joystick_Left)]->GetStatus(); - const auto [stick_r_x_f, stick_r_y_f] = - analog_state[static_cast(JoystickId::Joystick_Right)]->GetStatus(); - using namespace Settings::NativeButton; - if (controller_type != NPadControllerType::JoyLeft) { - pad_state.a.Assign(button_state[A - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.b.Assign(button_state[B - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.x.Assign(button_state[X - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.y.Assign(button_state[Y - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.r_stick.Assign(button_state[RStick - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.r.Assign(button_state[R - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.zr.Assign(button_state[ZR - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.plus.Assign(button_state[Plus - BUTTON_HID_BEGIN]->GetStatus()); + auto& pad_entry = controller.npad_pad_state; + auto& trigger_entry = controller.npad_trigger_state; + const auto button_state = controller.device->GetNpadButtons(); + const auto stick_state = controller.device->GetSticks(); - pad_state.r_stick_right.Assign( - analog_state[static_cast(JoystickId::Joystick_Right)] - ->GetAnalogDirectionStatus(Input::AnalogDirection::RIGHT)); - pad_state.r_stick_left.Assign( - analog_state[static_cast(JoystickId::Joystick_Right)] - ->GetAnalogDirectionStatus(Input::AnalogDirection::LEFT)); - pad_state.r_stick_up.Assign( - analog_state[static_cast(JoystickId::Joystick_Right)] - ->GetAnalogDirectionStatus(Input::AnalogDirection::UP)); - pad_state.r_stick_down.Assign( - analog_state[static_cast(JoystickId::Joystick_Right)] - ->GetAnalogDirectionStatus(Input::AnalogDirection::DOWN)); - rstick_entry.x = static_cast(stick_r_x_f * HID_JOYSTICK_MAX); - rstick_entry.y = static_cast(stick_r_y_f * HID_JOYSTICK_MAX); + using btn = Core::HID::NpadButton; + pad_entry.npad_buttons.raw = btn::None; + if (controller_type != Core::HID::NpadStyleIndex::JoyconLeft) { + constexpr btn right_button_mask = btn::A | btn::B | btn::X | btn::Y | btn::StickR | btn::R | + btn::ZR | btn::Plus | btn::StickRLeft | btn::StickRUp | + btn::StickRRight | btn::StickRDown; + pad_entry.npad_buttons.raw = button_state.raw & right_button_mask; + pad_entry.r_stick = stick_state.right; } - if (controller_type != NPadControllerType::JoyRight) { - pad_state.d_left.Assign(button_state[DLeft - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.d_up.Assign(button_state[DUp - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.d_right.Assign(button_state[DRight - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.d_down.Assign(button_state[DDown - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.l_stick.Assign(button_state[LStick - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.l.Assign(button_state[L - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.zl.Assign(button_state[ZL - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.minus.Assign(button_state[Minus - BUTTON_HID_BEGIN]->GetStatus()); - - pad_state.l_stick_right.Assign( - analog_state[static_cast(JoystickId::Joystick_Left)] - ->GetAnalogDirectionStatus(Input::AnalogDirection::RIGHT)); - pad_state.l_stick_left.Assign( - analog_state[static_cast(JoystickId::Joystick_Left)] - ->GetAnalogDirectionStatus(Input::AnalogDirection::LEFT)); - pad_state.l_stick_up.Assign( - analog_state[static_cast(JoystickId::Joystick_Left)] - ->GetAnalogDirectionStatus(Input::AnalogDirection::UP)); - pad_state.l_stick_down.Assign( - analog_state[static_cast(JoystickId::Joystick_Left)] - ->GetAnalogDirectionStatus(Input::AnalogDirection::DOWN)); - lstick_entry.x = static_cast(stick_l_x_f * HID_JOYSTICK_MAX); - lstick_entry.y = static_cast(stick_l_y_f * HID_JOYSTICK_MAX); + if (controller_type != Core::HID::NpadStyleIndex::JoyconRight) { + constexpr btn left_button_mask = + btn::Left | btn::Up | btn::Right | btn::Down | btn::StickL | btn::L | btn::ZL | + btn::Minus | btn::StickLLeft | btn::StickLUp | btn::StickLRight | btn::StickLDown; + pad_entry.npad_buttons.raw |= button_state.raw & left_button_mask; + pad_entry.l_stick = stick_state.left; } - if (controller_type == NPadControllerType::JoyLeft) { - pad_state.left_sl.Assign(button_state[SL - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.left_sr.Assign(button_state[SR - BUTTON_HID_BEGIN]->GetStatus()); + if (controller_type == Core::HID::NpadStyleIndex::JoyconLeft) { + pad_entry.npad_buttons.left_sl.Assign(button_state.left_sl); + pad_entry.npad_buttons.left_sr.Assign(button_state.left_sr); } - if (controller_type == NPadControllerType::JoyRight) { - pad_state.right_sl.Assign(button_state[SL - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.right_sr.Assign(button_state[SR - BUTTON_HID_BEGIN]->GetStatus()); + if (controller_type == Core::HID::NpadStyleIndex::JoyconRight) { + pad_entry.npad_buttons.right_sl.Assign(button_state.right_sl); + pad_entry.npad_buttons.right_sr.Assign(button_state.right_sr); } - if (controller_type == NPadControllerType::GameCube) { - trigger_entry.l_analog = static_cast( - button_state[ZL - BUTTON_HID_BEGIN]->GetStatus() ? HID_TRIGGER_MAX : 0); - trigger_entry.r_analog = static_cast( - button_state[ZR - BUTTON_HID_BEGIN]->GetStatus() ? HID_TRIGGER_MAX : 0); - pad_state.zl.Assign(false); - pad_state.zr.Assign(button_state[R - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.l.Assign(button_state[ZL - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.r.Assign(button_state[ZR - BUTTON_HID_BEGIN]->GetStatus()); + if (controller_type == Core::HID::NpadStyleIndex::GameCube) { + const auto& trigger_state = controller.device->GetTriggers(); + trigger_entry.l_analog = trigger_state.left; + trigger_entry.r_analog = trigger_state.right; + pad_entry.npad_buttons.zl.Assign(false); + pad_entry.npad_buttons.zr.Assign(button_state.r); + pad_entry.npad_buttons.l.Assign(button_state.zl); + pad_entry.npad_buttons.r.Assign(button_state.zr); } } @@ -448,173 +385,132 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* if (!IsControllerActivated()) { return; } - for (std::size_t i = 0; i < shared_memory_entries.size(); ++i) { - auto& npad = shared_memory_entries[i]; - const std::array controller_npads{ - &npad.fullkey_states, &npad.handheld_states, &npad.joy_dual_states, - &npad.joy_left_states, &npad.joy_right_states, &npad.palma_states, - &npad.system_ext_states}; - // There is the posibility to have more controllers with analog triggers - const std::array controller_triggers{ - &npad.gc_trigger_states, - }; + for (std::size_t i = 0; i < controller_data.size(); ++i) { + auto& controller = controller_data[i]; + auto& npad = controller.shared_memory_entry; - for (auto* main_controller : controller_npads) { - main_controller->common.entry_count = 16; - main_controller->common.total_entry_count = 17; + const auto& controller_type = controller.device->GetNpadStyleIndex(); - const auto& last_entry = - main_controller->npad[main_controller->common.last_entry_index]; - - main_controller->common.timestamp = core_timing.GetCPUTicks(); - main_controller->common.last_entry_index = - (main_controller->common.last_entry_index + 1) % 17; - - auto& cur_entry = main_controller->npad[main_controller->common.last_entry_index]; - - cur_entry.timestamp = last_entry.timestamp + 1; - cur_entry.timestamp2 = cur_entry.timestamp; - } - - for (auto* analog_trigger : controller_triggers) { - analog_trigger->entry_count = 16; - analog_trigger->total_entry_count = 17; - - const auto& last_entry = analog_trigger->trigger[analog_trigger->last_entry_index]; - - analog_trigger->timestamp = core_timing.GetCPUTicks(); - analog_trigger->last_entry_index = (analog_trigger->last_entry_index + 1) % 17; - - auto& cur_entry = analog_trigger->trigger[analog_trigger->last_entry_index]; - - cur_entry.timestamp = last_entry.timestamp + 1; - cur_entry.timestamp2 = cur_entry.timestamp; - } - - const auto& controller_type = connected_controllers[i].type; - - if (controller_type == NPadControllerType::None || !connected_controllers[i].is_connected) { + if (controller_type == Core::HID::NpadStyleIndex::None || + !controller.device->IsConnected()) { + // Refresh shared memory + std::memcpy(data + NPAD_OFFSET + (i * sizeof(NpadInternalState)), + &controller.shared_memory_entry, sizeof(NpadInternalState)); continue; } - const u32 npad_index = static_cast(i); - RequestPadStateUpdate(npad_index); - auto& pad_state = npad_pad_states[npad_index]; - auto& trigger_state = npad_trigger_states[npad_index]; - - auto& main_controller = - npad.fullkey_states.npad[npad.fullkey_states.common.last_entry_index]; - auto& handheld_entry = - npad.handheld_states.npad[npad.handheld_states.common.last_entry_index]; - auto& dual_entry = npad.joy_dual_states.npad[npad.joy_dual_states.common.last_entry_index]; - auto& left_entry = npad.joy_left_states.npad[npad.joy_left_states.common.last_entry_index]; - auto& right_entry = - npad.joy_right_states.npad[npad.joy_right_states.common.last_entry_index]; - auto& pokeball_entry = npad.palma_states.npad[npad.palma_states.common.last_entry_index]; - auto& libnx_entry = - npad.system_ext_states.npad[npad.system_ext_states.common.last_entry_index]; - auto& trigger_entry = - npad.gc_trigger_states.trigger[npad.gc_trigger_states.last_entry_index]; - - libnx_entry.connection_status.raw = 0; - libnx_entry.connection_status.is_connected.Assign(1); - - switch (controller_type) { - case NPadControllerType::None: - UNREACHABLE(); - break; - case NPadControllerType::ProController: - main_controller.connection_status.raw = 0; - main_controller.connection_status.is_connected.Assign(1); - main_controller.connection_status.is_wired.Assign(1); - main_controller.pad.pad_states.raw = pad_state.pad_states.raw; - main_controller.pad.l_stick = pad_state.l_stick; - main_controller.pad.r_stick = pad_state.r_stick; - - libnx_entry.connection_status.is_wired.Assign(1); - break; - case NPadControllerType::Handheld: - handheld_entry.connection_status.raw = 0; - handheld_entry.connection_status.is_connected.Assign(1); - handheld_entry.connection_status.is_wired.Assign(1); - handheld_entry.connection_status.is_left_connected.Assign(1); - handheld_entry.connection_status.is_right_connected.Assign(1); - handheld_entry.connection_status.is_left_wired.Assign(1); - handheld_entry.connection_status.is_right_wired.Assign(1); - handheld_entry.pad.pad_states.raw = pad_state.pad_states.raw; - handheld_entry.pad.l_stick = pad_state.l_stick; - handheld_entry.pad.r_stick = pad_state.r_stick; - - libnx_entry.connection_status.is_wired.Assign(1); - libnx_entry.connection_status.is_left_connected.Assign(1); - libnx_entry.connection_status.is_right_connected.Assign(1); - libnx_entry.connection_status.is_left_wired.Assign(1); - libnx_entry.connection_status.is_right_wired.Assign(1); - break; - case NPadControllerType::JoyDual: - dual_entry.connection_status.raw = 0; - dual_entry.connection_status.is_connected.Assign(1); - dual_entry.connection_status.is_left_connected.Assign(1); - dual_entry.connection_status.is_right_connected.Assign(1); - dual_entry.pad.pad_states.raw = pad_state.pad_states.raw; - dual_entry.pad.l_stick = pad_state.l_stick; - dual_entry.pad.r_stick = pad_state.r_stick; - - libnx_entry.connection_status.is_left_connected.Assign(1); - libnx_entry.connection_status.is_right_connected.Assign(1); - break; - case NPadControllerType::JoyLeft: - left_entry.connection_status.raw = 0; - left_entry.connection_status.is_connected.Assign(1); - left_entry.connection_status.is_left_connected.Assign(1); - left_entry.pad.pad_states.raw = pad_state.pad_states.raw; - left_entry.pad.l_stick = pad_state.l_stick; - left_entry.pad.r_stick = pad_state.r_stick; - - libnx_entry.connection_status.is_left_connected.Assign(1); - break; - case NPadControllerType::JoyRight: - right_entry.connection_status.raw = 0; - right_entry.connection_status.is_connected.Assign(1); - right_entry.connection_status.is_right_connected.Assign(1); - right_entry.pad.pad_states.raw = pad_state.pad_states.raw; - right_entry.pad.l_stick = pad_state.l_stick; - right_entry.pad.r_stick = pad_state.r_stick; - - libnx_entry.connection_status.is_right_connected.Assign(1); - break; - case NPadControllerType::GameCube: - main_controller.connection_status.raw = 0; - main_controller.connection_status.is_connected.Assign(1); - main_controller.connection_status.is_wired.Assign(1); - main_controller.pad.pad_states.raw = pad_state.pad_states.raw; - main_controller.pad.l_stick = pad_state.l_stick; - main_controller.pad.r_stick = pad_state.r_stick; - trigger_entry.l_analog = trigger_state.l_analog; - trigger_entry.r_analog = trigger_state.r_analog; - - libnx_entry.connection_status.is_wired.Assign(1); - break; - case NPadControllerType::Pokeball: - pokeball_entry.connection_status.raw = 0; - pokeball_entry.connection_status.is_connected.Assign(1); - pokeball_entry.pad.pad_states.raw = pad_state.pad_states.raw; - pokeball_entry.pad.l_stick = pad_state.l_stick; - pokeball_entry.pad.r_stick = pad_state.r_stick; - break; - } + RequestPadStateUpdate(controller.device->GetNpadIdType()); + auto& pad_state = controller.npad_pad_state; + auto& libnx_state = controller.npad_libnx_state; + auto& trigger_state = controller.npad_trigger_state; // LibNX exclusively uses this section, so we always update it since LibNX doesn't activate // any controllers. - libnx_entry.pad.pad_states.raw = pad_state.pad_states.raw; - libnx_entry.pad.l_stick = pad_state.l_stick; - libnx_entry.pad.r_stick = pad_state.r_stick; + libnx_state.connection_status.raw = 0; + libnx_state.connection_status.is_connected.Assign(1); + switch (controller_type) { + case Core::HID::NpadStyleIndex::None: + UNREACHABLE(); + break; + case Core::HID::NpadStyleIndex::ProController: + case Core::HID::NpadStyleIndex::NES: + case Core::HID::NpadStyleIndex::SNES: + case Core::HID::NpadStyleIndex::N64: + case Core::HID::NpadStyleIndex::SegaGenesis: + pad_state.connection_status.raw = 0; + pad_state.connection_status.is_connected.Assign(1); + pad_state.connection_status.is_wired.Assign(1); - press_state |= static_cast(pad_state.pad_states.raw); + libnx_state.connection_status.is_wired.Assign(1); + pad_state.sampling_number = + npad.fullkey_lifo.ReadCurrentEntry().state.sampling_number + 1; + npad.fullkey_lifo.WriteNextEntry(pad_state); + break; + case Core::HID::NpadStyleIndex::Handheld: + pad_state.connection_status.raw = 0; + pad_state.connection_status.is_connected.Assign(1); + pad_state.connection_status.is_wired.Assign(1); + pad_state.connection_status.is_left_connected.Assign(1); + pad_state.connection_status.is_right_connected.Assign(1); + pad_state.connection_status.is_left_wired.Assign(1); + pad_state.connection_status.is_right_wired.Assign(1); + + libnx_state.connection_status.is_wired.Assign(1); + libnx_state.connection_status.is_left_connected.Assign(1); + libnx_state.connection_status.is_right_connected.Assign(1); + libnx_state.connection_status.is_left_wired.Assign(1); + libnx_state.connection_status.is_right_wired.Assign(1); + pad_state.sampling_number = + npad.handheld_lifo.ReadCurrentEntry().state.sampling_number + 1; + npad.handheld_lifo.WriteNextEntry(pad_state); + break; + case Core::HID::NpadStyleIndex::JoyconDual: + pad_state.connection_status.raw = 0; + pad_state.connection_status.is_connected.Assign(1); + pad_state.connection_status.is_left_connected.Assign(1); + pad_state.connection_status.is_right_connected.Assign(1); + + libnx_state.connection_status.is_left_connected.Assign(1); + libnx_state.connection_status.is_right_connected.Assign(1); + pad_state.sampling_number = + npad.joy_dual_lifo.ReadCurrentEntry().state.sampling_number + 1; + npad.joy_dual_lifo.WriteNextEntry(pad_state); + break; + case Core::HID::NpadStyleIndex::JoyconLeft: + pad_state.connection_status.raw = 0; + pad_state.connection_status.is_connected.Assign(1); + pad_state.connection_status.is_left_connected.Assign(1); + + libnx_state.connection_status.is_left_connected.Assign(1); + pad_state.sampling_number = + npad.joy_left_lifo.ReadCurrentEntry().state.sampling_number + 1; + npad.joy_left_lifo.WriteNextEntry(pad_state); + break; + case Core::HID::NpadStyleIndex::JoyconRight: + pad_state.connection_status.raw = 0; + pad_state.connection_status.is_connected.Assign(1); + pad_state.connection_status.is_right_connected.Assign(1); + + libnx_state.connection_status.is_right_connected.Assign(1); + pad_state.sampling_number = + npad.joy_right_lifo.ReadCurrentEntry().state.sampling_number + 1; + npad.joy_right_lifo.WriteNextEntry(pad_state); + break; + case Core::HID::NpadStyleIndex::GameCube: + pad_state.connection_status.raw = 0; + pad_state.connection_status.is_connected.Assign(1); + pad_state.connection_status.is_wired.Assign(1); + + libnx_state.connection_status.is_wired.Assign(1); + pad_state.sampling_number = + npad.fullkey_lifo.ReadCurrentEntry().state.sampling_number + 1; + trigger_state.sampling_number = + npad.gc_trigger_lifo.ReadCurrentEntry().state.sampling_number + 1; + npad.fullkey_lifo.WriteNextEntry(pad_state); + npad.gc_trigger_lifo.WriteNextEntry(trigger_state); + break; + case Core::HID::NpadStyleIndex::Pokeball: + pad_state.connection_status.raw = 0; + pad_state.connection_status.is_connected.Assign(1); + pad_state.sampling_number = + npad.palma_lifo.ReadCurrentEntry().state.sampling_number + 1; + npad.palma_lifo.WriteNextEntry(pad_state); + break; + default: + break; + } + + libnx_state.npad_buttons.raw = pad_state.npad_buttons.raw; + libnx_state.l_stick = pad_state.l_stick; + libnx_state.r_stick = pad_state.r_stick; + npad.system_ext_lifo.WriteNextEntry(pad_state); + + press_state |= static_cast(pad_state.npad_buttons.raw); + + std::memcpy(data + NPAD_OFFSET + (i * sizeof(NpadInternalState)), + &controller.shared_memory_entry, sizeof(NpadInternalState)); } - std::memcpy(data + NPAD_OFFSET, shared_memory_entries.data(), - shared_memory_entries.size() * sizeof(NPadEntry)); } void Controller_NPad::OnMotionUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, @@ -622,145 +518,138 @@ void Controller_NPad::OnMotionUpdate(const Core::Timing::CoreTiming& core_timing if (!IsControllerActivated()) { return; } - for (std::size_t i = 0; i < shared_memory_entries.size(); ++i) { - auto& npad = shared_memory_entries[i]; - const auto& controller_type = connected_controllers[i].type; + for (std::size_t i = 0; i < controller_data.size(); ++i) { + auto& controller = controller_data[i]; - if (controller_type == NPadControllerType::None || !connected_controllers[i].is_connected) { + const auto& controller_type = controller.device->GetNpadStyleIndex(); + + if (controller_type == Core::HID::NpadStyleIndex::None || + !controller.device->IsConnected()) { continue; } - const std::array controller_sixaxes{ - &npad.sixaxis_fullkey, &npad.sixaxis_handheld, &npad.sixaxis_dual_left, - &npad.sixaxis_dual_right, &npad.sixaxis_left, &npad.sixaxis_right, - }; + auto& npad = controller.shared_memory_entry; + const auto& motion_state = controller.device->GetMotions(); + auto& sixaxis_fullkey_state = controller.sixaxis_fullkey_state; + auto& sixaxis_handheld_state = controller.sixaxis_handheld_state; + auto& sixaxis_dual_left_state = controller.sixaxis_dual_left_state; + auto& sixaxis_dual_right_state = controller.sixaxis_dual_right_state; + auto& sixaxis_left_lifo_state = controller.sixaxis_left_lifo_state; + auto& sixaxis_right_lifo_state = controller.sixaxis_right_lifo_state; - for (auto* sixaxis_sensor : controller_sixaxes) { - sixaxis_sensor->common.entry_count = 16; - sixaxis_sensor->common.total_entry_count = 17; - - const auto& last_entry = - sixaxis_sensor->sixaxis[sixaxis_sensor->common.last_entry_index]; - - sixaxis_sensor->common.timestamp = core_timing.GetCPUTicks(); - sixaxis_sensor->common.last_entry_index = - (sixaxis_sensor->common.last_entry_index + 1) % 17; - - auto& cur_entry = sixaxis_sensor->sixaxis[sixaxis_sensor->common.last_entry_index]; - - cur_entry.timestamp = last_entry.timestamp + 1; - cur_entry.timestamp2 = cur_entry.timestamp; - } - - // Try to read sixaxis sensor states - std::array motion_devices; - - if (sixaxis_sensors_enabled && Settings::values.motion_enabled.GetValue()) { - sixaxis_at_rest = true; - for (std::size_t e = 0; e < motion_devices.size(); ++e) { - const auto& device = motions[i][e]; - if (device) { - std::tie(motion_devices[e].accel, motion_devices[e].gyro, - motion_devices[e].rotation, motion_devices[e].orientation, - motion_devices[e].quaternion) = device->GetStatus(); - sixaxis_at_rest = sixaxis_at_rest && motion_devices[e].gyro.Length2() < 0.0001f; - } + if (controller.sixaxis_sensor_enabled && Settings::values.motion_enabled.GetValue()) { + controller.sixaxis_at_rest = true; + for (std::size_t e = 0; e < motion_state.size(); ++e) { + controller.sixaxis_at_rest = + controller.sixaxis_at_rest && motion_state[e].is_at_rest; } } - auto& full_sixaxis_entry = - npad.sixaxis_fullkey.sixaxis[npad.sixaxis_fullkey.common.last_entry_index]; - auto& handheld_sixaxis_entry = - npad.sixaxis_handheld.sixaxis[npad.sixaxis_handheld.common.last_entry_index]; - auto& dual_left_sixaxis_entry = - npad.sixaxis_dual_left.sixaxis[npad.sixaxis_dual_left.common.last_entry_index]; - auto& dual_right_sixaxis_entry = - npad.sixaxis_dual_right.sixaxis[npad.sixaxis_dual_right.common.last_entry_index]; - auto& left_sixaxis_entry = - npad.sixaxis_left.sixaxis[npad.sixaxis_left.common.last_entry_index]; - auto& right_sixaxis_entry = - npad.sixaxis_right.sixaxis[npad.sixaxis_right.common.last_entry_index]; - switch (controller_type) { - case NPadControllerType::None: + case Core::HID::NpadStyleIndex::None: UNREACHABLE(); break; - case NPadControllerType::ProController: - full_sixaxis_entry.attribute.raw = 0; - if (sixaxis_sensors_enabled && motions[i][0]) { - full_sixaxis_entry.attribute.is_connected.Assign(1); - full_sixaxis_entry.accel = motion_devices[0].accel; - full_sixaxis_entry.gyro = motion_devices[0].gyro; - full_sixaxis_entry.rotation = motion_devices[0].rotation; - full_sixaxis_entry.orientation = motion_devices[0].orientation; + case Core::HID::NpadStyleIndex::ProController: + sixaxis_fullkey_state.attribute.raw = 0; + if (controller.sixaxis_sensor_enabled) { + sixaxis_fullkey_state.attribute.is_connected.Assign(1); + sixaxis_fullkey_state.accel = motion_state[0].accel; + sixaxis_fullkey_state.gyro = motion_state[0].gyro; + sixaxis_fullkey_state.rotation = motion_state[0].rotation; + sixaxis_fullkey_state.orientation = motion_state[0].orientation; } break; - case NPadControllerType::Handheld: - handheld_sixaxis_entry.attribute.raw = 0; - if (sixaxis_sensors_enabled && motions[i][0]) { - handheld_sixaxis_entry.attribute.is_connected.Assign(1); - handheld_sixaxis_entry.accel = motion_devices[0].accel; - handheld_sixaxis_entry.gyro = motion_devices[0].gyro; - handheld_sixaxis_entry.rotation = motion_devices[0].rotation; - handheld_sixaxis_entry.orientation = motion_devices[0].orientation; + case Core::HID::NpadStyleIndex::Handheld: + sixaxis_handheld_state.attribute.raw = 0; + if (controller.sixaxis_sensor_enabled) { + sixaxis_handheld_state.attribute.is_connected.Assign(1); + sixaxis_handheld_state.accel = motion_state[0].accel; + sixaxis_handheld_state.gyro = motion_state[0].gyro; + sixaxis_handheld_state.rotation = motion_state[0].rotation; + sixaxis_handheld_state.orientation = motion_state[0].orientation; } break; - case NPadControllerType::JoyDual: - dual_left_sixaxis_entry.attribute.raw = 0; - dual_right_sixaxis_entry.attribute.raw = 0; - if (sixaxis_sensors_enabled && motions[i][0]) { + case Core::HID::NpadStyleIndex::JoyconDual: + sixaxis_dual_left_state.attribute.raw = 0; + sixaxis_dual_right_state.attribute.raw = 0; + if (controller.sixaxis_sensor_enabled) { // Set motion for the left joycon - dual_left_sixaxis_entry.attribute.is_connected.Assign(1); - dual_left_sixaxis_entry.accel = motion_devices[0].accel; - dual_left_sixaxis_entry.gyro = motion_devices[0].gyro; - dual_left_sixaxis_entry.rotation = motion_devices[0].rotation; - dual_left_sixaxis_entry.orientation = motion_devices[0].orientation; + sixaxis_dual_left_state.attribute.is_connected.Assign(1); + sixaxis_dual_left_state.accel = motion_state[0].accel; + sixaxis_dual_left_state.gyro = motion_state[0].gyro; + sixaxis_dual_left_state.rotation = motion_state[0].rotation; + sixaxis_dual_left_state.orientation = motion_state[0].orientation; } - if (sixaxis_sensors_enabled && motions[i][1]) { + if (controller.sixaxis_sensor_enabled) { // Set motion for the right joycon - dual_right_sixaxis_entry.attribute.is_connected.Assign(1); - dual_right_sixaxis_entry.accel = motion_devices[1].accel; - dual_right_sixaxis_entry.gyro = motion_devices[1].gyro; - dual_right_sixaxis_entry.rotation = motion_devices[1].rotation; - dual_right_sixaxis_entry.orientation = motion_devices[1].orientation; + sixaxis_dual_right_state.attribute.is_connected.Assign(1); + sixaxis_dual_right_state.accel = motion_state[1].accel; + sixaxis_dual_right_state.gyro = motion_state[1].gyro; + sixaxis_dual_right_state.rotation = motion_state[1].rotation; + sixaxis_dual_right_state.orientation = motion_state[1].orientation; } break; - case NPadControllerType::JoyLeft: - left_sixaxis_entry.attribute.raw = 0; - if (sixaxis_sensors_enabled && motions[i][0]) { - left_sixaxis_entry.attribute.is_connected.Assign(1); - left_sixaxis_entry.accel = motion_devices[0].accel; - left_sixaxis_entry.gyro = motion_devices[0].gyro; - left_sixaxis_entry.rotation = motion_devices[0].rotation; - left_sixaxis_entry.orientation = motion_devices[0].orientation; + case Core::HID::NpadStyleIndex::JoyconLeft: + sixaxis_left_lifo_state.attribute.raw = 0; + if (controller.sixaxis_sensor_enabled) { + sixaxis_left_lifo_state.attribute.is_connected.Assign(1); + sixaxis_left_lifo_state.accel = motion_state[0].accel; + sixaxis_left_lifo_state.gyro = motion_state[0].gyro; + sixaxis_left_lifo_state.rotation = motion_state[0].rotation; + sixaxis_left_lifo_state.orientation = motion_state[0].orientation; } break; - case NPadControllerType::JoyRight: - right_sixaxis_entry.attribute.raw = 0; - if (sixaxis_sensors_enabled && motions[i][1]) { - right_sixaxis_entry.attribute.is_connected.Assign(1); - right_sixaxis_entry.accel = motion_devices[1].accel; - right_sixaxis_entry.gyro = motion_devices[1].gyro; - right_sixaxis_entry.rotation = motion_devices[1].rotation; - right_sixaxis_entry.orientation = motion_devices[1].orientation; + case Core::HID::NpadStyleIndex::JoyconRight: + sixaxis_right_lifo_state.attribute.raw = 0; + if (controller.sixaxis_sensor_enabled) { + sixaxis_right_lifo_state.attribute.is_connected.Assign(1); + sixaxis_right_lifo_state.accel = motion_state[1].accel; + sixaxis_right_lifo_state.gyro = motion_state[1].gyro; + sixaxis_right_lifo_state.rotation = motion_state[1].rotation; + sixaxis_right_lifo_state.orientation = motion_state[1].orientation; } break; - case NPadControllerType::GameCube: - case NPadControllerType::Pokeball: + default: break; } + + sixaxis_fullkey_state.sampling_number = + npad.sixaxis_fullkey_lifo.ReadCurrentEntry().state.sampling_number + 1; + sixaxis_handheld_state.sampling_number = + npad.sixaxis_handheld_lifo.ReadCurrentEntry().state.sampling_number + 1; + sixaxis_dual_left_state.sampling_number = + npad.sixaxis_dual_left_lifo.ReadCurrentEntry().state.sampling_number + 1; + sixaxis_dual_right_state.sampling_number = + npad.sixaxis_dual_right_lifo.ReadCurrentEntry().state.sampling_number + 1; + sixaxis_left_lifo_state.sampling_number = + npad.sixaxis_left_lifo.ReadCurrentEntry().state.sampling_number + 1; + sixaxis_right_lifo_state.sampling_number = + npad.sixaxis_right_lifo.ReadCurrentEntry().state.sampling_number + 1; + + if (Core::HID::IndexToNpadIdType(i) == Core::HID::NpadIdType::Handheld) { + // This buffer only is updated on handheld on HW + npad.sixaxis_handheld_lifo.WriteNextEntry(sixaxis_handheld_state); + } else { + // Hanheld doesn't update this buffer on HW + npad.sixaxis_fullkey_lifo.WriteNextEntry(sixaxis_fullkey_state); + } + + npad.sixaxis_dual_left_lifo.WriteNextEntry(sixaxis_dual_left_state); + npad.sixaxis_dual_right_lifo.WriteNextEntry(sixaxis_dual_right_state); + npad.sixaxis_left_lifo.WriteNextEntry(sixaxis_left_lifo_state); + npad.sixaxis_right_lifo.WriteNextEntry(sixaxis_right_lifo_state); + std::memcpy(data + NPAD_OFFSET + (i * sizeof(NpadInternalState)), + &controller.shared_memory_entry, sizeof(NpadInternalState)); } - std::memcpy(data + NPAD_OFFSET, shared_memory_entries.data(), - shared_memory_entries.size() * sizeof(NPadEntry)); } -void Controller_NPad::SetSupportedStyleSet(NpadStyleSet style_set) { - style.raw = style_set.raw; +void Controller_NPad::SetSupportedStyleSet(Core::HID::NpadStyleTag style_set) { + hid_core.SetSupportedStyleTag(style_set); } -Controller_NPad::NpadStyleSet Controller_NPad::GetSupportedStyleSet() const { - return style; +Core::HID::NpadStyleTag Controller_NPad::GetSupportedStyleSet() const { + return hid_core.GetSupportedStyleTag(); } void Controller_NPad::SetSupportedNpadIdTypes(u8* data, std::size_t length) { @@ -779,11 +668,11 @@ std::size_t Controller_NPad::GetSupportedNpadIdTypesSize() const { return supported_npad_id_types.size(); } -void Controller_NPad::SetHoldType(NpadHoldType joy_hold_type) { +void Controller_NPad::SetHoldType(NpadJoyHoldType joy_hold_type) { hold_type = joy_hold_type; } -Controller_NPad::NpadHoldType Controller_NPad::GetHoldType() const { +Controller_NPad::NpadJoyHoldType Controller_NPad::GetHoldType() const { return hold_type; } @@ -803,29 +692,35 @@ Controller_NPad::NpadCommunicationMode Controller_NPad::GetNpadCommunicationMode return communication_mode; } -void Controller_NPad::SetNpadMode(u32 npad_id, NpadAssignments assignment_mode) { - const std::size_t npad_index = NPadIdToIndex(npad_id); - ASSERT(npad_index < shared_memory_entries.size()); - if (shared_memory_entries[npad_index].assignment_mode != assignment_mode) { - shared_memory_entries[npad_index].assignment_mode = assignment_mode; +void Controller_NPad::SetNpadMode(Core::HID::NpadIdType npad_id, + NpadJoyAssignmentMode assignment_mode) { + if (!IsNpadIdValid(npad_id)) { + LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id:{}", npad_id); + return; + } + + auto& controller = GetControllerFromNpadIdType(npad_id); + if (controller.shared_memory_entry.assignment_mode != assignment_mode) { + controller.shared_memory_entry.assignment_mode = assignment_mode; } } -bool Controller_NPad::VibrateControllerAtIndex(std::size_t npad_index, std::size_t device_index, - const VibrationValue& vibration_value) { - if (!connected_controllers[npad_index].is_connected || !vibrations[npad_index][device_index]) { +bool Controller_NPad::VibrateControllerAtIndex(Core::HID::NpadIdType npad_id, + std::size_t device_index, + const Core::HID::VibrationValue& vibration_value) { + auto& controller = GetControllerFromNpadIdType(npad_id); + if (!controller.device->IsConnected()) { return false; } - const auto& player = Settings::values.players.GetValue()[npad_index]; - - if (!player.vibration_enabled) { - if (latest_vibration_values[npad_index][device_index].amp_low != 0.0f || - latest_vibration_values[npad_index][device_index].amp_high != 0.0f) { + if (!controller.device->IsVibrationEnabled()) { + if (controller.vibration[device_index].latest_vibration_value.low_amplitude != 0.0f || + controller.vibration[device_index].latest_vibration_value.high_amplitude != 0.0f) { // Send an empty vibration to stop any vibrations. - vibrations[npad_index][device_index]->SetRumblePlay(0.0f, 160.0f, 0.0f, 320.0f); + Core::HID::VibrationValue vibration{0.0f, 160.0f, 0.0f, 320.0f}; + controller.device->SetVibration(device_index, vibration); // Then reset the vibration value to its default value. - latest_vibration_values[npad_index][device_index] = DEFAULT_VIBRATION_VALUE; + controller.vibration[device_index].latest_vibration_value = DEFAULT_VIBRATION_VALUE; } return false; @@ -839,27 +734,25 @@ bool Controller_NPad::VibrateControllerAtIndex(std::size_t npad_index, std::size const auto now = steady_clock::now(); // Filter out non-zero vibrations that are within 10ms of each other. - if ((vibration_value.amp_low != 0.0f || vibration_value.amp_high != 0.0f) && - duration_cast(now - last_vibration_timepoints[npad_index][device_index]) < + if ((vibration_value.low_amplitude != 0.0f || vibration_value.high_amplitude != 0.0f) && + duration_cast( + now - controller.vibration[device_index].last_vibration_timepoint) < milliseconds(10)) { return false; } - last_vibration_timepoints[npad_index][device_index] = now; + controller.vibration[device_index].last_vibration_timepoint = now; } - auto& vibration = vibrations[npad_index][device_index]; - const auto player_vibration_strength = static_cast(player.vibration_strength); - const auto amp_low = - std::min(vibration_value.amp_low * player_vibration_strength / 100.0f, 1.0f); - const auto amp_high = - std::min(vibration_value.amp_high * player_vibration_strength / 100.0f, 1.0f); - return vibration->SetRumblePlay(amp_low, vibration_value.freq_low, amp_high, - vibration_value.freq_high); + Core::HID::VibrationValue vibration{ + vibration_value.low_amplitude, vibration_value.low_frequency, + vibration_value.high_amplitude, vibration_value.high_frequency}; + return controller.device->SetVibration(device_index, vibration); } -void Controller_NPad::VibrateController(const DeviceHandle& vibration_device_handle, - const VibrationValue& vibration_value) { +void Controller_NPad::VibrateController( + const Core::HID::VibrationDeviceHandle& vibration_device_handle, + const Core::HID::VibrationValue& vibration_value) { if (!IsDeviceHandleValid(vibration_device_handle)) { return; } @@ -868,42 +761,45 @@ void Controller_NPad::VibrateController(const DeviceHandle& vibration_device_han return; } - const auto npad_index = NPadIdToIndex(vibration_device_handle.npad_id); + auto& controller = GetControllerFromHandle(vibration_device_handle); const auto device_index = static_cast(vibration_device_handle.device_index); - if (!vibration_devices_mounted[npad_index][device_index] || - !connected_controllers[npad_index].is_connected) { + if (!controller.vibration[device_index].device_mounted || !controller.device->IsConnected()) { return; } - if (vibration_device_handle.device_index == DeviceIndex::None) { + if (vibration_device_handle.device_index == Core::HID::DeviceIndex::None) { UNREACHABLE_MSG("DeviceIndex should never be None!"); return; } // Some games try to send mismatched parameters in the device handle, block these. - if ((connected_controllers[npad_index].type == NPadControllerType::JoyLeft && - (vibration_device_handle.npad_type == NpadType::JoyconRight || - vibration_device_handle.device_index == DeviceIndex::Right)) || - (connected_controllers[npad_index].type == NPadControllerType::JoyRight && - (vibration_device_handle.npad_type == NpadType::JoyconLeft || - vibration_device_handle.device_index == DeviceIndex::Left))) { + if ((controller.device->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::JoyconLeft && + (vibration_device_handle.npad_type == Core::HID::NpadStyleIndex::JoyconRight || + vibration_device_handle.device_index == Core::HID::DeviceIndex::Right)) || + (controller.device->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::JoyconRight && + (vibration_device_handle.npad_type == Core::HID::NpadStyleIndex::JoyconLeft || + vibration_device_handle.device_index == Core::HID::DeviceIndex::Left))) { return; } // Filter out vibrations with equivalent values to reduce unnecessary state changes. - if (vibration_value.amp_low == latest_vibration_values[npad_index][device_index].amp_low && - vibration_value.amp_high == latest_vibration_values[npad_index][device_index].amp_high) { + if (vibration_value.low_amplitude == + controller.vibration[device_index].latest_vibration_value.low_amplitude && + vibration_value.high_amplitude == + controller.vibration[device_index].latest_vibration_value.high_amplitude) { return; } - if (VibrateControllerAtIndex(npad_index, device_index, vibration_value)) { - latest_vibration_values[npad_index][device_index] = vibration_value; + if (VibrateControllerAtIndex(controller.device->GetNpadIdType(), device_index, + vibration_value)) { + controller.vibration[device_index].latest_vibration_value = vibration_value; } } -void Controller_NPad::VibrateControllers(const std::vector& vibration_device_handles, - const std::vector& vibration_values) { +void Controller_NPad::VibrateControllers( + const std::vector& vibration_device_handles, + const std::vector& vibration_values) { if (!Settings::values.vibration_enabled.GetValue() && !permit_vibration_session_enabled) { return; } @@ -918,167 +814,231 @@ void Controller_NPad::VibrateControllers(const std::vector& vibrat } } -Controller_NPad::VibrationValue Controller_NPad::GetLastVibration( - const DeviceHandle& vibration_device_handle) const { +Core::HID::VibrationValue Controller_NPad::GetLastVibration( + const Core::HID::VibrationDeviceHandle& vibration_device_handle) const { if (!IsDeviceHandleValid(vibration_device_handle)) { return {}; } - const auto npad_index = NPadIdToIndex(vibration_device_handle.npad_id); + const auto& controller = GetControllerFromHandle(vibration_device_handle); const auto device_index = static_cast(vibration_device_handle.device_index); - return latest_vibration_values[npad_index][device_index]; + return controller.vibration[device_index].latest_vibration_value; } -void Controller_NPad::InitializeVibrationDevice(const DeviceHandle& vibration_device_handle) { +void Controller_NPad::InitializeVibrationDevice( + const Core::HID::VibrationDeviceHandle& vibration_device_handle) { if (!IsDeviceHandleValid(vibration_device_handle)) { return; } - const auto npad_index = NPadIdToIndex(vibration_device_handle.npad_id); + const auto npad_index = static_cast(vibration_device_handle.npad_id); const auto device_index = static_cast(vibration_device_handle.device_index); InitializeVibrationDeviceAtIndex(npad_index, device_index); } -void Controller_NPad::InitializeVibrationDeviceAtIndex(std::size_t npad_index, +void Controller_NPad::InitializeVibrationDeviceAtIndex(Core::HID::NpadIdType npad_id, std::size_t device_index) { + auto& controller = GetControllerFromNpadIdType(npad_id); if (!Settings::values.vibration_enabled.GetValue()) { - vibration_devices_mounted[npad_index][device_index] = false; + controller.vibration[device_index].device_mounted = false; return; } - if (vibrations[npad_index][device_index]) { - vibration_devices_mounted[npad_index][device_index] = - vibrations[npad_index][device_index]->GetStatus() == 1; - } else { - vibration_devices_mounted[npad_index][device_index] = false; - } + controller.vibration[device_index].device_mounted = + controller.device->TestVibration(device_index); } void Controller_NPad::SetPermitVibrationSession(bool permit_vibration_session) { permit_vibration_session_enabled = permit_vibration_session; } -bool Controller_NPad::IsVibrationDeviceMounted(const DeviceHandle& vibration_device_handle) const { +bool Controller_NPad::IsVibrationDeviceMounted( + const Core::HID::VibrationDeviceHandle& vibration_device_handle) const { if (!IsDeviceHandleValid(vibration_device_handle)) { return false; } - const auto npad_index = NPadIdToIndex(vibration_device_handle.npad_id); + const auto& controller = GetControllerFromHandle(vibration_device_handle); const auto device_index = static_cast(vibration_device_handle.device_index); - return vibration_devices_mounted[npad_index][device_index]; + return controller.vibration[device_index].device_mounted; } -Kernel::KReadableEvent& Controller_NPad::GetStyleSetChangedEvent(u32 npad_id) { - return styleset_changed_events[NPadIdToIndex(npad_id)]->GetReadableEvent(); +Kernel::KReadableEvent& Controller_NPad::GetStyleSetChangedEvent(Core::HID::NpadIdType npad_id) { + if (!IsNpadIdValid(npad_id)) { + LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id:{}", npad_id); + // Fallback to player 1 + const auto& controller = GetControllerFromNpadIdType(Core::HID::NpadIdType::Player1); + return controller.styleset_changed_event->GetReadableEvent(); + } + + const auto& controller = GetControllerFromNpadIdType(npad_id); + return controller.styleset_changed_event->GetReadableEvent(); } -void Controller_NPad::SignalStyleSetChangedEvent(u32 npad_id) const { - styleset_changed_events[NPadIdToIndex(npad_id)]->GetWritableEvent().Signal(); +void Controller_NPad::SignalStyleSetChangedEvent(Core::HID::NpadIdType npad_id) const { + const auto& controller = GetControllerFromNpadIdType(npad_id); + controller.styleset_changed_event->GetWritableEvent().Signal(); } -void Controller_NPad::AddNewControllerAt(NPadControllerType controller, std::size_t npad_index) { - UpdateControllerAt(controller, npad_index, true); +void Controller_NPad::AddNewControllerAt(Core::HID::NpadStyleIndex controller, + Core::HID::NpadIdType npad_id) { + UpdateControllerAt(controller, npad_id, true); } -void Controller_NPad::UpdateControllerAt(NPadControllerType controller, std::size_t npad_index, - bool connected) { +void Controller_NPad::UpdateControllerAt(Core::HID::NpadStyleIndex type, + Core::HID::NpadIdType npad_id, bool connected) { + auto& controller = GetControllerFromNpadIdType(npad_id); if (!connected) { - DisconnectNpadAtIndex(npad_index); + DisconnectNpad(npad_id); return; } - if (controller == NPadControllerType::Handheld && npad_index == HANDHELD_INDEX) { - Settings::values.players.GetValue()[HANDHELD_INDEX].controller_type = - MapNPadToSettingsType(controller); - Settings::values.players.GetValue()[HANDHELD_INDEX].connected = true; - connected_controllers[HANDHELD_INDEX] = {controller, true}; - InitNewlyAddedController(HANDHELD_INDEX); + controller.device->SetNpadStyleIndex(type); + InitNewlyAddedController(npad_id); +} + +void Controller_NPad::DisconnectNpad(Core::HID::NpadIdType npad_id) { + if (!IsNpadIdValid(npad_id)) { + LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id:{}", npad_id); return; } - Settings::values.players.GetValue()[npad_index].controller_type = - MapNPadToSettingsType(controller); - Settings::values.players.GetValue()[npad_index].connected = true; - connected_controllers[npad_index] = {controller, true}; - InitNewlyAddedController(npad_index); -} - -void Controller_NPad::DisconnectNpad(u32 npad_id) { - DisconnectNpadAtIndex(NPadIdToIndex(npad_id)); -} - -void Controller_NPad::DisconnectNpadAtIndex(std::size_t npad_index) { - for (std::size_t device_idx = 0; device_idx < vibrations[npad_index].size(); ++device_idx) { + LOG_DEBUG(Service_HID, "Npad disconnected {}", npad_id); + auto& controller = GetControllerFromNpadIdType(npad_id); + for (std::size_t device_idx = 0; device_idx < controller.vibration.size(); ++device_idx) { // Send an empty vibration to stop any vibrations. - VibrateControllerAtIndex(npad_index, device_idx, {}); - vibration_devices_mounted[npad_index][device_idx] = false; + VibrateControllerAtIndex(npad_id, device_idx, {}); + controller.vibration[device_idx].device_mounted = false; } - Settings::values.players.GetValue()[npad_index].connected = false; - connected_controllers[npad_index].is_connected = false; - - auto& controller = shared_memory_entries[npad_index]; - controller.style_set.raw = 0; // Zero out - controller.device_type.raw = 0; - controller.system_properties.raw = 0; - controller.button_properties.raw = 0; - controller.battery_level_dual = 0; - controller.battery_level_left = 0; - controller.battery_level_right = 0; - controller.fullkey_color = {}; - controller.joycon_color = {}; - controller.assignment_mode = NpadAssignments::Dual; - controller.footer_type = AppletFooterUiType::None; - - SignalStyleSetChangedEvent(IndexToNPad(npad_index)); -} - -void Controller_NPad::SetGyroscopeZeroDriftMode(GyroscopeZeroDriftMode drift_mode) { - gyroscope_zero_drift_mode = drift_mode; -} - -Controller_NPad::GyroscopeZeroDriftMode Controller_NPad::GetGyroscopeZeroDriftMode() const { - return gyroscope_zero_drift_mode; -} - -bool Controller_NPad::IsSixAxisSensorAtRest() const { - return sixaxis_at_rest; -} - -void Controller_NPad::SetSixAxisEnabled(bool six_axis_status) { - sixaxis_sensors_enabled = six_axis_status; -} - -void Controller_NPad::SetSixAxisFusionParameters(f32 parameter1, f32 parameter2) { - sixaxis_fusion_parameter1 = parameter1; - sixaxis_fusion_parameter2 = parameter2; -} - -std::pair Controller_NPad::GetSixAxisFusionParameters() { - return { - sixaxis_fusion_parameter1, - sixaxis_fusion_parameter2, + auto& shared_memory_entry = controller.shared_memory_entry; + shared_memory_entry.style_tag.raw = Core::HID::NpadStyleSet::None; // Zero out + shared_memory_entry.device_type.raw = 0; + shared_memory_entry.system_properties.raw = 0; + shared_memory_entry.button_properties.raw = 0; + shared_memory_entry.battery_level_dual = 0; + shared_memory_entry.battery_level_left = 0; + shared_memory_entry.battery_level_right = 0; + shared_memory_entry.fullkey_color = { + .attribute = ColorAttribute::NoController, + .fullkey = {}, }; + shared_memory_entry.joycon_color = { + .attribute = ColorAttribute::NoController, + .left = {}, + .right = {}, + }; + shared_memory_entry.assignment_mode = NpadJoyAssignmentMode::Dual; + shared_memory_entry.applet_footer.type = AppletFooterUiType::None; + + controller.is_connected = false; + controller.device->Disconnect(); + SignalStyleSetChangedEvent(npad_id); + WriteEmptyEntry(controller.shared_memory_entry); } -void Controller_NPad::ResetSixAxisFusionParameters() { - sixaxis_fusion_parameter1 = 0.0f; - sixaxis_fusion_parameter2 = 0.0f; +void Controller_NPad::SetGyroscopeZeroDriftMode(Core::HID::SixAxisSensorHandle sixaxis_handle, + GyroscopeZeroDriftMode drift_mode) { + if (!IsDeviceHandleValid(sixaxis_handle)) { + LOG_ERROR(Service_HID, "Invalid handle"); + return; + } + auto& controller = GetControllerFromHandle(sixaxis_handle); + controller.gyroscope_zero_drift_mode = drift_mode; } -void Controller_NPad::MergeSingleJoyAsDualJoy(u32 npad_id_1, u32 npad_id_2) { - const auto npad_index_1 = NPadIdToIndex(npad_id_1); - const auto npad_index_2 = NPadIdToIndex(npad_id_2); +Controller_NPad::GyroscopeZeroDriftMode Controller_NPad::GetGyroscopeZeroDriftMode( + Core::HID::SixAxisSensorHandle sixaxis_handle) const { + if (!IsDeviceHandleValid(sixaxis_handle)) { + LOG_ERROR(Service_HID, "Invalid handle"); + // Return the default value + return GyroscopeZeroDriftMode::Standard; + } + const auto& controller = GetControllerFromHandle(sixaxis_handle); + return controller.gyroscope_zero_drift_mode; +} + +bool Controller_NPad::IsSixAxisSensorAtRest(Core::HID::SixAxisSensorHandle sixaxis_handle) const { + if (!IsDeviceHandleValid(sixaxis_handle)) { + LOG_ERROR(Service_HID, "Invalid handle"); + // Return the default value + return true; + } + const auto& controller = GetControllerFromHandle(sixaxis_handle); + return controller.sixaxis_at_rest; +} + +void Controller_NPad::SetSixAxisEnabled(Core::HID::SixAxisSensorHandle sixaxis_handle, + bool sixaxis_status) { + if (!IsDeviceHandleValid(sixaxis_handle)) { + LOG_ERROR(Service_HID, "Invalid handle"); + return; + } + auto& controller = GetControllerFromHandle(sixaxis_handle); + controller.sixaxis_sensor_enabled = sixaxis_status; +} + +void Controller_NPad::SetSixAxisFusionEnabled(Core::HID::SixAxisSensorHandle sixaxis_handle, + bool sixaxis_fusion_status) { + if (!IsDeviceHandleValid(sixaxis_handle)) { + LOG_ERROR(Service_HID, "Invalid handle"); + return; + } + auto& controller = GetControllerFromHandle(sixaxis_handle); + controller.sixaxis_fusion_enabled = sixaxis_fusion_status; +} + +void Controller_NPad::SetSixAxisFusionParameters( + Core::HID::SixAxisSensorHandle sixaxis_handle, + Core::HID::SixAxisSensorFusionParameters sixaxis_fusion_parameters) { + if (!IsDeviceHandleValid(sixaxis_handle)) { + LOG_ERROR(Service_HID, "Invalid handle"); + return; + } + auto& controller = GetControllerFromHandle(sixaxis_handle); + controller.sixaxis_fusion = sixaxis_fusion_parameters; +} + +Core::HID::SixAxisSensorFusionParameters Controller_NPad::GetSixAxisFusionParameters( + Core::HID::SixAxisSensorHandle sixaxis_handle) { + if (!IsDeviceHandleValid(sixaxis_handle)) { + LOG_ERROR(Service_HID, "Invalid handle"); + // Since these parameters are unknow just return zeros + return {}; + } + auto& controller = GetControllerFromHandle(sixaxis_handle); + return controller.sixaxis_fusion; +} + +void Controller_NPad::ResetSixAxisFusionParameters(Core::HID::SixAxisSensorHandle sixaxis_handle) { + if (!IsDeviceHandleValid(sixaxis_handle)) { + LOG_ERROR(Service_HID, "Invalid handle"); + return; + } + auto& controller = GetControllerFromHandle(sixaxis_handle); + // Since these parameters are unknow just fill with zeros + controller.sixaxis_fusion = {}; +} + +void Controller_NPad::MergeSingleJoyAsDualJoy(Core::HID::NpadIdType npad_id_1, + Core::HID::NpadIdType npad_id_2) { + if (!IsNpadIdValid(npad_id_1) || !IsNpadIdValid(npad_id_2)) { + LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id_1:{}, npad_id_2:{}", npad_id_1, + npad_id_2); + return; + } + auto& controller_1 = GetControllerFromNpadIdType(npad_id_1).device; + auto& controller_2 = GetControllerFromNpadIdType(npad_id_2).device; // If the controllers at both npad indices form a pair of left and right joycons, merge them. // Otherwise, do nothing. - if ((connected_controllers[npad_index_1].type == NPadControllerType::JoyLeft && - connected_controllers[npad_index_2].type == NPadControllerType::JoyRight) || - (connected_controllers[npad_index_2].type == NPadControllerType::JoyLeft && - connected_controllers[npad_index_1].type == NPadControllerType::JoyRight)) { + if ((controller_1->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::JoyconLeft && + controller_2->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::JoyconRight) || + (controller_2->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::JoyconLeft && + controller_1->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::JoyconRight)) { // Disconnect the joycon at the second id and connect the dual joycon at the first index. DisconnectNpad(npad_id_2); - AddNewControllerAt(NPadControllerType::JoyDual, npad_index_1); + AddNewControllerAt(Core::HID::NpadStyleIndex::JoyconDual, npad_id_1); } } @@ -1092,61 +1052,61 @@ void Controller_NPad::StopLRAssignmentMode() { is_in_lr_assignment_mode = false; } -bool Controller_NPad::SwapNpadAssignment(u32 npad_id_1, u32 npad_id_2) { - if (npad_id_1 == NPAD_HANDHELD || npad_id_2 == NPAD_HANDHELD || npad_id_1 == NPAD_UNKNOWN || - npad_id_2 == NPAD_UNKNOWN) { +bool Controller_NPad::SwapNpadAssignment(Core::HID::NpadIdType npad_id_1, + Core::HID::NpadIdType npad_id_2) { + if (!IsNpadIdValid(npad_id_1) || !IsNpadIdValid(npad_id_2)) { + LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id_1:{}, npad_id_2:{}", npad_id_1, + npad_id_2); + return false; + } + if (npad_id_1 == Core::HID::NpadIdType::Handheld || + npad_id_2 == Core::HID::NpadIdType::Handheld || npad_id_1 == Core::HID::NpadIdType::Other || + npad_id_2 == Core::HID::NpadIdType::Other) { return true; } - const auto npad_index_1 = NPadIdToIndex(npad_id_1); - const auto npad_index_2 = NPadIdToIndex(npad_id_2); + const auto& controller_1 = GetControllerFromNpadIdType(npad_id_1).device; + const auto& controller_2 = GetControllerFromNpadIdType(npad_id_2).device; + const auto type_index_1 = controller_1->GetNpadStyleIndex(); + const auto type_index_2 = controller_2->GetNpadStyleIndex(); - if (!IsControllerSupported(connected_controllers[npad_index_1].type) || - !IsControllerSupported(connected_controllers[npad_index_2].type)) { + if (!IsControllerSupported(type_index_1) || !IsControllerSupported(type_index_2)) { return false; } - std::swap(connected_controllers[npad_index_1].type, connected_controllers[npad_index_2].type); - - AddNewControllerAt(connected_controllers[npad_index_1].type, npad_index_1); - AddNewControllerAt(connected_controllers[npad_index_2].type, npad_index_2); + AddNewControllerAt(type_index_2, npad_id_1); + AddNewControllerAt(type_index_1, npad_id_2); return true; } -Controller_NPad::LedPattern Controller_NPad::GetLedPattern(u32 npad_id) { - if (npad_id == npad_id_list.back() || npad_id == npad_id_list[npad_id_list.size() - 2]) { - // These are controllers without led patterns - return LedPattern{0, 0, 0, 0}; - } - switch (npad_id) { - case 0: - return LedPattern{1, 0, 0, 0}; - case 1: - return LedPattern{1, 1, 0, 0}; - case 2: - return LedPattern{1, 1, 1, 0}; - case 3: - return LedPattern{1, 1, 1, 1}; - case 4: - return LedPattern{1, 0, 0, 1}; - case 5: - return LedPattern{1, 0, 1, 0}; - case 6: - return LedPattern{1, 0, 1, 1}; - case 7: - return LedPattern{0, 1, 1, 0}; - default: - return LedPattern{0, 0, 0, 0}; +Core::HID::LedPattern Controller_NPad::GetLedPattern(Core::HID::NpadIdType npad_id) { + if (!IsNpadIdValid(npad_id)) { + LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id:{}", npad_id); + return Core::HID::LedPattern{0, 0, 0, 0}; } + const auto& controller = GetControllerFromNpadIdType(npad_id).device; + return controller->GetLedPattern(); } -bool Controller_NPad::IsUnintendedHomeButtonInputProtectionEnabled(u32 npad_id) const { - return unintended_home_button_input_protection[NPadIdToIndex(npad_id)]; +bool Controller_NPad::IsUnintendedHomeButtonInputProtectionEnabled( + Core::HID::NpadIdType npad_id) const { + if (!IsNpadIdValid(npad_id)) { + LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id:{}", npad_id); + // Return the default value + return false; + } + const auto& controller = GetControllerFromNpadIdType(npad_id); + return controller.unintended_home_button_input_protection; } void Controller_NPad::SetUnintendedHomeButtonInputProtectionEnabled(bool is_protection_enabled, - u32 npad_id) { - unintended_home_button_input_protection[NPadIdToIndex(npad_id)] = is_protection_enabled; + Core::HID::NpadIdType npad_id) { + if (!IsNpadIdValid(npad_id)) { + LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id:{}", npad_id); + return; + } + auto& controller = GetControllerFromNpadIdType(npad_id); + controller.unintended_home_button_input_protection = is_protection_enabled; } void Controller_NPad::SetAnalogStickUseCenterClamp(bool use_center_clamp) { @@ -1154,32 +1114,34 @@ void Controller_NPad::SetAnalogStickUseCenterClamp(bool use_center_clamp) { } void Controller_NPad::ClearAllConnectedControllers() { - for (auto& controller : connected_controllers) { - if (controller.is_connected && controller.type != NPadControllerType::None) { - controller.type = NPadControllerType::None; - controller.is_connected = false; + for (auto& controller : controller_data) { + if (controller.device->IsConnected() && + controller.device->GetNpadStyleIndex() != Core::HID::NpadStyleIndex::None) { + controller.device->Disconnect(); + controller.device->SetNpadStyleIndex(Core::HID::NpadStyleIndex::None); } } } void Controller_NPad::DisconnectAllConnectedControllers() { - for (auto& controller : connected_controllers) { - controller.is_connected = false; + for (auto& controller : controller_data) { + controller.device->Disconnect(); } } void Controller_NPad::ConnectAllDisconnectedControllers() { - for (auto& controller : connected_controllers) { - if (controller.type != NPadControllerType::None && !controller.is_connected) { - controller.is_connected = true; + for (auto& controller : controller_data) { + if (controller.device->GetNpadStyleIndex() != Core::HID::NpadStyleIndex::None && + !controller.device->IsConnected()) { + controller.device->Connect(); } } } void Controller_NPad::ClearAllControllers() { - for (auto& controller : connected_controllers) { - controller.type = NPadControllerType::None; - controller.is_connected = false; + for (auto& controller : controller_data) { + controller.device->Disconnect(); + controller.device->SetNpadStyleIndex(Core::HID::NpadStyleIndex::None); } } @@ -1187,16 +1149,16 @@ u32 Controller_NPad::GetAndResetPressState() { return press_state.exchange(0); } -bool Controller_NPad::IsControllerSupported(NPadControllerType controller) const { - if (controller == NPadControllerType::Handheld) { +bool Controller_NPad::IsControllerSupported(Core::HID::NpadStyleIndex controller) const { + if (controller == Core::HID::NpadStyleIndex::Handheld) { const bool support_handheld = std::find(supported_npad_id_types.begin(), supported_npad_id_types.end(), - NPAD_HANDHELD) != supported_npad_id_types.end(); + Core::HID::NpadIdType::Handheld) != supported_npad_id_types.end(); // Handheld is not even a supported type, lets stop here if (!support_handheld) { return false; } - // Handheld should not be supported in docked mode + // Handheld shouldn't be supported in docked mode if (Settings::values.use_docked_mode.GetValue()) { return false; } @@ -1205,20 +1167,31 @@ bool Controller_NPad::IsControllerSupported(NPadControllerType controller) const } if (std::any_of(supported_npad_id_types.begin(), supported_npad_id_types.end(), - [](u32 npad_id) { return npad_id <= MAX_NPAD_ID; })) { + [](Core::HID::NpadIdType npad_id) { + return npad_id <= Core::HID::NpadIdType::Player8; + })) { + Core::HID::NpadStyleTag style = GetSupportedStyleSet(); switch (controller) { - case NPadControllerType::ProController: + case Core::HID::NpadStyleIndex::ProController: return style.fullkey; - case NPadControllerType::JoyDual: + case Core::HID::NpadStyleIndex::JoyconDual: return style.joycon_dual; - case NPadControllerType::JoyLeft: + case Core::HID::NpadStyleIndex::JoyconLeft: return style.joycon_left; - case NPadControllerType::JoyRight: + case Core::HID::NpadStyleIndex::JoyconRight: return style.joycon_right; - case NPadControllerType::GameCube: + case Core::HID::NpadStyleIndex::GameCube: return style.gamecube; - case NPadControllerType::Pokeball: + case Core::HID::NpadStyleIndex::Pokeball: return style.palma; + case Core::HID::NpadStyleIndex::NES: + return style.lark; + case Core::HID::NpadStyleIndex::SNES: + return style.lucia; + case Core::HID::NpadStyleIndex::N64: + return style.lagoon; + case Core::HID::NpadStyleIndex::SegaGenesis: + return style.lager; default: return false; } @@ -1227,4 +1200,48 @@ bool Controller_NPad::IsControllerSupported(NPadControllerType controller) const return false; } +Controller_NPad::NpadControllerData& Controller_NPad::GetControllerFromHandle( + const Core::HID::SixAxisSensorHandle& device_handle) { + const auto npad_id = static_cast(device_handle.npad_id); + return GetControllerFromNpadIdType(npad_id); +} + +const Controller_NPad::NpadControllerData& Controller_NPad::GetControllerFromHandle( + const Core::HID::SixAxisSensorHandle& device_handle) const { + const auto npad_id = static_cast(device_handle.npad_id); + return GetControllerFromNpadIdType(npad_id); +} + +Controller_NPad::NpadControllerData& Controller_NPad::GetControllerFromHandle( + const Core::HID::VibrationDeviceHandle& device_handle) { + const auto npad_id = static_cast(device_handle.npad_id); + return GetControllerFromNpadIdType(npad_id); +} + +const Controller_NPad::NpadControllerData& Controller_NPad::GetControllerFromHandle( + const Core::HID::VibrationDeviceHandle& device_handle) const { + const auto npad_id = static_cast(device_handle.npad_id); + return GetControllerFromNpadIdType(npad_id); +} + +Controller_NPad::NpadControllerData& Controller_NPad::GetControllerFromNpadIdType( + Core::HID::NpadIdType npad_id) { + if (!IsNpadIdValid(npad_id)) { + LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id:{}", npad_id); + npad_id = Core::HID::NpadIdType::Player1; + } + const auto npad_index = Core::HID::NpadIdTypeToIndex(npad_id); + return controller_data[npad_index]; +} + +const Controller_NPad::NpadControllerData& Controller_NPad::GetControllerFromNpadIdType( + Core::HID::NpadIdType npad_id) const { + if (!IsNpadIdValid(npad_id)) { + LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id:{}", npad_id); + npad_id = Core::HID::NpadIdType::Player1; + } + const auto npad_index = Core::HID::NpadIdTypeToIndex(npad_id); + return controller_data[npad_index]; +} + } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h index 9ee146caf7..9fa113bb6b 100644 --- a/src/core/hle/service/hid/controllers/npad.h +++ b/src/core/hle/service/hid/controllers/npad.h @@ -11,9 +11,14 @@ #include "common/bit_field.h" #include "common/common_types.h" #include "common/quaternion.h" -#include "common/settings.h" -#include "core/frontend/input.h" +#include "core/hid/hid_types.h" #include "core/hle/service/hid/controllers/controller_base.h" +#include "core/hle/service/hid/ring_lifo.h" + +namespace Core::HID { +class EmulatedController; +enum class ControllerTriggerType; +} // namespace Core::HID namespace Kernel { class KEvent; @@ -26,12 +31,9 @@ class ServiceContext; namespace Service::HID { -constexpr u32 NPAD_HANDHELD = 32; -constexpr u32 NPAD_UNKNOWN = 16; // TODO(ogniK): What is this? - class Controller_NPad final : public ControllerBase { public: - explicit Controller_NPad(Core::System& system_, + explicit Controller_NPad(Core::HID::HIDCore& hid_core_, KernelHelpers::ServiceContext& service_context_); ~Controller_NPad() override; @@ -48,60 +50,39 @@ public: void OnMotionUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) override; - // Called when input devices should be loaded - void OnLoadInputDevices() override; - - enum class NPadControllerType { - None, - ProController, - Handheld, - JoyDual, - JoyLeft, - JoyRight, - GameCube, - Pokeball, - }; - - enum class NpadType : u8 { - ProController = 3, - Handheld = 4, - JoyconDual = 5, - JoyconLeft = 6, - JoyconRight = 7, - GameCube = 8, - Pokeball = 9, - MaxNpadType = 10, - }; - - enum class DeviceIndex : u8 { - Left = 0, - Right = 1, - None = 2, - MaxDeviceIndex = 3, - }; - + // This is nn::hid::GyroscopeZeroDriftMode enum class GyroscopeZeroDriftMode : u32 { Loose = 0, Standard = 1, Tight = 2, }; - enum class NpadHoldType : u64 { + // This is nn::hid::NpadJoyHoldType + enum class NpadJoyHoldType : u64 { Vertical = 0, Horizontal = 1, }; - enum class NpadAssignments : u32 { + // This is nn::hid::NpadJoyAssignmentMode + enum class NpadJoyAssignmentMode : u32 { Dual = 0, Single = 1, }; + // This is nn::hid::NpadJoyDeviceType + enum class NpadJoyDeviceType : s64 { + Left = 0, + Right = 1, + }; + + // This is nn::hid::NpadHandheldActivationMode enum class NpadHandheldActivationMode : u64 { Dual = 0, Single = 1, None = 2, }; + // This is nn::hid::NpadCommunicationMode enum class NpadCommunicationMode : u64 { Mode_5ms = 0, Mode_10ms = 1, @@ -109,74 +90,22 @@ public: Default = 3, }; - struct DeviceHandle { - NpadType npad_type; - u8 npad_id; - DeviceIndex device_index; - INSERT_PADDING_BYTES_NOINIT(1); - }; - static_assert(sizeof(DeviceHandle) == 4, "DeviceHandle is an invalid size"); - - struct NpadStyleSet { - union { - u32_le raw{}; - - BitField<0, 1, u32> fullkey; - BitField<1, 1, u32> handheld; - BitField<2, 1, u32> joycon_dual; - BitField<3, 1, u32> joycon_left; - BitField<4, 1, u32> joycon_right; - BitField<5, 1, u32> gamecube; - BitField<6, 1, u32> palma; - BitField<7, 1, u32> lark; - BitField<8, 1, u32> handheld_lark; - BitField<9, 1, u32> lucia; - BitField<29, 1, u32> system_ext; - BitField<30, 1, u32> system; - }; - }; - static_assert(sizeof(NpadStyleSet) == 4, "NpadStyleSet is an invalid size"); - - struct VibrationValue { - f32 amp_low; - f32 freq_low; - f32 amp_high; - f32 freq_high; - }; - static_assert(sizeof(VibrationValue) == 0x10, "Vibration is an invalid size"); - - static constexpr VibrationValue DEFAULT_VIBRATION_VALUE{ - .amp_low = 0.0f, - .freq_low = 160.0f, - .amp_high = 0.0f, - .freq_high = 320.0f, + static constexpr Core::HID::VibrationValue DEFAULT_VIBRATION_VALUE{ + .low_amplitude = 0.0f, + .low_frequency = 160.0f, + .high_amplitude = 0.0f, + .high_frequency = 320.0f, }; - struct LedPattern { - explicit LedPattern(u64 light1, u64 light2, u64 light3, u64 light4) { - position1.Assign(light1); - position2.Assign(light2); - position3.Assign(light3); - position4.Assign(light4); - } - union { - u64 raw{}; - BitField<0, 1, u64> position1; - BitField<1, 1, u64> position2; - BitField<2, 1, u64> position3; - BitField<3, 1, u64> position4; - }; - }; - - void SetSupportedStyleSet(NpadStyleSet style_set); - NpadStyleSet GetSupportedStyleSet() const; + void SetSupportedStyleSet(Core::HID::NpadStyleTag style_set); + Core::HID::NpadStyleTag GetSupportedStyleSet() const; void SetSupportedNpadIdTypes(u8* data, std::size_t length); void GetSupportedNpadIdTypes(u32* data, std::size_t max_length); std::size_t GetSupportedNpadIdTypesSize() const; - void SetHoldType(NpadHoldType joy_hold_type); - NpadHoldType GetHoldType() const; + void SetHoldType(NpadJoyHoldType joy_hold_type); + NpadJoyHoldType GetHoldType() const; void SetNpadHandheldActivationMode(NpadHandheldActivationMode activation_mode); NpadHandheldActivationMode GetNpadHandheldActivationMode() const; @@ -184,162 +113,106 @@ public: void SetNpadCommunicationMode(NpadCommunicationMode communication_mode_); NpadCommunicationMode GetNpadCommunicationMode() const; - void SetNpadMode(u32 npad_id, NpadAssignments assignment_mode); + void SetNpadMode(Core::HID::NpadIdType npad_id, NpadJoyAssignmentMode assignment_mode); - bool VibrateControllerAtIndex(std::size_t npad_index, std::size_t device_index, - const VibrationValue& vibration_value); + bool VibrateControllerAtIndex(Core::HID::NpadIdType npad_id, std::size_t device_index, + const Core::HID::VibrationValue& vibration_value); - void VibrateController(const DeviceHandle& vibration_device_handle, - const VibrationValue& vibration_value); + void VibrateController(const Core::HID::VibrationDeviceHandle& vibration_device_handle, + const Core::HID::VibrationValue& vibration_value); - void VibrateControllers(const std::vector& vibration_device_handles, - const std::vector& vibration_values); + void VibrateControllers( + const std::vector& vibration_device_handles, + const std::vector& vibration_values); - VibrationValue GetLastVibration(const DeviceHandle& vibration_device_handle) const; + Core::HID::VibrationValue GetLastVibration( + const Core::HID::VibrationDeviceHandle& vibration_device_handle) const; - void InitializeVibrationDevice(const DeviceHandle& vibration_device_handle); + void InitializeVibrationDevice(const Core::HID::VibrationDeviceHandle& vibration_device_handle); - void InitializeVibrationDeviceAtIndex(std::size_t npad_index, std::size_t device_index); + void InitializeVibrationDeviceAtIndex(Core::HID::NpadIdType npad_id, std::size_t device_index); void SetPermitVibrationSession(bool permit_vibration_session); - bool IsVibrationDeviceMounted(const DeviceHandle& vibration_device_handle) const; + bool IsVibrationDeviceMounted( + const Core::HID::VibrationDeviceHandle& vibration_device_handle) const; - Kernel::KReadableEvent& GetStyleSetChangedEvent(u32 npad_id); - void SignalStyleSetChangedEvent(u32 npad_id) const; + Kernel::KReadableEvent& GetStyleSetChangedEvent(Core::HID::NpadIdType npad_id); + void SignalStyleSetChangedEvent(Core::HID::NpadIdType npad_id) const; // Adds a new controller at an index. - void AddNewControllerAt(NPadControllerType controller, std::size_t npad_index); + void AddNewControllerAt(Core::HID::NpadStyleIndex controller, Core::HID::NpadIdType npad_id); // Adds a new controller at an index with connection status. - void UpdateControllerAt(NPadControllerType controller, std::size_t npad_index, bool connected); + void UpdateControllerAt(Core::HID::NpadStyleIndex controller, Core::HID::NpadIdType npad_id, + bool connected); - void DisconnectNpad(u32 npad_id); - void DisconnectNpadAtIndex(std::size_t index); + void DisconnectNpad(Core::HID::NpadIdType npad_id); - void SetGyroscopeZeroDriftMode(GyroscopeZeroDriftMode drift_mode); - GyroscopeZeroDriftMode GetGyroscopeZeroDriftMode() const; - bool IsSixAxisSensorAtRest() const; - void SetSixAxisEnabled(bool six_axis_status); - void SetSixAxisFusionParameters(f32 parameter1, f32 parameter2); - std::pair GetSixAxisFusionParameters(); - void ResetSixAxisFusionParameters(); - LedPattern GetLedPattern(u32 npad_id); - bool IsUnintendedHomeButtonInputProtectionEnabled(u32 npad_id) const; - void SetUnintendedHomeButtonInputProtectionEnabled(bool is_protection_enabled, u32 npad_id); + void SetGyroscopeZeroDriftMode(Core::HID::SixAxisSensorHandle sixaxis_handle, + GyroscopeZeroDriftMode drift_mode); + GyroscopeZeroDriftMode GetGyroscopeZeroDriftMode( + Core::HID::SixAxisSensorHandle sixaxis_handle) const; + bool IsSixAxisSensorAtRest(Core::HID::SixAxisSensorHandle sixaxis_handle) const; + void SetSixAxisEnabled(Core::HID::SixAxisSensorHandle sixaxis_handle, bool sixaxis_status); + void SetSixAxisFusionEnabled(Core::HID::SixAxisSensorHandle sixaxis_handle, + bool sixaxis_fusion_status); + void SetSixAxisFusionParameters( + Core::HID::SixAxisSensorHandle sixaxis_handle, + Core::HID::SixAxisSensorFusionParameters sixaxis_fusion_parameters); + Core::HID::SixAxisSensorFusionParameters GetSixAxisFusionParameters( + Core::HID::SixAxisSensorHandle sixaxis_handle); + void ResetSixAxisFusionParameters(Core::HID::SixAxisSensorHandle sixaxis_handle); + Core::HID::LedPattern GetLedPattern(Core::HID::NpadIdType npad_id); + bool IsUnintendedHomeButtonInputProtectionEnabled(Core::HID::NpadIdType npad_id) const; + void SetUnintendedHomeButtonInputProtectionEnabled(bool is_protection_enabled, + Core::HID::NpadIdType npad_id); void SetAnalogStickUseCenterClamp(bool use_center_clamp); void ClearAllConnectedControllers(); void DisconnectAllConnectedControllers(); void ConnectAllDisconnectedControllers(); void ClearAllControllers(); - void MergeSingleJoyAsDualJoy(u32 npad_id_1, u32 npad_id_2); + void MergeSingleJoyAsDualJoy(Core::HID::NpadIdType npad_id_1, Core::HID::NpadIdType npad_id_2); void StartLRAssignmentMode(); void StopLRAssignmentMode(); - bool SwapNpadAssignment(u32 npad_id_1, u32 npad_id_2); + bool SwapNpadAssignment(Core::HID::NpadIdType npad_id_1, Core::HID::NpadIdType npad_id_2); // Logical OR for all buttons presses on all controllers // Specifically for cheat engine and other features. u32 GetAndResetPressState(); - static Controller_NPad::NPadControllerType MapSettingsTypeToNPad(Settings::ControllerType type); - static Settings::ControllerType MapNPadToSettingsType(Controller_NPad::NPadControllerType type); - static std::size_t NPadIdToIndex(u32 npad_id); - static u32 IndexToNPad(std::size_t index); - static bool IsNpadIdValid(u32 npad_id); - static bool IsDeviceHandleValid(const DeviceHandle& device_handle); + static bool IsNpadIdValid(Core::HID::NpadIdType npad_id); + static bool IsDeviceHandleValid(const Core::HID::SixAxisSensorHandle& device_handle); + static bool IsDeviceHandleValid(const Core::HID::VibrationDeviceHandle& device_handle); private: - struct CommonHeader { - s64_le timestamp; - s64_le total_entry_count; - s64_le last_entry_index; - s64_le entry_count; - }; - static_assert(sizeof(CommonHeader) == 0x20, "CommonHeader is an invalid size"); - - enum class ColorAttributes : u32_le { + // This is nn::hid::detail::ColorAttribute + enum class ColorAttribute : u32 { Ok = 0, ReadError = 1, NoController = 2, }; - static_assert(sizeof(ColorAttributes) == 4, "ColorAttributes is an invalid size"); + static_assert(sizeof(ColorAttribute) == 4, "ColorAttribute is an invalid size"); - struct ControllerColor { - u32_le body; - u32_le button; + // This is nn::hid::detail::NpadFullKeyColorState + struct NpadFullKeyColorState { + ColorAttribute attribute; + Core::HID::NpadControllerColor fullkey; }; - static_assert(sizeof(ControllerColor) == 8, "ControllerColor is an invalid size"); + static_assert(sizeof(NpadFullKeyColorState) == 0xC, "NpadFullKeyColorState is an invalid size"); - struct FullKeyColor { - ColorAttributes attribute; - ControllerColor fullkey; + // This is nn::hid::detail::NpadJoyColorState + struct NpadJoyColorState { + ColorAttribute attribute; + Core::HID::NpadControllerColor left; + Core::HID::NpadControllerColor right; }; - static_assert(sizeof(FullKeyColor) == 0xC, "FullKeyColor is an invalid size"); + static_assert(sizeof(NpadJoyColorState) == 0x14, "NpadJoyColorState is an invalid size"); - struct JoyconColor { - ColorAttributes attribute; - ControllerColor left; - ControllerColor right; - }; - static_assert(sizeof(JoyconColor) == 0x14, "JoyconColor is an invalid size"); - - struct ControllerPadState { + // This is nn::hid::NpadAttribute + struct NpadAttribute { union { - u64_le raw{}; - // Button states - BitField<0, 1, u64> a; - BitField<1, 1, u64> b; - BitField<2, 1, u64> x; - BitField<3, 1, u64> y; - BitField<4, 1, u64> l_stick; - BitField<5, 1, u64> r_stick; - BitField<6, 1, u64> l; - BitField<7, 1, u64> r; - BitField<8, 1, u64> zl; - BitField<9, 1, u64> zr; - BitField<10, 1, u64> plus; - BitField<11, 1, u64> minus; - - // D-Pad - BitField<12, 1, u64> d_left; - BitField<13, 1, u64> d_up; - BitField<14, 1, u64> d_right; - BitField<15, 1, u64> d_down; - - // Left JoyStick - BitField<16, 1, u64> l_stick_left; - BitField<17, 1, u64> l_stick_up; - BitField<18, 1, u64> l_stick_right; - BitField<19, 1, u64> l_stick_down; - - // Right JoyStick - BitField<20, 1, u64> r_stick_left; - BitField<21, 1, u64> r_stick_up; - BitField<22, 1, u64> r_stick_right; - BitField<23, 1, u64> r_stick_down; - - // Not always active? - BitField<24, 1, u64> left_sl; - BitField<25, 1, u64> left_sr; - - BitField<26, 1, u64> right_sl; - BitField<27, 1, u64> right_sr; - - BitField<28, 1, u64> palma; - BitField<30, 1, u64> handheld_left_b; - }; - }; - static_assert(sizeof(ControllerPadState) == 8, "ControllerPadState is an invalid size"); - - struct AnalogPosition { - s32_le x; - s32_le y; - }; - static_assert(sizeof(AnalogPosition) == 8, "AnalogPosition is an invalid size"); - - struct ConnectionState { - union { - u32_le raw{}; + u32 raw{}; BitField<0, 1, u32> is_connected; BitField<1, 1, u32> is_wired; BitField<2, 1, u32> is_left_connected; @@ -348,79 +221,60 @@ private: BitField<5, 1, u32> is_right_wired; }; }; - static_assert(sizeof(ConnectionState) == 4, "ConnectionState is an invalid size"); + static_assert(sizeof(NpadAttribute) == 4, "NpadAttribute is an invalid size"); - struct ControllerPad { - ControllerPadState pad_states; - AnalogPosition l_stick; - AnalogPosition r_stick; + // This is nn::hid::NpadFullKeyState + // This is nn::hid::NpadHandheldState + // This is nn::hid::NpadJoyDualState + // This is nn::hid::NpadJoyLeftState + // This is nn::hid::NpadJoyRightState + // This is nn::hid::NpadPalmaState + // This is nn::hid::NpadSystemExtState + struct NPadGenericState { + s64_le sampling_number; + Core::HID::NpadButtonState npad_buttons; + Core::HID::AnalogStickState l_stick; + Core::HID::AnalogStickState r_stick; + NpadAttribute connection_status; + INSERT_PADDING_BYTES(4); // Reserved }; - static_assert(sizeof(ControllerPad) == 0x18, "ControllerPad is an invalid size"); + static_assert(sizeof(NPadGenericState) == 0x28, "NPadGenericState is an invalid size"); - struct GenericStates { - s64_le timestamp; - s64_le timestamp2; - ControllerPad pad; - ConnectionState connection_status; - }; - static_assert(sizeof(GenericStates) == 0x30, "NPadGenericStates is an invalid size"); - - struct NPadGeneric { - CommonHeader common; - std::array npad; - }; - static_assert(sizeof(NPadGeneric) == 0x350, "NPadGeneric is an invalid size"); - - struct SixAxisAttributes { + // This is nn::hid::SixAxisSensorAttribute + struct SixAxisSensorAttribute { union { - u32_le raw{}; + u32 raw{}; BitField<0, 1, u32> is_connected; BitField<1, 1, u32> is_interpolated; }; }; - static_assert(sizeof(SixAxisAttributes) == 4, "SixAxisAttributes is an invalid size"); + static_assert(sizeof(SixAxisSensorAttribute) == 4, "SixAxisSensorAttribute is an invalid size"); - struct SixAxisStates { - s64_le timestamp{}; - INSERT_PADDING_WORDS(2); - s64_le timestamp2{}; + // This is nn::hid::SixAxisSensorState + struct SixAxisSensorState { + s64 delta_time{}; + s64 sampling_number{}; Common::Vec3f accel{}; Common::Vec3f gyro{}; Common::Vec3f rotation{}; std::array orientation{}; - SixAxisAttributes attribute; + SixAxisSensorAttribute attribute; INSERT_PADDING_BYTES(4); // Reserved }; - static_assert(sizeof(SixAxisStates) == 0x68, "SixAxisStates is an invalid size"); + static_assert(sizeof(SixAxisSensorState) == 0x60, "SixAxisSensorState is an invalid size"); - struct SixAxisGeneric { - CommonHeader common{}; - std::array sixaxis{}; + // This is nn::hid::server::NpadGcTriggerState + struct NpadGcTriggerState { + s64 sampling_number{}; + s32 l_analog{}; + s32 r_analog{}; }; - static_assert(sizeof(SixAxisGeneric) == 0x708, "SixAxisGeneric is an invalid size"); - - struct TriggerState { - s64_le timestamp{}; - s64_le timestamp2{}; - s32_le l_analog{}; - s32_le r_analog{}; - }; - static_assert(sizeof(TriggerState) == 0x18, "TriggerState is an invalid size"); - - struct TriggerGeneric { - INSERT_PADDING_BYTES(0x4); - s64_le timestamp; - INSERT_PADDING_BYTES(0x4); - s64_le total_entry_count; - s64_le last_entry_index; - s64_le entry_count; - std::array trigger{}; - }; - static_assert(sizeof(TriggerGeneric) == 0x1C8, "TriggerGeneric is an invalid size"); + static_assert(sizeof(NpadGcTriggerState) == 0x10, "NpadGcTriggerState is an invalid size"); + // This is nn::hid::NpadSystemProperties struct NPadSystemProperties { union { - s64_le raw{}; + s64 raw{}; BitField<0, 1, s64> is_charging_joy_dual; BitField<1, 1, s64> is_charging_joy_left; BitField<2, 1, s64> is_charging_joy_right; @@ -438,17 +292,20 @@ private: }; static_assert(sizeof(NPadSystemProperties) == 0x8, "NPadSystemProperties is an invalid size"); - struct NPadButtonProperties { + // This is nn::hid::NpadSystemButtonProperties + struct NpadSystemButtonProperties { union { - s32_le raw{}; + s32 raw{}; BitField<0, 1, s32> is_home_button_protection_enabled; }; }; - static_assert(sizeof(NPadButtonProperties) == 0x4, "NPadButtonProperties is an invalid size"); + static_assert(sizeof(NpadSystemButtonProperties) == 0x4, + "NPadButtonProperties is an invalid size"); - struct NPadDevice { + // This is nn::hid::system::DeviceType + struct DeviceType { union { - u32_le raw{}; + u32 raw{}; BitField<0, 1, s32> fullkey; BitField<1, 1, s32> debug_pad; BitField<2, 1, s32> handheld_left; @@ -465,26 +322,29 @@ private: BitField<13, 1, s32> handheld_lark_nes_left; BitField<14, 1, s32> handheld_lark_nes_right; BitField<15, 1, s32> lucia; + BitField<16, 1, s32> lagon; + BitField<17, 1, s32> lager; BitField<31, 1, s32> system; }; }; - struct MotionDevice { - Common::Vec3f accel; - Common::Vec3f gyro; - Common::Vec3f rotation; - std::array orientation; - Common::Quaternion quaternion; - }; - - struct NfcXcdHandle { - INSERT_PADDING_BYTES(0x60); + // This is nn::hid::detail::NfcXcdDeviceHandleStateImpl + struct NfcXcdDeviceHandleStateImpl { + u64 handle; + bool is_available; + bool is_activated; + INSERT_PADDING_BYTES(0x6); // Reserved + u64 sampling_number; }; + static_assert(sizeof(NfcXcdDeviceHandleStateImpl) == 0x18, + "NfcXcdDeviceHandleStateImpl is an invalid size"); + // This is nn::hid::system::AppletFooterUiAttributesSet struct AppletFooterUiAttributes { INSERT_PADDING_BYTES(0x4); }; + // This is nn::hid::system::AppletFooterUiType enum class AppletFooterUiType : u8 { None = 0, HandheldNone = 1, @@ -510,95 +370,150 @@ private: Lagon = 21, }; - struct NPadEntry { - NpadStyleSet style_set; - NpadAssignments assignment_mode; - FullKeyColor fullkey_color; - JoyconColor joycon_color; + struct AppletFooterUi { + AppletFooterUiAttributes attributes; + AppletFooterUiType type; + INSERT_PADDING_BYTES(0x5B); // Reserved + }; + static_assert(sizeof(AppletFooterUi) == 0x60, "AppletFooterUi is an invalid size"); - NPadGeneric fullkey_states; - NPadGeneric handheld_states; - NPadGeneric joy_dual_states; - NPadGeneric joy_left_states; - NPadGeneric joy_right_states; - NPadGeneric palma_states; - NPadGeneric system_ext_states; - SixAxisGeneric sixaxis_fullkey; - SixAxisGeneric sixaxis_handheld; - SixAxisGeneric sixaxis_dual_left; - SixAxisGeneric sixaxis_dual_right; - SixAxisGeneric sixaxis_left; - SixAxisGeneric sixaxis_right; - NPadDevice device_type; - INSERT_PADDING_BYTES(0x4); // reserved + // This is nn::hid::NpadLarkType + enum class NpadLarkType : u32 { + Invalid, + H1, + H2, + NL, + NR, + }; + + // This is nn::hid::NpadLuciaType + enum class NpadLuciaType : u32 { + Invalid, + J, + E, + U, + }; + + // This is nn::hid::NpadLagonType + enum class NpadLagonType : u32 { + Invalid, + }; + + // This is nn::hid::NpadLagerType + enum class NpadLagerType : u32 { + Invalid, + J, + E, + U, + }; + + // This is nn::hid::detail::NpadInternalState + struct NpadInternalState { + Core::HID::NpadStyleTag style_tag; + NpadJoyAssignmentMode assignment_mode; + NpadFullKeyColorState fullkey_color; + NpadJoyColorState joycon_color; + Lifo fullkey_lifo; + Lifo handheld_lifo; + Lifo joy_dual_lifo; + Lifo joy_left_lifo; + Lifo joy_right_lifo; + Lifo palma_lifo; + Lifo system_ext_lifo; + Lifo sixaxis_fullkey_lifo; + Lifo sixaxis_handheld_lifo; + Lifo sixaxis_dual_left_lifo; + Lifo sixaxis_dual_right_lifo; + Lifo sixaxis_left_lifo; + Lifo sixaxis_right_lifo; + DeviceType device_type; + INSERT_PADDING_BYTES(0x4); // Reserved NPadSystemProperties system_properties; - NPadButtonProperties button_properties; - u32 battery_level_dual; - u32 battery_level_left; - u32 battery_level_right; - AppletFooterUiAttributes footer_attributes; - AppletFooterUiType footer_type; - // nfc_states needs to be checked switchbrew does not match with HW - NfcXcdHandle nfc_states; - INSERT_PADDING_BYTES(0x8); // Mutex - TriggerGeneric gc_trigger_states; - INSERT_PADDING_BYTES(0xc1f); + NpadSystemButtonProperties button_properties; + Core::HID::NpadBatteryLevel battery_level_dual; + Core::HID::NpadBatteryLevel battery_level_left; + Core::HID::NpadBatteryLevel battery_level_right; + union { + Lifo nfc_xcd_device_lifo{}; + AppletFooterUi applet_footer; + }; + INSERT_PADDING_BYTES(0x20); // Unknown + Lifo gc_trigger_lifo; + NpadLarkType lark_type_l_and_main; + NpadLarkType lark_type_r; + NpadLuciaType lucia_type; + NpadLagonType lagon_type; + NpadLagerType lager_type; + // FW 13.x Investigate there is some sort of bitflag related to joycons + INSERT_PADDING_BYTES(0x4); + INSERT_PADDING_BYTES(0xc08); // Unknown }; - static_assert(sizeof(NPadEntry) == 0x5000, "NPadEntry is an invalid size"); + static_assert(sizeof(NpadInternalState) == 0x5000, "NpadInternalState is an invalid size"); - struct ControllerHolder { - NPadControllerType type; - bool is_connected; + struct VibrationData { + bool device_mounted{}; + Core::HID::VibrationValue latest_vibration_value{}; + std::chrono::steady_clock::time_point last_vibration_timepoint{}; }; - void InitNewlyAddedController(std::size_t controller_idx); - bool IsControllerSupported(NPadControllerType controller) const; - void RequestPadStateUpdate(u32 npad_id); + struct NpadControllerData { + Core::HID::EmulatedController* device; + Kernel::KEvent* styleset_changed_event{}; + NpadInternalState shared_memory_entry{}; + + std::array vibration{}; + bool unintended_home_button_input_protection{}; + bool is_connected{}; + Core::HID::NpadStyleIndex npad_type{Core::HID::NpadStyleIndex::None}; + + // Motion parameters + bool sixaxis_at_rest{true}; + bool sixaxis_sensor_enabled{true}; + bool sixaxis_fusion_enabled{false}; + Core::HID::SixAxisSensorFusionParameters sixaxis_fusion{}; + GyroscopeZeroDriftMode gyroscope_zero_drift_mode{GyroscopeZeroDriftMode::Standard}; + + // Current pad state + NPadGenericState npad_pad_state{}; + NPadGenericState npad_libnx_state{}; + NpadGcTriggerState npad_trigger_state{}; + SixAxisSensorState sixaxis_fullkey_state{}; + SixAxisSensorState sixaxis_handheld_state{}; + SixAxisSensorState sixaxis_dual_left_state{}; + SixAxisSensorState sixaxis_dual_right_state{}; + SixAxisSensorState sixaxis_left_lifo_state{}; + SixAxisSensorState sixaxis_right_lifo_state{}; + int callback_key; + }; + + void ControllerUpdate(Core::HID::ControllerTriggerType type, std::size_t controller_idx); + void InitNewlyAddedController(Core::HID::NpadIdType npad_id); + bool IsControllerSupported(Core::HID::NpadStyleIndex controller) const; + void RequestPadStateUpdate(Core::HID::NpadIdType npad_id); + void WriteEmptyEntry(NpadInternalState& npad); + + NpadControllerData& GetControllerFromHandle( + const Core::HID::SixAxisSensorHandle& device_handle); + const NpadControllerData& GetControllerFromHandle( + const Core::HID::SixAxisSensorHandle& device_handle) const; + NpadControllerData& GetControllerFromHandle( + const Core::HID::VibrationDeviceHandle& device_handle); + const NpadControllerData& GetControllerFromHandle( + const Core::HID::VibrationDeviceHandle& device_handle) const; + NpadControllerData& GetControllerFromNpadIdType(Core::HID::NpadIdType npad_id); + const NpadControllerData& GetControllerFromNpadIdType(Core::HID::NpadIdType npad_id) const; std::atomic press_state{}; - NpadStyleSet style{}; - std::array shared_memory_entries{}; - using ButtonArray = std::array< - std::array, Settings::NativeButton::NUM_BUTTONS_HID>, - 10>; - using StickArray = std::array< - std::array, Settings::NativeAnalog::NUM_STICKS_HID>, - 10>; - using VibrationArray = std::array, - Settings::NativeVibration::NUM_VIBRATIONS_HID>, - 10>; - using MotionArray = std::array< - std::array, Settings::NativeMotion::NUM_MOTIONS_HID>, - 10>; - + std::array controller_data{}; KernelHelpers::ServiceContext& service_context; std::mutex mutex; - ButtonArray buttons; - StickArray sticks; - VibrationArray vibrations; - MotionArray motions; - std::vector supported_npad_id_types{}; - NpadHoldType hold_type{NpadHoldType::Vertical}; + std::vector supported_npad_id_types{}; + NpadJoyHoldType hold_type{NpadJoyHoldType::Vertical}; NpadHandheldActivationMode handheld_activation_mode{NpadHandheldActivationMode::Dual}; NpadCommunicationMode communication_mode{NpadCommunicationMode::Default}; - // Each controller should have their own styleset changed event - std::array styleset_changed_events{}; - std::array, 10> - last_vibration_timepoints{}; - std::array, 10> latest_vibration_values{}; bool permit_vibration_session_enabled{false}; - std::array, 10> vibration_devices_mounted{}; - std::array connected_controllers{}; - std::array unintended_home_button_input_protection{}; bool analog_stick_use_center_clamp{}; - GyroscopeZeroDriftMode gyroscope_zero_drift_mode{GyroscopeZeroDriftMode::Standard}; - bool sixaxis_sensors_enabled{true}; - f32 sixaxis_fusion_parameter1{}; - f32 sixaxis_fusion_parameter2{}; - bool sixaxis_at_rest{true}; - std::array npad_pad_states{}; - std::array npad_trigger_states{}; bool is_in_lr_assignment_mode{false}; }; } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/stubbed.cpp b/src/core/hle/service/hid/controllers/stubbed.cpp index 772c20453d..b7d7a57560 100644 --- a/src/core/hle/service/hid/controllers/stubbed.cpp +++ b/src/core/hle/service/hid/controllers/stubbed.cpp @@ -5,11 +5,12 @@ #include #include "common/common_types.h" #include "core/core_timing.h" +#include "core/hid/hid_core.h" #include "core/hle/service/hid/controllers/stubbed.h" namespace Service::HID { -Controller_Stubbed::Controller_Stubbed(Core::System& system_) : ControllerBase{system_} {} +Controller_Stubbed::Controller_Stubbed(Core::HID::HIDCore& hid_core_) : ControllerBase{hid_core_} {} Controller_Stubbed::~Controller_Stubbed() = default; void Controller_Stubbed::OnInit() {} @@ -31,10 +32,9 @@ void Controller_Stubbed::OnUpdate(const Core::Timing::CoreTiming& core_timing, u std::memcpy(data + common_offset, &header, sizeof(CommonHeader)); } -void Controller_Stubbed::OnLoadInputDevices() {} - void Controller_Stubbed::SetCommonHeaderOffset(std::size_t off) { common_offset = off; smart_update = true; } + } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/stubbed.h b/src/core/hle/service/hid/controllers/stubbed.h index 21092af0d0..0044a4efaa 100644 --- a/src/core/hle/service/hid/controllers/stubbed.h +++ b/src/core/hle/service/hid/controllers/stubbed.h @@ -10,7 +10,7 @@ namespace Service::HID { class Controller_Stubbed final : public ControllerBase { public: - explicit Controller_Stubbed(Core::System& system_); + explicit Controller_Stubbed(Core::HID::HIDCore& hid_core_); ~Controller_Stubbed() override; // Called when the controller is initialized @@ -22,12 +22,17 @@ public: // When the controller is requesting an update for the shared memory void OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) override; - // Called when input devices should be loaded - void OnLoadInputDevices() override; - void SetCommonHeaderOffset(std::size_t off); private: + struct CommonHeader { + s64 timestamp; + s64 total_entry_count; + s64 last_entry_index; + s64 entry_count; + }; + static_assert(sizeof(CommonHeader) == 0x20, "CommonHeader is an invalid size"); + bool smart_update{}; std::size_t common_offset{}; }; diff --git a/src/core/hle/service/hid/controllers/touchscreen.cpp b/src/core/hle/service/hid/controllers/touchscreen.cpp index 6ef17acc51..48978e5c6a 100644 --- a/src/core/hle/service/hid/controllers/touchscreen.cpp +++ b/src/core/hle/service/hid/controllers/touchscreen.cpp @@ -7,72 +7,82 @@ #include "common/common_types.h" #include "common/logging/log.h" #include "common/settings.h" +#include "core/core.h" #include "core/core_timing.h" #include "core/frontend/emu_window.h" -#include "core/frontend/input.h" +#include "core/hid/emulated_console.h" +#include "core/hid/hid_core.h" #include "core/hle/service/hid/controllers/touchscreen.h" namespace Service::HID { constexpr std::size_t SHARED_MEMORY_OFFSET = 0x400; -Controller_Touchscreen::Controller_Touchscreen(Core::System& system_) : ControllerBase{system_} {} +Controller_Touchscreen::Controller_Touchscreen(Core::HID::HIDCore& hid_core_) + : ControllerBase{hid_core_} { + console = hid_core.GetEmulatedConsole(); +} + Controller_Touchscreen::~Controller_Touchscreen() = default; -void Controller_Touchscreen::OnInit() { - for (std::size_t id = 0; id < MAX_FINGERS; ++id) { - mouse_finger_id[id] = MAX_FINGERS; - keyboard_finger_id[id] = MAX_FINGERS; - udp_finger_id[id] = MAX_FINGERS; - } -} +void Controller_Touchscreen::OnInit() {} void Controller_Touchscreen::OnRelease() {} void Controller_Touchscreen::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) { - shared_memory.header.timestamp = core_timing.GetCPUTicks(); - shared_memory.header.total_entry_count = 17; + touch_screen_lifo.timestamp = core_timing.GetCPUTicks(); if (!IsControllerActivated()) { - shared_memory.header.entry_count = 0; - shared_memory.header.last_entry_index = 0; + touch_screen_lifo.buffer_count = 0; + touch_screen_lifo.buffer_tail = 0; + std::memcpy(data, &touch_screen_lifo, sizeof(touch_screen_lifo)); return; } - shared_memory.header.entry_count = 16; - const auto& last_entry = - shared_memory.shared_memory_entries[shared_memory.header.last_entry_index]; - shared_memory.header.last_entry_index = (shared_memory.header.last_entry_index + 1) % 17; - auto& cur_entry = shared_memory.shared_memory_entries[shared_memory.header.last_entry_index]; + const auto touch_status = console->GetTouch(); + for (std::size_t id = 0; id < MAX_FINGERS; id++) { + const auto& current_touch = touch_status[id]; + auto& finger = fingers[id]; + finger.position = current_touch.position; + finger.id = current_touch.id; - cur_entry.sampling_number = last_entry.sampling_number + 1; - cur_entry.sampling_number2 = cur_entry.sampling_number; + if (finger.attribute.start_touch) { + finger.attribute.raw = 0; + continue; + } - const Input::TouchStatus& mouse_status = touch_mouse_device->GetStatus(); - const Input::TouchStatus& udp_status = touch_udp_device->GetStatus(); - for (std::size_t id = 0; id < mouse_status.size(); ++id) { - mouse_finger_id[id] = UpdateTouchInputEvent(mouse_status[id], mouse_finger_id[id]); - udp_finger_id[id] = UpdateTouchInputEvent(udp_status[id], udp_finger_id[id]); - } + if (finger.attribute.end_touch) { + finger.attribute.raw = 0; + finger.pressed = false; + continue; + } - if (Settings::values.use_touch_from_button) { - const Input::TouchStatus& keyboard_status = touch_btn_device->GetStatus(); - for (std::size_t id = 0; id < mouse_status.size(); ++id) { - keyboard_finger_id[id] = - UpdateTouchInputEvent(keyboard_status[id], keyboard_finger_id[id]); + if (!finger.pressed && current_touch.pressed) { + finger.attribute.start_touch.Assign(1); + finger.pressed = true; + continue; + } + + if (finger.pressed && !current_touch.pressed) { + finger.attribute.raw = 0; + finger.attribute.end_touch.Assign(1); } } - std::array active_fingers; + std::array active_fingers; const auto end_iter = std::copy_if(fingers.begin(), fingers.end(), active_fingers.begin(), [](const auto& finger) { return finger.pressed; }); const auto active_fingers_count = static_cast(std::distance(active_fingers.begin(), end_iter)); const u64 tick = core_timing.GetCPUTicks(); - cur_entry.entry_count = static_cast(active_fingers_count); + const auto& last_entry = touch_screen_lifo.ReadCurrentEntry().state; + + next_state.sampling_number = last_entry.sampling_number + 1; + next_state.entry_count = static_cast(active_fingers_count); + for (std::size_t id = 0; id < MAX_FINGERS; ++id) { - auto& touch_entry = cur_entry.states[id]; + auto& touch_entry = next_state.states[id]; if (id < active_fingers_count) { const auto& [active_x, active_y] = active_fingers[id].position; touch_entry.position = { @@ -97,66 +107,9 @@ void Controller_Touchscreen::OnUpdate(const Core::Timing::CoreTiming& core_timin touch_entry.finger = 0; } } - std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(TouchScreenSharedMemory)); -} -void Controller_Touchscreen::OnLoadInputDevices() { - touch_mouse_device = Input::CreateDevice("engine:emu_window"); - touch_udp_device = Input::CreateDevice("engine:cemuhookudp"); - touch_btn_device = Input::CreateDevice("engine:touch_from_button"); -} - -std::optional Controller_Touchscreen::GetUnusedFingerID() const { - // Dont assign any touch input to a finger if disabled - if (!Settings::values.touchscreen.enabled) { - return std::nullopt; - } - std::size_t first_free_id = 0; - while (first_free_id < MAX_FINGERS) { - if (!fingers[first_free_id].pressed) { - return first_free_id; - } else { - first_free_id++; - } - } - return std::nullopt; -} - -std::size_t Controller_Touchscreen::UpdateTouchInputEvent( - const std::tuple& touch_input, std::size_t finger_id) { - const auto& [x, y, pressed] = touch_input; - if (finger_id > MAX_FINGERS) { - LOG_ERROR(Service_HID, "Invalid finger id {}", finger_id); - return MAX_FINGERS; - } - if (pressed) { - Attributes attribute{}; - if (finger_id == MAX_FINGERS) { - const auto first_free_id = GetUnusedFingerID(); - if (!first_free_id) { - // Invalid finger id do nothing - return MAX_FINGERS; - } - finger_id = first_free_id.value(); - fingers[finger_id].pressed = true; - fingers[finger_id].id = static_cast(finger_id); - attribute.start_touch.Assign(1); - } - fingers[finger_id].position = {x, y}; - fingers[finger_id].attribute = attribute; - return finger_id; - } - - if (finger_id != MAX_FINGERS) { - if (!fingers[finger_id].attribute.end_touch) { - fingers[finger_id].attribute.end_touch.Assign(1); - fingers[finger_id].attribute.start_touch.Assign(0); - return finger_id; - } - fingers[finger_id].pressed = false; - } - - return MAX_FINGERS; + touch_screen_lifo.WriteNextEntry(next_state); + std::memcpy(data + SHARED_MEMORY_OFFSET, &touch_screen_lifo, sizeof(touch_screen_lifo)); } } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/touchscreen.h b/src/core/hle/service/hid/controllers/touchscreen.h index 8e9b40c0a3..708dde4f08 100644 --- a/src/core/hle/service/hid/controllers/touchscreen.h +++ b/src/core/hle/service/hid/controllers/touchscreen.h @@ -9,18 +9,25 @@ #include "common/common_types.h" #include "common/point.h" #include "common/swap.h" -#include "core/frontend/input.h" +#include "core/hid/hid_types.h" #include "core/hle/service/hid/controllers/controller_base.h" +#include "core/hle/service/hid/ring_lifo.h" + +namespace Core::HID { +class EmulatedConsole; +} // namespace Core::HID namespace Service::HID { class Controller_Touchscreen final : public ControllerBase { public: + // This is nn::hid::TouchScreenModeForNx enum class TouchScreenModeForNx : u8 { UseSystemSetting, Finger, Heat2, }; + // This is nn::hid::TouchScreenConfigurationForNx struct TouchScreenConfigurationForNx { TouchScreenModeForNx mode; INSERT_PADDING_BYTES_NOINIT(0x7); @@ -29,7 +36,7 @@ public: static_assert(sizeof(TouchScreenConfigurationForNx) == 0x17, "TouchScreenConfigurationForNx is an invalid size"); - explicit Controller_Touchscreen(Core::System& system_); + explicit Controller_Touchscreen(Core::HID::HIDCore& hid_core_); ~Controller_Touchscreen() override; // Called when the controller is initialized @@ -41,73 +48,24 @@ public: // When the controller is requesting an update for the shared memory void OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) override; - // Called when input devices should be loaded - void OnLoadInputDevices() override; - private: static constexpr std::size_t MAX_FINGERS = 16; - // Returns an unused finger id, if there is no fingers available std::nullopt will be returned - std::optional GetUnusedFingerID() const; - - // If the touch is new it tries to assing a new finger id, if there is no fingers avaliable no - // changes will be made. Updates the coordinates if the finger id it's already set. If the touch - // ends delays the output by one frame to set the end_touch flag before finally freeing the - // finger id - std::size_t UpdateTouchInputEvent(const std::tuple& touch_input, - std::size_t finger_id); - - struct Attributes { - union { - u32 raw{}; - BitField<0, 1, u32> start_touch; - BitField<1, 1, u32> end_touch; - }; + // This is nn::hid::TouchScreenState + struct TouchScreenState { + s64 sampling_number; + s32 entry_count; + INSERT_PADDING_BYTES(4); // Reserved + std::array states; }; - static_assert(sizeof(Attributes) == 0x4, "Attributes is an invalid size"); + static_assert(sizeof(TouchScreenState) == 0x290, "TouchScreenState is an invalid size"); - struct TouchState { - u64_le delta_time; - Attributes attribute; - u32_le finger; - Common::Point position; - u32_le diameter_x; - u32_le diameter_y; - u32_le rotation_angle; - }; - static_assert(sizeof(TouchState) == 0x28, "Touchstate is an invalid size"); + // This is nn::hid::detail::TouchScreenLifo + Lifo touch_screen_lifo{}; + static_assert(sizeof(touch_screen_lifo) == 0x2C38, "touch_screen_lifo is an invalid size"); + TouchScreenState next_state{}; - struct TouchScreenEntry { - s64_le sampling_number; - s64_le sampling_number2; - s32_le entry_count; - std::array states; - }; - static_assert(sizeof(TouchScreenEntry) == 0x298, "TouchScreenEntry is an invalid size"); - - struct TouchScreenSharedMemory { - CommonHeader header; - std::array shared_memory_entries{}; - INSERT_PADDING_BYTES(0x3c8); - }; - static_assert(sizeof(TouchScreenSharedMemory) == 0x3000, - "TouchScreenSharedMemory is an invalid size"); - - struct Finger { - u64_le last_touch{}; - Common::Point position; - u32_le id{}; - bool pressed{}; - Attributes attribute; - }; - - TouchScreenSharedMemory shared_memory{}; - std::unique_ptr touch_mouse_device; - std::unique_ptr touch_udp_device; - std::unique_ptr touch_btn_device; - std::array mouse_finger_id; - std::array keyboard_finger_id; - std::array udp_finger_id; - std::array fingers; + std::array fingers; + Core::HID::EmulatedConsole* console; }; } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/xpad.cpp b/src/core/hle/service/hid/controllers/xpad.cpp index 41dc22cf96..e4da164665 100644 --- a/src/core/hle/service/hid/controllers/xpad.cpp +++ b/src/core/hle/service/hid/controllers/xpad.cpp @@ -5,12 +5,13 @@ #include #include "common/common_types.h" #include "core/core_timing.h" +#include "core/hid/hid_core.h" #include "core/hle/service/hid/controllers/xpad.h" namespace Service::HID { constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3C00; -Controller_XPad::Controller_XPad(Core::System& system_) : ControllerBase{system_} {} +Controller_XPad::Controller_XPad(Core::HID::HIDCore& hid_core_) : ControllerBase{hid_core_} {} Controller_XPad::~Controller_XPad() = default; void Controller_XPad::OnInit() {} @@ -19,28 +20,19 @@ void Controller_XPad::OnRelease() {} void Controller_XPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) { - for (auto& xpad_entry : shared_memory.shared_memory_entries) { - xpad_entry.header.timestamp = core_timing.GetCPUTicks(); - xpad_entry.header.total_entry_count = 17; - - if (!IsControllerActivated()) { - xpad_entry.header.entry_count = 0; - xpad_entry.header.last_entry_index = 0; - return; - } - xpad_entry.header.entry_count = 16; - - const auto& last_entry = xpad_entry.pad_states[xpad_entry.header.last_entry_index]; - xpad_entry.header.last_entry_index = (xpad_entry.header.last_entry_index + 1) % 17; - auto& cur_entry = xpad_entry.pad_states[xpad_entry.header.last_entry_index]; - - cur_entry.sampling_number = last_entry.sampling_number + 1; - cur_entry.sampling_number2 = cur_entry.sampling_number; + if (!IsControllerActivated()) { + basic_xpad_lifo.buffer_count = 0; + basic_xpad_lifo.buffer_tail = 0; + std::memcpy(data + SHARED_MEMORY_OFFSET, &basic_xpad_lifo, sizeof(basic_xpad_lifo)); + return; } + + const auto& last_entry = basic_xpad_lifo.ReadCurrentEntry().state; + next_state.sampling_number = last_entry.sampling_number + 1; // TODO(ogniK): Update xpad states - std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(SharedMemory)); + basic_xpad_lifo.WriteNextEntry(next_state); + std::memcpy(data + SHARED_MEMORY_OFFSET, &basic_xpad_lifo, sizeof(basic_xpad_lifo)); } -void Controller_XPad::OnLoadInputDevices() {} } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/xpad.h b/src/core/hle/service/hid/controllers/xpad.h index f9ab5facf5..ba8db8d9d8 100644 --- a/src/core/hle/service/hid/controllers/xpad.h +++ b/src/core/hle/service/hid/controllers/xpad.h @@ -8,12 +8,14 @@ #include "common/common_funcs.h" #include "common/common_types.h" #include "common/swap.h" +#include "core/hid/hid_types.h" #include "core/hle/service/hid/controllers/controller_base.h" +#include "core/hle/service/hid/ring_lifo.h" namespace Service::HID { class Controller_XPad final : public ControllerBase { public: - explicit Controller_XPad(Core::System& system_); + explicit Controller_XPad(Core::HID::HIDCore& hid_core_); ~Controller_XPad() override; // Called when the controller is initialized @@ -25,13 +27,11 @@ public: // When the controller is requesting an update for the shared memory void OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) override; - // Called when input devices should be loaded - void OnLoadInputDevices() override; - private: - struct Attributes { + // This is nn::hid::BasicXpadAttributeSet + struct BasicXpadAttributeSet { union { - u32_le raw{}; + u32 raw{}; BitField<0, 1, u32> is_connected; BitField<1, 1, u32> is_wired; BitField<2, 1, u32> is_left_connected; @@ -40,11 +40,12 @@ private: BitField<5, 1, u32> is_right_wired; }; }; - static_assert(sizeof(Attributes) == 4, "Attributes is an invalid size"); + static_assert(sizeof(BasicXpadAttributeSet) == 4, "BasicXpadAttributeSet is an invalid size"); - struct Buttons { + // This is nn::hid::BasicXpadButtonSet + struct BasicXpadButtonSet { union { - u32_le raw{}; + u32 raw{}; // Button states BitField<0, 1, u32> a; BitField<1, 1, u32> b; @@ -88,35 +89,21 @@ private: BitField<30, 1, u32> handheld_left_b; }; }; - static_assert(sizeof(Buttons) == 4, "Buttons is an invalid size"); + static_assert(sizeof(BasicXpadButtonSet) == 4, "BasicXpadButtonSet is an invalid size"); - struct AnalogStick { - s32_le x; - s32_le y; + // This is nn::hid::detail::BasicXpadState + struct BasicXpadState { + s64 sampling_number; + BasicXpadAttributeSet attributes; + BasicXpadButtonSet pad_states; + Core::HID::AnalogStickState l_stick; + Core::HID::AnalogStickState r_stick; }; - static_assert(sizeof(AnalogStick) == 0x8, "AnalogStick is an invalid size"); + static_assert(sizeof(BasicXpadState) == 0x20, "BasicXpadState is an invalid size"); - struct XPadState { - s64_le sampling_number; - s64_le sampling_number2; - Attributes attributes; - Buttons pad_states; - AnalogStick l_stick; - AnalogStick r_stick; - }; - static_assert(sizeof(XPadState) == 0x28, "XPadState is an invalid size"); - - struct XPadEntry { - CommonHeader header; - std::array pad_states{}; - INSERT_PADDING_BYTES(0x138); - }; - static_assert(sizeof(XPadEntry) == 0x400, "XPadEntry is an invalid size"); - - struct SharedMemory { - std::array shared_memory_entries{}; - }; - static_assert(sizeof(SharedMemory) == 0x1000, "SharedMemory is an invalid size"); - SharedMemory shared_memory{}; + // This is nn::hid::detail::BasicXpadLifo + Lifo basic_xpad_lifo{}; + static_assert(sizeof(basic_xpad_lifo) == 0x2C8, "basic_xpad_lifo is an invalid size"); + BasicXpadState next_state{}; }; } // namespace Service::HID diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 10c64d41a3..95fc07325b 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -8,7 +8,7 @@ #include "common/settings.h" #include "core/core.h" #include "core/core_timing.h" -#include "core/frontend/input.h" +#include "core/hid/hid_core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/k_readable_event.h" #include "core/hle/kernel/k_shared_memory.h" @@ -34,10 +34,10 @@ namespace Service::HID { // Updating period for each HID device. -// HID is polled every 15ms, this value was derived from -// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering#joy-con-status-data-packet -constexpr auto pad_update_ns = std::chrono::nanoseconds{1000 * 1000}; // (1ms, 1000Hz) -constexpr auto motion_update_ns = std::chrono::nanoseconds{15 * 1000 * 1000}; // (15ms, 66.666Hz) +// Period time is obtained by measuring the number of samples in a second on HW using a homebrew +constexpr auto pad_update_ns = std::chrono::nanoseconds{4 * 1000 * 1000}; // (4ms, 250Hz) +constexpr auto mouse_keyboard_update_ns = std::chrono::nanoseconds{8 * 1000 * 1000}; // (8ms, 125Hz) +constexpr auto motion_update_ns = std::chrono::nanoseconds{5 * 1000 * 1000}; // (5ms, 200Hz) constexpr std::size_t SHARED_MEMORY_SIZE = 0x40000; IAppletResource::IAppletResource(Core::System& system_, @@ -79,17 +79,24 @@ IAppletResource::IAppletResource(Core::System& system_, const auto guard = LockService(); UpdateControllers(user_data, ns_late); }); + mouse_keyboard_update_event = Core::Timing::CreateEvent( + "HID::UpdateMouseKeyboardCallback", + [this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) { + const auto guard = LockService(); + UpdateMouseKeyboard(user_data, ns_late); + }); motion_update_event = Core::Timing::CreateEvent( - "HID::MotionPadCallback", + "HID::UpdateMotionCallback", [this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) { const auto guard = LockService(); UpdateMotion(user_data, ns_late); }); system.CoreTiming().ScheduleEvent(pad_update_ns, pad_update_event); + system.CoreTiming().ScheduleEvent(mouse_keyboard_update_ns, mouse_keyboard_update_event); system.CoreTiming().ScheduleEvent(motion_update_ns, motion_update_event); - ReloadInputDevices(); + system.HIDCore().ReloadInputDevices(); } void IAppletResource::ActivateController(HidController controller) { @@ -102,6 +109,7 @@ void IAppletResource::DeactivateController(HidController controller) { IAppletResource::~IAppletResource() { system.CoreTiming().UnscheduleEvent(pad_update_event, 0); + system.CoreTiming().UnscheduleEvent(mouse_keyboard_update_event, 0); system.CoreTiming().UnscheduleEvent(motion_update_event, 0); } @@ -117,23 +125,44 @@ void IAppletResource::UpdateControllers(std::uintptr_t user_data, std::chrono::nanoseconds ns_late) { auto& core_timing = system.CoreTiming(); - const bool should_reload = Settings::values.is_device_reload_pending.exchange(false); for (const auto& controller : controllers) { - if (should_reload) { - controller->OnLoadInputDevices(); + // Keyboard has it's own update event + if (controller == controllers[static_cast(HidController::Keyboard)]) { + continue; + } + // Mouse has it's own update event + if (controller == controllers[static_cast(HidController::Mouse)]) { + continue; } controller->OnUpdate(core_timing, system.Kernel().GetHidSharedMem().GetPointer(), SHARED_MEMORY_SIZE); } // If ns_late is higher than the update rate ignore the delay - if (ns_late > motion_update_ns) { + if (ns_late > pad_update_ns) { ns_late = {}; } core_timing.ScheduleEvent(pad_update_ns - ns_late, pad_update_event); } +void IAppletResource::UpdateMouseKeyboard(std::uintptr_t user_data, + std::chrono::nanoseconds ns_late) { + auto& core_timing = system.CoreTiming(); + + controllers[static_cast(HidController::Mouse)]->OnUpdate( + core_timing, system.Kernel().GetHidSharedMem().GetPointer(), SHARED_MEMORY_SIZE); + controllers[static_cast(HidController::Keyboard)]->OnUpdate( + core_timing, system.Kernel().GetHidSharedMem().GetPointer(), SHARED_MEMORY_SIZE); + + // If ns_late is higher than the update rate ignore the delay + if (ns_late > mouse_keyboard_update_ns) { + ns_late = {}; + } + + core_timing.ScheduleEvent(mouse_keyboard_update_ns - ns_late, mouse_keyboard_update_event); +} + void IAppletResource::UpdateMotion(std::uintptr_t user_data, std::chrono::nanoseconds ns_late) { auto& core_timing = system.CoreTiming(); @@ -166,7 +195,7 @@ public: private: void InitializeVibrationDevice(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto vibration_device_handle{rp.PopRaw()}; + const auto vibration_device_handle{rp.PopRaw()}; if (applet_resource != nullptr) { applet_resource->GetController(HidController::NPad) @@ -422,6 +451,7 @@ void Hid::ActivateXpad(Kernel::HLERequestContext& ctx) { INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; }; + static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); const auto parameters{rp.PopRaw()}; @@ -448,19 +478,18 @@ void Hid::GetXpadIDs(Kernel::HLERequestContext& ctx) { void Hid::ActivateSixAxisSensor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - Controller_NPad::DeviceHandle sixaxis_handle; + u32 basic_xpad_id; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; }; + static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); const auto parameters{rp.PopRaw()}; - applet_resource->GetController(HidController::NPad).SetSixAxisEnabled(true); + // This function does nothing on 10.0.0+ - LOG_DEBUG(Service_HID, - "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", - parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, - parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); + LOG_WARNING(Service_HID, "(STUBBED) called, basic_xpad_id={}, applet_resource_user_id={}", + parameters.basic_xpad_id, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); @@ -469,19 +498,18 @@ void Hid::ActivateSixAxisSensor(Kernel::HLERequestContext& ctx) { void Hid::DeactivateSixAxisSensor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - Controller_NPad::DeviceHandle sixaxis_handle; + u32 basic_xpad_id; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; }; + static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); const auto parameters{rp.PopRaw()}; - applet_resource->GetController(HidController::NPad).SetSixAxisEnabled(false); + // This function does nothing on 10.0.0+ - LOG_DEBUG(Service_HID, - "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", - parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, - parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); + LOG_WARNING(Service_HID, "(STUBBED) called, basic_xpad_id={}, applet_resource_user_id={}", + parameters.basic_xpad_id, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); @@ -490,14 +518,16 @@ void Hid::DeactivateSixAxisSensor(Kernel::HLERequestContext& ctx) { void Hid::StartSixAxisSensor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - Controller_NPad::DeviceHandle sixaxis_handle; + Core::HID::SixAxisSensorHandle sixaxis_handle; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; }; + static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); const auto parameters{rp.PopRaw()}; - applet_resource->GetController(HidController::NPad).SetSixAxisEnabled(true); + applet_resource->GetController(HidController::NPad) + .SetSixAxisEnabled(parameters.sixaxis_handle, true); LOG_DEBUG(Service_HID, "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", @@ -511,14 +541,16 @@ void Hid::StartSixAxisSensor(Kernel::HLERequestContext& ctx) { void Hid::StopSixAxisSensor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - Controller_NPad::DeviceHandle sixaxis_handle; + Core::HID::SixAxisSensorHandle sixaxis_handle; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; }; + static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); const auto parameters{rp.PopRaw()}; - applet_resource->GetController(HidController::NPad).SetSixAxisEnabled(false); + applet_resource->GetController(HidController::NPad) + .SetSixAxisEnabled(parameters.sixaxis_handle, false); LOG_DEBUG(Service_HID, "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", @@ -534,19 +566,23 @@ void Hid::EnableSixAxisSensorFusion(Kernel::HLERequestContext& ctx) { struct Parameters { bool enable_sixaxis_sensor_fusion; INSERT_PADDING_BYTES_NOINIT(3); - Controller_NPad::DeviceHandle sixaxis_handle; + Core::HID::SixAxisSensorHandle sixaxis_handle; u64 applet_resource_user_id; }; static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); const auto parameters{rp.PopRaw()}; - LOG_WARNING(Service_HID, - "(STUBBED) called, enable_sixaxis_sensor_fusion={}, npad_type={}, npad_id={}, " - "device_index={}, applet_resource_user_id={}", - parameters.enable_sixaxis_sensor_fusion, parameters.sixaxis_handle.npad_type, - parameters.sixaxis_handle.npad_id, parameters.sixaxis_handle.device_index, - parameters.applet_resource_user_id); + applet_resource->GetController(HidController::NPad) + .SetSixAxisFusionEnabled(parameters.sixaxis_handle, + parameters.enable_sixaxis_sensor_fusion); + + LOG_DEBUG(Service_HID, + "called, enable_sixaxis_sensor_fusion={}, npad_type={}, npad_id={}, " + "device_index={}, applet_resource_user_id={}", + parameters.enable_sixaxis_sensor_fusion, parameters.sixaxis_handle.npad_type, + parameters.sixaxis_handle.npad_id, parameters.sixaxis_handle.device_index, + parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); @@ -555,9 +591,9 @@ void Hid::EnableSixAxisSensorFusion(Kernel::HLERequestContext& ctx) { void Hid::SetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - Controller_NPad::DeviceHandle sixaxis_handle; - f32 parameter1; - f32 parameter2; + Core::HID::SixAxisSensorHandle sixaxis_handle; + Core::HID::SixAxisSensorFusionParameters sixaxis_fusion; + INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; }; static_assert(sizeof(Parameters) == 0x18, "Parameters has incorrect size."); @@ -565,14 +601,14 @@ void Hid::SetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx) { const auto parameters{rp.PopRaw()}; applet_resource->GetController(HidController::NPad) - .SetSixAxisFusionParameters(parameters.parameter1, parameters.parameter2); + .SetSixAxisFusionParameters(parameters.sixaxis_handle, parameters.sixaxis_fusion); - LOG_WARNING(Service_HID, - "(STUBBED) called, npad_type={}, npad_id={}, device_index={}, parameter1={}, " - "parameter2={}, applet_resource_user_id={}", - parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, - parameters.sixaxis_handle.device_index, parameters.parameter1, - parameters.parameter2, parameters.applet_resource_user_id); + LOG_DEBUG(Service_HID, + "called, npad_type={}, npad_id={}, device_index={}, parameter1={}, " + "parameter2={}, applet_resource_user_id={}", + parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, + parameters.sixaxis_handle.device_index, parameters.sixaxis_fusion.parameter1, + parameters.sixaxis_fusion.parameter2, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); @@ -581,35 +617,33 @@ void Hid::SetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx) { void Hid::GetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - Controller_NPad::DeviceHandle sixaxis_handle; + Core::HID::SixAxisSensorHandle sixaxis_handle; + INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; }; static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); - f32 parameter1 = 0; - f32 parameter2 = 0; const auto parameters{rp.PopRaw()}; - std::tie(parameter1, parameter2) = + const auto sixaxis_fusion_parameters = applet_resource->GetController(HidController::NPad) - .GetSixAxisFusionParameters(); + .GetSixAxisFusionParameters(parameters.sixaxis_handle); - LOG_WARNING( - Service_HID, - "(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", - parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, - parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); + LOG_DEBUG(Service_HID, + "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, + parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(ResultSuccess); - rb.Push(parameter1); - rb.Push(parameter2); + rb.PushRaw(sixaxis_fusion_parameters); } void Hid::ResetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - Controller_NPad::DeviceHandle sixaxis_handle; + Core::HID::SixAxisSensorHandle sixaxis_handle; + INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; }; static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); @@ -617,13 +651,12 @@ void Hid::ResetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx) { const auto parameters{rp.PopRaw()}; applet_resource->GetController(HidController::NPad) - .ResetSixAxisFusionParameters(); + .ResetSixAxisFusionParameters(parameters.sixaxis_handle); - LOG_WARNING( - Service_HID, - "(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", - parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, - parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); + LOG_DEBUG(Service_HID, + "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, + parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); @@ -631,12 +664,12 @@ void Hid::ResetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx) { void Hid::SetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto sixaxis_handle{rp.PopRaw()}; + const auto sixaxis_handle{rp.PopRaw()}; const auto drift_mode{rp.PopEnum()}; const auto applet_resource_user_id{rp.Pop()}; applet_resource->GetController(HidController::NPad) - .SetGyroscopeZeroDriftMode(drift_mode); + .SetGyroscopeZeroDriftMode(sixaxis_handle, drift_mode); LOG_DEBUG(Service_HID, "called, npad_type={}, npad_id={}, device_index={}, drift_mode={}, " @@ -651,10 +684,11 @@ void Hid::SetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) { void Hid::GetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - Controller_NPad::DeviceHandle sixaxis_handle; + Core::HID::SixAxisSensorHandle sixaxis_handle; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; }; + static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); const auto parameters{rp.PopRaw()}; @@ -666,21 +700,23 @@ void Hid::GetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); rb.PushEnum(applet_resource->GetController(HidController::NPad) - .GetGyroscopeZeroDriftMode()); + .GetGyroscopeZeroDriftMode(parameters.sixaxis_handle)); } void Hid::ResetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - Controller_NPad::DeviceHandle sixaxis_handle; + Core::HID::SixAxisSensorHandle sixaxis_handle; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; }; + static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); const auto parameters{rp.PopRaw()}; + const auto drift_mode{Controller_NPad::GyroscopeZeroDriftMode::Standard}; applet_resource->GetController(HidController::NPad) - .SetGyroscopeZeroDriftMode(Controller_NPad::GyroscopeZeroDriftMode::Standard); + .SetGyroscopeZeroDriftMode(parameters.sixaxis_handle, drift_mode); LOG_DEBUG(Service_HID, "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", @@ -694,10 +730,11 @@ void Hid::ResetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) { void Hid::IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - Controller_NPad::DeviceHandle sixaxis_handle; + Core::HID::SixAxisSensorHandle sixaxis_handle; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; }; + static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); const auto parameters{rp.PopRaw()}; @@ -709,16 +746,17 @@ void Hid::IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); rb.Push(applet_resource->GetController(HidController::NPad) - .IsSixAxisSensorAtRest()); + .IsSixAxisSensorAtRest(parameters.sixaxis_handle)); } void Hid::IsFirmwareUpdateAvailableForSixAxisSensor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - Controller_NPad::DeviceHandle sixaxis_handle; + Core::HID::SixAxisSensorHandle sixaxis_handle; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; }; + static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); const auto parameters{rp.PopRaw()}; @@ -740,13 +778,14 @@ void Hid::ActivateGesture(Kernel::HLERequestContext& ctx) { INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; }; + static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); const auto parameters{rp.PopRaw()}; applet_resource->ActivateController(HidController::Gesture); - LOG_DEBUG(Service_HID, "called, unknown={}, applet_resource_user_id={}", parameters.unknown, - parameters.applet_resource_user_id); + LOG_WARNING(Service_HID, "(STUBBED) called, unknown={}, applet_resource_user_id={}", + parameters.unknown, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); @@ -754,12 +793,20 @@ void Hid::ActivateGesture(Kernel::HLERequestContext& ctx) { void Hid::SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto supported_styleset{rp.Pop()}; + struct Parameters { + Core::HID::NpadStyleSet supported_styleset; + INSERT_PADDING_WORDS_NOINIT(1); + u64 applet_resource_user_id; + }; + static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); + + const auto parameters{rp.PopRaw()}; applet_resource->GetController(HidController::NPad) - .SetSupportedStyleSet({supported_styleset}); + .SetSupportedStyleSet({parameters.supported_styleset}); - LOG_DEBUG(Service_HID, "called, supported_styleset={}", supported_styleset); + LOG_DEBUG(Service_HID, "called, supported_styleset={}, applet_resource_user_id={}", + parameters.supported_styleset, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); @@ -773,9 +820,9 @@ void Hid::GetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push(applet_resource->GetController(HidController::NPad) - .GetSupportedStyleSet() - .raw); + rb.PushEnum(applet_resource->GetController(HidController::NPad) + .GetSupportedStyleSet() + .raw); } void Hid::SetSupportedNpadIdType(Kernel::HLERequestContext& ctx) { @@ -818,11 +865,12 @@ void Hid::DeactivateNpad(Kernel::HLERequestContext& ctx) { void Hid::AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - u32 npad_id; + Core::HID::NpadIdType npad_id; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; u64 unknown; }; + static_assert(sizeof(Parameters) == 0x18, "Parameters has incorrect size."); const auto parameters{rp.PopRaw()}; @@ -838,10 +886,11 @@ void Hid::AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx) { void Hid::DisconnectNpad(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - u32 npad_id; + Core::HID::NpadIdType npad_id; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; }; + static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); const auto parameters{rp.PopRaw()}; @@ -857,7 +906,7 @@ void Hid::DisconnectNpad(Kernel::HLERequestContext& ctx) { void Hid::GetPlayerLedPattern(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto npad_id{rp.Pop()}; + const auto npad_id{rp.PopEnum()}; LOG_DEBUG(Service_HID, "called, npad_id={}", npad_id); @@ -872,16 +921,17 @@ void Hid::ActivateNpadWithRevision(Kernel::HLERequestContext& ctx) { // Should have no effect with how our npad sets up the data IPC::RequestParser rp{ctx}; struct Parameters { - u32 unknown; + s32 revision; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; }; + static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); const auto parameters{rp.PopRaw()}; applet_resource->ActivateController(HidController::NPad); - LOG_DEBUG(Service_HID, "called, unknown={}, applet_resource_user_id={}", parameters.unknown, + LOG_DEBUG(Service_HID, "called, revision={}, applet_resource_user_id={}", parameters.revision, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; @@ -891,7 +941,7 @@ void Hid::ActivateNpadWithRevision(Kernel::HLERequestContext& ctx) { void Hid::SetNpadJoyHoldType(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_resource_user_id{rp.Pop()}; - const auto hold_type{rp.PopEnum()}; + const auto hold_type{rp.PopEnum()}; applet_resource->GetController(HidController::NPad).SetHoldType(hold_type); @@ -916,15 +966,16 @@ void Hid::GetNpadJoyHoldType(Kernel::HLERequestContext& ctx) { void Hid::SetNpadJoyAssignmentModeSingleByDefault(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - u32 npad_id; + Core::HID::NpadIdType npad_id; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; }; + static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); const auto parameters{rp.PopRaw()}; applet_resource->GetController(HidController::NPad) - .SetNpadMode(parameters.npad_id, Controller_NPad::NpadAssignments::Single); + .SetNpadMode(parameters.npad_id, Controller_NPad::NpadJoyAssignmentMode::Single); LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}", parameters.npad_id, parameters.applet_resource_user_id); @@ -937,16 +988,17 @@ void Hid::SetNpadJoyAssignmentModeSingle(Kernel::HLERequestContext& ctx) { // TODO: Check the differences between this and SetNpadJoyAssignmentModeSingleByDefault IPC::RequestParser rp{ctx}; struct Parameters { - u32 npad_id; + Core::HID::NpadIdType npad_id; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; u64 npad_joy_device_type; }; + static_assert(sizeof(Parameters) == 0x18, "Parameters has incorrect size."); const auto parameters{rp.PopRaw()}; applet_resource->GetController(HidController::NPad) - .SetNpadMode(parameters.npad_id, Controller_NPad::NpadAssignments::Single); + .SetNpadMode(parameters.npad_id, Controller_NPad::NpadJoyAssignmentMode::Single); LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}, npad_joy_device_type={}", @@ -960,15 +1012,16 @@ void Hid::SetNpadJoyAssignmentModeSingle(Kernel::HLERequestContext& ctx) { void Hid::SetNpadJoyAssignmentModeDual(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - u32 npad_id; + Core::HID::NpadIdType npad_id; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; }; + static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); const auto parameters{rp.PopRaw()}; applet_resource->GetController(HidController::NPad) - .SetNpadMode(parameters.npad_id, Controller_NPad::NpadAssignments::Dual); + .SetNpadMode(parameters.npad_id, Controller_NPad::NpadJoyAssignmentMode::Dual); LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}", parameters.npad_id, parameters.applet_resource_user_id); @@ -979,8 +1032,8 @@ void Hid::SetNpadJoyAssignmentModeDual(Kernel::HLERequestContext& ctx) { void Hid::MergeSingleJoyAsDualJoy(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto npad_id_1{rp.Pop()}; - const auto npad_id_2{rp.Pop()}; + const auto npad_id_1{rp.PopEnum()}; + const auto npad_id_2{rp.PopEnum()}; const auto applet_resource_user_id{rp.Pop()}; applet_resource->GetController(HidController::NPad) @@ -1046,8 +1099,8 @@ void Hid::GetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx) { void Hid::SwapNpadAssignment(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto npad_id_1{rp.Pop()}; - const auto npad_id_2{rp.Pop()}; + const auto npad_id_1{rp.PopEnum()}; + const auto npad_id_2{rp.PopEnum()}; const auto applet_resource_user_id{rp.Pop()}; const bool res = applet_resource->GetController(HidController::NPad) @@ -1068,10 +1121,11 @@ void Hid::SwapNpadAssignment(Kernel::HLERequestContext& ctx) { void Hid::IsUnintendedHomeButtonInputProtectionEnabled(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - u32 npad_id; + Core::HID::NpadIdType npad_id; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; }; + static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); const auto parameters{rp.PopRaw()}; @@ -1089,9 +1143,10 @@ void Hid::EnableUnintendedHomeButtonInputProtection(Kernel::HLERequestContext& c struct Parameters { bool unintended_home_button_input_protection; INSERT_PADDING_BYTES_NOINIT(3); - u32 npad_id; + Core::HID::NpadIdType npad_id; u64 applet_resource_user_id; }; + static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); const auto parameters{rp.PopRaw()}; @@ -1113,6 +1168,7 @@ void Hid::SetNpadAnalogStickUseCenterClamp(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { bool analog_stick_use_center_clamp; + INSERT_PADDING_BYTES_NOINIT(7); u64 applet_resource_user_id; }; static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); @@ -1132,38 +1188,38 @@ void Hid::SetNpadAnalogStickUseCenterClamp(Kernel::HLERequestContext& ctx) { void Hid::GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto vibration_device_handle{rp.PopRaw()}; + const auto vibration_device_handle{rp.PopRaw()}; - VibrationDeviceInfo vibration_device_info; + Core::HID::VibrationDeviceInfo vibration_device_info; switch (vibration_device_handle.npad_type) { - case Controller_NPad::NpadType::ProController: - case Controller_NPad::NpadType::Handheld: - case Controller_NPad::NpadType::JoyconDual: - case Controller_NPad::NpadType::JoyconLeft: - case Controller_NPad::NpadType::JoyconRight: + case Core::HID::NpadStyleIndex::ProController: + case Core::HID::NpadStyleIndex::Handheld: + case Core::HID::NpadStyleIndex::JoyconDual: + case Core::HID::NpadStyleIndex::JoyconLeft: + case Core::HID::NpadStyleIndex::JoyconRight: default: - vibration_device_info.type = VibrationDeviceType::LinearResonantActuator; + vibration_device_info.type = Core::HID::VibrationDeviceType::LinearResonantActuator; break; - case Controller_NPad::NpadType::GameCube: - vibration_device_info.type = VibrationDeviceType::GcErm; + case Core::HID::NpadStyleIndex::GameCube: + vibration_device_info.type = Core::HID::VibrationDeviceType::GcErm; break; - case Controller_NPad::NpadType::Pokeball: - vibration_device_info.type = VibrationDeviceType::Unknown; + case Core::HID::NpadStyleIndex::Pokeball: + vibration_device_info.type = Core::HID::VibrationDeviceType::Unknown; break; } switch (vibration_device_handle.device_index) { - case Controller_NPad::DeviceIndex::Left: - vibration_device_info.position = VibrationDevicePosition::Left; + case Core::HID::DeviceIndex::Left: + vibration_device_info.position = Core::HID::VibrationDevicePosition::Left; break; - case Controller_NPad::DeviceIndex::Right: - vibration_device_info.position = VibrationDevicePosition::Right; + case Core::HID::DeviceIndex::Right: + vibration_device_info.position = Core::HID::VibrationDevicePosition::Right; break; - case Controller_NPad::DeviceIndex::None: + case Core::HID::DeviceIndex::None: default: UNREACHABLE_MSG("DeviceIndex should never be None!"); - vibration_device_info.position = VibrationDevicePosition::None; + vibration_device_info.position = Core::HID::VibrationDevicePosition::None; break; } @@ -1178,11 +1234,12 @@ void Hid::GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx) { void Hid::SendVibrationValue(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - Controller_NPad::DeviceHandle vibration_device_handle; - Controller_NPad::VibrationValue vibration_value; + Core::HID::VibrationDeviceHandle vibration_device_handle; + Core::HID::VibrationValue vibration_value; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; }; + static_assert(sizeof(Parameters) == 0x20, "Parameters has incorrect size."); const auto parameters{rp.PopRaw()}; @@ -1202,10 +1259,11 @@ void Hid::SendVibrationValue(Kernel::HLERequestContext& ctx) { void Hid::GetActualVibrationValue(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - Controller_NPad::DeviceHandle vibration_device_handle; + Core::HID::VibrationDeviceHandle vibration_device_handle; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; }; + static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); const auto parameters{rp.PopRaw()}; @@ -1256,10 +1314,10 @@ void Hid::SendVibrationValues(Kernel::HLERequestContext& ctx) { const auto handles = ctx.ReadBuffer(0); const auto vibrations = ctx.ReadBuffer(1); - std::vector vibration_device_handles( - handles.size() / sizeof(Controller_NPad::DeviceHandle)); - std::vector vibration_values( - vibrations.size() / sizeof(Controller_NPad::VibrationValue)); + std::vector vibration_device_handles( + handles.size() / sizeof(Core::HID::VibrationDeviceHandle)); + std::vector vibration_values(vibrations.size() / + sizeof(Core::HID::VibrationValue)); std::memcpy(vibration_device_handles.data(), handles.data(), handles.size()); std::memcpy(vibration_values.data(), vibrations.data(), vibrations.size()); @@ -1276,9 +1334,10 @@ void Hid::SendVibrationValues(Kernel::HLERequestContext& ctx) { void Hid::SendVibrationGcErmCommand(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - Controller_NPad::DeviceHandle vibration_device_handle; + Core::HID::VibrationDeviceHandle vibration_device_handle; + INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; - VibrationGcErmCommand gc_erm_command; + Core::HID::VibrationGcErmCommand gc_erm_command; }; static_assert(sizeof(Parameters) == 0x18, "Parameters has incorrect size."); @@ -1292,26 +1351,26 @@ void Hid::SendVibrationGcErmCommand(Kernel::HLERequestContext& ctx) { */ const auto vibration_value = [parameters] { switch (parameters.gc_erm_command) { - case VibrationGcErmCommand::Stop: - return Controller_NPad::VibrationValue{ - .amp_low = 0.0f, - .freq_low = 160.0f, - .amp_high = 0.0f, - .freq_high = 320.0f, + case Core::HID::VibrationGcErmCommand::Stop: + return Core::HID::VibrationValue{ + .low_amplitude = 0.0f, + .low_frequency = 160.0f, + .high_amplitude = 0.0f, + .high_frequency = 320.0f, }; - case VibrationGcErmCommand::Start: - return Controller_NPad::VibrationValue{ - .amp_low = 1.0f, - .freq_low = 160.0f, - .amp_high = 1.0f, - .freq_high = 320.0f, + case Core::HID::VibrationGcErmCommand::Start: + return Core::HID::VibrationValue{ + .low_amplitude = 1.0f, + .low_frequency = 160.0f, + .high_amplitude = 1.0f, + .high_frequency = 320.0f, }; - case VibrationGcErmCommand::StopHard: - return Controller_NPad::VibrationValue{ - .amp_low = 0.0f, - .freq_low = 0.0f, - .amp_high = 0.0f, - .freq_high = 0.0f, + case Core::HID::VibrationGcErmCommand::StopHard: + return Core::HID::VibrationValue{ + .low_amplitude = 0.0f, + .low_frequency = 0.0f, + .high_amplitude = 0.0f, + .high_frequency = 0.0f, }; default: return Controller_NPad::DEFAULT_VIBRATION_VALUE; @@ -1336,7 +1395,7 @@ void Hid::SendVibrationGcErmCommand(Kernel::HLERequestContext& ctx) { void Hid::GetActualVibrationGcErmCommand(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - Controller_NPad::DeviceHandle vibration_device_handle; + Core::HID::VibrationDeviceHandle vibration_device_handle; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; }; @@ -1347,8 +1406,8 @@ void Hid::GetActualVibrationGcErmCommand(Kernel::HLERequestContext& ctx) { .GetLastVibration(parameters.vibration_device_handle); const auto gc_erm_command = [last_vibration] { - if (last_vibration.amp_low != 0.0f || last_vibration.amp_high != 0.0f) { - return VibrationGcErmCommand::Start; + if (last_vibration.low_amplitude != 0.0f || last_vibration.high_amplitude != 0.0f) { + return Core::HID::VibrationGcErmCommand::Start; } /** @@ -1357,11 +1416,11 @@ void Hid::GetActualVibrationGcErmCommand(Kernel::HLERequestContext& ctx) { * SendVibrationGcErmCommand, in order to differentiate between Stop and StopHard commands. * This is done to reuse the controller vibration functions made for regular controllers. */ - if (last_vibration.freq_low == 0.0f && last_vibration.freq_high == 0.0f) { - return VibrationGcErmCommand::StopHard; + if (last_vibration.low_frequency == 0.0f && last_vibration.high_frequency == 0.0f) { + return Core::HID::VibrationGcErmCommand::StopHard; } - return VibrationGcErmCommand::Stop; + return Core::HID::VibrationGcErmCommand::Stop; }(); LOG_DEBUG(Service_HID, @@ -1401,10 +1460,11 @@ void Hid::EndPermitVibrationSession(Kernel::HLERequestContext& ctx) { void Hid::IsVibrationDeviceMounted(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - Controller_NPad::DeviceHandle vibration_device_handle; + Core::HID::VibrationDeviceHandle vibration_device_handle; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; }; + static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); const auto parameters{rp.PopRaw()}; @@ -1435,18 +1495,18 @@ void Hid::ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) { void Hid::StartConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - Controller_NPad::DeviceHandle sixaxis_handle; + Core::HID::ConsoleSixAxisSensorHandle console_sixaxis_handle; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; }; + static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); const auto parameters{rp.PopRaw()}; - LOG_WARNING( - Service_HID, - "(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", - parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, - parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); + LOG_WARNING(Service_HID, + "(STUBBED) called, unknown_1={}, unknown_2={}, applet_resource_user_id={}", + parameters.console_sixaxis_handle.unknown_1, + parameters.console_sixaxis_handle.unknown_2, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); @@ -1455,18 +1515,18 @@ void Hid::StartConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) { void Hid::StopConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - Controller_NPad::DeviceHandle sixaxis_handle; + Core::HID::ConsoleSixAxisSensorHandle console_sixaxis_handle; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; }; + static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); const auto parameters{rp.PopRaw()}; - LOG_WARNING( - Service_HID, - "(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", - parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, - parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); + LOG_WARNING(Service_HID, + "(STUBBED) called, unknown_1={}, unknown_2={}, applet_resource_user_id={}", + parameters.console_sixaxis_handle.unknown_1, + parameters.console_sixaxis_handle.unknown_2, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); @@ -1620,10 +1680,8 @@ void Hid::SetNpadCommunicationMode(Kernel::HLERequestContext& ctx) { void Hid::GetNpadCommunicationMode(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto applet_resource_user_id{rp.Pop()}; - LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}", - applet_resource_user_id); + LOG_WARNING(Service_HID, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(ResultSuccess); @@ -2037,10 +2095,6 @@ public: } }; -void ReloadInputDevices() { - Settings::values.is_device_reload_pending.store(true); -} - void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { std::make_shared(system)->InstallAsService(service_manager); std::make_shared(system)->InstallAsService(service_manager); diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index b1fe75e948..ab00841180 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -60,21 +60,23 @@ public: private: template void MakeController(HidController controller) { - controllers[static_cast(controller)] = std::make_unique(system); + controllers[static_cast(controller)] = std::make_unique(system.HIDCore()); } template void MakeControllerWithServiceContext(HidController controller) { controllers[static_cast(controller)] = - std::make_unique(system, service_context); + std::make_unique(system.HIDCore(), service_context); } void GetSharedMemoryHandle(Kernel::HLERequestContext& ctx); void UpdateControllers(std::uintptr_t user_data, std::chrono::nanoseconds ns_late); + void UpdateMouseKeyboard(std::uintptr_t user_data, std::chrono::nanoseconds ns_late); void UpdateMotion(std::uintptr_t user_data, std::chrono::nanoseconds ns_late); KernelHelpers::ServiceContext& service_context; std::shared_ptr pad_update_event; + std::shared_ptr mouse_keyboard_update_event; std::shared_ptr motion_update_event; std::array, static_cast(HidController::MaxControllers)> @@ -161,38 +163,11 @@ private: void GetNpadCommunicationMode(Kernel::HLERequestContext& ctx); void SetTouchScreenConfiguration(Kernel::HLERequestContext& ctx); - enum class VibrationDeviceType : u32 { - Unknown = 0, - LinearResonantActuator = 1, - GcErm = 2, - }; - - enum class VibrationDevicePosition : u32 { - None = 0, - Left = 1, - Right = 2, - }; - - enum class VibrationGcErmCommand : u64 { - Stop = 0, - Start = 1, - StopHard = 2, - }; - - struct VibrationDeviceInfo { - VibrationDeviceType type{}; - VibrationDevicePosition position{}; - }; - static_assert(sizeof(VibrationDeviceInfo) == 0x8, "VibrationDeviceInfo has incorrect size."); - std::shared_ptr applet_resource; KernelHelpers::ServiceContext service_context; }; -/// Reload input devices. Used when input configuration changed -void ReloadInputDevices(); - /// Registers all HID services with the specified service manager. void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system); diff --git a/src/core/hle/service/hid/ring_lifo.h b/src/core/hle/service/hid/ring_lifo.h new file mode 100644 index 0000000000..44c20d9673 --- /dev/null +++ b/src/core/hle/service/hid/ring_lifo.h @@ -0,0 +1,54 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included + +#pragma once + +#include + +#include "common/common_types.h" + +namespace Service::HID { + +template +struct AtomicStorage { + s64 sampling_number; + State state; +}; + +template +struct Lifo { + s64 timestamp{}; + s64 total_buffer_count = static_cast(max_buffer_size); + s64 buffer_tail{}; + s64 buffer_count{}; + std::array, max_buffer_size> entries{}; + + const AtomicStorage& ReadCurrentEntry() const { + return entries[buffer_tail]; + } + + const AtomicStorage& ReadPreviousEntry() const { + return entries[GetPreviousEntryIndex()]; + } + + std::size_t GetPreviousEntryIndex() const { + return static_cast((buffer_tail + total_buffer_count - 1) % total_buffer_count); + } + + std::size_t GetNextEntryIndex() const { + return static_cast((buffer_tail + 1) % total_buffer_count); + } + + void WriteNextEntry(const State& new_state) { + if (buffer_count < total_buffer_count - 1) { + buffer_count++; + } + buffer_tail = GetNextEntryIndex(); + const auto& previous_entry = ReadPreviousEntry(); + entries[buffer_tail].sampling_number = previous_entry.sampling_number + 1; + entries[buffer_tail].state = new_state; + } +}; + +} // namespace Service::HID diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index dd13d948f7..d4fa69a775 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt @@ -1,36 +1,32 @@ add_library(input_common STATIC - analog_from_button.cpp - analog_from_button.h - keyboard.cpp - keyboard.h + drivers/gc_adapter.cpp + drivers/gc_adapter.h + drivers/keyboard.cpp + drivers/keyboard.h + drivers/mouse.cpp + drivers/mouse.h + drivers/sdl_driver.cpp + drivers/sdl_driver.h + drivers/tas_input.cpp + drivers/tas_input.h + drivers/touch_screen.cpp + drivers/touch_screen.h + drivers/udp_client.cpp + drivers/udp_client.h + helpers/stick_from_buttons.cpp + helpers/stick_from_buttons.h + helpers/touch_from_buttons.cpp + helpers/touch_from_buttons.h + helpers/udp_protocol.cpp + helpers/udp_protocol.h + input_engine.cpp + input_engine.h + input_mapping.cpp + input_mapping.h + input_poller.cpp + input_poller.h main.cpp main.h - motion_from_button.cpp - motion_from_button.h - motion_input.cpp - motion_input.h - touch_from_button.cpp - touch_from_button.h - gcadapter/gc_adapter.cpp - gcadapter/gc_adapter.h - gcadapter/gc_poller.cpp - gcadapter/gc_poller.h - mouse/mouse_input.cpp - mouse/mouse_input.h - mouse/mouse_poller.cpp - mouse/mouse_poller.h - sdl/sdl.cpp - sdl/sdl.h - tas/tas_input.cpp - tas/tas_input.h - tas/tas_poller.cpp - tas/tas_poller.h - udp/client.cpp - udp/client.h - udp/protocol.cpp - udp/protocol.h - udp/udp.cpp - udp/udp.h ) if (MSVC) @@ -57,8 +53,8 @@ endif() if (ENABLE_SDL2) target_sources(input_common PRIVATE - sdl/sdl_impl.cpp - sdl/sdl_impl.h + drivers/sdl_driver.cpp + drivers/sdl_driver.h ) target_link_libraries(input_common PRIVATE SDL2) target_compile_definitions(input_common PRIVATE HAVE_SDL2) diff --git a/src/input_common/analog_from_button.cpp b/src/input_common/analog_from_button.cpp deleted file mode 100755 index 2fafd077f7..0000000000 --- a/src/input_common/analog_from_button.cpp +++ /dev/null @@ -1,238 +0,0 @@ -// Copyright 2017 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include -#include -#include -#include "common/math_util.h" -#include "common/settings.h" -#include "input_common/analog_from_button.h" - -namespace InputCommon { - -class Analog final : public Input::AnalogDevice { -public: - using Button = std::unique_ptr; - - Analog(Button up_, Button down_, Button left_, Button right_, Button modifier_, - float modifier_scale_, float modifier_angle_) - : up(std::move(up_)), down(std::move(down_)), left(std::move(left_)), - right(std::move(right_)), modifier(std::move(modifier_)), modifier_scale(modifier_scale_), - modifier_angle(modifier_angle_) { - Input::InputCallback callbacks{ - [this]([[maybe_unused]] bool status) { UpdateStatus(); }}; - up->SetCallback(callbacks); - down->SetCallback(callbacks); - left->SetCallback(callbacks); - right->SetCallback(callbacks); - modifier->SetCallback(callbacks); - } - - bool IsAngleGreater(float old_angle, float new_angle) const { - constexpr float TAU = Common::PI * 2.0f; - // Use wider angle to ease the transition. - constexpr float aperture = TAU * 0.15f; - const float top_limit = new_angle + aperture; - return (old_angle > new_angle && old_angle <= top_limit) || - (old_angle + TAU > new_angle && old_angle + TAU <= top_limit); - } - - bool IsAngleSmaller(float old_angle, float new_angle) const { - constexpr float TAU = Common::PI * 2.0f; - // Use wider angle to ease the transition. - constexpr float aperture = TAU * 0.15f; - const float bottom_limit = new_angle - aperture; - return (old_angle >= bottom_limit && old_angle < new_angle) || - (old_angle - TAU >= bottom_limit && old_angle - TAU < new_angle); - } - - float GetAngle(std::chrono::time_point now) const { - constexpr float TAU = Common::PI * 2.0f; - float new_angle = angle; - - auto time_difference = static_cast( - std::chrono::duration_cast(now - last_update).count()); - time_difference /= 1000.0f * 1000.0f; - if (time_difference > 0.5f) { - time_difference = 0.5f; - } - - if (IsAngleGreater(new_angle, goal_angle)) { - new_angle -= modifier_angle * time_difference; - if (new_angle < 0) { - new_angle += TAU; - } - if (!IsAngleGreater(new_angle, goal_angle)) { - return goal_angle; - } - } else if (IsAngleSmaller(new_angle, goal_angle)) { - new_angle += modifier_angle * time_difference; - if (new_angle >= TAU) { - new_angle -= TAU; - } - if (!IsAngleSmaller(new_angle, goal_angle)) { - return goal_angle; - } - } else { - return goal_angle; - } - return new_angle; - } - - void SetGoalAngle(bool r, bool l, bool u, bool d) { - // Move to the right - if (r && !u && !d) { - goal_angle = 0.0f; - } - - // Move to the upper right - if (r && u && !d) { - goal_angle = Common::PI * 0.25f; - } - - // Move up - if (u && !l && !r) { - goal_angle = Common::PI * 0.5f; - } - - // Move to the upper left - if (l && u && !d) { - goal_angle = Common::PI * 0.75f; - } - - // Move to the left - if (l && !u && !d) { - goal_angle = Common::PI; - } - - // Move to the bottom left - if (l && !u && d) { - goal_angle = Common::PI * 1.25f; - } - - // Move down - if (d && !l && !r) { - goal_angle = Common::PI * 1.5f; - } - - // Move to the bottom right - if (r && !u && d) { - goal_angle = Common::PI * 1.75f; - } - } - - void UpdateStatus() { - const float coef = modifier->GetStatus() ? modifier_scale : 1.0f; - - bool r = right->GetStatus(); - bool l = left->GetStatus(); - bool u = up->GetStatus(); - bool d = down->GetStatus(); - - // Eliminate contradictory movements - if (r && l) { - r = false; - l = false; - } - if (u && d) { - u = false; - d = false; - } - - // Move if a key is pressed - if (r || l || u || d) { - amplitude = coef; - } else { - amplitude = 0; - } - - const auto now = std::chrono::steady_clock::now(); - const auto time_difference = static_cast( - std::chrono::duration_cast(now - last_update).count()); - - if (time_difference < 10) { - // Disable analog mode if inputs are too fast - SetGoalAngle(r, l, u, d); - angle = goal_angle; - } else { - angle = GetAngle(now); - SetGoalAngle(r, l, u, d); - } - - last_update = now; - } - - std::tuple GetStatus() const override { - if (Settings::values.emulate_analog_keyboard) { - const auto now = std::chrono::steady_clock::now(); - float angle_ = GetAngle(now); - return std::make_tuple(std::cos(angle_) * amplitude, std::sin(angle_) * amplitude); - } - constexpr float SQRT_HALF = 0.707106781f; - int x = 0, y = 0; - if (right->GetStatus()) { - ++x; - } - if (left->GetStatus()) { - --x; - } - if (up->GetStatus()) { - ++y; - } - if (down->GetStatus()) { - --y; - } - const float coef = modifier->GetStatus() ? modifier_scale : 1.0f; - return std::make_tuple(static_cast(x) * coef * (y == 0 ? 1.0f : SQRT_HALF), - static_cast(y) * coef * (x == 0 ? 1.0f : SQRT_HALF)); - } - - Input::AnalogProperties GetAnalogProperties() const override { - return {modifier_scale, 1.0f, 0.5f}; - } - - bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override { - switch (direction) { - case Input::AnalogDirection::RIGHT: - return right->GetStatus(); - case Input::AnalogDirection::LEFT: - return left->GetStatus(); - case Input::AnalogDirection::UP: - return up->GetStatus(); - case Input::AnalogDirection::DOWN: - return down->GetStatus(); - } - return false; - } - -private: - Button up; - Button down; - Button left; - Button right; - Button modifier; - float modifier_scale; - float modifier_angle; - float angle{}; - float goal_angle{}; - float amplitude{}; - std::chrono::time_point last_update; -}; - -std::unique_ptr AnalogFromButton::Create(const Common::ParamPackage& params) { - const std::string null_engine = Common::ParamPackage{{"engine", "null"}}.Serialize(); - auto up = Input::CreateDevice(params.Get("up", null_engine)); - auto down = Input::CreateDevice(params.Get("down", null_engine)); - auto left = Input::CreateDevice(params.Get("left", null_engine)); - auto right = Input::CreateDevice(params.Get("right", null_engine)); - auto modifier = Input::CreateDevice(params.Get("modifier", null_engine)); - auto modifier_scale = params.Get("modifier_scale", 0.5f); - auto modifier_angle = params.Get("modifier_angle", 5.5f); - return std::make_unique(std::move(up), std::move(down), std::move(left), - std::move(right), std::move(modifier), modifier_scale, - modifier_angle); -} - -} // namespace InputCommon diff --git a/src/input_common/gcadapter/gc_adapter.cpp b/src/input_common/drivers/gc_adapter.cpp similarity index 51% rename from src/input_common/gcadapter/gc_adapter.cpp rename to src/input_common/drivers/gc_adapter.cpp index a2f1bb67c8..8b65742236 100644 --- a/src/input_common/gcadapter/gc_adapter.cpp +++ b/src/input_common/drivers/gc_adapter.cpp @@ -2,47 +2,103 @@ // Licensed under GPLv2+ // Refer to the license.txt file included. -#include -#include - +#include #include #include "common/logging/log.h" #include "common/param_package.h" #include "common/settings_input.h" -#include "input_common/gcadapter/gc_adapter.h" +#include "common/thread.h" +#include "input_common/drivers/gc_adapter.h" -namespace GCAdapter { +namespace InputCommon { -Adapter::Adapter() { - if (usb_adapter_handle != nullptr) { +class LibUSBContext { +public: + explicit LibUSBContext() { + init_result = libusb_init(&ctx); + } + + ~LibUSBContext() { + libusb_exit(ctx); + } + + LibUSBContext& operator=(const LibUSBContext&) = delete; + LibUSBContext(const LibUSBContext&) = delete; + + LibUSBContext& operator=(LibUSBContext&&) noexcept = delete; + LibUSBContext(LibUSBContext&&) noexcept = delete; + + [[nodiscard]] int InitResult() const noexcept { + return init_result; + } + + [[nodiscard]] libusb_context* get() noexcept { + return ctx; + } + +private: + libusb_context* ctx; + int init_result{}; +}; + +class LibUSBDeviceHandle { +public: + explicit LibUSBDeviceHandle(libusb_context* ctx, uint16_t vid, uint16_t pid) noexcept { + handle = libusb_open_device_with_vid_pid(ctx, vid, pid); + } + + ~LibUSBDeviceHandle() noexcept { + if (handle) { + libusb_release_interface(handle, 1); + libusb_close(handle); + } + } + + LibUSBDeviceHandle& operator=(const LibUSBDeviceHandle&) = delete; + LibUSBDeviceHandle(const LibUSBDeviceHandle&) = delete; + + LibUSBDeviceHandle& operator=(LibUSBDeviceHandle&&) noexcept = delete; + LibUSBDeviceHandle(LibUSBDeviceHandle&&) noexcept = delete; + + [[nodiscard]] libusb_device_handle* get() noexcept { + return handle; + } + +private: + libusb_device_handle* handle{}; +}; + +GCAdapter::GCAdapter(const std::string& input_engine_) : InputEngine(input_engine_) { + if (usb_adapter_handle) { return; } - LOG_INFO(Input, "GC Adapter Initialization started"); + LOG_DEBUG(Input, "Initialization started"); - const int init_res = libusb_init(&libusb_ctx); + libusb_ctx = std::make_unique(); + const int init_res = libusb_ctx->InitResult(); if (init_res == LIBUSB_SUCCESS) { - adapter_scan_thread = std::thread(&Adapter::AdapterScanThread, this); + adapter_scan_thread = + std::jthread([this](std::stop_token stop_token) { AdapterScanThread(stop_token); }); } else { LOG_ERROR(Input, "libusb could not be initialized. failed with error = {}", init_res); } } -Adapter::~Adapter() { +GCAdapter::~GCAdapter() { Reset(); } -void Adapter::AdapterInputThread() { - LOG_DEBUG(Input, "GC Adapter input thread started"); +void GCAdapter::AdapterInputThread(std::stop_token stop_token) { + LOG_DEBUG(Input, "Input thread started"); + Common::SetCurrentThreadName("yuzu:input:GCAdapter"); s32 payload_size{}; AdapterPayload adapter_payload{}; - if (adapter_scan_thread.joinable()) { - adapter_scan_thread.join(); - } + adapter_scan_thread = {}; - while (adapter_input_thread_running) { - libusb_interrupt_transfer(usb_adapter_handle, input_endpoint, adapter_payload.data(), + while (!stop_token.stop_requested()) { + libusb_interrupt_transfer(usb_adapter_handle->get(), input_endpoint, adapter_payload.data(), static_cast(adapter_payload.size()), &payload_size, 16); if (IsPayloadCorrect(adapter_payload, payload_size)) { UpdateControllers(adapter_payload); @@ -52,19 +108,20 @@ void Adapter::AdapterInputThread() { } if (restart_scan_thread) { - adapter_scan_thread = std::thread(&Adapter::AdapterScanThread, this); + adapter_scan_thread = + std::jthread([this](std::stop_token token) { AdapterScanThread(token); }); restart_scan_thread = false; } } -bool Adapter::IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size) { +bool GCAdapter::IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size) { if (payload_size != static_cast(adapter_payload.size()) || adapter_payload[0] != LIBUSB_DT_HID) { LOG_DEBUG(Input, "Error reading payload (size: {}, type: {:02x})", payload_size, adapter_payload[0]); if (input_error_counter++ > 20) { - LOG_ERROR(Input, "GC adapter timeout, Is the adapter connected?"); - adapter_input_thread_running = false; + LOG_ERROR(Input, "Timeout, Is the adapter connected?"); + adapter_input_thread.request_stop(); restart_scan_thread = true; } return false; @@ -74,7 +131,7 @@ bool Adapter::IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payloa return true; } -void Adapter::UpdateControllers(const AdapterPayload& adapter_payload) { +void GCAdapter::UpdateControllers(const AdapterPayload& adapter_payload) { for (std::size_t port = 0; port < pads.size(); ++port) { const std::size_t offset = 1 + (9 * port); const auto type = static_cast(adapter_payload[offset] >> 4); @@ -84,23 +141,24 @@ void Adapter::UpdateControllers(const AdapterPayload& adapter_payload) { const u8 b2 = adapter_payload[offset + 2]; UpdateStateButtons(port, b1, b2); UpdateStateAxes(port, adapter_payload); - if (configuring) { - UpdateYuzuSettings(port); - } } } } -void Adapter::UpdatePadType(std::size_t port, ControllerTypes pad_type) { +void GCAdapter::UpdatePadType(std::size_t port, ControllerTypes pad_type) { if (pads[port].type == pad_type) { return; } // Device changed reset device and set new type - ResetDevice(port); + pads[port].axis_origin = {}; + pads[port].reset_origin_counter = {}; + pads[port].enable_vibration = {}; + pads[port].rumble_amplitude = {}; pads[port].type = pad_type; } -void Adapter::UpdateStateButtons(std::size_t port, u8 b1, u8 b2) { +void GCAdapter::UpdateStateButtons(std::size_t port, [[maybe_unused]] u8 b1, + [[maybe_unused]] u8 b2) { if (port >= pads.size()) { return; } @@ -116,25 +174,21 @@ void Adapter::UpdateStateButtons(std::size_t port, u8 b1, u8 b2) { PadButton::TriggerR, PadButton::TriggerL, }; - pads[port].buttons = 0; + for (std::size_t i = 0; i < b1_buttons.size(); ++i) { - if ((b1 & (1U << i)) != 0) { - pads[port].buttons = - static_cast(pads[port].buttons | static_cast(b1_buttons[i])); - pads[port].last_button = b1_buttons[i]; - } + const bool button_status = (b1 & (1U << i)) != 0; + const int button = static_cast(b1_buttons[i]); + SetButton(pads[port].identifier, button, button_status); } for (std::size_t j = 0; j < b2_buttons.size(); ++j) { - if ((b2 & (1U << j)) != 0) { - pads[port].buttons = - static_cast(pads[port].buttons | static_cast(b2_buttons[j])); - pads[port].last_button = b2_buttons[j]; - } + const bool button_status = (b2 & (1U << j)) != 0; + const int button = static_cast(b2_buttons[j]); + SetButton(pads[port].identifier, button, button_status); } } -void Adapter::UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload) { +void GCAdapter::UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload) { if (port >= pads.size()) { return; } @@ -155,134 +209,63 @@ void Adapter::UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_pa pads[port].axis_origin[index] = axis_value; pads[port].reset_origin_counter++; } - pads[port].axis_values[index] = - static_cast(axis_value - pads[port].axis_origin[index]); + const f32 axis_status = (axis_value - pads[port].axis_origin[index]) / 100.0f; + SetAxis(pads[port].identifier, static_cast(index), axis_status); } } -void Adapter::UpdateYuzuSettings(std::size_t port) { - if (port >= pads.size()) { - return; - } - - constexpr u8 axis_threshold = 50; - GCPadStatus pad_status = {.port = port}; - - if (pads[port].buttons != 0) { - pad_status.button = pads[port].last_button; - pad_queue.Push(pad_status); - } - - // Accounting for a threshold here to ensure an intentional press - for (std::size_t i = 0; i < pads[port].axis_values.size(); ++i) { - const s16 value = pads[port].axis_values[i]; - - if (value > axis_threshold || value < -axis_threshold) { - pad_status.axis = static_cast(i); - pad_status.axis_value = value; - pad_status.axis_threshold = axis_threshold; - pad_queue.Push(pad_status); - } +void GCAdapter::AdapterScanThread(std::stop_token stop_token) { + Common::SetCurrentThreadName("yuzu:input:ScanGCAdapter"); + usb_adapter_handle = nullptr; + pads = {}; + while (!stop_token.stop_requested() && !Setup()) { + std::this_thread::sleep_for(std::chrono::seconds(2)); } } -void Adapter::UpdateVibrations() { - // Use 8 states to keep the switching between on/off fast enough for - // a human to not notice the difference between switching from on/off - // More states = more rumble strengths = slower update time - constexpr u8 vibration_states = 8; - - vibration_counter = (vibration_counter + 1) % vibration_states; - - for (GCController& pad : pads) { - const bool vibrate = pad.rumble_amplitude > vibration_counter; - vibration_changed |= vibrate != pad.enable_vibration; - pad.enable_vibration = vibrate; - } - SendVibrations(); -} - -void Adapter::SendVibrations() { - if (!rumble_enabled || !vibration_changed) { - return; - } - s32 size{}; - constexpr u8 rumble_command = 0x11; - const u8 p1 = pads[0].enable_vibration; - const u8 p2 = pads[1].enable_vibration; - const u8 p3 = pads[2].enable_vibration; - const u8 p4 = pads[3].enable_vibration; - std::array payload = {rumble_command, p1, p2, p3, p4}; - const int err = libusb_interrupt_transfer(usb_adapter_handle, output_endpoint, payload.data(), - static_cast(payload.size()), &size, 16); - if (err) { - LOG_DEBUG(Input, "Adapter libusb write failed: {}", libusb_error_name(err)); - if (output_error_counter++ > 5) { - LOG_ERROR(Input, "GC adapter output timeout, Rumble disabled"); - rumble_enabled = false; - } - return; - } - output_error_counter = 0; - vibration_changed = false; -} - -bool Adapter::RumblePlay(std::size_t port, u8 amplitude) { - pads[port].rumble_amplitude = amplitude; - - return rumble_enabled; -} - -void Adapter::AdapterScanThread() { - adapter_scan_thread_running = true; - adapter_input_thread_running = false; - if (adapter_input_thread.joinable()) { - adapter_input_thread.join(); - } - ClearLibusbHandle(); - ResetDevices(); - while (adapter_scan_thread_running && !adapter_input_thread_running) { - Setup(); - std::this_thread::sleep_for(std::chrono::seconds(1)); - } -} - -void Adapter::Setup() { - usb_adapter_handle = libusb_open_device_with_vid_pid(libusb_ctx, 0x057e, 0x0337); - - if (usb_adapter_handle == NULL) { - return; +bool GCAdapter::Setup() { + constexpr u16 nintendo_vid = 0x057e; + constexpr u16 gc_adapter_pid = 0x0337; + usb_adapter_handle = + std::make_unique(libusb_ctx->get(), nintendo_vid, gc_adapter_pid); + if (!usb_adapter_handle->get()) { + return false; } if (!CheckDeviceAccess()) { - ClearLibusbHandle(); - return; + usb_adapter_handle = nullptr; + return false; } - libusb_device* device = libusb_get_device(usb_adapter_handle); + libusb_device* const device = libusb_get_device(usb_adapter_handle->get()); LOG_INFO(Input, "GC adapter is now connected"); // GC Adapter found and accessible, registering it if (GetGCEndpoint(device)) { - adapter_scan_thread_running = false; - adapter_input_thread_running = true; rumble_enabled = true; input_error_counter = 0; output_error_counter = 0; - adapter_input_thread = std::thread(&Adapter::AdapterInputThread, this); + + std::size_t port = 0; + for (GCController& pad : pads) { + pad.identifier = { + .guid = Common::UUID{Common::INVALID_UUID}, + .port = port++, + .pad = 0, + }; + PreSetController(pad.identifier); + } + + adapter_input_thread = + std::jthread([this](std::stop_token stop_token) { AdapterInputThread(stop_token); }); + return true; } + return false; } -bool Adapter::CheckDeviceAccess() { - // This fixes payload problems from offbrand GCAdapters - const s32 control_transfer_error = - libusb_control_transfer(usb_adapter_handle, 0x21, 11, 0x0001, 0, nullptr, 0, 1000); - if (control_transfer_error < 0) { - LOG_ERROR(Input, "libusb_control_transfer failed with error= {}", control_transfer_error); - } - - s32 kernel_driver_error = libusb_kernel_driver_active(usb_adapter_handle, 0); +bool GCAdapter::CheckDeviceAccess() { + s32 kernel_driver_error = libusb_kernel_driver_active(usb_adapter_handle->get(), 0); if (kernel_driver_error == 1) { - kernel_driver_error = libusb_detach_kernel_driver(usb_adapter_handle, 0); + kernel_driver_error = libusb_detach_kernel_driver(usb_adapter_handle->get(), 0); if (kernel_driver_error != 0 && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) { LOG_ERROR(Input, "libusb_detach_kernel_driver failed with error = {}", kernel_driver_error); @@ -290,23 +273,28 @@ bool Adapter::CheckDeviceAccess() { } if (kernel_driver_error && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) { - libusb_close(usb_adapter_handle); usb_adapter_handle = nullptr; return false; } - const int interface_claim_error = libusb_claim_interface(usb_adapter_handle, 0); + const int interface_claim_error = libusb_claim_interface(usb_adapter_handle->get(), 0); if (interface_claim_error) { LOG_ERROR(Input, "libusb_claim_interface failed with error = {}", interface_claim_error); - libusb_close(usb_adapter_handle); usb_adapter_handle = nullptr; return false; } + // This fixes payload problems from offbrand GCAdapters + const s32 control_transfer_error = + libusb_control_transfer(usb_adapter_handle->get(), 0x21, 11, 0x0001, 0, nullptr, 0, 1000); + if (control_transfer_error < 0) { + LOG_ERROR(Input, "libusb_control_transfer failed with error= {}", control_transfer_error); + } + return true; } -bool Adapter::GetGCEndpoint(libusb_device* device) { +bool GCAdapter::GetGCEndpoint(libusb_device* device) { libusb_config_descriptor* config = nullptr; const int config_descriptor_return = libusb_get_config_descriptor(device, 0, &config); if (config_descriptor_return != LIBUSB_SUCCESS) { @@ -332,77 +320,95 @@ bool Adapter::GetGCEndpoint(libusb_device* device) { // This transfer seems to be responsible for clearing the state of the adapter // Used to clear the "busy" state of when the device is unexpectedly unplugged unsigned char clear_payload = 0x13; - libusb_interrupt_transfer(usb_adapter_handle, output_endpoint, &clear_payload, + libusb_interrupt_transfer(usb_adapter_handle->get(), output_endpoint, &clear_payload, sizeof(clear_payload), nullptr, 16); return true; } -void Adapter::JoinThreads() { - restart_scan_thread = false; - adapter_input_thread_running = false; - adapter_scan_thread_running = false; +Common::Input::VibrationError GCAdapter::SetRumble(const PadIdentifier& identifier, + const Common::Input::VibrationStatus vibration) { + const auto mean_amplitude = (vibration.low_amplitude + vibration.high_amplitude) * 0.5f; + const auto processed_amplitude = + static_cast((mean_amplitude + std::pow(mean_amplitude, 0.3f)) * 0.5f * 0x8); - if (adapter_scan_thread.joinable()) { - adapter_scan_thread.join(); - } + pads[identifier.port].rumble_amplitude = processed_amplitude; - if (adapter_input_thread.joinable()) { - adapter_input_thread.join(); + if (!rumble_enabled) { + return Common::Input::VibrationError::Disabled; } + return Common::Input::VibrationError::None; } -void Adapter::ClearLibusbHandle() { - if (usb_adapter_handle) { - libusb_release_interface(usb_adapter_handle, 1); - libusb_close(usb_adapter_handle); - usb_adapter_handle = nullptr; +void GCAdapter::UpdateVibrations() { + // Use 8 states to keep the switching between on/off fast enough for + // a human to feel different vibration strenght + // More states == more rumble strengths == slower update time + constexpr u8 vibration_states = 8; + + vibration_counter = (vibration_counter + 1) % vibration_states; + + for (GCController& pad : pads) { + const bool vibrate = pad.rumble_amplitude > vibration_counter; + vibration_changed |= vibrate != pad.enable_vibration; + pad.enable_vibration = vibrate; } + SendVibrations(); } -void Adapter::ResetDevices() { - for (std::size_t i = 0; i < pads.size(); ++i) { - ResetDevice(i); +void GCAdapter::SendVibrations() { + if (!rumble_enabled || !vibration_changed) { + return; } -} - -void Adapter::ResetDevice(std::size_t port) { - pads[port].type = ControllerTypes::None; - pads[port].enable_vibration = false; - pads[port].rumble_amplitude = 0; - pads[port].buttons = 0; - pads[port].last_button = PadButton::Undefined; - pads[port].axis_values.fill(0); - pads[port].reset_origin_counter = 0; -} - -void Adapter::Reset() { - JoinThreads(); - ClearLibusbHandle(); - ResetDevices(); - - if (libusb_ctx) { - libusb_exit(libusb_ctx); + s32 size{}; + constexpr u8 rumble_command = 0x11; + const u8 p1 = pads[0].enable_vibration; + const u8 p2 = pads[1].enable_vibration; + const u8 p3 = pads[2].enable_vibration; + const u8 p4 = pads[3].enable_vibration; + std::array payload = {rumble_command, p1, p2, p3, p4}; + const int err = + libusb_interrupt_transfer(usb_adapter_handle->get(), output_endpoint, payload.data(), + static_cast(payload.size()), &size, 16); + if (err) { + LOG_DEBUG(Input, "Libusb write failed: {}", libusb_error_name(err)); + if (output_error_counter++ > 5) { + LOG_ERROR(Input, "Output timeout, Rumble disabled"); + rumble_enabled = false; + } + return; } + output_error_counter = 0; + vibration_changed = false; } -std::vector Adapter::GetInputDevices() const { +bool GCAdapter::DeviceConnected(std::size_t port) const { + return pads[port].type != ControllerTypes::None; +} + +void GCAdapter::Reset() { + adapter_scan_thread = {}; + adapter_input_thread = {}; + usb_adapter_handle = nullptr; + pads = {}; + libusb_ctx = nullptr; +} + +std::vector GCAdapter::GetInputDevices() const { std::vector devices; for (std::size_t port = 0; port < pads.size(); ++port) { if (!DeviceConnected(port)) { continue; } - std::string name = fmt::format("Gamecube Controller {}", port + 1); - devices.emplace_back(Common::ParamPackage{ - {"class", "gcpad"}, - {"display", std::move(name)}, - {"port", std::to_string(port)}, - }); + Common::ParamPackage identifier{}; + identifier.Set("engine", GetEngineName()); + identifier.Set("display", fmt::format("Gamecube Controller {}", port + 1)); + identifier.Set("port", static_cast(port)); + devices.emplace_back(identifier); } return devices; } -InputCommon::ButtonMapping Adapter::GetButtonMappingForDevice( - const Common::ParamPackage& params) const { +ButtonMapping GCAdapter::GetButtonMappingForDevice(const Common::ParamPackage& params) { // This list is missing ZL/ZR since those are not considered buttons. // We will add those afterwards // This list also excludes any button that can't be really mapped @@ -425,47 +431,49 @@ InputCommon::ButtonMapping Adapter::GetButtonMappingForDevice( return {}; } - InputCommon::ButtonMapping mapping{}; + ButtonMapping mapping{}; for (const auto& [switch_button, gcadapter_button] : switch_to_gcadapter_button) { - Common::ParamPackage button_params({{"engine", "gcpad"}}); + Common::ParamPackage button_params{}; + button_params.Set("engine", GetEngineName()); button_params.Set("port", params.Get("port", 0)); button_params.Set("button", static_cast(gcadapter_button)); mapping.insert_or_assign(switch_button, std::move(button_params)); } // Add the missing bindings for ZL/ZR - static constexpr std::array, 2> + static constexpr std::array, 2> switch_to_gcadapter_axis = { - std::pair{Settings::NativeButton::ZL, PadAxes::TriggerLeft}, - {Settings::NativeButton::ZR, PadAxes::TriggerRight}, + std::tuple{Settings::NativeButton::ZL, PadButton::TriggerL, PadAxes::TriggerLeft}, + {Settings::NativeButton::ZR, PadButton::TriggerR, PadAxes::TriggerRight}, }; - for (const auto& [switch_button, gcadapter_axis] : switch_to_gcadapter_axis) { - Common::ParamPackage button_params({{"engine", "gcpad"}}); + for (const auto& [switch_button, gcadapter_buton, gcadapter_axis] : switch_to_gcadapter_axis) { + Common::ParamPackage button_params{}; + button_params.Set("engine", GetEngineName()); button_params.Set("port", params.Get("port", 0)); - button_params.Set("button", static_cast(PadButton::Stick)); + button_params.Set("button", static_cast(gcadapter_buton)); button_params.Set("axis", static_cast(gcadapter_axis)); button_params.Set("threshold", 0.5f); + button_params.Set("range", 1.9f); button_params.Set("direction", "+"); mapping.insert_or_assign(switch_button, std::move(button_params)); } return mapping; } -InputCommon::AnalogMapping Adapter::GetAnalogMappingForDevice( - const Common::ParamPackage& params) const { +AnalogMapping GCAdapter::GetAnalogMappingForDevice(const Common::ParamPackage& params) { if (!params.Has("port")) { return {}; } - InputCommon::AnalogMapping mapping = {}; + AnalogMapping mapping = {}; Common::ParamPackage left_analog_params; - left_analog_params.Set("engine", "gcpad"); + left_analog_params.Set("engine", GetEngineName()); left_analog_params.Set("port", params.Get("port", 0)); left_analog_params.Set("axis_x", static_cast(PadAxes::StickX)); left_analog_params.Set("axis_y", static_cast(PadAxes::StickY)); mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params)); Common::ParamPackage right_analog_params; - right_analog_params.Set("engine", "gcpad"); + right_analog_params.Set("engine", GetEngineName()); right_analog_params.Set("port", params.Get("port", 0)); right_analog_params.Set("axis_x", static_cast(PadAxes::SubstickX)); right_analog_params.Set("axis_y", static_cast(PadAxes::SubstickY)); @@ -473,34 +481,47 @@ InputCommon::AnalogMapping Adapter::GetAnalogMappingForDevice( return mapping; } -bool Adapter::DeviceConnected(std::size_t port) const { - return pads[port].type != ControllerTypes::None; +Common::Input::ButtonNames GCAdapter::GetUIButtonName(const Common::ParamPackage& params) const { + PadButton button = static_cast(params.Get("button", 0)); + switch (button) { + case PadButton::ButtonLeft: + return Common::Input::ButtonNames::ButtonLeft; + case PadButton::ButtonRight: + return Common::Input::ButtonNames::ButtonRight; + case PadButton::ButtonDown: + return Common::Input::ButtonNames::ButtonDown; + case PadButton::ButtonUp: + return Common::Input::ButtonNames::ButtonUp; + case PadButton::TriggerZ: + return Common::Input::ButtonNames::TriggerZ; + case PadButton::TriggerR: + return Common::Input::ButtonNames::TriggerR; + case PadButton::TriggerL: + return Common::Input::ButtonNames::TriggerL; + case PadButton::ButtonA: + return Common::Input::ButtonNames::ButtonA; + case PadButton::ButtonB: + return Common::Input::ButtonNames::ButtonB; + case PadButton::ButtonX: + return Common::Input::ButtonNames::ButtonX; + case PadButton::ButtonY: + return Common::Input::ButtonNames::ButtonY; + case PadButton::ButtonStart: + return Common::Input::ButtonNames::ButtonStart; + default: + return Common::Input::ButtonNames::Undefined; + } } -void Adapter::BeginConfiguration() { - pad_queue.Clear(); - configuring = true; +Common::Input::ButtonNames GCAdapter::GetUIName(const Common::ParamPackage& params) const { + if (params.Has("button")) { + return GetUIButtonName(params); + } + if (params.Has("axis")) { + return Common::Input::ButtonNames::Value; + } + + return Common::Input::ButtonNames::Invalid; } -void Adapter::EndConfiguration() { - pad_queue.Clear(); - configuring = false; -} - -Common::SPSCQueue& Adapter::GetPadQueue() { - return pad_queue; -} - -const Common::SPSCQueue& Adapter::GetPadQueue() const { - return pad_queue; -} - -GCController& Adapter::GetPadState(std::size_t port) { - return pads.at(port); -} - -const GCController& Adapter::GetPadState(std::size_t port) const { - return pads.at(port); -} - -} // namespace GCAdapter +} // namespace InputCommon diff --git a/src/input_common/drivers/gc_adapter.h b/src/input_common/drivers/gc_adapter.h new file mode 100644 index 0000000000..8dc51d2e58 --- /dev/null +++ b/src/input_common/drivers/gc_adapter.h @@ -0,0 +1,135 @@ +// Copyright 2014 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "input_common/input_engine.h" + +struct libusb_context; +struct libusb_device; +struct libusb_device_handle; + +namespace InputCommon { + +class LibUSBContext; +class LibUSBDeviceHandle; + +class GCAdapter : public InputCommon::InputEngine { +public: + explicit GCAdapter(const std::string& input_engine_); + ~GCAdapter(); + + Common::Input::VibrationError SetRumble( + const PadIdentifier& identifier, const Common::Input::VibrationStatus vibration) override; + + /// Used for automapping features + std::vector GetInputDevices() const override; + ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override; + AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override; + Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override; + +private: + enum class PadButton { + Undefined = 0x0000, + ButtonLeft = 0x0001, + ButtonRight = 0x0002, + ButtonDown = 0x0004, + ButtonUp = 0x0008, + TriggerZ = 0x0010, + TriggerR = 0x0020, + TriggerL = 0x0040, + ButtonA = 0x0100, + ButtonB = 0x0200, + ButtonX = 0x0400, + ButtonY = 0x0800, + ButtonStart = 0x1000, + }; + + enum class PadAxes : u8 { + StickX, + StickY, + SubstickX, + SubstickY, + TriggerLeft, + TriggerRight, + Undefined, + }; + + enum class ControllerTypes { + None, + Wired, + Wireless, + }; + + struct GCController { + ControllerTypes type = ControllerTypes::None; + PadIdentifier identifier{}; + bool enable_vibration = false; + u8 rumble_amplitude{}; + std::array axis_origin{}; + u8 reset_origin_counter{}; + }; + + using AdapterPayload = std::array; + + void UpdatePadType(std::size_t port, ControllerTypes pad_type); + void UpdateControllers(const AdapterPayload& adapter_payload); + void UpdateStateButtons(std::size_t port, u8 b1, u8 b2); + void UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload); + + void AdapterInputThread(std::stop_token stop_token); + + void AdapterScanThread(std::stop_token stop_token); + + bool IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size); + + /// For use in initialization, querying devices to find the adapter + bool Setup(); + + /// Returns true if we successfully gain access to GC Adapter + bool CheckDeviceAccess(); + + /// Captures GC Adapter endpoint address + /// Returns true if the endpoint was set correctly + bool GetGCEndpoint(libusb_device* device); + + /// Returns true if there is a device connected to port + bool DeviceConnected(std::size_t port) const; + + /// For shutting down, clear all data, join all threads, release usb + void Reset(); + + void UpdateVibrations(); + + /// Updates vibration state of all controllers + void SendVibrations(); + + Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const; + + std::unique_ptr usb_adapter_handle; + std::array pads; + + std::jthread adapter_input_thread; + std::jthread adapter_scan_thread; + bool restart_scan_thread{}; + + std::unique_ptr libusb_ctx; + + u8 input_endpoint{0}; + u8 output_endpoint{0}; + u8 input_error_counter{0}; + u8 output_error_counter{0}; + int vibration_counter{0}; + + bool rumble_enabled{true}; + bool vibration_changed{true}; +}; +} // namespace InputCommon diff --git a/src/input_common/drivers/keyboard.cpp b/src/input_common/drivers/keyboard.cpp new file mode 100644 index 0000000000..23b0c0ccf3 --- /dev/null +++ b/src/input_common/drivers/keyboard.cpp @@ -0,0 +1,112 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included + +#include "common/param_package.h" +#include "common/settings_input.h" +#include "input_common/drivers/keyboard.h" + +namespace InputCommon { + +constexpr PadIdentifier key_identifier = { + .guid = Common::UUID{Common::INVALID_UUID}, + .port = 0, + .pad = 0, +}; +constexpr PadIdentifier keyboard_key_identifier = { + .guid = Common::UUID{Common::INVALID_UUID}, + .port = 1, + .pad = 0, +}; +constexpr PadIdentifier keyboard_modifier_identifier = { + .guid = Common::UUID{Common::INVALID_UUID}, + .port = 1, + .pad = 1, +}; + +Keyboard::Keyboard(const std::string& input_engine_) : InputEngine(input_engine_) { + // Keyboard is broken into 3 diferent sets: + // key: Unfiltered intended for controllers. + // keyboard_key: Allows only Settings::NativeKeyboard::Keys intended for keyboard emulation. + // keyboard_modifier: Allows only Settings::NativeKeyboard::Modifiers intended for keyboard + // emulation. + PreSetController(key_identifier); + PreSetController(keyboard_key_identifier); + PreSetController(keyboard_modifier_identifier); +} + +void Keyboard::PressKey(int key_code) { + SetButton(key_identifier, key_code, true); +} + +void Keyboard::ReleaseKey(int key_code) { + SetButton(key_identifier, key_code, false); +} + +void Keyboard::PressKeyboardKey(int key_index) { + if (key_index == Settings::NativeKeyboard::None) { + return; + } + SetButton(keyboard_key_identifier, key_index, true); +} + +void Keyboard::ReleaseKeyboardKey(int key_index) { + if (key_index == Settings::NativeKeyboard::None) { + return; + } + SetButton(keyboard_key_identifier, key_index, false); +} + +void Keyboard::SetKeyboardModifiers(int key_modifiers) { + for (int i = 0; i < 32; ++i) { + bool key_value = ((key_modifiers >> i) & 0x1) != 0; + SetButton(keyboard_modifier_identifier, i, key_value); + // Use the modifier to press the key button equivalent + switch (i) { + case Settings::NativeKeyboard::LeftControl: + SetButton(keyboard_key_identifier, Settings::NativeKeyboard::LeftControlKey, key_value); + break; + case Settings::NativeKeyboard::LeftShift: + SetButton(keyboard_key_identifier, Settings::NativeKeyboard::LeftShiftKey, key_value); + break; + case Settings::NativeKeyboard::LeftAlt: + SetButton(keyboard_key_identifier, Settings::NativeKeyboard::LeftAltKey, key_value); + break; + case Settings::NativeKeyboard::LeftMeta: + SetButton(keyboard_key_identifier, Settings::NativeKeyboard::LeftMetaKey, key_value); + break; + case Settings::NativeKeyboard::RightControl: + SetButton(keyboard_key_identifier, Settings::NativeKeyboard::RightControlKey, + key_value); + break; + case Settings::NativeKeyboard::RightShift: + SetButton(keyboard_key_identifier, Settings::NativeKeyboard::RightShiftKey, key_value); + break; + case Settings::NativeKeyboard::RightAlt: + SetButton(keyboard_key_identifier, Settings::NativeKeyboard::RightAltKey, key_value); + break; + case Settings::NativeKeyboard::RightMeta: + SetButton(keyboard_key_identifier, Settings::NativeKeyboard::RightMetaKey, key_value); + break; + default: + // Other modifier keys should be pressed with PressKey since they stay enabled until + // next press + break; + } + } +} + +void Keyboard::ReleaseAllKeys() { + ResetButtonState(); +} + +std::vector Keyboard::GetInputDevices() const { + std::vector devices; + devices.emplace_back(Common::ParamPackage{ + {"engine", GetEngineName()}, + {"display", "Keyboard Only"}, + }); + return devices; +} + +} // namespace InputCommon diff --git a/src/input_common/drivers/keyboard.h b/src/input_common/drivers/keyboard.h new file mode 100644 index 0000000000..ad123b1368 --- /dev/null +++ b/src/input_common/drivers/keyboard.h @@ -0,0 +1,56 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included + +#pragma once + +#include "input_common/input_engine.h" + +namespace InputCommon { + +/** + * A button device factory representing a keyboard. It receives keyboard events and forward them + * to all button devices it created. + */ +class Keyboard final : public InputCommon::InputEngine { +public: + explicit Keyboard(const std::string& input_engine_); + + /** + * Sets the status of all buttons bound with the key to pressed + * @param key_code the code of the key to press + */ + void PressKey(int key_code); + + /** + * Sets the status of all buttons bound with the key to released + * @param key_code the code of the key to release + */ + void ReleaseKey(int key_code); + + /** + * Sets the status of the keyboard key to pressed + * @param key_index index of the key to press + */ + void PressKeyboardKey(int key_index); + + /** + * Sets the status of the keyboard key to released + * @param key_index index of the key to release + */ + void ReleaseKeyboardKey(int key_index); + + /** + * Sets the status of all keyboard modifier keys + * @param key_modifiers the code of the key to release + */ + void SetKeyboardModifiers(int key_modifiers); + + /// Sets all keys to the non pressed state + void ReleaseAllKeys(); + + /// Used for automapping features + std::vector GetInputDevices() const override; +}; + +} // namespace InputCommon diff --git a/src/input_common/drivers/mouse.cpp b/src/input_common/drivers/mouse.cpp new file mode 100644 index 0000000000..752118e97e --- /dev/null +++ b/src/input_common/drivers/mouse.cpp @@ -0,0 +1,185 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included + +#include +#include +#include + +#include "common/param_package.h" +#include "common/settings.h" +#include "common/thread.h" +#include "input_common/drivers/mouse.h" + +namespace InputCommon { +constexpr int mouse_axis_x = 0; +constexpr int mouse_axis_y = 1; +constexpr int wheel_axis_x = 2; +constexpr int wheel_axis_y = 3; +constexpr int touch_axis_x = 10; +constexpr int touch_axis_y = 11; +constexpr PadIdentifier identifier = { + .guid = Common::UUID{Common::INVALID_UUID}, + .port = 0, + .pad = 0, +}; + +Mouse::Mouse(const std::string& input_engine_) : InputEngine(input_engine_) { + PreSetController(identifier); + PreSetAxis(identifier, mouse_axis_x); + PreSetAxis(identifier, mouse_axis_y); + PreSetAxis(identifier, wheel_axis_x); + PreSetAxis(identifier, wheel_axis_y); + PreSetAxis(identifier, touch_axis_x); + PreSetAxis(identifier, touch_axis_x); + update_thread = std::jthread([this](std::stop_token stop_token) { UpdateThread(stop_token); }); +} + +void Mouse::UpdateThread(std::stop_token stop_token) { + Common::SetCurrentThreadName("yuzu:input:Mouse"); + constexpr int update_time = 10; + while (!stop_token.stop_requested()) { + if (Settings::values.mouse_panning && !Settings::values.mouse_enabled) { + // Slow movement by 4% + last_mouse_change *= 0.96f; + const float sensitivity = + Settings::values.mouse_panning_sensitivity.GetValue() * 0.022f; + SetAxis(identifier, mouse_axis_x, last_mouse_change.x * sensitivity); + SetAxis(identifier, mouse_axis_y, -last_mouse_change.y * sensitivity); + } + + if (mouse_panning_timout++ > 20) { + StopPanning(); + } + std::this_thread::sleep_for(std::chrono::milliseconds(update_time)); + } +} + +void Mouse::MouseMove(int x, int y, f32 touch_x, f32 touch_y, int center_x, int center_y) { + // If native mouse is enabled just set the screen coordinates + if (Settings::values.mouse_enabled) { + SetAxis(identifier, mouse_axis_x, touch_x); + SetAxis(identifier, mouse_axis_y, touch_y); + return; + } + + SetAxis(identifier, touch_axis_x, touch_x); + SetAxis(identifier, touch_axis_y, touch_y); + + if (Settings::values.mouse_panning) { + auto mouse_change = + (Common::MakeVec(x, y) - Common::MakeVec(center_x, center_y)).Cast(); + mouse_panning_timout = 0; + + const auto move_distance = mouse_change.Length(); + if (move_distance == 0) { + return; + } + + // Make slow movements at least 3 units on lenght + if (move_distance < 3.0f) { + // Normalize value + mouse_change /= move_distance; + mouse_change *= 3.0f; + } + + // Average mouse movements + last_mouse_change = (last_mouse_change * 0.91f) + (mouse_change * 0.09f); + + const auto last_move_distance = last_mouse_change.Length(); + + // Make fast movements clamp to 8 units on lenght + if (last_move_distance > 8.0f) { + // Normalize value + last_mouse_change /= last_move_distance; + last_mouse_change *= 8.0f; + } + + // Ignore average if it's less than 1 unit and use current movement value + if (last_move_distance < 1.0f) { + last_mouse_change = mouse_change / mouse_change.Length(); + } + + return; + } + + if (button_pressed) { + const auto mouse_move = Common::MakeVec(x, y) - mouse_origin; + const float sensitivity = Settings::values.mouse_panning_sensitivity.GetValue() * 0.0012f; + SetAxis(identifier, mouse_axis_x, static_cast(mouse_move.x) * sensitivity); + SetAxis(identifier, mouse_axis_y, static_cast(-mouse_move.y) * sensitivity); + } +} + +void Mouse::PressButton(int x, int y, f32 touch_x, f32 touch_y, MouseButton button) { + SetAxis(identifier, touch_axis_x, touch_x); + SetAxis(identifier, touch_axis_y, touch_y); + SetButton(identifier, static_cast(button), true); + // Set initial analog parameters + mouse_origin = {x, y}; + last_mouse_position = {x, y}; + button_pressed = true; +} + +void Mouse::ReleaseButton(MouseButton button) { + SetButton(identifier, static_cast(button), false); + + if (!Settings::values.mouse_panning && !Settings::values.mouse_enabled) { + SetAxis(identifier, mouse_axis_x, 0); + SetAxis(identifier, mouse_axis_y, 0); + } + button_pressed = false; +} + +void Mouse::MouseWheelChange(int x, int y) { + wheel_position.x += x; + wheel_position.y += y; + SetAxis(identifier, wheel_axis_x, static_cast(wheel_position.x)); + SetAxis(identifier, wheel_axis_y, static_cast(wheel_position.y)); +} + +void Mouse::ReleaseAllButtons() { + ResetButtonState(); + button_pressed = false; +} + +void Mouse::StopPanning() { + last_mouse_change = {}; +} + +std::vector Mouse::GetInputDevices() const { + std::vector devices; + devices.emplace_back(Common::ParamPackage{ + {"engine", GetEngineName()}, + {"display", "Keyboard/Mouse"}, + }); + return devices; +} + +AnalogMapping Mouse::GetAnalogMappingForDevice( + [[maybe_unused]] const Common::ParamPackage& params) { + // Only overwrite different buttons from default + AnalogMapping mapping = {}; + Common::ParamPackage right_analog_params; + right_analog_params.Set("engine", GetEngineName()); + right_analog_params.Set("axis_x", 0); + right_analog_params.Set("axis_y", 1); + right_analog_params.Set("threshold", 0.5f); + right_analog_params.Set("range", 1.0f); + right_analog_params.Set("deadzone", 0.0f); + mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params)); + return mapping; +} + +Common::Input::ButtonNames Mouse::GetUIName(const Common::ParamPackage& params) const { + if (params.Has("button")) { + return Common::Input::ButtonNames::Value; + } + if (params.Has("axis")) { + return Common::Input::ButtonNames::Value; + } + + return Common::Input::ButtonNames::Invalid; +} + +} // namespace InputCommon diff --git a/src/input_common/drivers/mouse.h b/src/input_common/drivers/mouse.h new file mode 100644 index 0000000000..4a1fd2fd96 --- /dev/null +++ b/src/input_common/drivers/mouse.h @@ -0,0 +1,81 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included + +#pragma once + +#include +#include + +#include "common/vector_math.h" +#include "input_common/input_engine.h" + +namespace InputCommon { + +enum class MouseButton { + Left, + Right, + Wheel, + Backward, + Forward, + Task, + Extra, + Undefined, +}; + +/** + * A button device factory representing a keyboard. It receives keyboard events and forward them + * to all button devices it created. + */ +class Mouse final : public InputCommon::InputEngine { +public: + explicit Mouse(const std::string& input_engine_); + + /** + * Signals that mouse has moved. + * @param x the x-coordinate of the cursor + * @param y the y-coordinate of the cursor + * @param center_x the x-coordinate of the middle of the screen + * @param center_y the y-coordinate of the middle of the screen + */ + void MouseMove(int x, int y, f32 touch_x, f32 touch_y, int center_x, int center_y); + + /** + * Sets the status of all buttons bound with the key to pressed + * @param key_code the code of the key to press + */ + void PressButton(int x, int y, f32 touch_x, f32 touch_y, MouseButton button); + + /** + * Sets the status of all buttons bound with the key to released + * @param key_code the code of the key to release + */ + void ReleaseButton(MouseButton button); + + /** + * Sets the status of the mouse wheel + * @param x delta movement in the x direction + * @param y delta movement in the y direction + */ + void MouseWheelChange(int x, int y); + + void ReleaseAllButtons(); + + std::vector GetInputDevices() const override; + AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override; + Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override; + +private: + void UpdateThread(std::stop_token stop_token); + void StopPanning(); + + Common::Vec2 mouse_origin; + Common::Vec2 last_mouse_position; + Common::Vec2 last_mouse_change; + Common::Vec2 wheel_position; + bool button_pressed; + int mouse_panning_timout{}; + std::jthread update_thread; +}; + +} // namespace InputCommon diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp new file mode 100644 index 0000000000..90128b6cf6 --- /dev/null +++ b/src/input_common/drivers/sdl_driver.cpp @@ -0,0 +1,924 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/logging/log.h" +#include "common/math_util.h" +#include "common/param_package.h" +#include "common/settings.h" +#include "common/thread.h" +#include "common/vector_math.h" +#include "input_common/drivers/sdl_driver.h" + +namespace InputCommon { + +namespace { +std::string GetGUID(SDL_Joystick* joystick) { + const SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick); + char guid_str[33]; + SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str)); + return guid_str; +} +} // Anonymous namespace + +static int SDLEventWatcher(void* user_data, SDL_Event* event) { + auto* const sdl_state = static_cast(user_data); + + sdl_state->HandleGameControllerEvent(*event); + + return 0; +} + +class SDLJoystick { +public: + SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick, + SDL_GameController* game_controller) + : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose}, + sdl_controller{game_controller, &SDL_GameControllerClose} { + EnableMotion(); + } + + void EnableMotion() { + if (sdl_controller) { + SDL_GameController* controller = sdl_controller.get(); + if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL) && !has_accel) { + SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE); + has_accel = true; + } + if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) && !has_gyro) { + SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_TRUE); + has_gyro = true; + } + } + } + + bool HasGyro() const { + return has_gyro; + } + + bool HasAccel() const { + return has_accel; + } + + bool UpdateMotion(SDL_ControllerSensorEvent event) { + constexpr float gravity_constant = 9.80665f; + std::lock_guard lock{mutex}; + const u64 time_difference = event.timestamp - last_motion_update; + last_motion_update = event.timestamp; + switch (event.sensor) { + case SDL_SENSOR_ACCEL: { + motion.accel_x = -event.data[0] / gravity_constant; + motion.accel_y = event.data[2] / gravity_constant; + motion.accel_z = -event.data[1] / gravity_constant; + break; + } + case SDL_SENSOR_GYRO: { + motion.gyro_x = event.data[0] / (Common::PI * 2); + motion.gyro_y = -event.data[2] / (Common::PI * 2); + motion.gyro_z = event.data[1] / (Common::PI * 2); + break; + } + } + + // Ignore duplicated timestamps + if (time_difference == 0) { + return false; + } + motion.delta_timestamp = time_difference * 1000; + return true; + } + + BasicMotion GetMotion() { + return motion; + } + + bool RumblePlay(const Common::Input::VibrationStatus vibration) { + constexpr u32 rumble_max_duration_ms = 1000; + if (sdl_controller) { + return SDL_GameControllerRumble( + sdl_controller.get(), static_cast(vibration.low_amplitude), + static_cast(vibration.high_amplitude), rumble_max_duration_ms) != -1; + } else if (sdl_joystick) { + return SDL_JoystickRumble(sdl_joystick.get(), static_cast(vibration.low_amplitude), + static_cast(vibration.high_amplitude), + rumble_max_duration_ms) != -1; + } + + return false; + } + + bool HasHDRumble() const { + if (sdl_controller) { + return (SDL_GameControllerGetType(sdl_controller.get()) == + SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO); + } + return false; + } + /** + * The Pad identifier of the joystick + */ + const PadIdentifier GetPadIdentifier() const { + return { + .guid = Common::UUID{guid}, + .port = static_cast(port), + .pad = 0, + }; + } + + /** + * The guid of the joystick + */ + const std::string& GetGUID() const { + return guid; + } + + /** + * The number of joystick from the same type that were connected before this joystick + */ + int GetPort() const { + return port; + } + + SDL_Joystick* GetSDLJoystick() const { + return sdl_joystick.get(); + } + + SDL_GameController* GetSDLGameController() const { + return sdl_controller.get(); + } + + void SetSDLJoystick(SDL_Joystick* joystick, SDL_GameController* controller) { + sdl_joystick.reset(joystick); + sdl_controller.reset(controller); + } + + bool IsJoyconLeft() const { + const std::string controller_name = GetControllerName(); + if (std::strstr(controller_name.c_str(), "Joy-Con Left") != nullptr) { + return true; + } + if (std::strstr(controller_name.c_str(), "Joy-Con (L)") != nullptr) { + return true; + } + return false; + } + + bool IsJoyconRight() const { + const std::string controller_name = GetControllerName(); + if (std::strstr(controller_name.c_str(), "Joy-Con Right") != nullptr) { + return true; + } + if (std::strstr(controller_name.c_str(), "Joy-Con (R)") != nullptr) { + return true; + } + return false; + } + + BatteryLevel GetBatteryLevel() { + const auto level = SDL_JoystickCurrentPowerLevel(sdl_joystick.get()); + switch (level) { + case SDL_JOYSTICK_POWER_EMPTY: + return BatteryLevel::Empty; + case SDL_JOYSTICK_POWER_LOW: + return BatteryLevel::Critical; + case SDL_JOYSTICK_POWER_MEDIUM: + return BatteryLevel::Low; + case SDL_JOYSTICK_POWER_FULL: + return BatteryLevel::Medium; + case SDL_JOYSTICK_POWER_MAX: + return BatteryLevel::Full; + case SDL_JOYSTICK_POWER_UNKNOWN: + case SDL_JOYSTICK_POWER_WIRED: + default: + return BatteryLevel::Charging; + } + } + + std::string GetControllerName() const { + if (sdl_controller) { + switch (SDL_GameControllerGetType(sdl_controller.get())) { + case SDL_CONTROLLER_TYPE_XBOX360: + return "XBox 360 Controller"; + case SDL_CONTROLLER_TYPE_XBOXONE: + return "XBox One Controller"; + default: + break; + } + const auto name = SDL_GameControllerName(sdl_controller.get()); + if (name) { + return name; + } + } + + if (sdl_joystick) { + const auto name = SDL_JoystickName(sdl_joystick.get()); + if (name) { + return name; + } + } + + return "Unknown"; + } + +private: + std::string guid; + int port; + std::unique_ptr sdl_joystick; + std::unique_ptr sdl_controller; + mutable std::mutex mutex; + + u64 last_motion_update{}; + bool has_gyro{false}; + bool has_accel{false}; + BasicMotion motion; +}; + +std::shared_ptr SDLDriver::GetSDLJoystickByGUID(const std::string& guid, int port) { + std::lock_guard lock{joystick_map_mutex}; + const auto it = joystick_map.find(guid); + + if (it != joystick_map.end()) { + while (it->second.size() <= static_cast(port)) { + auto joystick = std::make_shared(guid, static_cast(it->second.size()), + nullptr, nullptr); + it->second.emplace_back(std::move(joystick)); + } + + return it->second[static_cast(port)]; + } + + auto joystick = std::make_shared(guid, 0, nullptr, nullptr); + + return joystick_map[guid].emplace_back(std::move(joystick)); +} + +std::shared_ptr SDLDriver::GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) { + auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id); + const std::string guid = GetGUID(sdl_joystick); + + std::lock_guard lock{joystick_map_mutex}; + const auto map_it = joystick_map.find(guid); + + if (map_it == joystick_map.end()) { + return nullptr; + } + + const auto vec_it = std::find_if(map_it->second.begin(), map_it->second.end(), + [&sdl_joystick](const auto& joystick) { + return joystick->GetSDLJoystick() == sdl_joystick; + }); + + if (vec_it == map_it->second.end()) { + return nullptr; + } + + return *vec_it; +} + +void SDLDriver::InitJoystick(int joystick_index) { + SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index); + SDL_GameController* sdl_gamecontroller = nullptr; + + if (SDL_IsGameController(joystick_index)) { + sdl_gamecontroller = SDL_GameControllerOpen(joystick_index); + } + + if (!sdl_joystick) { + LOG_ERROR(Input, "Failed to open joystick {}", joystick_index); + return; + } + + const std::string guid = GetGUID(sdl_joystick); + + std::lock_guard lock{joystick_map_mutex}; + if (joystick_map.find(guid) == joystick_map.end()) { + auto joystick = std::make_shared(guid, 0, sdl_joystick, sdl_gamecontroller); + PreSetController(joystick->GetPadIdentifier()); + SetBattery(joystick->GetPadIdentifier(), joystick->GetBatteryLevel()); + joystick_map[guid].emplace_back(std::move(joystick)); + return; + } + + auto& joystick_guid_list = joystick_map[guid]; + const auto joystick_it = + std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(), + [](const auto& joystick) { return !joystick->GetSDLJoystick(); }); + + if (joystick_it != joystick_guid_list.end()) { + (*joystick_it)->SetSDLJoystick(sdl_joystick, sdl_gamecontroller); + return; + } + + const int port = static_cast(joystick_guid_list.size()); + auto joystick = std::make_shared(guid, port, sdl_joystick, sdl_gamecontroller); + PreSetController(joystick->GetPadIdentifier()); + SetBattery(joystick->GetPadIdentifier(), joystick->GetBatteryLevel()); + joystick_guid_list.emplace_back(std::move(joystick)); +} + +void SDLDriver::CloseJoystick(SDL_Joystick* sdl_joystick) { + const std::string guid = GetGUID(sdl_joystick); + + std::lock_guard lock{joystick_map_mutex}; + // This call to guid is safe since the joystick is guaranteed to be in the map + const auto& joystick_guid_list = joystick_map[guid]; + const auto joystick_it = std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(), + [&sdl_joystick](const auto& joystick) { + return joystick->GetSDLJoystick() == sdl_joystick; + }); + + if (joystick_it != joystick_guid_list.end()) { + (*joystick_it)->SetSDLJoystick(nullptr, nullptr); + } +} + +void SDLDriver::HandleGameControllerEvent(const SDL_Event& event) { + switch (event.type) { + case SDL_JOYBUTTONUP: { + if (const auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) { + const PadIdentifier identifier = joystick->GetPadIdentifier(); + SetButton(identifier, event.jbutton.button, false); + } + break; + } + case SDL_JOYBUTTONDOWN: { + if (const auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) { + const PadIdentifier identifier = joystick->GetPadIdentifier(); + SetButton(identifier, event.jbutton.button, true); + } + break; + } + case SDL_JOYHATMOTION: { + if (const auto joystick = GetSDLJoystickBySDLID(event.jhat.which)) { + const PadIdentifier identifier = joystick->GetPadIdentifier(); + SetHatButton(identifier, event.jhat.hat, event.jhat.value); + } + break; + } + case SDL_JOYAXISMOTION: { + if (const auto joystick = GetSDLJoystickBySDLID(event.jaxis.which)) { + const PadIdentifier identifier = joystick->GetPadIdentifier(); + SetAxis(identifier, event.jaxis.axis, event.jaxis.value / 32767.0f); + } + break; + } + case SDL_CONTROLLERSENSORUPDATE: { + if (auto joystick = GetSDLJoystickBySDLID(event.csensor.which)) { + if (joystick->UpdateMotion(event.csensor)) { + const PadIdentifier identifier = joystick->GetPadIdentifier(); + SetMotion(identifier, 0, joystick->GetMotion()); + }; + } + break; + } + case SDL_JOYDEVICEREMOVED: + LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which); + CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which)); + break; + case SDL_JOYDEVICEADDED: + LOG_DEBUG(Input, "Controller connected with device index {}", event.jdevice.which); + InitJoystick(event.jdevice.which); + break; + } +} + +void SDLDriver::CloseJoysticks() { + std::lock_guard lock{joystick_map_mutex}; + joystick_map.clear(); +} + +SDLDriver::SDLDriver(const std::string& input_engine_) : InputEngine(input_engine_) { + Common::SetCurrentThreadName("yuzu:input:SDL"); + + if (!Settings::values.enable_raw_input) { + // Disable raw input. When enabled this setting causes SDL to die when a web applet opens + SDL_SetHint(SDL_HINT_JOYSTICK_RAWINPUT, "0"); + } + + // Prevent SDL from adding undesired axis + SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0"); + + // Enable HIDAPI rumble. This prevents SDL from disabling motion on PS4 and PS5 controllers + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1"); + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1"); + SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); + + // Use hidapi driver for joycons. This will allow joycons to be detected as a GameController and + // not a generic one + SDL_SetHint("SDL_JOYSTICK_HIDAPI_JOY_CONS", "1"); + + // Turn off Pro controller home led + SDL_SetHint("SDL_JOYSTICK_HIDAPI_SWITCH_HOME_LED", "0"); + + // If the frontend is going to manage the event loop, then we don't start one here + start_thread = SDL_WasInit(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) == 0; + if (start_thread && SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) < 0) { + LOG_CRITICAL(Input, "SDL_Init failed with: {}", SDL_GetError()); + return; + } + + SDL_AddEventWatch(&SDLEventWatcher, this); + + initialized = true; + if (start_thread) { + poll_thread = std::thread([this] { + using namespace std::chrono_literals; + while (initialized) { + SDL_PumpEvents(); + std::this_thread::sleep_for(1ms); + } + }); + } + // Because the events for joystick connection happens before we have our event watcher added, we + // can just open all the joysticks right here + for (int i = 0; i < SDL_NumJoysticks(); ++i) { + InitJoystick(i); + } +} + +SDLDriver::~SDLDriver() { + CloseJoysticks(); + SDL_DelEventWatch(&SDLEventWatcher, this); + + initialized = false; + if (start_thread) { + poll_thread.join(); + SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER); + } +} + +std::vector SDLDriver::GetInputDevices() const { + std::vector devices; + std::unordered_map> joycon_pairs; + for (const auto& [key, value] : joystick_map) { + for (const auto& joystick : value) { + if (!joystick->GetSDLJoystick()) { + continue; + } + const std::string name = + fmt::format("{} {}", joystick->GetControllerName(), joystick->GetPort()); + devices.emplace_back(Common::ParamPackage{ + {"engine", GetEngineName()}, + {"display", std::move(name)}, + {"guid", joystick->GetGUID()}, + {"port", std::to_string(joystick->GetPort())}, + }); + if (joystick->IsJoyconLeft()) { + joycon_pairs.insert_or_assign(joystick->GetPort(), joystick); + } + } + } + + // Add dual controllers + for (const auto& [key, value] : joystick_map) { + for (const auto& joystick : value) { + if (joystick->IsJoyconRight()) { + if (!joycon_pairs.contains(joystick->GetPort())) { + continue; + } + const auto joystick2 = joycon_pairs.at(joystick->GetPort()); + + const std::string name = + fmt::format("{} {}", "Nintendo Dual Joy-Con", joystick->GetPort()); + devices.emplace_back(Common::ParamPackage{ + {"engine", GetEngineName()}, + {"display", std::move(name)}, + {"guid", joystick->GetGUID()}, + {"guid2", joystick2->GetGUID()}, + {"port", std::to_string(joystick->GetPort())}, + }); + } + } + } + return devices; +} +Common::Input::VibrationError SDLDriver::SetRumble(const PadIdentifier& identifier, + const Common::Input::VibrationStatus vibration) { + const auto joystick = + GetSDLJoystickByGUID(identifier.guid.Format(), static_cast(identifier.port)); + const auto process_amplitude_exp = [](f32 amplitude, f32 factor) { + return (amplitude + std::pow(amplitude, factor)) * 0.5f * 0xFFFF; + }; + + // Default exponential curve for rumble + f32 factor = 0.35f; + + // If vibration is set as a linear output use a flatter value + if (vibration.type == Common::Input::VibrationAmplificationType::Linear) { + factor = 0.5f; + } + + // Amplitude for HD rumble needs no modification + if (joystick->HasHDRumble()) { + factor = 1.0f; + } + + const Common::Input::VibrationStatus new_vibration{ + .low_amplitude = process_amplitude_exp(vibration.low_amplitude, factor), + .low_frequency = vibration.low_frequency, + .high_amplitude = process_amplitude_exp(vibration.high_amplitude, factor), + .high_frequency = vibration.high_frequency, + .type = Common::Input::VibrationAmplificationType::Exponential, + }; + + if (!joystick->RumblePlay(new_vibration)) { + return Common::Input::VibrationError::Unknown; + } + + return Common::Input::VibrationError::None; +} +Common::ParamPackage SDLDriver::BuildAnalogParamPackageForButton(int port, std::string guid, + s32 axis, float value) const { + Common::ParamPackage params{}; + params.Set("engine", GetEngineName()); + params.Set("port", port); + params.Set("guid", std::move(guid)); + params.Set("axis", axis); + params.Set("threshold", "0.5"); + params.Set("invert", value < 0 ? "-" : "+"); + return params; +} + +Common::ParamPackage SDLDriver::BuildButtonParamPackageForButton(int port, std::string guid, + s32 button) const { + Common::ParamPackage params{}; + params.Set("engine", GetEngineName()); + params.Set("port", port); + params.Set("guid", std::move(guid)); + params.Set("button", button); + return params; +} + +Common::ParamPackage SDLDriver::BuildHatParamPackageForButton(int port, std::string guid, s32 hat, + u8 value) const { + Common::ParamPackage params{}; + params.Set("engine", GetEngineName()); + params.Set("port", port); + params.Set("guid", std::move(guid)); + params.Set("hat", hat); + params.Set("direction", GetHatButtonName(value)); + return params; +} + +Common::ParamPackage SDLDriver::BuildMotionParam(int port, std::string guid) const { + Common::ParamPackage params{}; + params.Set("engine", GetEngineName()); + params.Set("motion", 0); + params.Set("port", port); + params.Set("guid", std::move(guid)); + return params; +} + +Common::ParamPackage SDLDriver::BuildParamPackageForBinding( + int port, const std::string& guid, const SDL_GameControllerButtonBind& binding) const { + switch (binding.bindType) { + case SDL_CONTROLLER_BINDTYPE_NONE: + break; + case SDL_CONTROLLER_BINDTYPE_AXIS: + return BuildAnalogParamPackageForButton(port, guid, binding.value.axis); + case SDL_CONTROLLER_BINDTYPE_BUTTON: + return BuildButtonParamPackageForButton(port, guid, binding.value.button); + case SDL_CONTROLLER_BINDTYPE_HAT: + return BuildHatParamPackageForButton(port, guid, binding.value.hat.hat, + static_cast(binding.value.hat.hat_mask)); + } + return {}; +} + +Common::ParamPackage SDLDriver::BuildParamPackageForAnalog(PadIdentifier identifier, int axis_x, + int axis_y, float offset_x, + float offset_y) const { + Common::ParamPackage params; + params.Set("engine", GetEngineName()); + params.Set("port", static_cast(identifier.port)); + params.Set("guid", identifier.guid.Format()); + params.Set("axis_x", axis_x); + params.Set("axis_y", axis_y); + params.Set("offset_x", offset_x); + params.Set("offset_y", offset_y); + params.Set("invert_x", "+"); + params.Set("invert_y", "+"); + return params; +} + +ButtonMapping SDLDriver::GetButtonMappingForDevice(const Common::ParamPackage& params) { + if (!params.Has("guid") || !params.Has("port")) { + return {}; + } + const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0)); + + auto* controller = joystick->GetSDLGameController(); + if (controller == nullptr) { + return {}; + } + + // This list is missing ZL/ZR since those are not considered buttons in SDL GameController. + // We will add those afterwards + // This list also excludes Screenshot since theres not really a mapping for that + ButtonBindings switch_to_sdl_button; + + if (SDL_GameControllerGetType(controller) == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO) { + switch_to_sdl_button = GetNintendoButtonBinding(joystick); + } else { + switch_to_sdl_button = GetDefaultButtonBinding(); + } + + // Add the missing bindings for ZL/ZR + static constexpr ZButtonBindings switch_to_sdl_axis{{ + {Settings::NativeButton::ZL, SDL_CONTROLLER_AXIS_TRIGGERLEFT}, + {Settings::NativeButton::ZR, SDL_CONTROLLER_AXIS_TRIGGERRIGHT}, + }}; + + // Parameters contain two joysticks return dual + if (params.Has("guid2")) { + const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0)); + + if (joystick2->GetSDLGameController() != nullptr) { + return GetDualControllerMapping(joystick, joystick2, switch_to_sdl_button, + switch_to_sdl_axis); + } + } + + return GetSingleControllerMapping(joystick, switch_to_sdl_button, switch_to_sdl_axis); +} + +ButtonBindings SDLDriver::GetDefaultButtonBinding() const { + return { + std::pair{Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_B}, + {Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_A}, + {Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_Y}, + {Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_X}, + {Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK}, + {Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK}, + {Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, + {Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, + {Settings::NativeButton::Plus, SDL_CONTROLLER_BUTTON_START}, + {Settings::NativeButton::Minus, SDL_CONTROLLER_BUTTON_BACK}, + {Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT}, + {Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP}, + {Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT}, + {Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN}, + {Settings::NativeButton::SL, SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, + {Settings::NativeButton::SR, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, + {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE}, + }; +} + +ButtonBindings SDLDriver::GetNintendoButtonBinding( + const std::shared_ptr& joystick) const { + // Default SL/SR mapping for pro controllers + auto sl_button = SDL_CONTROLLER_BUTTON_LEFTSHOULDER; + auto sr_button = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER; + + if (joystick->IsJoyconLeft()) { + sl_button = SDL_CONTROLLER_BUTTON_PADDLE2; + sr_button = SDL_CONTROLLER_BUTTON_PADDLE4; + } + if (joystick->IsJoyconRight()) { + sl_button = SDL_CONTROLLER_BUTTON_PADDLE3; + sr_button = SDL_CONTROLLER_BUTTON_PADDLE1; + } + + return { + std::pair{Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_A}, + {Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_B}, + {Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_X}, + {Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_Y}, + {Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK}, + {Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK}, + {Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, + {Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, + {Settings::NativeButton::Plus, SDL_CONTROLLER_BUTTON_START}, + {Settings::NativeButton::Minus, SDL_CONTROLLER_BUTTON_BACK}, + {Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT}, + {Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP}, + {Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT}, + {Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN}, + {Settings::NativeButton::SL, sl_button}, + {Settings::NativeButton::SR, sr_button}, + {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE}, + }; +} + +ButtonMapping SDLDriver::GetSingleControllerMapping( + const std::shared_ptr& joystick, const ButtonBindings& switch_to_sdl_button, + const ZButtonBindings& switch_to_sdl_axis) const { + ButtonMapping mapping; + mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size()); + auto* controller = joystick->GetSDLGameController(); + + for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) { + const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button); + mapping.insert_or_assign( + switch_button, + BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); + } + for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) { + const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis); + mapping.insert_or_assign( + switch_button, + BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); + } + + return mapping; +} + +ButtonMapping SDLDriver::GetDualControllerMapping(const std::shared_ptr& joystick, + const std::shared_ptr& joystick2, + const ButtonBindings& switch_to_sdl_button, + const ZButtonBindings& switch_to_sdl_axis) const { + ButtonMapping mapping; + mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size()); + auto* controller = joystick->GetSDLGameController(); + auto* controller2 = joystick2->GetSDLGameController(); + + for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) { + if (IsButtonOnLeftSide(switch_button)) { + const auto& binding = SDL_GameControllerGetBindForButton(controller2, sdl_button); + mapping.insert_or_assign( + switch_button, + BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), binding)); + continue; + } + const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button); + mapping.insert_or_assign( + switch_button, + BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); + } + for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) { + if (IsButtonOnLeftSide(switch_button)) { + const auto& binding = SDL_GameControllerGetBindForAxis(controller2, sdl_axis); + mapping.insert_or_assign( + switch_button, + BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), binding)); + continue; + } + const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis); + mapping.insert_or_assign( + switch_button, + BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); + } + + return mapping; +} + +bool SDLDriver::IsButtonOnLeftSide(Settings::NativeButton::Values button) const { + switch (button) { + case Settings::NativeButton::DDown: + case Settings::NativeButton::DLeft: + case Settings::NativeButton::DRight: + case Settings::NativeButton::DUp: + case Settings::NativeButton::L: + case Settings::NativeButton::LStick: + case Settings::NativeButton::Minus: + case Settings::NativeButton::Screenshot: + case Settings::NativeButton::ZL: + return true; + default: + return false; + } +} + +AnalogMapping SDLDriver::GetAnalogMappingForDevice(const Common::ParamPackage& params) { + if (!params.Has("guid") || !params.Has("port")) { + return {}; + } + const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0)); + const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0)); + auto* controller = joystick->GetSDLGameController(); + if (controller == nullptr) { + return {}; + } + + AnalogMapping mapping = {}; + const auto& binding_left_x = + SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX); + const auto& binding_left_y = + SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY); + if (params.Has("guid2")) { + const auto identifier = joystick2->GetPadIdentifier(); + PreSetController(identifier); + PreSetAxis(identifier, binding_left_x.value.axis); + PreSetAxis(identifier, binding_left_y.value.axis); + const auto left_offset_x = -GetAxis(identifier, binding_left_x.value.axis); + const auto left_offset_y = -GetAxis(identifier, binding_left_y.value.axis); + mapping.insert_or_assign(Settings::NativeAnalog::LStick, + BuildParamPackageForAnalog(identifier, binding_left_x.value.axis, + binding_left_y.value.axis, + left_offset_x, left_offset_y)); + } else { + const auto identifier = joystick->GetPadIdentifier(); + PreSetController(identifier); + PreSetAxis(identifier, binding_left_x.value.axis); + PreSetAxis(identifier, binding_left_y.value.axis); + const auto left_offset_x = -GetAxis(identifier, binding_left_x.value.axis); + const auto left_offset_y = -GetAxis(identifier, binding_left_y.value.axis); + mapping.insert_or_assign(Settings::NativeAnalog::LStick, + BuildParamPackageForAnalog(identifier, binding_left_x.value.axis, + binding_left_y.value.axis, + left_offset_x, left_offset_y)); + } + const auto& binding_right_x = + SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX); + const auto& binding_right_y = + SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY); + const auto identifier = joystick->GetPadIdentifier(); + PreSetController(identifier); + PreSetAxis(identifier, binding_right_x.value.axis); + PreSetAxis(identifier, binding_right_y.value.axis); + const auto right_offset_x = -GetAxis(identifier, binding_right_x.value.axis); + const auto right_offset_y = -GetAxis(identifier, binding_right_y.value.axis); + mapping.insert_or_assign(Settings::NativeAnalog::RStick, + BuildParamPackageForAnalog(identifier, binding_right_x.value.axis, + binding_right_y.value.axis, right_offset_x, + right_offset_y)); + return mapping; +} + +MotionMapping SDLDriver::GetMotionMappingForDevice(const Common::ParamPackage& params) { + if (!params.Has("guid") || !params.Has("port")) { + return {}; + } + const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0)); + const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0)); + auto* controller = joystick->GetSDLGameController(); + if (controller == nullptr) { + return {}; + } + + MotionMapping mapping = {}; + joystick->EnableMotion(); + + if (joystick->HasGyro() || joystick->HasAccel()) { + mapping.insert_or_assign(Settings::NativeMotion::MotionRight, + BuildMotionParam(joystick->GetPort(), joystick->GetGUID())); + } + if (params.Has("guid2")) { + joystick2->EnableMotion(); + if (joystick2->HasGyro() || joystick2->HasAccel()) { + mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, + BuildMotionParam(joystick2->GetPort(), joystick2->GetGUID())); + } + } else { + if (joystick->HasGyro() || joystick->HasAccel()) { + mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, + BuildMotionParam(joystick->GetPort(), joystick->GetGUID())); + } + } + + return mapping; +} + +Common::Input::ButtonNames SDLDriver::GetUIName(const Common::ParamPackage& params) const { + if (params.Has("button")) { + // TODO(German77): Find how to substitue the values for real button names + return Common::Input::ButtonNames::Value; + } + if (params.Has("hat")) { + return Common::Input::ButtonNames::Value; + } + if (params.Has("axis")) { + return Common::Input::ButtonNames::Value; + } + if (params.Has("axis_x") && params.Has("axis_y") && params.Has("axis_z")) { + return Common::Input::ButtonNames::Value; + } + if (params.Has("motion")) { + return Common::Input::ButtonNames::Engine; + } + + return Common::Input::ButtonNames::Invalid; +} + +std::string SDLDriver::GetHatButtonName(u8 direction_value) const { + switch (direction_value) { + case SDL_HAT_UP: + return "up"; + case SDL_HAT_DOWN: + return "down"; + case SDL_HAT_LEFT: + return "left"; + case SDL_HAT_RIGHT: + return "right"; + default: + return {}; + } +} + +u8 SDLDriver::GetHatButtonId(const std::string& direction_name) const { + Uint8 direction; + if (direction_name == "up") { + direction = SDL_HAT_UP; + } else if (direction_name == "down") { + direction = SDL_HAT_DOWN; + } else if (direction_name == "left") { + direction = SDL_HAT_LEFT; + } else if (direction_name == "right") { + direction = SDL_HAT_RIGHT; + } else { + direction = 0; + } + return direction; +} + +} // namespace InputCommon diff --git a/src/input_common/sdl/sdl_impl.h b/src/input_common/drivers/sdl_driver.h similarity index 66% rename from src/input_common/sdl/sdl_impl.h rename to src/input_common/drivers/sdl_driver.h index 7a9ad63465..d03ff4b84f 100644 --- a/src/input_common/sdl/sdl_impl.h +++ b/src/input_common/drivers/sdl_driver.h @@ -5,7 +5,6 @@ #pragma once #include -#include #include #include #include @@ -13,8 +12,7 @@ #include #include "common/common_types.h" -#include "common/threadsafe_queue.h" -#include "input_common/sdl/sdl.h" +#include "input_common/input_engine.h" union SDL_Event; using SDL_GameController = struct _SDL_GameController; @@ -26,21 +24,17 @@ using ButtonBindings = using ZButtonBindings = std::array, 2>; -namespace InputCommon::SDL { +namespace InputCommon { -class SDLAnalogFactory; -class SDLButtonFactory; -class SDLMotionFactory; -class SDLVibrationFactory; class SDLJoystick; -class SDLState : public State { +class SDLDriver : public InputCommon::InputEngine { public: /// Initializes and registers SDL device factories - SDLState(); + SDLDriver(const std::string& input_engine_); /// Unregisters SDL device factories and shut them down. - ~SDLState() override; + ~SDLDriver() override; /// Handle SDL_Events for joysticks from SDL_PollEvent void HandleGameControllerEvent(const SDL_Event& event); @@ -54,18 +48,18 @@ public: */ std::shared_ptr GetSDLJoystickByGUID(const std::string& guid, int port); - /// Get all DevicePoller that use the SDL backend for a specific device type - Pollers GetPollers(Polling::DeviceType type) override; - - /// Used by the Pollers during config - std::atomic polling = false; - Common::SPSCQueue event_queue; - - std::vector GetInputDevices() override; + std::vector GetInputDevices() const override; ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override; AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override; MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& params) override; + Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override; + + std::string GetHatButtonName(u8 direction_value) const override; + u8 GetHatButtonId(const std::string& direction_name) const override; + + Common::Input::VibrationError SetRumble( + const PadIdentifier& identifier, const Common::Input::VibrationStatus vibration) override; private: void InitJoystick(int joystick_index); @@ -74,6 +68,23 @@ private: /// Needs to be called before SDL_QuitSubSystem. void CloseJoysticks(); + Common::ParamPackage BuildAnalogParamPackageForButton(int port, std::string guid, s32 axis, + float value = 0.1f) const; + Common::ParamPackage BuildButtonParamPackageForButton(int port, std::string guid, + s32 button) const; + + Common::ParamPackage BuildHatParamPackageForButton(int port, std::string guid, s32 hat, + u8 value) const; + + Common::ParamPackage BuildMotionParam(int port, std::string guid) const; + + Common::ParamPackage BuildParamPackageForBinding( + int port, const std::string& guid, const SDL_GameControllerButtonBind& binding) const; + + Common::ParamPackage BuildParamPackageForAnalog(PadIdentifier identifier, int axis_x, + int axis_y, float offset_x, + float offset_y) const; + /// Returns the default button bindings list for generic controllers ButtonBindings GetDefaultButtonBinding() const; @@ -94,21 +105,13 @@ private: /// Returns true if the button is on the left joycon bool IsButtonOnLeftSide(Settings::NativeButton::Values button) const; - // Set to true if SDL supports game controller subsystem - bool has_gamecontroller = false; - /// Map of GUID of a list of corresponding virtual Joysticks std::unordered_map>> joystick_map; std::mutex joystick_map_mutex; - std::shared_ptr button_factory; - std::shared_ptr analog_factory; - std::shared_ptr vibration_factory; - std::shared_ptr motion_factory; - bool start_thread = false; std::atomic initialized = false; std::thread poll_thread; }; -} // namespace InputCommon::SDL +} // namespace InputCommon diff --git a/src/input_common/drivers/tas_input.cpp b/src/input_common/drivers/tas_input.cpp new file mode 100644 index 0000000000..0e01fb0d96 --- /dev/null +++ b/src/input_common/drivers/tas_input.cpp @@ -0,0 +1,315 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include +#include +#include + +#include "common/fs/file.h" +#include "common/fs/fs_types.h" +#include "common/fs/path_util.h" +#include "common/logging/log.h" +#include "common/settings.h" +#include "input_common/drivers/tas_input.h" + +namespace InputCommon::TasInput { + +enum TasAxes : u8 { + StickX, + StickY, + SubstickX, + SubstickY, + Undefined, +}; + +// Supported keywords and buttons from a TAS file +constexpr std::array, 20> text_to_tas_button = { + std::pair{"KEY_A", TasButton::BUTTON_A}, + {"KEY_B", TasButton::BUTTON_B}, + {"KEY_X", TasButton::BUTTON_X}, + {"KEY_Y", TasButton::BUTTON_Y}, + {"KEY_LSTICK", TasButton::STICK_L}, + {"KEY_RSTICK", TasButton::STICK_R}, + {"KEY_L", TasButton::TRIGGER_L}, + {"KEY_R", TasButton::TRIGGER_R}, + {"KEY_PLUS", TasButton::BUTTON_PLUS}, + {"KEY_MINUS", TasButton::BUTTON_MINUS}, + {"KEY_DLEFT", TasButton::BUTTON_LEFT}, + {"KEY_DUP", TasButton::BUTTON_UP}, + {"KEY_DRIGHT", TasButton::BUTTON_RIGHT}, + {"KEY_DDOWN", TasButton::BUTTON_DOWN}, + {"KEY_SL", TasButton::BUTTON_SL}, + {"KEY_SR", TasButton::BUTTON_SR}, + {"KEY_CAPTURE", TasButton::BUTTON_CAPTURE}, + {"KEY_HOME", TasButton::BUTTON_HOME}, + {"KEY_ZL", TasButton::TRIGGER_ZL}, + {"KEY_ZR", TasButton::TRIGGER_ZR}, +}; + +Tas::Tas(const std::string& input_engine_) : InputCommon::InputEngine(input_engine_) { + for (size_t player_index = 0; player_index < PLAYER_NUMBER; player_index++) { + PadIdentifier identifier{ + .guid = Common::UUID{}, + .port = player_index, + .pad = 0, + }; + PreSetController(identifier); + } + ClearInput(); + if (!Settings::values.tas_enable) { + needs_reset = true; + return; + } + LoadTasFiles(); +} + +Tas::~Tas() { + Stop(); +}; + +void Tas::LoadTasFiles() { + script_length = 0; + for (size_t i = 0; i < commands.size(); i++) { + LoadTasFile(i, 0); + if (commands[i].size() > script_length) { + script_length = commands[i].size(); + } + } +} + +void Tas::LoadTasFile(size_t player_index, size_t file_index) { + if (!commands[player_index].empty()) { + commands[player_index].clear(); + } + std::string file = Common::FS::ReadStringFromFile( + Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) / + fmt::format("script{}-{}.txt", file_index, player_index + 1), + Common::FS::FileType::BinaryFile); + std::stringstream command_line(file); + std::string line; + int frame_no = 0; + while (std::getline(command_line, line, '\n')) { + if (line.empty()) { + continue; + } + std::smatch m; + + std::stringstream linestream(line); + std::string segment; + std::vector seglist; + + while (std::getline(linestream, segment, ' ')) { + seglist.push_back(segment); + } + + if (seglist.size() < 4) { + continue; + } + + while (frame_no < std::stoi(seglist.at(0))) { + commands[player_index].push_back({}); + frame_no++; + } + + TASCommand command = { + .buttons = ReadCommandButtons(seglist.at(1)), + .l_axis = ReadCommandAxis(seglist.at(2)), + .r_axis = ReadCommandAxis(seglist.at(3)), + }; + commands[player_index].push_back(command); + frame_no++; + } + LOG_INFO(Input, "TAS file loaded! {} frames", frame_no); +} + +void Tas::WriteTasFile(std::u8string file_name) { + std::string output_text; + for (size_t frame = 0; frame < record_commands.size(); frame++) { + const TASCommand& line = record_commands[frame]; + output_text += fmt::format("{} {} {} {}\n", frame, WriteCommandButtons(line.buttons), + WriteCommandAxis(line.l_axis), WriteCommandAxis(line.r_axis)); + } + const auto bytes_written = Common::FS::WriteStringToFile( + Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) / file_name, + Common::FS::FileType::TextFile, output_text); + if (bytes_written == output_text.size()) { + LOG_INFO(Input, "TAS file written to file!"); + } else { + LOG_ERROR(Input, "Writing the TAS-file has failed! {} / {} bytes written", bytes_written, + output_text.size()); + } +} + +void Tas::RecordInput(u64 buttons, TasAnalog left_axis, TasAnalog right_axis) { + last_input = { + .buttons = buttons, + .l_axis = left_axis, + .r_axis = right_axis, + }; +} + +std::tuple Tas::GetStatus() const { + TasState state; + if (is_recording) { + return {TasState::Recording, 0, record_commands.size()}; + } + + if (is_running) { + state = TasState::Running; + } else { + state = TasState::Stopped; + } + + return {state, current_command, script_length}; +} + +void Tas::UpdateThread() { + if (!Settings::values.tas_enable) { + if (is_running) { + Stop(); + } + return; + } + + if (is_recording) { + record_commands.push_back(last_input); + } + if (needs_reset) { + current_command = 0; + needs_reset = false; + LoadTasFiles(); + LOG_DEBUG(Input, "tas_reset done"); + } + + if (!is_running) { + ClearInput(); + return; + } + if (current_command < script_length) { + LOG_DEBUG(Input, "Playing TAS {}/{}", current_command, script_length); + const size_t frame = current_command++; + for (size_t player_index = 0; player_index < commands.size(); player_index++) { + TASCommand command{}; + if (frame < commands[player_index].size()) { + command = commands[player_index][frame]; + } + + PadIdentifier identifier{ + .guid = Common::UUID{}, + .port = player_index, + .pad = 0, + }; + for (std::size_t i = 0; i < sizeof(command.buttons) * 8; ++i) { + const bool button_status = (command.buttons & (1LLU << i)) != 0; + const int button = static_cast(i); + SetButton(identifier, button, button_status); + } + SetAxis(identifier, TasAxes::StickX, command.l_axis.x); + SetAxis(identifier, TasAxes::StickY, command.l_axis.y); + SetAxis(identifier, TasAxes::SubstickX, command.r_axis.x); + SetAxis(identifier, TasAxes::SubstickY, command.r_axis.y); + } + } else { + is_running = Settings::values.tas_loop.GetValue(); + LoadTasFiles(); + current_command = 0; + ClearInput(); + } +} + +void Tas::ClearInput() { + ResetButtonState(); + ResetAnalogState(); +} + +TasAnalog Tas::ReadCommandAxis(const std::string& line) const { + std::stringstream linestream(line); + std::string segment; + std::vector seglist; + + while (std::getline(linestream, segment, ';')) { + seglist.push_back(segment); + } + + const float x = std::stof(seglist.at(0)) / 32767.0f; + const float y = std::stof(seglist.at(1)) / 32767.0f; + + return {x, y}; +} + +u64 Tas::ReadCommandButtons(const std::string& data) const { + std::stringstream button_text(data); + std::string line; + u64 buttons = 0; + while (std::getline(button_text, line, ';')) { + for (auto [text, tas_button] : text_to_tas_button) { + if (text == line) { + buttons |= static_cast(tas_button); + break; + } + } + } + return buttons; +} + +std::string Tas::WriteCommandButtons(u64 buttons) const { + std::string returns = ""; + for (auto [text_button, tas_button] : text_to_tas_button) { + if ((buttons & static_cast(tas_button)) != 0) { + returns += fmt::format("{};", text_button); + } + } + return returns.empty() ? "NONE" : returns; +} + +std::string Tas::WriteCommandAxis(TasAnalog analog) const { + return fmt::format("{};{}", analog.x * 32767, analog.y * 32767); +} + +void Tas::StartStop() { + if (!Settings::values.tas_enable) { + return; + } + if (is_running) { + Stop(); + } else { + is_running = true; + } +} + +void Tas::Stop() { + is_running = false; +} + +void Tas::Reset() { + if (!Settings::values.tas_enable) { + return; + } + needs_reset = true; +} + +bool Tas::Record() { + if (!Settings::values.tas_enable) { + return true; + } + is_recording = !is_recording; + return is_recording; +} + +void Tas::SaveRecording(bool overwrite_file) { + if (is_recording) { + return; + } + if (record_commands.empty()) { + return; + } + WriteTasFile(u8"record.txt"); + if (overwrite_file) { + WriteTasFile(u8"script0-1.txt"); + } + needs_reset = true; + record_commands.clear(); +} + +} // namespace InputCommon::TasInput diff --git a/src/input_common/tas/tas_input.h b/src/input_common/drivers/tas_input.h similarity index 51% rename from src/input_common/tas/tas_input.h rename to src/input_common/drivers/tas_input.h index 3e2db8f006..c95a130fcb 100644 --- a/src/input_common/tas/tas_input.h +++ b/src/input_common/drivers/tas_input.h @@ -8,7 +8,7 @@ #include "common/common_types.h" #include "common/settings_input.h" -#include "core/frontend/input.h" +#include "input_common/input_engine.h" #include "input_common/main.h" /* @@ -43,19 +43,11 @@ For debugging purposes, the common controller debugger can be used (View -> Debu P1). */ -namespace TasInput { +namespace InputCommon::TasInput { -constexpr size_t PLAYER_NUMBER = 8; +constexpr size_t PLAYER_NUMBER = 10; -using TasAnalog = std::pair; - -enum class TasState { - Running, - Recording, - Stopped, -}; - -enum class TasButton : u32 { +enum class TasButton : u64 { BUTTON_A = 1U << 0, BUTTON_B = 1U << 1, BUTTON_X = 1U << 2, @@ -78,26 +70,29 @@ enum class TasButton : u32 { BUTTON_CAPTURE = 1U << 19, }; -enum class TasAxes : u8 { - StickX, - StickY, - SubstickX, - SubstickY, - Undefined, +struct TasAnalog { + float x{}; + float y{}; }; -struct TasData { - u32 buttons{}; - std::array axis{}; +enum class TasState { + Running, + Recording, + Stopped, }; -class Tas { +class Tas final : public InputCommon::InputEngine { public: - Tas(); + explicit Tas(const std::string& input_engine_); ~Tas(); - // Changes the input status that will be stored in each frame - void RecordInput(u32 buttons, const std::array, 2>& axes); + /** + * Changes the input status that will be stored in each frame + * @param buttons: bitfield with the status of the buttons + * @param left_axis: value of the left axis + * @param right_axis: value of the right axis + */ + void RecordInput(u64 buttons, TasAnalog left_axis, TasAnalog right_axis); // Main loop that records or executes input void UpdateThread(); @@ -117,112 +112,77 @@ public: */ bool Record(); - // Saves contents of record_commands on a file if overwrite is enabled player 1 will be - // overwritten with the recorded commands + /** + * Saves contents of record_commands on a file + * @param overwrite_file: Indicates if player 1 should be overwritten + */ void SaveRecording(bool overwrite_file); /** * Returns the current status values of TAS playback/recording * @return Tuple of - * TasState indicating the current state out of Running, Recording or Stopped ; - * Current playback progress or amount of frames (so far) for Recording ; - * Total length of script file currently loaded or amount of frames (so far) for Recording + * TasState indicating the current state out of Running ; + * Current playback progress ; + * Total length of script file currently loaded or being recorded */ std::tuple GetStatus() const; - // Retuns an array of the default button mappings - InputCommon::ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) const; - - // Retuns an array of the default analog mappings - InputCommon::AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) const; - [[nodiscard]] const TasData& GetTasState(std::size_t pad) const; - private: struct TASCommand { - u32 buttons{}; + u64 buttons{}; TasAnalog l_axis{}; TasAnalog r_axis{}; }; - // Loads TAS files from all players + /// Loads TAS files from all players void LoadTasFiles(); - // Loads TAS file from the specified player - void LoadTasFile(size_t player_index); + /** Loads TAS file from the specified player + * @param player_index: player number to save the script + * @param file_index: script number of the file + */ + void LoadTasFile(size_t player_index, size_t file_index); - // Writes a TAS file from the recorded commands + /** Writes a TAS file from the recorded commands + * @param file_name: name of the file to be written + */ void WriteTasFile(std::u8string file_name); /** - * Parses a string containing the axis values with the following format "x;y" - * X and Y have a range from -32767 to 32767 + * Parses a string containing the axis values. X and Y have a range from -32767 to 32767 + * @param line: string containing axis values with the following format "x;y" * @return Returns a TAS analog object with axis values with range from -1.0 to 1.0 */ TasAnalog ReadCommandAxis(const std::string& line) const; /** - * Parses a string containing the button values with the following format "a;b;c;d..." - * Each button is represented by it's text format specified in text_to_tas_button array - * @return Returns a u32 with each bit representing the status of a button + * Parses a string containing the button values. Each button is represented by it's text format + * specified in text_to_tas_button array + * @param line: string containing button name with the following format "a;b;c;d..." + * @return Returns a u64 with each bit representing the status of a button */ - u32 ReadCommandButtons(const std::string& line) const; + u64 ReadCommandButtons(const std::string& line) const; /** - * Converts an u32 containing the button status into the text equivalent + * Reset state of all players + */ + void ClearInput(); + + /** + * Converts an u64 containing the button status into the text equivalent + * @param buttons: bitfield with the status of the buttons * @return Returns a string with the name of the buttons to be written to the file */ - std::string WriteCommandButtons(u32 data) const; + std::string WriteCommandButtons(u64 buttons) const; /** * Converts an TAS analog object containing the axis status into the text equivalent - * @return Returns a string with the value of the axis to be written to the file + * @param data: value of the axis + * @return A string with the value of the axis to be written to the file */ std::string WriteCommandAxis(TasAnalog data) const; - // Inverts the Y axis polarity - std::pair FlipAxisY(std::pair old); - - /** - * Converts an u32 containing the button status into the text equivalent - * @return Returns a string with the name of the buttons to be printed on console - */ - std::string DebugButtons(u32 buttons) const; - - /** - * Converts an TAS analog object containing the axis status into the text equivalent - * @return Returns a string with the value of the axis to be printed on console - */ - std::string DebugJoystick(float x, float y) const; - - /** - * Converts the given TAS status into the text equivalent - * @return Returns a string with the value of the TAS status to be printed on console - */ - std::string DebugInput(const TasData& data) const; - - /** - * Converts the given TAS status of multiple players into the text equivalent - * @return Returns a string with the value of the status of all TAS players to be printed on - * console - */ - std::string DebugInputs(const std::array& arr) const; - - /** - * Converts an u32 containing the button status into the text equivalent - * @return Returns a string with the name of the buttons - */ - std::string ButtonsToString(u32 button) const; - - // Stores current controller configuration and sets a TAS controller for every active controller - // to the current config - void SwapToTasController(); - - // Sets the stored controller configuration to the current config - void SwapToStoredController(); - size_t script_length{0}; - std::array tas_data; - bool is_old_input_saved{false}; bool is_recording{false}; bool is_running{false}; bool needs_reset{false}; @@ -230,8 +190,5 @@ private: std::vector record_commands{}; size_t current_command{0}; TASCommand last_input{}; // only used for recording - - // Old settings for swapping controllers - std::array player_mappings; }; -} // namespace TasInput +} // namespace InputCommon::TasInput diff --git a/src/input_common/drivers/touch_screen.cpp b/src/input_common/drivers/touch_screen.cpp new file mode 100644 index 0000000000..45b3086f6a --- /dev/null +++ b/src/input_common/drivers/touch_screen.cpp @@ -0,0 +1,53 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included + +#include "common/param_package.h" +#include "input_common/drivers/touch_screen.h" + +namespace InputCommon { + +constexpr PadIdentifier identifier = { + .guid = Common::UUID{Common::INVALID_UUID}, + .port = 0, + .pad = 0, +}; + +TouchScreen::TouchScreen(const std::string& input_engine_) : InputEngine(input_engine_) { + PreSetController(identifier); +} + +void TouchScreen::TouchMoved(float x, float y, std::size_t finger) { + if (finger >= 16) { + return; + } + TouchPressed(x, y, finger); +} + +void TouchScreen::TouchPressed(float x, float y, std::size_t finger) { + if (finger >= 16) { + return; + } + SetButton(identifier, static_cast(finger), true); + SetAxis(identifier, static_cast(finger * 2), x); + SetAxis(identifier, static_cast(finger * 2 + 1), y); +} + +void TouchScreen::TouchReleased(std::size_t finger) { + if (finger >= 16) { + return; + } + SetButton(identifier, static_cast(finger), false); + SetAxis(identifier, static_cast(finger * 2), 0.0f); + SetAxis(identifier, static_cast(finger * 2 + 1), 0.0f); +} + +void TouchScreen::ReleaseAllTouch() { + for (int index = 0; index < 16; ++index) { + SetButton(identifier, index, false); + SetAxis(identifier, index * 2, 0.0f); + SetAxis(identifier, index * 2 + 1, 0.0f); + } +} + +} // namespace InputCommon diff --git a/src/input_common/drivers/touch_screen.h b/src/input_common/drivers/touch_screen.h new file mode 100644 index 0000000000..25c11e8bfd --- /dev/null +++ b/src/input_common/drivers/touch_screen.h @@ -0,0 +1,44 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included + +#pragma once + +#include "input_common/input_engine.h" + +namespace InputCommon { + +/** + * A button device factory representing a keyboard. It receives keyboard events and forward them + * to all button devices it created. + */ +class TouchScreen final : public InputCommon::InputEngine { +public: + explicit TouchScreen(const std::string& input_engine_); + + /** + * Signals that mouse has moved. + * @param x the x-coordinate of the cursor + * @param y the y-coordinate of the cursor + * @param center_x the x-coordinate of the middle of the screen + * @param center_y the y-coordinate of the middle of the screen + */ + void TouchMoved(float x, float y, std::size_t finger); + + /** + * Sets the status of all buttons bound with the key to pressed + * @param key_code the code of the key to press + */ + void TouchPressed(float x, float y, std::size_t finger); + + /** + * Sets the status of all buttons bound with the key to released + * @param key_code the code of the key to release + */ + void TouchReleased(std::size_t finger); + + /// Resets all inputs to their initial value + void ReleaseAllTouch(); +}; + +} // namespace InputCommon diff --git a/src/input_common/drivers/udp_client.cpp b/src/input_common/drivers/udp_client.cpp new file mode 100644 index 0000000000..fdee0f2d53 --- /dev/null +++ b/src/input_common/drivers/udp_client.cpp @@ -0,0 +1,591 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include + +#include "common/logging/log.h" +#include "common/param_package.h" +#include "common/settings.h" +#include "input_common/drivers/udp_client.h" +#include "input_common/helpers/udp_protocol.h" + +using boost::asio::ip::udp; + +namespace InputCommon::CemuhookUDP { + +struct SocketCallback { + std::function version; + std::function port_info; + std::function pad_data; +}; + +class Socket { +public: + using clock = std::chrono::system_clock; + + explicit Socket(const std::string& host, u16 port, SocketCallback callback_) + : callback(std::move(callback_)), timer(io_service), + socket(io_service, udp::endpoint(udp::v4(), 0)), client_id(GenerateRandomClientId()) { + boost::system::error_code ec{}; + auto ipv4 = boost::asio::ip::make_address_v4(host, ec); + if (ec.value() != boost::system::errc::success) { + LOG_ERROR(Input, "Invalid IPv4 address \"{}\" provided to socket", host); + ipv4 = boost::asio::ip::address_v4{}; + } + + send_endpoint = {udp::endpoint(ipv4, port)}; + } + + void Stop() { + io_service.stop(); + } + + void Loop() { + io_service.run(); + } + + void StartSend(const clock::time_point& from) { + timer.expires_at(from + std::chrono::seconds(3)); + timer.async_wait([this](const boost::system::error_code& error) { HandleSend(error); }); + } + + void StartReceive() { + socket.async_receive_from( + boost::asio::buffer(receive_buffer), receive_endpoint, + [this](const boost::system::error_code& error, std::size_t bytes_transferred) { + HandleReceive(error, bytes_transferred); + }); + } + +private: + u32 GenerateRandomClientId() const { + std::random_device device; + return device(); + } + + void HandleReceive(const boost::system::error_code&, std::size_t bytes_transferred) { + if (auto type = Response::Validate(receive_buffer.data(), bytes_transferred)) { + switch (*type) { + case Type::Version: { + Response::Version version; + std::memcpy(&version, &receive_buffer[sizeof(Header)], sizeof(Response::Version)); + callback.version(std::move(version)); + break; + } + case Type::PortInfo: { + Response::PortInfo port_info; + std::memcpy(&port_info, &receive_buffer[sizeof(Header)], + sizeof(Response::PortInfo)); + callback.port_info(std::move(port_info)); + break; + } + case Type::PadData: { + Response::PadData pad_data; + std::memcpy(&pad_data, &receive_buffer[sizeof(Header)], sizeof(Response::PadData)); + callback.pad_data(std::move(pad_data)); + break; + } + } + } + StartReceive(); + } + + void HandleSend(const boost::system::error_code&) { + boost::system::error_code _ignored{}; + // Send a request for getting port info for the pad + const Request::PortInfo port_info{4, {0, 1, 2, 3}}; + const auto port_message = Request::Create(port_info, client_id); + std::memcpy(&send_buffer1, &port_message, PORT_INFO_SIZE); + socket.send_to(boost::asio::buffer(send_buffer1), send_endpoint, {}, _ignored); + + // Send a request for getting pad data for the pad + const Request::PadData pad_data{ + Request::RegisterFlags::AllPads, + 0, + EMPTY_MAC_ADDRESS, + }; + const auto pad_message = Request::Create(pad_data, client_id); + std::memcpy(send_buffer2.data(), &pad_message, PAD_DATA_SIZE); + socket.send_to(boost::asio::buffer(send_buffer2), send_endpoint, {}, _ignored); + StartSend(timer.expiry()); + } + + SocketCallback callback; + boost::asio::io_service io_service; + boost::asio::basic_waitable_timer timer; + udp::socket socket; + + const u32 client_id; + + static constexpr std::size_t PORT_INFO_SIZE = sizeof(Message); + static constexpr std::size_t PAD_DATA_SIZE = sizeof(Message); + std::array send_buffer1; + std::array send_buffer2; + udp::endpoint send_endpoint; + + std::array receive_buffer; + udp::endpoint receive_endpoint; +}; + +static void SocketLoop(Socket* socket) { + socket->StartReceive(); + socket->StartSend(Socket::clock::now()); + socket->Loop(); +} + +UDPClient::UDPClient(const std::string& input_engine_) : InputEngine(input_engine_) { + LOG_INFO(Input, "Udp Initialization started"); + ReloadSockets(); +} + +UDPClient::~UDPClient() { + Reset(); +} + +UDPClient::ClientConnection::ClientConnection() = default; + +UDPClient::ClientConnection::~ClientConnection() = default; + +void UDPClient::ReloadSockets() { + Reset(); + + std::stringstream servers_ss(Settings::values.udp_input_servers.GetValue()); + std::string server_token; + std::size_t client = 0; + while (std::getline(servers_ss, server_token, ',')) { + if (client == MAX_UDP_CLIENTS) { + break; + } + std::stringstream server_ss(server_token); + std::string token; + std::getline(server_ss, token, ':'); + std::string udp_input_address = token; + std::getline(server_ss, token, ':'); + char* temp; + const u16 udp_input_port = static_cast(std::strtol(token.c_str(), &temp, 0)); + if (*temp != '\0') { + LOG_ERROR(Input, "Port number is not valid {}", token); + continue; + } + + const std::size_t client_number = GetClientNumber(udp_input_address, udp_input_port); + if (client_number != MAX_UDP_CLIENTS) { + LOG_ERROR(Input, "Duplicated UDP servers found"); + continue; + } + StartCommunication(client++, udp_input_address, udp_input_port); + } +} + +std::size_t UDPClient::GetClientNumber(std::string_view host, u16 port) const { + for (std::size_t client = 0; client < clients.size(); client++) { + if (clients[client].active == -1) { + continue; + } + if (clients[client].host == host && clients[client].port == port) { + return client; + } + } + return MAX_UDP_CLIENTS; +} + +void UDPClient::OnVersion([[maybe_unused]] Response::Version data) { + LOG_TRACE(Input, "Version packet received: {}", data.version); +} + +void UDPClient::OnPortInfo([[maybe_unused]] Response::PortInfo data) { + LOG_TRACE(Input, "PortInfo packet received: {}", data.model); +} + +void UDPClient::OnPadData(Response::PadData data, std::size_t client) { + const std::size_t pad_index = (client * PADS_PER_CLIENT) + data.info.id; + + if (pad_index >= pads.size()) { + LOG_ERROR(Input, "Invalid pad id {}", data.info.id); + return; + } + + LOG_TRACE(Input, "PadData packet received"); + if (data.packet_counter == pads[pad_index].packet_sequence) { + LOG_WARNING( + Input, + "PadData packet dropped because its stale info. Current count: {} Packet count: {}", + pads[pad_index].packet_sequence, data.packet_counter); + pads[pad_index].connected = false; + return; + } + + clients[client].active = 1; + pads[pad_index].connected = true; + pads[pad_index].packet_sequence = data.packet_counter; + + const auto now = std::chrono::steady_clock::now(); + const auto time_difference = static_cast( + std::chrono::duration_cast(now - pads[pad_index].last_update) + .count()); + pads[pad_index].last_update = now; + + // Gyroscope values are not it the correct scale from better joy. + // Dividing by 312 allows us to make one full turn = 1 turn + // This must be a configurable valued called sensitivity + const float gyro_scale = 1.0f / 312.0f; + + const BasicMotion motion{ + .gyro_x = data.gyro.pitch * gyro_scale, + .gyro_y = data.gyro.roll * gyro_scale, + .gyro_z = -data.gyro.yaw * gyro_scale, + .accel_x = data.accel.x, + .accel_y = -data.accel.z, + .accel_z = data.accel.y, + .delta_timestamp = time_difference, + }; + const PadIdentifier identifier = GetPadIdentifier(pad_index); + SetMotion(identifier, 0, motion); + + for (std::size_t id = 0; id < data.touch.size(); ++id) { + const auto touch_pad = data.touch[id]; + const auto touch_axis_x_id = + static_cast(id == 0 ? PadAxes::Touch1X : PadAxes::Touch2X); + const auto touch_axis_y_id = + static_cast(id == 0 ? PadAxes::Touch1Y : PadAxes::Touch2Y); + const auto touch_button_id = + static_cast(id == 0 ? PadButton::Touch1 : PadButton::touch2); + + // TODO: Use custom calibration per device + const Common::ParamPackage touch_param(Settings::values.touch_device.GetValue()); + const u16 min_x = static_cast(touch_param.Get("min_x", 100)); + const u16 min_y = static_cast(touch_param.Get("min_y", 50)); + const u16 max_x = static_cast(touch_param.Get("max_x", 1800)); + const u16 max_y = static_cast(touch_param.Get("max_y", 850)); + + const f32 x = + static_cast(std::clamp(static_cast(touch_pad.x), min_x, max_x) - min_x) / + static_cast(max_x - min_x); + const f32 y = + static_cast(std::clamp(static_cast(touch_pad.y), min_y, max_y) - min_y) / + static_cast(max_y - min_y); + + if (touch_pad.is_active) { + SetAxis(identifier, touch_axis_x_id, x); + SetAxis(identifier, touch_axis_y_id, y); + SetButton(identifier, touch_button_id, true); + continue; + } + SetAxis(identifier, touch_axis_x_id, 0); + SetAxis(identifier, touch_axis_y_id, 0); + SetButton(identifier, touch_button_id, false); + } + + SetAxis(identifier, static_cast(PadAxes::LeftStickX), + (data.left_stick_x - 127.0f) / 127.0f); + SetAxis(identifier, static_cast(PadAxes::LeftStickY), + (data.left_stick_y - 127.0f) / 127.0f); + SetAxis(identifier, static_cast(PadAxes::RightStickX), + (data.right_stick_x - 127.0f) / 127.0f); + SetAxis(identifier, static_cast(PadAxes::RightStickY), + (data.right_stick_y - 127.0f) / 127.0f); + + static constexpr std::array buttons{ + PadButton::Share, PadButton::L3, PadButton::R3, PadButton::Options, + PadButton::Up, PadButton::Right, PadButton::Down, PadButton::Left, + PadButton::L2, PadButton::R2, PadButton::L1, PadButton::R1, + PadButton::Triangle, PadButton::Circle, PadButton::Cross, PadButton::Square}; + + for (std::size_t i = 0; i < buttons.size(); ++i) { + const bool button_status = (data.digital_button & (1U << i)) != 0; + const int button = static_cast(buttons[i]); + SetButton(identifier, button, button_status); + } +} + +void UDPClient::StartCommunication(std::size_t client, const std::string& host, u16 port) { + SocketCallback callback{[this](Response::Version version) { OnVersion(version); }, + [this](Response::PortInfo info) { OnPortInfo(info); }, + [this, client](Response::PadData data) { OnPadData(data, client); }}; + LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port); + clients[client].uuid = GetHostUUID(host); + clients[client].host = host; + clients[client].port = port; + clients[client].active = 0; + clients[client].socket = std::make_unique(host, port, callback); + clients[client].thread = std::thread{SocketLoop, clients[client].socket.get()}; + for (std::size_t index = 0; index < PADS_PER_CLIENT; ++index) { + const PadIdentifier identifier = GetPadIdentifier(client * PADS_PER_CLIENT + index); + PreSetController(identifier); + } +} + +const PadIdentifier UDPClient::GetPadIdentifier(std::size_t pad_index) const { + const std::size_t client = pad_index / PADS_PER_CLIENT; + return { + .guid = clients[client].uuid, + .port = static_cast(clients[client].port), + .pad = pad_index, + }; +} + +const Common::UUID UDPClient::GetHostUUID(const std::string host) const { + const auto ip = boost::asio::ip::address_v4::from_string(host); + const auto hex_host = fmt::format("{:06x}", ip.to_ulong()); + return Common::UUID{hex_host}; +} + +void UDPClient::Reset() { + for (auto& client : clients) { + if (client.thread.joinable()) { + client.active = -1; + client.socket->Stop(); + client.thread.join(); + } + } +} + +std::vector UDPClient::GetInputDevices() const { + std::vector devices; + if (!Settings::values.enable_udp_controller) { + return devices; + } + for (std::size_t client = 0; client < clients.size(); client++) { + if (clients[client].active != 1) { + continue; + } + for (std::size_t index = 0; index < PADS_PER_CLIENT; ++index) { + const std::size_t pad_index = client * PADS_PER_CLIENT + index; + if (!pads[pad_index].connected) { + continue; + } + const auto pad_identifier = GetPadIdentifier(pad_index); + Common::ParamPackage identifier{}; + identifier.Set("engine", GetEngineName()); + identifier.Set("display", fmt::format("UDP Controller {}", pad_identifier.pad)); + identifier.Set("guid", pad_identifier.guid.Format()); + identifier.Set("port", static_cast(pad_identifier.port)); + identifier.Set("pad", static_cast(pad_identifier.pad)); + devices.emplace_back(identifier); + } + } + return devices; +} + +ButtonMapping UDPClient::GetButtonMappingForDevice(const Common::ParamPackage& params) { + // This list excludes any button that can't be really mapped + static constexpr std::array, 18> + switch_to_dsu_button = { + std::pair{Settings::NativeButton::A, PadButton::Circle}, + {Settings::NativeButton::B, PadButton::Cross}, + {Settings::NativeButton::X, PadButton::Triangle}, + {Settings::NativeButton::Y, PadButton::Square}, + {Settings::NativeButton::Plus, PadButton::Options}, + {Settings::NativeButton::Minus, PadButton::Share}, + {Settings::NativeButton::DLeft, PadButton::Left}, + {Settings::NativeButton::DUp, PadButton::Up}, + {Settings::NativeButton::DRight, PadButton::Right}, + {Settings::NativeButton::DDown, PadButton::Down}, + {Settings::NativeButton::L, PadButton::L1}, + {Settings::NativeButton::R, PadButton::R1}, + {Settings::NativeButton::ZL, PadButton::L2}, + {Settings::NativeButton::ZR, PadButton::R2}, + {Settings::NativeButton::SL, PadButton::L2}, + {Settings::NativeButton::SR, PadButton::R2}, + {Settings::NativeButton::LStick, PadButton::L3}, + {Settings::NativeButton::RStick, PadButton::R3}, + }; + if (!params.Has("guid") || !params.Has("port") || !params.Has("pad")) { + return {}; + } + + ButtonMapping mapping{}; + for (const auto& [switch_button, dsu_button] : switch_to_dsu_button) { + Common::ParamPackage button_params{}; + button_params.Set("engine", GetEngineName()); + button_params.Set("guid", params.Get("guid", "")); + button_params.Set("port", params.Get("port", 0)); + button_params.Set("pad", params.Get("pad", 0)); + button_params.Set("button", static_cast(dsu_button)); + mapping.insert_or_assign(switch_button, std::move(button_params)); + } + + return mapping; +} + +AnalogMapping UDPClient::GetAnalogMappingForDevice(const Common::ParamPackage& params) { + if (!params.Has("guid") || !params.Has("port") || !params.Has("pad")) { + return {}; + } + + AnalogMapping mapping = {}; + Common::ParamPackage left_analog_params; + left_analog_params.Set("engine", GetEngineName()); + left_analog_params.Set("guid", params.Get("guid", "")); + left_analog_params.Set("port", params.Get("port", 0)); + left_analog_params.Set("pad", params.Get("pad", 0)); + left_analog_params.Set("axis_x", static_cast(PadAxes::LeftStickX)); + left_analog_params.Set("axis_y", static_cast(PadAxes::LeftStickY)); + mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params)); + Common::ParamPackage right_analog_params; + right_analog_params.Set("engine", GetEngineName()); + right_analog_params.Set("guid", params.Get("guid", "")); + right_analog_params.Set("port", params.Get("port", 0)); + right_analog_params.Set("pad", params.Get("pad", 0)); + right_analog_params.Set("axis_x", static_cast(PadAxes::RightStickX)); + right_analog_params.Set("axis_y", static_cast(PadAxes::RightStickY)); + mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params)); + return mapping; +} + +MotionMapping UDPClient::GetMotionMappingForDevice(const Common::ParamPackage& params) { + if (!params.Has("guid") || !params.Has("port") || !params.Has("pad")) { + return {}; + } + + MotionMapping mapping = {}; + Common::ParamPackage motion_params; + motion_params.Set("engine", GetEngineName()); + motion_params.Set("guid", params.Get("guid", "")); + motion_params.Set("port", params.Get("port", 0)); + motion_params.Set("pad", params.Get("pad", 0)); + motion_params.Set("motion", 0); + mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, std::move(motion_params)); + mapping.insert_or_assign(Settings::NativeMotion::MotionRight, std::move(motion_params)); + return mapping; +} + +Common::Input::ButtonNames UDPClient::GetUIButtonName(const Common::ParamPackage& params) const { + PadButton button = static_cast(params.Get("button", 0)); + switch (button) { + case PadButton::Left: + return Common::Input::ButtonNames::ButtonLeft; + case PadButton::Right: + return Common::Input::ButtonNames::ButtonRight; + case PadButton::Down: + return Common::Input::ButtonNames::ButtonDown; + case PadButton::Up: + return Common::Input::ButtonNames::ButtonUp; + case PadButton::L1: + return Common::Input::ButtonNames::L1; + case PadButton::L2: + return Common::Input::ButtonNames::L2; + case PadButton::L3: + return Common::Input::ButtonNames::L3; + case PadButton::R1: + return Common::Input::ButtonNames::R1; + case PadButton::R2: + return Common::Input::ButtonNames::R2; + case PadButton::R3: + return Common::Input::ButtonNames::R3; + case PadButton::Circle: + return Common::Input::ButtonNames::Circle; + case PadButton::Cross: + return Common::Input::ButtonNames::Cross; + case PadButton::Square: + return Common::Input::ButtonNames::Square; + case PadButton::Triangle: + return Common::Input::ButtonNames::Triangle; + case PadButton::Share: + return Common::Input::ButtonNames::Share; + case PadButton::Options: + return Common::Input::ButtonNames::Options; + default: + return Common::Input::ButtonNames::Undefined; + } +} + +Common::Input::ButtonNames UDPClient::GetUIName(const Common::ParamPackage& params) const { + if (params.Has("button")) { + return GetUIButtonName(params); + } + if (params.Has("axis")) { + return Common::Input::ButtonNames::Value; + } + if (params.Has("motion")) { + return Common::Input::ButtonNames::Engine; + } + + return Common::Input::ButtonNames::Invalid; +} + +void TestCommunication(const std::string& host, u16 port, + const std::function& success_callback, + const std::function& failure_callback) { + std::thread([=] { + Common::Event success_event; + SocketCallback callback{ + .version = [](Response::Version) {}, + .port_info = [](Response::PortInfo) {}, + .pad_data = [&](Response::PadData) { success_event.Set(); }, + }; + Socket socket{host, port, std::move(callback)}; + std::thread worker_thread{SocketLoop, &socket}; + const bool result = + success_event.WaitUntil(std::chrono::steady_clock::now() + std::chrono::seconds(10)); + socket.Stop(); + worker_thread.join(); + if (result) { + success_callback(); + } else { + failure_callback(); + } + }).detach(); +} + +CalibrationConfigurationJob::CalibrationConfigurationJob( + const std::string& host, u16 port, std::function status_callback, + std::function data_callback) { + + std::thread([=, this] { + Status current_status{Status::Initialized}; + SocketCallback callback{ + [](Response::Version) {}, [](Response::PortInfo) {}, + [&](Response::PadData data) { + static constexpr u16 CALIBRATION_THRESHOLD = 100; + static constexpr u16 MAX_VALUE = UINT16_MAX; + + if (current_status == Status::Initialized) { + // Receiving data means the communication is ready now + current_status = Status::Ready; + status_callback(current_status); + } + const auto& touchpad_0 = data.touch[0]; + if (touchpad_0.is_active == 0) { + return; + } + LOG_DEBUG(Input, "Current touch: {} {}", touchpad_0.x, touchpad_0.y); + const u16 min_x = std::min(MAX_VALUE, static_cast(touchpad_0.x)); + const u16 min_y = std::min(MAX_VALUE, static_cast(touchpad_0.y)); + if (current_status == Status::Ready) { + // First touch - min data (min_x/min_y) + current_status = Status::Stage1Completed; + status_callback(current_status); + } + if (touchpad_0.x - min_x > CALIBRATION_THRESHOLD && + touchpad_0.y - min_y > CALIBRATION_THRESHOLD) { + // Set the current position as max value and finishes configuration + const u16 max_x = touchpad_0.x; + const u16 max_y = touchpad_0.y; + current_status = Status::Completed; + data_callback(min_x, min_y, max_x, max_y); + status_callback(current_status); + + complete_event.Set(); + } + }}; + Socket socket{host, port, std::move(callback)}; + std::thread worker_thread{SocketLoop, &socket}; + complete_event.Wait(); + socket.Stop(); + worker_thread.join(); + }).detach(); +} + +CalibrationConfigurationJob::~CalibrationConfigurationJob() { + Stop(); +} + +void CalibrationConfigurationJob::Stop() { + complete_event.Set(); +} + +} // namespace InputCommon::CemuhookUDP diff --git a/src/input_common/udp/client.h b/src/input_common/drivers/udp_client.h similarity index 58% rename from src/input_common/udp/client.h rename to src/input_common/drivers/udp_client.h index 380f9bb76d..5d483f26b3 100644 --- a/src/input_common/udp/client.h +++ b/src/input_common/drivers/udp_client.h @@ -4,20 +4,11 @@ #pragma once -#include -#include -#include #include -#include -#include -#include + #include "common/common_types.h" -#include "common/param_package.h" #include "common/thread.h" -#include "common/threadsafe_queue.h" -#include "common/vector_math.h" -#include "core/frontend/input.h" -#include "input_common/motion_input.h" +#include "input_common/input_engine.h" namespace InputCommon::CemuhookUDP { @@ -30,16 +21,6 @@ struct TouchPad; struct Version; } // namespace Response -enum class PadMotion { - GyroX, - GyroY, - GyroZ, - AccX, - AccY, - AccZ, - Undefined, -}; - enum class PadTouch { Click, Undefined, @@ -49,14 +30,10 @@ struct UDPPadStatus { std::string host{"127.0.0.1"}; u16 port{26760}; std::size_t pad_index{}; - PadMotion motion{PadMotion::Undefined}; - f32 motion_value{0.0f}; }; struct DeviceStatus { std::mutex update_mutex; - Input::MotionStatus motion_status; - std::tuple touch_status; // calibration data for scaling the device's touch area to 3ds struct CalibrationData { @@ -68,48 +45,85 @@ struct DeviceStatus { std::optional touch_calibration; }; -class Client { +/** + * A button device factory representing a keyboard. It receives keyboard events and forward them + * to all button devices it created. + */ +class UDPClient final : public InputCommon::InputEngine { public: - // Initialize the UDP client capture and read sequence - Client(); + explicit UDPClient(const std::string& input_engine_); + ~UDPClient(); - // Close and release the client - ~Client(); - - // Used for polling - void BeginConfiguration(); - void EndConfiguration(); - - std::vector GetInputDevices() const; - - bool DeviceConnected(std::size_t pad) const; void ReloadSockets(); - Common::SPSCQueue& GetPadQueue(); - const Common::SPSCQueue& GetPadQueue() const; - - DeviceStatus& GetPadState(const std::string& host, u16 port, std::size_t pad); - const DeviceStatus& GetPadState(const std::string& host, u16 port, std::size_t pad) const; - - Input::TouchStatus& GetTouchState(); - const Input::TouchStatus& GetTouchState() const; + /// Used for automapping features + std::vector GetInputDevices() const override; + ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override; + AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override; + MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& params) override; + Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override; private: + enum class PadButton { + Undefined = 0x0000, + Share = 0x0001, + L3 = 0x0002, + R3 = 0x0004, + Options = 0x0008, + Up = 0x0010, + Right = 0x0020, + Down = 0x0040, + Left = 0x0080, + L2 = 0x0100, + R2 = 0x0200, + L1 = 0x0400, + R1 = 0x0800, + Triangle = 0x1000, + Circle = 0x2000, + Cross = 0x4000, + Square = 0x8000, + Touch1 = 0x10000, + touch2 = 0x20000, + }; + + enum class PadAxes : u8 { + LeftStickX, + LeftStickY, + RightStickX, + RightStickY, + AnalogLeft, + AnalogDown, + AnalogRight, + AnalogUp, + AnalogSquare, + AnalogCross, + AnalogCircle, + AnalogTriangle, + AnalogR1, + AnalogL1, + AnalogR2, + AnalogL3, + AnalogR3, + Touch1X, + Touch1Y, + Touch2X, + Touch2Y, + Undefined, + }; + struct PadData { std::size_t pad_index{}; bool connected{}; DeviceStatus status; u64 packet_sequence{}; - // Realtime values - // motion is initalized with PID values for drift correction on joycons - InputCommon::MotionInput motion{0.3f, 0.005f, 0.0f}; std::chrono::time_point last_update; }; struct ClientConnection { ClientConnection(); ~ClientConnection(); + Common::UUID uuid{"7F000001"}; std::string host{"127.0.0.1"}; u16 port{26760}; s8 active{-1}; @@ -127,28 +141,16 @@ private: void OnPortInfo(Response::PortInfo); void OnPadData(Response::PadData, std::size_t client); void StartCommunication(std::size_t client, const std::string& host, u16 port); - void UpdateYuzuSettings(std::size_t client, std::size_t pad_index, - const Common::Vec3& acc, const Common::Vec3& gyro); + const PadIdentifier GetPadIdentifier(std::size_t pad_index) const; + const Common::UUID GetHostUUID(const std::string host) const; - // Returns an unused finger id, if there is no fingers available std::nullopt will be - // returned - std::optional GetUnusedFingerID() const; - - // Merges and updates all touch inputs into the touch_status array - void UpdateTouchInput(Response::TouchPad& touch_pad, std::size_t client, std::size_t id); - - bool configuring = false; + Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const; // Allocate clients for 8 udp servers static constexpr std::size_t MAX_UDP_CLIENTS = 8; static constexpr std::size_t PADS_PER_CLIENT = 4; - // Each client can have up 2 touch inputs - static constexpr std::size_t MAX_TOUCH_FINGERS = MAX_UDP_CLIENTS * 2; std::array pads{}; std::array clients{}; - Common::SPSCQueue pad_queue{}; - Input::TouchStatus touch_status{}; - std::array finger_id{}; }; /// An async job allowing configuration of the touchpad calibration. diff --git a/src/input_common/gcadapter/gc_adapter.h b/src/input_common/gcadapter/gc_adapter.h deleted file mode 100644 index e5de5e94fc..0000000000 --- a/src/input_common/gcadapter/gc_adapter.h +++ /dev/null @@ -1,168 +0,0 @@ -// Copyright 2014 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#pragma once -#include -#include -#include -#include -#include -#include "common/common_types.h" -#include "common/threadsafe_queue.h" -#include "input_common/main.h" - -struct libusb_context; -struct libusb_device; -struct libusb_device_handle; - -namespace GCAdapter { - -enum class PadButton { - Undefined = 0x0000, - ButtonLeft = 0x0001, - ButtonRight = 0x0002, - ButtonDown = 0x0004, - ButtonUp = 0x0008, - TriggerZ = 0x0010, - TriggerR = 0x0020, - TriggerL = 0x0040, - ButtonA = 0x0100, - ButtonB = 0x0200, - ButtonX = 0x0400, - ButtonY = 0x0800, - ButtonStart = 0x1000, - // Below is for compatibility with "AxisButton" type - Stick = 0x2000, -}; - -enum class PadAxes : u8 { - StickX, - StickY, - SubstickX, - SubstickY, - TriggerLeft, - TriggerRight, - Undefined, -}; - -enum class ControllerTypes { - None, - Wired, - Wireless, -}; - -struct GCPadStatus { - std::size_t port{}; - - PadButton button{PadButton::Undefined}; // Or-ed PAD_BUTTON_* and PAD_TRIGGER_* bits - - PadAxes axis{PadAxes::Undefined}; - s16 axis_value{}; - u8 axis_threshold{50}; -}; - -struct GCController { - ControllerTypes type{}; - bool enable_vibration{}; - u8 rumble_amplitude{}; - u16 buttons{}; - PadButton last_button{}; - std::array axis_values{}; - std::array axis_origin{}; - u8 reset_origin_counter{}; -}; - -class Adapter { -public: - Adapter(); - ~Adapter(); - - /// Request a vibration for a controller - bool RumblePlay(std::size_t port, u8 amplitude); - - /// Used for polling - void BeginConfiguration(); - void EndConfiguration(); - - Common::SPSCQueue& GetPadQueue(); - const Common::SPSCQueue& GetPadQueue() const; - - GCController& GetPadState(std::size_t port); - const GCController& GetPadState(std::size_t port) const; - - /// Returns true if there is a device connected to port - bool DeviceConnected(std::size_t port) const; - - /// Used for automapping features - std::vector GetInputDevices() const; - InputCommon::ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) const; - InputCommon::AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) const; - -private: - using AdapterPayload = std::array; - - void UpdatePadType(std::size_t port, ControllerTypes pad_type); - void UpdateControllers(const AdapterPayload& adapter_payload); - void UpdateYuzuSettings(std::size_t port); - void UpdateStateButtons(std::size_t port, u8 b1, u8 b2); - void UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload); - void UpdateVibrations(); - - void AdapterInputThread(); - - void AdapterScanThread(); - - bool IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size); - - // Updates vibration state of all controllers - void SendVibrations(); - - /// For use in initialization, querying devices to find the adapter - void Setup(); - - /// Resets status of all GC controller devices to a disconnected state - void ResetDevices(); - - /// Resets status of device connected to a disconnected state - void ResetDevice(std::size_t port); - - /// Returns true if we successfully gain access to GC Adapter - bool CheckDeviceAccess(); - - /// Captures GC Adapter endpoint address - /// Returns true if the endpoint was set correctly - bool GetGCEndpoint(libusb_device* device); - - /// For shutting down, clear all data, join all threads, release usb - void Reset(); - - // Join all threads - void JoinThreads(); - - // Release usb handles - void ClearLibusbHandle(); - - libusb_device_handle* usb_adapter_handle = nullptr; - std::array pads; - Common::SPSCQueue pad_queue; - - std::thread adapter_input_thread; - std::thread adapter_scan_thread; - bool adapter_input_thread_running; - bool adapter_scan_thread_running; - bool restart_scan_thread; - - libusb_context* libusb_ctx; - - u8 input_endpoint{0}; - u8 output_endpoint{0}; - u8 input_error_counter{0}; - u8 output_error_counter{0}; - int vibration_counter{0}; - - bool configuring{false}; - bool rumble_enabled{true}; - bool vibration_changed{true}; -}; -} // namespace GCAdapter diff --git a/src/input_common/gcadapter/gc_poller.cpp b/src/input_common/gcadapter/gc_poller.cpp deleted file mode 100644 index 1b6ded8d60..0000000000 --- a/src/input_common/gcadapter/gc_poller.cpp +++ /dev/null @@ -1,356 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include -#include -#include -#include "common/assert.h" -#include "common/threadsafe_queue.h" -#include "input_common/gcadapter/gc_adapter.h" -#include "input_common/gcadapter/gc_poller.h" - -namespace InputCommon { - -class GCButton final : public Input::ButtonDevice { -public: - explicit GCButton(u32 port_, s32 button_, const GCAdapter::Adapter* adapter) - : port(port_), button(button_), gcadapter(adapter) {} - - ~GCButton() override; - - bool GetStatus() const override { - if (gcadapter->DeviceConnected(port)) { - return (gcadapter->GetPadState(port).buttons & button) != 0; - } - return false; - } - -private: - const u32 port; - const s32 button; - const GCAdapter::Adapter* gcadapter; -}; - -class GCAxisButton final : public Input::ButtonDevice { -public: - explicit GCAxisButton(u32 port_, u32 axis_, float threshold_, bool trigger_if_greater_, - const GCAdapter::Adapter* adapter) - : port(port_), axis(axis_), threshold(threshold_), trigger_if_greater(trigger_if_greater_), - gcadapter(adapter) {} - - bool GetStatus() const override { - if (gcadapter->DeviceConnected(port)) { - const float current_axis_value = gcadapter->GetPadState(port).axis_values.at(axis); - const float axis_value = current_axis_value / 128.0f; - if (trigger_if_greater) { - // TODO: Might be worthwile to set a slider for the trigger threshold. It is - // currently always set to 0.5 in configure_input_player.cpp ZL/ZR HandleClick - return axis_value > threshold; - } - return axis_value < -threshold; - } - return false; - } - -private: - const u32 port; - const u32 axis; - float threshold; - bool trigger_if_greater; - const GCAdapter::Adapter* gcadapter; -}; - -GCButtonFactory::GCButtonFactory(std::shared_ptr adapter_) - : adapter(std::move(adapter_)) {} - -GCButton::~GCButton() = default; - -std::unique_ptr GCButtonFactory::Create(const Common::ParamPackage& params) { - const auto button_id = params.Get("button", 0); - const auto port = static_cast(params.Get("port", 0)); - - constexpr s32 PAD_STICK_ID = static_cast(GCAdapter::PadButton::Stick); - - // button is not an axis/stick button - if (button_id != PAD_STICK_ID) { - return std::make_unique(port, button_id, adapter.get()); - } - - // For Axis buttons, used by the binary sticks. - if (button_id == PAD_STICK_ID) { - const int axis = params.Get("axis", 0); - const float threshold = params.Get("threshold", 0.25f); - const std::string direction_name = params.Get("direction", ""); - bool trigger_if_greater; - if (direction_name == "+") { - trigger_if_greater = true; - } else if (direction_name == "-") { - trigger_if_greater = false; - } else { - trigger_if_greater = true; - LOG_ERROR(Input, "Unknown direction {}", direction_name); - } - return std::make_unique(port, axis, threshold, trigger_if_greater, - adapter.get()); - } - - return nullptr; -} - -Common::ParamPackage GCButtonFactory::GetNextInput() const { - Common::ParamPackage params; - GCAdapter::GCPadStatus pad; - auto& queue = adapter->GetPadQueue(); - while (queue.Pop(pad)) { - // This while loop will break on the earliest detected button - params.Set("engine", "gcpad"); - params.Set("port", static_cast(pad.port)); - if (pad.button != GCAdapter::PadButton::Undefined) { - params.Set("button", static_cast(pad.button)); - } - - // For Axis button implementation - if (pad.axis != GCAdapter::PadAxes::Undefined) { - params.Set("axis", static_cast(pad.axis)); - params.Set("button", static_cast(GCAdapter::PadButton::Stick)); - params.Set("threshold", "0.25"); - if (pad.axis_value > 0) { - params.Set("direction", "+"); - } else { - params.Set("direction", "-"); - } - break; - } - } - return params; -} - -void GCButtonFactory::BeginConfiguration() { - polling = true; - adapter->BeginConfiguration(); -} - -void GCButtonFactory::EndConfiguration() { - polling = false; - adapter->EndConfiguration(); -} - -class GCAnalog final : public Input::AnalogDevice { -public: - explicit GCAnalog(u32 port_, u32 axis_x_, u32 axis_y_, bool invert_x_, bool invert_y_, - float deadzone_, float range_, const GCAdapter::Adapter* adapter) - : port(port_), axis_x(axis_x_), axis_y(axis_y_), invert_x(invert_x_), invert_y(invert_y_), - deadzone(deadzone_), range(range_), gcadapter(adapter) {} - - float GetAxis(u32 axis) const { - if (gcadapter->DeviceConnected(port)) { - std::lock_guard lock{mutex}; - const auto axis_value = - static_cast(gcadapter->GetPadState(port).axis_values.at(axis)); - return (axis_value) / (100.0f * range); - } - return 0.0f; - } - - std::pair GetAnalog(u32 analog_axis_x, u32 analog_axis_y) const { - float x = GetAxis(analog_axis_x); - float y = GetAxis(analog_axis_y); - if (invert_x) { - x = -x; - } - if (invert_y) { - y = -y; - } - // Make sure the coordinates are in the unit circle, - // otherwise normalize it. - float r = x * x + y * y; - if (r > 1.0f) { - r = std::sqrt(r); - x /= r; - y /= r; - } - - return {x, y}; - } - - std::tuple GetStatus() const override { - const auto [x, y] = GetAnalog(axis_x, axis_y); - const float r = std::sqrt((x * x) + (y * y)); - if (r > deadzone) { - return {x / r * (r - deadzone) / (1 - deadzone), - y / r * (r - deadzone) / (1 - deadzone)}; - } - return {0.0f, 0.0f}; - } - - std::tuple GetRawStatus() const override { - const float x = GetAxis(axis_x); - const float y = GetAxis(axis_y); - return {x, y}; - } - - Input::AnalogProperties GetAnalogProperties() const override { - return {deadzone, range, 0.5f}; - } - - bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override { - const auto [x, y] = GetStatus(); - const float directional_deadzone = 0.5f; - switch (direction) { - case Input::AnalogDirection::RIGHT: - return x > directional_deadzone; - case Input::AnalogDirection::LEFT: - return x < -directional_deadzone; - case Input::AnalogDirection::UP: - return y > directional_deadzone; - case Input::AnalogDirection::DOWN: - return y < -directional_deadzone; - } - return false; - } - -private: - const u32 port; - const u32 axis_x; - const u32 axis_y; - const bool invert_x; - const bool invert_y; - const float deadzone; - const float range; - const GCAdapter::Adapter* gcadapter; - mutable std::mutex mutex; -}; - -/// An analog device factory that creates analog devices from GC Adapter -GCAnalogFactory::GCAnalogFactory(std::shared_ptr adapter_) - : adapter(std::move(adapter_)) {} - -/** - * Creates analog device from joystick axes - * @param params contains parameters for creating the device: - * - "port": the nth gcpad on the adapter - * - "axis_x": the index of the axis to be bind as x-axis - * - "axis_y": the index of the axis to be bind as y-axis - */ -std::unique_ptr GCAnalogFactory::Create(const Common::ParamPackage& params) { - const auto port = static_cast(params.Get("port", 0)); - const auto axis_x = static_cast(params.Get("axis_x", 0)); - const auto axis_y = static_cast(params.Get("axis_y", 1)); - const auto deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f); - const auto range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f); - const std::string invert_x_value = params.Get("invert_x", "+"); - const std::string invert_y_value = params.Get("invert_y", "+"); - const bool invert_x = invert_x_value == "-"; - const bool invert_y = invert_y_value == "-"; - - return std::make_unique(port, axis_x, axis_y, invert_x, invert_y, deadzone, range, - adapter.get()); -} - -void GCAnalogFactory::BeginConfiguration() { - polling = true; - adapter->BeginConfiguration(); -} - -void GCAnalogFactory::EndConfiguration() { - polling = false; - adapter->EndConfiguration(); -} - -Common::ParamPackage GCAnalogFactory::GetNextInput() { - GCAdapter::GCPadStatus pad; - Common::ParamPackage params; - auto& queue = adapter->GetPadQueue(); - while (queue.Pop(pad)) { - if (pad.button != GCAdapter::PadButton::Undefined) { - params.Set("engine", "gcpad"); - params.Set("port", static_cast(pad.port)); - params.Set("button", static_cast(pad.button)); - return params; - } - if (pad.axis == GCAdapter::PadAxes::Undefined || - std::abs(static_cast(pad.axis_value) / 128.0f) < 0.1f) { - continue; - } - // An analog device needs two axes, so we need to store the axis for later and wait for - // a second input event. The axes also must be from the same joystick. - const u8 axis = static_cast(pad.axis); - if (axis == 0 || axis == 1) { - analog_x_axis = 0; - analog_y_axis = 1; - controller_number = static_cast(pad.port); - break; - } - if (axis == 2 || axis == 3) { - analog_x_axis = 2; - analog_y_axis = 3; - controller_number = static_cast(pad.port); - break; - } - - if (analog_x_axis == -1) { - analog_x_axis = axis; - controller_number = static_cast(pad.port); - } else if (analog_y_axis == -1 && analog_x_axis != axis && - controller_number == static_cast(pad.port)) { - analog_y_axis = axis; - break; - } - } - if (analog_x_axis != -1 && analog_y_axis != -1) { - params.Set("engine", "gcpad"); - params.Set("port", controller_number); - params.Set("axis_x", analog_x_axis); - params.Set("axis_y", analog_y_axis); - params.Set("invert_x", "+"); - params.Set("invert_y", "+"); - analog_x_axis = -1; - analog_y_axis = -1; - controller_number = -1; - return params; - } - return params; -} - -class GCVibration final : public Input::VibrationDevice { -public: - explicit GCVibration(u32 port_, GCAdapter::Adapter* adapter) - : port(port_), gcadapter(adapter) {} - - u8 GetStatus() const override { - return gcadapter->RumblePlay(port, 0); - } - - bool SetRumblePlay(f32 amp_low, [[maybe_unused]] f32 freq_low, f32 amp_high, - [[maybe_unused]] f32 freq_high) const override { - const auto mean_amplitude = (amp_low + amp_high) * 0.5f; - const auto processed_amplitude = - static_cast((mean_amplitude + std::pow(mean_amplitude, 0.3f)) * 0.5f * 0x8); - - return gcadapter->RumblePlay(port, processed_amplitude); - } - -private: - const u32 port; - GCAdapter::Adapter* gcadapter; -}; - -/// An vibration device factory that creates vibration devices from GC Adapter -GCVibrationFactory::GCVibrationFactory(std::shared_ptr adapter_) - : adapter(std::move(adapter_)) {} - -/** - * Creates a vibration device from a joystick - * @param params contains parameters for creating the device: - * - "port": the nth gcpad on the adapter - */ -std::unique_ptr GCVibrationFactory::Create( - const Common::ParamPackage& params) { - const auto port = static_cast(params.Get("port", 0)); - - return std::make_unique(port, adapter.get()); -} - -} // namespace InputCommon diff --git a/src/input_common/gcadapter/gc_poller.h b/src/input_common/gcadapter/gc_poller.h deleted file mode 100644 index d1271e3ead..0000000000 --- a/src/input_common/gcadapter/gc_poller.h +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include "core/frontend/input.h" -#include "input_common/gcadapter/gc_adapter.h" - -namespace InputCommon { - -/** - * A button device factory representing a gcpad. It receives gcpad events and forward them - * to all button devices it created. - */ -class GCButtonFactory final : public Input::Factory { -public: - explicit GCButtonFactory(std::shared_ptr adapter_); - - /** - * Creates a button device from a button press - * @param params contains parameters for creating the device: - * - "code": the code of the key to bind with the button - */ - std::unique_ptr Create(const Common::ParamPackage& params) override; - - Common::ParamPackage GetNextInput() const; - - /// For device input configuration/polling - void BeginConfiguration(); - void EndConfiguration(); - - bool IsPolling() const { - return polling; - } - -private: - std::shared_ptr adapter; - bool polling = false; -}; - -/// An analog device factory that creates analog devices from GC Adapter -class GCAnalogFactory final : public Input::Factory { -public: - explicit GCAnalogFactory(std::shared_ptr adapter_); - - std::unique_ptr Create(const Common::ParamPackage& params) override; - Common::ParamPackage GetNextInput(); - - /// For device input configuration/polling - void BeginConfiguration(); - void EndConfiguration(); - - bool IsPolling() const { - return polling; - } - -private: - std::shared_ptr adapter; - int analog_x_axis = -1; - int analog_y_axis = -1; - int controller_number = -1; - bool polling = false; -}; - -/// A vibration device factory creates vibration devices from GC Adapter -class GCVibrationFactory final : public Input::Factory { -public: - explicit GCVibrationFactory(std::shared_ptr adapter_); - - std::unique_ptr Create(const Common::ParamPackage& params) override; - -private: - std::shared_ptr adapter; -}; - -} // namespace InputCommon diff --git a/src/input_common/helpers/stick_from_buttons.cpp b/src/input_common/helpers/stick_from_buttons.cpp new file mode 100644 index 0000000000..77fcd655ed --- /dev/null +++ b/src/input_common/helpers/stick_from_buttons.cpp @@ -0,0 +1,304 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include "common/math_util.h" +#include "common/settings.h" +#include "input_common/helpers/stick_from_buttons.h" + +namespace InputCommon { + +class Stick final : public Common::Input::InputDevice { +public: + using Button = std::unique_ptr; + + Stick(Button up_, Button down_, Button left_, Button right_, Button modifier_, + float modifier_scale_, float modifier_angle_) + : up(std::move(up_)), down(std::move(down_)), left(std::move(left_)), + right(std::move(right_)), modifier(std::move(modifier_)), modifier_scale(modifier_scale_), + modifier_angle(modifier_angle_) { + Common::Input::InputCallback button_up_callback{ + [this](Common::Input::CallbackStatus callback_) { UpdateUpButtonStatus(callback_); }}; + Common::Input::InputCallback button_down_callback{ + [this](Common::Input::CallbackStatus callback_) { UpdateDownButtonStatus(callback_); }}; + Common::Input::InputCallback button_left_callback{ + [this](Common::Input::CallbackStatus callback_) { UpdateLeftButtonStatus(callback_); }}; + Common::Input::InputCallback button_right_callback{ + [this](Common::Input::CallbackStatus callback_) { + UpdateRightButtonStatus(callback_); + }}; + Common::Input::InputCallback button_modifier_callback{ + [this](Common::Input::CallbackStatus callback_) { UpdateModButtonStatus(callback_); }}; + up->SetCallback(button_up_callback); + down->SetCallback(button_down_callback); + left->SetCallback(button_left_callback); + right->SetCallback(button_right_callback); + modifier->SetCallback(button_modifier_callback); + last_x_axis_value = 0.0f; + last_y_axis_value = 0.0f; + } + + bool IsAngleGreater(float old_angle, float new_angle) const { + constexpr float TAU = Common::PI * 2.0f; + // Use wider angle to ease the transition. + constexpr float aperture = TAU * 0.15f; + const float top_limit = new_angle + aperture; + return (old_angle > new_angle && old_angle <= top_limit) || + (old_angle + TAU > new_angle && old_angle + TAU <= top_limit); + } + + bool IsAngleSmaller(float old_angle, float new_angle) const { + constexpr float TAU = Common::PI * 2.0f; + // Use wider angle to ease the transition. + constexpr float aperture = TAU * 0.15f; + const float bottom_limit = new_angle - aperture; + return (old_angle >= bottom_limit && old_angle < new_angle) || + (old_angle - TAU >= bottom_limit && old_angle - TAU < new_angle); + } + + float GetAngle(std::chrono::time_point now) const { + constexpr float TAU = Common::PI * 2.0f; + float new_angle = angle; + + auto time_difference = static_cast( + std::chrono::duration_cast(now - last_update).count()); + time_difference /= 1000.0f * 1000.0f; + if (time_difference > 0.5f) { + time_difference = 0.5f; + } + + if (IsAngleGreater(new_angle, goal_angle)) { + new_angle -= modifier_angle * time_difference; + if (new_angle < 0) { + new_angle += TAU; + } + if (!IsAngleGreater(new_angle, goal_angle)) { + return goal_angle; + } + } else if (IsAngleSmaller(new_angle, goal_angle)) { + new_angle += modifier_angle * time_difference; + if (new_angle >= TAU) { + new_angle -= TAU; + } + if (!IsAngleSmaller(new_angle, goal_angle)) { + return goal_angle; + } + } else { + return goal_angle; + } + return new_angle; + } + + void SetGoalAngle(bool r, bool l, bool u, bool d) { + // Move to the right + if (r && !u && !d) { + goal_angle = 0.0f; + } + + // Move to the upper right + if (r && u && !d) { + goal_angle = Common::PI * 0.25f; + } + + // Move up + if (u && !l && !r) { + goal_angle = Common::PI * 0.5f; + } + + // Move to the upper left + if (l && u && !d) { + goal_angle = Common::PI * 0.75f; + } + + // Move to the left + if (l && !u && !d) { + goal_angle = Common::PI; + } + + // Move to the bottom left + if (l && !u && d) { + goal_angle = Common::PI * 1.25f; + } + + // Move down + if (d && !l && !r) { + goal_angle = Common::PI * 1.5f; + } + + // Move to the bottom right + if (r && !u && d) { + goal_angle = Common::PI * 1.75f; + } + } + + void UpdateUpButtonStatus(Common::Input::CallbackStatus button_callback) { + up_status = button_callback.button_status.value; + UpdateStatus(); + } + + void UpdateDownButtonStatus(Common::Input::CallbackStatus button_callback) { + down_status = button_callback.button_status.value; + UpdateStatus(); + } + + void UpdateLeftButtonStatus(Common::Input::CallbackStatus button_callback) { + left_status = button_callback.button_status.value; + UpdateStatus(); + } + + void UpdateRightButtonStatus(Common::Input::CallbackStatus button_callback) { + right_status = button_callback.button_status.value; + UpdateStatus(); + } + + void UpdateModButtonStatus(Common::Input::CallbackStatus button_callback) { + modifier_status = button_callback.button_status.value; + UpdateStatus(); + } + + void UpdateStatus() { + const float coef = modifier_status ? modifier_scale : 1.0f; + + bool r = right_status; + bool l = left_status; + bool u = up_status; + bool d = down_status; + + // Eliminate contradictory movements + if (r && l) { + r = false; + l = false; + } + if (u && d) { + u = false; + d = false; + } + + // Move if a key is pressed + if (r || l || u || d) { + amplitude = coef; + } else { + amplitude = 0; + } + + const auto now = std::chrono::steady_clock::now(); + const auto time_difference = static_cast( + std::chrono::duration_cast(now - last_update).count()); + + if (time_difference < 10) { + // Disable analog mode if inputs are too fast + SetGoalAngle(r, l, u, d); + angle = goal_angle; + } else { + angle = GetAngle(now); + SetGoalAngle(r, l, u, d); + } + + last_update = now; + Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Stick, + .stick_status = GetStatus(), + }; + last_x_axis_value = status.stick_status.x.raw_value; + last_y_axis_value = status.stick_status.y.raw_value; + TriggerOnChange(status); + } + + void ForceUpdate() override { + up->ForceUpdate(); + down->ForceUpdate(); + left->ForceUpdate(); + right->ForceUpdate(); + modifier->ForceUpdate(); + } + + void SoftUpdate() override { + Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Stick, + .stick_status = GetStatus(), + }; + if (last_x_axis_value == status.stick_status.x.raw_value && + last_y_axis_value == status.stick_status.y.raw_value) { + return; + } + last_x_axis_value = status.stick_status.x.raw_value; + last_y_axis_value = status.stick_status.y.raw_value; + TriggerOnChange(status); + } + + Common::Input::StickStatus GetStatus() const { + Common::Input::StickStatus status{}; + status.x.properties = properties; + status.y.properties = properties; + if (Settings::values.emulate_analog_keyboard) { + const auto now = std::chrono::steady_clock::now(); + float angle_ = GetAngle(now); + status.x.raw_value = std::cos(angle_) * amplitude; + status.y.raw_value = std::sin(angle_) * amplitude; + return status; + } + constexpr float SQRT_HALF = 0.707106781f; + int x = 0, y = 0; + if (right_status) { + ++x; + } + if (left_status) { + --x; + } + if (up_status) { + ++y; + } + if (down_status) { + --y; + } + const float coef = modifier_status ? modifier_scale : 1.0f; + status.x.raw_value = static_cast(x) * coef * (y == 0 ? 1.0f : SQRT_HALF); + status.y.raw_value = static_cast(y) * coef * (x == 0 ? 1.0f : SQRT_HALF); + return status; + } + +private: + Button up; + Button down; + Button left; + Button right; + Button modifier; + float modifier_scale; + float modifier_angle; + float angle{}; + float goal_angle{}; + float amplitude{}; + bool up_status; + bool down_status; + bool left_status; + bool right_status; + bool modifier_status; + float last_x_axis_value; + float last_y_axis_value; + const Common::Input::AnalogProperties properties{0.0f, 1.0f, 0.5f, 0.0f, false}; + std::chrono::time_point last_update; +}; + +std::unique_ptr StickFromButton::Create( + const Common::ParamPackage& params) { + const std::string null_engine = Common::ParamPackage{{"engine", "null"}}.Serialize(); + auto up = Common::Input::CreateDeviceFromString( + params.Get("up", null_engine)); + auto down = Common::Input::CreateDeviceFromString( + params.Get("down", null_engine)); + auto left = Common::Input::CreateDeviceFromString( + params.Get("left", null_engine)); + auto right = Common::Input::CreateDeviceFromString( + params.Get("right", null_engine)); + auto modifier = Common::Input::CreateDeviceFromString( + params.Get("modifier", null_engine)); + auto modifier_scale = params.Get("modifier_scale", 0.5f); + auto modifier_angle = params.Get("modifier_angle", 5.5f); + return std::make_unique(std::move(up), std::move(down), std::move(left), + std::move(right), std::move(modifier), modifier_scale, + modifier_angle); +} + +} // namespace InputCommon diff --git a/src/input_common/analog_from_button.h b/src/input_common/helpers/stick_from_buttons.h old mode 100755 new mode 100644 similarity index 82% rename from src/input_common/analog_from_button.h rename to src/input_common/helpers/stick_from_buttons.h index bbd583dd93..437ace4f7e --- a/src/input_common/analog_from_button.h +++ b/src/input_common/helpers/stick_from_buttons.h @@ -4,8 +4,7 @@ #pragma once -#include -#include "core/frontend/input.h" +#include "common/input.h" namespace InputCommon { @@ -13,7 +12,7 @@ namespace InputCommon { * An analog device factory that takes direction button devices and combines them into a analog * device. */ -class AnalogFromButton final : public Input::Factory { +class StickFromButton final : public Common::Input::Factory { public: /** * Creates an analog device from direction button devices @@ -25,7 +24,7 @@ public: * - "modifier": a serialized ParamPackage for creating a button device as the modifier * - "modifier_scale": a float for the multiplier the modifier gives to the position */ - std::unique_ptr Create(const Common::ParamPackage& params) override; + std::unique_ptr Create(const Common::ParamPackage& params) override; }; } // namespace InputCommon diff --git a/src/input_common/helpers/touch_from_buttons.cpp b/src/input_common/helpers/touch_from_buttons.cpp new file mode 100644 index 0000000000..35d60bc909 --- /dev/null +++ b/src/input_common/helpers/touch_from_buttons.cpp @@ -0,0 +1,81 @@ +// Copyright 2020 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include "common/settings.h" +#include "core/frontend/framebuffer_layout.h" +#include "input_common/helpers/touch_from_buttons.h" + +namespace InputCommon { + +class TouchFromButtonDevice final : public Common::Input::InputDevice { +public: + using Button = std::unique_ptr; + TouchFromButtonDevice(Button button_, int touch_id_, float x_, float y_) + : button(std::move(button_)), touch_id(touch_id_), x(x_), y(y_) { + Common::Input::InputCallback button_up_callback{ + [this](Common::Input::CallbackStatus callback_) { UpdateButtonStatus(callback_); }}; + last_button_value = false; + button->SetCallback(button_up_callback); + button->ForceUpdate(); + } + + void ForceUpdate() override { + button->ForceUpdate(); + } + + Common::Input::TouchStatus GetStatus(bool pressed) const { + const Common::Input::ButtonStatus button_status{ + .value = pressed, + }; + Common::Input::TouchStatus status{ + .pressed = button_status, + .x = {}, + .y = {}, + .id = touch_id, + }; + status.x.properties = properties; + status.y.properties = properties; + + if (!pressed) { + return status; + } + + status.x.raw_value = x; + status.y.raw_value = y; + return status; + } + + void UpdateButtonStatus(Common::Input::CallbackStatus button_callback) { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Touch, + .touch_status = GetStatus(button_callback.button_status.value), + }; + if (last_button_value != button_callback.button_status.value) { + last_button_value = button_callback.button_status.value; + TriggerOnChange(status); + } + } + +private: + Button button; + bool last_button_value; + const int touch_id; + const float x; + const float y; + const Common::Input::AnalogProperties properties{0.0f, 1.0f, 0.5f, 0.0f, false}; +}; + +std::unique_ptr TouchFromButton::Create( + const Common::ParamPackage& params) { + const std::string null_engine = Common::ParamPackage{{"engine", "null"}}.Serialize(); + auto button = Common::Input::CreateDeviceFromString( + params.Get("button", null_engine)); + const auto touch_id = params.Get("touch_id", 0); + const float x = params.Get("x", 0.0f) / 1280.0f; + const float y = params.Get("y", 0.0f) / 720.0f; + return std::make_unique(std::move(button), touch_id, x, y); +} + +} // namespace InputCommon diff --git a/src/input_common/touch_from_button.h b/src/input_common/helpers/touch_from_buttons.h similarity index 63% rename from src/input_common/touch_from_button.h rename to src/input_common/helpers/touch_from_buttons.h index 8b4d1aa961..628f182157 100644 --- a/src/input_common/touch_from_button.h +++ b/src/input_common/helpers/touch_from_buttons.h @@ -4,20 +4,19 @@ #pragma once -#include -#include "core/frontend/input.h" +#include "common/input.h" namespace InputCommon { /** * A touch device factory that takes a list of button devices and combines them into a touch device. */ -class TouchFromButtonFactory final : public Input::Factory { +class TouchFromButton final : public Common::Input::Factory { public: /** * Creates a touch device from a list of button devices */ - std::unique_ptr Create(const Common::ParamPackage& params) override; + std::unique_ptr Create(const Common::ParamPackage& params) override; }; } // namespace InputCommon diff --git a/src/input_common/udp/protocol.cpp b/src/input_common/helpers/udp_protocol.cpp similarity index 98% rename from src/input_common/udp/protocol.cpp rename to src/input_common/helpers/udp_protocol.cpp index 5e50bd6129..cdeab7e118 100644 --- a/src/input_common/udp/protocol.cpp +++ b/src/input_common/helpers/udp_protocol.cpp @@ -5,7 +5,7 @@ #include #include #include "common/logging/log.h" -#include "input_common/udp/protocol.h" +#include "input_common/helpers/udp_protocol.h" namespace InputCommon::CemuhookUDP { diff --git a/src/input_common/udp/protocol.h b/src/input_common/helpers/udp_protocol.h similarity index 88% rename from src/input_common/udp/protocol.h rename to src/input_common/helpers/udp_protocol.h index 1bdc9209e7..bcba12c58e 100644 --- a/src/input_common/udp/protocol.h +++ b/src/input_common/helpers/udp_protocol.h @@ -56,6 +56,12 @@ constexpr Type GetMessageType(); namespace Request { +enum RegisterFlags : u8 { + AllPads, + PadID, + PadMACAdddress, +}; + struct Version {}; /** * Requests the server to send information about what controllers are plugged into the ports @@ -77,13 +83,8 @@ static_assert(std::is_trivially_copyable_v, * timeout seems to be 5 seconds. */ struct PadData { - enum class Flags : u8 { - AllPorts, - Id, - Mac, - }; /// Determines which method will be used as a look up for the controller - Flags flags{}; + RegisterFlags flags{}; /// Index of the port of the controller to retrieve data about u8 port_id{}; /// Mac address of the controller to retrieve data about @@ -113,6 +114,36 @@ Message Create(const T data, const u32 client_id = 0) { namespace Response { +enum class ConnectionType : u8 { + None, + Usb, + Bluetooth, +}; + +enum class State : u8 { + Disconnected, + Reserved, + Connected, +}; + +enum class Model : u8 { + None, + PartialGyro, + FullGyro, + Generic, +}; + +enum class Battery : u8 { + None = 0x00, + Dying = 0x01, + Low = 0x02, + Medium = 0x03, + High = 0x04, + Full = 0x05, + Charging = 0xEE, + Charged = 0xEF, +}; + struct Version { u16_le version{}; }; @@ -122,11 +153,11 @@ static_assert(std::is_trivially_copyable_v, struct PortInfo { u8 id{}; - u8 state{}; - u8 model{}; - u8 connection_type{}; + State state{}; + Model model{}; + ConnectionType connection_type{}; MacAddress mac; - u8 battery{}; + Battery battery{}; u8 is_pad_active{}; }; static_assert(sizeof(PortInfo) == 12, "UDP Response PortInfo struct has wrong size"); @@ -177,18 +208,18 @@ struct PadData { u8 right_stick_y{}; struct AnalogButton { - u8 button_8{}; - u8 button_7{}; - u8 button_6{}; - u8 button_5{}; - u8 button_12{}; - u8 button_11{}; - u8 button_10{}; - u8 button_9{}; - u8 button_16{}; - u8 button_15{}; - u8 button_14{}; - u8 button_13{}; + u8 button_dpad_left_analog{}; + u8 button_dpad_down_analog{}; + u8 button_dpad_right_analog{}; + u8 button_dpad_up_analog{}; + u8 button_square_analog{}; + u8 button_cross_analog{}; + u8 button_circle_analog{}; + u8 button_triangle_analog{}; + u8 button_r1_analog{}; + u8 button_l1_analog{}; + u8 trigger_r2{}; + u8 trigger_l2{}; } analog_button; std::array touch; diff --git a/src/input_common/input_engine.cpp b/src/input_common/input_engine.cpp new file mode 100644 index 0000000000..2b2105376f --- /dev/null +++ b/src/input_common/input_engine.cpp @@ -0,0 +1,364 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included + +#include "common/logging/log.h" +#include "common/param_package.h" +#include "input_common/input_engine.h" + +namespace InputCommon { + +void InputEngine::PreSetController(const PadIdentifier& identifier) { + std::lock_guard lock{mutex}; + if (!controller_list.contains(identifier)) { + controller_list.insert_or_assign(identifier, ControllerData{}); + } +} + +void InputEngine::PreSetButton(const PadIdentifier& identifier, int button) { + std::lock_guard lock{mutex}; + ControllerData& controller = controller_list.at(identifier); + if (!controller.buttons.contains(button)) { + controller.buttons.insert_or_assign(button, false); + } +} + +void InputEngine::PreSetHatButton(const PadIdentifier& identifier, int button) { + std::lock_guard lock{mutex}; + ControllerData& controller = controller_list.at(identifier); + if (!controller.hat_buttons.contains(button)) { + controller.hat_buttons.insert_or_assign(button, u8{0}); + } +} + +void InputEngine::PreSetAxis(const PadIdentifier& identifier, int axis) { + std::lock_guard lock{mutex}; + ControllerData& controller = controller_list.at(identifier); + if (!controller.axes.contains(axis)) { + controller.axes.insert_or_assign(axis, 0.0f); + } +} + +void InputEngine::PreSetMotion(const PadIdentifier& identifier, int motion) { + std::lock_guard lock{mutex}; + ControllerData& controller = controller_list.at(identifier); + if (!controller.motions.contains(motion)) { + controller.motions.insert_or_assign(motion, BasicMotion{}); + } +} + +void InputEngine::SetButton(const PadIdentifier& identifier, int button, bool value) { + { + std::lock_guard lock{mutex}; + ControllerData& controller = controller_list.at(identifier); + if (!configuring) { + controller.buttons.insert_or_assign(button, value); + } + } + TriggerOnButtonChange(identifier, button, value); +} + +void InputEngine::SetHatButton(const PadIdentifier& identifier, int button, u8 value) { + { + std::lock_guard lock{mutex}; + ControllerData& controller = controller_list.at(identifier); + if (!configuring) { + controller.hat_buttons.insert_or_assign(button, value); + } + } + TriggerOnHatButtonChange(identifier, button, value); +} + +void InputEngine::SetAxis(const PadIdentifier& identifier, int axis, f32 value) { + { + std::lock_guard lock{mutex}; + ControllerData& controller = controller_list.at(identifier); + if (!configuring) { + controller.axes.insert_or_assign(axis, value); + } + } + TriggerOnAxisChange(identifier, axis, value); +} + +void InputEngine::SetBattery(const PadIdentifier& identifier, BatteryLevel value) { + { + std::lock_guard lock{mutex}; + ControllerData& controller = controller_list.at(identifier); + if (!configuring) { + controller.battery = value; + } + } + TriggerOnBatteryChange(identifier, value); +} + +void InputEngine::SetMotion(const PadIdentifier& identifier, int motion, BasicMotion value) { + { + std::lock_guard lock{mutex}; + ControllerData& controller = controller_list.at(identifier); + if (!configuring) { + controller.motions.insert_or_assign(motion, value); + } + } + TriggerOnMotionChange(identifier, motion, value); +} + +bool InputEngine::GetButton(const PadIdentifier& identifier, int button) const { + std::lock_guard lock{mutex}; + if (!controller_list.contains(identifier)) { + LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.Format(), + identifier.pad, identifier.port); + return false; + } + ControllerData controller = controller_list.at(identifier); + if (!controller.buttons.contains(button)) { + LOG_ERROR(Input, "Invalid button {}", button); + return false; + } + return controller.buttons.at(button); +} + +bool InputEngine::GetHatButton(const PadIdentifier& identifier, int button, u8 direction) const { + std::lock_guard lock{mutex}; + if (!controller_list.contains(identifier)) { + LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.Format(), + identifier.pad, identifier.port); + return false; + } + ControllerData controller = controller_list.at(identifier); + if (!controller.hat_buttons.contains(button)) { + LOG_ERROR(Input, "Invalid hat button {}", button); + return false; + } + return (controller.hat_buttons.at(button) & direction) != 0; +} + +f32 InputEngine::GetAxis(const PadIdentifier& identifier, int axis) const { + std::lock_guard lock{mutex}; + if (!controller_list.contains(identifier)) { + LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.Format(), + identifier.pad, identifier.port); + return 0.0f; + } + ControllerData controller = controller_list.at(identifier); + if (!controller.axes.contains(axis)) { + LOG_ERROR(Input, "Invalid axis {}", axis); + return 0.0f; + } + return controller.axes.at(axis); +} + +BatteryLevel InputEngine::GetBattery(const PadIdentifier& identifier) const { + std::lock_guard lock{mutex}; + if (!controller_list.contains(identifier)) { + LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.Format(), + identifier.pad, identifier.port); + return BatteryLevel::Charging; + } + ControllerData controller = controller_list.at(identifier); + return controller.battery; +} + +BasicMotion InputEngine::GetMotion(const PadIdentifier& identifier, int motion) const { + std::lock_guard lock{mutex}; + if (!controller_list.contains(identifier)) { + LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.Format(), + identifier.pad, identifier.port); + return {}; + } + ControllerData controller = controller_list.at(identifier); + return controller.motions.at(motion); +} + +void InputEngine::ResetButtonState() { + for (std::pair controller : controller_list) { + for (std::pair button : controller.second.buttons) { + SetButton(controller.first, button.first, false); + } + for (std::pair button : controller.second.hat_buttons) { + SetHatButton(controller.first, button.first, false); + } + } +} + +void InputEngine::ResetAnalogState() { + for (std::pair controller : controller_list) { + for (std::pair axis : controller.second.axes) { + SetAxis(controller.first, axis.first, 0.0); + } + } +} + +void InputEngine::TriggerOnButtonChange(const PadIdentifier& identifier, int button, bool value) { + std::lock_guard lock{mutex_callback}; + for (const std::pair poller_pair : callback_list) { + const InputIdentifier& poller = poller_pair.second; + if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Button, button)) { + continue; + } + if (poller.callback.on_change) { + poller.callback.on_change(); + } + } + if (!configuring || !mapping_callback.on_data) { + return; + } + + PreSetButton(identifier, button); + if (value == GetButton(identifier, button)) { + return; + } + mapping_callback.on_data(MappingData{ + .engine = GetEngineName(), + .pad = identifier, + .type = EngineInputType::Button, + .index = button, + .button_value = value, + }); +} + +void InputEngine::TriggerOnHatButtonChange(const PadIdentifier& identifier, int button, u8 value) { + std::lock_guard lock{mutex_callback}; + for (const std::pair poller_pair : callback_list) { + const InputIdentifier& poller = poller_pair.second; + if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::HatButton, button)) { + continue; + } + if (poller.callback.on_change) { + poller.callback.on_change(); + } + } + if (!configuring || !mapping_callback.on_data) { + return; + } + for (std::size_t index = 1; index < 0xff; index <<= 1) { + bool button_value = (value & index) != 0; + if (button_value == GetHatButton(identifier, button, static_cast(index))) { + continue; + } + mapping_callback.on_data(MappingData{ + .engine = GetEngineName(), + .pad = identifier, + .type = EngineInputType::HatButton, + .index = button, + .hat_name = GetHatButtonName(static_cast(index)), + }); + } +} + +void InputEngine::TriggerOnAxisChange(const PadIdentifier& identifier, int axis, f32 value) { + std::lock_guard lock{mutex_callback}; + for (const std::pair poller_pair : callback_list) { + const InputIdentifier& poller = poller_pair.second; + if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Analog, axis)) { + continue; + } + if (poller.callback.on_change) { + poller.callback.on_change(); + } + } + if (!configuring || !mapping_callback.on_data) { + return; + } + if (std::abs(value - GetAxis(identifier, axis)) < 0.5f) { + return; + } + mapping_callback.on_data(MappingData{ + .engine = GetEngineName(), + .pad = identifier, + .type = EngineInputType::Analog, + .index = axis, + .axis_value = value, + }); +} + +void InputEngine::TriggerOnBatteryChange(const PadIdentifier& identifier, + [[maybe_unused]] BatteryLevel value) { + std::lock_guard lock{mutex_callback}; + for (const std::pair poller_pair : callback_list) { + const InputIdentifier& poller = poller_pair.second; + if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Battery, 0)) { + continue; + } + if (poller.callback.on_change) { + poller.callback.on_change(); + } + } +} + +void InputEngine::TriggerOnMotionChange(const PadIdentifier& identifier, int motion, + BasicMotion value) { + std::lock_guard lock{mutex_callback}; + for (const std::pair poller_pair : callback_list) { + const InputIdentifier& poller = poller_pair.second; + if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Motion, motion)) { + continue; + } + if (poller.callback.on_change) { + poller.callback.on_change(); + } + } + if (!configuring || !mapping_callback.on_data) { + return; + } + if (std::abs(value.gyro_x) < 0.6f && std::abs(value.gyro_y) < 0.6f && + std::abs(value.gyro_z) < 0.6f) { + return; + } + mapping_callback.on_data(MappingData{ + .engine = GetEngineName(), + .pad = identifier, + .type = EngineInputType::Motion, + .index = motion, + .motion_value = value, + }); +} + +bool InputEngine::IsInputIdentifierEqual(const InputIdentifier& input_identifier, + const PadIdentifier& identifier, EngineInputType type, + int index) const { + if (input_identifier.type != type) { + return false; + } + if (input_identifier.index != index) { + return false; + } + if (input_identifier.identifier != identifier) { + return false; + } + return true; +} + +void InputEngine::BeginConfiguration() { + configuring = true; +} + +void InputEngine::EndConfiguration() { + configuring = false; +} + +const std::string& InputEngine::GetEngineName() const { + return input_engine; +} + +int InputEngine::SetCallback(InputIdentifier input_identifier) { + std::lock_guard lock{mutex_callback}; + callback_list.insert_or_assign(last_callback_key, input_identifier); + return last_callback_key++; +} + +void InputEngine::SetMappingCallback(MappingCallback callback) { + std::lock_guard lock{mutex_callback}; + mapping_callback = std::move(callback); +} + +void InputEngine::DeleteCallback(int key) { + std::lock_guard lock{mutex_callback}; + const auto& iterator = callback_list.find(key); + if (iterator == callback_list.end()) { + LOG_ERROR(Input, "Tried to delete non-existent callback {}", key); + return; + } + callback_list.erase(iterator); +} + +} // namespace InputCommon diff --git a/src/input_common/input_engine.h b/src/input_common/input_engine.h new file mode 100644 index 0000000000..02272b3f87 --- /dev/null +++ b/src/input_common/input_engine.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/common_types.h" +#include "common/input.h" +#include "common/param_package.h" +#include "common/uuid.h" +#include "input_common/main.h" + +// Pad Identifier of data source +struct PadIdentifier { + Common::UUID guid{}; + std::size_t port{}; + std::size_t pad{}; + + friend constexpr bool operator==(const PadIdentifier&, const PadIdentifier&) = default; +}; + +// Basic motion data containing data from the sensors and a timestamp in microsecons +struct BasicMotion { + float gyro_x; + float gyro_y; + float gyro_z; + float accel_x; + float accel_y; + float accel_z; + u64 delta_timestamp; +}; + +// Stages of a battery charge +enum class BatteryLevel { + Empty, + Critical, + Low, + Medium, + Full, + Charging, +}; + +// Types of input that are stored in the engine +enum class EngineInputType { + None, + Button, + HatButton, + Analog, + Motion, + Battery, +}; + +namespace std { +// Hash used to create lists from PadIdentifier data +template <> +struct hash { + size_t operator()(const PadIdentifier& pad_id) const noexcept { + u64 hash_value = pad_id.guid.uuid[1] ^ pad_id.guid.uuid[0]; + hash_value ^= (static_cast(pad_id.port) << 32); + hash_value ^= static_cast(pad_id.pad); + return static_cast(hash_value); + } +}; + +} // namespace std + +namespace InputCommon { + +// Data from the engine and device needed for creating a ParamPackage +struct MappingData { + std::string engine{}; + PadIdentifier pad{}; + EngineInputType type{}; + int index{}; + bool button_value{}; + std::string hat_name{}; + f32 axis_value{}; + BasicMotion motion_value{}; +}; + +// Triggered if data changed on the controller +struct UpdateCallback { + std::function on_change; +}; + +// Triggered if data changed on the controller and the engine is on configuring mode +struct MappingCallback { + std::function on_data; +}; + +// Input Identifier of data source +struct InputIdentifier { + PadIdentifier identifier; + EngineInputType type; + int index; + UpdateCallback callback; +}; + +class InputEngine { +public: + explicit InputEngine(const std::string& input_engine_) : input_engine(input_engine_) { + callback_list.clear(); + } + + virtual ~InputEngine() = default; + + // Enable configuring mode for mapping + void BeginConfiguration(); + + // Disable configuring mode for mapping + void EndConfiguration(); + + // Sets a led pattern for a controller + virtual void SetLeds([[maybe_unused]] const PadIdentifier& identifier, + [[maybe_unused]] const Common::Input::LedStatus led_status) { + return; + } + + // Sets rumble to a controller + virtual Common::Input::VibrationError SetRumble( + [[maybe_unused]] const PadIdentifier& identifier, + [[maybe_unused]] const Common::Input::VibrationStatus vibration) { + return Common::Input::VibrationError::NotSupported; + } + + // Sets polling mode to a controller + virtual Common::Input::PollingError SetPollingMode( + [[maybe_unused]] const PadIdentifier& identifier, + [[maybe_unused]] const Common::Input::PollingMode vibration) { + return Common::Input::PollingError::NotSupported; + } + + // Returns the engine name + [[nodiscard]] const std::string& GetEngineName() const; + + /// Used for automapping features + virtual std::vector GetInputDevices() const { + return {}; + }; + + /// Retrieves the button mappings for the given device + virtual InputCommon::ButtonMapping GetButtonMappingForDevice( + [[maybe_unused]] const Common::ParamPackage& params) { + return {}; + }; + + /// Retrieves the analog mappings for the given device + virtual InputCommon::AnalogMapping GetAnalogMappingForDevice( + [[maybe_unused]] const Common::ParamPackage& params) { + return {}; + }; + + /// Retrieves the motion mappings for the given device + virtual InputCommon::MotionMapping GetMotionMappingForDevice( + [[maybe_unused]] const Common::ParamPackage& params) { + return {}; + }; + + /// Retrieves the name of the given input. + virtual Common::Input::ButtonNames GetUIName( + [[maybe_unused]] const Common::ParamPackage& params) const { + return Common::Input::ButtonNames::Engine; + }; + + /// Retrieves the index number of the given hat button direction + virtual u8 GetHatButtonId([[maybe_unused]] const std::string& direction_name) const { + return 0; + }; + + void PreSetController(const PadIdentifier& identifier); + void PreSetButton(const PadIdentifier& identifier, int button); + void PreSetHatButton(const PadIdentifier& identifier, int button); + void PreSetAxis(const PadIdentifier& identifier, int axis); + void PreSetMotion(const PadIdentifier& identifier, int motion); + void ResetButtonState(); + void ResetAnalogState(); + + bool GetButton(const PadIdentifier& identifier, int button) const; + bool GetHatButton(const PadIdentifier& identifier, int button, u8 direction) const; + f32 GetAxis(const PadIdentifier& identifier, int axis) const; + BatteryLevel GetBattery(const PadIdentifier& identifier) const; + BasicMotion GetMotion(const PadIdentifier& identifier, int motion) const; + + int SetCallback(InputIdentifier input_identifier); + void SetMappingCallback(MappingCallback callback); + void DeleteCallback(int key); + +protected: + void SetButton(const PadIdentifier& identifier, int button, bool value); + void SetHatButton(const PadIdentifier& identifier, int button, u8 value); + void SetAxis(const PadIdentifier& identifier, int axis, f32 value); + void SetBattery(const PadIdentifier& identifier, BatteryLevel value); + void SetMotion(const PadIdentifier& identifier, int motion, BasicMotion value); + + virtual std::string GetHatButtonName([[maybe_unused]] u8 direction_value) const { + return "Unknown"; + } + +private: + struct ControllerData { + std::unordered_map buttons; + std::unordered_map hat_buttons; + std::unordered_map axes; + std::unordered_map motions; + BatteryLevel battery; + }; + + void TriggerOnButtonChange(const PadIdentifier& identifier, int button, bool value); + void TriggerOnHatButtonChange(const PadIdentifier& identifier, int button, u8 value); + void TriggerOnAxisChange(const PadIdentifier& identifier, int button, f32 value); + void TriggerOnBatteryChange(const PadIdentifier& identifier, BatteryLevel value); + void TriggerOnMotionChange(const PadIdentifier& identifier, int motion, BasicMotion value); + + bool IsInputIdentifierEqual(const InputIdentifier& input_identifier, + const PadIdentifier& identifier, EngineInputType type, + int index) const; + + mutable std::mutex mutex; + mutable std::mutex mutex_callback; + bool configuring{false}; + const std::string input_engine; + int last_callback_key = 0; + std::unordered_map controller_list; + std::unordered_map callback_list; + MappingCallback mapping_callback; +}; + +} // namespace InputCommon diff --git a/src/input_common/input_mapping.cpp b/src/input_common/input_mapping.cpp new file mode 100644 index 0000000000..6e0024b2da --- /dev/null +++ b/src/input_common/input_mapping.cpp @@ -0,0 +1,207 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included + +#include "common/common_types.h" +#include "common/settings.h" +#include "input_common/input_engine.h" +#include "input_common/input_mapping.h" + +namespace InputCommon { + +MappingFactory::MappingFactory() {} + +void MappingFactory::BeginMapping(Polling::InputType type) { + is_enabled = true; + input_type = type; + input_queue.Clear(); + first_axis = -1; + second_axis = -1; +} + +[[nodiscard]] const Common::ParamPackage MappingFactory::GetNextInput() { + Common::ParamPackage input; + input_queue.Pop(input); + return input; +} + +void MappingFactory::RegisterInput(const MappingData& data) { + if (!is_enabled) { + return; + } + if (!IsDriverValid(data)) { + return; + } + + switch (input_type) { + case Polling::InputType::Button: + RegisterButton(data); + return; + case Polling::InputType::Stick: + RegisterStick(data); + return; + case Polling::InputType::Motion: + RegisterMotion(data); + return; + default: + return; + } +} + +void MappingFactory::StopMapping() { + is_enabled = false; + input_type = Polling::InputType::None; + input_queue.Clear(); +} + +void MappingFactory::RegisterButton(const MappingData& data) { + Common::ParamPackage new_input; + new_input.Set("engine", data.engine); + if (data.pad.guid != Common::UUID{}) { + new_input.Set("guid", data.pad.guid.Format()); + } + new_input.Set("port", static_cast(data.pad.port)); + new_input.Set("pad", static_cast(data.pad.pad)); + + switch (data.type) { + case EngineInputType::Button: + // Workaround for old compatibility + if (data.engine == "keyboard") { + new_input.Set("code", data.index); + break; + } + new_input.Set("button", data.index); + break; + case EngineInputType::HatButton: + new_input.Set("hat", data.index); + new_input.Set("direction", data.hat_name); + break; + case EngineInputType::Analog: + // Ignore mouse axis when mapping buttons + if (data.engine == "mouse") { + return; + } + new_input.Set("axis", data.index); + new_input.Set("threshold", 0.5f); + break; + default: + return; + } + input_queue.Push(new_input); +} + +void MappingFactory::RegisterStick(const MappingData& data) { + Common::ParamPackage new_input; + new_input.Set("engine", data.engine); + if (data.pad.guid != Common::UUID{}) { + new_input.Set("guid", data.pad.guid.Format()); + } + new_input.Set("port", static_cast(data.pad.port)); + new_input.Set("pad", static_cast(data.pad.pad)); + + // If engine is mouse map the mouse position as a joystick + if (data.engine == "mouse") { + new_input.Set("axis_x", 0); + new_input.Set("axis_y", 1); + new_input.Set("threshold", 0.5f); + new_input.Set("range", 1.0f); + new_input.Set("deadzone", 0.0f); + input_queue.Push(new_input); + return; + } + + switch (data.type) { + case EngineInputType::Button: + case EngineInputType::HatButton: + RegisterButton(data); + return; + case EngineInputType::Analog: + if (first_axis == data.index) { + return; + } + if (first_axis == -1) { + first_axis = data.index; + return; + } + new_input.Set("axis_x", first_axis); + new_input.Set("axis_y", data.index); + new_input.Set("threshold", 0.5f); + new_input.Set("range", 0.95f); + new_input.Set("deadzone", 0.15f); + break; + default: + return; + } + input_queue.Push(new_input); +} + +void MappingFactory::RegisterMotion(const MappingData& data) { + Common::ParamPackage new_input; + new_input.Set("engine", data.engine); + if (data.pad.guid != Common::UUID{}) { + new_input.Set("guid", data.pad.guid.Format()); + } + new_input.Set("port", static_cast(data.pad.port)); + new_input.Set("pad", static_cast(data.pad.pad)); + switch (data.type) { + case EngineInputType::Button: + case EngineInputType::HatButton: + RegisterButton(data); + return; + case EngineInputType::Analog: + if (first_axis == data.index) { + return; + } + if (second_axis == data.index) { + return; + } + if (first_axis == -1) { + first_axis = data.index; + return; + } + if (second_axis == -1) { + second_axis = data.index; + return; + } + new_input.Set("axis_x", first_axis); + new_input.Set("axis_y", second_axis); + new_input.Set("axis_z", data.index); + new_input.Set("range", 1.0f); + new_input.Set("deadzone", 0.20f); + break; + case EngineInputType::Motion: + new_input.Set("motion", data.index); + break; + default: + return; + } + input_queue.Push(new_input); +} + +bool MappingFactory::IsDriverValid(const MappingData& data) const { + // Only port 0 can be mapped on the keyboard + if (data.engine == "keyboard" && data.pad.port != 0) { + return false; + } + // To prevent mapping with two devices we disable any UDP except motion + if (!Settings::values.enable_udp_controller && data.engine == "cemuhookudp" && + data.type != EngineInputType::Motion) { + return false; + } + // The following drivers don't need to be mapped + if (data.engine == "tas") { + return false; + } + if (data.engine == "touch") { + return false; + } + if (data.engine == "touch_from_button") { + return false; + } + if (data.engine == "analog_from_button") { + return false; + } + return true; +} + +} // namespace InputCommon diff --git a/src/input_common/input_mapping.h b/src/input_common/input_mapping.h new file mode 100644 index 0000000000..44eb8ad9a3 --- /dev/null +++ b/src/input_common/input_mapping.h @@ -0,0 +1,83 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included + +#pragma once +#include "common/threadsafe_queue.h" + +namespace InputCommon { +class InputEngine; +struct MappingData; + +class MappingFactory { +public: + MappingFactory(); + + /** + * Resets all varables to beggin the mapping process + * @param "type": type of input desired to be returned + */ + void BeginMapping(Polling::InputType type); + + /// Returns an input event with mapping information from the input_queue + [[nodiscard]] const Common::ParamPackage GetNextInput(); + + /** + * Registers mapping input data from the driver + * @param "data": An struct containing all the information needed to create a proper + * ParamPackage + */ + void RegisterInput(const MappingData& data); + + /// Stop polling from all backends + void StopMapping(); + +private: + /** + * If provided data satisfies the requeriments it will push an element to the input_queue + * Supported input: + * - Button: Creates a basic button ParamPackage + * - HatButton: Creates a basic hat button ParamPackage + * - Analog: Creates a basic analog ParamPackage + * @param "data": An struct containing all the information needed to create a proper + * ParamPackage + */ + void RegisterButton(const MappingData& data); + + /** + * If provided data satisfies the requeriments it will push an element to the input_queue + * Supported input: + * - Button, HatButton: Pass the data to RegisterButton + * - Analog: Stores the first axis and on the second axis creates a basic stick ParamPackage + * @param "data": An struct containing all the information needed to create a proper + * ParamPackage + */ + void RegisterStick(const MappingData& data); + + /** + * If provided data satisfies the requeriments it will push an element to the input_queue + * Supported input: + * - Button, HatButton: Pass the data to RegisterButton + * - Analog: Stores the first two axis and on the third axis creates a basic Motion + * ParamPackage + * - Motion: Creates a basic Motion ParamPackage + * @param "data": An struct containing all the information needed to create a proper + * ParamPackage + */ + void RegisterMotion(const MappingData& data); + + /** + * Returns true if driver can be mapped + * @param "data": An struct containing all the information needed to create a proper + * ParamPackage + */ + bool IsDriverValid(const MappingData& data) const; + + Common::SPSCQueue input_queue; + Polling::InputType input_type{Polling::InputType::None}; + bool is_enabled{}; + int first_axis = -1; + int second_axis = -1; +}; + +} // namespace InputCommon diff --git a/src/input_common/input_poller.cpp b/src/input_common/input_poller.cpp new file mode 100644 index 0000000000..7e4eafded0 --- /dev/null +++ b/src/input_common/input_poller.cpp @@ -0,0 +1,971 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included + +#include "common/common_types.h" +#include "common/input.h" + +#include "input_common/input_engine.h" +#include "input_common/input_poller.h" + +namespace InputCommon { + +class DummyInput final : public Common::Input::InputDevice { +public: + explicit DummyInput() {} + ~DummyInput() {} +}; + +class InputFromButton final : public Common::Input::InputDevice { +public: + explicit InputFromButton(PadIdentifier identifier_, int button_, bool toggle_, bool inverted_, + InputEngine* input_engine_) + : identifier(identifier_), button(button_), toggle(toggle_), inverted(inverted_), + input_engine(input_engine_) { + UpdateCallback engine_callback{[this]() { OnChange(); }}; + const InputIdentifier input_identifier{ + .identifier = identifier, + .type = EngineInputType::Button, + .index = button, + .callback = engine_callback, + }; + last_button_value = false; + callback_key = input_engine->SetCallback(input_identifier); + } + + ~InputFromButton() { + input_engine->DeleteCallback(callback_key); + } + + Common::Input::ButtonStatus GetStatus() const { + return { + .value = input_engine->GetButton(identifier, button), + .inverted = inverted, + .toggle = toggle, + }; + } + + void ForceUpdate() { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Button, + .button_status = GetStatus(), + }; + + last_button_value = status.button_status.value; + TriggerOnChange(status); + } + + void OnChange() { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Button, + .button_status = GetStatus(), + }; + + if (status.button_status.value != last_button_value) { + last_button_value = status.button_status.value; + TriggerOnChange(status); + } + } + +private: + const PadIdentifier identifier; + const int button; + const bool toggle; + const bool inverted; + int callback_key; + bool last_button_value; + InputEngine* input_engine; +}; + +class InputFromHatButton final : public Common::Input::InputDevice { +public: + explicit InputFromHatButton(PadIdentifier identifier_, int button_, u8 direction_, bool toggle_, + bool inverted_, InputEngine* input_engine_) + : identifier(identifier_), button(button_), direction(direction_), toggle(toggle_), + inverted(inverted_), input_engine(input_engine_) { + UpdateCallback engine_callback{[this]() { OnChange(); }}; + const InputIdentifier input_identifier{ + .identifier = identifier, + .type = EngineInputType::HatButton, + .index = button, + .callback = engine_callback, + }; + last_button_value = false; + callback_key = input_engine->SetCallback(input_identifier); + } + + ~InputFromHatButton() { + input_engine->DeleteCallback(callback_key); + } + + Common::Input::ButtonStatus GetStatus() const { + return { + .value = input_engine->GetHatButton(identifier, button, direction), + .inverted = inverted, + .toggle = toggle, + }; + } + + void ForceUpdate() { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Button, + .button_status = GetStatus(), + }; + + last_button_value = status.button_status.value; + TriggerOnChange(status); + } + + void OnChange() { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Button, + .button_status = GetStatus(), + }; + + if (status.button_status.value != last_button_value) { + last_button_value = status.button_status.value; + TriggerOnChange(status); + } + } + +private: + const PadIdentifier identifier; + const int button; + const u8 direction; + const bool toggle; + const bool inverted; + int callback_key; + bool last_button_value; + InputEngine* input_engine; +}; + +class InputFromStick final : public Common::Input::InputDevice { +public: + explicit InputFromStick(PadIdentifier identifier_, int axis_x_, int axis_y_, + Common::Input::AnalogProperties properties_x_, + Common::Input::AnalogProperties properties_y_, + InputEngine* input_engine_) + : identifier(identifier_), axis_x(axis_x_), axis_y(axis_y_), properties_x(properties_x_), + properties_y(properties_y_), + input_engine(input_engine_), invert_axis_y{input_engine_->GetEngineName() == "sdl"} { + UpdateCallback engine_callback{[this]() { OnChange(); }}; + const InputIdentifier x_input_identifier{ + .identifier = identifier, + .type = EngineInputType::Analog, + .index = axis_x, + .callback = engine_callback, + }; + const InputIdentifier y_input_identifier{ + .identifier = identifier, + .type = EngineInputType::Analog, + .index = axis_y, + .callback = engine_callback, + }; + last_axis_x_value = 0.0f; + last_axis_y_value = 0.0f; + callback_key_x = input_engine->SetCallback(x_input_identifier); + callback_key_y = input_engine->SetCallback(y_input_identifier); + } + + ~InputFromStick() { + input_engine->DeleteCallback(callback_key_x); + input_engine->DeleteCallback(callback_key_y); + } + + Common::Input::StickStatus GetStatus() const { + Common::Input::StickStatus status; + status.x = { + .raw_value = input_engine->GetAxis(identifier, axis_x), + .properties = properties_x, + }; + status.y = { + .raw_value = input_engine->GetAxis(identifier, axis_y), + .properties = properties_y, + }; + // This is a workaround too keep compatibility with old yuzu versions. Vertical axis is + // inverted on SDL compared to Nintendo + if (invert_axis_y) { + status.y.raw_value = -status.y.raw_value; + } + return status; + } + + void ForceUpdate() { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Stick, + .stick_status = GetStatus(), + }; + + last_axis_x_value = status.stick_status.x.raw_value; + last_axis_y_value = status.stick_status.y.raw_value; + TriggerOnChange(status); + } + + void OnChange() { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Stick, + .stick_status = GetStatus(), + }; + + if (status.stick_status.x.raw_value != last_axis_x_value || + status.stick_status.y.raw_value != last_axis_y_value) { + last_axis_x_value = status.stick_status.x.raw_value; + last_axis_y_value = status.stick_status.y.raw_value; + TriggerOnChange(status); + } + } + +private: + const PadIdentifier identifier; + const int axis_x; + const int axis_y; + const Common::Input::AnalogProperties properties_x; + const Common::Input::AnalogProperties properties_y; + int callback_key_x; + int callback_key_y; + float last_axis_x_value; + float last_axis_y_value; + InputEngine* input_engine; + const bool invert_axis_y; +}; + +class InputFromTouch final : public Common::Input::InputDevice { +public: + explicit InputFromTouch(PadIdentifier identifier_, int touch_id_, int button_, bool toggle_, + bool inverted_, int axis_x_, int axis_y_, + Common::Input::AnalogProperties properties_x_, + Common::Input::AnalogProperties properties_y_, + InputEngine* input_engine_) + : identifier(identifier_), touch_id(touch_id_), button(button_), toggle(toggle_), + inverted(inverted_), axis_x(axis_x_), axis_y(axis_y_), properties_x(properties_x_), + properties_y(properties_y_), input_engine(input_engine_) { + UpdateCallback engine_callback{[this]() { OnChange(); }}; + const InputIdentifier button_input_identifier{ + .identifier = identifier, + .type = EngineInputType::Button, + .index = button, + .callback = engine_callback, + }; + const InputIdentifier x_input_identifier{ + .identifier = identifier, + .type = EngineInputType::Analog, + .index = axis_x, + .callback = engine_callback, + }; + const InputIdentifier y_input_identifier{ + .identifier = identifier, + .type = EngineInputType::Analog, + .index = axis_y, + .callback = engine_callback, + }; + last_axis_x_value = 0.0f; + last_axis_y_value = 0.0f; + last_button_value = false; + callback_key_button = input_engine->SetCallback(button_input_identifier); + callback_key_x = input_engine->SetCallback(x_input_identifier); + callback_key_y = input_engine->SetCallback(y_input_identifier); + } + + ~InputFromTouch() { + input_engine->DeleteCallback(callback_key_button); + input_engine->DeleteCallback(callback_key_x); + input_engine->DeleteCallback(callback_key_y); + } + + Common::Input::TouchStatus GetStatus() const { + Common::Input::TouchStatus status; + status.id = touch_id; + status.pressed = { + .value = input_engine->GetButton(identifier, button), + .inverted = inverted, + .toggle = toggle, + }; + status.x = { + .raw_value = input_engine->GetAxis(identifier, axis_x), + .properties = properties_x, + }; + status.y = { + .raw_value = input_engine->GetAxis(identifier, axis_y), + .properties = properties_y, + }; + return status; + } + + void OnChange() { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Touch, + .touch_status = GetStatus(), + }; + + if (status.touch_status.x.raw_value != last_axis_x_value || + status.touch_status.y.raw_value != last_axis_y_value || + status.touch_status.pressed.value != last_button_value) { + last_axis_x_value = status.touch_status.x.raw_value; + last_axis_y_value = status.touch_status.y.raw_value; + last_button_value = status.touch_status.pressed.value; + TriggerOnChange(status); + } + } + +private: + const PadIdentifier identifier; + const int touch_id; + const int button; + const bool toggle; + const bool inverted; + const int axis_x; + const int axis_y; + const Common::Input::AnalogProperties properties_x; + const Common::Input::AnalogProperties properties_y; + int callback_key_button; + int callback_key_x; + int callback_key_y; + bool last_button_value; + float last_axis_x_value; + float last_axis_y_value; + InputEngine* input_engine; +}; + +class InputFromTrigger final : public Common::Input::InputDevice { +public: + explicit InputFromTrigger(PadIdentifier identifier_, int button_, bool toggle_, bool inverted_, + int axis_, Common::Input::AnalogProperties properties_, + InputEngine* input_engine_) + : identifier(identifier_), button(button_), toggle(toggle_), inverted(inverted_), + axis(axis_), properties(properties_), input_engine(input_engine_) { + UpdateCallback engine_callback{[this]() { OnChange(); }}; + const InputIdentifier button_input_identifier{ + .identifier = identifier, + .type = EngineInputType::Button, + .index = button, + .callback = engine_callback, + }; + const InputIdentifier axis_input_identifier{ + .identifier = identifier, + .type = EngineInputType::Analog, + .index = axis, + .callback = engine_callback, + }; + last_axis_value = 0.0f; + last_button_value = false; + callback_key_button = input_engine->SetCallback(button_input_identifier); + axis_callback_key = input_engine->SetCallback(axis_input_identifier); + } + + ~InputFromTrigger() { + input_engine->DeleteCallback(callback_key_button); + input_engine->DeleteCallback(axis_callback_key); + } + + Common::Input::TriggerStatus GetStatus() const { + const Common::Input::AnalogStatus analog_status{ + .raw_value = input_engine->GetAxis(identifier, axis), + .properties = properties, + }; + const Common::Input::ButtonStatus button_status{ + .value = input_engine->GetButton(identifier, button), + .inverted = inverted, + .toggle = toggle, + }; + return { + .analog = analog_status, + .pressed = button_status, + }; + } + + void OnChange() { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Trigger, + .trigger_status = GetStatus(), + }; + + if (status.trigger_status.analog.raw_value != last_axis_value || + status.trigger_status.pressed.value != last_button_value) { + last_axis_value = status.trigger_status.analog.raw_value; + last_button_value = status.trigger_status.pressed.value; + TriggerOnChange(status); + } + } + +private: + const PadIdentifier identifier; + const int button; + const bool toggle; + const bool inverted; + const int axis; + const Common::Input::AnalogProperties properties; + int callback_key_button; + int axis_callback_key; + bool last_button_value; + float last_axis_value; + InputEngine* input_engine; +}; + +class InputFromAnalog final : public Common::Input::InputDevice { +public: + explicit InputFromAnalog(PadIdentifier identifier_, int axis_, + Common::Input::AnalogProperties properties_, + InputEngine* input_engine_) + : identifier(identifier_), axis(axis_), properties(properties_), + input_engine(input_engine_) { + UpdateCallback engine_callback{[this]() { OnChange(); }}; + const InputIdentifier input_identifier{ + .identifier = identifier, + .type = EngineInputType::Analog, + .index = axis, + .callback = engine_callback, + }; + last_axis_value = 0.0f; + callback_key = input_engine->SetCallback(input_identifier); + } + + ~InputFromAnalog() { + input_engine->DeleteCallback(callback_key); + } + + Common::Input::AnalogStatus GetStatus() const { + return { + .raw_value = input_engine->GetAxis(identifier, axis), + .properties = properties, + }; + } + + void OnChange() { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Analog, + .analog_status = GetStatus(), + }; + + if (status.analog_status.raw_value != last_axis_value) { + last_axis_value = status.analog_status.raw_value; + TriggerOnChange(status); + } + } + +private: + const PadIdentifier identifier; + const int axis; + const Common::Input::AnalogProperties properties; + int callback_key; + float last_axis_value; + InputEngine* input_engine; +}; + +class InputFromBattery final : public Common::Input::InputDevice { +public: + explicit InputFromBattery(PadIdentifier identifier_, InputEngine* input_engine_) + : identifier(identifier_), input_engine(input_engine_) { + UpdateCallback engine_callback{[this]() { OnChange(); }}; + const InputIdentifier input_identifier{ + .identifier = identifier, + .type = EngineInputType::Battery, + .index = 0, + .callback = engine_callback, + }; + last_battery_value = Common::Input::BatteryStatus::Charging; + callback_key = input_engine->SetCallback(input_identifier); + } + + ~InputFromBattery() { + input_engine->DeleteCallback(callback_key); + } + + Common::Input::BatteryStatus GetStatus() const { + return static_cast(input_engine->GetBattery(identifier)); + } + + void ForceUpdate() { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Battery, + .battery_status = GetStatus(), + }; + + last_battery_value = status.battery_status; + TriggerOnChange(status); + } + + void OnChange() { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Battery, + .battery_status = GetStatus(), + }; + + if (status.battery_status != last_battery_value) { + last_battery_value = status.battery_status; + TriggerOnChange(status); + } + } + +private: + const PadIdentifier identifier; + int callback_key; + Common::Input::BatteryStatus last_battery_value; + InputEngine* input_engine; +}; + +class InputFromMotion final : public Common::Input::InputDevice { +public: + explicit InputFromMotion(PadIdentifier identifier_, int motion_sensor_, + InputEngine* input_engine_) + : identifier(identifier_), motion_sensor(motion_sensor_), input_engine(input_engine_) { + UpdateCallback engine_callback{[this]() { OnChange(); }}; + const InputIdentifier input_identifier{ + .identifier = identifier, + .type = EngineInputType::Motion, + .index = motion_sensor, + .callback = engine_callback, + }; + callback_key = input_engine->SetCallback(input_identifier); + } + + ~InputFromMotion() { + input_engine->DeleteCallback(callback_key); + } + + Common::Input::MotionStatus GetStatus() const { + const auto basic_motion = input_engine->GetMotion(identifier, motion_sensor); + Common::Input::MotionStatus status{}; + const Common::Input::AnalogProperties properties = { + .deadzone = 0.001f, + .range = 1.0f, + .offset = 0.0f, + }; + status.accel.x = {.raw_value = basic_motion.accel_x, .properties = properties}; + status.accel.y = {.raw_value = basic_motion.accel_y, .properties = properties}; + status.accel.z = {.raw_value = basic_motion.accel_z, .properties = properties}; + status.gyro.x = {.raw_value = basic_motion.gyro_x, .properties = properties}; + status.gyro.y = {.raw_value = basic_motion.gyro_y, .properties = properties}; + status.gyro.z = {.raw_value = basic_motion.gyro_z, .properties = properties}; + status.delta_timestamp = basic_motion.delta_timestamp; + return status; + } + + void OnChange() { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Motion, + .motion_status = GetStatus(), + }; + + TriggerOnChange(status); + } + +private: + const PadIdentifier identifier; + const int motion_sensor; + int callback_key; + InputEngine* input_engine; +}; + +class InputFromAxisMotion final : public Common::Input::InputDevice { +public: + explicit InputFromAxisMotion(PadIdentifier identifier_, int axis_x_, int axis_y_, int axis_z_, + Common::Input::AnalogProperties properties_x_, + Common::Input::AnalogProperties properties_y_, + Common::Input::AnalogProperties properties_z_, + InputEngine* input_engine_) + : identifier(identifier_), axis_x(axis_x_), axis_y(axis_y_), axis_z(axis_z_), + properties_x(properties_x_), properties_y(properties_y_), properties_z(properties_z_), + input_engine(input_engine_) { + UpdateCallback engine_callback{[this]() { OnChange(); }}; + const InputIdentifier x_input_identifier{ + .identifier = identifier, + .type = EngineInputType::Analog, + .index = axis_x, + .callback = engine_callback, + }; + const InputIdentifier y_input_identifier{ + .identifier = identifier, + .type = EngineInputType::Analog, + .index = axis_y, + .callback = engine_callback, + }; + const InputIdentifier z_input_identifier{ + .identifier = identifier, + .type = EngineInputType::Analog, + .index = axis_z, + .callback = engine_callback, + }; + last_axis_x_value = 0.0f; + last_axis_y_value = 0.0f; + last_axis_z_value = 0.0f; + callback_key_x = input_engine->SetCallback(x_input_identifier); + callback_key_y = input_engine->SetCallback(y_input_identifier); + callback_key_z = input_engine->SetCallback(z_input_identifier); + } + + ~InputFromAxisMotion() { + input_engine->DeleteCallback(callback_key_x); + input_engine->DeleteCallback(callback_key_y); + input_engine->DeleteCallback(callback_key_z); + } + + Common::Input::MotionStatus GetStatus() const { + Common::Input::MotionStatus status{}; + status.gyro.x = { + .raw_value = input_engine->GetAxis(identifier, axis_x), + .properties = properties_x, + }; + status.gyro.y = { + .raw_value = input_engine->GetAxis(identifier, axis_y), + .properties = properties_y, + }; + status.gyro.z = { + .raw_value = input_engine->GetAxis(identifier, axis_z), + .properties = properties_z, + }; + status.delta_timestamp = 5000; + status.force_update = true; + return status; + } + + void ForceUpdate() { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Motion, + .motion_status = GetStatus(), + }; + + last_axis_x_value = status.motion_status.gyro.x.raw_value; + last_axis_y_value = status.motion_status.gyro.y.raw_value; + last_axis_z_value = status.motion_status.gyro.z.raw_value; + TriggerOnChange(status); + } + + void OnChange() { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Motion, + .motion_status = GetStatus(), + }; + + if (status.motion_status.gyro.x.raw_value != last_axis_x_value || + status.motion_status.gyro.y.raw_value != last_axis_y_value || + status.motion_status.gyro.z.raw_value != last_axis_z_value) { + last_axis_x_value = status.motion_status.gyro.x.raw_value; + last_axis_y_value = status.motion_status.gyro.y.raw_value; + last_axis_z_value = status.motion_status.gyro.z.raw_value; + TriggerOnChange(status); + } + } + +private: + const PadIdentifier identifier; + const int axis_x; + const int axis_y; + const int axis_z; + const Common::Input::AnalogProperties properties_x; + const Common::Input::AnalogProperties properties_y; + const Common::Input::AnalogProperties properties_z; + int callback_key_x; + int callback_key_y; + int callback_key_z; + float last_axis_x_value; + float last_axis_y_value; + float last_axis_z_value; + InputEngine* input_engine; +}; + +class OutputFromIdentifier final : public Common::Input::OutputDevice { +public: + explicit OutputFromIdentifier(PadIdentifier identifier_, InputEngine* input_engine_) + : identifier(identifier_), input_engine(input_engine_) {} + + virtual void SetLED(Common::Input::LedStatus led_status) { + input_engine->SetLeds(identifier, led_status); + } + + virtual Common::Input::VibrationError SetVibration( + Common::Input::VibrationStatus vibration_status) { + return input_engine->SetRumble(identifier, vibration_status); + } + + virtual Common::Input::PollingError SetPollingMode(Common::Input::PollingMode polling_mode) { + return input_engine->SetPollingMode(identifier, polling_mode); + } + +private: + const PadIdentifier identifier; + InputEngine* input_engine; +}; + +std::unique_ptr InputFactory::CreateButtonDevice( + const Common::ParamPackage& params) { + const PadIdentifier identifier = { + .guid = Common::UUID{params.Get("guid", "")}, + .port = static_cast(params.Get("port", 0)), + .pad = static_cast(params.Get("pad", 0)), + }; + + const auto button_id = params.Get("button", 0); + const auto keyboard_key = params.Get("code", 0); + const auto toggle = params.Get("toggle", false); + const auto inverted = params.Get("inverted", false); + input_engine->PreSetController(identifier); + input_engine->PreSetButton(identifier, button_id); + input_engine->PreSetButton(identifier, keyboard_key); + if (keyboard_key != 0) { + return std::make_unique(identifier, keyboard_key, toggle, inverted, + input_engine.get()); + } + return std::make_unique(identifier, button_id, toggle, inverted, + input_engine.get()); +} + +std::unique_ptr InputFactory::CreateHatButtonDevice( + const Common::ParamPackage& params) { + const PadIdentifier identifier = { + .guid = Common::UUID{params.Get("guid", "")}, + .port = static_cast(params.Get("port", 0)), + .pad = static_cast(params.Get("pad", 0)), + }; + + const auto button_id = params.Get("hat", 0); + const auto direction = input_engine->GetHatButtonId(params.Get("direction", "")); + const auto toggle = params.Get("toggle", false); + const auto inverted = params.Get("inverted", false); + + input_engine->PreSetController(identifier); + input_engine->PreSetHatButton(identifier, button_id); + return std::make_unique(identifier, button_id, direction, toggle, inverted, + input_engine.get()); +} + +std::unique_ptr InputFactory::CreateStickDevice( + const Common::ParamPackage& params) { + const auto deadzone = std::clamp(params.Get("deadzone", 0.15f), 0.0f, 1.0f); + const auto range = std::clamp(params.Get("range", 1.0f), 0.25f, 1.50f); + const auto threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f); + const PadIdentifier identifier = { + .guid = Common::UUID{params.Get("guid", "")}, + .port = static_cast(params.Get("port", 0)), + .pad = static_cast(params.Get("pad", 0)), + }; + + const auto axis_x = params.Get("axis_x", 0); + const Common::Input::AnalogProperties properties_x = { + .deadzone = deadzone, + .range = range, + .threshold = threshold, + .offset = std::clamp(params.Get("offset_x", 0.0f), -1.0f, 1.0f), + .inverted = params.Get("invert_x", "+") == "-", + }; + + const auto axis_y = params.Get("axis_y", 1); + const Common::Input::AnalogProperties properties_y = { + .deadzone = deadzone, + .range = range, + .threshold = threshold, + .offset = std::clamp(params.Get("offset_y", 0.0f), -1.0f, 1.0f), + .inverted = params.Get("invert_y", "+") != "+", + }; + input_engine->PreSetController(identifier); + input_engine->PreSetAxis(identifier, axis_x); + input_engine->PreSetAxis(identifier, axis_y); + return std::make_unique(identifier, axis_x, axis_y, properties_x, properties_y, + input_engine.get()); +} + +std::unique_ptr InputFactory::CreateAnalogDevice( + const Common::ParamPackage& params) { + const PadIdentifier identifier = { + .guid = Common::UUID{params.Get("guid", "")}, + .port = static_cast(params.Get("port", 0)), + .pad = static_cast(params.Get("pad", 0)), + }; + + const auto axis = params.Get("axis", 0); + const Common::Input::AnalogProperties properties = { + .deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f), + .range = std::clamp(params.Get("range", 1.0f), 0.25f, 1.50f), + .threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f), + .offset = std::clamp(params.Get("offset", 0.0f), -1.0f, 1.0f), + .inverted = params.Get("invert", "+") == "-", + }; + input_engine->PreSetController(identifier); + input_engine->PreSetAxis(identifier, axis); + return std::make_unique(identifier, axis, properties, input_engine.get()); +} + +std::unique_ptr InputFactory::CreateTriggerDevice( + const Common::ParamPackage& params) { + const PadIdentifier identifier = { + .guid = Common::UUID{params.Get("guid", "")}, + .port = static_cast(params.Get("port", 0)), + .pad = static_cast(params.Get("pad", 0)), + }; + + const auto button = params.Get("button", 0); + const auto toggle = params.Get("toggle", false); + const auto inverted = params.Get("inverted", false); + + const auto axis = params.Get("axis", 0); + const Common::Input::AnalogProperties properties = { + .deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f), + .range = std::clamp(params.Get("range", 1.0f), 0.25f, 2.50f), + .threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f), + .offset = std::clamp(params.Get("offset", 0.0f), -1.0f, 1.0f), + .inverted = params.Get("invert", false) != 0, + }; + input_engine->PreSetController(identifier); + input_engine->PreSetAxis(identifier, axis); + input_engine->PreSetButton(identifier, button); + return std::make_unique(identifier, button, toggle, inverted, axis, + properties, input_engine.get()); +} + +std::unique_ptr InputFactory::CreateTouchDevice( + const Common::ParamPackage& params) { + const auto touch_id = params.Get("touch_id", 0); + const auto deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f); + const auto range = std::clamp(params.Get("range", 1.0f), 0.25f, 1.50f); + const auto threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f); + const PadIdentifier identifier = { + .guid = Common::UUID{params.Get("guid", "")}, + .port = static_cast(params.Get("port", 0)), + .pad = static_cast(params.Get("pad", 0)), + }; + + const auto button = params.Get("button", 0); + const auto toggle = params.Get("toggle", false); + const auto inverted = params.Get("inverted", false); + + const auto axis_x = params.Get("axis_x", 0); + const Common::Input::AnalogProperties properties_x = { + .deadzone = deadzone, + .range = range, + .threshold = threshold, + .offset = std::clamp(params.Get("offset_x", 0.0f), -1.0f, 1.0f), + .inverted = params.Get("invert_x", "+") == "-", + }; + + const auto axis_y = params.Get("axis_y", 1); + const Common::Input::AnalogProperties properties_y = { + .deadzone = deadzone, + .range = range, + .threshold = threshold, + .offset = std::clamp(params.Get("offset_y", 0.0f), -1.0f, 1.0f), + .inverted = params.Get("invert_y", false) != 0, + }; + input_engine->PreSetController(identifier); + input_engine->PreSetAxis(identifier, axis_x); + input_engine->PreSetAxis(identifier, axis_y); + input_engine->PreSetButton(identifier, button); + return std::make_unique(identifier, touch_id, button, toggle, inverted, axis_x, + axis_y, properties_x, properties_y, input_engine.get()); +} + +std::unique_ptr InputFactory::CreateBatteryDevice( + const Common::ParamPackage& params) { + const PadIdentifier identifier = { + .guid = Common::UUID{params.Get("guid", "")}, + .port = static_cast(params.Get("port", 0)), + .pad = static_cast(params.Get("pad", 0)), + }; + + input_engine->PreSetController(identifier); + return std::make_unique(identifier, input_engine.get()); +} + +std::unique_ptr InputFactory::CreateMotionDevice( + Common::ParamPackage params) { + const PadIdentifier identifier = { + .guid = Common::UUID{params.Get("guid", "")}, + .port = static_cast(params.Get("port", 0)), + .pad = static_cast(params.Get("pad", 0)), + }; + + if (params.Has("motion")) { + const auto motion_sensor = params.Get("motion", 0); + input_engine->PreSetController(identifier); + input_engine->PreSetMotion(identifier, motion_sensor); + return std::make_unique(identifier, motion_sensor, input_engine.get()); + } + + const auto deadzone = std::clamp(params.Get("deadzone", 0.15f), 0.0f, 1.0f); + const auto range = std::clamp(params.Get("range", 1.0f), 0.25f, 1.50f); + const auto threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f); + + const auto axis_x = params.Get("axis_x", 0); + const Common::Input::AnalogProperties properties_x = { + .deadzone = deadzone, + .range = range, + .threshold = threshold, + .offset = std::clamp(params.Get("offset_x", 0.0f), -1.0f, 1.0f), + .inverted = params.Get("invert_x", "+") == "-", + }; + + const auto axis_y = params.Get("axis_y", 1); + const Common::Input::AnalogProperties properties_y = { + .deadzone = deadzone, + .range = range, + .threshold = threshold, + .offset = std::clamp(params.Get("offset_y", 0.0f), -1.0f, 1.0f), + .inverted = params.Get("invert_y", "+") != "+", + }; + + const auto axis_z = params.Get("axis_z", 1); + const Common::Input::AnalogProperties properties_z = { + .deadzone = deadzone, + .range = range, + .threshold = threshold, + .offset = std::clamp(params.Get("offset_z", 0.0f), -1.0f, 1.0f), + .inverted = params.Get("invert_z", "+") != "+", + }; + input_engine->PreSetController(identifier); + input_engine->PreSetAxis(identifier, axis_x); + input_engine->PreSetAxis(identifier, axis_y); + input_engine->PreSetAxis(identifier, axis_z); + return std::make_unique(identifier, axis_x, axis_y, axis_z, properties_x, + properties_y, properties_z, input_engine.get()); +} + +InputFactory::InputFactory(std::shared_ptr input_engine_) + : input_engine(std::move(input_engine_)) {} + +std::unique_ptr InputFactory::Create( + const Common::ParamPackage& params) { + if (params.Has("battery")) { + return CreateBatteryDevice(params); + } + if (params.Has("button") && params.Has("axis")) { + return CreateTriggerDevice(params); + } + if (params.Has("button") && params.Has("axis_x") && params.Has("axis_y")) { + return CreateTouchDevice(params); + } + if (params.Has("button") || params.Has("code")) { + return CreateButtonDevice(params); + } + if (params.Has("hat")) { + return CreateHatButtonDevice(params); + } + if (params.Has("axis_x") && params.Has("axis_y") && params.Has("axis_z")) { + return CreateMotionDevice(params); + } + if (params.Has("motion")) { + return CreateMotionDevice(params); + } + if (params.Has("axis_x") && params.Has("axis_y")) { + return CreateStickDevice(params); + } + if (params.Has("axis")) { + return CreateAnalogDevice(params); + } + LOG_ERROR(Input, "Invalid parameters given"); + return std::make_unique(); +} + +OutputFactory::OutputFactory(std::shared_ptr input_engine_) + : input_engine(std::move(input_engine_)) {} + +std::unique_ptr OutputFactory::Create( + const Common::ParamPackage& params) { + const PadIdentifier identifier = { + .guid = Common::UUID{params.Get("guid", "")}, + .port = static_cast(params.Get("port", 0)), + .pad = static_cast(params.Get("pad", 0)), + }; + + input_engine->PreSetController(identifier); + return std::make_unique(identifier, input_engine.get()); +} + +} // namespace InputCommon diff --git a/src/input_common/input_poller.h b/src/input_common/input_poller.h new file mode 100644 index 0000000000..573f09fded --- /dev/null +++ b/src/input_common/input_poller.h @@ -0,0 +1,217 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included + +#pragma once + +namespace Input { +class InputDevice; + +template +class Factory; +}; // namespace Input + +namespace InputCommon { +class InputEngine; +/** + * An Input factory. It receives input events and forward them to all input devices it created. + */ + +class OutputFactory final : public Common::Input::Factory { +public: + explicit OutputFactory(std::shared_ptr input_engine_); + + /** + * Creates an output device from the parameters given. + * @param params contains parameters for creating the device: + * @param - "guid": text string for identifing controllers + * @param - "port": port of the connected device + * @param - "pad": slot of the connected controller + * @return an unique ouput device with the parameters specified + */ + std::unique_ptr Create( + const Common::ParamPackage& params) override; + +private: + std::shared_ptr input_engine; +}; + +class InputFactory final : public Common::Input::Factory { +public: + explicit InputFactory(std::shared_ptr input_engine_); + + /** + * Creates an input device from the parameters given. Identifies the type of input to be + * returned if it contains the following parameters: + * - button: Contains "button" or "code" + * - hat_button: Contains "hat" + * - analog: Contains "axis" + * - trigger: Contains "button" and "axis" + * - stick: Contains "axis_x" and "axis_y" + * - motion: Contains "axis_x", "axis_y" and "axis_z" + * - motion: Contains "motion" + * - touch: Contains "button", "axis_x" and "axis_y" + * - battery: Contains "battery" + * - output: Contains "output" + * @param params contains parameters for creating the device: + * @param - "code": the code of the keyboard key to bind with the input + * @param - "button": same as "code" but for controller buttons + * @param - "hat": similar as "button" but it's a group of hat buttons from SDL + * @param - "axis": the axis number of the axis to bind with the input + * @param - "motion": the motion number of the motion to bind with the input + * @param - "axis_x": same as axis but specifing horizontal direction + * @param - "axis_y": same as axis but specifing vertical direction + * @param - "axis_z": same as axis but specifing forward direction + * @param - "battery": Only used as a placeholder to set the input type + * @return an unique input device with the parameters specified + */ + std::unique_ptr Create(const Common::ParamPackage& params) override; + +private: + /** + * Creates a button device from the parameters given. + * @param params contains parameters for creating the device: + * @param - "code": the code of the keyboard key to bind with the input + * @param - "button": same as "code" but for controller buttons + * @param - "toggle": press once to enable, press again to disable + * @param - "inverted": inverts the output of the button + * @param - "guid": text string for identifing controllers + * @param - "port": port of the connected device + * @param - "pad": slot of the connected controller + * @return an unique input device with the parameters specified + */ + std::unique_ptr CreateButtonDevice( + const Common::ParamPackage& params); + + /** + * Creates a hat button device from the parameters given. + * @param params contains parameters for creating the device: + * @param - "button": the controller hat id to bind with the input + * @param - "direction": the direction id to be detected + * @param - "toggle": press once to enable, press again to disable + * @param - "inverted": inverts the output of the button + * @param - "guid": text string for identifing controllers + * @param - "port": port of the connected device + * @param - "pad": slot of the connected controller + * @return an unique input device with the parameters specified + */ + std::unique_ptr CreateHatButtonDevice( + const Common::ParamPackage& params); + + /** + * Creates a stick device from the parameters given. + * @param params contains parameters for creating the device: + * @param - "axis_x": the controller horizontal axis id to bind with the input + * @param - "axis_y": the controller vertical axis id to bind with the input + * @param - "deadzone": the mimimum required value to be detected + * @param - "range": the maximum value required to reach 100% + * @param - "threshold": the mimimum required value to considered pressed + * @param - "offset_x": the amount of offset in the x axis + * @param - "offset_y": the amount of offset in the y axis + * @param - "invert_x": inverts the sign of the horizontal axis + * @param - "invert_y": inverts the sign of the vertical axis + * @param - "guid": text string for identifing controllers + * @param - "port": port of the connected device + * @param - "pad": slot of the connected controller + * @return an unique input device with the parameters specified + */ + std::unique_ptr CreateStickDevice( + const Common::ParamPackage& params); + + /** + * Creates an analog device from the parameters given. + * @param params contains parameters for creating the device: + * @param - "axis": the controller axis id to bind with the input + * @param - "deadzone": the mimimum required value to be detected + * @param - "range": the maximum value required to reach 100% + * @param - "threshold": the mimimum required value to considered pressed + * @param - "offset": the amount of offset in the axis + * @param - "invert": inverts the sign of the axis + * @param - "guid": text string for identifing controllers + * @param - "port": port of the connected device + * @param - "pad": slot of the connected controller + * @return an unique input device with the parameters specified + */ + std::unique_ptr CreateAnalogDevice( + const Common::ParamPackage& params); + + /** + * Creates a trigger device from the parameters given. + * @param params contains parameters for creating the device: + * @param - "button": the controller hat id to bind with the input + * @param - "direction": the direction id to be detected + * @param - "toggle": press once to enable, press again to disable + * @param - "inverted": inverts the output of the button + * @param - "axis": the controller axis id to bind with the input + * @param - "deadzone": the mimimum required value to be detected + * @param - "range": the maximum value required to reach 100% + * @param - "threshold": the mimimum required value to considered pressed + * @param - "offset": the amount of offset in the axis + * @param - "invert": inverts the sign of the axis + * @param - "guid": text string for identifing controllers + * @param - "port": port of the connected device + * @param - "pad": slot of the connected controller + * @return an unique input device with the parameters specified + */ + std::unique_ptr CreateTriggerDevice( + const Common::ParamPackage& params); + + /** + * Creates a touch device from the parameters given. + * @param params contains parameters for creating the device: + * @param - "button": the controller hat id to bind with the input + * @param - "direction": the direction id to be detected + * @param - "toggle": press once to enable, press again to disable + * @param - "inverted": inverts the output of the button + * @param - "axis_x": the controller horizontal axis id to bind with the input + * @param - "axis_y": the controller vertical axis id to bind with the input + * @param - "deadzone": the mimimum required value to be detected + * @param - "range": the maximum value required to reach 100% + * @param - "threshold": the mimimum required value to considered pressed + * @param - "offset_x": the amount of offset in the x axis + * @param - "offset_y": the amount of offset in the y axis + * @param - "invert_x": inverts the sign of the horizontal axis + * @param - "invert_y": inverts the sign of the vertical axis + * @param - "guid": text string for identifing controllers + * @param - "port": port of the connected device + * @param - "pad": slot of the connected controller + * @return an unique input device with the parameters specified + */ + std::unique_ptr CreateTouchDevice( + const Common::ParamPackage& params); + + /** + * Creates a battery device from the parameters given. + * @param params contains parameters for creating the device: + * @param - "guid": text string for identifing controllers + * @param - "port": port of the connected device + * @param - "pad": slot of the connected controller + * @return an unique input device with the parameters specified + */ + std::unique_ptr CreateBatteryDevice( + const Common::ParamPackage& params); + + /** + * Creates a motion device from the parameters given. + * @param params contains parameters for creating the device: + * @param - "axis_x": the controller horizontal axis id to bind with the input + * @param - "axis_y": the controller vertical axis id to bind with the input + * @param - "axis_z": the controller fordward axis id to bind with the input + * @param - "deadzone": the mimimum required value to be detected + * @param - "range": the maximum value required to reach 100% + * @param - "offset_x": the amount of offset in the x axis + * @param - "offset_y": the amount of offset in the y axis + * @param - "offset_z": the amount of offset in the z axis + * @param - "invert_x": inverts the sign of the horizontal axis + * @param - "invert_y": inverts the sign of the vertical axis + * @param - "invert_z": inverts the sign of the fordward axis + * @param - "guid": text string for identifing controllers + * @param - "port": port of the connected device + * @param - "pad": slot of the connected controller + * @return an unique input device with the parameters specified + */ + std::unique_ptr CreateMotionDevice(Common::ParamPackage params); + + std::shared_ptr input_engine; +}; +} // namespace InputCommon diff --git a/src/input_common/keyboard.cpp b/src/input_common/keyboard.cpp deleted file mode 100644 index 8261e76fd7..0000000000 --- a/src/input_common/keyboard.cpp +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright 2017 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include -#include -#include -#include "input_common/keyboard.h" - -namespace InputCommon { - -class KeyButton final : public Input::ButtonDevice { -public: - explicit KeyButton(std::shared_ptr key_button_list_, bool toggle_) - : key_button_list(std::move(key_button_list_)), toggle(toggle_) {} - - ~KeyButton() override; - - bool GetStatus() const override { - if (toggle) { - return toggled_status.load(std::memory_order_relaxed); - } - return status.load(); - } - - void ToggleButton() { - if (lock) { - return; - } - lock = true; - const bool old_toggle_status = toggled_status.load(); - toggled_status.store(!old_toggle_status); - } - - void UnlockButton() { - lock = false; - } - - friend class KeyButtonList; - -private: - std::shared_ptr key_button_list; - std::atomic status{false}; - std::atomic toggled_status{false}; - bool lock{false}; - const bool toggle; -}; - -struct KeyButtonPair { - int key_code; - KeyButton* key_button; -}; - -class KeyButtonList { -public: - void AddKeyButton(int key_code, KeyButton* key_button) { - std::lock_guard guard{mutex}; - list.push_back(KeyButtonPair{key_code, key_button}); - } - - void RemoveKeyButton(const KeyButton* key_button) { - std::lock_guard guard{mutex}; - list.remove_if( - [key_button](const KeyButtonPair& pair) { return pair.key_button == key_button; }); - } - - void ChangeKeyStatus(int key_code, bool pressed) { - std::lock_guard guard{mutex}; - for (const KeyButtonPair& pair : list) { - if (pair.key_code == key_code) { - pair.key_button->status.store(pressed); - if (pressed) { - pair.key_button->ToggleButton(); - } else { - pair.key_button->UnlockButton(); - } - pair.key_button->TriggerOnChange(); - } - } - } - - void ChangeAllKeyStatus(bool pressed) { - std::lock_guard guard{mutex}; - for (const KeyButtonPair& pair : list) { - pair.key_button->status.store(pressed); - } - } - -private: - std::mutex mutex; - std::list list; -}; - -Keyboard::Keyboard() : key_button_list{std::make_shared()} {} - -KeyButton::~KeyButton() { - key_button_list->RemoveKeyButton(this); -} - -std::unique_ptr Keyboard::Create(const Common::ParamPackage& params) { - const int key_code = params.Get("code", 0); - const bool toggle = params.Get("toggle", false); - std::unique_ptr button = std::make_unique(key_button_list, toggle); - key_button_list->AddKeyButton(key_code, button.get()); - return button; -} - -void Keyboard::PressKey(int key_code) { - key_button_list->ChangeKeyStatus(key_code, true); -} - -void Keyboard::ReleaseKey(int key_code) { - key_button_list->ChangeKeyStatus(key_code, false); -} - -void Keyboard::ReleaseAllKeys() { - key_button_list->ChangeAllKeyStatus(false); -} - -} // namespace InputCommon diff --git a/src/input_common/keyboard.h b/src/input_common/keyboard.h deleted file mode 100644 index 8619504729..0000000000 --- a/src/input_common/keyboard.h +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2017 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include "core/frontend/input.h" - -namespace InputCommon { - -class KeyButtonList; - -/** - * A button device factory representing a keyboard. It receives keyboard events and forward them - * to all button devices it created. - */ -class Keyboard final : public Input::Factory { -public: - Keyboard(); - - /** - * Creates a button device from a keyboard key - * @param params contains parameters for creating the device: - * - "code": the code of the key to bind with the button - */ - std::unique_ptr Create(const Common::ParamPackage& params) override; - - /** - * Sets the status of all buttons bound with the key to pressed - * @param key_code the code of the key to press - */ - void PressKey(int key_code); - - /** - * Sets the status of all buttons bound with the key to released - * @param key_code the code of the key to release - */ - void ReleaseKey(int key_code); - - void ReleaseAllKeys(); - -private: - std::shared_ptr key_button_list; -}; - -} // namespace InputCommon diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index f3907c65ac..940744c5f0 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -4,146 +4,173 @@ #include #include +#include "common/input.h" #include "common/param_package.h" -#include "common/settings.h" -#include "input_common/analog_from_button.h" -#include "input_common/gcadapter/gc_adapter.h" -#include "input_common/gcadapter/gc_poller.h" -#include "input_common/keyboard.h" +#include "input_common/drivers/gc_adapter.h" +#include "input_common/drivers/keyboard.h" +#include "input_common/drivers/mouse.h" +#include "input_common/drivers/tas_input.h" +#include "input_common/drivers/touch_screen.h" +#include "input_common/drivers/udp_client.h" +#include "input_common/helpers/stick_from_buttons.h" +#include "input_common/helpers/touch_from_buttons.h" +#include "input_common/input_engine.h" +#include "input_common/input_mapping.h" +#include "input_common/input_poller.h" #include "input_common/main.h" -#include "input_common/motion_from_button.h" -#include "input_common/mouse/mouse_input.h" -#include "input_common/mouse/mouse_poller.h" -#include "input_common/tas/tas_input.h" -#include "input_common/tas/tas_poller.h" -#include "input_common/touch_from_button.h" -#include "input_common/udp/client.h" -#include "input_common/udp/udp.h" #ifdef HAVE_SDL2 -#include "input_common/sdl/sdl.h" +#include "input_common/drivers/sdl_driver.h" #endif namespace InputCommon { struct InputSubsystem::Impl { void Initialize() { - gcadapter = std::make_shared(); - gcbuttons = std::make_shared(gcadapter); - Input::RegisterFactory("gcpad", gcbuttons); - gcanalog = std::make_shared(gcadapter); - Input::RegisterFactory("gcpad", gcanalog); - gcvibration = std::make_shared(gcadapter); - Input::RegisterFactory("gcpad", gcvibration); + mapping_factory = std::make_shared(); + MappingCallback mapping_callback{[this](MappingData data) { RegisterInput(data); }}; - keyboard = std::make_shared(); - Input::RegisterFactory("keyboard", keyboard); - Input::RegisterFactory("analog_from_button", - std::make_shared()); - Input::RegisterFactory("keyboard", - std::make_shared()); - Input::RegisterFactory("touch_from_button", - std::make_shared()); + keyboard = std::make_shared("keyboard"); + keyboard->SetMappingCallback(mapping_callback); + keyboard_factory = std::make_shared(keyboard); + keyboard_output_factory = std::make_shared(keyboard); + Common::Input::RegisterFactory(keyboard->GetEngineName(), + keyboard_factory); + Common::Input::RegisterFactory(keyboard->GetEngineName(), + keyboard_output_factory); + + mouse = std::make_shared("mouse"); + mouse->SetMappingCallback(mapping_callback); + mouse_factory = std::make_shared(mouse); + mouse_output_factory = std::make_shared(mouse); + Common::Input::RegisterFactory(mouse->GetEngineName(), + mouse_factory); + Common::Input::RegisterFactory(mouse->GetEngineName(), + mouse_output_factory); + + touch_screen = std::make_shared("touch"); + touch_screen_factory = std::make_shared(touch_screen); + Common::Input::RegisterFactory(touch_screen->GetEngineName(), + touch_screen_factory); + + gcadapter = std::make_shared("gcpad"); + gcadapter->SetMappingCallback(mapping_callback); + gcadapter_input_factory = std::make_shared(gcadapter); + gcadapter_output_factory = std::make_shared(gcadapter); + Common::Input::RegisterFactory(gcadapter->GetEngineName(), + gcadapter_input_factory); + Common::Input::RegisterFactory(gcadapter->GetEngineName(), + gcadapter_output_factory); + + udp_client = std::make_shared("cemuhookudp"); + udp_client->SetMappingCallback(mapping_callback); + udp_client_input_factory = std::make_shared(udp_client); + udp_client_output_factory = std::make_shared(udp_client); + Common::Input::RegisterFactory(udp_client->GetEngineName(), + udp_client_input_factory); + Common::Input::RegisterFactory(udp_client->GetEngineName(), + udp_client_output_factory); + + tas_input = std::make_shared("tas"); + tas_input->SetMappingCallback(mapping_callback); + tas_input_factory = std::make_shared(tas_input); + tas_output_factory = std::make_shared(tas_input); + Common::Input::RegisterFactory(tas_input->GetEngineName(), + tas_input_factory); + Common::Input::RegisterFactory(tas_input->GetEngineName(), + tas_output_factory); #ifdef HAVE_SDL2 - sdl = SDL::Init(); + sdl = std::make_shared("sdl"); + sdl->SetMappingCallback(mapping_callback); + sdl_input_factory = std::make_shared(sdl); + sdl_output_factory = std::make_shared(sdl); + Common::Input::RegisterFactory(sdl->GetEngineName(), + sdl_input_factory); + Common::Input::RegisterFactory(sdl->GetEngineName(), + sdl_output_factory); #endif - udp = std::make_shared(); - udpmotion = std::make_shared(udp); - Input::RegisterFactory("cemuhookudp", udpmotion); - udptouch = std::make_shared(udp); - Input::RegisterFactory("cemuhookudp", udptouch); - - mouse = std::make_shared(); - mousebuttons = std::make_shared(mouse); - Input::RegisterFactory("mouse", mousebuttons); - mouseanalog = std::make_shared(mouse); - Input::RegisterFactory("mouse", mouseanalog); - mousemotion = std::make_shared(mouse); - Input::RegisterFactory("mouse", mousemotion); - mousetouch = std::make_shared(mouse); - Input::RegisterFactory("mouse", mousetouch); - - tas = std::make_shared(); - tasbuttons = std::make_shared(tas); - Input::RegisterFactory("tas", tasbuttons); - tasanalog = std::make_shared(tas); - Input::RegisterFactory("tas", tasanalog); + Common::Input::RegisterFactory( + "touch_from_button", std::make_shared()); + Common::Input::RegisterFactory( + "analog_from_button", std::make_shared()); } void Shutdown() { - Input::UnregisterFactory("keyboard"); - Input::UnregisterFactory("keyboard"); + Common::Input::UnregisterFactory(keyboard->GetEngineName()); + Common::Input::UnregisterFactory(keyboard->GetEngineName()); keyboard.reset(); - Input::UnregisterFactory("analog_from_button"); - Input::UnregisterFactory("touch_from_button"); + + Common::Input::UnregisterFactory(mouse->GetEngineName()); + Common::Input::UnregisterFactory(mouse->GetEngineName()); + mouse.reset(); + + Common::Input::UnregisterFactory(touch_screen->GetEngineName()); + touch_screen.reset(); + + Common::Input::UnregisterFactory(gcadapter->GetEngineName()); + Common::Input::UnregisterFactory(gcadapter->GetEngineName()); + gcadapter.reset(); + + Common::Input::UnregisterFactory(udp_client->GetEngineName()); + Common::Input::UnregisterFactory(udp_client->GetEngineName()); + udp_client.reset(); + + Common::Input::UnregisterFactory(tas_input->GetEngineName()); + Common::Input::UnregisterFactory(tas_input->GetEngineName()); + tas_input.reset(); + #ifdef HAVE_SDL2 + Common::Input::UnregisterFactory(sdl->GetEngineName()); + Common::Input::UnregisterFactory(sdl->GetEngineName()); sdl.reset(); #endif - Input::UnregisterFactory("gcpad"); - Input::UnregisterFactory("gcpad"); - Input::UnregisterFactory("gcpad"); - gcbuttons.reset(); - gcanalog.reset(); - gcvibration.reset(); - - Input::UnregisterFactory("cemuhookudp"); - Input::UnregisterFactory("cemuhookudp"); - - udpmotion.reset(); - udptouch.reset(); - - Input::UnregisterFactory("mouse"); - Input::UnregisterFactory("mouse"); - Input::UnregisterFactory("mouse"); - Input::UnregisterFactory("mouse"); - - mousebuttons.reset(); - mouseanalog.reset(); - mousemotion.reset(); - mousetouch.reset(); - - Input::UnregisterFactory("tas"); - Input::UnregisterFactory("tas"); - - tasbuttons.reset(); - tasanalog.reset(); + Common::Input::UnregisterFactory("touch_from_button"); + Common::Input::UnregisterFactory("analog_from_button"); } [[nodiscard]] std::vector GetInputDevices() const { std::vector devices = { - Common::ParamPackage{{"display", "Any"}, {"class", "any"}}, - Common::ParamPackage{{"display", "Keyboard/Mouse"}, {"class", "keyboard"}}, + Common::ParamPackage{{"display", "Any"}, {"engine", "any"}}, }; - if (Settings::values.tas_enable) { - devices.emplace_back( - Common::ParamPackage{{"display", "TAS Controller"}, {"class", "tas"}}); - } + + auto keyboard_devices = keyboard->GetInputDevices(); + devices.insert(devices.end(), keyboard_devices.begin(), keyboard_devices.end()); + auto mouse_devices = mouse->GetInputDevices(); + devices.insert(devices.end(), mouse_devices.begin(), mouse_devices.end()); + auto gcadapter_devices = gcadapter->GetInputDevices(); + devices.insert(devices.end(), gcadapter_devices.begin(), gcadapter_devices.end()); + auto udp_devices = udp_client->GetInputDevices(); + devices.insert(devices.end(), udp_devices.begin(), udp_devices.end()); #ifdef HAVE_SDL2 auto sdl_devices = sdl->GetInputDevices(); devices.insert(devices.end(), sdl_devices.begin(), sdl_devices.end()); #endif - auto udp_devices = udp->GetInputDevices(); - devices.insert(devices.end(), udp_devices.begin(), udp_devices.end()); - auto gcpad_devices = gcadapter->GetInputDevices(); - devices.insert(devices.end(), gcpad_devices.begin(), gcpad_devices.end()); + return devices; } [[nodiscard]] AnalogMapping GetAnalogMappingForDevice( const Common::ParamPackage& params) const { - if (!params.Has("class") || params.Get("class", "") == "any") { + if (!params.Has("engine") || params.Get("engine", "") == "any") { return {}; } - if (params.Get("class", "") == "gcpad") { + const std::string engine = params.Get("engine", ""); + if (engine == mouse->GetEngineName()) { + return mouse->GetAnalogMappingForDevice(params); + } + if (engine == gcadapter->GetEngineName()) { return gcadapter->GetAnalogMappingForDevice(params); } - if (params.Get("class", "") == "tas") { - return tas->GetAnalogMappingForDevice(params); + if (engine == udp_client->GetEngineName()) { + return udp_client->GetAnalogMappingForDevice(params); + } + if (engine == tas_input->GetEngineName()) { + return tas_input->GetAnalogMappingForDevice(params); } #ifdef HAVE_SDL2 - if (params.Get("class", "") == "sdl") { + if (engine == sdl->GetEngineName()) { return sdl->GetAnalogMappingForDevice(params); } #endif @@ -152,17 +179,21 @@ struct InputSubsystem::Impl { [[nodiscard]] ButtonMapping GetButtonMappingForDevice( const Common::ParamPackage& params) const { - if (!params.Has("class") || params.Get("class", "") == "any") { + if (!params.Has("engine") || params.Get("engine", "") == "any") { return {}; } - if (params.Get("class", "") == "gcpad") { + const std::string engine = params.Get("engine", ""); + if (engine == gcadapter->GetEngineName()) { return gcadapter->GetButtonMappingForDevice(params); } - if (params.Get("class", "") == "tas") { - return tas->GetButtonMappingForDevice(params); + if (engine == udp_client->GetEngineName()) { + return udp_client->GetButtonMappingForDevice(params); + } + if (engine == tas_input->GetEngineName()) { + return tas_input->GetButtonMappingForDevice(params); } #ifdef HAVE_SDL2 - if (params.Get("class", "") == "sdl") { + if (engine == sdl->GetEngineName()) { return sdl->GetButtonMappingForDevice(params); } #endif @@ -171,40 +202,119 @@ struct InputSubsystem::Impl { [[nodiscard]] MotionMapping GetMotionMappingForDevice( const Common::ParamPackage& params) const { - if (!params.Has("class") || params.Get("class", "") == "any") { + if (!params.Has("engine") || params.Get("engine", "") == "any") { return {}; } - if (params.Get("class", "") == "cemuhookudp") { - // TODO return the correct motion device - return {}; + const std::string engine = params.Get("engine", ""); + if (engine == udp_client->GetEngineName()) { + return udp_client->GetMotionMappingForDevice(params); } #ifdef HAVE_SDL2 - if (params.Get("class", "") == "sdl") { + if (engine == sdl->GetEngineName()) { return sdl->GetMotionMappingForDevice(params); } #endif return {}; } - std::shared_ptr keyboard; + Common::Input::ButtonNames GetButtonName(const Common::ParamPackage& params) const { + if (!params.Has("engine") || params.Get("engine", "") == "any") { + return Common::Input::ButtonNames::Undefined; + } + const std::string engine = params.Get("engine", ""); + if (engine == mouse->GetEngineName()) { + return mouse->GetUIName(params); + } + if (engine == gcadapter->GetEngineName()) { + return gcadapter->GetUIName(params); + } + if (engine == udp_client->GetEngineName()) { + return udp_client->GetUIName(params); + } + if (engine == tas_input->GetEngineName()) { + return tas_input->GetUIName(params); + } #ifdef HAVE_SDL2 - std::unique_ptr sdl; + if (engine == sdl->GetEngineName()) { + return sdl->GetUIName(params); + } +#endif + return Common::Input::ButtonNames::Invalid; + } + + bool IsController(const Common::ParamPackage& params) { + const std::string engine = params.Get("engine", ""); + if (engine == mouse->GetEngineName()) { + return true; + } + if (engine == gcadapter->GetEngineName()) { + return true; + } + if (engine == udp_client->GetEngineName()) { + return true; + } + if (engine == tas_input->GetEngineName()) { + return true; + } +#ifdef HAVE_SDL2 + if (engine == sdl->GetEngineName()) { + return true; + } +#endif + return false; + } + + void BeginConfiguration() { + keyboard->BeginConfiguration(); + mouse->BeginConfiguration(); + gcadapter->BeginConfiguration(); + udp_client->BeginConfiguration(); +#ifdef HAVE_SDL2 + sdl->BeginConfiguration(); +#endif + } + + void EndConfiguration() { + keyboard->EndConfiguration(); + mouse->EndConfiguration(); + gcadapter->EndConfiguration(); + udp_client->EndConfiguration(); +#ifdef HAVE_SDL2 + sdl->EndConfiguration(); +#endif + } + + void RegisterInput(MappingData data) { + mapping_factory->RegisterInput(data); + } + + std::shared_ptr mapping_factory; + + std::shared_ptr keyboard; + std::shared_ptr mouse; + std::shared_ptr gcadapter; + std::shared_ptr touch_screen; + std::shared_ptr tas_input; + std::shared_ptr udp_client; + + std::shared_ptr keyboard_factory; + std::shared_ptr mouse_factory; + std::shared_ptr gcadapter_input_factory; + std::shared_ptr touch_screen_factory; + std::shared_ptr udp_client_input_factory; + std::shared_ptr tas_input_factory; + + std::shared_ptr keyboard_output_factory; + std::shared_ptr mouse_output_factory; + std::shared_ptr gcadapter_output_factory; + std::shared_ptr udp_client_output_factory; + std::shared_ptr tas_output_factory; + +#ifdef HAVE_SDL2 + std::shared_ptr sdl; + std::shared_ptr sdl_input_factory; + std::shared_ptr sdl_output_factory; #endif - std::shared_ptr gcbuttons; - std::shared_ptr gcanalog; - std::shared_ptr gcvibration; - std::shared_ptr udpmotion; - std::shared_ptr udptouch; - std::shared_ptr mousebuttons; - std::shared_ptr mouseanalog; - std::shared_ptr mousemotion; - std::shared_ptr mousetouch; - std::shared_ptr tasbuttons; - std::shared_ptr tasanalog; - std::shared_ptr udp; - std::shared_ptr gcadapter; - std::shared_ptr mouse; - std::shared_ptr tas; }; InputSubsystem::InputSubsystem() : impl{std::make_unique()} {} @@ -227,20 +337,28 @@ const Keyboard* InputSubsystem::GetKeyboard() const { return impl->keyboard.get(); } -MouseInput::Mouse* InputSubsystem::GetMouse() { +Mouse* InputSubsystem::GetMouse() { return impl->mouse.get(); } -const MouseInput::Mouse* InputSubsystem::GetMouse() const { +const Mouse* InputSubsystem::GetMouse() const { return impl->mouse.get(); } +TouchScreen* InputSubsystem::GetTouchScreen() { + return impl->touch_screen.get(); +} + +const TouchScreen* InputSubsystem::GetTouchScreen() const { + return impl->touch_screen.get(); +} + TasInput::Tas* InputSubsystem::GetTas() { - return impl->tas.get(); + return impl->tas_input.get(); } const TasInput::Tas* InputSubsystem::GetTas() const { - return impl->tas.get(); + return impl->tas_input.get(); } std::vector InputSubsystem::GetInputDevices() const { @@ -259,100 +377,30 @@ MotionMapping InputSubsystem::GetMotionMappingForDevice(const Common::ParamPacka return impl->GetMotionMappingForDevice(device); } -GCAnalogFactory* InputSubsystem::GetGCAnalogs() { - return impl->gcanalog.get(); +Common::Input::ButtonNames InputSubsystem::GetButtonName(const Common::ParamPackage& params) const { + return impl->GetButtonName(params); } -const GCAnalogFactory* InputSubsystem::GetGCAnalogs() const { - return impl->gcanalog.get(); -} - -GCButtonFactory* InputSubsystem::GetGCButtons() { - return impl->gcbuttons.get(); -} - -const GCButtonFactory* InputSubsystem::GetGCButtons() const { - return impl->gcbuttons.get(); -} - -UDPMotionFactory* InputSubsystem::GetUDPMotions() { - return impl->udpmotion.get(); -} - -const UDPMotionFactory* InputSubsystem::GetUDPMotions() const { - return impl->udpmotion.get(); -} - -UDPTouchFactory* InputSubsystem::GetUDPTouch() { - return impl->udptouch.get(); -} - -const UDPTouchFactory* InputSubsystem::GetUDPTouch() const { - return impl->udptouch.get(); -} - -MouseButtonFactory* InputSubsystem::GetMouseButtons() { - return impl->mousebuttons.get(); -} - -const MouseButtonFactory* InputSubsystem::GetMouseButtons() const { - return impl->mousebuttons.get(); -} - -MouseAnalogFactory* InputSubsystem::GetMouseAnalogs() { - return impl->mouseanalog.get(); -} - -const MouseAnalogFactory* InputSubsystem::GetMouseAnalogs() const { - return impl->mouseanalog.get(); -} - -MouseMotionFactory* InputSubsystem::GetMouseMotions() { - return impl->mousemotion.get(); -} - -const MouseMotionFactory* InputSubsystem::GetMouseMotions() const { - return impl->mousemotion.get(); -} - -MouseTouchFactory* InputSubsystem::GetMouseTouch() { - return impl->mousetouch.get(); -} - -const MouseTouchFactory* InputSubsystem::GetMouseTouch() const { - return impl->mousetouch.get(); -} - -TasButtonFactory* InputSubsystem::GetTasButtons() { - return impl->tasbuttons.get(); -} - -const TasButtonFactory* InputSubsystem::GetTasButtons() const { - return impl->tasbuttons.get(); -} - -TasAnalogFactory* InputSubsystem::GetTasAnalogs() { - return impl->tasanalog.get(); -} - -const TasAnalogFactory* InputSubsystem::GetTasAnalogs() const { - return impl->tasanalog.get(); +bool InputSubsystem::IsController(const Common::ParamPackage& params) const { + return impl->IsController(params); } void InputSubsystem::ReloadInputDevices() { - if (!impl->udp) { - return; - } - impl->udp->ReloadSockets(); + impl->udp_client.get()->ReloadSockets(); } -std::vector> InputSubsystem::GetPollers( - [[maybe_unused]] Polling::DeviceType type) const { -#ifdef HAVE_SDL2 - return impl->sdl->GetPollers(type); -#else - return {}; -#endif +void InputSubsystem::BeginMapping(Polling::InputType type) { + impl->BeginConfiguration(); + impl->mapping_factory->BeginMapping(type); +} + +const Common::ParamPackage InputSubsystem::GetNextInput() const { + return impl->mapping_factory->GetNextInput(); +} + +void InputSubsystem::StopMapping() const { + impl->EndConfiguration(); + impl->mapping_factory->StopMapping(); } std::string GenerateKeyboardParam(int key_code) { diff --git a/src/input_common/main.h b/src/input_common/main.h index 6390d3f094..c6f97f6918 100644 --- a/src/input_common/main.h +++ b/src/input_common/main.h @@ -13,6 +13,10 @@ namespace Common { class ParamPackage; } +namespace Common::Input { +enum class ButtonNames; +} + namespace Settings::NativeAnalog { enum Values : int; } @@ -25,56 +29,26 @@ namespace Settings::NativeMotion { enum Values : int; } -namespace MouseInput { +namespace InputCommon { +class Keyboard; class Mouse; -} +class TouchScreen; +struct MappingData; +} // namespace InputCommon -namespace TasInput { +namespace InputCommon::TasInput { class Tas; -} +} // namespace InputCommon::TasInput namespace InputCommon { namespace Polling { - -enum class DeviceType { Button, AnalogPreferred, Motion }; - -/** - * A class that can be used to get inputs from an input device like controllers without having to - * poll the device's status yourself - */ -class DevicePoller { -public: - virtual ~DevicePoller() = default; - /// Setup and start polling for inputs, should be called before GetNextInput - /// If a device_id is provided, events should be filtered to only include events from this - /// device id - virtual void Start(const std::string& device_id = "") = 0; - /// Stop polling - virtual void Stop() = 0; - /** - * Every call to this function returns the next input recorded since calling Start - * @return A ParamPackage of the recorded input, which can be used to create an InputDevice. - * If there has been no input, the package is empty - */ - virtual Common::ParamPackage GetNextInput() = 0; -}; +/// Type of input desired for mapping purposes +enum class InputType { None, Button, Stick, Motion, Touch }; } // namespace Polling -class GCAnalogFactory; -class GCButtonFactory; -class UDPMotionFactory; -class UDPTouchFactory; -class MouseButtonFactory; -class MouseAnalogFactory; -class MouseMotionFactory; -class MouseTouchFactory; -class TasButtonFactory; -class TasAnalogFactory; -class Keyboard; - /** * Given a ParamPackage for a Device returned from `GetInputDevices`, attempt to get the default - * mapping for the device. This is currently only implemented for the SDL backend devices. + * mapping for the device. */ using AnalogMapping = std::unordered_map; using ButtonMapping = std::unordered_map; @@ -104,20 +78,27 @@ public: [[nodiscard]] const Keyboard* GetKeyboard() const; /// Retrieves the underlying mouse device. - [[nodiscard]] MouseInput::Mouse* GetMouse(); + [[nodiscard]] Mouse* GetMouse(); /// Retrieves the underlying mouse device. - [[nodiscard]] const MouseInput::Mouse* GetMouse() const; + [[nodiscard]] const Mouse* GetMouse() const; - /// Retrieves the underlying tas device. + /// Retrieves the underlying touch screen device. + [[nodiscard]] TouchScreen* GetTouchScreen(); + + /// Retrieves the underlying touch screen device. + [[nodiscard]] const TouchScreen* GetTouchScreen() const; + + /// Retrieves the underlying tas input device. [[nodiscard]] TasInput::Tas* GetTas(); - /// Retrieves the underlying tas device. + /// Retrieves the underlying tas input device. [[nodiscard]] const TasInput::Tas* GetTas() const; + /** * Returns all available input devices that this Factory can create a new device with. - * Each returned ParamPackage should have a `display` field used for display, a class field for - * backends to determine if this backend is meant to service the request and any other + * Each returned ParamPackage should have a `display` field used for display, a `engine` field + * for backends to determine if this backend is meant to service the request and any other * information needed to identify this in the backend later. */ [[nodiscard]] std::vector GetInputDevices() const; @@ -131,83 +112,34 @@ public: /// Retrieves the motion mappings for the given device. [[nodiscard]] MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& device) const; - /// Retrieves the underlying GameCube analog handler. - [[nodiscard]] GCAnalogFactory* GetGCAnalogs(); + /// Returns an enum contaning the name to be displayed from the input engine. + [[nodiscard]] Common::Input::ButtonNames GetButtonName( + const Common::ParamPackage& params) const; - /// Retrieves the underlying GameCube analog handler. - [[nodiscard]] const GCAnalogFactory* GetGCAnalogs() const; + /// Returns true if device is a controller. + [[nodiscard]] bool IsController(const Common::ParamPackage& params) const; - /// Retrieves the underlying GameCube button handler. - [[nodiscard]] GCButtonFactory* GetGCButtons(); - - /// Retrieves the underlying GameCube button handler. - [[nodiscard]] const GCButtonFactory* GetGCButtons() const; - - /// Retrieves the underlying udp motion handler. - [[nodiscard]] UDPMotionFactory* GetUDPMotions(); - - /// Retrieves the underlying udp motion handler. - [[nodiscard]] const UDPMotionFactory* GetUDPMotions() const; - - /// Retrieves the underlying udp touch handler. - [[nodiscard]] UDPTouchFactory* GetUDPTouch(); - - /// Retrieves the underlying udp touch handler. - [[nodiscard]] const UDPTouchFactory* GetUDPTouch() const; - - /// Retrieves the underlying mouse button handler. - [[nodiscard]] MouseButtonFactory* GetMouseButtons(); - - /// Retrieves the underlying mouse button handler. - [[nodiscard]] const MouseButtonFactory* GetMouseButtons() const; - - /// Retrieves the underlying mouse analog handler. - [[nodiscard]] MouseAnalogFactory* GetMouseAnalogs(); - - /// Retrieves the underlying mouse analog handler. - [[nodiscard]] const MouseAnalogFactory* GetMouseAnalogs() const; - - /// Retrieves the underlying mouse motion handler. - [[nodiscard]] MouseMotionFactory* GetMouseMotions(); - - /// Retrieves the underlying mouse motion handler. - [[nodiscard]] const MouseMotionFactory* GetMouseMotions() const; - - /// Retrieves the underlying mouse touch handler. - [[nodiscard]] MouseTouchFactory* GetMouseTouch(); - - /// Retrieves the underlying mouse touch handler. - [[nodiscard]] const MouseTouchFactory* GetMouseTouch() const; - - /// Retrieves the underlying tas button handler. - [[nodiscard]] TasButtonFactory* GetTasButtons(); - - /// Retrieves the underlying tas button handler. - [[nodiscard]] const TasButtonFactory* GetTasButtons() const; - - /// Retrieves the underlying tas analogs handler. - [[nodiscard]] TasAnalogFactory* GetTasAnalogs(); - - /// Retrieves the underlying tas analogs handler. - [[nodiscard]] const TasAnalogFactory* GetTasAnalogs() const; - - /// Reloads the input devices + /// Reloads the input devices. void ReloadInputDevices(); - /// Get all DevicePoller from all backends for a specific device type - [[nodiscard]] std::vector> GetPollers( - Polling::DeviceType type) const; + /// Start polling from all backends for a desired input type. + void BeginMapping(Polling::InputType type); + + /// Returns an input event with mapping information. + [[nodiscard]] const Common::ParamPackage GetNextInput() const; + + /// Stop polling from all backends. + void StopMapping() const; private: struct Impl; std::unique_ptr impl; }; -/// Generates a serialized param package for creating a keyboard button device +/// Generates a serialized param package for creating a keyboard button device. std::string GenerateKeyboardParam(int key_code); -/// Generates a serialized param package for creating an analog device taking input from keyboard +/// Generates a serialized param package for creating an analog device taking input from keyboard. std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right, int key_modifier, float modifier_scale); - } // namespace InputCommon diff --git a/src/input_common/motion_from_button.cpp b/src/input_common/motion_from_button.cpp deleted file mode 100644 index 29045a673f..0000000000 --- a/src/input_common/motion_from_button.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "input_common/motion_from_button.h" -#include "input_common/motion_input.h" - -namespace InputCommon { - -class MotionKey final : public Input::MotionDevice { -public: - using Button = std::unique_ptr; - - explicit MotionKey(Button key_) : key(std::move(key_)) {} - - Input::MotionStatus GetStatus() const override { - - if (key->GetStatus()) { - return motion.GetRandomMotion(2, 6); - } - return motion.GetRandomMotion(0, 0); - } - -private: - Button key; - InputCommon::MotionInput motion{0.0f, 0.0f, 0.0f}; -}; - -std::unique_ptr MotionFromButton::Create(const Common::ParamPackage& params) { - auto key = Input::CreateDevice(params.Serialize()); - return std::make_unique(std::move(key)); -} - -} // namespace InputCommon diff --git a/src/input_common/motion_from_button.h b/src/input_common/motion_from_button.h deleted file mode 100644 index a959046fb5..0000000000 --- a/src/input_common/motion_from_button.h +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include "core/frontend/input.h" - -namespace InputCommon { - -/** - * An motion device factory that takes a keyboard button and uses it as a random - * motion device. - */ -class MotionFromButton final : public Input::Factory { -public: - /** - * Creates an motion device from button devices - * @param params contains parameters for creating the device: - * - "key": a serialized ParamPackage for creating a button device - */ - std::unique_ptr Create(const Common::ParamPackage& params) override; -}; - -} // namespace InputCommon diff --git a/src/input_common/mouse/mouse_input.cpp b/src/input_common/mouse/mouse_input.cpp deleted file mode 100644 index 3b052ffb22..0000000000 --- a/src/input_common/mouse/mouse_input.cpp +++ /dev/null @@ -1,223 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#include -#include - -#include "common/settings.h" -#include "input_common/mouse/mouse_input.h" - -namespace MouseInput { - -Mouse::Mouse() { - update_thread = std::jthread([this](std::stop_token stop_token) { UpdateThread(stop_token); }); -} - -Mouse::~Mouse() = default; - -void Mouse::UpdateThread(std::stop_token stop_token) { - constexpr int update_time = 10; - while (!stop_token.stop_requested()) { - for (MouseInfo& info : mouse_info) { - const Common::Vec3f angular_direction{ - -info.tilt_direction.y, - 0.0f, - -info.tilt_direction.x, - }; - - info.motion.SetGyroscope(angular_direction * info.tilt_speed); - info.motion.UpdateRotation(update_time * 1000); - info.motion.UpdateOrientation(update_time * 1000); - info.tilt_speed = 0; - info.data.motion = info.motion.GetMotion(); - if (Settings::values.mouse_panning) { - info.last_mouse_change *= 0.96f; - info.data.axis = {static_cast(16 * info.last_mouse_change.x), - static_cast(16 * -info.last_mouse_change.y)}; - } - } - if (configuring) { - UpdateYuzuSettings(); - } - if (mouse_panning_timout++ > 20) { - StopPanning(); - } - std::this_thread::sleep_for(std::chrono::milliseconds(update_time)); - } -} - -void Mouse::UpdateYuzuSettings() { - if (buttons == 0) { - return; - } - - mouse_queue.Push(MouseStatus{ - .button = last_button, - }); -} - -void Mouse::PressButton(int x, int y, MouseButton button_) { - const auto button_index = static_cast(button_); - if (button_index >= mouse_info.size()) { - return; - } - - const auto button = 1U << button_index; - buttons |= static_cast(button); - last_button = button_; - - mouse_info[button_index].mouse_origin = Common::MakeVec(x, y); - mouse_info[button_index].last_mouse_position = Common::MakeVec(x, y); - mouse_info[button_index].data.pressed = true; -} - -void Mouse::StopPanning() { - for (MouseInfo& info : mouse_info) { - if (Settings::values.mouse_panning) { - info.data.axis = {}; - info.tilt_speed = 0; - info.last_mouse_change = {}; - } - } -} - -void Mouse::MouseMove(int x, int y, int center_x, int center_y) { - for (MouseInfo& info : mouse_info) { - if (Settings::values.mouse_panning) { - auto mouse_change = - (Common::MakeVec(x, y) - Common::MakeVec(center_x, center_y)).Cast(); - mouse_panning_timout = 0; - - if (mouse_change.y == 0 && mouse_change.x == 0) { - continue; - } - const auto mouse_change_length = mouse_change.Length(); - if (mouse_change_length < 3.0f) { - mouse_change /= mouse_change_length / 3.0f; - } - - info.last_mouse_change = (info.last_mouse_change * 0.91f) + (mouse_change * 0.09f); - - const auto last_mouse_change_length = info.last_mouse_change.Length(); - if (last_mouse_change_length > 8.0f) { - info.last_mouse_change /= last_mouse_change_length / 8.0f; - } else if (last_mouse_change_length < 1.0f) { - info.last_mouse_change = mouse_change / mouse_change.Length(); - } - - info.tilt_direction = info.last_mouse_change; - info.tilt_speed = info.tilt_direction.Normalize() * info.sensitivity; - continue; - } - - if (info.data.pressed) { - const auto mouse_move = Common::MakeVec(x, y) - info.mouse_origin; - const auto mouse_change = Common::MakeVec(x, y) - info.last_mouse_position; - info.last_mouse_position = Common::MakeVec(x, y); - info.data.axis = {mouse_move.x, -mouse_move.y}; - - if (mouse_change.x == 0 && mouse_change.y == 0) { - info.tilt_speed = 0; - } else { - info.tilt_direction = mouse_change.Cast(); - info.tilt_speed = info.tilt_direction.Normalize() * info.sensitivity; - } - } - } -} - -void Mouse::ReleaseButton(MouseButton button_) { - const auto button_index = static_cast(button_); - if (button_index >= mouse_info.size()) { - return; - } - - const auto button = 1U << button_index; - buttons &= static_cast(0xFF - button); - - mouse_info[button_index].tilt_speed = 0; - mouse_info[button_index].data.pressed = false; - mouse_info[button_index].data.axis = {0, 0}; -} - -void Mouse::ReleaseAllButtons() { - buttons = 0; - for (auto& info : mouse_info) { - info.tilt_speed = 0; - info.data.pressed = false; - info.data.axis = {0, 0}; - } -} - -void Mouse::BeginConfiguration() { - buttons = 0; - last_button = MouseButton::Undefined; - mouse_queue.Clear(); - configuring = true; -} - -void Mouse::EndConfiguration() { - buttons = 0; - for (MouseInfo& info : mouse_info) { - info.tilt_speed = 0; - info.data.pressed = false; - info.data.axis = {0, 0}; - } - last_button = MouseButton::Undefined; - mouse_queue.Clear(); - configuring = false; -} - -bool Mouse::ToggleButton(std::size_t button_) { - if (button_ >= mouse_info.size()) { - return false; - } - const auto button = 1U << button_; - const bool button_state = (toggle_buttons & button) != 0; - const bool button_lock = (lock_buttons & button) != 0; - - if (button_lock) { - return button_state; - } - - lock_buttons |= static_cast(button); - - if (button_state) { - toggle_buttons &= static_cast(0xFF - button); - } else { - toggle_buttons |= static_cast(button); - } - - return !button_state; -} - -bool Mouse::UnlockButton(std::size_t button_) { - if (button_ >= mouse_info.size()) { - return false; - } - - const auto button = 1U << button_; - const bool button_state = (toggle_buttons & button) != 0; - - lock_buttons &= static_cast(0xFF - button); - - return button_state; -} - -Common::SPSCQueue& Mouse::GetMouseQueue() { - return mouse_queue; -} - -const Common::SPSCQueue& Mouse::GetMouseQueue() const { - return mouse_queue; -} - -MouseData& Mouse::GetMouseState(std::size_t button) { - return mouse_info[button].data; -} - -const MouseData& Mouse::GetMouseState(std::size_t button) const { - return mouse_info[button].data; -} -} // namespace MouseInput diff --git a/src/input_common/mouse/mouse_input.h b/src/input_common/mouse/mouse_input.h deleted file mode 100644 index c8bae99c14..0000000000 --- a/src/input_common/mouse/mouse_input.h +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include -#include - -#include "common/common_types.h" -#include "common/threadsafe_queue.h" -#include "common/vector_math.h" -#include "core/frontend/input.h" -#include "input_common/motion_input.h" - -namespace MouseInput { - -enum class MouseButton { - Left, - Right, - Wheel, - Backward, - Forward, - Task, - Extra, - Undefined, -}; - -struct MouseStatus { - MouseButton button{MouseButton::Undefined}; -}; - -struct MouseData { - bool pressed{}; - std::array axis{}; - Input::MotionStatus motion{}; - Input::TouchStatus touch{}; -}; - -class Mouse { -public: - Mouse(); - ~Mouse(); - - /// Used for polling - void BeginConfiguration(); - void EndConfiguration(); - - /** - * Signals that a button is pressed. - * @param x the x-coordinate of the cursor - * @param y the y-coordinate of the cursor - * @param button_ the button pressed - */ - void PressButton(int x, int y, MouseButton button_); - - /** - * Signals that mouse has moved. - * @param x the x-coordinate of the cursor - * @param y the y-coordinate of the cursor - * @param center_x the x-coordinate of the middle of the screen - * @param center_y the y-coordinate of the middle of the screen - */ - void MouseMove(int x, int y, int center_x, int center_y); - - /** - * Signals that a button is released. - * @param button_ the button pressed - */ - void ReleaseButton(MouseButton button_); - - /** - * Signals that all buttons are released - */ - void ReleaseAllButtons(); - - [[nodiscard]] bool ToggleButton(std::size_t button_); - [[nodiscard]] bool UnlockButton(std::size_t button_); - - [[nodiscard]] Common::SPSCQueue& GetMouseQueue(); - [[nodiscard]] const Common::SPSCQueue& GetMouseQueue() const; - - [[nodiscard]] MouseData& GetMouseState(std::size_t button); - [[nodiscard]] const MouseData& GetMouseState(std::size_t button) const; - -private: - void UpdateThread(std::stop_token stop_token); - void UpdateYuzuSettings(); - void StopPanning(); - - struct MouseInfo { - InputCommon::MotionInput motion{0.0f, 0.0f, 0.0f}; - Common::Vec2 mouse_origin; - Common::Vec2 last_mouse_position; - Common::Vec2 last_mouse_change; - bool is_tilting = false; - float sensitivity{0.120f}; - - float tilt_speed = 0; - Common::Vec2 tilt_direction; - MouseData data; - }; - - u16 buttons{}; - u16 toggle_buttons{}; - u16 lock_buttons{}; - std::jthread update_thread; - MouseButton last_button{MouseButton::Undefined}; - std::array mouse_info; - Common::SPSCQueue mouse_queue; - bool configuring{false}; - int mouse_panning_timout{}; -}; -} // namespace MouseInput diff --git a/src/input_common/mouse/mouse_poller.cpp b/src/input_common/mouse/mouse_poller.cpp deleted file mode 100644 index 090b26972d..0000000000 --- a/src/input_common/mouse/mouse_poller.cpp +++ /dev/null @@ -1,299 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include -#include -#include - -#include "common/settings.h" -#include "common/threadsafe_queue.h" -#include "input_common/mouse/mouse_input.h" -#include "input_common/mouse/mouse_poller.h" - -namespace InputCommon { - -class MouseButton final : public Input::ButtonDevice { -public: - explicit MouseButton(u32 button_, bool toggle_, MouseInput::Mouse* mouse_input_) - : button(button_), toggle(toggle_), mouse_input(mouse_input_) {} - - bool GetStatus() const override { - const bool button_state = mouse_input->GetMouseState(button).pressed; - if (!toggle) { - return button_state; - } - - if (button_state) { - return mouse_input->ToggleButton(button); - } - return mouse_input->UnlockButton(button); - } - -private: - const u32 button; - const bool toggle; - MouseInput::Mouse* mouse_input; -}; - -MouseButtonFactory::MouseButtonFactory(std::shared_ptr mouse_input_) - : mouse_input(std::move(mouse_input_)) {} - -std::unique_ptr MouseButtonFactory::Create( - const Common::ParamPackage& params) { - const auto button_id = params.Get("button", 0); - const auto toggle = params.Get("toggle", false); - - return std::make_unique(button_id, toggle, mouse_input.get()); -} - -Common::ParamPackage MouseButtonFactory::GetNextInput() const { - MouseInput::MouseStatus pad; - Common::ParamPackage params; - auto& queue = mouse_input->GetMouseQueue(); - while (queue.Pop(pad)) { - // This while loop will break on the earliest detected button - if (pad.button != MouseInput::MouseButton::Undefined) { - params.Set("engine", "mouse"); - params.Set("button", static_cast(pad.button)); - params.Set("toggle", false); - return params; - } - } - return params; -} - -void MouseButtonFactory::BeginConfiguration() { - polling = true; - mouse_input->BeginConfiguration(); -} - -void MouseButtonFactory::EndConfiguration() { - polling = false; - mouse_input->EndConfiguration(); -} - -class MouseAnalog final : public Input::AnalogDevice { -public: - explicit MouseAnalog(u32 port_, u32 axis_x_, u32 axis_y_, bool invert_x_, bool invert_y_, - float deadzone_, float range_, const MouseInput::Mouse* mouse_input_) - : button(port_), axis_x(axis_x_), axis_y(axis_y_), invert_x(invert_x_), invert_y(invert_y_), - deadzone(deadzone_), range(range_), mouse_input(mouse_input_) {} - - float GetAxis(u32 axis) const { - std::lock_guard lock{mutex}; - const auto axis_value = - static_cast(mouse_input->GetMouseState(button).axis.at(axis)); - const float sensitivity = Settings::values.mouse_panning_sensitivity.GetValue() * 0.10f; - return axis_value * sensitivity / (100.0f * range); - } - - std::pair GetAnalog(u32 analog_axis_x, u32 analog_axis_y) const { - float x = GetAxis(analog_axis_x); - float y = GetAxis(analog_axis_y); - if (invert_x) { - x = -x; - } - if (invert_y) { - y = -y; - } - - // Make sure the coordinates are in the unit circle, - // otherwise normalize it. - float r = x * x + y * y; - if (r > 1.0f) { - r = std::sqrt(r); - x /= r; - y /= r; - } - - return {x, y}; - } - - std::tuple GetStatus() const override { - const auto [x, y] = GetAnalog(axis_x, axis_y); - const float r = std::sqrt((x * x) + (y * y)); - if (r > deadzone) { - return {x / r * (r - deadzone) / (1 - deadzone), - y / r * (r - deadzone) / (1 - deadzone)}; - } - return {0.0f, 0.0f}; - } - - std::tuple GetRawStatus() const override { - const float x = GetAxis(axis_x); - const float y = GetAxis(axis_y); - return {x, y}; - } - - Input::AnalogProperties GetAnalogProperties() const override { - return {deadzone, range, 0.5f}; - } - -private: - const u32 button; - const u32 axis_x; - const u32 axis_y; - const bool invert_x; - const bool invert_y; - const float deadzone; - const float range; - const MouseInput::Mouse* mouse_input; - mutable std::mutex mutex; -}; - -/// An analog device factory that creates analog devices from GC Adapter -MouseAnalogFactory::MouseAnalogFactory(std::shared_ptr mouse_input_) - : mouse_input(std::move(mouse_input_)) {} - -/** - * Creates analog device from joystick axes - * @param params contains parameters for creating the device: - * - "port": the nth gcpad on the adapter - * - "axis_x": the index of the axis to be bind as x-axis - * - "axis_y": the index of the axis to be bind as y-axis - */ -std::unique_ptr MouseAnalogFactory::Create( - const Common::ParamPackage& params) { - const auto port = static_cast(params.Get("port", 0)); - const auto axis_x = static_cast(params.Get("axis_x", 0)); - const auto axis_y = static_cast(params.Get("axis_y", 1)); - const auto deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f); - const auto range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f); - const std::string invert_x_value = params.Get("invert_x", "+"); - const std::string invert_y_value = params.Get("invert_y", "+"); - const bool invert_x = invert_x_value == "-"; - const bool invert_y = invert_y_value == "-"; - - return std::make_unique(port, axis_x, axis_y, invert_x, invert_y, deadzone, range, - mouse_input.get()); -} - -void MouseAnalogFactory::BeginConfiguration() { - polling = true; - mouse_input->BeginConfiguration(); -} - -void MouseAnalogFactory::EndConfiguration() { - polling = false; - mouse_input->EndConfiguration(); -} - -Common::ParamPackage MouseAnalogFactory::GetNextInput() const { - MouseInput::MouseStatus pad; - Common::ParamPackage params; - auto& queue = mouse_input->GetMouseQueue(); - while (queue.Pop(pad)) { - // This while loop will break on the earliest detected button - if (pad.button != MouseInput::MouseButton::Undefined) { - params.Set("engine", "mouse"); - params.Set("port", static_cast(pad.button)); - params.Set("axis_x", 0); - params.Set("axis_y", 1); - params.Set("invert_x", "+"); - params.Set("invert_y", "+"); - return params; - } - } - return params; -} - -class MouseMotion final : public Input::MotionDevice { -public: - explicit MouseMotion(u32 button_, const MouseInput::Mouse* mouse_input_) - : button(button_), mouse_input(mouse_input_) {} - - Input::MotionStatus GetStatus() const override { - return mouse_input->GetMouseState(button).motion; - } - -private: - const u32 button; - const MouseInput::Mouse* mouse_input; -}; - -MouseMotionFactory::MouseMotionFactory(std::shared_ptr mouse_input_) - : mouse_input(std::move(mouse_input_)) {} - -std::unique_ptr MouseMotionFactory::Create( - const Common::ParamPackage& params) { - const auto button_id = params.Get("button", 0); - - return std::make_unique(button_id, mouse_input.get()); -} - -Common::ParamPackage MouseMotionFactory::GetNextInput() const { - MouseInput::MouseStatus pad; - Common::ParamPackage params; - auto& queue = mouse_input->GetMouseQueue(); - while (queue.Pop(pad)) { - // This while loop will break on the earliest detected button - if (pad.button != MouseInput::MouseButton::Undefined) { - params.Set("engine", "mouse"); - params.Set("button", static_cast(pad.button)); - return params; - } - } - return params; -} - -void MouseMotionFactory::BeginConfiguration() { - polling = true; - mouse_input->BeginConfiguration(); -} - -void MouseMotionFactory::EndConfiguration() { - polling = false; - mouse_input->EndConfiguration(); -} - -class MouseTouch final : public Input::TouchDevice { -public: - explicit MouseTouch(u32 button_, const MouseInput::Mouse* mouse_input_) - : button(button_), mouse_input(mouse_input_) {} - - Input::TouchStatus GetStatus() const override { - return mouse_input->GetMouseState(button).touch; - } - -private: - const u32 button; - const MouseInput::Mouse* mouse_input; -}; - -MouseTouchFactory::MouseTouchFactory(std::shared_ptr mouse_input_) - : mouse_input(std::move(mouse_input_)) {} - -std::unique_ptr MouseTouchFactory::Create(const Common::ParamPackage& params) { - const auto button_id = params.Get("button", 0); - - return std::make_unique(button_id, mouse_input.get()); -} - -Common::ParamPackage MouseTouchFactory::GetNextInput() const { - MouseInput::MouseStatus pad; - Common::ParamPackage params; - auto& queue = mouse_input->GetMouseQueue(); - while (queue.Pop(pad)) { - // This while loop will break on the earliest detected button - if (pad.button != MouseInput::MouseButton::Undefined) { - params.Set("engine", "mouse"); - params.Set("button", static_cast(pad.button)); - return params; - } - } - return params; -} - -void MouseTouchFactory::BeginConfiguration() { - polling = true; - mouse_input->BeginConfiguration(); -} - -void MouseTouchFactory::EndConfiguration() { - polling = false; - mouse_input->EndConfiguration(); -} - -} // namespace InputCommon diff --git a/src/input_common/mouse/mouse_poller.h b/src/input_common/mouse/mouse_poller.h deleted file mode 100644 index cf331293b8..0000000000 --- a/src/input_common/mouse/mouse_poller.h +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include "core/frontend/input.h" -#include "input_common/mouse/mouse_input.h" - -namespace InputCommon { - -/** - * A button device factory representing a mouse. It receives mouse events and forward them - * to all button devices it created. - */ -class MouseButtonFactory final : public Input::Factory { -public: - explicit MouseButtonFactory(std::shared_ptr mouse_input_); - - /** - * Creates a button device from a button press - * @param params contains parameters for creating the device: - * - "code": the code of the key to bind with the button - */ - std::unique_ptr Create(const Common::ParamPackage& params) override; - - Common::ParamPackage GetNextInput() const; - - /// For device input configuration/polling - void BeginConfiguration(); - void EndConfiguration(); - - bool IsPolling() const { - return polling; - } - -private: - std::shared_ptr mouse_input; - bool polling = false; -}; - -/// An analog device factory that creates analog devices from mouse -class MouseAnalogFactory final : public Input::Factory { -public: - explicit MouseAnalogFactory(std::shared_ptr mouse_input_); - - std::unique_ptr Create(const Common::ParamPackage& params) override; - - Common::ParamPackage GetNextInput() const; - - /// For device input configuration/polling - void BeginConfiguration(); - void EndConfiguration(); - - bool IsPolling() const { - return polling; - } - -private: - std::shared_ptr mouse_input; - bool polling = false; -}; - -/// A motion device factory that creates motion devices from mouse -class MouseMotionFactory final : public Input::Factory { -public: - explicit MouseMotionFactory(std::shared_ptr mouse_input_); - - std::unique_ptr Create(const Common::ParamPackage& params) override; - - Common::ParamPackage GetNextInput() const; - - /// For device input configuration/polling - void BeginConfiguration(); - void EndConfiguration(); - - bool IsPolling() const { - return polling; - } - -private: - std::shared_ptr mouse_input; - bool polling = false; -}; - -/// An touch device factory that creates touch devices from mouse -class MouseTouchFactory final : public Input::Factory { -public: - explicit MouseTouchFactory(std::shared_ptr mouse_input_); - - std::unique_ptr Create(const Common::ParamPackage& params) override; - - Common::ParamPackage GetNextInput() const; - - /// For device input configuration/polling - void BeginConfiguration(); - void EndConfiguration(); - - bool IsPolling() const { - return polling; - } - -private: - std::shared_ptr mouse_input; - bool polling = false; -}; - -} // namespace InputCommon diff --git a/src/input_common/sdl/sdl.cpp b/src/input_common/sdl/sdl.cpp deleted file mode 100644 index 644db3448f..0000000000 --- a/src/input_common/sdl/sdl.cpp +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2018 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "input_common/sdl/sdl.h" -#ifdef HAVE_SDL2 -#include "input_common/sdl/sdl_impl.h" -#endif - -namespace InputCommon::SDL { - -std::unique_ptr Init() { -#ifdef HAVE_SDL2 - return std::make_unique(); -#else - return std::make_unique(); -#endif -} -} // namespace InputCommon::SDL diff --git a/src/input_common/sdl/sdl.h b/src/input_common/sdl/sdl.h deleted file mode 100644 index b5d41bba42..0000000000 --- a/src/input_common/sdl/sdl.h +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2018 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include "common/param_package.h" -#include "input_common/main.h" - -namespace InputCommon::Polling { -class DevicePoller; -enum class DeviceType; -} // namespace InputCommon::Polling - -namespace InputCommon::SDL { - -class State { -public: - using Pollers = std::vector>; - - /// Unregisters SDL device factories and shut them down. - virtual ~State() = default; - - virtual Pollers GetPollers(Polling::DeviceType) { - return {}; - } - - virtual std::vector GetInputDevices() { - return {}; - } - - virtual ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage&) { - return {}; - } - virtual AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage&) { - return {}; - } - virtual MotionMapping GetMotionMappingForDevice(const Common::ParamPackage&) { - return {}; - } -}; - -class NullState : public State { -public: -}; - -std::unique_ptr Init(); - -} // namespace InputCommon::SDL diff --git a/src/input_common/sdl/sdl_impl.cpp b/src/input_common/sdl/sdl_impl.cpp deleted file mode 100644 index ecb00d4286..0000000000 --- a/src/input_common/sdl/sdl_impl.cpp +++ /dev/null @@ -1,1658 +0,0 @@ -// Copyright 2018 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "common/logging/log.h" -#include "common/math_util.h" -#include "common/param_package.h" -#include "common/settings.h" -#include "common/threadsafe_queue.h" -#include "core/frontend/input.h" -#include "input_common/motion_input.h" -#include "input_common/sdl/sdl_impl.h" - -namespace InputCommon::SDL { - -namespace { -std::string GetGUID(SDL_Joystick* joystick) { - const SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick); - char guid_str[33]; - SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str)); - return guid_str; -} - -/// Creates a ParamPackage from an SDL_Event that can directly be used to create a ButtonDevice -Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event); -} // Anonymous namespace - -static int SDLEventWatcher(void* user_data, SDL_Event* event) { - auto* const sdl_state = static_cast(user_data); - - // Don't handle the event if we are configuring - if (sdl_state->polling) { - sdl_state->event_queue.Push(*event); - } else { - sdl_state->HandleGameControllerEvent(*event); - } - - return 0; -} - -class SDLJoystick { -public: - SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick, - SDL_GameController* game_controller) - : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose}, - sdl_controller{game_controller, &SDL_GameControllerClose} { - EnableMotion(); - } - - void EnableMotion() { - if (sdl_controller) { - SDL_GameController* controller = sdl_controller.get(); - if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL) && !has_accel) { - SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE); - has_accel = true; - } - if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) && !has_gyro) { - SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_TRUE); - has_gyro = true; - } - } - } - - void SetButton(int button, bool value) { - std::lock_guard lock{mutex}; - state.buttons.insert_or_assign(button, value); - } - - void PreSetButton(int button) { - if (!state.buttons.contains(button)) { - SetButton(button, false); - } - } - - void SetMotion(SDL_ControllerSensorEvent event) { - constexpr float gravity_constant = 9.80665f; - std::lock_guard lock{mutex}; - u64 time_difference = event.timestamp - last_motion_update; - last_motion_update = event.timestamp; - switch (event.sensor) { - case SDL_SENSOR_ACCEL: { - const Common::Vec3f acceleration = {-event.data[0], event.data[2], -event.data[1]}; - motion.SetAcceleration(acceleration / gravity_constant); - break; - } - case SDL_SENSOR_GYRO: { - const Common::Vec3f gyroscope = {event.data[0], -event.data[2], event.data[1]}; - motion.SetGyroscope(gyroscope / (Common::PI * 2)); - break; - } - } - - // Ignore duplicated timestamps - if (time_difference == 0) { - return; - } - - motion.SetGyroThreshold(0.0001f); - motion.UpdateRotation(time_difference * 1000); - motion.UpdateOrientation(time_difference * 1000); - } - - bool GetButton(int button) const { - std::lock_guard lock{mutex}; - return state.buttons.at(button); - } - - bool ToggleButton(int button) { - std::lock_guard lock{mutex}; - - if (!state.toggle_buttons.contains(button) || !state.lock_buttons.contains(button)) { - state.toggle_buttons.insert_or_assign(button, false); - state.lock_buttons.insert_or_assign(button, false); - } - - const bool button_state = state.toggle_buttons.at(button); - const bool button_lock = state.lock_buttons.at(button); - - if (button_lock) { - return button_state; - } - - state.lock_buttons.insert_or_assign(button, true); - - if (button_state) { - state.toggle_buttons.insert_or_assign(button, false); - } else { - state.toggle_buttons.insert_or_assign(button, true); - } - - return !button_state; - } - - bool UnlockButton(int button) { - std::lock_guard lock{mutex}; - if (!state.toggle_buttons.contains(button)) { - return false; - } - state.lock_buttons.insert_or_assign(button, false); - return state.toggle_buttons.at(button); - } - - void SetAxis(int axis, Sint16 value) { - std::lock_guard lock{mutex}; - state.axes.insert_or_assign(axis, value); - } - - void PreSetAxis(int axis) { - if (!state.axes.contains(axis)) { - SetAxis(axis, 0); - } - } - - float GetAxis(int axis, float range, float offset) const { - std::lock_guard lock{mutex}; - const float value = static_cast(state.axes.at(axis)) / 32767.0f; - const float offset_scale = (value + offset) > 0.0f ? 1.0f + offset : 1.0f - offset; - return (value + offset) / range / offset_scale; - } - - bool RumblePlay(u16 amp_low, u16 amp_high) { - constexpr u32 rumble_max_duration_ms = 1000; - - if (sdl_controller) { - return SDL_GameControllerRumble(sdl_controller.get(), amp_low, amp_high, - rumble_max_duration_ms) != -1; - } else if (sdl_joystick) { - return SDL_JoystickRumble(sdl_joystick.get(), amp_low, amp_high, - rumble_max_duration_ms) != -1; - } - - return false; - } - - std::tuple GetAnalog(int axis_x, int axis_y, float range, float offset_x, - float offset_y) const { - float x = GetAxis(axis_x, range, offset_x); - float y = GetAxis(axis_y, range, offset_y); - y = -y; // 3DS uses an y-axis inverse from SDL - - // Make sure the coordinates are in the unit circle, - // otherwise normalize it. - float r = x * x + y * y; - if (r > 1.0f) { - r = std::sqrt(r); - x /= r; - y /= r; - } - - return std::make_tuple(x, y); - } - - bool HasGyro() const { - return has_gyro; - } - - bool HasAccel() const { - return has_accel; - } - - const MotionInput& GetMotion() const { - return motion; - } - - void SetHat(int hat, Uint8 direction) { - std::lock_guard lock{mutex}; - state.hats.insert_or_assign(hat, direction); - } - - bool GetHatDirection(int hat, Uint8 direction) const { - std::lock_guard lock{mutex}; - return (state.hats.at(hat) & direction) != 0; - } - /** - * The guid of the joystick - */ - const std::string& GetGUID() const { - return guid; - } - - /** - * The number of joystick from the same type that were connected before this joystick - */ - int GetPort() const { - return port; - } - - SDL_Joystick* GetSDLJoystick() const { - return sdl_joystick.get(); - } - - SDL_GameController* GetSDLGameController() const { - return sdl_controller.get(); - } - - void SetSDLJoystick(SDL_Joystick* joystick, SDL_GameController* controller) { - sdl_joystick.reset(joystick); - sdl_controller.reset(controller); - } - - bool IsJoyconLeft() const { - const std::string controller_name = GetControllerName(); - if (std::strstr(controller_name.c_str(), "Joy-Con Left") != nullptr) { - return true; - } - if (std::strstr(controller_name.c_str(), "Joy-Con (L)") != nullptr) { - return true; - } - return false; - } - - bool IsJoyconRight() const { - const std::string controller_name = GetControllerName(); - if (std::strstr(controller_name.c_str(), "Joy-Con Right") != nullptr) { - return true; - } - if (std::strstr(controller_name.c_str(), "Joy-Con (R)") != nullptr) { - return true; - } - return false; - } - - std::string GetControllerName() const { - if (sdl_controller) { - switch (SDL_GameControllerGetType(sdl_controller.get())) { - case SDL_CONTROLLER_TYPE_XBOX360: - return "XBox 360 Controller"; - case SDL_CONTROLLER_TYPE_XBOXONE: - return "XBox One Controller"; - default: - break; - } - const auto name = SDL_GameControllerName(sdl_controller.get()); - if (name) { - return name; - } - } - - if (sdl_joystick) { - const auto name = SDL_JoystickName(sdl_joystick.get()); - if (name) { - return name; - } - } - - return "Unknown"; - } - -private: - struct State { - std::unordered_map buttons; - std::unordered_map toggle_buttons{}; - std::unordered_map lock_buttons{}; - std::unordered_map axes; - std::unordered_map hats; - } state; - std::string guid; - int port; - std::unique_ptr sdl_joystick; - std::unique_ptr sdl_controller; - mutable std::mutex mutex; - - // Motion is initialized with the PID values - MotionInput motion{0.3f, 0.005f, 0.0f}; - u64 last_motion_update{}; - bool has_gyro{false}; - bool has_accel{false}; -}; - -std::shared_ptr SDLState::GetSDLJoystickByGUID(const std::string& guid, int port) { - std::lock_guard lock{joystick_map_mutex}; - const auto it = joystick_map.find(guid); - - if (it != joystick_map.end()) { - while (it->second.size() <= static_cast(port)) { - auto joystick = std::make_shared(guid, static_cast(it->second.size()), - nullptr, nullptr); - it->second.emplace_back(std::move(joystick)); - } - - return it->second[static_cast(port)]; - } - - auto joystick = std::make_shared(guid, 0, nullptr, nullptr); - - return joystick_map[guid].emplace_back(std::move(joystick)); -} - -std::shared_ptr SDLState::GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) { - auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id); - const std::string guid = GetGUID(sdl_joystick); - - std::lock_guard lock{joystick_map_mutex}; - const auto map_it = joystick_map.find(guid); - - if (map_it == joystick_map.end()) { - return nullptr; - } - - const auto vec_it = std::find_if(map_it->second.begin(), map_it->second.end(), - [&sdl_joystick](const auto& joystick) { - return joystick->GetSDLJoystick() == sdl_joystick; - }); - - if (vec_it == map_it->second.end()) { - return nullptr; - } - - return *vec_it; -} - -void SDLState::InitJoystick(int joystick_index) { - SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index); - SDL_GameController* sdl_gamecontroller = nullptr; - - if (SDL_IsGameController(joystick_index)) { - sdl_gamecontroller = SDL_GameControllerOpen(joystick_index); - } - - if (!sdl_joystick) { - LOG_ERROR(Input, "Failed to open joystick {}", joystick_index); - return; - } - - const std::string guid = GetGUID(sdl_joystick); - - std::lock_guard lock{joystick_map_mutex}; - if (joystick_map.find(guid) == joystick_map.end()) { - auto joystick = std::make_shared(guid, 0, sdl_joystick, sdl_gamecontroller); - joystick_map[guid].emplace_back(std::move(joystick)); - return; - } - - auto& joystick_guid_list = joystick_map[guid]; - const auto joystick_it = - std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(), - [](const auto& joystick) { return !joystick->GetSDLJoystick(); }); - - if (joystick_it != joystick_guid_list.end()) { - (*joystick_it)->SetSDLJoystick(sdl_joystick, sdl_gamecontroller); - return; - } - - const int port = static_cast(joystick_guid_list.size()); - auto joystick = std::make_shared(guid, port, sdl_joystick, sdl_gamecontroller); - joystick_guid_list.emplace_back(std::move(joystick)); -} - -void SDLState::CloseJoystick(SDL_Joystick* sdl_joystick) { - const std::string guid = GetGUID(sdl_joystick); - - std::lock_guard lock{joystick_map_mutex}; - // This call to guid is safe since the joystick is guaranteed to be in the map - const auto& joystick_guid_list = joystick_map[guid]; - const auto joystick_it = std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(), - [&sdl_joystick](const auto& joystick) { - return joystick->GetSDLJoystick() == sdl_joystick; - }); - - if (joystick_it != joystick_guid_list.end()) { - (*joystick_it)->SetSDLJoystick(nullptr, nullptr); - } -} - -void SDLState::HandleGameControllerEvent(const SDL_Event& event) { - switch (event.type) { - case SDL_JOYBUTTONUP: { - if (auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) { - joystick->SetButton(event.jbutton.button, false); - } - break; - } - case SDL_JOYBUTTONDOWN: { - if (auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) { - joystick->SetButton(event.jbutton.button, true); - } - break; - } - case SDL_JOYHATMOTION: { - if (auto joystick = GetSDLJoystickBySDLID(event.jhat.which)) { - joystick->SetHat(event.jhat.hat, event.jhat.value); - } - break; - } - case SDL_JOYAXISMOTION: { - if (auto joystick = GetSDLJoystickBySDLID(event.jaxis.which)) { - joystick->SetAxis(event.jaxis.axis, event.jaxis.value); - } - break; - } - case SDL_CONTROLLERSENSORUPDATE: { - if (auto joystick = GetSDLJoystickBySDLID(event.csensor.which)) { - joystick->SetMotion(event.csensor); - } - break; - } - case SDL_JOYDEVICEREMOVED: - LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which); - CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which)); - break; - case SDL_JOYDEVICEADDED: - LOG_DEBUG(Input, "Controller connected with device index {}", event.jdevice.which); - InitJoystick(event.jdevice.which); - break; - } -} - -void SDLState::CloseJoysticks() { - std::lock_guard lock{joystick_map_mutex}; - joystick_map.clear(); -} - -class SDLButton final : public Input::ButtonDevice { -public: - explicit SDLButton(std::shared_ptr joystick_, int button_, bool toggle_) - : joystick(std::move(joystick_)), button(button_), toggle(toggle_) {} - - bool GetStatus() const override { - const bool button_state = joystick->GetButton(button); - if (!toggle) { - return button_state; - } - - if (button_state) { - return joystick->ToggleButton(button); - } - return joystick->UnlockButton(button); - } - -private: - std::shared_ptr joystick; - int button; - bool toggle; -}; - -class SDLDirectionButton final : public Input::ButtonDevice { -public: - explicit SDLDirectionButton(std::shared_ptr joystick_, int hat_, Uint8 direction_) - : joystick(std::move(joystick_)), hat(hat_), direction(direction_) {} - - bool GetStatus() const override { - return joystick->GetHatDirection(hat, direction); - } - -private: - std::shared_ptr joystick; - int hat; - Uint8 direction; -}; - -class SDLAxisButton final : public Input::ButtonDevice { -public: - explicit SDLAxisButton(std::shared_ptr joystick_, int axis_, float threshold_, - bool trigger_if_greater_) - : joystick(std::move(joystick_)), axis(axis_), threshold(threshold_), - trigger_if_greater(trigger_if_greater_) {} - - bool GetStatus() const override { - const float axis_value = joystick->GetAxis(axis, 1.0f, 0.0f); - if (trigger_if_greater) { - return axis_value > threshold; - } - return axis_value < threshold; - } - -private: - std::shared_ptr joystick; - int axis; - float threshold; - bool trigger_if_greater; -}; - -class SDLAnalog final : public Input::AnalogDevice { -public: - explicit SDLAnalog(std::shared_ptr joystick_, int axis_x_, int axis_y_, - bool invert_x_, bool invert_y_, float deadzone_, float range_, - float offset_x_, float offset_y_) - : joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_), invert_x(invert_x_), - invert_y(invert_y_), deadzone(deadzone_), range(range_), offset_x(offset_x_), - offset_y(offset_y_) {} - - std::tuple GetStatus() const override { - auto [x, y] = joystick->GetAnalog(axis_x, axis_y, range, offset_x, offset_y); - const float r = std::sqrt((x * x) + (y * y)); - if (invert_x) { - x = -x; - } - if (invert_y) { - y = -y; - } - - if (r > deadzone) { - return std::make_tuple(x / r * (r - deadzone) / (1 - deadzone), - y / r * (r - deadzone) / (1 - deadzone)); - } - return {}; - } - - std::tuple GetRawStatus() const override { - const float x = joystick->GetAxis(axis_x, range, offset_x); - const float y = joystick->GetAxis(axis_y, range, offset_y); - return {x, -y}; - } - - Input::AnalogProperties GetAnalogProperties() const override { - return {deadzone, range, 0.5f}; - } - - bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override { - const auto [x, y] = GetStatus(); - const float directional_deadzone = 0.5f; - switch (direction) { - case Input::AnalogDirection::RIGHT: - return x > directional_deadzone; - case Input::AnalogDirection::LEFT: - return x < -directional_deadzone; - case Input::AnalogDirection::UP: - return y > directional_deadzone; - case Input::AnalogDirection::DOWN: - return y < -directional_deadzone; - } - return false; - } - -private: - std::shared_ptr joystick; - const int axis_x; - const int axis_y; - const bool invert_x; - const bool invert_y; - const float deadzone; - const float range; - const float offset_x; - const float offset_y; -}; - -class SDLVibration final : public Input::VibrationDevice { -public: - explicit SDLVibration(std::shared_ptr joystick_) - : joystick(std::move(joystick_)) {} - - u8 GetStatus() const override { - joystick->RumblePlay(1, 1); - return joystick->RumblePlay(0, 0); - } - - bool SetRumblePlay(f32 amp_low, [[maybe_unused]] f32 freq_low, f32 amp_high, - [[maybe_unused]] f32 freq_high) const override { - const auto process_amplitude = [](f32 amplitude) { - return static_cast((amplitude + std::pow(amplitude, 0.3f)) * 0.5f * 0xFFFF); - }; - - const auto processed_amp_low = process_amplitude(amp_low); - const auto processed_amp_high = process_amplitude(amp_high); - - return joystick->RumblePlay(processed_amp_low, processed_amp_high); - } - -private: - std::shared_ptr joystick; -}; - -class SDLMotion final : public Input::MotionDevice { -public: - explicit SDLMotion(std::shared_ptr joystick_) : joystick(std::move(joystick_)) {} - - Input::MotionStatus GetStatus() const override { - return joystick->GetMotion().GetMotion(); - } - -private: - std::shared_ptr joystick; -}; - -class SDLDirectionMotion final : public Input::MotionDevice { -public: - explicit SDLDirectionMotion(std::shared_ptr joystick_, int hat_, Uint8 direction_) - : joystick(std::move(joystick_)), hat(hat_), direction(direction_) {} - - Input::MotionStatus GetStatus() const override { - if (joystick->GetHatDirection(hat, direction)) { - return joystick->GetMotion().GetRandomMotion(2, 6); - } - return joystick->GetMotion().GetRandomMotion(0, 0); - } - -private: - std::shared_ptr joystick; - int hat; - Uint8 direction; -}; - -class SDLAxisMotion final : public Input::MotionDevice { -public: - explicit SDLAxisMotion(std::shared_ptr joystick_, int axis_, float threshold_, - bool trigger_if_greater_) - : joystick(std::move(joystick_)), axis(axis_), threshold(threshold_), - trigger_if_greater(trigger_if_greater_) {} - - Input::MotionStatus GetStatus() const override { - const float axis_value = joystick->GetAxis(axis, 1.0f, 0.0f); - bool trigger = axis_value < threshold; - if (trigger_if_greater) { - trigger = axis_value > threshold; - } - - if (trigger) { - return joystick->GetMotion().GetRandomMotion(2, 6); - } - return joystick->GetMotion().GetRandomMotion(0, 0); - } - -private: - std::shared_ptr joystick; - int axis; - float threshold; - bool trigger_if_greater; -}; - -class SDLButtonMotion final : public Input::MotionDevice { -public: - explicit SDLButtonMotion(std::shared_ptr joystick_, int button_) - : joystick(std::move(joystick_)), button(button_) {} - - Input::MotionStatus GetStatus() const override { - if (joystick->GetButton(button)) { - return joystick->GetMotion().GetRandomMotion(2, 6); - } - return joystick->GetMotion().GetRandomMotion(0, 0); - } - -private: - std::shared_ptr joystick; - int button; -}; - -/// A button device factory that creates button devices from SDL joystick -class SDLButtonFactory final : public Input::Factory { -public: - explicit SDLButtonFactory(SDLState& state_) : state(state_) {} - - /** - * Creates a button device from a joystick button - * @param params contains parameters for creating the device: - * - "guid": the guid of the joystick to bind - * - "port": the nth joystick of the same type to bind - * - "button"(optional): the index of the button to bind - * - "hat"(optional): the index of the hat to bind as direction buttons - * - "axis"(optional): the index of the axis to bind - * - "direction"(only used for hat): the direction name of the hat to bind. Can be "up", - * "down", "left" or "right" - * - "threshold"(only used for axis): a float value in (-1.0, 1.0) which the button is - * triggered if the axis value crosses - * - "direction"(only used for axis): "+" means the button is triggered when the axis - * value is greater than the threshold; "-" means the button is triggered when the axis - * value is smaller than the threshold - */ - std::unique_ptr Create(const Common::ParamPackage& params) override { - const std::string guid = params.Get("guid", "0"); - const int port = params.Get("port", 0); - const auto toggle = params.Get("toggle", false); - - auto joystick = state.GetSDLJoystickByGUID(guid, port); - - if (params.Has("hat")) { - const int hat = params.Get("hat", 0); - const std::string direction_name = params.Get("direction", ""); - Uint8 direction; - if (direction_name == "up") { - direction = SDL_HAT_UP; - } else if (direction_name == "down") { - direction = SDL_HAT_DOWN; - } else if (direction_name == "left") { - direction = SDL_HAT_LEFT; - } else if (direction_name == "right") { - direction = SDL_HAT_RIGHT; - } else { - direction = 0; - } - // This is necessary so accessing GetHat with hat won't crash - joystick->SetHat(hat, SDL_HAT_CENTERED); - return std::make_unique(joystick, hat, direction); - } - - if (params.Has("axis")) { - const int axis = params.Get("axis", 0); - // Convert range from (0.0, 1.0) to (-1.0, 1.0) - const float threshold = (params.Get("threshold", 0.5f) - 0.5f) * 2.0f; - const std::string direction_name = params.Get("direction", ""); - bool trigger_if_greater; - if (direction_name == "+") { - trigger_if_greater = true; - } else if (direction_name == "-") { - trigger_if_greater = false; - } else { - trigger_if_greater = true; - LOG_ERROR(Input, "Unknown direction {}", direction_name); - } - // This is necessary so accessing GetAxis with axis won't crash - joystick->PreSetAxis(axis); - return std::make_unique(joystick, axis, threshold, trigger_if_greater); - } - - const int button = params.Get("button", 0); - // This is necessary so accessing GetButton with button won't crash - joystick->PreSetButton(button); - return std::make_unique(joystick, button, toggle); - } - -private: - SDLState& state; -}; - -/// An analog device factory that creates analog devices from SDL joystick -class SDLAnalogFactory final : public Input::Factory { -public: - explicit SDLAnalogFactory(SDLState& state_) : state(state_) {} - /** - * Creates an analog device from joystick axes - * @param params contains parameters for creating the device: - * - "guid": the guid of the joystick to bind - * - "port": the nth joystick of the same type - * - "axis_x": the index of the axis to be bind as x-axis - * - "axis_y": the index of the axis to be bind as y-axis - */ - std::unique_ptr Create(const Common::ParamPackage& params) override { - const std::string guid = params.Get("guid", "0"); - const int port = params.Get("port", 0); - const int axis_x = params.Get("axis_x", 0); - const int axis_y = params.Get("axis_y", 1); - const float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f); - const float range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f); - const std::string invert_x_value = params.Get("invert_x", "+"); - const std::string invert_y_value = params.Get("invert_y", "+"); - const bool invert_x = invert_x_value == "-"; - const bool invert_y = invert_y_value == "-"; - const float offset_x = std::clamp(params.Get("offset_x", 0.0f), -0.99f, 0.99f); - const float offset_y = std::clamp(params.Get("offset_y", 0.0f), -0.99f, 0.99f); - auto joystick = state.GetSDLJoystickByGUID(guid, port); - - // This is necessary so accessing GetAxis with axis_x and axis_y won't crash - joystick->PreSetAxis(axis_x); - joystick->PreSetAxis(axis_y); - return std::make_unique(joystick, axis_x, axis_y, invert_x, invert_y, deadzone, - range, offset_x, offset_y); - } - -private: - SDLState& state; -}; - -/// An vibration device factory that creates vibration devices from SDL joystick -class SDLVibrationFactory final : public Input::Factory { -public: - explicit SDLVibrationFactory(SDLState& state_) : state(state_) {} - /** - * Creates a vibration device from a joystick - * @param params contains parameters for creating the device: - * - "guid": the guid of the joystick to bind - * - "port": the nth joystick of the same type - */ - std::unique_ptr Create(const Common::ParamPackage& params) override { - const std::string guid = params.Get("guid", "0"); - const int port = params.Get("port", 0); - return std::make_unique(state.GetSDLJoystickByGUID(guid, port)); - } - -private: - SDLState& state; -}; - -/// A motion device factory that creates motion devices from SDL joystick -class SDLMotionFactory final : public Input::Factory { -public: - explicit SDLMotionFactory(SDLState& state_) : state(state_) {} - /** - * Creates motion device from joystick axes - * @param params contains parameters for creating the device: - * - "guid": the guid of the joystick to bind - * - "port": the nth joystick of the same type - */ - std::unique_ptr Create(const Common::ParamPackage& params) override { - const std::string guid = params.Get("guid", "0"); - const int port = params.Get("port", 0); - - auto joystick = state.GetSDLJoystickByGUID(guid, port); - - if (params.Has("motion")) { - return std::make_unique(joystick); - } - - if (params.Has("hat")) { - const int hat = params.Get("hat", 0); - const std::string direction_name = params.Get("direction", ""); - Uint8 direction; - if (direction_name == "up") { - direction = SDL_HAT_UP; - } else if (direction_name == "down") { - direction = SDL_HAT_DOWN; - } else if (direction_name == "left") { - direction = SDL_HAT_LEFT; - } else if (direction_name == "right") { - direction = SDL_HAT_RIGHT; - } else { - direction = 0; - } - // This is necessary so accessing GetHat with hat won't crash - joystick->SetHat(hat, SDL_HAT_CENTERED); - return std::make_unique(joystick, hat, direction); - } - - if (params.Has("axis")) { - const int axis = params.Get("axis", 0); - const float threshold = params.Get("threshold", 0.5f); - const std::string direction_name = params.Get("direction", ""); - bool trigger_if_greater; - if (direction_name == "+") { - trigger_if_greater = true; - } else if (direction_name == "-") { - trigger_if_greater = false; - } else { - trigger_if_greater = true; - LOG_ERROR(Input, "Unknown direction {}", direction_name); - } - // This is necessary so accessing GetAxis with axis won't crash - joystick->PreSetAxis(axis); - return std::make_unique(joystick, axis, threshold, trigger_if_greater); - } - - const int button = params.Get("button", 0); - // This is necessary so accessing GetButton with button won't crash - joystick->PreSetButton(button); - return std::make_unique(joystick, button); - } - -private: - SDLState& state; -}; - -SDLState::SDLState() { - using namespace Input; - button_factory = std::make_shared(*this); - analog_factory = std::make_shared(*this); - vibration_factory = std::make_shared(*this); - motion_factory = std::make_shared(*this); - RegisterFactory("sdl", button_factory); - RegisterFactory("sdl", analog_factory); - RegisterFactory("sdl", vibration_factory); - RegisterFactory("sdl", motion_factory); - - if (!Settings::values.enable_raw_input) { - // Disable raw input. When enabled this setting causes SDL to die when a web applet opens - SDL_SetHint(SDL_HINT_JOYSTICK_RAWINPUT, "0"); - } - - // Enable HIDAPI rumble. This prevents SDL from disabling motion on PS4 and PS5 controllers - SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1"); - SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1"); - - // Tell SDL2 to use the hidapi driver. This will allow joycons to be detected as a - // GameController and not a generic one - SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1"); - - // Turn off Pro controller home led - SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_HOME_LED, "0"); - - // If the frontend is going to manage the event loop, then we don't start one here - start_thread = SDL_WasInit(SDL_INIT_JOYSTICK) == 0; - if (start_thread && SDL_Init(SDL_INIT_JOYSTICK) < 0) { - LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: {}", SDL_GetError()); - return; - } - has_gamecontroller = SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) != 0; - if (SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1") == SDL_FALSE) { - LOG_ERROR(Input, "Failed to set hint for background events with: {}", SDL_GetError()); - } - - SDL_AddEventWatch(&SDLEventWatcher, this); - - initialized = true; - if (start_thread) { - poll_thread = std::thread([this] { - using namespace std::chrono_literals; - while (initialized) { - SDL_PumpEvents(); - std::this_thread::sleep_for(1ms); - } - }); - } - // Because the events for joystick connection happens before we have our event watcher added, we - // can just open all the joysticks right here - for (int i = 0; i < SDL_NumJoysticks(); ++i) { - InitJoystick(i); - } -} - -SDLState::~SDLState() { - using namespace Input; - UnregisterFactory("sdl"); - UnregisterFactory("sdl"); - UnregisterFactory("sdl"); - UnregisterFactory("sdl"); - - CloseJoysticks(); - SDL_DelEventWatch(&SDLEventWatcher, this); - - initialized = false; - if (start_thread) { - poll_thread.join(); - SDL_QuitSubSystem(SDL_INIT_JOYSTICK); - } -} - -std::vector SDLState::GetInputDevices() { - std::scoped_lock lock(joystick_map_mutex); - std::vector devices; - std::unordered_map> joycon_pairs; - for (const auto& [key, value] : joystick_map) { - for (const auto& joystick : value) { - if (!joystick->GetSDLJoystick()) { - continue; - } - std::string name = - fmt::format("{} {}", joystick->GetControllerName(), joystick->GetPort()); - devices.emplace_back(Common::ParamPackage{ - {"class", "sdl"}, - {"display", std::move(name)}, - {"guid", joystick->GetGUID()}, - {"port", std::to_string(joystick->GetPort())}, - }); - if (joystick->IsJoyconLeft()) { - joycon_pairs.insert_or_assign(joystick->GetPort(), joystick); - } - } - } - - // Add dual controllers - for (const auto& [key, value] : joystick_map) { - for (const auto& joystick : value) { - if (joystick->IsJoyconRight()) { - if (!joycon_pairs.contains(joystick->GetPort())) { - continue; - } - const auto joystick2 = joycon_pairs.at(joystick->GetPort()); - - std::string name = - fmt::format("{} {}", "Nintendo Dual Joy-Con", joystick->GetPort()); - devices.emplace_back(Common::ParamPackage{ - {"class", "sdl"}, - {"display", std::move(name)}, - {"guid", joystick->GetGUID()}, - {"guid2", joystick2->GetGUID()}, - {"port", std::to_string(joystick->GetPort())}, - }); - } - } - } - return devices; -} - -namespace { -Common::ParamPackage BuildAnalogParamPackageForButton(int port, std::string guid, s32 axis, - float value = 0.1f) { - Common::ParamPackage params({{"engine", "sdl"}}); - params.Set("port", port); - params.Set("guid", std::move(guid)); - params.Set("axis", axis); - params.Set("threshold", "0.5"); - if (value > 0) { - params.Set("direction", "+"); - } else { - params.Set("direction", "-"); - } - return params; -} - -Common::ParamPackage BuildButtonParamPackageForButton(int port, std::string guid, s32 button) { - Common::ParamPackage params({{"engine", "sdl"}}); - params.Set("port", port); - params.Set("guid", std::move(guid)); - params.Set("button", button); - params.Set("toggle", false); - return params; -} - -Common::ParamPackage BuildHatParamPackageForButton(int port, std::string guid, s32 hat, s32 value) { - Common::ParamPackage params({{"engine", "sdl"}}); - - params.Set("port", port); - params.Set("guid", std::move(guid)); - params.Set("hat", hat); - switch (value) { - case SDL_HAT_UP: - params.Set("direction", "up"); - break; - case SDL_HAT_DOWN: - params.Set("direction", "down"); - break; - case SDL_HAT_LEFT: - params.Set("direction", "left"); - break; - case SDL_HAT_RIGHT: - params.Set("direction", "right"); - break; - default: - return {}; - } - return params; -} - -Common::ParamPackage BuildMotionParam(int port, std::string guid) { - Common::ParamPackage params({{"engine", "sdl"}, {"motion", "0"}}); - params.Set("port", port); - params.Set("guid", std::move(guid)); - return params; -} - -Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event) { - switch (event.type) { - case SDL_JOYAXISMOTION: { - if (const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which)) { - return BuildAnalogParamPackageForButton(joystick->GetPort(), joystick->GetGUID(), - static_cast(event.jaxis.axis), - event.jaxis.value); - } - break; - } - case SDL_JOYBUTTONUP: { - if (const auto joystick = state.GetSDLJoystickBySDLID(event.jbutton.which)) { - return BuildButtonParamPackageForButton(joystick->GetPort(), joystick->GetGUID(), - static_cast(event.jbutton.button)); - } - break; - } - case SDL_JOYHATMOTION: { - if (const auto joystick = state.GetSDLJoystickBySDLID(event.jhat.which)) { - return BuildHatParamPackageForButton(joystick->GetPort(), joystick->GetGUID(), - static_cast(event.jhat.hat), - static_cast(event.jhat.value)); - } - break; - } - } - return {}; -} - -Common::ParamPackage SDLEventToMotionParamPackage(SDLState& state, const SDL_Event& event) { - switch (event.type) { - case SDL_JOYAXISMOTION: { - if (const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which)) { - return BuildAnalogParamPackageForButton(joystick->GetPort(), joystick->GetGUID(), - static_cast(event.jaxis.axis), - event.jaxis.value); - } - break; - } - case SDL_JOYBUTTONUP: { - if (const auto joystick = state.GetSDLJoystickBySDLID(event.jbutton.which)) { - return BuildButtonParamPackageForButton(joystick->GetPort(), joystick->GetGUID(), - static_cast(event.jbutton.button)); - } - break; - } - case SDL_JOYHATMOTION: { - if (const auto joystick = state.GetSDLJoystickBySDLID(event.jhat.which)) { - return BuildHatParamPackageForButton(joystick->GetPort(), joystick->GetGUID(), - static_cast(event.jhat.hat), - static_cast(event.jhat.value)); - } - break; - } - case SDL_CONTROLLERSENSORUPDATE: { - bool is_motion_shaking = false; - constexpr float gyro_threshold = 5.0f; - constexpr float accel_threshold = 11.0f; - if (event.csensor.sensor == SDL_SENSOR_ACCEL) { - const Common::Vec3f acceleration = {-event.csensor.data[0], event.csensor.data[2], - -event.csensor.data[1]}; - if (acceleration.Length() > accel_threshold) { - is_motion_shaking = true; - } - } - - if (event.csensor.sensor == SDL_SENSOR_GYRO) { - const Common::Vec3f gyroscope = {event.csensor.data[0], -event.csensor.data[2], - event.csensor.data[1]}; - if (gyroscope.Length() > gyro_threshold) { - is_motion_shaking = true; - } - } - - if (!is_motion_shaking) { - break; - } - - if (const auto joystick = state.GetSDLJoystickBySDLID(event.csensor.which)) { - return BuildMotionParam(joystick->GetPort(), joystick->GetGUID()); - } - break; - } - } - return {}; -} - -Common::ParamPackage BuildParamPackageForBinding(int port, const std::string& guid, - const SDL_GameControllerButtonBind& binding) { - switch (binding.bindType) { - case SDL_CONTROLLER_BINDTYPE_NONE: - break; - case SDL_CONTROLLER_BINDTYPE_AXIS: - return BuildAnalogParamPackageForButton(port, guid, binding.value.axis); - case SDL_CONTROLLER_BINDTYPE_BUTTON: - return BuildButtonParamPackageForButton(port, guid, binding.value.button); - case SDL_CONTROLLER_BINDTYPE_HAT: - return BuildHatParamPackageForButton(port, guid, binding.value.hat.hat, - binding.value.hat.hat_mask); - } - return {}; -} - -Common::ParamPackage BuildParamPackageForAnalog(int port, const std::string& guid, int axis_x, - int axis_y, float offset_x, float offset_y) { - Common::ParamPackage params; - params.Set("engine", "sdl"); - params.Set("port", port); - params.Set("guid", guid); - params.Set("axis_x", axis_x); - params.Set("axis_y", axis_y); - params.Set("offset_x", offset_x); - params.Set("offset_y", offset_y); - params.Set("invert_x", "+"); - params.Set("invert_y", "+"); - return params; -} -} // Anonymous namespace - -ButtonMapping SDLState::GetButtonMappingForDevice(const Common::ParamPackage& params) { - if (!params.Has("guid") || !params.Has("port")) { - return {}; - } - const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0)); - - auto* controller = joystick->GetSDLGameController(); - if (controller == nullptr) { - return {}; - } - - // This list is missing ZL/ZR since those are not considered buttons in SDL GameController. - // We will add those afterwards - // This list also excludes Screenshot since theres not really a mapping for that - ButtonBindings switch_to_sdl_button; - - if (SDL_GameControllerGetType(controller) == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO) { - switch_to_sdl_button = GetNintendoButtonBinding(joystick); - } else { - switch_to_sdl_button = GetDefaultButtonBinding(); - } - - // Add the missing bindings for ZL/ZR - static constexpr ZButtonBindings switch_to_sdl_axis{{ - {Settings::NativeButton::ZL, SDL_CONTROLLER_AXIS_TRIGGERLEFT}, - {Settings::NativeButton::ZR, SDL_CONTROLLER_AXIS_TRIGGERRIGHT}, - }}; - - // Parameters contain two joysticks return dual - if (params.Has("guid2")) { - const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0)); - - if (joystick2->GetSDLGameController() != nullptr) { - return GetDualControllerMapping(joystick, joystick2, switch_to_sdl_button, - switch_to_sdl_axis); - } - } - - return GetSingleControllerMapping(joystick, switch_to_sdl_button, switch_to_sdl_axis); -} - -ButtonBindings SDLState::GetDefaultButtonBinding() const { - return { - std::pair{Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_B}, - {Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_A}, - {Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_Y}, - {Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_X}, - {Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK}, - {Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK}, - {Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, - {Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, - {Settings::NativeButton::Plus, SDL_CONTROLLER_BUTTON_START}, - {Settings::NativeButton::Minus, SDL_CONTROLLER_BUTTON_BACK}, - {Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT}, - {Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP}, - {Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT}, - {Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN}, - {Settings::NativeButton::SL, SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, - {Settings::NativeButton::SR, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, - {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE}, - }; -} - -ButtonBindings SDLState::GetNintendoButtonBinding( - const std::shared_ptr& joystick) const { - // Default SL/SR mapping for pro controllers - auto sl_button = SDL_CONTROLLER_BUTTON_LEFTSHOULDER; - auto sr_button = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER; - - if (joystick->IsJoyconLeft()) { - sl_button = SDL_CONTROLLER_BUTTON_PADDLE2; - sr_button = SDL_CONTROLLER_BUTTON_PADDLE4; - } - if (joystick->IsJoyconRight()) { - sl_button = SDL_CONTROLLER_BUTTON_PADDLE3; - sr_button = SDL_CONTROLLER_BUTTON_PADDLE1; - } - - return { - std::pair{Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_A}, - {Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_B}, - {Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_X}, - {Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_Y}, - {Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK}, - {Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK}, - {Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, - {Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, - {Settings::NativeButton::Plus, SDL_CONTROLLER_BUTTON_START}, - {Settings::NativeButton::Minus, SDL_CONTROLLER_BUTTON_BACK}, - {Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT}, - {Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP}, - {Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT}, - {Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN}, - {Settings::NativeButton::SL, sl_button}, - {Settings::NativeButton::SR, sr_button}, - {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE}, - }; -} - -ButtonMapping SDLState::GetSingleControllerMapping( - const std::shared_ptr& joystick, const ButtonBindings& switch_to_sdl_button, - const ZButtonBindings& switch_to_sdl_axis) const { - ButtonMapping mapping; - mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size()); - auto* controller = joystick->GetSDLGameController(); - - for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) { - const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button); - mapping.insert_or_assign( - switch_button, - BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); - } - for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) { - const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis); - mapping.insert_or_assign( - switch_button, - BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); - } - - return mapping; -} - -ButtonMapping SDLState::GetDualControllerMapping(const std::shared_ptr& joystick, - const std::shared_ptr& joystick2, - const ButtonBindings& switch_to_sdl_button, - const ZButtonBindings& switch_to_sdl_axis) const { - ButtonMapping mapping; - mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size()); - auto* controller = joystick->GetSDLGameController(); - auto* controller2 = joystick2->GetSDLGameController(); - - for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) { - if (IsButtonOnLeftSide(switch_button)) { - const auto& binding = SDL_GameControllerGetBindForButton(controller2, sdl_button); - mapping.insert_or_assign( - switch_button, - BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), binding)); - continue; - } - const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button); - mapping.insert_or_assign( - switch_button, - BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); - } - for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) { - if (IsButtonOnLeftSide(switch_button)) { - const auto& binding = SDL_GameControllerGetBindForAxis(controller2, sdl_axis); - mapping.insert_or_assign( - switch_button, - BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), binding)); - continue; - } - const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis); - mapping.insert_or_assign( - switch_button, - BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); - } - - return mapping; -} - -bool SDLState::IsButtonOnLeftSide(Settings::NativeButton::Values button) const { - switch (button) { - case Settings::NativeButton::DDown: - case Settings::NativeButton::DLeft: - case Settings::NativeButton::DRight: - case Settings::NativeButton::DUp: - case Settings::NativeButton::L: - case Settings::NativeButton::LStick: - case Settings::NativeButton::Minus: - case Settings::NativeButton::Screenshot: - case Settings::NativeButton::ZL: - return true; - default: - return false; - } -} - -AnalogMapping SDLState::GetAnalogMappingForDevice(const Common::ParamPackage& params) { - if (!params.Has("guid") || !params.Has("port")) { - return {}; - } - const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0)); - const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0)); - auto* controller = joystick->GetSDLGameController(); - if (controller == nullptr) { - return {}; - } - - AnalogMapping mapping = {}; - const auto& binding_left_x = - SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX); - const auto& binding_left_y = - SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY); - if (params.Has("guid2")) { - joystick2->PreSetAxis(binding_left_x.value.axis); - joystick2->PreSetAxis(binding_left_y.value.axis); - const auto left_offset_x = -joystick2->GetAxis(binding_left_x.value.axis, 1.0f, 0); - const auto left_offset_y = -joystick2->GetAxis(binding_left_y.value.axis, 1.0f, 0); - mapping.insert_or_assign( - Settings::NativeAnalog::LStick, - BuildParamPackageForAnalog(joystick2->GetPort(), joystick2->GetGUID(), - binding_left_x.value.axis, binding_left_y.value.axis, - left_offset_x, left_offset_y)); - } else { - joystick->PreSetAxis(binding_left_x.value.axis); - joystick->PreSetAxis(binding_left_y.value.axis); - const auto left_offset_x = -joystick->GetAxis(binding_left_x.value.axis, 1.0f, 0); - const auto left_offset_y = -joystick->GetAxis(binding_left_y.value.axis, 1.0f, 0); - mapping.insert_or_assign( - Settings::NativeAnalog::LStick, - BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(), - binding_left_x.value.axis, binding_left_y.value.axis, - left_offset_x, left_offset_y)); - } - const auto& binding_right_x = - SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX); - const auto& binding_right_y = - SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY); - joystick->PreSetAxis(binding_right_x.value.axis); - joystick->PreSetAxis(binding_right_y.value.axis); - const auto right_offset_x = -joystick->GetAxis(binding_right_x.value.axis, 1.0f, 0); - const auto right_offset_y = -joystick->GetAxis(binding_right_y.value.axis, 1.0f, 0); - mapping.insert_or_assign(Settings::NativeAnalog::RStick, - BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(), - binding_right_x.value.axis, - binding_right_y.value.axis, right_offset_x, - right_offset_y)); - return mapping; -} - -MotionMapping SDLState::GetMotionMappingForDevice(const Common::ParamPackage& params) { - if (!params.Has("guid") || !params.Has("port")) { - return {}; - } - const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0)); - const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0)); - auto* controller = joystick->GetSDLGameController(); - if (controller == nullptr) { - return {}; - } - - MotionMapping mapping = {}; - joystick->EnableMotion(); - - if (joystick->HasGyro() || joystick->HasAccel()) { - mapping.insert_or_assign(Settings::NativeMotion::MotionRight, - BuildMotionParam(joystick->GetPort(), joystick->GetGUID())); - } - if (params.Has("guid2")) { - joystick2->EnableMotion(); - if (joystick2->HasGyro() || joystick2->HasAccel()) { - mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, - BuildMotionParam(joystick2->GetPort(), joystick2->GetGUID())); - } - } else { - if (joystick->HasGyro() || joystick->HasAccel()) { - mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, - BuildMotionParam(joystick->GetPort(), joystick->GetGUID())); - } - } - - return mapping; -} -namespace Polling { -class SDLPoller : public InputCommon::Polling::DevicePoller { -public: - explicit SDLPoller(SDLState& state_) : state(state_) {} - - void Start([[maybe_unused]] const std::string& device_id) override { - state.event_queue.Clear(); - state.polling = true; - } - - void Stop() override { - state.polling = false; - } - -protected: - SDLState& state; -}; - -class SDLButtonPoller final : public SDLPoller { -public: - explicit SDLButtonPoller(SDLState& state_) : SDLPoller(state_) {} - - Common::ParamPackage GetNextInput() override { - SDL_Event event; - while (state.event_queue.Pop(event)) { - const auto package = FromEvent(event); - if (package) { - return *package; - } - } - return {}; - } - [[nodiscard]] std::optional FromEvent(SDL_Event& event) { - switch (event.type) { - case SDL_JOYAXISMOTION: - if (!axis_memory.count(event.jaxis.which) || - !axis_memory[event.jaxis.which].count(event.jaxis.axis)) { - axis_memory[event.jaxis.which][event.jaxis.axis] = event.jaxis.value; - axis_event_count[event.jaxis.which][event.jaxis.axis] = 1; - break; - } else { - axis_event_count[event.jaxis.which][event.jaxis.axis]++; - // The joystick and axis exist in our map if we take this branch, so no checks - // needed - if (std::abs( - (event.jaxis.value - axis_memory[event.jaxis.which][event.jaxis.axis]) / - 32767.0) < 0.5) { - break; - } else { - if (axis_event_count[event.jaxis.which][event.jaxis.axis] == 2 && - IsAxisAtPole(event.jaxis.value) && - IsAxisAtPole(axis_memory[event.jaxis.which][event.jaxis.axis])) { - // If we have exactly two events and both are near a pole, this is - // likely a digital input masquerading as an analog axis; Instead of - // trying to look at the direction the axis travelled, assume the first - // event was press and the second was release; This should handle most - // digital axes while deferring to the direction of travel for analog - // axes - event.jaxis.value = static_cast( - std::copysign(32767, axis_memory[event.jaxis.which][event.jaxis.axis])); - } else { - // There are more than two events, so this is likely a true analog axis, - // check the direction it travelled - event.jaxis.value = static_cast(std::copysign( - 32767, - event.jaxis.value - axis_memory[event.jaxis.which][event.jaxis.axis])); - } - axis_memory.clear(); - axis_event_count.clear(); - } - } - [[fallthrough]]; - case SDL_JOYBUTTONUP: - case SDL_JOYHATMOTION: - return {SDLEventToButtonParamPackage(state, event)}; - } - return std::nullopt; - } - -private: - // Determine whether an axis value is close to an extreme or center - // Some controllers have a digital D-Pad as a pair of analog sticks, with 3 possible values per - // axis, which is why the center must be considered a pole - bool IsAxisAtPole(int16_t value) const { - return std::abs(value) >= 32767 || std::abs(value) < 327; - } - std::unordered_map> axis_memory; - std::unordered_map> axis_event_count; -}; - -class SDLMotionPoller final : public SDLPoller { -public: - explicit SDLMotionPoller(SDLState& state_) : SDLPoller(state_) {} - - Common::ParamPackage GetNextInput() override { - SDL_Event event; - while (state.event_queue.Pop(event)) { - const auto package = FromEvent(event); - if (package) { - return *package; - } - } - return {}; - } - [[nodiscard]] std::optional FromEvent(const SDL_Event& event) const { - switch (event.type) { - case SDL_JOYAXISMOTION: - if (std::abs(event.jaxis.value / 32767.0) < 0.5) { - break; - } - [[fallthrough]]; - case SDL_JOYBUTTONUP: - case SDL_JOYHATMOTION: - case SDL_CONTROLLERSENSORUPDATE: - return {SDLEventToMotionParamPackage(state, event)}; - } - return std::nullopt; - } -}; - -/** - * Attempts to match the press to a controller joy axis (left/right stick) and if a match - * isn't found, checks if the event matches anything from SDLButtonPoller and uses that - * instead - */ -class SDLAnalogPreferredPoller final : public SDLPoller { -public: - explicit SDLAnalogPreferredPoller(SDLState& state_) - : SDLPoller(state_), button_poller(state_) {} - - void Start(const std::string& device_id) override { - SDLPoller::Start(device_id); - // Reset stored axes - first_axis = -1; - } - - Common::ParamPackage GetNextInput() override { - SDL_Event event; - while (state.event_queue.Pop(event)) { - if (event.type != SDL_JOYAXISMOTION) { - // Check for a button press - auto button_press = button_poller.FromEvent(event); - if (button_press) { - return *button_press; - } - continue; - } - const auto axis = event.jaxis.axis; - - // Filter out axis events that are below a threshold - if (std::abs(event.jaxis.value / 32767.0) < 0.5) { - continue; - } - - // Filter out axis events that are the same - if (first_axis == axis) { - continue; - } - - // In order to return a complete analog param, we need inputs for both axes. - // If the first axis isn't set we set the value then wait till next event - if (first_axis == -1) { - first_axis = axis; - continue; - } - - if (const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which)) { - // Set offset to zero since the joystick is not on center - auto params = BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(), - first_axis, axis, 0, 0); - first_axis = -1; - return params; - } - } - return {}; - } - -private: - int first_axis = -1; - SDLButtonPoller button_poller; -}; -} // namespace Polling - -SDLState::Pollers SDLState::GetPollers(InputCommon::Polling::DeviceType type) { - Pollers pollers; - - switch (type) { - case InputCommon::Polling::DeviceType::AnalogPreferred: - pollers.emplace_back(std::make_unique(*this)); - break; - case InputCommon::Polling::DeviceType::Button: - pollers.emplace_back(std::make_unique(*this)); - break; - case InputCommon::Polling::DeviceType::Motion: - pollers.emplace_back(std::make_unique(*this)); - break; - } - - return pollers; -} - -} // namespace InputCommon::SDL diff --git a/src/input_common/tas/tas_input.cpp b/src/input_common/tas/tas_input.cpp deleted file mode 100644 index 1598092b61..0000000000 --- a/src/input_common/tas/tas_input.cpp +++ /dev/null @@ -1,455 +0,0 @@ -// Copyright 2021 yuzu Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#include -#include - -#include "common/fs/file.h" -#include "common/fs/fs_types.h" -#include "common/fs/path_util.h" -#include "common/logging/log.h" -#include "common/settings.h" -#include "input_common/tas/tas_input.h" - -namespace TasInput { - -// Supported keywords and buttons from a TAS file -constexpr std::array, 20> text_to_tas_button = { - std::pair{"KEY_A", TasButton::BUTTON_A}, - {"KEY_B", TasButton::BUTTON_B}, - {"KEY_X", TasButton::BUTTON_X}, - {"KEY_Y", TasButton::BUTTON_Y}, - {"KEY_LSTICK", TasButton::STICK_L}, - {"KEY_RSTICK", TasButton::STICK_R}, - {"KEY_L", TasButton::TRIGGER_L}, - {"KEY_R", TasButton::TRIGGER_R}, - {"KEY_PLUS", TasButton::BUTTON_PLUS}, - {"KEY_MINUS", TasButton::BUTTON_MINUS}, - {"KEY_DLEFT", TasButton::BUTTON_LEFT}, - {"KEY_DUP", TasButton::BUTTON_UP}, - {"KEY_DRIGHT", TasButton::BUTTON_RIGHT}, - {"KEY_DDOWN", TasButton::BUTTON_DOWN}, - {"KEY_SL", TasButton::BUTTON_SL}, - {"KEY_SR", TasButton::BUTTON_SR}, - {"KEY_CAPTURE", TasButton::BUTTON_CAPTURE}, - {"KEY_HOME", TasButton::BUTTON_HOME}, - {"KEY_ZL", TasButton::TRIGGER_ZL}, - {"KEY_ZR", TasButton::TRIGGER_ZR}, -}; - -Tas::Tas() { - if (!Settings::values.tas_enable) { - needs_reset = true; - return; - } - LoadTasFiles(); -} - -Tas::~Tas() { - Stop(); -}; - -void Tas::LoadTasFiles() { - script_length = 0; - for (size_t i = 0; i < commands.size(); i++) { - LoadTasFile(i); - if (commands[i].size() > script_length) { - script_length = commands[i].size(); - } - } -} - -void Tas::LoadTasFile(size_t player_index) { - if (!commands[player_index].empty()) { - commands[player_index].clear(); - } - std::string file = - Common::FS::ReadStringFromFile(Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) / - fmt::format("script0-{}.txt", player_index + 1), - Common::FS::FileType::BinaryFile); - std::stringstream command_line(file); - std::string line; - int frame_no = 0; - while (std::getline(command_line, line, '\n')) { - if (line.empty()) { - continue; - } - LOG_DEBUG(Input, "Loading line: {}", line); - std::smatch m; - - std::stringstream linestream(line); - std::string segment; - std::vector seglist; - - while (std::getline(linestream, segment, ' ')) { - seglist.push_back(segment); - } - - if (seglist.size() < 4) { - continue; - } - - while (frame_no < std::stoi(seglist.at(0))) { - commands[player_index].push_back({}); - frame_no++; - } - - TASCommand command = { - .buttons = ReadCommandButtons(seglist.at(1)), - .l_axis = ReadCommandAxis(seglist.at(2)), - .r_axis = ReadCommandAxis(seglist.at(3)), - }; - commands[player_index].push_back(command); - frame_no++; - } - LOG_INFO(Input, "TAS file loaded! {} frames", frame_no); -} - -void Tas::WriteTasFile(std::u8string file_name) { - std::string output_text; - for (size_t frame = 0; frame < record_commands.size(); frame++) { - if (!output_text.empty()) { - output_text += "\n"; - } - const TASCommand& line = record_commands[frame]; - output_text += std::to_string(frame) + " " + WriteCommandButtons(line.buttons) + " " + - WriteCommandAxis(line.l_axis) + " " + WriteCommandAxis(line.r_axis); - } - const auto bytes_written = Common::FS::WriteStringToFile( - Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) / file_name, - Common::FS::FileType::TextFile, output_text); - if (bytes_written == output_text.size()) { - LOG_INFO(Input, "TAS file written to file!"); - } else { - LOG_ERROR(Input, "Writing the TAS-file has failed! {} / {} bytes written", bytes_written, - output_text.size()); - } -} - -std::pair Tas::FlipAxisY(std::pair old) { - auto [x, y] = old; - return {x, -y}; -} - -void Tas::RecordInput(u32 buttons, const std::array, 2>& axes) { - last_input = {buttons, FlipAxisY(axes[0]), FlipAxisY(axes[1])}; -} - -std::tuple Tas::GetStatus() const { - TasState state; - if (is_recording) { - return {TasState::Recording, 0, record_commands.size()}; - } - - if (is_running) { - state = TasState::Running; - } else { - state = TasState::Stopped; - } - - return {state, current_command, script_length}; -} - -std::string Tas::DebugButtons(u32 buttons) const { - return fmt::format("{{ {} }}", TasInput::Tas::ButtonsToString(buttons)); -} - -std::string Tas::DebugJoystick(float x, float y) const { - return fmt::format("[ {} , {} ]", std::to_string(x), std::to_string(y)); -} - -std::string Tas::DebugInput(const TasData& data) const { - return fmt::format("{{ {} , {} , {} }}", DebugButtons(data.buttons), - DebugJoystick(data.axis[0], data.axis[1]), - DebugJoystick(data.axis[2], data.axis[3])); -} - -std::string Tas::DebugInputs(const std::array& arr) const { - std::string returns = "[ "; - for (size_t i = 0; i < arr.size(); i++) { - returns += DebugInput(arr[i]); - if (i != arr.size() - 1) { - returns += " , "; - } - } - return returns + "]"; -} - -std::string Tas::ButtonsToString(u32 button) const { - std::string returns; - for (auto [text_button, tas_button] : text_to_tas_button) { - if ((button & static_cast(tas_button)) != 0) - returns += fmt::format(", {}", text_button.substr(4)); - } - return returns.empty() ? "" : returns.substr(2); -} - -void Tas::UpdateThread() { - if (!Settings::values.tas_enable) { - if (is_running) { - Stop(); - } - return; - } - - if (is_recording) { - record_commands.push_back(last_input); - } - if (needs_reset) { - current_command = 0; - needs_reset = false; - LoadTasFiles(); - LOG_DEBUG(Input, "tas_reset done"); - } - - if (!is_running) { - tas_data.fill({}); - return; - } - if (current_command < script_length) { - LOG_DEBUG(Input, "Playing TAS {}/{}", current_command, script_length); - size_t frame = current_command++; - for (size_t i = 0; i < commands.size(); i++) { - if (frame < commands[i].size()) { - TASCommand command = commands[i][frame]; - tas_data[i].buttons = command.buttons; - auto [l_axis_x, l_axis_y] = command.l_axis; - tas_data[i].axis[0] = l_axis_x; - tas_data[i].axis[1] = l_axis_y; - auto [r_axis_x, r_axis_y] = command.r_axis; - tas_data[i].axis[2] = r_axis_x; - tas_data[i].axis[3] = r_axis_y; - } else { - tas_data[i] = {}; - } - } - } else { - is_running = Settings::values.tas_loop.GetValue(); - current_command = 0; - tas_data.fill({}); - if (!is_running) { - SwapToStoredController(); - } - } - LOG_DEBUG(Input, "TAS inputs: {}", DebugInputs(tas_data)); -} - -TasAnalog Tas::ReadCommandAxis(const std::string& line) const { - std::stringstream linestream(line); - std::string segment; - std::vector seglist; - - while (std::getline(linestream, segment, ';')) { - seglist.push_back(segment); - } - - const float x = std::stof(seglist.at(0)) / 32767.0f; - const float y = std::stof(seglist.at(1)) / 32767.0f; - - return {x, y}; -} - -u32 Tas::ReadCommandButtons(const std::string& data) const { - std::stringstream button_text(data); - std::string line; - u32 buttons = 0; - while (std::getline(button_text, line, ';')) { - for (auto [text, tas_button] : text_to_tas_button) { - if (text == line) { - buttons |= static_cast(tas_button); - break; - } - } - } - return buttons; -} - -std::string Tas::WriteCommandAxis(TasAnalog data) const { - auto [x, y] = data; - std::string line; - line += std::to_string(static_cast(x * 32767)); - line += ";"; - line += std::to_string(static_cast(y * 32767)); - return line; -} - -std::string Tas::WriteCommandButtons(u32 data) const { - if (data == 0) { - return "NONE"; - } - - std::string line; - u32 index = 0; - while (data > 0) { - if ((data & 1) == 1) { - for (auto [text, tas_button] : text_to_tas_button) { - if (tas_button == static_cast(1 << index)) { - if (line.size() > 0) { - line += ";"; - } - line += text; - break; - } - } - } - index++; - data >>= 1; - } - return line; -} - -void Tas::StartStop() { - if (!Settings::values.tas_enable) { - return; - } - if (is_running) { - Stop(); - } else { - is_running = true; - SwapToTasController(); - } -} - -void Tas::Stop() { - is_running = false; - SwapToStoredController(); -} - -void Tas::SwapToTasController() { - if (!Settings::values.tas_swap_controllers) { - return; - } - auto& players = Settings::values.players.GetValue(); - for (std::size_t index = 0; index < players.size(); index++) { - auto& player = players[index]; - player_mappings[index] = player; - - // Only swap active controllers - if (!player.connected) { - continue; - } - - Common::ParamPackage tas_param; - tas_param.Set("pad", static_cast(index)); - auto button_mapping = GetButtonMappingForDevice(tas_param); - auto analog_mapping = GetAnalogMappingForDevice(tas_param); - auto& buttons = player.buttons; - auto& analogs = player.analogs; - - for (std::size_t i = 0; i < buttons.size(); ++i) { - buttons[i] = button_mapping[static_cast(i)].Serialize(); - } - for (std::size_t i = 0; i < analogs.size(); ++i) { - analogs[i] = analog_mapping[static_cast(i)].Serialize(); - } - } - is_old_input_saved = true; - Settings::values.is_device_reload_pending.store(true); -} - -void Tas::SwapToStoredController() { - if (!is_old_input_saved) { - return; - } - auto& players = Settings::values.players.GetValue(); - for (std::size_t index = 0; index < players.size(); index++) { - players[index] = player_mappings[index]; - } - is_old_input_saved = false; - Settings::values.is_device_reload_pending.store(true); -} - -void Tas::Reset() { - if (!Settings::values.tas_enable) { - return; - } - needs_reset = true; -} - -bool Tas::Record() { - if (!Settings::values.tas_enable) { - return true; - } - is_recording = !is_recording; - return is_recording; -} - -void Tas::SaveRecording(bool overwrite_file) { - if (is_recording) { - return; - } - if (record_commands.empty()) { - return; - } - WriteTasFile(u8"record.txt"); - if (overwrite_file) { - WriteTasFile(u8"script0-1.txt"); - } - needs_reset = true; - record_commands.clear(); -} - -InputCommon::ButtonMapping Tas::GetButtonMappingForDevice( - const Common::ParamPackage& params) const { - // This list is missing ZL/ZR since those are not considered buttons. - // We will add those afterwards - // This list also excludes any button that can't be really mapped - static constexpr std::array, 20> - switch_to_tas_button = { - std::pair{Settings::NativeButton::A, TasButton::BUTTON_A}, - {Settings::NativeButton::B, TasButton::BUTTON_B}, - {Settings::NativeButton::X, TasButton::BUTTON_X}, - {Settings::NativeButton::Y, TasButton::BUTTON_Y}, - {Settings::NativeButton::LStick, TasButton::STICK_L}, - {Settings::NativeButton::RStick, TasButton::STICK_R}, - {Settings::NativeButton::L, TasButton::TRIGGER_L}, - {Settings::NativeButton::R, TasButton::TRIGGER_R}, - {Settings::NativeButton::Plus, TasButton::BUTTON_PLUS}, - {Settings::NativeButton::Minus, TasButton::BUTTON_MINUS}, - {Settings::NativeButton::DLeft, TasButton::BUTTON_LEFT}, - {Settings::NativeButton::DUp, TasButton::BUTTON_UP}, - {Settings::NativeButton::DRight, TasButton::BUTTON_RIGHT}, - {Settings::NativeButton::DDown, TasButton::BUTTON_DOWN}, - {Settings::NativeButton::SL, TasButton::BUTTON_SL}, - {Settings::NativeButton::SR, TasButton::BUTTON_SR}, - {Settings::NativeButton::Screenshot, TasButton::BUTTON_CAPTURE}, - {Settings::NativeButton::Home, TasButton::BUTTON_HOME}, - {Settings::NativeButton::ZL, TasButton::TRIGGER_ZL}, - {Settings::NativeButton::ZR, TasButton::TRIGGER_ZR}, - }; - - InputCommon::ButtonMapping mapping{}; - for (const auto& [switch_button, tas_button] : switch_to_tas_button) { - Common::ParamPackage button_params({{"engine", "tas"}}); - button_params.Set("pad", params.Get("pad", 0)); - button_params.Set("button", static_cast(tas_button)); - mapping.insert_or_assign(switch_button, std::move(button_params)); - } - - return mapping; -} - -InputCommon::AnalogMapping Tas::GetAnalogMappingForDevice( - const Common::ParamPackage& params) const { - - InputCommon::AnalogMapping mapping = {}; - Common::ParamPackage left_analog_params; - left_analog_params.Set("engine", "tas"); - left_analog_params.Set("pad", params.Get("pad", 0)); - left_analog_params.Set("axis_x", static_cast(TasAxes::StickX)); - left_analog_params.Set("axis_y", static_cast(TasAxes::StickY)); - mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params)); - Common::ParamPackage right_analog_params; - right_analog_params.Set("engine", "tas"); - right_analog_params.Set("pad", params.Get("pad", 0)); - right_analog_params.Set("axis_x", static_cast(TasAxes::SubstickX)); - right_analog_params.Set("axis_y", static_cast(TasAxes::SubstickY)); - mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params)); - return mapping; -} - -const TasData& Tas::GetTasState(std::size_t pad) const { - return tas_data[pad]; -} -} // namespace TasInput diff --git a/src/input_common/tas/tas_poller.cpp b/src/input_common/tas/tas_poller.cpp deleted file mode 100644 index 15810d6b0d..0000000000 --- a/src/input_common/tas/tas_poller.cpp +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2021 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include - -#include "common/settings.h" -#include "common/threadsafe_queue.h" -#include "input_common/tas/tas_input.h" -#include "input_common/tas/tas_poller.h" - -namespace InputCommon { - -class TasButton final : public Input::ButtonDevice { -public: - explicit TasButton(u32 button_, u32 pad_, const TasInput::Tas* tas_input_) - : button(button_), pad(pad_), tas_input(tas_input_) {} - - bool GetStatus() const override { - return (tas_input->GetTasState(pad).buttons & button) != 0; - } - -private: - const u32 button; - const u32 pad; - const TasInput::Tas* tas_input; -}; - -TasButtonFactory::TasButtonFactory(std::shared_ptr tas_input_) - : tas_input(std::move(tas_input_)) {} - -std::unique_ptr TasButtonFactory::Create(const Common::ParamPackage& params) { - const auto button_id = params.Get("button", 0); - const auto pad = params.Get("pad", 0); - - return std::make_unique(button_id, pad, tas_input.get()); -} - -class TasAnalog final : public Input::AnalogDevice { -public: - explicit TasAnalog(u32 pad_, u32 axis_x_, u32 axis_y_, const TasInput::Tas* tas_input_) - : pad(pad_), axis_x(axis_x_), axis_y(axis_y_), tas_input(tas_input_) {} - - float GetAxis(u32 axis) const { - std::lock_guard lock{mutex}; - return tas_input->GetTasState(pad).axis.at(axis); - } - - std::pair GetAnalog(u32 analog_axis_x, u32 analog_axis_y) const { - float x = GetAxis(analog_axis_x); - float y = GetAxis(analog_axis_y); - - // Make sure the coordinates are in the unit circle, - // otherwise normalize it. - float r = x * x + y * y; - if (r > 1.0f) { - r = std::sqrt(r); - x /= r; - y /= r; - } - - return {x, y}; - } - - std::tuple GetStatus() const override { - return GetAnalog(axis_x, axis_y); - } - - Input::AnalogProperties GetAnalogProperties() const override { - return {0.0f, 1.0f, 0.5f}; - } - -private: - const u32 pad; - const u32 axis_x; - const u32 axis_y; - const TasInput::Tas* tas_input; - mutable std::mutex mutex; -}; - -/// An analog device factory that creates analog devices from GC Adapter -TasAnalogFactory::TasAnalogFactory(std::shared_ptr tas_input_) - : tas_input(std::move(tas_input_)) {} - -/** - * Creates analog device from joystick axes - * @param params contains parameters for creating the device: - * - "port": the nth gcpad on the adapter - * - "axis_x": the index of the axis to be bind as x-axis - * - "axis_y": the index of the axis to be bind as y-axis - */ -std::unique_ptr TasAnalogFactory::Create(const Common::ParamPackage& params) { - const auto pad = static_cast(params.Get("pad", 0)); - const auto axis_x = static_cast(params.Get("axis_x", 0)); - const auto axis_y = static_cast(params.Get("axis_y", 1)); - - return std::make_unique(pad, axis_x, axis_y, tas_input.get()); -} - -} // namespace InputCommon diff --git a/src/input_common/tas/tas_poller.h b/src/input_common/tas/tas_poller.h deleted file mode 100644 index 09e426cef5..0000000000 --- a/src/input_common/tas/tas_poller.h +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2021 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include "core/frontend/input.h" -#include "input_common/tas/tas_input.h" - -namespace InputCommon { - -/** - * A button device factory representing a tas bot. It receives tas events and forward them - * to all button devices it created. - */ -class TasButtonFactory final : public Input::Factory { -public: - explicit TasButtonFactory(std::shared_ptr tas_input_); - - /** - * Creates a button device from a button press - * @param params contains parameters for creating the device: - * - "code": the code of the key to bind with the button - */ - std::unique_ptr Create(const Common::ParamPackage& params) override; - -private: - std::shared_ptr tas_input; -}; - -/// An analog device factory that creates analog devices from tas -class TasAnalogFactory final : public Input::Factory { -public: - explicit TasAnalogFactory(std::shared_ptr tas_input_); - - std::unique_ptr Create(const Common::ParamPackage& params) override; - -private: - std::shared_ptr tas_input; -}; - -} // namespace InputCommon diff --git a/src/input_common/touch_from_button.cpp b/src/input_common/touch_from_button.cpp deleted file mode 100644 index 7878a56d77..0000000000 --- a/src/input_common/touch_from_button.cpp +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2020 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include "common/settings.h" -#include "core/frontend/framebuffer_layout.h" -#include "input_common/touch_from_button.h" - -namespace InputCommon { - -class TouchFromButtonDevice final : public Input::TouchDevice { -public: - TouchFromButtonDevice() { - const auto button_index = - static_cast(Settings::values.touch_from_button_map_index.GetValue()); - const auto& buttons = Settings::values.touch_from_button_maps[button_index].buttons; - - for (const auto& config_entry : buttons) { - const Common::ParamPackage package{config_entry}; - map.emplace_back( - Input::CreateDevice(config_entry), - std::clamp(package.Get("x", 0), 0, static_cast(Layout::ScreenUndocked::Width)), - std::clamp(package.Get("y", 0), 0, - static_cast(Layout::ScreenUndocked::Height))); - } - } - - Input::TouchStatus GetStatus() const override { - Input::TouchStatus touch_status{}; - for (std::size_t id = 0; id < map.size() && id < touch_status.size(); ++id) { - const bool state = std::get<0>(map[id])->GetStatus(); - if (state) { - const float x = static_cast(std::get<1>(map[id])) / - static_cast(Layout::ScreenUndocked::Width); - const float y = static_cast(std::get<2>(map[id])) / - static_cast(Layout::ScreenUndocked::Height); - touch_status[id] = {x, y, true}; - } - } - return touch_status; - } - -private: - // A vector of the mapped button, its x and its y-coordinate - std::vector, int, int>> map; -}; - -std::unique_ptr TouchFromButtonFactory::Create(const Common::ParamPackage&) { - return std::make_unique(); -} - -} // namespace InputCommon diff --git a/src/input_common/udp/client.cpp b/src/input_common/udp/client.cpp deleted file mode 100644 index b9512aa2e9..0000000000 --- a/src/input_common/udp/client.cpp +++ /dev/null @@ -1,526 +0,0 @@ -// Copyright 2018 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include -#include -#include -#include -#include -#include "common/logging/log.h" -#include "common/settings.h" -#include "input_common/udp/client.h" -#include "input_common/udp/protocol.h" - -using boost::asio::ip::udp; - -namespace InputCommon::CemuhookUDP { - -struct SocketCallback { - std::function version; - std::function port_info; - std::function pad_data; -}; - -class Socket { -public: - using clock = std::chrono::system_clock; - - explicit Socket(const std::string& host, u16 port, SocketCallback callback_) - : callback(std::move(callback_)), timer(io_service), - socket(io_service, udp::endpoint(udp::v4(), 0)), client_id(GenerateRandomClientId()) { - boost::system::error_code ec{}; - auto ipv4 = boost::asio::ip::make_address_v4(host, ec); - if (ec.value() != boost::system::errc::success) { - LOG_ERROR(Input, "Invalid IPv4 address \"{}\" provided to socket", host); - ipv4 = boost::asio::ip::address_v4{}; - } - - send_endpoint = {udp::endpoint(ipv4, port)}; - } - - void Stop() { - io_service.stop(); - } - - void Loop() { - io_service.run(); - } - - void StartSend(const clock::time_point& from) { - timer.expires_at(from + std::chrono::seconds(3)); - timer.async_wait([this](const boost::system::error_code& error) { HandleSend(error); }); - } - - void StartReceive() { - socket.async_receive_from( - boost::asio::buffer(receive_buffer), receive_endpoint, - [this](const boost::system::error_code& error, std::size_t bytes_transferred) { - HandleReceive(error, bytes_transferred); - }); - } - -private: - u32 GenerateRandomClientId() const { - std::random_device device; - return device(); - } - - void HandleReceive(const boost::system::error_code&, std::size_t bytes_transferred) { - if (auto type = Response::Validate(receive_buffer.data(), bytes_transferred)) { - switch (*type) { - case Type::Version: { - Response::Version version; - std::memcpy(&version, &receive_buffer[sizeof(Header)], sizeof(Response::Version)); - callback.version(std::move(version)); - break; - } - case Type::PortInfo: { - Response::PortInfo port_info; - std::memcpy(&port_info, &receive_buffer[sizeof(Header)], - sizeof(Response::PortInfo)); - callback.port_info(std::move(port_info)); - break; - } - case Type::PadData: { - Response::PadData pad_data; - std::memcpy(&pad_data, &receive_buffer[sizeof(Header)], sizeof(Response::PadData)); - SanitizeMotion(pad_data); - callback.pad_data(std::move(pad_data)); - break; - } - } - } - StartReceive(); - } - - void HandleSend(const boost::system::error_code&) { - boost::system::error_code _ignored{}; - // Send a request for getting port info for the pad - const Request::PortInfo port_info{4, {0, 1, 2, 3}}; - const auto port_message = Request::Create(port_info, client_id); - std::memcpy(&send_buffer1, &port_message, PORT_INFO_SIZE); - socket.send_to(boost::asio::buffer(send_buffer1), send_endpoint, {}, _ignored); - - // Send a request for getting pad data for the pad - const Request::PadData pad_data{ - Request::PadData::Flags::AllPorts, - 0, - EMPTY_MAC_ADDRESS, - }; - const auto pad_message = Request::Create(pad_data, client_id); - std::memcpy(send_buffer2.data(), &pad_message, PAD_DATA_SIZE); - socket.send_to(boost::asio::buffer(send_buffer2), send_endpoint, {}, _ignored); - StartSend(timer.expiry()); - } - - void SanitizeMotion(Response::PadData& data) { - // Zero out any non number value - if (!std::isnormal(data.gyro.pitch)) { - data.gyro.pitch = 0; - } - if (!std::isnormal(data.gyro.roll)) { - data.gyro.roll = 0; - } - if (!std::isnormal(data.gyro.yaw)) { - data.gyro.yaw = 0; - } - if (!std::isnormal(data.accel.x)) { - data.accel.x = 0; - } - if (!std::isnormal(data.accel.y)) { - data.accel.y = 0; - } - if (!std::isnormal(data.accel.z)) { - data.accel.z = 0; - } - } - - SocketCallback callback; - boost::asio::io_service io_service; - boost::asio::basic_waitable_timer timer; - udp::socket socket; - - const u32 client_id; - - static constexpr std::size_t PORT_INFO_SIZE = sizeof(Message); - static constexpr std::size_t PAD_DATA_SIZE = sizeof(Message); - std::array send_buffer1; - std::array send_buffer2; - udp::endpoint send_endpoint; - - std::array receive_buffer; - udp::endpoint receive_endpoint; -}; - -static void SocketLoop(Socket* socket) { - socket->StartReceive(); - socket->StartSend(Socket::clock::now()); - socket->Loop(); -} - -Client::Client() { - LOG_INFO(Input, "Udp Initialization started"); - finger_id.fill(MAX_TOUCH_FINGERS); - ReloadSockets(); -} - -Client::~Client() { - Reset(); -} - -Client::ClientConnection::ClientConnection() = default; - -Client::ClientConnection::~ClientConnection() = default; - -std::vector Client::GetInputDevices() const { - std::vector devices; - for (std::size_t pad = 0; pad < pads.size(); pad++) { - if (!DeviceConnected(pad)) { - continue; - } - std::string name = fmt::format("UDP Controller {}", pad); - devices.emplace_back(Common::ParamPackage{ - {"class", "cemuhookudp"}, - {"display", std::move(name)}, - {"port", std::to_string(pad)}, - }); - } - return devices; -} - -bool Client::DeviceConnected(std::size_t pad) const { - // Use last timestamp to detect if the socket has stopped sending data - const auto now = std::chrono::steady_clock::now(); - const auto time_difference = static_cast( - std::chrono::duration_cast(now - pads[pad].last_update).count()); - return time_difference < 1000 && pads[pad].connected; -} - -void Client::ReloadSockets() { - Reset(); - - std::stringstream servers_ss(static_cast(Settings::values.udp_input_servers)); - std::string server_token; - std::size_t client = 0; - while (std::getline(servers_ss, server_token, ',')) { - if (client == MAX_UDP_CLIENTS) { - break; - } - std::stringstream server_ss(server_token); - std::string token; - std::getline(server_ss, token, ':'); - std::string udp_input_address = token; - std::getline(server_ss, token, ':'); - char* temp; - const u16 udp_input_port = static_cast(std::strtol(token.c_str(), &temp, 0)); - if (*temp != '\0') { - LOG_ERROR(Input, "Port number is not valid {}", token); - continue; - } - - const std::size_t client_number = GetClientNumber(udp_input_address, udp_input_port); - if (client_number != MAX_UDP_CLIENTS) { - LOG_ERROR(Input, "Duplicated UDP servers found"); - continue; - } - StartCommunication(client++, udp_input_address, udp_input_port); - } -} - -std::size_t Client::GetClientNumber(std::string_view host, u16 port) const { - for (std::size_t client = 0; client < clients.size(); client++) { - if (clients[client].active == -1) { - continue; - } - if (clients[client].host == host && clients[client].port == port) { - return client; - } - } - return MAX_UDP_CLIENTS; -} - -void Client::OnVersion([[maybe_unused]] Response::Version data) { - LOG_TRACE(Input, "Version packet received: {}", data.version); -} - -void Client::OnPortInfo([[maybe_unused]] Response::PortInfo data) { - LOG_TRACE(Input, "PortInfo packet received: {}", data.model); -} - -void Client::OnPadData(Response::PadData data, std::size_t client) { - const std::size_t pad_index = (client * PADS_PER_CLIENT) + data.info.id; - - if (pad_index >= pads.size()) { - LOG_ERROR(Input, "Invalid pad id {}", data.info.id); - return; - } - - LOG_TRACE(Input, "PadData packet received"); - if (data.packet_counter == pads[pad_index].packet_sequence) { - LOG_WARNING( - Input, - "PadData packet dropped because its stale info. Current count: {} Packet count: {}", - pads[pad_index].packet_sequence, data.packet_counter); - pads[pad_index].connected = false; - return; - } - - clients[client].active = 1; - pads[pad_index].connected = true; - pads[pad_index].packet_sequence = data.packet_counter; - - const auto now = std::chrono::steady_clock::now(); - const auto time_difference = static_cast( - std::chrono::duration_cast(now - pads[pad_index].last_update) - .count()); - pads[pad_index].last_update = now; - - const Common::Vec3f raw_gyroscope = {data.gyro.pitch, data.gyro.roll, -data.gyro.yaw}; - pads[pad_index].motion.SetAcceleration({data.accel.x, -data.accel.z, data.accel.y}); - // Gyroscope values are not it the correct scale from better joy. - // Dividing by 312 allows us to make one full turn = 1 turn - // This must be a configurable valued called sensitivity - pads[pad_index].motion.SetGyroscope(raw_gyroscope / 312.0f); - pads[pad_index].motion.UpdateRotation(time_difference); - pads[pad_index].motion.UpdateOrientation(time_difference); - - { - std::lock_guard guard(pads[pad_index].status.update_mutex); - pads[pad_index].status.motion_status = pads[pad_index].motion.GetMotion(); - - for (std::size_t id = 0; id < data.touch.size(); ++id) { - UpdateTouchInput(data.touch[id], client, id); - } - - if (configuring) { - const Common::Vec3f gyroscope = pads[pad_index].motion.GetGyroscope(); - const Common::Vec3f accelerometer = pads[pad_index].motion.GetAcceleration(); - UpdateYuzuSettings(client, data.info.id, accelerometer, gyroscope); - } - } -} - -void Client::StartCommunication(std::size_t client, const std::string& host, u16 port) { - SocketCallback callback{[this](Response::Version version) { OnVersion(version); }, - [this](Response::PortInfo info) { OnPortInfo(info); }, - [this, client](Response::PadData data) { OnPadData(data, client); }}; - LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port); - clients[client].host = host; - clients[client].port = port; - clients[client].active = 0; - clients[client].socket = std::make_unique(host, port, callback); - clients[client].thread = std::thread{SocketLoop, clients[client].socket.get()}; - - // Set motion parameters - // SetGyroThreshold value should be dependent on GyroscopeZeroDriftMode - // Real HW values are unknown, 0.0001 is an approximate to Standard - for (std::size_t pad = 0; pad < PADS_PER_CLIENT; pad++) { - pads[client * PADS_PER_CLIENT + pad].motion.SetGyroThreshold(0.0001f); - } -} - -void Client::Reset() { - for (auto& client : clients) { - if (client.thread.joinable()) { - client.active = -1; - client.socket->Stop(); - client.thread.join(); - } - } -} - -void Client::UpdateYuzuSettings(std::size_t client, std::size_t pad_index, - const Common::Vec3& acc, const Common::Vec3& gyro) { - if (gyro.Length() > 0.2f) { - LOG_DEBUG(Input, "UDP Controller {}: gyro=({}, {}, {}), accel=({}, {}, {})", client, - gyro[0], gyro[1], gyro[2], acc[0], acc[1], acc[2]); - } - UDPPadStatus pad{ - .host = clients[client].host, - .port = clients[client].port, - .pad_index = pad_index, - }; - for (std::size_t i = 0; i < 3; ++i) { - if (gyro[i] > 5.0f || gyro[i] < -5.0f) { - pad.motion = static_cast(i); - pad.motion_value = gyro[i]; - pad_queue.Push(pad); - } - if (acc[i] > 1.75f || acc[i] < -1.75f) { - pad.motion = static_cast(i + 3); - pad.motion_value = acc[i]; - pad_queue.Push(pad); - } - } -} - -std::optional Client::GetUnusedFingerID() const { - std::size_t first_free_id = 0; - while (first_free_id < MAX_TOUCH_FINGERS) { - if (!std::get<2>(touch_status[first_free_id])) { - return first_free_id; - } else { - first_free_id++; - } - } - return std::nullopt; -} - -void Client::UpdateTouchInput(Response::TouchPad& touch_pad, std::size_t client, std::size_t id) { - // TODO: Use custom calibration per device - const Common::ParamPackage touch_param(Settings::values.touch_device.GetValue()); - const u16 min_x = static_cast(touch_param.Get("min_x", 100)); - const u16 min_y = static_cast(touch_param.Get("min_y", 50)); - const u16 max_x = static_cast(touch_param.Get("max_x", 1800)); - const u16 max_y = static_cast(touch_param.Get("max_y", 850)); - const std::size_t touch_id = client * 2 + id; - if (touch_pad.is_active) { - if (finger_id[touch_id] == MAX_TOUCH_FINGERS) { - const auto first_free_id = GetUnusedFingerID(); - if (!first_free_id) { - // Invalid finger id skip to next input - return; - } - finger_id[touch_id] = *first_free_id; - } - auto& [x, y, pressed] = touch_status[finger_id[touch_id]]; - x = static_cast(std::clamp(static_cast(touch_pad.x), min_x, max_x) - min_x) / - static_cast(max_x - min_x); - y = static_cast(std::clamp(static_cast(touch_pad.y), min_y, max_y) - min_y) / - static_cast(max_y - min_y); - pressed = true; - return; - } - - if (finger_id[touch_id] != MAX_TOUCH_FINGERS) { - touch_status[finger_id[touch_id]] = {}; - finger_id[touch_id] = MAX_TOUCH_FINGERS; - } -} - -void Client::BeginConfiguration() { - pad_queue.Clear(); - configuring = true; -} - -void Client::EndConfiguration() { - pad_queue.Clear(); - configuring = false; -} - -DeviceStatus& Client::GetPadState(const std::string& host, u16 port, std::size_t pad) { - const std::size_t client_number = GetClientNumber(host, port); - if (client_number == MAX_UDP_CLIENTS || pad >= PADS_PER_CLIENT) { - return pads[0].status; - } - return pads[(client_number * PADS_PER_CLIENT) + pad].status; -} - -const DeviceStatus& Client::GetPadState(const std::string& host, u16 port, std::size_t pad) const { - const std::size_t client_number = GetClientNumber(host, port); - if (client_number == MAX_UDP_CLIENTS || pad >= PADS_PER_CLIENT) { - return pads[0].status; - } - return pads[(client_number * PADS_PER_CLIENT) + pad].status; -} - -Input::TouchStatus& Client::GetTouchState() { - return touch_status; -} - -const Input::TouchStatus& Client::GetTouchState() const { - return touch_status; -} - -Common::SPSCQueue& Client::GetPadQueue() { - return pad_queue; -} - -const Common::SPSCQueue& Client::GetPadQueue() const { - return pad_queue; -} - -void TestCommunication(const std::string& host, u16 port, - const std::function& success_callback, - const std::function& failure_callback) { - std::thread([=] { - Common::Event success_event; - SocketCallback callback{ - .version = [](Response::Version) {}, - .port_info = [](Response::PortInfo) {}, - .pad_data = [&](Response::PadData) { success_event.Set(); }, - }; - Socket socket{host, port, std::move(callback)}; - std::thread worker_thread{SocketLoop, &socket}; - const bool result = - success_event.WaitUntil(std::chrono::steady_clock::now() + std::chrono::seconds(10)); - socket.Stop(); - worker_thread.join(); - if (result) { - success_callback(); - } else { - failure_callback(); - } - }).detach(); -} - -CalibrationConfigurationJob::CalibrationConfigurationJob( - const std::string& host, u16 port, std::function status_callback, - std::function data_callback) { - - std::thread([=, this] { - Status current_status{Status::Initialized}; - SocketCallback callback{ - [](Response::Version) {}, [](Response::PortInfo) {}, - [&](Response::PadData data) { - static constexpr u16 CALIBRATION_THRESHOLD = 100; - static constexpr u16 MAX_VALUE = UINT16_MAX; - - if (current_status == Status::Initialized) { - // Receiving data means the communication is ready now - current_status = Status::Ready; - status_callback(current_status); - } - const auto& touchpad_0 = data.touch[0]; - if (touchpad_0.is_active == 0) { - return; - } - LOG_DEBUG(Input, "Current touch: {} {}", touchpad_0.x, touchpad_0.y); - const u16 min_x = std::min(MAX_VALUE, static_cast(touchpad_0.x)); - const u16 min_y = std::min(MAX_VALUE, static_cast(touchpad_0.y)); - if (current_status == Status::Ready) { - // First touch - min data (min_x/min_y) - current_status = Status::Stage1Completed; - status_callback(current_status); - } - if (touchpad_0.x - min_x > CALIBRATION_THRESHOLD && - touchpad_0.y - min_y > CALIBRATION_THRESHOLD) { - // Set the current position as max value and finishes configuration - const u16 max_x = touchpad_0.x; - const u16 max_y = touchpad_0.y; - current_status = Status::Completed; - data_callback(min_x, min_y, max_x, max_y); - status_callback(current_status); - - complete_event.Set(); - } - }}; - Socket socket{host, port, std::move(callback)}; - std::thread worker_thread{SocketLoop, &socket}; - complete_event.Wait(); - socket.Stop(); - worker_thread.join(); - }).detach(); -} - -CalibrationConfigurationJob::~CalibrationConfigurationJob() { - Stop(); -} - -void CalibrationConfigurationJob::Stop() { - complete_event.Set(); -} - -} // namespace InputCommon::CemuhookUDP diff --git a/src/input_common/udp/udp.cpp b/src/input_common/udp/udp.cpp deleted file mode 100644 index 9829da6f00..0000000000 --- a/src/input_common/udp/udp.cpp +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include -#include "common/assert.h" -#include "common/threadsafe_queue.h" -#include "input_common/udp/client.h" -#include "input_common/udp/udp.h" - -namespace InputCommon { - -class UDPMotion final : public Input::MotionDevice { -public: - explicit UDPMotion(std::string ip_, u16 port_, u16 pad_, CemuhookUDP::Client* client_) - : ip(std::move(ip_)), port(port_), pad(pad_), client(client_) {} - - Input::MotionStatus GetStatus() const override { - return client->GetPadState(ip, port, pad).motion_status; - } - -private: - const std::string ip; - const u16 port; - const u16 pad; - CemuhookUDP::Client* client; - mutable std::mutex mutex; -}; - -/// A motion device factory that creates motion devices from a UDP client -UDPMotionFactory::UDPMotionFactory(std::shared_ptr client_) - : client(std::move(client_)) {} - -/** - * Creates motion device - * @param params contains parameters for creating the device: - * - "port": the UDP port number - */ -std::unique_ptr UDPMotionFactory::Create(const Common::ParamPackage& params) { - auto ip = params.Get("ip", "127.0.0.1"); - const auto port = static_cast(params.Get("port", 26760)); - const auto pad = static_cast(params.Get("pad_index", 0)); - - return std::make_unique(std::move(ip), port, pad, client.get()); -} - -void UDPMotionFactory::BeginConfiguration() { - polling = true; - client->BeginConfiguration(); -} - -void UDPMotionFactory::EndConfiguration() { - polling = false; - client->EndConfiguration(); -} - -Common::ParamPackage UDPMotionFactory::GetNextInput() { - Common::ParamPackage params; - CemuhookUDP::UDPPadStatus pad; - auto& queue = client->GetPadQueue(); - while (queue.Pop(pad)) { - if (pad.motion == CemuhookUDP::PadMotion::Undefined || std::abs(pad.motion_value) < 1) { - continue; - } - params.Set("engine", "cemuhookudp"); - params.Set("ip", pad.host); - params.Set("port", static_cast(pad.port)); - params.Set("pad_index", static_cast(pad.pad_index)); - params.Set("motion", static_cast(pad.motion)); - return params; - } - return params; -} - -class UDPTouch final : public Input::TouchDevice { -public: - explicit UDPTouch(std::string ip_, u16 port_, u16 pad_, CemuhookUDP::Client* client_) - : ip(std::move(ip_)), port(port_), pad(pad_), client(client_) {} - - Input::TouchStatus GetStatus() const override { - return client->GetTouchState(); - } - -private: - const std::string ip; - [[maybe_unused]] const u16 port; - [[maybe_unused]] const u16 pad; - CemuhookUDP::Client* client; - mutable std::mutex mutex; -}; - -/// A motion device factory that creates motion devices from a UDP client -UDPTouchFactory::UDPTouchFactory(std::shared_ptr client_) - : client(std::move(client_)) {} - -/** - * Creates motion device - * @param params contains parameters for creating the device: - * - "port": the UDP port number - */ -std::unique_ptr UDPTouchFactory::Create(const Common::ParamPackage& params) { - auto ip = params.Get("ip", "127.0.0.1"); - const auto port = static_cast(params.Get("port", 26760)); - const auto pad = static_cast(params.Get("pad_index", 0)); - - return std::make_unique(std::move(ip), port, pad, client.get()); -} - -} // namespace InputCommon diff --git a/src/input_common/udp/udp.h b/src/input_common/udp/udp.h deleted file mode 100644 index ea3fd41751..0000000000 --- a/src/input_common/udp/udp.h +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include "core/frontend/input.h" -#include "input_common/udp/client.h" - -namespace InputCommon { - -/// A motion device factory that creates motion devices from udp clients -class UDPMotionFactory final : public Input::Factory { -public: - explicit UDPMotionFactory(std::shared_ptr client_); - - std::unique_ptr Create(const Common::ParamPackage& params) override; - - Common::ParamPackage GetNextInput(); - - /// For device input configuration/polling - void BeginConfiguration(); - void EndConfiguration(); - - bool IsPolling() const { - return polling; - } - -private: - std::shared_ptr client; - bool polling = false; -}; - -/// A touch device factory that creates touch devices from udp clients -class UDPTouchFactory final : public Input::Factory { -public: - explicit UDPTouchFactory(std::shared_ptr client_); - - std::unique_ptr Create(const Common::ParamPackage& params) override; - - Common::ParamPackage GetNextInput(); - - /// For device input configuration/polling - void BeginConfiguration(); - void EndConfiguration(); - - bool IsPolling() const { - return polling; - } - -private: - std::shared_ptr client; - bool polling = false; -}; - -} // namespace InputCommon diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index d62fd566f7..a44815e71e 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -90,9 +90,6 @@ add_executable(yuzu configuration/configure_motion_touch.cpp configuration/configure_motion_touch.h configuration/configure_motion_touch.ui - configuration/configure_mouse_advanced.cpp - configuration/configure_mouse_advanced.h - configuration/configure_mouse_advanced.ui configuration/configure_per_game.cpp configuration/configure_per_game.h configuration/configure_per_game.ui diff --git a/src/yuzu/applets/qt_controller.cpp b/src/yuzu/applets/qt_controller.cpp index bf8445a893..589e0577a0 100644 --- a/src/yuzu/applets/qt_controller.cpp +++ b/src/yuzu/applets/qt_controller.cpp @@ -6,8 +6,12 @@ #include #include "common/assert.h" +#include "common/param_package.h" #include "common/string_util.h" #include "core/core.h" +#include "core/hid/emulated_controller.h" +#include "core/hid/hid_core.h" +#include "core/hid/hid_types.h" #include "core/hle/lock.h" #include "core/hle/service/hid/controllers/npad.h" #include "core/hle/service/hid/hid.h" @@ -23,49 +27,32 @@ namespace { -constexpr std::size_t HANDHELD_INDEX = 8; - -constexpr std::array, 8> led_patterns{{ - {true, false, false, false}, - {true, true, false, false}, - {true, true, true, false}, - {true, true, true, true}, - {true, false, false, true}, - {true, false, true, false}, - {true, false, true, true}, - {false, true, true, false}, -}}; - -void UpdateController(Settings::ControllerType controller_type, std::size_t npad_index, - bool connected, Core::System& system) { - if (!system.IsPoweredOn()) { - return; +void UpdateController(Core::HID::EmulatedController* controller, + Core::HID::NpadStyleIndex controller_type, bool connected) { + if (controller->IsConnected(true)) { + controller->Disconnect(); + } + controller->SetNpadStyleIndex(controller_type); + if (connected) { + controller->Connect(); } - - auto& npad = - system.ServiceManager() - .GetService("hid") - ->GetAppletResource() - ->GetController(Service::HID::HidController::NPad); - - npad.UpdateControllerAt(npad.MapSettingsTypeToNPad(controller_type), npad_index, connected); } // Returns true if the given controller type is compatible with the given parameters. -bool IsControllerCompatible(Settings::ControllerType controller_type, +bool IsControllerCompatible(Core::HID::NpadStyleIndex controller_type, Core::Frontend::ControllerParameters parameters) { switch (controller_type) { - case Settings::ControllerType::ProController: + case Core::HID::NpadStyleIndex::ProController: return parameters.allow_pro_controller; - case Settings::ControllerType::DualJoyconDetached: + case Core::HID::NpadStyleIndex::JoyconDual: return parameters.allow_dual_joycons; - case Settings::ControllerType::LeftJoycon: + case Core::HID::NpadStyleIndex::JoyconLeft: return parameters.allow_left_joycon; - case Settings::ControllerType::RightJoycon: + case Core::HID::NpadStyleIndex::JoyconRight: return parameters.allow_right_joycon; - case Settings::ControllerType::Handheld: + case Core::HID::NpadStyleIndex::Handheld: return parameters.enable_single_mode && parameters.allow_handheld; - case Settings::ControllerType::GameCube: + case Core::HID::NpadStyleIndex::GameCube: return parameters.allow_gamecube_controller; default: return false; @@ -152,6 +139,7 @@ QtControllerSelectorDialog::QtControllerSelectorDialog( DisableUnsupportedPlayers(); for (std::size_t player_index = 0; player_index < NUM_PLAYERS; ++player_index) { + system.HIDCore().GetEmulatedControllerByIndex(player_index)->EnableConfiguration(); SetEmulatedControllers(player_index); } @@ -196,7 +184,7 @@ QtControllerSelectorDialog::QtControllerSelectorDialog( connect(emulated_controllers[i], qOverload(&QComboBox::currentIndexChanged), [this, i](int index) { UpdateDockedState(GetControllerTypeFromIndex(index, i) == - Settings::ControllerType::Handheld); + Core::HID::NpadStyleIndex::Handheld); }); } } @@ -246,20 +234,24 @@ void QtControllerSelectorDialog::ApplyConfiguration() { Settings::values.vibration_enabled.SetValue(ui->vibrationGroup->isChecked()); Settings::values.motion_enabled.SetValue(ui->motionGroup->isChecked()); + for (std::size_t player_index = 0; player_index < NUM_PLAYERS; ++player_index) { + system.HIDCore().GetEmulatedControllerByIndex(player_index)->DisableConfiguration(); + } } void QtControllerSelectorDialog::LoadConfiguration() { + const auto* handheld = system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld); for (std::size_t index = 0; index < NUM_PLAYERS; ++index) { + const auto* controller = system.HIDCore().GetEmulatedControllerByIndex(index); const auto connected = - Settings::values.players.GetValue()[index].connected || - (index == 0 && Settings::values.players.GetValue()[HANDHELD_INDEX].connected); + controller->IsConnected(true) || (index == 0 && handheld->IsConnected(true)); player_groupboxes[index]->setChecked(connected); connected_controller_checkboxes[index]->setChecked(connected); - emulated_controllers[index]->setCurrentIndex(GetIndexFromControllerType( - Settings::values.players.GetValue()[index].controller_type, index)); + emulated_controllers[index]->setCurrentIndex( + GetIndexFromControllerType(controller->GetNpadStyleIndex(true), index)); } - UpdateDockedState(Settings::values.players.GetValue()[HANDHELD_INDEX].connected); + UpdateDockedState(handheld->IsConnected(true)); ui->vibrationGroup->setChecked(Settings::values.vibration_enabled.GetValue()); ui->motionGroup->setChecked(Settings::values.motion_enabled.GetValue()); @@ -415,33 +407,33 @@ void QtControllerSelectorDialog::SetEmulatedControllers(std::size_t player_index emulated_controllers[player_index]->clear(); pairs.emplace_back(emulated_controllers[player_index]->count(), - Settings::ControllerType::ProController); + Core::HID::NpadStyleIndex::ProController); emulated_controllers[player_index]->addItem(tr("Pro Controller")); pairs.emplace_back(emulated_controllers[player_index]->count(), - Settings::ControllerType::DualJoyconDetached); + Core::HID::NpadStyleIndex::JoyconDual); emulated_controllers[player_index]->addItem(tr("Dual Joycons")); pairs.emplace_back(emulated_controllers[player_index]->count(), - Settings::ControllerType::LeftJoycon); + Core::HID::NpadStyleIndex::JoyconLeft); emulated_controllers[player_index]->addItem(tr("Left Joycon")); pairs.emplace_back(emulated_controllers[player_index]->count(), - Settings::ControllerType::RightJoycon); + Core::HID::NpadStyleIndex::JoyconRight); emulated_controllers[player_index]->addItem(tr("Right Joycon")); if (player_index == 0) { pairs.emplace_back(emulated_controllers[player_index]->count(), - Settings::ControllerType::Handheld); + Core::HID::NpadStyleIndex::Handheld); emulated_controllers[player_index]->addItem(tr("Handheld")); } pairs.emplace_back(emulated_controllers[player_index]->count(), - Settings::ControllerType::GameCube); + Core::HID::NpadStyleIndex::GameCube); emulated_controllers[player_index]->addItem(tr("GameCube Controller")); } -Settings::ControllerType QtControllerSelectorDialog::GetControllerTypeFromIndex( +Core::HID::NpadStyleIndex QtControllerSelectorDialog::GetControllerTypeFromIndex( int index, std::size_t player_index) const { const auto& pairs = index_controller_type_pairs[player_index]; @@ -449,13 +441,13 @@ Settings::ControllerType QtControllerSelectorDialog::GetControllerTypeFromIndex( [index](const auto& pair) { return pair.first == index; }); if (it == pairs.end()) { - return Settings::ControllerType::ProController; + return Core::HID::NpadStyleIndex::ProController; } return it->second; } -int QtControllerSelectorDialog::GetIndexFromControllerType(Settings::ControllerType type, +int QtControllerSelectorDialog::GetIndexFromControllerType(Core::HID::NpadStyleIndex type, std::size_t player_index) const { const auto& pairs = index_controller_type_pairs[player_index]; @@ -479,16 +471,16 @@ void QtControllerSelectorDialog::UpdateControllerIcon(std::size_t player_index) const QString stylesheet = [this, player_index] { switch (GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex(), player_index)) { - case Settings::ControllerType::ProController: - case Settings::ControllerType::GameCube: + case Core::HID::NpadStyleIndex::ProController: + case Core::HID::NpadStyleIndex::GameCube: return QStringLiteral("image: url(:/controller/applet_pro_controller%0); "); - case Settings::ControllerType::DualJoyconDetached: + case Core::HID::NpadStyleIndex::JoyconDual: return QStringLiteral("image: url(:/controller/applet_dual_joycon%0); "); - case Settings::ControllerType::LeftJoycon: + case Core::HID::NpadStyleIndex::JoyconLeft: return QStringLiteral("image: url(:/controller/applet_joycon_left%0); "); - case Settings::ControllerType::RightJoycon: + case Core::HID::NpadStyleIndex::JoyconRight: return QStringLiteral("image: url(:/controller/applet_joycon_right%0); "); - case Settings::ControllerType::Handheld: + case Core::HID::NpadStyleIndex::Handheld: return QStringLiteral("image: url(:/controller/applet_handheld%0); "); default: return QString{}; @@ -516,54 +508,38 @@ void QtControllerSelectorDialog::UpdateControllerIcon(std::size_t player_index) } void QtControllerSelectorDialog::UpdateControllerState(std::size_t player_index) { - auto& player = Settings::values.players.GetValue()[player_index]; + auto* controller = system.HIDCore().GetEmulatedControllerByIndex(player_index); const auto controller_type = GetControllerTypeFromIndex( emulated_controllers[player_index]->currentIndex(), player_index); const auto player_connected = player_groupboxes[player_index]->isChecked() && - controller_type != Settings::ControllerType::Handheld; + controller_type != Core::HID::NpadStyleIndex::Handheld; - if (player.controller_type == controller_type && player.connected == player_connected) { - // Set vibration devices in the event that the input device has changed. - ConfigureVibration::SetVibrationDevices(player_index); + if (controller->GetNpadStyleIndex(true) == controller_type && + controller->IsConnected(true) == player_connected) { return; } // Disconnect the controller first. - UpdateController(controller_type, player_index, false, system); - - player.controller_type = controller_type; - player.connected = player_connected; - - ConfigureVibration::SetVibrationDevices(player_index); + UpdateController(controller, controller_type, false); // Handheld if (player_index == 0) { - auto& handheld = Settings::values.players.GetValue()[HANDHELD_INDEX]; - if (controller_type == Settings::ControllerType::Handheld) { - handheld = player; + if (controller_type == Core::HID::NpadStyleIndex::Handheld) { + auto* handheld = + system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld); + UpdateController(handheld, Core::HID::NpadStyleIndex::Handheld, + player_groupboxes[player_index]->isChecked()); } - handheld.connected = player_groupboxes[player_index]->isChecked() && - controller_type == Settings::ControllerType::Handheld; - UpdateController(Settings::ControllerType::Handheld, 8, handheld.connected, system); } - if (!player.connected) { - return; - } - - // This emulates a delay between disconnecting and reconnecting controllers as some games - // do not respond to a change in controller type if it was instantaneous. - using namespace std::chrono_literals; - std::this_thread::sleep_for(60ms); - - UpdateController(controller_type, player_index, player_connected, system); + UpdateController(controller, controller_type, player_connected); } void QtControllerSelectorDialog::UpdateLEDPattern(std::size_t player_index) { if (!player_groupboxes[player_index]->isChecked() || GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex(), - player_index) == Settings::ControllerType::Handheld) { + player_index) == Core::HID::NpadStyleIndex::Handheld) { led_patterns_boxes[player_index][0]->setChecked(false); led_patterns_boxes[player_index][1]->setChecked(false); led_patterns_boxes[player_index][2]->setChecked(false); @@ -571,10 +547,12 @@ void QtControllerSelectorDialog::UpdateLEDPattern(std::size_t player_index) { return; } - led_patterns_boxes[player_index][0]->setChecked(led_patterns[player_index][0]); - led_patterns_boxes[player_index][1]->setChecked(led_patterns[player_index][1]); - led_patterns_boxes[player_index][2]->setChecked(led_patterns[player_index][2]); - led_patterns_boxes[player_index][3]->setChecked(led_patterns[player_index][3]); + const auto* controller = system.HIDCore().GetEmulatedControllerByIndex(player_index); + const auto led_pattern = controller->GetLedPattern(); + led_patterns_boxes[player_index][0]->setChecked(led_pattern.position1); + led_patterns_boxes[player_index][1]->setChecked(led_pattern.position2); + led_patterns_boxes[player_index][2]->setChecked(led_pattern.position3); + led_patterns_boxes[player_index][3]->setChecked(led_pattern.position4); } void QtControllerSelectorDialog::UpdateBorderColor(std::size_t player_index) { @@ -654,10 +632,9 @@ void QtControllerSelectorDialog::DisableUnsupportedPlayers() { } for (std::size_t index = max_supported_players; index < NUM_PLAYERS; ++index) { + auto* controller = system.HIDCore().GetEmulatedControllerByIndex(index); // Disconnect any unsupported players here and disable or hide them if applicable. - Settings::values.players.GetValue()[index].connected = false; - UpdateController(Settings::values.players.GetValue()[index].controller_type, index, false, - system); + UpdateController(controller, controller->GetNpadStyleIndex(true), false); // Hide the player widgets when max_supported_controllers is less than or equal to 4. if (max_supported_players <= 4) { player_widgets[index]->hide(); diff --git a/src/yuzu/applets/qt_controller.h b/src/yuzu/applets/qt_controller.h index 037325f504..cc343e5aea 100644 --- a/src/yuzu/applets/qt_controller.h +++ b/src/yuzu/applets/qt_controller.h @@ -23,14 +23,18 @@ namespace InputCommon { class InputSubsystem; } -namespace Settings { -enum class ControllerType; -} - namespace Ui { class QtControllerSelectorDialog; } +namespace Core { +class System; +} + +namespace Core::HID { +enum class NpadStyleIndex : u8; +} + class QtControllerSelectorDialog final : public QDialog { Q_OBJECT @@ -70,10 +74,10 @@ private: void SetEmulatedControllers(std::size_t player_index); // Gets the Controller Type for a given controller combobox index per player. - Settings::ControllerType GetControllerTypeFromIndex(int index, std::size_t player_index) const; + Core::HID::NpadStyleIndex GetControllerTypeFromIndex(int index, std::size_t player_index) const; // Gets the controller combobox index for a given Controller Type per player. - int GetIndexFromControllerType(Settings::ControllerType type, std::size_t player_index) const; + int GetIndexFromControllerType(Core::HID::NpadStyleIndex type, std::size_t player_index) const; // Updates the controller icons per player. void UpdateControllerIcon(std::size_t player_index); @@ -135,7 +139,7 @@ private: std::array emulated_controllers; /// Pairs of emulated controller index and Controller Type enum per player. - std::array>, NUM_PLAYERS> + std::array>, NUM_PLAYERS> index_controller_type_pairs; // Labels representing the number of connected controllers diff --git a/src/yuzu/applets/qt_software_keyboard.cpp b/src/yuzu/applets/qt_software_keyboard.cpp index a83a11a956..de7f98c4f3 100644 --- a/src/yuzu/applets/qt_software_keyboard.cpp +++ b/src/yuzu/applets/qt_software_keyboard.cpp @@ -10,7 +10,10 @@ #include "common/settings.h" #include "common/string_util.h" #include "core/core.h" -#include "core/frontend/input_interpreter.h" +#include "core/hid/emulated_controller.h" +#include "core/hid/hid_core.h" +#include "core/hid/hid_types.h" +#include "core/hid/input_interpreter.h" #include "ui_qt_software_keyboard.h" #include "yuzu/applets/qt_software_keyboard.h" #include "yuzu/main.h" @@ -484,7 +487,7 @@ void QtSoftwareKeyboardDialog::open() { void QtSoftwareKeyboardDialog::reject() { // Pressing the ESC key in a dialog calls QDialog::reject(). // We will override this behavior to the "Cancel" action on the software keyboard. - TranslateButtonPress(HIDButton::X); + TranslateButtonPress(Core::HID::NpadButton::X); } void QtSoftwareKeyboardDialog::keyPressEvent(QKeyEvent* event) { @@ -722,7 +725,7 @@ void QtSoftwareKeyboardDialog::SetTextDrawType() { connect( ui->line_edit_osk, &QLineEdit::returnPressed, this, - [this] { TranslateButtonPress(HIDButton::Plus); }, Qt::QueuedConnection); + [this] { TranslateButtonPress(Core::HID::NpadButton::Plus); }, Qt::QueuedConnection); ui->line_edit_osk->setPlaceholderText( QString::fromStdU16String(initialize_parameters.guide_text)); @@ -795,9 +798,10 @@ void QtSoftwareKeyboardDialog::SetTextDrawType() { } void QtSoftwareKeyboardDialog::SetControllerImage() { - const auto controller_type = Settings::values.players.GetValue()[8].connected - ? Settings::values.players.GetValue()[8].controller_type - : Settings::values.players.GetValue()[0].controller_type; + const auto* handheld = system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld); + const auto* player_1 = system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); + const auto controller_type = + handheld->IsConnected() ? handheld->GetNpadStyleIndex() : player_1->GetNpadStyleIndex(); const QString theme = [] { if (QIcon::themeName().contains(QStringLiteral("dark")) || @@ -809,8 +813,8 @@ void QtSoftwareKeyboardDialog::SetControllerImage() { }(); switch (controller_type) { - case Settings::ControllerType::ProController: - case Settings::ControllerType::GameCube: + case Core::HID::NpadStyleIndex::ProController: + case Core::HID::NpadStyleIndex::GameCube: ui->icon_controller->setStyleSheet( QStringLiteral("image: url(:/overlay/controller_pro%1.png);").arg(theme)); ui->icon_controller_shift->setStyleSheet( @@ -818,7 +822,7 @@ void QtSoftwareKeyboardDialog::SetControllerImage() { ui->icon_controller_num->setStyleSheet( QStringLiteral("image: url(:/overlay/controller_pro%1.png);").arg(theme)); break; - case Settings::ControllerType::DualJoyconDetached: + case Core::HID::NpadStyleIndex::JoyconDual: ui->icon_controller->setStyleSheet( QStringLiteral("image: url(:/overlay/controller_dual_joycon%1.png);").arg(theme)); ui->icon_controller_shift->setStyleSheet( @@ -826,7 +830,7 @@ void QtSoftwareKeyboardDialog::SetControllerImage() { ui->icon_controller_num->setStyleSheet( QStringLiteral("image: url(:/overlay/controller_dual_joycon%1.png);").arg(theme)); break; - case Settings::ControllerType::LeftJoycon: + case Core::HID::NpadStyleIndex::JoyconLeft: ui->icon_controller->setStyleSheet( QStringLiteral("image: url(:/overlay/controller_single_joycon_left%1.png);") .arg(theme)); @@ -837,7 +841,7 @@ void QtSoftwareKeyboardDialog::SetControllerImage() { QStringLiteral("image: url(:/overlay/controller_single_joycon_left%1.png);") .arg(theme)); break; - case Settings::ControllerType::RightJoycon: + case Core::HID::NpadStyleIndex::JoyconRight: ui->icon_controller->setStyleSheet( QStringLiteral("image: url(:/overlay/controller_single_joycon_right%1.png);") .arg(theme)); @@ -848,7 +852,7 @@ void QtSoftwareKeyboardDialog::SetControllerImage() { QStringLiteral("image: url(:/overlay/controller_single_joycon_right%1.png);") .arg(theme)); break; - case Settings::ControllerType::Handheld: + case Core::HID::NpadStyleIndex::Handheld: ui->icon_controller->setStyleSheet( QStringLiteral("image: url(:/overlay/controller_handheld%1.png);").arg(theme)); ui->icon_controller_shift->setStyleSheet( @@ -1208,9 +1212,9 @@ void QtSoftwareKeyboardDialog::SetupMouseHover() { } } -template +template void QtSoftwareKeyboardDialog::HandleButtonPressedOnce() { - const auto f = [this](HIDButton button) { + const auto f = [this](Core::HID::NpadButton button) { if (input_interpreter->IsButtonPressedOnce(button)) { TranslateButtonPress(button); } @@ -1219,9 +1223,9 @@ void QtSoftwareKeyboardDialog::HandleButtonPressedOnce() { (f(T), ...); } -template +template void QtSoftwareKeyboardDialog::HandleButtonHold() { - const auto f = [this](HIDButton button) { + const auto f = [this](Core::HID::NpadButton button) { if (input_interpreter->IsButtonHeld(button)) { TranslateButtonPress(button); } @@ -1230,9 +1234,9 @@ void QtSoftwareKeyboardDialog::HandleButtonHold() { (f(T), ...); } -void QtSoftwareKeyboardDialog::TranslateButtonPress(HIDButton button) { +void QtSoftwareKeyboardDialog::TranslateButtonPress(Core::HID::NpadButton button) { switch (button) { - case HIDButton::A: + case Core::HID::NpadButton::A: switch (bottom_osk_index) { case BottomOSKIndex::LowerCase: case BottomOSKIndex::UpperCase: @@ -1245,7 +1249,7 @@ void QtSoftwareKeyboardDialog::TranslateButtonPress(HIDButton button) { break; } break; - case HIDButton::B: + case Core::HID::NpadButton::B: switch (bottom_osk_index) { case BottomOSKIndex::LowerCase: ui->button_backspace->click(); @@ -1260,7 +1264,7 @@ void QtSoftwareKeyboardDialog::TranslateButtonPress(HIDButton button) { break; } break; - case HIDButton::X: + case Core::HID::NpadButton::X: if (is_inline) { emit SubmitInlineText(SwkbdReplyType::DecidedCancel, current_text, cursor_position); } else { @@ -1271,7 +1275,7 @@ void QtSoftwareKeyboardDialog::TranslateButtonPress(HIDButton button) { emit SubmitNormalText(SwkbdResult::Cancel, std::move(text)); } break; - case HIDButton::Y: + case Core::HID::NpadButton::Y: switch (bottom_osk_index) { case BottomOSKIndex::LowerCase: ui->button_space->click(); @@ -1284,8 +1288,8 @@ void QtSoftwareKeyboardDialog::TranslateButtonPress(HIDButton button) { break; } break; - case HIDButton::LStick: - case HIDButton::RStick: + case Core::HID::NpadButton::StickL: + case Core::HID::NpadButton::StickR: switch (bottom_osk_index) { case BottomOSKIndex::LowerCase: ui->button_shift->click(); @@ -1298,13 +1302,13 @@ void QtSoftwareKeyboardDialog::TranslateButtonPress(HIDButton button) { break; } break; - case HIDButton::L: + case Core::HID::NpadButton::L: MoveTextCursorDirection(Direction::Left); break; - case HIDButton::R: + case Core::HID::NpadButton::R: MoveTextCursorDirection(Direction::Right); break; - case HIDButton::Plus: + case Core::HID::NpadButton::Plus: switch (bottom_osk_index) { case BottomOSKIndex::LowerCase: ui->button_ok->click(); @@ -1319,24 +1323,24 @@ void QtSoftwareKeyboardDialog::TranslateButtonPress(HIDButton button) { break; } break; - case HIDButton::DLeft: - case HIDButton::LStickLeft: - case HIDButton::RStickLeft: + case Core::HID::NpadButton::Left: + case Core::HID::NpadButton::StickLLeft: + case Core::HID::NpadButton::StickRLeft: MoveButtonDirection(Direction::Left); break; - case HIDButton::DUp: - case HIDButton::LStickUp: - case HIDButton::RStickUp: + case Core::HID::NpadButton::Up: + case Core::HID::NpadButton::StickLUp: + case Core::HID::NpadButton::StickRUp: MoveButtonDirection(Direction::Up); break; - case HIDButton::DRight: - case HIDButton::LStickRight: - case HIDButton::RStickRight: + case Core::HID::NpadButton::Right: + case Core::HID::NpadButton::StickLRight: + case Core::HID::NpadButton::StickRRight: MoveButtonDirection(Direction::Right); break; - case HIDButton::DDown: - case HIDButton::LStickDown: - case HIDButton::RStickDown: + case Core::HID::NpadButton::Down: + case Core::HID::NpadButton::StickLDown: + case Core::HID::NpadButton::StickRDown: MoveButtonDirection(Direction::Down); break; default: @@ -1467,19 +1471,25 @@ void QtSoftwareKeyboardDialog::InputThread() { while (input_thread_running) { input_interpreter->PollInput(); - HandleButtonPressedOnce(); + HandleButtonPressedOnce< + Core::HID::NpadButton::A, Core::HID::NpadButton::B, Core::HID::NpadButton::X, + Core::HID::NpadButton::Y, Core::HID::NpadButton::StickL, Core::HID::NpadButton::StickR, + Core::HID::NpadButton::L, Core::HID::NpadButton::R, Core::HID::NpadButton::Plus, + Core::HID::NpadButton::Left, Core::HID::NpadButton::Up, Core::HID::NpadButton::Right, + Core::HID::NpadButton::Down, Core::HID::NpadButton::StickLLeft, + Core::HID::NpadButton::StickLUp, Core::HID::NpadButton::StickLRight, + Core::HID::NpadButton::StickLDown, Core::HID::NpadButton::StickRLeft, + Core::HID::NpadButton::StickRUp, Core::HID::NpadButton::StickRRight, + Core::HID::NpadButton::StickRDown>(); - HandleButtonHold(); + HandleButtonHold(); std::this_thread::sleep_for(std::chrono::milliseconds(50)); } diff --git a/src/yuzu/applets/qt_software_keyboard.h b/src/yuzu/applets/qt_software_keyboard.h index 592d9c085b..b030cdcf7c 100644 --- a/src/yuzu/applets/qt_software_keyboard.h +++ b/src/yuzu/applets/qt_software_keyboard.h @@ -14,14 +14,16 @@ #include "core/frontend/applets/software_keyboard.h" -enum class HIDButton : u8; - class InputInterpreter; namespace Core { class System; } +namespace Core::HID { +enum class NpadButton : u64; +} + namespace Ui { class QtSoftwareKeyboardDialog; } @@ -146,7 +148,7 @@ private: * * @tparam HIDButton The list of buttons that can be converted into keyboard input. */ - template + template void HandleButtonPressedOnce(); /** @@ -154,7 +156,7 @@ private: * * @tparam HIDButton The list of buttons that can be converted into keyboard input. */ - template + template void HandleButtonHold(); /** @@ -162,7 +164,7 @@ private: * * @param button The button press to process. */ - void TranslateButtonPress(HIDButton button); + void TranslateButtonPress(Core::HID::NpadButton button); /** * Moves the focus of a button in a certain direction. diff --git a/src/yuzu/applets/qt_web_browser.cpp b/src/yuzu/applets/qt_web_browser.cpp index da8c6882ab..cb3c5d8268 100644 --- a/src/yuzu/applets/qt_web_browser.cpp +++ b/src/yuzu/applets/qt_web_browser.cpp @@ -14,9 +14,11 @@ #endif #include "common/fs/path_util.h" +#include "common/param_package.h" #include "core/core.h" -#include "core/frontend/input_interpreter.h" -#include "input_common/keyboard.h" +#include "core/hid/hid_types.h" +#include "core/hid/input_interpreter.h" +#include "input_common/drivers/keyboard.h" #include "input_common/main.h" #include "yuzu/applets/qt_web_browser.h" #include "yuzu/applets/qt_web_browser_scripts.h" @@ -27,19 +29,19 @@ namespace { -constexpr int HIDButtonToKey(HIDButton button) { +constexpr int HIDButtonToKey(Core::HID::NpadButton button) { switch (button) { - case HIDButton::DLeft: - case HIDButton::LStickLeft: + case Core::HID::NpadButton::Left: + case Core::HID::NpadButton::StickLLeft: return Qt::Key_Left; - case HIDButton::DUp: - case HIDButton::LStickUp: + case Core::HID::NpadButton::Up: + case Core::HID::NpadButton::StickLUp: return Qt::Key_Up; - case HIDButton::DRight: - case HIDButton::LStickRight: + case Core::HID::NpadButton::Right: + case Core::HID::NpadButton::StickLRight: return Qt::Key_Right; - case HIDButton::DDown: - case HIDButton::LStickDown: + case Core::HID::NpadButton::Down: + case Core::HID::NpadButton::StickLDown: return Qt::Key_Down; default: return 0; @@ -208,25 +210,25 @@ void QtNXWebEngineView::keyReleaseEvent(QKeyEvent* event) { } } -template +template void QtNXWebEngineView::HandleWindowFooterButtonPressedOnce() { - const auto f = [this](HIDButton button) { + const auto f = [this](Core::HID::NpadButton button) { if (input_interpreter->IsButtonPressedOnce(button)) { page()->runJavaScript( QStringLiteral("yuzu_key_callbacks[%1] == null;").arg(static_cast(button)), [this, button](const QVariant& variant) { if (variant.toBool()) { switch (button) { - case HIDButton::A: + case Core::HID::NpadButton::A: SendMultipleKeyPressEvents(); break; - case HIDButton::B: + case Core::HID::NpadButton::B: SendKeyPressEvent(Qt::Key_B); break; - case HIDButton::X: + case Core::HID::NpadButton::X: SendKeyPressEvent(Qt::Key_X); break; - case HIDButton::Y: + case Core::HID::NpadButton::Y: SendKeyPressEvent(Qt::Key_Y); break; default: @@ -244,9 +246,9 @@ void QtNXWebEngineView::HandleWindowFooterButtonPressedOnce() { (f(T), ...); } -template +template void QtNXWebEngineView::HandleWindowKeyButtonPressedOnce() { - const auto f = [this](HIDButton button) { + const auto f = [this](Core::HID::NpadButton button) { if (input_interpreter->IsButtonPressedOnce(button)) { SendKeyPressEvent(HIDButtonToKey(button)); } @@ -255,9 +257,9 @@ void QtNXWebEngineView::HandleWindowKeyButtonPressedOnce() { (f(T), ...); } -template +template void QtNXWebEngineView::HandleWindowKeyButtonHold() { - const auto f = [this](HIDButton button) { + const auto f = [this](Core::HID::NpadButton button) { if (input_interpreter->IsButtonHeld(button)) { SendKeyPressEvent(HIDButtonToKey(button)); } @@ -308,17 +310,21 @@ void QtNXWebEngineView::InputThread() { while (input_thread_running) { input_interpreter->PollInput(); - HandleWindowFooterButtonPressedOnce(); + HandleWindowFooterButtonPressedOnce(); - HandleWindowKeyButtonPressedOnce(); + HandleWindowKeyButtonPressedOnce< + Core::HID::NpadButton::Left, Core::HID::NpadButton::Up, Core::HID::NpadButton::Right, + Core::HID::NpadButton::Down, Core::HID::NpadButton::StickLLeft, + Core::HID::NpadButton::StickLUp, Core::HID::NpadButton::StickLRight, + Core::HID::NpadButton::StickLDown>(); - HandleWindowKeyButtonHold(); + HandleWindowKeyButtonHold< + Core::HID::NpadButton::Left, Core::HID::NpadButton::Up, Core::HID::NpadButton::Right, + Core::HID::NpadButton::Down, Core::HID::NpadButton::StickLLeft, + Core::HID::NpadButton::StickLUp, Core::HID::NpadButton::StickLRight, + Core::HID::NpadButton::StickLDown>(); std::this_thread::sleep_for(std::chrono::milliseconds(50)); } diff --git a/src/yuzu/applets/qt_web_browser.h b/src/yuzu/applets/qt_web_browser.h index 7e9f703fcc..fa18aecac8 100644 --- a/src/yuzu/applets/qt_web_browser.h +++ b/src/yuzu/applets/qt_web_browser.h @@ -16,8 +16,6 @@ #include "core/frontend/applets/web_browser.h" -enum class HIDButton : u8; - class GMainWindow; class InputInterpreter; class UrlRequestInterceptor; @@ -26,6 +24,10 @@ namespace Core { class System; } +namespace Core::HID { +enum class NpadButton : u64; +} + namespace InputCommon { class InputSubsystem; } @@ -114,7 +116,7 @@ private: * * @tparam HIDButton The list of buttons contained in yuzu_key_callbacks */ - template + template void HandleWindowFooterButtonPressedOnce(); /** @@ -123,7 +125,7 @@ private: * * @tparam HIDButton The list of buttons that can be converted into keyboard input. */ - template + template void HandleWindowKeyButtonPressedOnce(); /** @@ -132,7 +134,7 @@ private: * * @tparam HIDButton The list of buttons that can be converted into keyboard input. */ - template + template void HandleWindowKeyButtonHold(); /** diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index fd0a130a3a..114f17c06b 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -32,10 +32,11 @@ #include "common/settings.h" #include "core/core.h" #include "core/frontend/framebuffer_layout.h" -#include "input_common/keyboard.h" +#include "input_common/drivers/keyboard.h" +#include "input_common/drivers/mouse.h" +#include "input_common/drivers/tas_input.h" +#include "input_common/drivers/touch_screen.h" #include "input_common/main.h" -#include "input_common/mouse/mouse_input.h" -#include "input_common/tas/tas_input.h" #include "video_core/renderer_base.h" #include "video_core/video_core.h" #include "yuzu/bootmanager.h" @@ -296,7 +297,6 @@ GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread_, layout->setContentsMargins(0, 0, 0, 0); setLayout(layout); input_subsystem->Initialize(); - this->setMouseTracking(true); connect(this, &GRenderWindow::FirstFrameDisplayed, parent, &GMainWindow::OnLoadComplete); @@ -320,7 +320,8 @@ GRenderWindow::~GRenderWindow() { void GRenderWindow::OnFrameDisplayed() { input_subsystem->GetTas()->UpdateThread(); - const TasInput::TasState new_tas_state = std::get<0>(input_subsystem->GetTas()->GetStatus()); + const InputCommon::TasInput::TasState new_tas_state = + std::get<0>(input_subsystem->GetTas()->GetStatus()); if (!first_frame) { last_tas_state = new_tas_state; @@ -392,34 +393,329 @@ void GRenderWindow::closeEvent(QCloseEvent* event) { QWidget::closeEvent(event); } +int GRenderWindow::QtKeyToSwitchKey(Qt::Key qt_key) { + switch (qt_key) { + case Qt::Key_A: + return Settings::NativeKeyboard::A; + case Qt::Key_B: + return Settings::NativeKeyboard::B; + case Qt::Key_C: + return Settings::NativeKeyboard::C; + case Qt::Key_D: + return Settings::NativeKeyboard::D; + case Qt::Key_E: + return Settings::NativeKeyboard::E; + case Qt::Key_F: + return Settings::NativeKeyboard::F; + case Qt::Key_G: + return Settings::NativeKeyboard::G; + case Qt::Key_H: + return Settings::NativeKeyboard::H; + case Qt::Key_I: + return Settings::NativeKeyboard::I; + case Qt::Key_J: + return Settings::NativeKeyboard::J; + case Qt::Key_K: + return Settings::NativeKeyboard::K; + case Qt::Key_L: + return Settings::NativeKeyboard::L; + case Qt::Key_M: + return Settings::NativeKeyboard::M; + case Qt::Key_N: + return Settings::NativeKeyboard::N; + case Qt::Key_O: + return Settings::NativeKeyboard::O; + case Qt::Key_P: + return Settings::NativeKeyboard::P; + case Qt::Key_Q: + return Settings::NativeKeyboard::Q; + case Qt::Key_R: + return Settings::NativeKeyboard::R; + case Qt::Key_S: + return Settings::NativeKeyboard::S; + case Qt::Key_T: + return Settings::NativeKeyboard::T; + case Qt::Key_U: + return Settings::NativeKeyboard::U; + case Qt::Key_V: + return Settings::NativeKeyboard::V; + case Qt::Key_W: + return Settings::NativeKeyboard::W; + case Qt::Key_X: + return Settings::NativeKeyboard::X; + case Qt::Key_Y: + return Settings::NativeKeyboard::Y; + case Qt::Key_Z: + return Settings::NativeKeyboard::Z; + case Qt::Key_1: + return Settings::NativeKeyboard::N1; + case Qt::Key_2: + return Settings::NativeKeyboard::N2; + case Qt::Key_3: + return Settings::NativeKeyboard::N3; + case Qt::Key_4: + return Settings::NativeKeyboard::N4; + case Qt::Key_5: + return Settings::NativeKeyboard::N5; + case Qt::Key_6: + return Settings::NativeKeyboard::N6; + case Qt::Key_7: + return Settings::NativeKeyboard::N7; + case Qt::Key_8: + return Settings::NativeKeyboard::N8; + case Qt::Key_9: + return Settings::NativeKeyboard::N9; + case Qt::Key_0: + return Settings::NativeKeyboard::N0; + case Qt::Key_Return: + return Settings::NativeKeyboard::Return; + case Qt::Key_Escape: + return Settings::NativeKeyboard::Escape; + case Qt::Key_Backspace: + return Settings::NativeKeyboard::Backspace; + case Qt::Key_Tab: + return Settings::NativeKeyboard::Tab; + case Qt::Key_Space: + return Settings::NativeKeyboard::Space; + case Qt::Key_Minus: + return Settings::NativeKeyboard::Minus; + case Qt::Key_Plus: + case Qt::Key_questiondown: + return Settings::NativeKeyboard::Plus; + case Qt::Key_BracketLeft: + case Qt::Key_BraceLeft: + return Settings::NativeKeyboard::OpenBracket; + case Qt::Key_BracketRight: + case Qt::Key_BraceRight: + return Settings::NativeKeyboard::CloseBracket; + case Qt::Key_Bar: + return Settings::NativeKeyboard::Pipe; + case Qt::Key_Dead_Tilde: + return Settings::NativeKeyboard::Tilde; + case Qt::Key_Ntilde: + case Qt::Key_Semicolon: + return Settings::NativeKeyboard::Semicolon; + case Qt::Key_Apostrophe: + return Settings::NativeKeyboard::Quote; + case Qt::Key_Dead_Grave: + return Settings::NativeKeyboard::Backquote; + case Qt::Key_Comma: + return Settings::NativeKeyboard::Comma; + case Qt::Key_Period: + return Settings::NativeKeyboard::Period; + case Qt::Key_Slash: + return Settings::NativeKeyboard::Slash; + case Qt::Key_CapsLock: + return Settings::NativeKeyboard::CapsLock; + case Qt::Key_F1: + return Settings::NativeKeyboard::F1; + case Qt::Key_F2: + return Settings::NativeKeyboard::F2; + case Qt::Key_F3: + return Settings::NativeKeyboard::F3; + case Qt::Key_F4: + return Settings::NativeKeyboard::F4; + case Qt::Key_F5: + return Settings::NativeKeyboard::F5; + case Qt::Key_F6: + return Settings::NativeKeyboard::F6; + case Qt::Key_F7: + return Settings::NativeKeyboard::F7; + case Qt::Key_F8: + return Settings::NativeKeyboard::F8; + case Qt::Key_F9: + return Settings::NativeKeyboard::F9; + case Qt::Key_F10: + return Settings::NativeKeyboard::F10; + case Qt::Key_F11: + return Settings::NativeKeyboard::F11; + case Qt::Key_F12: + return Settings::NativeKeyboard::F12; + case Qt::Key_Print: + return Settings::NativeKeyboard::PrintScreen; + case Qt::Key_ScrollLock: + return Settings::NativeKeyboard::ScrollLock; + case Qt::Key_Pause: + return Settings::NativeKeyboard::Pause; + case Qt::Key_Insert: + return Settings::NativeKeyboard::Insert; + case Qt::Key_Home: + return Settings::NativeKeyboard::Home; + case Qt::Key_PageUp: + return Settings::NativeKeyboard::PageUp; + case Qt::Key_Delete: + return Settings::NativeKeyboard::Delete; + case Qt::Key_End: + return Settings::NativeKeyboard::End; + case Qt::Key_PageDown: + return Settings::NativeKeyboard::PageDown; + case Qt::Key_Right: + return Settings::NativeKeyboard::Right; + case Qt::Key_Left: + return Settings::NativeKeyboard::Left; + case Qt::Key_Down: + return Settings::NativeKeyboard::Down; + case Qt::Key_Up: + return Settings::NativeKeyboard::Up; + case Qt::Key_NumLock: + return Settings::NativeKeyboard::NumLock; + // Numpad keys are missing here + case Qt::Key_F13: + return Settings::NativeKeyboard::F13; + case Qt::Key_F14: + return Settings::NativeKeyboard::F14; + case Qt::Key_F15: + return Settings::NativeKeyboard::F15; + case Qt::Key_F16: + return Settings::NativeKeyboard::F16; + case Qt::Key_F17: + return Settings::NativeKeyboard::F17; + case Qt::Key_F18: + return Settings::NativeKeyboard::F18; + case Qt::Key_F19: + return Settings::NativeKeyboard::F19; + case Qt::Key_F20: + return Settings::NativeKeyboard::F20; + case Qt::Key_F21: + return Settings::NativeKeyboard::F21; + case Qt::Key_F22: + return Settings::NativeKeyboard::F22; + case Qt::Key_F23: + return Settings::NativeKeyboard::F23; + case Qt::Key_F24: + return Settings::NativeKeyboard::F24; + // case Qt::: + // return Settings::NativeKeyboard::KPComma; + // case Qt::: + // return Settings::NativeKeyboard::Ro; + case Qt::Key_Hiragana_Katakana: + return Settings::NativeKeyboard::KatakanaHiragana; + case Qt::Key_yen: + return Settings::NativeKeyboard::Yen; + case Qt::Key_Henkan: + return Settings::NativeKeyboard::Henkan; + case Qt::Key_Muhenkan: + return Settings::NativeKeyboard::Muhenkan; + // case Qt::: + // return Settings::NativeKeyboard::NumPadCommaPc98; + case Qt::Key_Hangul: + return Settings::NativeKeyboard::HangulEnglish; + case Qt::Key_Hangul_Hanja: + return Settings::NativeKeyboard::Hanja; + case Qt::Key_Katakana: + return Settings::NativeKeyboard::KatakanaKey; + case Qt::Key_Hiragana: + return Settings::NativeKeyboard::HiraganaKey; + case Qt::Key_Zenkaku_Hankaku: + return Settings::NativeKeyboard::ZenkakuHankaku; + // Modifier keys are handled by the modifier property + default: + return Settings::NativeKeyboard::None; + } +} + +int GRenderWindow::QtModifierToSwitchModifier(Qt::KeyboardModifiers qt_modifiers) { + int modifier = 0; + + if ((qt_modifiers & Qt::KeyboardModifier::ShiftModifier) != 0) { + modifier |= 1 << Settings::NativeKeyboard::LeftShift; + } + if ((qt_modifiers & Qt::KeyboardModifier::ControlModifier) != 0) { + modifier |= 1 << Settings::NativeKeyboard::LeftControl; + } + if ((qt_modifiers & Qt::KeyboardModifier::AltModifier) != 0) { + modifier |= 1 << Settings::NativeKeyboard::LeftAlt; + } + if ((qt_modifiers & Qt::KeyboardModifier::MetaModifier) != 0) { + modifier |= 1 << Settings::NativeKeyboard::LeftMeta; + } + + // TODO: These keys can't be obtained with Qt::KeyboardModifier + + // if ((qt_modifiers & 0x10) != 0) { + // modifier |= 1 << Settings::NativeKeyboard::RightShift; + // } + // if ((qt_modifiers & 0x20) != 0) { + // modifier |= 1 << Settings::NativeKeyboard::RightControl; + // } + // if ((qt_modifiers & 0x40) != 0) { + // modifier |= 1 << Settings::NativeKeyboard::RightAlt; + // } + // if ((qt_modifiers & 0x80) != 0) { + // modifier |= 1 << Settings::NativeKeyboard::RightMeta; + // } + // if ((qt_modifiers & 0x100) != 0) { + // modifier |= 1 << Settings::NativeKeyboard::CapsLock; + // } + // if ((qt_modifiers & 0x200) != 0) { + // modifier |= 1 << Settings::NativeKeyboard::NumLock; + // } + // if ((qt_modifiers & ???) != 0) { + // modifier |= 1 << Settings::NativeKeyboard::ScrollLock; + // } + // if ((qt_modifiers & ???) != 0) { + // modifier |= 1 << Settings::NativeKeyboard::Katakana; + // } + // if ((qt_modifiers & ???) != 0) { + // modifier |= 1 << Settings::NativeKeyboard::Hiragana; + // } + return modifier; +} + void GRenderWindow::keyPressEvent(QKeyEvent* event) { + /** + * This feature can be enhanced with the following functions, but they do not provide + * cross-platform behavior. + * + * event->nativeVirtualKey() can distinguish between keys on the numpad. + * event->nativeModifiers() can distinguish between left and right keys and numlock, + * capslock, scroll lock. + */ if (!event->isAutoRepeat()) { + const auto modifier = QtModifierToSwitchModifier(event->modifiers()); + const auto key = QtKeyToSwitchKey(Qt::Key(event->key())); + input_subsystem->GetKeyboard()->SetKeyboardModifiers(modifier); + input_subsystem->GetKeyboard()->PressKeyboardKey(key); + // This is used for gamepads that can have any key mapped input_subsystem->GetKeyboard()->PressKey(event->key()); } } void GRenderWindow::keyReleaseEvent(QKeyEvent* event) { + /** + * This feature can be enhanced with the following functions, but they do not provide + * cross-platform behavior. + * + * event->nativeVirtualKey() can distinguish between keys on the numpad. + * event->nativeModifiers() can distinguish between left and right buttons and numlock, + * capslock, scroll lock. + */ if (!event->isAutoRepeat()) { + const auto modifier = QtModifierToSwitchModifier(event->modifiers()); + const auto key = QtKeyToSwitchKey(Qt::Key(event->key())); + input_subsystem->GetKeyboard()->SetKeyboardModifiers(modifier); + input_subsystem->GetKeyboard()->ReleaseKeyboardKey(key); + // This is used for gamepads that can have any key mapped input_subsystem->GetKeyboard()->ReleaseKey(event->key()); } } -MouseInput::MouseButton GRenderWindow::QtButtonToMouseButton(Qt::MouseButton button) { +InputCommon::MouseButton GRenderWindow::QtButtonToMouseButton(Qt::MouseButton button) { switch (button) { case Qt::LeftButton: - return MouseInput::MouseButton::Left; + return InputCommon::MouseButton::Left; case Qt::RightButton: - return MouseInput::MouseButton::Right; + return InputCommon::MouseButton::Right; case Qt::MiddleButton: - return MouseInput::MouseButton::Wheel; + return InputCommon::MouseButton::Wheel; case Qt::BackButton: - return MouseInput::MouseButton::Backward; + return InputCommon::MouseButton::Backward; case Qt::ForwardButton: - return MouseInput::MouseButton::Forward; + return InputCommon::MouseButton::Forward; case Qt::TaskButton: - return MouseInput::MouseButton::Task; + return InputCommon::MouseButton::Task; default: - return MouseInput::MouseButton::Extra; + return InputCommon::MouseButton::Extra; } } @@ -432,12 +728,9 @@ void GRenderWindow::mousePressEvent(QMouseEvent* event) { // coordinates and map them to the current render area const auto pos = mapFromGlobal(QCursor::pos()); const auto [x, y] = ScaleTouch(pos); + const auto [touch_x, touch_y] = MapToTouchScreen(x, y); const auto button = QtButtonToMouseButton(event->button()); - input_subsystem->GetMouse()->PressButton(x, y, button); - - if (event->button() == Qt::LeftButton) { - this->TouchPressed(x, y, 0); - } + input_subsystem->GetMouse()->PressButton(x, y, touch_x, touch_y, button); emit MouseActivity(); } @@ -451,12 +744,12 @@ void GRenderWindow::mouseMoveEvent(QMouseEvent* event) { // coordinates and map them to the current render area const auto pos = mapFromGlobal(QCursor::pos()); const auto [x, y] = ScaleTouch(pos); + const auto [touch_x, touch_y] = MapToTouchScreen(x, y); const int center_x = width() / 2; const int center_y = height() / 2; - input_subsystem->GetMouse()->MouseMove(x, y, center_x, center_y); - this->TouchMoved(x, y, 0); + input_subsystem->GetMouse()->MouseMove(x, y, touch_x, touch_y, center_x, center_y); - if (Settings::values.mouse_panning) { + if (Settings::values.mouse_panning && !Settings::values.mouse_enabled) { QCursor::setPos(mapToGlobal({center_x, center_y})); } @@ -471,10 +764,12 @@ void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) { const auto button = QtButtonToMouseButton(event->button()); input_subsystem->GetMouse()->ReleaseButton(button); +} - if (event->button() == Qt::LeftButton) { - this->TouchReleased(0); - } +void GRenderWindow::wheelEvent(QWheelEvent* event) { + const int x = event->angleDelta().x(); + const int y = event->angleDelta().y(); + input_subsystem->GetMouse()->MouseWheelChange(x, y); } void GRenderWindow::TouchBeginEvent(const QTouchEvent* event) { @@ -497,7 +792,7 @@ void GRenderWindow::TouchUpdateEvent(const QTouchEvent* event) { for (std::size_t id = 0; id < touch_ids.size(); ++id) { if (!TouchExist(touch_ids[id], touch_points)) { touch_ids[id] = 0; - this->TouchReleased(id + 1); + input_subsystem->GetTouchScreen()->TouchReleased(id); } } } @@ -506,28 +801,28 @@ void GRenderWindow::TouchEndEvent() { for (std::size_t id = 0; id < touch_ids.size(); ++id) { if (touch_ids[id] != 0) { touch_ids[id] = 0; - this->TouchReleased(id + 1); + input_subsystem->GetTouchScreen()->TouchReleased(id); } } } -bool GRenderWindow::TouchStart(const QTouchEvent::TouchPoint& touch_point) { +void GRenderWindow::TouchStart(const QTouchEvent::TouchPoint& touch_point) { for (std::size_t id = 0; id < touch_ids.size(); ++id) { if (touch_ids[id] == 0) { touch_ids[id] = touch_point.id() + 1; const auto [x, y] = ScaleTouch(touch_point.pos()); - this->TouchPressed(x, y, id + 1); - return true; + const auto [touch_x, touch_y] = MapToTouchScreen(x, y); + input_subsystem->GetTouchScreen()->TouchPressed(touch_x, touch_y, id); } } - return false; } bool GRenderWindow::TouchUpdate(const QTouchEvent::TouchPoint& touch_point) { for (std::size_t id = 0; id < touch_ids.size(); ++id) { if (touch_ids[id] == static_cast(touch_point.id() + 1)) { const auto [x, y] = ScaleTouch(touch_point.pos()); - this->TouchMoved(x, y, id + 1); + const auto [touch_x, touch_y] = MapToTouchScreen(x, y); + input_subsystem->GetTouchScreen()->TouchMoved(touch_x, touch_y, id); return true; } } @@ -560,7 +855,7 @@ void GRenderWindow::focusOutEvent(QFocusEvent* event) { QWidget::focusOutEvent(event); input_subsystem->GetKeyboard()->ReleaseAllKeys(); input_subsystem->GetMouse()->ReleaseAllButtons(); - this->TouchReleased(0); + input_subsystem->GetTouchScreen()->ReleaseAllTouch(); } void GRenderWindow::resizeEvent(QResizeEvent* event) { @@ -769,7 +1064,7 @@ void GRenderWindow::showEvent(QShowEvent* event) { bool GRenderWindow::eventFilter(QObject* object, QEvent* event) { if (event->type() == QEvent::HoverMove) { - if (Settings::values.mouse_panning) { + if (Settings::values.mouse_panning || Settings::values.mouse_enabled) { auto* hover_event = static_cast(event); mouseMoveEvent(hover_event); return false; diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h index 061e3605f3..92297a43b3 100644 --- a/src/yuzu/bootmanager.h +++ b/src/yuzu/bootmanager.h @@ -30,21 +30,18 @@ class System; namespace InputCommon { class InputSubsystem; -} - -namespace MouseInput { enum class MouseButton; -} +} // namespace InputCommon + +namespace InputCommon::TasInput { +enum class TasState; +} // namespace InputCommon::TasInput namespace VideoCore { enum class LoadCallbackStage; class RendererBase; } // namespace VideoCore -namespace TasInput { -enum class TasState; -} - class EmuThread final : public QThread { Q_OBJECT @@ -161,15 +158,22 @@ public: void resizeEvent(QResizeEvent* event) override; + /// Converts a Qt keybard key into NativeKeyboard key + static int QtKeyToSwitchKey(Qt::Key qt_keys); + + /// Converts a Qt modifier keys into NativeKeyboard modifier keys + static int QtModifierToSwitchModifier(Qt::KeyboardModifiers qt_modifiers); + void keyPressEvent(QKeyEvent* event) override; void keyReleaseEvent(QKeyEvent* event) override; /// Converts a Qt mouse button into MouseInput mouse button - static MouseInput::MouseButton QtButtonToMouseButton(Qt::MouseButton button); + static InputCommon::MouseButton QtButtonToMouseButton(Qt::MouseButton button); void mousePressEvent(QMouseEvent* event) override; void mouseMoveEvent(QMouseEvent* event) override; void mouseReleaseEvent(QMouseEvent* event) override; + void wheelEvent(QWheelEvent* event) override; bool event(QEvent* event) override; @@ -214,7 +218,7 @@ private: void TouchUpdateEvent(const QTouchEvent* event); void TouchEndEvent(); - bool TouchStart(const QTouchEvent::TouchPoint& touch_point); + void TouchStart(const QTouchEvent::TouchPoint& touch_point); bool TouchUpdate(const QTouchEvent::TouchPoint& touch_point); bool TouchExist(std::size_t id, const QList& touch_points) const; @@ -241,7 +245,7 @@ private: QWidget* child_widget = nullptr; bool first_frame = false; - TasInput::TasState last_tas_state; + InputCommon::TasInput::TasState last_tas_state; std::array touch_ids{}; diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 2b670ddfd0..2c70d05486 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -11,7 +11,6 @@ #include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/hid/controllers/npad.h" #include "input_common/main.h" -#include "input_common/udp/client.h" #include "yuzu/configuration/config.h" namespace FS = Common::FS; @@ -61,162 +60,6 @@ const std::array Config::default_stick_mod = { 0, }; -const std::array Config::default_mouse_buttons = - { - Qt::Key_BracketLeft, Qt::Key_BracketRight, Qt::Key_Apostrophe, Qt::Key_Minus, Qt::Key_Equal, -}; - -const std::array Config::default_keyboard_keys = { - 0, - 0, - 0, - 0, - Qt::Key_A, - Qt::Key_B, - Qt::Key_C, - Qt::Key_D, - Qt::Key_E, - Qt::Key_F, - Qt::Key_G, - Qt::Key_H, - Qt::Key_I, - Qt::Key_J, - Qt::Key_K, - Qt::Key_L, - Qt::Key_M, - Qt::Key_N, - Qt::Key_O, - Qt::Key_P, - Qt::Key_Q, - Qt::Key_R, - Qt::Key_S, - Qt::Key_T, - Qt::Key_U, - Qt::Key_V, - Qt::Key_W, - Qt::Key_X, - Qt::Key_Y, - Qt::Key_Z, - Qt::Key_1, - Qt::Key_2, - Qt::Key_3, - Qt::Key_4, - Qt::Key_5, - Qt::Key_6, - Qt::Key_7, - Qt::Key_8, - Qt::Key_9, - Qt::Key_0, - Qt::Key_Enter, - Qt::Key_Escape, - Qt::Key_Backspace, - Qt::Key_Tab, - Qt::Key_Space, - Qt::Key_Minus, - Qt::Key_Equal, - Qt::Key_BracketLeft, - Qt::Key_BracketRight, - Qt::Key_Backslash, - Qt::Key_Dead_Tilde, - Qt::Key_Semicolon, - Qt::Key_Apostrophe, - Qt::Key_Dead_Grave, - Qt::Key_Comma, - Qt::Key_Period, - Qt::Key_Slash, - Qt::Key_CapsLock, - - Qt::Key_F1, - Qt::Key_F2, - Qt::Key_F3, - Qt::Key_F4, - Qt::Key_F5, - Qt::Key_F6, - Qt::Key_F7, - Qt::Key_F8, - Qt::Key_F9, - Qt::Key_F10, - Qt::Key_F11, - Qt::Key_F12, - - Qt::Key_SysReq, - Qt::Key_ScrollLock, - Qt::Key_Pause, - Qt::Key_Insert, - Qt::Key_Home, - Qt::Key_PageUp, - Qt::Key_Delete, - Qt::Key_End, - Qt::Key_PageDown, - Qt::Key_Right, - Qt::Key_Left, - Qt::Key_Down, - Qt::Key_Up, - - Qt::Key_NumLock, - Qt::Key_Slash, - Qt::Key_Asterisk, - Qt::Key_Minus, - Qt::Key_Plus, - Qt::Key_Enter, - Qt::Key_1, - Qt::Key_2, - Qt::Key_3, - Qt::Key_4, - Qt::Key_5, - Qt::Key_6, - Qt::Key_7, - Qt::Key_8, - Qt::Key_9, - Qt::Key_0, - Qt::Key_Period, - - 0, - 0, - Qt::Key_PowerOff, - Qt::Key_Equal, - - Qt::Key_F13, - Qt::Key_F14, - Qt::Key_F15, - Qt::Key_F16, - Qt::Key_F17, - Qt::Key_F18, - Qt::Key_F19, - Qt::Key_F20, - Qt::Key_F21, - Qt::Key_F22, - Qt::Key_F23, - Qt::Key_F24, - - Qt::Key_Open, - Qt::Key_Help, - Qt::Key_Menu, - 0, - Qt::Key_Stop, - Qt::Key_AudioRepeat, - Qt::Key_Undo, - Qt::Key_Cut, - Qt::Key_Copy, - Qt::Key_Paste, - Qt::Key_Find, - Qt::Key_VolumeMute, - Qt::Key_VolumeUp, - Qt::Key_VolumeDown, - Qt::Key_CapsLock, - Qt::Key_NumLock, - Qt::Key_ScrollLock, - Qt::Key_Comma, - - Qt::Key_ParenLeft, - Qt::Key_ParenRight, -}; - -const std::array Config::default_keyboard_mods = { - Qt::Key_Control, Qt::Key_Shift, Qt::Key_Alt, Qt::Key_ApplicationLeft, - Qt::Key_Control, Qt::Key_Shift, Qt::Key_AltGr, Qt::Key_ApplicationRight, -}; - // This shouldn't have anything except static initializers (no functions). So // QKeySequence(...).toString() is NOT ALLOWED HERE. // This must be in alphabetical order according to action name as it must have the same order as @@ -430,18 +273,6 @@ void Config::ReadPlayerValue(std::size_t player_index) { } } - for (int i = 0; i < Settings::NativeVibration::NumVibrations; ++i) { - auto& player_vibrations = player.vibrations[i]; - - player_vibrations = - qt_config - ->value(QStringLiteral("%1").arg(player_prefix) + - QString::fromUtf8(Settings::NativeVibration::mapping[i]), - QString{}) - .toString() - .toStdString(); - } - for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) { const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]); auto& player_motions = player.motions[i]; @@ -496,35 +327,10 @@ void Config::ReadDebugValues() { void Config::ReadKeyboardValues() { ReadBasicSetting(Settings::values.keyboard_enabled); - - std::transform(default_keyboard_keys.begin(), default_keyboard_keys.end(), - Settings::values.keyboard_keys.begin(), InputCommon::GenerateKeyboardParam); - std::transform(default_keyboard_mods.begin(), default_keyboard_mods.end(), - Settings::values.keyboard_keys.begin() + - Settings::NativeKeyboard::LeftControlKey, - InputCommon::GenerateKeyboardParam); - std::transform(default_keyboard_mods.begin(), default_keyboard_mods.end(), - Settings::values.keyboard_mods.begin(), InputCommon::GenerateKeyboardParam); } void Config::ReadMouseValues() { ReadBasicSetting(Settings::values.mouse_enabled); - - for (int i = 0; i < Settings::NativeMouseButton::NumMouseButtons; ++i) { - const std::string default_param = - InputCommon::GenerateKeyboardParam(default_mouse_buttons[i]); - auto& mouse_buttons = Settings::values.mouse_buttons[i]; - - mouse_buttons = qt_config - ->value(QStringLiteral("mouse_") + - QString::fromUtf8(Settings::NativeMouseButton::mapping[i]), - QString::fromStdString(default_param)) - .toString() - .toStdString(); - if (mouse_buttons.empty()) { - mouse_buttons = default_param; - } - } } void Config::ReadTouchscreenValues() { @@ -574,7 +380,6 @@ void Config::ReadControlValues() { ReadBasicSetting(Settings::values.tas_enable); ReadBasicSetting(Settings::values.tas_loop); - ReadBasicSetting(Settings::values.tas_swap_controllers); ReadBasicSetting(Settings::values.pause_tas_on_load); ReadGlobalSetting(Settings::values.use_docked_mode); @@ -625,13 +430,12 @@ void Config::ReadMotionTouchValues() { } qt_config->endArray(); - ReadBasicSetting(Settings::values.motion_device); ReadBasicSetting(Settings::values.touch_device); - ReadBasicSetting(Settings::values.use_touch_from_button); ReadBasicSetting(Settings::values.touch_from_button_map_index); Settings::values.touch_from_button_map_index = std::clamp( Settings::values.touch_from_button_map_index.GetValue(), 0, num_touch_from_button_maps - 1); ReadBasicSetting(Settings::values.udp_input_servers); + ReadBasicSetting(Settings::values.enable_udp_controller); } void Config::ReadCoreValues() { @@ -1075,11 +879,6 @@ void Config::SavePlayerValue(std::size_t player_index) { QString::fromStdString(player.analogs[i]), QString::fromStdString(default_param)); } - for (int i = 0; i < Settings::NativeVibration::NumVibrations; ++i) { - WriteSetting(QStringLiteral("%1").arg(player_prefix) + - QString::fromStdString(Settings::NativeVibration::mapping[i]), - QString::fromStdString(player.vibrations[i]), QString{}); - } for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) { const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]); WriteSetting(QStringLiteral("%1").arg(player_prefix) + @@ -1111,15 +910,6 @@ void Config::SaveDebugValues() { void Config::SaveMouseValues() { WriteBasicSetting(Settings::values.mouse_enabled); - - for (int i = 0; i < Settings::NativeMouseButton::NumMouseButtons; ++i) { - const std::string default_param = - InputCommon::GenerateKeyboardParam(default_mouse_buttons[i]); - WriteSetting(QStringLiteral("mouse_") + - QString::fromStdString(Settings::NativeMouseButton::mapping[i]), - QString::fromStdString(Settings::values.mouse_buttons[i]), - QString::fromStdString(default_param)); - } } void Config::SaveTouchscreenValues() { @@ -1133,11 +923,10 @@ void Config::SaveTouchscreenValues() { } void Config::SaveMotionTouchValues() { - WriteBasicSetting(Settings::values.motion_device); WriteBasicSetting(Settings::values.touch_device); - WriteBasicSetting(Settings::values.use_touch_from_button); WriteBasicSetting(Settings::values.touch_from_button_map_index); WriteBasicSetting(Settings::values.udp_input_servers); + WriteBasicSetting(Settings::values.enable_udp_controller); qt_config->beginWriteArray(QStringLiteral("touch_from_button_maps")); for (std::size_t p = 0; p < Settings::values.touch_from_button_maps.size(); ++p) { @@ -1210,7 +999,6 @@ void Config::SaveControlValues() { WriteBasicSetting(Settings::values.tas_enable); WriteBasicSetting(Settings::values.tas_loop); - WriteBasicSetting(Settings::values.tas_swap_controllers); WriteBasicSetting(Settings::values.pause_tas_on_load); qt_config->endGroup(); diff --git a/src/yuzu/configuration/configure_debug_controller.cpp b/src/yuzu/configuration/configure_debug_controller.cpp index 31ec483840..9a8de92a1e 100644 --- a/src/yuzu/configuration/configure_debug_controller.cpp +++ b/src/yuzu/configuration/configure_debug_controller.cpp @@ -2,17 +2,18 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "core/core.h" +#include "core/hid/hid_core.h" #include "ui_configure_debug_controller.h" #include "yuzu/configuration/configure_debug_controller.h" #include "yuzu/configuration/configure_input_player.h" ConfigureDebugController::ConfigureDebugController(QWidget* parent, InputCommon::InputSubsystem* input_subsystem, - InputProfiles* profiles, Core::System& system) + InputProfiles* profiles, + Core::HID::HIDCore& hid_core, bool is_powered_on) : QDialog(parent), ui(std::make_unique()), - debug_controller( - new ConfigureInputPlayer(this, 9, nullptr, input_subsystem, profiles, system, true)) { + debug_controller(new ConfigureInputPlayer(this, 9, nullptr, input_subsystem, profiles, + hid_core, is_powered_on, true)) { ui->setupUi(this); ui->controllerLayout->addWidget(debug_controller); diff --git a/src/yuzu/configuration/configure_debug_controller.h b/src/yuzu/configuration/configure_debug_controller.h index 6e17c5aa07..d716edbc24 100644 --- a/src/yuzu/configuration/configure_debug_controller.h +++ b/src/yuzu/configuration/configure_debug_controller.h @@ -13,8 +13,8 @@ class ConfigureInputPlayer; class InputProfiles; -namespace Core { -class System; +namespace Core::HID { +class HIDCore; } namespace InputCommon { @@ -30,7 +30,8 @@ class ConfigureDebugController : public QDialog { public: explicit ConfigureDebugController(QWidget* parent, InputCommon::InputSubsystem* input_subsystem, - InputProfiles* profiles, Core::System& system); + InputProfiles* profiles, Core::HID::HIDCore& hid_core, + bool is_powered_on); ~ConfigureDebugController() override; void ApplyConfiguration(); diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index 1599299db2..d53179dbbb 100644 --- a/src/yuzu/configuration/configure_input.cpp +++ b/src/yuzu/configuration/configure_input.cpp @@ -10,6 +10,8 @@ #include #include "core/core.h" +#include "core/hid/emulated_controller.h" +#include "core/hid/hid_core.h" #include "core/hle/service/am/am.h" #include "core/hle/service/am/applet_ae.h" #include "core/hle/service/am/applet_oe.h" @@ -22,7 +24,6 @@ #include "yuzu/configuration/configure_input_advanced.h" #include "yuzu/configuration/configure_input_player.h" #include "yuzu/configuration/configure_motion_touch.h" -#include "yuzu/configuration/configure_mouse_advanced.h" #include "yuzu/configuration/configure_touchscreen_advanced.h" #include "yuzu/configuration/configure_vibration.h" #include "yuzu/configuration/input_profiles.h" @@ -75,23 +76,25 @@ ConfigureInput::~ConfigureInput() = default; void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem, std::size_t max_players) { + const bool is_powered_on = system.IsPoweredOn(); + auto& hid_core = system.HIDCore(); player_controllers = { new ConfigureInputPlayer(this, 0, ui->consoleInputSettings, input_subsystem, profiles.get(), - system), + hid_core, is_powered_on), new ConfigureInputPlayer(this, 1, ui->consoleInputSettings, input_subsystem, profiles.get(), - system), + hid_core, is_powered_on), new ConfigureInputPlayer(this, 2, ui->consoleInputSettings, input_subsystem, profiles.get(), - system), + hid_core, is_powered_on), new ConfigureInputPlayer(this, 3, ui->consoleInputSettings, input_subsystem, profiles.get(), - system), + hid_core, is_powered_on), new ConfigureInputPlayer(this, 4, ui->consoleInputSettings, input_subsystem, profiles.get(), - system), + hid_core, is_powered_on), new ConfigureInputPlayer(this, 5, ui->consoleInputSettings, input_subsystem, profiles.get(), - system), + hid_core, is_powered_on), new ConfigureInputPlayer(this, 6, ui->consoleInputSettings, input_subsystem, profiles.get(), - system), + hid_core, is_powered_on), new ConfigureInputPlayer(this, 7, ui->consoleInputSettings, input_subsystem, profiles.get(), - system), + hid_core, is_powered_on), }; player_tabs = { @@ -114,6 +117,7 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem, player_tabs[i]->setLayout(new QHBoxLayout(player_tabs[i])); player_tabs[i]->layout()->addWidget(player_controllers[i]); connect(player_controllers[i], &ConfigureInputPlayer::Connected, [&, i](bool is_connected) { + // Ensures that the controllers are always connected in sequential order if (is_connected) { for (std::size_t index = 0; index <= i; ++index) { player_connected[index]->setChecked(is_connected); @@ -146,13 +150,12 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem, advanced = new ConfigureInputAdvanced(this); ui->tabAdvanced->setLayout(new QHBoxLayout(ui->tabAdvanced)); ui->tabAdvanced->layout()->addWidget(advanced); - connect(advanced, &ConfigureInputAdvanced::CallDebugControllerDialog, [this, input_subsystem] { - CallConfigureDialog(*this, input_subsystem, profiles.get(), - system); - }); - connect(advanced, &ConfigureInputAdvanced::CallMouseConfigDialog, [this, input_subsystem] { - CallConfigureDialog(*this, input_subsystem); - }); + + connect(advanced, &ConfigureInputAdvanced::CallDebugControllerDialog, + [this, input_subsystem, &hid_core, is_powered_on] { + CallConfigureDialog( + *this, input_subsystem, profiles.get(), hid_core, is_powered_on); + }); connect(advanced, &ConfigureInputAdvanced::CallTouchscreenConfigDialog, [this] { CallConfigureDialog(*this); }); connect(advanced, &ConfigureInputAdvanced::CallMotionTouchConfigDialog, @@ -184,22 +187,8 @@ QList ConfigureInput::GetSubTabs() const { void ConfigureInput::ApplyConfiguration() { for (auto* controller : player_controllers) { controller->ApplyConfiguration(); - controller->TryDisconnectSelectedController(); } - // This emulates a delay between disconnecting and reconnecting controllers as some games - // do not respond to a change in controller type if it was instantaneous. - using namespace std::chrono_literals; - std::this_thread::sleep_for(150ms); - - for (auto* controller : player_controllers) { - controller->TryConnectSelectedController(); - } - - // This emulates a delay between disconnecting and reconnecting controllers as some games - // do not respond to a change in controller type if it was instantaneous. - std::this_thread::sleep_for(150ms); - advanced->ApplyConfiguration(); const bool pre_docked_mode = Settings::values.use_docked_mode.GetValue(); @@ -223,8 +212,10 @@ void ConfigureInput::RetranslateUI() { } void ConfigureInput::LoadConfiguration() { + const auto* handheld = system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld); + LoadPlayerControllerIndices(); - UpdateDockedState(Settings::values.players.GetValue()[8].connected); + UpdateDockedState(handheld->IsConnected()); ui->vibrationGroup->setChecked(Settings::values.vibration_enabled.GetValue()); ui->motionGroup->setChecked(Settings::values.motion_enabled.GetValue()); @@ -232,9 +223,16 @@ void ConfigureInput::LoadConfiguration() { void ConfigureInput::LoadPlayerControllerIndices() { for (std::size_t i = 0; i < player_connected.size(); ++i) { - const auto connected = Settings::values.players.GetValue()[i].connected || - (i == 0 && Settings::values.players.GetValue()[8].connected); - player_connected[i]->setChecked(connected); + if (i == 0) { + auto* handheld = + system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld); + if (handheld->IsConnected()) { + player_connected[i]->setChecked(true); + continue; + } + } + const auto* controller = system.HIDCore().GetEmulatedControllerByIndex(i); + player_connected[i]->setChecked(controller->IsConnected()); } } diff --git a/src/yuzu/configuration/configure_input_advanced.cpp b/src/yuzu/configuration/configure_input_advanced.cpp index b30f09013c..65c8e59ac4 100644 --- a/src/yuzu/configuration/configure_input_advanced.cpp +++ b/src/yuzu/configuration/configure_input_advanced.cpp @@ -82,7 +82,6 @@ ConfigureInputAdvanced::ConfigureInputAdvanced(QWidget* parent) connect(ui->debug_configure, &QPushButton::clicked, this, [this] { CallDebugControllerDialog(); }); - connect(ui->mouse_advanced, &QPushButton::clicked, this, [this] { CallMouseConfigDialog(); }); connect(ui->touchscreen_advanced, &QPushButton::clicked, this, [this] { CallTouchscreenConfigDialog(); }); connect(ui->buttonMotionTouch, &QPushButton::clicked, this, @@ -131,6 +130,7 @@ void ConfigureInputAdvanced::ApplyConfiguration() { static_cast(ui->mouse_panning_sensitivity->value()); Settings::values.touchscreen.enabled = ui->touchscreen_enabled->isChecked(); Settings::values.enable_raw_input = ui->enable_raw_input->isChecked(); + Settings::values.enable_udp_controller = ui->enable_udp_controller->isChecked(); } void ConfigureInputAdvanced::LoadConfiguration() { @@ -161,6 +161,7 @@ void ConfigureInputAdvanced::LoadConfiguration() { ui->mouse_panning_sensitivity->setValue(Settings::values.mouse_panning_sensitivity.GetValue()); ui->touchscreen_enabled->setChecked(Settings::values.touchscreen.enabled); ui->enable_raw_input->setChecked(Settings::values.enable_raw_input.GetValue()); + ui->enable_udp_controller->setChecked(Settings::values.enable_udp_controller.GetValue()); UpdateUIEnabled(); } @@ -178,7 +179,8 @@ void ConfigureInputAdvanced::RetranslateUI() { } void ConfigureInputAdvanced::UpdateUIEnabled() { - ui->mouse_advanced->setEnabled(ui->mouse_enabled->isChecked()); ui->debug_configure->setEnabled(ui->debug_enabled->isChecked()); ui->touchscreen_advanced->setEnabled(ui->touchscreen_enabled->isChecked()); + ui->mouse_panning->setEnabled(!ui->mouse_enabled->isChecked()); + ui->mouse_panning_sensitivity->setEnabled(!ui->mouse_enabled->isChecked()); } diff --git a/src/yuzu/configuration/configure_input_advanced.ui b/src/yuzu/configuration/configure_input_advanced.ui index 9095206a07..df0e4d6024 100644 --- a/src/yuzu/configuration/configure_input_advanced.ui +++ b/src/yuzu/configuration/configure_input_advanced.ui @@ -2528,11 +2528,11 @@ 0 - + - Other + Emulated Devices - + @@ -2547,7 +2547,7 @@ - + 0 @@ -2555,53 +2555,18 @@ - Emulate Analog with Keyboard Input + Mouse - - - - 0 - 23 - - + - Enable mouse panning + Touchscreen - - - - Mouse sensitivity - - - Qt::AlignCenter - - - % - - - 1 - - - 100 - - - 100 - - - - - - - Advanced - - - - + Qt::Horizontal @@ -2617,80 +2582,130 @@ - - - - Advanced - - - - - - - Touchscreen - - - + + + + Advanced + + + - - - - 0 - 23 - - - - Mouse - - - - - - - Motion / Touch - - - - - - - Configure - - - - Debug Controller - + Configure - - - - Requires restarting yuzu - - - - 0 - 23 - - - - Enable XInput 8 player support (disables web applet) - - - + + + + Other + + + + + + + 0 + 23 + + + + Emulate Analog with Keyboard Input + + + + + + + Requires restarting yuzu + + + + 0 + 23 + + + + Enable XInput 8 player support (disables web applet) + + + + + + + + 0 + 23 + + + + Enable UDP controllers (not needed for motion) + + + + + + + + 0 + 23 + + + + Enable mouse panning + + + + + + + Mouse sensitivity + + + Qt::AlignCenter + + + % + + + 1 + + + 100 + + + 100 + + + + + + + Motion / Touch + + + + + + + Configure + + + + + + diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index 3aab5d5f8f..ec071d6ec4 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -12,14 +12,12 @@ #include #include #include "common/param_package.h" -#include "core/core.h" -#include "core/hle/service/hid/controllers/npad.h" -#include "core/hle/service/hid/hid.h" -#include "core/hle/service/sm/sm.h" -#include "input_common/gcadapter/gc_poller.h" +#include "core/hid/emulated_controller.h" +#include "core/hid/hid_core.h" +#include "core/hid/hid_types.h" +#include "input_common/drivers/keyboard.h" +#include "input_common/drivers/mouse.h" #include "input_common/main.h" -#include "input_common/mouse/mouse_poller.h" -#include "input_common/udp/udp.h" #include "ui_configure_input_player.h" #include "yuzu/bootmanager.h" #include "yuzu/configuration/config.h" @@ -29,8 +27,6 @@ #include "yuzu/configuration/input_profiles.h" #include "yuzu/util/limitable_input_dialog.h" -using namespace Service::HID; - const std::array ConfigureInputPlayer::analog_sub_buttons{{ "up", @@ -41,33 +37,8 @@ const std::array namespace { -constexpr std::size_t HANDHELD_INDEX = 8; - -void UpdateController(Settings::ControllerType controller_type, std::size_t npad_index, - bool connected, Core::System& system) { - if (!system.IsPoweredOn()) { - return; - } - Service::SM::ServiceManager& sm = system.ServiceManager(); - - auto& npad = sm.GetService("hid")->GetAppletResource()->GetController( - HidController::NPad); - - npad.UpdateControllerAt(npad.MapSettingsTypeToNPad(controller_type), npad_index, connected); -} - QString GetKeyName(int key_code) { switch (key_code) { - case Qt::LeftButton: - return QObject::tr("Click 0"); - case Qt::RightButton: - return QObject::tr("Click 1"); - case Qt::MiddleButton: - return QObject::tr("Click 2"); - case Qt::BackButton: - return QObject::tr("Click 3"); - case Qt::ForwardButton: - return QObject::tr("Click 4"); case Qt::Key_Shift: return QObject::tr("Shift"); case Qt::Key_Control: @@ -81,6 +52,61 @@ QString GetKeyName(int key_code) { } } +QString GetButtonName(Common::Input::ButtonNames button_name) { + switch (button_name) { + case Common::Input::ButtonNames::ButtonLeft: + return QObject::tr("Left"); + case Common::Input::ButtonNames::ButtonRight: + return QObject::tr("Right"); + case Common::Input::ButtonNames::ButtonDown: + return QObject::tr("Down"); + case Common::Input::ButtonNames::ButtonUp: + return QObject::tr("Up"); + case Common::Input::ButtonNames::TriggerZ: + return QObject::tr("Z"); + case Common::Input::ButtonNames::TriggerR: + return QObject::tr("R"); + case Common::Input::ButtonNames::TriggerL: + return QObject::tr("L"); + case Common::Input::ButtonNames::ButtonA: + return QObject::tr("A"); + case Common::Input::ButtonNames::ButtonB: + return QObject::tr("B"); + case Common::Input::ButtonNames::ButtonX: + return QObject::tr("X"); + case Common::Input::ButtonNames::ButtonY: + return QObject::tr("Y"); + case Common::Input::ButtonNames::ButtonStart: + return QObject::tr("Start"); + case Common::Input::ButtonNames::L1: + return QObject::tr("L1"); + case Common::Input::ButtonNames::L2: + return QObject::tr("L2"); + case Common::Input::ButtonNames::L3: + return QObject::tr("L3"); + case Common::Input::ButtonNames::R1: + return QObject::tr("R1"); + case Common::Input::ButtonNames::R2: + return QObject::tr("R2"); + case Common::Input::ButtonNames::R3: + return QObject::tr("R3"); + case Common::Input::ButtonNames::Circle: + return QObject::tr("Circle"); + case Common::Input::ButtonNames::Cross: + return QObject::tr("Cross"); + case Common::Input::ButtonNames::Square: + return QObject::tr("Square"); + case Common::Input::ButtonNames::Triangle: + return QObject::tr("Triangle"); + case Common::Input::ButtonNames::Share: + return QObject::tr("Share"); + case Common::Input::ButtonNames::Options: + return QObject::tr("Options"); + default: + return QObject::tr("[undefined]"); + } +} + void SetAnalogParam(const Common::ParamPackage& input_param, Common::ParamPackage& analog_param, const std::string& button_name) { // The poller returned a complete axis, so set all the buttons @@ -97,95 +123,75 @@ void SetAnalogParam(const Common::ParamPackage& input_param, Common::ParamPackag } analog_param.Set(button_name, input_param.Serialize()); } +} // namespace -QString ButtonToText(const Common::ParamPackage& param) { +QString ConfigureInputPlayer::ButtonToText(const Common::ParamPackage& param) { if (!param.Has("engine")) { return QObject::tr("[not set]"); } + const QString toggle = QString::fromStdString(param.Get("toggle", false) ? "~" : ""); + const QString inverted = QString::fromStdString(param.Get("inverted", false) ? "!" : ""); + const auto common_button_name = input_subsystem->GetButtonName(param); + + // Retrieve the names from Qt if (param.Get("engine", "") == "keyboard") { const QString button_str = GetKeyName(param.Get("code", 0)); - const QString toggle = QString::fromStdString(param.Get("toggle", false) ? "~" : ""); return QObject::tr("%1%2").arg(toggle, button_str); } - if (param.Get("engine", "") == "gcpad") { - if (param.Has("axis")) { - const QString axis_str = QString::fromStdString(param.Get("axis", "")); - const QString direction_str = QString::fromStdString(param.Get("direction", "")); - - return QObject::tr("GC Axis %1%2").arg(axis_str, direction_str); - } - if (param.Has("button")) { - const QString button_str = QString::number(int(std::log2(param.Get("button", 0)))); - return QObject::tr("GC Button %1").arg(button_str); - } - return GetKeyName(param.Get("code", 0)); + if (common_button_name == Common::Input::ButtonNames::Invalid) { + return QObject::tr("[invalid]"); } - if (param.Get("engine", "") == "tas") { - if (param.Has("axis")) { - const QString axis_str = QString::fromStdString(param.Get("axis", "")); - - return QObject::tr("TAS Axis %1").arg(axis_str); - } - if (param.Has("button")) { - const QString button_str = QString::number(int(std::log2(param.Get("button", 0)))); - return QObject::tr("TAS Btn %1").arg(button_str); - } - return GetKeyName(param.Get("code", 0)); + if (common_button_name == Common::Input::ButtonNames::Engine) { + return QString::fromStdString(param.Get("engine", "")); } - if (param.Get("engine", "") == "cemuhookudp") { - if (param.Has("pad_index")) { - const QString motion_str = QString::fromStdString(param.Get("pad_index", "")); - return QObject::tr("Motion %1").arg(motion_str); - } - return GetKeyName(param.Get("code", 0)); - } - - if (param.Get("engine", "") == "sdl") { + if (common_button_name == Common::Input::ButtonNames::Value) { if (param.Has("hat")) { - const QString hat_str = QString::fromStdString(param.Get("hat", "")); - const QString direction_str = QString::fromStdString(param.Get("direction", "")); - - return QObject::tr("Hat %1 %2").arg(hat_str, direction_str); + const QString hat = QString::fromStdString(param.Get("direction", "")); + return QObject::tr("%1%2Hat %3").arg(toggle, inverted, hat); } - if (param.Has("axis")) { - const QString axis_str = QString::fromStdString(param.Get("axis", "")); - const QString direction_str = QString::fromStdString(param.Get("direction", "")); - - return QObject::tr("Axis %1%2").arg(axis_str, direction_str); + const QString axis = QString::fromStdString(param.Get("axis", "")); + return QObject::tr("%1%2Axis %3").arg(toggle, inverted, axis); } - - if (param.Has("button")) { - const QString button_str = QString::fromStdString(param.Get("button", "")); - const QString toggle = QString::fromStdString(param.Get("toggle", false) ? "~" : ""); - - return QObject::tr("%1Button %2").arg(toggle, button_str); + if (param.Has("axis_x") && param.Has("axis_y") && param.Has("axis_z")) { + const QString axis_x = QString::fromStdString(param.Get("axis_x", "")); + const QString axis_y = QString::fromStdString(param.Get("axis_y", "")); + const QString axis_z = QString::fromStdString(param.Get("axis_z", "")); + return QObject::tr("%1%2Axis %3,%4,%5").arg(toggle, inverted, axis_x, axis_y, axis_z); } - if (param.Has("motion")) { - return QObject::tr("SDL Motion"); + const QString motion = QString::fromStdString(param.Get("motion", "")); + return QObject::tr("%1%2Motion %3").arg(toggle, inverted, motion); + } + if (param.Has("button")) { + const QString button = QString::fromStdString(param.Get("button", "")); + return QObject::tr("%1%2Button %3").arg(toggle, inverted, button); } - - return {}; } - if (param.Get("engine", "") == "mouse") { - if (param.Has("button")) { - const QString button_str = QString::number(int(param.Get("button", 0))); - const QString toggle = QString::fromStdString(param.Get("toggle", false) ? "~" : ""); - return QObject::tr("%1Click %2").arg(toggle, button_str); - } - return GetKeyName(param.Get("code", 0)); + QString button_name = GetButtonName(common_button_name); + if (param.Has("hat")) { + return QObject::tr("%1%2Hat %3").arg(toggle, inverted, button_name); + } + if (param.Has("axis")) { + return QObject::tr("%1%2Axis %3").arg(toggle, inverted, button_name); + } + if (param.Has("motion")) { + return QObject::tr("%1%2Axis %3").arg(toggle, inverted, button_name); + } + if (param.Has("button")) { + return QObject::tr("%1%2Button %3").arg(toggle, inverted, button_name); } return QObject::tr("[unknown]"); } -QString AnalogToText(const Common::ParamPackage& param, const std::string& dir) { +QString ConfigureInputPlayer::AnalogToText(const Common::ParamPackage& param, + const std::string& dir) { if (!param.Has("engine")) { return QObject::tr("[not set]"); } @@ -194,49 +200,69 @@ QString AnalogToText(const Common::ParamPackage& param, const std::string& dir) return ButtonToText(Common::ParamPackage{param.Get(dir, "")}); } + if (!param.Has("axis_x") || !param.Has("axis_y")) { + return QObject::tr("[unknown]"); + } + const auto engine_str = param.Get("engine", ""); const QString axis_x_str = QString::fromStdString(param.Get("axis_x", "")); const QString axis_y_str = QString::fromStdString(param.Get("axis_y", "")); const bool invert_x = param.Get("invert_x", "+") == "-"; const bool invert_y = param.Get("invert_y", "+") == "-"; - if (engine_str == "sdl" || engine_str == "gcpad" || engine_str == "mouse" || - engine_str == "tas") { - if (dir == "modifier") { - return QObject::tr("[unused]"); - } - if (dir == "left") { - const QString invert_x_str = QString::fromStdString(invert_x ? "+" : "-"); - return QObject::tr("Axis %1%2").arg(axis_x_str, invert_x_str); - } - if (dir == "right") { - const QString invert_x_str = QString::fromStdString(invert_x ? "-" : "+"); - return QObject::tr("Axis %1%2").arg(axis_x_str, invert_x_str); - } - if (dir == "up") { - const QString invert_y_str = QString::fromStdString(invert_y ? "-" : "+"); - return QObject::tr("Axis %1%2").arg(axis_y_str, invert_y_str); - } - if (dir == "down") { - const QString invert_y_str = QString::fromStdString(invert_y ? "+" : "-"); - return QObject::tr("Axis %1%2").arg(axis_y_str, invert_y_str); - } - - return {}; + if (dir == "modifier") { + return QObject::tr("[unused]"); } + + if (dir == "left") { + const QString invert_x_str = QString::fromStdString(invert_x ? "+" : "-"); + return QObject::tr("Axis %1%2").arg(axis_x_str, invert_x_str); + } + if (dir == "right") { + const QString invert_x_str = QString::fromStdString(invert_x ? "-" : "+"); + return QObject::tr("Axis %1%2").arg(axis_x_str, invert_x_str); + } + if (dir == "up") { + const QString invert_y_str = QString::fromStdString(invert_y ? "-" : "+"); + return QObject::tr("Axis %1%2").arg(axis_y_str, invert_y_str); + } + if (dir == "down") { + const QString invert_y_str = QString::fromStdString(invert_y ? "+" : "-"); + return QObject::tr("Axis %1%2").arg(axis_y_str, invert_y_str); + } + return QObject::tr("[unknown]"); } -} // namespace ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_index, QWidget* bottom_row, InputCommon::InputSubsystem* input_subsystem_, - InputProfiles* profiles_, Core::System& system_, - bool debug) + InputProfiles* profiles_, Core::HID::HIDCore& hid_core_, + bool is_powered_on_, bool debug) : QWidget(parent), ui(std::make_unique()), player_index(player_index), - debug(debug), input_subsystem{input_subsystem_}, profiles(profiles_), - timeout_timer(std::make_unique()), poll_timer(std::make_unique()), - bottom_row(bottom_row), system{system_} { + debug(debug), is_powered_on{is_powered_on_}, input_subsystem{input_subsystem_}, + profiles(profiles_), timeout_timer(std::make_unique()), + poll_timer(std::make_unique()), bottom_row(bottom_row), hid_core{hid_core_} { + if (player_index == 0) { + auto* emulated_controller_p1 = + hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1); + auto* emulated_controller_hanheld = + hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld); + emulated_controller_p1->SaveCurrentConfig(); + emulated_controller_p1->EnableConfiguration(); + emulated_controller_hanheld->SaveCurrentConfig(); + emulated_controller_hanheld->EnableConfiguration(); + if (emulated_controller_hanheld->IsConnected(true)) { + emulated_controller_p1->Disconnect(); + emulated_controller = emulated_controller_hanheld; + } else { + emulated_controller = emulated_controller_p1; + } + } else { + emulated_controller = hid_core.GetEmulatedControllerByIndex(player_index); + emulated_controller->SaveCurrentConfig(); + emulated_controller->EnableConfiguration(); + } ui->setupUi(this); setFocusPolicy(Qt::ClickFocus); @@ -278,31 +304,7 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i analog_map_range_groupbox = {ui->buttonLStickRangeGroup, ui->buttonRStickRangeGroup}; analog_map_range_spinbox = {ui->spinboxLStickRange, ui->spinboxRStickRange}; - const auto ConfigureButtonClick = [&](QPushButton* button, std::size_t button_id, - Common::ParamPackage* param, int default_val, - InputCommon::Polling::DeviceType type) { - connect(button, &QPushButton::clicked, [=, this] { - HandleClick( - button, button_id, - [=, this](Common::ParamPackage params) { - // Workaround for ZL & ZR for analog triggers like on XBOX - // controllers. Analog triggers (from controllers like the XBOX - // controller) would not work due to a different range of their - // signals (from 0 to 255 on analog triggers instead of -32768 to - // 32768 on analog joysticks). The SDL driver misinterprets analog - // triggers as analog joysticks. - // TODO: reinterpret the signal range for analog triggers to map the - // values correctly. This is required for the correct emulation of - // the analog triggers of the GameCube controller. - if (button == ui->buttonZL || button == ui->buttonZR) { - params.Set("direction", "+"); - params.Set("threshold", "0.5"); - } - *param = std::move(params); - }, - type); - }); - }; + ui->controllerFrame->SetController(emulated_controller); for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) { auto* const button = button_map[button_id]; @@ -311,34 +313,52 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i continue; } - ConfigureButtonClick(button_map[button_id], button_id, &buttons_param[button_id], - Config::default_buttons[button_id], - InputCommon::Polling::DeviceType::Button); + connect(button, &QPushButton::clicked, [=, this] { + HandleClick( + button, button_id, + [=, this](Common::ParamPackage params) { + emulated_controller->SetButtonParam(button_id, params); + }, + InputCommon::Polling::InputType::Button); + }); button->setContextMenuPolicy(Qt::CustomContextMenu); connect(button, &QPushButton::customContextMenuRequested, [=, this](const QPoint& menu_location) { QMenu context_menu; + Common::ParamPackage param = emulated_controller->GetButtonParam(button_id); context_menu.addAction(tr("Clear"), [&] { - buttons_param[button_id].Clear(); + emulated_controller->SetButtonParam(button_id, {}); button_map[button_id]->setText(tr("[not set]")); }); - if (buttons_param[button_id].Has("toggle")) { + if (param.Has("button") || param.Has("hat")) { context_menu.addAction(tr("Toggle button"), [&] { - const bool toggle_value = - !buttons_param[button_id].Get("toggle", false); - buttons_param[button_id].Set("toggle", toggle_value); - button_map[button_id]->setText(ButtonToText(buttons_param[button_id])); + const bool toggle_value = !param.Get("toggle", false); + param.Set("toggle", toggle_value); + button_map[button_id]->setText(ButtonToText(param)); + emulated_controller->SetButtonParam(button_id, param); + }); + context_menu.addAction(tr("Invert button"), [&] { + const bool toggle_value = !param.Get("inverted", false); + param.Set("inverted", toggle_value); + button_map[button_id]->setText(ButtonToText(param)); + emulated_controller->SetButtonParam(button_id, param); }); } - if (buttons_param[button_id].Has("threshold")) { + if (param.Has("axis")) { + context_menu.addAction(tr("Invert axis"), [&] { + const bool toggle_value = !(param.Get("invert", "+") == "-"); + param.Set("invert", toggle_value ? "-" : "+"); + button_map[button_id]->setText(ButtonToText(param)); + emulated_controller->SetButtonParam(button_id, param); + }); context_menu.addAction(tr("Set threshold"), [&] { - const int button_threshold = static_cast( - buttons_param[button_id].Get("threshold", 0.5f) * 100.0f); + const int button_threshold = + static_cast(param.Get("threshold", 0.5f) * 100.0f); const int new_threshold = QInputDialog::getInt( this, tr("Set threshold"), tr("Choose a value between 0% and 100%"), button_threshold, 0, 100); - buttons_param[button_id].Set("threshold", new_threshold / 100.0f); + param.Set("threshold", new_threshold / 100.0f); if (button_id == Settings::NativeButton::ZL) { ui->sliderZLThreshold->setValue(new_threshold); @@ -346,11 +366,10 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i if (button_id == Settings::NativeButton::ZR) { ui->sliderZRThreshold->setValue(new_threshold); } + emulated_controller->SetButtonParam(button_id, param); }); } - context_menu.exec(button_map[button_id]->mapToGlobal(menu_location)); - ui->controllerFrame->SetPlayerInput(player_index, buttons_param, analogs_param); }); } @@ -360,9 +379,14 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i continue; } - ConfigureButtonClick(motion_map[motion_id], motion_id, &motions_param[motion_id], - Config::default_motions[motion_id], - InputCommon::Polling::DeviceType::Motion); + connect(button, &QPushButton::clicked, [=, this] { + HandleClick( + button, motion_id, + [=, this](Common::ParamPackage params) { + emulated_controller->SetMotionParam(motion_id, params); + }, + InputCommon::Polling::InputType::Motion); + }); button->setContextMenuPolicy(Qt::CustomContextMenu); @@ -370,7 +394,7 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i [=, this](const QPoint& menu_location) { QMenu context_menu; context_menu.addAction(tr("Clear"), [&] { - motions_param[motion_id].Clear(); + emulated_controller->SetMotionParam(motion_id, {}); motion_map[motion_id]->setText(tr("[not set]")); }); context_menu.exec(motion_map[motion_id]->mapToGlobal(menu_location)); @@ -378,16 +402,22 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i } connect(ui->sliderZLThreshold, &QSlider::valueChanged, [=, this] { - if (buttons_param[Settings::NativeButton::ZL].Has("threshold")) { + Common::ParamPackage param = + emulated_controller->GetButtonParam(Settings::NativeButton::ZL); + if (param.Has("threshold")) { const auto slider_value = ui->sliderZLThreshold->value(); - buttons_param[Settings::NativeButton::ZL].Set("threshold", slider_value / 100.0f); + param.Set("threshold", slider_value / 100.0f); + emulated_controller->SetButtonParam(Settings::NativeButton::ZL, param); } }); connect(ui->sliderZRThreshold, &QSlider::valueChanged, [=, this] { - if (buttons_param[Settings::NativeButton::ZR].Has("threshold")) { + Common::ParamPackage param = + emulated_controller->GetButtonParam(Settings::NativeButton::ZR); + if (param.Has("threshold")) { const auto slider_value = ui->sliderZRThreshold->value(); - buttons_param[Settings::NativeButton::ZR].Set("threshold", slider_value / 100.0f); + param.Set("threshold", slider_value / 100.0f); + emulated_controller->SetButtonParam(Settings::NativeButton::ZR, param); } }); @@ -415,45 +445,45 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i HandleClick( analog_map_buttons[analog_id][sub_button_id], analog_id, [=, this](const Common::ParamPackage& params) { - SetAnalogParam(params, analogs_param[analog_id], - analog_sub_buttons[sub_button_id]); + Common::ParamPackage param = emulated_controller->GetStickParam(analog_id); + SetAnalogParam(params, param, analog_sub_buttons[sub_button_id]); + emulated_controller->SetStickParam(analog_id, param); }, - InputCommon::Polling::DeviceType::AnalogPreferred); + InputCommon::Polling::InputType::Stick); }); analog_button->setContextMenuPolicy(Qt::CustomContextMenu); - connect( - analog_button, &QPushButton::customContextMenuRequested, - [=, this](const QPoint& menu_location) { - QMenu context_menu; - context_menu.addAction(tr("Clear"), [&] { - analogs_param[analog_id].Clear(); - analog_map_buttons[analog_id][sub_button_id]->setText(tr("[not set]")); + connect(analog_button, &QPushButton::customContextMenuRequested, + [=, this](const QPoint& menu_location) { + QMenu context_menu; + Common::ParamPackage param = emulated_controller->GetStickParam(analog_id); + context_menu.addAction(tr("Clear"), [&] { + emulated_controller->SetStickParam(analog_id, {}); + analog_map_buttons[analog_id][sub_button_id]->setText(tr("[not set]")); + }); + context_menu.addAction(tr("Invert axis"), [&] { + if (sub_button_id == 2 || sub_button_id == 3) { + const bool invert_value = param.Get("invert_x", "+") == "-"; + const std::string invert_str = invert_value ? "+" : "-"; + param.Set("invert_x", invert_str); + emulated_controller->SetStickParam(analog_id, param); + } + if (sub_button_id == 0 || sub_button_id == 1) { + const bool invert_value = param.Get("invert_y", "+") == "-"; + const std::string invert_str = invert_value ? "+" : "-"; + param.Set("invert_y", invert_str); + emulated_controller->SetStickParam(analog_id, param); + } + for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; + ++sub_button_id) { + analog_map_buttons[analog_id][sub_button_id]->setText( + AnalogToText(param, analog_sub_buttons[sub_button_id])); + } + }); + context_menu.exec(analog_map_buttons[analog_id][sub_button_id]->mapToGlobal( + menu_location)); }); - context_menu.addAction(tr("Invert axis"), [&] { - if (sub_button_id == 2 || sub_button_id == 3) { - const bool invert_value = - analogs_param[analog_id].Get("invert_x", "+") == "-"; - const std::string invert_str = invert_value ? "+" : "-"; - analogs_param[analog_id].Set("invert_x", invert_str); - } - if (sub_button_id == 0 || sub_button_id == 1) { - const bool invert_value = - analogs_param[analog_id].Get("invert_y", "+") == "-"; - const std::string invert_str = invert_value ? "+" : "-"; - analogs_param[analog_id].Set("invert_y", invert_str); - } - for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; - ++sub_button_id) { - analog_map_buttons[analog_id][sub_button_id]->setText(AnalogToText( - analogs_param[analog_id], analog_sub_buttons[sub_button_id])); - } - }); - context_menu.exec( - analog_map_buttons[analog_id][sub_button_id]->mapToGlobal(menu_location)); - ui->controllerFrame->SetPlayerInput(player_index, buttons_param, analogs_param); - }); } // Handle clicks for the modifier buttons as well. @@ -461,9 +491,11 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i HandleClick( analog_map_modifier_button[analog_id], analog_id, [=, this](const Common::ParamPackage& params) { - analogs_param[analog_id].Set("modifier", params.Serialize()); + Common::ParamPackage param = emulated_controller->GetStickParam(analog_id); + param.Set("modifier", params.Serialize()); + emulated_controller->SetStickParam(analog_id, param); }, - InputCommon::Polling::DeviceType::Button); + InputCommon::Polling::InputType::Button); }); analog_map_modifier_button[analog_id]->setContextMenuPolicy(Qt::CustomContextMenu); @@ -471,18 +503,21 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i connect(analog_map_modifier_button[analog_id], &QPushButton::customContextMenuRequested, [=, this](const QPoint& menu_location) { QMenu context_menu; + Common::ParamPackage param = emulated_controller->GetStickParam(analog_id); context_menu.addAction(tr("Clear"), [&] { - analogs_param[analog_id].Set("modifier", ""); + param.Set("modifier", ""); analog_map_modifier_button[analog_id]->setText(tr("[not set]")); + emulated_controller->SetStickParam(analog_id, param); }); context_menu.addAction(tr("Toggle button"), [&] { Common::ParamPackage modifier_param = - Common::ParamPackage{analogs_param[analog_id].Get("modifier", "")}; + Common::ParamPackage{param.Get("modifier", "")}; const bool toggle_value = !modifier_param.Get("toggle", false); modifier_param.Set("toggle", toggle_value); - analogs_param[analog_id].Set("modifier", modifier_param.Serialize()); + param.Set("modifier", modifier_param.Serialize()); analog_map_modifier_button[analog_id]->setText( ButtonToText(modifier_param)); + emulated_controller->SetStickParam(analog_id, param); }); context_menu.exec( analog_map_modifier_button[analog_id]->mapToGlobal(menu_location)); @@ -490,37 +525,39 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i connect(analog_map_range_spinbox[analog_id], qOverload(&QSpinBox::valueChanged), [=, this] { + Common::ParamPackage param = emulated_controller->GetStickParam(analog_id); const auto spinbox_value = analog_map_range_spinbox[analog_id]->value(); - analogs_param[analog_id].Set("range", spinbox_value / 100.0f); - ui->controllerFrame->SetPlayerInput(player_index, buttons_param, analogs_param); + param.Set("range", spinbox_value / 100.0f); + emulated_controller->SetStickParam(analog_id, param); }); connect(analog_map_deadzone_slider[analog_id], &QSlider::valueChanged, [=, this] { + Common::ParamPackage param = emulated_controller->GetStickParam(analog_id); const auto slider_value = analog_map_deadzone_slider[analog_id]->value(); analog_map_deadzone_label[analog_id]->setText(tr("Deadzone: %1%").arg(slider_value)); - analogs_param[analog_id].Set("deadzone", slider_value / 100.0f); - ui->controllerFrame->SetPlayerInput(player_index, buttons_param, analogs_param); + param.Set("deadzone", slider_value / 100.0f); + emulated_controller->SetStickParam(analog_id, param); }); connect(analog_map_modifier_slider[analog_id], &QSlider::valueChanged, [=, this] { + Common::ParamPackage param = emulated_controller->GetStickParam(analog_id); const auto slider_value = analog_map_modifier_slider[analog_id]->value(); analog_map_modifier_label[analog_id]->setText( tr("Modifier Range: %1%").arg(slider_value)); - analogs_param[analog_id].Set("modifier_scale", slider_value / 100.0f); + param.Set("modifier_scale", slider_value / 100.0f); + emulated_controller->SetStickParam(analog_id, param); }); } // Player Connected checkbox - connect(ui->groupConnectedController, &QGroupBox::toggled, [this](bool checked) { - emit Connected(checked); - ui->controllerFrame->SetConnectedStatus(checked); - }); + connect(ui->groupConnectedController, &QGroupBox::toggled, + [this](bool checked) { emit Connected(checked); }); if (player_index == 0) { connect(ui->comboControllerType, qOverload(&QComboBox::currentIndexChanged), [this](int index) { emit HandheldStateChanged(GetControllerTypeFromIndex(index) == - Settings::ControllerType::Handheld); + Core::HID::NpadStyleIndex::Handheld); }); } @@ -537,18 +574,43 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i SetConnectableControllers(); } - UpdateControllerIcon(); UpdateControllerAvailableButtons(); UpdateControllerEnabledButtons(); UpdateControllerButtonNames(); UpdateMotionButtons(); - connect(ui->comboControllerType, qOverload(&QComboBox::currentIndexChanged), [this](int) { - UpdateControllerIcon(); - UpdateControllerAvailableButtons(); - UpdateControllerEnabledButtons(); - UpdateControllerButtonNames(); - UpdateMotionButtons(); - }); + connect(ui->comboControllerType, qOverload(&QComboBox::currentIndexChanged), + [this, player_index](int) { + UpdateControllerAvailableButtons(); + UpdateControllerEnabledButtons(); + UpdateControllerButtonNames(); + UpdateMotionButtons(); + const Core::HID::NpadStyleIndex type = + GetControllerTypeFromIndex(ui->comboControllerType->currentIndex()); + + if (player_index == 0) { + auto* emulated_controller_p1 = + hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1); + auto* emulated_controller_hanheld = + hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld); + bool is_connected = emulated_controller->IsConnected(true); + + emulated_controller_p1->SetNpadStyleIndex(type); + emulated_controller_hanheld->SetNpadStyleIndex(type); + if (is_connected) { + if (type == Core::HID::NpadStyleIndex::Handheld) { + emulated_controller_p1->Disconnect(); + emulated_controller_hanheld->Connect(); + emulated_controller = emulated_controller_hanheld; + } else { + emulated_controller_hanheld->Disconnect(); + emulated_controller_p1->Connect(); + emulated_controller = emulated_controller_p1; + } + } + ui->controllerFrame->SetController(emulated_controller); + } + emulated_controller->SetNpadStyleIndex(type); + }); connect(ui->comboDevices, qOverload(&QComboBox::activated), this, &ConfigureInputPlayer::UpdateMappingWithDefaults); @@ -563,62 +625,10 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); }); connect(poll_timer.get(), &QTimer::timeout, [this] { - Common::ParamPackage params; - if (input_subsystem->GetGCButtons()->IsPolling()) { - params = input_subsystem->GetGCButtons()->GetNextInput(); - if (params.Has("engine") && IsInputAcceptable(params)) { - SetPollingResult(params, false); - return; - } - } - if (input_subsystem->GetGCAnalogs()->IsPolling()) { - params = input_subsystem->GetGCAnalogs()->GetNextInput(); - if (params.Has("engine") && IsInputAcceptable(params)) { - SetPollingResult(params, false); - return; - } - } - if (input_subsystem->GetUDPMotions()->IsPolling()) { - params = input_subsystem->GetUDPMotions()->GetNextInput(); - if (params.Has("engine")) { - SetPollingResult(params, false); - return; - } - } - if (input_subsystem->GetMouseButtons()->IsPolling()) { - params = input_subsystem->GetMouseButtons()->GetNextInput(); - if (params.Has("engine") && IsInputAcceptable(params)) { - SetPollingResult(params, false); - return; - } - } - if (input_subsystem->GetMouseAnalogs()->IsPolling()) { - params = input_subsystem->GetMouseAnalogs()->GetNextInput(); - if (params.Has("engine") && IsInputAcceptable(params)) { - SetPollingResult(params, false); - return; - } - } - if (input_subsystem->GetMouseMotions()->IsPolling()) { - params = input_subsystem->GetMouseMotions()->GetNextInput(); - if (params.Has("engine") && IsInputAcceptable(params)) { - SetPollingResult(params, false); - return; - } - } - if (input_subsystem->GetMouseTouch()->IsPolling()) { - params = input_subsystem->GetMouseTouch()->GetNextInput(); - if (params.Has("engine") && IsInputAcceptable(params)) { - SetPollingResult(params, false); - return; - } - } - for (auto& poller : device_pollers) { - params = poller->GetNextInput(); - if (params.Has("engine") && IsInputAcceptable(params)) { - SetPollingResult(params, false); - return; - } + const auto& params = input_subsystem->GetNextInput(); + if (params.Has("engine") && IsInputAcceptable(params)) { + SetPollingResult(params, false); + return; } }); @@ -634,110 +644,38 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i &ConfigureInputPlayer::SaveProfile); LoadConfiguration(); - ui->controllerFrame->SetPlayerInput(player_index, buttons_param, analogs_param); - ui->controllerFrame->SetConnectedStatus(ui->groupConnectedController->isChecked()); } -ConfigureInputPlayer::~ConfigureInputPlayer() = default; +ConfigureInputPlayer::~ConfigureInputPlayer() { + if (player_index == 0) { + auto* emulated_controller_p1 = + hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1); + auto* emulated_controller_hanheld = + hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld); + emulated_controller_p1->DisableConfiguration(); + emulated_controller_hanheld->DisableConfiguration(); + } else { + emulated_controller->DisableConfiguration(); + } +} void ConfigureInputPlayer::ApplyConfiguration() { - auto& player = Settings::values.players.GetValue()[player_index]; - auto& buttons = debug ? Settings::values.debug_pad_buttons : player.buttons; - auto& analogs = debug ? Settings::values.debug_pad_analogs : player.analogs; - - std::transform(buttons_param.begin(), buttons_param.end(), buttons.begin(), - [](const Common::ParamPackage& param) { return param.Serialize(); }); - std::transform(analogs_param.begin(), analogs_param.end(), analogs.begin(), - [](const Common::ParamPackage& param) { return param.Serialize(); }); - - if (debug) { - return; - } - - auto& motions = player.motions; - - std::transform(motions_param.begin(), motions_param.end(), motions.begin(), - [](const Common::ParamPackage& param) { return param.Serialize(); }); - - // Apply configuration for handheld if (player_index == 0) { - auto& handheld = Settings::values.players.GetValue()[HANDHELD_INDEX]; - const auto handheld_connected = handheld.connected; - handheld = player; - handheld.connected = handheld_connected; - } -} - -void ConfigureInputPlayer::TryConnectSelectedController() { - auto& player = Settings::values.players.GetValue()[player_index]; - - const auto controller_type = - GetControllerTypeFromIndex(ui->comboControllerType->currentIndex()); - const auto player_connected = ui->groupConnectedController->isChecked() && - controller_type != Settings::ControllerType::Handheld; - - // Connect Handheld depending on Player 1's controller configuration. - if (player_index == 0) { - auto& handheld = Settings::values.players.GetValue()[HANDHELD_INDEX]; - const auto handheld_connected = ui->groupConnectedController->isChecked() && - controller_type == Settings::ControllerType::Handheld; - // Connect only if handheld is going from disconnected to connected - if (!handheld.connected && handheld_connected) { - UpdateController(controller_type, HANDHELD_INDEX, true, system); - } - handheld.connected = handheld_connected; - } - - if (player.controller_type == controller_type && player.connected == player_connected) { - // Set vibration devices in the event that the input device has changed. - ConfigureVibration::SetVibrationDevices(player_index); + auto* emulated_controller_p1 = + hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1); + auto* emulated_controller_hanheld = + hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld); + emulated_controller_p1->DisableConfiguration(); + emulated_controller_p1->SaveCurrentConfig(); + emulated_controller_p1->EnableConfiguration(); + emulated_controller_hanheld->DisableConfiguration(); + emulated_controller_hanheld->SaveCurrentConfig(); + emulated_controller_hanheld->EnableConfiguration(); return; } - - player.controller_type = controller_type; - player.connected = player_connected; - - ConfigureVibration::SetVibrationDevices(player_index); - - if (!player.connected) { - return; - } - - UpdateController(controller_type, player_index, true, system); -} - -void ConfigureInputPlayer::TryDisconnectSelectedController() { - const auto& player = Settings::values.players.GetValue()[player_index]; - - const auto controller_type = - GetControllerTypeFromIndex(ui->comboControllerType->currentIndex()); - const auto player_connected = ui->groupConnectedController->isChecked() && - controller_type != Settings::ControllerType::Handheld; - - // Disconnect Handheld depending on Player 1's controller configuration. - if (player_index == 0 && player.controller_type == Settings::ControllerType::Handheld) { - const auto& handheld = Settings::values.players.GetValue()[HANDHELD_INDEX]; - const auto handheld_connected = ui->groupConnectedController->isChecked() && - controller_type == Settings::ControllerType::Handheld; - // Disconnect only if handheld is going from connected to disconnected - if (handheld.connected && !handheld_connected) { - UpdateController(controller_type, HANDHELD_INDEX, false, system); - } - return; - } - - // Do not do anything if the controller configuration has not changed. - if (player.controller_type == controller_type && player.connected == player_connected) { - return; - } - - // Do not disconnect if the controller is already disconnected - if (!player.connected) { - return; - } - - // Disconnect the controller first. - UpdateController(controller_type, player_index, false, system); + emulated_controller->DisableConfiguration(); + emulated_controller->SaveCurrentConfig(); + emulated_controller->EnableConfiguration(); } void ConfigureInputPlayer::showEvent(QShowEvent* event) { @@ -762,22 +700,7 @@ void ConfigureInputPlayer::RetranslateUI() { } void ConfigureInputPlayer::LoadConfiguration() { - auto& player = Settings::values.players.GetValue()[player_index]; - if (debug) { - std::transform(Settings::values.debug_pad_buttons.begin(), - Settings::values.debug_pad_buttons.end(), buttons_param.begin(), - [](const std::string& str) { return Common::ParamPackage(str); }); - std::transform(Settings::values.debug_pad_analogs.begin(), - Settings::values.debug_pad_analogs.end(), analogs_param.begin(), - [](const std::string& str) { return Common::ParamPackage(str); }); - } else { - std::transform(player.buttons.begin(), player.buttons.end(), buttons_param.begin(), - [](const std::string& str) { return Common::ParamPackage(str); }); - std::transform(player.analogs.begin(), player.analogs.end(), analogs_param.begin(), - [](const std::string& str) { return Common::ParamPackage(str); }); - std::transform(player.motions.begin(), player.motions.end(), motions_param.begin(), - [](const std::string& str) { return Common::ParamPackage(str); }); - } + emulated_controller->ReloadFromSettings(); UpdateUI(); UpdateInputDeviceCombobox(); @@ -786,14 +709,19 @@ void ConfigureInputPlayer::LoadConfiguration() { return; } - ui->comboControllerType->setCurrentIndex(GetIndexFromControllerType(player.controller_type)); - ui->groupConnectedController->setChecked( - player.connected || - (player_index == 0 && Settings::values.players.GetValue()[HANDHELD_INDEX].connected)); + const int comboBoxIndex = + GetIndexFromControllerType(emulated_controller->GetNpadStyleIndex(true)); + ui->comboControllerType->setCurrentIndex(comboBoxIndex); + ui->groupConnectedController->setChecked(emulated_controller->IsConnected(true)); } void ConfigureInputPlayer::ConnectPlayer(bool connected) { ui->groupConnectedController->setChecked(connected); + if (connected) { + emulated_controller->Connect(); + } else { + emulated_controller->Disconnect(); + } } void ConfigureInputPlayer::UpdateInputDeviceCombobox() { @@ -803,48 +731,64 @@ void ConfigureInputPlayer::UpdateInputDeviceCombobox() { return; } - // Find the first button that isn't empty. - const auto button_param = - std::find_if(buttons_param.begin(), buttons_param.end(), - [](const Common::ParamPackage param) { return param.Has("engine"); }); - const bool buttons_empty = button_param == buttons_param.end(); - - const auto current_engine = button_param->Get("engine", ""); - const auto current_guid = button_param->Get("guid", ""); - const auto current_port = button_param->Get("port", ""); - - const bool is_keyboard_mouse = current_engine == "keyboard" || current_engine == "mouse"; - + const auto devices = + emulated_controller->GetMappedDevices(Core::HID::EmulatedDeviceIndex::AllDevices); UpdateInputDevices(); - if (buttons_empty) { + if (devices.empty()) { return; } - const bool all_one_device = - std::all_of(buttons_param.begin(), buttons_param.end(), - [current_engine, current_guid, current_port, - is_keyboard_mouse](const Common::ParamPackage param) { - if (is_keyboard_mouse) { - return !param.Has("engine") || param.Get("engine", "") == "keyboard" || - param.Get("engine", "") == "mouse"; - } - return !param.Has("engine") || (param.Get("engine", "") == current_engine && - param.Get("guid", "") == current_guid && - param.Get("port", "") == current_port); - }); + if (devices.size() > 2) { + ui->comboDevices->setCurrentIndex(0); + return; + } - if (all_one_device) { - if (is_keyboard_mouse) { - ui->comboDevices->setCurrentIndex(1); - return; - } + const auto first_engine = devices[0].Get("engine", ""); + const auto first_guid = devices[0].Get("guid", ""); + const auto first_port = devices[0].Get("port", 0); + + if (devices.size() == 1) { + const auto devices_it = + std::find_if(input_devices.begin(), input_devices.end(), + [first_engine, first_guid, first_port](const Common::ParamPackage param) { + return param.Get("engine", "") == first_engine && + param.Get("guid", "") == first_guid && + param.Get("port", 0) == first_port; + }); + const int device_index = + devices_it != input_devices.end() + ? static_cast(std::distance(input_devices.begin(), devices_it)) + : 0; + ui->comboDevices->setCurrentIndex(device_index); + return; + } + + const auto second_engine = devices[1].Get("engine", ""); + const auto second_guid = devices[1].Get("guid", ""); + const auto second_port = devices[1].Get("port", 0); + + const bool is_keyboard_mouse = (first_engine == "keyboard" || first_engine == "mouse") && + (second_engine == "keyboard" || second_engine == "mouse"); + + if (is_keyboard_mouse) { + ui->comboDevices->setCurrentIndex(2); + return; + } + + const bool is_engine_equal = first_engine == second_engine; + const bool is_port_equal = first_port == second_port; + + if (is_engine_equal && is_port_equal) { const auto devices_it = std::find_if( input_devices.begin(), input_devices.end(), - [current_engine, current_guid, current_port](const Common::ParamPackage param) { - return param.Get("class", "") == current_engine && - param.Get("guid", "") == current_guid && - param.Get("port", "") == current_port; + [first_engine, first_guid, second_guid, first_port](const Common::ParamPackage param) { + const bool is_guid_valid = + (param.Get("guid", "") == first_guid && + param.Get("guid2", "") == second_guid) || + (param.Get("guid", "") == second_guid && param.Get("guid2", "") == first_guid); + return param.Get("engine", "") == first_engine && is_guid_valid && + param.Get("port", 0) == first_port; }); const int device_index = devices_it != input_devices.end() @@ -866,8 +810,7 @@ void ConfigureInputPlayer::ClearAll() { if (button == nullptr) { continue; } - - buttons_param[button_id].Clear(); + emulated_controller->SetButtonParam(button_id, {}); } for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) { @@ -876,8 +819,7 @@ void ConfigureInputPlayer::ClearAll() { if (analog_button == nullptr) { continue; } - - analogs_param[analog_id].Clear(); + emulated_controller->SetStickParam(analog_id, {}); } } @@ -886,8 +828,7 @@ void ConfigureInputPlayer::ClearAll() { if (motion_button == nullptr) { continue; } - - motions_param[motion_id].Clear(); + emulated_controller->SetMotionParam(motion_id, {}); } UpdateUI(); @@ -896,26 +837,31 @@ void ConfigureInputPlayer::ClearAll() { void ConfigureInputPlayer::UpdateUI() { for (int button = 0; button < Settings::NativeButton::NumButtons; ++button) { - button_map[button]->setText(ButtonToText(buttons_param[button])); + const Common::ParamPackage param = emulated_controller->GetButtonParam(button); + button_map[button]->setText(ButtonToText(param)); } - if (buttons_param[Settings::NativeButton::ZL].Has("threshold")) { - const int button_threshold = static_cast( - buttons_param[Settings::NativeButton::ZL].Get("threshold", 0.5f) * 100.0f); + const Common::ParamPackage ZL_param = + emulated_controller->GetButtonParam(Settings::NativeButton::ZL); + if (ZL_param.Has("threshold")) { + const int button_threshold = static_cast(ZL_param.Get("threshold", 0.5f) * 100.0f); ui->sliderZLThreshold->setValue(button_threshold); } - if (buttons_param[Settings::NativeButton::ZR].Has("threshold")) { - const int button_threshold = static_cast( - buttons_param[Settings::NativeButton::ZR].Get("threshold", 0.5f) * 100.0f); + const Common::ParamPackage ZR_param = + emulated_controller->GetButtonParam(Settings::NativeButton::ZR); + if (ZR_param.Has("threshold")) { + const int button_threshold = static_cast(ZR_param.Get("threshold", 0.5f) * 100.0f); ui->sliderZRThreshold->setValue(button_threshold); } for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) { - motion_map[motion_id]->setText(ButtonToText(motions_param[motion_id])); + const Common::ParamPackage param = emulated_controller->GetMotionParam(motion_id); + motion_map[motion_id]->setText(ButtonToText(param)); } for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) { + const Common::ParamPackage param = emulated_controller->GetStickParam(analog_id); for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) { auto* const analog_button = analog_map_buttons[analog_id][sub_button_id]; @@ -923,12 +869,11 @@ void ConfigureInputPlayer::UpdateUI() { continue; } - analog_button->setText( - AnalogToText(analogs_param[analog_id], analog_sub_buttons[sub_button_id])); + analog_button->setText(AnalogToText(param, analog_sub_buttons[sub_button_id])); } analog_map_modifier_button[analog_id]->setText( - ButtonToText(Common::ParamPackage{analogs_param[analog_id].Get("modifier", "")})); + ButtonToText(Common::ParamPackage{param.Get("modifier", "")})); const auto deadzone_label = analog_map_deadzone_label[analog_id]; const auto deadzone_slider = analog_map_deadzone_slider[analog_id]; @@ -939,26 +884,14 @@ void ConfigureInputPlayer::UpdateUI() { const auto range_spinbox = analog_map_range_spinbox[analog_id]; int slider_value; - auto& param = analogs_param[analog_id]; - const bool is_controller = - param.Get("engine", "") == "sdl" || param.Get("engine", "") == "gcpad" || - param.Get("engine", "") == "mouse" || param.Get("engine", "") == "tas"; + const bool is_controller = input_subsystem->IsController(param); if (is_controller) { - if (!param.Has("deadzone")) { - param.Set("deadzone", 0.1f); - } - slider_value = static_cast(param.Get("deadzone", 0.1f) * 100); + slider_value = static_cast(param.Get("deadzone", 0.15f) * 100); deadzone_label->setText(tr("Deadzone: %1%").arg(slider_value)); deadzone_slider->setValue(slider_value); - if (!param.Has("range")) { - param.Set("range", 1.0f); - } range_spinbox->setValue(static_cast(param.Get("range", 1.0f) * 100)); } else { - if (!param.Has("modifier_scale")) { - param.Set("modifier_scale", 0.5f); - } slider_value = static_cast(param.Get("modifier_scale", 0.5f) * 100); modifier_label->setText(tr("Modifier Range: %1%").arg(slider_value)); modifier_slider->setValue(slider_value); @@ -970,79 +903,73 @@ void ConfigureInputPlayer::UpdateUI() { modifier_label->setVisible(!is_controller); modifier_slider->setVisible(!is_controller); range_groupbox->setVisible(is_controller); - ui->controllerFrame->SetPlayerInput(player_index, buttons_param, analogs_param); } } void ConfigureInputPlayer::SetConnectableControllers() { const auto add_controllers = [this](bool enable_all, - Controller_NPad::NpadStyleSet npad_style_set = {}) { + Core::HID::NpadStyleTag npad_style_set = {}) { index_controller_type_pairs.clear(); ui->comboControllerType->clear(); if (enable_all || npad_style_set.fullkey == 1) { index_controller_type_pairs.emplace_back(ui->comboControllerType->count(), - Settings::ControllerType::ProController); + Core::HID::NpadStyleIndex::ProController); ui->comboControllerType->addItem(tr("Pro Controller")); } if (enable_all || npad_style_set.joycon_dual == 1) { index_controller_type_pairs.emplace_back(ui->comboControllerType->count(), - Settings::ControllerType::DualJoyconDetached); + Core::HID::NpadStyleIndex::JoyconDual); ui->comboControllerType->addItem(tr("Dual Joycons")); } if (enable_all || npad_style_set.joycon_left == 1) { index_controller_type_pairs.emplace_back(ui->comboControllerType->count(), - Settings::ControllerType::LeftJoycon); + Core::HID::NpadStyleIndex::JoyconLeft); ui->comboControllerType->addItem(tr("Left Joycon")); } if (enable_all || npad_style_set.joycon_right == 1) { index_controller_type_pairs.emplace_back(ui->comboControllerType->count(), - Settings::ControllerType::RightJoycon); + Core::HID::NpadStyleIndex::JoyconRight); ui->comboControllerType->addItem(tr("Right Joycon")); } if (player_index == 0 && (enable_all || npad_style_set.handheld == 1)) { index_controller_type_pairs.emplace_back(ui->comboControllerType->count(), - Settings::ControllerType::Handheld); + Core::HID::NpadStyleIndex::Handheld); ui->comboControllerType->addItem(tr("Handheld")); } if (enable_all || npad_style_set.gamecube == 1) { index_controller_type_pairs.emplace_back(ui->comboControllerType->count(), - Settings::ControllerType::GameCube); + Core::HID::NpadStyleIndex::GameCube); ui->comboControllerType->addItem(tr("GameCube Controller")); } }; - if (!system.IsPoweredOn()) { + if (!is_powered_on) { add_controllers(true); return; } - Service::SM::ServiceManager& sm = system.ServiceManager(); - - auto& npad = sm.GetService("hid")->GetAppletResource()->GetController( - HidController::NPad); - - add_controllers(false, npad.GetSupportedStyleSet()); + add_controllers(false, hid_core.GetSupportedStyleTag()); } -Settings::ControllerType ConfigureInputPlayer::GetControllerTypeFromIndex(int index) const { +Core::HID::NpadStyleIndex ConfigureInputPlayer::GetControllerTypeFromIndex(int index) const { const auto it = std::find_if(index_controller_type_pairs.begin(), index_controller_type_pairs.end(), [index](const auto& pair) { return pair.first == index; }); if (it == index_controller_type_pairs.end()) { - return Settings::ControllerType::ProController; + return Core::HID::NpadStyleIndex::ProController; } return it->second; } -int ConfigureInputPlayer::GetIndexFromControllerType(Settings::ControllerType type) const { +int ConfigureInputPlayer::GetIndexFromControllerType(Core::HID::NpadStyleIndex type) const { const auto it = std::find_if(index_controller_type_pairs.begin(), index_controller_type_pairs.end(), [type](const auto& pair) { return pair.second == type; }); @@ -1057,52 +984,15 @@ int ConfigureInputPlayer::GetIndexFromControllerType(Settings::ControllerType ty void ConfigureInputPlayer::UpdateInputDevices() { input_devices = input_subsystem->GetInputDevices(); ui->comboDevices->clear(); - for (auto& device : input_devices) { - const std::string display = device.Get("display", "Unknown"); - ui->comboDevices->addItem(QString::fromStdString(display), {}); - if (display == "TAS") { - device.Set("pad", static_cast(player_index)); - } + for (auto device : input_devices) { + ui->comboDevices->addItem(QString::fromStdString(device.Get("display", "Unknown")), {}); } } -void ConfigureInputPlayer::UpdateControllerIcon() { - // We aren't using Qt's built in theme support here since we aren't drawing an icon (and its - // "nonstandard" to use an image through the icon support) - const QString stylesheet = [this] { - switch (GetControllerTypeFromIndex(ui->comboControllerType->currentIndex())) { - case Settings::ControllerType::ProController: - return QStringLiteral("image: url(:/controller/pro_controller%0)"); - case Settings::ControllerType::DualJoyconDetached: - return QStringLiteral("image: url(:/controller/dual_joycon%0)"); - case Settings::ControllerType::LeftJoycon: - return QStringLiteral("image: url(:/controller/single_joycon_left_vertical%0)"); - case Settings::ControllerType::RightJoycon: - return QStringLiteral("image: url(:/controller/single_joycon_right_vertical%0)"); - case Settings::ControllerType::Handheld: - return QStringLiteral("image: url(:/controller/handheld%0)"); - default: - return QString{}; - } - }(); - - const QString theme = [] { - if (QIcon::themeName().contains(QStringLiteral("dark"))) { - return QStringLiteral("_dark"); - } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) { - return QStringLiteral("_midnight"); - } else { - return QString{}; - } - }(); - ui->controllerFrame->SetControllerType( - GetControllerTypeFromIndex(ui->comboControllerType->currentIndex())); -} - void ConfigureInputPlayer::UpdateControllerAvailableButtons() { auto layout = GetControllerTypeFromIndex(ui->comboControllerType->currentIndex()); if (debug) { - layout = Settings::ControllerType::ProController; + layout = Core::HID::NpadStyleIndex::ProController; } // List of all the widgets that will be hidden by any of the following layouts that need @@ -1127,15 +1017,15 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() { std::vector layout_hidden; switch (layout) { - case Settings::ControllerType::ProController: - case Settings::ControllerType::DualJoyconDetached: - case Settings::ControllerType::Handheld: + case Core::HID::NpadStyleIndex::ProController: + case Core::HID::NpadStyleIndex::JoyconDual: + case Core::HID::NpadStyleIndex::Handheld: layout_hidden = { ui->buttonShoulderButtonsSLSR, ui->horizontalSpacerShoulderButtonsWidget2, }; break; - case Settings::ControllerType::LeftJoycon: + case Core::HID::NpadStyleIndex::JoyconLeft: layout_hidden = { ui->horizontalSpacerShoulderButtonsWidget2, ui->buttonShoulderButtonsRight, @@ -1143,7 +1033,7 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() { ui->bottomRight, }; break; - case Settings::ControllerType::RightJoycon: + case Core::HID::NpadStyleIndex::JoyconRight: layout_hidden = { ui->horizontalSpacerShoulderButtonsWidget, ui->buttonShoulderButtonsLeft, @@ -1151,7 +1041,7 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() { ui->bottomLeft, }; break; - case Settings::ControllerType::GameCube: + case Core::HID::NpadStyleIndex::GameCube: layout_hidden = { ui->buttonShoulderButtonsSLSR, ui->horizontalSpacerShoulderButtonsWidget2, @@ -1159,6 +1049,8 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() { ui->buttonMiscButtonsScreenshotGroup, }; break; + default: + break; } for (auto* widget : layout_hidden) { @@ -1169,13 +1061,12 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() { void ConfigureInputPlayer::UpdateControllerEnabledButtons() { auto layout = GetControllerTypeFromIndex(ui->comboControllerType->currentIndex()); if (debug) { - layout = Settings::ControllerType::ProController; + layout = Core::HID::NpadStyleIndex::ProController; } // List of all the widgets that will be disabled by any of the following layouts that need // "enabled" after the controller type changes - const std::array layout_enable = { - ui->buttonHome, + const std::array layout_enable = { ui->buttonLStickPressedGroup, ui->groupRStickPressed, ui->buttonShoulderButtonsButtonLGroup, @@ -1187,17 +1078,13 @@ void ConfigureInputPlayer::UpdateControllerEnabledButtons() { std::vector layout_disable; switch (layout) { - case Settings::ControllerType::ProController: - case Settings::ControllerType::DualJoyconDetached: - case Settings::ControllerType::Handheld: - case Settings::ControllerType::LeftJoycon: - case Settings::ControllerType::RightJoycon: - // TODO(wwylele): enable this when we actually emulate it - layout_disable = { - ui->buttonHome, - }; + case Core::HID::NpadStyleIndex::ProController: + case Core::HID::NpadStyleIndex::JoyconDual: + case Core::HID::NpadStyleIndex::Handheld: + case Core::HID::NpadStyleIndex::JoyconLeft: + case Core::HID::NpadStyleIndex::JoyconRight: break; - case Settings::ControllerType::GameCube: + case Core::HID::NpadStyleIndex::GameCube: layout_disable = { ui->buttonHome, ui->buttonLStickPressedGroup, @@ -1205,6 +1092,8 @@ void ConfigureInputPlayer::UpdateControllerEnabledButtons() { ui->buttonShoulderButtonsButtonLGroup, }; break; + default: + break; } for (auto* widget : layout_disable) { @@ -1222,24 +1111,24 @@ void ConfigureInputPlayer::UpdateMotionButtons() { // Show/hide the "Motion 1/2" groupboxes depending on the currently selected controller. switch (GetControllerTypeFromIndex(ui->comboControllerType->currentIndex())) { - case Settings::ControllerType::ProController: - case Settings::ControllerType::LeftJoycon: - case Settings::ControllerType::Handheld: + case Core::HID::NpadStyleIndex::ProController: + case Core::HID::NpadStyleIndex::JoyconLeft: + case Core::HID::NpadStyleIndex::Handheld: // Show "Motion 1" and hide "Motion 2". ui->buttonMotionLeftGroup->show(); ui->buttonMotionRightGroup->hide(); break; - case Settings::ControllerType::RightJoycon: + case Core::HID::NpadStyleIndex::JoyconRight: // Show "Motion 2" and hide "Motion 1". ui->buttonMotionLeftGroup->hide(); ui->buttonMotionRightGroup->show(); break; - case Settings::ControllerType::GameCube: + case Core::HID::NpadStyleIndex::GameCube: // Hide both "Motion 1/2". ui->buttonMotionLeftGroup->hide(); ui->buttonMotionRightGroup->hide(); break; - case Settings::ControllerType::DualJoyconDetached: + case Core::HID::NpadStyleIndex::JoyconDual: default: // Show both "Motion 1/2". ui->buttonMotionLeftGroup->show(); @@ -1251,15 +1140,15 @@ void ConfigureInputPlayer::UpdateMotionButtons() { void ConfigureInputPlayer::UpdateControllerButtonNames() { auto layout = GetControllerTypeFromIndex(ui->comboControllerType->currentIndex()); if (debug) { - layout = Settings::ControllerType::ProController; + layout = Core::HID::NpadStyleIndex::ProController; } switch (layout) { - case Settings::ControllerType::ProController: - case Settings::ControllerType::DualJoyconDetached: - case Settings::ControllerType::Handheld: - case Settings::ControllerType::LeftJoycon: - case Settings::ControllerType::RightJoycon: + case Core::HID::NpadStyleIndex::ProController: + case Core::HID::NpadStyleIndex::JoyconDual: + case Core::HID::NpadStyleIndex::Handheld: + case Core::HID::NpadStyleIndex::JoyconLeft: + case Core::HID::NpadStyleIndex::JoyconRight: ui->buttonMiscButtonsPlusGroup->setTitle(tr("Plus")); ui->buttonShoulderButtonsButtonZLGroup->setTitle(tr("ZL")); ui->buttonShoulderButtonsZRGroup->setTitle(tr("ZR")); @@ -1267,7 +1156,7 @@ void ConfigureInputPlayer::UpdateControllerButtonNames() { ui->LStick->setTitle(tr("Left Stick")); ui->RStick->setTitle(tr("Right Stick")); break; - case Settings::ControllerType::GameCube: + case Core::HID::NpadStyleIndex::GameCube: ui->buttonMiscButtonsPlusGroup->setTitle(tr("Start / Pause")); ui->buttonShoulderButtonsButtonZLGroup->setTitle(tr("L")); ui->buttonShoulderButtonsZRGroup->setTitle(tr("R")); @@ -1275,6 +1164,8 @@ void ConfigureInputPlayer::UpdateControllerButtonNames() { ui->LStick->setTitle(tr("Control Stick")); ui->RStick->setTitle(tr("C-Stick")); break; + default: + break; } } @@ -1283,45 +1174,82 @@ void ConfigureInputPlayer::UpdateMappingWithDefaults() { return; } - if (ui->comboDevices->currentIndex() == 1) { - // Reset keyboard bindings + for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) { + const auto* const button = button_map[button_id]; + if (button == nullptr) { + continue; + } + emulated_controller->SetButtonParam(button_id, {}); + } + + for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) { + for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) { + const auto* const analog_button = analog_map_buttons[analog_id][sub_button_id]; + if (analog_button == nullptr) { + continue; + } + emulated_controller->SetStickParam(analog_id, {}); + } + } + + for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) { + const auto* const motion_button = motion_map[motion_id]; + if (motion_button == nullptr) { + continue; + } + emulated_controller->SetMotionParam(motion_id, {}); + } + + // Reset keyboard or mouse bindings + if (ui->comboDevices->currentIndex() == 1 || ui->comboDevices->currentIndex() == 2) { for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) { - buttons_param[button_id] = Common::ParamPackage{ - InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])}; + emulated_controller->SetButtonParam( + button_id, Common::ParamPackage{InputCommon::GenerateKeyboardParam( + Config::default_buttons[button_id])}); } for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) { + Common::ParamPackage analog_param{}; for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) { Common::ParamPackage params{InputCommon::GenerateKeyboardParam( Config::default_analogs[analog_id][sub_button_id])}; - SetAnalogParam(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]); + SetAnalogParam(params, analog_param, analog_sub_buttons[sub_button_id]); } - analogs_param[analog_id].Set("modifier", InputCommon::GenerateKeyboardParam( - Config::default_stick_mod[analog_id])); + analog_param.Set("modifier", InputCommon::GenerateKeyboardParam( + Config::default_stick_mod[analog_id])); + emulated_controller->SetStickParam(analog_id, analog_param); } for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) { - motions_param[motion_id] = Common::ParamPackage{ - InputCommon::GenerateKeyboardParam(Config::default_motions[motion_id])}; + emulated_controller->SetMotionParam( + motion_id, Common::ParamPackage{InputCommon::GenerateKeyboardParam( + Config::default_motions[motion_id])}); } - UpdateUI(); - return; + // If mouse is selected we want to override with mappings from the driver + if (ui->comboDevices->currentIndex() == 1) { + UpdateUI(); + return; + } } // Reset controller bindings const auto& device = input_devices[ui->comboDevices->currentIndex()]; - auto button_mapping = input_subsystem->GetButtonMappingForDevice(device); - auto analog_mapping = input_subsystem->GetAnalogMappingForDevice(device); - auto motion_mapping = input_subsystem->GetMotionMappingForDevice(device); - for (std::size_t i = 0; i < buttons_param.size(); ++i) { - buttons_param[i] = button_mapping[static_cast(i)]; + auto button_mappings = input_subsystem->GetButtonMappingForDevice(device); + auto analog_mappings = input_subsystem->GetAnalogMappingForDevice(device); + auto motion_mappings = input_subsystem->GetMotionMappingForDevice(device); + + for (const auto& button_mapping : button_mappings) { + const std::size_t index = button_mapping.first; + emulated_controller->SetButtonParam(index, button_mapping.second); } - for (std::size_t i = 0; i < analogs_param.size(); ++i) { - analogs_param[i] = analog_mapping[static_cast(i)]; + for (const auto& analog_mapping : analog_mappings) { + const std::size_t index = analog_mapping.first; + emulated_controller->SetStickParam(index, analog_mapping.second); } - for (std::size_t i = 0; i < motions_param.size(); ++i) { - motions_param[i] = motion_mapping[static_cast(i)]; + for (const auto& motion_mapping : motion_mappings) { + const std::size_t index = motion_mapping.first; + emulated_controller->SetMotionParam(index, motion_mapping.second); } UpdateUI(); @@ -1330,7 +1258,7 @@ void ConfigureInputPlayer::UpdateMappingWithDefaults() { void ConfigureInputPlayer::HandleClick( QPushButton* button, std::size_t button_id, std::function new_input_setter, - InputCommon::Polling::DeviceType type) { + InputCommon::Polling::InputType type) { if (button == ui->buttonMotionLeft || button == ui->buttonMotionRight) { button->setText(tr("Shake!")); } else { @@ -1338,71 +1266,31 @@ void ConfigureInputPlayer::HandleClick( } button->setFocus(); - // The first two input devices are always Any and Keyboard/Mouse. If the user filtered to a - // controller, then they don't want keyboard/mouse input - want_keyboard_mouse = ui->comboDevices->currentIndex() < 2; - input_setter = new_input_setter; - device_pollers = input_subsystem->GetPollers(type); - - for (auto& poller : device_pollers) { - poller->Start(); - } + input_subsystem->BeginMapping(type); QWidget::grabMouse(); QWidget::grabKeyboard(); - if (type == InputCommon::Polling::DeviceType::Button) { - input_subsystem->GetGCButtons()->BeginConfiguration(); - } else { - input_subsystem->GetGCAnalogs()->BeginConfiguration(); - } - - if (type == InputCommon::Polling::DeviceType::Motion) { - input_subsystem->GetUDPMotions()->BeginConfiguration(); - } - - if (type == InputCommon::Polling::DeviceType::Button) { - input_subsystem->GetMouseButtons()->BeginConfiguration(); - } else if (type == InputCommon::Polling::DeviceType::AnalogPreferred) { - input_subsystem->GetMouseAnalogs()->BeginConfiguration(); - } else if (type == InputCommon::Polling::DeviceType::Motion) { - input_subsystem->GetMouseMotions()->BeginConfiguration(); - } else { - input_subsystem->GetMouseTouch()->BeginConfiguration(); - } - - if (type == InputCommon::Polling::DeviceType::Button) { + if (type == InputCommon::Polling::InputType::Button) { ui->controllerFrame->BeginMappingButton(button_id); - } else if (type == InputCommon::Polling::DeviceType::AnalogPreferred) { + } else if (type == InputCommon::Polling::InputType::Stick) { ui->controllerFrame->BeginMappingAnalog(button_id); } timeout_timer->start(2500); // Cancel after 2.5 seconds - poll_timer->start(50); // Check for new inputs every 50ms + poll_timer->start(25); // Check for new inputs every 25ms } void ConfigureInputPlayer::SetPollingResult(const Common::ParamPackage& params, bool abort) { timeout_timer->stop(); poll_timer->stop(); - for (auto& poller : device_pollers) { - poller->Stop(); - } + input_subsystem->StopMapping(); QWidget::releaseMouse(); QWidget::releaseKeyboard(); - input_subsystem->GetGCButtons()->EndConfiguration(); - input_subsystem->GetGCAnalogs()->EndConfiguration(); - - input_subsystem->GetUDPMotions()->EndConfiguration(); - - input_subsystem->GetMouseButtons()->EndConfiguration(); - input_subsystem->GetMouseAnalogs()->EndConfiguration(); - input_subsystem->GetMouseMotions()->EndConfiguration(); - input_subsystem->GetMouseTouch()->EndConfiguration(); - if (!abort) { (*input_setter)(params); } @@ -1419,15 +1307,20 @@ bool ConfigureInputPlayer::IsInputAcceptable(const Common::ParamPackage& params) return true; } + if (params.Has("motion")) { + return true; + } + // Keyboard/Mouse - if (ui->comboDevices->currentIndex() == 1) { + if (ui->comboDevices->currentIndex() == 1 || ui->comboDevices->currentIndex() == 2) { return params.Get("engine", "") == "keyboard" || params.Get("engine", "") == "mouse"; } const auto current_input_device = input_devices[ui->comboDevices->currentIndex()]; - return params.Get("engine", "") == current_input_device.Get("class", "") && - params.Get("guid", "") == current_input_device.Get("guid", "") && - params.Get("port", "") == current_input_device.Get("port", ""); + return params.Get("engine", "") == current_input_device.Get("engine", "") && + (params.Get("guid", "") == current_input_device.Get("guid", "") || + params.Get("guid", "") == current_input_device.Get("guid2", "")) && + params.Get("port", 0) == current_input_device.Get("port", 0); } void ConfigureInputPlayer::mousePressEvent(QMouseEvent* event) { @@ -1436,25 +1329,17 @@ void ConfigureInputPlayer::mousePressEvent(QMouseEvent* event) { } const auto button = GRenderWindow::QtButtonToMouseButton(event->button()); - input_subsystem->GetMouse()->PressButton(0, 0, button); + input_subsystem->GetMouse()->PressButton(0, 0, 0, 0, button); } void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) { + event->ignore(); if (!input_setter || !event) { return; } - if (event->key() != Qt::Key_Escape) { - if (want_keyboard_mouse) { - SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())}, - false); - } else { - // Escape key wasn't pressed and we don't want any keyboard keys, so don't stop polling - return; - } + input_subsystem->GetKeyboard()->PressKey(event->key()); } - - SetPollingResult({}, true); } void ConfigureInputPlayer::CreateProfile() { diff --git a/src/yuzu/configuration/configure_input_player.h b/src/yuzu/configuration/configure_input_player.h index 39b44b8a56..47df6b3d33 100644 --- a/src/yuzu/configuration/configure_input_player.h +++ b/src/yuzu/configuration/configure_input_player.h @@ -29,48 +29,37 @@ class QWidget; class InputProfiles; -namespace Core { -class System; -} - namespace InputCommon { class InputSubsystem; } namespace InputCommon::Polling { -class DevicePoller; -enum class DeviceType; +enum class InputType; } // namespace InputCommon::Polling namespace Ui { class ConfigureInputPlayer; } +namespace Core::HID { +class HIDCore; +class EmulatedController; +enum class NpadStyleIndex : u8; +} // namespace Core::HID + class ConfigureInputPlayer : public QWidget { Q_OBJECT public: explicit ConfigureInputPlayer(QWidget* parent, std::size_t player_index, QWidget* bottom_row, InputCommon::InputSubsystem* input_subsystem_, - InputProfiles* profiles_, Core::System& system_, - bool debug = false); + InputProfiles* profiles_, Core::HID::HIDCore& hid_core_, + bool is_powered_on_, bool debug = false); ~ConfigureInputPlayer() override; /// Save all button configurations to settings file. void ApplyConfiguration(); - /** - * Attempts to connect the currently selected controller in the HID backend. - * This function will not do anything if it is not connected in the frontend. - */ - void TryConnectSelectedController(); - - /** - * Attempts to disconnect the currently selected controller in the HID backend. - * This function will not do anything if the configuration has not changed. - */ - void TryDisconnectSelectedController(); - /// Set the connection state checkbox (used to sync state). void ConnectPlayer(bool connected); @@ -104,6 +93,10 @@ protected: void showEvent(QShowEvent* event) override; private: + QString ButtonToText(const Common::ParamPackage& param); + + QString AnalogToText(const Common::ParamPackage& param, const std::string& dir); + void changeEvent(QEvent* event) override; void RetranslateUI(); @@ -113,7 +106,7 @@ private: /// Called when the button was pressed. void HandleClick(QPushButton* button, std::size_t button_id, std::function new_input_setter, - InputCommon::Polling::DeviceType type); + InputCommon::Polling::InputType type); /// Finish polling and configure input using the input_setter. void SetPollingResult(const Common::ParamPackage& params, bool abort); @@ -134,17 +127,14 @@ private: void SetConnectableControllers(); /// Gets the Controller Type for a given controller combobox index. - Settings::ControllerType GetControllerTypeFromIndex(int index) const; + Core::HID::NpadStyleIndex GetControllerTypeFromIndex(int index) const; /// Gets the controller combobox index for a given Controller Type. - int GetIndexFromControllerType(Settings::ControllerType type) const; + int GetIndexFromControllerType(Core::HID::NpadStyleIndex type) const; /// Update the available input devices. void UpdateInputDevices(); - /// Update the current controller icon. - void UpdateControllerIcon(); - /// Hides and disables controller settings based on the current controller type. void UpdateControllerAvailableButtons(); @@ -176,6 +166,7 @@ private: std::size_t player_index; bool debug; + bool is_powered_on; InputCommon::InputSubsystem* input_subsystem; @@ -185,7 +176,7 @@ private: std::unique_ptr poll_timer; /// Stores a pair of "Connected Controllers" combobox index and Controller Type enum. - std::vector> index_controller_type_pairs; + std::vector> index_controller_type_pairs; static constexpr int PLAYER_COUNT = 8; std::array player_connected_checkbox; @@ -193,9 +184,7 @@ private: /// This will be the the setting function when an input is awaiting configuration. std::optional> input_setter; - std::array buttons_param; - std::array analogs_param; - std::array motions_param; + Core::HID::EmulatedController* emulated_controller; static constexpr int ANALOG_SUB_BUTTONS_NUM = 4; @@ -221,15 +210,9 @@ private: static const std::array analog_sub_buttons; - std::vector> device_pollers; - /// A flag to indicate that the "Map Analog Stick" pop-up has been shown and accepted once. bool map_analog_stick_accepted{}; - /// A flag to indicate if keyboard keys are okay when configuring an input. If this is false, - /// keyboard events are ignored. - bool want_keyboard_mouse{}; - /// List of physical devices users can map with. If a SDL backed device is selected, then you /// can use this device to get a default mapping. std::vector input_devices; @@ -239,5 +222,5 @@ private: /// parent of the widget to this widget (but thats fine). QWidget* bottom_row; - Core::System& system; + Core::HID::HIDCore& hid_core; }; diff --git a/src/yuzu/configuration/configure_input_player.ui b/src/yuzu/configuration/configure_input_player.ui index e7433912b8..756a414b58 100644 --- a/src/yuzu/configuration/configure_input_player.ui +++ b/src/yuzu/configuration/configure_input_player.ui @@ -89,31 +89,6 @@ 21 - - - Pro Controller - - - - - Dual Joycons - - - - - Left Joycon - - - - - Right Joycon - - - - - Handheld - - @@ -142,22 +117,9 @@ - - - 0 - 21 - + + 60 - - - Any - - - - - Keyboard/Mouse - - @@ -342,7 +304,7 @@ 3 - 0 + 6 3 @@ -918,7 +880,7 @@ 3 - 0 + 6 3 @@ -2221,7 +2183,7 @@ 3 - 0 + 6 3 @@ -2570,7 +2532,7 @@ 3 - 0 + 6 3 diff --git a/src/yuzu/configuration/configure_input_player_widget.cpp b/src/yuzu/configuration/configure_input_player_widget.cpp index f31f86339e..6630321cb6 100644 --- a/src/yuzu/configuration/configure_input_player_widget.cpp +++ b/src/yuzu/configuration/configure_input_player_widget.cpp @@ -6,10 +6,12 @@ #include #include #include + +#include "core/hid/emulated_controller.h" #include "yuzu/configuration/configure_input_player_widget.h" PlayerControlPreview::PlayerControlPreview(QWidget* parent) : QFrame(parent) { - UpdateColors(); + is_controller_set = false; QTimer* timer = new QTimer(this); connect(timer, &QTimer::timeout, this, QOverload<>::of(&PlayerControlPreview::UpdateInput)); @@ -17,91 +19,37 @@ PlayerControlPreview::PlayerControlPreview(QWidget* parent) : QFrame(parent) { timer->start(16); } -PlayerControlPreview::~PlayerControlPreview() = default; +PlayerControlPreview::~PlayerControlPreview() { + UnloadController(); +}; -void PlayerControlPreview::SetPlayerInput(std::size_t index, const ButtonParam& buttons_param, - const AnalogParam& analogs_param) { - player_index = index; - Settings::ButtonsRaw buttonss; - Settings::AnalogsRaw analogs; - std::transform(buttons_param.begin(), buttons_param.end(), buttonss.begin(), - [](const Common::ParamPackage& param) { return param.Serialize(); }); - std::transform(analogs_param.begin(), analogs_param.end(), analogs.begin(), - [](const Common::ParamPackage& param) { return param.Serialize(); }); - - std::transform(buttonss.begin() + Settings::NativeButton::BUTTON_HID_BEGIN, - buttonss.begin() + Settings::NativeButton::BUTTON_NS_END, buttons.begin(), - Input::CreateDevice); - std::transform(analogs.begin() + Settings::NativeAnalog::STICK_HID_BEGIN, - analogs.begin() + Settings::NativeAnalog::STICK_HID_END, sticks.begin(), - Input::CreateDevice); - UpdateColors(); -} -void PlayerControlPreview::SetPlayerInputRaw(std::size_t index, - const Settings::ButtonsRaw& buttons_, - Settings::AnalogsRaw analogs_) { - player_index = index; - std::transform(buttons_.begin() + Settings::NativeButton::BUTTON_HID_BEGIN, - buttons_.begin() + Settings::NativeButton::BUTTON_NS_END, buttons.begin(), - Input::CreateDevice); - std::transform(analogs_.begin() + Settings::NativeAnalog::STICK_HID_BEGIN, - analogs_.begin() + Settings::NativeAnalog::STICK_HID_END, sticks.begin(), - Input::CreateDevice); - UpdateColors(); +void PlayerControlPreview::SetController(Core::HID::EmulatedController* controller_) { + UnloadController(); + is_controller_set = true; + controller = controller_; + Core::HID::ControllerUpdateCallback engine_callback{ + .on_change = [this](Core::HID::ControllerTriggerType type) { ControllerUpdate(type); }, + .is_npad_service = false, + }; + callback_key = controller->SetCallback(engine_callback); + ControllerUpdate(Core::HID::ControllerTriggerType::All); } -PlayerControlPreview::LedPattern PlayerControlPreview::GetColorPattern(std::size_t index, - bool player_on) { - if (!player_on) { - return {0, 0, 0, 0}; - } - - switch (index) { - case 0: - return {1, 0, 0, 0}; - case 1: - return {1, 1, 0, 0}; - case 2: - return {1, 1, 1, 0}; - case 3: - return {1, 1, 1, 1}; - case 4: - return {1, 0, 0, 1}; - case 5: - return {1, 0, 1, 0}; - case 6: - return {1, 0, 1, 1}; - case 7: - return {0, 1, 1, 0}; - default: - return {0, 0, 0, 0}; +void PlayerControlPreview::UnloadController() { + if (is_controller_set) { + controller->DeleteCallback(callback_key); + is_controller_set = false; } } -void PlayerControlPreview::SetConnectedStatus(bool checked) { - LedPattern led_pattern = GetColorPattern(player_index, checked); - - led_color[0] = led_pattern.position1 ? colors.led_on : colors.led_off; - led_color[1] = led_pattern.position2 ? colors.led_on : colors.led_off; - led_color[2] = led_pattern.position3 ? colors.led_on : colors.led_off; - led_color[3] = led_pattern.position4 ? colors.led_on : colors.led_off; - is_enabled = checked; - ResetInputs(); -} - -void PlayerControlPreview::SetControllerType(const Settings::ControllerType type) { - controller_type = type; - UpdateColors(); -} - -void PlayerControlPreview::BeginMappingButton(std::size_t index) { - button_mapping_index = index; +void PlayerControlPreview::BeginMappingButton(std::size_t button_id) { + button_mapping_index = button_id; mapping_active = true; } -void PlayerControlPreview::BeginMappingAnalog(std::size_t index) { - button_mapping_index = Settings::NativeButton::LStick + index; - analog_mapping_index = index; +void PlayerControlPreview::BeginMappingAnalog(std::size_t stick_id) { + button_mapping_index = Settings::NativeButton::LStick + stick_id; + analog_mapping_index = stick_id; mapping_active = true; } @@ -157,84 +105,109 @@ void PlayerControlPreview::UpdateColors() { colors.left = colors.primary; colors.right = colors.primary; // Possible alternative to set colors from settings - // colors.left = QColor(Settings::values.players.GetValue()[player_index].body_color_left); - // colors.right = QColor(Settings::values.players.GetValue()[player_index].body_color_right); + // colors.left = QColor(controller->GetColors().left.body); + // colors.right = QColor(controller->GetColors().right.body); } void PlayerControlPreview::ResetInputs() { - for (std::size_t index = 0; index < button_values.size(); ++index) { - button_values[index] = false; - } - - for (std::size_t index = 0; index < axis_values.size(); ++index) { - axis_values[index].properties = {0, 1, 0}; - axis_values[index].value = {0, 0}; - axis_values[index].raw_value = {0, 0}; - } + button_values.fill({ + .value = false, + }); + stick_values.fill({ + .x = {.value = 0, .properties = {0, 1, 0}}, + .y = {.value = 0, .properties = {0, 1, 0}}, + }); + trigger_values.fill({ + .analog = {.value = 0, .properties = {0, 1, 0}}, + .pressed = {.value = false}, + }); update(); } -void PlayerControlPreview::UpdateInput() { - if (!is_enabled && !mapping_active && !Settings::values.tas_enable) { +void PlayerControlPreview::ControllerUpdate(Core::HID::ControllerTriggerType type) { + if (type == Core::HID::ControllerTriggerType::All) { + ControllerUpdate(Core::HID::ControllerTriggerType::Color); + ControllerUpdate(Core::HID::ControllerTriggerType::Type); + ControllerUpdate(Core::HID::ControllerTriggerType::Connected); + ControllerUpdate(Core::HID::ControllerTriggerType::Button); + ControllerUpdate(Core::HID::ControllerTriggerType::Stick); + ControllerUpdate(Core::HID::ControllerTriggerType::Trigger); + ControllerUpdate(Core::HID::ControllerTriggerType::Battery); return; } - bool input_changed = false; - const auto& button_state = buttons; - for (std::size_t index = 0; index < button_values.size(); ++index) { - bool value = false; - if (index < Settings::NativeButton::BUTTON_NS_END) { - value = button_state[index]->GetStatus(); - } - bool blink = mapping_active && index == button_mapping_index; - if (analog_mapping_index == Settings::NativeAnalog::NUM_STICKS_HID) { - blink &= blink_counter > 25; - } - if (button_values[index] != value || blink) { - input_changed = true; - } - button_values[index] = value || blink; + + switch (type) { + case Core::HID::ControllerTriggerType::Connected: + is_connected = true; + led_pattern = controller->GetLedPattern(); + needs_redraw = true; + break; + case Core::HID::ControllerTriggerType::Disconnected: + is_connected = false; + led_pattern.raw = 0; + needs_redraw = true; + break; + case Core::HID::ControllerTriggerType::Type: + controller_type = controller->GetNpadStyleIndex(true); + needs_redraw = true; + break; + case Core::HID::ControllerTriggerType::Color: + UpdateColors(); + needs_redraw = true; + break; + case Core::HID::ControllerTriggerType::Button: + button_values = controller->GetButtonsValues(); + needs_redraw = true; + break; + case Core::HID::ControllerTriggerType::Stick: + using namespace Settings::NativeAnalog; + stick_values = controller->GetSticksValues(); + // Y axis is inverted + stick_values[LStick].y.value = -stick_values[LStick].y.value; + stick_values[LStick].y.raw_value = -stick_values[LStick].y.raw_value; + stick_values[RStick].y.value = -stick_values[RStick].y.value; + stick_values[RStick].y.raw_value = -stick_values[RStick].y.raw_value; + needs_redraw = true; + break; + case Core::HID::ControllerTriggerType::Trigger: + trigger_values = controller->GetTriggersValues(); + needs_redraw = true; + break; + case Core::HID::ControllerTriggerType::Battery: + battery_values = controller->GetBatteryValues(); + needs_redraw = true; + break; + default: + break; } +} - const auto& analog_state = sticks; - for (std::size_t index = 0; index < axis_values.size(); ++index) { - const auto [stick_x_f, stick_y_f] = analog_state[index]->GetStatus(); - const auto [stick_x_rf, stick_y_rf] = analog_state[index]->GetRawStatus(); +void PlayerControlPreview::UpdateInput() { + if (mapping_active) { - if (static_cast(stick_x_rf * 45) != - static_cast(axis_values[index].raw_value.x() * 45) || - static_cast(-stick_y_rf * 45) != - static_cast(axis_values[index].raw_value.y() * 45)) { - input_changed = true; + for (std::size_t index = 0; index < button_values.size(); ++index) { + bool blink = index == button_mapping_index; + if (analog_mapping_index == Settings::NativeAnalog::NumAnalogs) { + blink &= blink_counter > 25; + } + if (button_values[index].value != blink) { + needs_redraw = true; + } + button_values[index].value = blink; } - axis_values[index].properties = analog_state[index]->GetAnalogProperties(); - axis_values[index].value = QPointF(stick_x_f, -stick_y_f); - axis_values[index].raw_value = QPointF(stick_x_rf, -stick_y_rf); - - const bool blink_analog = mapping_active && index == analog_mapping_index; - if (blink_analog) { - input_changed = true; - axis_values[index].value = - QPointF(blink_counter < 25 ? -blink_counter / 25.0f : 0, - blink_counter > 25 ? -(blink_counter - 25) / 25.0f : 0); + for (std::size_t index = 0; index < stick_values.size(); ++index) { + const bool blink_analog = index == analog_mapping_index; + if (blink_analog) { + needs_redraw = true; + stick_values[index].x.value = blink_counter < 25 ? -blink_counter / 25.0f : 0; + stick_values[index].y.value = + blink_counter > 25 ? -(blink_counter - 25) / 25.0f : 0; + } } } - - if (input_changed) { + if (needs_redraw) { update(); - if (controller_callback.input != nullptr) { - ControllerInput input{ - .axis_values = {std::pair{ - axis_values[Settings::NativeAnalog::LStick].value.x(), - axis_values[Settings::NativeAnalog::LStick].value.y()}, - std::pair{ - axis_values[Settings::NativeAnalog::RStick].value.x(), - axis_values[Settings::NativeAnalog::RStick].value.y()}}, - .button_values = button_values, - .changed = true, - }; - controller_callback.input(std::move(input)); - } } if (mapping_active) { @@ -242,10 +215,6 @@ void PlayerControlPreview::UpdateInput() { } } -void PlayerControlPreview::SetCallBack(ControllerCallback callback_) { - controller_callback = std::move(callback_); -} - void PlayerControlPreview::paintEvent(QPaintEvent* event) { QFrame::paintEvent(event); QPainter p(this); @@ -253,22 +222,22 @@ void PlayerControlPreview::paintEvent(QPaintEvent* event) { const QPointF center = rect().center(); switch (controller_type) { - case Settings::ControllerType::Handheld: + case Core::HID::NpadStyleIndex::Handheld: DrawHandheldController(p, center); break; - case Settings::ControllerType::DualJoyconDetached: + case Core::HID::NpadStyleIndex::JoyconDual: DrawDualController(p, center); break; - case Settings::ControllerType::LeftJoycon: + case Core::HID::NpadStyleIndex::JoyconLeft: DrawLeftController(p, center); break; - case Settings::ControllerType::RightJoycon: + case Core::HID::NpadStyleIndex::JoyconRight: DrawRightController(p, center); break; - case Settings::ControllerType::GameCube: + case Core::HID::NpadStyleIndex::GameCube: DrawGCController(p, center); break; - case Settings::ControllerType::ProController: + case Core::HID::NpadStyleIndex::ProController: default: DrawProController(p, center); break; @@ -281,7 +250,7 @@ void PlayerControlPreview::DrawLeftController(QPainter& p, const QPointF center) // Sideview left joystick DrawJoystickSideview(p, center + QPoint(142, -69), - -axis_values[Settings::NativeAnalog::LStick].value.y(), 1.15f, + -stick_values[Settings::NativeAnalog::LStick].y.value, 1.15f, button_values[LStick]); // Topview D-pad buttons @@ -292,7 +261,7 @@ void PlayerControlPreview::DrawLeftController(QPainter& p, const QPointF center) // Topview left joystick DrawJoystickSideview(p, center + QPointF(-140.5f, -28), - -axis_values[Settings::NativeAnalog::LStick].value.x() + 15.0f, 1.15f, + -stick_values[Settings::NativeAnalog::LStick].x.value + 15.0f, 1.15f, button_values[LStick]); // Topview minus button @@ -334,8 +303,10 @@ void PlayerControlPreview::DrawLeftController(QPainter& p, const QPointF center) { // Draw joysticks using namespace Settings::NativeAnalog; - DrawJoystick(p, center + QPointF(9, -69) + (axis_values[LStick].value * 8), 1.8f, - button_values[Settings::NativeButton::LStick]); + DrawJoystick(p, + center + QPointF(9, -69) + + (QPointF(stick_values[LStick].x.value, stick_values[LStick].y.value) * 8), + 1.8f, button_values[Settings::NativeButton::LStick]); DrawRawJoystick(p, center + QPointF(-140, 90), QPointF(0, 0)); } @@ -384,6 +355,10 @@ void PlayerControlPreview::DrawLeftController(QPainter& p, const QPointF center) p.setPen(colors.font2); p.setBrush(colors.font2); DrawCircle(p, center + QPoint(26, 71), 5); + + // Draw battery + DrawBattery(p, center + QPoint(-170, -140), + battery_values[Core::HID::EmulatedDeviceIndex::LeftIndex]); } void PlayerControlPreview::DrawRightController(QPainter& p, const QPointF center) { @@ -392,20 +367,22 @@ void PlayerControlPreview::DrawRightController(QPainter& p, const QPointF center // Sideview right joystick DrawJoystickSideview(p, center + QPoint(173 - 315, 11), - axis_values[Settings::NativeAnalog::RStick].value.y() + 10.0f, 1.15f, + stick_values[Settings::NativeAnalog::RStick].y.value + 10.0f, 1.15f, button_values[Settings::NativeButton::RStick]); + // Topview right joystick + DrawJoystickSideview(p, center + QPointF(140, -28), + -stick_values[Settings::NativeAnalog::RStick].x.value + 15.0f, 1.15f, + button_values[RStick]); + // Topview face buttons p.setPen(colors.outline); button_color = colors.button; DrawRoundButton(p, center + QPoint(163, -21), button_values[A], 11, 5, Direction::Up); + DrawRoundButton(p, center + QPoint(140, -21), button_values[B], 11, 5, Direction::Up); + DrawRoundButton(p, center + QPoint(140, -21), button_values[X], 11, 5, Direction::Up); DrawRoundButton(p, center + QPoint(117, -21), button_values[Y], 11, 5, Direction::Up); - // Topview right joystick - DrawJoystickSideview(p, center + QPointF(140, -28), - -axis_values[Settings::NativeAnalog::RStick].value.x() + 15.0f, 1.15f, - button_values[RStick]); - // Topview plus button p.setPen(colors.outline); button_color = colors.button; @@ -448,8 +425,10 @@ void PlayerControlPreview::DrawRightController(QPainter& p, const QPointF center { // Draw joysticks using namespace Settings::NativeAnalog; - DrawJoystick(p, center + QPointF(-9, 11) + (axis_values[RStick].value * 8), 1.8f, - button_values[Settings::NativeButton::RStick]); + DrawJoystick(p, + center + QPointF(-9, 11) + + (QPointF(stick_values[RStick].x.value, stick_values[RStick].y.value) * 8), + 1.8f, button_values[Settings::NativeButton::RStick]); DrawRawJoystick(p, QPointF(0, 0), center + QPointF(140, 90)); } @@ -503,6 +482,10 @@ void PlayerControlPreview::DrawRightController(QPainter& p, const QPointF center p.setPen(colors.transparent); p.setBrush(colors.font2); DrawSymbol(p, center + QPoint(-26, 66), Symbol::House, 5); + + // Draw battery + DrawBattery(p, center + QPoint(110, -140), + battery_values[Core::HID::EmulatedDeviceIndex::RightIndex]); } void PlayerControlPreview::DrawDualController(QPainter& p, const QPointF center) { @@ -512,17 +495,19 @@ void PlayerControlPreview::DrawDualController(QPainter& p, const QPointF center) // Left/Right trigger DrawDualTriggers(p, center, button_values[L], button_values[R]); + // Topview right joystick + DrawJoystickSideview(p, center + QPointF(180, -78), + -stick_values[Settings::NativeAnalog::RStick].x.value + 15.0f, 1, + button_values[RStick]); + // Topview face buttons p.setPen(colors.outline); button_color = colors.button; DrawRoundButton(p, center + QPoint(200, -71), button_values[A], 10, 5, Direction::Up); + DrawRoundButton(p, center + QPoint(180, -71), button_values[B], 10, 5, Direction::Up); + DrawRoundButton(p, center + QPoint(180, -71), button_values[X], 10, 5, Direction::Up); DrawRoundButton(p, center + QPoint(160, -71), button_values[Y], 10, 5, Direction::Up); - // Topview right joystick - DrawJoystickSideview(p, center + QPointF(180, -78), - -axis_values[Settings::NativeAnalog::RStick].value.x() + 15.0f, 1, - button_values[RStick]); - // Topview plus button p.setPen(colors.outline); button_color = colors.button; @@ -538,7 +523,7 @@ void PlayerControlPreview::DrawDualController(QPainter& p, const QPointF center) // Topview left joystick DrawJoystickSideview(p, center + QPointF(-180.5f, -78), - -axis_values[Settings::NativeAnalog::LStick].value.x() + 15.0f, 1, + -stick_values[Settings::NativeAnalog::LStick].x.value + 15.0f, 1, button_values[LStick]); // Topview minus button @@ -557,13 +542,13 @@ void PlayerControlPreview::DrawDualController(QPainter& p, const QPointF center) { // Draw joysticks using namespace Settings::NativeAnalog; - const auto& l_stick = axis_values[LStick]; + const auto l_stick = QPointF(stick_values[LStick].x.value, stick_values[LStick].y.value); const auto l_button = button_values[Settings::NativeButton::LStick]; - const auto& r_stick = axis_values[RStick]; + const auto r_stick = QPointF(stick_values[RStick].x.value, stick_values[RStick].y.value); const auto r_button = button_values[Settings::NativeButton::RStick]; - DrawJoystick(p, center + QPointF(-65, -65) + (l_stick.value * 7), 1.62f, l_button); - DrawJoystick(p, center + QPointF(65, 12) + (r_stick.value * 7), 1.62f, r_button); + DrawJoystick(p, center + QPointF(-65, -65) + (l_stick * 7), 1.62f, l_button); + DrawJoystick(p, center + QPointF(65, 12) + (r_stick * 7), 1.62f, r_button); DrawRawJoystick(p, center + QPointF(-180, 90), center + QPointF(180, 90)); } @@ -634,6 +619,12 @@ void PlayerControlPreview::DrawDualController(QPainter& p, const QPointF center) p.setPen(colors.transparent); p.setBrush(colors.font2); DrawSymbol(p, center + QPoint(50, 60), Symbol::House, 4.2f); + + // Draw battery + DrawBattery(p, center + QPoint(-100, -160), + battery_values[Core::HID::EmulatedDeviceIndex::LeftIndex]); + DrawBattery(p, center + QPoint(40, -160), + battery_values[Core::HID::EmulatedDeviceIndex::RightIndex]); } void PlayerControlPreview::DrawHandheldController(QPainter& p, const QPointF center) { @@ -643,13 +634,13 @@ void PlayerControlPreview::DrawHandheldController(QPainter& p, const QPointF cen { // Draw joysticks using namespace Settings::NativeAnalog; - const auto& l_stick = axis_values[LStick]; + const auto l_stick = QPointF(stick_values[LStick].x.value, stick_values[LStick].y.value); const auto l_button = button_values[Settings::NativeButton::LStick]; - const auto& r_stick = axis_values[RStick]; + const auto r_stick = QPointF(stick_values[RStick].x.value, stick_values[RStick].y.value); const auto r_button = button_values[Settings::NativeButton::RStick]; - DrawJoystick(p, center + QPointF(-171, -41) + (l_stick.value * 4), 1.0f, l_button); - DrawJoystick(p, center + QPointF(171, 8) + (r_stick.value * 4), 1.0f, r_button); + DrawJoystick(p, center + QPointF(-171, -41) + (l_stick * 4), 1.0f, l_button); + DrawJoystick(p, center + QPointF(171, 8) + (r_stick * 4), 1.0f, r_button); DrawRawJoystick(p, center + QPointF(-50, 0), center + QPointF(50, 0)); } @@ -732,6 +723,12 @@ void PlayerControlPreview::DrawHandheldController(QPainter& p, const QPointF cen p.setPen(colors.transparent); p.setBrush(colors.font2); DrawSymbol(p, center + QPoint(161, 37), Symbol::House, 2.75f); + + // Draw battery + DrawBattery(p, center + QPoint(-200, 110), + battery_values[Core::HID::EmulatedDeviceIndex::LeftIndex]); + DrawBattery(p, center + QPoint(130, 110), + battery_values[Core::HID::EmulatedDeviceIndex::RightIndex]); } void PlayerControlPreview::DrawProController(QPainter& p, const QPointF center) { @@ -741,9 +738,11 @@ void PlayerControlPreview::DrawProController(QPainter& p, const QPointF center) { // Draw joysticks using namespace Settings::NativeAnalog; - DrawProJoystick(p, center + QPointF(-111, -55), axis_values[LStick].value, 11, + const auto l_stick = QPointF(stick_values[LStick].x.value, stick_values[LStick].y.value); + const auto r_stick = QPointF(stick_values[RStick].x.value, stick_values[RStick].y.value); + DrawProJoystick(p, center + QPointF(-111, -55), l_stick, 11, button_values[Settings::NativeButton::LStick]); - DrawProJoystick(p, center + QPointF(51, 0), axis_values[RStick].value, 11, + DrawProJoystick(p, center + QPointF(51, 0), r_stick, 11, button_values[Settings::NativeButton::RStick]); DrawRawJoystick(p, center + QPointF(-50, 105), center + QPointF(50, 105)); } @@ -817,24 +816,27 @@ void PlayerControlPreview::DrawProController(QPainter& p, const QPointF center) p.setPen(colors.transparent); p.setBrush(colors.font2); DrawSymbol(p, center + QPoint(29, -56), Symbol::House, 3.9f); + + // Draw battery + DrawBattery(p, center + QPoint(-30, -160), + battery_values[Core::HID::EmulatedDeviceIndex::LeftIndex]); } void PlayerControlPreview::DrawGCController(QPainter& p, const QPointF center) { - DrawGCTriggers(p, center, button_values[Settings::NativeButton::ZL], - button_values[Settings::NativeButton::ZR]); + DrawGCTriggers(p, center, trigger_values[0], trigger_values[1]); DrawGCButtonZ(p, center, button_values[Settings::NativeButton::R]); DrawGCBody(p, center); { // Draw joysticks using namespace Settings::NativeAnalog; - DrawGCJoystick(p, center + QPointF(-111, -44) + (axis_values[LStick].value * 10), false); + const auto l_stick = QPointF(stick_values[LStick].x.value, stick_values[LStick].y.value); + const auto r_stick = QPointF(stick_values[RStick].x.value, stick_values[RStick].y.value); + DrawGCJoystick(p, center + QPointF(-111, -44) + (l_stick * 10), {}); button_color = colors.button2; - DrawCircleButton(p, center + QPointF(61, 37) + (axis_values[RStick].value * 9.5f), false, - 15); + DrawCircleButton(p, center + QPointF(61, 37) + (r_stick * 9.5f), {}, 15); p.setPen(colors.transparent); p.setBrush(colors.font); - DrawSymbol(p, center + QPointF(61, 37) + (axis_values[RStick].value * 9.5f), Symbol::C, - 1.0f); + DrawSymbol(p, center + QPointF(61, 37) + (r_stick * 9.5f), Symbol::C, 1.0f); DrawRawJoystick(p, center + QPointF(-198, -125), center + QPointF(198, -125)); } @@ -871,6 +873,10 @@ void PlayerControlPreview::DrawGCController(QPainter& p, const QPointF center) { // Minus and Plus buttons p.setPen(colors.outline); DrawCircleButton(p, center + QPoint(0, -44), button_values[Plus], 8); + + // Draw battery + DrawBattery(p, center + QPoint(-30, -165), + battery_values[Core::HID::EmulatedDeviceIndex::LeftIndex]); } constexpr std::array symbol_a = { @@ -1837,10 +1843,14 @@ void PlayerControlPreview::DrawLeftBody(QPainter& p, const QPointF center) { const float led_size = 5.0f; const QPointF led_position = sideview_center + QPointF(0, -36); int led_count = 0; - for (const auto& color : led_color) { - p.setBrush(color); - DrawRectangle(p, led_position + QPointF(0, 12 * led_count++), led_size, led_size); - } + p.setBrush(led_pattern.position1 ? colors.led_on : colors.led_off); + DrawRectangle(p, led_position + QPointF(0, 12 * led_count++), led_size, led_size); + p.setBrush(led_pattern.position2 ? colors.led_on : colors.led_off); + DrawRectangle(p, led_position + QPointF(0, 12 * led_count++), led_size, led_size); + p.setBrush(led_pattern.position3 ? colors.led_on : colors.led_off); + DrawRectangle(p, led_position + QPointF(0, 12 * led_count++), led_size, led_size); + p.setBrush(led_pattern.position4 ? colors.led_on : colors.led_off); + DrawRectangle(p, led_position + QPointF(0, 12 * led_count++), led_size, led_size); } void PlayerControlPreview::DrawRightBody(QPainter& p, const QPointF center) { @@ -1933,14 +1943,19 @@ void PlayerControlPreview::DrawRightBody(QPainter& p, const QPointF center) { const float led_size = 5.0f; const QPointF led_position = sideview_center + QPointF(0, -36); int led_count = 0; - for (const auto& color : led_color) { - p.setBrush(color); - DrawRectangle(p, led_position + QPointF(0, 12 * led_count++), led_size, led_size); - } + p.setBrush(led_pattern.position1 ? colors.led_on : colors.led_off); + DrawRectangle(p, led_position + QPointF(0, 12 * led_count++), led_size, led_size); + p.setBrush(led_pattern.position2 ? colors.led_on : colors.led_off); + DrawRectangle(p, led_position + QPointF(0, 12 * led_count++), led_size, led_size); + p.setBrush(led_pattern.position3 ? colors.led_on : colors.led_off); + DrawRectangle(p, led_position + QPointF(0, 12 * led_count++), led_size, led_size); + p.setBrush(led_pattern.position4 ? colors.led_on : colors.led_off); + DrawRectangle(p, led_position + QPointF(0, 12 * led_count++), led_size, led_size); } -void PlayerControlPreview::DrawProTriggers(QPainter& p, const QPointF center, bool left_pressed, - bool right_pressed) { +void PlayerControlPreview::DrawProTriggers(QPainter& p, const QPointF center, + const Common::Input::ButtonStatus& left_pressed, + const Common::Input::ButtonStatus& right_pressed) { std::array qleft_trigger; std::array qright_trigger; std::array qbody_top; @@ -1949,8 +1964,10 @@ void PlayerControlPreview::DrawProTriggers(QPainter& p, const QPointF center, bo const float trigger_x = pro_left_trigger[point * 2 + 0]; const float trigger_y = pro_left_trigger[point * 2 + 1]; - qleft_trigger[point] = center + QPointF(trigger_x, trigger_y + (left_pressed ? 2 : 0)); - qright_trigger[point] = center + QPointF(-trigger_x, trigger_y + (right_pressed ? 2 : 0)); + qleft_trigger[point] = + center + QPointF(trigger_x, trigger_y + (left_pressed.value ? 2 : 0)); + qright_trigger[point] = + center + QPointF(-trigger_x, trigger_y + (right_pressed.value ? 2 : 0)); } for (std::size_t point = 0; point < pro_body_top.size() / 2; ++point) { @@ -1967,16 +1984,17 @@ void PlayerControlPreview::DrawProTriggers(QPainter& p, const QPointF center, bo DrawPolygon(p, qbody_top); // Left trigger - p.setBrush(left_pressed ? colors.highlight : colors.button); + p.setBrush(left_pressed.value ? colors.highlight : colors.button); DrawPolygon(p, qleft_trigger); // Right trigger - p.setBrush(right_pressed ? colors.highlight : colors.button); + p.setBrush(right_pressed.value ? colors.highlight : colors.button); DrawPolygon(p, qright_trigger); } -void PlayerControlPreview::DrawGCTriggers(QPainter& p, const QPointF center, bool left_pressed, - bool right_pressed) { +void PlayerControlPreview::DrawGCTriggers(QPainter& p, const QPointF center, + Common::Input::TriggerStatus left_trigger, + Common::Input::TriggerStatus right_trigger) { std::array qleft_trigger; std::array qright_trigger; @@ -1984,32 +2002,37 @@ void PlayerControlPreview::DrawGCTriggers(QPainter& p, const QPointF center, boo const float trigger_x = left_gc_trigger[point * 2 + 0]; const float trigger_y = left_gc_trigger[point * 2 + 1]; - qleft_trigger[point] = center + QPointF(trigger_x, trigger_y + (left_pressed ? 10 : 0)); - qright_trigger[point] = center + QPointF(-trigger_x, trigger_y + (right_pressed ? 10 : 0)); + qleft_trigger[point] = + center + QPointF(trigger_x, trigger_y + (left_trigger.analog.value * 10.0f)); + qright_trigger[point] = + center + QPointF(-trigger_x, trigger_y + (right_trigger.analog.value * 10.0f)); } // Left trigger p.setPen(colors.outline); - p.setBrush(left_pressed ? colors.highlight : colors.button); + p.setBrush(left_trigger.pressed.value ? colors.highlight : colors.button); DrawPolygon(p, qleft_trigger); // Right trigger - p.setBrush(right_pressed ? colors.highlight : colors.button); + p.setBrush(right_trigger.pressed.value ? colors.highlight : colors.button); DrawPolygon(p, qright_trigger); // Draw L text p.setPen(colors.transparent); p.setBrush(colors.font); - DrawSymbol(p, center + QPointF(-132, -119 + (left_pressed ? 10 : 0)), Symbol::L, 1.7f); + DrawSymbol(p, center + QPointF(-132, -119 + (left_trigger.analog.value * 10.0f)), Symbol::L, + 1.7f); // Draw R text p.setPen(colors.transparent); p.setBrush(colors.font); - DrawSymbol(p, center + QPointF(121.5f, -119 + (right_pressed ? 10 : 0)), Symbol::R, 1.7f); + DrawSymbol(p, center + QPointF(121.5f, -119 + (right_trigger.analog.value * 10.0f)), Symbol::R, + 1.7f); } void PlayerControlPreview::DrawHandheldTriggers(QPainter& p, const QPointF center, - bool left_pressed, bool right_pressed) { + const Common::Input::ButtonStatus& left_pressed, + const Common::Input::ButtonStatus& right_pressed) { std::array qleft_trigger; std::array qright_trigger; @@ -2018,23 +2041,24 @@ void PlayerControlPreview::DrawHandheldTriggers(QPainter& p, const QPointF cente const float left_trigger_y = left_joycon_trigger[point * 2 + 1]; qleft_trigger[point] = - center + QPointF(left_trigger_x, left_trigger_y + (left_pressed ? 0.5f : 0)); + center + QPointF(left_trigger_x, left_trigger_y + (left_pressed.value ? 0.5f : 0)); qright_trigger[point] = - center + QPointF(-left_trigger_x, left_trigger_y + (right_pressed ? 0.5f : 0)); + center + QPointF(-left_trigger_x, left_trigger_y + (right_pressed.value ? 0.5f : 0)); } // Left trigger p.setPen(colors.outline); - p.setBrush(left_pressed ? colors.highlight : colors.button); + p.setBrush(left_pressed.value ? colors.highlight : colors.button); DrawPolygon(p, qleft_trigger); // Right trigger - p.setBrush(right_pressed ? colors.highlight : colors.button); + p.setBrush(right_pressed.value ? colors.highlight : colors.button); DrawPolygon(p, qright_trigger); } -void PlayerControlPreview::DrawDualTriggers(QPainter& p, const QPointF center, bool left_pressed, - bool right_pressed) { +void PlayerControlPreview::DrawDualTriggers(QPainter& p, const QPointF center, + const Common::Input::ButtonStatus& left_pressed, + const Common::Input::ButtonStatus& right_pressed) { std::array qleft_trigger; std::array qright_trigger; constexpr float size = 1.62f; @@ -2043,25 +2067,27 @@ void PlayerControlPreview::DrawDualTriggers(QPainter& p, const QPointF center, b const float left_trigger_x = left_joycon_trigger[point * 2 + 0]; const float left_trigger_y = left_joycon_trigger[point * 2 + 1]; - qleft_trigger[point] = center + QPointF(left_trigger_x * size + offset, - left_trigger_y * size + (left_pressed ? 0.5f : 0)); + qleft_trigger[point] = + center + QPointF(left_trigger_x * size + offset, + left_trigger_y * size + (left_pressed.value ? 0.5f : 0)); qright_trigger[point] = center + QPointF(-left_trigger_x * size - offset, - left_trigger_y * size + (right_pressed ? 0.5f : 0)); + left_trigger_y * size + (right_pressed.value ? 0.5f : 0)); } // Left trigger p.setPen(colors.outline); - p.setBrush(left_pressed ? colors.highlight : colors.button); + p.setBrush(left_pressed.value ? colors.highlight : colors.button); DrawPolygon(p, qleft_trigger); // Right trigger - p.setBrush(right_pressed ? colors.highlight : colors.button); + p.setBrush(right_pressed.value ? colors.highlight : colors.button); DrawPolygon(p, qright_trigger); } -void PlayerControlPreview::DrawDualTriggersTopView(QPainter& p, const QPointF center, - bool left_pressed, bool right_pressed) { +void PlayerControlPreview::DrawDualTriggersTopView( + QPainter& p, const QPointF center, const Common::Input::ButtonStatus& left_pressed, + const Common::Input::ButtonStatus& right_pressed) { std::array qleft_trigger; std::array qright_trigger; constexpr float size = 0.9f; @@ -2080,9 +2106,9 @@ void PlayerControlPreview::DrawDualTriggersTopView(QPainter& p, const QPointF ce } p.setPen(colors.outline); - p.setBrush(left_pressed ? colors.highlight : colors.button); + p.setBrush(left_pressed.value ? colors.highlight : colors.button); DrawPolygon(p, qleft_trigger); - p.setBrush(right_pressed ? colors.highlight : colors.button); + p.setBrush(right_pressed.value ? colors.highlight : colors.button); DrawPolygon(p, qright_trigger); // Draw L text @@ -2096,8 +2122,9 @@ void PlayerControlPreview::DrawDualTriggersTopView(QPainter& p, const QPointF ce DrawSymbol(p, center + QPointF(177, -84), Symbol::R, 1.0f); } -void PlayerControlPreview::DrawDualZTriggersTopView(QPainter& p, const QPointF center, - bool left_pressed, bool right_pressed) { +void PlayerControlPreview::DrawDualZTriggersTopView( + QPainter& p, const QPointF center, const Common::Input::ButtonStatus& left_pressed, + const Common::Input::ButtonStatus& right_pressed) { std::array qleft_trigger; std::array qright_trigger; constexpr float size = 0.9f; @@ -2114,9 +2141,9 @@ void PlayerControlPreview::DrawDualZTriggersTopView(QPainter& p, const QPointF c } p.setPen(colors.outline); - p.setBrush(left_pressed ? colors.highlight : colors.button); + p.setBrush(left_pressed.value ? colors.highlight : colors.button); DrawPolygon(p, qleft_trigger); - p.setBrush(right_pressed ? colors.highlight : colors.button); + p.setBrush(right_pressed.value ? colors.highlight : colors.button); DrawPolygon(p, qright_trigger); // Draw ZL text @@ -2130,7 +2157,8 @@ void PlayerControlPreview::DrawDualZTriggersTopView(QPainter& p, const QPointF c DrawSymbol(p, center + QPointF(180, -113), Symbol::ZR, 1.0f); } -void PlayerControlPreview::DrawLeftTriggers(QPainter& p, const QPointF center, bool left_pressed) { +void PlayerControlPreview::DrawLeftTriggers(QPainter& p, const QPointF center, + const Common::Input::ButtonStatus& left_pressed) { std::array qleft_trigger; constexpr float size = 1.78f; constexpr float offset = 311.5f; @@ -2138,15 +2166,16 @@ void PlayerControlPreview::DrawLeftTriggers(QPainter& p, const QPointF center, b for (std::size_t point = 0; point < left_joycon_trigger.size() / 2; ++point) { qleft_trigger[point] = center + QPointF(left_joycon_trigger[point * 2] * size + offset, left_joycon_trigger[point * 2 + 1] * size - - (left_pressed ? 0.5f : 1.0f)); + (left_pressed.value ? 0.5f : 1.0f)); } p.setPen(colors.outline); - p.setBrush(left_pressed ? colors.highlight : colors.button); + p.setBrush(left_pressed.value ? colors.highlight : colors.button); DrawPolygon(p, qleft_trigger); } -void PlayerControlPreview::DrawLeftZTriggers(QPainter& p, const QPointF center, bool left_pressed) { +void PlayerControlPreview::DrawLeftZTriggers(QPainter& p, const QPointF center, + const Common::Input::ButtonStatus& left_pressed) { std::array qleft_trigger; constexpr float size = 1.1115f; constexpr float offset2 = 335; @@ -2154,18 +2183,18 @@ void PlayerControlPreview::DrawLeftZTriggers(QPainter& p, const QPointF center, for (std::size_t point = 0; point < left_joycon_sideview_zl.size() / 2; ++point) { qleft_trigger[point] = center + QPointF(left_joycon_sideview_zl[point * 2] * size + offset2, left_joycon_sideview_zl[point * 2 + 1] * size + - (left_pressed ? 1.5f : 1.0f)); + (left_pressed.value ? 1.5f : 1.0f)); } p.setPen(colors.outline); - p.setBrush(left_pressed ? colors.highlight : colors.button); + p.setBrush(left_pressed.value ? colors.highlight : colors.button); DrawPolygon(p, qleft_trigger); - p.drawArc(center.x() + 158, center.y() + (left_pressed ? -203.5f : -204.0f), 77, 77, 225 * 16, - 44 * 16); + p.drawArc(center.x() + 158, center.y() + (left_pressed.value ? -203.5f : -204.0f), 77, 77, + 225 * 16, 44 * 16); } -void PlayerControlPreview::DrawLeftTriggersTopView(QPainter& p, const QPointF center, - bool left_pressed) { +void PlayerControlPreview::DrawLeftTriggersTopView( + QPainter& p, const QPointF center, const Common::Input::ButtonStatus& left_pressed) { std::array qleft_trigger; for (std::size_t point = 0; point < left_joystick_L_topview.size() / 2; ++point) { @@ -2174,7 +2203,7 @@ void PlayerControlPreview::DrawLeftTriggersTopView(QPainter& p, const QPointF ce } p.setPen(colors.outline); - p.setBrush(left_pressed ? colors.highlight : colors.button); + p.setBrush(left_pressed.value ? colors.highlight : colors.button); DrawPolygon(p, qleft_trigger); // Draw L text @@ -2183,8 +2212,8 @@ void PlayerControlPreview::DrawLeftTriggersTopView(QPainter& p, const QPointF ce DrawSymbol(p, center + QPointF(-143, -36), Symbol::L, 1.0f); } -void PlayerControlPreview::DrawLeftZTriggersTopView(QPainter& p, const QPointF center, - bool left_pressed) { +void PlayerControlPreview::DrawLeftZTriggersTopView( + QPainter& p, const QPointF center, const Common::Input::ButtonStatus& left_pressed) { std::array qleft_trigger; for (std::size_t point = 0; point < left_joystick_ZL_topview.size() / 2; ++point) { @@ -2193,7 +2222,7 @@ void PlayerControlPreview::DrawLeftZTriggersTopView(QPainter& p, const QPointF c } p.setPen(colors.outline); - p.setBrush(left_pressed ? colors.highlight : colors.button); + p.setBrush(left_pressed.value ? colors.highlight : colors.button); DrawPolygon(p, qleft_trigger); // Draw ZL text @@ -2203,7 +2232,7 @@ void PlayerControlPreview::DrawLeftZTriggersTopView(QPainter& p, const QPointF c } void PlayerControlPreview::DrawRightTriggers(QPainter& p, const QPointF center, - bool right_pressed) { + const Common::Input::ButtonStatus& right_pressed) { std::array qright_trigger; constexpr float size = 1.78f; constexpr float offset = 311.5f; @@ -2211,36 +2240,36 @@ void PlayerControlPreview::DrawRightTriggers(QPainter& p, const QPointF center, for (std::size_t point = 0; point < left_joycon_trigger.size() / 2; ++point) { qright_trigger[point] = center + QPointF(-left_joycon_trigger[point * 2] * size - offset, left_joycon_trigger[point * 2 + 1] * size - - (right_pressed ? 0.5f : 1.0f)); + (right_pressed.value ? 0.5f : 1.0f)); } p.setPen(colors.outline); - p.setBrush(right_pressed ? colors.highlight : colors.button); + p.setBrush(right_pressed.value ? colors.highlight : colors.button); DrawPolygon(p, qright_trigger); } void PlayerControlPreview::DrawRightZTriggers(QPainter& p, const QPointF center, - bool right_pressed) { + const Common::Input::ButtonStatus& right_pressed) { std::array qright_trigger; constexpr float size = 1.1115f; constexpr float offset2 = 335; for (std::size_t point = 0; point < left_joycon_sideview_zl.size() / 2; ++point) { qright_trigger[point] = - center + - QPointF(-left_joycon_sideview_zl[point * 2] * size - offset2, - left_joycon_sideview_zl[point * 2 + 1] * size + (right_pressed ? 0.5f : 0) + 1); + center + QPointF(-left_joycon_sideview_zl[point * 2] * size - offset2, + left_joycon_sideview_zl[point * 2 + 1] * size + + (right_pressed.value ? 0.5f : 0) + 1); } p.setPen(colors.outline); - p.setBrush(right_pressed ? colors.highlight : colors.button); + p.setBrush(right_pressed.value ? colors.highlight : colors.button); DrawPolygon(p, qright_trigger); - p.drawArc(center.x() - 236, center.y() + (right_pressed ? -203.5f : -204.0f), 77, 77, 271 * 16, - 44 * 16); + p.drawArc(center.x() - 236, center.y() + (right_pressed.value ? -203.5f : -204.0f), 77, 77, + 271 * 16, 44 * 16); } -void PlayerControlPreview::DrawRightTriggersTopView(QPainter& p, const QPointF center, - bool right_pressed) { +void PlayerControlPreview::DrawRightTriggersTopView( + QPainter& p, const QPointF center, const Common::Input::ButtonStatus& right_pressed) { std::array qright_trigger; for (std::size_t point = 0; point < left_joystick_L_topview.size() / 2; ++point) { @@ -2249,7 +2278,7 @@ void PlayerControlPreview::DrawRightTriggersTopView(QPainter& p, const QPointF c } p.setPen(colors.outline); - p.setBrush(right_pressed ? colors.highlight : colors.button); + p.setBrush(right_pressed.value ? colors.highlight : colors.button); DrawPolygon(p, qright_trigger); // Draw R text @@ -2258,8 +2287,8 @@ void PlayerControlPreview::DrawRightTriggersTopView(QPainter& p, const QPointF c DrawSymbol(p, center + QPointF(137, -36), Symbol::R, 1.0f); } -void PlayerControlPreview::DrawRightZTriggersTopView(QPainter& p, const QPointF center, - bool right_pressed) { +void PlayerControlPreview::DrawRightZTriggersTopView( + QPainter& p, const QPointF center, const Common::Input::ButtonStatus& right_pressed) { std::array qright_trigger; for (std::size_t point = 0; point < left_joystick_ZL_topview.size() / 2; ++point) { @@ -2268,7 +2297,7 @@ void PlayerControlPreview::DrawRightZTriggersTopView(QPainter& p, const QPointF } p.setPen(colors.outline); - p.setBrush(right_pressed ? colors.highlight : colors.button); + p.setBrush(right_pressed.value ? colors.highlight : colors.button); DrawPolygon(p, qright_trigger); // Draw ZR text @@ -2278,13 +2307,13 @@ void PlayerControlPreview::DrawRightZTriggersTopView(QPainter& p, const QPointF } void PlayerControlPreview::DrawJoystick(QPainter& p, const QPointF center, float size, - bool pressed) { + const Common::Input::ButtonStatus& pressed) { const float radius1 = 13.0f * size; const float radius2 = 9.0f * size; // Outer circle p.setPen(colors.outline); - p.setBrush(pressed ? colors.highlight : colors.button); + p.setBrush(pressed.value ? colors.highlight : colors.button); DrawCircle(p, center, radius1); // Cross @@ -2292,17 +2321,18 @@ void PlayerControlPreview::DrawJoystick(QPainter& p, const QPointF center, float p.drawLine(center - QPoint(0, radius1), center + QPoint(0, radius1)); // Inner circle - p.setBrush(pressed ? colors.highlight2 : colors.button2); + p.setBrush(pressed.value ? colors.highlight2 : colors.button2); DrawCircle(p, center, radius2); } void PlayerControlPreview::DrawJoystickSideview(QPainter& p, const QPointF center, float angle, - float size, bool pressed) { + float size, + const Common::Input::ButtonStatus& pressed) { QVector joystick; joystick.reserve(static_cast(left_joystick_sideview.size() / 2)); for (std::size_t point = 0; point < left_joystick_sideview.size() / 2; ++point) { - joystick.append(QPointF(left_joystick_sideview[point * 2] * size + (pressed ? 1 : 0), + joystick.append(QPointF(left_joystick_sideview[point * 2] * size + (pressed.value ? 1 : 0), left_joystick_sideview[point * 2 + 1] * size - 1)); } @@ -2314,14 +2344,15 @@ void PlayerControlPreview::DrawJoystickSideview(QPainter& p, const QPointF cente // Draw joystick p.setPen(colors.outline); - p.setBrush(pressed ? colors.highlight : colors.button); + p.setBrush(pressed.value ? colors.highlight : colors.button); p.drawPolygon(p2); p.drawLine(p2.at(1), p2.at(30)); p.drawLine(p2.at(32), p2.at(71)); } void PlayerControlPreview::DrawProJoystick(QPainter& p, const QPointF center, const QPointF offset, - float offset_scalar, bool pressed) { + float offset_scalar, + const Common::Input::ButtonStatus& pressed) { const float radius1 = 24.0f; const float radius2 = 17.0f; @@ -2339,11 +2370,11 @@ void PlayerControlPreview::DrawProJoystick(QPainter& p, const QPointF center, co // Outer circle p.setPen(colors.outline); - p.setBrush(pressed ? colors.highlight : colors.button); + p.setBrush(pressed.value ? colors.highlight : colors.button); p.drawEllipse(QPointF(0, 0), radius1 * amplitude, radius1); // Inner circle - p.setBrush(pressed ? colors.highlight2 : colors.button2); + p.setBrush(pressed.value ? colors.highlight2 : colors.button2); const float inner_offset = (radius1 - radius2) * 0.4f * ((offset.x() == 0 && offset.y() < 0) ? -1.0f : 1.0f); @@ -2355,14 +2386,15 @@ void PlayerControlPreview::DrawProJoystick(QPainter& p, const QPointF center, co p.restore(); } -void PlayerControlPreview::DrawGCJoystick(QPainter& p, const QPointF center, bool pressed) { +void PlayerControlPreview::DrawGCJoystick(QPainter& p, const QPointF center, + const Common::Input::ButtonStatus& pressed) { // Outer circle p.setPen(colors.outline); - p.setBrush(pressed ? colors.highlight : colors.button); + p.setBrush(pressed.value ? colors.highlight : colors.button); DrawCircle(p, center, 26.0f); // Inner circle - p.setBrush(pressed ? colors.highlight2 : colors.button2); + p.setBrush(pressed.value ? colors.highlight2 : colors.button2); DrawCircle(p, center, 19.0f); p.setBrush(colors.transparent); DrawCircle(p, center, 13.5f); @@ -2371,31 +2403,29 @@ void PlayerControlPreview::DrawGCJoystick(QPainter& p, const QPointF center, boo void PlayerControlPreview::DrawRawJoystick(QPainter& p, QPointF center_left, QPointF center_right) { using namespace Settings::NativeAnalog; - if (controller_type != Settings::ControllerType::LeftJoycon) { - DrawJoystickProperties(p, center_right, axis_values[RStick].properties); + if (center_right != QPointF(0, 0)) { + DrawJoystickProperties(p, center_right, stick_values[RStick].x.properties); p.setPen(colors.indicator); p.setBrush(colors.indicator); - DrawJoystickDot(p, center_right, axis_values[RStick].raw_value, - axis_values[RStick].properties); + DrawJoystickDot(p, center_right, stick_values[RStick], true); p.setPen(colors.indicator2); p.setBrush(colors.indicator2); - DrawJoystickDot(p, center_right, axis_values[RStick].value, axis_values[RStick].properties); + DrawJoystickDot(p, center_right, stick_values[RStick], false); } - if (controller_type != Settings::ControllerType::RightJoycon) { - DrawJoystickProperties(p, center_left, axis_values[LStick].properties); + if (center_left != QPointF(0, 0)) { + DrawJoystickProperties(p, center_left, stick_values[LStick].x.properties); p.setPen(colors.indicator); p.setBrush(colors.indicator); - DrawJoystickDot(p, center_left, axis_values[LStick].raw_value, - axis_values[LStick].properties); + DrawJoystickDot(p, center_left, stick_values[LStick], true); p.setPen(colors.indicator2); p.setBrush(colors.indicator2); - DrawJoystickDot(p, center_left, axis_values[LStick].value, axis_values[LStick].properties); + DrawJoystickDot(p, center_left, stick_values[LStick], false); } } -void PlayerControlPreview::DrawJoystickProperties(QPainter& p, const QPointF center, - const Input::AnalogProperties& properties) { +void PlayerControlPreview::DrawJoystickProperties( + QPainter& p, const QPointF center, const Common::Input::AnalogProperties& properties) { constexpr float size = 45.0f; const float range = size * properties.range; const float deadzone = size * properties.deadzone; @@ -2414,19 +2444,26 @@ void PlayerControlPreview::DrawJoystickProperties(QPainter& p, const QPointF cen DrawCircle(p, center, deadzone); } -void PlayerControlPreview::DrawJoystickDot(QPainter& p, const QPointF center, const QPointF value, - const Input::AnalogProperties& properties) { +void PlayerControlPreview::DrawJoystickDot(QPainter& p, const QPointF center, + const Common::Input::StickStatus& stick, bool raw) { constexpr float size = 45.0f; - const float range = size * properties.range; + const float range = size * stick.x.properties.range; - // Dot pointer - DrawCircle(p, center + (value * range), 2); + if (raw) { + const QPointF value = QPointF(stick.x.raw_value, stick.y.raw_value) * size; + DrawCircle(p, center + value, 2); + return; + } + + const QPointF value = QPointF(stick.x.value, stick.y.value) * range; + DrawCircle(p, center + value, 2); } -void PlayerControlPreview::DrawRoundButton(QPainter& p, QPointF center, bool pressed, float width, +void PlayerControlPreview::DrawRoundButton(QPainter& p, QPointF center, + const Common::Input::ButtonStatus& pressed, float width, float height, Direction direction, float radius) { p.setBrush(button_color); - if (pressed) { + if (pressed.value) { switch (direction) { case Direction::Left: center.setX(center.x() - 1); @@ -2448,17 +2485,19 @@ void PlayerControlPreview::DrawRoundButton(QPainter& p, QPointF center, bool pre QRectF rect = {center.x() - width, center.y() - height, width * 2.0f, height * 2.0f}; p.drawRoundedRect(rect, radius, radius); } -void PlayerControlPreview::DrawMinusButton(QPainter& p, const QPointF center, bool pressed, +void PlayerControlPreview::DrawMinusButton(QPainter& p, const QPointF center, + const Common::Input::ButtonStatus& pressed, int button_size) { p.setPen(colors.outline); - p.setBrush(pressed ? colors.highlight : colors.button); + p.setBrush(pressed.value ? colors.highlight : colors.button); DrawRectangle(p, center, button_size, button_size / 3.0f); } -void PlayerControlPreview::DrawPlusButton(QPainter& p, const QPointF center, bool pressed, +void PlayerControlPreview::DrawPlusButton(QPainter& p, const QPointF center, + const Common::Input::ButtonStatus& pressed, int button_size) { // Draw outer line p.setPen(colors.outline); - p.setBrush(pressed ? colors.highlight : colors.button); + p.setBrush(pressed.value ? colors.highlight : colors.button); DrawRectangle(p, center, button_size, button_size / 3.0f); DrawRectangle(p, center, button_size / 3.0f, button_size); @@ -2471,7 +2510,8 @@ void PlayerControlPreview::DrawPlusButton(QPainter& p, const QPointF center, boo DrawRectangle(p, center, button_size / 3.0f, button_size); } -void PlayerControlPreview::DrawGCButtonX(QPainter& p, const QPointF center, bool pressed) { +void PlayerControlPreview::DrawGCButtonX(QPainter& p, const QPointF center, + const Common::Input::ButtonStatus& pressed) { std::array button_x; for (std::size_t point = 0; point < gc_button_x.size() / 2; ++point) { @@ -2479,11 +2519,12 @@ void PlayerControlPreview::DrawGCButtonX(QPainter& p, const QPointF center, bool } p.setPen(colors.outline); - p.setBrush(pressed ? colors.highlight : colors.button); + p.setBrush(pressed.value ? colors.highlight : colors.button); DrawPolygon(p, button_x); } -void PlayerControlPreview::DrawGCButtonY(QPainter& p, const QPointF center, bool pressed) { +void PlayerControlPreview::DrawGCButtonY(QPainter& p, const QPointF center, + const Common::Input::ButtonStatus& pressed) { std::array button_x; for (std::size_t point = 0; point < gc_button_y.size() / 2; ++point) { @@ -2491,27 +2532,29 @@ void PlayerControlPreview::DrawGCButtonY(QPainter& p, const QPointF center, bool } p.setPen(colors.outline); - p.setBrush(pressed ? colors.highlight : colors.button); + p.setBrush(pressed.value ? colors.highlight : colors.button); DrawPolygon(p, button_x); } -void PlayerControlPreview::DrawGCButtonZ(QPainter& p, const QPointF center, bool pressed) { +void PlayerControlPreview::DrawGCButtonZ(QPainter& p, const QPointF center, + const Common::Input::ButtonStatus& pressed) { std::array button_x; for (std::size_t point = 0; point < gc_button_z.size() / 2; ++point) { button_x[point] = center + QPointF(gc_button_z[point * 2], - gc_button_z[point * 2 + 1] + (pressed ? 1 : 0)); + gc_button_z[point * 2 + 1] + (pressed.value ? 1 : 0)); } p.setPen(colors.outline); - p.setBrush(pressed ? colors.highlight : colors.button2); + p.setBrush(pressed.value ? colors.highlight : colors.button2); DrawPolygon(p, button_x); } -void PlayerControlPreview::DrawCircleButton(QPainter& p, const QPointF center, bool pressed, +void PlayerControlPreview::DrawCircleButton(QPainter& p, const QPointF center, + const Common::Input::ButtonStatus& pressed, float button_size) { p.setBrush(button_color); - if (pressed) { + if (pressed.value) { p.setBrush(colors.highlight); } p.drawEllipse(center, button_size, button_size); @@ -2540,7 +2583,8 @@ void PlayerControlPreview::DrawArrowButtonOutline(QPainter& p, const QPointF cen } void PlayerControlPreview::DrawArrowButton(QPainter& p, const QPointF center, - const Direction direction, bool pressed, float size) { + const Direction direction, + const Common::Input::ButtonStatus& pressed, float size) { std::array arrow_button; QPoint offset; @@ -2552,38 +2596,39 @@ void PlayerControlPreview::DrawArrowButton(QPainter& p, const QPointF center, case Direction::Up: arrow_button[point] = center + QPointF(up_arrow_x * size, up_arrow_y * size); break; - case Direction::Left: - arrow_button[point] = center + QPointF(up_arrow_y * size, up_arrow_x * size); - break; case Direction::Right: arrow_button[point] = center + QPointF(-up_arrow_y * size, up_arrow_x * size); break; case Direction::Down: arrow_button[point] = center + QPointF(up_arrow_x * size, -up_arrow_y * size); break; + case Direction::Left: + // Compiler doesn't optimize this correctly check why + arrow_button[point] = center + QPointF(up_arrow_y * size, up_arrow_x * size); + break; case Direction::None: break; } } // Draw arrow button - p.setPen(pressed ? colors.highlight : colors.button); - p.setBrush(pressed ? colors.highlight : colors.button); + p.setPen(pressed.value ? colors.highlight : colors.button); + p.setBrush(pressed.value ? colors.highlight : colors.button); DrawPolygon(p, arrow_button); switch (direction) { case Direction::Up: offset = QPoint(0, -20 * size); break; - case Direction::Left: - offset = QPoint(-20 * size, 0); - break; case Direction::Right: offset = QPoint(20 * size, 0); break; case Direction::Down: offset = QPoint(0, 20 * size); break; + case Direction::Left: + offset = QPoint(-20 * size, 0); + break; case Direction::None: offset = QPoint(0, 0); break; @@ -2596,7 +2641,8 @@ void PlayerControlPreview::DrawArrowButton(QPainter& p, const QPointF center, } void PlayerControlPreview::DrawTriggerButton(QPainter& p, const QPointF center, - const Direction direction, bool pressed) { + const Direction direction, + const Common::Input::ButtonStatus& pressed) { std::array qtrigger_button; for (std::size_t point = 0; point < trigger_button.size() / 2; ++point) { @@ -2619,10 +2665,51 @@ void PlayerControlPreview::DrawTriggerButton(QPainter& p, const QPointF center, // Draw arrow button p.setPen(colors.outline); - p.setBrush(pressed ? colors.highlight : colors.button); + p.setBrush(pressed.value ? colors.highlight : colors.button); DrawPolygon(p, qtrigger_button); } +void PlayerControlPreview::DrawBattery(QPainter& p, QPointF center, + Common::Input::BatteryLevel battery) { + if (battery == Common::Input::BatteryLevel::None) { + return; + } + p.setPen(colors.outline); + p.setBrush(colors.transparent); + p.drawRect(center.x(), center.y(), 56, 20); + p.drawRect(center.x() + 56, center.y() + 6, 3, 8); + p.setBrush(colors.deadzone); + switch (battery) { + case Common::Input::BatteryLevel::Charging: + p.setBrush(colors.indicator2); + p.drawText(center + QPoint(2, 14), tr("Charging")); + break; + case Common::Input::BatteryLevel::Full: + p.drawRect(center.x() + 42, center.y(), 14, 20); + p.drawRect(center.x() + 28, center.y(), 14, 20); + p.drawRect(center.x() + 14, center.y(), 14, 20); + p.drawRect(center.x(), center.y(), 14, 20); + break; + case Common::Input::BatteryLevel::Medium: + p.drawRect(center.x() + 28, center.y(), 14, 20); + p.drawRect(center.x() + 14, center.y(), 14, 20); + p.drawRect(center.x(), center.y(), 14, 20); + break; + case Common::Input::BatteryLevel::Low: + p.drawRect(center.x() + 14, center.y(), 14, 20); + p.drawRect(center.x(), center.y(), 14, 20); + break; + case Common::Input::BatteryLevel::Critical: + p.drawRect(center.x(), center.y(), 14, 20); + break; + case Common::Input::BatteryLevel::Empty: + p.drawRect(center.x(), center.y(), 5, 20); + break; + default: + break; + } +} + void PlayerControlPreview::DrawSymbol(QPainter& p, const QPointF center, Symbol symbol, float icon_size) { std::array house_icon; diff --git a/src/yuzu/configuration/configure_input_player_widget.h b/src/yuzu/configuration/configure_input_player_widget.h index f4bbfa5281..4cd5c3be05 100644 --- a/src/yuzu/configuration/configure_input_player_widget.h +++ b/src/yuzu/configuration/configure_input_player_widget.h @@ -7,9 +7,11 @@ #include #include #include -#include "common/settings.h" -#include "core/frontend/input.h" -#include "yuzu/debugger/controller.h" + +#include "common/input.h" +#include "common/settings_input.h" +#include "core/hid/emulated_controller.h" +#include "core/hid/hid_types.h" class QLabel; @@ -24,17 +26,26 @@ public: explicit PlayerControlPreview(QWidget* parent); ~PlayerControlPreview() override; - void SetPlayerInput(std::size_t index, const ButtonParam& buttons_param, - const AnalogParam& analogs_param); - void SetPlayerInputRaw(std::size_t index, const Settings::ButtonsRaw& buttons_, - Settings::AnalogsRaw analogs_); - void SetConnectedStatus(bool checked); - void SetControllerType(Settings::ControllerType type); + // Sets the emulated controller to be displayed + void SetController(Core::HID::EmulatedController* controller); + + // Disables events from the emulated controller + void UnloadController(); + + // Starts blinking animation at the button specified void BeginMappingButton(std::size_t button_id); - void BeginMappingAnalog(std::size_t button_id); + + // Starts moving animation at the stick specified + void BeginMappingAnalog(std::size_t stick_id); + + // Stops any ongoing animation void EndMapping(); + + // Handles emulated controller events + void ControllerUpdate(Core::HID::ControllerTriggerType type); + + // Updates input on sheduled interval void UpdateInput(); - void SetCallBack(ControllerCallback callback_); protected: void paintEvent(QPaintEvent* event) override; @@ -63,22 +74,6 @@ private: SR, }; - struct AxisValue { - QPointF value{}; - QPointF raw_value{}; - Input::AnalogProperties properties{}; - int size{}; - QPoint offset{}; - bool active{}; - }; - - struct LedPattern { - bool position1; - bool position2; - bool position3; - bool position4; - }; - struct ColorMapping { QColor outline{}; QColor primary{}; @@ -101,7 +96,6 @@ private: QColor deadzone{}; }; - static LedPattern GetColorPattern(std::size_t index, bool player_on); void UpdateColors(); void ResetInputs(); @@ -122,47 +116,75 @@ private: void DrawGCBody(QPainter& p, QPointF center); // Draw triggers functions - void DrawProTriggers(QPainter& p, QPointF center, bool left_pressed, bool right_pressed); - void DrawGCTriggers(QPainter& p, QPointF center, bool left_pressed, bool right_pressed); - void DrawHandheldTriggers(QPainter& p, QPointF center, bool left_pressed, bool right_pressed); - void DrawDualTriggers(QPainter& p, QPointF center, bool left_pressed, bool right_pressed); - void DrawDualTriggersTopView(QPainter& p, QPointF center, bool left_pressed, - bool right_pressed); - void DrawDualZTriggersTopView(QPainter& p, QPointF center, bool left_pressed, - bool right_pressed); - void DrawLeftTriggers(QPainter& p, QPointF center, bool left_pressed); - void DrawLeftZTriggers(QPainter& p, QPointF center, bool left_pressed); - void DrawLeftTriggersTopView(QPainter& p, QPointF center, bool left_pressed); - void DrawLeftZTriggersTopView(QPainter& p, QPointF center, bool left_pressed); - void DrawRightTriggers(QPainter& p, QPointF center, bool right_pressed); - void DrawRightZTriggers(QPainter& p, QPointF center, bool right_pressed); - void DrawRightTriggersTopView(QPainter& p, QPointF center, bool right_pressed); - void DrawRightZTriggersTopView(QPainter& p, QPointF center, bool right_pressed); + void DrawProTriggers(QPainter& p, QPointF center, + const Common::Input::ButtonStatus& left_pressed, + const Common::Input::ButtonStatus& right_pressed); + void DrawGCTriggers(QPainter& p, QPointF center, Common::Input::TriggerStatus left_trigger, + Common::Input::TriggerStatus right_trigger); + void DrawHandheldTriggers(QPainter& p, QPointF center, + const Common::Input::ButtonStatus& left_pressed, + const Common::Input::ButtonStatus& right_pressed); + void DrawDualTriggers(QPainter& p, QPointF center, + const Common::Input::ButtonStatus& left_pressed, + const Common::Input::ButtonStatus& right_pressed); + void DrawDualTriggersTopView(QPainter& p, QPointF center, + const Common::Input::ButtonStatus& left_pressed, + const Common::Input::ButtonStatus& right_pressed); + void DrawDualZTriggersTopView(QPainter& p, QPointF center, + const Common::Input::ButtonStatus& left_pressed, + const Common::Input::ButtonStatus& right_pressed); + void DrawLeftTriggers(QPainter& p, QPointF center, + const Common::Input::ButtonStatus& left_pressed); + void DrawLeftZTriggers(QPainter& p, QPointF center, + const Common::Input::ButtonStatus& left_pressed); + void DrawLeftTriggersTopView(QPainter& p, QPointF center, + const Common::Input::ButtonStatus& left_pressed); + void DrawLeftZTriggersTopView(QPainter& p, QPointF center, + const Common::Input::ButtonStatus& left_pressed); + void DrawRightTriggers(QPainter& p, QPointF center, + const Common::Input::ButtonStatus& right_pressed); + void DrawRightZTriggers(QPainter& p, QPointF center, + const Common::Input::ButtonStatus& right_pressed); + void DrawRightTriggersTopView(QPainter& p, QPointF center, + const Common::Input::ButtonStatus& right_pressed); + void DrawRightZTriggersTopView(QPainter& p, QPointF center, + const Common::Input::ButtonStatus& right_pressed); // Draw joystick functions - void DrawJoystick(QPainter& p, QPointF center, float size, bool pressed); - void DrawJoystickSideview(QPainter& p, QPointF center, float angle, float size, bool pressed); + void DrawJoystick(QPainter& p, QPointF center, float size, + const Common::Input::ButtonStatus& pressed); + void DrawJoystickSideview(QPainter& p, QPointF center, float angle, float size, + const Common::Input::ButtonStatus& pressed); void DrawRawJoystick(QPainter& p, QPointF center_left, QPointF center_right); void DrawJoystickProperties(QPainter& p, QPointF center, - const Input::AnalogProperties& properties); - void DrawJoystickDot(QPainter& p, QPointF center, QPointF value, - const Input::AnalogProperties& properties); - void DrawProJoystick(QPainter& p, QPointF center, QPointF offset, float scalar, bool pressed); - void DrawGCJoystick(QPainter& p, QPointF center, bool pressed); + const Common::Input::AnalogProperties& properties); + void DrawJoystickDot(QPainter& p, QPointF center, const Common::Input::StickStatus& stick, + bool raw); + void DrawProJoystick(QPainter& p, QPointF center, QPointF offset, float scalar, + const Common::Input::ButtonStatus& pressed); + void DrawGCJoystick(QPainter& p, QPointF center, const Common::Input::ButtonStatus& pressed); // Draw button functions - void DrawCircleButton(QPainter& p, QPointF center, bool pressed, float button_size); - void DrawRoundButton(QPainter& p, QPointF center, bool pressed, float width, float height, - Direction direction = Direction::None, float radius = 2); - void DrawMinusButton(QPainter& p, QPointF center, bool pressed, int button_size); - void DrawPlusButton(QPainter& p, QPointF center, bool pressed, int button_size); - void DrawGCButtonX(QPainter& p, QPointF center, bool pressed); - void DrawGCButtonY(QPainter& p, QPointF center, bool pressed); - void DrawGCButtonZ(QPainter& p, QPointF center, bool pressed); + void DrawCircleButton(QPainter& p, QPointF center, const Common::Input::ButtonStatus& pressed, + float button_size); + void DrawRoundButton(QPainter& p, QPointF center, const Common::Input::ButtonStatus& pressed, + float width, float height, Direction direction = Direction::None, + float radius = 2); + void DrawMinusButton(QPainter& p, QPointF center, const Common::Input::ButtonStatus& pressed, + int button_size); + void DrawPlusButton(QPainter& p, QPointF center, const Common::Input::ButtonStatus& pressed, + int button_size); + void DrawGCButtonX(QPainter& p, QPointF center, const Common::Input::ButtonStatus& pressed); + void DrawGCButtonY(QPainter& p, QPointF center, const Common::Input::ButtonStatus& pressed); + void DrawGCButtonZ(QPainter& p, QPointF center, const Common::Input::ButtonStatus& pressed); void DrawArrowButtonOutline(QPainter& p, const QPointF center, float size = 1.0f); - void DrawArrowButton(QPainter& p, QPointF center, Direction direction, bool pressed, - float size = 1.0f); - void DrawTriggerButton(QPainter& p, QPointF center, Direction direction, bool pressed); + void DrawArrowButton(QPainter& p, QPointF center, Direction direction, + const Common::Input::ButtonStatus& pressed, float size = 1.0f); + void DrawTriggerButton(QPainter& p, QPointF center, Direction direction, + const Common::Input::ButtonStatus& pressed); + + // Draw battery functions + void DrawBattery(QPainter& p, QPointF center, Common::Input::BatteryLevel battery); // Draw icon functions void DrawSymbol(QPainter& p, QPointF center, Symbol symbol, float icon_size); @@ -178,24 +200,23 @@ private: void SetTextFont(QPainter& p, float text_size, const QString& font_family = QStringLiteral("sans-serif")); - using ButtonArray = - std::array, Settings::NativeButton::BUTTON_NS_END>; - using StickArray = - std::array, Settings::NativeAnalog::NUM_STICKS_HID>; + bool is_controller_set{}; + bool is_connected{}; + bool needs_redraw{}; + Core::HID::NpadStyleIndex controller_type; - ControllerCallback controller_callback; - bool is_enabled{}; bool mapping_active{}; int blink_counter{}; + int callback_key; QColor button_color{}; ColorMapping colors{}; - std::array led_color{}; - ButtonArray buttons{}; - StickArray sticks{}; + Core::HID::LedPattern led_pattern{0, 0, 0, 0}; std::size_t player_index{}; - std::size_t button_mapping_index{Settings::NativeButton::BUTTON_NS_END}; - std::size_t analog_mapping_index{Settings::NativeAnalog::NUM_STICKS_HID}; - std::array axis_values{}; - std::array button_values{}; - Settings::ControllerType controller_type{Settings::ControllerType::ProController}; + Core::HID::EmulatedController* controller; + std::size_t button_mapping_index{Settings::NativeButton::NumButtons}; + std::size_t analog_mapping_index{Settings::NativeAnalog::NumAnalogs}; + Core::HID::ButtonValues button_values{}; + Core::HID::SticksValues stick_values{}; + Core::HID::TriggerValues trigger_values{}; + Core::HID::BatteryValues battery_values{}; }; diff --git a/src/yuzu/configuration/configure_input_profile_dialog.cpp b/src/yuzu/configuration/configure_input_profile_dialog.cpp index cd5a88cea4..17bbe6b61f 100644 --- a/src/yuzu/configuration/configure_input_profile_dialog.cpp +++ b/src/yuzu/configuration/configure_input_profile_dialog.cpp @@ -11,8 +11,8 @@ ConfigureInputProfileDialog::ConfigureInputProfileDialog( QWidget* parent, InputCommon::InputSubsystem* input_subsystem, InputProfiles* profiles, Core::System& system) : QDialog(parent), ui(std::make_unique()), - profile_widget( - new ConfigureInputPlayer(this, 9, nullptr, input_subsystem, profiles, system, false)) { + profile_widget(new ConfigureInputPlayer(this, 9, nullptr, input_subsystem, profiles, + system.HIDCore(), system.IsPoweredOn(), false)) { ui->setupUi(this); ui->controllerLayout->addWidget(profile_widget); diff --git a/src/yuzu/configuration/configure_motion_touch.cpp b/src/yuzu/configuration/configure_motion_touch.cpp index f8e08c4222..8539a5c8b6 100644 --- a/src/yuzu/configuration/configure_motion_touch.cpp +++ b/src/yuzu/configuration/configure_motion_touch.cpp @@ -15,9 +15,9 @@ #include "common/logging/log.h" #include "common/settings.h" +#include "input_common/drivers/udp_client.h" +#include "input_common/helpers/udp_protocol.h" #include "input_common/main.h" -#include "input_common/udp/client.h" -#include "input_common/udp/udp.h" #include "ui_configure_motion_touch.h" #include "yuzu/configuration/configure_motion_touch.h" #include "yuzu/configuration/configure_touch_from_button.h" @@ -93,6 +93,7 @@ ConfigureMotionTouch::ConfigureMotionTouch(QWidget* parent, "using-a-controller-or-android-phone-for-motion-or-touch-input'>Learn More")); + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); SetConfiguration(); UpdateUiDisplay(); ConnectEvents(); @@ -101,17 +102,14 @@ ConfigureMotionTouch::ConfigureMotionTouch(QWidget* parent, ConfigureMotionTouch::~ConfigureMotionTouch() = default; void ConfigureMotionTouch::SetConfiguration() { - const Common::ParamPackage motion_param(Settings::values.motion_device.GetValue()); const Common::ParamPackage touch_param(Settings::values.touch_device.GetValue()); - ui->touch_from_button_checkbox->setChecked(Settings::values.use_touch_from_button.GetValue()); touch_from_button_maps = Settings::values.touch_from_button_maps; for (const auto& touch_map : touch_from_button_maps) { ui->touch_from_button_map->addItem(QString::fromStdString(touch_map.name)); } ui->touch_from_button_map->setCurrentIndex( Settings::values.touch_from_button_map_index.GetValue()); - ui->motion_sensitivity->setValue(motion_param.Get("sensitivity", 0.01f)); min_x = touch_param.Get("min_x", 100); min_y = touch_param.Get("min_y", 50); @@ -139,9 +137,6 @@ void ConfigureMotionTouch::SetConfiguration() { void ConfigureMotionTouch::UpdateUiDisplay() { const QString cemuhook_udp = QStringLiteral("cemuhookudp"); - ui->motion_sensitivity_label->setVisible(true); - ui->motion_sensitivity->setVisible(true); - ui->touch_calibration->setVisible(true); ui->touch_calibration_config->setVisible(true); ui->touch_calibration_label->setVisible(true); @@ -312,7 +307,6 @@ void ConfigureMotionTouch::ApplyConfiguration() { touch_param.Set("max_y", max_y); Settings::values.touch_device = touch_param.Serialize(); - Settings::values.use_touch_from_button = ui->touch_from_button_checkbox->isChecked(); Settings::values.touch_from_button_map_index = ui->touch_from_button_map->currentIndex(); Settings::values.touch_from_button_maps = touch_from_button_maps; Settings::values.udp_input_servers = GetUDPServerString(); diff --git a/src/yuzu/configuration/configure_motion_touch.ui b/src/yuzu/configuration/configure_motion_touch.ui index 1e35ea946e..c75a84ae41 100644 --- a/src/yuzu/configuration/configure_motion_touch.ui +++ b/src/yuzu/configuration/configure_motion_touch.ui @@ -2,14 +2,6 @@ ConfigureMotionTouch - - - 0 - 0 - 500 - 482 - - Configure Motion / Touch @@ -17,48 +9,6 @@ - - - - Mouse Motion - - - - - - - - Sensitivity: - - - - - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - 4 - - - 0.010000000000000 - - - 10.000000000000000 - - - 0.001000000000000 - - - 0.010000000000000 - - - - - - - - @@ -101,19 +51,13 @@ - - - - - 0 - 0 - - - - Use button mapping: - - - + + + + Touch from button profile: + + + diff --git a/src/yuzu/configuration/configure_mouse_advanced.cpp b/src/yuzu/configuration/configure_mouse_advanced.cpp deleted file mode 100644 index 2af3afda84..0000000000 --- a/src/yuzu/configuration/configure_mouse_advanced.cpp +++ /dev/null @@ -1,276 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include - -#include -#include -#include - -#include "common/assert.h" -#include "common/param_package.h" -#include "input_common/main.h" -#include "ui_configure_mouse_advanced.h" -#include "yuzu/configuration/config.h" -#include "yuzu/configuration/configure_mouse_advanced.h" - -static QString GetKeyName(int key_code) { - switch (key_code) { - case Qt::LeftButton: - return QObject::tr("Click 0"); - case Qt::RightButton: - return QObject::tr("Click 1"); - case Qt::MiddleButton: - return QObject::tr("Click 2"); - case Qt::BackButton: - return QObject::tr("Click 3"); - case Qt::ForwardButton: - return QObject::tr("Click 4"); - case Qt::Key_Shift: - return QObject::tr("Shift"); - case Qt::Key_Control: - return QObject::tr("Ctrl"); - case Qt::Key_Alt: - return QObject::tr("Alt"); - case Qt::Key_Meta: - return {}; - default: - return QKeySequence(key_code).toString(); - } -} - -static QString ButtonToText(const Common::ParamPackage& param) { - if (!param.Has("engine")) { - return QObject::tr("[not set]"); - } - - if (param.Get("engine", "") == "keyboard") { - return GetKeyName(param.Get("code", 0)); - } - - if (param.Get("engine", "") == "sdl") { - if (param.Has("hat")) { - const QString hat_str = QString::fromStdString(param.Get("hat", "")); - const QString direction_str = QString::fromStdString(param.Get("direction", "")); - - return QObject::tr("Hat %1 %2").arg(hat_str, direction_str); - } - - if (param.Has("axis")) { - const QString axis_str = QString::fromStdString(param.Get("axis", "")); - const QString direction_str = QString::fromStdString(param.Get("direction", "")); - - return QObject::tr("Axis %1%2").arg(axis_str, direction_str); - } - - if (param.Has("button")) { - const QString button_str = QString::fromStdString(param.Get("button", "")); - - return QObject::tr("Button %1").arg(button_str); - } - return {}; - } - - return QObject::tr("[unknown]"); -} - -ConfigureMouseAdvanced::ConfigureMouseAdvanced(QWidget* parent, - InputCommon::InputSubsystem* input_subsystem_) - : QDialog(parent), - ui(std::make_unique()), input_subsystem{input_subsystem_}, - timeout_timer(std::make_unique()), poll_timer(std::make_unique()) { - ui->setupUi(this); - setFocusPolicy(Qt::ClickFocus); - - button_map = { - ui->left_button, ui->right_button, ui->middle_button, ui->forward_button, ui->back_button, - }; - - for (int button_id = 0; button_id < Settings::NativeMouseButton::NumMouseButtons; button_id++) { - auto* const button = button_map[button_id]; - if (button == nullptr) { - continue; - } - - button->setContextMenuPolicy(Qt::CustomContextMenu); - connect(button, &QPushButton::clicked, [=, this] { - HandleClick( - button_map[button_id], - [=, this](const Common::ParamPackage& params) { - buttons_param[button_id] = params; - }, - InputCommon::Polling::DeviceType::Button); - }); - connect(button, &QPushButton::customContextMenuRequested, - [=, this](const QPoint& menu_location) { - QMenu context_menu; - context_menu.addAction(tr("Clear"), [&] { - buttons_param[button_id].Clear(); - button_map[button_id]->setText(tr("[not set]")); - }); - context_menu.addAction(tr("Restore Default"), [&] { - buttons_param[button_id] = - Common::ParamPackage{InputCommon::GenerateKeyboardParam( - Config::default_mouse_buttons[button_id])}; - button_map[button_id]->setText(ButtonToText(buttons_param[button_id])); - }); - context_menu.exec(button_map[button_id]->mapToGlobal(menu_location)); - }); - } - - connect(ui->buttonClearAll, &QPushButton::clicked, [this] { ClearAll(); }); - connect(ui->buttonRestoreDefaults, &QPushButton::clicked, [this] { RestoreDefaults(); }); - - timeout_timer->setSingleShot(true); - connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); }); - - connect(poll_timer.get(), &QTimer::timeout, [this] { - Common::ParamPackage params; - for (auto& poller : device_pollers) { - params = poller->GetNextInput(); - if (params.Has("engine")) { - SetPollingResult(params, false); - return; - } - } - }); - - LoadConfiguration(); - resize(0, 0); -} - -ConfigureMouseAdvanced::~ConfigureMouseAdvanced() = default; - -void ConfigureMouseAdvanced::ApplyConfiguration() { - std::transform(buttons_param.begin(), buttons_param.end(), - Settings::values.mouse_buttons.begin(), - [](const Common::ParamPackage& param) { return param.Serialize(); }); -} - -void ConfigureMouseAdvanced::LoadConfiguration() { - std::transform(Settings::values.mouse_buttons.begin(), Settings::values.mouse_buttons.end(), - buttons_param.begin(), - [](const std::string& str) { return Common::ParamPackage(str); }); - UpdateButtonLabels(); -} - -void ConfigureMouseAdvanced::changeEvent(QEvent* event) { - if (event->type() == QEvent::LanguageChange) { - RetranslateUI(); - } - - QDialog::changeEvent(event); -} - -void ConfigureMouseAdvanced::RetranslateUI() { - ui->retranslateUi(this); -} - -void ConfigureMouseAdvanced::RestoreDefaults() { - for (int button_id = 0; button_id < Settings::NativeMouseButton::NumMouseButtons; button_id++) { - buttons_param[button_id] = Common::ParamPackage{ - InputCommon::GenerateKeyboardParam(Config::default_mouse_buttons[button_id])}; - } - - UpdateButtonLabels(); -} - -void ConfigureMouseAdvanced::ClearAll() { - for (int i = 0; i < Settings::NativeMouseButton::NumMouseButtons; ++i) { - const auto* const button = button_map[i]; - if (button != nullptr && button->isEnabled()) { - buttons_param[i].Clear(); - } - } - - UpdateButtonLabels(); -} - -void ConfigureMouseAdvanced::UpdateButtonLabels() { - for (int button = 0; button < Settings::NativeMouseButton::NumMouseButtons; button++) { - button_map[button]->setText(ButtonToText(buttons_param[button])); - } -} - -void ConfigureMouseAdvanced::HandleClick( - QPushButton* button, std::function new_input_setter, - InputCommon::Polling::DeviceType type) { - button->setText(tr("[press key]")); - button->setFocus(); - - // Keyboard keys or mouse buttons can only be used as button devices - want_keyboard_mouse = type == InputCommon::Polling::DeviceType::Button; - if (want_keyboard_mouse) { - const auto iter = std::find(button_map.begin(), button_map.end(), button); - ASSERT(iter != button_map.end()); - const auto index = std::distance(button_map.begin(), iter); - ASSERT(index < Settings::NativeButton::NumButtons && index >= 0); - } - - input_setter = new_input_setter; - - device_pollers = input_subsystem->GetPollers(type); - - for (auto& poller : device_pollers) { - poller->Start(); - } - - QWidget::grabMouse(); - QWidget::grabKeyboard(); - - timeout_timer->start(2500); // Cancel after 2.5 seconds - poll_timer->start(50); // Check for new inputs every 50ms -} - -void ConfigureMouseAdvanced::SetPollingResult(const Common::ParamPackage& params, bool abort) { - timeout_timer->stop(); - poll_timer->stop(); - for (auto& poller : device_pollers) { - poller->Stop(); - } - - QWidget::releaseMouse(); - QWidget::releaseKeyboard(); - - if (!abort) { - (*input_setter)(params); - } - - UpdateButtonLabels(); - input_setter = std::nullopt; -} - -void ConfigureMouseAdvanced::mousePressEvent(QMouseEvent* event) { - if (!input_setter || !event) { - return; - } - - if (want_keyboard_mouse) { - SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->button())}, - false); - } else { - // We don't want any mouse buttons, so don't stop polling - return; - } - - SetPollingResult({}, true); -} - -void ConfigureMouseAdvanced::keyPressEvent(QKeyEvent* event) { - if (!input_setter || !event) { - return; - } - - if (event->key() != Qt::Key_Escape) { - if (want_keyboard_mouse) { - SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())}, - false); - } else { - // Escape key wasn't pressed and we don't want any keyboard keys, so don't stop polling - return; - } - } - SetPollingResult({}, true); -} diff --git a/src/yuzu/configuration/configure_mouse_advanced.h b/src/yuzu/configuration/configure_mouse_advanced.h deleted file mode 100644 index 65b6fca9aa..0000000000 --- a/src/yuzu/configuration/configure_mouse_advanced.h +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include - -class QCheckBox; -class QPushButton; -class QTimer; - -namespace InputCommon { -class InputSubsystem; -} - -namespace Ui { -class ConfigureMouseAdvanced; -} - -class ConfigureMouseAdvanced : public QDialog { - Q_OBJECT - -public: - explicit ConfigureMouseAdvanced(QWidget* parent, InputCommon::InputSubsystem* input_subsystem_); - ~ConfigureMouseAdvanced() override; - - void ApplyConfiguration(); - -private: - void changeEvent(QEvent* event) override; - void RetranslateUI(); - - /// Load configuration settings. - void LoadConfiguration(); - /// Restore all buttons to their default values. - void RestoreDefaults(); - /// Clear all input configuration - void ClearAll(); - - /// Update UI to reflect current configuration. - void UpdateButtonLabels(); - - /// Called when the button was pressed. - void HandleClick(QPushButton* button, - std::function new_input_setter, - InputCommon::Polling::DeviceType type); - - /// Finish polling and configure input using the input_setter - void SetPollingResult(const Common::ParamPackage& params, bool abort); - - /// Handle mouse button press events. - void mousePressEvent(QMouseEvent* event) override; - - /// Handle key press events. - void keyPressEvent(QKeyEvent* event) override; - - std::unique_ptr ui; - - InputCommon::InputSubsystem* input_subsystem; - - /// This will be the the setting function when an input is awaiting configuration. - std::optional> input_setter; - - std::array button_map; - std::array buttons_param; - - std::vector> device_pollers; - - std::unique_ptr timeout_timer; - std::unique_ptr poll_timer; - - /// A flag to indicate if keyboard keys are okay when configuring an input. If this is false, - /// keyboard events are ignored. - bool want_keyboard_mouse = false; -}; diff --git a/src/yuzu/configuration/configure_mouse_advanced.ui b/src/yuzu/configuration/configure_mouse_advanced.ui deleted file mode 100644 index 5b99e1c375..0000000000 --- a/src/yuzu/configuration/configure_mouse_advanced.ui +++ /dev/null @@ -1,335 +0,0 @@ - - - ConfigureMouseAdvanced - - - - 0 - 0 - 310 - 193 - - - - Configure Mouse - - - QPushButton { - min-width: 60px; -} - - - - - - Mouse Buttons - - - - - - - - - - Forward: - - - - - - - - - - 68 - 0 - - - - - 68 - 16777215 - - - - - - - - - - - - - - - - - - 54 - 0 - - - - Back: - - - - - - - - - - 68 - 0 - - - - - - - - - - - - - - - - - Left: - - - - - - - - - - 68 - 0 - - - - - - - - - - - - - - - - - Middle: - - - - - - - - - - 68 - 0 - - - - - 68 - 16777215 - - - - - - - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 0 - 20 - - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 0 - 20 - - - - - - - - - - - - Right: - - - - - - - - - - 68 - 0 - - - - - 68 - 16777215 - - - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - - - - 68 - 0 - - - - - 68 - 16777215 - - - - Clear - - - - - - - - 68 - 0 - - - - - 68 - 16777215 - - - - Defaults - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - - - - buttonBox - accepted() - ConfigureMouseAdvanced - accept() - - - buttonBox - rejected() - ConfigureMouseAdvanced - reject() - - - diff --git a/src/yuzu/configuration/configure_tas.cpp b/src/yuzu/configuration/configure_tas.cpp index 8e5a4c72d1..979a8db61e 100644 --- a/src/yuzu/configuration/configure_tas.cpp +++ b/src/yuzu/configuration/configure_tas.cpp @@ -32,7 +32,6 @@ void ConfigureTasDialog::LoadConfiguration() { ui->tas_path_edit->setText( QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::TASDir))); ui->tas_enable->setChecked(Settings::values.tas_enable.GetValue()); - ui->tas_control_swap->setChecked(Settings::values.tas_swap_controllers.GetValue()); ui->tas_loop_script->setChecked(Settings::values.tas_loop.GetValue()); ui->tas_pause_on_load->setChecked(Settings::values.pause_tas_on_load.GetValue()); } @@ -40,7 +39,6 @@ void ConfigureTasDialog::LoadConfiguration() { void ConfigureTasDialog::ApplyConfiguration() { Common::FS::SetYuzuPath(Common::FS::YuzuPath::TASDir, ui->tas_path_edit->text().toStdString()); Settings::values.tas_enable.SetValue(ui->tas_enable->isChecked()); - Settings::values.tas_swap_controllers.SetValue(ui->tas_control_swap->isChecked()); Settings::values.tas_loop.SetValue(ui->tas_loop_script->isChecked()); Settings::values.pause_tas_on_load.SetValue(ui->tas_pause_on_load->isChecked()); } diff --git a/src/yuzu/configuration/configure_tas.ui b/src/yuzu/configuration/configure_tas.ui index 7d44895c40..cf88a5bf08 100644 --- a/src/yuzu/configuration/configure_tas.ui +++ b/src/yuzu/configuration/configure_tas.ui @@ -59,20 +59,13 @@ - - - Automatic controller profile swapping - - - - Loop script - + false diff --git a/src/yuzu/configuration/configure_touch_from_button.cpp b/src/yuzu/configuration/configure_touch_from_button.cpp index 40129f2289..bde0a08c4b 100644 --- a/src/yuzu/configuration/configure_touch_from_button.cpp +++ b/src/yuzu/configuration/configure_touch_from_button.cpp @@ -163,13 +163,10 @@ void ConfigureTouchFromButton::ConnectEvents() { connect(timeout_timer.get(), &QTimer::timeout, [this]() { SetPollingResult({}, true); }); connect(poll_timer.get(), &QTimer::timeout, [this]() { - Common::ParamPackage params; - for (auto& poller : device_pollers) { - params = poller->GetNextInput(); - if (params.Has("engine")) { - SetPollingResult(params, false); - return; - } + const auto& params = input_subsystem->GetNextInput(); + if (params.Has("engine")) { + SetPollingResult(params, false); + return; } }); } @@ -248,11 +245,7 @@ void ConfigureTouchFromButton::GetButtonInput(const int row_index, const bool is } }; - device_pollers = input_subsystem->GetPollers(InputCommon::Polling::DeviceType::Button); - - for (auto& poller : device_pollers) { - poller->Start(); - } + input_subsystem->BeginMapping(InputCommon::Polling::InputType::Button); grabKeyboard(); grabMouse(); @@ -365,14 +358,14 @@ void ConfigureTouchFromButton::SetCoordinates(const int dot_id, const QPoint& po void ConfigureTouchFromButton::SetPollingResult(const Common::ParamPackage& params, const bool cancel) { + timeout_timer->stop(); + poll_timer->stop(); + input_subsystem->StopMapping(); + releaseKeyboard(); releaseMouse(); qApp->restoreOverrideCursor(); - timeout_timer->stop(); - poll_timer->stop(); - for (auto& poller : device_pollers) { - poller->Stop(); - } + if (input_setter) { (*input_setter)(params, cancel); input_setter.reset(); diff --git a/src/yuzu/configuration/configure_touch_from_button.h b/src/yuzu/configuration/configure_touch_from_button.h index d9513e3bc6..e1400481a2 100644 --- a/src/yuzu/configuration/configure_touch_from_button.h +++ b/src/yuzu/configuration/configure_touch_from_button.h @@ -24,10 +24,6 @@ namespace InputCommon { class InputSubsystem; } -namespace InputCommon::Polling { -class DevicePoller; -} - namespace Settings { struct TouchFromButtonMap; } @@ -85,7 +81,6 @@ private: std::unique_ptr timeout_timer; std::unique_ptr poll_timer; - std::vector> device_pollers; std::optional> input_setter; static constexpr int DataRoleDot = Qt::ItemDataRole::UserRole + 2; diff --git a/src/yuzu/configuration/configure_vibration.cpp b/src/yuzu/configuration/configure_vibration.cpp index 46a0f30258..adce04b278 100644 --- a/src/yuzu/configuration/configure_vibration.cpp +++ b/src/yuzu/configuration/configure_vibration.cpp @@ -59,80 +59,6 @@ void ConfigureVibration::ApplyConfiguration() { ui->checkBoxAccurateVibration->isChecked()); } -void ConfigureVibration::SetVibrationDevices(std::size_t player_index) { - using namespace Settings::NativeButton; - static constexpr std::array, 2> buttons{{ - {DLeft, DUp, DRight, DDown, L, ZL}, // Left Buttons - {A, B, X, Y, R, ZR}, // Right Buttons - }}; - - auto& player = Settings::values.players.GetValue()[player_index]; - - for (std::size_t device_idx = 0; device_idx < buttons.size(); ++device_idx) { - std::unordered_map params_count; - - for (const auto button_index : buttons[device_idx]) { - const auto& player_button = player.buttons[button_index]; - - if (params_count.find(player_button) != params_count.end()) { - ++params_count[player_button]; - continue; - } - - params_count.insert_or_assign(player_button, 1); - } - - const auto it = std::max_element( - params_count.begin(), params_count.end(), - [](const auto& lhs, const auto& rhs) { return lhs.second < rhs.second; }); - - auto& vibration_param_str = player.vibrations[device_idx]; - vibration_param_str.clear(); - - if (it->first.empty()) { - continue; - } - - const auto param = Common::ParamPackage(it->first); - - const auto engine = param.Get("engine", ""); - const auto guid = param.Get("guid", ""); - const auto port = param.Get("port", ""); - - if (engine.empty() || engine == "keyboard" || engine == "mouse" || engine == "tas") { - continue; - } - - vibration_param_str += fmt::format("engine:{}", engine); - - if (!port.empty()) { - vibration_param_str += fmt::format(",port:{}", port); - } - if (!guid.empty()) { - vibration_param_str += fmt::format(",guid:{}", guid); - } - } - - if (player.vibrations[0] != player.vibrations[1]) { - return; - } - - if (!player.vibrations[0].empty() && - player.controller_type != Settings::ControllerType::RightJoycon) { - player.vibrations[1].clear(); - } else if (!player.vibrations[1].empty() && - player.controller_type == Settings::ControllerType::RightJoycon) { - player.vibrations[0].clear(); - } -} - -void ConfigureVibration::SetAllVibrationDevices() { - // Set vibration devices for all player indices including handheld - for (std::size_t player_idx = 0; player_idx < NUM_PLAYERS + 1; ++player_idx) { - SetVibrationDevices(player_idx); - } -} - void ConfigureVibration::changeEvent(QEvent* event) { if (event->type() == QEvent::LanguageChange) { RetranslateUI(); diff --git a/src/yuzu/configuration/configure_vibration.h b/src/yuzu/configuration/configure_vibration.h index 07411a86f8..37bbc26536 100644 --- a/src/yuzu/configuration/configure_vibration.h +++ b/src/yuzu/configuration/configure_vibration.h @@ -24,9 +24,6 @@ public: void ApplyConfiguration(); - static void SetVibrationDevices(std::size_t player_index); - static void SetAllVibrationDevices(); - private: void changeEvent(QEvent* event) override; void RetranslateUI(); diff --git a/src/yuzu/debugger/controller.cpp b/src/yuzu/debugger/controller.cpp index 5a844409b1..6b834c42e9 100644 --- a/src/yuzu/debugger/controller.cpp +++ b/src/yuzu/debugger/controller.cpp @@ -6,13 +6,17 @@ #include #include #include "common/settings.h" +#include "core/hid/emulated_controller.h" +#include "core/hid/hid_core.h" +#include "input_common/drivers/tas_input.h" #include "input_common/main.h" -#include "input_common/tas/tas_input.h" #include "yuzu/configuration/configure_input_player_widget.h" #include "yuzu/debugger/controller.h" -ControllerDialog::ControllerDialog(QWidget* parent, InputCommon::InputSubsystem* input_subsystem_) - : QWidget(parent, Qt::Dialog), input_subsystem{input_subsystem_} { +ControllerDialog::ControllerDialog(Core::HID::HIDCore& hid_core_, + std::shared_ptr input_subsystem_, + QWidget* parent) + : QWidget(parent, Qt::Dialog), hid_core{hid_core_}, input_subsystem{input_subsystem_} { setObjectName(QStringLiteral("Controller")); setWindowTitle(tr("Controller P1")); resize(500, 350); @@ -31,20 +35,24 @@ ControllerDialog::ControllerDialog(QWidget* parent, InputCommon::InputSubsystem* // Configure focus so that widget is focusable and the dialog automatically forwards focus to // it. setFocusProxy(widget); - widget->SetConnectedStatus(false); widget->setFocusPolicy(Qt::StrongFocus); widget->setFocus(); } void ControllerDialog::refreshConfiguration() { - const auto& players = Settings::values.players.GetValue(); - constexpr std::size_t player = 0; - widget->SetPlayerInputRaw(player, players[player].buttons, players[player].analogs); - widget->SetControllerType(players[player].controller_type); - ControllerCallback callback{[this](ControllerInput input) { InputController(input); }}; - widget->SetCallBack(callback); - widget->repaint(); - widget->SetConnectedStatus(players[player].connected); + UnloadController(); + auto* player_1 = hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1); + auto* handheld = hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld); + // Display the correct controller + controller = handheld->IsConnected() ? handheld : player_1; + + Core::HID::ControllerUpdateCallback engine_callback{ + .on_change = [this](Core::HID::ControllerTriggerType type) { ControllerUpdate(type); }, + .is_npad_service = true, + }; + callback_key = controller->SetCallback(engine_callback); + widget->SetController(controller); + is_controller_set = true; } QAction* ControllerDialog::toggleViewAction() { @@ -58,11 +66,18 @@ QAction* ControllerDialog::toggleViewAction() { return toggle_view_action; } +void ControllerDialog::UnloadController() { + widget->UnloadController(); + if (is_controller_set) { + controller->DeleteCallback(callback_key); + is_controller_set = false; + } +} + void ControllerDialog::showEvent(QShowEvent* ev) { if (toggle_view_action) { toggle_view_action->setChecked(isVisible()); } - refreshConfiguration(); QWidget::showEvent(ev); } @@ -70,16 +85,34 @@ void ControllerDialog::hideEvent(QHideEvent* ev) { if (toggle_view_action) { toggle_view_action->setChecked(isVisible()); } - widget->SetConnectedStatus(false); QWidget::hideEvent(ev); } -void ControllerDialog::InputController(ControllerInput input) { - u32 buttons = 0; - int index = 0; - for (bool btn : input.button_values) { - buttons |= (btn ? 1U : 0U) << index; - index++; +void ControllerDialog::ControllerUpdate(Core::HID::ControllerTriggerType type) { + // TODO(german77): Remove TAS from here + switch (type) { + case Core::HID::ControllerTriggerType::Button: + case Core::HID::ControllerTriggerType::Stick: { + const auto buttons_values = controller->GetButtonsValues(); + const auto stick_values = controller->GetSticksValues(); + u64 buttons = 0; + std::size_t index = 0; + for (const auto& button : buttons_values) { + buttons |= button.value ? 1LLU << index : 0; + index++; + } + const InputCommon::TasInput::TasAnalog left_axis = { + .x = stick_values[Settings::NativeAnalog::LStick].x.value, + .y = stick_values[Settings::NativeAnalog::LStick].y.value, + }; + const InputCommon::TasInput::TasAnalog right_axis = { + .x = stick_values[Settings::NativeAnalog::RStick].x.value, + .y = stick_values[Settings::NativeAnalog::RStick].y.value, + }; + input_subsystem->GetTas()->RecordInput(buttons, left_axis, right_axis); + break; + } + default: + break; } - input_subsystem->GetTas()->RecordInput(buttons, input.axis_values); } diff --git a/src/yuzu/debugger/controller.h b/src/yuzu/debugger/controller.h index 7742db58b6..52cea33265 100644 --- a/src/yuzu/debugger/controller.h +++ b/src/yuzu/debugger/controller.h @@ -4,9 +4,7 @@ #pragma once -#include #include -#include "common/settings.h" class QAction; class QHideEvent; @@ -17,35 +15,43 @@ namespace InputCommon { class InputSubsystem; } -struct ControllerInput { - std::array, Settings::NativeAnalog::NUM_STICKS_HID> axis_values{}; - std::array button_values{}; - bool changed{}; -}; - -struct ControllerCallback { - std::function input; -}; +namespace Core::HID { +class HIDCore; +class EmulatedController; +enum class ControllerTriggerType; +} // namespace Core::HID class ControllerDialog : public QWidget { Q_OBJECT public: - explicit ControllerDialog(QWidget* parent = nullptr, - InputCommon::InputSubsystem* input_subsystem_ = nullptr); + explicit ControllerDialog(Core::HID::HIDCore& hid_core_, + std::shared_ptr input_subsystem_, + QWidget* parent = nullptr); /// Returns a QAction that can be used to toggle visibility of this dialog. QAction* toggleViewAction(); + + /// Reloads the widget to apply any changes in the configuration void refreshConfiguration(); + /// Disables events from the emulated controller + void UnloadController(); + protected: void showEvent(QShowEvent* ev) override; void hideEvent(QHideEvent* ev) override; private: - void InputController(ControllerInput input); + /// Redirects input from the widget to the TAS driver + void ControllerUpdate(Core::HID::ControllerTriggerType type); + + int callback_key; + bool is_controller_set{}; + Core::HID::EmulatedController* controller; + QAction* toggle_view_action = nullptr; - QFileSystemWatcher* watcher = nullptr; PlayerControlPreview* widget; - InputCommon::InputSubsystem* input_subsystem; + Core::HID::HIDCore& hid_core; + std::shared_ptr input_subsystem; }; diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 6a070934d1..9bd0db10ac 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -26,6 +26,8 @@ #include "core/frontend/applets/controller.h" #include "core/frontend/applets/general_frontend.h" #include "core/frontend/applets/software_keyboard.h" +#include "core/hid/emulated_controller.h" +#include "core/hid/hid_core.h" #include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/am/applet_ae.h" #include "core/hle/service/am/applet_oe.h" @@ -106,8 +108,8 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "core/loader/loader.h" #include "core/perf_stats.h" #include "core/telemetry_session.h" +#include "input_common/drivers/tas_input.h" #include "input_common/main.h" -#include "input_common/tas/tas_input.h" #include "ui_main.h" #include "util/overlay_dialog.h" #include "video_core/gpu.h" @@ -227,6 +229,9 @@ GMainWindow::GMainWindow() ConnectMenuEvents(); ConnectWidgetEvents(); + system->HIDCore().ReloadInputDevices(); + controller_dialog->refreshConfiguration(); + const auto branch_name = std::string(Common::g_scm_branch); const auto description = std::string(Common::g_scm_desc); const auto build_id = std::string(Common::g_build_id); @@ -829,15 +834,16 @@ void GMainWindow::InitializeWidgets() { dock_status_button->setFocusPolicy(Qt::NoFocus); connect(dock_status_button, &QPushButton::clicked, [&] { const bool is_docked = Settings::values.use_docked_mode.GetValue(); - auto& controller_type = Settings::values.players.GetValue()[0].controller_type; + auto* player_1 = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); + auto* handheld = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld); - if (!is_docked && controller_type == Settings::ControllerType::Handheld) { + if (!is_docked && handheld->IsConnected()) { QMessageBox::warning(this, tr("Invalid config detected"), tr("Handheld controller can't be used on docked mode. Pro " "controller will be selected.")); - controller_type = Settings::ControllerType::ProController; - ConfigureDialog configure_dialog(this, hotkey_registry, input_subsystem.get(), *system); - configure_dialog.ApplyConfiguration(); + handheld->Disconnect(); + player_1->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController); + player_1->Connect(); controller_dialog->refreshConfiguration(); } @@ -922,7 +928,7 @@ void GMainWindow::InitializeDebugWidgets() { waitTreeWidget->hide(); debug_menu->addAction(waitTreeWidget->toggleViewAction()); - controller_dialog = new ControllerDialog(this, input_subsystem.get()); + controller_dialog = new ControllerDialog(system->HIDCore(), input_subsystem, this); controller_dialog->hide(); debug_menu->addAction(controller_dialog->toggleViewAction()); @@ -1374,8 +1380,6 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t Config per_game_config(*system, config_file_name, Config::ConfigType::PerGameConfig); } - ConfigureVibration::SetAllVibrationDevices(); - // Disable fps limit toggle when booting a new title Settings::values.disable_fps_limit.SetValue(false); @@ -2708,7 +2712,6 @@ void GMainWindow::OnConfigure() { ShowTelemetryCallout(); } - controller_dialog->refreshConfiguration(); InitializeHotkeys(); if (UISettings::values.theme != old_theme) { @@ -2741,6 +2744,7 @@ void GMainWindow::OnConfigure() { } UpdateStatusButtons(); + controller_dialog->refreshConfiguration(); } void GMainWindow::OnConfigureTas() { @@ -2971,11 +2975,11 @@ void GMainWindow::UpdateWindowTitle(std::string_view title_name, std::string_vie QString GMainWindow::GetTasStateDescription() const { auto [tas_status, current_tas_frame, total_tas_frames] = input_subsystem->GetTas()->GetStatus(); switch (tas_status) { - case TasInput::TasState::Running: + case InputCommon::TasInput::TasState::Running: return tr("TAS state: Running %1/%2").arg(current_tas_frame).arg(total_tas_frames); - case TasInput::TasState::Recording: + case InputCommon::TasInput::TasState::Recording: return tr("TAS state: Recording %1").arg(total_tas_frames); - case TasInput::TasState::Stopped: + case InputCommon::TasInput::TasState::Stopped: return tr("TAS state: Idle %1/%2").arg(current_tas_frame).arg(total_tas_frames); default: return tr("TAS State: Invalid"); @@ -2986,9 +2990,10 @@ void GMainWindow::OnTasStateChanged() { bool is_running = false; bool is_recording = false; if (emulation_running) { - const TasInput::TasState tas_status = std::get<0>(input_subsystem->GetTas()->GetStatus()); - is_running = tas_status == TasInput::TasState::Running; - is_recording = tas_status == TasInput::TasState::Recording; + const InputCommon::TasInput::TasState tas_status = + std::get<0>(input_subsystem->GetTas()->GetStatus()); + is_running = tas_status == InputCommon::TasInput::TasState::Running; + is_recording = tas_status == InputCommon::TasInput::TasState::Recording; } ui->action_TAS_Start->setText(is_running ? tr("&Stop Running") : tr("&Start")); @@ -3371,6 +3376,8 @@ void GMainWindow::closeEvent(QCloseEvent* event) { UpdateUISettings(); game_list->SaveInterfaceLayout(); hotkey_registry.SaveHotkeys(); + controller_dialog->UnloadController(); + system->HIDCore().UnloadInputDevices(); // Shutdown session if the emu thread is active... if (emu_thread != nullptr) { diff --git a/src/yuzu/util/overlay_dialog.cpp b/src/yuzu/util/overlay_dialog.cpp index 95b1485450..c66dfbdff0 100644 --- a/src/yuzu/util/overlay_dialog.cpp +++ b/src/yuzu/util/overlay_dialog.cpp @@ -6,7 +6,8 @@ #include #include "core/core.h" -#include "core/frontend/input_interpreter.h" +#include "core/hid/hid_types.h" +#include "core/hid/input_interpreter.h" #include "ui_overlay_dialog.h" #include "yuzu/util/overlay_dialog.h" @@ -179,9 +180,9 @@ void OverlayDialog::MoveAndResizeWindow() { QDialog::resize(width, height); } -template +template void OverlayDialog::HandleButtonPressedOnce() { - const auto f = [this](HIDButton button) { + const auto f = [this](Core::HID::NpadButton button) { if (input_interpreter->IsButtonPressedOnce(button)) { TranslateButtonPress(button); } @@ -190,7 +191,7 @@ void OverlayDialog::HandleButtonPressedOnce() { (f(T), ...); } -void OverlayDialog::TranslateButtonPress(HIDButton button) { +void OverlayDialog::TranslateButtonPress(Core::HID::NpadButton button) { QPushButton* left_button = use_rich_text ? ui->button_cancel_rich : ui->button_cancel; QPushButton* right_button = use_rich_text ? ui->button_ok_rich : ui->button_ok_label; @@ -198,20 +199,20 @@ void OverlayDialog::TranslateButtonPress(HIDButton button) { // TODO (Morph): focusPrevious/NextChild() doesn't work well with the rich text dialog, fix it switch (button) { - case HIDButton::A: - case HIDButton::B: + case Core::HID::NpadButton::A: + case Core::HID::NpadButton::B: if (left_button->hasFocus()) { left_button->click(); } else if (right_button->hasFocus()) { right_button->click(); } break; - case HIDButton::DLeft: - case HIDButton::LStickLeft: + case Core::HID::NpadButton::Left: + case Core::HID::NpadButton::StickLLeft: focusPreviousChild(); break; - case HIDButton::DRight: - case HIDButton::LStickRight: + case Core::HID::NpadButton::Right: + case Core::HID::NpadButton::StickLRight: focusNextChild(); break; default: @@ -241,8 +242,10 @@ void OverlayDialog::InputThread() { while (input_thread_running) { input_interpreter->PollInput(); - HandleButtonPressedOnce(); + HandleButtonPressedOnce(); std::this_thread::sleep_for(std::chrono::milliseconds(50)); } diff --git a/src/yuzu/util/overlay_dialog.h b/src/yuzu/util/overlay_dialog.h index e8c388bd01..d8a140ff3f 100644 --- a/src/yuzu/util/overlay_dialog.h +++ b/src/yuzu/util/overlay_dialog.h @@ -13,14 +13,16 @@ #include "common/common_types.h" -enum class HIDButton : u8; - class InputInterpreter; namespace Core { class System; } +namespace Core::HID { +enum class NpadButton : u64; +} + namespace Ui { class OverlayDialog; } @@ -79,7 +81,7 @@ private: * * @tparam HIDButton The list of buttons that can be converted into keyboard input. */ - template + template void HandleButtonPressedOnce(); /** @@ -87,7 +89,7 @@ private: * * @param button The button press to process. */ - void TranslateButtonPress(HIDButton button); + void TranslateButtonPress(Core::HID::NpadButton button); void StartInputThread(); void StopInputThread(); diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index 33241ea986..8e9c7d211c 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -24,7 +24,6 @@ #include "common/settings.h" #include "core/hle/service/acc/profile_manager.h" #include "input_common/main.h" -#include "input_common/udp/client.h" #include "yuzu_cmd/config.h" #include "yuzu_cmd/default_ini.h" @@ -84,163 +83,6 @@ static const std::array, Settings::NativeAnalog::NumAnalogs> }, }}; -static const std::array default_mouse_buttons = { - SDL_SCANCODE_LEFTBRACKET, SDL_SCANCODE_RIGHTBRACKET, SDL_SCANCODE_APOSTROPHE, - SDL_SCANCODE_MINUS, SDL_SCANCODE_EQUALS, -}; - -static const std::array keyboard_keys = { - 0, - 0, - 0, - 0, - SDL_SCANCODE_A, - SDL_SCANCODE_B, - SDL_SCANCODE_C, - SDL_SCANCODE_D, - SDL_SCANCODE_E, - SDL_SCANCODE_F, - SDL_SCANCODE_G, - SDL_SCANCODE_H, - SDL_SCANCODE_I, - SDL_SCANCODE_J, - SDL_SCANCODE_K, - SDL_SCANCODE_L, - SDL_SCANCODE_M, - SDL_SCANCODE_N, - SDL_SCANCODE_O, - SDL_SCANCODE_P, - SDL_SCANCODE_Q, - SDL_SCANCODE_R, - SDL_SCANCODE_S, - SDL_SCANCODE_T, - SDL_SCANCODE_U, - SDL_SCANCODE_V, - SDL_SCANCODE_W, - SDL_SCANCODE_X, - SDL_SCANCODE_Y, - SDL_SCANCODE_Z, - SDL_SCANCODE_1, - SDL_SCANCODE_2, - SDL_SCANCODE_3, - SDL_SCANCODE_4, - SDL_SCANCODE_5, - SDL_SCANCODE_6, - SDL_SCANCODE_7, - SDL_SCANCODE_8, - SDL_SCANCODE_9, - SDL_SCANCODE_0, - SDL_SCANCODE_RETURN, - SDL_SCANCODE_ESCAPE, - SDL_SCANCODE_BACKSPACE, - SDL_SCANCODE_TAB, - SDL_SCANCODE_SPACE, - SDL_SCANCODE_MINUS, - SDL_SCANCODE_EQUALS, - SDL_SCANCODE_LEFTBRACKET, - SDL_SCANCODE_RIGHTBRACKET, - SDL_SCANCODE_BACKSLASH, - 0, - SDL_SCANCODE_SEMICOLON, - SDL_SCANCODE_APOSTROPHE, - SDL_SCANCODE_GRAVE, - SDL_SCANCODE_COMMA, - SDL_SCANCODE_PERIOD, - SDL_SCANCODE_SLASH, - SDL_SCANCODE_CAPSLOCK, - - SDL_SCANCODE_F1, - SDL_SCANCODE_F2, - SDL_SCANCODE_F3, - SDL_SCANCODE_F4, - SDL_SCANCODE_F5, - SDL_SCANCODE_F6, - SDL_SCANCODE_F7, - SDL_SCANCODE_F8, - SDL_SCANCODE_F9, - SDL_SCANCODE_F10, - SDL_SCANCODE_F11, - SDL_SCANCODE_F12, - - 0, - SDL_SCANCODE_SCROLLLOCK, - SDL_SCANCODE_PAUSE, - SDL_SCANCODE_INSERT, - SDL_SCANCODE_HOME, - SDL_SCANCODE_PAGEUP, - SDL_SCANCODE_DELETE, - SDL_SCANCODE_END, - SDL_SCANCODE_PAGEDOWN, - SDL_SCANCODE_RIGHT, - SDL_SCANCODE_LEFT, - SDL_SCANCODE_DOWN, - SDL_SCANCODE_UP, - - SDL_SCANCODE_NUMLOCKCLEAR, - SDL_SCANCODE_KP_DIVIDE, - SDL_SCANCODE_KP_MULTIPLY, - SDL_SCANCODE_KP_MINUS, - SDL_SCANCODE_KP_PLUS, - SDL_SCANCODE_KP_ENTER, - SDL_SCANCODE_KP_1, - SDL_SCANCODE_KP_2, - SDL_SCANCODE_KP_3, - SDL_SCANCODE_KP_4, - SDL_SCANCODE_KP_5, - SDL_SCANCODE_KP_6, - SDL_SCANCODE_KP_7, - SDL_SCANCODE_KP_8, - SDL_SCANCODE_KP_9, - SDL_SCANCODE_KP_0, - SDL_SCANCODE_KP_PERIOD, - - 0, - 0, - SDL_SCANCODE_POWER, - SDL_SCANCODE_KP_EQUALS, - - SDL_SCANCODE_F13, - SDL_SCANCODE_F14, - SDL_SCANCODE_F15, - SDL_SCANCODE_F16, - SDL_SCANCODE_F17, - SDL_SCANCODE_F18, - SDL_SCANCODE_F19, - SDL_SCANCODE_F20, - SDL_SCANCODE_F21, - SDL_SCANCODE_F22, - SDL_SCANCODE_F23, - SDL_SCANCODE_F24, - - 0, - SDL_SCANCODE_HELP, - SDL_SCANCODE_MENU, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - SDL_SCANCODE_KP_COMMA, - SDL_SCANCODE_KP_LEFTPAREN, - SDL_SCANCODE_KP_RIGHTPAREN, - 0, - 0, - 0, - 0, -}; - -static const std::array keyboard_mods{ - SDL_SCANCODE_LCTRL, SDL_SCANCODE_LSHIFT, SDL_SCANCODE_LALT, SDL_SCANCODE_LGUI, - SDL_SCANCODE_RCTRL, SDL_SCANCODE_RSHIFT, SDL_SCANCODE_RALT, SDL_SCANCODE_RGUI, -}; - template <> void Config::ReadSetting(const std::string& group, Settings::BasicSetting& setting) { setting = sdl2_config->Get(group, setting.GetLabel(), setting.GetDefault()); @@ -284,16 +126,6 @@ void Config::ReadValues() { } ReadSetting("ControlsGeneral", Settings::values.mouse_enabled); - for (int i = 0; i < Settings::NativeMouseButton::NumMouseButtons; ++i) { - std::string default_param = InputCommon::GenerateKeyboardParam(default_mouse_buttons[i]); - Settings::values.mouse_buttons[i] = sdl2_config->Get( - "ControlsGeneral", std::string("mouse_") + Settings::NativeMouseButton::mapping[i], - default_param); - if (Settings::values.mouse_buttons[i].empty()) - Settings::values.mouse_buttons[i] = default_param; - } - - ReadSetting("ControlsGeneral", Settings::values.motion_device); ReadSetting("ControlsGeneral", Settings::values.touch_device); @@ -363,21 +195,11 @@ void Config::ReadValues() { Settings::TouchFromButtonMap{"default", {}}); num_touch_from_button_maps = 1; } - ReadSetting("ControlsGeneral", Settings::values.use_touch_from_button); Settings::values.touch_from_button_map_index = std::clamp( Settings::values.touch_from_button_map_index.GetValue(), 0, num_touch_from_button_maps - 1); ReadSetting("ControlsGeneral", Settings::values.udp_input_servers); - std::transform(keyboard_keys.begin(), keyboard_keys.end(), - Settings::values.keyboard_keys.begin(), InputCommon::GenerateKeyboardParam); - std::transform(keyboard_mods.begin(), keyboard_mods.end(), - Settings::values.keyboard_keys.begin() + - Settings::NativeKeyboard::LeftControlKey, - InputCommon::GenerateKeyboardParam); - std::transform(keyboard_mods.begin(), keyboard_mods.end(), - Settings::values.keyboard_mods.begin(), InputCommon::GenerateKeyboardParam); - // Data Storage ReadSetting("Data Storage", Settings::values.use_virtual_sd); FS::SetYuzuPath(FS::YuzuPath::NANDDir, diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h index ecdc271a80..6d613bf7a1 100644 --- a/src/yuzu_cmd/default_ini.h +++ b/src/yuzu_cmd/default_ini.h @@ -84,23 +84,10 @@ enable_accurate_vibrations= # 0: Disabled, 1 (default): Enabled motion_enabled = -# for motion input, the following devices are available: -# - "motion_emu" (default) for emulating motion input from mouse input. Required parameters: -# - "update_period": update period in milliseconds (default to 100) -# - "sensitivity": the coefficient converting mouse movement to tilting angle (default to 0.01) -# - "cemuhookudp" reads motion input from a udp server that uses cemuhook's udp protocol -motion_device= - -# for touch input, the following devices are available: -# - "emu_window" (default) for emulating touch input from mouse input to the emulation window. No parameters required -# - "cemuhookudp" reads touch input from a udp server that uses cemuhook's udp protocol -# - "min_x", "min_y", "max_x", "max_y": defines the udp device's touch screen coordinate system +# Defines the udp device's touch screen coordinate system for cemuhookudp devices +# - "min_x", "min_y", "max_x", "max_y" touch_device= -# Whether to enable or disable touch input from button -# 0 (default): Disabled, 1: Enabled -use_touch_from_button= - # for mapping buttons to touch inputs. #touch_from_button_map=1 #touch_from_button_maps_0_name=default diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp index 87fce0c230..57f8078265 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp @@ -9,10 +9,10 @@ #include "common/settings.h" #include "core/core.h" #include "core/perf_stats.h" -#include "input_common/keyboard.h" +#include "input_common/drivers/keyboard.h" +#include "input_common/drivers/mouse.h" +#include "input_common/drivers/touch_screen.h" #include "input_common/main.h" -#include "input_common/mouse/mouse_input.h" -#include "input_common/sdl/sdl.h" #include "yuzu_cmd/emu_window/emu_window_sdl2.h" #include "yuzu_cmd/yuzu_icon.h" @@ -32,42 +32,32 @@ EmuWindow_SDL2::~EmuWindow_SDL2() { } void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) { - TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0), 0); - - input_subsystem->GetMouse()->MouseMove(x, y, 0, 0); + input_subsystem->GetMouse()->MouseMove(x, y, 0, 0, 0, 0); } -MouseInput::MouseButton EmuWindow_SDL2::SDLButtonToMouseButton(u32 button) const { +InputCommon::MouseButton EmuWindow_SDL2::SDLButtonToMouseButton(u32 button) const { switch (button) { case SDL_BUTTON_LEFT: - return MouseInput::MouseButton::Left; + return InputCommon::MouseButton::Left; case SDL_BUTTON_RIGHT: - return MouseInput::MouseButton::Right; + return InputCommon::MouseButton::Right; case SDL_BUTTON_MIDDLE: - return MouseInput::MouseButton::Wheel; + return InputCommon::MouseButton::Wheel; case SDL_BUTTON_X1: - return MouseInput::MouseButton::Backward; + return InputCommon::MouseButton::Backward; case SDL_BUTTON_X2: - return MouseInput::MouseButton::Forward; + return InputCommon::MouseButton::Forward; default: - return MouseInput::MouseButton::Undefined; + return InputCommon::MouseButton::Undefined; } } void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) { const auto mouse_button = SDLButtonToMouseButton(button); - if (button == SDL_BUTTON_LEFT) { - if (state == SDL_PRESSED) { - TouchPressed((unsigned)std::max(x, 0), (unsigned)std::max(y, 0), 0); - } else { - TouchReleased(0); - } + if (state == SDL_PRESSED) { + input_subsystem->GetMouse()->PressButton(x, y, 0, 0, mouse_button); } else { - if (state == SDL_PRESSED) { - input_subsystem->GetMouse()->PressButton(x, y, mouse_button); - } else { - input_subsystem->GetMouse()->ReleaseButton(mouse_button); - } + input_subsystem->GetMouse()->ReleaseButton(mouse_button); } } @@ -82,29 +72,35 @@ std::pair EmuWindow_SDL2::TouchToPixelPos(float touch_x, flo static_cast(std::max(std::round(touch_y), 0.0f))}; } -void EmuWindow_SDL2::OnFingerDown(float x, float y) { - // TODO(NeatNit): keep track of multitouch using the fingerID and a dictionary of some kind - // This isn't critical because the best we can do when we have that is to average them, like the - // 3DS does - +void EmuWindow_SDL2::OnFingerDown(float x, float y, std::size_t id) { + int width, height; + SDL_GetWindowSize(render_window, &width, &height); const auto [px, py] = TouchToPixelPos(x, y); - TouchPressed(px, py, 0); + const float fx = px * 1.0f / width; + const float fy = py * 1.0f / height; + + input_subsystem->GetTouchScreen()->TouchPressed(fx, fy, id); } -void EmuWindow_SDL2::OnFingerMotion(float x, float y) { +void EmuWindow_SDL2::OnFingerMotion(float x, float y, std::size_t id) { + int width, height; + SDL_GetWindowSize(render_window, &width, &height); const auto [px, py] = TouchToPixelPos(x, y); - TouchMoved(px, py, 0); + const float fx = px * 1.0f / width; + const float fy = py * 1.0f / height; + + input_subsystem->GetTouchScreen()->TouchMoved(fx, fy, id); } void EmuWindow_SDL2::OnFingerUp() { - TouchReleased(0); + input_subsystem->GetTouchScreen()->TouchReleased(0); } void EmuWindow_SDL2::OnKeyEvent(int key, u8 state) { if (state == SDL_PRESSED) { - input_subsystem->GetKeyboard()->PressKey(key); + input_subsystem->GetKeyboard()->PressKey(static_cast(key)); } else if (state == SDL_RELEASED) { - input_subsystem->GetKeyboard()->ReleaseKey(key); + input_subsystem->GetKeyboard()->ReleaseKey(static_cast(key)); } } @@ -205,10 +201,12 @@ void EmuWindow_SDL2::WaitEvent() { } break; case SDL_FINGERDOWN: - OnFingerDown(event.tfinger.x, event.tfinger.y); + OnFingerDown(event.tfinger.x, event.tfinger.y, + static_cast(event.tfinger.touchId)); break; case SDL_FINGERMOTION: - OnFingerMotion(event.tfinger.x, event.tfinger.y); + OnFingerMotion(event.tfinger.x, event.tfinger.y, + static_cast(event.tfinger.touchId)); break; case SDL_FINGERUP: OnFingerUp(); diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.h b/src/yuzu_cmd/emu_window/emu_window_sdl2.h index 4810f87755..0af002693e 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2.h +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.h @@ -16,11 +16,8 @@ class System; namespace InputCommon { class InputSubsystem; -} - -namespace MouseInput { enum class MouseButton; -} +} // namespace InputCommon class EmuWindow_SDL2 : public Core::Frontend::EmuWindow { public: @@ -47,7 +44,7 @@ protected: void OnMouseMotion(s32 x, s32 y); /// Converts a SDL mouse button into MouseInput mouse button - MouseInput::MouseButton SDLButtonToMouseButton(u32 button) const; + InputCommon::MouseButton SDLButtonToMouseButton(u32 button) const; /// Called by WaitEvent when a mouse button is pressed or released void OnMouseButton(u32 button, u8 state, s32 x, s32 y); @@ -56,10 +53,10 @@ protected: std::pair TouchToPixelPos(float touch_x, float touch_y) const; /// Called by WaitEvent when a finger starts touching the touchscreen - void OnFingerDown(float x, float y); + void OnFingerDown(float x, float y, std::size_t id); /// Called by WaitEvent when a finger moves while touching the touchscreen - void OnFingerMotion(float x, float y); + void OnFingerMotion(float x, float y, std::size_t id); /// Called by WaitEvent when a finger stops touching the touchscreen void OnFingerUp(); diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp index a075ad08ae..70db865ec3 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp @@ -17,7 +17,6 @@ #include "common/settings.h" #include "common/string_util.h" #include "core/core.h" -#include "input_common/keyboard.h" #include "input_common/main.h" #include "video_core/renderer_base.h" #include "yuzu_cmd/emu_window/emu_window_sdl2_gl.h"