Skip to content
Permalink
Browse files

GlobalShortcut_win, XboxInput: implement native XInput support in Glo…

…balShortcut_win.

This commit adds native support for XInput to GlobalShortcut on Windows.

Most things XInput-related is named XboxInput in an attempt to reduce
confusion with X11's XInput.

This commit adds a new class, XboxInputLibrary, which is a thin wrapper
class for handling the XInput DLL.

XboxInput supports various versions of the XInput DLL, as well as
the non-public XInputGetStateEx function (ordinal 100) -- if it is
available in the loaded XInput DLL. XInputGetStateEx makes it possible to
query the state of the guide button, allowing it to be used as a global
shortcut in Mumble.

The most suitable GetState function is chosen by XboxInput and is
exposed simply via the GetState member variable.

This commit does nothing to prevent DirectInput from *also* getting input
from any connected Xbox controllers. However, no ill effects have been
observed yet.

For now, the result simply is that -- for the buttons that DirectInput
supports -- we get a button click from both engines. For example,
pressing A yields the equivalent of "DirectInput:A + Xbox1:A" as the
shortcut name, because both engines handled the button press.

In the future, we should blacklist the DirectInput device if XboxInput
is enabled to rule out any future incompatibilities.

The current implementation treats a shortcut from the first Xbox
controller as a different shortcut than the same button from a
potential second, third or fourth Xbox controller connected to
the system. As a result, if a button is bound on the first controller
in one session, Mumble will only respond if that button is pressed on
the first controller in future sessions.

Fixes #1995
  • Loading branch information...
mkrautz committed Dec 24, 2015
1 parent df28734 commit 608e802847a6776e1429f9ed4aae2bc77b4ea4d6
@@ -166,3 +166,7 @@ CONFIG+=no-qssldiffiehellmanparameters
parameters, even if the QSslDiffieHellmanParameters
class is available in the version of Qt you are
building against.

CONFIG+=no-xboxinput (Mumble, Win32)
Don't build in support for global shortcuts
from Xbox controllers via the XInput DLL.
@@ -158,6 +158,14 @@ void GlobalShortcutWin::run() {
}
#endif

#ifdef USE_XBOXINPUT
if (g.s.bEnableXboxInput) {
xboxinput = new XboxInput();
ZeroMemory(&xboxinputLastPacket, sizeof(xboxinputLastPacket));
qWarning("GlobalShortcutWin: XboxInput initialized, isValid: %d", xboxinput->isValid());
}
#endif

