blob: 4d042c99449cc9648c62ce67bf628ff22ecbd029 [file] [log] [blame]
/****************************************************************************
**
** 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() 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 (uint 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