summaryrefslogtreecommitdiff
path: root/src/input_common/gcadapter/gc_poller.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/input_common/gcadapter/gc_poller.cpp')
-rw-r--r--src/input_common/gcadapter/gc_poller.cpp272
1 files changed, 272 insertions, 0 deletions
diff --git a/src/input_common/gcadapter/gc_poller.cpp b/src/input_common/gcadapter/gc_poller.cpp
new file mode 100644
index 000000000..385ce8430
--- /dev/null
+++ b/src/input_common/gcadapter/gc_poller.cpp
@@ -0,0 +1,272 @@
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/threadsafe_queue.h"
10#include "input_common/gcadapter/gc_adapter.h"
11#include "input_common/gcadapter/gc_poller.h"
12
13namespace InputCommon {
14
15class GCButton final : public Input::ButtonDevice {
16public:
17 explicit GCButton(int port_, int button_, GCAdapter::Adapter* adapter)
18 : port(port_), button(button_), gcadapter(adapter) {}
19
20 ~GCButton() override;
21
22 bool GetStatus() const override {
23 return gcadapter->GetPadState()[port].buttons.at(button);
24 }
25
26private:
27 const int port;
28 const int button;
29 GCAdapter::Adapter* gcadapter;
30};
31
32class GCAxisButton final : public Input::ButtonDevice {
33public:
34 explicit GCAxisButton(int port_, int axis_, float threshold_, bool trigger_if_greater_,
35 GCAdapter::Adapter* adapter)
36 : port(port_), axis(axis_), threshold(threshold_), trigger_if_greater(trigger_if_greater_),
37 gcadapter(adapter) {
38 // L/R triggers range is only in positive direction beginning near 0
39 // 0.0 threshold equates to near half trigger press, but threshold accounts for variability.
40 if (axis > 3) {
41 threshold *= -0.5;
42 }
43 }
44
45 bool GetStatus() const override {
46 const float axis_value = (gcadapter->GetPadState()[port].axes.at(axis) - 128.0f) / 128.0f;
47 if (trigger_if_greater) {
48 // TODO: Might be worthwile to set a slider for the trigger threshold. It is currently
49 // 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
55private:
56 const int port;
57 const int axis;
58 float threshold;
59 bool trigger_if_greater;
60 GCAdapter::Adapter* gcadapter;
61};
62
63GCButtonFactory::GCButtonFactory(std::shared_ptr<GCAdapter::Adapter> adapter_)
64 : adapter(std::move(adapter_)) {}
65
66GCButton::~GCButton() = default;
67
68std::unique_ptr<Input::ButtonDevice> GCButtonFactory::Create(const Common::ParamPackage& params) {
69 const int button_id = params.Get("button", 0);
70 const int port = params.Get("port", 0);
71
72 constexpr int PAD_STICK_ID = static_cast<u16>(GCAdapter::PadButton::PAD_STICK);
73
74 // button is not an axis/stick button
75 if (button_id != PAD_STICK_ID) {
76 auto button = std::make_unique<GCButton>(port, button_id, adapter.get());
77 return std::move(button);
78 }
79
80 // For Axis buttons, used by the binary sticks.
81 if (button_id == PAD_STICK_ID) {
82 const int axis = params.Get("axis", 0);
83 const float threshold = params.Get("threshold", 0.25f);
84 const std::string direction_name = params.Get("direction", "");
85 bool trigger_if_greater;
86 if (direction_name == "+") {
87 trigger_if_greater = true;
88 } else if (direction_name == "-") {
89 trigger_if_greater = false;
90 } else {
91 trigger_if_greater = true;
92 LOG_ERROR(Input, "Unknown direction {}", direction_name);
93 }
94 return std::make_unique<GCAxisButton>(port, axis, threshold, trigger_if_greater,
95 adapter.get());
96 }
97}
98
99Common::ParamPackage GCButtonFactory::GetNextInput() {
100 Common::ParamPackage params;
101 GCAdapter::GCPadStatus pad;
102 auto& queue = adapter->GetPadQueue();
103 for (std::size_t port = 0; port < queue.size(); ++port) {
104 while (queue[port].Pop(pad)) {
105 // This while loop will break on the earliest detected button
106 params.Set("engine", "gcpad");
107 params.Set("port", static_cast<int>(port));
108 for (const auto& button : GCAdapter::PadButtonArray) {
109 const u16 button_value = static_cast<u16>(button);
110 if (pad.button & button_value) {
111 params.Set("button", button_value);
112 break;
113 }
114 }
115
116 // For Axis button implementation
117 if (pad.axis != GCAdapter::PadAxes::Undefined) {
118 params.Set("axis", static_cast<u8>(pad.axis));
119 params.Set("button", static_cast<u16>(GCAdapter::PadButton::PAD_STICK));
120 if (pad.axis_value > 128) {
121 params.Set("direction", "+");
122 params.Set("threshold", "0.25");
123 } else {
124 params.Set("direction", "-");
125 params.Set("threshold", "-0.25");
126 }
127 break;
128 }
129 }
130 }
131 return params;
132}
133
134void GCButtonFactory::BeginConfiguration() {
135 polling = true;
136 adapter->BeginConfiguration();
137}
138
139void GCButtonFactory::EndConfiguration() {
140 polling = false;
141 adapter->EndConfiguration();
142}
143
144class GCAnalog final : public Input::AnalogDevice {
145public:
146 GCAnalog(int port_, int axis_x_, int axis_y_, float deadzone_, GCAdapter::Adapter* adapter)
147 : port(port_), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_), gcadapter(adapter) {}
148
149 float GetAxis(int axis) const {
150 std::lock_guard lock{mutex};
151 // division is not by a perfect 128 to account for some variance in center location
152 // e.g. my device idled at 131 in X, 120 in Y, and full range of motion was in range
153 // [20-230]
154 return (gcadapter->GetPadState()[port].axes.at(axis) - 128.0f) / 95.0f;
155 }
156
157 std::pair<float, float> GetAnalog(int axis_x, int axis_y) const {
158 float x = GetAxis(axis_x);
159 float y = GetAxis(axis_y);
160
161 // Make sure the coordinates are in the unit circle,
162 // otherwise normalize it.
163 float r = x * x + y * y;
164 if (r > 1.0f) {
165 r = std::sqrt(r);
166 x /= r;
167 y /= r;
168 }
169
170 return {x, y};
171 }
172
173 std::tuple<float, float> GetStatus() const override {
174 const auto [x, y] = GetAnalog(axis_x, axis_y);
175 const float r = std::sqrt((x * x) + (y * y));
176 if (r > deadzone) {
177 return {x / r * (r - deadzone) / (1 - deadzone),
178 y / r * (r - deadzone) / (1 - deadzone)};
179 }
180 return {0.0f, 0.0f};
181 }
182
183 bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override {
184 const auto [x, y] = GetStatus();
185 const float directional_deadzone = 0.4f;
186 switch (direction) {
187 case Input::AnalogDirection::RIGHT:
188 return x > directional_deadzone;
189 case Input::AnalogDirection::LEFT:
190 return x < -directional_deadzone;
191 case Input::AnalogDirection::UP:
192 return y > directional_deadzone;
193 case Input::AnalogDirection::DOWN:
194 return y < -directional_deadzone;
195 }
196 return false;
197 }
198
199private:
200 const int port;
201 const int axis_x;
202 const int axis_y;
203 const float deadzone;
204 mutable std::mutex mutex;
205 GCAdapter::Adapter* gcadapter;
206};
207
208/// An analog device factory that creates analog devices from GC Adapter
209GCAnalogFactory::GCAnalogFactory(std::shared_ptr<GCAdapter::Adapter> adapter_)
210 : adapter(std::move(adapter_)) {}
211
212/**
213 * Creates analog device from joystick axes
214 * @param params contains parameters for creating the device:
215 * - "port": the nth gcpad on the adapter
216 * - "axis_x": the index of the axis to be bind as x-axis
217 * - "axis_y": the index of the axis to be bind as y-axis
218 */
219std::unique_ptr<Input::AnalogDevice> GCAnalogFactory::Create(const Common::ParamPackage& params) {
220 const int port = params.Get("port", 0);
221 const int axis_x = params.Get("axis_x", 0);
222 const int axis_y = params.Get("axis_y", 1);
223 const float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, .99f);
224
225 return std::make_unique<GCAnalog>(port, axis_x, axis_y, deadzone, adapter.get());
226}
227
228void GCAnalogFactory::BeginConfiguration() {
229 polling = true;
230 adapter->BeginConfiguration();
231}
232
233void GCAnalogFactory::EndConfiguration() {
234 polling = false;
235 adapter->EndConfiguration();
236}
237
238Common::ParamPackage GCAnalogFactory::GetNextInput() {
239 GCAdapter::GCPadStatus pad;
240 auto& queue = adapter->GetPadQueue();
241 for (std::size_t port = 0; port < queue.size(); ++port) {
242 while (queue[port].Pop(pad)) {
243 if (pad.axis == GCAdapter::PadAxes::Undefined ||
244 std::abs((pad.axis_value - 128.0f) / 128.0f) < 0.1) {
245 continue;
246 }
247 // An analog device needs two axes, so we need to store the axis for later and wait for
248 // a second input event. The axes also must be from the same joystick.
249 const u8 axis = static_cast<u8>(pad.axis);
250 if (analog_x_axis == -1) {
251 analog_x_axis = axis;
252 controller_number = port;
253 } else if (analog_y_axis == -1 && analog_x_axis != axis && controller_number == port) {
254 analog_y_axis = axis;
255 }
256 }
257 }
258 Common::ParamPackage params;
259 if (analog_x_axis != -1 && analog_y_axis != -1) {
260 params.Set("engine", "gcpad");
261 params.Set("port", controller_number);
262 params.Set("axis_x", analog_x_axis);
263 params.Set("axis_y", analog_y_axis);
264 analog_x_axis = -1;
265 analog_y_axis = -1;
266 controller_number = -1;
267 return params;
268 }
269 return params;
270}
271
272} // namespace InputCommon