QTimer * timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(timeTicked()));
timer->start(20);
@@ -170,6 +178,10 @@ void GlobalShortcutWin::run() {
delete gkey;
#endif

#ifdef USE_XBOXINPUT
delete xboxinput;
#endif

if (bHook) {
UnhookWindowsHookEx(hhKeyboard);
UnhookWindowsHookEx(hhMouse);
@@ -630,6 +642,7 @@ void GlobalShortcutWin::timeTicked() {
handleButton(ql, rgdod[j].dwData & 0x80);
}
}

#ifdef USE_GKEY
if (g.s.bEnableGKey && gkey->isValid()) {
for (int button = GKEY_MIN_MOUSE_BUTTON; button <= GKEY_MAX_MOUSE_BUTTON; button++) {
@@ -650,6 +663,61 @@ void GlobalShortcutWin::timeTicked() {
}
}
#endif

#ifdef USE_XBOXINPUT
if (g.s.bEnableXboxInput && xboxinput->isValid()) {
XboxInputState state;
for (uint32_t i = 0; i < XBOXINPUT_MAX_DEVICES; i++) {
if (xboxinput->GetState(i, &state) == 0) {
// Skip the result of GetState() if the packet number hasn't changed,
// or if we're at the first packet.
if (xboxinputLastPacket[i] != 0 && state.packetNumber == xboxinputLastPacket[i]) {
continue;
}

// The buttons field of XboxInputState contains a bit
// for each button on the Xbox controller. The official
// headers enumerate the bits via XINPUT_GAMEPAD_*.
// The official mapping uses all 16-bits, but leaves
// bit 10 and 11 (counting from 0) undocumented.
//
// It turns out that bit 10 is the guide button,
// which can be queried using the non-public
// XInputGetStateEx() function.
//
// Our mapping uses the bit number as a button index.
// So 0x1 -> 0, 0x2 -> 1, 0x4 -> 2, and so on...
//
// However, since the buttons field is only a 16-bit value,
// and we also want to use the left and right triggers as
// buttons, we assign them the button indexes 16 and 17.
uint32_t buttonMask = state.buttons;
for (uint32_t j = 0; j < 18; j++) {
QList<QVariant> ql;

bool pressed = false;
if (j >= 16) {
if (j == 16) { // LeftTrigger
pressed = state.leftTrigger > XBOXINPUT_TRIGGER_THRESHOLD;
} else if (j == 17) { // RightTrigger
pressed = state.rightTrigger > XBOXINPUT_TRIGGER_THRESHOLD;
}
} else {
uint32_t currentButtonMask = (1 << j);
pressed = (buttonMask & currentButtonMask) != 0;
}

uint32_t type = (i << 24) | j;
ql << static_cast<uint>(type);
ql << XboxInput::s_XboxInputGuid;
handleButton(ql, pressed);
}

xboxinputLastPacket[i] = state.packetNumber;
}
}
}
#endif
}

QString GlobalShortcutWin::buttonName(const QVariant &v) {
@@ -686,6 +754,56 @@ QString GlobalShortcutWin::buttonName(const QVariant &v) {
}
#endif

#ifdef USE_XBOXINPUT
if (g.s.bEnableXboxInput && xboxinput->isValid() && guid == XboxInput::s_XboxInputGuid) {
uint32_t idx = (type >> 24) & 0xff;
uint32_t button = (type & 0x00ffffffff);

// Translate from our own button index mapping to
// the actual Xbox controller button names.
// For a description of the mapping, see the state
// querying code in GlobalShortcutWin::timeTicked().
switch (button) {
case 0:
return QString::fromLatin1("Xbox%1:Up").arg(idx + 1);
case 1:
return QString::fromLatin1("Xbox%1:Down").arg(idx + 1);
case 2:
return QString::fromLatin1("Xbox%1:Left").arg(idx + 1);
case 3:
return QString::fromLatin1("Xbox%1:Right").arg(idx + 1);
case 4:
return QString::fromLatin1("Xbox%1:Start").arg(idx + 1);
case 5:
return QString::fromLatin1("Xbox%1:Back").arg(idx + 1);
case 6:
return QString::fromLatin1("Xbox%1:LeftThumb").arg(idx + 1);
case 7:
return QString::fromLatin1("Xbox%1:RightThumb").arg(idx + 1);
case 8:
return QString::fromLatin1("Xbox%1:LeftShoulder").arg(idx + 1);
case 9:
return QString::fromLatin1("Xbox%1:RightShoulder").arg(idx + 1);
case 10:
return QString::fromLatin1("Xbox%1:Guide").arg(idx + 1);
case 11:
return QString::fromLatin1("Xbox%1:11").arg(idx + 1);
case 12:
return QString::fromLatin1("Xbox%1:A").arg(idx + 1);
case 13:
return QString::fromLatin1("Xbox%1:B").arg(idx + 1);
case 14:
return QString::fromLatin1("Xbox%1:X").arg(idx + 1);
case 15:
return QString::fromLatin1("Xbox%1:Y").arg(idx + 1);
case 16:
return QString::fromLatin1("Xbox%1:LeftTrigger").arg(idx + 1);
case 17:
return QString::fromLatin1("Xbox%1:RightTrigger").arg(idx + 1);
}
}
#endif

InputDevice *id = gsw->qhInputDevices.value(guid);
if (guid == GUID_SysMouse)
device=QLatin1String("M:");
@@ -38,6 +38,10 @@
#include "GKey.h"
#endif

#ifdef USE_XBOXINPUT
#include "XboxInput.h"
#endif

#define DIRECTINPUT_VERSION 0x0800
#include <dinput.h>

@@ -85,6 +89,15 @@ class GlobalShortcutWin : public GlobalShortcutEngine {
#ifdef USE_GKEY
GKeyLibrary *gkey;
#endif
#ifdef USE_XBOXINPUT
/// xboxinputLastPacket holds the last packet number
/// that was processed. Any new data queried for a
/// device is only valid if the packet number is
/// different than last time we queried it.
uint32_t xboxinputLastPacket[XBOXINPUT_MAX_DEVICES];
XboxInput *xboxinput;
#endif

static BOOL CALLBACK EnumSuitableDevicesCB(LPCDIDEVICEINSTANCE, LPDIRECTINPUTDEVICE8, DWORD, DWORD, LPVOID);
static BOOL CALLBACK EnumDevicesCB(LPCDIDEVICEINSTANCE, LPVOID);
static BOOL CALLBACK EnumDeviceObjectsCallback(LPCDIDEVICEOBJECTINSTANCE lpddoi, LPVOID pvRef);
@@ -414,6 +414,7 @@ Settings::Settings() {
bEnableEvdev = false;
bEnableXInput2 = true;
bEnableGKey = true;
bEnableXboxInput = true;

for (int i=Log::firstMsgType; i<=Log::lastMsgType; ++i) {
qmMessages.insert(i, Settings::LogConsole | Settings::LogBalloon | Settings::LogTTS);
@@ -740,6 +741,7 @@ void Settings::load(QSettings* settings_ptr) {
SAVELOAD(bEnableEvdev, "shortcut/linux/evdev/enable");
SAVELOAD(bEnableXInput2, "shortcut/x11/xinput2/enable");
SAVELOAD(bEnableGKey, "shortcut/gkey");
SAVELOAD(bEnableXboxInput, "shortcut/windows/xbox/enable");

int nshorts = settings_ptr->beginReadArray(QLatin1String("shortcuts"));
for (int i=0; i<nshorts; i++) {
@@ -1043,6 +1045,7 @@ void Settings::save() {
SAVELOAD(bSuppressMacEventTapWarning, "shortcut/mac/suppresswarning");
SAVELOAD(bEnableEvdev, "shortcut/linux/evdev/enable");
SAVELOAD(bEnableXInput2, "shortcut/x11/xinput2/enable");
SAVELOAD(bEnableXboxInput, "shortcut/windows/xbox/enable");

settings_ptr->beginWriteArray(QLatin1String("shortcuts"));
int idx = 0;
@@ -245,6 +245,7 @@ struct Settings {
bool bEnableEvdev;
bool bEnableXInput2;
bool bEnableGKey;
bool bEnableXboxInput;
QList<Shortcut> qlShortcuts;

enum MessageLog { LogNone = 0x00, LogConsole = 0x01, LogTTS = 0x02, LogBalloon = 0x04, LogSoundfile = 0x08};
@@ -0,0 +1,99 @@
/* Copyright (C) 2015, Mikkel Krautz <mikkel@krautz.dk>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
- Neither the name of the Mumble Developers nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include "mumble_pch.hpp"

#include "XboxInput.h"

const QUuid XboxInput::s_XboxInputGuid = QUuid(QString::fromLatin1("ca3937e3-640c-4d9e-9ef3-903f8b4fbcab"));

XboxInput::XboxInput()
: GetState(NULL)
, m_getStateFunc(NULL)
, m_getStateExFunc(NULL)
, m_xinputlib(NULL)
, m_valid(false) {

// Load the most suitable XInput DLL available.
//
// We prefer 1_4 and 1_3 over the others because they provide
// XInputGetStateEx(), which allows us to query the state of
// the guide button.
//
// See https://msdn.microsoft.com/en-us/library/windows/desktop/hh405051(v=vs.85).aspx
// for more information.
QStringList alternatives;
alternatives << QLatin1String("XInput1_4.dll");
alternatives << QLatin1String("xinput1_3.dll");
alternatives << QLatin1String("XInput9_1_0.dll");
alternatives << QLatin1String("xinput1_2.dll");
alternatives << QLatin1String("xinput1_1.dll");

foreach(const QString &lib, alternatives) {
m_xinputlib = LoadLibraryW(reinterpret_cast<const wchar_t *>(lib.utf16()));
if (m_xinputlib != NULL) {
qWarning("XboxInput: using XInput DLL '%s'", qPrintable(lib));
m_valid = true;
break;
}
}

if (!m_valid) {
qWarning("XboxInput: no valid XInput DLL was found on the system.");
return;
}

*(reinterpret_cast<void **>(&m_getStateFunc)) = reinterpret_cast<void *>(GetProcAddress(m_xinputlib, "XInputGetState"));
// Undocumented XInputGetStateEx -- ordinal 100. It is available in XInput 1.3 and greater.
// It provides access to the state of the guide button.
// For reference, see SDL's XInput support: http://www.libsdl.org/tmp/SDL/src/core/windows/SDL_xinput.c
*(reinterpret_cast<void **>(&m_getStateExFunc)) = reinterpret_cast<void *>(GetProcAddress(m_xinputlib, (char *)100));

if (m_getStateExFunc != NULL) {
GetState = m_getStateExFunc;
qWarning("XboxInput: using XInputGetStateEx() as querying function.");
} else if (m_getStateFunc != NULL) {
GetState = m_getStateFunc;
qWarning("XboxInput: using XInputGetState() as querying function.");
} else {
m_valid = false;
qWarning("XboxInput: no valid querying function found.");
}
}

XboxInput::~XboxInput() {
if (m_xinputlib) {
FreeLibrary(m_xinputlib);
}
}

bool XboxInput::isValid() const {
return m_valid;
}

0 comments on commit 608e802

Please sign in to comment.
You can’t perform that action at this time.