aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/gamepads/xinput/qxinputgamepadbackend.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/gamepads/xinput/qxinputgamepadbackend.cpp')
-rw-r--r--src/plugins/gamepads/xinput/qxinputgamepadbackend.cpp291
1 files changed, 291 insertions, 0 deletions
diff --git a/src/plugins/gamepads/xinput/qxinputgamepadbackend.cpp b/src/plugins/gamepads/xinput/qxinputgamepadbackend.cpp
new file mode 100644
index 0000000..97680cb
--- /dev/null
+++ b/src/plugins/gamepads/xinput/qxinputgamepadbackend.cpp
@@ -0,0 +1,291 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gamepad module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#include "qxinputgamepadbackend_p.h"
+#include <QtCore/QLoggingCategory>
+#include <QtCore/QThread>
+#include <qmath.h>
+#include <windows.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_LOGGING_CATEGORY(lcXGB, "qt.gamepad")
+
+#define POLL_SLEEP_MS 5
+#define POLL_SLOT_CHECK_MS 4000
+
+#define XUSER_MAX_COUNT 4
+
+#define XINPUT_GAMEPAD_DPAD_UP 0x0001
+#define XINPUT_GAMEPAD_DPAD_DOWN 0x0002
+#define XINPUT_GAMEPAD_DPAD_LEFT 0x0004
+#define XINPUT_GAMEPAD_DPAD_RIGHT 0x0008
+#define XINPUT_GAMEPAD_START 0x0010
+#define XINPUT_GAMEPAD_BACK 0x0020
+#define XINPUT_GAMEPAD_LEFT_THUMB 0x0040
+#define XINPUT_GAMEPAD_RIGHT_THUMB 0x0080
+#define XINPUT_GAMEPAD_LEFT_SHOULDER 0x0100
+#define XINPUT_GAMEPAD_RIGHT_SHOULDER 0x0200
+#define XINPUT_GAMEPAD_A 0x1000
+#define XINPUT_GAMEPAD_B 0x2000
+#define XINPUT_GAMEPAD_X 0x4000
+#define XINPUT_GAMEPAD_Y 0x8000
+
+#define XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE 7849
+#define XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE 8689
+#define XINPUT_GAMEPAD_TRIGGER_THRESHOLD 30
+
+struct XINPUT_GAMEPAD
+{
+ unsigned short wButtons;
+ unsigned char bLeftTrigger;
+ unsigned char bRightTrigger;
+ short sThumbLX;
+ short sThumbLY;
+ short sThumbRX;
+ short sThumbRY;
+};
+
+struct XINPUT_STATE
+{
+ unsigned long dwPacketNumber;
+ XINPUT_GAMEPAD Gamepad;
+};
+
+typedef DWORD (WINAPI *XInputGetState_t)(DWORD dwUserIndex, XINPUT_STATE *pState);
+static XInputGetState_t XInputGetState;
+
+class QXInputThread : public QThread
+{
+public:
+ QXInputThread(QXInputGamepadBackend *backend);
+ void run() Q_DECL_OVERRIDE;
+ void signalQuit() { m_quit.fetchAndStoreAcquire(1); }
+
+private:
+ void dispatch(int idx, XINPUT_GAMEPAD *state);
+
+ QXInputGamepadBackend *m_backend;
+ QAtomicInt m_quit;
+ struct Controller {
+ bool connected;
+ int skippedPolls;
+ unsigned long lastPacketNumber;
+ // State cache. Only want to emit signals for values that really change.
+ unsigned short buttons;
+ unsigned char triggers[2];
+ double axis[2][2];
+ } m_controllers[XUSER_MAX_COUNT];
+};
+
+QXInputThread::QXInputThread(QXInputGamepadBackend *backend)
+ : m_backend(backend),
+ m_quit(false)
+{
+ memset(m_controllers, 0, sizeof(m_controllers));
+}
+
+void QXInputThread::dispatch(int idx, XINPUT_GAMEPAD *state)
+{
+ static const struct ButtonMap {
+ unsigned short xbutton;
+ QGamepadManager::GamepadButton qbutton;
+ } buttonMap[] = {
+ { XINPUT_GAMEPAD_DPAD_UP, QGamepadManager::ButtonUp },
+ { XINPUT_GAMEPAD_DPAD_DOWN, QGamepadManager::ButtonDown },
+ { XINPUT_GAMEPAD_DPAD_LEFT, QGamepadManager::ButtonLeft },
+ { XINPUT_GAMEPAD_DPAD_RIGHT, QGamepadManager::ButtonRight },
+ { XINPUT_GAMEPAD_START, QGamepadManager::ButtonStart },
+ { XINPUT_GAMEPAD_BACK, QGamepadManager::ButtonSelect },
+ { XINPUT_GAMEPAD_LEFT_SHOULDER, QGamepadManager::ButtonL1 },
+ { XINPUT_GAMEPAD_RIGHT_SHOULDER, QGamepadManager::ButtonR1 },
+ { XINPUT_GAMEPAD_LEFT_THUMB, QGamepadManager::ButtonL3 },
+ { XINPUT_GAMEPAD_RIGHT_THUMB, QGamepadManager::ButtonR3 },
+ { XINPUT_GAMEPAD_A, QGamepadManager::ButtonA },
+ { XINPUT_GAMEPAD_B, QGamepadManager::ButtonB },
+ { XINPUT_GAMEPAD_X, QGamepadManager::ButtonX },
+ { XINPUT_GAMEPAD_Y, QGamepadManager::ButtonY }
+ };
+ for (int i = 0; i < sizeof(buttonMap) / sizeof(ButtonMap); ++i) {
+ const unsigned short xb = buttonMap[i].xbutton;
+ unsigned short isDown = state->wButtons & xb;
+ if (isDown != (m_controllers[idx].buttons & xb)) {
+ if (isDown) {
+ m_controllers[idx].buttons |= xb;
+ emit m_backend->gamepadButtonPressed(idx, buttonMap[i].qbutton, 1);
+ } else {
+ m_controllers[idx].buttons &= ~xb;
+ emit m_backend->gamepadButtonReleased(idx, buttonMap[i].qbutton);
+ }
+ }
+ }
+
+ if (m_controllers[idx].triggers[0] != state->bLeftTrigger) {
+ m_controllers[idx].triggers[0] = state->bLeftTrigger;
+ const double value = state->bLeftTrigger > XINPUT_GAMEPAD_TRIGGER_THRESHOLD
+ ? (state->bLeftTrigger - XINPUT_GAMEPAD_TRIGGER_THRESHOLD)
+ / (255.0 - XINPUT_GAMEPAD_TRIGGER_THRESHOLD)
+ : 0.0;
+ if (!qFuzzyIsNull(value))
+ emit m_backend->gamepadButtonPressed(idx, QGamepadManager::ButtonL2, value);
+ else
+ emit m_backend->gamepadButtonReleased(idx, QGamepadManager::ButtonL2);
+ }
+ if (m_controllers[idx].triggers[1] != state->bRightTrigger) {
+ m_controllers[idx].triggers[1] = state->bRightTrigger;
+ const double value = state->bRightTrigger > XINPUT_GAMEPAD_TRIGGER_THRESHOLD
+ ? (state->bRightTrigger - XINPUT_GAMEPAD_TRIGGER_THRESHOLD)
+ / (255.0 - XINPUT_GAMEPAD_TRIGGER_THRESHOLD)
+ : 0.0;
+ if (!qFuzzyIsNull(value))
+ emit m_backend->gamepadButtonPressed(idx, QGamepadManager::ButtonR2, value);
+ else
+ emit m_backend->gamepadButtonReleased(idx, QGamepadManager::ButtonR2);
+ }
+
+ double x, y;
+ if (qSqrt(state->sThumbLX * state->sThumbLX + state->sThumbLY * state->sThumbLY) > XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE) {
+ x = 2 * (state->sThumbLX + 32768.0) / 65535.0 - 1.0;
+ y = 2 * (-state->sThumbLY + 32768.0) / 65535.0 - 1.0;
+ } else {
+ x = y = 0;
+ }
+ if (m_controllers[idx].axis[0][0] != x) {
+ m_controllers[idx].axis[0][0] = x;
+ emit m_backend->gamepadAxisMoved(idx, QGamepadManager::AxisLeftX, x);
+ }
+ if (m_controllers[idx].axis[0][1] != y) {
+ m_controllers[idx].axis[0][1] = y;
+ emit m_backend->gamepadAxisMoved(idx, QGamepadManager::AxisLeftY, y);
+ }
+ if (qSqrt(state->sThumbRX * state->sThumbRX + state->sThumbRY * state->sThumbRY) > XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE) {
+ x = 2 * (state->sThumbRX + 32768.0) / 65535.0 - 1.0;
+ y = 2 * (-state->sThumbRY + 32768.0) / 65535.0 - 1.0;
+ } else {
+ x = y = 0;
+ }
+ if (m_controllers[idx].axis[1][0] != x) {
+ m_controllers[idx].axis[1][0] = x;
+ emit m_backend->gamepadAxisMoved(idx, QGamepadManager::AxisRightX, x);
+ }
+ if (m_controllers[idx].axis[1][1] != y) {
+ m_controllers[idx].axis[1][1] = y;
+ emit m_backend->gamepadAxisMoved(idx, QGamepadManager::AxisRightY, y);
+ }
+}
+
+void QXInputThread::run()
+{
+ qCDebug(lcXGB, "XInput thread running");
+ bool firstPoll = true;
+ while (!m_quit.testAndSetAcquire(1, 0)) {
+ for (int i = 0; i < XUSER_MAX_COUNT; ++i) {
+ Controller *controller = m_controllers + i;
+
+ if (!firstPoll && !controller->connected && controller->skippedPolls < POLL_SLOT_CHECK_MS / POLL_SLEEP_MS) {
+ controller->skippedPolls++;
+ continue;
+ }
+
+ firstPoll = false;
+ controller->skippedPolls = 0;
+ XINPUT_STATE state;
+ memset(&state, 0, sizeof(state));
+
+ if (XInputGetState(i, &state) == ERROR_SUCCESS) {
+ if (controller->connected) {
+ if (controller->lastPacketNumber != state.dwPacketNumber) {
+ controller->lastPacketNumber = state.dwPacketNumber;
+ dispatch(i, &state.Gamepad);
+ }
+ } else {
+ controller->connected = true;
+ controller->lastPacketNumber = state.dwPacketNumber;
+ emit m_backend->gamepadAdded(i);
+ dispatch(i, &state.Gamepad);
+ }
+ } else {
+ if (controller->connected) {
+ controller->connected = false;
+ emit m_backend->gamepadRemoved(i);
+ }
+ }
+ }
+
+ Sleep(POLL_SLEEP_MS);
+ }
+ qCDebug(lcXGB, "XInput thread stopping");
+}
+
+QXInputGamepadBackend::QXInputGamepadBackend()
+ : m_thread(0)
+{
+}
+
+bool QXInputGamepadBackend::start()
+{
+ qCDebug(lcXGB) << "start";
+
+ m_lib.setFileName(QStringLiteral("xinput1_4.dll"));
+ if (!m_lib.load()) {
+ m_lib.setFileName(QStringLiteral("xinput1_3.dll"));
+ m_lib.load();
+ }
+
+ if (m_lib.isLoaded()) {
+ qCDebug(lcXGB, "Loaded XInput library %s", qPrintable(m_lib.fileName()));
+ XInputGetState = (XInputGetState_t) m_lib.resolve("XInputGetState");
+ if (XInputGetState) {
+ m_thread = new QXInputThread(this);
+ m_thread->start();
+ } else {
+ qWarning("Failed to resolve XInputGetState");
+ }
+ } else {
+ qWarning("Failed to load XInput library %s", qPrintable(m_lib.fileName()));
+ }
+
+ return m_lib.isLoaded();
+}
+
+void QXInputGamepadBackend::stop()
+{
+ qCDebug(lcXGB) << "stop";
+ m_thread->signalQuit();
+ m_thread->wait();
+ XInputGetState = 0;
+}
+
+QT_END_NAMESPACE