summaryrefslogtreecommitdiff
path: root/src/input_common
diff options
context:
space:
mode:
authorGravatar Fernando S2021-11-27 11:52:08 +0100
committerGravatar GitHub2021-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.gz
yuzu-564f10527745f870621c08bbb5d16badee0ed861.tar.xz
yuzu-564f10527745f870621c08bbb5d16badee0ed861.zip
Merge pull request #7255 from german77/kraken
Project Kraken: Input rewrite
Diffstat (limited to 'src/input_common')
-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/motion_input.cpp307
-rw-r--r--src/input_common/motion_input.h74
-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, 5748 insertions, 6073 deletions
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 @@
1add_library(input_common STATIC 1add_library(input_common STATIC
2 analog_from_button.cpp 2 drivers/gc_adapter.cpp
3 analog_from_button.h 3 drivers/gc_adapter.h
4 keyboard.cpp 4 drivers/keyboard.cpp
5 keyboard.h 5 drivers/keyboard.h
6 drivers/mouse.cpp
7 drivers/mouse.h
8 drivers/sdl_driver.cpp
9 drivers/sdl_driver.h
10 drivers/tas_input.cpp
11 drivers/tas_input.h
12 drivers/touch_screen.cpp
13 drivers/touch_screen.h
14 drivers/udp_client.cpp
15 drivers/udp_client.h
16 helpers/stick_from_buttons.cpp
17 helpers/stick_from_buttons.h
18 helpers/touch_from_buttons.cpp
19 helpers/touch_from_buttons.h
20 helpers/udp_protocol.cpp
21 helpers/udp_protocol.h
22 input_engine.cpp
23 input_engine.h
24 input_mapping.cpp
25 input_mapping.h
26 input_poller.cpp
27 input_poller.h
6 main.cpp 28 main.cpp
7 main.h 29 main.h
8 motion_from_button.cpp
9 motion_from_button.h
10 motion_input.cpp
11 motion_input.h
12 touch_from_button.cpp
13 touch_from_button.h
14 gcadapter/gc_adapter.cpp
15 gcadapter/gc_adapter.h
16 gcadapter/gc_poller.cpp
17 gcadapter/gc_poller.h
18 mouse/mouse_input.cpp
19 mouse/mouse_input.h
20 mouse/mouse_poller.cpp
21 mouse/mouse_poller.h
22 sdl/sdl.cpp
23 sdl/sdl.h
24 tas/tas_input.cpp
25 tas/tas_input.h
26 tas/tas_poller.cpp
27 tas/tas_poller.h
28 udp/client.cpp
29 udp/client.h
30 udp/protocol.cpp
31 udp/protocol.h
32 udp/udp.cpp
33 udp/udp.h
34) 30)
35 31
36if (MSVC) 32if (MSVC)
@@ -57,8 +53,8 @@ endif()
57 53
58if (ENABLE_SDL2) 54if (ENABLE_SDL2)
59 target_sources(input_common PRIVATE 55 target_sources(input_common PRIVATE
60 sdl/sdl_impl.cpp 56 drivers/sdl_driver.cpp
61 sdl/sdl_impl.h 57 drivers/sdl_driver.h
62 ) 58 )
63 target_link_libraries(input_common PRIVATE SDL2) 59 target_link_libraries(input_common PRIVATE SDL2)
64 target_compile_definitions(input_common PRIVATE HAVE_SDL2) 60 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 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <atomic>
6#include <chrono>
7#include <cmath>
8#include <thread>
9#include "common/math_util.h"
10#include "common/settings.h"
11#include "input_common/analog_from_button.h"
12
13namespace InputCommon {
14
15class Analog final : public Input::AnalogDevice {
16public:
17 using Button = std::unique_ptr<Input::ButtonDevice>;
18
19 Analog(Button up_, Button down_, Button left_, Button right_, Button modifier_,
20 float modifier_scale_, float modifier_angle_)
21 : up(std::move(up_)), down(std::move(down_)), left(std::move(left_)),
22 right(std::move(right_)), modifier(std::move(modifier_)), modifier_scale(modifier_scale_),
23 modifier_angle(modifier_angle_) {
24 Input::InputCallback<bool> callbacks{
25 [this]([[maybe_unused]] bool status) { UpdateStatus(); }};
26 up->SetCallback(callbacks);
27 down->SetCallback(callbacks);
28 left->SetCallback(callbacks);
29 right->SetCallback(callbacks);
30 modifier->SetCallback(callbacks);
31 }
32
33 bool IsAngleGreater(float old_angle, float new_angle) const {
34 constexpr float TAU = Common::PI * 2.0f;
35 // Use wider angle to ease the transition.
36 constexpr float aperture = TAU * 0.15f;
37 const float top_limit = new_angle + aperture;
38 return (old_angle > new_angle && old_angle <= top_limit) ||
39 (old_angle + TAU > new_angle && old_angle + TAU <= top_limit);
40 }
41
42 bool IsAngleSmaller(float old_angle, float new_angle) const {
43 constexpr float TAU = Common::PI * 2.0f;
44 // Use wider angle to ease the transition.
45 constexpr float aperture = TAU * 0.15f;
46 const float bottom_limit = new_angle - aperture;
47 return (old_angle >= bottom_limit && old_angle < new_angle) ||
48 (old_angle - TAU >= bottom_limit && old_angle - TAU < new_angle);
49 }
50
51 float GetAngle(std::chrono::time_point<std::chrono::steady_clock> now) const {
52 constexpr float TAU = Common::PI * 2.0f;
53 float new_angle = angle;
54
55 auto time_difference = static_cast<float>(
56 std::chrono::duration_cast<std::chrono::microseconds>(now - last_update).count());
57 time_difference /= 1000.0f * 1000.0f;
58 if (time_difference > 0.5f) {
59 time_difference = 0.5f;
60 }
61
62 if (IsAngleGreater(new_angle, goal_angle)) {
63 new_angle -= modifier_angle * time_difference;
64 if (new_angle < 0) {
65 new_angle += TAU;
66 }
67 if (!IsAngleGreater(new_angle, goal_angle)) {
68 return goal_angle;
69 }
70 } else if (IsAngleSmaller(new_angle, goal_angle)) {
71 new_angle += modifier_angle * time_difference;
72 if (new_angle >= TAU) {
73 new_angle -= TAU;
74 }
75 if (!IsAngleSmaller(new_angle, goal_angle)) {
76 return goal_angle;
77 }
78 } else {
79 return goal_angle;
80 }
81 return new_angle;
82 }
83
84 void SetGoalAngle(bool r, bool l, bool u, bool d) {
85 // Move to the right
86 if (r && !u && !d) {
87 goal_angle = 0.0f;
88 }
89
90 // Move to the upper right
91 if (r && u && !d) {
92 goal_angle = Common::PI * 0.25f;
93 }
94
95 // Move up
96 if (u && !l && !r) {
97 goal_angle = Common::PI * 0.5f;
98 }
99
100 // Move to the upper left
101 if (l && u && !d) {
102 goal_angle = Common::PI * 0.75f;
103 }
104
105 // Move to the left
106 if (l && !u && !d) {
107 goal_angle = Common::PI;
108 }
109
110 // Move to the bottom left
111 if (l && !u && d) {
112 goal_angle = Common::PI * 1.25f;
113 }
114
115 // Move down
116 if (d && !l && !r) {
117 goal_angle = Common::PI * 1.5f;
118 }
119
120 // Move to the bottom right
121 if (r && !u && d) {
122 goal_angle = Common::PI * 1.75f;
123 }
124 }
125
126 void UpdateStatus() {
127 const float coef = modifier->GetStatus() ? modifier_scale : 1.0f;
128
129 bool r = right->GetStatus();
130 bool l = left->GetStatus();
131 bool u = up->GetStatus();
132 bool d = down->GetStatus();
133
134 // Eliminate contradictory movements
135 if (r && l) {
136 r = false;
137 l = false;
138 }
139 if (u && d) {
140 u = false;
141 d = false;
142 }
143
144 // Move if a key is pressed
145 if (r || l || u || d) {
146 amplitude = coef;
147 } else {
148 amplitude = 0;
149 }
150
151 const auto now = std::chrono::steady_clock::now();
152 const auto time_difference = static_cast<u64>(
153 std::chrono::duration_cast<std::chrono::milliseconds>(now - last_update).count());
154
155 if (time_difference < 10) {
156 // Disable analog mode if inputs are too fast
157 SetGoalAngle(r, l, u, d);
158 angle = goal_angle;
159 } else {
160 angle = GetAngle(now);
161 SetGoalAngle(r, l, u, d);
162 }
163
164 last_update = now;
165 }
166
167 std::tuple<float, float> GetStatus() const override {
168 if (Settings::values.emulate_analog_keyboard) {
169 const auto now = std::chrono::steady_clock::now();
170 float angle_ = GetAngle(now);
171 return std::make_tuple(std::cos(angle_) * amplitude, std::sin(angle_) * amplitude);
172 }
173 constexpr float SQRT_HALF = 0.707106781f;
174 int x = 0, y = 0;
175 if (right->GetStatus()) {
176 ++x;
177 }
178 if (left->GetStatus()) {
179 --x;
180 }
181 if (up->GetStatus()) {
182 ++y;
183 }
184 if (down->GetStatus()) {
185 --y;
186 }
187 const float coef = modifier->GetStatus() ? modifier_scale : 1.0f;
188 return std::make_tuple(static_cast<float>(x) * coef * (y == 0 ? 1.0f : SQRT_HALF),
189 static_cast<float>(y) * coef * (x == 0 ? 1.0f : SQRT_HALF));
190 }
191
192 Input::AnalogProperties GetAnalogProperties() const override {
193 return {modifier_scale, 1.0f, 0.5f};
194 }
195
196 bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override {
197 switch (direction) {
198 case Input::AnalogDirection::RIGHT:
199 return right->GetStatus();
200 case Input::AnalogDirection::LEFT:
201 return left->GetStatus();
202 case Input::AnalogDirection::UP:
203 return up->GetStatus();
204 case Input::AnalogDirection::DOWN:
205 return down->GetStatus();
206 }
207 return false;
208 }
209
210private:
211 Button up;
212 Button down;
213 Button left;
214 Button right;
215 Button modifier;
216 float modifier_scale;
217 float modifier_angle;
218 float angle{};
219 float goal_angle{};
220 float amplitude{};
221 std::chrono::time_point<std::chrono::steady_clock> last_update;
222};
223
224std::unique_ptr<Input::AnalogDevice> AnalogFromButton::Create(const Common::ParamPackage& params) {
225 const std::string null_engine = Common::ParamPackage{{"engine", "null"}}.Serialize();
226 auto up = Input::CreateDevice<Input::ButtonDevice>(params.Get("up", null_engine));
227 auto down = Input::CreateDevice<Input::ButtonDevice>(params.Get("down", null_engine));
228 auto left = Input::CreateDevice<Input::ButtonDevice>(params.Get("left", null_engine));
229 auto right = Input::CreateDevice<Input::ButtonDevice>(params.Get("right", null_engine));
230 auto modifier = Input::CreateDevice<Input::ButtonDevice>(params.Get("modifier", null_engine));
231 auto modifier_scale = params.Get("modifier_scale", 0.5f);
232 auto modifier_angle = params.Get("modifier_angle", 5.5f);
233 return std::make_unique<Analog>(std::move(up), std::move(down), std::move(left),
234 std::move(right), std::move(modifier), modifier_scale,
235 modifier_angle);
236}
237
238} // 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 @@
2// Licensed under GPLv2+ 2// Licensed under GPLv2+
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <chrono> 5#include <fmt/format.h>
6#include <thread>
7
8#include <libusb.h> 6#include <libusb.h>
9 7
10#include "common/logging/log.h" 8#include "common/logging/log.h"
11#include "common/param_package.h" 9#include "common/param_package.h"
12#include "common/settings_input.h" 10#include "common/settings_input.h"
13#include "input_common/gcadapter/gc_adapter.h" 11#include "common/thread.h"
12#include "input_common/drivers/gc_adapter.h"
13
14namespace InputCommon {
15
16class LibUSBContext {
17public:
18 explicit LibUSBContext() {
19 init_result = libusb_init(&ctx);
20 }
21
22 ~LibUSBContext() {
23 libusb_exit(ctx);
24 }
25
26 LibUSBContext& operator=(const LibUSBContext&) = delete;
27 LibUSBContext(const LibUSBContext&) = delete;
28
29 LibUSBContext& operator=(LibUSBContext&&) noexcept = delete;
30 LibUSBContext(LibUSBContext&&) noexcept = delete;
31
32 [[nodiscard]] int InitResult() const noexcept {
33 return init_result;
34 }
35
36 [[nodiscard]] libusb_context* get() noexcept {
37 return ctx;
38 }
39
40private:
41 libusb_context* ctx;
42 int init_result{};
43};
44
45class LibUSBDeviceHandle {
46public:
47 explicit LibUSBDeviceHandle(libusb_context* ctx, uint16_t vid, uint16_t pid) noexcept {
48 handle = libusb_open_device_with_vid_pid(ctx, vid, pid);
49 }
50
51 ~LibUSBDeviceHandle() noexcept {
52 if (handle) {
53 libusb_release_interface(handle, 1);
54 libusb_close(handle);
55 }
56 }
14 57
15namespace GCAdapter { 58 LibUSBDeviceHandle& operator=(const LibUSBDeviceHandle&) = delete;
59 LibUSBDeviceHandle(const LibUSBDeviceHandle&) = delete;
16 60
17Adapter::Adapter() { 61 LibUSBDeviceHandle& operator=(LibUSBDeviceHandle&&) noexcept = delete;
18 if (usb_adapter_handle != nullptr) { 62 LibUSBDeviceHandle(LibUSBDeviceHandle&&) noexcept = delete;
63
64 [[nodiscard]] libusb_device_handle* get() noexcept {
65 return handle;
66 }
67
68private:
69 libusb_device_handle* handle{};
70};
71
72GCAdapter::GCAdapter(const std::string& input_engine_) : InputEngine(input_engine_) {
73 if (usb_adapter_handle) {
19 return; 74 return;
20 } 75 }
21 LOG_INFO(Input, "GC Adapter Initialization started"); 76 LOG_DEBUG(Input, "Initialization started");
22 77
23 const int init_res = libusb_init(&libusb_ctx); 78 libusb_ctx = std::make_unique<LibUSBContext>();
79 const int init_res = libusb_ctx->InitResult();
24 if (init_res == LIBUSB_SUCCESS) { 80 if (init_res == LIBUSB_SUCCESS) {
25 adapter_scan_thread = std::thread(&Adapter::AdapterScanThread, this); 81 adapter_scan_thread =
82 std::jthread([this](std::stop_token stop_token) { AdapterScanThread(stop_token); });
26 } else { 83 } else {
27 LOG_ERROR(Input, "libusb could not be initialized. failed with error = {}", init_res); 84 LOG_ERROR(Input, "libusb could not be initialized. failed with error = {}", init_res);
28 } 85 }
29} 86}
30 87
31Adapter::~Adapter() { 88GCAdapter::~GCAdapter() {
32 Reset(); 89 Reset();
33} 90}
34 91
35void Adapter::AdapterInputThread() { 92void GCAdapter::AdapterInputThread(std::stop_token stop_token) {
36 LOG_DEBUG(Input, "GC Adapter input thread started"); 93 LOG_DEBUG(Input, "Input thread started");
94 Common::SetCurrentThreadName("yuzu:input:GCAdapter");
37 s32 payload_size{}; 95 s32 payload_size{};
38 AdapterPayload adapter_payload{}; 96 AdapterPayload adapter_payload{};
39 97
40 if (adapter_scan_thread.joinable()) { 98 adapter_scan_thread = {};
41 adapter_scan_thread.join();
42 }
43 99
44 while (adapter_input_thread_running) { 100 while (!stop_token.stop_requested()) {
45 libusb_interrupt_transfer(usb_adapter_handle, input_endpoint, adapter_payload.data(), 101 libusb_interrupt_transfer(usb_adapter_handle->get(), input_endpoint, adapter_payload.data(),
46 static_cast<s32>(adapter_payload.size()), &payload_size, 16); 102 static_cast<s32>(adapter_payload.size()), &payload_size, 16);
47 if (IsPayloadCorrect(adapter_payload, payload_size)) { 103 if (IsPayloadCorrect(adapter_payload, payload_size)) {
48 UpdateControllers(adapter_payload); 104 UpdateControllers(adapter_payload);
@@ -52,19 +108,20 @@ void Adapter::AdapterInputThread() {
52 } 108 }
53 109
54 if (restart_scan_thread) { 110 if (restart_scan_thread) {
55 adapter_scan_thread = std::thread(&Adapter::AdapterScanThread, this); 111 adapter_scan_thread =
112 std::jthread([this](std::stop_token token) { AdapterScanThread(token); });
56 restart_scan_thread = false; 113 restart_scan_thread = false;
57 } 114 }
58} 115}
59 116
60bool Adapter::IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size) { 117bool GCAdapter::IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size) {
61 if (payload_size != static_cast<s32>(adapter_payload.size()) || 118 if (payload_size != static_cast<s32>(adapter_payload.size()) ||
62 adapter_payload[0] != LIBUSB_DT_HID) { 119 adapter_payload[0] != LIBUSB_DT_HID) {
63 LOG_DEBUG(Input, "Error reading payload (size: {}, type: {:02x})", payload_size, 120 LOG_DEBUG(Input, "Error reading payload (size: {}, type: {:02x})", payload_size,
64 adapter_payload[0]); 121 adapter_payload[0]);
65 if (input_error_counter++ > 20) { 122 if (input_error_counter++ > 20) {
66 LOG_ERROR(Input, "GC adapter timeout, Is the adapter connected?"); 123 LOG_ERROR(Input, "Timeout, Is the adapter connected?");
67 adapter_input_thread_running = false; 124 adapter_input_thread.request_stop();
68 restart_scan_thread = true; 125 restart_scan_thread = true;
69 } 126 }
70 return false; 127 return false;
@@ -74,7 +131,7 @@ bool Adapter::IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payloa
74 return true; 131 return true;
75} 132}
76 133
77void Adapter::UpdateControllers(const AdapterPayload& adapter_payload) { 134void GCAdapter::UpdateControllers(const AdapterPayload& adapter_payload) {
78 for (std::size_t port = 0; port < pads.size(); ++port) { 135 for (std::size_t port = 0; port < pads.size(); ++port) {
79 const std::size_t offset = 1 + (9 * port); 136 const std::size_t offset = 1 + (9 * port);
80 const auto type = static_cast<ControllerTypes>(adapter_payload[offset] >> 4); 137 const auto type = static_cast<ControllerTypes>(adapter_payload[offset] >> 4);
@@ -84,23 +141,24 @@ void Adapter::UpdateControllers(const AdapterPayload& adapter_payload) {
84 const u8 b2 = adapter_payload[offset + 2]; 141 const u8 b2 = adapter_payload[offset + 2];
85 UpdateStateButtons(port, b1, b2); 142 UpdateStateButtons(port, b1, b2);
86 UpdateStateAxes(port, adapter_payload); 143 UpdateStateAxes(port, adapter_payload);
87 if (configuring) {
88 UpdateYuzuSettings(port);
89 }
90 } 144 }
91 } 145 }
92} 146}
93 147
94void Adapter::UpdatePadType(std::size_t port, ControllerTypes pad_type) { 148void GCAdapter::UpdatePadType(std::size_t port, ControllerTypes pad_type) {
95 if (pads[port].type == pad_type) { 149 if (pads[port].type == pad_type) {
96 return; 150 return;
97 } 151 }
98 // Device changed reset device and set new type 152 // Device changed reset device and set new type
99 ResetDevice(port); 153 pads[port].axis_origin = {};
154 pads[port].reset_origin_counter = {};
155 pads[port].enable_vibration = {};
156 pads[port].rumble_amplitude = {};
100 pads[port].type = pad_type; 157 pads[port].type = pad_type;
101} 158}
102 159
103void Adapter::UpdateStateButtons(std::size_t port, u8 b1, u8 b2) { 160void GCAdapter::UpdateStateButtons(std::size_t port, [[maybe_unused]] u8 b1,
161 [[maybe_unused]] u8 b2) {
104 if (port >= pads.size()) { 162 if (port >= pads.size()) {
105 return; 163 return;
106 } 164 }
@@ -116,25 +174,21 @@ void Adapter::UpdateStateButtons(std::size_t port, u8 b1, u8 b2) {
116 PadButton::TriggerR, 174 PadButton::TriggerR,
117 PadButton::TriggerL, 175 PadButton::TriggerL,
118 }; 176 };
119 pads[port].buttons = 0; 177
120 for (std::size_t i = 0; i < b1_buttons.size(); ++i) { 178 for (std::size_t i = 0; i < b1_buttons.size(); ++i) {
121 if ((b1 & (1U << i)) != 0) { 179 const bool button_status = (b1 & (1U << i)) != 0;
122 pads[port].buttons = 180 const int button = static_cast<int>(b1_buttons[i]);
123 static_cast<u16>(pads[port].buttons | static_cast<u16>(b1_buttons[i])); 181 SetButton(pads[port].identifier, button, button_status);
124 pads[port].last_button = b1_buttons[i];
125 }
126 } 182 }
127 183
128 for (std::size_t j = 0; j < b2_buttons.size(); ++j) { 184 for (std::size_t j = 0; j < b2_buttons.size(); ++j) {
129 if ((b2 & (1U << j)) != 0) { 185 const bool button_status = (b2 & (1U << j)) != 0;
130 pads[port].buttons = 186 const int button = static_cast<int>(b2_buttons[j]);
131 static_cast<u16>(pads[port].buttons | static_cast<u16>(b2_buttons[j])); 187 SetButton(pads[port].identifier, button, button_status);
132 pads[port].last_button = b2_buttons[j];
133 }
134 } 188 }
135} 189}
136 190
137void Adapter::UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload) { 191void GCAdapter::UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload) {
138 if (port >= pads.size()) { 192 if (port >= pads.size()) {
139 return; 193 return;
140 } 194 }
@@ -155,134 +209,63 @@ void Adapter::UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_pa
155 pads[port].axis_origin[index] = axis_value; 209 pads[port].axis_origin[index] = axis_value;
156 pads[port].reset_origin_counter++; 210 pads[port].reset_origin_counter++;
157 } 211 }
158 pads[port].axis_values[index] = 212 const f32 axis_status = (axis_value - pads[port].axis_origin[index]) / 100.0f;
159 static_cast<s16>(axis_value - pads[port].axis_origin[index]); 213 SetAxis(pads[port].identifier, static_cast<int>(index), axis_status);
160 }
161}
162
163void Adapter::UpdateYuzuSettings(std::size_t port) {
164 if (port >= pads.size()) {
165 return;
166 }
167
168 constexpr u8 axis_threshold = 50;
169 GCPadStatus pad_status = {.port = port};
170
171 if (pads[port].buttons != 0) {
172 pad_status.button = pads[port].last_button;
173 pad_queue.Push(pad_status);
174 }
175
176 // Accounting for a threshold here to ensure an intentional press
177 for (std::size_t i = 0; i < pads[port].axis_values.size(); ++i) {
178 const s16 value = pads[port].axis_values[i];
179
180 if (value > axis_threshold || value < -axis_threshold) {
181 pad_status.axis = static_cast<PadAxes>(i);
182 pad_status.axis_value = value;
183 pad_status.axis_threshold = axis_threshold;
184 pad_queue.Push(pad_status);
185 }
186 }
187}
188
189void Adapter::UpdateVibrations() {
190 // Use 8 states to keep the switching between on/off fast enough for
191 // a human to not notice the difference between switching from on/off
192 // More states = more rumble strengths = slower update time
193 constexpr u8 vibration_states = 8;
194
195 vibration_counter = (vibration_counter + 1) % vibration_states;
196
197 for (GCController& pad : pads) {
198 const bool vibrate = pad.rumble_amplitude > vibration_counter;
199 vibration_changed |= vibrate != pad.enable_vibration;
200 pad.enable_vibration = vibrate;
201 }
202 SendVibrations();
203}
204
205void Adapter::SendVibrations() {
206 if (!rumble_enabled || !vibration_changed) {
207 return;
208 }
209 s32 size{};
210 constexpr u8 rumble_command = 0x11;
211 const u8 p1 = pads[0].enable_vibration;
212 const u8 p2 = pads[1].enable_vibration;
213 const u8 p3 = pads[2].enable_vibration;
214 const u8 p4 = pads[3].enable_vibration;
215 std::array<u8, 5> payload = {rumble_command, p1, p2, p3, p4};
216 const int err = libusb_interrupt_transfer(usb_adapter_handle, output_endpoint, payload.data(),
217 static_cast<s32>(payload.size()), &size, 16);
218 if (err) {
219 LOG_DEBUG(Input, "Adapter libusb write failed: {}", libusb_error_name(err));
220 if (output_error_counter++ > 5) {
221 LOG_ERROR(Input, "GC adapter output timeout, Rumble disabled");
222 rumble_enabled = false;
223 }
224 return;
225 } 214 }
226 output_error_counter = 0;
227 vibration_changed = false;
228} 215}
229 216
230bool Adapter::RumblePlay(std::size_t port, u8 amplitude) { 217void GCAdapter::AdapterScanThread(std::stop_token stop_token) {
231 pads[port].rumble_amplitude = amplitude; 218 Common::SetCurrentThreadName("yuzu:input:ScanGCAdapter");
232 219 usb_adapter_handle = nullptr;
233 return rumble_enabled; 220 pads = {};
234} 221 while (!stop_token.stop_requested() && !Setup()) {
235 222 std::this_thread::sleep_for(std::chrono::seconds(2));
236void Adapter::AdapterScanThread() {
237 adapter_scan_thread_running = true;
238 adapter_input_thread_running = false;
239 if (adapter_input_thread.joinable()) {
240 adapter_input_thread.join();
241 }
242 ClearLibusbHandle();
243 ResetDevices();
244 while (adapter_scan_thread_running && !adapter_input_thread_running) {
245 Setup();
246 std::this_thread::sleep_for(std::chrono::seconds(1));
247 } 223 }
248} 224}
249 225
250void Adapter::Setup() { 226bool GCAdapter::Setup() {
251 usb_adapter_handle = libusb_open_device_with_vid_pid(libusb_ctx, 0x057e, 0x0337); 227 constexpr u16 nintendo_vid = 0x057e;
252 228 constexpr u16 gc_adapter_pid = 0x0337;
253 if (usb_adapter_handle == NULL) { 229 usb_adapter_handle =
254 return; 230 std::make_unique<LibUSBDeviceHandle>(libusb_ctx->get(), nintendo_vid, gc_adapter_pid);
231 if (!usb_adapter_handle->get()) {
232 return false;
255 } 233 }
256 if (!CheckDeviceAccess()) { 234 if (!CheckDeviceAccess()) {
257 ClearLibusbHandle(); 235 usb_adapter_handle = nullptr;
258 return; 236 return false;
259 } 237 }
260 238
261 libusb_device* device = libusb_get_device(usb_adapter_handle); 239 libusb_device* const device = libusb_get_device(usb_adapter_handle->get());
262 240
263 LOG_INFO(Input, "GC adapter is now connected"); 241 LOG_INFO(Input, "GC adapter is now connected");
264 // GC Adapter found and accessible, registering it 242 // GC Adapter found and accessible, registering it
265 if (GetGCEndpoint(device)) { 243 if (GetGCEndpoint(device)) {
266 adapter_scan_thread_running = false;
267 adapter_input_thread_running = true;
268 rumble_enabled = true; 244 rumble_enabled = true;
269 input_error_counter = 0; 245 input_error_counter = 0;
270 output_error_counter = 0; 246 output_error_counter = 0;
271 adapter_input_thread = std::thread(&Adapter::AdapterInputThread, this);
272 }
273}
274 247
275bool Adapter::CheckDeviceAccess() { 248 std::size_t port = 0;
276 // This fixes payload problems from offbrand GCAdapters 249 for (GCController& pad : pads) {
277 const s32 control_transfer_error = 250 pad.identifier = {
278 libusb_control_transfer(usb_adapter_handle, 0x21, 11, 0x0001, 0, nullptr, 0, 1000); 251 .guid = Common::UUID{Common::INVALID_UUID},
279 if (control_transfer_error < 0) { 252 .port = port++,
280 LOG_ERROR(Input, "libusb_control_transfer failed with error= {}", control_transfer_error); 253 .pad = 0,
254 };
255 PreSetController(pad.identifier);
256 }
257
258 adapter_input_thread =
259 std::jthread([this](std::stop_token stop_token) { AdapterInputThread(stop_token); });
260 return true;
281 } 261 }
262 return false;
263}
282 264
283 s32 kernel_driver_error = libusb_kernel_driver_active(usb_adapter_handle, 0); 265bool GCAdapter::CheckDeviceAccess() {
266 s32 kernel_driver_error = libusb_kernel_driver_active(usb_adapter_handle->get(), 0);
284 if (kernel_driver_error == 1) { 267 if (kernel_driver_error == 1) {
285 kernel_driver_error = libusb_detach_kernel_driver(usb_adapter_handle, 0); 268 kernel_driver_error = libusb_detach_kernel_driver(usb_adapter_handle->get(), 0);
286 if (kernel_driver_error != 0 && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) { 269 if (kernel_driver_error != 0 && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) {
287 LOG_ERROR(Input, "libusb_detach_kernel_driver failed with error = {}", 270 LOG_ERROR(Input, "libusb_detach_kernel_driver failed with error = {}",
288 kernel_driver_error); 271 kernel_driver_error);
@@ -290,23 +273,28 @@ bool Adapter::CheckDeviceAccess() {
290 } 273 }
291 274
292 if (kernel_driver_error && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) { 275 if (kernel_driver_error && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) {
293 libusb_close(usb_adapter_handle);
294 usb_adapter_handle = nullptr; 276 usb_adapter_handle = nullptr;
295 return false; 277 return false;
296 } 278 }
297 279
298 const int interface_claim_error = libusb_claim_interface(usb_adapter_handle, 0); 280 const int interface_claim_error = libusb_claim_interface(usb_adapter_handle->get(), 0);
299 if (interface_claim_error) { 281 if (interface_claim_error) {
300 LOG_ERROR(Input, "libusb_claim_interface failed with error = {}", interface_claim_error); 282 LOG_ERROR(Input, "libusb_claim_interface failed with error = {}", interface_claim_error);
301 libusb_close(usb_adapter_handle);
302 usb_adapter_handle = nullptr; 283 usb_adapter_handle = nullptr;
303 return false; 284 return false;
304 } 285 }
305 286
287 // This fixes payload problems from offbrand GCAdapters
288 const s32 control_transfer_error =
289 libusb_control_transfer(usb_adapter_handle->get(), 0x21, 11, 0x0001, 0, nullptr, 0, 1000);
290 if (control_transfer_error < 0) {
291 LOG_ERROR(Input, "libusb_control_transfer failed with error= {}", control_transfer_error);
292 }
293
306 return true; 294 return true;
307} 295}
308 296
309bool Adapter::GetGCEndpoint(libusb_device* device) { 297bool GCAdapter::GetGCEndpoint(libusb_device* device) {
310 libusb_config_descriptor* config = nullptr; 298 libusb_config_descriptor* config = nullptr;
311 const int config_descriptor_return = libusb_get_config_descriptor(device, 0, &config); 299 const int config_descriptor_return = libusb_get_config_descriptor(device, 0, &config);
312 if (config_descriptor_return != LIBUSB_SUCCESS) { 300 if (config_descriptor_return != LIBUSB_SUCCESS) {
@@ -332,77 +320,95 @@ bool Adapter::GetGCEndpoint(libusb_device* device) {
332 // This transfer seems to be responsible for clearing the state of the adapter 320 // This transfer seems to be responsible for clearing the state of the adapter
333 // Used to clear the "busy" state of when the device is unexpectedly unplugged 321 // Used to clear the "busy" state of when the device is unexpectedly unplugged
334 unsigned char clear_payload = 0x13; 322 unsigned char clear_payload = 0x13;
335 libusb_interrupt_transfer(usb_adapter_handle, output_endpoint, &clear_payload, 323 libusb_interrupt_transfer(usb_adapter_handle->get(), output_endpoint, &clear_payload,
336 sizeof(clear_payload), nullptr, 16); 324 sizeof(clear_payload), nullptr, 16);
337 return true; 325 return true;
338} 326}
339 327
340void Adapter::JoinThreads() { 328Common::Input::VibrationError GCAdapter::SetRumble(const PadIdentifier& identifier,
341 restart_scan_thread = false; 329 const Common::Input::VibrationStatus vibration) {
342 adapter_input_thread_running = false; 330 const auto mean_amplitude = (vibration.low_amplitude + vibration.high_amplitude) * 0.5f;
343 adapter_scan_thread_running = false; 331 const auto processed_amplitude =
332 static_cast<u8>((mean_amplitude + std::pow(mean_amplitude, 0.3f)) * 0.5f * 0x8);
344 333
345 if (adapter_scan_thread.joinable()) { 334 pads[identifier.port].rumble_amplitude = processed_amplitude;
346 adapter_scan_thread.join();
347 }
348 335
349 if (adapter_input_thread.joinable()) { 336 if (!rumble_enabled) {
350 adapter_input_thread.join(); 337 return Common::Input::VibrationError::Disabled;
351 } 338 }
339 return Common::Input::VibrationError::None;
352} 340}
353 341
354void Adapter::ClearLibusbHandle() { 342void GCAdapter::UpdateVibrations() {
355 if (usb_adapter_handle) { 343 // Use 8 states to keep the switching between on/off fast enough for
356 libusb_release_interface(usb_adapter_handle, 1); 344 // a human to feel different vibration strenght
357 libusb_close(usb_adapter_handle); 345 // More states == more rumble strengths == slower update time
358 usb_adapter_handle = nullptr; 346 constexpr u8 vibration_states = 8;
347
348 vibration_counter = (vibration_counter + 1) % vibration_states;
349
350 for (GCController& pad : pads) {
351 const bool vibrate = pad.rumble_amplitude > vibration_counter;
352 vibration_changed |= vibrate != pad.enable_vibration;
353 pad.enable_vibration = vibrate;
359 } 354 }
355 SendVibrations();
360} 356}
361 357
362void Adapter::ResetDevices() { 358void GCAdapter::SendVibrations() {
363 for (std::size_t i = 0; i < pads.size(); ++i) { 359 if (!rumble_enabled || !vibration_changed) {
364 ResetDevice(i); 360 return;
361 }
362 s32 size{};
363 constexpr u8 rumble_command = 0x11;
364 const u8 p1 = pads[0].enable_vibration;
365 const u8 p2 = pads[1].enable_vibration;
366 const u8 p3 = pads[2].enable_vibration;
367 const u8 p4 = pads[3].enable_vibration;
368 std::array<u8, 5> payload = {rumble_command, p1, p2, p3, p4};
369 const int err =
370 libusb_interrupt_transfer(usb_adapter_handle->get(), output_endpoint, payload.data(),
371 static_cast<s32>(payload.size()), &size, 16);
372 if (err) {
373 LOG_DEBUG(Input, "Libusb write failed: {}", libusb_error_name(err));
374 if (output_error_counter++ > 5) {
375 LOG_ERROR(Input, "Output timeout, Rumble disabled");
376 rumble_enabled = false;
377 }
378 return;
365 } 379 }
380 output_error_counter = 0;
381 vibration_changed = false;
366} 382}
367 383
368void Adapter::ResetDevice(std::size_t port) { 384bool GCAdapter::DeviceConnected(std::size_t port) const {
369 pads[port].type = ControllerTypes::None; 385 return pads[port].type != ControllerTypes::None;
370 pads[port].enable_vibration = false;
371 pads[port].rumble_amplitude = 0;
372 pads[port].buttons = 0;
373 pads[port].last_button = PadButton::Undefined;
374 pads[port].axis_values.fill(0);
375 pads[port].reset_origin_counter = 0;
376} 386}
377 387
378void Adapter::Reset() { 388void GCAdapter::Reset() {
379 JoinThreads(); 389 adapter_scan_thread = {};
380 ClearLibusbHandle(); 390 adapter_input_thread = {};
381 ResetDevices(); 391 usb_adapter_handle = nullptr;
382 392 pads = {};
383 if (libusb_ctx) { 393 libusb_ctx = nullptr;
384 libusb_exit(libusb_ctx);
385 }
386} 394}
387 395
388std::vector<Common::ParamPackage> Adapter::GetInputDevices() const { 396std::vector<Common::ParamPackage> GCAdapter::GetInputDevices() const {
389 std::vector<Common::ParamPackage> devices; 397 std::vector<Common::ParamPackage> devices;
390 for (std::size_t port = 0; port < pads.size(); ++port) { 398 for (std::size_t port = 0; port < pads.size(); ++port) {
391 if (!DeviceConnected(port)) { 399 if (!DeviceConnected(port)) {
392 continue; 400 continue;
393 } 401 }
394 std::string name = fmt::format("Gamecube Controller {}", port + 1); 402 Common::ParamPackage identifier{};
395 devices.emplace_back(Common::ParamPackage{ 403 identifier.Set("engine", GetEngineName());
396 {"class", "gcpad"}, 404 identifier.Set("display", fmt::format("Gamecube Controller {}", port + 1));
397 {"display", std::move(name)}, 405 identifier.Set("port", static_cast<int>(port));
398 {"port", std::to_string(port)}, 406 devices.emplace_back(identifier);
399 });
400 } 407 }
401 return devices; 408 return devices;
402} 409}
403 410
404InputCommon::ButtonMapping Adapter::GetButtonMappingForDevice( 411ButtonMapping GCAdapter::GetButtonMappingForDevice(const Common::ParamPackage& params) {
405 const Common::ParamPackage& params) const {
406 // This list is missing ZL/ZR since those are not considered buttons. 412 // This list is missing ZL/ZR since those are not considered buttons.
407 // We will add those afterwards 413 // We will add those afterwards
408 // This list also excludes any button that can't be really mapped 414 // This list also excludes any button that can't be really mapped
@@ -425,47 +431,49 @@ InputCommon::ButtonMapping Adapter::GetButtonMappingForDevice(
425 return {}; 431 return {};
426 } 432 }
427 433
428 InputCommon::ButtonMapping mapping{}; 434 ButtonMapping mapping{};
429 for (const auto& [switch_button, gcadapter_button] : switch_to_gcadapter_button) { 435 for (const auto& [switch_button, gcadapter_button] : switch_to_gcadapter_button) {
430 Common::ParamPackage button_params({{"engine", "gcpad"}}); 436 Common::ParamPackage button_params{};
437 button_params.Set("engine", GetEngineName());
431 button_params.Set("port", params.Get("port", 0)); 438 button_params.Set("port", params.Get("port", 0));
432 button_params.Set("button", static_cast<int>(gcadapter_button)); 439 button_params.Set("button", static_cast<int>(gcadapter_button));
433 mapping.insert_or_assign(switch_button, std::move(button_params)); 440 mapping.insert_or_assign(switch_button, std::move(button_params));
434 } 441 }
435 442
436 // Add the missing bindings for ZL/ZR 443 // Add the missing bindings for ZL/ZR
437 static constexpr std::array<std::pair<Settings::NativeButton::Values, PadAxes>, 2> 444 static constexpr std::array<std::tuple<Settings::NativeButton::Values, PadButton, PadAxes>, 2>
438 switch_to_gcadapter_axis = { 445 switch_to_gcadapter_axis = {
439 std::pair{Settings::NativeButton::ZL, PadAxes::TriggerLeft}, 446 std::tuple{Settings::NativeButton::ZL, PadButton::TriggerL, PadAxes::TriggerLeft},
440 {Settings::NativeButton::ZR, PadAxes::TriggerRight}, 447 {Settings::NativeButton::ZR, PadButton::TriggerR, PadAxes::TriggerRight},
441 }; 448 };
442 for (const auto& [switch_button, gcadapter_axis] : switch_to_gcadapter_axis) { 449 for (const auto& [switch_button, gcadapter_buton, gcadapter_axis] : switch_to_gcadapter_axis) {
443 Common::ParamPackage button_params({{"engine", "gcpad"}}); 450 Common::ParamPackage button_params{};
451 button_params.Set("engine", GetEngineName());
444 button_params.Set("port", params.Get("port", 0)); 452 button_params.Set("port", params.Get("port", 0));
445 button_params.Set("button", static_cast<s32>(PadButton::Stick)); 453 button_params.Set("button", static_cast<s32>(gcadapter_buton));
446 button_params.Set("axis", static_cast<s32>(gcadapter_axis)); 454 button_params.Set("axis", static_cast<s32>(gcadapter_axis));
447 button_params.Set("threshold", 0.5f); 455 button_params.Set("threshold", 0.5f);
456 button_params.Set("range", 1.9f);
448 button_params.Set("direction", "+"); 457 button_params.Set("direction", "+");
449 mapping.insert_or_assign(switch_button, std::move(button_params)); 458 mapping.insert_or_assign(switch_button, std::move(button_params));
450 } 459 }
451 return mapping; 460 return mapping;
452} 461}
453 462
454InputCommon::AnalogMapping Adapter::GetAnalogMappingForDevice( 463AnalogMapping GCAdapter::GetAnalogMappingForDevice(const Common::ParamPackage& params) {
455 const Common::ParamPackage& params) const {
456 if (!params.Has("port")) { 464 if (!params.Has("port")) {
457 return {}; 465 return {};
458 } 466 }
459 467
460 InputCommon::AnalogMapping mapping = {}; 468 AnalogMapping mapping = {};
461 Common::ParamPackage left_analog_params; 469 Common::ParamPackage left_analog_params;
462 left_analog_params.Set("engine", "gcpad"); 470 left_analog_params.Set("engine", GetEngineName());
463 left_analog_params.Set("port", params.Get("port", 0)); 471 left_analog_params.Set("port", params.Get("port", 0));
464 left_analog_params.Set("axis_x", static_cast<int>(PadAxes::StickX)); 472 left_analog_params.Set("axis_x", static_cast<int>(PadAxes::StickX));
465 left_analog_params.Set("axis_y", static_cast<int>(PadAxes::StickY)); 473 left_analog_params.Set("axis_y", static_cast<int>(PadAxes::StickY));
466 mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params)); 474 mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params));
467 Common::ParamPackage right_analog_params; 475 Common::ParamPackage right_analog_params;
468 right_analog_params.Set("engine", "gcpad"); 476 right_analog_params.Set("engine", GetEngineName());
469 right_analog_params.Set("port", params.Get("port", 0)); 477 right_analog_params.Set("port", params.Get("port", 0));
470 right_analog_params.Set("axis_x", static_cast<int>(PadAxes::SubstickX)); 478 right_analog_params.Set("axis_x", static_cast<int>(PadAxes::SubstickX));
471 right_analog_params.Set("axis_y", static_cast<int>(PadAxes::SubstickY)); 479 right_analog_params.Set("axis_y", static_cast<int>(PadAxes::SubstickY));
@@ -473,34 +481,47 @@ InputCommon::AnalogMapping Adapter::GetAnalogMappingForDevice(
473 return mapping; 481 return mapping;
474} 482}
475 483
476bool Adapter::DeviceConnected(std::size_t port) const { 484Common::Input::ButtonNames GCAdapter::GetUIButtonName(const Common::ParamPackage& params) const {
477 return pads[port].type != ControllerTypes::None; 485 PadButton button = static_cast<PadButton>(params.Get("button", 0));
478} 486 switch (button) {
479 487 case PadButton::ButtonLeft:
480void Adapter::BeginConfiguration() { 488 return Common::Input::ButtonNames::ButtonLeft;
481 pad_queue.Clear(); 489 case PadButton::ButtonRight:
482 configuring = true; 490 return Common::Input::ButtonNames::ButtonRight;
483} 491 case PadButton::ButtonDown:
484 492 return Common::Input::ButtonNames::ButtonDown;
485void Adapter::EndConfiguration() { 493 case PadButton::ButtonUp:
486 pad_queue.Clear(); 494 return Common::Input::ButtonNames::ButtonUp;
487 configuring = false; 495 case PadButton::TriggerZ:
488} 496 return Common::Input::ButtonNames::TriggerZ;
489 497 case PadButton::TriggerR:
490Common::SPSCQueue<GCPadStatus>& Adapter::GetPadQueue() { 498 return Common::Input::ButtonNames::TriggerR;
491 return pad_queue; 499 case PadButton::TriggerL:
492} 500 return Common::Input::ButtonNames::TriggerL;
493 501 case PadButton::ButtonA:
494const Common::SPSCQueue<GCPadStatus>& Adapter::GetPadQueue() const { 502 return Common::Input::ButtonNames::ButtonA;
495 return pad_queue; 503 case PadButton::ButtonB:
504 return Common::Input::ButtonNames::ButtonB;
505 case PadButton::ButtonX:
506 return Common::Input::ButtonNames::ButtonX;
507 case PadButton::ButtonY:
508 return Common::Input::ButtonNames::ButtonY;
509 case PadButton::ButtonStart:
510 return Common::Input::ButtonNames::ButtonStart;
511 default:
512 return Common::Input::ButtonNames::Undefined;
513 }
496} 514}
497 515
498GCController& Adapter::GetPadState(std::size_t port) { 516Common::Input::ButtonNames GCAdapter::GetUIName(const Common::ParamPackage& params) const {
499 return pads.at(port); 517 if (params.Has("button")) {
500} 518 return GetUIButtonName(params);
519 }
520 if (params.Has("axis")) {
521 return Common::Input::ButtonNames::Value;
522 }
501 523
502const GCController& Adapter::GetPadState(std::size_t port) const { 524 return Common::Input::ButtonNames::Invalid;
503 return pads.at(port);
504} 525}
505 526
506} // namespace GCAdapter 527} // 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 @@
1// Copyright 2014 Dolphin Emulator Project
2// Licensed under GPLv2+
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8#include <memory>
9#include <mutex>
10#include <stop_token>
11#include <string>
12#include <thread>
13
14#include "input_common/input_engine.h"
15
16struct libusb_context;
17struct libusb_device;
18struct libusb_device_handle;
19
20namespace InputCommon {
21
22class LibUSBContext;
23class LibUSBDeviceHandle;
24
25class GCAdapter : public InputCommon::InputEngine {
26public:
27 explicit GCAdapter(const std::string& input_engine_);
28 ~GCAdapter();
29
30 Common::Input::VibrationError SetRumble(
31 const PadIdentifier& identifier, const Common::Input::VibrationStatus vibration) override;
32
33 /// Used for automapping features
34 std::vector<Common::ParamPackage> GetInputDevices() const override;
35 ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override;
36 AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override;
37 Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override;
38
39private:
40 enum class PadButton {
41 Undefined = 0x0000,
42 ButtonLeft = 0x0001,
43 ButtonRight = 0x0002,
44 ButtonDown = 0x0004,
45 ButtonUp = 0x0008,
46 TriggerZ = 0x0010,
47 TriggerR = 0x0020,
48 TriggerL = 0x0040,
49 ButtonA = 0x0100,
50 ButtonB = 0x0200,
51 ButtonX = 0x0400,
52 ButtonY = 0x0800,
53 ButtonStart = 0x1000,
54 };
55
56 enum class PadAxes : u8 {
57 StickX,
58 StickY,
59 SubstickX,
60 SubstickY,
61 TriggerLeft,
62 TriggerRight,
63 Undefined,
64 };
65
66 enum class ControllerTypes {
67 None,
68 Wired,
69 Wireless,
70 };
71
72 struct GCController {
73 ControllerTypes type = ControllerTypes::None;
74 PadIdentifier identifier{};
75 bool enable_vibration = false;
76 u8 rumble_amplitude{};
77 std::array<u8, 6> axis_origin{};
78 u8 reset_origin_counter{};
79 };
80
81 using AdapterPayload = std::array<u8, 37>;
82
83 void UpdatePadType(std::size_t port, ControllerTypes pad_type);
84 void UpdateControllers(const AdapterPayload& adapter_payload);
85 void UpdateStateButtons(std::size_t port, u8 b1, u8 b2);
86 void UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload);
87
88 void AdapterInputThread(std::stop_token stop_token);
89
90 void AdapterScanThread(std::stop_token stop_token);
91
92 bool IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size);
93
94 /// For use in initialization, querying devices to find the adapter
95 bool Setup();
96
97 /// Returns true if we successfully gain access to GC Adapter
98 bool CheckDeviceAccess();
99
100 /// Captures GC Adapter endpoint address
101 /// Returns true if the endpoint was set correctly
102 bool GetGCEndpoint(libusb_device* device);
103
104 /// Returns true if there is a device connected to port
105 bool DeviceConnected(std::size_t port) const;
106
107 /// For shutting down, clear all data, join all threads, release usb
108 void Reset();
109
110 void UpdateVibrations();
111
112 /// Updates vibration state of all controllers
113 void SendVibrations();
114
115 Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const;
116
117 std::unique_ptr<LibUSBDeviceHandle> usb_adapter_handle;
118 std::array<GCController, 4> pads;
119
120 std::jthread adapter_input_thread;
121 std::jthread adapter_scan_thread;
122 bool restart_scan_thread{};
123
124 std::unique_ptr<LibUSBContext> libusb_ctx;
125
126 u8 input_endpoint{0};
127 u8 output_endpoint{0};
128 u8 input_error_counter{0};
129 u8 output_error_counter{0};
130 int vibration_counter{0};
131
132 bool rumble_enabled{true};
133 bool vibration_changed{true};
134};
135} // 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 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included
4
5#include "common/param_package.h"
6#include "common/settings_input.h"
7#include "input_common/drivers/keyboard.h"
8
9namespace InputCommon {
10
11constexpr PadIdentifier key_identifier = {
12 .guid = Common::UUID{Common::INVALID_UUID},
13 .port = 0,
14 .pad = 0,
15};
16constexpr PadIdentifier keyboard_key_identifier = {
17 .guid = Common::UUID{Common::INVALID_UUID},
18 .port = 1,
19 .pad = 0,
20};
21constexpr PadIdentifier keyboard_modifier_identifier = {
22 .guid = Common::UUID{Common::INVALID_UUID},
23 .port = 1,
24 .pad = 1,
25};
26
27Keyboard::Keyboard(const std::string& input_engine_) : InputEngine(input_engine_) {
28 // Keyboard is broken into 3 diferent sets:
29 // key: Unfiltered intended for controllers.
30 // keyboard_key: Allows only Settings::NativeKeyboard::Keys intended for keyboard emulation.
31 // keyboard_modifier: Allows only Settings::NativeKeyboard::Modifiers intended for keyboard
32 // emulation.
33 PreSetController(key_identifier);
34 PreSetController(keyboard_key_identifier);
35 PreSetController(keyboard_modifier_identifier);
36}
37
38void Keyboard::PressKey(int key_code) {
39 SetButton(key_identifier, key_code, true);
40}
41
42void Keyboard::ReleaseKey(int key_code) {
43 SetButton(key_identifier, key_code, false);
44}
45
46void Keyboard::PressKeyboardKey(int key_index) {
47 if (key_index == Settings::NativeKeyboard::None) {
48 return;
49 }
50 SetButton(keyboard_key_identifier, key_index, true);
51}
52
53void Keyboard::ReleaseKeyboardKey(int key_index) {
54 if (key_index == Settings::NativeKeyboard::None) {
55 return;
56 }
57 SetButton(keyboard_key_identifier, key_index, false);
58}
59
60void Keyboard::SetKeyboardModifiers(int key_modifiers) {
61 for (int i = 0; i < 32; ++i) {
62 bool key_value = ((key_modifiers >> i) & 0x1) != 0;
63 SetButton(keyboard_modifier_identifier, i, key_value);
64 // Use the modifier to press the key button equivalent
65 switch (i) {
66 case Settings::NativeKeyboard::LeftControl:
67 SetButton(keyboard_key_identifier, Settings::NativeKeyboard::LeftControlKey, key_value);
68 break;
69 case Settings::NativeKeyboard::LeftShift:
70 SetButton(keyboard_key_identifier, Settings::NativeKeyboard::LeftShiftKey, key_value);
71 break;
72 case Settings::NativeKeyboard::LeftAlt:
73 SetButton(keyboard_key_identifier, Settings::NativeKeyboard::LeftAltKey, key_value);
74 break;
75 case Settings::NativeKeyboard::LeftMeta:
76 SetButton(keyboard_key_identifier, Settings::NativeKeyboard::LeftMetaKey, key_value);
77 break;
78 case Settings::NativeKeyboard::RightControl:
79 SetButton(keyboard_key_identifier, Settings::NativeKeyboard::RightControlKey,
80 key_value);
81 break;
82 case Settings::NativeKeyboard::RightShift:
83 SetButton(keyboard_key_identifier, Settings::NativeKeyboard::RightShiftKey, key_value);
84 break;
85 case Settings::NativeKeyboard::RightAlt:
86 SetButton(keyboard_key_identifier, Settings::NativeKeyboard::RightAltKey, key_value);
87 break;
88 case Settings::NativeKeyboard::RightMeta:
89 SetButton(keyboard_key_identifier, Settings::NativeKeyboard::RightMetaKey, key_value);
90 break;
91 default:
92 // Other modifier keys should be pressed with PressKey since they stay enabled until
93 // next press
94 break;
95 }
96 }
97}
98
99void Keyboard::ReleaseAllKeys() {
100 ResetButtonState();
101}
102
103std::vector<Common::ParamPackage> Keyboard::GetInputDevices() const {
104 std::vector<Common::ParamPackage> devices;
105 devices.emplace_back(Common::ParamPackage{
106 {"engine", GetEngineName()},
107 {"display", "Keyboard Only"},
108 });
109 return devices;
110}
111
112} // 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 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included
4
5#pragma once
6
7#include "input_common/input_engine.h"
8
9namespace InputCommon {
10
11/**
12 * A button device factory representing a keyboard. It receives keyboard events and forward them
13 * to all button devices it created.
14 */
15class Keyboard final : public InputCommon::InputEngine {
16public:
17 explicit Keyboard(const std::string& input_engine_);
18
19 /**
20 * Sets the status of all buttons bound with the key to pressed
21 * @param key_code the code of the key to press
22 */
23 void PressKey(int key_code);
24
25 /**
26 * Sets the status of all buttons bound with the key to released
27 * @param key_code the code of the key to release
28 */
29 void ReleaseKey(int key_code);
30
31 /**
32 * Sets the status of the keyboard key to pressed
33 * @param key_index index of the key to press
34 */
35 void PressKeyboardKey(int key_index);
36
37 /**
38 * Sets the status of the keyboard key to released
39 * @param key_index index of the key to release
40 */
41 void ReleaseKeyboardKey(int key_index);
42
43 /**
44 * Sets the status of all keyboard modifier keys
45 * @param key_modifiers the code of the key to release
46 */
47 void SetKeyboardModifiers(int key_modifiers);
48
49 /// Sets all keys to the non pressed state
50 void ReleaseAllKeys();
51
52 /// Used for automapping features
53 std::vector<Common::ParamPackage> GetInputDevices() const override;
54};
55
56} // 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 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included
4
5#include <stop_token>
6#include <thread>
7#include <fmt/format.h>
8
9#include "common/param_package.h"
10#include "common/settings.h"
11#include "common/thread.h"
12#include "input_common/drivers/mouse.h"
13
14namespace InputCommon {
15constexpr int mouse_axis_x = 0;
16constexpr int mouse_axis_y = 1;
17constexpr int wheel_axis_x = 2;
18constexpr int wheel_axis_y = 3;
19constexpr int touch_axis_x = 10;
20constexpr int touch_axis_y = 11;
21constexpr PadIdentifier identifier = {
22 .guid = Common::UUID{Common::INVALID_UUID},
23 .port = 0,
24 .pad = 0,
25};
26
27Mouse::Mouse(const std::string& input_engine_) : InputEngine(input_engine_) {
28 PreSetController(identifier);
29 PreSetAxis(identifier, mouse_axis_x);
30 PreSetAxis(identifier, mouse_axis_y);
31 PreSetAxis(identifier, wheel_axis_x);
32 PreSetAxis(identifier, wheel_axis_y);
33 PreSetAxis(identifier, touch_axis_x);
34 PreSetAxis(identifier, touch_axis_x);
35 update_thread = std::jthread([this](std::stop_token stop_token) { UpdateThread(stop_token); });
36}
37
38void Mouse::UpdateThread(std::stop_token stop_token) {
39 Common::SetCurrentThreadName("yuzu:input:Mouse");
40 constexpr int update_time = 10;
41 while (!stop_token.stop_requested()) {
42 if (Settings::values.mouse_panning && !Settings::values.mouse_enabled) {
43 // Slow movement by 4%
44 last_mouse_change *= 0.96f;
45 const float sensitivity =
46 Settings::values.mouse_panning_sensitivity.GetValue() * 0.022f;
47 SetAxis(identifier, mouse_axis_x, last_mouse_change.x * sensitivity);
48 SetAxis(identifier, mouse_axis_y, -last_mouse_change.y * sensitivity);
49 }
50
51 if (mouse_panning_timout++ > 20) {
52 StopPanning();
53 }
54 std::this_thread::sleep_for(std::chrono::milliseconds(update_time));
55 }
56}
57
58void Mouse::MouseMove(int x, int y, f32 touch_x, f32 touch_y, int center_x, int center_y) {
59 // If native mouse is enabled just set the screen coordinates
60 if (Settings::values.mouse_enabled) {
61 SetAxis(identifier, mouse_axis_x, touch_x);
62 SetAxis(identifier, mouse_axis_y, touch_y);
63 return;
64 }
65
66 SetAxis(identifier, touch_axis_x, touch_x);
67 SetAxis(identifier, touch_axis_y, touch_y);
68
69 if (Settings::values.mouse_panning) {
70 auto mouse_change =
71 (Common::MakeVec(x, y) - Common::MakeVec(center_x, center_y)).Cast<float>();
72 mouse_panning_timout = 0;
73
74 const auto move_distance = mouse_change.Length();
75 if (move_distance == 0) {
76 return;
77 }
78
79 // Make slow movements at least 3 units on lenght
80 if (move_distance < 3.0f) {
81 // Normalize value
82 mouse_change /= move_distance;
83 mouse_change *= 3.0f;
84 }
85
86 // Average mouse movements
87 last_mouse_change = (last_mouse_change * 0.91f) + (mouse_change * 0.09f);
88
89 const auto last_move_distance = last_mouse_change.Length();
90
91 // Make fast movements clamp to 8 units on lenght
92 if (last_move_distance > 8.0f) {
93 // Normalize value
94 last_mouse_change /= last_move_distance;
95 last_mouse_change *= 8.0f;
96 }
97
98 // Ignore average if it's less than 1 unit and use current movement value
99 if (last_move_distance < 1.0f) {
100 last_mouse_change = mouse_change / mouse_change.Length();
101 }
102
103 return;
104 }
105
106 if (button_pressed) {
107 const auto mouse_move = Common::MakeVec<int>(x, y) - mouse_origin;
108 const float sensitivity = Settings::values.mouse_panning_sensitivity.GetValue() * 0.0012f;
109 SetAxis(identifier, mouse_axis_x, static_cast<float>(mouse_move.x) * sensitivity);
110 SetAxis(identifier, mouse_axis_y, static_cast<float>(-mouse_move.y) * sensitivity);
111 }
112}
113
114void Mouse::PressButton(int x, int y, f32 touch_x, f32 touch_y, MouseButton button) {
115 SetAxis(identifier, touch_axis_x, touch_x);
116 SetAxis(identifier, touch_axis_y, touch_y);
117 SetButton(identifier, static_cast<int>(button), true);
118 // Set initial analog parameters
119 mouse_origin = {x, y};
120 last_mouse_position = {x, y};
121 button_pressed = true;
122}
123
124void Mouse::ReleaseButton(MouseButton button) {
125 SetButton(identifier, static_cast<int>(button), false);
126
127 if (!Settings::values.mouse_panning && !Settings::values.mouse_enabled) {
128 SetAxis(identifier, mouse_axis_x, 0);
129 SetAxis(identifier, mouse_axis_y, 0);
130 }
131 button_pressed = false;
132}
133
134void Mouse::MouseWheelChange(int x, int y) {
135 wheel_position.x += x;
136 wheel_position.y += y;
137 SetAxis(identifier, wheel_axis_x, static_cast<f32>(wheel_position.x));
138 SetAxis(identifier, wheel_axis_y, static_cast<f32>(wheel_position.y));
139}
140
141void Mouse::ReleaseAllButtons() {
142 ResetButtonState();
143 button_pressed = false;
144}
145
146void Mouse::StopPanning() {
147 last_mouse_change = {};
148}
149
150std::vector<Common::ParamPackage> Mouse::GetInputDevices() const {
151 std::vector<Common::ParamPackage> devices;
152 devices.emplace_back(Common::ParamPackage{
153 {"engine", GetEngineName()},
154 {"display", "Keyboard/Mouse"},
155 });
156 return devices;
157}
158
159AnalogMapping Mouse::GetAnalogMappingForDevice(
160 [[maybe_unused]] const Common::ParamPackage& params) {
161 // Only overwrite different buttons from default
162 AnalogMapping mapping = {};
163 Common::ParamPackage right_analog_params;
164 right_analog_params.Set("engine", GetEngineName());
165 right_analog_params.Set("axis_x", 0);
166 right_analog_params.Set("axis_y", 1);
167 right_analog_params.Set("threshold", 0.5f);
168 right_analog_params.Set("range", 1.0f);
169 right_analog_params.Set("deadzone", 0.0f);
170 mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params));
171 return mapping;
172}
173
174Common::Input::ButtonNames Mouse::GetUIName(const Common::ParamPackage& params) const {
175 if (params.Has("button")) {
176 return Common::Input::ButtonNames::Value;
177 }
178 if (params.Has("axis")) {
179 return Common::Input::ButtonNames::Value;
180 }
181
182 return Common::Input::ButtonNames::Invalid;
183}
184
185} // 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 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included
4
5#pragma once
6
7#include <stop_token>
8#include <thread>
9
10#include "common/vector_math.h"
11#include "input_common/input_engine.h"
12
13namespace InputCommon {
14
15enum class MouseButton {
16 Left,
17 Right,
18 Wheel,
19 Backward,
20 Forward,
21 Task,
22 Extra,
23 Undefined,
24};
25
26/**
27 * A button device factory representing a keyboard. It receives keyboard events and forward them
28 * to all button devices it created.
29 */
30class Mouse final : public InputCommon::InputEngine {
31public:
32 explicit Mouse(const std::string& input_engine_);
33
34 /**
35 * Signals that mouse has moved.
36 * @param x the x-coordinate of the cursor
37 * @param y the y-coordinate of the cursor
38 * @param center_x the x-coordinate of the middle of the screen
39 * @param center_y the y-coordinate of the middle of the screen
40 */
41 void MouseMove(int x, int y, f32 touch_x, f32 touch_y, int center_x, int center_y);
42
43 /**
44 * Sets the status of all buttons bound with the key to pressed
45 * @param key_code the code of the key to press
46 */
47 void PressButton(int x, int y, f32 touch_x, f32 touch_y, MouseButton button);
48
49 /**
50 * Sets the status of all buttons bound with the key to released
51 * @param key_code the code of the key to release
52 */
53 void ReleaseButton(MouseButton button);
54
55 /**
56 * Sets the status of the mouse wheel
57 * @param x delta movement in the x direction
58 * @param y delta movement in the y direction
59 */
60 void MouseWheelChange(int x, int y);
61
62 void ReleaseAllButtons();
63
64 std::vector<Common::ParamPackage> GetInputDevices() const override;
65 AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override;
66 Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override;
67
68private:
69 void UpdateThread(std::stop_token stop_token);
70 void StopPanning();
71
72 Common::Vec2<int> mouse_origin;
73 Common::Vec2<int> last_mouse_position;
74 Common::Vec2<float> last_mouse_change;
75 Common::Vec2<int> wheel_position;
76 bool button_pressed;
77 int mouse_panning_timout{};
78 std::jthread update_thread;
79};
80
81} // 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 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "common/logging/log.h"
6#include "common/math_util.h"
7#include "common/param_package.h"
8#include "common/settings.h"
9#include "common/thread.h"
10#include "common/vector_math.h"
11#include "input_common/drivers/sdl_driver.h"
12
13namespace InputCommon {
14
15namespace {
16std::string GetGUID(SDL_Joystick* joystick) {
17 const SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick);
18 char guid_str[33];
19 SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str));
20 return guid_str;
21}
22} // Anonymous namespace
23
24static int SDLEventWatcher(void* user_data, SDL_Event* event) {
25 auto* const sdl_state = static_cast<SDLDriver*>(user_data);
26
27 sdl_state->HandleGameControllerEvent(*event);
28
29 return 0;
30}
31
32class SDLJoystick {
33public:
34 SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick,
35 SDL_GameController* game_controller)
36 : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose},
37 sdl_controller{game_controller, &SDL_GameControllerClose} {
38 EnableMotion();
39 }
40
41 void EnableMotion() {
42 if (sdl_controller) {
43 SDL_GameController* controller = sdl_controller.get();
44 if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL) && !has_accel) {
45 SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE);
46 has_accel = true;
47 }
48 if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) && !has_gyro) {
49 SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_TRUE);
50 has_gyro = true;
51 }
52 }
53 }
54
55 bool HasGyro() const {
56 return has_gyro;
57 }
58
59 bool HasAccel() const {
60 return has_accel;
61 }
62
63 bool UpdateMotion(SDL_ControllerSensorEvent event) {
64 constexpr float gravity_constant = 9.80665f;
65 std::lock_guard lock{mutex};
66 const u64 time_difference = event.timestamp - last_motion_update;
67 last_motion_update = event.timestamp;
68 switch (event.sensor) {
69 case SDL_SENSOR_ACCEL: {
70 motion.accel_x = -event.data[0] / gravity_constant;
71 motion.accel_y = event.data[2] / gravity_constant;
72 motion.accel_z = -event.data[1] / gravity_constant;
73 break;
74 }
75 case SDL_SENSOR_GYRO: {
76 motion.gyro_x = event.data[0] / (Common::PI * 2);
77 motion.gyro_y = -event.data[2] / (Common::PI * 2);
78 motion.gyro_z = event.data[1] / (Common::PI * 2);
79 break;
80 }
81 }
82
83 // Ignore duplicated timestamps
84 if (time_difference == 0) {
85 return false;
86 }
87 motion.delta_timestamp = time_difference * 1000;
88 return true;
89 }
90
91 BasicMotion GetMotion() {
92 return motion;
93 }
94
95 bool RumblePlay(const Common::Input::VibrationStatus vibration) {
96 constexpr u32 rumble_max_duration_ms = 1000;
97 if (sdl_controller) {
98 return SDL_GameControllerRumble(
99 sdl_controller.get(), static_cast<u16>(vibration.low_amplitude),
100 static_cast<u16>(vibration.high_amplitude), rumble_max_duration_ms) != -1;
101 } else if (sdl_joystick) {
102 return SDL_JoystickRumble(sdl_joystick.get(), static_cast<u16>(vibration.low_amplitude),
103 static_cast<u16>(vibration.high_amplitude),
104 rumble_max_duration_ms) != -1;
105 }
106
107 return false;
108 }
109
110 bool HasHDRumble() const {
111 if (sdl_controller) {
112 return (SDL_GameControllerGetType(sdl_controller.get()) ==
113 SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO);
114 }
115 return false;
116 }
117 /**
118 * The Pad identifier of the joystick
119 */
120 const PadIdentifier GetPadIdentifier() const {
121 return {
122 .guid = Common::UUID{guid},
123 .port = static_cast<std::size_t>(port),
124 .pad = 0,
125 };
126 }
127
128 /**
129 * The guid of the joystick
130 */
131 const std::string& GetGUID() const {
132 return guid;
133 }
134
135 /**
136 * The number of joystick from the same type that were connected before this joystick
137 */
138 int GetPort() const {
139 return port;
140 }
141
142 SDL_Joystick* GetSDLJoystick() const {
143 return sdl_joystick.get();
144 }
145
146 SDL_GameController* GetSDLGameController() const {
147 return sdl_controller.get();
148 }
149
150 void SetSDLJoystick(SDL_Joystick* joystick, SDL_GameController* controller) {
151 sdl_joystick.reset(joystick);
152 sdl_controller.reset(controller);
153 }
154
155 bool IsJoyconLeft() const {
156 const std::string controller_name = GetControllerName();
157 if (std::strstr(controller_name.c_str(), "Joy-Con Left") != nullptr) {
158 return true;
159 }
160 if (std::strstr(controller_name.c_str(), "Joy-Con (L)") != nullptr) {
161 return true;
162 }
163 return false;
164 }
165
166 bool IsJoyconRight() const {
167 const std::string controller_name = GetControllerName();
168 if (std::strstr(controller_name.c_str(), "Joy-Con Right") != nullptr) {
169 return true;
170 }
171 if (std::strstr(controller_name.c_str(), "Joy-Con (R)") != nullptr) {
172 return true;
173 }
174 return false;
175 }
176
177 BatteryLevel GetBatteryLevel() {
178 const auto level = SDL_JoystickCurrentPowerLevel(sdl_joystick.get());
179 switch (level) {
180 case SDL_JOYSTICK_POWER_EMPTY:
181 return BatteryLevel::Empty;
182 case SDL_JOYSTICK_POWER_LOW:
183 return BatteryLevel::Critical;
184 case SDL_JOYSTICK_POWER_MEDIUM:
185 return BatteryLevel::Low;
186 case SDL_JOYSTICK_POWER_FULL:
187 return BatteryLevel::Medium;
188 case SDL_JOYSTICK_POWER_MAX:
189 return BatteryLevel::Full;
190 case SDL_JOYSTICK_POWER_UNKNOWN:
191 case SDL_JOYSTICK_POWER_WIRED:
192 default:
193 return BatteryLevel::Charging;
194 }
195 }
196
197 std::string GetControllerName() const {
198 if (sdl_controller) {
199 switch (SDL_GameControllerGetType(sdl_controller.get())) {
200 case SDL_CONTROLLER_TYPE_XBOX360:
201 return "XBox 360 Controller";
202 case SDL_CONTROLLER_TYPE_XBOXONE:
203 return "XBox One Controller";
204 default:
205 break;
206 }
207 const auto name = SDL_GameControllerName(sdl_controller.get());
208 if (name) {
209 return name;
210 }
211 }
212
213 if (sdl_joystick) {
214 const auto name = SDL_JoystickName(sdl_joystick.get());
215 if (name) {
216 return name;
217 }
218 }
219
220 return "Unknown";
221 }
222
223private:
224 std::string guid;
225 int port;
226 std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick;
227 std::unique_ptr<SDL_GameController, decltype(&SDL_GameControllerClose)> sdl_controller;
228 mutable std::mutex mutex;
229
230 u64 last_motion_update{};
231 bool has_gyro{false};
232 bool has_accel{false};
233 BasicMotion motion;
234};
235
236std::shared_ptr<SDLJoystick> SDLDriver::GetSDLJoystickByGUID(const std::string& guid, int port) {
237 std::lock_guard lock{joystick_map_mutex};
238 const auto it = joystick_map.find(guid);
239
240 if (it != joystick_map.end()) {
241 while (it->second.size() <= static_cast<std::size_t>(port)) {
242 auto joystick = std::make_shared<SDLJoystick>(guid, static_cast<int>(it->second.size()),
243 nullptr, nullptr);
244 it->second.emplace_back(std::move(joystick));
245 }
246
247 return it->second[static_cast<std::size_t>(port)];
248 }
249
250 auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr, nullptr);
251
252 return joystick_map[guid].emplace_back(std::move(joystick));
253}
254
255std::shared_ptr<SDLJoystick> SDLDriver::GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) {
256 auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id);
257 const std::string guid = GetGUID(sdl_joystick);
258
259 std::lock_guard lock{joystick_map_mutex};
260 const auto map_it = joystick_map.find(guid);
261
262 if (map_it == joystick_map.end()) {
263 return nullptr;
264 }
265
266 const auto vec_it = std::find_if(map_it->second.begin(), map_it->second.end(),
267 [&sdl_joystick](const auto& joystick) {
268 return joystick->GetSDLJoystick() == sdl_joystick;
269 });
270
271 if (vec_it == map_it->second.end()) {
272 return nullptr;
273 }
274
275 return *vec_it;
276}
277
278void SDLDriver::InitJoystick(int joystick_index) {
279 SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index);
280 SDL_GameController* sdl_gamecontroller = nullptr;
281
282 if (SDL_IsGameController(joystick_index)) {
283 sdl_gamecontroller = SDL_GameControllerOpen(joystick_index);
284 }
285
286 if (!sdl_joystick) {
287 LOG_ERROR(Input, "Failed to open joystick {}", joystick_index);
288 return;
289 }
290
291 const std::string guid = GetGUID(sdl_joystick);
292
293 std::lock_guard lock{joystick_map_mutex};
294 if (joystick_map.find(guid) == joystick_map.end()) {
295 auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_gamecontroller);
296 PreSetController(joystick->GetPadIdentifier());
297 SetBattery(joystick->GetPadIdentifier(), joystick->GetBatteryLevel());
298 joystick_map[guid].emplace_back(std::move(joystick));
299 return;
300 }
301
302 auto& joystick_guid_list = joystick_map[guid];
303 const auto joystick_it =
304 std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(),
305 [](const auto& joystick) { return !joystick->GetSDLJoystick(); });
306
307 if (joystick_it != joystick_guid_list.end()) {
308 (*joystick_it)->SetSDLJoystick(sdl_joystick, sdl_gamecontroller);
309 return;
310 }
311
312 const int port = static_cast<int>(joystick_guid_list.size());
313 auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick, sdl_gamecontroller);
314 PreSetController(joystick->GetPadIdentifier());
315 SetBattery(joystick->GetPadIdentifier(), joystick->GetBatteryLevel());
316 joystick_guid_list.emplace_back(std::move(joystick));
317}
318
319void SDLDriver::CloseJoystick(SDL_Joystick* sdl_joystick) {
320 const std::string guid = GetGUID(sdl_joystick);
321
322 std::lock_guard lock{joystick_map_mutex};
323 // This call to guid is safe since the joystick is guaranteed to be in the map
324 const auto& joystick_guid_list = joystick_map[guid];
325 const auto joystick_it = std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(),
326 [&sdl_joystick](const auto& joystick) {
327 return joystick->GetSDLJoystick() == sdl_joystick;
328 });
329
330 if (joystick_it != joystick_guid_list.end()) {
331 (*joystick_it)->SetSDLJoystick(nullptr, nullptr);
332 }
333}
334
335void SDLDriver::HandleGameControllerEvent(const SDL_Event& event) {
336 switch (event.type) {
337 case SDL_JOYBUTTONUP: {
338 if (const auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) {
339 const PadIdentifier identifier = joystick->GetPadIdentifier();
340 SetButton(identifier, event.jbutton.button, false);
341 }
342 break;
343 }
344 case SDL_JOYBUTTONDOWN: {
345 if (const auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) {
346 const PadIdentifier identifier = joystick->GetPadIdentifier();
347 SetButton(identifier, event.jbutton.button, true);
348 }
349 break;
350 }
351 case SDL_JOYHATMOTION: {
352 if (const auto joystick = GetSDLJoystickBySDLID(event.jhat.which)) {
353 const PadIdentifier identifier = joystick->GetPadIdentifier();
354 SetHatButton(identifier, event.jhat.hat, event.jhat.value);
355 }
356 break;
357 }
358 case SDL_JOYAXISMOTION: {
359 if (const auto joystick = GetSDLJoystickBySDLID(event.jaxis.which)) {
360 const PadIdentifier identifier = joystick->GetPadIdentifier();
361 SetAxis(identifier, event.jaxis.axis, event.jaxis.value / 32767.0f);
362 }
363 break;
364 }
365 case SDL_CONTROLLERSENSORUPDATE: {
366 if (auto joystick = GetSDLJoystickBySDLID(event.csensor.which)) {
367 if (joystick->UpdateMotion(event.csensor)) {
368 const PadIdentifier identifier = joystick->GetPadIdentifier();
369 SetMotion(identifier, 0, joystick->GetMotion());
370 };
371 }
372 break;
373 }
374 case SDL_JOYDEVICEREMOVED:
375 LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which);
376 CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which));
377 break;
378 case SDL_JOYDEVICEADDED:
379 LOG_DEBUG(Input, "Controller connected with device index {}", event.jdevice.which);
380 InitJoystick(event.jdevice.which);
381 break;
382 }
383}
384
385void SDLDriver::CloseJoysticks() {
386 std::lock_guard lock{joystick_map_mutex};
387 joystick_map.clear();
388}
389
390SDLDriver::SDLDriver(const std::string& input_engine_) : InputEngine(input_engine_) {
391 Common::SetCurrentThreadName("yuzu:input:SDL");
392
393 if (!Settings::values.enable_raw_input) {
394 // Disable raw input. When enabled this setting causes SDL to die when a web applet opens
395 SDL_SetHint(SDL_HINT_JOYSTICK_RAWINPUT, "0");
396 }
397
398 // Prevent SDL from adding undesired axis
399 SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0");
400
401 // Enable HIDAPI rumble. This prevents SDL from disabling motion on PS4 and PS5 controllers
402 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1");
403 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
404 SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
405
406 // Use hidapi driver for joycons. This will allow joycons to be detected as a GameController and
407 // not a generic one
408 SDL_SetHint("SDL_JOYSTICK_HIDAPI_JOY_CONS", "1");
409
410 // Turn off Pro controller home led
411 SDL_SetHint("SDL_JOYSTICK_HIDAPI_SWITCH_HOME_LED", "0");
412
413 // If the frontend is going to manage the event loop, then we don't start one here
414 start_thread = SDL_WasInit(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) == 0;
415 if (start_thread && SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) < 0) {
416 LOG_CRITICAL(Input, "SDL_Init failed with: {}", SDL_GetError());
417 return;
418 }
419
420 SDL_AddEventWatch(&SDLEventWatcher, this);
421
422 initialized = true;
423 if (start_thread) {
424 poll_thread = std::thread([this] {
425 using namespace std::chrono_literals;
426 while (initialized) {
427 SDL_PumpEvents();
428 std::this_thread::sleep_for(1ms);
429 }
430 });
431 }
432 // Because the events for joystick connection happens before we have our event watcher added, we
433 // can just open all the joysticks right here
434 for (int i = 0; i < SDL_NumJoysticks(); ++i) {
435 InitJoystick(i);
436 }
437}
438
439SDLDriver::~SDLDriver() {
440 CloseJoysticks();
441 SDL_DelEventWatch(&SDLEventWatcher, this);
442
443 initialized = false;
444 if (start_thread) {
445 poll_thread.join();
446 SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER);
447 }
448}
449
450std::vector<Common::ParamPackage> SDLDriver::GetInputDevices() const {
451 std::vector<Common::ParamPackage> devices;
452 std::unordered_map<int, std::shared_ptr<SDLJoystick>> joycon_pairs;
453 for (const auto& [key, value] : joystick_map) {
454 for (const auto& joystick : value) {
455 if (!joystick->GetSDLJoystick()) {
456 continue;
457 }
458 const std::string name =
459 fmt::format("{} {}", joystick->GetControllerName(), joystick->GetPort());
460 devices.emplace_back(Common::ParamPackage{
461 {"engine", GetEngineName()},
462 {"display", std::move(name)},
463 {"guid", joystick->GetGUID()},
464 {"port", std::to_string(joystick->GetPort())},
465 });
466 if (joystick->IsJoyconLeft()) {
467 joycon_pairs.insert_or_assign(joystick->GetPort(), joystick);
468 }
469 }
470 }
471
472 // Add dual controllers
473 for (const auto& [key, value] : joystick_map) {
474 for (const auto& joystick : value) {
475 if (joystick->IsJoyconRight()) {
476 if (!joycon_pairs.contains(joystick->GetPort())) {
477 continue;
478 }
479 const auto joystick2 = joycon_pairs.at(joystick->GetPort());
480
481 const std::string name =
482 fmt::format("{} {}", "Nintendo Dual Joy-Con", joystick->GetPort());
483 devices.emplace_back(Common::ParamPackage{
484 {"engine", GetEngineName()},
485 {"display", std::move(name)},
486 {"guid", joystick->GetGUID()},
487 {"guid2", joystick2->GetGUID()},
488 {"port", std::to_string(joystick->GetPort())},
489 });
490 }
491 }
492 }
493 return devices;
494}
495Common::Input::VibrationError SDLDriver::SetRumble(const PadIdentifier& identifier,
496 const Common::Input::VibrationStatus vibration) {
497 const auto joystick =
498 GetSDLJoystickByGUID(identifier.guid.Format(), static_cast<int>(identifier.port));
499 const auto process_amplitude_exp = [](f32 amplitude, f32 factor) {
500 return (amplitude + std::pow(amplitude, factor)) * 0.5f * 0xFFFF;
501 };
502
503 // Default exponential curve for rumble
504 f32 factor = 0.35f;
505
506 // If vibration is set as a linear output use a flatter value
507 if (vibration.type == Common::Input::VibrationAmplificationType::Linear) {
508 factor = 0.5f;
509 }
510
511 // Amplitude for HD rumble needs no modification
512 if (joystick->HasHDRumble()) {
513 factor = 1.0f;
514 }
515
516 const Common::Input::VibrationStatus new_vibration{
517 .low_amplitude = process_amplitude_exp(vibration.low_amplitude, factor),
518 .low_frequency = vibration.low_frequency,
519 .high_amplitude = process_amplitude_exp(vibration.high_amplitude, factor),
520 .high_frequency = vibration.high_frequency,
521 .type = Common::Input::VibrationAmplificationType::Exponential,
522 };
523
524 if (!joystick->RumblePlay(new_vibration)) {
525 return Common::Input::VibrationError::Unknown;
526 }
527
528 return Common::Input::VibrationError::None;
529}
530Common::ParamPackage SDLDriver::BuildAnalogParamPackageForButton(int port, std::string guid,
531 s32 axis, float value) const {
532 Common::ParamPackage params{};
533 params.Set("engine", GetEngineName());
534 params.Set("port", port);
535 params.Set("guid", std::move(guid));
536 params.Set("axis", axis);
537 params.Set("threshold", "0.5");
538 params.Set("invert", value < 0 ? "-" : "+");
539 return params;
540}
541
542Common::ParamPackage SDLDriver::BuildButtonParamPackageForButton(int port, std::string guid,
543 s32 button) const {
544 Common::ParamPackage params{};
545 params.Set("engine", GetEngineName());
546 params.Set("port", port);
547 params.Set("guid", std::move(guid));
548 params.Set("button", button);
549 return params;
550}
551
552Common::ParamPackage SDLDriver::BuildHatParamPackageForButton(int port, std::string guid, s32 hat,
553 u8 value) const {
554 Common::ParamPackage params{};
555 params.Set("engine", GetEngineName());
556 params.Set("port", port);
557 params.Set("guid", std::move(guid));
558 params.Set("hat", hat);
559 params.Set("direction", GetHatButtonName(value));
560 return params;
561}
562
563Common::ParamPackage SDLDriver::BuildMotionParam(int port, std::string guid) const {
564 Common::ParamPackage params{};
565 params.Set("engine", GetEngineName());
566 params.Set("motion", 0);
567 params.Set("port", port);
568 params.Set("guid", std::move(guid));
569 return params;
570}
571
572Common::ParamPackage SDLDriver::BuildParamPackageForBinding(
573 int port, const std::string& guid, const SDL_GameControllerButtonBind& binding) const {
574 switch (binding.bindType) {
575 case SDL_CONTROLLER_BINDTYPE_NONE:
576 break;
577 case SDL_CONTROLLER_BINDTYPE_AXIS:
578 return BuildAnalogParamPackageForButton(port, guid, binding.value.axis);
579 case SDL_CONTROLLER_BINDTYPE_BUTTON:
580 return BuildButtonParamPackageForButton(port, guid, binding.value.button);
581 case SDL_CONTROLLER_BINDTYPE_HAT:
582 return BuildHatParamPackageForButton(port, guid, binding.value.hat.hat,
583 static_cast<u8>(binding.value.hat.hat_mask));
584 }
585 return {};
586}
587
588Common::ParamPackage SDLDriver::BuildParamPackageForAnalog(PadIdentifier identifier, int axis_x,
589 int axis_y, float offset_x,
590 float offset_y) const {
591 Common::ParamPackage params;
592 params.Set("engine", GetEngineName());
593 params.Set("port", static_cast<int>(identifier.port));
594 params.Set("guid", identifier.guid.Format());
595 params.Set("axis_x", axis_x);
596 params.Set("axis_y", axis_y);
597 params.Set("offset_x", offset_x);
598 params.Set("offset_y", offset_y);
599 params.Set("invert_x", "+");
600 params.Set("invert_y", "+");
601 return params;
602}
603
604ButtonMapping SDLDriver::GetButtonMappingForDevice(const Common::ParamPackage& params) {
605 if (!params.Has("guid") || !params.Has("port")) {
606 return {};
607 }
608 const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
609
610 auto* controller = joystick->GetSDLGameController();
611 if (controller == nullptr) {
612 return {};
613 }
614
615 // This list is missing ZL/ZR since those are not considered buttons in SDL GameController.
616 // We will add those afterwards
617 // This list also excludes Screenshot since theres not really a mapping for that
618 ButtonBindings switch_to_sdl_button;
619
620 if (SDL_GameControllerGetType(controller) == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO) {
621 switch_to_sdl_button = GetNintendoButtonBinding(joystick);
622 } else {
623 switch_to_sdl_button = GetDefaultButtonBinding();
624 }
625
626 // Add the missing bindings for ZL/ZR
627 static constexpr ZButtonBindings switch_to_sdl_axis{{
628 {Settings::NativeButton::ZL, SDL_CONTROLLER_AXIS_TRIGGERLEFT},
629 {Settings::NativeButton::ZR, SDL_CONTROLLER_AXIS_TRIGGERRIGHT},
630 }};
631
632 // Parameters contain two joysticks return dual
633 if (params.Has("guid2")) {
634 const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0));
635
636 if (joystick2->GetSDLGameController() != nullptr) {
637 return GetDualControllerMapping(joystick, joystick2, switch_to_sdl_button,
638 switch_to_sdl_axis);
639 }
640 }
641
642 return GetSingleControllerMapping(joystick, switch_to_sdl_button, switch_to_sdl_axis);
643}
644
645ButtonBindings SDLDriver::GetDefaultButtonBinding() const {
646 return {
647 std::pair{Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_B},
648 {Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_A},
649 {Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_Y},
650 {Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_X},
651 {Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK},
652 {Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK},
653 {Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
654 {Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
655 {Settings::NativeButton::Plus, SDL_CONTROLLER_BUTTON_START},
656 {Settings::NativeButton::Minus, SDL_CONTROLLER_BUTTON_BACK},
657 {Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT},
658 {Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP},
659 {Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT},
660 {Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN},
661 {Settings::NativeButton::SL, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
662 {Settings::NativeButton::SR, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
663 {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE},
664 };
665}
666
667ButtonBindings SDLDriver::GetNintendoButtonBinding(
668 const std::shared_ptr<SDLJoystick>& joystick) const {
669 // Default SL/SR mapping for pro controllers
670 auto sl_button = SDL_CONTROLLER_BUTTON_LEFTSHOULDER;
671 auto sr_button = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER;
672
673 if (joystick->IsJoyconLeft()) {
674 sl_button = SDL_CONTROLLER_BUTTON_PADDLE2;
675 sr_button = SDL_CONTROLLER_BUTTON_PADDLE4;
676 }
677 if (joystick->IsJoyconRight()) {
678 sl_button = SDL_CONTROLLER_BUTTON_PADDLE3;
679 sr_button = SDL_CONTROLLER_BUTTON_PADDLE1;
680 }
681
682 return {
683 std::pair{Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_A},
684 {Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_B},
685 {Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_X},
686 {Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_Y},
687 {Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK},
688 {Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK},
689 {Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
690 {Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
691 {Settings::NativeButton::Plus, SDL_CONTROLLER_BUTTON_START},
692 {Settings::NativeButton::Minus, SDL_CONTROLLER_BUTTON_BACK},
693 {Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT},
694 {Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP},
695 {Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT},
696 {Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN},
697 {Settings::NativeButton::SL, sl_button},
698 {Settings::NativeButton::SR, sr_button},
699 {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE},
700 };
701}
702
703ButtonMapping SDLDriver::GetSingleControllerMapping(
704 const std::shared_ptr<SDLJoystick>& joystick, const ButtonBindings& switch_to_sdl_button,
705 const ZButtonBindings& switch_to_sdl_axis) const {
706 ButtonMapping mapping;
707 mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size());
708 auto* controller = joystick->GetSDLGameController();
709
710 for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) {
711 const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button);
712 mapping.insert_or_assign(
713 switch_button,
714 BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
715 }
716 for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) {
717 const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis);
718 mapping.insert_or_assign(
719 switch_button,
720 BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
721 }
722
723 return mapping;
724}
725
726ButtonMapping SDLDriver::GetDualControllerMapping(const std::shared_ptr<SDLJoystick>& joystick,
727 const std::shared_ptr<SDLJoystick>& joystick2,
728 const ButtonBindings& switch_to_sdl_button,
729 const ZButtonBindings& switch_to_sdl_axis) const {
730 ButtonMapping mapping;
731 mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size());
732 auto* controller = joystick->GetSDLGameController();
733 auto* controller2 = joystick2->GetSDLGameController();
734
735 for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) {
736 if (IsButtonOnLeftSide(switch_button)) {
737 const auto& binding = SDL_GameControllerGetBindForButton(controller2, sdl_button);
738 mapping.insert_or_assign(
739 switch_button,
740 BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), binding));
741 continue;
742 }
743 const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button);
744 mapping.insert_or_assign(
745 switch_button,
746 BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
747 }
748 for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) {
749 if (IsButtonOnLeftSide(switch_button)) {
750 const auto& binding = SDL_GameControllerGetBindForAxis(controller2, sdl_axis);
751 mapping.insert_or_assign(
752 switch_button,
753 BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), binding));
754 continue;
755 }
756 const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis);
757 mapping.insert_or_assign(
758 switch_button,
759 BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
760 }
761
762 return mapping;
763}
764
765bool SDLDriver::IsButtonOnLeftSide(Settings::NativeButton::Values button) const {
766 switch (button) {
767 case Settings::NativeButton::DDown:
768 case Settings::NativeButton::DLeft:
769 case Settings::NativeButton::DRight:
770 case Settings::NativeButton::DUp:
771 case Settings::NativeButton::L:
772 case Settings::NativeButton::LStick:
773 case Settings::NativeButton::Minus:
774 case Settings::NativeButton::Screenshot:
775 case Settings::NativeButton::ZL:
776 return true;
777 default:
778 return false;
779 }
780}
781
782AnalogMapping SDLDriver::GetAnalogMappingForDevice(const Common::ParamPackage& params) {
783 if (!params.Has("guid") || !params.Has("port")) {
784 return {};
785 }
786 const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
787 const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0));
788 auto* controller = joystick->GetSDLGameController();
789 if (controller == nullptr) {
790 return {};
791 }
792
793 AnalogMapping mapping = {};
794 const auto& binding_left_x =
795 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX);
796 const auto& binding_left_y =
797 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY);
798 if (params.Has("guid2")) {
799 const auto identifier = joystick2->GetPadIdentifier();
800 PreSetController(identifier);
801 PreSetAxis(identifier, binding_left_x.value.axis);
802 PreSetAxis(identifier, binding_left_y.value.axis);
803 const auto left_offset_x = -GetAxis(identifier, binding_left_x.value.axis);
804 const auto left_offset_y = -GetAxis(identifier, binding_left_y.value.axis);
805 mapping.insert_or_assign(Settings::NativeAnalog::LStick,
806 BuildParamPackageForAnalog(identifier, binding_left_x.value.axis,
807 binding_left_y.value.axis,
808 left_offset_x, left_offset_y));
809 } else {
810 const auto identifier = joystick->GetPadIdentifier();
811 PreSetController(identifier);
812 PreSetAxis(identifier, binding_left_x.value.axis);
813 PreSetAxis(identifier, binding_left_y.value.axis);
814 const auto left_offset_x = -GetAxis(identifier, binding_left_x.value.axis);
815 const auto left_offset_y = -GetAxis(identifier, binding_left_y.value.axis);
816 mapping.insert_or_assign(Settings::NativeAnalog::LStick,
817 BuildParamPackageForAnalog(identifier, binding_left_x.value.axis,
818 binding_left_y.value.axis,
819 left_offset_x, left_offset_y));
820 }
821 const auto& binding_right_x =
822 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX);
823 const auto& binding_right_y =
824 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY);
825 const auto identifier = joystick->GetPadIdentifier();
826 PreSetController(identifier);
827 PreSetAxis(identifier, binding_right_x.value.axis);
828 PreSetAxis(identifier, binding_right_y.value.axis);
829 const auto right_offset_x = -GetAxis(identifier, binding_right_x.value.axis);
830 const auto right_offset_y = -GetAxis(identifier, binding_right_y.value.axis);
831 mapping.insert_or_assign(Settings::NativeAnalog::RStick,
832 BuildParamPackageForAnalog(identifier, binding_right_x.value.axis,
833 binding_right_y.value.axis, right_offset_x,
834 right_offset_y));
835 return mapping;
836}
837
838MotionMapping SDLDriver::GetMotionMappingForDevice(const Common::ParamPackage& params) {
839 if (!params.Has("guid") || !params.Has("port")) {
840 return {};
841 }
842 const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
843 const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0));
844 auto* controller = joystick->GetSDLGameController();
845 if (controller == nullptr) {
846 return {};
847 }
848
849 MotionMapping mapping = {};
850 joystick->EnableMotion();
851
852 if (joystick->HasGyro() || joystick->HasAccel()) {
853 mapping.insert_or_assign(Settings::NativeMotion::MotionRight,
854 BuildMotionParam(joystick->GetPort(), joystick->GetGUID()));
855 }
856 if (params.Has("guid2")) {
857 joystick2->EnableMotion();
858 if (joystick2->HasGyro() || joystick2->HasAccel()) {
859 mapping.insert_or_assign(Settings::NativeMotion::MotionLeft,
860 BuildMotionParam(joystick2->GetPort(), joystick2->GetGUID()));
861 }
862 } else {
863 if (joystick->HasGyro() || joystick->HasAccel()) {
864 mapping.insert_or_assign(Settings::NativeMotion::MotionLeft,
865 BuildMotionParam(joystick->GetPort(), joystick->GetGUID()));
866 }
867 }
868
869 return mapping;
870}
871
872Common::Input::ButtonNames SDLDriver::GetUIName(const Common::ParamPackage& params) const {
873 if (params.Has("button")) {
874 // TODO(German77): Find how to substitue the values for real button names
875 return Common::Input::ButtonNames::Value;
876 }
877 if (params.Has("hat")) {
878 return Common::Input::ButtonNames::Value;
879 }
880 if (params.Has("axis")) {
881 return Common::Input::ButtonNames::Value;
882 }
883 if (params.Has("axis_x") && params.Has("axis_y") && params.Has("axis_z")) {
884 return Common::Input::ButtonNames::Value;
885 }
886 if (params.Has("motion")) {
887 return Common::Input::ButtonNames::Engine;
888 }
889
890 return Common::Input::ButtonNames::Invalid;
891}
892
893std::string SDLDriver::GetHatButtonName(u8 direction_value) const {
894 switch (direction_value) {
895 case SDL_HAT_UP:
896 return "up";
897 case SDL_HAT_DOWN:
898 return "down";
899 case SDL_HAT_LEFT:
900 return "left";
901 case SDL_HAT_RIGHT:
902 return "right";
903 default:
904 return {};
905 }
906}
907
908u8 SDLDriver::GetHatButtonId(const std::string& direction_name) const {
909 Uint8 direction;
910 if (direction_name == "up") {
911 direction = SDL_HAT_UP;
912 } else if (direction_name == "down") {
913 direction = SDL_HAT_DOWN;
914 } else if (direction_name == "left") {
915 direction = SDL_HAT_LEFT;
916 } else if (direction_name == "right") {
917 direction = SDL_HAT_RIGHT;
918 } else {
919 direction = 0;
920 }
921 return direction;
922}
923
924} // 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 @@
5#pragma once 5#pragma once
6 6
7#include <atomic> 7#include <atomic>
8#include <memory>
9#include <mutex> 8#include <mutex>
10#include <thread> 9#include <thread>
11#include <unordered_map> 10#include <unordered_map>
@@ -13,8 +12,7 @@
13#include <SDL.h> 12#include <SDL.h>
14 13
15#include "common/common_types.h" 14#include "common/common_types.h"
16#include "common/threadsafe_queue.h" 15#include "input_common/input_engine.h"
17#include "input_common/sdl/sdl.h"
18 16
19union SDL_Event; 17union SDL_Event;
20using SDL_GameController = struct _SDL_GameController; 18using SDL_GameController = struct _SDL_GameController;
@@ -26,21 +24,17 @@ using ButtonBindings =
26using ZButtonBindings = 24using ZButtonBindings =
27 std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerAxis>, 2>; 25 std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerAxis>, 2>;
28 26
29namespace InputCommon::SDL { 27namespace InputCommon {
30 28
31class SDLAnalogFactory;
32class SDLButtonFactory;
33class SDLMotionFactory;
34class SDLVibrationFactory;
35class SDLJoystick; 29class SDLJoystick;
36 30
37class SDLState : public State { 31class SDLDriver : public InputCommon::InputEngine {
38public: 32public:
39 /// Initializes and registers SDL device factories 33 /// Initializes and registers SDL device factories
40 SDLState(); 34 SDLDriver(const std::string& input_engine_);
41 35
42 /// Unregisters SDL device factories and shut them down. 36 /// Unregisters SDL device factories and shut them down.
43 ~SDLState() override; 37 ~SDLDriver() override;
44 38
45 /// Handle SDL_Events for joysticks from SDL_PollEvent 39 /// Handle SDL_Events for joysticks from SDL_PollEvent
46 void HandleGameControllerEvent(const SDL_Event& event); 40 void HandleGameControllerEvent(const SDL_Event& event);
@@ -54,18 +48,18 @@ public:
54 */ 48 */
55 std::shared_ptr<SDLJoystick> GetSDLJoystickByGUID(const std::string& guid, int port); 49 std::shared_ptr<SDLJoystick> GetSDLJoystickByGUID(const std::string& guid, int port);
56 50
57 /// Get all DevicePoller that use the SDL backend for a specific device type 51 std::vector<Common::ParamPackage> GetInputDevices() const override;
58 Pollers GetPollers(Polling::DeviceType type) override;
59
60 /// Used by the Pollers during config
61 std::atomic<bool> polling = false;
62 Common::SPSCQueue<SDL_Event> event_queue;
63
64 std::vector<Common::ParamPackage> GetInputDevices() override;
65 52
66 ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override; 53 ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override;
67 AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override; 54 AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override;
68 MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& params) override; 55 MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& params) override;
56 Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override;
57
58 std::string GetHatButtonName(u8 direction_value) const override;
59 u8 GetHatButtonId(const std::string& direction_name) const override;
60
61 Common::Input::VibrationError SetRumble(
62 const PadIdentifier& identifier, const Common::Input::VibrationStatus vibration) override;
69 63
70private: 64private:
71 void InitJoystick(int joystick_index); 65 void InitJoystick(int joystick_index);
@@ -74,6 +68,23 @@ private:
74 /// Needs to be called before SDL_QuitSubSystem. 68 /// Needs to be called before SDL_QuitSubSystem.
75 void CloseJoysticks(); 69 void CloseJoysticks();
76 70
71 Common::ParamPackage BuildAnalogParamPackageForButton(int port, std::string guid, s32 axis,
72 float value = 0.1f) const;
73 Common::ParamPackage BuildButtonParamPackageForButton(int port, std::string guid,
74 s32 button) const;
75
76 Common::ParamPackage BuildHatParamPackageForButton(int port, std::string guid, s32 hat,
77 u8 value) const;
78
79 Common::ParamPackage BuildMotionParam(int port, std::string guid) const;
80
81 Common::ParamPackage BuildParamPackageForBinding(
82 int port, const std::string& guid, const SDL_GameControllerButtonBind& binding) const;
83
84 Common::ParamPackage BuildParamPackageForAnalog(PadIdentifier identifier, int axis_x,
85 int axis_y, float offset_x,
86 float offset_y) const;
87
77 /// Returns the default button bindings list for generic controllers 88 /// Returns the default button bindings list for generic controllers
78 ButtonBindings GetDefaultButtonBinding() const; 89 ButtonBindings GetDefaultButtonBinding() const;
79 90
@@ -94,21 +105,13 @@ private:
94 /// Returns true if the button is on the left joycon 105 /// Returns true if the button is on the left joycon
95 bool IsButtonOnLeftSide(Settings::NativeButton::Values button) const; 106 bool IsButtonOnLeftSide(Settings::NativeButton::Values button) const;
96 107
97 // Set to true if SDL supports game controller subsystem
98 bool has_gamecontroller = false;
99
100 /// Map of GUID of a list of corresponding virtual Joysticks 108 /// Map of GUID of a list of corresponding virtual Joysticks
101 std::unordered_map<std::string, std::vector<std::shared_ptr<SDLJoystick>>> joystick_map; 109 std::unordered_map<std::string, std::vector<std::shared_ptr<SDLJoystick>>> joystick_map;
102 std::mutex joystick_map_mutex; 110 std::mutex joystick_map_mutex;
103 111
104 std::shared_ptr<SDLButtonFactory> button_factory;
105 std::shared_ptr<SDLAnalogFactory> analog_factory;
106 std::shared_ptr<SDLVibrationFactory> vibration_factory;
107 std::shared_ptr<SDLMotionFactory> motion_factory;
108
109 bool start_thread = false; 112 bool start_thread = false;
110 std::atomic<bool> initialized = false; 113 std::atomic<bool> initialized = false;
111 114
112 std::thread poll_thread; 115 std::thread poll_thread;
113}; 116};
114} // namespace InputCommon::SDL 117} // 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 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2+
3// Refer to the license.txt file included.
4
5#include <cstring>
6#include <regex>
7#include <fmt/format.h>
8
9#include "common/fs/file.h"
10#include "common/fs/fs_types.h"
11#include "common/fs/path_util.h"
12#include "common/logging/log.h"
13#include "common/settings.h"
14#include "input_common/drivers/tas_input.h"
15
16namespace InputCommon::TasInput {
17
18enum TasAxes : u8 {
19 StickX,
20 StickY,
21 SubstickX,
22 SubstickY,
23 Undefined,
24};
25
26// Supported keywords and buttons from a TAS file
27constexpr std::array<std::pair<std::string_view, TasButton>, 20> text_to_tas_button = {
28 std::pair{"KEY_A", TasButton::BUTTON_A},
29 {"KEY_B", TasButton::BUTTON_B},
30 {"KEY_X", TasButton::BUTTON_X},
31 {"KEY_Y", TasButton::BUTTON_Y},
32 {"KEY_LSTICK", TasButton::STICK_L},
33 {"KEY_RSTICK", TasButton::STICK_R},
34 {"KEY_L", TasButton::TRIGGER_L},
35 {"KEY_R", TasButton::TRIGGER_R},
36 {"KEY_PLUS", TasButton::BUTTON_PLUS},
37 {"KEY_MINUS", TasButton::BUTTON_MINUS},
38 {"KEY_DLEFT", TasButton::BUTTON_LEFT},
39 {"KEY_DUP", TasButton::BUTTON_UP},
40 {"KEY_DRIGHT", TasButton::BUTTON_RIGHT},
41 {"KEY_DDOWN", TasButton::BUTTON_DOWN},
42 {"KEY_SL", TasButton::BUTTON_SL},
43 {"KEY_SR", TasButton::BUTTON_SR},
44 {"KEY_CAPTURE", TasButton::BUTTON_CAPTURE},
45 {"KEY_HOME", TasButton::BUTTON_HOME},
46 {"KEY_ZL", TasButton::TRIGGER_ZL},
47 {"KEY_ZR", TasButton::TRIGGER_ZR},
48};
49
50Tas::Tas(const std::string& input_engine_) : InputCommon::InputEngine(input_engine_) {
51 for (size_t player_index = 0; player_index < PLAYER_NUMBER; player_index++) {
52 PadIdentifier identifier{
53 .guid = Common::UUID{},
54 .port = player_index,
55 .pad = 0,
56 };
57 PreSetController(identifier);
58 }
59 ClearInput();
60 if (!Settings::values.tas_enable) {
61 needs_reset = true;
62 return;
63 }
64 LoadTasFiles();
65}
66
67Tas::~Tas() {
68 Stop();
69};
70
71void Tas::LoadTasFiles() {
72 script_length = 0;
73 for (size_t i = 0; i < commands.size(); i++) {
74 LoadTasFile(i, 0);
75 if (commands[i].size() > script_length) {
76 script_length = commands[i].size();
77 }
78 }
79}
80
81void Tas::LoadTasFile(size_t player_index, size_t file_index) {
82 if (!commands[player_index].empty()) {
83 commands[player_index].clear();
84 }
85 std::string file = Common::FS::ReadStringFromFile(
86 Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) /
87 fmt::format("script{}-{}.txt", file_index, player_index + 1),
88 Common::FS::FileType::BinaryFile);
89 std::stringstream command_line(file);
90 std::string line;
91 int frame_no = 0;
92 while (std::getline(command_line, line, '\n')) {
93 if (line.empty()) {
94 continue;
95 }
96 std::smatch m;
97
98 std::stringstream linestream(line);
99 std::string segment;
100 std::vector<std::string> seglist;
101
102 while (std::getline(linestream, segment, ' ')) {
103 seglist.push_back(segment);
104 }
105
106 if (seglist.size() < 4) {
107 continue;
108 }
109
110 while (frame_no < std::stoi(seglist.at(0))) {
111 commands[player_index].push_back({});
112 frame_no++;
113 }
114
115 TASCommand command = {
116 .buttons = ReadCommandButtons(seglist.at(1)),
117 .l_axis = ReadCommandAxis(seglist.at(2)),
118 .r_axis = ReadCommandAxis(seglist.at(3)),
119 };
120 commands[player_index].push_back(command);
121 frame_no++;
122 }
123 LOG_INFO(Input, "TAS file loaded! {} frames", frame_no);
124}
125
126void Tas::WriteTasFile(std::u8string file_name) {
127 std::string output_text;
128 for (size_t frame = 0; frame < record_commands.size(); frame++) {
129 const TASCommand& line = record_commands[frame];
130 output_text += fmt::format("{} {} {} {}\n", frame, WriteCommandButtons(line.buttons),
131 WriteCommandAxis(line.l_axis), WriteCommandAxis(line.r_axis));
132 }
133 const auto bytes_written = Common::FS::WriteStringToFile(
134 Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) / file_name,
135 Common::FS::FileType::TextFile, output_text);
136 if (bytes_written == output_text.size()) {
137 LOG_INFO(Input, "TAS file written to file!");
138 } else {
139 LOG_ERROR(Input, "Writing the TAS-file has failed! {} / {} bytes written", bytes_written,
140 output_text.size());
141 }
142}
143
144void Tas::RecordInput(u64 buttons, TasAnalog left_axis, TasAnalog right_axis) {
145 last_input = {
146 .buttons = buttons,
147 .l_axis = left_axis,
148 .r_axis = right_axis,
149 };
150}
151
152std::tuple<TasState, size_t, size_t> Tas::GetStatus() const {
153 TasState state;
154 if (is_recording) {
155 return {TasState::Recording, 0, record_commands.size()};
156 }
157
158 if (is_running) {
159 state = TasState::Running;
160 } else {
161 state = TasState::Stopped;
162 }
163
164 return {state, current_command, script_length};
165}
166
167void Tas::UpdateThread() {
168 if (!Settings::values.tas_enable) {
169 if (is_running) {
170 Stop();
171 }
172 return;
173 }
174
175 if (is_recording) {
176 record_commands.push_back(last_input);
177 }
178 if (needs_reset) {
179 current_command = 0;
180 needs_reset = false;
181 LoadTasFiles();
182 LOG_DEBUG(Input, "tas_reset done");
183 }
184
185 if (!is_running) {
186 ClearInput();
187 return;
188 }
189 if (current_command < script_length) {
190 LOG_DEBUG(Input, "Playing TAS {}/{}", current_command, script_length);
191 const size_t frame = current_command++;
192 for (size_t player_index = 0; player_index < commands.size(); player_index++) {
193 TASCommand command{};
194 if (frame < commands[player_index].size()) {
195 command = commands[player_index][frame];
196 }
197
198 PadIdentifier identifier{
199 .guid = Common::UUID{},
200 .port = player_index,
201 .pad = 0,
202 };
203 for (std::size_t i = 0; i < sizeof(command.buttons) * 8; ++i) {
204 const bool button_status = (command.buttons & (1LLU << i)) != 0;
205 const int button = static_cast<int>(i);
206 SetButton(identifier, button, button_status);
207 }
208 SetAxis(identifier, TasAxes::StickX, command.l_axis.x);
209 SetAxis(identifier, TasAxes::StickY, command.l_axis.y);
210 SetAxis(identifier, TasAxes::SubstickX, command.r_axis.x);
211 SetAxis(identifier, TasAxes::SubstickY, command.r_axis.y);
212 }
213 } else {
214 is_running = Settings::values.tas_loop.GetValue();
215 LoadTasFiles();
216 current_command = 0;
217 ClearInput();
218 }
219}
220
221void Tas::ClearInput() {
222 ResetButtonState();
223 ResetAnalogState();
224}
225
226TasAnalog Tas::ReadCommandAxis(const std::string& line) const {
227 std::stringstream linestream(line);
228 std::string segment;
229 std::vector<std::string> seglist;
230
231 while (std::getline(linestream, segment, ';')) {
232 seglist.push_back(segment);
233 }
234
235 const float x = std::stof(seglist.at(0)) / 32767.0f;
236 const float y = std::stof(seglist.at(1)) / 32767.0f;
237
238 return {x, y};
239}
240
241u64 Tas::ReadCommandButtons(const std::string& data) const {
242 std::stringstream button_text(data);
243 std::string line;
244 u64 buttons = 0;
245 while (std::getline(button_text, line, ';')) {
246 for (auto [text, tas_button] : text_to_tas_button) {
247 if (text == line) {
248 buttons |= static_cast<u64>(tas_button);
249 break;
250 }
251 }
252 }
253 return buttons;
254}
255
256std::string Tas::WriteCommandButtons(u64 buttons) const {
257 std::string returns = "";
258 for (auto [text_button, tas_button] : text_to_tas_button) {
259 if ((buttons & static_cast<u64>(tas_button)) != 0) {
260 returns += fmt::format("{};", text_button);
261 }
262 }
263 return returns.empty() ? "NONE" : returns;
264}
265
266std::string Tas::WriteCommandAxis(TasAnalog analog) const {
267 return fmt::format("{};{}", analog.x * 32767, analog.y * 32767);
268}
269
270void Tas::StartStop() {
271 if (!Settings::values.tas_enable) {
272 return;
273 }
274 if (is_running) {
275 Stop();
276 } else {
277 is_running = true;
278 }
279}
280
281void Tas::Stop() {
282 is_running = false;
283}
284
285void Tas::Reset() {
286 if (!Settings::values.tas_enable) {
287 return;
288 }
289 needs_reset = true;
290}
291
292bool Tas::Record() {
293 if (!Settings::values.tas_enable) {
294 return true;
295 }
296 is_recording = !is_recording;
297 return is_recording;
298}
299
300void Tas::SaveRecording(bool overwrite_file) {
301 if (is_recording) {
302 return;
303 }
304 if (record_commands.empty()) {
305 return;
306 }
307 WriteTasFile(u8"record.txt");
308 if (overwrite_file) {
309 WriteTasFile(u8"script0-1.txt");
310 }
311 needs_reset = true;
312 record_commands.clear();
313}
314
315} // 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 @@
8 8
9#include "common/common_types.h" 9#include "common/common_types.h"
10#include "common/settings_input.h" 10#include "common/settings_input.h"
11#include "core/frontend/input.h" 11#include "input_common/input_engine.h"
12#include "input_common/main.h" 12#include "input_common/main.h"
13 13
14/* 14/*
@@ -43,19 +43,11 @@ For debugging purposes, the common controller debugger can be used (View -> Debu
43P1). 43P1).
44*/ 44*/
45 45
46namespace TasInput { 46namespace InputCommon::TasInput {
47 47
48constexpr size_t PLAYER_NUMBER = 8; 48constexpr size_t PLAYER_NUMBER = 10;
49 49
50using TasAnalog = std::pair<float, float>; 50enum class TasButton : u64 {
51
52enum class TasState {
53 Running,
54 Recording,
55 Stopped,
56};
57
58enum class TasButton : u32 {
59 BUTTON_A = 1U << 0, 51 BUTTON_A = 1U << 0,
60 BUTTON_B = 1U << 1, 52 BUTTON_B = 1U << 1,
61 BUTTON_X = 1U << 2, 53 BUTTON_X = 1U << 2,
@@ -78,26 +70,29 @@ enum class TasButton : u32 {
78 BUTTON_CAPTURE = 1U << 19, 70 BUTTON_CAPTURE = 1U << 19,
79}; 71};
80 72
81enum class TasAxes : u8 { 73struct TasAnalog {
82 StickX, 74 float x{};
83 StickY, 75 float y{};
84 SubstickX,
85 SubstickY,
86 Undefined,
87}; 76};
88 77
89struct TasData { 78enum class TasState {
90 u32 buttons{}; 79 Running,
91 std::array<float, 4> axis{}; 80 Recording,
81 Stopped,
92}; 82};
93 83
94class Tas { 84class Tas final : public InputCommon::InputEngine {
95public: 85public:
96 Tas(); 86 explicit Tas(const std::string& input_engine_);
97 ~Tas(); 87 ~Tas();
98 88
99 // Changes the input status that will be stored in each frame 89 /**
100 void RecordInput(u32 buttons, const std::array<std::pair<float, float>, 2>& axes); 90 * Changes the input status that will be stored in each frame
91 * @param buttons: bitfield with the status of the buttons
92 * @param left_axis: value of the left axis
93 * @param right_axis: value of the right axis
94 */
95 void RecordInput(u64 buttons, TasAnalog left_axis, TasAnalog right_axis);
101 96
102 // Main loop that records or executes input 97 // Main loop that records or executes input
103 void UpdateThread(); 98 void UpdateThread();
@@ -117,112 +112,77 @@ public:
117 */ 112 */
118 bool Record(); 113 bool Record();
119 114
120 // Saves contents of record_commands on a file if overwrite is enabled player 1 will be 115 /**
121 // overwritten with the recorded commands 116 * Saves contents of record_commands on a file
117 * @param overwrite_file: Indicates if player 1 should be overwritten
118 */
122 void SaveRecording(bool overwrite_file); 119 void SaveRecording(bool overwrite_file);
123 120
124 /** 121 /**
125 * Returns the current status values of TAS playback/recording 122 * Returns the current status values of TAS playback/recording
126 * @return Tuple of 123 * @return Tuple of
127 * TasState indicating the current state out of Running, Recording or Stopped ; 124 * TasState indicating the current state out of Running ;
128 * Current playback progress or amount of frames (so far) for Recording ; 125 * Current playback progress ;
129 * Total length of script file currently loaded or amount of frames (so far) for Recording 126 * Total length of script file currently loaded or being recorded
130 */ 127 */
131 std::tuple<TasState, size_t, size_t> GetStatus() const; 128 std::tuple<TasState, size_t, size_t> GetStatus() const;
132 129
133 // Retuns an array of the default button mappings
134 InputCommon::ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) const;
135
136 // Retuns an array of the default analog mappings
137 InputCommon::AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) const;
138 [[nodiscard]] const TasData& GetTasState(std::size_t pad) const;
139
140private: 130private:
141 struct TASCommand { 131 struct TASCommand {
142 u32 buttons{}; 132 u64 buttons{};
143 TasAnalog l_axis{}; 133 TasAnalog l_axis{};
144 TasAnalog r_axis{}; 134 TasAnalog r_axis{};
145 }; 135 };
146 136
147 // Loads TAS files from all players 137 /// Loads TAS files from all players
148 void LoadTasFiles(); 138 void LoadTasFiles();
149 139
150 // Loads TAS file from the specified player 140 /** Loads TAS file from the specified player
151 void LoadTasFile(size_t player_index); 141 * @param player_index: player number to save the script
142 * @param file_index: script number of the file
143 */
144 void LoadTasFile(size_t player_index, size_t file_index);
152 145
153 // Writes a TAS file from the recorded commands 146 /** Writes a TAS file from the recorded commands
147 * @param file_name: name of the file to be written
148 */
154 void WriteTasFile(std::u8string file_name); 149 void WriteTasFile(std::u8string file_name);
155 150
156 /** 151 /**
157 * Parses a string containing the axis values with the following format "x;y" 152 * Parses a string containing the axis values. X and Y have a range from -32767 to 32767
158 * X and Y have a range from -32767 to 32767 153 * @param line: string containing axis values with the following format "x;y"
159 * @return Returns a TAS analog object with axis values with range from -1.0 to 1.0 154 * @return Returns a TAS analog object with axis values with range from -1.0 to 1.0
160 */ 155 */
161 TasAnalog ReadCommandAxis(const std::string& line) const; 156 TasAnalog ReadCommandAxis(const std::string& line) const;
162 157
163 /** 158 /**
164 * Parses a string containing the button values with the following format "a;b;c;d..." 159 * Parses a string containing the button values. Each button is represented by it's text format
165 * Each button is represented by it's text format specified in text_to_tas_button array 160 * specified in text_to_tas_button array
166 * @return Returns a u32 with each bit representing the status of a button 161 * @param line: string containing button name with the following format "a;b;c;d..."
167 */ 162 * @return Returns a u64 with each bit representing the status of a button
168 u32 ReadCommandButtons(const std::string& line) const;
169
170 /**
171 * Converts an u32 containing the button status into the text equivalent
172 * @return Returns a string with the name of the buttons to be written to the file
173 */ 163 */
174 std::string WriteCommandButtons(u32 data) const; 164 u64 ReadCommandButtons(const std::string& line) const;
175 165
176 /** 166 /**
177 * Converts an TAS analog object containing the axis status into the text equivalent 167 * Reset state of all players
178 * @return Returns a string with the value of the axis to be written to the file
179 */ 168 */
180 std::string WriteCommandAxis(TasAnalog data) const; 169 void ClearInput();
181
182 // Inverts the Y axis polarity
183 std::pair<float, float> FlipAxisY(std::pair<float, float> old);
184 170
185 /** 171 /**
186 * Converts an u32 containing the button status into the text equivalent 172 * Converts an u64 containing the button status into the text equivalent
187 * @return Returns a string with the name of the buttons to be printed on console 173 * @param buttons: bitfield with the status of the buttons
174 * @return Returns a string with the name of the buttons to be written to the file
188 */ 175 */
189 std::string DebugButtons(u32 buttons) const; 176 std::string WriteCommandButtons(u64 buttons) const;
190 177
191 /** 178 /**
192 * Converts an TAS analog object containing the axis status into the text equivalent 179 * Converts an TAS analog object containing the axis status into the text equivalent
193 * @return Returns a string with the value of the axis to be printed on console 180 * @param data: value of the axis
194 */ 181 * @return A string with the value of the axis to be written to the file
195 std::string DebugJoystick(float x, float y) const;
196
197 /**
198 * Converts the given TAS status into the text equivalent
199 * @return Returns a string with the value of the TAS status to be printed on console
200 */ 182 */
201 std::string DebugInput(const TasData& data) const; 183 std::string WriteCommandAxis(TasAnalog data) const;
202
203 /**
204 * Converts the given TAS status of multiple players into the text equivalent
205 * @return Returns a string with the value of the status of all TAS players to be printed on
206 * console
207 */
208 std::string DebugInputs(const std::array<TasData, PLAYER_NUMBER>& arr) const;
209
210 /**
211 * Converts an u32 containing the button status into the text equivalent
212 * @return Returns a string with the name of the buttons
213 */
214 std::string ButtonsToString(u32 button) const;
215
216 // Stores current controller configuration and sets a TAS controller for every active controller
217 // to the current config
218 void SwapToTasController();
219
220 // Sets the stored controller configuration to the current config
221 void SwapToStoredController();
222 184
223 size_t script_length{0}; 185 size_t script_length{0};
224 std::array<TasData, PLAYER_NUMBER> tas_data;
225 bool is_old_input_saved{false};
226 bool is_recording{false}; 186 bool is_recording{false};
227 bool is_running{false}; 187 bool is_running{false};
228 bool needs_reset{false}; 188 bool needs_reset{false};
@@ -230,8 +190,5 @@ private:
230 std::vector<TASCommand> record_commands{}; 190 std::vector<TASCommand> record_commands{};
231 size_t current_command{0}; 191 size_t current_command{0};
232 TASCommand last_input{}; // only used for recording 192 TASCommand last_input{}; // only used for recording
233
234 // Old settings for swapping controllers
235 std::array<Settings::PlayerInput, 10> player_mappings;
236}; 193};
237} // namespace TasInput 194} // 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 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included
4
5#include "common/param_package.h"
6#include "input_common/drivers/touch_screen.h"
7
8namespace InputCommon {
9
10constexpr PadIdentifier identifier = {
11 .guid = Common::UUID{Common::INVALID_UUID},
12 .port = 0,
13 .pad = 0,
14};
15
16TouchScreen::TouchScreen(const std::string& input_engine_) : InputEngine(input_engine_) {
17 PreSetController(identifier);
18}
19
20void TouchScreen::TouchMoved(float x, float y, std::size_t finger) {
21 if (finger >= 16) {
22 return;
23 }
24 TouchPressed(x, y, finger);
25}
26
27void TouchScreen::TouchPressed(float x, float y, std::size_t finger) {
28 if (finger >= 16) {
29 return;
30 }
31 SetButton(identifier, static_cast<int>(finger), true);
32 SetAxis(identifier, static_cast<int>(finger * 2), x);
33 SetAxis(identifier, static_cast<int>(finger * 2 + 1), y);
34}
35
36void TouchScreen::TouchReleased(std::size_t finger) {
37 if (finger >= 16) {
38 return;
39 }
40 SetButton(identifier, static_cast<int>(finger), false);
41 SetAxis(identifier, static_cast<int>(finger * 2), 0.0f);
42 SetAxis(identifier, static_cast<int>(finger * 2 + 1), 0.0f);
43}
44
45void TouchScreen::ReleaseAllTouch() {
46 for (int index = 0; index < 16; ++index) {
47 SetButton(identifier, index, false);
48 SetAxis(identifier, index * 2, 0.0f);
49 SetAxis(identifier, index * 2 + 1, 0.0f);
50 }
51}
52
53} // 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 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included
4
5#pragma once
6
7#include "input_common/input_engine.h"
8
9namespace InputCommon {
10
11/**
12 * A button device factory representing a keyboard. It receives keyboard events and forward them
13 * to all button devices it created.
14 */
15class TouchScreen final : public InputCommon::InputEngine {
16public:
17 explicit TouchScreen(const std::string& input_engine_);
18
19 /**
20 * Signals that mouse has moved.
21 * @param x the x-coordinate of the cursor
22 * @param y the y-coordinate of the cursor
23 * @param center_x the x-coordinate of the middle of the screen
24 * @param center_y the y-coordinate of the middle of the screen
25 */
26 void TouchMoved(float x, float y, std::size_t finger);
27
28 /**
29 * Sets the status of all buttons bound with the key to pressed
30 * @param key_code the code of the key to press
31 */
32 void TouchPressed(float x, float y, std::size_t finger);
33
34 /**
35 * Sets the status of all buttons bound with the key to released
36 * @param key_code the code of the key to release
37 */
38 void TouchReleased(std::size_t finger);
39
40 /// Resets all inputs to their initial value
41 void ReleaseAllTouch();
42};
43
44} // 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 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <random>
6#include <boost/asio.hpp>
7#include <fmt/format.h>
8
9#include "common/logging/log.h"
10#include "common/param_package.h"
11#include "common/settings.h"
12#include "input_common/drivers/udp_client.h"
13#include "input_common/helpers/udp_protocol.h"
14
15using boost::asio::ip::udp;
16
17namespace InputCommon::CemuhookUDP {
18
19struct SocketCallback {
20 std::function<void(Response::Version)> version;
21 std::function<void(Response::PortInfo)> port_info;
22 std::function<void(Response::PadData)> pad_data;
23};
24
25class Socket {
26public:
27 using clock = std::chrono::system_clock;
28
29 explicit Socket(const std::string& host, u16 port, SocketCallback callback_)
30 : callback(std::move(callback_)), timer(io_service),
31 socket(io_service, udp::endpoint(udp::v4(), 0)), client_id(GenerateRandomClientId()) {
32 boost::system::error_code ec{};
33 auto ipv4 = boost::asio::ip::make_address_v4(host, ec);
34 if (ec.value() != boost::system::errc::success) {
35 LOG_ERROR(Input, "Invalid IPv4 address \"{}\" provided to socket", host);
36 ipv4 = boost::asio::ip::address_v4{};
37 }
38
39 send_endpoint = {udp::endpoint(ipv4, port)};
40 }
41
42 void Stop() {
43 io_service.stop();
44 }
45
46 void Loop() {
47 io_service.run();
48 }
49
50 void StartSend(const clock::time_point& from) {
51 timer.expires_at(from + std::chrono::seconds(3));
52 timer.async_wait([this](const boost::system::error_code& error) { HandleSend(error); });
53 }
54
55 void StartReceive() {
56 socket.async_receive_from(
57 boost::asio::buffer(receive_buffer), receive_endpoint,
58 [this](const boost::system::error_code& error, std::size_t bytes_transferred) {
59 HandleReceive(error, bytes_transferred);
60 });
61 }
62
63private:
64 u32 GenerateRandomClientId() const {
65 std::random_device device;
66 return device();
67 }
68
69 void HandleReceive(const boost::system::error_code&, std::size_t bytes_transferred) {
70 if (auto type = Response::Validate(receive_buffer.data(), bytes_transferred)) {
71 switch (*type) {
72 case Type::Version: {
73 Response::Version version;
74 std::memcpy(&version, &receive_buffer[sizeof(Header)], sizeof(Response::Version));
75 callback.version(std::move(version));
76 break;
77 }
78 case Type::PortInfo: {
79 Response::PortInfo port_info;
80 std::memcpy(&port_info, &receive_buffer[sizeof(Header)],
81 sizeof(Response::PortInfo));
82 callback.port_info(std::move(port_info));
83 break;
84 }
85 case Type::PadData: {
86 Response::PadData pad_data;
87 std::memcpy(&pad_data, &receive_buffer[sizeof(Header)], sizeof(Response::PadData));
88 callback.pad_data(std::move(pad_data));
89 break;
90 }
91 }
92 }
93 StartReceive();
94 }
95
96 void HandleSend(const boost::system::error_code&) {
97 boost::system::error_code _ignored{};
98 // Send a request for getting port info for the pad
99 const Request::PortInfo port_info{4, {0, 1, 2, 3}};
100 const auto port_message = Request::Create(port_info, client_id);
101 std::memcpy(&send_buffer1, &port_message, PORT_INFO_SIZE);
102 socket.send_to(boost::asio::buffer(send_buffer1), send_endpoint, {}, _ignored);
103
104 // Send a request for getting pad data for the pad
105 const Request::PadData pad_data{
106 Request::RegisterFlags::AllPads,
107 0,
108 EMPTY_MAC_ADDRESS,
109 };
110 const auto pad_message = Request::Create(pad_data, client_id);
111 std::memcpy(send_buffer2.data(), &pad_message, PAD_DATA_SIZE);
112 socket.send_to(boost::asio::buffer(send_buffer2), send_endpoint, {}, _ignored);
113 StartSend(timer.expiry());
114 }
115
116 SocketCallback callback;
117 boost::asio::io_service io_service;
118 boost::asio::basic_waitable_timer<clock> timer;
119 udp::socket socket;
120
121 const u32 client_id;
122
123 static constexpr std::size_t PORT_INFO_SIZE = sizeof(Message<Request::PortInfo>);
124 static constexpr std::size_t PAD_DATA_SIZE = sizeof(Message<Request::PadData>);
125 std::array<u8, PORT_INFO_SIZE> send_buffer1;
126 std::array<u8, PAD_DATA_SIZE> send_buffer2;
127 udp::endpoint send_endpoint;
128
129 std::array<u8, MAX_PACKET_SIZE> receive_buffer;
130 udp::endpoint receive_endpoint;
131};
132
133static void SocketLoop(Socket* socket) {
134 socket->StartReceive();
135 socket->StartSend(Socket::clock::now());
136 socket->Loop();
137}
138
139UDPClient::UDPClient(const std::string& input_engine_) : InputEngine(input_engine_) {
140 LOG_INFO(Input, "Udp Initialization started");
141 ReloadSockets();
142}
143
144UDPClient::~UDPClient() {
145 Reset();
146}
147
148UDPClient::ClientConnection::ClientConnection() = default;
149
150UDPClient::ClientConnection::~ClientConnection() = default;
151
152void UDPClient::ReloadSockets() {
153 Reset();
154
155 std::stringstream servers_ss(Settings::values.udp_input_servers.GetValue());
156 std::string server_token;
157 std::size_t client = 0;
158 while (std::getline(servers_ss, server_token, ',')) {
159 if (client == MAX_UDP_CLIENTS) {
160 break;
161 }
162 std::stringstream server_ss(server_token);
163 std::string token;
164 std::getline(server_ss, token, ':');
165 std::string udp_input_address = token;
166 std::getline(server_ss, token, ':');
167 char* temp;
168 const u16 udp_input_port = static_cast<u16>(std::strtol(token.c_str(), &temp, 0));
169 if (*temp != '\0') {
170 LOG_ERROR(Input, "Port number is not valid {}", token);
171 continue;
172 }
173
174 const std::size_t client_number = GetClientNumber(udp_input_address, udp_input_port);
175 if (client_number != MAX_UDP_CLIENTS) {
176 LOG_ERROR(Input, "Duplicated UDP servers found");
177 continue;
178 }
179 StartCommunication(client++, udp_input_address, udp_input_port);
180 }
181}
182
183std::size_t UDPClient::GetClientNumber(std::string_view host, u16 port) const {
184 for (std::size_t client = 0; client < clients.size(); client++) {
185 if (clients[client].active == -1) {
186 continue;
187 }
188 if (clients[client].host == host && clients[client].port == port) {
189 return client;
190 }
191 }
192 return MAX_UDP_CLIENTS;
193}
194
195void UDPClient::OnVersion([[maybe_unused]] Response::Version data) {
196 LOG_TRACE(Input, "Version packet received: {}", data.version);
197}
198
199void UDPClient::OnPortInfo([[maybe_unused]] Response::PortInfo data) {
200 LOG_TRACE(Input, "PortInfo packet received: {}", data.model);
201}
202
203void UDPClient::OnPadData(Response::PadData data, std::size_t client) {
204 const std::size_t pad_index = (client * PADS_PER_CLIENT) + data.info.id;
205
206 if (pad_index >= pads.size()) {
207 LOG_ERROR(Input, "Invalid pad id {}", data.info.id);
208 return;
209 }
210
211 LOG_TRACE(Input, "PadData packet received");
212 if (data.packet_counter == pads[pad_index].packet_sequence) {
213 LOG_WARNING(
214 Input,
215 "PadData packet dropped because its stale info. Current count: {} Packet count: {}",
216 pads[pad_index].packet_sequence, data.packet_counter);
217 pads[pad_index].connected = false;
218 return;
219 }
220
221 clients[client].active = 1;
222 pads[pad_index].connected = true;
223 pads[pad_index].packet_sequence = data.packet_counter;
224
225 const auto now = std::chrono::steady_clock::now();
226 const auto time_difference = static_cast<u64>(
227 std::chrono::duration_cast<std::chrono::microseconds>(now - pads[pad_index].last_update)
228 .count());
229 pads[pad_index].last_update = now;
230
231 // Gyroscope values are not it the correct scale from better joy.
232 // Dividing by 312 allows us to make one full turn = 1 turn
233 // This must be a configurable valued called sensitivity
234 const float gyro_scale = 1.0f / 312.0f;
235
236 const BasicMotion motion{
237 .gyro_x = data.gyro.pitch * gyro_scale,
238 .gyro_y = data.gyro.roll * gyro_scale,
239 .gyro_z = -data.gyro.yaw * gyro_scale,
240 .accel_x = data.accel.x,
241 .accel_y = -data.accel.z,
242 .accel_z = data.accel.y,
243 .delta_timestamp = time_difference,
244 };
245 const PadIdentifier identifier = GetPadIdentifier(pad_index);
246 SetMotion(identifier, 0, motion);
247
248 for (std::size_t id = 0; id < data.touch.size(); ++id) {
249 const auto touch_pad = data.touch[id];
250 const auto touch_axis_x_id =
251 static_cast<int>(id == 0 ? PadAxes::Touch1X : PadAxes::Touch2X);
252 const auto touch_axis_y_id =
253 static_cast<int>(id == 0 ? PadAxes::Touch1Y : PadAxes::Touch2Y);
254 const auto touch_button_id =
255 static_cast<int>(id == 0 ? PadButton::Touch1 : PadButton::touch2);
256
257 // TODO: Use custom calibration per device
258 const Common::ParamPackage touch_param(Settings::values.touch_device.GetValue());
259 const u16 min_x = static_cast<u16>(touch_param.Get("min_x", 100));
260 const u16 min_y = static_cast<u16>(touch_param.Get("min_y", 50));
261 const u16 max_x = static_cast<u16>(touch_param.Get("max_x", 1800));
262 const u16 max_y = static_cast<u16>(touch_param.Get("max_y", 850));
263
264 const f32 x =
265 static_cast<f32>(std::clamp(static_cast<u16>(touch_pad.x), min_x, max_x) - min_x) /
266 static_cast<f32>(max_x - min_x);
267 const f32 y =
268 static_cast<f32>(std::clamp(static_cast<u16>(touch_pad.y), min_y, max_y) - min_y) /
269 static_cast<f32>(max_y - min_y);
270
271 if (touch_pad.is_active) {
272 SetAxis(identifier, touch_axis_x_id, x);
273 SetAxis(identifier, touch_axis_y_id, y);
274 SetButton(identifier, touch_button_id, true);
275 continue;
276 }
277 SetAxis(identifier, touch_axis_x_id, 0);
278 SetAxis(identifier, touch_axis_y_id, 0);
279 SetButton(identifier, touch_button_id, false);
280 }
281
282 SetAxis(identifier, static_cast<int>(PadAxes::LeftStickX),
283 (data.left_stick_x - 127.0f) / 127.0f);
284 SetAxis(identifier, static_cast<int>(PadAxes::LeftStickY),
285 (data.left_stick_y - 127.0f) / 127.0f);
286 SetAxis(identifier, static_cast<int>(PadAxes::RightStickX),
287 (data.right_stick_x - 127.0f) / 127.0f);
288 SetAxis(identifier, static_cast<int>(PadAxes::RightStickY),
289 (data.right_stick_y - 127.0f) / 127.0f);
290
291 static constexpr std::array<PadButton, 16> buttons{
292 PadButton::Share, PadButton::L3, PadButton::R3, PadButton::Options,
293 PadButton::Up, PadButton::Right, PadButton::Down, PadButton::Left,
294 PadButton::L2, PadButton::R2, PadButton::L1, PadButton::R1,
295 PadButton::Triangle, PadButton::Circle, PadButton::Cross, PadButton::Square};
296
297 for (std::size_t i = 0; i < buttons.size(); ++i) {
298 const bool button_status = (data.digital_button & (1U << i)) != 0;
299 const int button = static_cast<int>(buttons[i]);
300 SetButton(identifier, button, button_status);
301 }
302}
303
304void UDPClient::StartCommunication(std::size_t client, const std::string& host, u16 port) {
305 SocketCallback callback{[this](Response::Version version) { OnVersion(version); },
306 [this](Response::PortInfo info) { OnPortInfo(info); },
307 [this, client](Response::PadData data) { OnPadData(data, client); }};
308 LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port);
309 clients[client].uuid = GetHostUUID(host);
310 clients[client].host = host;
311 clients[client].port = port;
312 clients[client].active = 0;
313 clients[client].socket = std::make_unique<Socket>(host, port, callback);
314 clients[client].thread = std::thread{SocketLoop, clients[client].socket.get()};
315 for (std::size_t index = 0; index < PADS_PER_CLIENT; ++index) {
316 const PadIdentifier identifier = GetPadIdentifier(client * PADS_PER_CLIENT + index);
317 PreSetController(identifier);
318 }
319}
320
321const PadIdentifier UDPClient::GetPadIdentifier(std::size_t pad_index) const {
322 const std::size_t client = pad_index / PADS_PER_CLIENT;
323 return {
324 .guid = clients[client].uuid,
325 .port = static_cast<std::size_t>(clients[client].port),
326 .pad = pad_index,
327 };
328}
329
330const Common::UUID UDPClient::GetHostUUID(const std::string host) const {
331 const auto ip = boost::asio::ip::address_v4::from_string(host);
332 const auto hex_host = fmt::format("{:06x}", ip.to_ulong());
333 return Common::UUID{hex_host};
334}
335
336void UDPClient::Reset() {
337 for (auto& client : clients) {
338 if (client.thread.joinable()) {
339 client.active = -1;
340 client.socket->Stop();
341 client.thread.join();
342 }
343 }
344}
345
346std::vector<Common::ParamPackage> UDPClient::GetInputDevices() const {
347 std::vector<Common::ParamPackage> devices;
348 if (!Settings::values.enable_udp_controller) {
349 return devices;
350 }
351 for (std::size_t client = 0; client < clients.size(); client++) {
352 if (clients[client].active != 1) {
353 continue;
354 }
355 for (std::size_t index = 0; index < PADS_PER_CLIENT; ++index) {
356 const std::size_t pad_index = client * PADS_PER_CLIENT + index;
357 if (!pads[pad_index].connected) {
358 continue;
359 }
360 const auto pad_identifier = GetPadIdentifier(pad_index);
361 Common::ParamPackage identifier{};
362 identifier.Set("engine", GetEngineName());
363 identifier.Set("display", fmt::format("UDP Controller {}", pad_identifier.pad));
364 identifier.Set("guid", pad_identifier.guid.Format());
365 identifier.Set("port", static_cast<int>(pad_identifier.port));
366 identifier.Set("pad", static_cast<int>(pad_identifier.pad));
367 devices.emplace_back(identifier);
368 }
369 }
370 return devices;
371}
372
373ButtonMapping UDPClient::GetButtonMappingForDevice(const Common::ParamPackage& params) {
374 // This list excludes any button that can't be really mapped
375 static constexpr std::array<std::pair<Settings::NativeButton::Values, PadButton>, 18>
376 switch_to_dsu_button = {
377 std::pair{Settings::NativeButton::A, PadButton::Circle},
378 {Settings::NativeButton::B, PadButton::Cross},
379 {Settings::NativeButton::X, PadButton::Triangle},
380 {Settings::NativeButton::Y, PadButton::Square},
381 {Settings::NativeButton::Plus, PadButton::Options},
382 {Settings::NativeButton::Minus, PadButton::Share},
383 {Settings::NativeButton::DLeft, PadButton::Left},
384 {Settings::NativeButton::DUp, PadButton::Up},
385 {Settings::NativeButton::DRight, PadButton::Right},
386 {Settings::NativeButton::DDown, PadButton::Down},
387 {Settings::NativeButton::L, PadButton::L1},
388 {Settings::NativeButton::R, PadButton::R1},
389 {Settings::NativeButton::ZL, PadButton::L2},
390 {Settings::NativeButton::ZR, PadButton::R2},
391 {Settings::NativeButton::SL, PadButton::L2},
392 {Settings::NativeButton::SR, PadButton::R2},
393 {Settings::NativeButton::LStick, PadButton::L3},
394 {Settings::NativeButton::RStick, PadButton::R3},
395 };
396 if (!params.Has("guid") || !params.Has("port") || !params.Has("pad")) {
397 return {};
398 }
399
400 ButtonMapping mapping{};
401 for (const auto& [switch_button, dsu_button] : switch_to_dsu_button) {
402 Common::ParamPackage button_params{};
403 button_params.Set("engine", GetEngineName());
404 button_params.Set("guid", params.Get("guid", ""));
405 button_params.Set("port", params.Get("port", 0));
406 button_params.Set("pad", params.Get("pad", 0));
407 button_params.Set("button", static_cast<int>(dsu_button));
408 mapping.insert_or_assign(switch_button, std::move(button_params));
409 }
410
411 return mapping;
412}
413
414AnalogMapping UDPClient::GetAnalogMappingForDevice(const Common::ParamPackage& params) {
415 if (!params.Has("guid") || !params.Has("port") || !params.Has("pad")) {
416 return {};
417 }
418
419 AnalogMapping mapping = {};
420 Common::ParamPackage left_analog_params;
421 left_analog_params.Set("engine", GetEngineName());
422 left_analog_params.Set("guid", params.Get("guid", ""));
423 left_analog_params.Set("port", params.Get("port", 0));
424 left_analog_params.Set("pad", params.Get("pad", 0));
425 left_analog_params.Set("axis_x", static_cast<int>(PadAxes::LeftStickX));
426 left_analog_params.Set("axis_y", static_cast<int>(PadAxes::LeftStickY));
427 mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params));
428 Common::ParamPackage right_analog_params;
429 right_analog_params.Set("engine", GetEngineName());
430 right_analog_params.Set("guid", params.Get("guid", ""));
431 right_analog_params.Set("port", params.Get("port", 0));
432 right_analog_params.Set("pad", params.Get("pad", 0));
433 right_analog_params.Set("axis_x", static_cast<int>(PadAxes::RightStickX));
434 right_analog_params.Set("axis_y", static_cast<int>(PadAxes::RightStickY));
435 mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params));
436 return mapping;
437}
438
439MotionMapping UDPClient::GetMotionMappingForDevice(const Common::ParamPackage& params) {
440 if (!params.Has("guid") || !params.Has("port") || !params.Has("pad")) {
441 return {};
442 }
443
444 MotionMapping mapping = {};
445 Common::ParamPackage motion_params;
446 motion_params.Set("engine", GetEngineName());
447 motion_params.Set("guid", params.Get("guid", ""));
448 motion_params.Set("port", params.Get("port", 0));
449 motion_params.Set("pad", params.Get("pad", 0));
450 motion_params.Set("motion", 0);
451 mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, std::move(motion_params));
452 mapping.insert_or_assign(Settings::NativeMotion::MotionRight, std::move(motion_params));
453 return mapping;
454}
455
456Common::Input::ButtonNames UDPClient::GetUIButtonName(const Common::ParamPackage& params) const {
457 PadButton button = static_cast<PadButton>(params.Get("button", 0));
458 switch (button) {
459 case PadButton::Left:
460 return Common::Input::ButtonNames::ButtonLeft;
461 case PadButton::Right:
462 return Common::Input::ButtonNames::ButtonRight;
463 case PadButton::Down:
464 return Common::Input::ButtonNames::ButtonDown;
465 case PadButton::Up:
466 return Common::Input::ButtonNames::ButtonUp;
467 case PadButton::L1:
468 return Common::Input::ButtonNames::L1;
469 case PadButton::L2:
470 return Common::Input::ButtonNames::L2;
471 case PadButton::L3:
472 return Common::Input::ButtonNames::L3;
473 case PadButton::R1:
474 return Common::Input::ButtonNames::R1;
475 case PadButton::R2:
476 return Common::Input::ButtonNames::R2;
477 case PadButton::R3:
478 return Common::Input::ButtonNames::R3;
479 case PadButton::Circle:
480 return Common::Input::ButtonNames::Circle;
481 case PadButton::Cross:
482 return Common::Input::ButtonNames::Cross;
483 case PadButton::Square:
484 return Common::Input::ButtonNames::Square;
485 case PadButton::Triangle:
486 return Common::Input::ButtonNames::Triangle;
487 case PadButton::Share:
488 return Common::Input::ButtonNames::Share;
489 case PadButton::Options:
490 return Common::Input::ButtonNames::Options;
491 default:
492 return Common::Input::ButtonNames::Undefined;
493 }
494}
495
496Common::Input::ButtonNames UDPClient::GetUIName(const Common::ParamPackage& params) const {
497 if (params.Has("button")) {
498 return GetUIButtonName(params);
499 }
500 if (params.Has("axis")) {
501 return Common::Input::ButtonNames::Value;
502 }
503 if (params.Has("motion")) {
504 return Common::Input::ButtonNames::Engine;
505 }
506
507 return Common::Input::ButtonNames::Invalid;
508}
509
510void TestCommunication(const std::string& host, u16 port,
511 const std::function<void()>& success_callback,
512 const std::function<void()>& failure_callback) {
513 std::thread([=] {
514 Common::Event success_event;
515 SocketCallback callback{
516 .version = [](Response::Version) {},
517 .port_info = [](Response::PortInfo) {},
518 .pad_data = [&](Response::PadData) { success_event.Set(); },
519 };
520 Socket socket{host, port, std::move(callback)};
521 std::thread worker_thread{SocketLoop, &socket};
522 const bool result =
523 success_event.WaitUntil(std::chrono::steady_clock::now() + std::chrono::seconds(10));
524 socket.Stop();
525 worker_thread.join();
526 if (result) {
527 success_callback();
528 } else {
529 failure_callback();
530 }
531 }).detach();
532}
533
534CalibrationConfigurationJob::CalibrationConfigurationJob(
535 const std::string& host, u16 port, std::function<void(Status)> status_callback,
536 std::function<void(u16, u16, u16, u16)> data_callback) {
537
538 std::thread([=, this] {
539 Status current_status{Status::Initialized};
540 SocketCallback callback{
541 [](Response::Version) {}, [](Response::PortInfo) {},
542 [&](Response::PadData data) {
543 static constexpr u16 CALIBRATION_THRESHOLD = 100;
544 static constexpr u16 MAX_VALUE = UINT16_MAX;
545
546 if (current_status == Status::Initialized) {
547 // Receiving data means the communication is ready now
548 current_status = Status::Ready;
549 status_callback(current_status);
550 }
551 const auto& touchpad_0 = data.touch[0];
552 if (touchpad_0.is_active == 0) {
553 return;
554 }
555 LOG_DEBUG(Input, "Current touch: {} {}", touchpad_0.x, touchpad_0.y);
556 const u16 min_x = std::min(MAX_VALUE, static_cast<u16>(touchpad_0.x));
557 const u16 min_y = std::min(MAX_VALUE, static_cast<u16>(touchpad_0.y));
558 if (current_status == Status::Ready) {
559 // First touch - min data (min_x/min_y)
560 current_status = Status::Stage1Completed;
561 status_callback(current_status);
562 }
563 if (touchpad_0.x - min_x > CALIBRATION_THRESHOLD &&
564 touchpad_0.y - min_y > CALIBRATION_THRESHOLD) {
565 // Set the current position as max value and finishes configuration
566 const u16 max_x = touchpad_0.x;
567 const u16 max_y = touchpad_0.y;
568 current_status = Status::Completed;
569 data_callback(min_x, min_y, max_x, max_y);
570 status_callback(current_status);
571
572 complete_event.Set();
573 }
574 }};
575 Socket socket{host, port, std::move(callback)};
576 std::thread worker_thread{SocketLoop, &socket};
577 complete_event.Wait();
578 socket.Stop();
579 worker_thread.join();
580 }).detach();
581}
582
583CalibrationConfigurationJob::~CalibrationConfigurationJob() {
584 Stop();
585}
586
587void CalibrationConfigurationJob::Stop() {
588 complete_event.Set();
589}
590
591} // 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 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <functional>
8#include <memory>
9#include <mutex>
10#include <optional> 7#include <optional>
11#include <string> 8
12#include <thread>
13#include <tuple>
14#include "common/common_types.h" 9#include "common/common_types.h"
15#include "common/param_package.h"
16#include "common/thread.h" 10#include "common/thread.h"
17#include "common/threadsafe_queue.h" 11#include "input_common/input_engine.h"
18#include "common/vector_math.h"
19#include "core/frontend/input.h"
20#include "input_common/motion_input.h"
21 12
22namespace InputCommon::CemuhookUDP { 13namespace InputCommon::CemuhookUDP {
23 14
@@ -30,16 +21,6 @@ struct TouchPad;
30struct Version; 21struct Version;
31} // namespace Response 22} // namespace Response
32 23
33enum class PadMotion {
34 GyroX,
35 GyroY,
36 GyroZ,
37 AccX,
38 AccY,
39 AccZ,
40 Undefined,
41};
42
43enum class PadTouch { 24enum class PadTouch {
44 Click, 25 Click,
45 Undefined, 26 Undefined,
@@ -49,14 +30,10 @@ struct UDPPadStatus {
49 std::string host{"127.0.0.1"}; 30 std::string host{"127.0.0.1"};
50 u16 port{26760}; 31 u16 port{26760};
51 std::size_t pad_index{}; 32 std::size_t pad_index{};
52 PadMotion motion{PadMotion::Undefined};
53 f32 motion_value{0.0f};
54}; 33};
55 34
56struct DeviceStatus { 35struct DeviceStatus {
57 std::mutex update_mutex; 36 std::mutex update_mutex;
58 Input::MotionStatus motion_status;
59 std::tuple<float, float, bool> touch_status;
60 37
61 // calibration data for scaling the device's touch area to 3ds 38 // calibration data for scaling the device's touch area to 3ds
62 struct CalibrationData { 39 struct CalibrationData {
@@ -68,48 +45,85 @@ struct DeviceStatus {
68 std::optional<CalibrationData> touch_calibration; 45 std::optional<CalibrationData> touch_calibration;
69}; 46};
70 47
71class Client { 48/**
49 * A button device factory representing a keyboard. It receives keyboard events and forward them
50 * to all button devices it created.
51 */
52class UDPClient final : public InputCommon::InputEngine {
72public: 53public:
73 // Initialize the UDP client capture and read sequence 54 explicit UDPClient(const std::string& input_engine_);
74 Client(); 55 ~UDPClient();
75
76 // Close and release the client
77 ~Client();
78
79 // Used for polling
80 void BeginConfiguration();
81 void EndConfiguration();
82
83 std::vector<Common::ParamPackage> GetInputDevices() const;
84 56
85 bool DeviceConnected(std::size_t pad) const;
86 void ReloadSockets(); 57 void ReloadSockets();
87 58
88 Common::SPSCQueue<UDPPadStatus>& GetPadQueue(); 59 /// Used for automapping features
89 const Common::SPSCQueue<UDPPadStatus>& GetPadQueue() const; 60 std::vector<Common::ParamPackage> GetInputDevices() const override;
61 ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override;
62 AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override;
63 MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& params) override;
64 Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override;
90 65
91 DeviceStatus& GetPadState(const std::string& host, u16 port, std::size_t pad); 66private:
92 const DeviceStatus& GetPadState(const std::string& host, u16 port, std::size_t pad) const; 67 enum class PadButton {
68 Undefined = 0x0000,
69 Share = 0x0001,
70 L3 = 0x0002,
71 R3 = 0x0004,
72 Options = 0x0008,
73 Up = 0x0010,
74 Right = 0x0020,
75 Down = 0x0040,
76 Left = 0x0080,
77 L2 = 0x0100,
78 R2 = 0x0200,
79 L1 = 0x0400,
80 R1 = 0x0800,
81 Triangle = 0x1000,
82 Circle = 0x2000,
83 Cross = 0x4000,
84 Square = 0x8000,
85 Touch1 = 0x10000,
86 touch2 = 0x20000,
87 };
93 88
94 Input::TouchStatus& GetTouchState(); 89 enum class PadAxes : u8 {
95 const Input::TouchStatus& GetTouchState() const; 90 LeftStickX,
91 LeftStickY,
92 RightStickX,
93 RightStickY,
94 AnalogLeft,
95 AnalogDown,
96 AnalogRight,
97 AnalogUp,
98 AnalogSquare,
99 AnalogCross,
100 AnalogCircle,
101 AnalogTriangle,
102 AnalogR1,
103 AnalogL1,
104 AnalogR2,
105 AnalogL3,
106 AnalogR3,
107 Touch1X,
108 Touch1Y,
109 Touch2X,
110 Touch2Y,
111 Undefined,
112 };
96 113
97private:
98 struct PadData { 114 struct PadData {
99 std::size_t pad_index{}; 115 std::size_t pad_index{};
100 bool connected{}; 116 bool connected{};
101 DeviceStatus status; 117 DeviceStatus status;
102 u64 packet_sequence{}; 118 u64 packet_sequence{};
103 119
104 // Realtime values
105 // motion is initalized with PID values for drift correction on joycons
106 InputCommon::MotionInput motion{0.3f, 0.005f, 0.0f};
107 std::chrono::time_point<std::chrono::steady_clock> last_update; 120 std::chrono::time_point<std::chrono::steady_clock> last_update;
108 }; 121 };
109 122
110 struct ClientConnection { 123 struct ClientConnection {
111 ClientConnection(); 124 ClientConnection();
112 ~ClientConnection(); 125 ~ClientConnection();
126 Common::UUID uuid{"7F000001"};
113 std::string host{"127.0.0.1"}; 127 std::string host{"127.0.0.1"};
114 u16 port{26760}; 128 u16 port{26760};
115 s8 active{-1}; 129 s8 active{-1};
@@ -127,28 +141,16 @@ private:
127 void OnPortInfo(Response::PortInfo); 141 void OnPortInfo(Response::PortInfo);
128 void OnPadData(Response::PadData, std::size_t client); 142 void OnPadData(Response::PadData, std::size_t client);
129 void StartCommunication(std::size_t client, const std::string& host, u16 port); 143 void StartCommunication(std::size_t client, const std::string& host, u16 port);
130 void UpdateYuzuSettings(std::size_t client, std::size_t pad_index, 144 const PadIdentifier GetPadIdentifier(std::size_t pad_index) const;
131 const Common::Vec3<float>& acc, const Common::Vec3<float>& gyro); 145 const Common::UUID GetHostUUID(const std::string host) const;
132
133 // Returns an unused finger id, if there is no fingers available std::nullopt will be
134 // returned
135 std::optional<std::size_t> GetUnusedFingerID() const;
136
137 // Merges and updates all touch inputs into the touch_status array
138 void UpdateTouchInput(Response::TouchPad& touch_pad, std::size_t client, std::size_t id);
139 146
140 bool configuring = false; 147 Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const;
141 148
142 // Allocate clients for 8 udp servers 149 // Allocate clients for 8 udp servers
143 static constexpr std::size_t MAX_UDP_CLIENTS = 8; 150 static constexpr std::size_t MAX_UDP_CLIENTS = 8;
144 static constexpr std::size_t PADS_PER_CLIENT = 4; 151 static constexpr std::size_t PADS_PER_CLIENT = 4;
145 // Each client can have up 2 touch inputs
146 static constexpr std::size_t MAX_TOUCH_FINGERS = MAX_UDP_CLIENTS * 2;
147 std::array<PadData, MAX_UDP_CLIENTS * PADS_PER_CLIENT> pads{}; 152 std::array<PadData, MAX_UDP_CLIENTS * PADS_PER_CLIENT> pads{};
148 std::array<ClientConnection, MAX_UDP_CLIENTS> clients{}; 153 std::array<ClientConnection, MAX_UDP_CLIENTS> clients{};
149 Common::SPSCQueue<UDPPadStatus> pad_queue{};
150 Input::TouchStatus touch_status{};
151 std::array<std::size_t, MAX_TOUCH_FINGERS> finger_id{};
152}; 154};
153 155
154/// An async job allowing configuration of the touchpad calibration. 156/// 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 @@
1// Copyright 2014 Dolphin Emulator Project
2// Licensed under GPLv2+
3// Refer to the license.txt file included.
4
5#pragma once
6#include <algorithm>
7#include <functional>
8#include <mutex>
9#include <thread>
10#include <unordered_map>
11#include "common/common_types.h"
12#include "common/threadsafe_queue.h"
13#include "input_common/main.h"
14
15struct libusb_context;
16struct libusb_device;
17struct libusb_device_handle;
18
19namespace GCAdapter {
20
21enum class PadButton {
22 Undefined = 0x0000,
23 ButtonLeft = 0x0001,
24 ButtonRight = 0x0002,
25 ButtonDown = 0x0004,
26 ButtonUp = 0x0008,
27 TriggerZ = 0x0010,
28 TriggerR = 0x0020,
29 TriggerL = 0x0040,
30 ButtonA = 0x0100,
31 ButtonB = 0x0200,
32 ButtonX = 0x0400,
33 ButtonY = 0x0800,
34 ButtonStart = 0x1000,
35 // Below is for compatibility with "AxisButton" type
36 Stick = 0x2000,
37};
38
39enum class PadAxes : u8 {
40 StickX,
41 StickY,
42 SubstickX,
43 SubstickY,
44 TriggerLeft,
45 TriggerRight,
46 Undefined,
47};
48
49enum class ControllerTypes {
50 None,
51 Wired,
52 Wireless,
53};
54
55struct GCPadStatus {
56 std::size_t port{};
57
58 PadButton button{PadButton::Undefined}; // Or-ed PAD_BUTTON_* and PAD_TRIGGER_* bits
59
60 PadAxes axis{PadAxes::Undefined};
61 s16 axis_value{};
62 u8 axis_threshold{50};
63};
64
65struct GCController {
66 ControllerTypes type{};
67 bool enable_vibration{};
68 u8 rumble_amplitude{};
69 u16 buttons{};
70 PadButton last_button{};
71 std::array<s16, 6> axis_values{};
72 std::array<u8, 6> axis_origin{};
73 u8 reset_origin_counter{};
74};
75
76class Adapter {
77public:
78 Adapter();
79 ~Adapter();
80
81 /// Request a vibration for a controller
82 bool RumblePlay(std::size_t port, u8 amplitude);
83
84 /// Used for polling
85 void BeginConfiguration();
86 void EndConfiguration();
87
88 Common::SPSCQueue<GCPadStatus>& GetPadQueue();
89 const Common::SPSCQueue<GCPadStatus>& GetPadQueue() const;
90
91 GCController& GetPadState(std::size_t port);
92 const GCController& GetPadState(std::size_t port) const;
93
94 /// Returns true if there is a device connected to port
95 bool DeviceConnected(std::size_t port) const;
96
97 /// Used for automapping features
98 std::vector<Common::ParamPackage> GetInputDevices() const;
99 InputCommon::ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) const;
100 InputCommon::AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) const;
101
102private:
103 using AdapterPayload = std::array<u8, 37>;
104
105 void UpdatePadType(std::size_t port, ControllerTypes pad_type);
106 void UpdateControllers(const AdapterPayload& adapter_payload);
107 void UpdateYuzuSettings(std::size_t port);
108 void UpdateStateButtons(std::size_t port, u8 b1, u8 b2);
109 void UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload);
110 void UpdateVibrations();
111
112 void AdapterInputThread();
113
114 void AdapterScanThread();
115
116 bool IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size);
117
118 // Updates vibration state of all controllers
119 void SendVibrations();
120
121 /// For use in initialization, querying devices to find the adapter
122 void Setup();
123
124 /// Resets status of all GC controller devices to a disconnected state
125 void ResetDevices();
126
127 /// Resets status of device connected to a disconnected state
128 void ResetDevice(std::size_t port);
129
130 /// Returns true if we successfully gain access to GC Adapter
131 bool CheckDeviceAccess();
132
133 /// Captures GC Adapter endpoint address
134 /// Returns true if the endpoint was set correctly
135 bool GetGCEndpoint(libusb_device* device);
136
137 /// For shutting down, clear all data, join all threads, release usb
138 void Reset();
139
140 // Join all threads
141 void JoinThreads();
142
143 // Release usb handles
144 void ClearLibusbHandle();
145
146 libusb_device_handle* usb_adapter_handle = nullptr;
147 std::array<GCController, 4> pads;
148 Common::SPSCQueue<GCPadStatus> pad_queue;
149
150 std::thread adapter_input_thread;
151 std::thread adapter_scan_thread;
152 bool adapter_input_thread_running;
153 bool adapter_scan_thread_running;
154 bool restart_scan_thread;
155
156 libusb_context* libusb_ctx;
157
158 u8 input_endpoint{0};
159 u8 output_endpoint{0};
160 u8 input_error_counter{0};
161 u8 output_error_counter{0};
162 int vibration_counter{0};
163
164 bool configuring{false};
165 bool rumble_enabled{true};
166 bool vibration_changed{true};
167};
168} // 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 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <atomic>
6#include <list>
7#include <mutex>
8#include <utility>
9#include "common/assert.h"
10#include "common/threadsafe_queue.h"
11#include "input_common/gcadapter/gc_adapter.h"
12#include "input_common/gcadapter/gc_poller.h"
13
14namespace InputCommon {
15
16class GCButton final : public Input::ButtonDevice {
17public:
18 explicit GCButton(u32 port_, s32 button_, const GCAdapter::Adapter* adapter)
19 : port(port_), button(button_), gcadapter(adapter) {}
20
21 ~GCButton() override;
22
23 bool GetStatus() const override {
24 if (gcadapter->DeviceConnected(port)) {
25 return (gcadapter->GetPadState(port).buttons & button) != 0;
26 }
27 return false;
28 }
29
30private:
31 const u32 port;
32 const s32 button;
33 const GCAdapter::Adapter* gcadapter;
34};
35
36class GCAxisButton final : public Input::ButtonDevice {
37public:
38 explicit GCAxisButton(u32 port_, u32 axis_, float threshold_, bool trigger_if_greater_,
39 const GCAdapter::Adapter* adapter)
40 : port(port_), axis(axis_), threshold(threshold_), trigger_if_greater(trigger_if_greater_),
41 gcadapter(adapter) {}
42
43 bool GetStatus() const override {
44 if (gcadapter->DeviceConnected(port)) {
45 const float current_axis_value = gcadapter->GetPadState(port).axis_values.at(axis);
46 const float axis_value = current_axis_value / 128.0f;
47 if (trigger_if_greater) {
48 // TODO: Might be worthwile to set a slider for the trigger threshold. It is
49 // currently always set to 0.5 in configure_input_player.cpp ZL/ZR HandleClick
50 return axis_value > threshold;
51 }
52 return axis_value < -threshold;
53 }
54 return false;
55 }
56
57private:
58 const u32 port;
59 const u32 axis;
60 float threshold;
61 bool trigger_if_greater;
62 const GCAdapter::Adapter* gcadapter;
63};
64
65GCButtonFactory::GCButtonFactory(std::shared_ptr<GCAdapter::Adapter> adapter_)
66 : adapter(std::move(adapter_)) {}
67
68GCButton::~GCButton() = default;
69
70std::unique_ptr<Input::ButtonDevice> GCButtonFactory::Create(const Common::ParamPackage& params) {
71 const auto button_id = params.Get("button", 0);
72 const auto port = static_cast<u32>(params.Get("port", 0));
73
74 constexpr s32 PAD_STICK_ID = static_cast<s32>(GCAdapter::PadButton::Stick);
75
76 // button is not an axis/stick button
77 if (button_id != PAD_STICK_ID) {
78 return std::make_unique<GCButton>(port, button_id, adapter.get());
79 }
80
81 // For Axis buttons, used by the binary sticks.
82 if (button_id == PAD_STICK_ID) {
83 const int axis = params.Get("axis", 0);
84 const float threshold = params.Get("threshold", 0.25f);
85 const std::string direction_name = params.Get("direction", "");
86 bool trigger_if_greater;
87 if (direction_name == "+") {
88 trigger_if_greater = true;
89 } else if (direction_name == "-") {
90 trigger_if_greater = false;
91 } else {
92 trigger_if_greater = true;
93 LOG_ERROR(Input, "Unknown direction {}", direction_name);
94 }
95 return std::make_unique<GCAxisButton>(port, axis, threshold, trigger_if_greater,
96 adapter.get());
97 }
98
99 return nullptr;
100}
101
102Common::ParamPackage GCButtonFactory::GetNextInput() const {
103 Common::ParamPackage params;
104 GCAdapter::GCPadStatus pad;
105 auto& queue = adapter->GetPadQueue();
106 while (queue.Pop(pad)) {
107 // This while loop will break on the earliest detected button
108 params.Set("engine", "gcpad");
109 params.Set("port", static_cast<s32>(pad.port));
110 if (pad.button != GCAdapter::PadButton::Undefined) {
111 params.Set("button", static_cast<u16>(pad.button));
112 }
113
114 // For Axis button implementation
115 if (pad.axis != GCAdapter::PadAxes::Undefined) {
116 params.Set("axis", static_cast<u8>(pad.axis));
117 params.Set("button", static_cast<u16>(GCAdapter::PadButton::Stick));
118 params.Set("threshold", "0.25");
119 if (pad.axis_value > 0) {
120 params.Set("direction", "+");
121 } else {
122 params.Set("direction", "-");
123 }
124 break;
125 }
126 }
127 return params;
128}
129
130void GCButtonFactory::BeginConfiguration() {
131 polling = true;
132 adapter->BeginConfiguration();
133}
134
135void GCButtonFactory::EndConfiguration() {
136 polling = false;
137 adapter->EndConfiguration();
138}
139
140class GCAnalog final : public Input::AnalogDevice {
141public:
142 explicit GCAnalog(u32 port_, u32 axis_x_, u32 axis_y_, bool invert_x_, bool invert_y_,
143 float deadzone_, float range_, const GCAdapter::Adapter* adapter)
144 : port(port_), axis_x(axis_x_), axis_y(axis_y_), invert_x(invert_x_), invert_y(invert_y_),
145 deadzone(deadzone_), range(range_), gcadapter(adapter) {}
146
147 float GetAxis(u32 axis) const {
148 if (gcadapter->DeviceConnected(port)) {
149 std::lock_guard lock{mutex};
150 const auto axis_value =
151 static_cast<float>(gcadapter->GetPadState(port).axis_values.at(axis));
152 return (axis_value) / (100.0f * range);
153 }
154 return 0.0f;
155 }
156
157 std::pair<float, float> GetAnalog(u32 analog_axis_x, u32 analog_axis_y) const {
158 float x = GetAxis(analog_axis_x);
159 float y = GetAxis(analog_axis_y);
160 if (invert_x) {
161 x = -x;
162 }
163 if (invert_y) {
164 y = -y;
165 }
166 // Make sure the coordinates are in the unit circle,
167 // otherwise normalize it.
168 float r = x * x + y * y;
169 if (r > 1.0f) {
170 r = std::sqrt(r);
171 x /= r;
172 y /= r;
173 }
174
175 return {x, y};
176 }
177
178 std::tuple<float, float> GetStatus() const override {
179 const auto [x, y] = GetAnalog(axis_x, axis_y);
180 const float r = std::sqrt((x * x) + (y * y));
181 if (r > deadzone) {
182 return {x / r * (r - deadzone) / (1 - deadzone),
183 y / r * (r - deadzone) / (1 - deadzone)};
184 }
185 return {0.0f, 0.0f};
186 }
187
188 std::tuple<float, float> GetRawStatus() const override {
189 const float x = GetAxis(axis_x);
190 const float y = GetAxis(axis_y);
191 return {x, y};
192 }
193
194 Input::AnalogProperties GetAnalogProperties() const override {
195 return {deadzone, range, 0.5f};
196 }
197
198 bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override {
199 const auto [x, y] = GetStatus();
200 const float directional_deadzone = 0.5f;
201 switch (direction) {
202 case Input::AnalogDirection::RIGHT:
203 return x > directional_deadzone;
204 case Input::AnalogDirection::LEFT:
205 return x < -directional_deadzone;
206 case Input::AnalogDirection::UP:
207 return y > directional_deadzone;
208 case Input::AnalogDirection::DOWN:
209 return y < -directional_deadzone;
210 }
211 return false;
212 }
213
214private:
215 const u32 port;
216 const u32 axis_x;
217 const u32 axis_y;
218 const bool invert_x;
219 const bool invert_y;
220 const float deadzone;
221 const float range;
222 const GCAdapter::Adapter* gcadapter;
223 mutable std::mutex mutex;
224};
225
226/// An analog device factory that creates analog devices from GC Adapter
227GCAnalogFactory::GCAnalogFactory(std::shared_ptr<GCAdapter::Adapter> adapter_)
228 : adapter(std::move(adapter_)) {}
229
230/**
231 * Creates analog device from joystick axes
232 * @param params contains parameters for creating the device:
233 * - "port": the nth gcpad on the adapter
234 * - "axis_x": the index of the axis to be bind as x-axis
235 * - "axis_y": the index of the axis to be bind as y-axis
236 */
237std::unique_ptr<Input::AnalogDevice> GCAnalogFactory::Create(const Common::ParamPackage& params) {
238 const auto port = static_cast<u32>(params.Get("port", 0));
239 const auto axis_x = static_cast<u32>(params.Get("axis_x", 0));
240 const auto axis_y = static_cast<u32>(params.Get("axis_y", 1));
241 const auto deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f);
242 const auto range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f);
243 const std::string invert_x_value = params.Get("invert_x", "+");
244 const std::string invert_y_value = params.Get("invert_y", "+");
245 const bool invert_x = invert_x_value == "-";
246 const bool invert_y = invert_y_value == "-";
247
248 return std::make_unique<GCAnalog>(port, axis_x, axis_y, invert_x, invert_y, deadzone, range,
249 adapter.get());
250}
251
252void GCAnalogFactory::BeginConfiguration() {
253 polling = true;
254 adapter->BeginConfiguration();
255}
256
257void GCAnalogFactory::EndConfiguration() {
258 polling = false;
259 adapter->EndConfiguration();
260}
261
262Common::ParamPackage GCAnalogFactory::GetNextInput() {
263 GCAdapter::GCPadStatus pad;
264 Common::ParamPackage params;
265 auto& queue = adapter->GetPadQueue();
266 while (queue.Pop(pad)) {
267 if (pad.button != GCAdapter::PadButton::Undefined) {
268 params.Set("engine", "gcpad");
269 params.Set("port", static_cast<s32>(pad.port));
270 params.Set("button", static_cast<u16>(pad.button));
271 return params;
272 }
273 if (pad.axis == GCAdapter::PadAxes::Undefined ||
274 std::abs(static_cast<float>(pad.axis_value) / 128.0f) < 0.1f) {
275 continue;
276 }
277 // An analog device needs two axes, so we need to store the axis for later and wait for
278 // a second input event. The axes also must be from the same joystick.
279 const u8 axis = static_cast<u8>(pad.axis);
280 if (axis == 0 || axis == 1) {
281 analog_x_axis = 0;
282 analog_y_axis = 1;
283 controller_number = static_cast<s32>(pad.port);
284 break;
285 }
286 if (axis == 2 || axis == 3) {
287 analog_x_axis = 2;
288 analog_y_axis = 3;
289 controller_number = static_cast<s32>(pad.port);
290 break;
291 }
292
293 if (analog_x_axis == -1) {
294 analog_x_axis = axis;
295 controller_number = static_cast<s32>(pad.port);
296 } else if (analog_y_axis == -1 && analog_x_axis != axis &&
297 controller_number == static_cast<s32>(pad.port)) {
298 analog_y_axis = axis;
299 break;
300 }
301 }
302 if (analog_x_axis != -1 && analog_y_axis != -1) {
303 params.Set("engine", "gcpad");
304 params.Set("port", controller_number);
305 params.Set("axis_x", analog_x_axis);
306 params.Set("axis_y", analog_y_axis);
307 params.Set("invert_x", "+");
308 params.Set("invert_y", "+");
309 analog_x_axis = -1;
310 analog_y_axis = -1;
311 controller_number = -1;
312 return params;
313 }
314 return params;
315}
316
317class GCVibration final : public Input::VibrationDevice {
318public:
319 explicit GCVibration(u32 port_, GCAdapter::Adapter* adapter)
320 : port(port_), gcadapter(adapter) {}
321
322 u8 GetStatus() const override {
323 return gcadapter->RumblePlay(port, 0);
324 }
325
326 bool SetRumblePlay(f32 amp_low, [[maybe_unused]] f32 freq_low, f32 amp_high,
327 [[maybe_unused]] f32 freq_high) const override {
328 const auto mean_amplitude = (amp_low + amp_high) * 0.5f;
329 const auto processed_amplitude =
330 static_cast<u8>((mean_amplitude + std::pow(mean_amplitude, 0.3f)) * 0.5f * 0x8);
331
332 return gcadapter->RumblePlay(port, processed_amplitude);
333 }
334
335private:
336 const u32 port;
337 GCAdapter::Adapter* gcadapter;
338};
339
340/// An vibration device factory that creates vibration devices from GC Adapter
341GCVibrationFactory::GCVibrationFactory(std::shared_ptr<GCAdapter::Adapter> adapter_)
342 : adapter(std::move(adapter_)) {}
343
344/**
345 * Creates a vibration device from a joystick
346 * @param params contains parameters for creating the device:
347 * - "port": the nth gcpad on the adapter
348 */
349std::unique_ptr<Input::VibrationDevice> GCVibrationFactory::Create(
350 const Common::ParamPackage& params) {
351 const auto port = static_cast<u32>(params.Get("port", 0));
352
353 return std::make_unique<GCVibration>(port, adapter.get());
354}
355
356} // 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 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include "core/frontend/input.h"
9#include "input_common/gcadapter/gc_adapter.h"
10
11namespace InputCommon {
12
13/**
14 * A button device factory representing a gcpad. It receives gcpad events and forward them
15 * to all button devices it created.
16 */
17class GCButtonFactory final : public Input::Factory<Input::ButtonDevice> {
18public:
19 explicit GCButtonFactory(std::shared_ptr<GCAdapter::Adapter> adapter_);
20
21 /**
22 * Creates a button device from a button press
23 * @param params contains parameters for creating the device:
24 * - "code": the code of the key to bind with the button
25 */
26 std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override;
27
28 Common::ParamPackage GetNextInput() const;
29
30 /// For device input configuration/polling
31 void BeginConfiguration();
32 void EndConfiguration();
33
34 bool IsPolling() const {
35 return polling;
36 }
37
38private:
39 std::shared_ptr<GCAdapter::Adapter> adapter;
40 bool polling = false;
41};
42
43/// An analog device factory that creates analog devices from GC Adapter
44class GCAnalogFactory final : public Input::Factory<Input::AnalogDevice> {
45public:
46 explicit GCAnalogFactory(std::shared_ptr<GCAdapter::Adapter> adapter_);
47
48 std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override;
49 Common::ParamPackage GetNextInput();
50
51 /// For device input configuration/polling
52 void BeginConfiguration();
53 void EndConfiguration();
54
55 bool IsPolling() const {
56 return polling;
57 }
58
59private:
60 std::shared_ptr<GCAdapter::Adapter> adapter;
61 int analog_x_axis = -1;
62 int analog_y_axis = -1;
63 int controller_number = -1;
64 bool polling = false;
65};
66
67/// A vibration device factory creates vibration devices from GC Adapter
68class GCVibrationFactory final : public Input::Factory<Input::VibrationDevice> {
69public:
70 explicit GCVibrationFactory(std::shared_ptr<GCAdapter::Adapter> adapter_);
71
72 std::unique_ptr<Input::VibrationDevice> Create(const Common::ParamPackage& params) override;
73
74private:
75 std::shared_ptr<GCAdapter::Adapter> adapter;
76};
77
78} // 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 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <chrono>
6#include <cmath>
7#include "common/math_util.h"
8#include "common/settings.h"
9#include "input_common/helpers/stick_from_buttons.h"
10
11namespace InputCommon {
12
13class Stick final : public Common::Input::InputDevice {
14public:
15 using Button = std::unique_ptr<Common::Input::InputDevice>;
16
17 Stick(Button up_, Button down_, Button left_, Button right_, Button modifier_,
18 float modifier_scale_, float modifier_angle_)
19 : up(std::move(up_)), down(std::move(down_)), left(std::move(left_)),
20 right(std::move(right_)), modifier(std::move(modifier_)), modifier_scale(modifier_scale_),
21 modifier_angle(modifier_angle_) {
22 Common::Input::InputCallback button_up_callback{
23 [this](Common::Input::CallbackStatus callback_) { UpdateUpButtonStatus(callback_); }};
24 Common::Input::InputCallback button_down_callback{
25 [this](Common::Input::CallbackStatus callback_) { UpdateDownButtonStatus(callback_); }};
26 Common::Input::InputCallback button_left_callback{
27 [this](Common::Input::CallbackStatus callback_) { UpdateLeftButtonStatus(callback_); }};
28 Common::Input::InputCallback button_right_callback{
29 [this](Common::Input::CallbackStatus callback_) {
30 UpdateRightButtonStatus(callback_);
31 }};
32 Common::Input::InputCallback button_modifier_callback{
33 [this](Common::Input::CallbackStatus callback_) { UpdateModButtonStatus(callback_); }};
34 up->SetCallback(button_up_callback);
35 down->SetCallback(button_down_callback);
36 left->SetCallback(button_left_callback);
37 right->SetCallback(button_right_callback);
38 modifier->SetCallback(button_modifier_callback);
39 last_x_axis_value = 0.0f;
40 last_y_axis_value = 0.0f;
41 }
42
43 bool IsAngleGreater(float old_angle, float new_angle) const {
44 constexpr float TAU = Common::PI * 2.0f;
45 // Use wider angle to ease the transition.
46 constexpr float aperture = TAU * 0.15f;
47 const float top_limit = new_angle + aperture;
48 return (old_angle > new_angle && old_angle <= top_limit) ||
49 (old_angle + TAU > new_angle && old_angle + TAU <= top_limit);
50 }
51
52 bool IsAngleSmaller(float old_angle, float new_angle) const {
53 constexpr float TAU = Common::PI * 2.0f;
54 // Use wider angle to ease the transition.
55 constexpr float aperture = TAU * 0.15f;
56 const float bottom_limit = new_angle - aperture;
57 return (old_angle >= bottom_limit && old_angle < new_angle) ||
58 (old_angle - TAU >= bottom_limit && old_angle - TAU < new_angle);
59 }
60
61 float GetAngle(std::chrono::time_point<std::chrono::steady_clock> now) const {
62 constexpr float TAU = Common::PI * 2.0f;
63 float new_angle = angle;
64
65 auto time_difference = static_cast<float>(
66 std::chrono::duration_cast<std::chrono::microseconds>(now - last_update).count());
67 time_difference /= 1000.0f * 1000.0f;
68 if (time_difference > 0.5f) {
69 time_difference = 0.5f;
70 }
71
72 if (IsAngleGreater(new_angle, goal_angle)) {
73 new_angle -= modifier_angle * time_difference;
74 if (new_angle < 0) {
75 new_angle += TAU;
76 }
77 if (!IsAngleGreater(new_angle, goal_angle)) {
78 return goal_angle;
79 }
80 } else if (IsAngleSmaller(new_angle, goal_angle)) {
81 new_angle += modifier_angle * time_difference;
82 if (new_angle >= TAU) {
83 new_angle -= TAU;
84 }
85 if (!IsAngleSmaller(new_angle, goal_angle)) {
86 return goal_angle;
87 }
88 } else {
89 return goal_angle;
90 }
91 return new_angle;
92 }
93
94 void SetGoalAngle(bool r, bool l, bool u, bool d) {
95 // Move to the right
96 if (r && !u && !d) {
97 goal_angle = 0.0f;
98 }
99
100 // Move to the upper right
101 if (r && u && !d) {
102 goal_angle = Common::PI * 0.25f;
103 }
104
105 // Move up
106 if (u && !l && !r) {
107 goal_angle = Common::PI * 0.5f;
108 }
109
110 // Move to the upper left
111 if (l && u && !d) {
112 goal_angle = Common::PI * 0.75f;
113 }
114
115 // Move to the left
116 if (l && !u && !d) {
117 goal_angle = Common::PI;
118 }
119
120 // Move to the bottom left
121 if (l && !u && d) {
122 goal_angle = Common::PI * 1.25f;
123 }
124
125 // Move down
126 if (d && !l && !r) {
127 goal_angle = Common::PI * 1.5f;
128 }
129
130 // Move to the bottom right
131 if (r && !u && d) {
132 goal_angle = Common::PI * 1.75f;
133 }
134 }
135
136 void UpdateUpButtonStatus(Common::Input::CallbackStatus button_callback) {
137 up_status = button_callback.button_status.value;
138 UpdateStatus();
139 }
140
141 void UpdateDownButtonStatus(Common::Input::CallbackStatus button_callback) {
142 down_status = button_callback.button_status.value;
143 UpdateStatus();
144 }
145
146 void UpdateLeftButtonStatus(Common::Input::CallbackStatus button_callback) {
147 left_status = button_callback.button_status.value;
148 UpdateStatus();
149 }
150
151 void UpdateRightButtonStatus(Common::Input::CallbackStatus button_callback) {
152 right_status = button_callback.button_status.value;
153 UpdateStatus();
154 }
155
156 void UpdateModButtonStatus(Common::Input::CallbackStatus button_callback) {
157 modifier_status = button_callback.button_status.value;
158 UpdateStatus();
159 }
160
161 void UpdateStatus() {
162 const float coef = modifier_status ? modifier_scale : 1.0f;
163
164 bool r = right_status;
165 bool l = left_status;
166 bool u = up_status;
167 bool d = down_status;
168
169 // Eliminate contradictory movements
170 if (r && l) {
171 r = false;
172 l = false;
173 }
174 if (u && d) {
175 u = false;
176 d = false;
177 }
178
179 // Move if a key is pressed
180 if (r || l || u || d) {
181 amplitude = coef;
182 } else {
183 amplitude = 0;
184 }
185
186 const auto now = std::chrono::steady_clock::now();
187 const auto time_difference = static_cast<u64>(
188 std::chrono::duration_cast<std::chrono::milliseconds>(now - last_update).count());
189
190 if (time_difference < 10) {
191 // Disable analog mode if inputs are too fast
192 SetGoalAngle(r, l, u, d);
193 angle = goal_angle;
194 } else {
195 angle = GetAngle(now);
196 SetGoalAngle(r, l, u, d);
197 }
198
199 last_update = now;
200 Common::Input::CallbackStatus status{
201 .type = Common::Input::InputType::Stick,
202 .stick_status = GetStatus(),
203 };
204 last_x_axis_value = status.stick_status.x.raw_value;
205 last_y_axis_value = status.stick_status.y.raw_value;
206 TriggerOnChange(status);
207 }
208
209 void ForceUpdate() override {
210 up->ForceUpdate();
211 down->ForceUpdate();
212 left->ForceUpdate();
213 right->ForceUpdate();
214 modifier->ForceUpdate();
215 }
216
217 void SoftUpdate() override {
218 Common::Input::CallbackStatus status{
219 .type = Common::Input::InputType::Stick,
220 .stick_status = GetStatus(),
221 };
222 if (last_x_axis_value == status.stick_status.x.raw_value &&
223 last_y_axis_value == status.stick_status.y.raw_value) {
224 return;
225 }
226 last_x_axis_value = status.stick_status.x.raw_value;
227 last_y_axis_value = status.stick_status.y.raw_value;
228 TriggerOnChange(status);
229 }
230
231 Common::Input::StickStatus GetStatus() const {
232 Common::Input::StickStatus status{};
233 status.x.properties = properties;
234 status.y.properties = properties;
235 if (Settings::values.emulate_analog_keyboard) {
236 const auto now = std::chrono::steady_clock::now();
237 float angle_ = GetAngle(now);
238 status.x.raw_value = std::cos(angle_) * amplitude;
239 status.y.raw_value = std::sin(angle_) * amplitude;
240 return status;
241 }
242 constexpr float SQRT_HALF = 0.707106781f;
243 int x = 0, y = 0;
244 if (right_status) {
245 ++x;
246 }
247 if (left_status) {
248 --x;
249 }
250 if (up_status) {
251 ++y;
252 }
253 if (down_status) {
254 --y;
255 }
256 const float coef = modifier_status ? modifier_scale : 1.0f;
257 status.x.raw_value = static_cast<float>(x) * coef * (y == 0 ? 1.0f : SQRT_HALF);
258 status.y.raw_value = static_cast<float>(y) * coef * (x == 0 ? 1.0f : SQRT_HALF);
259 return status;
260 }
261
262private:
263 Button up;
264 Button down;
265 Button left;
266 Button right;
267 Button modifier;
268 float modifier_scale;
269 float modifier_angle;
270 float angle{};
271 float goal_angle{};
272 float amplitude{};
273 bool up_status;
274 bool down_status;
275 bool left_status;
276 bool right_status;
277 bool modifier_status;
278 float last_x_axis_value;
279 float last_y_axis_value;
280 const Common::Input::AnalogProperties properties{0.0f, 1.0f, 0.5f, 0.0f, false};
281 std::chrono::time_point<std::chrono::steady_clock> last_update;
282};
283
284std::unique_ptr<Common::Input::InputDevice> StickFromButton::Create(
285 const Common::ParamPackage& params) {
286 const std::string null_engine = Common::ParamPackage{{"engine", "null"}}.Serialize();
287 auto up = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>(
288 params.Get("up", null_engine));
289 auto down = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>(
290 params.Get("down", null_engine));
291 auto left = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>(
292 params.Get("left", null_engine));
293 auto right = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>(
294 params.Get("right", null_engine));
295 auto modifier = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>(
296 params.Get("modifier", null_engine));
297 auto modifier_scale = params.Get("modifier_scale", 0.5f);
298 auto modifier_angle = params.Get("modifier_angle", 5.5f);
299 return std::make_unique<Stick>(std::move(up), std::move(down), std::move(left),
300 std::move(right), std::move(modifier), modifier_scale,
301 modifier_angle);
302}
303
304} // 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 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <memory> 7#include "common/input.h"
8#include "core/frontend/input.h"
9 8
10namespace InputCommon { 9namespace InputCommon {
11 10
@@ -13,7 +12,7 @@ namespace InputCommon {
13 * An analog device factory that takes direction button devices and combines them into a analog 12 * An analog device factory that takes direction button devices and combines them into a analog
14 * device. 13 * device.
15 */ 14 */
16class AnalogFromButton final : public Input::Factory<Input::AnalogDevice> { 15class StickFromButton final : public Common::Input::Factory<Common::Input::InputDevice> {
17public: 16public:
18 /** 17 /**
19 * Creates an analog device from direction button devices 18 * Creates an analog device from direction button devices
@@ -25,7 +24,7 @@ public:
25 * - "modifier": a serialized ParamPackage for creating a button device as the modifier 24 * - "modifier": a serialized ParamPackage for creating a button device as the modifier
26 * - "modifier_scale": a float for the multiplier the modifier gives to the position 25 * - "modifier_scale": a float for the multiplier the modifier gives to the position
27 */ 26 */
28 std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override; 27 std::unique_ptr<Common::Input::InputDevice> Create(const Common::ParamPackage& params) override;
29}; 28};
30 29
31} // namespace InputCommon 30} // 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 @@
1// Copyright 2020 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <algorithm>
6#include "common/settings.h"
7#include "core/frontend/framebuffer_layout.h"
8#include "input_common/helpers/touch_from_buttons.h"
9
10namespace InputCommon {
11
12class TouchFromButtonDevice final : public Common::Input::InputDevice {
13public:
14 using Button = std::unique_ptr<Common::Input::InputDevice>;
15 TouchFromButtonDevice(Button button_, int touch_id_, float x_, float y_)
16 : button(std::move(button_)), touch_id(touch_id_), x(x_), y(y_) {
17 Common::Input::InputCallback button_up_callback{
18 [this](Common::Input::CallbackStatus callback_) { UpdateButtonStatus(callback_); }};
19 last_button_value = false;
20 button->SetCallback(button_up_callback);
21 button->ForceUpdate();
22 }
23
24 void ForceUpdate() override {
25 button->ForceUpdate();
26 }
27
28 Common::Input::TouchStatus GetStatus(bool pressed) const {
29 const Common::Input::ButtonStatus button_status{
30 .value = pressed,
31 };
32 Common::Input::TouchStatus status{
33 .pressed = button_status,
34 .x = {},
35 .y = {},
36 .id = touch_id,
37 };
38 status.x.properties = properties;
39 status.y.properties = properties;
40
41 if (!pressed) {
42 return status;
43 }
44
45 status.x.raw_value = x;
46 status.y.raw_value = y;
47 return status;
48 }
49
50 void UpdateButtonStatus(Common::Input::CallbackStatus button_callback) {
51 const Common::Input::CallbackStatus status{
52 .type = Common::Input::InputType::Touch,
53 .touch_status = GetStatus(button_callback.button_status.value),
54 };
55 if (last_button_value != button_callback.button_status.value) {
56 last_button_value = button_callback.button_status.value;
57 TriggerOnChange(status);
58 }
59 }
60
61private:
62 Button button;
63 bool last_button_value;
64 const int touch_id;
65 const float x;
66 const float y;
67 const Common::Input::AnalogProperties properties{0.0f, 1.0f, 0.5f, 0.0f, false};
68};
69
70std::unique_ptr<Common::Input::InputDevice> TouchFromButton::Create(
71 const Common::ParamPackage& params) {
72 const std::string null_engine = Common::ParamPackage{{"engine", "null"}}.Serialize();
73 auto button = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>(
74 params.Get("button", null_engine));
75 const auto touch_id = params.Get("touch_id", 0);
76 const float x = params.Get("x", 0.0f) / 1280.0f;
77 const float y = params.Get("y", 0.0f) / 720.0f;
78 return std::make_unique<TouchFromButtonDevice>(std::move(button), touch_id, x, y);
79}
80
81} // 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 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <memory> 7#include "common/input.h"
8#include "core/frontend/input.h"
9 8
10namespace InputCommon { 9namespace InputCommon {
11 10
12/** 11/**
13 * A touch device factory that takes a list of button devices and combines them into a touch device. 12 * A touch device factory that takes a list of button devices and combines them into a touch device.
14 */ 13 */
15class TouchFromButtonFactory final : public Input::Factory<Input::TouchDevice> { 14class TouchFromButton final : public Common::Input::Factory<Common::Input::InputDevice> {
16public: 15public:
17 /** 16 /**
18 * Creates a touch device from a list of button devices 17 * Creates a touch device from a list of button devices
19 */ 18 */
20 std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override; 19 std::unique_ptr<Common::Input::InputDevice> Create(const Common::ParamPackage& params) override;
21}; 20};
22 21
23} // namespace InputCommon 22} // 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 @@
5#include <cstddef> 5#include <cstddef>
6#include <cstring> 6#include <cstring>
7#include "common/logging/log.h" 7#include "common/logging/log.h"
8#include "input_common/udp/protocol.h" 8#include "input_common/helpers/udp_protocol.h"
9 9
10namespace InputCommon::CemuhookUDP { 10namespace InputCommon::CemuhookUDP {
11 11
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();
56 56
57namespace Request { 57namespace Request {
58 58
59enum RegisterFlags : u8 {
60 AllPads,
61 PadID,
62 PadMACAdddress,
63};
64
59struct Version {}; 65struct Version {};
60/** 66/**
61 * Requests the server to send information about what controllers are plugged into the ports 67 * 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>,
77 * timeout seems to be 5 seconds. 83 * timeout seems to be 5 seconds.
78 */ 84 */
79struct PadData { 85struct PadData {
80 enum class Flags : u8 {
81 AllPorts,
82 Id,
83 Mac,
84 };
85 /// Determines which method will be used as a look up for the controller 86 /// Determines which method will be used as a look up for the controller
86 Flags flags{}; 87 RegisterFlags flags{};
87 /// Index of the port of the controller to retrieve data about 88 /// Index of the port of the controller to retrieve data about
88 u8 port_id{}; 89 u8 port_id{};
89 /// Mac address of the controller to retrieve data about 90 /// Mac address of the controller to retrieve data about
@@ -113,6 +114,36 @@ Message<T> Create(const T data, const u32 client_id = 0) {
113 114
114namespace Response { 115namespace Response {
115 116
117enum class ConnectionType : u8 {
118 None,
119 Usb,
120 Bluetooth,
121};
122
123enum class State : u8 {
124 Disconnected,
125 Reserved,
126 Connected,
127};
128
129enum class Model : u8 {
130 None,
131 PartialGyro,
132 FullGyro,
133 Generic,
134};
135
136enum class Battery : u8 {
137 None = 0x00,
138 Dying = 0x01,
139 Low = 0x02,
140 Medium = 0x03,
141 High = 0x04,
142 Full = 0x05,
143 Charging = 0xEE,
144 Charged = 0xEF,
145};
146
116struct Version { 147struct Version {
117 u16_le version{}; 148 u16_le version{};
118}; 149};
@@ -122,11 +153,11 @@ static_assert(std::is_trivially_copyable_v<Version>,
122 153
123struct PortInfo { 154struct PortInfo {
124 u8 id{}; 155 u8 id{};
125 u8 state{}; 156 State state{};
126 u8 model{}; 157 Model model{};
127 u8 connection_type{}; 158 ConnectionType connection_type{};
128 MacAddress mac; 159 MacAddress mac;
129 u8 battery{}; 160 Battery battery{};
130 u8 is_pad_active{}; 161 u8 is_pad_active{};
131}; 162};
132static_assert(sizeof(PortInfo) == 12, "UDP Response PortInfo struct has wrong size"); 163static_assert(sizeof(PortInfo) == 12, "UDP Response PortInfo struct has wrong size");
@@ -177,18 +208,18 @@ struct PadData {
177 u8 right_stick_y{}; 208 u8 right_stick_y{};
178 209
179 struct AnalogButton { 210 struct AnalogButton {
180 u8 button_8{}; 211 u8 button_dpad_left_analog{};
181 u8 button_7{}; 212 u8 button_dpad_down_analog{};
182 u8 button_6{}; 213 u8 button_dpad_right_analog{};
183 u8 button_5{}; 214 u8 button_dpad_up_analog{};
184 u8 button_12{}; 215 u8 button_square_analog{};
185 u8 button_11{}; 216 u8 button_cross_analog{};
186 u8 button_10{}; 217 u8 button_circle_analog{};
187 u8 button_9{}; 218 u8 button_triangle_analog{};
188 u8 button_16{}; 219 u8 button_r1_analog{};
189 u8 button_15{}; 220 u8 button_l1_analog{};
190 u8 button_14{}; 221 u8 trigger_r2{};
191 u8 button_13{}; 222 u8 trigger_l2{};
192 } analog_button; 223 } analog_button;
193 224
194 std::array<TouchPad, 2> touch; 225 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 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included
4
5#include "common/logging/log.h"
6#include "common/param_package.h"
7#include "input_common/input_engine.h"
8
9namespace InputCommon {
10
11void InputEngine::PreSetController(const PadIdentifier& identifier) {
12 std::lock_guard lock{mutex};
13 if (!controller_list.contains(identifier)) {
14 controller_list.insert_or_assign(identifier, ControllerData{});
15 }
16}
17
18void InputEngine::PreSetButton(const PadIdentifier& identifier, int button) {
19 std::lock_guard lock{mutex};
20 ControllerData& controller = controller_list.at(identifier);
21 if (!controller.buttons.contains(button)) {
22 controller.buttons.insert_or_assign(button, false);
23 }
24}
25
26void InputEngine::PreSetHatButton(const PadIdentifier& identifier, int button) {
27 std::lock_guard lock{mutex};
28 ControllerData& controller = controller_list.at(identifier);
29 if (!controller.hat_buttons.contains(button)) {
30 controller.hat_buttons.insert_or_assign(button, u8{0});
31 }
32}
33
34void InputEngine::PreSetAxis(const PadIdentifier& identifier, int axis) {
35 std::lock_guard lock{mutex};
36 ControllerData& controller = controller_list.at(identifier);
37 if (!controller.axes.contains(axis)) {
38 controller.axes.insert_or_assign(axis, 0.0f);
39 }
40}
41
42void InputEngine::PreSetMotion(const PadIdentifier& identifier, int motion) {
43 std::lock_guard lock{mutex};
44 ControllerData& controller = controller_list.at(identifier);
45 if (!controller.motions.contains(motion)) {
46 controller.motions.insert_or_assign(motion, BasicMotion{});
47 }
48}
49
50void InputEngine::SetButton(const PadIdentifier& identifier, int button, bool value) {
51 {
52 std::lock_guard lock{mutex};
53 ControllerData& controller = controller_list.at(identifier);
54 if (!configuring) {
55 controller.buttons.insert_or_assign(button, value);
56 }
57 }
58 TriggerOnButtonChange(identifier, button, value);
59}
60
61void InputEngine::SetHatButton(const PadIdentifier& identifier, int button, u8 value) {
62 {
63 std::lock_guard lock{mutex};
64 ControllerData& controller = controller_list.at(identifier);
65 if (!configuring) {
66 controller.hat_buttons.insert_or_assign(button, value);
67 }
68 }
69 TriggerOnHatButtonChange(identifier, button, value);
70}
71
72void InputEngine::SetAxis(const PadIdentifier& identifier, int axis, f32 value) {
73 {
74 std::lock_guard lock{mutex};
75 ControllerData& controller = controller_list.at(identifier);
76 if (!configuring) {
77 controller.axes.insert_or_assign(axis, value);
78 }
79 }
80 TriggerOnAxisChange(identifier, axis, value);
81}
82
83void InputEngine::SetBattery(const PadIdentifier& identifier, BatteryLevel value) {
84 {
85 std::lock_guard lock{mutex};
86 ControllerData& controller = controller_list.at(identifier);
87 if (!configuring) {
88 controller.battery = value;
89 }
90 }
91 TriggerOnBatteryChange(identifier, value);
92}
93
94void InputEngine::SetMotion(const PadIdentifier& identifier, int motion, BasicMotion value) {
95 {
96 std::lock_guard lock{mutex};
97 ControllerData& controller = controller_list.at(identifier);
98 if (!configuring) {
99 controller.motions.insert_or_assign(motion, value);
100 }
101 }
102 TriggerOnMotionChange(identifier, motion, value);
103}
104
105bool InputEngine::GetButton(const PadIdentifier& identifier, int button) const {
106 std::lock_guard lock{mutex};
107 if (!controller_list.contains(identifier)) {
108 LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.Format(),
109 identifier.pad, identifier.port);
110 return false;
111 }
112 ControllerData controller = controller_list.at(identifier);
113 if (!controller.buttons.contains(button)) {
114 LOG_ERROR(Input, "Invalid button {}", button);
115 return false;
116 }
117 return controller.buttons.at(button);
118}
119
120bool InputEngine::GetHatButton(const PadIdentifier& identifier, int button, u8 direction) const {
121 std::lock_guard lock{mutex};
122 if (!controller_list.contains(identifier)) {
123 LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.Format(),
124 identifier.pad, identifier.port);
125 return false;
126 }
127 ControllerData controller = controller_list.at(identifier);
128 if (!controller.hat_buttons.contains(button)) {
129 LOG_ERROR(Input, "Invalid hat button {}", button);
130 return false;
131 }
132 return (controller.hat_buttons.at(button) & direction) != 0;
133}
134
135f32 InputEngine::GetAxis(const PadIdentifier& identifier, int axis) const {
136 std::lock_guard lock{mutex};
137 if (!controller_list.contains(identifier)) {
138 LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.Format(),
139 identifier.pad, identifier.port);
140 return 0.0f;
141 }
142 ControllerData controller = controller_list.at(identifier);
143 if (!controller.axes.contains(axis)) {
144 LOG_ERROR(Input, "Invalid axis {}", axis);
145 return 0.0f;
146 }
147 return controller.axes.at(axis);
148}
149
150BatteryLevel InputEngine::GetBattery(const PadIdentifier& identifier) const {
151 std::lock_guard lock{mutex};
152 if (!controller_list.contains(identifier)) {
153 LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.Format(),
154 identifier.pad, identifier.port);
155 return BatteryLevel::Charging;
156 }
157 ControllerData controller = controller_list.at(identifier);
158 return controller.battery;
159}
160
161BasicMotion InputEngine::GetMotion(const PadIdentifier& identifier, int motion) const {
162 std::lock_guard lock{mutex};
163 if (!controller_list.contains(identifier)) {
164 LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.Format(),
165 identifier.pad, identifier.port);
166 return {};
167 }
168 ControllerData controller = controller_list.at(identifier);
169 return controller.motions.at(motion);
170}
171
172void InputEngine::ResetButtonState() {
173 for (std::pair<PadIdentifier, ControllerData> controller : controller_list) {
174 for (std::pair<int, bool> button : controller.second.buttons) {
175 SetButton(controller.first, button.first, false);
176 }
177 for (std::pair<int, bool> button : controller.second.hat_buttons) {
178 SetHatButton(controller.first, button.first, false);
179 }
180 }
181}
182
183void InputEngine::ResetAnalogState() {
184 for (std::pair<PadIdentifier, ControllerData> controller : controller_list) {
185 for (std::pair<int, float> axis : controller.second.axes) {
186 SetAxis(controller.first, axis.first, 0.0);
187 }
188 }
189}
190
191void InputEngine::TriggerOnButtonChange(const PadIdentifier& identifier, int button, bool value) {
192 std::lock_guard lock{mutex_callback};
193 for (const std::pair<int, InputIdentifier> poller_pair : callback_list) {
194 const InputIdentifier& poller = poller_pair.second;
195 if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Button, button)) {
196 continue;
197 }
198 if (poller.callback.on_change) {
199 poller.callback.on_change();
200 }
201 }
202 if (!configuring || !mapping_callback.on_data) {
203 return;
204 }
205
206 PreSetButton(identifier, button);
207 if (value == GetButton(identifier, button)) {
208 return;
209 }
210 mapping_callback.on_data(MappingData{
211 .engine = GetEngineName(),
212 .pad = identifier,
213 .type = EngineInputType::Button,
214 .index = button,
215 .button_value = value,
216 });
217}
218
219void InputEngine::TriggerOnHatButtonChange(const PadIdentifier& identifier, int button, u8 value) {
220 std::lock_guard lock{mutex_callback};
221 for (const std::pair<int, InputIdentifier> poller_pair : callback_list) {
222 const InputIdentifier& poller = poller_pair.second;
223 if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::HatButton, button)) {
224 continue;
225 }
226 if (poller.callback.on_change) {
227 poller.callback.on_change();
228 }
229 }
230 if (!configuring || !mapping_callback.on_data) {
231 return;
232 }
233 for (std::size_t index = 1; index < 0xff; index <<= 1) {
234 bool button_value = (value & index) != 0;
235 if (button_value == GetHatButton(identifier, button, static_cast<u8>(index))) {
236 continue;
237 }
238 mapping_callback.on_data(MappingData{
239 .engine = GetEngineName(),
240 .pad = identifier,
241 .type = EngineInputType::HatButton,
242 .index = button,
243 .hat_name = GetHatButtonName(static_cast<u8>(index)),
244 });
245 }
246}
247
248void InputEngine::TriggerOnAxisChange(const PadIdentifier& identifier, int axis, f32 value) {
249 std::lock_guard lock{mutex_callback};
250 for (const std::pair<int, InputIdentifier> poller_pair : callback_list) {
251 const InputIdentifier& poller = poller_pair.second;
252 if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Analog, axis)) {
253 continue;
254 }
255 if (poller.callback.on_change) {
256 poller.callback.on_change();
257 }
258 }
259 if (!configuring || !mapping_callback.on_data) {
260 return;
261 }
262 if (std::abs(value - GetAxis(identifier, axis)) < 0.5f) {
263 return;
264 }
265 mapping_callback.on_data(MappingData{
266 .engine = GetEngineName(),
267 .pad = identifier,
268 .type = EngineInputType::Analog,
269 .index = axis,
270 .axis_value = value,
271 });
272}
273
274void InputEngine::TriggerOnBatteryChange(const PadIdentifier& identifier,
275 [[maybe_unused]] BatteryLevel value) {
276 std::lock_guard lock{mutex_callback};
277 for (const std::pair<int, InputIdentifier> poller_pair : callback_list) {
278 const InputIdentifier& poller = poller_pair.second;
279 if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Battery, 0)) {
280 continue;
281 }
282 if (poller.callback.on_change) {
283 poller.callback.on_change();
284 }
285 }
286}
287
288void InputEngine::TriggerOnMotionChange(const PadIdentifier& identifier, int motion,
289 BasicMotion value) {
290 std::lock_guard lock{mutex_callback};
291 for (const std::pair<int, InputIdentifier> poller_pair : callback_list) {
292 const InputIdentifier& poller = poller_pair.second;
293 if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Motion, motion)) {
294 continue;
295 }
296 if (poller.callback.on_change) {
297 poller.callback.on_change();
298 }
299 }
300 if (!configuring || !mapping_callback.on_data) {
301 return;
302 }
303 if (std::abs(value.gyro_x) < 0.6f && std::abs(value.gyro_y) < 0.6f &&
304 std::abs(value.gyro_z) < 0.6f) {
305 return;
306 }
307 mapping_callback.on_data(MappingData{
308 .engine = GetEngineName(),
309 .pad = identifier,
310 .type = EngineInputType::Motion,
311 .index = motion,
312 .motion_value = value,
313 });
314}
315
316bool InputEngine::IsInputIdentifierEqual(const InputIdentifier& input_identifier,
317 const PadIdentifier& identifier, EngineInputType type,
318 int index) const {
319 if (input_identifier.type != type) {
320 return false;
321 }
322 if (input_identifier.index != index) {
323 return false;
324 }
325 if (input_identifier.identifier != identifier) {
326 return false;
327 }
328 return true;
329}
330
331void InputEngine::BeginConfiguration() {
332 configuring = true;
333}
334
335void InputEngine::EndConfiguration() {
336 configuring = false;
337}
338
339const std::string& InputEngine::GetEngineName() const {
340 return input_engine;
341}
342
343int InputEngine::SetCallback(InputIdentifier input_identifier) {
344 std::lock_guard lock{mutex_callback};
345 callback_list.insert_or_assign(last_callback_key, input_identifier);
346 return last_callback_key++;
347}
348
349void InputEngine::SetMappingCallback(MappingCallback callback) {
350 std::lock_guard lock{mutex_callback};
351 mapping_callback = std::move(callback);
352}
353
354void InputEngine::DeleteCallback(int key) {
355 std::lock_guard lock{mutex_callback};
356 const auto& iterator = callback_list.find(key);
357 if (iterator == callback_list.end()) {
358 LOG_ERROR(Input, "Tried to delete non-existent callback {}", key);
359 return;
360 }
361 callback_list.erase(iterator);
362}
363
364} // 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 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included
4
5#pragma once
6
7#include <functional>
8#include <mutex>
9#include <unordered_map>
10
11#include "common/common_types.h"
12#include "common/input.h"
13#include "common/param_package.h"
14#include "common/uuid.h"
15#include "input_common/main.h"
16
17// Pad Identifier of data source
18struct PadIdentifier {
19 Common::UUID guid{};
20 std::size_t port{};
21 std::size_t pad{};
22
23 friend constexpr bool operator==(const PadIdentifier&, const PadIdentifier&) = default;
24};
25
26// Basic motion data containing data from the sensors and a timestamp in microsecons
27struct BasicMotion {
28 float gyro_x;
29 float gyro_y;
30 float gyro_z;
31 float accel_x;
32 float accel_y;
33 float accel_z;
34 u64 delta_timestamp;
35};
36
37// Stages of a battery charge
38enum class BatteryLevel {
39 Empty,
40 Critical,
41 Low,
42 Medium,
43 Full,
44 Charging,
45};
46
47// Types of input that are stored in the engine
48enum class EngineInputType {
49 None,
50 Button,
51 HatButton,
52 Analog,
53 Motion,
54 Battery,
55};
56
57namespace std {
58// Hash used to create lists from PadIdentifier data
59template <>
60struct hash<PadIdentifier> {
61 size_t operator()(const PadIdentifier& pad_id) const noexcept {
62 u64 hash_value = pad_id.guid.uuid[1] ^ pad_id.guid.uuid[0];
63 hash_value ^= (static_cast<u64>(pad_id.port) << 32);
64 hash_value ^= static_cast<u64>(pad_id.pad);
65 return static_cast<size_t>(hash_value);
66 }
67};
68
69} // namespace std
70
71namespace InputCommon {
72
73// Data from the engine and device needed for creating a ParamPackage
74struct MappingData {
75 std::string engine{};
76 PadIdentifier pad{};
77 EngineInputType type{};
78 int index{};
79 bool button_value{};
80 std::string hat_name{};
81 f32 axis_value{};
82 BasicMotion motion_value{};
83};
84
85// Triggered if data changed on the controller
86struct UpdateCallback {
87 std::function<void()> on_change;
88};
89
90// Triggered if data changed on the controller and the engine is on configuring mode
91struct MappingCallback {
92 std::function<void(MappingData)> on_data;
93};
94
95// Input Identifier of data source
96struct InputIdentifier {
97 PadIdentifier identifier;
98 EngineInputType type;
99 int index;
100 UpdateCallback callback;
101};
102
103class InputEngine {
104public:
105 explicit InputEngine(const std::string& input_engine_) : input_engine(input_engine_) {
106 callback_list.clear();
107 }
108
109 virtual ~InputEngine() = default;
110
111 // Enable configuring mode for mapping
112 void BeginConfiguration();
113
114 // Disable configuring mode for mapping
115 void EndConfiguration();
116
117 // Sets a led pattern for a controller
118 virtual void SetLeds([[maybe_unused]] const PadIdentifier& identifier,
119 [[maybe_unused]] const Common::Input::LedStatus led_status) {
120 return;
121 }
122
123 // Sets rumble to a controller
124 virtual Common::Input::VibrationError SetRumble(
125 [[maybe_unused]] const PadIdentifier& identifier,
126 [[maybe_unused]] const Common::Input::VibrationStatus vibration) {
127 return Common::Input::VibrationError::NotSupported;
128 }
129
130 // Sets polling mode to a controller
131 virtual Common::Input::PollingError SetPollingMode(
132 [[maybe_unused]] const PadIdentifier& identifier,
133 [[maybe_unused]] const Common::Input::PollingMode vibration) {
134 return Common::Input::PollingError::NotSupported;
135 }
136
137 // Returns the engine name
138 [[nodiscard]] const std::string& GetEngineName() const;
139
140 /// Used for automapping features
141 virtual std::vector<Common::ParamPackage> GetInputDevices() const {
142 return {};
143 };
144
145 /// Retrieves the button mappings for the given device
146 virtual InputCommon::ButtonMapping GetButtonMappingForDevice(
147 [[maybe_unused]] const Common::ParamPackage& params) {
148 return {};
149 };
150
151 /// Retrieves the analog mappings for the given device
152 virtual InputCommon::AnalogMapping GetAnalogMappingForDevice(
153 [[maybe_unused]] const Common::ParamPackage& params) {
154 return {};
155 };
156
157 /// Retrieves the motion mappings for the given device
158 virtual InputCommon::MotionMapping GetMotionMappingForDevice(
159 [[maybe_unused]] const Common::ParamPackage& params) {
160 return {};
161 };
162
163 /// Retrieves the name of the given input.
164 virtual Common::Input::ButtonNames GetUIName(
165 [[maybe_unused]] const Common::ParamPackage& params) const {
166 return Common::Input::ButtonNames::Engine;
167 };
168
169 /// Retrieves the index number of the given hat button direction
170 virtual u8 GetHatButtonId([[maybe_unused]] const std::string& direction_name) const {
171 return 0;
172 };
173
174 void PreSetController(const PadIdentifier& identifier);
175 void PreSetButton(const PadIdentifier& identifier, int button);
176 void PreSetHatButton(const PadIdentifier& identifier, int button);
177 void PreSetAxis(const PadIdentifier& identifier, int axis);
178 void PreSetMotion(const PadIdentifier& identifier, int motion);
179 void ResetButtonState();
180 void ResetAnalogState();
181
182 bool GetButton(const PadIdentifier& identifier, int button) const;
183 bool GetHatButton(const PadIdentifier& identifier, int button, u8 direction) const;
184 f32 GetAxis(const PadIdentifier& identifier, int axis) const;
185 BatteryLevel GetBattery(const PadIdentifier& identifier) const;
186 BasicMotion GetMotion(const PadIdentifier& identifier, int motion) const;
187
188 int SetCallback(InputIdentifier input_identifier);
189 void SetMappingCallback(MappingCallback callback);
190 void DeleteCallback(int key);
191
192protected:
193 void SetButton(const PadIdentifier& identifier, int button, bool value);
194 void SetHatButton(const PadIdentifier& identifier, int button, u8 value);
195 void SetAxis(const PadIdentifier& identifier, int axis, f32 value);
196 void SetBattery(const PadIdentifier& identifier, BatteryLevel value);
197 void SetMotion(const PadIdentifier& identifier, int motion, BasicMotion value);
198
199 virtual std::string GetHatButtonName([[maybe_unused]] u8 direction_value) const {
200 return "Unknown";
201 }
202
203private:
204 struct ControllerData {
205 std::unordered_map<int, bool> buttons;
206 std::unordered_map<int, u8> hat_buttons;
207 std::unordered_map<int, float> axes;
208 std::unordered_map<int, BasicMotion> motions;
209 BatteryLevel battery;
210 };
211
212 void TriggerOnButtonChange(const PadIdentifier& identifier, int button, bool value);
213 void TriggerOnHatButtonChange(const PadIdentifier& identifier, int button, u8 value);
214 void TriggerOnAxisChange(const PadIdentifier& identifier, int button, f32 value);
215 void TriggerOnBatteryChange(const PadIdentifier& identifier, BatteryLevel value);
216 void TriggerOnMotionChange(const PadIdentifier& identifier, int motion, BasicMotion value);
217
218 bool IsInputIdentifierEqual(const InputIdentifier& input_identifier,
219 const PadIdentifier& identifier, EngineInputType type,
220 int index) const;
221
222 mutable std::mutex mutex;
223 mutable std::mutex mutex_callback;
224 bool configuring{false};
225 const std::string input_engine;
226 int last_callback_key = 0;
227 std::unordered_map<PadIdentifier, ControllerData> controller_list;
228 std::unordered_map<int, InputIdentifier> callback_list;
229 MappingCallback mapping_callback;
230};
231
232} // 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 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included
4
5#include "common/common_types.h"
6#include "common/settings.h"
7#include "input_common/input_engine.h"
8#include "input_common/input_mapping.h"
9
10namespace InputCommon {
11
12MappingFactory::MappingFactory() {}
13
14void MappingFactory::BeginMapping(Polling::InputType type) {
15 is_enabled = true;
16 input_type = type;
17 input_queue.Clear();
18 first_axis = -1;
19 second_axis = -1;
20}
21
22[[nodiscard]] const Common::ParamPackage MappingFactory::GetNextInput() {
23 Common::ParamPackage input;
24 input_queue.Pop(input);
25 return input;
26}
27
28void MappingFactory::RegisterInput(const MappingData& data) {
29 if (!is_enabled) {
30 return;
31 }
32 if (!IsDriverValid(data)) {
33 return;
34 }
35
36 switch (input_type) {
37 case Polling::InputType::Button:
38 RegisterButton(data);
39 return;
40 case Polling::InputType::Stick:
41 RegisterStick(data);
42 return;
43 case Polling::InputType::Motion:
44 RegisterMotion(data);
45 return;
46 default:
47 return;
48 }
49}
50
51void MappingFactory::StopMapping() {
52 is_enabled = false;
53 input_type = Polling::InputType::None;
54 input_queue.Clear();
55}
56
57void MappingFactory::RegisterButton(const MappingData& data) {
58 Common::ParamPackage new_input;
59 new_input.Set("engine", data.engine);
60 if (data.pad.guid != Common::UUID{}) {
61 new_input.Set("guid", data.pad.guid.Format());
62 }
63 new_input.Set("port", static_cast<int>(data.pad.port));
64 new_input.Set("pad", static_cast<int>(data.pad.pad));
65
66 switch (data.type) {
67 case EngineInputType::Button:
68 // Workaround for old compatibility
69 if (data.engine == "keyboard") {
70 new_input.Set("code", data.index);
71 break;
72 }
73 new_input.Set("button", data.index);
74 break;
75 case EngineInputType::HatButton:
76 new_input.Set("hat", data.index);
77 new_input.Set("direction", data.hat_name);
78 break;
79 case EngineInputType::Analog:
80 // Ignore mouse axis when mapping buttons
81 if (data.engine == "mouse") {
82 return;
83 }
84 new_input.Set("axis", data.index);
85 new_input.Set("threshold", 0.5f);
86 break;
87 default:
88 return;
89 }
90 input_queue.Push(new_input);
91}
92
93void MappingFactory::RegisterStick(const MappingData& data) {
94 Common::ParamPackage new_input;
95 new_input.Set("engine", data.engine);
96 if (data.pad.guid != Common::UUID{}) {
97 new_input.Set("guid", data.pad.guid.Format());
98 }
99 new_input.Set("port", static_cast<int>(data.pad.port));
100 new_input.Set("pad", static_cast<int>(data.pad.pad));
101
102 // If engine is mouse map the mouse position as a joystick
103 if (data.engine == "mouse") {
104 new_input.Set("axis_x", 0);
105 new_input.Set("axis_y", 1);
106 new_input.Set("threshold", 0.5f);
107 new_input.Set("range", 1.0f);
108 new_input.Set("deadzone", 0.0f);
109 input_queue.Push(new_input);
110 return;
111 }
112
113 switch (data.type) {
114 case EngineInputType::Button:
115 case EngineInputType::HatButton:
116 RegisterButton(data);
117 return;
118 case EngineInputType::Analog:
119 if (first_axis == data.index) {
120 return;
121 }
122 if (first_axis == -1) {
123 first_axis = data.index;
124 return;
125 }
126 new_input.Set("axis_x", first_axis);
127 new_input.Set("axis_y", data.index);
128 new_input.Set("threshold", 0.5f);
129 new_input.Set("range", 0.95f);
130 new_input.Set("deadzone", 0.15f);
131 break;
132 default:
133 return;
134 }
135 input_queue.Push(new_input);
136}
137
138void MappingFactory::RegisterMotion(const MappingData& data) {
139 Common::ParamPackage new_input;
140 new_input.Set("engine", data.engine);
141 if (data.pad.guid != Common::UUID{}) {
142 new_input.Set("guid", data.pad.guid.Format());
143 }
144 new_input.Set("port", static_cast<int>(data.pad.port));
145 new_input.Set("pad", static_cast<int>(data.pad.pad));
146 switch (data.type) {
147 case EngineInputType::Button:
148 case EngineInputType::HatButton:
149 RegisterButton(data);
150 return;
151 case EngineInputType::Analog:
152 if (first_axis == data.index) {
153 return;
154 }
155 if (second_axis == data.index) {
156 return;
157 }
158 if (first_axis == -1) {
159 first_axis = data.index;
160 return;
161 }
162 if (second_axis == -1) {
163 second_axis = data.index;
164 return;
165 }
166 new_input.Set("axis_x", first_axis);
167 new_input.Set("axis_y", second_axis);
168 new_input.Set("axis_z", data.index);
169 new_input.Set("range", 1.0f);
170 new_input.Set("deadzone", 0.20f);
171 break;
172 case EngineInputType::Motion:
173 new_input.Set("motion", data.index);
174 break;
175 default:
176 return;
177 }
178 input_queue.Push(new_input);
179}
180
181bool MappingFactory::IsDriverValid(const MappingData& data) const {
182 // Only port 0 can be mapped on the keyboard
183 if (data.engine == "keyboard" && data.pad.port != 0) {
184 return false;
185 }
186 // To prevent mapping with two devices we disable any UDP except motion
187 if (!Settings::values.enable_udp_controller && data.engine == "cemuhookudp" &&
188 data.type != EngineInputType::Motion) {
189 return false;
190 }
191 // The following drivers don't need to be mapped
192 if (data.engine == "tas") {
193 return false;
194 }
195 if (data.engine == "touch") {
196 return false;
197 }
198 if (data.engine == "touch_from_button") {
199 return false;
200 }
201 if (data.engine == "analog_from_button") {
202 return false;
203 }
204 return true;
205}
206
207} // 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 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included
4
5#pragma once
6#include "common/threadsafe_queue.h"
7
8namespace InputCommon {
9class InputEngine;
10struct MappingData;
11
12class MappingFactory {
13public:
14 MappingFactory();
15
16 /**
17 * Resets all varables to beggin the mapping process
18 * @param "type": type of input desired to be returned
19 */
20 void BeginMapping(Polling::InputType type);
21
22 /// Returns an input event with mapping information from the input_queue
23 [[nodiscard]] const Common::ParamPackage GetNextInput();
24
25 /**
26 * Registers mapping input data from the driver
27 * @param "data": An struct containing all the information needed to create a proper
28 * ParamPackage
29 */
30 void RegisterInput(const MappingData& data);
31
32 /// Stop polling from all backends
33 void StopMapping();
34
35private:
36 /**
37 * If provided data satisfies the requeriments it will push an element to the input_queue
38 * Supported input:
39 * - Button: Creates a basic button ParamPackage
40 * - HatButton: Creates a basic hat button ParamPackage
41 * - Analog: Creates a basic analog ParamPackage
42 * @param "data": An struct containing all the information needed to create a proper
43 * ParamPackage
44 */
45 void RegisterButton(const MappingData& data);
46
47 /**
48 * If provided data satisfies the requeriments it will push an element to the input_queue
49 * Supported input:
50 * - Button, HatButton: Pass the data to RegisterButton
51 * - Analog: Stores the first axis and on the second axis creates a basic stick ParamPackage
52 * @param "data": An struct containing all the information needed to create a proper
53 * ParamPackage
54 */
55 void RegisterStick(const MappingData& data);
56
57 /**
58 * If provided data satisfies the requeriments it will push an element to the input_queue
59 * Supported input:
60 * - Button, HatButton: Pass the data to RegisterButton
61 * - Analog: Stores the first two axis and on the third axis creates a basic Motion
62 * ParamPackage
63 * - Motion: Creates a basic Motion ParamPackage
64 * @param "data": An struct containing all the information needed to create a proper
65 * ParamPackage
66 */
67 void RegisterMotion(const MappingData& data);
68
69 /**
70 * Returns true if driver can be mapped
71 * @param "data": An struct containing all the information needed to create a proper
72 * ParamPackage
73 */
74 bool IsDriverValid(const MappingData& data) const;
75
76 Common::SPSCQueue<Common::ParamPackage> input_queue;
77 Polling::InputType input_type{Polling::InputType::None};
78 bool is_enabled{};
79 int first_axis = -1;
80 int second_axis = -1;
81};
82
83} // 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 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included
4
5#include "common/common_types.h"
6#include "common/input.h"
7
8#include "input_common/input_engine.h"
9#include "input_common/input_poller.h"
10
11namespace InputCommon {
12
13class DummyInput final : public Common::Input::InputDevice {
14public:
15 explicit DummyInput() {}
16 ~DummyInput() {}
17};
18
19class InputFromButton final : public Common::Input::InputDevice {
20public:
21 explicit InputFromButton(PadIdentifier identifier_, int button_, bool toggle_, bool inverted_,
22 InputEngine* input_engine_)
23 : identifier(identifier_), button(button_), toggle(toggle_), inverted(inverted_),
24 input_engine(input_engine_) {
25 UpdateCallback engine_callback{[this]() { OnChange(); }};
26 const InputIdentifier input_identifier{
27 .identifier = identifier,
28 .type = EngineInputType::Button,
29 .index = button,
30 .callback = engine_callback,
31 };
32 last_button_value = false;
33 callback_key = input_engine->SetCallback(input_identifier);
34 }
35
36 ~InputFromButton() {
37 input_engine->DeleteCallback(callback_key);
38 }
39
40 Common::Input::ButtonStatus GetStatus() const {
41 return {
42 .value = input_engine->GetButton(identifier, button),
43 .inverted = inverted,
44 .toggle = toggle,
45 };
46 }
47
48 void ForceUpdate() {
49 const Common::Input::CallbackStatus status{
50 .type = Common::Input::InputType::Button,
51 .button_status = GetStatus(),
52 };
53
54 last_button_value = status.button_status.value;
55 TriggerOnChange(status);
56 }
57
58 void OnChange() {
59 const Common::Input::CallbackStatus status{
60 .type = Common::Input::InputType::Button,
61 .button_status = GetStatus(),
62 };
63
64 if (status.button_status.value != last_button_value) {
65 last_button_value = status.button_status.value;
66 TriggerOnChange(status);
67 }
68 }
69
70private:
71 const PadIdentifier identifier;
72 const int button;
73 const bool toggle;
74 const bool inverted;
75 int callback_key;
76 bool last_button_value;
77 InputEngine* input_engine;
78};
79
80class InputFromHatButton final : public Common::Input::InputDevice {
81public:
82 explicit InputFromHatButton(PadIdentifier identifier_, int button_, u8 direction_, bool toggle_,
83 bool inverted_, InputEngine* input_engine_)
84 : identifier(identifier_), button(button_), direction(direction_), toggle(toggle_),
85 inverted(inverted_), input_engine(input_engine_) {
86 UpdateCallback engine_callback{[this]() { OnChange(); }};
87 const InputIdentifier input_identifier{
88 .identifier = identifier,
89 .type = EngineInputType::HatButton,
90 .index = button,
91 .callback = engine_callback,
92 };
93 last_button_value = false;
94 callback_key = input_engine->SetCallback(input_identifier);
95 }
96
97 ~InputFromHatButton() {
98 input_engine->DeleteCallback(callback_key);
99 }
100
101 Common::Input::ButtonStatus GetStatus() const {
102 return {
103 .value = input_engine->GetHatButton(identifier, button, direction),
104 .inverted = inverted,
105 .toggle = toggle,
106 };
107 }
108
109 void ForceUpdate() {
110 const Common::Input::CallbackStatus status{
111 .type = Common::Input::InputType::Button,
112 .button_status = GetStatus(),
113 };
114
115 last_button_value = status.button_status.value;
116 TriggerOnChange(status);
117 }
118
119 void OnChange() {
120 const Common::Input::CallbackStatus status{
121 .type = Common::Input::InputType::Button,
122 .button_status = GetStatus(),
123 };
124
125 if (status.button_status.value != last_button_value) {
126 last_button_value = status.button_status.value;
127 TriggerOnChange(status);
128 }
129 }
130
131private:
132 const PadIdentifier identifier;
133 const int button;
134 const u8 direction;
135 const bool toggle;
136 const bool inverted;
137 int callback_key;
138 bool last_button_value;
139 InputEngine* input_engine;
140};
141
142class InputFromStick final : public Common::Input::InputDevice {
143public:
144 explicit InputFromStick(PadIdentifier identifier_, int axis_x_, int axis_y_,
145 Common::Input::AnalogProperties properties_x_,
146 Common::Input::AnalogProperties properties_y_,
147 InputEngine* input_engine_)
148 : identifier(identifier_), axis_x(axis_x_), axis_y(axis_y_), properties_x(properties_x_),
149 properties_y(properties_y_),
150 input_engine(input_engine_), invert_axis_y{input_engine_->GetEngineName() == "sdl"} {
151 UpdateCallback engine_callback{[this]() { OnChange(); }};
152 const InputIdentifier x_input_identifier{
153 .identifier = identifier,
154 .type = EngineInputType::Analog,
155 .index = axis_x,
156 .callback = engine_callback,
157 };
158 const InputIdentifier y_input_identifier{
159 .identifier = identifier,
160 .type = EngineInputType::Analog,
161 .index = axis_y,
162 .callback = engine_callback,
163 };
164 last_axis_x_value = 0.0f;
165 last_axis_y_value = 0.0f;
166 callback_key_x = input_engine->SetCallback(x_input_identifier);
167 callback_key_y = input_engine->SetCallback(y_input_identifier);
168 }
169
170 ~InputFromStick() {
171 input_engine->DeleteCallback(callback_key_x);
172 input_engine->DeleteCallback(callback_key_y);
173 }
174
175 Common::Input::StickStatus GetStatus() const {
176 Common::Input::StickStatus status;
177 status.x = {
178 .raw_value = input_engine->GetAxis(identifier, axis_x),
179 .properties = properties_x,
180 };
181 status.y = {
182 .raw_value = input_engine->GetAxis(identifier, axis_y),
183 .properties = properties_y,
184 };
185 // This is a workaround too keep compatibility with old yuzu versions. Vertical axis is
186 // inverted on SDL compared to Nintendo
187 if (invert_axis_y) {
188 status.y.raw_value = -status.y.raw_value;
189 }
190 return status;
191 }
192
193 void ForceUpdate() {
194 const Common::Input::CallbackStatus status{
195 .type = Common::Input::InputType::Stick,
196 .stick_status = GetStatus(),
197 };
198
199 last_axis_x_value = status.stick_status.x.raw_value;
200 last_axis_y_value = status.stick_status.y.raw_value;
201 TriggerOnChange(status);
202 }
203
204 void OnChange() {
205 const Common::Input::CallbackStatus status{
206 .type = Common::Input::InputType::Stick,
207 .stick_status = GetStatus(),
208 };
209
210 if (status.stick_status.x.raw_value != last_axis_x_value ||
211 status.stick_status.y.raw_value != last_axis_y_value) {
212 last_axis_x_value = status.stick_status.x.raw_value;
213 last_axis_y_value = status.stick_status.y.raw_value;
214 TriggerOnChange(status);
215 }
216 }
217
218private:
219 const PadIdentifier identifier;
220 const int axis_x;
221 const int axis_y;
222 const Common::Input::AnalogProperties properties_x;
223 const Common::Input::AnalogProperties properties_y;
224 int callback_key_x;
225 int callback_key_y;
226 float last_axis_x_value;
227 float last_axis_y_value;
228 InputEngine* input_engine;
229 const bool invert_axis_y;
230};
231
232class InputFromTouch final : public Common::Input::InputDevice {
233public:
234 explicit InputFromTouch(PadIdentifier identifier_, int touch_id_, int button_, bool toggle_,
235 bool inverted_, int axis_x_, int axis_y_,
236 Common::Input::AnalogProperties properties_x_,
237 Common::Input::AnalogProperties properties_y_,
238 InputEngine* input_engine_)
239 : identifier(identifier_), touch_id(touch_id_), button(button_), toggle(toggle_),
240 inverted(inverted_), axis_x(axis_x_), axis_y(axis_y_), properties_x(properties_x_),
241 properties_y(properties_y_), input_engine(input_engine_) {
242 UpdateCallback engine_callback{[this]() { OnChange(); }};
243 const InputIdentifier button_input_identifier{
244 .identifier = identifier,
245 .type = EngineInputType::Button,
246 .index = button,
247 .callback = engine_callback,
248 };
249 const InputIdentifier x_input_identifier{
250 .identifier = identifier,
251 .type = EngineInputType::Analog,
252 .index = axis_x,
253 .callback = engine_callback,
254 };
255 const InputIdentifier y_input_identifier{
256 .identifier = identifier,
257 .type = EngineInputType::Analog,
258 .index = axis_y,
259 .callback = engine_callback,
260 };
261 last_axis_x_value = 0.0f;
262 last_axis_y_value = 0.0f;
263 last_button_value = false;
264 callback_key_button = input_engine->SetCallback(button_input_identifier);
265 callback_key_x = input_engine->SetCallback(x_input_identifier);
266 callback_key_y = input_engine->SetCallback(y_input_identifier);
267 }
268
269 ~InputFromTouch() {
270 input_engine->DeleteCallback(callback_key_button);
271 input_engine->DeleteCallback(callback_key_x);
272 input_engine->DeleteCallback(callback_key_y);
273 }
274
275 Common::Input::TouchStatus GetStatus() const {
276 Common::Input::TouchStatus status;
277 status.id = touch_id;
278 status.pressed = {
279 .value = input_engine->GetButton(identifier, button),
280 .inverted = inverted,
281 .toggle = toggle,
282 };
283 status.x = {
284 .raw_value = input_engine->GetAxis(identifier, axis_x),
285 .properties = properties_x,
286 };
287 status.y = {
288 .raw_value = input_engine->GetAxis(identifier, axis_y),
289 .properties = properties_y,
290 };
291 return status;
292 }
293
294 void OnChange() {
295 const Common::Input::CallbackStatus status{
296 .type = Common::Input::InputType::Touch,
297 .touch_status = GetStatus(),
298 };
299
300 if (status.touch_status.x.raw_value != last_axis_x_value ||
301 status.touch_status.y.raw_value != last_axis_y_value ||
302 status.touch_status.pressed.value != last_button_value) {
303 last_axis_x_value = status.touch_status.x.raw_value;
304 last_axis_y_value = status.touch_status.y.raw_value;
305 last_button_value = status.touch_status.pressed.value;
306 TriggerOnChange(status);
307 }
308 }
309
310private:
311 const PadIdentifier identifier;
312 const int touch_id;
313 const int button;
314 const bool toggle;
315 const bool inverted;
316 const int axis_x;
317 const int axis_y;
318 const Common::Input::AnalogProperties properties_x;
319 const Common::Input::AnalogProperties properties_y;
320 int callback_key_button;
321 int callback_key_x;
322 int callback_key_y;
323 bool last_button_value;
324 float last_axis_x_value;
325 float last_axis_y_value;
326 InputEngine* input_engine;
327};
328
329class InputFromTrigger final : public Common::Input::InputDevice {
330public:
331 explicit InputFromTrigger(PadIdentifier identifier_, int button_, bool toggle_, bool inverted_,
332 int axis_, Common::Input::AnalogProperties properties_,
333 InputEngine* input_engine_)
334 : identifier(identifier_), button(button_), toggle(toggle_), inverted(inverted_),
335 axis(axis_), properties(properties_), input_engine(input_engine_) {
336 UpdateCallback engine_callback{[this]() { OnChange(); }};
337 const InputIdentifier button_input_identifier{
338 .identifier = identifier,
339 .type = EngineInputType::Button,
340 .index = button,
341 .callback = engine_callback,
342 };
343 const InputIdentifier axis_input_identifier{
344 .identifier = identifier,
345 .type = EngineInputType::Analog,
346 .index = axis,
347 .callback = engine_callback,
348 };
349 last_axis_value = 0.0f;
350 last_button_value = false;
351 callback_key_button = input_engine->SetCallback(button_input_identifier);
352 axis_callback_key = input_engine->SetCallback(axis_input_identifier);
353 }
354
355 ~InputFromTrigger() {
356 input_engine->DeleteCallback(callback_key_button);
357 input_engine->DeleteCallback(axis_callback_key);
358 }
359
360 Common::Input::TriggerStatus GetStatus() const {
361 const Common::Input::AnalogStatus analog_status{
362 .raw_value = input_engine->GetAxis(identifier, axis),
363 .properties = properties,
364 };
365 const Common::Input::ButtonStatus button_status{
366 .value = input_engine->GetButton(identifier, button),
367 .inverted = inverted,
368 .toggle = toggle,
369 };
370 return {
371 .analog = analog_status,
372 .pressed = button_status,
373 };
374 }
375
376 void OnChange() {
377 const Common::Input::CallbackStatus status{
378 .type = Common::Input::InputType::Trigger,
379 .trigger_status = GetStatus(),
380 };
381
382 if (status.trigger_status.analog.raw_value != last_axis_value ||
383 status.trigger_status.pressed.value != last_button_value) {
384 last_axis_value = status.trigger_status.analog.raw_value;
385 last_button_value = status.trigger_status.pressed.value;
386 TriggerOnChange(status);
387 }
388 }
389
390private:
391 const PadIdentifier identifier;
392 const int button;
393 const bool toggle;
394 const bool inverted;
395 const int axis;
396 const Common::Input::AnalogProperties properties;
397 int callback_key_button;
398 int axis_callback_key;
399 bool last_button_value;
400 float last_axis_value;
401 InputEngine* input_engine;
402};
403
404class InputFromAnalog final : public Common::Input::InputDevice {
405public:
406 explicit InputFromAnalog(PadIdentifier identifier_, int axis_,
407 Common::Input::AnalogProperties properties_,
408 InputEngine* input_engine_)
409 : identifier(identifier_), axis(axis_), properties(properties_),
410 input_engine(input_engine_) {
411 UpdateCallback engine_callback{[this]() { OnChange(); }};
412 const InputIdentifier input_identifier{
413 .identifier = identifier,
414 .type = EngineInputType::Analog,
415 .index = axis,
416 .callback = engine_callback,
417 };
418 last_axis_value = 0.0f;
419 callback_key = input_engine->SetCallback(input_identifier);
420 }
421
422 ~InputFromAnalog() {
423 input_engine->DeleteCallback(callback_key);
424 }
425
426 Common::Input::AnalogStatus GetStatus() const {
427 return {
428 .raw_value = input_engine->GetAxis(identifier, axis),
429 .properties = properties,
430 };
431 }
432
433 void OnChange() {
434 const Common::Input::CallbackStatus status{
435 .type = Common::Input::InputType::Analog,
436 .analog_status = GetStatus(),
437 };
438
439 if (status.analog_status.raw_value != last_axis_value) {
440 last_axis_value = status.analog_status.raw_value;
441 TriggerOnChange(status);
442 }
443 }
444
445private:
446 const PadIdentifier identifier;
447 const int axis;
448 const Common::Input::AnalogProperties properties;
449 int callback_key;
450 float last_axis_value;
451 InputEngine* input_engine;
452};
453
454class InputFromBattery final : public Common::Input::InputDevice {
455public:
456 explicit InputFromBattery(PadIdentifier identifier_, InputEngine* input_engine_)
457 : identifier(identifier_), input_engine(input_engine_) {
458 UpdateCallback engine_callback{[this]() { OnChange(); }};
459 const InputIdentifier input_identifier{
460 .identifier = identifier,
461 .type = EngineInputType::Battery,
462 .index = 0,
463 .callback = engine_callback,
464 };
465 last_battery_value = Common::Input::BatteryStatus::Charging;
466 callback_key = input_engine->SetCallback(input_identifier);
467 }
468
469 ~InputFromBattery() {
470 input_engine->DeleteCallback(callback_key);
471 }
472
473 Common::Input::BatteryStatus GetStatus() const {
474 return static_cast<Common::Input::BatteryLevel>(input_engine->GetBattery(identifier));
475 }
476
477 void ForceUpdate() {
478 const Common::Input::CallbackStatus status{
479 .type = Common::Input::InputType::Battery,
480 .battery_status = GetStatus(),
481 };
482
483 last_battery_value = status.battery_status;
484 TriggerOnChange(status);
485 }
486
487 void OnChange() {
488 const Common::Input::CallbackStatus status{
489 .type = Common::Input::InputType::Battery,
490 .battery_status = GetStatus(),
491 };
492
493 if (status.battery_status != last_battery_value) {
494 last_battery_value = status.battery_status;
495 TriggerOnChange(status);
496 }
497 }
498
499private:
500 const PadIdentifier identifier;
501 int callback_key;
502 Common::Input::BatteryStatus last_battery_value;
503 InputEngine* input_engine;
504};
505
506class InputFromMotion final : public Common::Input::InputDevice {
507public:
508 explicit InputFromMotion(PadIdentifier identifier_, int motion_sensor_,
509 InputEngine* input_engine_)
510 : identifier(identifier_), motion_sensor(motion_sensor_), input_engine(input_engine_) {
511 UpdateCallback engine_callback{[this]() { OnChange(); }};
512 const InputIdentifier input_identifier{
513 .identifier = identifier,
514 .type = EngineInputType::Motion,
515 .index = motion_sensor,
516 .callback = engine_callback,
517 };
518 callback_key = input_engine->SetCallback(input_identifier);
519 }
520
521 ~InputFromMotion() {
522 input_engine->DeleteCallback(callback_key);
523 }
524
525 Common::Input::MotionStatus GetStatus() const {
526 const auto basic_motion = input_engine->GetMotion(identifier, motion_sensor);
527 Common::Input::MotionStatus status{};
528 const Common::Input::AnalogProperties properties = {
529 .deadzone = 0.001f,
530 .range = 1.0f,
531 .offset = 0.0f,
532 };
533 status.accel.x = {.raw_value = basic_motion.accel_x, .properties = properties};
534 status.accel.y = {.raw_value = basic_motion.accel_y, .properties = properties};
535 status.accel.z = {.raw_value = basic_motion.accel_z, .properties = properties};
536 status.gyro.x = {.raw_value = basic_motion.gyro_x, .properties = properties};
537 status.gyro.y = {.raw_value = basic_motion.gyro_y, .properties = properties};
538 status.gyro.z = {.raw_value = basic_motion.gyro_z, .properties = properties};
539 status.delta_timestamp = basic_motion.delta_timestamp;
540 return status;
541 }
542
543 void OnChange() {
544 const Common::Input::CallbackStatus status{
545 .type = Common::Input::InputType::Motion,
546 .motion_status = GetStatus(),
547 };
548
549 TriggerOnChange(status);
550 }
551
552private:
553 const PadIdentifier identifier;
554 const int motion_sensor;
555 int callback_key;
556 InputEngine* input_engine;
557};
558
559class InputFromAxisMotion final : public Common::Input::InputDevice {
560public:
561 explicit InputFromAxisMotion(PadIdentifier identifier_, int axis_x_, int axis_y_, int axis_z_,
562 Common::Input::AnalogProperties properties_x_,
563 Common::Input::AnalogProperties properties_y_,
564 Common::Input::AnalogProperties properties_z_,
565 InputEngine* input_engine_)
566 : identifier(identifier_), axis_x(axis_x_), axis_y(axis_y_), axis_z(axis_z_),
567 properties_x(properties_x_), properties_y(properties_y_), properties_z(properties_z_),
568 input_engine(input_engine_) {
569 UpdateCallback engine_callback{[this]() { OnChange(); }};
570 const InputIdentifier x_input_identifier{
571 .identifier = identifier,
572 .type = EngineInputType::Analog,
573 .index = axis_x,
574 .callback = engine_callback,
575 };
576 const InputIdentifier y_input_identifier{
577 .identifier = identifier,
578 .type = EngineInputType::Analog,
579 .index = axis_y,
580 .callback = engine_callback,
581 };
582 const InputIdentifier z_input_identifier{
583 .identifier = identifier,
584 .type = EngineInputType::Analog,
585 .index = axis_z,
586 .callback = engine_callback,
587 };
588 last_axis_x_value = 0.0f;
589 last_axis_y_value = 0.0f;
590 last_axis_z_value = 0.0f;
591 callback_key_x = input_engine->SetCallback(x_input_identifier);
592 callback_key_y = input_engine->SetCallback(y_input_identifier);
593 callback_key_z = input_engine->SetCallback(z_input_identifier);
594 }
595
596 ~InputFromAxisMotion() {
597 input_engine->DeleteCallback(callback_key_x);
598 input_engine->DeleteCallback(callback_key_y);
599 input_engine->DeleteCallback(callback_key_z);
600 }
601
602 Common::Input::MotionStatus GetStatus() const {
603 Common::Input::MotionStatus status{};
604 status.gyro.x = {
605 .raw_value = input_engine->GetAxis(identifier, axis_x),
606 .properties = properties_x,
607 };
608 status.gyro.y = {
609 .raw_value = input_engine->GetAxis(identifier, axis_y),
610 .properties = properties_y,
611 };
612 status.gyro.z = {
613 .raw_value = input_engine->GetAxis(identifier, axis_z),
614 .properties = properties_z,
615 };
616 status.delta_timestamp = 5000;
617 status.force_update = true;
618 return status;
619 }
620
621 void ForceUpdate() {
622 const Common::Input::CallbackStatus status{
623 .type = Common::Input::InputType::Motion,
624 .motion_status = GetStatus(),
625 };
626
627 last_axis_x_value = status.motion_status.gyro.x.raw_value;
628 last_axis_y_value = status.motion_status.gyro.y.raw_value;
629 last_axis_z_value = status.motion_status.gyro.z.raw_value;
630 TriggerOnChange(status);
631 }
632
633 void OnChange() {
634 const Common::Input::CallbackStatus status{
635 .type = Common::Input::InputType::Motion,
636 .motion_status = GetStatus(),
637 };
638
639 if (status.motion_status.gyro.x.raw_value != last_axis_x_value ||
640 status.motion_status.gyro.y.raw_value != last_axis_y_value ||
641 status.motion_status.gyro.z.raw_value != last_axis_z_value) {
642 last_axis_x_value = status.motion_status.gyro.x.raw_value;
643 last_axis_y_value = status.motion_status.gyro.y.raw_value;
644 last_axis_z_value = status.motion_status.gyro.z.raw_value;
645 TriggerOnChange(status);
646 }
647 }
648
649private:
650 const PadIdentifier identifier;
651 const int axis_x;
652 const int axis_y;
653 const int axis_z;
654 const Common::Input::AnalogProperties properties_x;
655 const Common::Input::AnalogProperties properties_y;
656 const Common::Input::AnalogProperties properties_z;
657 int callback_key_x;
658 int callback_key_y;
659 int callback_key_z;
660 float last_axis_x_value;
661 float last_axis_y_value;
662 float last_axis_z_value;
663 InputEngine* input_engine;
664};
665
666class OutputFromIdentifier final : public Common::Input::OutputDevice {
667public:
668 explicit OutputFromIdentifier(PadIdentifier identifier_, InputEngine* input_engine_)
669 : identifier(identifier_), input_engine(input_engine_) {}
670
671 virtual void SetLED(Common::Input::LedStatus led_status) {
672 input_engine->SetLeds(identifier, led_status);
673 }
674
675 virtual Common::Input::VibrationError SetVibration(
676 Common::Input::VibrationStatus vibration_status) {
677 return input_engine->SetRumble(identifier, vibration_status);
678 }
679
680 virtual Common::Input::PollingError SetPollingMode(Common::Input::PollingMode polling_mode) {
681 return input_engine->SetPollingMode(identifier, polling_mode);
682 }
683
684private:
685 const PadIdentifier identifier;
686 InputEngine* input_engine;
687};
688
689std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateButtonDevice(
690 const Common::ParamPackage& params) {
691 const PadIdentifier identifier = {
692 .guid = Common::UUID{params.Get("guid", "")},
693 .port = static_cast<std::size_t>(params.Get("port", 0)),
694 .pad = static_cast<std::size_t>(params.Get("pad", 0)),
695 };
696
697 const auto button_id = params.Get("button", 0);
698 const auto keyboard_key = params.Get("code", 0);
699 const auto toggle = params.Get("toggle", false);
700 const auto inverted = params.Get("inverted", false);
701 input_engine->PreSetController(identifier);
702 input_engine->PreSetButton(identifier, button_id);
703 input_engine->PreSetButton(identifier, keyboard_key);
704 if (keyboard_key != 0) {
705 return std::make_unique<InputFromButton>(identifier, keyboard_key, toggle, inverted,
706 input_engine.get());
707 }
708 return std::make_unique<InputFromButton>(identifier, button_id, toggle, inverted,
709 input_engine.get());
710}
711
712std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateHatButtonDevice(
713 const Common::ParamPackage& params) {
714 const PadIdentifier identifier = {
715 .guid = Common::UUID{params.Get("guid", "")},
716 .port = static_cast<std::size_t>(params.Get("port", 0)),
717 .pad = static_cast<std::size_t>(params.Get("pad", 0)),
718 };
719
720 const auto button_id = params.Get("hat", 0);
721 const auto direction = input_engine->GetHatButtonId(params.Get("direction", ""));
722 const auto toggle = params.Get("toggle", false);
723 const auto inverted = params.Get("inverted", false);
724
725 input_engine->PreSetController(identifier);
726 input_engine->PreSetHatButton(identifier, button_id);
727 return std::make_unique<InputFromHatButton>(identifier, button_id, direction, toggle, inverted,
728 input_engine.get());
729}
730
731std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateStickDevice(
732 const Common::ParamPackage& params) {
733 const auto deadzone = std::clamp(params.Get("deadzone", 0.15f), 0.0f, 1.0f);
734 const auto range = std::clamp(params.Get("range", 1.0f), 0.25f, 1.50f);
735 const auto threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f);
736 const PadIdentifier identifier = {
737 .guid = Common::UUID{params.Get("guid", "")},
738 .port = static_cast<std::size_t>(params.Get("port", 0)),
739 .pad = static_cast<std::size_t>(params.Get("pad", 0)),
740 };
741
742 const auto axis_x = params.Get("axis_x", 0);
743 const Common::Input::AnalogProperties properties_x = {
744 .deadzone = deadzone,
745 .range = range,
746 .threshold = threshold,
747 .offset = std::clamp(params.Get("offset_x", 0.0f), -1.0f, 1.0f),
748 .inverted = params.Get("invert_x", "+") == "-",
749 };
750
751 const auto axis_y = params.Get("axis_y", 1);
752 const Common::Input::AnalogProperties properties_y = {
753 .deadzone = deadzone,
754 .range = range,
755 .threshold = threshold,
756 .offset = std::clamp(params.Get("offset_y", 0.0f), -1.0f, 1.0f),
757 .inverted = params.Get("invert_y", "+") != "+",
758 };
759 input_engine->PreSetController(identifier);
760 input_engine->PreSetAxis(identifier, axis_x);
761 input_engine->PreSetAxis(identifier, axis_y);
762 return std::make_unique<InputFromStick>(identifier, axis_x, axis_y, properties_x, properties_y,
763 input_engine.get());
764}
765
766std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateAnalogDevice(
767 const Common::ParamPackage& params) {
768 const PadIdentifier identifier = {
769 .guid = Common::UUID{params.Get("guid", "")},
770 .port = static_cast<std::size_t>(params.Get("port", 0)),
771 .pad = static_cast<std::size_t>(params.Get("pad", 0)),
772 };
773
774 const auto axis = params.Get("axis", 0);
775 const Common::Input::AnalogProperties properties = {
776 .deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f),
777 .range = std::clamp(params.Get("range", 1.0f), 0.25f, 1.50f),
778 .threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f),
779 .offset = std::clamp(params.Get("offset", 0.0f), -1.0f, 1.0f),
780 .inverted = params.Get("invert", "+") == "-",
781 };
782 input_engine->PreSetController(identifier);
783 input_engine->PreSetAxis(identifier, axis);
784 return std::make_unique<InputFromAnalog>(identifier, axis, properties, input_engine.get());
785}
786
787std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateTriggerDevice(
788 const Common::ParamPackage& params) {
789 const PadIdentifier identifier = {
790 .guid = Common::UUID{params.Get("guid", "")},
791 .port = static_cast<std::size_t>(params.Get("port", 0)),
792 .pad = static_cast<std::size_t>(params.Get("pad", 0)),
793 };
794
795 const auto button = params.Get("button", 0);
796 const auto toggle = params.Get("toggle", false);
797 const auto inverted = params.Get("inverted", false);
798
799 const auto axis = params.Get("axis", 0);
800 const Common::Input::AnalogProperties properties = {
801 .deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f),
802 .range = std::clamp(params.Get("range", 1.0f), 0.25f, 2.50f),
803 .threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f),
804 .offset = std::clamp(params.Get("offset", 0.0f), -1.0f, 1.0f),
805 .inverted = params.Get("invert", false) != 0,
806 };
807 input_engine->PreSetController(identifier);
808 input_engine->PreSetAxis(identifier, axis);
809 input_engine->PreSetButton(identifier, button);
810 return std::make_unique<InputFromTrigger>(identifier, button, toggle, inverted, axis,
811 properties, input_engine.get());
812}
813
814std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateTouchDevice(
815 const Common::ParamPackage& params) {
816 const auto touch_id = params.Get("touch_id", 0);
817 const auto deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f);
818 const auto range = std::clamp(params.Get("range", 1.0f), 0.25f, 1.50f);
819 const auto threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f);
820 const PadIdentifier identifier = {
821 .guid = Common::UUID{params.Get("guid", "")},
822 .port = static_cast<std::size_t>(params.Get("port", 0)),
823 .pad = static_cast<std::size_t>(params.Get("pad", 0)),
824 };
825
826 const auto button = params.Get("button", 0);
827 const auto toggle = params.Get("toggle", false);
828 const auto inverted = params.Get("inverted", false);
829
830 const auto axis_x = params.Get("axis_x", 0);
831 const Common::Input::AnalogProperties properties_x = {
832 .deadzone = deadzone,
833 .range = range,
834 .threshold = threshold,
835 .offset = std::clamp(params.Get("offset_x", 0.0f), -1.0f, 1.0f),
836 .inverted = params.Get("invert_x", "+") == "-",
837 };
838
839 const auto axis_y = params.Get("axis_y", 1);
840 const Common::Input::AnalogProperties properties_y = {
841 .deadzone = deadzone,
842 .range = range,
843 .threshold = threshold,
844 .offset = std::clamp(params.Get("offset_y", 0.0f), -1.0f, 1.0f),
845 .inverted = params.Get("invert_y", false) != 0,
846 };
847 input_engine->PreSetController(identifier);
848 input_engine->PreSetAxis(identifier, axis_x);
849 input_engine->PreSetAxis(identifier, axis_y);
850 input_engine->PreSetButton(identifier, button);
851 return std::make_unique<InputFromTouch>(identifier, touch_id, button, toggle, inverted, axis_x,
852 axis_y, properties_x, properties_y, input_engine.get());
853}
854
855std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateBatteryDevice(
856 const Common::ParamPackage& params) {
857 const PadIdentifier identifier = {
858 .guid = Common::UUID{params.Get("guid", "")},
859 .port = static_cast<std::size_t>(params.Get("port", 0)),
860 .pad = static_cast<std::size_t>(params.Get("pad", 0)),
861 };
862
863 input_engine->PreSetController(identifier);
864 return std::make_unique<InputFromBattery>(identifier, input_engine.get());
865}
866
867std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateMotionDevice(
868 Common::ParamPackage params) {
869 const PadIdentifier identifier = {
870 .guid = Common::UUID{params.Get("guid", "")},
871 .port = static_cast<std::size_t>(params.Get("port", 0)),
872 .pad = static_cast<std::size_t>(params.Get("pad", 0)),
873 };
874
875 if (params.Has("motion")) {
876 const auto motion_sensor = params.Get("motion", 0);
877 input_engine->PreSetController(identifier);
878 input_engine->PreSetMotion(identifier, motion_sensor);
879 return std::make_unique<InputFromMotion>(identifier, motion_sensor, input_engine.get());
880 }
881
882 const auto deadzone = std::clamp(params.Get("deadzone", 0.15f), 0.0f, 1.0f);
883 const auto range = std::clamp(params.Get("range", 1.0f), 0.25f, 1.50f);
884 const auto threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f);
885
886 const auto axis_x = params.Get("axis_x", 0);
887 const Common::Input::AnalogProperties properties_x = {
888 .deadzone = deadzone,
889 .range = range,
890 .threshold = threshold,
891 .offset = std::clamp(params.Get("offset_x", 0.0f), -1.0f, 1.0f),
892 .inverted = params.Get("invert_x", "+") == "-",
893 };
894
895 const auto axis_y = params.Get("axis_y", 1);
896 const Common::Input::AnalogProperties properties_y = {
897 .deadzone = deadzone,
898 .range = range,
899 .threshold = threshold,
900 .offset = std::clamp(params.Get("offset_y", 0.0f), -1.0f, 1.0f),
901 .inverted = params.Get("invert_y", "+") != "+",
902 };
903
904 const auto axis_z = params.Get("axis_z", 1);
905 const Common::Input::AnalogProperties properties_z = {
906 .deadzone = deadzone,
907 .range = range,
908 .threshold = threshold,
909 .offset = std::clamp(params.Get("offset_z", 0.0f), -1.0f, 1.0f),
910 .inverted = params.Get("invert_z", "+") != "+",
911 };
912 input_engine->PreSetController(identifier);
913 input_engine->PreSetAxis(identifier, axis_x);
914 input_engine->PreSetAxis(identifier, axis_y);
915 input_engine->PreSetAxis(identifier, axis_z);
916 return std::make_unique<InputFromAxisMotion>(identifier, axis_x, axis_y, axis_z, properties_x,
917 properties_y, properties_z, input_engine.get());
918}
919
920InputFactory::InputFactory(std::shared_ptr<InputEngine> input_engine_)
921 : input_engine(std::move(input_engine_)) {}
922
923std::unique_ptr<Common::Input::InputDevice> InputFactory::Create(
924 const Common::ParamPackage& params) {
925 if (params.Has("battery")) {
926 return CreateBatteryDevice(params);
927 }
928 if (params.Has("button") && params.Has("axis")) {
929 return CreateTriggerDevice(params);
930 }
931 if (params.Has("button") && params.Has("axis_x") && params.Has("axis_y")) {
932 return CreateTouchDevice(params);
933 }
934 if (params.Has("button") || params.Has("code")) {
935 return CreateButtonDevice(params);
936 }
937 if (params.Has("hat")) {
938 return CreateHatButtonDevice(params);
939 }
940 if (params.Has("axis_x") && params.Has("axis_y") && params.Has("axis_z")) {
941 return CreateMotionDevice(params);
942 }
943 if (params.Has("motion")) {
944 return CreateMotionDevice(params);
945 }
946 if (params.Has("axis_x") && params.Has("axis_y")) {
947 return CreateStickDevice(params);
948 }
949 if (params.Has("axis")) {
950 return CreateAnalogDevice(params);
951 }
952 LOG_ERROR(Input, "Invalid parameters given");
953 return std::make_unique<DummyInput>();
954}
955
956OutputFactory::OutputFactory(std::shared_ptr<InputEngine> input_engine_)
957 : input_engine(std::move(input_engine_)) {}
958
959std::unique_ptr<Common::Input::OutputDevice> OutputFactory::Create(
960 const Common::ParamPackage& params) {
961 const PadIdentifier identifier = {
962 .guid = Common::UUID{params.Get("guid", "")},
963 .port = static_cast<std::size_t>(params.Get("port", 0)),
964 .pad = static_cast<std::size_t>(params.Get("pad", 0)),
965 };
966
967 input_engine->PreSetController(identifier);
968 return std::make_unique<OutputFromIdentifier>(identifier, input_engine.get());
969}
970
971} // 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 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included
4
5#pragma once
6
7namespace Input {
8class InputDevice;
9
10template <typename InputDevice>
11class Factory;
12}; // namespace Input
13
14namespace InputCommon {
15class InputEngine;
16/**
17 * An Input factory. It receives input events and forward them to all input devices it created.
18 */
19
20class OutputFactory final : public Common::Input::Factory<Common::Input::OutputDevice> {
21public:
22 explicit OutputFactory(std::shared_ptr<InputEngine> input_engine_);
23
24 /**
25 * Creates an output device from the parameters given.
26 * @param params contains parameters for creating the device:
27 * @param - "guid": text string for identifing controllers
28 * @param - "port": port of the connected device
29 * @param - "pad": slot of the connected controller
30 * @return an unique ouput device with the parameters specified
31 */
32 std::unique_ptr<Common::Input::OutputDevice> Create(
33 const Common::ParamPackage& params) override;
34
35private:
36 std::shared_ptr<InputEngine> input_engine;
37};
38
39class InputFactory final : public Common::Input::Factory<Common::Input::InputDevice> {
40public:
41 explicit InputFactory(std::shared_ptr<InputEngine> input_engine_);
42
43 /**
44 * Creates an input device from the parameters given. Identifies the type of input to be
45 * returned if it contains the following parameters:
46 * - button: Contains "button" or "code"
47 * - hat_button: Contains "hat"
48 * - analog: Contains "axis"
49 * - trigger: Contains "button" and "axis"
50 * - stick: Contains "axis_x" and "axis_y"
51 * - motion: Contains "axis_x", "axis_y" and "axis_z"
52 * - motion: Contains "motion"
53 * - touch: Contains "button", "axis_x" and "axis_y"
54 * - battery: Contains "battery"
55 * - output: Contains "output"
56 * @param params contains parameters for creating the device:
57 * @param - "code": the code of the keyboard key to bind with the input
58 * @param - "button": same as "code" but for controller buttons
59 * @param - "hat": similar as "button" but it's a group of hat buttons from SDL
60 * @param - "axis": the axis number of the axis to bind with the input
61 * @param - "motion": the motion number of the motion to bind with the input
62 * @param - "axis_x": same as axis but specifing horizontal direction
63 * @param - "axis_y": same as axis but specifing vertical direction
64 * @param - "axis_z": same as axis but specifing forward direction
65 * @param - "battery": Only used as a placeholder to set the input type
66 * @return an unique input device with the parameters specified
67 */
68 std::unique_ptr<Common::Input::InputDevice> Create(const Common::ParamPackage& params) override;
69
70private:
71 /**
72 * Creates a button device from the parameters given.
73 * @param params contains parameters for creating the device:
74 * @param - "code": the code of the keyboard key to bind with the input
75 * @param - "button": same as "code" but for controller buttons
76 * @param - "toggle": press once to enable, press again to disable
77 * @param - "inverted": inverts the output of the button
78 * @param - "guid": text string for identifing controllers
79 * @param - "port": port of the connected device
80 * @param - "pad": slot of the connected controller
81 * @return an unique input device with the parameters specified
82 */
83 std::unique_ptr<Common::Input::InputDevice> CreateButtonDevice(
84 const Common::ParamPackage& params);
85
86 /**
87 * Creates a hat button device from the parameters given.
88 * @param params contains parameters for creating the device:
89 * @param - "button": the controller hat id to bind with the input
90 * @param - "direction": the direction id to be detected
91 * @param - "toggle": press once to enable, press again to disable
92 * @param - "inverted": inverts the output of the button
93 * @param - "guid": text string for identifing controllers
94 * @param - "port": port of the connected device
95 * @param - "pad": slot of the connected controller
96 * @return an unique input device with the parameters specified
97 */
98 std::unique_ptr<Common::Input::InputDevice> CreateHatButtonDevice(
99 const Common::ParamPackage& params);
100
101 /**
102 * Creates a stick device from the parameters given.
103 * @param params contains parameters for creating the device:
104 * @param - "axis_x": the controller horizontal axis id to bind with the input
105 * @param - "axis_y": the controller vertical axis id to bind with the input
106 * @param - "deadzone": the mimimum required value to be detected
107 * @param - "range": the maximum value required to reach 100%
108 * @param - "threshold": the mimimum required value to considered pressed
109 * @param - "offset_x": the amount of offset in the x axis
110 * @param - "offset_y": the amount of offset in the y axis
111 * @param - "invert_x": inverts the sign of the horizontal axis
112 * @param - "invert_y": inverts the sign of the vertical axis
113 * @param - "guid": text string for identifing controllers
114 * @param - "port": port of the connected device
115 * @param - "pad": slot of the connected controller
116 * @return an unique input device with the parameters specified
117 */
118 std::unique_ptr<Common::Input::InputDevice> CreateStickDevice(
119 const Common::ParamPackage& params);
120
121 /**
122 * Creates an analog device from the parameters given.
123 * @param params contains parameters for creating the device:
124 * @param - "axis": the controller axis id to bind with the input
125 * @param - "deadzone": the mimimum required value to be detected
126 * @param - "range": the maximum value required to reach 100%
127 * @param - "threshold": the mimimum required value to considered pressed
128 * @param - "offset": the amount of offset in the axis
129 * @param - "invert": inverts the sign of the axis
130 * @param - "guid": text string for identifing controllers
131 * @param - "port": port of the connected device
132 * @param - "pad": slot of the connected controller
133 * @return an unique input device with the parameters specified
134 */
135 std::unique_ptr<Common::Input::InputDevice> CreateAnalogDevice(
136 const Common::ParamPackage& params);
137
138 /**
139 * Creates a trigger device from the parameters given.
140 * @param params contains parameters for creating the device:
141 * @param - "button": the controller hat id to bind with the input
142 * @param - "direction": the direction id to be detected
143 * @param - "toggle": press once to enable, press again to disable
144 * @param - "inverted": inverts the output of the button
145 * @param - "axis": the controller axis id to bind with the input
146 * @param - "deadzone": the mimimum required value to be detected
147 * @param - "range": the maximum value required to reach 100%
148 * @param - "threshold": the mimimum required value to considered pressed
149 * @param - "offset": the amount of offset in the axis
150 * @param - "invert": inverts the sign of the axis
151 * @param - "guid": text string for identifing controllers
152 * @param - "port": port of the connected device
153 * @param - "pad": slot of the connected controller
154 * @return an unique input device with the parameters specified
155 */
156 std::unique_ptr<Common::Input::InputDevice> CreateTriggerDevice(
157 const Common::ParamPackage& params);
158
159 /**
160 * Creates a touch device from the parameters given.
161 * @param params contains parameters for creating the device:
162 * @param - "button": the controller hat id to bind with the input
163 * @param - "direction": the direction id to be detected
164 * @param - "toggle": press once to enable, press again to disable
165 * @param - "inverted": inverts the output of the button
166 * @param - "axis_x": the controller horizontal axis id to bind with the input
167 * @param - "axis_y": the controller vertical axis id to bind with the input
168 * @param - "deadzone": the mimimum required value to be detected
169 * @param - "range": the maximum value required to reach 100%
170 * @param - "threshold": the mimimum required value to considered pressed
171 * @param - "offset_x": the amount of offset in the x axis
172 * @param - "offset_y": the amount of offset in the y axis
173 * @param - "invert_x": inverts the sign of the horizontal axis
174 * @param - "invert_y": inverts the sign of the vertical axis
175 * @param - "guid": text string for identifing controllers
176 * @param - "port": port of the connected device
177 * @param - "pad": slot of the connected controller
178 * @return an unique input device with the parameters specified
179 */
180 std::unique_ptr<Common::Input::InputDevice> CreateTouchDevice(
181 const Common::ParamPackage& params);
182
183 /**
184 * Creates a battery device from the parameters given.
185 * @param params contains parameters for creating the device:
186 * @param - "guid": text string for identifing controllers
187 * @param - "port": port of the connected device
188 * @param - "pad": slot of the connected controller
189 * @return an unique input device with the parameters specified
190 */
191 std::unique_ptr<Common::Input::InputDevice> CreateBatteryDevice(
192 const Common::ParamPackage& params);
193
194 /**
195 * Creates a motion device from the parameters given.
196 * @param params contains parameters for creating the device:
197 * @param - "axis_x": the controller horizontal axis id to bind with the input
198 * @param - "axis_y": the controller vertical axis id to bind with the input
199 * @param - "axis_z": the controller fordward axis id to bind with the input
200 * @param - "deadzone": the mimimum required value to be detected
201 * @param - "range": the maximum value required to reach 100%
202 * @param - "offset_x": the amount of offset in the x axis
203 * @param - "offset_y": the amount of offset in the y axis
204 * @param - "offset_z": the amount of offset in the z axis
205 * @param - "invert_x": inverts the sign of the horizontal axis
206 * @param - "invert_y": inverts the sign of the vertical axis
207 * @param - "invert_z": inverts the sign of the fordward axis
208 * @param - "guid": text string for identifing controllers
209 * @param - "port": port of the connected device
210 * @param - "pad": slot of the connected controller
211 * @return an unique input device with the parameters specified
212 */
213 std::unique_ptr<Common::Input::InputDevice> CreateMotionDevice(Common::ParamPackage params);
214
215 std::shared_ptr<InputEngine> input_engine;
216};
217} // 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 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <atomic>
6#include <list>
7#include <mutex>
8#include <utility>
9#include "input_common/keyboard.h"
10
11namespace InputCommon {
12
13class KeyButton final : public Input::ButtonDevice {
14public:
15 explicit KeyButton(std::shared_ptr<KeyButtonList> key_button_list_, bool toggle_)
16 : key_button_list(std::move(key_button_list_)), toggle(toggle_) {}
17
18 ~KeyButton() override;
19
20 bool GetStatus() const override {
21 if (toggle) {
22 return toggled_status.load(std::memory_order_relaxed);
23 }
24 return status.load();
25 }
26
27 void ToggleButton() {
28 if (lock) {
29 return;
30 }
31 lock = true;
32 const bool old_toggle_status = toggled_status.load();
33 toggled_status.store(!old_toggle_status);
34 }
35
36 void UnlockButton() {
37 lock = false;
38 }
39
40 friend class KeyButtonList;
41
42private:
43 std::shared_ptr<KeyButtonList> key_button_list;
44 std::atomic<bool> status{false};
45 std::atomic<bool> toggled_status{false};
46 bool lock{false};
47 const bool toggle;
48};
49
50struct KeyButtonPair {
51 int key_code;
52 KeyButton* key_button;
53};
54
55class KeyButtonList {
56public:
57 void AddKeyButton(int key_code, KeyButton* key_button) {
58 std::lock_guard guard{mutex};
59 list.push_back(KeyButtonPair{key_code, key_button});
60 }
61
62 void RemoveKeyButton(const KeyButton* key_button) {
63 std::lock_guard guard{mutex};
64 list.remove_if(
65 [key_button](const KeyButtonPair& pair) { return pair.key_button == key_button; });
66 }
67
68 void ChangeKeyStatus(int key_code, bool pressed) {
69 std::lock_guard guard{mutex};
70 for (const KeyButtonPair& pair : list) {
71 if (pair.key_code == key_code) {
72 pair.key_button->status.store(pressed);
73 if (pressed) {
74 pair.key_button->ToggleButton();
75 } else {
76 pair.key_button->UnlockButton();
77 }
78 pair.key_button->TriggerOnChange();
79 }
80 }
81 }
82
83 void ChangeAllKeyStatus(bool pressed) {
84 std::lock_guard guard{mutex};
85 for (const KeyButtonPair& pair : list) {
86 pair.key_button->status.store(pressed);
87 }
88 }
89
90private:
91 std::mutex mutex;
92 std::list<KeyButtonPair> list;
93};
94
95Keyboard::Keyboard() : key_button_list{std::make_shared<KeyButtonList>()} {}
96
97KeyButton::~KeyButton() {
98 key_button_list->RemoveKeyButton(this);
99}
100
101std::unique_ptr<Input::ButtonDevice> Keyboard::Create(const Common::ParamPackage& params) {
102 const int key_code = params.Get("code", 0);
103 const bool toggle = params.Get("toggle", false);
104 std::unique_ptr<KeyButton> button = std::make_unique<KeyButton>(key_button_list, toggle);
105 key_button_list->AddKeyButton(key_code, button.get());
106 return button;
107}
108
109void Keyboard::PressKey(int key_code) {
110 key_button_list->ChangeKeyStatus(key_code, true);
111}
112
113void Keyboard::ReleaseKey(int key_code) {
114 key_button_list->ChangeKeyStatus(key_code, false);
115}
116
117void Keyboard::ReleaseAllKeys() {
118 key_button_list->ChangeAllKeyStatus(false);
119}
120
121} // 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 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include "core/frontend/input.h"
9
10namespace InputCommon {
11
12class KeyButtonList;
13
14/**
15 * A button device factory representing a keyboard. It receives keyboard events and forward them
16 * to all button devices it created.
17 */
18class Keyboard final : public Input::Factory<Input::ButtonDevice> {
19public:
20 Keyboard();
21
22 /**
23 * Creates a button device from a keyboard key
24 * @param params contains parameters for creating the device:
25 * - "code": the code of the key to bind with the button
26 */
27 std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override;
28
29 /**
30 * Sets the status of all buttons bound with the key to pressed
31 * @param key_code the code of the key to press
32 */
33 void PressKey(int key_code);
34
35 /**
36 * Sets the status of all buttons bound with the key to released
37 * @param key_code the code of the key to release
38 */
39 void ReleaseKey(int key_code);
40
41 void ReleaseAllKeys();
42
43private:
44 std::shared_ptr<KeyButtonList> key_button_list;
45};
46
47} // 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 @@
4 4
5#include <memory> 5#include <memory>
6#include <thread> 6#include <thread>
7#include "common/input.h"
7#include "common/param_package.h" 8#include "common/param_package.h"
8#include "common/settings.h" 9#include "input_common/drivers/gc_adapter.h"
9#include "input_common/analog_from_button.h" 10#include "input_common/drivers/keyboard.h"
10#include "input_common/gcadapter/gc_adapter.h" 11#include "input_common/drivers/mouse.h"
11#include "input_common/gcadapter/gc_poller.h" 12#include "input_common/drivers/tas_input.h"
12#include "input_common/keyboard.h" 13#include "input_common/drivers/touch_screen.h"
14#include "input_common/drivers/udp_client.h"
15#include "input_common/helpers/stick_from_buttons.h"
16#include "input_common/helpers/touch_from_buttons.h"
17#include "input_common/input_engine.h"
18#include "input_common/input_mapping.h"
19#include "input_common/input_poller.h"
13#include "input_common/main.h" 20#include "input_common/main.h"
14#include "input_common/motion_from_button.h"
15#include "input_common/mouse/mouse_input.h"
16#include "input_common/mouse/mouse_poller.h"
17#include "input_common/tas/tas_input.h"
18#include "input_common/tas/tas_poller.h"
19#include "input_common/touch_from_button.h"
20#include "input_common/udp/client.h"
21#include "input_common/udp/udp.h"
22#ifdef HAVE_SDL2 21#ifdef HAVE_SDL2
23#include "input_common/sdl/sdl.h" 22#include "input_common/drivers/sdl_driver.h"
24#endif 23#endif
25 24
26namespace InputCommon { 25namespace InputCommon {
27 26
28struct InputSubsystem::Impl { 27struct InputSubsystem::Impl {
29 void Initialize() { 28 void Initialize() {
30 gcadapter = std::make_shared<GCAdapter::Adapter>(); 29 mapping_factory = std::make_shared<MappingFactory>();
31 gcbuttons = std::make_shared<GCButtonFactory>(gcadapter); 30 MappingCallback mapping_callback{[this](MappingData data) { RegisterInput(data); }};
32 Input::RegisterFactory<Input::ButtonDevice>("gcpad", gcbuttons); 31
33 gcanalog = std::make_shared<GCAnalogFactory>(gcadapter); 32 keyboard = std::make_shared<Keyboard>("keyboard");
34 Input::RegisterFactory<Input::AnalogDevice>("gcpad", gcanalog); 33 keyboard->SetMappingCallback(mapping_callback);
35 gcvibration = std::make_shared<GCVibrationFactory>(gcadapter); 34 keyboard_factory = std::make_shared<InputFactory>(keyboard);
36 Input::RegisterFactory<Input::VibrationDevice>("gcpad", gcvibration); 35 keyboard_output_factory = std::make_shared<OutputFactory>(keyboard);
37 36 Common::Input::RegisterFactory<Common::Input::InputDevice>(keyboard->GetEngineName(),
38 keyboard = std::make_shared<Keyboard>(); 37 keyboard_factory);
39 Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard); 38 Common::Input::RegisterFactory<Common::Input::OutputDevice>(keyboard->GetEngineName(),
40 Input::RegisterFactory<Input::AnalogDevice>("analog_from_button", 39 keyboard_output_factory);
41 std::make_shared<AnalogFromButton>()); 40
42 Input::RegisterFactory<Input::MotionDevice>("keyboard", 41 mouse = std::make_shared<Mouse>("mouse");
43 std::make_shared<MotionFromButton>()); 42 mouse->SetMappingCallback(mapping_callback);
44 Input::RegisterFactory<Input::TouchDevice>("touch_from_button", 43 mouse_factory = std::make_shared<InputFactory>(mouse);
45 std::make_shared<TouchFromButtonFactory>()); 44 mouse_output_factory = std::make_shared<OutputFactory>(mouse);
45 Common::Input::RegisterFactory<Common::Input::InputDevice>(mouse->GetEngineName(),
46 mouse_factory);
47 Common::Input::RegisterFactory<Common::Input::OutputDevice>(mouse->GetEngineName(),
48 mouse_output_factory);
49
50 touch_screen = std::make_shared<TouchScreen>("touch");
51 touch_screen_factory = std::make_shared<InputFactory>(touch_screen);
52 Common::Input::RegisterFactory<Common::Input::InputDevice>(touch_screen->GetEngineName(),
53 touch_screen_factory);
54
55 gcadapter = std::make_shared<GCAdapter>("gcpad");
56 gcadapter->SetMappingCallback(mapping_callback);
57 gcadapter_input_factory = std::make_shared<InputFactory>(gcadapter);
58 gcadapter_output_factory = std::make_shared<OutputFactory>(gcadapter);
59 Common::Input::RegisterFactory<Common::Input::InputDevice>(gcadapter->GetEngineName(),
60 gcadapter_input_factory);
61 Common::Input::RegisterFactory<Common::Input::OutputDevice>(gcadapter->GetEngineName(),
62 gcadapter_output_factory);
63
64 udp_client = std::make_shared<CemuhookUDP::UDPClient>("cemuhookudp");
65 udp_client->SetMappingCallback(mapping_callback);
66 udp_client_input_factory = std::make_shared<InputFactory>(udp_client);
67 udp_client_output_factory = std::make_shared<OutputFactory>(udp_client);
68 Common::Input::RegisterFactory<Common::Input::InputDevice>(udp_client->GetEngineName(),
69 udp_client_input_factory);
70 Common::Input::RegisterFactory<Common::Input::OutputDevice>(udp_client->GetEngineName(),
71 udp_client_output_factory);
72
73 tas_input = std::make_shared<TasInput::Tas>("tas");
74 tas_input->SetMappingCallback(mapping_callback);
75 tas_input_factory = std::make_shared<InputFactory>(tas_input);
76 tas_output_factory = std::make_shared<OutputFactory>(tas_input);
77 Common::Input::RegisterFactory<Common::Input::InputDevice>(tas_input->GetEngineName(),
78 tas_input_factory);
79 Common::Input::RegisterFactory<Common::Input::OutputDevice>(tas_input->GetEngineName(),
80 tas_output_factory);
46 81
47#ifdef HAVE_SDL2 82#ifdef HAVE_SDL2
48 sdl = SDL::Init(); 83 sdl = std::make_shared<SDLDriver>("sdl");
84 sdl->SetMappingCallback(mapping_callback);
85 sdl_input_factory = std::make_shared<InputFactory>(sdl);
86 sdl_output_factory = std::make_shared<OutputFactory>(sdl);
87 Common::Input::RegisterFactory<Common::Input::InputDevice>(sdl->GetEngineName(),
88 sdl_input_factory);
89 Common::Input::RegisterFactory<Common::Input::OutputDevice>(sdl->GetEngineName(),
90 sdl_output_factory);
49#endif 91#endif
50 92
51 udp = std::make_shared<InputCommon::CemuhookUDP::Client>(); 93 Common::Input::RegisterFactory<Common::Input::InputDevice>(
52 udpmotion = std::make_shared<UDPMotionFactory>(udp); 94 "touch_from_button", std::make_shared<TouchFromButton>());
53 Input::RegisterFactory<Input::MotionDevice>("cemuhookudp", udpmotion); 95 Common::Input::RegisterFactory<Common::Input::InputDevice>(
54 udptouch = std::make_shared<UDPTouchFactory>(udp); 96 "analog_from_button", std::make_shared<StickFromButton>());
55 Input::RegisterFactory<Input::TouchDevice>("cemuhookudp", udptouch);
56
57 mouse = std::make_shared<MouseInput::Mouse>();
58 mousebuttons = std::make_shared<MouseButtonFactory>(mouse);
59 Input::RegisterFactory<Input::ButtonDevice>("mouse", mousebuttons);
60 mouseanalog = std::make_shared<MouseAnalogFactory>(mouse);
61 Input::RegisterFactory<Input::AnalogDevice>("mouse", mouseanalog);
62 mousemotion = std::make_shared<MouseMotionFactory>(mouse);
63 Input::RegisterFactory<Input::MotionDevice>("mouse", mousemotion);
64 mousetouch = std::make_shared<MouseTouchFactory>(mouse);
65 Input::RegisterFactory<Input::TouchDevice>("mouse", mousetouch);
66
67 tas = std::make_shared<TasInput::Tas>();
68 tasbuttons = std::make_shared<TasButtonFactory>(tas);
69 Input::RegisterFactory<Input::ButtonDevice>("tas", tasbuttons);
70 tasanalog = std::make_shared<TasAnalogFactory>(tas);
71 Input::RegisterFactory<Input::AnalogDevice>("tas", tasanalog);
72 } 97 }
73 98
74 void Shutdown() { 99 void Shutdown() {
75 Input::UnregisterFactory<Input::ButtonDevice>("keyboard"); 100 Common::Input::UnregisterFactory<Common::Input::InputDevice>(keyboard->GetEngineName());
76 Input::UnregisterFactory<Input::MotionDevice>("keyboard"); 101 Common::Input::UnregisterFactory<Common::Input::OutputDevice>(keyboard->GetEngineName());
77 keyboard.reset(); 102 keyboard.reset();
78 Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button");
79 Input::UnregisterFactory<Input::TouchDevice>("touch_from_button");
80#ifdef HAVE_SDL2
81 sdl.reset();
82#endif
83 Input::UnregisterFactory<Input::ButtonDevice>("gcpad");
84 Input::UnregisterFactory<Input::AnalogDevice>("gcpad");
85 Input::UnregisterFactory<Input::VibrationDevice>("gcpad");
86 103
87 gcbuttons.reset(); 104 Common::Input::UnregisterFactory<Common::Input::InputDevice>(mouse->GetEngineName());
88 gcanalog.reset(); 105 Common::Input::UnregisterFactory<Common::Input::OutputDevice>(mouse->GetEngineName());
89 gcvibration.reset(); 106 mouse.reset();
90 107
91 Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp"); 108 Common::Input::UnregisterFactory<Common::Input::InputDevice>(touch_screen->GetEngineName());
92 Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp"); 109 touch_screen.reset();
93 110
94 udpmotion.reset(); 111 Common::Input::UnregisterFactory<Common::Input::InputDevice>(gcadapter->GetEngineName());
95 udptouch.reset(); 112 Common::Input::UnregisterFactory<Common::Input::OutputDevice>(gcadapter->GetEngineName());
113 gcadapter.reset();
96 114
97 Input::UnregisterFactory<Input::ButtonDevice>("mouse"); 115 Common::Input::UnregisterFactory<Common::Input::InputDevice>(udp_client->GetEngineName());
98 Input::UnregisterFactory<Input::AnalogDevice>("mouse"); 116 Common::Input::UnregisterFactory<Common::Input::OutputDevice>(udp_client->GetEngineName());
99 Input::UnregisterFactory<Input::MotionDevice>("mouse"); 117 udp_client.reset();
100 Input::UnregisterFactory<Input::TouchDevice>("mouse");
101 118
102 mousebuttons.reset(); 119 Common::Input::UnregisterFactory<Common::Input::InputDevice>(tas_input->GetEngineName());
103 mouseanalog.reset(); 120 Common::Input::UnregisterFactory<Common::Input::OutputDevice>(tas_input->GetEngineName());
104 mousemotion.reset(); 121 tas_input.reset();
105 mousetouch.reset();
106 122
107 Input::UnregisterFactory<Input::ButtonDevice>("tas"); 123#ifdef HAVE_SDL2
108 Input::UnregisterFactory<Input::AnalogDevice>("tas"); 124 Common::Input::UnregisterFactory<Common::Input::InputDevice>(sdl->GetEngineName());
125 Common::Input::UnregisterFactory<Common::Input::OutputDevice>(sdl->GetEngineName());
126 sdl.reset();
127#endif
109 128
110 tasbuttons.reset(); 129 Common::Input::UnregisterFactory<Common::Input::InputDevice>("touch_from_button");
111 tasanalog.reset(); 130 Common::Input::UnregisterFactory<Common::Input::InputDevice>("analog_from_button");
112 } 131 }
113 132
114 [[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const { 133 [[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const {
115 std::vector<Common::ParamPackage> devices = { 134 std::vector<Common::ParamPackage> devices = {
116 Common::ParamPackage{{"display", "Any"}, {"class", "any"}}, 135 Common::ParamPackage{{"display", "Any"}, {"engine", "any"}},
117 Common::ParamPackage{{"display", "Keyboard/Mouse"}, {"class", "keyboard"}},
118 }; 136 };
119 if (Settings::values.tas_enable) { 137
120 devices.emplace_back( 138 auto keyboard_devices = keyboard->GetInputDevices();
121 Common::ParamPackage{{"display", "TAS Controller"}, {"class", "tas"}}); 139 devices.insert(devices.end(), keyboard_devices.begin(), keyboard_devices.end());
122 } 140 auto mouse_devices = mouse->GetInputDevices();
141 devices.insert(devices.end(), mouse_devices.begin(), mouse_devices.end());
142 auto gcadapter_devices = gcadapter->GetInputDevices();
143 devices.insert(devices.end(), gcadapter_devices.begin(), gcadapter_devices.end());
144 auto udp_devices = udp_client->GetInputDevices();
145 devices.insert(devices.end(), udp_devices.begin(), udp_devices.end());
123#ifdef HAVE_SDL2 146#ifdef HAVE_SDL2
124 auto sdl_devices = sdl->GetInputDevices(); 147 auto sdl_devices = sdl->GetInputDevices();
125 devices.insert(devices.end(), sdl_devices.begin(), sdl_devices.end()); 148 devices.insert(devices.end(), sdl_devices.begin(), sdl_devices.end());
126#endif 149#endif
127 auto udp_devices = udp->GetInputDevices(); 150
128 devices.insert(devices.end(), udp_devices.begin(), udp_devices.end());
129 auto gcpad_devices = gcadapter->GetInputDevices();
130 devices.insert(devices.end(), gcpad_devices.begin(), gcpad_devices.end());
131 return devices; 151 return devices;
132 } 152 }
133 153
134 [[nodiscard]] AnalogMapping GetAnalogMappingForDevice( 154 [[nodiscard]] AnalogMapping GetAnalogMappingForDevice(
135 const Common::ParamPackage& params) const { 155 const Common::ParamPackage& params) const {
136 if (!params.Has("class") || params.Get("class", "") == "any") { 156 if (!params.Has("engine") || params.Get("engine", "") == "any") {
137 return {}; 157 return {};
138 } 158 }
139 if (params.Get("class", "") == "gcpad") { 159 const std::string engine = params.Get("engine", "");
160 if (engine == mouse->GetEngineName()) {
161 return mouse->GetAnalogMappingForDevice(params);
162 }
163 if (engine == gcadapter->GetEngineName()) {
140 return gcadapter->GetAnalogMappingForDevice(params); 164 return gcadapter->GetAnalogMappingForDevice(params);
141 } 165 }
142 if (params.Get("class", "") == "tas") { 166 if (engine == udp_client->GetEngineName()) {
143 return tas->GetAnalogMappingForDevice(params); 167 return udp_client->GetAnalogMappingForDevice(params);
168 }
169 if (engine == tas_input->GetEngineName()) {
170 return tas_input->GetAnalogMappingForDevice(params);
144 } 171 }
145#ifdef HAVE_SDL2 172#ifdef HAVE_SDL2
146 if (params.Get("class", "") == "sdl") { 173 if (engine == sdl->GetEngineName()) {
147 return sdl->GetAnalogMappingForDevice(params); 174 return sdl->GetAnalogMappingForDevice(params);
148 } 175 }
149#endif 176#endif
@@ -152,17 +179,21 @@ struct InputSubsystem::Impl {
152 179
153 [[nodiscard]] ButtonMapping GetButtonMappingForDevice( 180 [[nodiscard]] ButtonMapping GetButtonMappingForDevice(
154 const Common::ParamPackage& params) const { 181 const Common::ParamPackage& params) const {
155 if (!params.Has("class") || params.Get("class", "") == "any") { 182 if (!params.Has("engine") || params.Get("engine", "") == "any") {
156 return {}; 183 return {};
157 } 184 }
158 if (params.Get("class", "") == "gcpad") { 185 const std::string engine = params.Get("engine", "");
186 if (engine == gcadapter->GetEngineName()) {
159 return gcadapter->GetButtonMappingForDevice(params); 187 return gcadapter->GetButtonMappingForDevice(params);
160 } 188 }
161 if (params.Get("class", "") == "tas") { 189 if (engine == udp_client->GetEngineName()) {
162 return tas->GetButtonMappingForDevice(params); 190 return udp_client->GetButtonMappingForDevice(params);
191 }
192 if (engine == tas_input->GetEngineName()) {
193 return tas_input->GetButtonMappingForDevice(params);
163 } 194 }
164#ifdef HAVE_SDL2 195#ifdef HAVE_SDL2
165 if (params.Get("class", "") == "sdl") { 196 if (engine == sdl->GetEngineName()) {
166 return sdl->GetButtonMappingForDevice(params); 197 return sdl->GetButtonMappingForDevice(params);
167 } 198 }
168#endif 199#endif
@@ -171,40 +202,119 @@ struct InputSubsystem::Impl {
171 202
172 [[nodiscard]] MotionMapping GetMotionMappingForDevice( 203 [[nodiscard]] MotionMapping GetMotionMappingForDevice(
173 const Common::ParamPackage& params) const { 204 const Common::ParamPackage& params) const {
174 if (!params.Has("class") || params.Get("class", "") == "any") { 205 if (!params.Has("engine") || params.Get("engine", "") == "any") {
175 return {}; 206 return {};
176 } 207 }
177 if (params.Get("class", "") == "cemuhookudp") { 208 const std::string engine = params.Get("engine", "");
178 // TODO return the correct motion device 209 if (engine == udp_client->GetEngineName()) {
179 return {}; 210 return udp_client->GetMotionMappingForDevice(params);
180 } 211 }
181#ifdef HAVE_SDL2 212#ifdef HAVE_SDL2
182 if (params.Get("class", "") == "sdl") { 213 if (engine == sdl->GetEngineName()) {
183 return sdl->GetMotionMappingForDevice(params); 214 return sdl->GetMotionMappingForDevice(params);
184 } 215 }
185#endif 216#endif
186 return {}; 217 return {};
187 } 218 }
188 219
220 Common::Input::ButtonNames GetButtonName(const Common::ParamPackage& params) const {
221 if (!params.Has("engine") || params.Get("engine", "") == "any") {
222 return Common::Input::ButtonNames::Undefined;
223 }
224 const std::string engine = params.Get("engine", "");
225 if (engine == mouse->GetEngineName()) {
226 return mouse->GetUIName(params);
227 }
228 if (engine == gcadapter->GetEngineName()) {
229 return gcadapter->GetUIName(params);
230 }
231 if (engine == udp_client->GetEngineName()) {
232 return udp_client->GetUIName(params);
233 }
234 if (engine == tas_input->GetEngineName()) {
235 return tas_input->GetUIName(params);
236 }
237#ifdef HAVE_SDL2
238 if (engine == sdl->GetEngineName()) {
239 return sdl->GetUIName(params);
240 }
241#endif
242 return Common::Input::ButtonNames::Invalid;
243 }
244
245 bool IsController(const Common::ParamPackage& params) {
246 const std::string engine = params.Get("engine", "");
247 if (engine == mouse->GetEngineName()) {
248 return true;
249 }
250 if (engine == gcadapter->GetEngineName()) {
251 return true;
252 }
253 if (engine == udp_client->GetEngineName()) {
254 return true;
255 }
256 if (engine == tas_input->GetEngineName()) {
257 return true;
258 }
259#ifdef HAVE_SDL2
260 if (engine == sdl->GetEngineName()) {
261 return true;
262 }
263#endif
264 return false;
265 }
266
267 void BeginConfiguration() {
268 keyboard->BeginConfiguration();
269 mouse->BeginConfiguration();
270 gcadapter->BeginConfiguration();
271 udp_client->BeginConfiguration();
272#ifdef HAVE_SDL2
273 sdl->BeginConfiguration();
274#endif
275 }
276
277 void EndConfiguration() {
278 keyboard->EndConfiguration();
279 mouse->EndConfiguration();
280 gcadapter->EndConfiguration();
281 udp_client->EndConfiguration();
282#ifdef HAVE_SDL2
283 sdl->EndConfiguration();
284#endif
285 }
286
287 void RegisterInput(MappingData data) {
288 mapping_factory->RegisterInput(data);
289 }
290
291 std::shared_ptr<MappingFactory> mapping_factory;
292
189 std::shared_ptr<Keyboard> keyboard; 293 std::shared_ptr<Keyboard> keyboard;
294 std::shared_ptr<Mouse> mouse;
295 std::shared_ptr<GCAdapter> gcadapter;
296 std::shared_ptr<TouchScreen> touch_screen;
297 std::shared_ptr<TasInput::Tas> tas_input;
298 std::shared_ptr<CemuhookUDP::UDPClient> udp_client;
299
300 std::shared_ptr<InputFactory> keyboard_factory;
301 std::shared_ptr<InputFactory> mouse_factory;
302 std::shared_ptr<InputFactory> gcadapter_input_factory;
303 std::shared_ptr<InputFactory> touch_screen_factory;
304 std::shared_ptr<InputFactory> udp_client_input_factory;
305 std::shared_ptr<InputFactory> tas_input_factory;
306
307 std::shared_ptr<OutputFactory> keyboard_output_factory;
308 std::shared_ptr<OutputFactory> mouse_output_factory;
309 std::shared_ptr<OutputFactory> gcadapter_output_factory;
310 std::shared_ptr<OutputFactory> udp_client_output_factory;
311 std::shared_ptr<OutputFactory> tas_output_factory;
312
190#ifdef HAVE_SDL2 313#ifdef HAVE_SDL2
191 std::unique_ptr<SDL::State> sdl; 314 std::shared_ptr<SDLDriver> sdl;
315 std::shared_ptr<InputFactory> sdl_input_factory;
316 std::shared_ptr<OutputFactory> sdl_output_factory;
192#endif 317#endif
193 std::shared_ptr<GCButtonFactory> gcbuttons;
194 std::shared_ptr<GCAnalogFactory> gcanalog;
195 std::shared_ptr<GCVibrationFactory> gcvibration;
196 std::shared_ptr<UDPMotionFactory> udpmotion;
197 std::shared_ptr<UDPTouchFactory> udptouch;
198 std::shared_ptr<MouseButtonFactory> mousebuttons;
199 std::shared_ptr<MouseAnalogFactory> mouseanalog;
200 std::shared_ptr<MouseMotionFactory> mousemotion;
201 std::shared_ptr<MouseTouchFactory> mousetouch;
202 std::shared_ptr<TasButtonFactory> tasbuttons;
203 std::shared_ptr<TasAnalogFactory> tasanalog;
204 std::shared_ptr<CemuhookUDP::Client> udp;
205 std::shared_ptr<GCAdapter::Adapter> gcadapter;
206 std::shared_ptr<MouseInput::Mouse> mouse;
207 std::shared_ptr<TasInput::Tas> tas;
208}; 318};
209 319
210InputSubsystem::InputSubsystem() : impl{std::make_unique<Impl>()} {} 320InputSubsystem::InputSubsystem() : impl{std::make_unique<Impl>()} {}
@@ -227,20 +337,28 @@ const Keyboard* InputSubsystem::GetKeyboard() const {
227 return impl->keyboard.get(); 337 return impl->keyboard.get();
228} 338}
229 339
230MouseInput::Mouse* InputSubsystem::GetMouse() { 340Mouse* InputSubsystem::GetMouse() {
231 return impl->mouse.get(); 341 return impl->mouse.get();
232} 342}
233 343
234const MouseInput::Mouse* InputSubsystem::GetMouse() const { 344const Mouse* InputSubsystem::GetMouse() const {
235 return impl->mouse.get(); 345 return impl->mouse.get();
236} 346}
237 347
348TouchScreen* InputSubsystem::GetTouchScreen() {
349 return impl->touch_screen.get();
350}
351
352const TouchScreen* InputSubsystem::GetTouchScreen() const {
353 return impl->touch_screen.get();
354}
355
238TasInput::Tas* InputSubsystem::GetTas() { 356TasInput::Tas* InputSubsystem::GetTas() {
239 return impl->tas.get(); 357 return impl->tas_input.get();
240} 358}
241 359
242const TasInput::Tas* InputSubsystem::GetTas() const { 360const TasInput::Tas* InputSubsystem::GetTas() const {
243 return impl->tas.get(); 361 return impl->tas_input.get();
244} 362}
245 363
246std::vector<Common::ParamPackage> InputSubsystem::GetInputDevices() const { 364std::vector<Common::ParamPackage> InputSubsystem::GetInputDevices() const {
@@ -259,100 +377,30 @@ MotionMapping InputSubsystem::GetMotionMappingForDevice(const Common::ParamPacka
259 return impl->GetMotionMappingForDevice(device); 377 return impl->GetMotionMappingForDevice(device);
260} 378}
261 379
262GCAnalogFactory* InputSubsystem::GetGCAnalogs() { 380Common::Input::ButtonNames InputSubsystem::GetButtonName(const Common::ParamPackage& params) const {
263 return impl->gcanalog.get(); 381 return impl->GetButtonName(params);
264}
265
266const GCAnalogFactory* InputSubsystem::GetGCAnalogs() const {
267 return impl->gcanalog.get();
268}
269
270GCButtonFactory* InputSubsystem::GetGCButtons() {
271 return impl->gcbuttons.get();
272}
273
274const GCButtonFactory* InputSubsystem::GetGCButtons() const {
275 return impl->gcbuttons.get();
276}
277
278UDPMotionFactory* InputSubsystem::GetUDPMotions() {
279 return impl->udpmotion.get();
280}
281
282const UDPMotionFactory* InputSubsystem::GetUDPMotions() const {
283 return impl->udpmotion.get();
284}
285
286UDPTouchFactory* InputSubsystem::GetUDPTouch() {
287 return impl->udptouch.get();
288}
289
290const UDPTouchFactory* InputSubsystem::GetUDPTouch() const {
291 return impl->udptouch.get();
292}
293
294MouseButtonFactory* InputSubsystem::GetMouseButtons() {
295 return impl->mousebuttons.get();
296} 382}
297 383
298const MouseButtonFactory* InputSubsystem::GetMouseButtons() const { 384bool InputSubsystem::IsController(const Common::ParamPackage& params) const {
299 return impl->mousebuttons.get(); 385 return impl->IsController(params);
300} 386}
301 387
302MouseAnalogFactory* InputSubsystem::GetMouseAnalogs() { 388void InputSubsystem::ReloadInputDevices() {
303 return impl->mouseanalog.get(); 389 impl->udp_client.get()->ReloadSockets();
304}
305
306const MouseAnalogFactory* InputSubsystem::GetMouseAnalogs() const {
307 return impl->mouseanalog.get();
308}
309
310MouseMotionFactory* InputSubsystem::GetMouseMotions() {
311 return impl->mousemotion.get();
312}
313
314const MouseMotionFactory* InputSubsystem::GetMouseMotions() const {
315 return impl->mousemotion.get();
316}
317
318MouseTouchFactory* InputSubsystem::GetMouseTouch() {
319 return impl->mousetouch.get();
320}
321
322const MouseTouchFactory* InputSubsystem::GetMouseTouch() const {
323 return impl->mousetouch.get();
324}
325
326TasButtonFactory* InputSubsystem::GetTasButtons() {
327 return impl->tasbuttons.get();
328}
329
330const TasButtonFactory* InputSubsystem::GetTasButtons() const {
331 return impl->tasbuttons.get();
332}
333
334TasAnalogFactory* InputSubsystem::GetTasAnalogs() {
335 return impl->tasanalog.get();
336} 390}
337 391
338const TasAnalogFactory* InputSubsystem::GetTasAnalogs() const { 392void InputSubsystem::BeginMapping(Polling::InputType type) {
339 return impl->tasanalog.get(); 393 impl->BeginConfiguration();
394 impl->mapping_factory->BeginMapping(type);
340} 395}
341 396
342void InputSubsystem::ReloadInputDevices() { 397const Common::ParamPackage InputSubsystem::GetNextInput() const {
343 if (!impl->udp) { 398 return impl->mapping_factory->GetNextInput();
344 return;
345 }
346 impl->udp->ReloadSockets();
347} 399}
348 400
349std::vector<std::unique_ptr<Polling::DevicePoller>> InputSubsystem::GetPollers( 401void InputSubsystem::StopMapping() const {
350 [[maybe_unused]] Polling::DeviceType type) const { 402 impl->EndConfiguration();
351#ifdef HAVE_SDL2 403 impl->mapping_factory->StopMapping();
352 return impl->sdl->GetPollers(type);
353#else
354 return {};
355#endif
356} 404}
357 405
358std::string GenerateKeyboardParam(int key_code) { 406std::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 {
13class ParamPackage; 13class ParamPackage;
14} 14}
15 15
16namespace Common::Input {
17enum class ButtonNames;
18}
19
16namespace Settings::NativeAnalog { 20namespace Settings::NativeAnalog {
17enum Values : int; 21enum Values : int;
18} 22}
@@ -25,56 +29,26 @@ namespace Settings::NativeMotion {
25enum Values : int; 29enum Values : int;
26} 30}
27 31
28namespace MouseInput { 32namespace InputCommon {
33class Keyboard;
29class Mouse; 34class Mouse;
30} 35class TouchScreen;
36struct MappingData;
37} // namespace InputCommon
31 38
32namespace TasInput { 39namespace InputCommon::TasInput {
33class Tas; 40class Tas;
34} 41} // namespace InputCommon::TasInput
35 42
36namespace InputCommon { 43namespace InputCommon {
37namespace Polling { 44namespace Polling {
38 45/// Type of input desired for mapping purposes
39enum class DeviceType { Button, AnalogPreferred, Motion }; 46enum class InputType { None, Button, Stick, Motion, Touch };
40
41/**
42 * A class that can be used to get inputs from an input device like controllers without having to
43 * poll the device's status yourself
44 */
45class DevicePoller {
46public:
47 virtual ~DevicePoller() = default;
48 /// Setup and start polling for inputs, should be called before GetNextInput
49 /// If a device_id is provided, events should be filtered to only include events from this
50 /// device id
51 virtual void Start(const std::string& device_id = "") = 0;
52 /// Stop polling
53 virtual void Stop() = 0;
54 /**
55 * Every call to this function returns the next input recorded since calling Start
56 * @return A ParamPackage of the recorded input, which can be used to create an InputDevice.
57 * If there has been no input, the package is empty
58 */
59 virtual Common::ParamPackage GetNextInput() = 0;
60};
61} // namespace Polling 47} // namespace Polling
62 48
63class GCAnalogFactory;
64class GCButtonFactory;
65class UDPMotionFactory;
66class UDPTouchFactory;
67class MouseButtonFactory;
68class MouseAnalogFactory;
69class MouseMotionFactory;
70class MouseTouchFactory;
71class TasButtonFactory;
72class TasAnalogFactory;
73class Keyboard;
74
75/** 49/**
76 * Given a ParamPackage for a Device returned from `GetInputDevices`, attempt to get the default 50 * Given a ParamPackage for a Device returned from `GetInputDevices`, attempt to get the default
77 * mapping for the device. This is currently only implemented for the SDL backend devices. 51 * mapping for the device.
78 */ 52 */
79using AnalogMapping = std::unordered_map<Settings::NativeAnalog::Values, Common::ParamPackage>; 53using AnalogMapping = std::unordered_map<Settings::NativeAnalog::Values, Common::ParamPackage>;
80using ButtonMapping = std::unordered_map<Settings::NativeButton::Values, Common::ParamPackage>; 54using ButtonMapping = std::unordered_map<Settings::NativeButton::Values, Common::ParamPackage>;
@@ -104,20 +78,27 @@ public:
104 [[nodiscard]] const Keyboard* GetKeyboard() const; 78 [[nodiscard]] const Keyboard* GetKeyboard() const;
105 79
106 /// Retrieves the underlying mouse device. 80 /// Retrieves the underlying mouse device.
107 [[nodiscard]] MouseInput::Mouse* GetMouse(); 81 [[nodiscard]] Mouse* GetMouse();
108 82
109 /// Retrieves the underlying mouse device. 83 /// Retrieves the underlying mouse device.
110 [[nodiscard]] const MouseInput::Mouse* GetMouse() const; 84 [[nodiscard]] const Mouse* GetMouse() const;
85
86 /// Retrieves the underlying touch screen device.
87 [[nodiscard]] TouchScreen* GetTouchScreen();
111 88
112 /// Retrieves the underlying tas device. 89 /// Retrieves the underlying touch screen device.
90 [[nodiscard]] const TouchScreen* GetTouchScreen() const;
91
92 /// Retrieves the underlying tas input device.
113 [[nodiscard]] TasInput::Tas* GetTas(); 93 [[nodiscard]] TasInput::Tas* GetTas();
114 94
115 /// Retrieves the underlying tas device. 95 /// Retrieves the underlying tas input device.
116 [[nodiscard]] const TasInput::Tas* GetTas() const; 96 [[nodiscard]] const TasInput::Tas* GetTas() const;
97
117 /** 98 /**
118 * Returns all available input devices that this Factory can create a new device with. 99 * Returns all available input devices that this Factory can create a new device with.
119 * Each returned ParamPackage should have a `display` field used for display, a class field for 100 * Each returned ParamPackage should have a `display` field used for display, a `engine` field
120 * backends to determine if this backend is meant to service the request and any other 101 * for backends to determine if this backend is meant to service the request and any other
121 * information needed to identify this in the backend later. 102 * information needed to identify this in the backend later.
122 */ 103 */
123 [[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const; 104 [[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const;
@@ -131,83 +112,34 @@ public:
131 /// Retrieves the motion mappings for the given device. 112 /// Retrieves the motion mappings for the given device.
132 [[nodiscard]] MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& device) const; 113 [[nodiscard]] MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& device) const;
133 114
134 /// Retrieves the underlying GameCube analog handler. 115 /// Returns an enum contaning the name to be displayed from the input engine.
135 [[nodiscard]] GCAnalogFactory* GetGCAnalogs(); 116 [[nodiscard]] Common::Input::ButtonNames GetButtonName(
117 const Common::ParamPackage& params) const;
136 118
137 /// Retrieves the underlying GameCube analog handler. 119 /// Returns true if device is a controller.
138 [[nodiscard]] const GCAnalogFactory* GetGCAnalogs() const; 120 [[nodiscard]] bool IsController(const Common::ParamPackage& params) const;
139 121
140 /// Retrieves the underlying GameCube button handler. 122 /// Reloads the input devices.
141 [[nodiscard]] GCButtonFactory* GetGCButtons(); 123 void ReloadInputDevices();
142
143 /// Retrieves the underlying GameCube button handler.
144 [[nodiscard]] const GCButtonFactory* GetGCButtons() const;
145
146 /// Retrieves the underlying udp motion handler.
147 [[nodiscard]] UDPMotionFactory* GetUDPMotions();
148
149 /// Retrieves the underlying udp motion handler.
150 [[nodiscard]] const UDPMotionFactory* GetUDPMotions() const;
151
152 /// Retrieves the underlying udp touch handler.
153 [[nodiscard]] UDPTouchFactory* GetUDPTouch();
154
155 /// Retrieves the underlying udp touch handler.
156 [[nodiscard]] const UDPTouchFactory* GetUDPTouch() const;
157
158 /// Retrieves the underlying mouse button handler.
159 [[nodiscard]] MouseButtonFactory* GetMouseButtons();
160
161 /// Retrieves the underlying mouse button handler.
162 [[nodiscard]] const MouseButtonFactory* GetMouseButtons() const;
163
164 /// Retrieves the underlying mouse analog handler.
165 [[nodiscard]] MouseAnalogFactory* GetMouseAnalogs();
166
167 /// Retrieves the underlying mouse analog handler.
168 [[nodiscard]] const MouseAnalogFactory* GetMouseAnalogs() const;
169
170 /// Retrieves the underlying mouse motion handler.
171 [[nodiscard]] MouseMotionFactory* GetMouseMotions();
172
173 /// Retrieves the underlying mouse motion handler.
174 [[nodiscard]] const MouseMotionFactory* GetMouseMotions() const;
175
176 /// Retrieves the underlying mouse touch handler.
177 [[nodiscard]] MouseTouchFactory* GetMouseTouch();
178
179 /// Retrieves the underlying mouse touch handler.
180 [[nodiscard]] const MouseTouchFactory* GetMouseTouch() const;
181
182 /// Retrieves the underlying tas button handler.
183 [[nodiscard]] TasButtonFactory* GetTasButtons();
184
185 /// Retrieves the underlying tas button handler.
186 [[nodiscard]] const TasButtonFactory* GetTasButtons() const;
187
188 /// Retrieves the underlying tas analogs handler.
189 [[nodiscard]] TasAnalogFactory* GetTasAnalogs();
190 124
191 /// Retrieves the underlying tas analogs handler. 125 /// Start polling from all backends for a desired input type.
192 [[nodiscard]] const TasAnalogFactory* GetTasAnalogs() const; 126 void BeginMapping(Polling::InputType type);
193 127
194 /// Reloads the input devices 128 /// Returns an input event with mapping information.
195 void ReloadInputDevices(); 129 [[nodiscard]] const Common::ParamPackage GetNextInput() const;
196 130
197 /// Get all DevicePoller from all backends for a specific device type 131 /// Stop polling from all backends.
198 [[nodiscard]] std::vector<std::unique_ptr<Polling::DevicePoller>> GetPollers( 132 void StopMapping() const;
199 Polling::DeviceType type) const;
200 133
201private: 134private:
202 struct Impl; 135 struct Impl;
203 std::unique_ptr<Impl> impl; 136 std::unique_ptr<Impl> impl;
204}; 137};
205 138
206/// Generates a serialized param package for creating a keyboard button device 139/// Generates a serialized param package for creating a keyboard button device.
207std::string GenerateKeyboardParam(int key_code); 140std::string GenerateKeyboardParam(int key_code);
208 141
209/// Generates a serialized param package for creating an analog device taking input from keyboard 142/// Generates a serialized param package for creating an analog device taking input from keyboard.
210std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right, 143std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right,
211 int key_modifier, float modifier_scale); 144 int key_modifier, float modifier_scale);
212
213} // namespace InputCommon 145} // 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 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "input_common/motion_from_button.h"
6#include "input_common/motion_input.h"
7
8namespace InputCommon {
9
10class MotionKey final : public Input::MotionDevice {
11public:
12 using Button = std::unique_ptr<Input::ButtonDevice>;
13
14 explicit MotionKey(Button key_) : key(std::move(key_)) {}
15
16 Input::MotionStatus GetStatus() const override {
17
18 if (key->GetStatus()) {
19 return motion.GetRandomMotion(2, 6);
20 }
21 return motion.GetRandomMotion(0, 0);
22 }
23
24private:
25 Button key;
26 InputCommon::MotionInput motion{0.0f, 0.0f, 0.0f};
27};
28
29std::unique_ptr<Input::MotionDevice> MotionFromButton::Create(const Common::ParamPackage& params) {
30 auto key = Input::CreateDevice<Input::ButtonDevice>(params.Serialize());
31 return std::make_unique<MotionKey>(std::move(key));
32}
33
34} // 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 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include "core/frontend/input.h"
8
9namespace InputCommon {
10
11/**
12 * An motion device factory that takes a keyboard button and uses it as a random
13 * motion device.
14 */
15class MotionFromButton final : public Input::Factory<Input::MotionDevice> {
16public:
17 /**
18 * Creates an motion device from button devices
19 * @param params contains parameters for creating the device:
20 * - "key": a serialized ParamPackage for creating a button device
21 */
22 std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override;
23};
24
25} // namespace InputCommon
diff --git a/src/input_common/motion_input.cpp b/src/input_common/motion_input.cpp
deleted file mode 100644
index 1c9d561c0..000000000
--- a/src/input_common/motion_input.cpp
+++ /dev/null
@@ -1,307 +0,0 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included
4
5#include <random>
6#include "common/math_util.h"
7#include "input_common/motion_input.h"
8
9namespace InputCommon {
10
11MotionInput::MotionInput(f32 new_kp, f32 new_ki, f32 new_kd) : kp(new_kp), ki(new_ki), kd(new_kd) {}
12
13void MotionInput::SetAcceleration(const Common::Vec3f& acceleration) {
14 accel = acceleration;
15}
16
17void MotionInput::SetGyroscope(const Common::Vec3f& gyroscope) {
18 gyro = gyroscope - gyro_drift;
19
20 // Auto adjust drift to minimize drift
21 if (!IsMoving(0.1f)) {
22 gyro_drift = (gyro_drift * 0.9999f) + (gyroscope * 0.0001f);
23 }
24
25 if (gyro.Length2() < gyro_threshold) {
26 gyro = {};
27 } else {
28 only_accelerometer = false;
29 }
30}
31
32void MotionInput::SetQuaternion(const Common::Quaternion<f32>& quaternion) {
33 quat = quaternion;
34}
35
36void MotionInput::SetGyroDrift(const Common::Vec3f& drift) {
37 gyro_drift = drift;
38}
39
40void MotionInput::SetGyroThreshold(f32 threshold) {
41 gyro_threshold = threshold;
42}
43
44void MotionInput::EnableReset(bool reset) {
45 reset_enabled = reset;
46}
47
48void MotionInput::ResetRotations() {
49 rotations = {};
50}
51
52bool MotionInput::IsMoving(f32 sensitivity) const {
53 return gyro.Length() >= sensitivity || accel.Length() <= 0.9f || accel.Length() >= 1.1f;
54}
55
56bool MotionInput::IsCalibrated(f32 sensitivity) const {
57 return real_error.Length() < sensitivity;
58}
59
60void MotionInput::UpdateRotation(u64 elapsed_time) {
61 const auto sample_period = static_cast<f32>(elapsed_time) / 1000000.0f;
62 if (sample_period > 0.1f) {
63 return;
64 }
65 rotations += gyro * sample_period;
66}
67
68void MotionInput::UpdateOrientation(u64 elapsed_time) {
69 if (!IsCalibrated(0.1f)) {
70 ResetOrientation();
71 }
72 // Short name local variable for readability
73 f32 q1 = quat.w;
74 f32 q2 = quat.xyz[0];
75 f32 q3 = quat.xyz[1];
76 f32 q4 = quat.xyz[2];
77 const auto sample_period = static_cast<f32>(elapsed_time) / 1000000.0f;
78
79 // Ignore invalid elapsed time
80 if (sample_period > 0.1f) {
81 return;
82 }
83
84 const auto normal_accel = accel.Normalized();
85 auto rad_gyro = gyro * Common::PI * 2;
86 const f32 swap = rad_gyro.x;
87 rad_gyro.x = rad_gyro.y;
88 rad_gyro.y = -swap;
89 rad_gyro.z = -rad_gyro.z;
90
91 // Clear gyro values if there is no gyro present
92 if (only_accelerometer) {
93 rad_gyro.x = 0;
94 rad_gyro.y = 0;
95 rad_gyro.z = 0;
96 }
97
98 // Ignore drift correction if acceleration is not reliable
99 if (accel.Length() >= 0.75f && accel.Length() <= 1.25f) {
100 const f32 ax = -normal_accel.x;
101 const f32 ay = normal_accel.y;
102 const f32 az = -normal_accel.z;
103
104 // Estimated direction of gravity
105 const f32 vx = 2.0f * (q2 * q4 - q1 * q3);
106 const f32 vy = 2.0f * (q1 * q2 + q3 * q4);
107 const f32 vz = q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4;
108
109 // Error is cross product between estimated direction and measured direction of gravity
110 const Common::Vec3f new_real_error = {
111 az * vx - ax * vz,
112 ay * vz - az * vy,
113 ax * vy - ay * vx,
114 };
115
116 derivative_error = new_real_error - real_error;
117 real_error = new_real_error;
118
119 // Prevent integral windup
120 if (ki != 0.0f && !IsCalibrated(0.05f)) {
121 integral_error += real_error;
122 } else {
123 integral_error = {};
124 }
125
126 // Apply feedback terms
127 if (!only_accelerometer) {
128 rad_gyro += kp * real_error;
129 rad_gyro += ki * integral_error;
130 rad_gyro += kd * derivative_error;
131 } else {
132 // Give more weight to accelerometer values to compensate for the lack of gyro
133 rad_gyro += 35.0f * kp * real_error;
134 rad_gyro += 10.0f * ki * integral_error;
135 rad_gyro += 10.0f * kd * derivative_error;
136
137 // Emulate gyro values for games that need them
138 gyro.x = -rad_gyro.y;
139 gyro.y = rad_gyro.x;
140 gyro.z = -rad_gyro.z;
141 UpdateRotation(elapsed_time);
142 }
143 }
144
145 const f32 gx = rad_gyro.y;
146 const f32 gy = rad_gyro.x;
147 const f32 gz = rad_gyro.z;
148
149 // Integrate rate of change of quaternion
150 const f32 pa = q2;
151 const f32 pb = q3;
152 const f32 pc = q4;
153 q1 = q1 + (-q2 * gx - q3 * gy - q4 * gz) * (0.5f * sample_period);
154 q2 = pa + (q1 * gx + pb * gz - pc * gy) * (0.5f * sample_period);
155 q3 = pb + (q1 * gy - pa * gz + pc * gx) * (0.5f * sample_period);
156 q4 = pc + (q1 * gz + pa * gy - pb * gx) * (0.5f * sample_period);
157
158 quat.w = q1;
159 quat.xyz[0] = q2;
160 quat.xyz[1] = q3;
161 quat.xyz[2] = q4;
162 quat = quat.Normalized();
163}
164
165std::array<Common::Vec3f, 3> MotionInput::GetOrientation() const {
166 const Common::Quaternion<float> quad{
167 .xyz = {-quat.xyz[1], -quat.xyz[0], -quat.w},
168 .w = -quat.xyz[2],
169 };
170 const std::array<float, 16> matrix4x4 = quad.ToMatrix();
171
172 return {Common::Vec3f(matrix4x4[0], matrix4x4[1], -matrix4x4[2]),
173 Common::Vec3f(matrix4x4[4], matrix4x4[5], -matrix4x4[6]),
174 Common::Vec3f(-matrix4x4[8], -matrix4x4[9], matrix4x4[10])};
175}
176
177Common::Vec3f MotionInput::GetAcceleration() const {
178 return accel;
179}
180
181Common::Vec3f MotionInput::GetGyroscope() const {
182 return gyro;
183}
184
185Common::Quaternion<f32> MotionInput::GetQuaternion() const {
186 return quat;
187}
188
189Common::Vec3f MotionInput::GetRotations() const {
190 return rotations;
191}
192
193Input::MotionStatus MotionInput::GetMotion() const {
194 const Common::Vec3f gyroscope = GetGyroscope();
195 const Common::Vec3f accelerometer = GetAcceleration();
196 const Common::Vec3f rotation = GetRotations();
197 const std::array<Common::Vec3f, 3> orientation = GetOrientation();
198 const Common::Quaternion<f32> quaternion = GetQuaternion();
199 return {accelerometer, gyroscope, rotation, orientation, quaternion};
200}
201
202Input::MotionStatus MotionInput::GetRandomMotion(int accel_magnitude, int gyro_magnitude) const {
203 std::random_device device;
204 std::mt19937 gen(device());
205 std::uniform_int_distribution<s16> distribution(-1000, 1000);
206 const Common::Vec3f gyroscope{
207 static_cast<f32>(distribution(gen)) * 0.001f,
208 static_cast<f32>(distribution(gen)) * 0.001f,
209 static_cast<f32>(distribution(gen)) * 0.001f,
210 };
211 const Common::Vec3f accelerometer{
212 static_cast<f32>(distribution(gen)) * 0.001f,
213 static_cast<f32>(distribution(gen)) * 0.001f,
214 static_cast<f32>(distribution(gen)) * 0.001f,
215 };
216 constexpr Common::Vec3f rotation;
217 constexpr std::array orientation{
218 Common::Vec3f{1.0f, 0.0f, 0.0f},
219 Common::Vec3f{0.0f, 1.0f, 0.0f},
220 Common::Vec3f{0.0f, 0.0f, 1.0f},
221 };
222 constexpr Common::Quaternion<f32> quaternion{
223 {0.0f, 0.0f, 0.0f},
224 1.0f,
225 };
226 return {accelerometer * accel_magnitude, gyroscope * gyro_magnitude, rotation, orientation,
227 quaternion};
228}
229
230void MotionInput::ResetOrientation() {
231 if (!reset_enabled || only_accelerometer) {
232 return;
233 }
234 if (!IsMoving(0.5f) && accel.z <= -0.9f) {
235 ++reset_counter;
236 if (reset_counter > 900) {
237 quat.w = 0;
238 quat.xyz[0] = 0;
239 quat.xyz[1] = 0;
240 quat.xyz[2] = -1;
241 SetOrientationFromAccelerometer();
242 integral_error = {};
243 reset_counter = 0;
244 }
245 } else {
246 reset_counter = 0;
247 }
248}
249
250void MotionInput::SetOrientationFromAccelerometer() {
251 int iterations = 0;
252 const f32 sample_period = 0.015f;
253
254 const auto normal_accel = accel.Normalized();
255
256 while (!IsCalibrated(0.01f) && ++iterations < 100) {
257 // Short name local variable for readability
258 f32 q1 = quat.w;
259 f32 q2 = quat.xyz[0];
260 f32 q3 = quat.xyz[1];
261 f32 q4 = quat.xyz[2];
262
263 Common::Vec3f rad_gyro;
264 const f32 ax = -normal_accel.x;
265 const f32 ay = normal_accel.y;
266 const f32 az = -normal_accel.z;
267
268 // Estimated direction of gravity
269 const f32 vx = 2.0f * (q2 * q4 - q1 * q3);
270 const f32 vy = 2.0f * (q1 * q2 + q3 * q4);
271 const f32 vz = q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4;
272
273 // Error is cross product between estimated direction and measured direction of gravity
274 const Common::Vec3f new_real_error = {
275 az * vx - ax * vz,
276 ay * vz - az * vy,
277 ax * vy - ay * vx,
278 };
279
280 derivative_error = new_real_error - real_error;
281 real_error = new_real_error;
282
283 rad_gyro += 10.0f * kp * real_error;
284 rad_gyro += 5.0f * ki * integral_error;
285 rad_gyro += 10.0f * kd * derivative_error;
286
287 const f32 gx = rad_gyro.y;
288 const f32 gy = rad_gyro.x;
289 const f32 gz = rad_gyro.z;
290
291 // Integrate rate of change of quaternion
292 const f32 pa = q2;
293 const f32 pb = q3;
294 const f32 pc = q4;
295 q1 = q1 + (-q2 * gx - q3 * gy - q4 * gz) * (0.5f * sample_period);
296 q2 = pa + (q1 * gx + pb * gz - pc * gy) * (0.5f * sample_period);
297 q3 = pb + (q1 * gy - pa * gz + pc * gx) * (0.5f * sample_period);
298 q4 = pc + (q1 * gz + pa * gy - pb * gx) * (0.5f * sample_period);
299
300 quat.w = q1;
301 quat.xyz[0] = q2;
302 quat.xyz[1] = q3;
303 quat.xyz[2] = q4;
304 quat = quat.Normalized();
305 }
306}
307} // namespace InputCommon
diff --git a/src/input_common/motion_input.h b/src/input_common/motion_input.h
deleted file mode 100644
index efe74cf19..000000000
--- a/src/input_common/motion_input.h
+++ /dev/null
@@ -1,74 +0,0 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included
4
5#pragma once
6
7#include "common/common_types.h"
8#include "common/quaternion.h"
9#include "common/vector_math.h"
10#include "core/frontend/input.h"
11
12namespace InputCommon {
13
14class MotionInput {
15public:
16 explicit MotionInput(f32 new_kp, f32 new_ki, f32 new_kd);
17
18 MotionInput(const MotionInput&) = default;
19 MotionInput& operator=(const MotionInput&) = default;
20
21 MotionInput(MotionInput&&) = default;
22 MotionInput& operator=(MotionInput&&) = default;
23
24 void SetAcceleration(const Common::Vec3f& acceleration);
25 void SetGyroscope(const Common::Vec3f& gyroscope);
26 void SetQuaternion(const Common::Quaternion<f32>& quaternion);
27 void SetGyroDrift(const Common::Vec3f& drift);
28 void SetGyroThreshold(f32 threshold);
29
30 void EnableReset(bool reset);
31 void ResetRotations();
32
33 void UpdateRotation(u64 elapsed_time);
34 void UpdateOrientation(u64 elapsed_time);
35
36 [[nodiscard]] std::array<Common::Vec3f, 3> GetOrientation() const;
37 [[nodiscard]] Common::Vec3f GetAcceleration() const;
38 [[nodiscard]] Common::Vec3f GetGyroscope() const;
39 [[nodiscard]] Common::Vec3f GetRotations() const;
40 [[nodiscard]] Common::Quaternion<f32> GetQuaternion() const;
41 [[nodiscard]] Input::MotionStatus GetMotion() const;
42 [[nodiscard]] Input::MotionStatus GetRandomMotion(int accel_magnitude,
43 int gyro_magnitude) const;
44
45 [[nodiscard]] bool IsMoving(f32 sensitivity) const;
46 [[nodiscard]] bool IsCalibrated(f32 sensitivity) const;
47
48private:
49 void ResetOrientation();
50 void SetOrientationFromAccelerometer();
51
52 // PID constants
53 f32 kp;
54 f32 ki;
55 f32 kd;
56
57 // PID errors
58 Common::Vec3f real_error;
59 Common::Vec3f integral_error;
60 Common::Vec3f derivative_error;
61
62 Common::Quaternion<f32> quat{{0.0f, 0.0f, -1.0f}, 0.0f};
63 Common::Vec3f rotations;
64 Common::Vec3f accel;
65 Common::Vec3f gyro;
66 Common::Vec3f gyro_drift;
67
68 f32 gyro_threshold = 0.0f;
69 u32 reset_counter = 0;
70 bool reset_enabled = true;
71 bool only_accelerometer = true;
72};
73
74} // 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 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2+
3// Refer to the license.txt file included.
4
5#include <stop_token>
6#include <thread>
7
8#include "common/settings.h"
9#include "input_common/mouse/mouse_input.h"
10
11namespace MouseInput {
12
13Mouse::Mouse() {
14 update_thread = std::jthread([this](std::stop_token stop_token) { UpdateThread(stop_token); });
15}
16
17Mouse::~Mouse() = default;
18
19void Mouse::UpdateThread(std::stop_token stop_token) {
20 constexpr int update_time = 10;
21 while (!stop_token.stop_requested()) {
22 for (MouseInfo& info : mouse_info) {
23 const Common::Vec3f angular_direction{
24 -info.tilt_direction.y,
25 0.0f,
26 -info.tilt_direction.x,
27 };
28
29 info.motion.SetGyroscope(angular_direction * info.tilt_speed);
30 info.motion.UpdateRotation(update_time * 1000);
31 info.motion.UpdateOrientation(update_time * 1000);
32 info.tilt_speed = 0;
33 info.data.motion = info.motion.GetMotion();
34 if (Settings::values.mouse_panning) {
35 info.last_mouse_change *= 0.96f;
36 info.data.axis = {static_cast<int>(16 * info.last_mouse_change.x),
37 static_cast<int>(16 * -info.last_mouse_change.y)};
38 }
39 }
40 if (configuring) {
41 UpdateYuzuSettings();
42 }
43 if (mouse_panning_timout++ > 20) {
44 StopPanning();
45 }
46 std::this_thread::sleep_for(std::chrono::milliseconds(update_time));
47 }
48}
49
50void Mouse::UpdateYuzuSettings() {
51 if (buttons == 0) {
52 return;
53 }
54
55 mouse_queue.Push(MouseStatus{
56 .button = last_button,
57 });
58}
59
60void Mouse::PressButton(int x, int y, MouseButton button_) {
61 const auto button_index = static_cast<std::size_t>(button_);
62 if (button_index >= mouse_info.size()) {
63 return;
64 }
65
66 const auto button = 1U << button_index;
67 buttons |= static_cast<u16>(button);
68 last_button = button_;
69
70 mouse_info[button_index].mouse_origin = Common::MakeVec(x, y);
71 mouse_info[button_index].last_mouse_position = Common::MakeVec(x, y);
72 mouse_info[button_index].data.pressed = true;
73}
74
75void Mouse::StopPanning() {
76 for (MouseInfo& info : mouse_info) {
77 if (Settings::values.mouse_panning) {
78 info.data.axis = {};
79 info.tilt_speed = 0;
80 info.last_mouse_change = {};
81 }
82 }
83}
84
85void Mouse::MouseMove(int x, int y, int center_x, int center_y) {
86 for (MouseInfo& info : mouse_info) {
87 if (Settings::values.mouse_panning) {
88 auto mouse_change =
89 (Common::MakeVec(x, y) - Common::MakeVec(center_x, center_y)).Cast<float>();
90 mouse_panning_timout = 0;
91
92 if (mouse_change.y == 0 && mouse_change.x == 0) {
93 continue;
94 }
95 const auto mouse_change_length = mouse_change.Length();
96 if (mouse_change_length < 3.0f) {
97 mouse_change /= mouse_change_length / 3.0f;
98 }
99
100 info.last_mouse_change = (info.last_mouse_change * 0.91f) + (mouse_change * 0.09f);
101
102 const auto last_mouse_change_length = info.last_mouse_change.Length();
103 if (last_mouse_change_length > 8.0f) {
104 info.last_mouse_change /= last_mouse_change_length / 8.0f;
105 } else if (last_mouse_change_length < 1.0f) {
106 info.last_mouse_change = mouse_change / mouse_change.Length();
107 }
108
109 info.tilt_direction = info.last_mouse_change;
110 info.tilt_speed = info.tilt_direction.Normalize() * info.sensitivity;
111 continue;
112 }
113
114 if (info.data.pressed) {
115 const auto mouse_move = Common::MakeVec(x, y) - info.mouse_origin;
116 const auto mouse_change = Common::MakeVec(x, y) - info.last_mouse_position;
117 info.last_mouse_position = Common::MakeVec(x, y);
118 info.data.axis = {mouse_move.x, -mouse_move.y};
119
120 if (mouse_change.x == 0 && mouse_change.y == 0) {
121 info.tilt_speed = 0;
122 } else {
123 info.tilt_direction = mouse_change.Cast<float>();
124 info.tilt_speed = info.tilt_direction.Normalize() * info.sensitivity;
125 }
126 }
127 }
128}
129
130void Mouse::ReleaseButton(MouseButton button_) {
131 const auto button_index = static_cast<std::size_t>(button_);
132 if (button_index >= mouse_info.size()) {
133 return;
134 }
135
136 const auto button = 1U << button_index;
137 buttons &= static_cast<u16>(0xFF - button);
138
139 mouse_info[button_index].tilt_speed = 0;
140 mouse_info[button_index].data.pressed = false;
141 mouse_info[button_index].data.axis = {0, 0};
142}
143
144void Mouse::ReleaseAllButtons() {
145 buttons = 0;
146 for (auto& info : mouse_info) {
147 info.tilt_speed = 0;
148 info.data.pressed = false;
149 info.data.axis = {0, 0};
150 }
151}
152
153void Mouse::BeginConfiguration() {
154 buttons = 0;
155 last_button = MouseButton::Undefined;
156 mouse_queue.Clear();
157 configuring = true;
158}
159
160void Mouse::EndConfiguration() {
161 buttons = 0;
162 for (MouseInfo& info : mouse_info) {
163 info.tilt_speed = 0;
164 info.data.pressed = false;
165 info.data.axis = {0, 0};
166 }
167 last_button = MouseButton::Undefined;
168 mouse_queue.Clear();
169 configuring = false;
170}
171
172bool Mouse::ToggleButton(std::size_t button_) {
173 if (button_ >= mouse_info.size()) {
174 return false;
175 }
176 const auto button = 1U << button_;
177 const bool button_state = (toggle_buttons & button) != 0;
178 const bool button_lock = (lock_buttons & button) != 0;
179
180 if (button_lock) {
181 return button_state;
182 }
183
184 lock_buttons |= static_cast<u16>(button);
185
186 if (button_state) {
187 toggle_buttons &= static_cast<u16>(0xFF - button);
188 } else {
189 toggle_buttons |= static_cast<u16>(button);
190 }
191
192 return !button_state;
193}
194
195bool Mouse::UnlockButton(std::size_t button_) {
196 if (button_ >= mouse_info.size()) {
197 return false;
198 }
199
200 const auto button = 1U << button_;
201 const bool button_state = (toggle_buttons & button) != 0;
202
203 lock_buttons &= static_cast<u16>(0xFF - button);
204
205 return button_state;
206}
207
208Common::SPSCQueue<MouseStatus>& Mouse::GetMouseQueue() {
209 return mouse_queue;
210}
211
212const Common::SPSCQueue<MouseStatus>& Mouse::GetMouseQueue() const {
213 return mouse_queue;
214}
215
216MouseData& Mouse::GetMouseState(std::size_t button) {
217 return mouse_info[button].data;
218}
219
220const MouseData& Mouse::GetMouseState(std::size_t button) const {
221 return mouse_info[button].data;
222}
223} // 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 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8#include <mutex>
9#include <stop_token>
10#include <thread>
11
12#include "common/common_types.h"
13#include "common/threadsafe_queue.h"
14#include "common/vector_math.h"
15#include "core/frontend/input.h"
16#include "input_common/motion_input.h"
17
18namespace MouseInput {
19
20enum class MouseButton {
21 Left,
22 Right,
23 Wheel,
24 Backward,
25 Forward,
26 Task,
27 Extra,
28 Undefined,
29};
30
31struct MouseStatus {
32 MouseButton button{MouseButton::Undefined};
33};
34
35struct MouseData {
36 bool pressed{};
37 std::array<int, 2> axis{};
38 Input::MotionStatus motion{};
39 Input::TouchStatus touch{};
40};
41
42class Mouse {
43public:
44 Mouse();
45 ~Mouse();
46
47 /// Used for polling
48 void BeginConfiguration();
49 void EndConfiguration();
50
51 /**
52 * Signals that a button is pressed.
53 * @param x the x-coordinate of the cursor
54 * @param y the y-coordinate of the cursor
55 * @param button_ the button pressed
56 */
57 void PressButton(int x, int y, MouseButton button_);
58
59 /**
60 * Signals that mouse has moved.
61 * @param x the x-coordinate of the cursor
62 * @param y the y-coordinate of the cursor
63 * @param center_x the x-coordinate of the middle of the screen
64 * @param center_y the y-coordinate of the middle of the screen
65 */
66 void MouseMove(int x, int y, int center_x, int center_y);
67
68 /**
69 * Signals that a button is released.
70 * @param button_ the button pressed
71 */
72 void ReleaseButton(MouseButton button_);
73
74 /**
75 * Signals that all buttons are released
76 */
77 void ReleaseAllButtons();
78
79 [[nodiscard]] bool ToggleButton(std::size_t button_);
80 [[nodiscard]] bool UnlockButton(std::size_t button_);
81
82 [[nodiscard]] Common::SPSCQueue<MouseStatus>& GetMouseQueue();
83 [[nodiscard]] const Common::SPSCQueue<MouseStatus>& GetMouseQueue() const;
84
85 [[nodiscard]] MouseData& GetMouseState(std::size_t button);
86 [[nodiscard]] const MouseData& GetMouseState(std::size_t button) const;
87
88private:
89 void UpdateThread(std::stop_token stop_token);
90 void UpdateYuzuSettings();
91 void StopPanning();
92
93 struct MouseInfo {
94 InputCommon::MotionInput motion{0.0f, 0.0f, 0.0f};
95 Common::Vec2<int> mouse_origin;
96 Common::Vec2<int> last_mouse_position;
97 Common::Vec2<float> last_mouse_change;
98 bool is_tilting = false;
99 float sensitivity{0.120f};
100
101 float tilt_speed = 0;
102 Common::Vec2<float> tilt_direction;
103 MouseData data;
104 };
105
106 u16 buttons{};
107 u16 toggle_buttons{};
108 u16 lock_buttons{};
109 std::jthread update_thread;
110 MouseButton last_button{MouseButton::Undefined};
111 std::array<MouseInfo, 7> mouse_info;
112 Common::SPSCQueue<MouseStatus> mouse_queue;
113 bool configuring{false};
114 int mouse_panning_timout{};
115};
116} // 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 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <algorithm>
6#include <memory>
7#include <mutex>
8#include <utility>
9
10#include "common/settings.h"
11#include "common/threadsafe_queue.h"
12#include "input_common/mouse/mouse_input.h"
13#include "input_common/mouse/mouse_poller.h"
14
15namespace InputCommon {
16
17class MouseButton final : public Input::ButtonDevice {
18public:
19 explicit MouseButton(u32 button_, bool toggle_, MouseInput::Mouse* mouse_input_)
20 : button(button_), toggle(toggle_), mouse_input(mouse_input_) {}
21
22 bool GetStatus() const override {
23 const bool button_state = mouse_input->GetMouseState(button).pressed;
24 if (!toggle) {
25 return button_state;
26 }
27
28 if (button_state) {
29 return mouse_input->ToggleButton(button);
30 }
31 return mouse_input->UnlockButton(button);
32 }
33
34private:
35 const u32 button;
36 const bool toggle;
37 MouseInput::Mouse* mouse_input;
38};
39
40MouseButtonFactory::MouseButtonFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_)
41 : mouse_input(std::move(mouse_input_)) {}
42
43std::unique_ptr<Input::ButtonDevice> MouseButtonFactory::Create(
44 const Common::ParamPackage& params) {
45 const auto button_id = params.Get("button", 0);
46 const auto toggle = params.Get("toggle", false);
47
48 return std::make_unique<MouseButton>(button_id, toggle, mouse_input.get());
49}
50
51Common::ParamPackage MouseButtonFactory::GetNextInput() const {
52 MouseInput::MouseStatus pad;
53 Common::ParamPackage params;
54 auto& queue = mouse_input->GetMouseQueue();
55 while (queue.Pop(pad)) {
56 // This while loop will break on the earliest detected button
57 if (pad.button != MouseInput::MouseButton::Undefined) {
58 params.Set("engine", "mouse");
59 params.Set("button", static_cast<u16>(pad.button));
60 params.Set("toggle", false);
61 return params;
62 }
63 }
64 return params;
65}
66
67void MouseButtonFactory::BeginConfiguration() {
68 polling = true;
69 mouse_input->BeginConfiguration();
70}
71
72void MouseButtonFactory::EndConfiguration() {
73 polling = false;
74 mouse_input->EndConfiguration();
75}
76
77class MouseAnalog final : public Input::AnalogDevice {
78public:
79 explicit MouseAnalog(u32 port_, u32 axis_x_, u32 axis_y_, bool invert_x_, bool invert_y_,
80 float deadzone_, float range_, const MouseInput::Mouse* mouse_input_)
81 : button(port_), axis_x(axis_x_), axis_y(axis_y_), invert_x(invert_x_), invert_y(invert_y_),
82 deadzone(deadzone_), range(range_), mouse_input(mouse_input_) {}
83
84 float GetAxis(u32 axis) const {
85 std::lock_guard lock{mutex};
86 const auto axis_value =
87 static_cast<float>(mouse_input->GetMouseState(button).axis.at(axis));
88 const float sensitivity = Settings::values.mouse_panning_sensitivity.GetValue() * 0.10f;
89 return axis_value * sensitivity / (100.0f * range);
90 }
91
92 std::pair<float, float> GetAnalog(u32 analog_axis_x, u32 analog_axis_y) const {
93 float x = GetAxis(analog_axis_x);
94 float y = GetAxis(analog_axis_y);
95 if (invert_x) {
96 x = -x;
97 }
98 if (invert_y) {
99 y = -y;
100 }
101
102 // Make sure the coordinates are in the unit circle,
103 // otherwise normalize it.
104 float r = x * x + y * y;
105 if (r > 1.0f) {
106 r = std::sqrt(r);
107 x /= r;
108 y /= r;
109 }
110
111 return {x, y};
112 }
113
114 std::tuple<float, float> GetStatus() const override {
115 const auto [x, y] = GetAnalog(axis_x, axis_y);
116 const float r = std::sqrt((x * x) + (y * y));
117 if (r > deadzone) {
118 return {x / r * (r - deadzone) / (1 - deadzone),
119 y / r * (r - deadzone) / (1 - deadzone)};
120 }
121 return {0.0f, 0.0f};
122 }
123
124 std::tuple<float, float> GetRawStatus() const override {
125 const float x = GetAxis(axis_x);
126 const float y = GetAxis(axis_y);
127 return {x, y};
128 }
129
130 Input::AnalogProperties GetAnalogProperties() const override {
131 return {deadzone, range, 0.5f};
132 }
133
134private:
135 const u32 button;
136 const u32 axis_x;
137 const u32 axis_y;
138 const bool invert_x;
139 const bool invert_y;
140 const float deadzone;
141 const float range;
142 const MouseInput::Mouse* mouse_input;
143 mutable std::mutex mutex;
144};
145
146/// An analog device factory that creates analog devices from GC Adapter
147MouseAnalogFactory::MouseAnalogFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_)
148 : mouse_input(std::move(mouse_input_)) {}
149
150/**
151 * Creates analog device from joystick axes
152 * @param params contains parameters for creating the device:
153 * - "port": the nth gcpad on the adapter
154 * - "axis_x": the index of the axis to be bind as x-axis
155 * - "axis_y": the index of the axis to be bind as y-axis
156 */
157std::unique_ptr<Input::AnalogDevice> MouseAnalogFactory::Create(
158 const Common::ParamPackage& params) {
159 const auto port = static_cast<u32>(params.Get("port", 0));
160 const auto axis_x = static_cast<u32>(params.Get("axis_x", 0));
161 const auto axis_y = static_cast<u32>(params.Get("axis_y", 1));
162 const auto deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f);
163 const auto range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f);
164 const std::string invert_x_value = params.Get("invert_x", "+");
165 const std::string invert_y_value = params.Get("invert_y", "+");
166 const bool invert_x = invert_x_value == "-";
167 const bool invert_y = invert_y_value == "-";
168
169 return std::make_unique<MouseAnalog>(port, axis_x, axis_y, invert_x, invert_y, deadzone, range,
170 mouse_input.get());
171}
172
173void MouseAnalogFactory::BeginConfiguration() {
174 polling = true;
175 mouse_input->BeginConfiguration();
176}
177
178void MouseAnalogFactory::EndConfiguration() {
179 polling = false;
180 mouse_input->EndConfiguration();
181}
182
183Common::ParamPackage MouseAnalogFactory::GetNextInput() const {
184 MouseInput::MouseStatus pad;
185 Common::ParamPackage params;
186 auto& queue = mouse_input->GetMouseQueue();
187 while (queue.Pop(pad)) {
188 // This while loop will break on the earliest detected button
189 if (pad.button != MouseInput::MouseButton::Undefined) {
190 params.Set("engine", "mouse");
191 params.Set("port", static_cast<u16>(pad.button));
192 params.Set("axis_x", 0);
193 params.Set("axis_y", 1);
194 params.Set("invert_x", "+");
195 params.Set("invert_y", "+");
196 return params;
197 }
198 }
199 return params;
200}
201
202class MouseMotion final : public Input::MotionDevice {
203public:
204 explicit MouseMotion(u32 button_, const MouseInput::Mouse* mouse_input_)
205 : button(button_), mouse_input(mouse_input_) {}
206
207 Input::MotionStatus GetStatus() const override {
208 return mouse_input->GetMouseState(button).motion;
209 }
210
211private:
212 const u32 button;
213 const MouseInput::Mouse* mouse_input;
214};
215
216MouseMotionFactory::MouseMotionFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_)
217 : mouse_input(std::move(mouse_input_)) {}
218
219std::unique_ptr<Input::MotionDevice> MouseMotionFactory::Create(
220 const Common::ParamPackage& params) {
221 const auto button_id = params.Get("button", 0);
222
223 return std::make_unique<MouseMotion>(button_id, mouse_input.get());
224}
225
226Common::ParamPackage MouseMotionFactory::GetNextInput() const {
227 MouseInput::MouseStatus pad;
228 Common::ParamPackage params;
229 auto& queue = mouse_input->GetMouseQueue();
230 while (queue.Pop(pad)) {
231 // This while loop will break on the earliest detected button
232 if (pad.button != MouseInput::MouseButton::Undefined) {
233 params.Set("engine", "mouse");
234 params.Set("button", static_cast<u16>(pad.button));
235 return params;
236 }
237 }
238 return params;
239}
240
241void MouseMotionFactory::BeginConfiguration() {
242 polling = true;
243 mouse_input->BeginConfiguration();
244}
245
246void MouseMotionFactory::EndConfiguration() {
247 polling = false;
248 mouse_input->EndConfiguration();
249}
250
251class MouseTouch final : public Input::TouchDevice {
252public:
253 explicit MouseTouch(u32 button_, const MouseInput::Mouse* mouse_input_)
254 : button(button_), mouse_input(mouse_input_) {}
255
256 Input::TouchStatus GetStatus() const override {
257 return mouse_input->GetMouseState(button).touch;
258 }
259
260private:
261 const u32 button;
262 const MouseInput::Mouse* mouse_input;
263};
264
265MouseTouchFactory::MouseTouchFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_)
266 : mouse_input(std::move(mouse_input_)) {}
267
268std::unique_ptr<Input::TouchDevice> MouseTouchFactory::Create(const Common::ParamPackage& params) {
269 const auto button_id = params.Get("button", 0);
270
271 return std::make_unique<MouseTouch>(button_id, mouse_input.get());
272}
273
274Common::ParamPackage MouseTouchFactory::GetNextInput() const {
275 MouseInput::MouseStatus pad;
276 Common::ParamPackage params;
277 auto& queue = mouse_input->GetMouseQueue();
278 while (queue.Pop(pad)) {
279 // This while loop will break on the earliest detected button
280 if (pad.button != MouseInput::MouseButton::Undefined) {
281 params.Set("engine", "mouse");
282 params.Set("button", static_cast<u16>(pad.button));
283 return params;
284 }
285 }
286 return params;
287}
288
289void MouseTouchFactory::BeginConfiguration() {
290 polling = true;
291 mouse_input->BeginConfiguration();
292}
293
294void MouseTouchFactory::EndConfiguration() {
295 polling = false;
296 mouse_input->EndConfiguration();
297}
298
299} // 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 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include "core/frontend/input.h"
9#include "input_common/mouse/mouse_input.h"
10
11namespace InputCommon {
12
13/**
14 * A button device factory representing a mouse. It receives mouse events and forward them
15 * to all button devices it created.
16 */
17class MouseButtonFactory final : public Input::Factory<Input::ButtonDevice> {
18public:
19 explicit MouseButtonFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_);
20
21 /**
22 * Creates a button device from a button press
23 * @param params contains parameters for creating the device:
24 * - "code": the code of the key to bind with the button
25 */
26 std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override;
27
28 Common::ParamPackage GetNextInput() const;
29
30 /// For device input configuration/polling
31 void BeginConfiguration();
32 void EndConfiguration();
33
34 bool IsPolling() const {
35 return polling;
36 }
37
38private:
39 std::shared_ptr<MouseInput::Mouse> mouse_input;
40 bool polling = false;
41};
42
43/// An analog device factory that creates analog devices from mouse
44class MouseAnalogFactory final : public Input::Factory<Input::AnalogDevice> {
45public:
46 explicit MouseAnalogFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_);
47
48 std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override;
49
50 Common::ParamPackage GetNextInput() const;
51
52 /// For device input configuration/polling
53 void BeginConfiguration();
54 void EndConfiguration();
55
56 bool IsPolling() const {
57 return polling;
58 }
59
60private:
61 std::shared_ptr<MouseInput::Mouse> mouse_input;
62 bool polling = false;
63};
64
65/// A motion device factory that creates motion devices from mouse
66class MouseMotionFactory final : public Input::Factory<Input::MotionDevice> {
67public:
68 explicit MouseMotionFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_);
69
70 std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override;
71
72 Common::ParamPackage GetNextInput() const;
73
74 /// For device input configuration/polling
75 void BeginConfiguration();
76 void EndConfiguration();
77
78 bool IsPolling() const {
79 return polling;
80 }
81
82private:
83 std::shared_ptr<MouseInput::Mouse> mouse_input;
84 bool polling = false;
85};
86
87/// An touch device factory that creates touch devices from mouse
88class MouseTouchFactory final : public Input::Factory<Input::TouchDevice> {
89public:
90 explicit MouseTouchFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_);
91
92 std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override;
93
94 Common::ParamPackage GetNextInput() const;
95
96 /// For device input configuration/polling
97 void BeginConfiguration();
98 void EndConfiguration();
99
100 bool IsPolling() const {
101 return polling;
102 }
103
104private:
105 std::shared_ptr<MouseInput::Mouse> mouse_input;
106 bool polling = false;
107};
108
109} // 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 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "input_common/sdl/sdl.h"
6#ifdef HAVE_SDL2
7#include "input_common/sdl/sdl_impl.h"
8#endif
9
10namespace InputCommon::SDL {
11
12std::unique_ptr<State> Init() {
13#ifdef HAVE_SDL2
14 return std::make_unique<SDLState>();
15#else
16 return std::make_unique<NullState>();
17#endif
18}
19} // 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 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include <vector>
9#include "common/param_package.h"
10#include "input_common/main.h"
11
12namespace InputCommon::Polling {
13class DevicePoller;
14enum class DeviceType;
15} // namespace InputCommon::Polling
16
17namespace InputCommon::SDL {
18
19class State {
20public:
21 using Pollers = std::vector<std::unique_ptr<Polling::DevicePoller>>;
22
23 /// Unregisters SDL device factories and shut them down.
24 virtual ~State() = default;
25
26 virtual Pollers GetPollers(Polling::DeviceType) {
27 return {};
28 }
29
30 virtual std::vector<Common::ParamPackage> GetInputDevices() {
31 return {};
32 }
33
34 virtual ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage&) {
35 return {};
36 }
37 virtual AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage&) {
38 return {};
39 }
40 virtual MotionMapping GetMotionMappingForDevice(const Common::ParamPackage&) {
41 return {};
42 }
43};
44
45class NullState : public State {
46public:
47};
48
49std::unique_ptr<State> Init();
50
51} // 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 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <algorithm>
6#include <array>
7#include <atomic>
8#include <chrono>
9#include <cmath>
10#include <functional>
11#include <mutex>
12#include <optional>
13#include <sstream>
14#include <string>
15#include <thread>
16#include <tuple>
17#include <unordered_map>
18#include <utility>
19#include <vector>
20
21#include "common/logging/log.h"
22#include "common/math_util.h"
23#include "common/param_package.h"
24#include "common/settings.h"
25#include "common/threadsafe_queue.h"
26#include "core/frontend/input.h"
27#include "input_common/motion_input.h"
28#include "input_common/sdl/sdl_impl.h"
29
30namespace InputCommon::SDL {
31
32namespace {
33std::string GetGUID(SDL_Joystick* joystick) {
34 const SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick);
35 char guid_str[33];
36 SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str));
37 return guid_str;
38}
39
40/// Creates a ParamPackage from an SDL_Event that can directly be used to create a ButtonDevice
41Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event);
42} // Anonymous namespace
43
44static int SDLEventWatcher(void* user_data, SDL_Event* event) {
45 auto* const sdl_state = static_cast<SDLState*>(user_data);
46
47 // Don't handle the event if we are configuring
48 if (sdl_state->polling) {
49 sdl_state->event_queue.Push(*event);
50 } else {
51 sdl_state->HandleGameControllerEvent(*event);
52 }
53
54 return 0;
55}
56
57class SDLJoystick {
58public:
59 SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick,
60 SDL_GameController* game_controller)
61 : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose},
62 sdl_controller{game_controller, &SDL_GameControllerClose} {
63 EnableMotion();
64 }
65
66 void EnableMotion() {
67 if (sdl_controller) {
68 SDL_GameController* controller = sdl_controller.get();
69 if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL) && !has_accel) {
70 SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE);
71 has_accel = true;
72 }
73 if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) && !has_gyro) {
74 SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_TRUE);
75 has_gyro = true;
76 }
77 }
78 }
79
80 void SetButton(int button, bool value) {
81 std::lock_guard lock{mutex};
82 state.buttons.insert_or_assign(button, value);
83 }
84
85 void PreSetButton(int button) {
86 if (!state.buttons.contains(button)) {
87 SetButton(button, false);
88 }
89 }
90
91 void SetMotion(SDL_ControllerSensorEvent event) {
92 constexpr float gravity_constant = 9.80665f;
93 std::lock_guard lock{mutex};
94 u64 time_difference = event.timestamp - last_motion_update;
95 last_motion_update = event.timestamp;
96 switch (event.sensor) {
97 case SDL_SENSOR_ACCEL: {
98 const Common::Vec3f acceleration = {-event.data[0], event.data[2], -event.data[1]};
99 motion.SetAcceleration(acceleration / gravity_constant);
100 break;
101 }
102 case SDL_SENSOR_GYRO: {
103 const Common::Vec3f gyroscope = {event.data[0], -event.data[2], event.data[1]};
104 motion.SetGyroscope(gyroscope / (Common::PI * 2));
105 break;
106 }
107 }
108
109 // Ignore duplicated timestamps
110 if (time_difference == 0) {
111 return;
112 }
113
114 motion.SetGyroThreshold(0.0001f);
115 motion.UpdateRotation(time_difference * 1000);
116 motion.UpdateOrientation(time_difference * 1000);
117 }
118
119 bool GetButton(int button) const {
120 std::lock_guard lock{mutex};
121 return state.buttons.at(button);
122 }
123
124 bool ToggleButton(int button) {
125 std::lock_guard lock{mutex};
126
127 if (!state.toggle_buttons.contains(button) || !state.lock_buttons.contains(button)) {
128 state.toggle_buttons.insert_or_assign(button, false);
129 state.lock_buttons.insert_or_assign(button, false);
130 }
131
132 const bool button_state = state.toggle_buttons.at(button);
133 const bool button_lock = state.lock_buttons.at(button);
134
135 if (button_lock) {
136 return button_state;
137 }
138
139 state.lock_buttons.insert_or_assign(button, true);
140
141 if (button_state) {
142 state.toggle_buttons.insert_or_assign(button, false);
143 } else {
144 state.toggle_buttons.insert_or_assign(button, true);
145 }
146
147 return !button_state;
148 }
149
150 bool UnlockButton(int button) {
151 std::lock_guard lock{mutex};
152 if (!state.toggle_buttons.contains(button)) {
153 return false;
154 }
155 state.lock_buttons.insert_or_assign(button, false);
156 return state.toggle_buttons.at(button);
157 }
158
159 void SetAxis(int axis, Sint16 value) {
160 std::lock_guard lock{mutex};
161 state.axes.insert_or_assign(axis, value);
162 }
163
164 void PreSetAxis(int axis) {
165 if (!state.axes.contains(axis)) {
166 SetAxis(axis, 0);
167 }
168 }
169
170 float GetAxis(int axis, float range, float offset) const {
171 std::lock_guard lock{mutex};
172 const float value = static_cast<float>(state.axes.at(axis)) / 32767.0f;
173 const float offset_scale = (value + offset) > 0.0f ? 1.0f + offset : 1.0f - offset;
174 return (value + offset) / range / offset_scale;
175 }
176
177 bool RumblePlay(u16 amp_low, u16 amp_high) {
178 constexpr u32 rumble_max_duration_ms = 1000;
179
180 if (sdl_controller) {
181 return SDL_GameControllerRumble(sdl_controller.get(), amp_low, amp_high,
182 rumble_max_duration_ms) != -1;
183 } else if (sdl_joystick) {
184 return SDL_JoystickRumble(sdl_joystick.get(), amp_low, amp_high,
185 rumble_max_duration_ms) != -1;
186 }
187
188 return false;
189 }
190
191 std::tuple<float, float> GetAnalog(int axis_x, int axis_y, float range, float offset_x,
192 float offset_y) const {
193 float x = GetAxis(axis_x, range, offset_x);
194 float y = GetAxis(axis_y, range, offset_y);
195 y = -y; // 3DS uses an y-axis inverse from SDL
196
197 // Make sure the coordinates are in the unit circle,
198 // otherwise normalize it.
199 float r = x * x + y * y;
200 if (r > 1.0f) {
201 r = std::sqrt(r);
202 x /= r;
203 y /= r;
204 }
205
206 return std::make_tuple(x, y);
207 }
208
209 bool HasGyro() const {
210 return has_gyro;
211 }
212
213 bool HasAccel() const {
214 return has_accel;
215 }
216
217 const MotionInput& GetMotion() const {
218 return motion;
219 }
220
221 void SetHat(int hat, Uint8 direction) {
222 std::lock_guard lock{mutex};
223 state.hats.insert_or_assign(hat, direction);
224 }
225
226 bool GetHatDirection(int hat, Uint8 direction) const {
227 std::lock_guard lock{mutex};
228 return (state.hats.at(hat) & direction) != 0;
229 }
230 /**
231 * The guid of the joystick
232 */
233 const std::string& GetGUID() const {
234 return guid;
235 }
236
237 /**
238 * The number of joystick from the same type that were connected before this joystick
239 */
240 int GetPort() const {
241 return port;
242 }
243
244 SDL_Joystick* GetSDLJoystick() const {
245 return sdl_joystick.get();
246 }
247
248 SDL_GameController* GetSDLGameController() const {
249 return sdl_controller.get();
250 }
251
252 void SetSDLJoystick(SDL_Joystick* joystick, SDL_GameController* controller) {
253 sdl_joystick.reset(joystick);
254 sdl_controller.reset(controller);
255 }
256
257 bool IsJoyconLeft() const {
258 const std::string controller_name = GetControllerName();
259 if (std::strstr(controller_name.c_str(), "Joy-Con Left") != nullptr) {
260 return true;
261 }
262 if (std::strstr(controller_name.c_str(), "Joy-Con (L)") != nullptr) {
263 return true;
264 }
265 return false;
266 }
267
268 bool IsJoyconRight() const {
269 const std::string controller_name = GetControllerName();
270 if (std::strstr(controller_name.c_str(), "Joy-Con Right") != nullptr) {
271 return true;
272 }
273 if (std::strstr(controller_name.c_str(), "Joy-Con (R)") != nullptr) {
274 return true;
275 }
276 return false;
277 }
278
279 std::string GetControllerName() const {
280 if (sdl_controller) {
281 switch (SDL_GameControllerGetType(sdl_controller.get())) {
282 case SDL_CONTROLLER_TYPE_XBOX360:
283 return "XBox 360 Controller";
284 case SDL_CONTROLLER_TYPE_XBOXONE:
285 return "XBox One Controller";
286 default:
287 break;
288 }
289 const auto name = SDL_GameControllerName(sdl_controller.get());
290 if (name) {
291 return name;
292 }
293 }
294
295 if (sdl_joystick) {
296 const auto name = SDL_JoystickName(sdl_joystick.get());
297 if (name) {
298 return name;
299 }
300 }
301
302 return "Unknown";
303 }
304
305private:
306 struct State {
307 std::unordered_map<int, bool> buttons;
308 std::unordered_map<int, bool> toggle_buttons{};
309 std::unordered_map<int, bool> lock_buttons{};
310 std::unordered_map<int, Sint16> axes;
311 std::unordered_map<int, Uint8> hats;
312 } state;
313 std::string guid;
314 int port;
315 std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick;
316 std::unique_ptr<SDL_GameController, decltype(&SDL_GameControllerClose)> sdl_controller;
317 mutable std::mutex mutex;
318
319 // Motion is initialized with the PID values
320 MotionInput motion{0.3f, 0.005f, 0.0f};
321 u64 last_motion_update{};
322 bool has_gyro{false};
323 bool has_accel{false};
324};
325
326std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickByGUID(const std::string& guid, int port) {
327 std::lock_guard lock{joystick_map_mutex};
328 const auto it = joystick_map.find(guid);
329
330 if (it != joystick_map.end()) {
331 while (it->second.size() <= static_cast<std::size_t>(port)) {
332 auto joystick = std::make_shared<SDLJoystick>(guid, static_cast<int>(it->second.size()),
333 nullptr, nullptr);
334 it->second.emplace_back(std::move(joystick));
335 }
336
337 return it->second[static_cast<std::size_t>(port)];
338 }
339
340 auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr, nullptr);
341
342 return joystick_map[guid].emplace_back(std::move(joystick));
343}
344
345std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) {
346 auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id);
347 const std::string guid = GetGUID(sdl_joystick);
348
349 std::lock_guard lock{joystick_map_mutex};
350 const auto map_it = joystick_map.find(guid);
351
352 if (map_it == joystick_map.end()) {
353 return nullptr;
354 }
355
356 const auto vec_it = std::find_if(map_it->second.begin(), map_it->second.end(),
357 [&sdl_joystick](const auto& joystick) {
358 return joystick->GetSDLJoystick() == sdl_joystick;
359 });
360
361 if (vec_it == map_it->second.end()) {
362 return nullptr;
363 }
364
365 return *vec_it;
366}
367
368void SDLState::InitJoystick(int joystick_index) {
369 SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index);
370 SDL_GameController* sdl_gamecontroller = nullptr;
371
372 if (SDL_IsGameController(joystick_index)) {
373 sdl_gamecontroller = SDL_GameControllerOpen(joystick_index);
374 }
375
376 if (!sdl_joystick) {
377 LOG_ERROR(Input, "Failed to open joystick {}", joystick_index);
378 return;
379 }
380
381 const std::string guid = GetGUID(sdl_joystick);
382
383 std::lock_guard lock{joystick_map_mutex};
384 if (joystick_map.find(guid) == joystick_map.end()) {
385 auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_gamecontroller);
386 joystick_map[guid].emplace_back(std::move(joystick));
387 return;
388 }
389
390 auto& joystick_guid_list = joystick_map[guid];
391 const auto joystick_it =
392 std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(),
393 [](const auto& joystick) { return !joystick->GetSDLJoystick(); });
394
395 if (joystick_it != joystick_guid_list.end()) {
396 (*joystick_it)->SetSDLJoystick(sdl_joystick, sdl_gamecontroller);
397 return;
398 }
399
400 const int port = static_cast<int>(joystick_guid_list.size());
401 auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick, sdl_gamecontroller);
402 joystick_guid_list.emplace_back(std::move(joystick));
403}
404
405void SDLState::CloseJoystick(SDL_Joystick* sdl_joystick) {
406 const std::string guid = GetGUID(sdl_joystick);
407
408 std::lock_guard lock{joystick_map_mutex};
409 // This call to guid is safe since the joystick is guaranteed to be in the map
410 const auto& joystick_guid_list = joystick_map[guid];
411 const auto joystick_it = std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(),
412 [&sdl_joystick](const auto& joystick) {
413 return joystick->GetSDLJoystick() == sdl_joystick;
414 });
415
416 if (joystick_it != joystick_guid_list.end()) {
417 (*joystick_it)->SetSDLJoystick(nullptr, nullptr);
418 }
419}
420
421void SDLState::HandleGameControllerEvent(const SDL_Event& event) {
422 switch (event.type) {
423 case SDL_JOYBUTTONUP: {
424 if (auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) {
425 joystick->SetButton(event.jbutton.button, false);
426 }
427 break;
428 }
429 case SDL_JOYBUTTONDOWN: {
430 if (auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) {
431 joystick->SetButton(event.jbutton.button, true);
432 }
433 break;
434 }
435 case SDL_JOYHATMOTION: {
436 if (auto joystick = GetSDLJoystickBySDLID(event.jhat.which)) {
437 joystick->SetHat(event.jhat.hat, event.jhat.value);
438 }
439 break;
440 }
441 case SDL_JOYAXISMOTION: {
442 if (auto joystick = GetSDLJoystickBySDLID(event.jaxis.which)) {
443 joystick->SetAxis(event.jaxis.axis, event.jaxis.value);
444 }
445 break;
446 }
447 case SDL_CONTROLLERSENSORUPDATE: {
448 if (auto joystick = GetSDLJoystickBySDLID(event.csensor.which)) {
449 joystick->SetMotion(event.csensor);
450 }
451 break;
452 }
453 case SDL_JOYDEVICEREMOVED:
454 LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which);
455 CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which));
456 break;
457 case SDL_JOYDEVICEADDED:
458 LOG_DEBUG(Input, "Controller connected with device index {}", event.jdevice.which);
459 InitJoystick(event.jdevice.which);
460 break;
461 }
462}
463
464void SDLState::CloseJoysticks() {
465 std::lock_guard lock{joystick_map_mutex};
466 joystick_map.clear();
467}
468
469class SDLButton final : public Input::ButtonDevice {
470public:
471 explicit SDLButton(std::shared_ptr<SDLJoystick> joystick_, int button_, bool toggle_)
472 : joystick(std::move(joystick_)), button(button_), toggle(toggle_) {}
473
474 bool GetStatus() const override {
475 const bool button_state = joystick->GetButton(button);
476 if (!toggle) {
477 return button_state;
478 }
479
480 if (button_state) {
481 return joystick->ToggleButton(button);
482 }
483 return joystick->UnlockButton(button);
484 }
485
486private:
487 std::shared_ptr<SDLJoystick> joystick;
488 int button;
489 bool toggle;
490};
491
492class SDLDirectionButton final : public Input::ButtonDevice {
493public:
494 explicit SDLDirectionButton(std::shared_ptr<SDLJoystick> joystick_, int hat_, Uint8 direction_)
495 : joystick(std::move(joystick_)), hat(hat_), direction(direction_) {}
496
497 bool GetStatus() const override {
498 return joystick->GetHatDirection(hat, direction);
499 }
500
501private:
502 std::shared_ptr<SDLJoystick> joystick;
503 int hat;
504 Uint8 direction;
505};
506
507class SDLAxisButton final : public Input::ButtonDevice {
508public:
509 explicit SDLAxisButton(std::shared_ptr<SDLJoystick> joystick_, int axis_, float threshold_,
510 bool trigger_if_greater_)
511 : joystick(std::move(joystick_)), axis(axis_), threshold(threshold_),
512 trigger_if_greater(trigger_if_greater_) {}
513
514 bool GetStatus() const override {
515 const float axis_value = joystick->GetAxis(axis, 1.0f, 0.0f);
516 if (trigger_if_greater) {
517 return axis_value > threshold;
518 }
519 return axis_value < threshold;
520 }
521
522private:
523 std::shared_ptr<SDLJoystick> joystick;
524 int axis;
525 float threshold;
526 bool trigger_if_greater;
527};
528
529class SDLAnalog final : public Input::AnalogDevice {
530public:
531 explicit SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_,
532 bool invert_x_, bool invert_y_, float deadzone_, float range_,
533 float offset_x_, float offset_y_)
534 : joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_), invert_x(invert_x_),
535 invert_y(invert_y_), deadzone(deadzone_), range(range_), offset_x(offset_x_),
536 offset_y(offset_y_) {}
537
538 std::tuple<float, float> GetStatus() const override {
539 auto [x, y] = joystick->GetAnalog(axis_x, axis_y, range, offset_x, offset_y);
540 const float r = std::sqrt((x * x) + (y * y));
541 if (invert_x) {
542 x = -x;
543 }
544 if (invert_y) {
545 y = -y;
546 }
547
548 if (r > deadzone) {
549 return std::make_tuple(x / r * (r - deadzone) / (1 - deadzone),
550 y / r * (r - deadzone) / (1 - deadzone));
551 }
552 return {};
553 }
554
555 std::tuple<float, float> GetRawStatus() const override {
556 const float x = joystick->GetAxis(axis_x, range, offset_x);
557 const float y = joystick->GetAxis(axis_y, range, offset_y);
558 return {x, -y};
559 }
560
561 Input::AnalogProperties GetAnalogProperties() const override {
562 return {deadzone, range, 0.5f};
563 }
564
565 bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override {
566 const auto [x, y] = GetStatus();
567 const float directional_deadzone = 0.5f;
568 switch (direction) {
569 case Input::AnalogDirection::RIGHT:
570 return x > directional_deadzone;
571 case Input::AnalogDirection::LEFT:
572 return x < -directional_deadzone;
573 case Input::AnalogDirection::UP:
574 return y > directional_deadzone;
575 case Input::AnalogDirection::DOWN:
576 return y < -directional_deadzone;
577 }
578 return false;
579 }
580
581private:
582 std::shared_ptr<SDLJoystick> joystick;
583 const int axis_x;
584 const int axis_y;
585 const bool invert_x;
586 const bool invert_y;
587 const float deadzone;
588 const float range;
589 const float offset_x;
590 const float offset_y;
591};
592
593class SDLVibration final : public Input::VibrationDevice {
594public:
595 explicit SDLVibration(std::shared_ptr<SDLJoystick> joystick_)
596 : joystick(std::move(joystick_)) {}
597
598 u8 GetStatus() const override {
599 joystick->RumblePlay(1, 1);
600 return joystick->RumblePlay(0, 0);
601 }
602
603 bool SetRumblePlay(f32 amp_low, [[maybe_unused]] f32 freq_low, f32 amp_high,
604 [[maybe_unused]] f32 freq_high) const override {
605 const auto process_amplitude = [](f32 amplitude) {
606 return static_cast<u16>((amplitude + std::pow(amplitude, 0.3f)) * 0.5f * 0xFFFF);
607 };
608
609 const auto processed_amp_low = process_amplitude(amp_low);
610 const auto processed_amp_high = process_amplitude(amp_high);
611
612 return joystick->RumblePlay(processed_amp_low, processed_amp_high);
613 }
614
615private:
616 std::shared_ptr<SDLJoystick> joystick;
617};
618
619class SDLMotion final : public Input::MotionDevice {
620public:
621 explicit SDLMotion(std::shared_ptr<SDLJoystick> joystick_) : joystick(std::move(joystick_)) {}
622
623 Input::MotionStatus GetStatus() const override {
624 return joystick->GetMotion().GetMotion();
625 }
626
627private:
628 std::shared_ptr<SDLJoystick> joystick;
629};
630
631class SDLDirectionMotion final : public Input::MotionDevice {
632public:
633 explicit SDLDirectionMotion(std::shared_ptr<SDLJoystick> joystick_, int hat_, Uint8 direction_)
634 : joystick(std::move(joystick_)), hat(hat_), direction(direction_) {}
635
636 Input::MotionStatus GetStatus() const override {
637 if (joystick->GetHatDirection(hat, direction)) {
638 return joystick->GetMotion().GetRandomMotion(2, 6);
639 }
640 return joystick->GetMotion().GetRandomMotion(0, 0);
641 }
642
643private:
644 std::shared_ptr<SDLJoystick> joystick;
645 int hat;
646 Uint8 direction;
647};
648
649class SDLAxisMotion final : public Input::MotionDevice {
650public:
651 explicit SDLAxisMotion(std::shared_ptr<SDLJoystick> joystick_, int axis_, float threshold_,
652 bool trigger_if_greater_)
653 : joystick(std::move(joystick_)), axis(axis_), threshold(threshold_),
654 trigger_if_greater(trigger_if_greater_) {}
655
656 Input::MotionStatus GetStatus() const override {
657 const float axis_value = joystick->GetAxis(axis, 1.0f, 0.0f);
658 bool trigger = axis_value < threshold;
659 if (trigger_if_greater) {
660 trigger = axis_value > threshold;
661 }
662
663 if (trigger) {
664 return joystick->GetMotion().GetRandomMotion(2, 6);
665 }
666 return joystick->GetMotion().GetRandomMotion(0, 0);
667 }
668
669private:
670 std::shared_ptr<SDLJoystick> joystick;
671 int axis;
672 float threshold;
673 bool trigger_if_greater;
674};
675
676class SDLButtonMotion final : public Input::MotionDevice {
677public:
678 explicit SDLButtonMotion(std::shared_ptr<SDLJoystick> joystick_, int button_)
679 : joystick(std::move(joystick_)), button(button_) {}
680
681 Input::MotionStatus GetStatus() const override {
682 if (joystick->GetButton(button)) {
683 return joystick->GetMotion().GetRandomMotion(2, 6);
684 }
685 return joystick->GetMotion().GetRandomMotion(0, 0);
686 }
687
688private:
689 std::shared_ptr<SDLJoystick> joystick;
690 int button;
691};
692
693/// A button device factory that creates button devices from SDL joystick
694class SDLButtonFactory final : public Input::Factory<Input::ButtonDevice> {
695public:
696 explicit SDLButtonFactory(SDLState& state_) : state(state_) {}
697
698 /**
699 * Creates a button device from a joystick button
700 * @param params contains parameters for creating the device:
701 * - "guid": the guid of the joystick to bind
702 * - "port": the nth joystick of the same type to bind
703 * - "button"(optional): the index of the button to bind
704 * - "hat"(optional): the index of the hat to bind as direction buttons
705 * - "axis"(optional): the index of the axis to bind
706 * - "direction"(only used for hat): the direction name of the hat to bind. Can be "up",
707 * "down", "left" or "right"
708 * - "threshold"(only used for axis): a float value in (-1.0, 1.0) which the button is
709 * triggered if the axis value crosses
710 * - "direction"(only used for axis): "+" means the button is triggered when the axis
711 * value is greater than the threshold; "-" means the button is triggered when the axis
712 * value is smaller than the threshold
713 */
714 std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override {
715 const std::string guid = params.Get("guid", "0");
716 const int port = params.Get("port", 0);
717 const auto toggle = params.Get("toggle", false);
718
719 auto joystick = state.GetSDLJoystickByGUID(guid, port);
720
721 if (params.Has("hat")) {
722 const int hat = params.Get("hat", 0);
723 const std::string direction_name = params.Get("direction", "");
724 Uint8 direction;
725 if (direction_name == "up") {
726 direction = SDL_HAT_UP;
727 } else if (direction_name == "down") {
728 direction = SDL_HAT_DOWN;
729 } else if (direction_name == "left") {
730 direction = SDL_HAT_LEFT;
731 } else if (direction_name == "right") {
732 direction = SDL_HAT_RIGHT;
733 } else {
734 direction = 0;
735 }
736 // This is necessary so accessing GetHat with hat won't crash
737 joystick->SetHat(hat, SDL_HAT_CENTERED);
738 return std::make_unique<SDLDirectionButton>(joystick, hat, direction);
739 }
740
741 if (params.Has("axis")) {
742 const int axis = params.Get("axis", 0);
743 // Convert range from (0.0, 1.0) to (-1.0, 1.0)
744 const float threshold = (params.Get("threshold", 0.5f) - 0.5f) * 2.0f;
745 const std::string direction_name = params.Get("direction", "");
746 bool trigger_if_greater;
747 if (direction_name == "+") {
748 trigger_if_greater = true;
749 } else if (direction_name == "-") {
750 trigger_if_greater = false;
751 } else {
752 trigger_if_greater = true;
753 LOG_ERROR(Input, "Unknown direction {}", direction_name);
754 }
755 // This is necessary so accessing GetAxis with axis won't crash
756 joystick->PreSetAxis(axis);
757 return std::make_unique<SDLAxisButton>(joystick, axis, threshold, trigger_if_greater);
758 }
759
760 const int button = params.Get("button", 0);
761 // This is necessary so accessing GetButton with button won't crash
762 joystick->PreSetButton(button);
763 return std::make_unique<SDLButton>(joystick, button, toggle);
764 }
765
766private:
767 SDLState& state;
768};
769
770/// An analog device factory that creates analog devices from SDL joystick
771class SDLAnalogFactory final : public Input::Factory<Input::AnalogDevice> {
772public:
773 explicit SDLAnalogFactory(SDLState& state_) : state(state_) {}
774 /**
775 * Creates an analog device from joystick axes
776 * @param params contains parameters for creating the device:
777 * - "guid": the guid of the joystick to bind
778 * - "port": the nth joystick of the same type
779 * - "axis_x": the index of the axis to be bind as x-axis
780 * - "axis_y": the index of the axis to be bind as y-axis
781 */
782 std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override {
783 const std::string guid = params.Get("guid", "0");
784 const int port = params.Get("port", 0);
785 const int axis_x = params.Get("axis_x", 0);
786 const int axis_y = params.Get("axis_y", 1);
787 const float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f);
788 const float range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f);
789 const std::string invert_x_value = params.Get("invert_x", "+");
790 const std::string invert_y_value = params.Get("invert_y", "+");
791 const bool invert_x = invert_x_value == "-";
792 const bool invert_y = invert_y_value == "-";
793 const float offset_x = std::clamp(params.Get("offset_x", 0.0f), -0.99f, 0.99f);
794 const float offset_y = std::clamp(params.Get("offset_y", 0.0f), -0.99f, 0.99f);
795 auto joystick = state.GetSDLJoystickByGUID(guid, port);
796
797 // This is necessary so accessing GetAxis with axis_x and axis_y won't crash
798 joystick->PreSetAxis(axis_x);
799 joystick->PreSetAxis(axis_y);
800 return std::make_unique<SDLAnalog>(joystick, axis_x, axis_y, invert_x, invert_y, deadzone,
801 range, offset_x, offset_y);
802 }
803
804private:
805 SDLState& state;
806};
807
808/// An vibration device factory that creates vibration devices from SDL joystick
809class SDLVibrationFactory final : public Input::Factory<Input::VibrationDevice> {
810public:
811 explicit SDLVibrationFactory(SDLState& state_) : state(state_) {}
812 /**
813 * Creates a vibration device from a joystick
814 * @param params contains parameters for creating the device:
815 * - "guid": the guid of the joystick to bind
816 * - "port": the nth joystick of the same type
817 */
818 std::unique_ptr<Input::VibrationDevice> Create(const Common::ParamPackage& params) override {
819 const std::string guid = params.Get("guid", "0");
820 const int port = params.Get("port", 0);
821 return std::make_unique<SDLVibration>(state.GetSDLJoystickByGUID(guid, port));
822 }
823
824private:
825 SDLState& state;
826};
827
828/// A motion device factory that creates motion devices from SDL joystick
829class SDLMotionFactory final : public Input::Factory<Input::MotionDevice> {
830public:
831 explicit SDLMotionFactory(SDLState& state_) : state(state_) {}
832 /**
833 * Creates motion device from joystick axes
834 * @param params contains parameters for creating the device:
835 * - "guid": the guid of the joystick to bind
836 * - "port": the nth joystick of the same type
837 */
838 std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override {
839 const std::string guid = params.Get("guid", "0");
840 const int port = params.Get("port", 0);
841
842 auto joystick = state.GetSDLJoystickByGUID(guid, port);
843
844 if (params.Has("motion")) {
845 return std::make_unique<SDLMotion>(joystick);
846 }
847
848 if (params.Has("hat")) {
849 const int hat = params.Get("hat", 0);
850 const std::string direction_name = params.Get("direction", "");
851 Uint8 direction;
852 if (direction_name == "up") {
853 direction = SDL_HAT_UP;
854 } else if (direction_name == "down") {
855 direction = SDL_HAT_DOWN;
856 } else if (direction_name == "left") {
857 direction = SDL_HAT_LEFT;
858 } else if (direction_name == "right") {
859 direction = SDL_HAT_RIGHT;
860 } else {
861 direction = 0;
862 }
863 // This is necessary so accessing GetHat with hat won't crash
864 joystick->SetHat(hat, SDL_HAT_CENTERED);
865 return std::make_unique<SDLDirectionMotion>(joystick, hat, direction);
866 }
867
868 if (params.Has("axis")) {
869 const int axis = params.Get("axis", 0);
870 const float threshold = params.Get("threshold", 0.5f);
871 const std::string direction_name = params.Get("direction", "");
872 bool trigger_if_greater;
873 if (direction_name == "+") {
874 trigger_if_greater = true;
875 } else if (direction_name == "-") {
876 trigger_if_greater = false;
877 } else {
878 trigger_if_greater = true;
879 LOG_ERROR(Input, "Unknown direction {}", direction_name);
880 }
881 // This is necessary so accessing GetAxis with axis won't crash
882 joystick->PreSetAxis(axis);
883 return std::make_unique<SDLAxisMotion>(joystick, axis, threshold, trigger_if_greater);
884 }
885
886 const int button = params.Get("button", 0);
887 // This is necessary so accessing GetButton with button won't crash
888 joystick->PreSetButton(button);
889 return std::make_unique<SDLButtonMotion>(joystick, button);
890 }
891
892private:
893 SDLState& state;
894};
895
896SDLState::SDLState() {
897 using namespace Input;
898 button_factory = std::make_shared<SDLButtonFactory>(*this);
899 analog_factory = std::make_shared<SDLAnalogFactory>(*this);
900 vibration_factory = std::make_shared<SDLVibrationFactory>(*this);
901 motion_factory = std::make_shared<SDLMotionFactory>(*this);
902 RegisterFactory<ButtonDevice>("sdl", button_factory);
903 RegisterFactory<AnalogDevice>("sdl", analog_factory);
904 RegisterFactory<VibrationDevice>("sdl", vibration_factory);
905 RegisterFactory<MotionDevice>("sdl", motion_factory);
906
907 if (!Settings::values.enable_raw_input) {
908 // Disable raw input. When enabled this setting causes SDL to die when a web applet opens
909 SDL_SetHint(SDL_HINT_JOYSTICK_RAWINPUT, "0");
910 }
911
912 // Enable HIDAPI rumble. This prevents SDL from disabling motion on PS4 and PS5 controllers
913 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1");
914 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
915
916 // Tell SDL2 to use the hidapi driver. This will allow joycons to be detected as a
917 // GameController and not a generic one
918 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1");
919
920 // Turn off Pro controller home led
921 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_HOME_LED, "0");
922
923 // If the frontend is going to manage the event loop, then we don't start one here
924 start_thread = SDL_WasInit(SDL_INIT_JOYSTICK) == 0;
925 if (start_thread && SDL_Init(SDL_INIT_JOYSTICK) < 0) {
926 LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: {}", SDL_GetError());
927 return;
928 }
929 has_gamecontroller = SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) != 0;
930 if (SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1") == SDL_FALSE) {
931 LOG_ERROR(Input, "Failed to set hint for background events with: {}", SDL_GetError());
932 }
933
934 SDL_AddEventWatch(&SDLEventWatcher, this);
935
936 initialized = true;
937 if (start_thread) {
938 poll_thread = std::thread([this] {
939 using namespace std::chrono_literals;
940 while (initialized) {
941 SDL_PumpEvents();
942 std::this_thread::sleep_for(1ms);
943 }
944 });
945 }
946 // Because the events for joystick connection happens before we have our event watcher added, we
947 // can just open all the joysticks right here
948 for (int i = 0; i < SDL_NumJoysticks(); ++i) {
949 InitJoystick(i);
950 }
951}
952
953SDLState::~SDLState() {
954 using namespace Input;
955 UnregisterFactory<ButtonDevice>("sdl");
956 UnregisterFactory<AnalogDevice>("sdl");
957 UnregisterFactory<VibrationDevice>("sdl");
958 UnregisterFactory<MotionDevice>("sdl");
959
960 CloseJoysticks();
961 SDL_DelEventWatch(&SDLEventWatcher, this);
962
963 initialized = false;
964 if (start_thread) {
965 poll_thread.join();
966 SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
967 }
968}
969
970std::vector<Common::ParamPackage> SDLState::GetInputDevices() {
971 std::scoped_lock lock(joystick_map_mutex);
972 std::vector<Common::ParamPackage> devices;
973 std::unordered_map<int, std::shared_ptr<SDLJoystick>> joycon_pairs;
974 for (const auto& [key, value] : joystick_map) {
975 for (const auto& joystick : value) {
976 if (!joystick->GetSDLJoystick()) {
977 continue;
978 }
979 std::string name =
980 fmt::format("{} {}", joystick->GetControllerName(), joystick->GetPort());
981 devices.emplace_back(Common::ParamPackage{
982 {"class", "sdl"},
983 {"display", std::move(name)},
984 {"guid", joystick->GetGUID()},
985 {"port", std::to_string(joystick->GetPort())},
986 });
987 if (joystick->IsJoyconLeft()) {
988 joycon_pairs.insert_or_assign(joystick->GetPort(), joystick);
989 }
990 }
991 }
992
993 // Add dual controllers
994 for (const auto& [key, value] : joystick_map) {
995 for (const auto& joystick : value) {
996 if (joystick->IsJoyconRight()) {
997 if (!joycon_pairs.contains(joystick->GetPort())) {
998 continue;
999 }
1000 const auto joystick2 = joycon_pairs.at(joystick->GetPort());
1001
1002 std::string name =
1003 fmt::format("{} {}", "Nintendo Dual Joy-Con", joystick->GetPort());
1004 devices.emplace_back(Common::ParamPackage{
1005 {"class", "sdl"},
1006 {"display", std::move(name)},
1007 {"guid", joystick->GetGUID()},
1008 {"guid2", joystick2->GetGUID()},
1009 {"port", std::to_string(joystick->GetPort())},
1010 });
1011 }
1012 }
1013 }
1014 return devices;
1015}
1016
1017namespace {
1018Common::ParamPackage BuildAnalogParamPackageForButton(int port, std::string guid, s32 axis,
1019 float value = 0.1f) {
1020 Common::ParamPackage params({{"engine", "sdl"}});
1021 params.Set("port", port);
1022 params.Set("guid", std::move(guid));
1023 params.Set("axis", axis);
1024 params.Set("threshold", "0.5");
1025 if (value > 0) {
1026 params.Set("direction", "+");
1027 } else {
1028 params.Set("direction", "-");
1029 }
1030 return params;
1031}
1032
1033Common::ParamPackage BuildButtonParamPackageForButton(int port, std::string guid, s32 button) {
1034 Common::ParamPackage params({{"engine", "sdl"}});
1035 params.Set("port", port);
1036 params.Set("guid", std::move(guid));
1037 params.Set("button", button);
1038 params.Set("toggle", false);
1039 return params;
1040}
1041
1042Common::ParamPackage BuildHatParamPackageForButton(int port, std::string guid, s32 hat, s32 value) {
1043 Common::ParamPackage params({{"engine", "sdl"}});
1044
1045 params.Set("port", port);
1046 params.Set("guid", std::move(guid));
1047 params.Set("hat", hat);
1048 switch (value) {
1049 case SDL_HAT_UP:
1050 params.Set("direction", "up");
1051 break;
1052 case SDL_HAT_DOWN:
1053 params.Set("direction", "down");
1054 break;
1055 case SDL_HAT_LEFT:
1056 params.Set("direction", "left");
1057 break;
1058 case SDL_HAT_RIGHT:
1059 params.Set("direction", "right");
1060 break;
1061 default:
1062 return {};
1063 }
1064 return params;
1065}
1066
1067Common::ParamPackage BuildMotionParam(int port, std::string guid) {
1068 Common::ParamPackage params({{"engine", "sdl"}, {"motion", "0"}});
1069 params.Set("port", port);
1070 params.Set("guid", std::move(guid));
1071 return params;
1072}
1073
1074Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event) {
1075 switch (event.type) {
1076 case SDL_JOYAXISMOTION: {
1077 if (const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which)) {
1078 return BuildAnalogParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
1079 static_cast<s32>(event.jaxis.axis),
1080 event.jaxis.value);
1081 }
1082 break;
1083 }
1084 case SDL_JOYBUTTONUP: {
1085 if (const auto joystick = state.GetSDLJoystickBySDLID(event.jbutton.which)) {
1086 return BuildButtonParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
1087 static_cast<s32>(event.jbutton.button));
1088 }
1089 break;
1090 }
1091 case SDL_JOYHATMOTION: {
1092 if (const auto joystick = state.GetSDLJoystickBySDLID(event.jhat.which)) {
1093 return BuildHatParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
1094 static_cast<s32>(event.jhat.hat),
1095 static_cast<s32>(event.jhat.value));
1096 }
1097 break;
1098 }
1099 }
1100 return {};
1101}
1102
1103Common::ParamPackage SDLEventToMotionParamPackage(SDLState& state, const SDL_Event& event) {
1104 switch (event.type) {
1105 case SDL_JOYAXISMOTION: {
1106 if (const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which)) {
1107 return BuildAnalogParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
1108 static_cast<s32>(event.jaxis.axis),
1109 event.jaxis.value);
1110 }
1111 break;
1112 }
1113 case SDL_JOYBUTTONUP: {
1114 if (const auto joystick = state.GetSDLJoystickBySDLID(event.jbutton.which)) {
1115 return BuildButtonParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
1116 static_cast<s32>(event.jbutton.button));
1117 }
1118 break;
1119 }
1120 case SDL_JOYHATMOTION: {
1121 if (const auto joystick = state.GetSDLJoystickBySDLID(event.jhat.which)) {
1122 return BuildHatParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
1123 static_cast<s32>(event.jhat.hat),
1124 static_cast<s32>(event.jhat.value));
1125 }
1126 break;
1127 }
1128 case SDL_CONTROLLERSENSORUPDATE: {
1129 bool is_motion_shaking = false;
1130 constexpr float gyro_threshold = 5.0f;
1131 constexpr float accel_threshold = 11.0f;
1132 if (event.csensor.sensor == SDL_SENSOR_ACCEL) {
1133 const Common::Vec3f acceleration = {-event.csensor.data[0], event.csensor.data[2],
1134 -event.csensor.data[1]};
1135 if (acceleration.Length() > accel_threshold) {
1136 is_motion_shaking = true;
1137 }
1138 }
1139
1140 if (event.csensor.sensor == SDL_SENSOR_GYRO) {
1141 const Common::Vec3f gyroscope = {event.csensor.data[0], -event.csensor.data[2],
1142 event.csensor.data[1]};
1143 if (gyroscope.Length() > gyro_threshold) {
1144 is_motion_shaking = true;
1145 }
1146 }
1147
1148 if (!is_motion_shaking) {
1149 break;
1150 }
1151
1152 if (const auto joystick = state.GetSDLJoystickBySDLID(event.csensor.which)) {
1153 return BuildMotionParam(joystick->GetPort(), joystick->GetGUID());
1154 }
1155 break;
1156 }
1157 }
1158 return {};
1159}
1160
1161Common::ParamPackage BuildParamPackageForBinding(int port, const std::string& guid,
1162 const SDL_GameControllerButtonBind& binding) {
1163 switch (binding.bindType) {
1164 case SDL_CONTROLLER_BINDTYPE_NONE:
1165 break;
1166 case SDL_CONTROLLER_BINDTYPE_AXIS:
1167 return BuildAnalogParamPackageForButton(port, guid, binding.value.axis);
1168 case SDL_CONTROLLER_BINDTYPE_BUTTON:
1169 return BuildButtonParamPackageForButton(port, guid, binding.value.button);
1170 case SDL_CONTROLLER_BINDTYPE_HAT:
1171 return BuildHatParamPackageForButton(port, guid, binding.value.hat.hat,
1172 binding.value.hat.hat_mask);
1173 }
1174 return {};
1175}
1176
1177Common::ParamPackage BuildParamPackageForAnalog(int port, const std::string& guid, int axis_x,
1178 int axis_y, float offset_x, float offset_y) {
1179 Common::ParamPackage params;
1180 params.Set("engine", "sdl");
1181 params.Set("port", port);
1182 params.Set("guid", guid);
1183 params.Set("axis_x", axis_x);
1184 params.Set("axis_y", axis_y);
1185 params.Set("offset_x", offset_x);
1186 params.Set("offset_y", offset_y);
1187 params.Set("invert_x", "+");
1188 params.Set("invert_y", "+");
1189 return params;
1190}
1191} // Anonymous namespace
1192
1193ButtonMapping SDLState::GetButtonMappingForDevice(const Common::ParamPackage& params) {
1194 if (!params.Has("guid") || !params.Has("port")) {
1195 return {};
1196 }
1197 const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
1198
1199 auto* controller = joystick->GetSDLGameController();
1200 if (controller == nullptr) {
1201 return {};
1202 }
1203
1204 // This list is missing ZL/ZR since those are not considered buttons in SDL GameController.
1205 // We will add those afterwards
1206 // This list also excludes Screenshot since theres not really a mapping for that
1207 ButtonBindings switch_to_sdl_button;
1208
1209 if (SDL_GameControllerGetType(controller) == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO) {
1210 switch_to_sdl_button = GetNintendoButtonBinding(joystick);
1211 } else {
1212 switch_to_sdl_button = GetDefaultButtonBinding();
1213 }
1214
1215 // Add the missing bindings for ZL/ZR
1216 static constexpr ZButtonBindings switch_to_sdl_axis{{
1217 {Settings::NativeButton::ZL, SDL_CONTROLLER_AXIS_TRIGGERLEFT},
1218 {Settings::NativeButton::ZR, SDL_CONTROLLER_AXIS_TRIGGERRIGHT},
1219 }};
1220
1221 // Parameters contain two joysticks return dual
1222 if (params.Has("guid2")) {
1223 const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0));
1224
1225 if (joystick2->GetSDLGameController() != nullptr) {
1226 return GetDualControllerMapping(joystick, joystick2, switch_to_sdl_button,
1227 switch_to_sdl_axis);
1228 }
1229 }
1230
1231 return GetSingleControllerMapping(joystick, switch_to_sdl_button, switch_to_sdl_axis);
1232}
1233
1234ButtonBindings SDLState::GetDefaultButtonBinding() const {
1235 return {
1236 std::pair{Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_B},
1237 {Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_A},
1238 {Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_Y},
1239 {Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_X},
1240 {Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK},
1241 {Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK},
1242 {Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
1243 {Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
1244 {Settings::NativeButton::Plus, SDL_CONTROLLER_BUTTON_START},
1245 {Settings::NativeButton::Minus, SDL_CONTROLLER_BUTTON_BACK},
1246 {Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT},
1247 {Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP},
1248 {Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT},
1249 {Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN},
1250 {Settings::NativeButton::SL, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
1251 {Settings::NativeButton::SR, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
1252 {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE},
1253 };
1254}
1255
1256ButtonBindings SDLState::GetNintendoButtonBinding(
1257 const std::shared_ptr<SDLJoystick>& joystick) const {
1258 // Default SL/SR mapping for pro controllers
1259 auto sl_button = SDL_CONTROLLER_BUTTON_LEFTSHOULDER;
1260 auto sr_button = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER;
1261
1262 if (joystick->IsJoyconLeft()) {
1263 sl_button = SDL_CONTROLLER_BUTTON_PADDLE2;
1264 sr_button = SDL_CONTROLLER_BUTTON_PADDLE4;
1265 }
1266 if (joystick->IsJoyconRight()) {
1267 sl_button = SDL_CONTROLLER_BUTTON_PADDLE3;
1268 sr_button = SDL_CONTROLLER_BUTTON_PADDLE1;
1269 }
1270
1271 return {
1272 std::pair{Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_A},
1273 {Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_B},
1274 {Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_X},
1275 {Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_Y},
1276 {Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK},
1277 {Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK},
1278 {Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
1279 {Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
1280 {Settings::NativeButton::Plus, SDL_CONTROLLER_BUTTON_START},
1281 {Settings::NativeButton::Minus, SDL_CONTROLLER_BUTTON_BACK},
1282 {Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT},
1283 {Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP},
1284 {Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT},
1285 {Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN},
1286 {Settings::NativeButton::SL, sl_button},
1287 {Settings::NativeButton::SR, sr_button},
1288 {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE},
1289 };
1290}
1291
1292ButtonMapping SDLState::GetSingleControllerMapping(
1293 const std::shared_ptr<SDLJoystick>& joystick, const ButtonBindings& switch_to_sdl_button,
1294 const ZButtonBindings& switch_to_sdl_axis) const {
1295 ButtonMapping mapping;
1296 mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size());
1297 auto* controller = joystick->GetSDLGameController();
1298
1299 for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) {
1300 const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button);
1301 mapping.insert_or_assign(
1302 switch_button,
1303 BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
1304 }
1305 for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) {
1306 const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis);
1307 mapping.insert_or_assign(
1308 switch_button,
1309 BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
1310 }
1311
1312 return mapping;
1313}
1314
1315ButtonMapping SDLState::GetDualControllerMapping(const std::shared_ptr<SDLJoystick>& joystick,
1316 const std::shared_ptr<SDLJoystick>& joystick2,
1317 const ButtonBindings& switch_to_sdl_button,
1318 const ZButtonBindings& switch_to_sdl_axis) const {
1319 ButtonMapping mapping;
1320 mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size());
1321 auto* controller = joystick->GetSDLGameController();
1322 auto* controller2 = joystick2->GetSDLGameController();
1323
1324 for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) {
1325 if (IsButtonOnLeftSide(switch_button)) {
1326 const auto& binding = SDL_GameControllerGetBindForButton(controller2, sdl_button);
1327 mapping.insert_or_assign(
1328 switch_button,
1329 BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), binding));
1330 continue;
1331 }
1332 const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button);
1333 mapping.insert_or_assign(
1334 switch_button,
1335 BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
1336 }
1337 for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) {
1338 if (IsButtonOnLeftSide(switch_button)) {
1339 const auto& binding = SDL_GameControllerGetBindForAxis(controller2, sdl_axis);
1340 mapping.insert_or_assign(
1341 switch_button,
1342 BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), binding));
1343 continue;
1344 }
1345 const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis);
1346 mapping.insert_or_assign(
1347 switch_button,
1348 BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
1349 }
1350
1351 return mapping;
1352}
1353
1354bool SDLState::IsButtonOnLeftSide(Settings::NativeButton::Values button) const {
1355 switch (button) {
1356 case Settings::NativeButton::DDown:
1357 case Settings::NativeButton::DLeft:
1358 case Settings::NativeButton::DRight:
1359 case Settings::NativeButton::DUp:
1360 case Settings::NativeButton::L:
1361 case Settings::NativeButton::LStick:
1362 case Settings::NativeButton::Minus:
1363 case Settings::NativeButton::Screenshot:
1364 case Settings::NativeButton::ZL:
1365 return true;
1366 default:
1367 return false;
1368 }
1369}
1370
1371AnalogMapping SDLState::GetAnalogMappingForDevice(const Common::ParamPackage& params) {
1372 if (!params.Has("guid") || !params.Has("port")) {
1373 return {};
1374 }
1375 const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
1376 const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0));
1377 auto* controller = joystick->GetSDLGameController();
1378 if (controller == nullptr) {
1379 return {};
1380 }
1381
1382 AnalogMapping mapping = {};
1383 const auto& binding_left_x =
1384 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX);
1385 const auto& binding_left_y =
1386 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY);
1387 if (params.Has("guid2")) {
1388 joystick2->PreSetAxis(binding_left_x.value.axis);
1389 joystick2->PreSetAxis(binding_left_y.value.axis);
1390 const auto left_offset_x = -joystick2->GetAxis(binding_left_x.value.axis, 1.0f, 0);
1391 const auto left_offset_y = -joystick2->GetAxis(binding_left_y.value.axis, 1.0f, 0);
1392 mapping.insert_or_assign(
1393 Settings::NativeAnalog::LStick,
1394 BuildParamPackageForAnalog(joystick2->GetPort(), joystick2->GetGUID(),
1395 binding_left_x.value.axis, binding_left_y.value.axis,
1396 left_offset_x, left_offset_y));
1397 } else {
1398 joystick->PreSetAxis(binding_left_x.value.axis);
1399 joystick->PreSetAxis(binding_left_y.value.axis);
1400 const auto left_offset_x = -joystick->GetAxis(binding_left_x.value.axis, 1.0f, 0);
1401 const auto left_offset_y = -joystick->GetAxis(binding_left_y.value.axis, 1.0f, 0);
1402 mapping.insert_or_assign(
1403 Settings::NativeAnalog::LStick,
1404 BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(),
1405 binding_left_x.value.axis, binding_left_y.value.axis,
1406 left_offset_x, left_offset_y));
1407 }
1408 const auto& binding_right_x =
1409 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX);
1410 const auto& binding_right_y =
1411 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY);
1412 joystick->PreSetAxis(binding_right_x.value.axis);
1413 joystick->PreSetAxis(binding_right_y.value.axis);
1414 const auto right_offset_x = -joystick->GetAxis(binding_right_x.value.axis, 1.0f, 0);
1415 const auto right_offset_y = -joystick->GetAxis(binding_right_y.value.axis, 1.0f, 0);
1416 mapping.insert_or_assign(Settings::NativeAnalog::RStick,
1417 BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(),
1418 binding_right_x.value.axis,
1419 binding_right_y.value.axis, right_offset_x,
1420 right_offset_y));
1421 return mapping;
1422}
1423
1424MotionMapping SDLState::GetMotionMappingForDevice(const Common::ParamPackage& params) {
1425 if (!params.Has("guid") || !params.Has("port")) {
1426 return {};
1427 }
1428 const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
1429 const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0));
1430 auto* controller = joystick->GetSDLGameController();
1431 if (controller == nullptr) {
1432 return {};
1433 }
1434
1435 MotionMapping mapping = {};
1436 joystick->EnableMotion();
1437
1438 if (joystick->HasGyro() || joystick->HasAccel()) {
1439 mapping.insert_or_assign(Settings::NativeMotion::MotionRight,
1440 BuildMotionParam(joystick->GetPort(), joystick->GetGUID()));
1441 }
1442 if (params.Has("guid2")) {
1443 joystick2->EnableMotion();
1444 if (joystick2->HasGyro() || joystick2->HasAccel()) {
1445 mapping.insert_or_assign(Settings::NativeMotion::MotionLeft,
1446 BuildMotionParam(joystick2->GetPort(), joystick2->GetGUID()));
1447 }
1448 } else {
1449 if (joystick->HasGyro() || joystick->HasAccel()) {
1450 mapping.insert_or_assign(Settings::NativeMotion::MotionLeft,
1451 BuildMotionParam(joystick->GetPort(), joystick->GetGUID()));
1452 }
1453 }
1454
1455 return mapping;
1456}
1457namespace Polling {
1458class SDLPoller : public InputCommon::Polling::DevicePoller {
1459public:
1460 explicit SDLPoller(SDLState& state_) : state(state_) {}
1461
1462 void Start([[maybe_unused]] const std::string& device_id) override {
1463 state.event_queue.Clear();
1464 state.polling = true;
1465 }
1466
1467 void Stop() override {
1468 state.polling = false;
1469 }
1470
1471protected:
1472 SDLState& state;
1473};
1474
1475class SDLButtonPoller final : public SDLPoller {
1476public:
1477 explicit SDLButtonPoller(SDLState& state_) : SDLPoller(state_) {}
1478
1479 Common::ParamPackage GetNextInput() override {
1480 SDL_Event event;
1481 while (state.event_queue.Pop(event)) {
1482 const auto package = FromEvent(event);
1483 if (package) {
1484 return *package;
1485 }
1486 }
1487 return {};
1488 }
1489 [[nodiscard]] std::optional<Common::ParamPackage> FromEvent(SDL_Event& event) {
1490 switch (event.type) {
1491 case SDL_JOYAXISMOTION:
1492 if (!axis_memory.count(event.jaxis.which) ||
1493 !axis_memory[event.jaxis.which].count(event.jaxis.axis)) {
1494 axis_memory[event.jaxis.which][event.jaxis.axis] = event.jaxis.value;
1495 axis_event_count[event.jaxis.which][event.jaxis.axis] = 1;
1496 break;
1497 } else {
1498 axis_event_count[event.jaxis.which][event.jaxis.axis]++;
1499 // The joystick and axis exist in our map if we take this branch, so no checks
1500 // needed
1501 if (std::abs(
1502 (event.jaxis.value - axis_memory[event.jaxis.which][event.jaxis.axis]) /
1503 32767.0) < 0.5) {
1504 break;
1505 } else {
1506 if (axis_event_count[event.jaxis.which][event.jaxis.axis] == 2 &&
1507 IsAxisAtPole(event.jaxis.value) &&
1508 IsAxisAtPole(axis_memory[event.jaxis.which][event.jaxis.axis])) {
1509 // If we have exactly two events and both are near a pole, this is
1510 // likely a digital input masquerading as an analog axis; Instead of
1511 // trying to look at the direction the axis travelled, assume the first
1512 // event was press and the second was release; This should handle most
1513 // digital axes while deferring to the direction of travel for analog
1514 // axes
1515 event.jaxis.value = static_cast<Sint16>(
1516 std::copysign(32767, axis_memory[event.jaxis.which][event.jaxis.axis]));
1517 } else {
1518 // There are more than two events, so this is likely a true analog axis,
1519 // check the direction it travelled
1520 event.jaxis.value = static_cast<Sint16>(std::copysign(
1521 32767,
1522 event.jaxis.value - axis_memory[event.jaxis.which][event.jaxis.axis]));
1523 }
1524 axis_memory.clear();
1525 axis_event_count.clear();
1526 }
1527 }
1528 [[fallthrough]];
1529 case SDL_JOYBUTTONUP:
1530 case SDL_JOYHATMOTION:
1531 return {SDLEventToButtonParamPackage(state, event)};
1532 }
1533 return std::nullopt;
1534 }
1535
1536private:
1537 // Determine whether an axis value is close to an extreme or center
1538 // Some controllers have a digital D-Pad as a pair of analog sticks, with 3 possible values per
1539 // axis, which is why the center must be considered a pole
1540 bool IsAxisAtPole(int16_t value) const {
1541 return std::abs(value) >= 32767 || std::abs(value) < 327;
1542 }
1543 std::unordered_map<SDL_JoystickID, std::unordered_map<uint8_t, int16_t>> axis_memory;
1544 std::unordered_map<SDL_JoystickID, std::unordered_map<uint8_t, uint32_t>> axis_event_count;
1545};
1546
1547class SDLMotionPoller final : public SDLPoller {
1548public:
1549 explicit SDLMotionPoller(SDLState& state_) : SDLPoller(state_) {}
1550
1551 Common::ParamPackage GetNextInput() override {
1552 SDL_Event event;
1553 while (state.event_queue.Pop(event)) {
1554 const auto package = FromEvent(event);
1555 if (package) {
1556 return *package;
1557 }
1558 }
1559 return {};
1560 }
1561 [[nodiscard]] std::optional<Common::ParamPackage> FromEvent(const SDL_Event& event) const {
1562 switch (event.type) {
1563 case SDL_JOYAXISMOTION:
1564 if (std::abs(event.jaxis.value / 32767.0) < 0.5) {
1565 break;
1566 }
1567 [[fallthrough]];
1568 case SDL_JOYBUTTONUP:
1569 case SDL_JOYHATMOTION:
1570 case SDL_CONTROLLERSENSORUPDATE:
1571 return {SDLEventToMotionParamPackage(state, event)};
1572 }
1573 return std::nullopt;
1574 }
1575};
1576
1577/**
1578 * Attempts to match the press to a controller joy axis (left/right stick) and if a match
1579 * isn't found, checks if the event matches anything from SDLButtonPoller and uses that
1580 * instead
1581 */
1582class SDLAnalogPreferredPoller final : public SDLPoller {
1583public:
1584 explicit SDLAnalogPreferredPoller(SDLState& state_)
1585 : SDLPoller(state_), button_poller(state_) {}
1586
1587 void Start(const std::string& device_id) override {
1588 SDLPoller::Start(device_id);
1589 // Reset stored axes
1590 first_axis = -1;
1591 }
1592
1593 Common::ParamPackage GetNextInput() override {
1594 SDL_Event event;
1595 while (state.event_queue.Pop(event)) {
1596 if (event.type != SDL_JOYAXISMOTION) {
1597 // Check for a button press
1598 auto button_press = button_poller.FromEvent(event);
1599 if (button_press) {
1600 return *button_press;
1601 }
1602 continue;
1603 }
1604 const auto axis = event.jaxis.axis;
1605
1606 // Filter out axis events that are below a threshold
1607 if (std::abs(event.jaxis.value / 32767.0) < 0.5) {
1608 continue;
1609 }
1610
1611 // Filter out axis events that are the same
1612 if (first_axis == axis) {
1613 continue;
1614 }
1615
1616 // In order to return a complete analog param, we need inputs for both axes.
1617 // If the first axis isn't set we set the value then wait till next event
1618 if (first_axis == -1) {
1619 first_axis = axis;
1620 continue;
1621 }
1622
1623 if (const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which)) {
1624 // Set offset to zero since the joystick is not on center
1625 auto params = BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(),
1626 first_axis, axis, 0, 0);
1627 first_axis = -1;
1628 return params;
1629 }
1630 }
1631 return {};
1632 }
1633
1634private:
1635 int first_axis = -1;
1636 SDLButtonPoller button_poller;
1637};
1638} // namespace Polling
1639
1640SDLState::Pollers SDLState::GetPollers(InputCommon::Polling::DeviceType type) {
1641 Pollers pollers;
1642
1643 switch (type) {
1644 case InputCommon::Polling::DeviceType::AnalogPreferred:
1645 pollers.emplace_back(std::make_unique<Polling::SDLAnalogPreferredPoller>(*this));
1646 break;
1647 case InputCommon::Polling::DeviceType::Button:
1648 pollers.emplace_back(std::make_unique<Polling::SDLButtonPoller>(*this));
1649 break;
1650 case InputCommon::Polling::DeviceType::Motion:
1651 pollers.emplace_back(std::make_unique<Polling::SDLMotionPoller>(*this));
1652 break;
1653 }
1654
1655 return pollers;
1656}
1657
1658} // 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 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2+
3// Refer to the license.txt file included.
4
5#include <cstring>
6#include <regex>
7
8#include "common/fs/file.h"
9#include "common/fs/fs_types.h"
10#include "common/fs/path_util.h"
11#include "common/logging/log.h"
12#include "common/settings.h"
13#include "input_common/tas/tas_input.h"
14
15namespace TasInput {
16
17// Supported keywords and buttons from a TAS file
18constexpr std::array<std::pair<std::string_view, TasButton>, 20> text_to_tas_button = {
19 std::pair{"KEY_A", TasButton::BUTTON_A},
20 {"KEY_B", TasButton::BUTTON_B},
21 {"KEY_X", TasButton::BUTTON_X},
22 {"KEY_Y", TasButton::BUTTON_Y},
23 {"KEY_LSTICK", TasButton::STICK_L},
24 {"KEY_RSTICK", TasButton::STICK_R},
25 {"KEY_L", TasButton::TRIGGER_L},
26 {"KEY_R", TasButton::TRIGGER_R},
27 {"KEY_PLUS", TasButton::BUTTON_PLUS},
28 {"KEY_MINUS", TasButton::BUTTON_MINUS},
29 {"KEY_DLEFT", TasButton::BUTTON_LEFT},
30 {"KEY_DUP", TasButton::BUTTON_UP},
31 {"KEY_DRIGHT", TasButton::BUTTON_RIGHT},
32 {"KEY_DDOWN", TasButton::BUTTON_DOWN},
33 {"KEY_SL", TasButton::BUTTON_SL},
34 {"KEY_SR", TasButton::BUTTON_SR},
35 {"KEY_CAPTURE", TasButton::BUTTON_CAPTURE},
36 {"KEY_HOME", TasButton::BUTTON_HOME},
37 {"KEY_ZL", TasButton::TRIGGER_ZL},
38 {"KEY_ZR", TasButton::TRIGGER_ZR},
39};
40
41Tas::Tas() {
42 if (!Settings::values.tas_enable) {
43 needs_reset = true;
44 return;
45 }
46 LoadTasFiles();
47}
48
49Tas::~Tas() {
50 Stop();
51};
52
53void Tas::LoadTasFiles() {
54 script_length = 0;
55 for (size_t i = 0; i < commands.size(); i++) {
56 LoadTasFile(i);
57 if (commands[i].size() > script_length) {
58 script_length = commands[i].size();
59 }
60 }
61}
62
63void Tas::LoadTasFile(size_t player_index) {
64 if (!commands[player_index].empty()) {
65 commands[player_index].clear();
66 }
67 std::string file =
68 Common::FS::ReadStringFromFile(Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) /
69 fmt::format("script0-{}.txt", player_index + 1),
70 Common::FS::FileType::BinaryFile);
71 std::stringstream command_line(file);
72 std::string line;
73 int frame_no = 0;
74 while (std::getline(command_line, line, '\n')) {
75 if (line.empty()) {
76 continue;
77 }
78 LOG_DEBUG(Input, "Loading line: {}", line);
79 std::smatch m;
80
81 std::stringstream linestream(line);
82 std::string segment;
83 std::vector<std::string> seglist;
84
85 while (std::getline(linestream, segment, ' ')) {
86 seglist.push_back(segment);
87 }
88
89 if (seglist.size() < 4) {
90 continue;
91 }
92
93 while (frame_no < std::stoi(seglist.at(0))) {
94 commands[player_index].push_back({});
95 frame_no++;
96 }
97
98 TASCommand command = {
99 .buttons = ReadCommandButtons(seglist.at(1)),
100 .l_axis = ReadCommandAxis(seglist.at(2)),
101 .r_axis = ReadCommandAxis(seglist.at(3)),
102 };
103 commands[player_index].push_back(command);
104 frame_no++;
105 }
106 LOG_INFO(Input, "TAS file loaded! {} frames", frame_no);
107}
108
109void Tas::WriteTasFile(std::u8string file_name) {
110 std::string output_text;
111 for (size_t frame = 0; frame < record_commands.size(); frame++) {
112 if (!output_text.empty()) {
113 output_text += "\n";
114 }
115 const TASCommand& line = record_commands[frame];
116 output_text += std::to_string(frame) + " " + WriteCommandButtons(line.buttons) + " " +
117 WriteCommandAxis(line.l_axis) + " " + WriteCommandAxis(line.r_axis);
118 }
119 const auto bytes_written = Common::FS::WriteStringToFile(
120 Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) / file_name,
121 Common::FS::FileType::TextFile, output_text);
122 if (bytes_written == output_text.size()) {
123 LOG_INFO(Input, "TAS file written to file!");
124 } else {
125 LOG_ERROR(Input, "Writing the TAS-file has failed! {} / {} bytes written", bytes_written,
126 output_text.size());
127 }
128}
129
130std::pair<float, float> Tas::FlipAxisY(std::pair<float, float> old) {
131 auto [x, y] = old;
132 return {x, -y};
133}
134
135void Tas::RecordInput(u32 buttons, const std::array<std::pair<float, float>, 2>& axes) {
136 last_input = {buttons, FlipAxisY(axes[0]), FlipAxisY(axes[1])};
137}
138
139std::tuple<TasState, size_t, size_t> Tas::GetStatus() const {
140 TasState state;
141 if (is_recording) {
142 return {TasState::Recording, 0, record_commands.size()};
143 }
144
145 if (is_running) {
146 state = TasState::Running;
147 } else {
148 state = TasState::Stopped;
149 }
150
151 return {state, current_command, script_length};
152}
153
154std::string Tas::DebugButtons(u32 buttons) const {
155 return fmt::format("{{ {} }}", TasInput::Tas::ButtonsToString(buttons));
156}
157
158std::string Tas::DebugJoystick(float x, float y) const {
159 return fmt::format("[ {} , {} ]", std::to_string(x), std::to_string(y));
160}
161
162std::string Tas::DebugInput(const TasData& data) const {
163 return fmt::format("{{ {} , {} , {} }}", DebugButtons(data.buttons),
164 DebugJoystick(data.axis[0], data.axis[1]),
165 DebugJoystick(data.axis[2], data.axis[3]));
166}
167
168std::string Tas::DebugInputs(const std::array<TasData, PLAYER_NUMBER>& arr) const {
169 std::string returns = "[ ";
170 for (size_t i = 0; i < arr.size(); i++) {
171 returns += DebugInput(arr[i]);
172 if (i != arr.size() - 1) {
173 returns += " , ";
174 }
175 }
176 return returns + "]";
177}
178
179std::string Tas::ButtonsToString(u32 button) const {
180 std::string returns;
181 for (auto [text_button, tas_button] : text_to_tas_button) {
182 if ((button & static_cast<u32>(tas_button)) != 0)
183 returns += fmt::format(", {}", text_button.substr(4));
184 }
185 return returns.empty() ? "" : returns.substr(2);
186}
187
188void Tas::UpdateThread() {
189 if (!Settings::values.tas_enable) {
190 if (is_running) {
191 Stop();
192 }
193 return;
194 }
195
196 if (is_recording) {
197 record_commands.push_back(last_input);
198 }
199 if (needs_reset) {
200 current_command = 0;
201 needs_reset = false;
202 LoadTasFiles();
203 LOG_DEBUG(Input, "tas_reset done");
204 }
205
206 if (!is_running) {
207 tas_data.fill({});
208 return;
209 }
210 if (current_command < script_length) {
211 LOG_DEBUG(Input, "Playing TAS {}/{}", current_command, script_length);
212 size_t frame = current_command++;
213 for (size_t i = 0; i < commands.size(); i++) {
214 if (frame < commands[i].size()) {
215 TASCommand command = commands[i][frame];
216 tas_data[i].buttons = command.buttons;
217 auto [l_axis_x, l_axis_y] = command.l_axis;
218 tas_data[i].axis[0] = l_axis_x;
219 tas_data[i].axis[1] = l_axis_y;
220 auto [r_axis_x, r_axis_y] = command.r_axis;
221 tas_data[i].axis[2] = r_axis_x;
222 tas_data[i].axis[3] = r_axis_y;
223 } else {
224 tas_data[i] = {};
225 }
226 }
227 } else {
228 is_running = Settings::values.tas_loop.GetValue();
229 current_command = 0;
230 tas_data.fill({});
231 if (!is_running) {
232 SwapToStoredController();
233 }
234 }
235 LOG_DEBUG(Input, "TAS inputs: {}", DebugInputs(tas_data));
236}
237
238TasAnalog Tas::ReadCommandAxis(const std::string& line) const {
239 std::stringstream linestream(line);
240 std::string segment;
241 std::vector<std::string> seglist;
242
243 while (std::getline(linestream, segment, ';')) {
244 seglist.push_back(segment);
245 }
246
247 const float x = std::stof(seglist.at(0)) / 32767.0f;
248 const float y = std::stof(seglist.at(1)) / 32767.0f;
249
250 return {x, y};
251}
252
253u32 Tas::ReadCommandButtons(const std::string& data) const {
254 std::stringstream button_text(data);
255 std::string line;
256 u32 buttons = 0;
257 while (std::getline(button_text, line, ';')) {
258 for (auto [text, tas_button] : text_to_tas_button) {
259 if (text == line) {
260 buttons |= static_cast<u32>(tas_button);
261 break;
262 }
263 }
264 }
265 return buttons;
266}
267
268std::string Tas::WriteCommandAxis(TasAnalog data) const {
269 auto [x, y] = data;
270 std::string line;
271 line += std::to_string(static_cast<int>(x * 32767));
272 line += ";";
273 line += std::to_string(static_cast<int>(y * 32767));
274 return line;
275}
276
277std::string Tas::WriteCommandButtons(u32 data) const {
278 if (data == 0) {
279 return "NONE";
280 }
281
282 std::string line;
283 u32 index = 0;
284 while (data > 0) {
285 if ((data & 1) == 1) {
286 for (auto [text, tas_button] : text_to_tas_button) {
287 if (tas_button == static_cast<TasButton>(1 << index)) {
288 if (line.size() > 0) {
289 line += ";";
290 }
291 line += text;
292 break;
293 }
294 }
295 }
296 index++;
297 data >>= 1;
298 }
299 return line;
300}
301
302void Tas::StartStop() {
303 if (!Settings::values.tas_enable) {
304 return;
305 }
306 if (is_running) {
307 Stop();
308 } else {
309 is_running = true;
310 SwapToTasController();
311 }
312}
313
314void Tas::Stop() {
315 is_running = false;
316 SwapToStoredController();
317}
318
319void Tas::SwapToTasController() {
320 if (!Settings::values.tas_swap_controllers) {
321 return;
322 }
323 auto& players = Settings::values.players.GetValue();
324 for (std::size_t index = 0; index < players.size(); index++) {
325 auto& player = players[index];
326 player_mappings[index] = player;
327
328 // Only swap active controllers
329 if (!player.connected) {
330 continue;
331 }
332
333 Common::ParamPackage tas_param;
334 tas_param.Set("pad", static_cast<u8>(index));
335 auto button_mapping = GetButtonMappingForDevice(tas_param);
336 auto analog_mapping = GetAnalogMappingForDevice(tas_param);
337 auto& buttons = player.buttons;
338 auto& analogs = player.analogs;
339
340 for (std::size_t i = 0; i < buttons.size(); ++i) {
341 buttons[i] = button_mapping[static_cast<Settings::NativeButton::Values>(i)].Serialize();
342 }
343 for (std::size_t i = 0; i < analogs.size(); ++i) {
344 analogs[i] = analog_mapping[static_cast<Settings::NativeAnalog::Values>(i)].Serialize();
345 }
346 }
347 is_old_input_saved = true;
348 Settings::values.is_device_reload_pending.store(true);
349}
350
351void Tas::SwapToStoredController() {
352 if (!is_old_input_saved) {
353 return;
354 }
355 auto& players = Settings::values.players.GetValue();
356 for (std::size_t index = 0; index < players.size(); index++) {
357 players[index] = player_mappings[index];
358 }
359 is_old_input_saved = false;
360 Settings::values.is_device_reload_pending.store(true);
361}
362
363void Tas::Reset() {
364 if (!Settings::values.tas_enable) {
365 return;
366 }
367 needs_reset = true;
368}
369
370bool Tas::Record() {
371 if (!Settings::values.tas_enable) {
372 return true;
373 }
374 is_recording = !is_recording;
375 return is_recording;
376}
377
378void Tas::SaveRecording(bool overwrite_file) {
379 if (is_recording) {
380 return;
381 }
382 if (record_commands.empty()) {
383 return;
384 }
385 WriteTasFile(u8"record.txt");
386 if (overwrite_file) {
387 WriteTasFile(u8"script0-1.txt");
388 }
389 needs_reset = true;
390 record_commands.clear();
391}
392
393InputCommon::ButtonMapping Tas::GetButtonMappingForDevice(
394 const Common::ParamPackage& params) const {
395 // This list is missing ZL/ZR since those are not considered buttons.
396 // We will add those afterwards
397 // This list also excludes any button that can't be really mapped
398 static constexpr std::array<std::pair<Settings::NativeButton::Values, TasButton>, 20>
399 switch_to_tas_button = {
400 std::pair{Settings::NativeButton::A, TasButton::BUTTON_A},
401 {Settings::NativeButton::B, TasButton::BUTTON_B},
402 {Settings::NativeButton::X, TasButton::BUTTON_X},
403 {Settings::NativeButton::Y, TasButton::BUTTON_Y},
404 {Settings::NativeButton::LStick, TasButton::STICK_L},
405 {Settings::NativeButton::RStick, TasButton::STICK_R},
406 {Settings::NativeButton::L, TasButton::TRIGGER_L},
407 {Settings::NativeButton::R, TasButton::TRIGGER_R},
408 {Settings::NativeButton::Plus, TasButton::BUTTON_PLUS},
409 {Settings::NativeButton::Minus, TasButton::BUTTON_MINUS},
410 {Settings::NativeButton::DLeft, TasButton::BUTTON_LEFT},
411 {Settings::NativeButton::DUp, TasButton::BUTTON_UP},
412 {Settings::NativeButton::DRight, TasButton::BUTTON_RIGHT},
413 {Settings::NativeButton::DDown, TasButton::BUTTON_DOWN},
414 {Settings::NativeButton::SL, TasButton::BUTTON_SL},
415 {Settings::NativeButton::SR, TasButton::BUTTON_SR},
416 {Settings::NativeButton::Screenshot, TasButton::BUTTON_CAPTURE},
417 {Settings::NativeButton::Home, TasButton::BUTTON_HOME},
418 {Settings::NativeButton::ZL, TasButton::TRIGGER_ZL},
419 {Settings::NativeButton::ZR, TasButton::TRIGGER_ZR},
420 };
421
422 InputCommon::ButtonMapping mapping{};
423 for (const auto& [switch_button, tas_button] : switch_to_tas_button) {
424 Common::ParamPackage button_params({{"engine", "tas"}});
425 button_params.Set("pad", params.Get("pad", 0));
426 button_params.Set("button", static_cast<int>(tas_button));
427 mapping.insert_or_assign(switch_button, std::move(button_params));
428 }
429
430 return mapping;
431}
432
433InputCommon::AnalogMapping Tas::GetAnalogMappingForDevice(
434 const Common::ParamPackage& params) const {
435
436 InputCommon::AnalogMapping mapping = {};
437 Common::ParamPackage left_analog_params;
438 left_analog_params.Set("engine", "tas");
439 left_analog_params.Set("pad", params.Get("pad", 0));
440 left_analog_params.Set("axis_x", static_cast<int>(TasAxes::StickX));
441 left_analog_params.Set("axis_y", static_cast<int>(TasAxes::StickY));
442 mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params));
443 Common::ParamPackage right_analog_params;
444 right_analog_params.Set("engine", "tas");
445 right_analog_params.Set("pad", params.Get("pad", 0));
446 right_analog_params.Set("axis_x", static_cast<int>(TasAxes::SubstickX));
447 right_analog_params.Set("axis_y", static_cast<int>(TasAxes::SubstickY));
448 mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params));
449 return mapping;
450}
451
452const TasData& Tas::GetTasState(std::size_t pad) const {
453 return tas_data[pad];
454}
455} // 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 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <mutex>
6#include <utility>
7
8#include "common/settings.h"
9#include "common/threadsafe_queue.h"
10#include "input_common/tas/tas_input.h"
11#include "input_common/tas/tas_poller.h"
12
13namespace InputCommon {
14
15class TasButton final : public Input::ButtonDevice {
16public:
17 explicit TasButton(u32 button_, u32 pad_, const TasInput::Tas* tas_input_)
18 : button(button_), pad(pad_), tas_input(tas_input_) {}
19
20 bool GetStatus() const override {
21 return (tas_input->GetTasState(pad).buttons & button) != 0;
22 }
23
24private:
25 const u32 button;
26 const u32 pad;
27 const TasInput::Tas* tas_input;
28};
29
30TasButtonFactory::TasButtonFactory(std::shared_ptr<TasInput::Tas> tas_input_)
31 : tas_input(std::move(tas_input_)) {}
32
33std::unique_ptr<Input::ButtonDevice> TasButtonFactory::Create(const Common::ParamPackage& params) {
34 const auto button_id = params.Get("button", 0);
35 const auto pad = params.Get("pad", 0);
36
37 return std::make_unique<TasButton>(button_id, pad, tas_input.get());
38}
39
40class TasAnalog final : public Input::AnalogDevice {
41public:
42 explicit TasAnalog(u32 pad_, u32 axis_x_, u32 axis_y_, const TasInput::Tas* tas_input_)
43 : pad(pad_), axis_x(axis_x_), axis_y(axis_y_), tas_input(tas_input_) {}
44
45 float GetAxis(u32 axis) const {
46 std::lock_guard lock{mutex};
47 return tas_input->GetTasState(pad).axis.at(axis);
48 }
49
50 std::pair<float, float> GetAnalog(u32 analog_axis_x, u32 analog_axis_y) const {
51 float x = GetAxis(analog_axis_x);
52 float y = GetAxis(analog_axis_y);
53
54 // Make sure the coordinates are in the unit circle,
55 // otherwise normalize it.
56 float r = x * x + y * y;
57 if (r > 1.0f) {
58 r = std::sqrt(r);
59 x /= r;
60 y /= r;
61 }
62
63 return {x, y};
64 }
65
66 std::tuple<float, float> GetStatus() const override {
67 return GetAnalog(axis_x, axis_y);
68 }
69
70 Input::AnalogProperties GetAnalogProperties() const override {
71 return {0.0f, 1.0f, 0.5f};
72 }
73
74private:
75 const u32 pad;
76 const u32 axis_x;
77 const u32 axis_y;
78 const TasInput::Tas* tas_input;
79 mutable std::mutex mutex;
80};
81
82/// An analog device factory that creates analog devices from GC Adapter
83TasAnalogFactory::TasAnalogFactory(std::shared_ptr<TasInput::Tas> tas_input_)
84 : tas_input(std::move(tas_input_)) {}
85
86/**
87 * Creates analog device from joystick axes
88 * @param params contains parameters for creating the device:
89 * - "port": the nth gcpad on the adapter
90 * - "axis_x": the index of the axis to be bind as x-axis
91 * - "axis_y": the index of the axis to be bind as y-axis
92 */
93std::unique_ptr<Input::AnalogDevice> TasAnalogFactory::Create(const Common::ParamPackage& params) {
94 const auto pad = static_cast<u32>(params.Get("pad", 0));
95 const auto axis_x = static_cast<u32>(params.Get("axis_x", 0));
96 const auto axis_y = static_cast<u32>(params.Get("axis_y", 1));
97
98 return std::make_unique<TasAnalog>(pad, axis_x, axis_y, tas_input.get());
99}
100
101} // 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 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include "core/frontend/input.h"
9#include "input_common/tas/tas_input.h"
10
11namespace InputCommon {
12
13/**
14 * A button device factory representing a tas bot. It receives tas events and forward them
15 * to all button devices it created.
16 */
17class TasButtonFactory final : public Input::Factory<Input::ButtonDevice> {
18public:
19 explicit TasButtonFactory(std::shared_ptr<TasInput::Tas> tas_input_);
20
21 /**
22 * Creates a button device from a button press
23 * @param params contains parameters for creating the device:
24 * - "code": the code of the key to bind with the button
25 */
26 std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override;
27
28private:
29 std::shared_ptr<TasInput::Tas> tas_input;
30};
31
32/// An analog device factory that creates analog devices from tas
33class TasAnalogFactory final : public Input::Factory<Input::AnalogDevice> {
34public:
35 explicit TasAnalogFactory(std::shared_ptr<TasInput::Tas> tas_input_);
36
37 std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override;
38
39private:
40 std::shared_ptr<TasInput::Tas> tas_input;
41};
42
43} // 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 @@
1// Copyright 2020 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <algorithm>
6#include "common/settings.h"
7#include "core/frontend/framebuffer_layout.h"
8#include "input_common/touch_from_button.h"
9
10namespace InputCommon {
11
12class TouchFromButtonDevice final : public Input::TouchDevice {
13public:
14 TouchFromButtonDevice() {
15 const auto button_index =
16 static_cast<u64>(Settings::values.touch_from_button_map_index.GetValue());
17 const auto& buttons = Settings::values.touch_from_button_maps[button_index].buttons;
18
19 for (const auto& config_entry : buttons) {
20 const Common::ParamPackage package{config_entry};
21 map.emplace_back(
22 Input::CreateDevice<Input::ButtonDevice>(config_entry),
23 std::clamp(package.Get("x", 0), 0, static_cast<int>(Layout::ScreenUndocked::Width)),
24 std::clamp(package.Get("y", 0), 0,
25 static_cast<int>(Layout::ScreenUndocked::Height)));
26 }
27 }
28
29 Input::TouchStatus GetStatus() const override {
30 Input::TouchStatus touch_status{};
31 for (std::size_t id = 0; id < map.size() && id < touch_status.size(); ++id) {
32 const bool state = std::get<0>(map[id])->GetStatus();
33 if (state) {
34 const float x = static_cast<float>(std::get<1>(map[id])) /
35 static_cast<int>(Layout::ScreenUndocked::Width);
36 const float y = static_cast<float>(std::get<2>(map[id])) /
37 static_cast<int>(Layout::ScreenUndocked::Height);
38 touch_status[id] = {x, y, true};
39 }
40 }
41 return touch_status;
42 }
43
44private:
45 // A vector of the mapped button, its x and its y-coordinate
46 std::vector<std::tuple<std::unique_ptr<Input::ButtonDevice>, int, int>> map;
47};
48
49std::unique_ptr<Input::TouchDevice> TouchFromButtonFactory::Create(const Common::ParamPackage&) {
50 return std::make_unique<TouchFromButtonDevice>();
51}
52
53} // 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 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <chrono>
6#include <cstring>
7#include <functional>
8#include <random>
9#include <thread>
10#include <boost/asio.hpp>
11#include "common/logging/log.h"
12#include "common/settings.h"
13#include "input_common/udp/client.h"
14#include "input_common/udp/protocol.h"
15
16using boost::asio::ip::udp;
17
18namespace InputCommon::CemuhookUDP {
19
20struct SocketCallback {
21 std::function<void(Response::Version)> version;
22 std::function<void(Response::PortInfo)> port_info;
23 std::function<void(Response::PadData)> pad_data;
24};
25
26class Socket {
27public:
28 using clock = std::chrono::system_clock;
29
30 explicit Socket(const std::string& host, u16 port, SocketCallback callback_)
31 : callback(std::move(callback_)), timer(io_service),
32 socket(io_service, udp::endpoint(udp::v4(), 0)), client_id(GenerateRandomClientId()) {
33 boost::system::error_code ec{};
34 auto ipv4 = boost::asio::ip::make_address_v4(host, ec);
35 if (ec.value() != boost::system::errc::success) {
36 LOG_ERROR(Input, "Invalid IPv4 address \"{}\" provided to socket", host);
37 ipv4 = boost::asio::ip::address_v4{};
38 }
39
40 send_endpoint = {udp::endpoint(ipv4, port)};
41 }
42
43 void Stop() {
44 io_service.stop();
45 }
46
47 void Loop() {
48 io_service.run();
49 }
50
51 void StartSend(const clock::time_point& from) {
52 timer.expires_at(from + std::chrono::seconds(3));
53 timer.async_wait([this](const boost::system::error_code& error) { HandleSend(error); });
54 }
55
56 void StartReceive() {
57 socket.async_receive_from(
58 boost::asio::buffer(receive_buffer), receive_endpoint,
59 [this](const boost::system::error_code& error, std::size_t bytes_transferred) {
60 HandleReceive(error, bytes_transferred);
61 });
62 }
63
64private:
65 u32 GenerateRandomClientId() const {
66 std::random_device device;
67 return device();
68 }
69
70 void HandleReceive(const boost::system::error_code&, std::size_t bytes_transferred) {
71 if (auto type = Response::Validate(receive_buffer.data(), bytes_transferred)) {
72 switch (*type) {
73 case Type::Version: {
74 Response::Version version;
75 std::memcpy(&version, &receive_buffer[sizeof(Header)], sizeof(Response::Version));
76 callback.version(std::move(version));
77 break;
78 }
79 case Type::PortInfo: {
80 Response::PortInfo port_info;
81 std::memcpy(&port_info, &receive_buffer[sizeof(Header)],
82 sizeof(Response::PortInfo));
83 callback.port_info(std::move(port_info));
84 break;
85 }
86 case Type::PadData: {
87 Response::PadData pad_data;
88 std::memcpy(&pad_data, &receive_buffer[sizeof(Header)], sizeof(Response::PadData));
89 SanitizeMotion(pad_data);
90 callback.pad_data(std::move(pad_data));
91 break;
92 }
93 }
94 }
95 StartReceive();
96 }
97
98 void HandleSend(const boost::system::error_code&) {
99 boost::system::error_code _ignored{};
100 // Send a request for getting port info for the pad
101 const Request::PortInfo port_info{4, {0, 1, 2, 3}};
102 const auto port_message = Request::Create(port_info, client_id);
103 std::memcpy(&send_buffer1, &port_message, PORT_INFO_SIZE);
104 socket.send_to(boost::asio::buffer(send_buffer1), send_endpoint, {}, _ignored);
105
106 // Send a request for getting pad data for the pad
107 const Request::PadData pad_data{
108 Request::PadData::Flags::AllPorts,
109 0,
110 EMPTY_MAC_ADDRESS,
111 };
112 const auto pad_message = Request::Create(pad_data, client_id);
113 std::memcpy(send_buffer2.data(), &pad_message, PAD_DATA_SIZE);
114 socket.send_to(boost::asio::buffer(send_buffer2), send_endpoint, {}, _ignored);
115 StartSend(timer.expiry());
116 }
117
118 void SanitizeMotion(Response::PadData& data) {
119 // Zero out any non number value
120 if (!std::isnormal(data.gyro.pitch)) {
121 data.gyro.pitch = 0;
122 }
123 if (!std::isnormal(data.gyro.roll)) {
124 data.gyro.roll = 0;
125 }
126 if (!std::isnormal(data.gyro.yaw)) {
127 data.gyro.yaw = 0;
128 }
129 if (!std::isnormal(data.accel.x)) {
130 data.accel.x = 0;
131 }
132 if (!std::isnormal(data.accel.y)) {
133 data.accel.y = 0;
134 }
135 if (!std::isnormal(data.accel.z)) {
136 data.accel.z = 0;
137 }
138 }
139
140 SocketCallback callback;
141 boost::asio::io_service io_service;
142 boost::asio::basic_waitable_timer<clock> timer;
143 udp::socket socket;
144
145 const u32 client_id;
146
147 static constexpr std::size_t PORT_INFO_SIZE = sizeof(Message<Request::PortInfo>);
148 static constexpr std::size_t PAD_DATA_SIZE = sizeof(Message<Request::PadData>);
149 std::array<u8, PORT_INFO_SIZE> send_buffer1;
150 std::array<u8, PAD_DATA_SIZE> send_buffer2;
151 udp::endpoint send_endpoint;
152
153 std::array<u8, MAX_PACKET_SIZE> receive_buffer;
154 udp::endpoint receive_endpoint;
155};
156
157static void SocketLoop(Socket* socket) {
158 socket->StartReceive();
159 socket->StartSend(Socket::clock::now());
160 socket->Loop();
161}
162
163Client::Client() {
164 LOG_INFO(Input, "Udp Initialization started");
165 finger_id.fill(MAX_TOUCH_FINGERS);
166 ReloadSockets();
167}
168
169Client::~Client() {
170 Reset();
171}
172
173Client::ClientConnection::ClientConnection() = default;
174
175Client::ClientConnection::~ClientConnection() = default;
176
177std::vector<Common::ParamPackage> Client::GetInputDevices() const {
178 std::vector<Common::ParamPackage> devices;
179 for (std::size_t pad = 0; pad < pads.size(); pad++) {
180 if (!DeviceConnected(pad)) {
181 continue;
182 }
183 std::string name = fmt::format("UDP Controller {}", pad);
184 devices.emplace_back(Common::ParamPackage{
185 {"class", "cemuhookudp"},
186 {"display", std::move(name)},
187 {"port", std::to_string(pad)},
188 });
189 }
190 return devices;
191}
192
193bool Client::DeviceConnected(std::size_t pad) const {
194 // Use last timestamp to detect if the socket has stopped sending data
195 const auto now = std::chrono::steady_clock::now();
196 const auto time_difference = static_cast<u64>(
197 std::chrono::duration_cast<std::chrono::milliseconds>(now - pads[pad].last_update).count());
198 return time_difference < 1000 && pads[pad].connected;
199}
200
201void Client::ReloadSockets() {
202 Reset();
203
204 std::stringstream servers_ss(static_cast<std::string>(Settings::values.udp_input_servers));
205 std::string server_token;
206 std::size_t client = 0;
207 while (std::getline(servers_ss, server_token, ',')) {
208 if (client == MAX_UDP_CLIENTS) {
209 break;
210 }
211 std::stringstream server_ss(server_token);
212 std::string token;
213 std::getline(server_ss, token, ':');
214 std::string udp_input_address = token;
215 std::getline(server_ss, token, ':');
216 char* temp;
217 const u16 udp_input_port = static_cast<u16>(std::strtol(token.c_str(), &temp, 0));
218 if (*temp != '\0') {
219 LOG_ERROR(Input, "Port number is not valid {}", token);
220 continue;
221 }
222
223 const std::size_t client_number = GetClientNumber(udp_input_address, udp_input_port);
224 if (client_number != MAX_UDP_CLIENTS) {
225 LOG_ERROR(Input, "Duplicated UDP servers found");
226 continue;
227 }
228 StartCommunication(client++, udp_input_address, udp_input_port);
229 }
230}
231
232std::size_t Client::GetClientNumber(std::string_view host, u16 port) const {
233 for (std::size_t client = 0; client < clients.size(); client++) {
234 if (clients[client].active == -1) {
235 continue;
236 }
237 if (clients[client].host == host && clients[client].port == port) {
238 return client;
239 }
240 }
241 return MAX_UDP_CLIENTS;
242}
243
244void Client::OnVersion([[maybe_unused]] Response::Version data) {
245 LOG_TRACE(Input, "Version packet received: {}", data.version);
246}
247
248void Client::OnPortInfo([[maybe_unused]] Response::PortInfo data) {
249 LOG_TRACE(Input, "PortInfo packet received: {}", data.model);
250}
251
252void Client::OnPadData(Response::PadData data, std::size_t client) {
253 const std::size_t pad_index = (client * PADS_PER_CLIENT) + data.info.id;
254
255 if (pad_index >= pads.size()) {
256 LOG_ERROR(Input, "Invalid pad id {}", data.info.id);
257 return;
258 }
259
260 LOG_TRACE(Input, "PadData packet received");
261 if (data.packet_counter == pads[pad_index].packet_sequence) {
262 LOG_WARNING(
263 Input,
264 "PadData packet dropped because its stale info. Current count: {} Packet count: {}",
265 pads[pad_index].packet_sequence, data.packet_counter);
266 pads[pad_index].connected = false;
267 return;
268 }
269
270 clients[client].active = 1;
271 pads[pad_index].connected = true;
272 pads[pad_index].packet_sequence = data.packet_counter;
273
274 const auto now = std::chrono::steady_clock::now();
275 const auto time_difference = static_cast<u64>(
276 std::chrono::duration_cast<std::chrono::microseconds>(now - pads[pad_index].last_update)
277 .count());
278 pads[pad_index].last_update = now;
279
280 const Common::Vec3f raw_gyroscope = {data.gyro.pitch, data.gyro.roll, -data.gyro.yaw};
281 pads[pad_index].motion.SetAcceleration({data.accel.x, -data.accel.z, data.accel.y});
282 // Gyroscope values are not it the correct scale from better joy.
283 // Dividing by 312 allows us to make one full turn = 1 turn
284 // This must be a configurable valued called sensitivity
285 pads[pad_index].motion.SetGyroscope(raw_gyroscope / 312.0f);
286 pads[pad_index].motion.UpdateRotation(time_difference);
287 pads[pad_index].motion.UpdateOrientation(time_difference);
288
289 {
290 std::lock_guard guard(pads[pad_index].status.update_mutex);
291 pads[pad_index].status.motion_status = pads[pad_index].motion.GetMotion();
292
293 for (std::size_t id = 0; id < data.touch.size(); ++id) {
294 UpdateTouchInput(data.touch[id], client, id);
295 }
296
297 if (configuring) {
298 const Common::Vec3f gyroscope = pads[pad_index].motion.GetGyroscope();
299 const Common::Vec3f accelerometer = pads[pad_index].motion.GetAcceleration();
300 UpdateYuzuSettings(client, data.info.id, accelerometer, gyroscope);
301 }
302 }
303}
304
305void Client::StartCommunication(std::size_t client, const std::string& host, u16 port) {
306 SocketCallback callback{[this](Response::Version version) { OnVersion(version); },
307 [this](Response::PortInfo info) { OnPortInfo(info); },
308 [this, client](Response::PadData data) { OnPadData(data, client); }};
309 LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port);
310 clients[client].host = host;
311 clients[client].port = port;
312 clients[client].active = 0;
313 clients[client].socket = std::make_unique<Socket>(host, port, callback);
314 clients[client].thread = std::thread{SocketLoop, clients[client].socket.get()};
315
316 // Set motion parameters
317 // SetGyroThreshold value should be dependent on GyroscopeZeroDriftMode
318 // Real HW values are unknown, 0.0001 is an approximate to Standard
319 for (std::size_t pad = 0; pad < PADS_PER_CLIENT; pad++) {
320 pads[client * PADS_PER_CLIENT + pad].motion.SetGyroThreshold(0.0001f);
321 }
322}
323
324void Client::Reset() {
325 for (auto& client : clients) {
326 if (client.thread.joinable()) {
327 client.active = -1;
328 client.socket->Stop();
329 client.thread.join();
330 }
331 }
332}
333
334void Client::UpdateYuzuSettings(std::size_t client, std::size_t pad_index,
335 const Common::Vec3<float>& acc, const Common::Vec3<float>& gyro) {
336 if (gyro.Length() > 0.2f) {
337 LOG_DEBUG(Input, "UDP Controller {}: gyro=({}, {}, {}), accel=({}, {}, {})", client,
338 gyro[0], gyro[1], gyro[2], acc[0], acc[1], acc[2]);
339 }
340 UDPPadStatus pad{
341 .host = clients[client].host,
342 .port = clients[client].port,
343 .pad_index = pad_index,
344 };
345 for (std::size_t i = 0; i < 3; ++i) {
346 if (gyro[i] > 5.0f || gyro[i] < -5.0f) {
347 pad.motion = static_cast<PadMotion>(i);
348 pad.motion_value = gyro[i];
349 pad_queue.Push(pad);
350 }
351 if (acc[i] > 1.75f || acc[i] < -1.75f) {
352 pad.motion = static_cast<PadMotion>(i + 3);
353 pad.motion_value = acc[i];
354 pad_queue.Push(pad);
355 }
356 }
357}
358
359std::optional<std::size_t> Client::GetUnusedFingerID() const {
360 std::size_t first_free_id = 0;
361 while (first_free_id < MAX_TOUCH_FINGERS) {
362 if (!std::get<2>(touch_status[first_free_id])) {
363 return first_free_id;
364 } else {
365 first_free_id++;
366 }
367 }
368 return std::nullopt;
369}
370
371void Client::UpdateTouchInput(Response::TouchPad& touch_pad, std::size_t client, std::size_t id) {
372 // TODO: Use custom calibration per device
373 const Common::ParamPackage touch_param(Settings::values.touch_device.GetValue());
374 const u16 min_x = static_cast<u16>(touch_param.Get("min_x", 100));
375 const u16 min_y = static_cast<u16>(touch_param.Get("min_y", 50));
376 const u16 max_x = static_cast<u16>(touch_param.Get("max_x", 1800));
377 const u16 max_y = static_cast<u16>(touch_param.Get("max_y", 850));
378 const std::size_t touch_id = client * 2 + id;
379 if (touch_pad.is_active) {
380 if (finger_id[touch_id] == MAX_TOUCH_FINGERS) {
381 const auto first_free_id = GetUnusedFingerID();
382 if (!first_free_id) {
383 // Invalid finger id skip to next input
384 return;
385 }
386 finger_id[touch_id] = *first_free_id;
387 }
388 auto& [x, y, pressed] = touch_status[finger_id[touch_id]];
389 x = static_cast<float>(std::clamp(static_cast<u16>(touch_pad.x), min_x, max_x) - min_x) /
390 static_cast<float>(max_x - min_x);
391 y = static_cast<float>(std::clamp(static_cast<u16>(touch_pad.y), min_y, max_y) - min_y) /
392 static_cast<float>(max_y - min_y);
393 pressed = true;
394 return;
395 }
396
397 if (finger_id[touch_id] != MAX_TOUCH_FINGERS) {
398 touch_status[finger_id[touch_id]] = {};
399 finger_id[touch_id] = MAX_TOUCH_FINGERS;
400 }
401}
402
403void Client::BeginConfiguration() {
404 pad_queue.Clear();
405 configuring = true;
406}
407
408void Client::EndConfiguration() {
409 pad_queue.Clear();
410 configuring = false;
411}
412
413DeviceStatus& Client::GetPadState(const std::string& host, u16 port, std::size_t pad) {
414 const std::size_t client_number = GetClientNumber(host, port);
415 if (client_number == MAX_UDP_CLIENTS || pad >= PADS_PER_CLIENT) {
416 return pads[0].status;
417 }
418 return pads[(client_number * PADS_PER_CLIENT) + pad].status;
419}
420
421const DeviceStatus& Client::GetPadState(const std::string& host, u16 port, std::size_t pad) const {
422 const std::size_t client_number = GetClientNumber(host, port);
423 if (client_number == MAX_UDP_CLIENTS || pad >= PADS_PER_CLIENT) {
424 return pads[0].status;
425 }
426 return pads[(client_number * PADS_PER_CLIENT) + pad].status;
427}
428
429Input::TouchStatus& Client::GetTouchState() {
430 return touch_status;
431}
432
433const Input::TouchStatus& Client::GetTouchState() const {
434 return touch_status;
435}
436
437Common::SPSCQueue<UDPPadStatus>& Client::GetPadQueue() {
438 return pad_queue;
439}
440
441const Common::SPSCQueue<UDPPadStatus>& Client::GetPadQueue() const {
442 return pad_queue;
443}
444
445void TestCommunication(const std::string& host, u16 port,
446 const std::function<void()>& success_callback,
447 const std::function<void()>& failure_callback) {
448 std::thread([=] {
449 Common::Event success_event;
450 SocketCallback callback{
451 .version = [](Response::Version) {},
452 .port_info = [](Response::PortInfo) {},
453 .pad_data = [&](Response::PadData) { success_event.Set(); },
454 };
455 Socket socket{host, port, std::move(callback)};
456 std::thread worker_thread{SocketLoop, &socket};
457 const bool result =
458 success_event.WaitUntil(std::chrono::steady_clock::now() + std::chrono::seconds(10));
459 socket.Stop();
460 worker_thread.join();
461 if (result) {
462 success_callback();
463 } else {
464 failure_callback();
465 }
466 }).detach();
467}
468
469CalibrationConfigurationJob::CalibrationConfigurationJob(
470 const std::string& host, u16 port, std::function<void(Status)> status_callback,
471 std::function<void(u16, u16, u16, u16)> data_callback) {
472
473 std::thread([=, this] {
474 Status current_status{Status::Initialized};
475 SocketCallback callback{
476 [](Response::Version) {}, [](Response::PortInfo) {},
477 [&](Response::PadData data) {
478 static constexpr u16 CALIBRATION_THRESHOLD = 100;
479 static constexpr u16 MAX_VALUE = UINT16_MAX;
480
481 if (current_status == Status::Initialized) {
482 // Receiving data means the communication is ready now
483 current_status = Status::Ready;
484 status_callback(current_status);
485 }
486 const auto& touchpad_0 = data.touch[0];
487 if (touchpad_0.is_active == 0) {
488 return;
489 }
490 LOG_DEBUG(Input, "Current touch: {} {}", touchpad_0.x, touchpad_0.y);
491 const u16 min_x = std::min(MAX_VALUE, static_cast<u16>(touchpad_0.x));
492 const u16 min_y = std::min(MAX_VALUE, static_cast<u16>(touchpad_0.y));
493 if (current_status == Status::Ready) {
494 // First touch - min data (min_x/min_y)
495 current_status = Status::Stage1Completed;
496 status_callback(current_status);
497 }
498 if (touchpad_0.x - min_x > CALIBRATION_THRESHOLD &&
499 touchpad_0.y - min_y > CALIBRATION_THRESHOLD) {
500 // Set the current position as max value and finishes configuration
501 const u16 max_x = touchpad_0.x;
502 const u16 max_y = touchpad_0.y;
503 current_status = Status::Completed;
504 data_callback(min_x, min_y, max_x, max_y);
505 status_callback(current_status);
506
507 complete_event.Set();
508 }
509 }};
510 Socket socket{host, port, std::move(callback)};
511 std::thread worker_thread{SocketLoop, &socket};
512 complete_event.Wait();
513 socket.Stop();
514 worker_thread.join();
515 }).detach();
516}
517
518CalibrationConfigurationJob::~CalibrationConfigurationJob() {
519 Stop();
520}
521
522void CalibrationConfigurationJob::Stop() {
523 complete_event.Set();
524}
525
526} // 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 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <mutex>
6#include <utility>
7#include "common/assert.h"
8#include "common/threadsafe_queue.h"
9#include "input_common/udp/client.h"
10#include "input_common/udp/udp.h"
11
12namespace InputCommon {
13
14class UDPMotion final : public Input::MotionDevice {
15public:
16 explicit UDPMotion(std::string ip_, u16 port_, u16 pad_, CemuhookUDP::Client* client_)
17 : ip(std::move(ip_)), port(port_), pad(pad_), client(client_) {}
18
19 Input::MotionStatus GetStatus() const override {
20 return client->GetPadState(ip, port, pad).motion_status;
21 }
22
23private:
24 const std::string ip;
25 const u16 port;
26 const u16 pad;
27 CemuhookUDP::Client* client;
28 mutable std::mutex mutex;
29};
30
31/// A motion device factory that creates motion devices from a UDP client
32UDPMotionFactory::UDPMotionFactory(std::shared_ptr<CemuhookUDP::Client> client_)
33 : client(std::move(client_)) {}
34
35/**
36 * Creates motion device
37 * @param params contains parameters for creating the device:
38 * - "port": the UDP port number
39 */
40std::unique_ptr<Input::MotionDevice> UDPMotionFactory::Create(const Common::ParamPackage& params) {
41 auto ip = params.Get("ip", "127.0.0.1");
42 const auto port = static_cast<u16>(params.Get("port", 26760));
43 const auto pad = static_cast<u16>(params.Get("pad_index", 0));
44
45 return std::make_unique<UDPMotion>(std::move(ip), port, pad, client.get());
46}
47
48void UDPMotionFactory::BeginConfiguration() {
49 polling = true;
50 client->BeginConfiguration();
51}
52
53void UDPMotionFactory::EndConfiguration() {
54 polling = false;
55 client->EndConfiguration();
56}
57
58Common::ParamPackage UDPMotionFactory::GetNextInput() {
59 Common::ParamPackage params;
60 CemuhookUDP::UDPPadStatus pad;
61 auto& queue = client->GetPadQueue();
62 while (queue.Pop(pad)) {
63 if (pad.motion == CemuhookUDP::PadMotion::Undefined || std::abs(pad.motion_value) < 1) {
64 continue;
65 }
66 params.Set("engine", "cemuhookudp");
67 params.Set("ip", pad.host);
68 params.Set("port", static_cast<u16>(pad.port));
69 params.Set("pad_index", static_cast<u16>(pad.pad_index));
70 params.Set("motion", static_cast<u16>(pad.motion));
71 return params;
72 }
73 return params;
74}
75
76class UDPTouch final : public Input::TouchDevice {
77public:
78 explicit UDPTouch(std::string ip_, u16 port_, u16 pad_, CemuhookUDP::Client* client_)
79 : ip(std::move(ip_)), port(port_), pad(pad_), client(client_) {}
80
81 Input::TouchStatus GetStatus() const override {
82 return client->GetTouchState();
83 }
84
85private:
86 const std::string ip;
87 [[maybe_unused]] const u16 port;
88 [[maybe_unused]] const u16 pad;
89 CemuhookUDP::Client* client;
90 mutable std::mutex mutex;
91};
92
93/// A motion device factory that creates motion devices from a UDP client
94UDPTouchFactory::UDPTouchFactory(std::shared_ptr<CemuhookUDP::Client> client_)
95 : client(std::move(client_)) {}
96
97/**
98 * Creates motion device
99 * @param params contains parameters for creating the device:
100 * - "port": the UDP port number
101 */
102std::unique_ptr<Input::TouchDevice> UDPTouchFactory::Create(const Common::ParamPackage& params) {
103 auto ip = params.Get("ip", "127.0.0.1");
104 const auto port = static_cast<u16>(params.Get("port", 26760));
105 const auto pad = static_cast<u16>(params.Get("pad_index", 0));
106
107 return std::make_unique<UDPTouch>(std::move(ip), port, pad, client.get());
108}
109
110} // 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 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include "core/frontend/input.h"
9#include "input_common/udp/client.h"
10
11namespace InputCommon {
12
13/// A motion device factory that creates motion devices from udp clients
14class UDPMotionFactory final : public Input::Factory<Input::MotionDevice> {
15public:
16 explicit UDPMotionFactory(std::shared_ptr<CemuhookUDP::Client> client_);
17
18 std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override;
19
20 Common::ParamPackage GetNextInput();
21
22 /// For device input configuration/polling
23 void BeginConfiguration();
24 void EndConfiguration();
25
26 bool IsPolling() const {
27 return polling;
28 }
29
30private:
31 std::shared_ptr<CemuhookUDP::Client> client;
32 bool polling = false;
33};
34
35/// A touch device factory that creates touch devices from udp clients
36class UDPTouchFactory final : public Input::Factory<Input::TouchDevice> {
37public:
38 explicit UDPTouchFactory(std::shared_ptr<CemuhookUDP::Client> client_);
39
40 std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override;
41
42 Common::ParamPackage GetNextInput();
43
44 /// For device input configuration/polling
45 void BeginConfiguration();
46 void EndConfiguration();
47
48 bool IsPolling() const {
49 return polling;
50 }
51
52private:
53 std::shared_ptr<CemuhookUDP::Client> client;
54 bool polling = false;
55};
56
57} // namespace InputCommon