// Copyright 2005-2020 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 . #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 #include #include #include // 3rdparty/xinputcheck-src. #include // 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(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 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(INJECTKEYBOARDMESSAGE_QEVENT)) , m_scancode(scancode) , m_vkcode(vkcode) , m_extended(extended) , m_down(down) {} inline boost::future 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 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(INJECTMOUSEMESSAGE_QEVENT)) , m_btn(btn) , m_down(down) {} inline boost::future 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(&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(&value), &len); if (err == ERROR_SUCCESS && type == REG_DWORD) { qWarning("GlobalShortcutWin: Found LowLevelHooksTimeout with value = 0x%lx", static_cast(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(err), static_cast(type), static_cast(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(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(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 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 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(engine); QList ql; // Convert the low-level key event to // a DirectInput key ID. unsigned int keyid = static_cast((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(engine); bool suppress = false; if (btn > 0) { QList ql; ql << static_cast((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(engine); KBDLLHOOKSTRUCT *key=reinterpret_cast(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(engine); MSLLHOOKSTRUCT *mouse=reinterpret_cast(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(pvRef); QString name = QString::fromUtf16(reinterpret_cast(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(lpddoi->dwType), qPrintable(name)); } return DIENUM_CONTINUE; } BOOL GlobalShortcutWin::EnumDevicesCB(LPCDIDEVICEINSTANCE pdidi, LPVOID pContext) { GlobalShortcutWin *cbgsw=static_cast(pContext); HRESULT hr; QString name = QString::fromUtf16(reinterpret_cast(pdidi->tszProductName)); QString sname = QString::fromUtf16(reinterpret_cast(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(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(id), DIDFT_BUTTON))) qFatal("GlobalShortcutWin: EnumObjects: %lx", hr); if (id->qhNames.count() > 0) { QList 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;iqhOfsToType[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(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(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(timer.elapsed())); } } hr = id->pDID->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), rgdod, &dwItems, 0); if (FAILED(hr)) continue; if (dwItems <= 0) continue; for (DWORD j=0; j 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 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 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 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(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(&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(GlobalShortcutEngine::engine); const QList &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; }