summaryrefslogtreecommitdiffstats
path: root/src/input_common
diff options
context:
space:
mode:
authorFernando S <fsahmkow27@gmail.com>2021-11-27 11:52:08 +0100
committerGitHub <noreply@github.com>2021-11-27 11:52:08 +0100
commit564f10527745f870621c08bbb5d16badee0ed861 (patch)
treee8ac8dee60086facf1837393882865f5df18c95e /src/input_common
parentMerge pull request #7431 from liushuyu/fix-linux-decoding (diff)
parentconfig: Remove vibration configuration (diff)
downloadyuzu-564f10527745f870621c08bbb5d16badee0ed861.tar
yuzu-564f10527745f870621c08bbb5d16badee0ed861.tar.gz
yuzu-564f10527745f870621c08bbb5d16badee0ed861.tar.bz2
yuzu-564f10527745f870621c08bbb5d16badee0ed861.tar.lz
yuzu-564f10527745f870621c08bbb5d16badee0ed861.tar.xz
yuzu-564f10527745f870621c08bbb5d16badee0ed861.tar.zst
yuzu-564f10527745f870621c08bbb5d16badee0ed861.zip
Diffstat (limited to '')
-rw-r--r--src/core/hid/motion_input.cpp (renamed from src/input_common/motion_input.cpp)57
-rw-r--r--src/core/hid/motion_input.h (renamed from src/input_common/motion_input.h)27
-rw-r--r--src/input_common/CMakeLists.txt60
-rwxr-xr-xsrc/input_common/analog_from_button.cpp238
-rw-r--r--src/input_common/drivers/gc_adapter.cpp (renamed from src/input_common/gcadapter/gc_adapter.cpp)489
-rw-r--r--src/input_common/drivers/gc_adapter.h135
-rw-r--r--src/input_common/drivers/keyboard.cpp112
-rw-r--r--src/input_common/drivers/keyboard.h56
-rw-r--r--src/input_common/drivers/mouse.cpp185
-rw-r--r--src/input_common/drivers/mouse.h81
-rw-r--r--src/input_common/drivers/sdl_driver.cpp924
-rw-r--r--src/input_common/drivers/sdl_driver.h (renamed from src/input_common/sdl/sdl_impl.h)59
-rw-r--r--src/input_common/drivers/tas_input.cpp315
-rw-r--r--src/input_common/drivers/tas_input.h (renamed from src/input_common/tas/tas_input.h)151
-rw-r--r--src/input_common/drivers/touch_screen.cpp53
-rw-r--r--src/input_common/drivers/touch_screen.h44
-rw-r--r--src/input_common/drivers/udp_client.cpp591
-rw-r--r--src/input_common/drivers/udp_client.h (renamed from src/input_common/udp/client.h)128
-rw-r--r--src/input_common/gcadapter/gc_adapter.h168
-rw-r--r--src/input_common/gcadapter/gc_poller.cpp356
-rw-r--r--src/input_common/gcadapter/gc_poller.h78
-rw-r--r--src/input_common/helpers/stick_from_buttons.cpp304
-rw-r--r--[-rwxr-xr-x]src/input_common/helpers/stick_from_buttons.h (renamed from src/input_common/analog_from_button.h)7
-rw-r--r--src/input_common/helpers/touch_from_buttons.cpp81
-rw-r--r--src/input_common/helpers/touch_from_buttons.h (renamed from src/input_common/touch_from_button.h)7
-rw-r--r--src/input_common/helpers/udp_protocol.cpp (renamed from src/input_common/udp/protocol.cpp)2
-rw-r--r--src/input_common/helpers/udp_protocol.h (renamed from src/input_common/udp/protocol.h)75
-rw-r--r--src/input_common/input_engine.cpp364
-rw-r--r--src/input_common/input_engine.h232
-rw-r--r--src/input_common/input_mapping.cpp207
-rw-r--r--src/input_common/input_mapping.h83
-rw-r--r--src/input_common/input_poller.cpp971
-rw-r--r--src/input_common/input_poller.h217
-rw-r--r--src/input_common/keyboard.cpp121
-rw-r--r--src/input_common/keyboard.h47
-rw-r--r--src/input_common/main.cpp468
-rw-r--r--src/input_common/main.h152
-rw-r--r--src/input_common/motion_from_button.cpp34
-rw-r--r--src/input_common/motion_from_button.h25
-rw-r--r--src/input_common/mouse/mouse_input.cpp223
-rw-r--r--src/input_common/mouse/mouse_input.h116
-rw-r--r--src/input_common/mouse/mouse_poller.cpp299
-rw-r--r--src/input_common/mouse/mouse_poller.h109
-rw-r--r--src/input_common/sdl/sdl.cpp19
-rw-r--r--src/input_common/sdl/sdl.h51
-rw-r--r--src/input_common/sdl/sdl_impl.cpp1658
-rw-r--r--src/input_common/tas/tas_input.cpp455
-rw-r--r--src/input_common/tas/tas_poller.cpp101
-rw-r--r--src/input_common/tas/tas_poller.h43
-rw-r--r--src/input_common/touch_from_button.cpp53
-rw-r--r--src/input_common/udp/client.cpp526
-rw-r--r--src/input_common/udp/udp.cpp110
-rw-r--r--src/input_common/udp/udp.h57
53 files changed, 5783 insertions, 5741 deletions
diff --git a/src/input_common/motion_input.cpp b/src/core/hid/motion_input.cpp
index 1c9d561c0..c25fea966 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 <random>
#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<Common::Vec3f, 3> orientation = GetOrientation();
- const Common::Quaternion<f32> 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<s16> distribution(-1000, 1000);
- const Common::Vec3f gyroscope{
- static_cast<f32>(distribution(gen)) * 0.001f,
- static_cast<f32>(distribution(gen)) * 0.001f,
- static_cast<f32>(distribution(gen)) * 0.001f,
- };
- const Common::Vec3f accelerometer{
- static_cast<f32>(distribution(gen)) * 0.001f,
- static_cast<f32>(distribution(gen)) * 0.001f,
- static_cast<f32>(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<f32> 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
index efe74cf19..5b5b420bb 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<f32>& quaternion);
@@ -38,9 +38,6 @@ public:
[[nodiscard]] Common::Vec3f GetGyroscope() const;
[[nodiscard]] Common::Vec3f GetRotations() const;
[[nodiscard]] Common::Quaternion<f32> 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<f32> 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/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index dd13d948f..d4fa69a77 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 2fafd077f..000000000
--- 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 <atomic>
-#include <chrono>
-#include <cmath>
-#include <thread>
-#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<Input::ButtonDevice>;
-
- 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<bool> 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<std::chrono::steady_clock> now) const {
- constexpr float TAU = Common::PI * 2.0f;
- float new_angle = angle;
-
- auto time_difference = static_cast<float>(
- std::chrono::duration_cast<std::chrono::microseconds>(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<u64>(
- std::chrono::duration_cast<std::chrono::milliseconds>(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<float, float> 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<float>(x) * coef * (y == 0 ? 1.0f : SQRT_HALF),
- static_cast<float>(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<std::chrono::steady_clock> last_update;
-};
-
-std::unique_ptr<Input::AnalogDevice> AnalogFromButton::Create(const Common::ParamPackage& params) {
- const std::string null_engine = Common::ParamPackage{{"engine", "null"}}.Serialize();
- auto up = Input::CreateDevice<Input::ButtonDevice>(params.Get("up", null_engine));
- auto down = Input::CreateDevice<Input::ButtonDevice>(params.Get("down", null_engine));
- auto left = Input::CreateDevice<Input::ButtonDevice>(params.Get("left", null_engine));
- auto right = Input::CreateDevice<Input::ButtonDevice>(params.Get("right", null_engine));
- auto modifier = Input::CreateDevice<Input::ButtonDevice>(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<Analog>(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
index a2f1bb67c..8b6574223 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 <chrono>
-#include <thread>
-
+#include <fmt/format.h>
#include <libusb.h>
#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 InputCommon {
+
+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);
+ }
+ }
-namespace GCAdapter {
+ LibUSBDeviceHandle& operator=(const LibUSBDeviceHandle&) = delete;
+ LibUSBDeviceHandle(const LibUSBDeviceHandle&) = delete;
-Adapter::Adapter() {
- if (usb_adapter_handle != nullptr) {
+ 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<LibUSBContext>();
+ 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<s32>(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<s32>(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<ControllerTypes>(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<u16>(pads[port].buttons | static_cast<u16>(b1_buttons[i]));
- pads[port].last_button = b1_buttons[i];
- }
+ const bool button_status = (b1 & (1U << i)) != 0;
+ const int button = static_cast<int>(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<u16>(pads[port].buttons | static_cast<u16>(b2_buttons[j]));
- pads[port].last_button = b2_buttons[j];
- }
+ const bool button_status = (b2 & (1U << j)) != 0;
+ const int button = static_cast<int>(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<s16>(axis_value - pads[port].axis_origin[index]);
- }
-}
-
-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<PadAxes>(i);
- pad_status.axis_value = value;
- pad_status.axis_threshold = axis_threshold;
- pad_queue.Push(pad_status);
- }
- }
-}
-
-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<u8, 5> payload = {rumble_command, p1, p2, p3, p4};
- const int err = libusb_interrupt_transfer(usb_adapter_handle, output_endpoint, payload.data(),
- static_cast<s32>(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;
+ const f32 axis_status = (axis_value - pads[port].axis_origin[index]) / 100.0f;
+ SetAxis(pads[port].identifier, static_cast<int>(index), axis_status);
}
- 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 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::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<LibUSBDeviceHandle>(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);
- }
-}
-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);
+ 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;
+}
- 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<u8>((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;
+ }
+ 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<u8, 5> payload = {rumble_command, p1, p2, p3, p4};
+ const int err =
+ libusb_interrupt_transfer(usb_adapter_handle->get(), output_endpoint, payload.data(),
+ static_cast<s32>(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;
}
-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;
+bool GCAdapter::DeviceConnected(std::size_t port) const {
+ return pads[port].type != ControllerTypes::None;
}
-void Adapter::Reset() {
- JoinThreads();
- ClearLibusbHandle();
- ResetDevices();
-
- if (libusb_ctx) {
- libusb_exit(libusb_ctx);
- }
+void GCAdapter::Reset() {
+ adapter_scan_thread = {};
+ adapter_input_thread = {};
+ usb_adapter_handle = nullptr;
+ pads = {};
+ libusb_ctx = nullptr;
}
-std::vector<Common::ParamPackage> Adapter::GetInputDevices() const {
+std::vector<Common::ParamPackage> GCAdapter::GetInputDevices() const {
std::vector<Common::ParamPackage> 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<int>(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<int>(gcadapter_button));
mapping.insert_or_assign(switch_button, std::move(button_params));
}
// Add the missing bindings for ZL/ZR
- static constexpr std::array<std::pair<Settings::NativeButton::Values, PadAxes>, 2>
+ static constexpr std::array<std::tuple<Settings::NativeButton::Values, PadButton, PadAxes>, 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<s32>(PadButton::Stick));
+ button_params.Set("button", static_cast<s32>(gcadapter_buton));
button_params.Set("axis", static_cast<s32>(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<int>(PadAxes::StickX));
left_analog_params.Set("axis_y", static_cast<int>(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<int>(PadAxes::SubstickX));
right_analog_params.Set("axis_y", static_cast<int>(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;
-}
-
-void Adapter::BeginConfiguration() {
- pad_queue.Clear();
- configuring = true;
-}
-
-void Adapter::EndConfiguration() {
- pad_queue.Clear();
- configuring = false;
-}
-
-Common::SPSCQueue<GCPadStatus>& Adapter::GetPadQueue() {
- return pad_queue;
-}
-
-const Common::SPSCQueue<GCPadStatus>& Adapter::GetPadQueue() const {
- return pad_queue;
+Common::Input::ButtonNames GCAdapter::GetUIButtonName(const Common::ParamPackage& params) const {
+ PadButton button = static_cast<PadButton>(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;
+ }
}
-GCController& Adapter::GetPadState(std::size_t port) {
- return pads.at(port);
-}
+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;
+ }
-const GCController& Adapter::GetPadState(std::size_t port) const {
- return pads.at(port);
+ return Common::Input::ButtonNames::Invalid;
}
-} // 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 000000000..8dc51d2e5
--- /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 <array>
+#include <memory>
+#include <mutex>
+#include <stop_token>
+#include <string>
+#include <thread>
+
+#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<Common::ParamPackage> 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<u8, 6> axis_origin{};
+ u8 reset_origin_counter{};
+ };
+
+ using AdapterPayload = std::array<u8, 37>;
+
+ 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<LibUSBDeviceHandle> usb_adapter_handle;
+ std::array<GCController, 4> pads;
+
+ std::jthread adapter_input_thread;
+ std::jthread adapter_scan_thread;
+ bool restart_scan_thread{};
+
+ std::unique_ptr<LibUSBContext> 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 000000000..23b0c0ccf
--- /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<Common::ParamPackage> Keyboard::GetInputDevices() const {
+ std::vector<Common::ParamPackage> 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 000000000..ad123b136
--- /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<Common::ParamPackage> 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 000000000..752118e97
--- /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 <stop_token>
+#include <thread>
+#include <fmt/format.h>
+
+#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<float>();
+ 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<int>(x, y) - mouse_origin;
+ const float sensitivity = Settings::values.mouse_panning_sensitivity.GetValue() * 0.0012f;
+ SetAxis(identifier, mouse_axis_x, static_cast<float>(mouse_move.x) * sensitivity);
+ SetAxis(identifier, mouse_axis_y, static_cast<float>(-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<int>(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<int>(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<f32>(wheel_position.x));
+ SetAxis(identifier, wheel_axis_y, static_cast<f32>(wheel_position.y));
+}
+
+void Mouse::ReleaseAllButtons() {
+ ResetButtonState();
+ button_pressed = false;
+}
+
+void Mouse::StopPanning() {
+ last_mouse_change = {};
+}
+
+std::vector<Common::ParamPackage> Mouse::GetInputDevices() const {
+ std::vector<Common::ParamPackage> 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 000000000..4a1fd2fd9
--- /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 <stop_token>
+#include <thread>
+
+#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<Common::ParamPackage> 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<int> mouse_origin;
+ Common::Vec2<int> last_mouse_position;
+ Common::Vec2<float> last_mouse_change;
+ Common::Vec2<int> 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 000000000..90128b6cf
--- /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<SDLDriver*>(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<u16>(vibration.low_amplitude),
+ static_cast<u16>(vibration.high_amplitude), rumble_max_duration_ms) != -1;
+ } else if (sdl_joystick) {
+ return SDL_JoystickRumble(sdl_joystick.get(), static_cast<u16>(vibration.low_amplitude),
+ static_cast<u16>(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<std::size_t>(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, decltype(&SDL_JoystickClose)> sdl_joystick;
+ std::unique_ptr<SDL_GameController, decltype(&SDL_GameControllerClose)> sdl_controller;
+ mutable std::mutex mutex;
+
+ u64 last_motion_update{};
+ bool has_gyro{false};
+ bool has_accel{false};
+ BasicMotion motion;
+};
+
+std::shared_ptr<SDLJoystick> 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<std::size_t>(port)) {
+ auto joystick = std::make_shared<SDLJoystick>(guid, static_cast<int>(it->second.size()),
+ nullptr, nullptr);
+ it->second.emplace_back(std::move(joystick));
+ }
+
+ return it->second[static_cast<std::size_t>(port)];
+ }
+
+ auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr, nullptr);
+
+ return joystick_map[guid].emplace_back(std::move(joystick));
+}
+
+std::shared_ptr<SDLJoystick> 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<SDLJoystick>(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<int>(joystick_guid_list.size());
+ auto joystick = std::make_shared<SDLJoystick>(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<Common::ParamPackage> SDLDriver::GetInputDevices() const {
+ std::vector<Common::ParamPackage> devices;
+ std::unordered_map<int, std::shared_ptr<SDLJoystick>> 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<int>(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<u8>(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<int>(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<SDLJoystick>& 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<SDLJoystick>& 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<SDLJoystick>& joystick,
+ const std::shared_ptr<SDLJoystick>& 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
index 7a9ad6346..d03ff4b84 100644
--- a/src/input_common/sdl/sdl_impl.h
+++ b/src/input_common/drivers/sdl_driver.h
@@ -5,7 +5,6 @@
#pragma once
#include <atomic>
-#include <memory>
#include <mutex>
#include <thread>
#include <unordered_map>
@@ -13,8 +12,7 @@
#include <SDL.h>
#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<std::pair<Settings::NativeButton::Values, SDL_GameControllerAxis>, 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<SDLJoystick> 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<bool> polling = false;
- Common::SPSCQueue<SDL_Event> event_queue;
-
- std::vector<Common::ParamPackage> GetInputDevices() override;
+ std::vector<Common::ParamPackage> 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<std::string, std::vector<std::shared_ptr<SDLJoystick>>> joystick_map;
std::mutex joystick_map_mutex;
- std::shared_ptr<SDLButtonFactory> button_factory;
- std::shared_ptr<SDLAnalogFactory> analog_factory;
- std::shared_ptr<SDLVibrationFactory> vibration_factory;
- std::shared_ptr<SDLMotionFactory> motion_factory;
-
bool start_thread = false;
std::atomic<bool> 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 000000000..0e01fb0d9
--- /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 <cstring>
+#include <regex>
+#include <fmt/format.h>
+
+#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<std::pair<std::string_view, TasButton>, 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<std::string> 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<TasState, size_t, size_t> 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<int>(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<std::string> 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<u64>(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<u64>(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
index 3e2db8f00..c95a130fc 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<float, float>;
-
-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<float, 4> 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<std::pair<float, float>, 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<TasState, size_t, size_t> 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
- */
- u32 ReadCommandButtons(const std::string& line) const;
-
- /**
- * Converts an u32 containing the button status into the text equivalent
- * @return Returns a string with the name of the buttons to be written to the file
+ * 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
*/
- std::string WriteCommandButtons(u32 data) const;
+ u64 ReadCommandButtons(const std::string& line) 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
+ * Reset state of all players
*/
- std::string WriteCommandAxis(TasAnalog data) const;
-
- // Inverts the Y axis polarity
- std::pair<float, float> FlipAxisY(std::pair<float, float> old);
+ void ClearInput();
/**
- * 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
+ * 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 DebugButtons(u32 buttons) 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 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
+ * @param data: value of the axis
+ * @return A string with the value of the axis to be written to the file
*/
- 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<TasData, PLAYER_NUMBER>& 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();
+ std::string WriteCommandAxis(TasAnalog data) const;
size_t script_length{0};
- std::array<TasData, PLAYER_NUMBER> 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<TASCommand> record_commands{};
size_t current_command{0};
TASCommand last_input{}; // only used for recording
-
- // Old settings for swapping controllers
- std::array<Settings::PlayerInput, 10> 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 000000000..45b3086f6
--- /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<int>(finger), true);
+ SetAxis(identifier, static_cast<int>(finger * 2), x);
+ SetAxis(identifier, static_cast<int>(finger * 2 + 1), y);
+}
+
+void TouchScreen::TouchReleased(std::size_t finger) {
+ if (finger >= 16) {
+ return;
+ }
+ SetButton(identifier, static_cast<int>(finger), false);
+ SetAxis(identifier, static_cast<int>(finger * 2), 0.0f);
+ SetAxis(identifier, static_cast<int>(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 000000000..25c11e8bf
--- /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 000000000..fdee0f2d5
--- /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 <random>
+#include <boost/asio.hpp>
+#include <fmt/format.h>
+
+#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<void(Response::Version)> version;
+ std::function<void(Response::PortInfo)> port_info;
+ std::function<void(Response::PadData)> 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<clock> timer;
+ udp::socket socket;
+
+ const u32 client_id;
+
+ static constexpr std::size_t PORT_INFO_SIZE = sizeof(Message<Request::PortInfo>);
+ static constexpr std::size_t PAD_DATA_SIZE = sizeof(Message<Request::PadData>);
+ std::array<u8, PORT_INFO_SIZE> send_buffer1;
+ std::array<u8, PAD_DATA_SIZE> send_buffer2;
+ udp::endpoint send_endpoint;
+
+ std::array<u8, MAX_PACKET_SIZE> 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<u16>(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<u64>(
+ std::chrono::duration_cast<std::chrono::microseconds>(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<int>(id == 0 ? PadAxes::Touch1X : PadAxes::Touch2X);
+ const auto touch_axis_y_id =
+ static_cast<int>(id == 0 ? PadAxes::Touch1Y : PadAxes::Touch2Y);
+ const auto touch_button_id =
+ static_cast<int>(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<u16>(touch_param.Get("min_x", 100));
+ const u16 min_y = static_cast<u16>(touch_param.Get("min_y", 50));
+ const u16 max_x = static_cast<u16>(touch_param.Get("max_x", 1800));
+ const u16 max_y = static_cast<u16>(touch_param.Get("max_y", 850));
+
+ const f32 x =
+ static_cast<f32>(std::clamp(static_cast<u16>(touch_pad.x), min_x, max_x) - min_x) /
+ static_cast<f32>(max_x - min_x);
+ const f32 y =
+ static_cast<f32>(std::clamp(static_cast<u16>(touch_pad.y), min_y, max_y) - min_y) /
+ static_cast<f32>(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<int>(PadAxes::LeftStickX),
+ (data.left_stick_x - 127.0f) / 127.0f);
+ SetAxis(identifier, static_cast<int>(PadAxes::LeftStickY),
+ (data.left_stick_y - 127.0f) / 127.0f);
+ SetAxis(identifier, static_cast<int>(PadAxes::RightStickX),
+ (data.right_stick_x - 127.0f) / 127.0f);
+ SetAxis(identifier, static_cast<int>(PadAxes::RightStickY),
+ (data.right_stick_y - 127.0f) / 127.0f);
+
+ static constexpr std::array<PadButton, 16> 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<int>(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<Socket>(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<std::size_t>(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<Common::ParamPackage> UDPClient::GetInputDevices() const {
+ std::vector<Common::ParamPackage> 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<int>(pad_identifier.port));
+ identifier.Set("pad", static_cast<int>(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<std::pair<Settings::NativeButton::Values, PadButton>, 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<int>(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<int>(PadAxes::LeftStickX));
+ left_analog_params.Set("axis_y", static_cast<int>(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<int>(PadAxes::RightStickX));
+ right_analog_params.Set("axis_y", static_cast<int>(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<PadButton>(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<void()>& success_callback,
+ const std::function<void()>& 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<void(Status)> status_callback,
+ std::function<void(u16, u16, u16, u16)> 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<u16>(touchpad_0.x));
+ const u16 min_y = std::min(MAX_VALUE, static_cast<u16>(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
index 380f9bb76..5d483f26b 100644
--- a/src/input_common/udp/client.h
+++ b/src/input_common/drivers/udp_client.h
@@ -4,20 +4,11 @@
#pragma once
-#include <functional>
-#include <memory>
-#include <mutex>
#include <optional>
-#include <string>
-#include <thread>
-#include <tuple>
+
#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<float, float, bool> touch_status;
// calibration data for scaling the device's touch area to 3ds
struct CalibrationData {
@@ -68,48 +45,85 @@ struct DeviceStatus {
std::optional<CalibrationData> 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();
-
- // Close and release the client
- ~Client();
-
- // Used for polling
- void BeginConfiguration();
- void EndConfiguration();
-
- std::vector<Common::ParamPackage> GetInputDevices() const;
+ explicit UDPClient(const std::string& input_engine_);
+ ~UDPClient();
- bool DeviceConnected(std::size_t pad) const;
void ReloadSockets();
- Common::SPSCQueue<UDPPadStatus>& GetPadQueue();
- const Common::SPSCQueue<UDPPadStatus>& GetPadQueue() const;
+ /// Used for automapping features
+ std::vector<Common::ParamPackage> 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;
- 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;
+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,
+ };
- Input::TouchStatus& GetTouchState();
- const Input::TouchStatus& GetTouchState() const;
+ 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,
+ };
-private:
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<std::chrono::steady_clock> 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<float>& acc, const Common::Vec3<float>& gyro);
-
- // Returns an unused finger id, if there is no fingers available std::nullopt will be
- // returned
- std::optional<std::size_t> 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);
+ const PadIdentifier GetPadIdentifier(std::size_t pad_index) const;
+ const Common::UUID GetHostUUID(const std::string host) const;
- 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<PadData, MAX_UDP_CLIENTS * PADS_PER_CLIENT> pads{};
std::array<ClientConnection, MAX_UDP_CLIENTS> clients{};
- Common::SPSCQueue<UDPPadStatus> pad_queue{};
- Input::TouchStatus touch_status{};
- std::array<std::size_t, MAX_TOUCH_FINGERS> 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 e5de5e94f..000000000
--- 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 <algorithm>
-#include <functional>
-#include <mutex>
-#include <thread>
-#include <unordered_map>
-#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<s16, 6> axis_values{};
- std::array<u8, 6> 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<GCPadStatus>& GetPadQueue();
- const Common::SPSCQueue<GCPadStatus>& 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<Common::ParamPackage> GetInputDevices() const;
- InputCommon::ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) const;
- InputCommon::AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) const;
-
-private:
- using AdapterPayload = std::array<u8, 37>;
-
- 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<GCController, 4> pads;
- Common::SPSCQueue<GCPadStatus> 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 1b6ded8d6..000000000
--- 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 <atomic>
-#include <list>
-#include <mutex>
-#include <utility>
-#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<GCAdapter::Adapter> adapter_)
- : adapter(std::move(adapter_)) {}
-
-GCButton::~GCButton() = default;
-
-std::unique_ptr<Input::ButtonDevice> GCButtonFactory::Create(const Common::ParamPackage& params) {
- const auto button_id = params.Get("button", 0);
- const auto port = static_cast<u32>(params.Get("port", 0));
-
- constexpr s32 PAD_STICK_ID = static_cast<s32>(GCAdapter::PadButton::Stick);
-
- // button is not an axis/stick button
- if (button_id != PAD_STICK_ID) {
- return std::make_unique<GCButton>(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<GCAxisButton>(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<s32>(pad.port));
- if (pad.button != GCAdapter::PadButton::Undefined) {
- params.Set("button", static_cast<u16>(pad.button));
- }
-
- // For Axis button implementation
- if (pad.axis != GCAdapter::PadAxes::Undefined) {
- params.Set("axis", static_cast<u8>(pad.axis));
- params.Set("button", static_cast<u16>(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<float>(gcadapter->GetPadState(port).axis_values.at(axis));
- return (axis_value) / (100.0f * range);
- }
- return 0.0f;
- }
-
- std::pair<float, float> 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<float, float> 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<float, float> 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<GCAdapter::Adapter> 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<Input::AnalogDevice> GCAnalogFactory::Create(const Common::ParamPackage& params) {
- const auto port = static_cast<u32>(params.Get("port", 0));
- const auto axis_x = static_cast<u32>(params.Get("axis_x", 0));
- const auto axis_y = static_cast<u32>(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<GCAnalog>(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<s32>(pad.port));
- params.Set("button", static_cast<u16>(pad.button));
- return params;
- }
- if (pad.axis == GCAdapter::PadAxes::Undefined ||
- std::abs(static_cast<float>(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<u8>(pad.axis);
- if (axis == 0 || axis == 1) {
- analog_x_axis = 0;
- analog_y_axis = 1;
- controller_number = static_cast<s32>(pad.port);
- break;
- }
- if (axis == 2 || axis == 3) {
- analog_x_axis = 2;
- analog_y_axis = 3;
- controller_number = static_cast<s32>(pad.port);
- break;
- }
-
- if (analog_x_axis == -1) {
- analog_x_axis = axis;
- controller_number = static_cast<s32>(pad.port);
- } else if (analog_y_axis == -1 && analog_x_axis != axis &&
- controller_number == static_cast<s32>(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<u8>((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<GCAdapter::Adapter> 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<Input::VibrationDevice> GCVibrationFactory::Create(
- const Common::ParamPackage& params) {
- const auto port = static_cast<u32>(params.Get("port", 0));
-
- return std::make_unique<GCVibration>(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 d1271e3ea..000000000
--- 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 <memory>
-#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<Input::ButtonDevice> {
-public:
- explicit GCButtonFactory(std::shared_ptr<GCAdapter::Adapter> 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<Input::ButtonDevice> 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<GCAdapter::Adapter> adapter;
- bool polling = false;
-};
-
-/// An analog device factory that creates analog devices from GC Adapter
-class GCAnalogFactory final : public Input::Factory<Input::AnalogDevice> {
-public:
- explicit GCAnalogFactory(std::shared_ptr<GCAdapter::Adapter> adapter_);
-
- std::unique_ptr<Input::AnalogDevice> 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<GCAdapter::Adapter> 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<Input::VibrationDevice> {
-public:
- explicit GCVibrationFactory(std::shared_ptr<GCAdapter::Adapter> adapter_);
-
- std::unique_ptr<Input::VibrationDevice> Create(const Common::ParamPackage& params) override;
-
-private:
- std::shared_ptr<GCAdapter::Adapter> 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 000000000..77fcd655e
--- /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 <chrono>
+#include <cmath>
+#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<Common::Input::InputDevice>;
+
+ 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<std::chrono::steady_clock> now) const {
+ constexpr float TAU = Common::PI * 2.0f;
+ float new_angle = angle;
+
+ auto time_difference = static_cast<float>(
+ std::chrono::duration_cast<std::chrono::microseconds>(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<u64>(
+ std::chrono::duration_cast<std::chrono::milliseconds>(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<float>(x) * coef * (y == 0 ? 1.0f : SQRT_HALF);
+ status.y.raw_value = static_cast<float>(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<std::chrono::steady_clock> last_update;
+};
+
+std::unique_ptr<Common::Input::InputDevice> StickFromButton::Create(
+ const Common::ParamPackage& params) {
+ const std::string null_engine = Common::ParamPackage{{"engine", "null"}}.Serialize();
+ auto up = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>(
+ params.Get("up", null_engine));
+ auto down = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>(
+ params.Get("down", null_engine));
+ auto left = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>(
+ params.Get("left", null_engine));
+ auto right = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>(
+ params.Get("right", null_engine));
+ auto modifier = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>(
+ 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<Stick>(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
index bbd583dd9..437ace4f7 100755..100644
--- a/src/input_common/analog_from_button.h
+++ b/src/input_common/helpers/stick_from_buttons.h
@@ -4,8 +4,7 @@
#pragma once
-#include <memory>
-#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<Input::AnalogDevice> {
+class StickFromButton final : public Common::Input::Factory<Common::Input::InputDevice> {
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<Input::AnalogDevice> Create(const Common::ParamPackage& params) override;
+ std::unique_ptr<Common::Input::InputDevice> 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 000000000..35d60bc90
--- /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 <algorithm>
+#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<Common::Input::InputDevice>;
+ 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<Common::Input::InputDevice> TouchFromButton::Create(
+ const Common::ParamPackage& params) {
+ const std::string null_engine = Common::ParamPackage{{"engine", "null"}}.Serialize();
+ auto button = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>(
+ 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<TouchFromButtonDevice>(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
index 8b4d1aa96..628f18215 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 <memory>
-#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<Input::TouchDevice> {
+class TouchFromButton final : public Common::Input::Factory<Common::Input::InputDevice> {
public:
/**
* Creates a touch device from a list of button devices
*/
- std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override;
+ std::unique_ptr<Common::Input::InputDevice> 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
index 5e50bd612..cdeab7e11 100644
--- a/src/input_common/udp/protocol.cpp
+++ b/src/input_common/helpers/udp_protocol.cpp
@@ -5,7 +5,7 @@
#include <cstddef>
#include <cstring>
#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
index 1bdc9209e..bcba12c58 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<PortInfo>,
* 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<T> 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<Version>,
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<TouchPad, 2> touch;
diff --git a/src/input_common/input_engine.cpp b/src/input_common/input_engine.cpp
new file mode 100644
index 000000000..2b2105376
--- /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<PadIdentifier, ControllerData> controller : controller_list) {
+ for (std::pair<int, bool> button : controller.second.buttons) {
+ SetButton(controller.first, button.first, false);
+ }
+ for (std::pair<int, bool> button : controller.second.hat_buttons) {
+ SetHatButton(controller.first, button.first, false);
+ }
+ }
+}
+
+void InputEngine::ResetAnalogState() {
+ for (std::pair<PadIdentifier, ControllerData> controller : controller_list) {
+ for (std::pair<int, float> 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<int, InputIdentifier> 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<int, InputIdentifier> 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<u8>(index))) {
+ continue;
+ }
+ mapping_callback.on_data(MappingData{
+ .engine = GetEngineName(),
+ .pad = identifier,
+ .type = EngineInputType::HatButton,
+ .index = button,
+ .hat_name = GetHatButtonName(static_cast<u8>(index)),
+ });
+ }
+}
+
+void InputEngine::TriggerOnAxisChange(const PadIdentifier& identifier, int axis, f32 value) {
+ std::lock_guard lock{mutex_callback};
+ for (const std::pair<int, InputIdentifier> 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<int, InputIdentifier> 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<int, InputIdentifier> 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 000000000..02272b3f8
--- /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 <functional>
+#include <mutex>
+#include <unordered_map>
+
+#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<PadIdentifier> {
+ 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<u64>(pad_id.port) << 32);
+ hash_value ^= static_cast<u64>(pad_id.pad);
+ return static_cast<size_t>(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<void()> on_change;
+};
+
+// Triggered if data changed on the controller and the engine is on configuring mode
+struct MappingCallback {
+ std::function<void(MappingData)> 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<Common::ParamPackage> 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<int, bool> buttons;
+ std::unordered_map<int, u8> hat_buttons;
+ std::unordered_map<int, float> axes;
+ std::unordered_map<int, BasicMotion> 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<PadIdentifier, ControllerData> controller_list;
+ std::unordered_map<int, InputIdentifier> 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 000000000..6e0024b2d
--- /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<int>(data.pad.port));
+ new_input.Set("pad", static_cast<int>(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<int>(data.pad.port));
+ new_input.Set("pad", static_cast<int>(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<int>(data.pad.port));
+ new_input.Set("pad", static_cast<int>(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 000000000..44eb8ad9a
--- /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<Common::ParamPackage> 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..7e4eafded
--- /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<Common::Input::BatteryLevel>(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<Common::Input::InputDevice> InputFactory::CreateButtonDevice(
+ const Common::ParamPackage& params) {
+ const PadIdentifier identifier = {
+ .guid = Common::UUID{params.Get("guid", "")},
+ .port = static_cast<std::size_t>(params.Get("port", 0)),
+ .pad = static_cast<std::size_t>(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<InputFromButton>(identifier, keyboard_key, toggle, inverted,
+ input_engine.get());
+ }
+ return std::make_unique<InputFromButton>(identifier, button_id, toggle, inverted,
+ input_engine.get());
+}
+
+std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateHatButtonDevice(
+ const Common::ParamPackage& params) {
+ const PadIdentifier identifier = {
+ .guid = Common::UUID{params.Get("guid", "")},
+ .port = static_cast<std::size_t>(params.Get("port", 0)),
+ .pad = static_cast<std::size_t>(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<InputFromHatButton>(identifier, button_id, direction, toggle, inverted,
+ input_engine.get());
+}
+
+std::unique_ptr<Common::Input::InputDevice> 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<std::size_t>(params.Get("port", 0)),
+ .pad = static_cast<std::size_t>(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<InputFromStick>(identifier, axis_x, axis_y, properties_x, properties_y,
+ input_engine.get());
+}
+
+std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateAnalogDevice(
+ const Common::ParamPackage& params) {
+ const PadIdentifier identifier = {
+ .guid = Common::UUID{params.Get("guid", "")},
+ .port = static_cast<std::size_t>(params.Get("port", 0)),
+ .pad = static_cast<std::size_t>(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<InputFromAnalog>(identifier, axis, properties, input_engine.get());
+}
+
+std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateTriggerDevice(
+ const Common::ParamPackage& params) {
+ const PadIdentifier identifier = {
+ .guid = Common::UUID{params.Get("guid", "")},
+ .port = static_cast<std::size_t>(params.Get("port", 0)),
+ .pad = static_cast<std::size_t>(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<InputFromTrigger>(identifier, button, toggle, inverted, axis,
+ properties, input_engine.get());
+}
+
+std::unique_ptr<Common::Input::InputDevice> 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<std::size_t>(params.Get("port", 0)),
+ .pad = static_cast<std::size_t>(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<InputFromTouch>(identifier, touch_id, button, toggle, inverted, axis_x,
+ axis_y, properties_x, properties_y, input_engine.get());
+}
+
+std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateBatteryDevice(
+ const Common::ParamPackage& params) {
+ const PadIdentifier identifier = {
+ .guid = Common::UUID{params.Get("guid", "")},
+ .port = static_cast<std::size_t>(params.Get("port", 0)),
+ .pad = static_cast<std::size_t>(params.Get("pad", 0)),
+ };
+
+ input_engine->PreSetController(identifier);
+ return std::make_unique<InputFromBattery>(identifier, input_engine.get());
+}
+
+std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateMotionDevice(
+ Common::ParamPackage params) {
+ const PadIdentifier identifier = {
+ .guid = Common::UUID{params.Get("guid", "")},
+ .port = static_cast<std::size_t>(params.Get("port", 0)),
+ .pad = static_cast<std::size_t>(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<InputFromMotion>(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<InputFromAxisMotion>(identifier, axis_x, axis_y, axis_z, properties_x,
+ properties_y, properties_z, input_engine.get());
+}
+
+InputFactory::InputFactory(std::shared_ptr<InputEngine> input_engine_)
+ : input_engine(std::move(input_engine_)) {}
+
+std::unique_ptr<Common::Input::InputDevice> 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<DummyInput>();
+}
+
+OutputFactory::OutputFactory(std::shared_ptr<InputEngine> input_engine_)
+ : input_engine(std::move(input_engine_)) {}
+
+std::unique_ptr<Common::Input::OutputDevice> OutputFactory::Create(
+ const Common::ParamPackage& params) {
+ const PadIdentifier identifier = {
+ .guid = Common::UUID{params.Get("guid", "")},
+ .port = static_cast<std::size_t>(params.Get("port", 0)),
+ .pad = static_cast<std::size_t>(params.Get("pad", 0)),
+ };
+
+ input_engine->PreSetController(identifier);
+ return std::make_unique<OutputFromIdentifier>(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 000000000..573f09fde
--- /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 <typename InputDevice>
+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<Common::Input::OutputDevice> {
+public:
+ explicit OutputFactory(std::shared_ptr<InputEngine> 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<Common::Input::OutputDevice> Create(
+ const Common::ParamPackage& params) override;
+
+private:
+ std::shared_ptr<InputEngine> input_engine;
+};
+
+class InputFactory final : public Common::Input::Factory<Common::Input::InputDevice> {
+public:
+ explicit InputFactory(std::shared_ptr<InputEngine> 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<Common::Input::InputDevice> 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<Common::Input::InputDevice> 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<Common::Input::InputDevice> 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<Common::Input::InputDevice> 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<Common::Input::InputDevice> 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<Common::Input::InputDevice> 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<Common::Input::InputDevice> 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<Common::Input::InputDevice> 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<Common::Input::InputDevice> CreateMotionDevice(Common::ParamPackage params);
+
+ std::shared_ptr<InputEngine> input_engine;
+};
+} // namespace InputCommon
diff --git a/src/input_common/keyboard.cpp b/src/input_common/keyboard.cpp
deleted file mode 100644
index 8261e76fd..000000000
--- 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 <atomic>
-#include <list>
-#include <mutex>
-#include <utility>
-#include "input_common/keyboard.h"
-
-namespace InputCommon {
-
-class KeyButton final : public Input::ButtonDevice {
-public:
- explicit KeyButton(std::shared_ptr<KeyButtonList> 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<KeyButtonList> key_button_list;
- std::atomic<bool> status{false};
- std::atomic<bool> 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<KeyButtonPair> list;
-};
-
-Keyboard::Keyboard() : key_button_list{std::make_shared<KeyButtonList>()} {}
-
-KeyButton::~KeyButton() {
- key_button_list->RemoveKeyButton(this);
-}
-
-std::unique_ptr<Input::ButtonDevice> Keyboard::Create(const Common::ParamPackage& params) {
- const int key_code = params.Get("code", 0);
- const bool toggle = params.Get("toggle", false);
- std::unique_ptr<KeyButton> button = std::make_unique<KeyButton>(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 861950472..000000000
--- 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 <memory>
-#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<Input::ButtonDevice> {
-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<Input::ButtonDevice> 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<KeyButtonList> key_button_list;
-};
-
-} // namespace InputCommon
diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp
index f3907c65a..940744c5f 100644
--- a/src/input_common/main.cpp
+++ b/src/input_common/main.cpp
@@ -4,146 +4,173 @@
#include <memory>
#include <thread>
+#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<GCAdapter::Adapter>();
- gcbuttons = std::make_shared<GCButtonFactory>(gcadapter);
- Input::RegisterFactory<Input::ButtonDevice>("gcpad", gcbuttons);
- gcanalog = std::make_shared<GCAnalogFactory>(gcadapter);
- Input::RegisterFactory<Input::AnalogDevice>("gcpad", gcanalog);
- gcvibration = std::make_shared<GCVibrationFactory>(gcadapter);
- Input::RegisterFactory<Input::VibrationDevice>("gcpad", gcvibration);
-
- keyboard = std::make_shared<Keyboard>();
- Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard);
- Input::RegisterFactory<Input::AnalogDevice>("analog_from_button",
- std::make_shared<AnalogFromButton>());
- Input::RegisterFactory<Input::MotionDevice>("keyboard",
- std::make_shared<MotionFromButton>());
- Input::RegisterFactory<Input::TouchDevice>("touch_from_button",
- std::make_shared<TouchFromButtonFactory>());
+ mapping_factory = std::make_shared<MappingFactory>();
+ MappingCallback mapping_callback{[this](MappingData data) { RegisterInput(data); }};
+
+ keyboard = std::make_shared<Keyboard>("keyboard");
+ keyboard->SetMappingCallback(mapping_callback);
+ keyboard_factory = std::make_shared<InputFactory>(keyboard);
+ keyboard_output_factory = std::make_shared<OutputFactory>(keyboard);
+ Common::Input::RegisterFactory<Common::Input::InputDevice>(keyboard->GetEngineName(),
+ keyboard_factory);
+ Common::Input::RegisterFactory<Common::Input::OutputDevice>(keyboard->GetEngineName(),
+ keyboard_output_factory);
+
+ mouse = std::make_shared<Mouse>("mouse");
+ mouse->SetMappingCallback(mapping_callback);
+ mouse_factory = std::make_shared<InputFactory>(mouse);
+ mouse_output_factory = std::make_shared<OutputFactory>(mouse);
+ Common::Input::RegisterFactory<Common::Input::InputDevice>(mouse->GetEngineName(),
+ mouse_factory);
+ Common::Input::RegisterFactory<Common::Input::OutputDevice>(mouse->GetEngineName(),
+ mouse_output_factory);
+
+ touch_screen = std::make_shared<TouchScreen>("touch");
+ touch_screen_factory = std::make_shared<InputFactory>(touch_screen);
+ Common::Input::RegisterFactory<Common::Input::InputDevice>(touch_screen->GetEngineName(),
+ touch_screen_factory);
+
+ gcadapter = std::make_shared<GCAdapter>("gcpad");
+ gcadapter->SetMappingCallback(mapping_callback);
+ gcadapter_input_factory = std::make_shared<InputFactory>(gcadapter);
+ gcadapter_output_factory = std::make_shared<OutputFactory>(gcadapter);
+ Common::Input::RegisterFactory<Common::Input::InputDevice>(gcadapter->GetEngineName(),
+ gcadapter_input_factory);
+ Common::Input::RegisterFactory<Common::Input::OutputDevice>(gcadapter->GetEngineName(),
+ gcadapter_output_factory);
+
+ udp_client = std::make_shared<CemuhookUDP::UDPClient>("cemuhookudp");
+ udp_client->SetMappingCallback(mapping_callback);
+ udp_client_input_factory = std::make_shared<InputFactory>(udp_client);
+ udp_client_output_factory = std::make_shared<OutputFactory>(udp_client);
+ Common::Input::RegisterFactory<Common::Input::InputDevice>(udp_client->GetEngineName(),
+ udp_client_input_factory);
+ Common::Input::RegisterFactory<Common::Input::OutputDevice>(udp_client->GetEngineName(),
+ udp_client_output_factory);
+
+ tas_input = std::make_shared<TasInput::Tas>("tas");
+ tas_input->SetMappingCallback(mapping_callback);
+ tas_input_factory = std::make_shared<InputFactory>(tas_input);
+ tas_output_factory = std::make_shared<OutputFactory>(tas_input);
+ Common::Input::RegisterFactory<Common::Input::InputDevice>(tas_input->GetEngineName(),
+ tas_input_factory);
+ Common::Input::RegisterFactory<Common::Input::OutputDevice>(tas_input->GetEngineName(),
+ tas_output_factory);
#ifdef HAVE_SDL2
- sdl = SDL::Init();
+ sdl = std::make_shared<SDLDriver>("sdl");
+ sdl->SetMappingCallback(mapping_callback);
+ sdl_input_factory = std::make_shared<InputFactory>(sdl);
+ sdl_output_factory = std::make_shared<OutputFactory>(sdl);
+ Common::Input::RegisterFactory<Common::Input::InputDevice>(sdl->GetEngineName(),
+ sdl_input_factory);
+ Common::Input::RegisterFactory<Common::Input::OutputDevice>(sdl->GetEngineName(),
+ sdl_output_factory);
#endif
- udp = std::make_shared<InputCommon::CemuhookUDP::Client>();
- udpmotion = std::make_shared<UDPMotionFactory>(udp);
- Input::RegisterFactory<Input::MotionDevice>("cemuhookudp", udpmotion);
- udptouch = std::make_shared<UDPTouchFactory>(udp);
- Input::RegisterFactory<Input::TouchDevice>("cemuhookudp", udptouch);
-
- mouse = std::make_shared<MouseInput::Mouse>();
- mousebuttons = std::make_shared<MouseButtonFactory>(mouse);
- Input::RegisterFactory<Input::ButtonDevice>("mouse", mousebuttons);
- mouseanalog = std::make_shared<MouseAnalogFactory>(mouse);
- Input::RegisterFactory<Input::AnalogDevice>("mouse", mouseanalog);
- mousemotion = std::make_shared<MouseMotionFactory>(mouse);
- Input::RegisterFactory<Input::MotionDevice>("mouse", mousemotion);
- mousetouch = std::make_shared<MouseTouchFactory>(mouse);
- Input::RegisterFactory<Input::TouchDevice>("mouse", mousetouch);
-
- tas = std::make_shared<TasInput::Tas>();
- tasbuttons = std::make_shared<TasButtonFactory>(tas);
- Input::RegisterFactory<Input::ButtonDevice>("tas", tasbuttons);
- tasanalog = std::make_shared<TasAnalogFactory>(tas);
- Input::RegisterFactory<Input::AnalogDevice>("tas", tasanalog);
+ Common::Input::RegisterFactory<Common::Input::InputDevice>(
+ "touch_from_button", std::make_shared<TouchFromButton>());
+ Common::Input::RegisterFactory<Common::Input::InputDevice>(
+ "analog_from_button", std::make_shared<StickFromButton>());
}
void Shutdown() {
- Input::UnregisterFactory<Input::ButtonDevice>("keyboard");
- Input::UnregisterFactory<Input::MotionDevice>("keyboard");
+ Common::Input::UnregisterFactory<Common::Input::InputDevice>(keyboard->GetEngineName());
+ Common::Input::UnregisterFactory<Common::Input::OutputDevice>(keyboard->GetEngineName());
keyboard.reset();
- Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button");
- Input::UnregisterFactory<Input::TouchDevice>("touch_from_button");
-#ifdef HAVE_SDL2
- sdl.reset();
-#endif
- Input::UnregisterFactory<Input::ButtonDevice>("gcpad");
- Input::UnregisterFactory<Input::AnalogDevice>("gcpad");
- Input::UnregisterFactory<Input::VibrationDevice>("gcpad");
- gcbuttons.reset();
- gcanalog.reset();
- gcvibration.reset();
+ Common::Input::UnregisterFactory<Common::Input::InputDevice>(mouse->GetEngineName());
+ Common::Input::UnregisterFactory<Common::Input::OutputDevice>(mouse->GetEngineName());
+ mouse.reset();
- Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp");
- Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp");
+ Common::Input::UnregisterFactory<Common::Input::InputDevice>(touch_screen->GetEngineName());
+ touch_screen.reset();
- udpmotion.reset();
- udptouch.reset();
+ Common::Input::UnregisterFactory<Common::Input::InputDevice>(gcadapter->GetEngineName());
+ Common::Input::UnregisterFactory<Common::Input::OutputDevice>(gcadapter->GetEngineName());
+ gcadapter.reset();
- Input::UnregisterFactory<Input::ButtonDevice>("mouse");
- Input::UnregisterFactory<Input::AnalogDevice>("mouse");
- Input::UnregisterFactory<Input::MotionDevice>("mouse");
- Input::UnregisterFactory<Input::TouchDevice>("mouse");
+ Common::Input::UnregisterFactory<Common::Input::InputDevice>(udp_client->GetEngineName());
+ Common::Input::UnregisterFactory<Common::Input::OutputDevice>(udp_client->GetEngineName());
+ udp_client.reset();
- mousebuttons.reset();
- mouseanalog.reset();
- mousemotion.reset();
- mousetouch.reset();
+ Common::Input::UnregisterFactory<Common::Input::InputDevice>(tas_input->GetEngineName());
+ Common::Input::UnregisterFactory<Common::Input::OutputDevice>(tas_input->GetEngineName());
+ tas_input.reset();
- Input::UnregisterFactory<Input::ButtonDevice>("tas");
- Input::UnregisterFactory<Input::AnalogDevice>("tas");
+#ifdef HAVE_SDL2
+ Common::Input::UnregisterFactory<Common::Input::InputDevice>(sdl->GetEngineName());
+ Common::Input::UnregisterFactory<Common::Input::OutputDevice>(sdl->GetEngineName());
+ sdl.reset();
+#endif
- tasbuttons.reset();
- tasanalog.reset();
+ Common::Input::UnregisterFactory<Common::Input::InputDevice>("touch_from_button");
+ Common::Input::UnregisterFactory<Common::Input::InputDevice>("analog_from_button");
}
[[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const {
std::vector<Common::ParamPackage> 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 {};
}
+ 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
+ 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<MappingFactory> mapping_factory;
+
std::shared_ptr<Keyboard> keyboard;
+ std::shared_ptr<Mouse> mouse;
+ std::shared_ptr<GCAdapter> gcadapter;
+ std::shared_ptr<TouchScreen> touch_screen;
+ std::shared_ptr<TasInput::Tas> tas_input;
+ std::shared_ptr<CemuhookUDP::UDPClient> udp_client;
+
+ std::shared_ptr<InputFactory> keyboard_factory;
+ std::shared_ptr<InputFactory> mouse_factory;
+ std::shared_ptr<InputFactory> gcadapter_input_factory;
+ std::shared_ptr<InputFactory> touch_screen_factory;
+ std::shared_ptr<InputFactory> udp_client_input_factory;
+ std::shared_ptr<InputFactory> tas_input_factory;
+
+ std::shared_ptr<OutputFactory> keyboard_output_factory;
+ std::shared_ptr<OutputFactory> mouse_output_factory;
+ std::shared_ptr<OutputFactory> gcadapter_output_factory;
+ std::shared_ptr<OutputFactory> udp_client_output_factory;
+ std::shared_ptr<OutputFactory> tas_output_factory;
+
#ifdef HAVE_SDL2
- std::unique_ptr<SDL::State> sdl;
+ std::shared_ptr<SDLDriver> sdl;
+ std::shared_ptr<InputFactory> sdl_input_factory;
+ std::shared_ptr<OutputFactory> sdl_output_factory;
#endif
- std::shared_ptr<GCButtonFactory> gcbuttons;
- std::shared_ptr<GCAnalogFactory> gcanalog;
- std::shared_ptr<GCVibrationFactory> gcvibration;
- std::shared_ptr<UDPMotionFactory> udpmotion;
- std::shared_ptr<UDPTouchFactory> udptouch;
- std::shared_ptr<MouseButtonFactory> mousebuttons;
- std::shared_ptr<MouseAnalogFactory> mouseanalog;
- std::shared_ptr<MouseMotionFactory> mousemotion;
- std::shared_ptr<MouseTouchFactory> mousetouch;
- std::shared_ptr<TasButtonFactory> tasbuttons;
- std::shared_ptr<TasAnalogFactory> tasanalog;
- std::shared_ptr<CemuhookUDP::Client> udp;
- std::shared_ptr<GCAdapter::Adapter> gcadapter;
- std::shared_ptr<MouseInput::Mouse> mouse;
- std::shared_ptr<TasInput::Tas> tas;
};
InputSubsystem::InputSubsystem() : impl{std::make_unique<Impl>()} {}
@@ -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<Common::ParamPackage> InputSubsystem::GetInputDevices() const {
@@ -259,100 +377,30 @@ MotionMapping InputSubsystem::GetMotionMappingForDevice(const Common::ParamPacka
return impl->GetMotionMappingForDevice(device);
}
-GCAnalogFactory* InputSubsystem::GetGCAnalogs() {
- return impl->gcanalog.get();
-}
-
-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();
+Common::Input::ButtonNames InputSubsystem::GetButtonName(const Common::ParamPackage& params) const {
+ return impl->GetButtonName(params);
}
-const MouseButtonFactory* InputSubsystem::GetMouseButtons() const {
- return impl->mousebuttons.get();
+bool InputSubsystem::IsController(const Common::ParamPackage& params) const {
+ return impl->IsController(params);
}
-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();
+void InputSubsystem::ReloadInputDevices() {
+ impl->udp_client.get()->ReloadSockets();
}
-const TasAnalogFactory* InputSubsystem::GetTasAnalogs() const {
- return impl->tasanalog.get();
+void InputSubsystem::BeginMapping(Polling::InputType type) {
+ impl->BeginConfiguration();
+ impl->mapping_factory->BeginMapping(type);
}
-void InputSubsystem::ReloadInputDevices() {
- if (!impl->udp) {
- return;
- }
- impl->udp->ReloadSockets();
+const Common::ParamPackage InputSubsystem::GetNextInput() const {
+ return impl->mapping_factory->GetNextInput();
}
-std::vector<std::unique_ptr<Polling::DevicePoller>> InputSubsystem::GetPollers(
- [[maybe_unused]] Polling::DeviceType type) const {
-#ifdef HAVE_SDL2
- return impl->sdl->GetPollers(type);
-#else
- return {};
-#endif
+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 6390d3f09..c6f97f691 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<Settings::NativeAnalog::Values, Common::ParamPackage>;
using ButtonMapping = std::unordered_map<Settings::NativeButton::Values, Common::ParamPackage>;
@@ -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 touch screen device.
+ [[nodiscard]] TouchScreen* GetTouchScreen();
- /// Retrieves the underlying tas device.
+ /// 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<Common::ParamPackage> 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();
+ /// Reloads the input devices.
+ void ReloadInputDevices();
- /// Retrieves the underlying tas analogs handler.
- [[nodiscard]] const TasAnalogFactory* GetTasAnalogs() const;
+ /// Start polling from all backends for a desired input type.
+ void BeginMapping(Polling::InputType type);
- /// Reloads the input devices
- void ReloadInputDevices();
+ /// Returns an input event with mapping information.
+ [[nodiscard]] const Common::ParamPackage GetNextInput() const;
- /// Get all DevicePoller from all backends for a specific device type
- [[nodiscard]] std::vector<std::unique_ptr<Polling::DevicePoller>> GetPollers(
- Polling::DeviceType type) const;
+ /// Stop polling from all backends.
+ void StopMapping() const;
private:
struct Impl;
std::unique_ptr<Impl> 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 29045a673..000000000
--- 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<Input::ButtonDevice>;
-
- 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<Input::MotionDevice> MotionFromButton::Create(const Common::ParamPackage& params) {
- auto key = Input::CreateDevice<Input::ButtonDevice>(params.Serialize());
- return std::make_unique<MotionKey>(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 a959046fb..000000000
--- 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<Input::MotionDevice> {
-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<Input::MotionDevice> 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 3b052ffb2..000000000
--- 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 <stop_token>
-#include <thread>
-
-#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<int>(16 * info.last_mouse_change.x),
- static_cast<int>(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<std::size_t>(button_);
- if (button_index >= mouse_info.size()) {
- return;
- }
-
- const auto button = 1U << button_index;
- buttons |= static_cast<u16>(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<float>();
- 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<float>();
- info.tilt_speed = info.tilt_direction.Normalize() * info.sensitivity;
- }
- }
- }
-}
-
-void Mouse::ReleaseButton(MouseButton button_) {
- const auto button_index = static_cast<std::size_t>(button_);
- if (button_index >= mouse_info.size()) {
- return;
- }
-
- const auto button = 1U << button_index;
- buttons &= static_cast<u16>(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<u16>(button);
-
- if (button_state) {
- toggle_buttons &= static_cast<u16>(0xFF - button);
- } else {
- toggle_buttons |= static_cast<u16>(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<u16>(0xFF - button);
-
- return button_state;
-}
-
-Common::SPSCQueue<MouseStatus>& Mouse::GetMouseQueue() {
- return mouse_queue;
-}
-
-const Common::SPSCQueue<MouseStatus>& 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 c8bae99c1..000000000
--- 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 <array>
-#include <mutex>
-#include <stop_token>
-#include <thread>
-
-#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<int, 2> 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<MouseStatus>& GetMouseQueue();
- [[nodiscard]] const Common::SPSCQueue<MouseStatus>& 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<int> mouse_origin;
- Common::Vec2<int> last_mouse_position;
- Common::Vec2<float> last_mouse_change;
- bool is_tilting = false;
- float sensitivity{0.120f};
-
- float tilt_speed = 0;
- Common::Vec2<float> tilt_direction;
- MouseData data;
- };
-
- u16 buttons{};
- u16 toggle_buttons{};
- u16 lock_buttons{};
- std::jthread update_thread;
- MouseButton last_button{MouseButton::Undefined};
- std::array<MouseInfo, 7> mouse_info;
- Common::SPSCQueue<MouseStatus> 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 090b26972..000000000
--- 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 <algorithm>
-#include <memory>
-#include <mutex>
-#include <utility>
-
-#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<MouseInput::Mouse> mouse_input_)
- : mouse_input(std::move(mouse_input_)) {}
-
-std::unique_ptr<Input::ButtonDevice> 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<MouseButton>(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<u16>(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<float>(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<float, float> 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<float, float> 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<float, float> 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<MouseInput::Mouse> 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<Input::AnalogDevice> MouseAnalogFactory::Create(
- const Common::ParamPackage& params) {
- const auto port = static_cast<u32>(params.Get("port", 0));
- const auto axis_x = static_cast<u32>(params.Get("axis_x", 0));
- const auto axis_y = static_cast<u32>(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<MouseAnalog>(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<u16>(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<MouseInput::Mouse> mouse_input_)
- : mouse_input(std::move(mouse_input_)) {}
-
-std::unique_ptr<Input::MotionDevice> MouseMotionFactory::Create(
- const Common::ParamPackage& params) {
- const auto button_id = params.Get("button", 0);
-
- return std::make_unique<MouseMotion>(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<u16>(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<MouseInput::Mouse> mouse_input_)
- : mouse_input(std::move(mouse_input_)) {}
-
-std::unique_ptr<Input::TouchDevice> MouseTouchFactory::Create(const Common::ParamPackage& params) {
- const auto button_id = params.Get("button", 0);
-
- return std::make_unique<MouseTouch>(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<u16>(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 cf331293b..000000000
--- 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 <memory>
-#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<Input::ButtonDevice> {
-public:
- explicit MouseButtonFactory(std::shared_ptr<MouseInput::Mouse> 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<Input::ButtonDevice> 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<MouseInput::Mouse> mouse_input;
- bool polling = false;
-};
-
-/// An analog device factory that creates analog devices from mouse
-class MouseAnalogFactory final : public Input::Factory<Input::AnalogDevice> {
-public:
- explicit MouseAnalogFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_);
-
- std::unique_ptr<Input::AnalogDevice> 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<MouseInput::Mouse> mouse_input;
- bool polling = false;
-};
-
-/// A motion device factory that creates motion devices from mouse
-class MouseMotionFactory final : public Input::Factory<Input::MotionDevice> {
-public:
- explicit MouseMotionFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_);
-
- std::unique_ptr<Input::MotionDevice> 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<MouseInput::Mouse> mouse_input;
- bool polling = false;
-};
-
-/// An touch device factory that creates touch devices from mouse
-class MouseTouchFactory final : public Input::Factory<Input::TouchDevice> {
-public:
- explicit MouseTouchFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_);
-
- std::unique_ptr<Input::TouchDevice> 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<MouseInput::Mouse> 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 644db3448..000000000
--- 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<State> Init() {
-#ifdef HAVE_SDL2
- return std::make_unique<SDLState>();
-#else
- return std::make_unique<NullState>();
-#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 b5d41bba4..000000000
--- 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 <memory>
-#include <vector>
-#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<std::unique_ptr<Polling::DevicePoller>>;
-
- /// Unregisters SDL device factories and shut them down.
- virtual ~State() = default;
-
- virtual Pollers GetPollers(Polling::DeviceType) {
- return {};
- }
-
- virtual std::vector<Common::ParamPackage> 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<State> 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 ecb00d428..000000000
--- 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 <algorithm>
-#include <array>
-#include <atomic>
-#include <chrono>
-#include <cmath>
-#include <functional>
-#include <mutex>
-#include <optional>
-#include <sstream>
-#include <string>
-#include <thread>
-#include <tuple>
-#include <unordered_map>
-#include <utility>
-#include <vector>
-
-#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<SDLState*>(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<float>(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<float, float> 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<int, bool> buttons;
- std::unordered_map<int, bool> toggle_buttons{};
- std::unordered_map<int, bool> lock_buttons{};
- std::unordered_map<int, Sint16> axes;
- std::unordered_map<int, Uint8> hats;
- } state;
- std::string guid;
- int port;
- std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick;
- std::unique_ptr<SDL_GameController, decltype(&SDL_GameControllerClose)> 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<SDLJoystick> 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<std::size_t>(port)) {
- auto joystick = std::make_shared<SDLJoystick>(guid, static_cast<int>(it->second.size()),
- nullptr, nullptr);
- it->second.emplace_back(std::move(joystick));
- }
-
- return it->second[static_cast<std::size_t>(port)];
- }
-
- auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr, nullptr);
-
- return joystick_map[guid].emplace_back(std::move(joystick));
-}
-
-std::shared_ptr<SDLJoystick> 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<SDLJoystick>(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<int>(joystick_guid_list.size());
- auto joystick = std::make_shared<SDLJoystick>(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<SDLJoystick> 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<SDLJoystick> joystick;
- int button;
- bool toggle;
-};
-
-class SDLDirectionButton final : public Input::ButtonDevice {
-public:
- explicit SDLDirectionButton(std::shared_ptr<SDLJoystick> 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<SDLJoystick> joystick;
- int hat;
- Uint8 direction;
-};
-
-class SDLAxisButton final : public Input::ButtonDevice {
-public:
- explicit SDLAxisButton(std::shared_ptr<SDLJoystick> 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<SDLJoystick> joystick;
- int axis;
- float threshold;
- bool trigger_if_greater;
-};
-
-class SDLAnalog final : public Input::AnalogDevice {
-public:
- explicit SDLAnalog(std::shared_ptr<SDLJoystick> 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<float, float> 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<float, float> 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<SDLJoystick> 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<SDLJoystick> 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<u16>((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<SDLJoystick> joystick;
-};
-
-class SDLMotion final : public Input::MotionDevice {
-public:
- explicit SDLMotion(std::shared_ptr<SDLJoystick> joystick_) : joystick(std::move(joystick_)) {}
-
- Input::MotionStatus GetStatus() const override {
- return joystick->GetMotion().GetMotion();
- }
-
-private:
- std::shared_ptr<SDLJoystick> joystick;
-};
-
-class SDLDirectionMotion final : public Input::MotionDevice {
-public:
- explicit SDLDirectionMotion(std::shared_ptr<SDLJoystick> 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<SDLJoystick> joystick;
- int hat;
- Uint8 direction;
-};
-
-class SDLAxisMotion final : public Input::MotionDevice {
-public:
- explicit SDLAxisMotion(std::shared_ptr<SDLJoystick> 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<SDLJoystick> joystick;
- int axis;
- float threshold;
- bool trigger_if_greater;
-};
-
-class SDLButtonMotion final : public Input::MotionDevice {
-public:
- explicit SDLButtonMotion(std::shared_ptr<SDLJoystick> 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<SDLJoystick> joystick;
- int button;
-};
-
-/// A button device factory that creates button devices from SDL joystick
-class SDLButtonFactory final : public Input::Factory<Input::ButtonDevice> {
-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<Input::ButtonDevice> 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<SDLDirectionButton>(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<SDLAxisButton>(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<SDLButton>(joystick, button, toggle);
- }
-
-private:
- SDLState& state;
-};
-
-/// An analog device factory that creates analog devices from SDL joystick
-class SDLAnalogFactory final : public Input::Factory<Input::AnalogDevice> {
-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<Input::AnalogDevice> 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<SDLAnalog>(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<Input::VibrationDevice> {
-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<Input::VibrationDevice> 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<SDLVibration>(state.GetSDLJoystickByGUID(guid, port));
- }
-
-private:
- SDLState& state;
-};
-
-/// A motion device factory that creates motion devices from SDL joystick
-class SDLMotionFactory final : public Input::Factory<Input::MotionDevice> {
-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<Input::MotionDevice> 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<SDLMotion>(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<SDLDirectionMotion>(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<SDLAxisMotion>(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<SDLButtonMotion>(joystick, button);
- }
-
-private:
- SDLState& state;
-};
-
-SDLState::SDLState() {
- using namespace Input;
- button_factory = std::make_shared<SDLButtonFactory>(*this);
- analog_factory = std::make_shared<SDLAnalogFactory>(*this);
- vibration_factory = std::make_shared<SDLVibrationFactory>(*this);
- motion_factory = std::make_shared<SDLMotionFactory>(*this);
- RegisterFactory<ButtonDevice>("sdl", button_factory);
- RegisterFactory<AnalogDevice>("sdl", analog_factory);
- RegisterFactory<VibrationDevice>("sdl", vibration_factory);
- RegisterFactory<MotionDevice>("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<ButtonDevice>("sdl");
- UnregisterFactory<AnalogDevice>("sdl");
- UnregisterFactory<VibrationDevice>("sdl");
- UnregisterFactory<MotionDevice>("sdl");
-
- CloseJoysticks();
- SDL_DelEventWatch(&SDLEventWatcher, this);
-
- initialized = false;
- if (start_thread) {
- poll_thread.join();
- SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
- }
-}
-
-std::vector<Common::ParamPackage> SDLState::GetInputDevices() {
- std::scoped_lock lock(joystick_map_mutex);
- std::vector<Common::ParamPackage> devices;
- std::unordered_map<int, std::shared_ptr<SDLJoystick>> 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<s32>(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<s32>(event.jbutton.button));
- }
- break;
- }
- case SDL_JOYHATMOTION: {
- if (const auto joystick = state.GetSDLJoystickBySDLID(event.jhat.which)) {
- return BuildHatParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
- static_cast<s32>(event.jhat.hat),
- static_cast<s32>(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<s32>(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<s32>(event.jbutton.button));
- }
- break;
- }
- case SDL_JOYHATMOTION: {
- if (const auto joystick = state.GetSDLJoystickBySDLID(event.jhat.which)) {
- return BuildHatParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
- static_cast<s32>(event.jhat.hat),
- static_cast<s32>(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<SDLJoystick>& 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<SDLJoystick>& 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<SDLJoystick>& joystick,
- const std::shared_ptr<SDLJoystick>& 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<Common::ParamPackage> 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<Sint16>(
- 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<Sint16>(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<SDL_JoystickID, std::unordered_map<uint8_t, int16_t>> axis_memory;
- std::unordered_map<SDL_JoystickID, std::unordered_map<uint8_t, uint32_t>> 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<Common::ParamPackage> 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<Polling::SDLAnalogPreferredPoller>(*this));
- break;
- case InputCommon::Polling::DeviceType::Button:
- pollers.emplace_back(std::make_unique<Polling::SDLButtonPoller>(*this));
- break;
- case InputCommon::Polling::DeviceType::Motion:
- pollers.emplace_back(std::make_unique<Polling::SDLMotionPoller>(*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 1598092b6..000000000
--- 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 <cstring>
-#include <regex>
-
-#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<std::pair<std::string_view, TasButton>, 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<std::string> 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<float, float> Tas::FlipAxisY(std::pair<float, float> old) {
- auto [x, y] = old;
- return {x, -y};
-}
-
-void Tas::RecordInput(u32 buttons, const std::array<std::pair<float, float>, 2>& axes) {
- last_input = {buttons, FlipAxisY(axes[0]), FlipAxisY(axes[1])};
-}
-
-std::tuple<TasState, size_t, size_t> 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<TasData, PLAYER_NUMBER>& 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<u32>(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<std::string> 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<u32>(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<int>(x * 32767));
- line += ";";
- line += std::to_string(static_cast<int>(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<TasButton>(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<u8>(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<Settings::NativeButton::Values>(i)].Serialize();
- }
- for (std::size_t i = 0; i < analogs.size(); ++i) {
- analogs[i] = analog_mapping[static_cast<Settings::NativeAnalog::Values>(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<std::pair<Settings::NativeButton::Values, TasButton>, 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<int>(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<int>(TasAxes::StickX));
- left_analog_params.Set("axis_y", static_cast<int>(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<int>(TasAxes::SubstickX));
- right_analog_params.Set("axis_y", static_cast<int>(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 15810d6b0..000000000
--- 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 <mutex>
-#include <utility>
-
-#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<TasInput::Tas> tas_input_)
- : tas_input(std::move(tas_input_)) {}
-
-std::unique_ptr<Input::ButtonDevice> 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<TasButton>(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<float, float> 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<float, float> 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<TasInput::Tas> 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<Input::AnalogDevice> TasAnalogFactory::Create(const Common::ParamPackage& params) {
- const auto pad = static_cast<u32>(params.Get("pad", 0));
- const auto axis_x = static_cast<u32>(params.Get("axis_x", 0));
- const auto axis_y = static_cast<u32>(params.Get("axis_y", 1));
-
- return std::make_unique<TasAnalog>(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 09e426cef..000000000
--- 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 <memory>
-#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<Input::ButtonDevice> {
-public:
- explicit TasButtonFactory(std::shared_ptr<TasInput::Tas> 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<Input::ButtonDevice> Create(const Common::ParamPackage& params) override;
-
-private:
- std::shared_ptr<TasInput::Tas> tas_input;
-};
-
-/// An analog device factory that creates analog devices from tas
-class TasAnalogFactory final : public Input::Factory<Input::AnalogDevice> {
-public:
- explicit TasAnalogFactory(std::shared_ptr<TasInput::Tas> tas_input_);
-
- std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override;
-
-private:
- std::shared_ptr<TasInput::Tas> 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 7878a56d7..000000000
--- 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 <algorithm>
-#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<u64>(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<Input::ButtonDevice>(config_entry),
- std::clamp(package.Get("x", 0), 0, static_cast<int>(Layout::ScreenUndocked::Width)),
- std::clamp(package.Get("y", 0), 0,
- static_cast<int>(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<float>(std::get<1>(map[id])) /
- static_cast<int>(Layout::ScreenUndocked::Width);
- const float y = static_cast<float>(std::get<2>(map[id])) /
- static_cast<int>(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<std::tuple<std::unique_ptr<Input::ButtonDevice>, int, int>> map;
-};
-
-std::unique_ptr<Input::TouchDevice> TouchFromButtonFactory::Create(const Common::ParamPackage&) {
- return std::make_unique<TouchFromButtonDevice>();
-}
-
-} // namespace InputCommon
diff --git a/src/input_common/udp/client.cpp b/src/input_common/udp/client.cpp
deleted file mode 100644
index b9512aa2e..000000000
--- 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 <chrono>
-#include <cstring>
-#include <functional>
-#include <random>
-#include <thread>
-#include <boost/asio.hpp>
-#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<void(Response::Version)> version;
- std::function<void(Response::PortInfo)> port_info;
- std::function<void(Response::PadData)> 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<clock> timer;
- udp::socket socket;
-
- const u32 client_id;
-
- static constexpr std::size_t PORT_INFO_SIZE = sizeof(Message<Request::PortInfo>);
- static constexpr std::size_t PAD_DATA_SIZE = sizeof(Message<Request::PadData>);
- std::array<u8, PORT_INFO_SIZE> send_buffer1;
- std::array<u8, PAD_DATA_SIZE> send_buffer2;
- udp::endpoint send_endpoint;
-
- std::array<u8, MAX_PACKET_SIZE> 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<Common::ParamPackage> Client::GetInputDevices() const {
- std::vector<Common::ParamPackage> 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<u64>(
- std::chrono::duration_cast<std::chrono::milliseconds>(now - pads[pad].last_update).count());
- return time_difference < 1000 && pads[pad].connected;
-}
-
-void Client::ReloadSockets() {
- Reset();
-
- std::stringstream servers_ss(static_cast<std::string>(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<u16>(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<u64>(
- std::chrono::duration_cast<std::chrono::microseconds>(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<Socket>(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<float>& acc, const Common::Vec3<float>& 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<PadMotion>(i);
- pad.motion_value = gyro[i];
- pad_queue.Push(pad);
- }
- if (acc[i] > 1.75f || acc[i] < -1.75f) {
- pad.motion = static_cast<PadMotion>(i + 3);
- pad.motion_value = acc[i];
- pad_queue.Push(pad);
- }
- }
-}
-
-std::optional<std::size_t> 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<u16>(touch_param.Get("min_x", 100));
- const u16 min_y = static_cast<u16>(touch_param.Get("min_y", 50));
- const u16 max_x = static_cast<u16>(touch_param.Get("max_x", 1800));
- const u16 max_y = static_cast<u16>(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<float>(std::clamp(static_cast<u16>(touch_pad.x), min_x, max_x) - min_x) /
- static_cast<float>(max_x - min_x);
- y = static_cast<float>(std::clamp(static_cast<u16>(touch_pad.y), min_y, max_y) - min_y) /
- static_cast<float>(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<UDPPadStatus>& Client::GetPadQueue() {
- return pad_queue;
-}
-
-const Common::SPSCQueue<UDPPadStatus>& Client::GetPadQueue() const {
- return pad_queue;
-}
-
-void TestCommunication(const std::string& host, u16 port,
- const std::function<void()>& success_callback,
- const std::function<void()>& 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<void(Status)> status_callback,
- std::function<void(u16, u16, u16, u16)> 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<u16>(touchpad_0.x));
- const u16 min_y = std::min(MAX_VALUE, static_cast<u16>(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 9829da6f0..000000000
--- 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 <mutex>
-#include <utility>
-#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<CemuhookUDP::Client> client_)
- : client(std::move(client_)) {}
-
-/**
- * Creates motion device
- * @param params contains parameters for creating the device:
- * - "port": the UDP port number
- */
-std::unique_ptr<Input::MotionDevice> UDPMotionFactory::Create(const Common::ParamPackage& params) {
- auto ip = params.Get("ip", "127.0.0.1");
- const auto port = static_cast<u16>(params.Get("port", 26760));
- const auto pad = static_cast<u16>(params.Get("pad_index", 0));
-
- return std::make_unique<UDPMotion>(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<u16>(pad.port));
- params.Set("pad_index", static_cast<u16>(pad.pad_index));
- params.Set("motion", static_cast<u16>(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<CemuhookUDP::Client> client_)
- : client(std::move(client_)) {}
-
-/**
- * Creates motion device
- * @param params contains parameters for creating the device:
- * - "port": the UDP port number
- */
-std::unique_ptr<Input::TouchDevice> UDPTouchFactory::Create(const Common::ParamPackage& params) {
- auto ip = params.Get("ip", "127.0.0.1");
- const auto port = static_cast<u16>(params.Get("port", 26760));
- const auto pad = static_cast<u16>(params.Get("pad_index", 0));
-
- return std::make_unique<UDPTouch>(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 ea3fd4175..000000000
--- 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 <memory>
-#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<Input::MotionDevice> {
-public:
- explicit UDPMotionFactory(std::shared_ptr<CemuhookUDP::Client> client_);
-
- std::unique_ptr<Input::MotionDevice> 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<CemuhookUDP::Client> client;
- bool polling = false;
-};
-
-/// A touch device factory that creates touch devices from udp clients
-class UDPTouchFactory final : public Input::Factory<Input::TouchDevice> {
-public:
- explicit UDPTouchFactory(std::shared_ptr<CemuhookUDP::Client> client_);
-
- std::unique_ptr<Input::TouchDevice> 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<CemuhookUDP::Client> client;
- bool polling = false;
-};
-
-} // namespace InputCommon