1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the Qt Gamepad module
7 **
8 ** $QT_BEGIN_LICENSE:LGPL3$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPLv3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or later as published by the Free
28 ** Software Foundation and appearing in the file LICENSE.GPL included in
29 ** the packaging of this file. Please review the following information to
30 ** ensure the GNU General Public License version 2.0 requirements will be
31 ** met: http://www.gnu.org/licenses/gpl-2.0.html.
32 **
33 ** $QT_END_LICENSE$
34 **
35 ****************************************************************************/
36 #include "qxinputgamepadbackend_p.h"
37 #include <QtCore/QLoggingCategory>
38 #include <QtCore/QThread>
39 #include <qmath.h>
40 #include <windows.h>
41
42 QT_BEGIN_NAMESPACE
43
44 Q_LOGGING_CATEGORY(lcXGB, "qt.gamepad")
45
46 #define POLL_SLEEP_MS 5
47 #define POLL_SLOT_CHECK_MS 4000
48
49 #define XUSER_MAX_COUNT 4
50
51 #define XINPUT_GAMEPAD_DPAD_UP 0x0001
52 #define XINPUT_GAMEPAD_DPAD_DOWN 0x0002
53 #define XINPUT_GAMEPAD_DPAD_LEFT 0x0004
54 #define XINPUT_GAMEPAD_DPAD_RIGHT 0x0008
55 #define XINPUT_GAMEPAD_START 0x0010
56 #define XINPUT_GAMEPAD_BACK 0x0020
57 #define XINPUT_GAMEPAD_LEFT_THUMB 0x0040
58 #define XINPUT_GAMEPAD_RIGHT_THUMB 0x0080
59 #define XINPUT_GAMEPAD_LEFT_SHOULDER 0x0100
60 #define XINPUT_GAMEPAD_RIGHT_SHOULDER 0x0200
61 #define XINPUT_GAMEPAD_A 0x1000
62 #define XINPUT_GAMEPAD_B 0x2000
63 #define XINPUT_GAMEPAD_X 0x4000
64 #define XINPUT_GAMEPAD_Y 0x8000
65
66 #define XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE 7849
67 #define XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE 8689
68 #define XINPUT_GAMEPAD_TRIGGER_THRESHOLD 30
69
70 struct XINPUT_GAMEPAD
71 {
72 unsigned short wButtons;
73 unsigned char bLeftTrigger;
74 unsigned char bRightTrigger;
75 short sThumbLX;
76 short sThumbLY;
77 short sThumbRX;
78 short sThumbRY;
79 };
80
81 struct XINPUT_STATE
82 {
83 unsigned long dwPacketNumber;
84 XINPUT_GAMEPAD Gamepad;
85 };
86
87 typedef DWORD (WINAPI *XInputGetState_t)(DWORD dwUserIndex, XINPUT_STATE *pState);
88 static XInputGetState_t XInputGetState;
89
90 class QXInputThread : public QThread
91 {
92 public:
93 QXInputThread(QXInputGamepadBackend *backend);
94 void run() override;
signalQuit()95 void signalQuit() { m_quit.fetchAndStoreAcquire(1); }
96
97 private:
98 void dispatch(int idx, XINPUT_GAMEPAD *state);
99
100 QXInputGamepadBackend *m_backend;
101 QAtomicInt m_quit;
102 struct Controller {
103 bool connected;
104 int skippedPolls;
105 unsigned long lastPacketNumber;
106 // State cache. Only want to emit signals for values that really change.
107 unsigned short buttons;
108 unsigned char triggers[2];
109 double axis[2][2];
110 } m_controllers[XUSER_MAX_COUNT];
111 };
112
QXInputThread(QXInputGamepadBackend * backend)113 QXInputThread::QXInputThread(QXInputGamepadBackend *backend)
114 : m_backend(backend),
115 m_quit(false)
116 {
117 memset(m_controllers, 0, sizeof(m_controllers));
118 }
119
dispatch(int idx,XINPUT_GAMEPAD * state)120 void QXInputThread::dispatch(int idx, XINPUT_GAMEPAD *state)
121 {
122 static const struct ButtonMap {
123 unsigned short xbutton;
124 QGamepadManager::GamepadButton qbutton;
125 } buttonMap[] = {
126 { XINPUT_GAMEPAD_DPAD_UP, QGamepadManager::ButtonUp },
127 { XINPUT_GAMEPAD_DPAD_DOWN, QGamepadManager::ButtonDown },
128 { XINPUT_GAMEPAD_DPAD_LEFT, QGamepadManager::ButtonLeft },
129 { XINPUT_GAMEPAD_DPAD_RIGHT, QGamepadManager::ButtonRight },
130 { XINPUT_GAMEPAD_START, QGamepadManager::ButtonStart },
131 { XINPUT_GAMEPAD_BACK, QGamepadManager::ButtonSelect },
132 { XINPUT_GAMEPAD_LEFT_SHOULDER, QGamepadManager::ButtonL1 },
133 { XINPUT_GAMEPAD_RIGHT_SHOULDER, QGamepadManager::ButtonR1 },
134 { XINPUT_GAMEPAD_LEFT_THUMB, QGamepadManager::ButtonL3 },
135 { XINPUT_GAMEPAD_RIGHT_THUMB, QGamepadManager::ButtonR3 },
136 { XINPUT_GAMEPAD_A, QGamepadManager::ButtonA },
137 { XINPUT_GAMEPAD_B, QGamepadManager::ButtonB },
138 { XINPUT_GAMEPAD_X, QGamepadManager::ButtonX },
139 { XINPUT_GAMEPAD_Y, QGamepadManager::ButtonY }
140 };
141 for (uint i = 0; i < sizeof(buttonMap) / sizeof(ButtonMap); ++i) {
142 const unsigned short xb = buttonMap[i].xbutton;
143 unsigned short isDown = state->wButtons & xb;
144 if (isDown != (m_controllers[idx].buttons & xb)) {
145 if (isDown) {
146 m_controllers[idx].buttons |= xb;
147 emit m_backend->gamepadButtonPressed(idx, buttonMap[i].qbutton, 1);
148 } else {
149 m_controllers[idx].buttons &= ~xb;
150 emit m_backend->gamepadButtonReleased(idx, buttonMap[i].qbutton);
151 }
152 }
153 }
154
155 if (m_controllers[idx].triggers[0] != state->bLeftTrigger) {
156 m_controllers[idx].triggers[0] = state->bLeftTrigger;
157 const double value = state->bLeftTrigger > XINPUT_GAMEPAD_TRIGGER_THRESHOLD
158 ? (state->bLeftTrigger - XINPUT_GAMEPAD_TRIGGER_THRESHOLD)
159 / (255.0 - XINPUT_GAMEPAD_TRIGGER_THRESHOLD)
160 : 0.0;
161 if (!qFuzzyIsNull(value))
162 emit m_backend->gamepadButtonPressed(idx, QGamepadManager::ButtonL2, value);
163 else
164 emit m_backend->gamepadButtonReleased(idx, QGamepadManager::ButtonL2);
165 }
166 if (m_controllers[idx].triggers[1] != state->bRightTrigger) {
167 m_controllers[idx].triggers[1] = state->bRightTrigger;
168 const double value = state->bRightTrigger > XINPUT_GAMEPAD_TRIGGER_THRESHOLD
169 ? (state->bRightTrigger - XINPUT_GAMEPAD_TRIGGER_THRESHOLD)
170 / (255.0 - XINPUT_GAMEPAD_TRIGGER_THRESHOLD)
171 : 0.0;
172 if (!qFuzzyIsNull(value))
173 emit m_backend->gamepadButtonPressed(idx, QGamepadManager::ButtonR2, value);
174 else
175 emit m_backend->gamepadButtonReleased(idx, QGamepadManager::ButtonR2);
176 }
177
178 double x, y;
179 if (qSqrt(state->sThumbLX * state->sThumbLX + state->sThumbLY * state->sThumbLY) > XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE) {
180 x = 2 * (state->sThumbLX + 32768.0) / 65535.0 - 1.0;
181 y = 2 * (-state->sThumbLY + 32768.0) / 65535.0 - 1.0;
182 } else {
183 x = y = 0;
184 }
185 if (m_controllers[idx].axis[0][0] != x) {
186 m_controllers[idx].axis[0][0] = x;
187 emit m_backend->gamepadAxisMoved(idx, QGamepadManager::AxisLeftX, x);
188 }
189 if (m_controllers[idx].axis[0][1] != y) {
190 m_controllers[idx].axis[0][1] = y;
191 emit m_backend->gamepadAxisMoved(idx, QGamepadManager::AxisLeftY, y);
192 }
193 if (qSqrt(state->sThumbRX * state->sThumbRX + state->sThumbRY * state->sThumbRY) > XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE) {
194 x = 2 * (state->sThumbRX + 32768.0) / 65535.0 - 1.0;
195 y = 2 * (-state->sThumbRY + 32768.0) / 65535.0 - 1.0;
196 } else {
197 x = y = 0;
198 }
199 if (m_controllers[idx].axis[1][0] != x) {
200 m_controllers[idx].axis[1][0] = x;
201 emit m_backend->gamepadAxisMoved(idx, QGamepadManager::AxisRightX, x);
202 }
203 if (m_controllers[idx].axis[1][1] != y) {
204 m_controllers[idx].axis[1][1] = y;
205 emit m_backend->gamepadAxisMoved(idx, QGamepadManager::AxisRightY, y);
206 }
207 }
208
run()209 void QXInputThread::run()
210 {
211 qCDebug(lcXGB, "XInput thread running");
212 bool firstPoll = true;
213 while (!m_quit.testAndSetAcquire(1, 0)) {
214 for (int i = 0; i < XUSER_MAX_COUNT; ++i) {
215 Controller *controller = m_controllers + i;
216
217 if (!firstPoll && !controller->connected && controller->skippedPolls < POLL_SLOT_CHECK_MS / POLL_SLEEP_MS) {
218 controller->skippedPolls++;
219 continue;
220 }
221
222 firstPoll = false;
223 controller->skippedPolls = 0;
224 XINPUT_STATE state;
225 memset(&state, 0, sizeof(state));
226
227 if (XInputGetState(i, &state) == ERROR_SUCCESS) {
228 if (controller->connected) {
229 if (controller->lastPacketNumber != state.dwPacketNumber) {
230 controller->lastPacketNumber = state.dwPacketNumber;
231 dispatch(i, &state.Gamepad);
232 }
233 } else {
234 controller->connected = true;
235 controller->lastPacketNumber = state.dwPacketNumber;
236 emit m_backend->gamepadAdded(i);
237 dispatch(i, &state.Gamepad);
238 }
239 } else {
240 if (controller->connected) {
241 controller->connected = false;
242 emit m_backend->gamepadRemoved(i);
243 }
244 }
245 }
246
247 Sleep(POLL_SLEEP_MS);
248 }
249 qCDebug(lcXGB, "XInput thread stopping");
250 }
251
QXInputGamepadBackend()252 QXInputGamepadBackend::QXInputGamepadBackend()
253 : m_thread(0)
254 {
255 }
256
start()257 bool QXInputGamepadBackend::start()
258 {
259 qCDebug(lcXGB) << "start";
260
261 m_lib.setFileName(QStringLiteral("xinput1_4.dll"));
262 if (!m_lib.load()) {
263 m_lib.setFileName(QStringLiteral("xinput1_3.dll"));
264 m_lib.load();
265 }
266
267 if (m_lib.isLoaded()) {
268 qCDebug(lcXGB, "Loaded XInput library %s", qPrintable(m_lib.fileName()));
269 XInputGetState = (XInputGetState_t) m_lib.resolve("XInputGetState");
270 if (XInputGetState) {
271 m_thread = new QXInputThread(this);
272 m_thread->start();
273 } else {
274 qWarning("Failed to resolve XInputGetState");
275 }
276 } else {
277 qWarning("Failed to load XInput library %s", qPrintable(m_lib.fileName()));
278 }
279
280 return m_lib.isLoaded();
281 }
282
stop()283 void QXInputGamepadBackend::stop()
284 {
285 qCDebug(lcXGB) << "stop";
286 m_thread->signalQuit();
287 m_thread->wait();
288 XInputGetState = 0;
289 }
290
291 QT_END_NAMESPACE
292