mirror of
https://github.com/mumble-voip/mumble.git
synced 2025-10-26 11:19:16 +00:00
The code accessed a variable ("nxboxinput") even when not existent, resulting in compiler errors.
This commit fixes the issue by adding the missing checks.
902 lines
27 KiB
C++
902 lines
27 KiB
C++
// Copyright 2005-2019 The Mumble Developers. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the LICENSE file at the root of the
|
|
// Mumble source tree or at <https://www.mumble.info/LICENSE>.
|
|
|
|
#include "GlobalShortcut_win.h"
|
|
|
|
#include "MainWindow.h"
|
|
#include "OverlayClient.h"
|
|
#include "Utils.h"
|
|
|
|
// MinGW does not support std::future/std::promise
|
|
// at present. Use Boost's implementation for now.
|
|
#define BOOST_THREAD_VERSION 4
|
|
#include <boost/thread.hpp>
|
|
#include <boost/thread/future.hpp>
|
|
|
|
#include <QtCore/QElapsedTimer>
|
|
#include <QtCore/QTimer>
|
|
|
|
// 3rdparty/xinputcheck-src.
|
|
#include <xinputcheck.h>
|
|
|
|
// We define a global macro called 'g'. This can lead to issues when included code uses 'g' as a type or parameter name (like protobuf 3.7 does). As such, for now, we have to make this our last include.
|
|
#include "Global.h"
|
|
|
|
#undef FAILED
|
|
#define FAILED(Status) (static_cast<HRESULT>(Status)<0)
|
|
|
|
#define DX_SAMPLE_BUFFER_SIZE 512
|
|
|
|
// from os_win.cpp
|
|
extern HWND mumble_mw_hwnd;
|
|
|
|
/// The QEvent::Type value to use for InjectKeyboardMessageEvent.
|
|
#define INJECTKEYBOARDMESSAGE_QEVENT (QEvent::User + 200)
|
|
/// The QEvent::Type value to use for InjectKeyboardMouseEvent.
|
|
#define INJECTMOUSEMESSAGE_QEVENT (QEvent::User + 201)
|
|
|
|
/// InjectKeyboardMessageEvent is an event that can be sent to
|
|
/// the GlobalShortcutWin engine to inject a native Windows keyboard
|
|
/// event into GlobalShortcutWin's event stream.
|
|
class InjectKeyboardMessageEvent : public QEvent {
|
|
Q_DISABLE_COPY(InjectKeyboardMessageEvent);
|
|
|
|
public:
|
|
boost::promise<bool> m_suppressionPromise;
|
|
DWORD m_scancode;
|
|
DWORD m_vkcode;
|
|
bool m_extended;
|
|
bool m_down;
|
|
|
|
/// Construct a new InjectKeyboardMessageEvent.
|
|
///
|
|
/// @param scancode The Windows scancode of the button.
|
|
/// @param vkcode The Windows virtual keycode of the button.
|
|
/// @param extended Indicates whether the button is an extended key in
|
|
/// Windows nomenclature. ("[...] such as the right-hand ALT
|
|
/// and CTRL keys that appear on an enhanced 101- or 102-key
|
|
/// keyboard")
|
|
/// @param down The down/pressed status of the keyboard button.
|
|
InjectKeyboardMessageEvent(DWORD scancode, DWORD vkcode, bool extended, bool down)
|
|
: QEvent(static_cast<QEvent::Type>(INJECTKEYBOARDMESSAGE_QEVENT))
|
|
, m_scancode(scancode)
|
|
, m_vkcode(vkcode)
|
|
, m_extended(extended)
|
|
, m_down(down) {}
|
|
|
|
inline boost::future<bool> shouldSuppress() {
|
|
return m_suppressionPromise.get_future();
|
|
}
|
|
};
|
|
|
|
/// InjectMouseMessageEvent is an event that can be sent to
|
|
/// the GlobalShortcutWin engine to inject a native Windows mouse
|
|
/// event into GlobalShortcutWin's event stream.
|
|
class InjectMouseMessageEvent : public QEvent {
|
|
Q_DISABLE_COPY(InjectMouseMessageEvent);
|
|
|
|
public:
|
|
boost::promise<bool> m_suppressionPromise;
|
|
unsigned int m_btn;
|
|
bool m_down;
|
|
|
|
/// Construct a new InjectMouseMessageEvent.
|
|
///
|
|
/// @param btn The DirectInput button index of the mouse event.
|
|
/// @param down The down/pressed status of the mouse button.
|
|
InjectMouseMessageEvent(unsigned int btn, bool down)
|
|
: QEvent(static_cast<QEvent::Type>(INJECTMOUSEMESSAGE_QEVENT))
|
|
, m_btn(btn)
|
|
, m_down(down) {}
|
|
|
|
inline boost::future<bool> shouldSuppress() {
|
|
return m_suppressionPromise.get_future();
|
|
}
|
|
};
|
|
|
|
uint qHash(const GUID &a) {
|
|
uint val = a.Data1 ^ a.Data2 ^ a.Data3;
|
|
for (int i=0;i<8;i++)
|
|
val += a.Data4[i];
|
|
return val;
|
|
}
|
|
|
|
/**
|
|
* Returns a platform specific GlobalShortcutEngine object.
|
|
*
|
|
* @see GlobalShortcutX
|
|
* @see GlobalShortcutMac
|
|
* @see GlobalShortcutWin
|
|
*/
|
|
GlobalShortcutEngine *GlobalShortcutEngine::platformInit() {
|
|
return new GlobalShortcutWin();
|
|
}
|
|
|
|
|
|
GlobalShortcutWin::GlobalShortcutWin()
|
|
: pDI(NULL)
|
|
, hhMouse(NULL)
|
|
, hhKeyboard(NULL)
|
|
, uiHardwareDevices(0)
|
|
#ifdef USE_GKEY
|
|
, gkey(NULL)
|
|
#endif
|
|
#ifdef USE_XBOXINPUT
|
|
, xboxinput(NULL)
|
|
, nxboxinput(0)
|
|
#endif
|
|
{
|
|
// Hidden setting to disable hooking
|
|
bHook = g.qs->value(QLatin1String("winhooks"), true).toBool();
|
|
|
|
moveToThread(this);
|
|
start(QThread::LowestPriority);
|
|
}
|
|
|
|
GlobalShortcutWin::~GlobalShortcutWin() {
|
|
quit();
|
|
wait();
|
|
}
|
|
|
|
void GlobalShortcutWin::run() {
|
|
if (FAILED(DirectInput8Create(GetModuleHandle(NULL), DIRECTINPUT_VERSION, IID_IDirectInput8, reinterpret_cast<void **>(&pDI), NULL))) {
|
|
qFatal("GlobalShortcutWin: Failed to create d8input");
|
|
return;
|
|
}
|
|
|
|
// Print the user's LowLevelHooksTimeout registry key for debugging purposes.
|
|
// On Windows 7 and greater, Windows will silently remove badly behaving hooks
|
|
// without telling the application. Users can tweak the timeout themselves
|
|
// with this registry key.
|
|
HKEY key = NULL;
|
|
DWORD type = 0;
|
|
DWORD value = 0;
|
|
DWORD len = sizeof(DWORD);
|
|
if (RegOpenKeyExA(HKEY_CURRENT_USER, "Control Panel\\Desktop", 0, KEY_READ, &key) == ERROR_SUCCESS) {
|
|
LONG err = RegQueryValueExA(key, "LowLevelHooksTimeout", NULL, &type, reinterpret_cast<LPBYTE>(&value), &len);
|
|
if (err == ERROR_SUCCESS && type == REG_DWORD) {
|
|
qWarning("GlobalShortcutWin: Found LowLevelHooksTimeout with value = 0x%lx", static_cast<unsigned long>(value));
|
|
} else if (err == ERROR_FILE_NOT_FOUND) {
|
|
qWarning("GlobalShortcutWin: No LowLevelHooksTimeout registry key found.");
|
|
} else {
|
|
qWarning("GlobalShortcutWin: Error looking up LowLevelHooksTimeout. (Error: 0x%lx, Type: 0x%lx, Value: 0x%lx)", static_cast<unsigned long>(err), static_cast<unsigned long>(type), static_cast<unsigned long>(value));
|
|
}
|
|
}
|
|
|
|
// Wait for MainWindow's constructor to finish before we enumerate DirectInput devices.
|
|
// We need to do this because adding a new device requires a Window handle. (SetCooperativeLevel())
|
|
while (! g.mw)
|
|
this->yieldCurrentThread();
|
|
|
|
#ifdef USE_GKEY
|
|
if (g.s.bEnableGKey) {
|
|
gkey = new GKeyLibrary();
|
|
qWarning("GlobalShortcutWin: GKeys initialized, isValid: %d", gkey->isValid());
|
|
}
|
|
#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;
|
|
connect(timer, SIGNAL(timeout()), this, SLOT(timeTicked()));
|
|
timer->start(20);
|
|
|
|
setPriority(QThread::TimeCriticalPriority);
|
|
|
|
exec();
|
|
|
|
delete timer;
|
|
|
|
#ifdef USE_GKEY
|
|
delete gkey;
|
|
#endif
|
|
|
|
#ifdef USE_XBOXINPUT
|
|
delete xboxinput;
|
|
#endif
|
|
|
|
if (bHook) {
|
|
if (hhMouse != NULL) {
|
|
UnhookWindowsHookEx(hhMouse);
|
|
}
|
|
if (hhKeyboard != NULL) {
|
|
UnhookWindowsHookEx(hhKeyboard);
|
|
}
|
|
}
|
|
|
|
foreach(InputDevice *id, qhInputDevices) {
|
|
if (id->pDID) {
|
|
id->pDID->Unacquire();
|
|
id->pDID->Release();
|
|
}
|
|
delete id;
|
|
}
|
|
pDI->Release();
|
|
}
|
|
|
|
bool GlobalShortcutWin::event(QEvent *event) {
|
|
QEvent::Type type = event->type();
|
|
if (type == INJECTKEYBOARDMESSAGE_QEVENT) {
|
|
InjectKeyboardMessageEvent *ikme = static_cast<InjectKeyboardMessageEvent *>(event);
|
|
bool suppress = handleKeyboardMessage(ikme->m_scancode, ikme->m_vkcode, ikme->m_extended, ikme->m_down);
|
|
ikme->m_suppressionPromise.set_value(suppress);
|
|
return true;
|
|
} else if (type == INJECTMOUSEMESSAGE_QEVENT) {
|
|
InjectMouseMessageEvent *imme = static_cast<InjectMouseMessageEvent *>(event);
|
|
bool suppress = handleMouseMessage(imme->m_btn, imme->m_down);
|
|
imme->m_suppressionPromise.set_value(suppress);
|
|
return true;
|
|
}
|
|
return GlobalShortcutEngine::event(event);
|
|
}
|
|
|
|
bool GlobalShortcutWin::injectKeyboardMessage(MSG *msg) {
|
|
if (!bHook) {
|
|
return false;
|
|
}
|
|
|
|
// Only allow keyboard messages.
|
|
switch (msg->message) {
|
|
case WM_KEYDOWN:
|
|
case WM_KEYUP:
|
|
case WM_SYSKEYDOWN:
|
|
case WM_SYSKEYUP:
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
DWORD scancode = (msg->lParam >> 16) & 0xff;
|
|
DWORD vkcode = msg->wParam;
|
|
bool extended = !!(msg->lParam & 0x01000000);
|
|
bool up = !!(msg->lParam & 0x80000000);
|
|
|
|
InjectKeyboardMessageEvent *ikme = new InjectKeyboardMessageEvent(scancode, vkcode, extended, !up);
|
|
boost::future<bool> suppress = ikme->shouldSuppress();
|
|
qApp->postEvent(this, ikme);
|
|
return suppress.get();
|
|
}
|
|
|
|
bool GlobalShortcutWin::injectMouseMessage(MSG *msg) {
|
|
if (!bHook) {
|
|
return false;
|
|
}
|
|
|
|
bool down = false;
|
|
unsigned int btn = 0;
|
|
|
|
// Convert the Windows mouse message into a DirectInput
|
|
// button index, and store the pressed state of the button.
|
|
switch (msg->message) {
|
|
case WM_LBUTTONDOWN:
|
|
down = true;
|
|
case WM_LBUTTONUP:
|
|
btn = 3;
|
|
break;
|
|
case WM_RBUTTONDOWN:
|
|
down = true;
|
|
case WM_RBUTTONUP:
|
|
btn = 4;
|
|
break;
|
|
case WM_MBUTTONDOWN:
|
|
down = true;
|
|
case WM_MBUTTONUP:
|
|
btn = 5;
|
|
break;
|
|
case WM_XBUTTONDOWN:
|
|
down = true;
|
|
case WM_XBUTTONUP: {
|
|
unsigned int offset = (msg->wParam >> 16) & 0xffff;
|
|
btn = 5 + offset;
|
|
}
|
|
default:
|
|
// Non-mouse event. Return early.
|
|
return false;
|
|
}
|
|
|
|
InjectMouseMessageEvent *imme = new InjectMouseMessageEvent(btn, down);
|
|
boost::future<bool> suppress = imme->shouldSuppress();
|
|
qApp->postEvent(this, imme);
|
|
return suppress.get();
|
|
}
|
|
|
|
bool GlobalShortcutWin::handleKeyboardMessage(DWORD scancode, DWORD vkcode, bool extended, bool down) {
|
|
GlobalShortcutWin *gsw = static_cast<GlobalShortcutWin *>(engine);
|
|
|
|
QList<QVariant> ql;
|
|
|
|
// Convert the low-level key event to
|
|
// a DirectInput key ID.
|
|
unsigned int keyid = static_cast<unsigned int>((scancode << 8) | 0x4);
|
|
if (extended) {
|
|
keyid |= 0x8000U;
|
|
}
|
|
|
|
// NumLock and Pause need special handling.
|
|
// For those keys, the method above of setting
|
|
// bit 15 high when the LLKHF_EXTENDED flag is
|
|
// set on the low-level key event does not work.
|
|
//
|
|
// When we receive a low-level Windows
|
|
// Pause key event, the extended flag isn't
|
|
// set, but DirectInput expects it to be.
|
|
//
|
|
// The opposite is true for NumLock key,
|
|
// where the extended flag for the low-level
|
|
// Windows event is set, but DirectInput expects
|
|
// it not to be.
|
|
//
|
|
// Without this fix-up, we would emit Pause as
|
|
// NumLock, and NumLock as pause. That was
|
|
// problematic, because at the same time,
|
|
// DirectInput would emit the correct key.
|
|
// This meant that when pressing one of Pause
|
|
// and NumLock, shortcut actions for both keys
|
|
// would be triggered.
|
|
//
|
|
// Originally reported in mumble-voip/mumble#1353
|
|
if (vkcode == VK_PAUSE) {
|
|
// Always set the extended bit for Pause.
|
|
keyid |= 0x8000U;
|
|
} else if (vkcode == VK_NUMLOCK) {
|
|
// Never set the extended bit for NumLock.
|
|
keyid &= ~0x8000U;
|
|
}
|
|
|
|
ql << keyid;
|
|
ql << QVariant(QUuid(GUID_SysKeyboard));
|
|
bool suppress = gsw->handleButton(ql, down);
|
|
|
|
return suppress;
|
|
}
|
|
|
|
bool GlobalShortcutWin::handleMouseMessage(unsigned int btn, bool down) {
|
|
GlobalShortcutWin *gsw = static_cast<GlobalShortcutWin *>(engine);
|
|
|
|
bool suppress = false;
|
|
|
|
if (btn > 0) {
|
|
QList<QVariant> ql;
|
|
ql << static_cast<unsigned int>((btn << 8) | 0x4);
|
|
ql << QVariant(QUuid(GUID_SysMouse));
|
|
|
|
// Do not suppress LBUTTONUP though (so suppression can be deactivated via mouse).
|
|
bool wantsuppress = gsw->handleButton(ql, down);
|
|
suppress = wantsuppress && (btn != 3);
|
|
}
|
|
|
|
return suppress;
|
|
}
|
|
|
|
LRESULT CALLBACK GlobalShortcutWin::HookKeyboard(int nCode, WPARAM wParam, LPARAM lParam) {
|
|
GlobalShortcutWin *gsw=static_cast<GlobalShortcutWin *>(engine);
|
|
KBDLLHOOKSTRUCT *key=reinterpret_cast<KBDLLHOOKSTRUCT *>(lParam);
|
|
|
|
#ifndef QT_NO_DEBUG
|
|
static int safety = 0;
|
|
|
|
if ((++safety < 100) && (nCode >= 0)) {
|
|
#else
|
|
if (nCode >= 0) {
|
|
#endif
|
|
DWORD scancode = key->scanCode;
|
|
DWORD vkcode = key->vkCode;
|
|
bool extended = !!(key->flags & LLKHF_EXTENDED);
|
|
bool up = !!(key->flags & LLKHF_UP);
|
|
bool suppress = handleKeyboardMessage(scancode, vkcode, extended, !up);
|
|
if (suppress) {
|
|
return 1;
|
|
}
|
|
}
|
|
return CallNextHookEx(gsw->hhKeyboard, nCode, wParam, lParam);
|
|
}
|
|
|
|
LRESULT CALLBACK GlobalShortcutWin::HookMouse(int nCode, WPARAM wParam, LPARAM lParam) {
|
|
GlobalShortcutWin *gsw=static_cast<GlobalShortcutWin *>(engine);
|
|
MSLLHOOKSTRUCT *mouse=reinterpret_cast<MSLLHOOKSTRUCT *>(lParam);
|
|
|
|
if (nCode >= 0) {
|
|
bool suppress = false;
|
|
UINT msg = wParam;
|
|
// Convert the hooked Windows mouse message into a DirectInput
|
|
// button index, and store the pressed state of the button.
|
|
bool down = false;
|
|
unsigned int btn = 0;
|
|
switch (msg) {
|
|
case WM_LBUTTONDOWN:
|
|
down = true;
|
|
case WM_LBUTTONUP:
|
|
btn = 3;
|
|
break;
|
|
case WM_RBUTTONDOWN:
|
|
down = true;
|
|
case WM_RBUTTONUP:
|
|
btn = 4;
|
|
break;
|
|
case WM_MBUTTONDOWN:
|
|
down = true;
|
|
case WM_MBUTTONUP:
|
|
btn = 5;
|
|
break;
|
|
case WM_XBUTTONDOWN:
|
|
down = true;
|
|
case WM_XBUTTONUP:
|
|
btn = 5 + (mouse->mouseData >> 16);
|
|
default:
|
|
break;
|
|
}
|
|
if (btn > 0) {
|
|
suppress = handleMouseMessage(btn, down);
|
|
if (suppress) {
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
return CallNextHookEx(gsw->hhMouse, nCode, wParam, lParam);
|
|
}
|
|
|
|
BOOL CALLBACK GlobalShortcutWin::EnumDeviceObjectsCallback(LPCDIDEVICEOBJECTINSTANCE lpddoi, LPVOID pvRef) {
|
|
InputDevice *id=static_cast<InputDevice *>(pvRef);
|
|
QString name = QString::fromUtf16(reinterpret_cast<const ushort *>(lpddoi->tszName));
|
|
id->qhNames[lpddoi->dwType] = name;
|
|
|
|
if (g.s.bDirectInputVerboseLogging) {
|
|
qWarning("GlobalShortcutWin: EnumObjects: device %s %s object 0x%.8lx %s",
|
|
qPrintable(QUuid(id->guid).toString()),
|
|
qPrintable(id->name),
|
|
static_cast<unsigned long>(lpddoi->dwType),
|
|
qPrintable(name));
|
|
}
|
|
|
|
return DIENUM_CONTINUE;
|
|
}
|
|
|
|
BOOL GlobalShortcutWin::EnumDevicesCB(LPCDIDEVICEINSTANCE pdidi, LPVOID pContext) {
|
|
GlobalShortcutWin *cbgsw=static_cast<GlobalShortcutWin *>(pContext);
|
|
HRESULT hr;
|
|
|
|
QString name = QString::fromUtf16(reinterpret_cast<const ushort *>(pdidi->tszProductName));
|
|
QString sname = QString::fromUtf16(reinterpret_cast<const ushort *>(pdidi->tszInstanceName));
|
|
|
|
InputDevice *id = new InputDevice;
|
|
|
|
id->pDID = NULL;
|
|
|
|
id->name = name;
|
|
|
|
id->guid = pdidi->guidInstance;
|
|
id->vguid = QVariant(QUuid(id->guid).toString());
|
|
|
|
id->guidproduct = pdidi->guidProduct;
|
|
id->vguidproduct = QVariant(QUuid(id->guidproduct).toString());
|
|
|
|
// Is it an XInput device? Skip it.
|
|
//
|
|
// This check is not restricted to USE_XBOXINPUT because
|
|
// Windows 10 (10586.122, ~March 2016) has issues with
|
|
// using XInput devices via DirectInput.
|
|
//
|
|
// See issues mumble-voip/mumble#2104 and mumble-voip/mumble#2147
|
|
// for more information.
|
|
if (XInputCheck_IsGuidProductXInputDevice(&id->guidproduct)) {
|
|
#ifdef USE_XBOXINPUT
|
|
cbgsw->nxboxinput += 1;
|
|
#endif
|
|
qWarning("GlobalShortcutWin: excluded XInput device '%s' (guid %s guid product %s) from DirectInput",
|
|
qPrintable(id->name),
|
|
qPrintable(id->vguid.toString()),
|
|
qPrintable(id->vguidproduct.toString()));
|
|
delete id;
|
|
return DIENUM_CONTINUE;
|
|
}
|
|
|
|
// Check for PIDVID at the end of the GUID, as
|
|
// per http://stackoverflow.com/q/25622780.
|
|
BYTE pidvid[8] = { 0, 0, 'P', 'I', 'D', 'V', 'I', 'D' };
|
|
if (memcmp(id->guidproduct.Data4, pidvid, 8) == 0) {
|
|
uint16_t vendor_id = id->guidproduct.Data1 & 0xffff;
|
|
uint16_t product_id = (id->guidproduct.Data1 >> 16) & 0xffff;
|
|
|
|
id->vendor_id = vendor_id;
|
|
id->product_id = product_id;
|
|
} else {
|
|
id->vendor_id = 0x00;
|
|
id->product_id = 0x00;
|
|
}
|
|
|
|
// Reject devices if they are blacklisted.
|
|
//
|
|
// Device Name: ODAC-revB
|
|
// Vendor/Product ID: 0x262A, 0x1048
|
|
// https://github.com/mumble-voip/mumble/issues/1977
|
|
//
|
|
// Device Name: Aune T1 MK2 - HID-compliant consumer control device
|
|
// Vendor/Product ID: 0x262A, 0x1168
|
|
// https://github.com/mumble-voip/mumble/issues/1880
|
|
//
|
|
// For now, we simply disable the 0x262A vendor ID.
|
|
//
|
|
// 0x26A is SAVITECH Corp.
|
|
// http://www.savitech-ic.com/, or
|
|
// http://www.saviaudio.com/product.html
|
|
// (via https://usb-ids.gowdy.us/read/UD/262a)
|
|
//
|
|
// In the future, if there are more devices in the
|
|
// blacklist, we need a more structured aproach.
|
|
{
|
|
if (id->vendor_id == 0x262A) {
|
|
qWarning("GlobalShortcutWin: rejected blacklisted device %s (GUID: %s, PGUID: %s, VID: 0x%.4x, PID: 0x%.4x, TYPE: 0x%.8lx)",
|
|
qPrintable(id->name),
|
|
qPrintable(id->vguid.toString()),
|
|
qPrintable(id->vguidproduct.toString()),
|
|
id->vendor_id,
|
|
id->product_id,
|
|
static_cast<unsigned long>(pdidi->dwDevType));
|
|
delete id;
|
|
return DIENUM_CONTINUE;
|
|
}
|
|
}
|
|
|
|
foreach(InputDevice *dev, cbgsw->qhInputDevices) {
|
|
if (dev->guid == id->guid) {
|
|
delete id;
|
|
return DIENUM_CONTINUE;
|
|
}
|
|
}
|
|
|
|
if (FAILED(hr = cbgsw->pDI->CreateDevice(pdidi->guidInstance, &id->pDID, NULL)))
|
|
qFatal("GlobalShortcutWin: CreateDevice: %lx", hr);
|
|
|
|
if (FAILED(hr = id->pDID->EnumObjects(EnumDeviceObjectsCallback, static_cast<void *>(id), DIDFT_BUTTON)))
|
|
qFatal("GlobalShortcutWin: EnumObjects: %lx", hr);
|
|
|
|
if (id->qhNames.count() > 0) {
|
|
QList<DWORD> types = id->qhNames.keys();
|
|
std::sort(types.begin(), types.end());
|
|
|
|
int nbuttons = types.count();
|
|
STACKVAR(DIOBJECTDATAFORMAT, rgodf, nbuttons);
|
|
DIDATAFORMAT df;
|
|
ZeroMemory(&df, sizeof(df));
|
|
df.dwSize = sizeof(df);
|
|
df.dwObjSize = sizeof(DIOBJECTDATAFORMAT);
|
|
df.dwFlags=DIDF_ABSAXIS;
|
|
df.dwDataSize = (nbuttons + 3) & (~0x3);
|
|
df.dwNumObjs = nbuttons;
|
|
df.rgodf = rgodf;
|
|
for (int i=0;i<nbuttons;i++) {
|
|
ZeroMemory(& rgodf[i], sizeof(DIOBJECTDATAFORMAT));
|
|
DWORD dwType = types[i];
|
|
DWORD dwOfs = i;
|
|
rgodf[i].dwOfs = dwOfs;
|
|
rgodf[i].dwType = dwType;
|
|
id->qhOfsToType[dwOfs] = dwType;
|
|
id->qhTypeToOfs[dwType] = dwOfs;
|
|
}
|
|
|
|
if (FAILED(hr = id->pDID->SetCooperativeLevel(mumble_mw_hwnd, DISCL_NONEXCLUSIVE|DISCL_BACKGROUND)))
|
|
qFatal("GlobalShortcutWin: SetCooperativeLevel: %lx", hr);
|
|
|
|
if (FAILED(hr = id->pDID->SetDataFormat(&df)))
|
|
qFatal("GlobalShortcutWin: SetDataFormat: %lx", hr);
|
|
|
|
DIPROPDWORD dipdw;
|
|
|
|
dipdw.diph.dwSize = sizeof(DIPROPDWORD);
|
|
dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
|
|
dipdw.diph.dwObj = 0;
|
|
dipdw.diph.dwHow = DIPH_DEVICE;
|
|
dipdw.dwData = DX_SAMPLE_BUFFER_SIZE;
|
|
|
|
if (FAILED(hr = id->pDID->SetProperty(DIPROP_BUFFERSIZE, &dipdw.diph)))
|
|
qFatal("GlobalShortcutWin: SetProperty: %lx", hr);
|
|
|
|
qWarning("Adding device %s %s %s:%d type 0x%.8lx guid product %s",
|
|
qPrintable(QUuid(id->guid).toString()),
|
|
qPrintable(name),
|
|
qPrintable(sname),
|
|
id->qhNames.count(),
|
|
static_cast<unsigned long>(pdidi->dwDevType),
|
|
qPrintable(id->vguidproduct.toString()));
|
|
|
|
cbgsw->qhInputDevices[id->guid] = id;
|
|
} else {
|
|
id->pDID->Release();
|
|
delete id;
|
|
}
|
|
|
|
return DIENUM_CONTINUE;
|
|
}
|
|
|
|
void GlobalShortcutWin::timeTicked() {
|
|
if (g.mw->uiNewHardware != uiHardwareDevices) {
|
|
uiHardwareDevices = g.mw->uiNewHardware;
|
|
|
|
XInputCheck_ClearDeviceCache();
|
|
#ifdef USE_XBOXINPUT
|
|
nxboxinput = 0;
|
|
#endif
|
|
pDI->EnumDevices(DI8DEVCLASS_ALL, EnumDevicesCB, static_cast<void *>(this), DIEDFL_ATTACHEDONLY);
|
|
}
|
|
|
|
if (bNeedRemap)
|
|
remap();
|
|
|
|
foreach(InputDevice *id, qhInputDevices) {
|
|
DIDEVICEOBJECTDATA rgdod[DX_SAMPLE_BUFFER_SIZE];
|
|
DWORD dwItems = DX_SAMPLE_BUFFER_SIZE;
|
|
HRESULT hr;
|
|
|
|
hr = id->pDID->Acquire();
|
|
|
|
switch (hr) {
|
|
case DI_OK:
|
|
case S_FALSE:
|
|
break;
|
|
case DIERR_UNPLUGGED:
|
|
case DIERR_GENERIC:
|
|
qWarning("Removing device %s", qPrintable(QUuid(id->guid).toString()));
|
|
id->pDID->Release();
|
|
qhInputDevices.remove(id->guid);
|
|
delete id;
|
|
return;
|
|
case DIERR_OTHERAPPHASPRIO:
|
|
continue;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
{
|
|
QElapsedTimer timer;
|
|
timer.start();
|
|
|
|
id->pDID->Poll();
|
|
|
|
// If a call to Poll takes more than
|
|
// a second, warn the user that they
|
|
// might have a misbehaving device.
|
|
if (timer.elapsed() > 1000) {
|
|
qWarning("GlobalShortcut_win: Poll() for device %s took %li msec. This is abnormal, the device is possibly misbehaving...", qPrintable(QUuid(id->guid).toString()), static_cast<long>(timer.elapsed()));
|
|
}
|
|
}
|
|
|
|
hr = id->pDID->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), rgdod, &dwItems, 0);
|
|
if (FAILED(hr))
|
|
continue;
|
|
|
|
if (dwItems <= 0)
|
|
continue;
|
|
|
|
for (DWORD j=0; j<dwItems; j++) {
|
|
QList<QVariant> ql;
|
|
|
|
quint32 uiType = id->qhOfsToType.value(rgdod[j].dwOfs);
|
|
ql << uiType;
|
|
ql << id->vguid;
|
|
handleButton(ql, rgdod[j].dwData & 0x80);
|
|
}
|
|
}
|
|
|
|
#ifdef USE_GKEY
|
|
if (g.s.bEnableGKey && gkey != NULL && gkey->isValid()) {
|
|
for (int button = GKEY_MIN_MOUSE_BUTTON; button <= GKEY_MAX_MOUSE_BUTTON; button++) {
|
|
QList<QVariant> ql;
|
|
ql << button;
|
|
ql << GKeyLibrary::quMouse;
|
|
handleButton(ql, gkey->isMouseButtonPressed(button));
|
|
}
|
|
for (int mode = GKEY_MIN_KEYBOARD_MODE; mode <= GKEY_MAX_KEYBOARD_MODE; mode++) {
|
|
for (int key = GKEY_MIN_KEYBOARD_BUTTON; key <= GKEY_MAX_KEYBOARD_BUTTON; key++) {
|
|
QList<QVariant> ql;
|
|
// Store the key and mode in one int
|
|
// bit 0..15: mode, bit 16..31: key
|
|
ql << (key | (mode << 16));
|
|
ql << GKeyLibrary::quKeyboard;
|
|
handleButton(ql, gkey->isKeyboardGkeyPressed(key, mode));
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef USE_XBOXINPUT
|
|
if (g.s.bEnableXboxInput && xboxinput != NULL && xboxinput->isValid() && nxboxinput > 0) {
|
|
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
|
|
|
|
// Initialize winhooks.
|
|
//
|
|
// We do this here, because at this point, we've just run our
|
|
// first timeTicked() slot. The GlobalShortcut_win thread's event
|
|
// loop has nothing else to do at this point, so there is nothing
|
|
// that blocks the callbacks of the hooks.
|
|
//
|
|
// That is, if we initialize here, our callbacks *can* be called
|
|
// immediately after initialization, which gives the best results
|
|
// as far as interactivity and user experience goes. The initialization
|
|
// cannot be "felt".
|
|
//
|
|
// Let me explain...
|
|
//
|
|
// Originally, this code lived in the body of run, ::run(), just
|
|
// before exec() was called.
|
|
//
|
|
// It turns out that if our hooks are initialized there, it can take
|
|
// a short while before the mouse and keyboard callbacks can be processed.
|
|
//
|
|
// During this time, where the mouse callback is not able to be called,
|
|
// the mouse in Windows becomes laggy. It makes the whole computer feel
|
|
// like it has locked up -- because input "stops".
|
|
//
|
|
// As explained above, initializing the hooks here yields a much superior
|
|
// experience, where this initialization has no observable effect on the
|
|
// behavior of the system's mouse input.
|
|
if (bHook && hhMouse == NULL && hhKeyboard == NULL) {
|
|
HMODULE hSelf;
|
|
GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, reinterpret_cast<LPCTSTR>(&HookKeyboard), &hSelf);
|
|
hhMouse = SetWindowsHookEx(WH_MOUSE_LL, HookMouse, hSelf, 0);
|
|
hhKeyboard = SetWindowsHookEx(WH_KEYBOARD_LL, HookKeyboard, hSelf, 0);
|
|
}
|
|
}
|
|
|
|
QString GlobalShortcutWin::buttonName(const QVariant &v) {
|
|
GlobalShortcutWin *gsw = static_cast<GlobalShortcutWin *>(GlobalShortcutEngine::engine);
|
|
|
|
const QList<QVariant> &sublist = v.toList();
|
|
if (sublist.count() != 2)
|
|
return QString();
|
|
|
|
bool ok = false;
|
|
DWORD type = sublist.at(0).toUInt(&ok);
|
|
QUuid guid(sublist.at(1).toString());
|
|
|
|
if (guid.isNull() || (!ok))
|
|
return QString();
|
|
|
|
QString device=guid.toString();
|
|
QString name=QLatin1String("Unknown");
|
|
|
|
#ifdef USE_GKEY
|
|
if (g.s.bEnableGKey && gkey != NULL && gkey->isValid()) {
|
|
bool isGKey = false;
|
|
if (guid == GKeyLibrary::quMouse) {
|
|
isGKey = true;
|
|
name = gkey->getMouseButtonString(type);
|
|
} else if (guid == GKeyLibrary::quKeyboard) {
|
|
isGKey = true;
|
|
name = gkey->getKeyboardGkeyString(type & 0xFFFF, type >> 16);
|
|
}
|
|
if (isGKey) {
|
|
device = QLatin1String("GKey:");
|
|
return device + name; // Example output: "Gkey:G6/M1"
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef USE_XBOXINPUT
|
|
if (g.s.bEnableXboxInput && xboxinput != NULL && xboxinput->isValid() && guid == XboxInput::s_XboxInputGuid) {
|
|
uint32_t idx = (type >> 24) & 0xff;
|
|
uint32_t button = (type & 0x00ffffff);
|
|
|
|
// 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:");
|
|
else if (guid == GUID_SysKeyboard)
|
|
device=QLatin1String("K:");
|
|
else if (id)
|
|
device=id->name+QLatin1String(":");
|
|
if (id) {
|
|
QString result = id->qhNames.value(type);
|
|
if (!result.isEmpty()) {
|
|
name = result;
|
|
}
|
|
}
|
|
return device+name;
|
|
}
|
|
|
|
bool GlobalShortcutWin::canSuppress() {
|
|
return bHook;
|
|
}
|