From 298e4e678b2fa6dc62f5a90e0b27caa954aaed66 Mon Sep 17 00:00:00 2001 From: Andy Nichols Date: Mon, 11 May 2015 16:36:50 +0200 Subject: Initial public release of Qt Gamepad module Qt Gamepad is a Qt 5 module that adds support for using Gamepad devices (like the XBox 360 controller) inside of Qt applications. Change-Id: I5dff629dcfdcc5625a90274017b8e97f45e8fd30 --- src/plugins/gamepads/xinput/main.cpp | 62 +++++ .../gamepads/xinput/qxinputgamepadbackend.cpp | 291 +++++++++++++++++++++ .../gamepads/xinput/qxinputgamepadbackend_p.h | 63 +++++ src/plugins/gamepads/xinput/xinput.json | 3 + src/plugins/gamepads/xinput/xinput.pro | 14 + 5 files changed, 433 insertions(+) create mode 100644 src/plugins/gamepads/xinput/main.cpp create mode 100644 src/plugins/gamepads/xinput/qxinputgamepadbackend.cpp create mode 100644 src/plugins/gamepads/xinput/qxinputgamepadbackend_p.h create mode 100644 src/plugins/gamepads/xinput/xinput.json create mode 100644 src/plugins/gamepads/xinput/xinput.pro (limited to 'src/plugins/gamepads/xinput') diff --git a/src/plugins/gamepads/xinput/main.cpp b/src/plugins/gamepads/xinput/main.cpp new file mode 100644 index 0000000..a5eb0f8 --- /dev/null +++ b/src/plugins/gamepads/xinput/main.cpp @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** 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 +#include + +#include "qxinputgamepadbackend_p.h" + +QT_BEGIN_NAMESPACE + +class QXInputGamepadBackendPlugin : public QGamepadBackendPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QtGamepadBackendFactoryInterface_iid FILE "xinput.json") +public: + QGamepadBackend *create(const QString &key, const QStringList ¶mList) Q_DECL_OVERRIDE; +}; + +QGamepadBackend *QXInputGamepadBackendPlugin::create(const QString &key, const QStringList ¶mList) +{ + Q_UNUSED(key) + Q_UNUSED(paramList) + + return new QXInputGamepadBackend; +} + +QT_END_NAMESPACE + +#include "main.moc" 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 +#include +#include +#include + +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 diff --git a/src/plugins/gamepads/xinput/qxinputgamepadbackend_p.h b/src/plugins/gamepads/xinput/qxinputgamepadbackend_p.h new file mode 100644 index 0000000..42f7a4a --- /dev/null +++ b/src/plugins/gamepads/xinput/qxinputgamepadbackend_p.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef QXINPUTGAMEPADCONTROLLER_H +#define QXINPUTGAMEPADCONTROLLER_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QXInputThread; + +class QXInputGamepadBackend : public QGamepadBackend +{ + Q_OBJECT + +public: + QXInputGamepadBackend(); + bool start() Q_DECL_OVERRIDE; + void stop() Q_DECL_OVERRIDE; + +private: + QXInputThread *m_thread; + QLibrary m_lib; +}; + +QT_END_NAMESPACE + +#endif // QXINPUTGAMEPADCONTROLLER_H diff --git a/src/plugins/gamepads/xinput/xinput.json b/src/plugins/gamepads/xinput/xinput.json new file mode 100644 index 0000000..f8e3fb2 --- /dev/null +++ b/src/plugins/gamepads/xinput/xinput.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "xinput" ] +} diff --git a/src/plugins/gamepads/xinput/xinput.pro b/src/plugins/gamepads/xinput/xinput.pro new file mode 100644 index 0000000..5dac854 --- /dev/null +++ b/src/plugins/gamepads/xinput/xinput.pro @@ -0,0 +1,14 @@ +TARGET = xinputgamepad +QT += gamepad gamepad-private + +PLUGIN_TYPE = gamepads +PLUGIN_CLASS_NAME = QXInputGamepadBackendPlugin +load(qt_plugin) + +HEADERS += qxinputgamepadbackend_p.h +SOURCES += \ + qxinputgamepadbackend.cpp \ + main.cpp + +OTHER_FILES += \ + xinput.json -- cgit v1.2.3