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