896 lines
26 KiB
C++
896 lines
26 KiB
C++
// Copyright 2007-2023 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>.
|
|
|
|
// For detailed info about RAWKEYBOARD handling:
|
|
// https://blog.molecular-matters.com/2011/09/05/properly-handling-keyboard-input
|
|
|
|
#ifdef _MSVC_LANG
|
|
# pragma warning(push)
|
|
// SPSCQueue does some funky alignment tricks which trigger the C4316
|
|
// warning about potential misalignment on the heap.
|
|
// We just have to trust the SPSCQueue implementation here.
|
|
# pragma warning(disable : 4316)
|
|
#endif
|
|
|
|
#include "GlobalShortcut_win.h"
|
|
|
|
#include "Global.h"
|
|
|
|
#ifdef USE_GKEY
|
|
# include "GKey.h"
|
|
#endif
|
|
|
|
#include <codecvt>
|
|
#include <iomanip>
|
|
#include <mutex>
|
|
#include <sstream>
|
|
|
|
#include <QUuid>
|
|
|
|
extern "C" {
|
|
// clang-format off
|
|
// Do NOT change the order of the includes below or compile errors will occur!
|
|
#include <hidsdi.h>
|
|
#include <hidpi.h>
|
|
// clang-format on
|
|
}
|
|
|
|
// From os_win.cpp
|
|
extern HWND mumble_mw_hwnd;
|
|
|
|
struct InputHid {
|
|
uint32_t button = 0;
|
|
std::string deviceName;
|
|
std::string devicePrefix;
|
|
|
|
friend QDataStream &operator<<(QDataStream &stream, const InputHid &output) {
|
|
stream << static_cast< quint32 >(output.button);
|
|
stream << QString::fromStdString(output.deviceName);
|
|
stream << QString::fromStdString(output.devicePrefix);
|
|
|
|
return stream;
|
|
}
|
|
|
|
friend QDataStream &operator>>(QDataStream &stream, InputHid &input) {
|
|
quint32 button;
|
|
QString deviceName;
|
|
QString devicePrefix;
|
|
|
|
stream >> button;
|
|
stream >> deviceName;
|
|
stream >> devicePrefix;
|
|
|
|
input.button = static_cast< uint32_t >(button);
|
|
input.deviceName = deviceName.toStdString();
|
|
input.devicePrefix = devicePrefix.toStdString();
|
|
|
|
return stream;
|
|
}
|
|
|
|
bool operator==(const InputHid &rhs) const {
|
|
return this->button == rhs.button && this->deviceName == rhs.deviceName
|
|
&& this->devicePrefix == rhs.devicePrefix;
|
|
}
|
|
|
|
bool operator<(const InputHid &rhs) const {
|
|
return this->button < rhs.button && this->deviceName < rhs.deviceName && this->devicePrefix < rhs.devicePrefix;
|
|
}
|
|
};
|
|
Q_DECLARE_METATYPE(InputHid)
|
|
|
|
struct InputKeyboard {
|
|
bool e0;
|
|
uint16_t code;
|
|
|
|
friend QDataStream &operator<<(QDataStream &stream, const InputKeyboard &output) {
|
|
stream << output.e0;
|
|
stream << static_cast< quint16 >(output.code);
|
|
|
|
return stream;
|
|
}
|
|
|
|
friend QDataStream &operator>>(QDataStream &stream, InputKeyboard &input) {
|
|
quint16 code;
|
|
|
|
stream >> input.e0;
|
|
stream >> code;
|
|
|
|
input.code = static_cast< uint16_t >(code);
|
|
|
|
return stream;
|
|
}
|
|
|
|
bool operator==(const InputKeyboard &rhs) const { return this->e0 == rhs.e0 && this->code == rhs.code; }
|
|
|
|
bool operator<(const InputKeyboard &rhs) const { return this->e0 < rhs.e0 && this->code < rhs.code; }
|
|
};
|
|
Q_DECLARE_METATYPE(InputKeyboard)
|
|
|
|
enum class InputMouse { Left = 1, Right, Middle, Side_1, Side_2 };
|
|
Q_DECLARE_METATYPE(InputMouse)
|
|
|
|
#ifdef USE_XBOXINPUT
|
|
struct InputXinput {
|
|
uint8_t device;
|
|
uint8_t code;
|
|
|
|
friend QDataStream &operator<<(QDataStream &stream, const InputXinput &output) {
|
|
stream << static_cast< qint8 >(output.device);
|
|
stream << static_cast< qint8 >(output.code);
|
|
|
|
return stream;
|
|
}
|
|
|
|
friend QDataStream &operator>>(QDataStream &stream, InputXinput &input) {
|
|
quint8 device;
|
|
quint8 code;
|
|
|
|
stream >> device;
|
|
stream >> code;
|
|
|
|
input.device = static_cast< uint8_t >(device);
|
|
input.code = static_cast< uint8_t >(code);
|
|
|
|
return stream;
|
|
}
|
|
|
|
bool operator==(const InputXinput &rhs) const { return this->device == rhs.device && this->code == rhs.code; }
|
|
|
|
bool operator<(const InputXinput &rhs) const { return this->device < rhs.device && this->code < rhs.code; }
|
|
};
|
|
Q_DECLARE_METATYPE(InputXinput)
|
|
#endif
|
|
#ifdef USE_GKEY
|
|
struct InputGkey {
|
|
bool keyboard;
|
|
uint8_t button;
|
|
uint8_t mode;
|
|
|
|
friend QDataStream &operator<<(QDataStream &stream, const InputGkey &output) {
|
|
stream << output.keyboard;
|
|
stream << static_cast< qint8 >(output.button);
|
|
stream << static_cast< qint8 >(output.mode);
|
|
|
|
return stream;
|
|
}
|
|
|
|
friend QDataStream &operator>>(QDataStream &stream, InputGkey &input) {
|
|
quint8 button;
|
|
quint8 mode;
|
|
|
|
stream >> input.keyboard;
|
|
stream >> button;
|
|
stream >> mode;
|
|
|
|
input.button = static_cast< uint8_t >(button);
|
|
input.mode = static_cast< uint8_t >(mode);
|
|
|
|
return stream;
|
|
}
|
|
|
|
bool operator==(const InputGkey &rhs) const {
|
|
return this->keyboard == rhs.keyboard && this->button == rhs.button && this->mode == rhs.mode;
|
|
}
|
|
|
|
bool operator<(const InputGkey &rhs) const {
|
|
return this->keyboard < rhs.keyboard && this->button < rhs.button && this->mode < rhs.mode;
|
|
}
|
|
};
|
|
Q_DECLARE_METATYPE(InputGkey)
|
|
#endif
|
|
|
|
GlobalShortcutEngine *GlobalShortcutEngine::platformInit() {
|
|
return new GlobalShortcutWin();
|
|
}
|
|
|
|
void GlobalShortcutWin::registerMetaTypes() {
|
|
static bool registered = false;
|
|
|
|
// Only register the MetaTypes once.
|
|
if (!registered) {
|
|
registered = true;
|
|
|
|
qRegisterMetaType< InputHid >();
|
|
qRegisterMetaTypeStreamOperators< InputHid >();
|
|
QMetaType::registerComparators< InputHid >();
|
|
|
|
qRegisterMetaType< InputKeyboard >();
|
|
qRegisterMetaTypeStreamOperators< InputKeyboard >();
|
|
QMetaType::registerComparators< InputKeyboard >();
|
|
|
|
qRegisterMetaType< InputMouse >();
|
|
qRegisterMetaTypeStreamOperators< InputMouse >();
|
|
QMetaType::registerComparators< InputMouse >();
|
|
#ifdef USE_XBOXINPUT
|
|
qRegisterMetaType< InputXinput >();
|
|
qRegisterMetaTypeStreamOperators< InputXinput >();
|
|
QMetaType::registerComparators< InputXinput >();
|
|
#endif
|
|
#ifdef USE_GKEY
|
|
qRegisterMetaType< InputGkey >();
|
|
qRegisterMetaTypeStreamOperators< InputGkey >();
|
|
QMetaType::registerComparators< InputGkey >();
|
|
#endif
|
|
}
|
|
}
|
|
|
|
QList< Shortcut > GlobalShortcutWin::migrateSettings(const QList< Shortcut > &oldShortcuts) {
|
|
constexpr QUuid KEYBOARD_UUID(0x6F1D2B61, 0xD5A0, 0x11CF, 0xBF, 0xC7, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00);
|
|
constexpr QUuid MOUSE_UUID(0x6F1D2B60, 0xD5A0, 0x11CF, 0xBF, 0xC7, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00);
|
|
constexpr QUuid XINPUT_UUID(0xCA3937E3, 0x640C, 0x4D9E, 0x9E, 0xF3, 0x90, 0x3F, 0x8B, 0x4F, 0xBC, 0xAB);
|
|
constexpr QUuid GKEY_KEYBOARD_UUID(0x153E64E6, 0x98C8, 0x4E03, 0x80, 0xEF, 0x5F, 0xFD, 0x33, 0xD2, 0x5B, 0x8A);
|
|
constexpr QUuid GKEY_MOUSE_UUID(0xC41E60AF, 0x9022, 0x46CF, 0xBC, 0x39, 0x37, 0x98, 0x10, 0x82, 0xD7, 0x16);
|
|
|
|
QList< Shortcut > newShortcuts;
|
|
|
|
for (Shortcut shortcut : oldShortcuts) {
|
|
bool ok = true;
|
|
|
|
for (QVariant &button : shortcut.qlButtons) {
|
|
if (!button.isValid()) {
|
|
// This happens when the user runs a version that uses
|
|
// the old format after the migration is performed.
|
|
ok = false;
|
|
break;
|
|
}
|
|
|
|
if (button.userType() == qMetaTypeId< InputHid >() || button.userType() == qMetaTypeId< InputKeyboard >()
|
|
|| button.userType() == qMetaTypeId< InputMouse >()
|
|
#ifdef USE_XBOXINPUT
|
|
|| button.userType() == qMetaTypeId< InputXinput >()
|
|
#endif
|
|
#ifdef USE_GKEY
|
|
|| button.userType() == qMetaTypeId< InputGkey >()
|
|
#endif
|
|
) {
|
|
// Already in the new format.
|
|
continue;
|
|
}
|
|
|
|
const QVariantList entries = button.toList();
|
|
if (entries.size() < 2) {
|
|
ok = false;
|
|
break;
|
|
}
|
|
|
|
auto value = entries.at(0).toUInt(&ok);
|
|
if (!ok) {
|
|
break;
|
|
}
|
|
|
|
const auto uuid = entries.at(1).toUuid();
|
|
if (uuid == KEYBOARD_UUID) {
|
|
InputKeyboard input;
|
|
input.code = (value & ~0x8000U) >> 8;
|
|
input.e0 = value & 0x8000U;
|
|
|
|
// With DirectInput the extended bit is:
|
|
// - Set for the Pause key.
|
|
// - Unset for the Numlock key.
|
|
// With raw input it's the opposite.
|
|
if (input.code == 0x45) {
|
|
input.e0 = !input.e0;
|
|
}
|
|
|
|
button = QVariant::fromValue(input);
|
|
} else if (uuid == MOUSE_UUID) {
|
|
value >>= 8;
|
|
if (value < 3 || value > 7) {
|
|
ok = false;
|
|
break;
|
|
}
|
|
|
|
button = QVariant::fromValue(static_cast< InputMouse >(value - 2));
|
|
#ifdef USE_XBOXINPUT
|
|
} else if (uuid == XINPUT_UUID) {
|
|
InputXinput input;
|
|
input.device = (value >> 24) & 0xFF;
|
|
input.code = value & 0x00FFFFFF;
|
|
|
|
button = QVariant::fromValue(input);
|
|
#endif
|
|
#ifdef USE_GKEY
|
|
} else if (uuid == GKEY_KEYBOARD_UUID) {
|
|
InputGkey input = {};
|
|
input.keyboard = true;
|
|
input.mode = value >> 16;
|
|
input.button = value & 0xFFFF;
|
|
|
|
button = QVariant::fromValue(input);
|
|
} else if (uuid == GKEY_MOUSE_UUID) {
|
|
InputGkey input = {};
|
|
input.button = value;
|
|
|
|
button = QVariant::fromValue(input);
|
|
#endif
|
|
} else {
|
|
ok = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ok) {
|
|
newShortcuts << shortcut;
|
|
}
|
|
}
|
|
|
|
return newShortcuts;
|
|
}
|
|
|
|
GlobalShortcutWin::GlobalShortcutWin()
|
|
: m_msgQueue(64)
|
|
#ifdef USE_XBOXINPUT
|
|
,
|
|
m_xinputDevices(0), m_xinputLastPacket()
|
|
#endif
|
|
{
|
|
// Register the MetaTypes if they have not already been registered (e.g in Settings)
|
|
registerMetaTypes();
|
|
|
|
start();
|
|
}
|
|
|
|
GlobalShortcutWin::~GlobalShortcutWin() {
|
|
requestInterruption();
|
|
m_condVar.notify_all();
|
|
wait();
|
|
}
|
|
|
|
void GlobalShortcutWin::run() {
|
|
#ifdef USE_XBOXINPUT
|
|
if (Global::get().s.bEnableXboxInput) {
|
|
static_assert(std::numeric_limits< uint8_t >::max() >= XBOXINPUT_MAX_DEVICES,
|
|
"Higher XBOXINPUT_MAX_DEVICES than anticipated.");
|
|
|
|
m_xinput = std::make_unique< XboxInput >();
|
|
if (!m_xinput->isValid()) {
|
|
qWarning("GlobalShortcutWin: Failed to initialize XboxInput!");
|
|
m_xinput.reset();
|
|
}
|
|
}
|
|
#endif
|
|
#ifdef USE_GKEY
|
|
if (Global::get().s.bEnableGKey) {
|
|
static_assert(std::numeric_limits< uint8_t >::max() >= GKEY_MAX_KEYBOARD_MODE,
|
|
"GKEY_MAX_KEYBOARD_MODE is higher than anticipated.");
|
|
static_assert(std::numeric_limits< uint8_t >::max() >= GKEY_MAX_MOUSE_BUTTON,
|
|
"GKEY_MAX_MOUSE_BUTTON is higher than anticipated.");
|
|
|
|
m_gkey = std::make_unique< GKeyLibrary >();
|
|
if (!m_gkey->isValid()) {
|
|
qWarning("GlobalShortcutWin: Failed to initialize GKey!");
|
|
m_gkey.reset();
|
|
}
|
|
}
|
|
#endif
|
|
// Wait for MainWindow's constructor to finish, so that we can get the window's handle.
|
|
while (!mumble_mw_hwnd) {
|
|
yieldCurrentThread();
|
|
}
|
|
|
|
constexpr uint8_t NRID = 5;
|
|
RAWINPUTDEVICE rid[NRID] = {};
|
|
|
|
rid[0].usUsagePage = HID_USAGE_PAGE_GENERIC;
|
|
rid[0].usUsage = HID_USAGE_GENERIC_MOUSE;
|
|
rid[0].dwFlags = RIDEV_INPUTSINK;
|
|
rid[0].hwndTarget = mumble_mw_hwnd;
|
|
|
|
rid[1].usUsagePage = HID_USAGE_PAGE_GENERIC;
|
|
rid[1].usUsage = HID_USAGE_GENERIC_JOYSTICK;
|
|
rid[1].dwFlags = RIDEV_INPUTSINK | RIDEV_DEVNOTIFY;
|
|
rid[1].hwndTarget = mumble_mw_hwnd;
|
|
|
|
rid[2].usUsagePage = HID_USAGE_PAGE_GENERIC;
|
|
rid[2].usUsage = HID_USAGE_GENERIC_GAMEPAD;
|
|
rid[2].dwFlags = RIDEV_INPUTSINK | RIDEV_DEVNOTIFY;
|
|
rid[2].hwndTarget = mumble_mw_hwnd;
|
|
|
|
rid[3].usUsagePage = HID_USAGE_PAGE_GENERIC;
|
|
rid[3].usUsage = HID_USAGE_GENERIC_KEYBOARD;
|
|
rid[3].dwFlags = RIDEV_INPUTSINK | RIDEV_DEVNOTIFY;
|
|
rid[3].hwndTarget = mumble_mw_hwnd;
|
|
|
|
rid[4].usUsagePage = HID_USAGE_PAGE_GENERIC;
|
|
rid[4].usUsage = HID_USAGE_GENERIC_MULTI_AXIS_CONTROLLER;
|
|
rid[4].dwFlags = RIDEV_INPUTSINK | RIDEV_DEVNOTIFY;
|
|
rid[4].hwndTarget = mumble_mw_hwnd;
|
|
|
|
if (!RegisterRawInputDevices(rid, NRID, sizeof(RAWINPUTDEVICE))) {
|
|
qWarning("GlobalShortcutWindows: RegisterRawInputDevices() failed with error %u!", GetLastError());
|
|
}
|
|
|
|
std::mutex mutex;
|
|
std::unique_lock< decltype(mutex) > lock(mutex);
|
|
|
|
do {
|
|
m_condVar.wait(lock);
|
|
|
|
if (bNeedRemap) {
|
|
remap();
|
|
}
|
|
|
|
while (const std::unique_ptr< MsgRaw > *item = m_msgQueue.front()) {
|
|
const std::unique_ptr< MsgRaw > &msg = *item;
|
|
|
|
switch (msg->type()) {
|
|
case MsgRaw::Hid:
|
|
processOther();
|
|
processMsgHid(static_cast< MsgHid & >(*msg));
|
|
break;
|
|
case MsgRaw::Keyboard:
|
|
processMsgKeyboard(static_cast< MsgKeyboard & >(*msg));
|
|
break;
|
|
case MsgRaw::Mouse:
|
|
processMsgMouse(static_cast< MsgMouse & >(*msg));
|
|
break;
|
|
}
|
|
|
|
m_msgQueue.pop();
|
|
}
|
|
} while (!isInterruptionRequested());
|
|
}
|
|
|
|
void GlobalShortcutWin::injectRawInputMessage(HRAWINPUT handle) {
|
|
UINT size = 0;
|
|
if (GetRawInputData(handle, RID_INPUT, nullptr, &size, sizeof(RAWINPUTHEADER)) != 0) {
|
|
return;
|
|
}
|
|
|
|
auto buffer = std::make_unique< uint8_t[] >(size);
|
|
if (GetRawInputData(handle, RID_INPUT, buffer.get(), &size, sizeof(RAWINPUTHEADER)) <= 0) {
|
|
return;
|
|
}
|
|
|
|
auto input = reinterpret_cast< const PRAWINPUT >(buffer.get());
|
|
switch (input->header.dwType) {
|
|
case RIM_TYPEMOUSE: {
|
|
const RAWMOUSE &mouse = input->data.mouse;
|
|
m_msgQueue.emplace(std::make_unique< MsgMouse >(mouse.usButtonFlags));
|
|
break;
|
|
}
|
|
case RIM_TYPEKEYBOARD: {
|
|
const RAWKEYBOARD &keyboard = input->data.keyboard;
|
|
if (keyboard.MakeCode == KEYBOARD_OVERRUN_MAKE_CODE) {
|
|
// Invalid or unrecognizable combination of keys is pressed or
|
|
// the number of keys pressed exceeds the limit for this keyboard.
|
|
return;
|
|
}
|
|
|
|
if (keyboard.VKey == 0xFF) {
|
|
// Discard "fake keys" which are part of an escaped sequence.
|
|
return;
|
|
}
|
|
|
|
m_msgQueue.emplace(std::make_unique< MsgKeyboard >(keyboard.Flags, keyboard.MakeCode, keyboard.VKey));
|
|
break;
|
|
}
|
|
case RIM_TYPEHID: {
|
|
const RAWHID &hid = input->data.hid;
|
|
MsgHid::RawReports reports(hid.bRawData, hid.dwSizeHid * hid.dwCount);
|
|
m_msgQueue.emplace(std::make_unique< MsgHid >(input->header.hDevice, reports, hid.dwSizeHid));
|
|
break;
|
|
}
|
|
default:
|
|
return;
|
|
}
|
|
|
|
m_condVar.notify_all();
|
|
}
|
|
|
|
void GlobalShortcutWin::processMsgHid(MsgHid &msg) {
|
|
auto iter = m_devices.find(msg.deviceHandle);
|
|
if (iter == m_devices.end()) {
|
|
iter = addDevice(msg.deviceHandle);
|
|
if (iter == m_devices.cend()) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
auto &device = (*iter).second;
|
|
#ifdef USE_XBOXINPUT
|
|
if (device.xinput && m_xinput) {
|
|
return;
|
|
}
|
|
#endif
|
|
auto data = reinterpret_cast< PHIDP_PREPARSED_DATA >(&device.data[0]);
|
|
|
|
ULONG nUsages = static_cast< ULONG >(device.buttons.size());
|
|
std::vector< USAGE > usages(nUsages);
|
|
if (HidP_GetUsages(HidP_Input, HID_USAGE_PAGE_BUTTON, 0, &usages[0], &nUsages, data, &msg.reports[0],
|
|
msg.reportSize)
|
|
!= HIDP_STATUS_SUCCESS) {
|
|
return;
|
|
}
|
|
|
|
device.buttons.assign(device.buttons.size(), false);
|
|
for (ULONG i = 0; i < nUsages; ++i) {
|
|
device.buttons[usages[i] - device.usageRange.first] = true;
|
|
}
|
|
|
|
for (uint32_t i = 0; i < device.buttons.size(); ++i) {
|
|
InputHid input;
|
|
input.button = i;
|
|
input.deviceName = device.name;
|
|
input.devicePrefix = device.prefix;
|
|
|
|
handleButton(QVariant::fromValue(input), device.buttons[i]);
|
|
}
|
|
}
|
|
|
|
void GlobalShortcutWin::processMsgKeyboard(MsgKeyboard &msg) {
|
|
if (msg.virtualKey == VK_NUMLOCK) {
|
|
// Keys like “Pause / Break” and “Numlock” act strangely,
|
|
// sometimes like they are not even the same physical key.
|
|
// They use the so called escaped sequences, which we have to decipher.
|
|
msg.scanCode = MapVirtualKey(msg.virtualKey, MAPVK_VK_TO_VSC) | 0x100;
|
|
}
|
|
|
|
// E0 and E1 are escape sequences used for certain special keys, such as PRINT and PAUSE/BREAK.
|
|
// See https://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html.
|
|
if (msg.flags & RI_KEY_E1) {
|
|
// For escaped sequences, turn the virtual key into the correct scan code using MapVirtualKey().
|
|
// MapVirtualKey() is unable to map VK_PAUSE (this is a known bug), hence we map that by hand.
|
|
msg.scanCode = msg.virtualKey != VK_PAUSE ? MapVirtualKey(msg.virtualKey, MAPVK_VK_TO_VSC) : 0x45;
|
|
}
|
|
|
|
InputKeyboard input = {};
|
|
input.code = msg.scanCode;
|
|
input.e0 = msg.flags & RI_KEY_E0;
|
|
|
|
handleButton(QVariant::fromValue(input), !(msg.flags & RI_KEY_BREAK));
|
|
}
|
|
|
|
void GlobalShortcutWin::processMsgMouse(const MsgMouse &msg) {
|
|
// Multiple mouse transitions can be contained in a single message.
|
|
// See https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-rawmouse.
|
|
if (msg.buttonFlags & RI_MOUSE_BUTTON_1_DOWN) {
|
|
handleButton(QVariant::fromValue(InputMouse::Left), true);
|
|
}
|
|
|
|
if (msg.buttonFlags & RI_MOUSE_BUTTON_1_UP) {
|
|
handleButton(QVariant::fromValue(InputMouse::Left), false);
|
|
}
|
|
|
|
if (msg.buttonFlags & RI_MOUSE_BUTTON_2_DOWN) {
|
|
handleButton(QVariant::fromValue(InputMouse::Right), true);
|
|
}
|
|
|
|
if (msg.buttonFlags & RI_MOUSE_BUTTON_2_UP) {
|
|
handleButton(QVariant::fromValue(InputMouse::Right), false);
|
|
}
|
|
|
|
if (msg.buttonFlags & RI_MOUSE_BUTTON_3_DOWN) {
|
|
handleButton(QVariant::fromValue(InputMouse::Middle), true);
|
|
}
|
|
|
|
if (msg.buttonFlags & RI_MOUSE_BUTTON_3_UP) {
|
|
handleButton(QVariant::fromValue(InputMouse::Middle), false);
|
|
}
|
|
|
|
if (msg.buttonFlags & RI_MOUSE_BUTTON_4_DOWN) {
|
|
handleButton(QVariant::fromValue(InputMouse::Side_1), true);
|
|
}
|
|
|
|
if (msg.buttonFlags & RI_MOUSE_BUTTON_4_UP) {
|
|
handleButton(QVariant::fromValue(InputMouse::Side_1), false);
|
|
}
|
|
|
|
if (msg.buttonFlags & RI_MOUSE_BUTTON_5_DOWN) {
|
|
handleButton(QVariant::fromValue(InputMouse::Side_2), true);
|
|
}
|
|
|
|
if (msg.buttonFlags & RI_MOUSE_BUTTON_5_UP) {
|
|
handleButton(QVariant::fromValue(InputMouse::Side_2), false);
|
|
}
|
|
}
|
|
|
|
GlobalShortcutWin::DeviceMap::iterator GlobalShortcutWin::addDevice(const HANDLE deviceHandle) {
|
|
RID_DEVICE_INFO deviceInfo = {};
|
|
UINT size = sizeof(deviceInfo);
|
|
if (GetRawInputDeviceInfo(deviceHandle, RIDI_DEVICEINFO, &deviceInfo, &size) <= 0) {
|
|
return m_devices.end();
|
|
}
|
|
|
|
Device device;
|
|
|
|
// E.g. "[045E:02EA]"
|
|
std::stringstream nameStream;
|
|
nameStream << std::uppercase << std::hex << std::setfill('0');
|
|
nameStream << '[';
|
|
nameStream << std::setw(4) << deviceInfo.hid.dwVendorId;
|
|
nameStream << ':';
|
|
nameStream << std::setw(4) << deviceInfo.hid.dwProductId;
|
|
nameStream << ']';
|
|
|
|
device.prefix = nameStream.str() + ':';
|
|
|
|
if (GetRawInputDeviceInfo(deviceHandle, RIDI_DEVICENAME, nullptr, &size) == 0) {
|
|
std::wstring name;
|
|
name.resize(size);
|
|
// E.g. "\\?\HID#VID_045E&PID_02FF&IG_00#8&15c9c520&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}"
|
|
if (GetRawInputDeviceInfo(deviceHandle, RIDI_DEVICENAME, &name[0], &size) > 0) {
|
|
#ifdef USE_XBOXINPUT
|
|
if (name.find(L"IG_") != name.npos) {
|
|
device.xinput = true;
|
|
}
|
|
#endif
|
|
auto handle = CreateFile(name.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
nullptr, OPEN_EXISTING, 0, nullptr);
|
|
if (handle != INVALID_HANDLE_VALUE) {
|
|
// From
|
|
// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/hidsdi/nf-hidsdi-hidd_getproductstring:
|
|
// "For USB devices, the maximum string length is 126 wide characters (not including the terminating
|
|
// NULL character)."
|
|
//
|
|
// std::string::resize() doesn't take the NULL character into account, but an extra byte doesn't hurt.
|
|
name.clear();
|
|
name.resize(127);
|
|
|
|
std::wstring_convert< std::codecvt_utf8_utf16< wchar_t > > conv;
|
|
|
|
if (HidD_GetManufacturerString(handle, &name[0], static_cast< ULONG >(sizeof(wchar_t) * name.size()))) {
|
|
nameStream << ' ' << conv.to_bytes(name);
|
|
name.clear();
|
|
}
|
|
|
|
if (HidD_GetProductString(handle, &name[0], static_cast< ULONG >(sizeof(wchar_t) * name.size()))) {
|
|
nameStream << ' ' << conv.to_bytes(name);
|
|
}
|
|
|
|
CloseHandle(handle);
|
|
}
|
|
}
|
|
}
|
|
|
|
device.name = nameStream.str();
|
|
|
|
if (GetRawInputDeviceInfo(deviceHandle, RIDI_PREPARSEDDATA, nullptr, &size) != 0) {
|
|
return m_devices.end();
|
|
}
|
|
|
|
device.data.resize(size);
|
|
auto data = reinterpret_cast< PHIDP_PREPARSED_DATA >(&device.data[0]);
|
|
if (GetRawInputDeviceInfo(deviceHandle, RIDI_PREPARSEDDATA, data, &size) <= 0) {
|
|
return m_devices.end();
|
|
}
|
|
|
|
HIDP_CAPS caps;
|
|
if (HidP_GetCaps(data, &caps) != HIDP_STATUS_SUCCESS) {
|
|
return m_devices.end();
|
|
}
|
|
|
|
USHORT nCaps = caps.NumberInputButtonCaps;
|
|
std::vector< HIDP_BUTTON_CAPS > buttonCaps(nCaps);
|
|
if (HidP_GetSpecificButtonCaps(HidP_Input, HID_USAGE_PAGE_BUTTON, 0, 0, &buttonCaps[0], &nCaps, data)
|
|
!= HIDP_STATUS_SUCCESS) {
|
|
return m_devices.end();
|
|
}
|
|
|
|
device.usageRange.first = buttonCaps[0].Range.UsageMin;
|
|
device.usageRange.second = buttonCaps[0].Range.UsageMax;
|
|
device.buttons.resize(device.usageRange.second - device.usageRange.first + 1);
|
|
|
|
if (device.xinput) {
|
|
++m_xinputDevices;
|
|
}
|
|
|
|
qInfo("GlobalShortcutWin: \"%s\" added", device.name.c_str());
|
|
|
|
return m_devices.insert({ deviceHandle, device }).first;
|
|
}
|
|
|
|
void GlobalShortcutWin::deviceRemoved(const HANDLE deviceHandle) {
|
|
const auto iter = m_devices.find(deviceHandle);
|
|
if (iter == m_devices.cend()) {
|
|
return;
|
|
}
|
|
|
|
const auto &device = (*iter).second;
|
|
if (device.xinput) {
|
|
--m_xinputDevices;
|
|
}
|
|
|
|
qInfo("GlobalShortcutWin: \"%s\" removed", device.name.c_str());
|
|
|
|
m_devices.erase(iter);
|
|
}
|
|
#ifdef USE_XBOXINPUT
|
|
bool GlobalShortcutWin::xinputIsPressed(const uint8_t bit, const XboxInputState &state) {
|
|
// 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.
|
|
switch (bit) {
|
|
case 16:
|
|
return state.leftTrigger > XBOXINPUT_TRIGGER_THRESHOLD;
|
|
case 17:
|
|
return state.rightTrigger > XBOXINPUT_TRIGGER_THRESHOLD;
|
|
default:
|
|
return state.buttons & (1 << bit);
|
|
}
|
|
}
|
|
#endif
|
|
void GlobalShortcutWin::processOther() {
|
|
#ifdef USE_XBOXINPUT
|
|
if (m_xinput && m_xinputDevices > 0) {
|
|
for (uint8_t i = 0; i < XBOXINPUT_MAX_DEVICES; ++i) {
|
|
XboxInputState state;
|
|
if (m_xinput->GetState(i, &state) != ERROR_SUCCESS) {
|
|
continue;
|
|
}
|
|
|
|
// Skip the result of GetState() if the packet number hasn't changed,
|
|
// or if we're at the first packet.
|
|
if (m_xinputLastPacket[i] != 0 && state.packetNumber == m_xinputLastPacket[i]) {
|
|
continue;
|
|
}
|
|
|
|
InputXinput input = {};
|
|
input.device = i;
|
|
|
|
for (uint8_t j = 0; j < 18; ++j) {
|
|
input.code = j;
|
|
handleButton(QVariant::fromValue(input), xinputIsPressed(j, state));
|
|
}
|
|
|
|
m_xinputLastPacket[i] = state.packetNumber;
|
|
}
|
|
}
|
|
#endif
|
|
#ifdef USE_GKEY
|
|
if (m_gkey) {
|
|
InputGkey input = {};
|
|
input.keyboard = false;
|
|
|
|
for (uint8_t button = GKEY_MIN_MOUSE_BUTTON; button <= GKEY_MAX_MOUSE_BUTTON; ++button) {
|
|
input.button = button;
|
|
handleButton(QVariant::fromValue(input), m_gkey->isMouseButtonPressed(button));
|
|
}
|
|
|
|
input.keyboard = true;
|
|
|
|
for (uint8_t mode = GKEY_MIN_KEYBOARD_MODE; mode <= GKEY_MAX_KEYBOARD_MODE; ++mode) {
|
|
input.mode = mode;
|
|
|
|
for (uint8_t button = GKEY_MIN_KEYBOARD_BUTTON; button <= GKEY_MAX_KEYBOARD_BUTTON; ++button) {
|
|
input.button = button;
|
|
handleButton(QVariant::fromValue(input), m_gkey->isKeyboardGkeyPressed(button, mode));
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
GlobalShortcutWin::ButtonInfo GlobalShortcutWin::buttonInfo(const QVariant &button) {
|
|
ButtonInfo info;
|
|
|
|
// Unfortunately qMetaTypeId() is not a constexpr function.
|
|
// As a result, we cannot use a switch-case.
|
|
if (button.userType() == qMetaTypeId< InputHid >()) {
|
|
const auto input = button.value< InputHid >();
|
|
info.device = QString::fromStdString(input.deviceName);
|
|
info.devicePrefix = QString::fromStdString(input.devicePrefix);
|
|
info.name = QString::number(input.button);
|
|
} else if (button.userType() == qMetaTypeId< InputKeyboard >()) {
|
|
info.device = tr("Keyboard");
|
|
|
|
const auto input = button.value< InputKeyboard >();
|
|
wchar_t buffer[MAX_PATH];
|
|
if (GetKeyNameText((input.code << 16) | (input.e0 << 24), buffer, MAX_PATH)) {
|
|
info.name = QString::fromWCharArray(buffer);
|
|
} else {
|
|
// If GetKeyNameText() cannot find the name. Show a K prefix and the scan code instead of Unknown.
|
|
// For keys like F13 - F24
|
|
info.devicePrefix = QStringLiteral("K");
|
|
info.name = QString::number(static_cast< uint >(input.code));
|
|
}
|
|
} else if (button.userType() == qMetaTypeId< InputMouse >()) {
|
|
info.device = tr("Mouse");
|
|
info.devicePrefix = QStringLiteral("M");
|
|
|
|
const auto input = button.value< InputMouse >();
|
|
info.name = QString::number(static_cast< uint >(input));
|
|
}
|
|
#ifdef USE_XBOXINPUT
|
|
else if (button.userType() == qMetaTypeId< InputXinput >()) {
|
|
const auto input = button.value< InputXinput >();
|
|
info.device = QString::fromLatin1("Xbox controller #%1").arg(input.device + 1);
|
|
info.devicePrefix = QString::fromLatin1("Xbox%1:").arg(input.device + 1);
|
|
|
|
// 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 (input.code) {
|
|
case 0:
|
|
info.name = QStringLiteral("Up");
|
|
break;
|
|
case 1:
|
|
info.name = QStringLiteral("Down");
|
|
break;
|
|
case 2:
|
|
info.name = QStringLiteral("Left");
|
|
break;
|
|
case 3:
|
|
info.name = QStringLiteral("Right");
|
|
break;
|
|
case 4:
|
|
info.name = QStringLiteral("Start");
|
|
break;
|
|
case 5:
|
|
info.name = QStringLiteral("Back");
|
|
break;
|
|
case 6:
|
|
info.name = QStringLiteral("LeftThumb");
|
|
break;
|
|
case 7:
|
|
info.name = QStringLiteral("RightThumb");
|
|
break;
|
|
case 8:
|
|
info.name = QStringLiteral("LeftShoulder");
|
|
break;
|
|
case 9:
|
|
info.name = QStringLiteral("RightShoulder");
|
|
break;
|
|
case 10:
|
|
info.name = QStringLiteral("Guide");
|
|
break;
|
|
case 11:
|
|
info.name = QStringLiteral("11");
|
|
break;
|
|
case 12:
|
|
info.name = QStringLiteral("A");
|
|
break;
|
|
case 13:
|
|
info.name = QStringLiteral("B");
|
|
break;
|
|
case 14:
|
|
info.name = QStringLiteral("X");
|
|
break;
|
|
case 15:
|
|
info.name = QStringLiteral("Y");
|
|
break;
|
|
case 16:
|
|
info.name = QStringLiteral("LeftTrigger");
|
|
break;
|
|
case 17:
|
|
info.name = QStringLiteral("RightTrigger");
|
|
}
|
|
}
|
|
#endif
|
|
#ifdef USE_GKEY
|
|
else if (button.userType() == qMetaTypeId< InputGkey >()) {
|
|
info.device = QStringLiteral("Logitech G-keys");
|
|
info.devicePrefix = QStringLiteral("GKey:");
|
|
|
|
const auto input = button.value< InputGkey >();
|
|
if (input.keyboard) {
|
|
info.name = m_gkey->getKeyboardGkeyString(input.button, input.mode);
|
|
} else {
|
|
info.name = m_gkey->getMouseButtonString(input.button);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return info;
|
|
}
|
|
|
|
#ifdef _MSVC_LANG
|
|
# pragma warning(pop)
|
|
#endif
|