From 854c933716f0f5e1dbf62157c5a76e65213b30b2 Mon Sep 17 00:00:00 2001 From: german77 Date: Mon, 20 Sep 2021 16:41:15 -0500 Subject: [PATCH] input_common: Create input poller and mapping --- src/input_common/CMakeLists.txt | 6 + src/input_common/input_mapping.cpp | 171 ++++++ src/input_common/input_mapping.h | 76 +++ src/input_common/input_poller.cpp | 860 +++++++++++++++++++++++++++++ src/input_common/input_poller.h | 189 +++++++ src/input_common/main.h | 3 + 6 files changed, 1305 insertions(+) create mode 100644 src/input_common/input_mapping.cpp create mode 100644 src/input_common/input_mapping.h create mode 100644 src/input_common/input_poller.cpp create mode 100644 src/input_common/input_poller.h diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index dd13d948f..72f1e0f4a 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt @@ -3,6 +3,12 @@ add_library(input_common STATIC analog_from_button.h keyboard.cpp keyboard.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 diff --git a/src/input_common/input_mapping.cpp b/src/input_common/input_mapping.cpp new file mode 100644 index 000000000..0ffc71028 --- /dev/null +++ b/src/input_common/input_mapping.cpp @@ -0,0 +1,171 @@ +// 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 "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; + } + 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: + 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); +} + +} // namespace InputCommon diff --git a/src/input_common/input_mapping.h b/src/input_common/input_mapping.h new file mode 100644 index 000000000..2622dba70 --- /dev/null +++ b/src/input_common/input_mapping.h @@ -0,0 +1,76 @@ +// 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); + + 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 000000000..46a7dd276 --- /dev/null +++ b/src/input_common/input_poller.cpp @@ -0,0 +1,860 @@ +// 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 Input::InputDevice { +public: + explicit DummyInput() {} + ~DummyInput() {} +}; + +class InputFromButton final : public Input::InputDevice { +public: + explicit InputFromButton(PadIdentifier identifier_, u32 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); + } + + Input::ButtonStatus GetStatus() const { + return { + .value = input_engine->GetButton(identifier, button), + .inverted = inverted, + .toggle = toggle, + }; + } + + void OnChange() { + const Input::CallbackStatus status{ + .type = 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 u32 button; + const bool toggle; + const bool inverted; + int callback_key; + bool last_button_value; + InputEngine* input_engine; +}; + +class InputFromHatButton final : public Input::InputDevice { +public: + explicit InputFromHatButton(PadIdentifier identifier_, u32 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); + } + + Input::ButtonStatus GetStatus() const { + return { + .value = input_engine->GetHatButton(identifier, button, direction), + .inverted = inverted, + .toggle = toggle, + }; + } + + void OnChange() { + const Input::CallbackStatus status{ + .type = 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 u32 button; + const u8 direction; + const bool toggle; + const bool inverted; + int callback_key; + bool last_button_value; + InputEngine* input_engine; +}; + +class InputFromStick final : public Input::InputDevice { +public: + explicit InputFromStick(PadIdentifier identifier_, u32 axis_x_, u32 axis_y_, + Input::AnalogProperties properties_x_, + 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_) { + 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); + } + + Input::StickStatus GetStatus() const { + 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, + }; + return status; + } + + void OnChange() { + const Input::CallbackStatus status{ + .type = 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 u32 axis_x; + const u32 axis_y; + const Input::AnalogProperties properties_x; + const 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; +}; + +class InputFromTouch final : public Input::InputDevice { +public: + explicit InputFromTouch(PadIdentifier identifier_, u32 touch_id_, u32 button_, bool toggle_, + bool inverted_, u32 axis_x_, u32 axis_y_, + Input::AnalogProperties properties_x_, + 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); + } + + Input::TouchStatus GetStatus() const { + 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 Input::CallbackStatus status{ + .type = 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 u32 touch_id; + const u32 button; + const bool toggle; + const bool inverted; + const u32 axis_x; + const u32 axis_y; + const Input::AnalogProperties properties_x; + const 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 Input::InputDevice { +public: + explicit InputFromTrigger(PadIdentifier identifier_, u32 button_, bool toggle_, bool inverted_, + u32 axis_, 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); + } + + Input::TriggerStatus GetStatus() const { + const Input::AnalogStatus analog_status{ + .raw_value = input_engine->GetAxis(identifier, axis), + .properties = properties, + }; + return { + .analog = analog_status, + .pressed = input_engine->GetButton(identifier, button), + }; + } + + void OnChange() { + const Input::CallbackStatus status{ + .type = Input::InputType::Trigger, + .trigger_status = GetStatus(), + }; + + if (status.trigger_status.analog.raw_value != last_axis_value || + status.trigger_status.pressed != last_button_value) { + last_axis_value = status.trigger_status.analog.raw_value; + last_button_value = status.trigger_status.pressed; + TriggerOnChange(status); + } + } + +private: + const PadIdentifier identifier; + const u32 button; + const bool toggle; + const bool inverted; + const u32 axis; + const 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 Input::InputDevice { +public: + explicit InputFromAnalog(PadIdentifier identifier_, u32 axis_, + 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); + } + + Input::AnalogStatus GetStatus() const { + return { + .raw_value = input_engine->GetAxis(identifier, axis), + .properties = properties, + }; + } + + void OnChange() { + const Input::CallbackStatus status{ + .type = 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 u32 axis; + const Input::AnalogProperties properties; + int callback_key; + float last_axis_value; + InputEngine* input_engine; +}; + +class InputFromBattery final : public 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 = Input::BatteryStatus::Charging; + callback_key = input_engine->SetCallback(input_identifier); + } + + ~InputFromBattery() { + input_engine->DeleteCallback(callback_key); + } + + Input::BatteryStatus GetStatus() const { + return static_cast(input_engine->GetBattery(identifier)); + } + + void OnChange() { + const Input::CallbackStatus status{ + .type = 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; + Input::BatteryStatus last_battery_value; + InputEngine* input_engine; +}; + +class InputFromMotion final : public Input::InputDevice { +public: + explicit InputFromMotion(PadIdentifier identifier_, u32 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); + } + + Input::MotionStatus GetStatus() const { + const auto basic_motion = input_engine->GetMotion(identifier, motion_sensor); + Input::MotionStatus status{}; + const 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 Input::CallbackStatus status{ + .type = Input::InputType::Motion, + .motion_status = GetStatus(), + }; + + TriggerOnChange(status); + } + +private: + const PadIdentifier identifier; + const u32 motion_sensor; + int callback_key; + InputEngine* input_engine; +}; + +class InputFromAxisMotion final : public Input::InputDevice { +public: + explicit InputFromAxisMotion(PadIdentifier identifier_, u32 axis_x_, u32 axis_y_, u32 axis_z_, + Input::AnalogProperties properties_x_, + Input::AnalogProperties properties_y_, + 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); + } + + Input::MotionStatus GetStatus() const { + 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, + }; + return status; + } + + void OnChange() { + const Input::CallbackStatus status{ + .type = 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 u32 axis_x; + const u32 axis_y; + const u32 axis_z; + const Input::AnalogProperties properties_x; + const Input::AnalogProperties properties_y; + const 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; +}; + +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 = static_cast(params.Get("button", 0)); + const auto keyboard_key = static_cast(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 = static_cast(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 = static_cast(params.Get("axis_x", 0)); + const 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 = static_cast(params.Get("axis_y", 1)); + const 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 = static_cast(params.Get("axis", 0)); + const 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 = static_cast(params.Get("button", 0)); + const auto toggle = params.Get("toggle", false); + const auto inverted = params.Get("inverted", false); + + const auto axis = static_cast(params.Get("axis", 0)); + const 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 = static_cast(params.Get("button", 0)); + const auto toggle = params.Get("toggle", false); + const auto inverted = params.Get("inverted", false); + + const auto axis_x = static_cast(params.Get("axis_x", 0)); + const 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 = static_cast(params.Get("axis_y", 1)); + const 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 = static_cast(params.Get("axis_x", 0)); + const 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 = static_cast(params.Get("axis_y", 1)); + const 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 = static_cast(params.Get("axis_z", 1)); + const 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("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); + } + if (params.Has("battery")) { + return CreateBatteryDevice(params); + } + LOG_ERROR(Input, "Invalid parameters given"); + return std::make_unique(); +} + +} // namespace InputCommon diff --git a/src/input_common/input_poller.h b/src/input_common/input_poller.h new file mode 100644 index 000000000..3c1e5b541 --- /dev/null +++ b/src/input_common/input_poller.h @@ -0,0 +1,189 @@ +// 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 InputFactory final : public Input::Factory { +public: + explicit InputFactory(std::shared_ptr input_engine_); + + /** + * Creates a 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" + * @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/main.h b/src/input_common/main.h index 6390d3f09..eb247e164 100644 --- a/src/input_common/main.h +++ b/src/input_common/main.h @@ -38,6 +38,9 @@ namespace Polling { enum class DeviceType { Button, AnalogPreferred, Motion }; +/// Type of input desired for mapping purposes +enum class InputType { None, Button, Stick, Motion, Touch }; + /** * A class that can be used to get inputs from an input device like controllers without having to * poll the device's status yourself