1 // Copyright 2005-2019 The Mumble Developers. All rights reserved.
2 // Use of this source code is governed by a BSD-style license
3 // that can be found in the LICENSE file at the root of the
4 // Mumble source tree or at <https://www.mumble.info/LICENSE>.
5
6 #include "mumble_pch.hpp"
7
8 // MinGW does not support std::future/std::promise
9 // at present. Use Boost's implementation for now.
10 #define BOOST_THREAD_VERSION 4
11 #include <boost/thread.hpp>
12 #include <boost/thread/future.hpp>
13
14 #include "GlobalShortcut_win.h"
15
16 #include "MainWindow.h"
17 #include "OverlayClient.h"
18 #include "Global.h"
19
20 // 3rdparty/xinputcheck-src.
21 #include <xinputcheck.h>
22
23 #undef FAILED
24 #define FAILED(Status) (static_cast<HRESULT>(Status)<0)
25
26 #define DX_SAMPLE_BUFFER_SIZE 512
27
28 // from os_win.cpp
29 extern HWND mumble_mw_hwnd;
30
31 /// The QEvent::Type value to use for InjectKeyboardMessageEvent.
32 #define INJECTKEYBOARDMESSAGE_QEVENT (QEvent::User + 200)
33 /// The QEvent::Type value to use for InjectKeyboardMouseEvent.
34 #define INJECTMOUSEMESSAGE_QEVENT (QEvent::User + 201)
35
36 /// InjectKeyboardMessageEvent is an event that can be sent to
37 /// the GlobalShortcutWin engine to inject a native Windows keyboard
38 /// event into GlobalShortcutWin's event stream.
39 class InjectKeyboardMessageEvent : public QEvent {
40 Q_DISABLE_COPY(InjectKeyboardMessageEvent);
41
42 public:
43 boost::promise<bool> m_suppressionPromise;
44 DWORD m_scancode;
45 DWORD m_vkcode;
46 bool m_extended;
47 bool m_down;
48
49 /// Construct a new InjectKeyboardMessageEvent.
50 ///
51 /// @param scancode The Windows scancode of the button.
52 /// @param vkcode The Windows virtual keycode of the button.
53 /// @param extended Indicates whether the button is an extended key in
54 /// Windows nomenclature. ("[...] such as the right-hand ALT
55 /// and CTRL keys that appear on an enhanced 101- or 102-key
56 /// keyboard")
57 /// @param down The down/pressed status of the keyboard button.
InjectKeyboardMessageEvent(DWORD scancode,DWORD vkcode,bool extended,bool down)58 InjectKeyboardMessageEvent(DWORD scancode, DWORD vkcode, bool extended, bool down)
59 : QEvent(static_cast<QEvent::Type>(INJECTKEYBOARDMESSAGE_QEVENT))
60 , m_scancode(scancode)
61 , m_vkcode(vkcode)
62 , m_extended(extended)
63 , m_down(down) {}
64
shouldSuppress()65 inline boost::future<bool> shouldSuppress() {
66 return m_suppressionPromise.get_future();
67 }
68 };
69
70 /// InjectMouseMessageEvent is an event that can be sent to
71 /// the GlobalShortcutWin engine to inject a native Windows mouse
72 /// event into GlobalShortcutWin's event stream.
73 class InjectMouseMessageEvent : public QEvent {
74 Q_DISABLE_COPY(InjectMouseMessageEvent);
75
76 public:
77 boost::promise<bool> m_suppressionPromise;
78 unsigned int m_btn;
79 bool m_down;
80
81 /// Construct a new InjectMouseMessageEvent.
82 ///
83 /// @param btn The DirectInput button index of the mouse event.
84 /// @param down The down/pressed status of the mouse button.
InjectMouseMessageEvent(unsigned int btn,bool down)85 InjectMouseMessageEvent(unsigned int btn, bool down)
86 : QEvent(static_cast<QEvent::Type>(INJECTMOUSEMESSAGE_QEVENT))
87 , m_btn(btn)
88 , m_down(down) {}
89
shouldSuppress()90 inline boost::future<bool> shouldSuppress() {
91 return m_suppressionPromise.get_future();
92 }
93 };
94
qHash(const GUID & a)95 uint qHash(const GUID &a) {
96 uint val = a.Data1 ^ a.Data2 ^ a.Data3;
97 for (int i=0;i<8;i++)
98 val += a.Data4[i];
99 return val;
100 }
101
102 /**
103 * Returns a platform specific GlobalShortcutEngine object.
104 *
105 * @see GlobalShortcutX
106 * @see GlobalShortcutMac
107 * @see GlobalShortcutWin
108 */
platformInit()109 GlobalShortcutEngine *GlobalShortcutEngine::platformInit() {
110 return new GlobalShortcutWin();
111 }
112
113
GlobalShortcutWin()114 GlobalShortcutWin::GlobalShortcutWin()
115 : pDI(NULL)
116 , hhMouse(NULL)
117 , hhKeyboard(NULL)
118 , uiHardwareDevices(0)
119 #ifdef USE_GKEY
120 , gkey(NULL)
121 #endif
122 #ifdef USE_XBOXINPUT
123 , xboxinput(NULL)
124 , nxboxinput(0)
125 #endif
126 {
127 // Hidden setting to disable hooking
128 bHook = g.qs->value(QLatin1String("winhooks"), true).toBool();
129
130 moveToThread(this);
131 start(QThread::LowestPriority);
132 }
133
~GlobalShortcutWin()134 GlobalShortcutWin::~GlobalShortcutWin() {
135 quit();
136 wait();
137 }
138
run()139 void GlobalShortcutWin::run() {
140 if (FAILED(DirectInput8Create(GetModuleHandle(NULL), DIRECTINPUT_VERSION, IID_IDirectInput8, reinterpret_cast<void **>(&pDI), NULL))) {
141 qFatal("GlobalShortcutWin: Failed to create d8input");
142 return;
143 }
144
145 // Print the user's LowLevelHooksTimeout registry key for debugging purposes.
146 // On Windows 7 and greater, Windows will silently remove badly behaving hooks
147 // without telling the application. Users can tweak the timeout themselves
148 // with this registry key.
149 HKEY key = NULL;
150 DWORD type = 0;
151 DWORD value = 0;
152 DWORD len = sizeof(DWORD);
153 if (RegOpenKeyExA(HKEY_CURRENT_USER, "Control Panel\\Desktop", 0, KEY_READ, &key) == ERROR_SUCCESS) {
154 LONG err = RegQueryValueExA(key, "LowLevelHooksTimeout", NULL, &type, reinterpret_cast<LPBYTE>(&value), &len);
155 if (err == ERROR_SUCCESS && type == REG_DWORD) {
156 qWarning("GlobalShortcutWin: Found LowLevelHooksTimeout with value = 0x%lx", static_cast<unsigned long>(value));
157 } else if (err == ERROR_FILE_NOT_FOUND) {
158 qWarning("GlobalShortcutWin: No LowLevelHooksTimeout registry key found.");
159 } else {
160 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));
161 }
162 }
163
164 // Wait for MainWindow's constructor to finish before we enumerate DirectInput devices.
165 // We need to do this because adding a new device requires a Window handle. (SetCooperativeLevel())
166 while (! g.mw)
167 this->yieldCurrentThread();
168
169 #ifdef USE_GKEY
170 if (g.s.bEnableGKey) {
171 gkey = new GKeyLibrary();
172 qWarning("GlobalShortcutWin: GKeys initialized, isValid: %d", gkey->isValid());
173 }
174 #endif
175
176 #ifdef USE_XBOXINPUT
177 if (g.s.bEnableXboxInput) {
178 xboxinput = new XboxInput();
179 ZeroMemory(&xboxinputLastPacket, sizeof(xboxinputLastPacket));
180 qWarning("GlobalShortcutWin: XboxInput initialized, isValid: %d", xboxinput->isValid());
181 }
182 #endif
183
184 QTimer *timer = new QTimer;
185 connect(timer, SIGNAL(timeout()), this, SLOT(timeTicked()));
186 timer->start(20);
187
188 setPriority(QThread::TimeCriticalPriority);
189
190 exec();
191
192 delete timer;
193
194 #ifdef USE_GKEY
195 delete gkey;
196 #endif
197
198 #ifdef USE_XBOXINPUT
199 delete xboxinput;
200 #endif
201
202 if (bHook) {
203 if (hhMouse != NULL) {
204 UnhookWindowsHookEx(hhMouse);
205 }
206 if (hhKeyboard != NULL) {
207 UnhookWindowsHookEx(hhKeyboard);
208 }
209 }
210
211 foreach(InputDevice *id, qhInputDevices) {
212 if (id->pDID) {
213 id->pDID->Unacquire();
214 id->pDID->Release();
215 }
216 delete id;
217 }
218 pDI->Release();
219 }
220
event(QEvent * event)221 bool GlobalShortcutWin::event(QEvent *event) {
222 QEvent::Type type = event->type();
223 if (type == INJECTKEYBOARDMESSAGE_QEVENT) {
224 InjectKeyboardMessageEvent *ikme = static_cast<InjectKeyboardMessageEvent *>(event);
225 bool suppress = handleKeyboardMessage(ikme->m_scancode, ikme->m_vkcode, ikme->m_extended, ikme->m_down);
226 ikme->m_suppressionPromise.set_value(suppress);
227 return true;
228 } else if (type == INJECTMOUSEMESSAGE_QEVENT) {
229 InjectMouseMessageEvent *imme = static_cast<InjectMouseMessageEvent *>(event);
230 bool suppress = handleMouseMessage(imme->m_btn, imme->m_down);
231 imme->m_suppressionPromise.set_value(suppress);
232 return true;
233 }
234 return GlobalShortcutEngine::event(event);
235 }
236
injectKeyboardMessage(MSG * msg)237 bool GlobalShortcutWin::injectKeyboardMessage(MSG *msg) {
238 if (!bHook) {
239 return false;
240 }
241
242 // Only allow keyboard messages.
243 switch (msg->message) {
244 case WM_KEYDOWN:
245 case WM_KEYUP:
246 case WM_SYSKEYDOWN:
247 case WM_SYSKEYUP:
248 break;
249 default:
250 return false;
251 }
252
253 DWORD scancode = (msg->lParam >> 16) & 0xff;
254 DWORD vkcode = msg->wParam;
255 bool extended = !!(msg->lParam & 0x01000000);
256 bool up = !!(msg->lParam & 0x80000000);
257
258 InjectKeyboardMessageEvent *ikme = new InjectKeyboardMessageEvent(scancode, vkcode, extended, !up);
259 boost::future<bool> suppress = ikme->shouldSuppress();
260 qApp->postEvent(this, ikme);
261 return suppress.get();
262 }
263
injectMouseMessage(MSG * msg)264 bool GlobalShortcutWin::injectMouseMessage(MSG *msg) {
265 if (!bHook) {
266 return false;
267 }
268
269 bool down = false;
270 unsigned int btn = 0;
271
272 // Convert the Windows mouse message into a DirectInput
273 // button index, and store the pressed state of the button.
274 switch (msg->message) {
275 case WM_LBUTTONDOWN:
276 down = true;
277 case WM_LBUTTONUP:
278 btn = 3;
279 break;
280 case WM_RBUTTONDOWN:
281 down = true;
282 case WM_RBUTTONUP:
283 btn = 4;
284 break;
285 case WM_MBUTTONDOWN:
286 down = true;
287 case WM_MBUTTONUP:
288 btn = 5;
289 break;
290 case WM_XBUTTONDOWN:
291 down = true;
292 case WM_XBUTTONUP: {
293 unsigned int offset = (msg->wParam >> 16) & 0xffff;
294 btn = 5 + offset;
295 }
296 default:
297 // Non-mouse event. Return early.
298 return false;
299 }
300
301 InjectMouseMessageEvent *imme = new InjectMouseMessageEvent(btn, down);
302 boost::future<bool> suppress = imme->shouldSuppress();
303 qApp->postEvent(this, imme);
304 return suppress.get();
305 }
306
handleKeyboardMessage(DWORD scancode,DWORD vkcode,bool extended,bool down)307 bool GlobalShortcutWin::handleKeyboardMessage(DWORD scancode, DWORD vkcode, bool extended, bool down) {
308 GlobalShortcutWin *gsw = static_cast<GlobalShortcutWin *>(engine);
309
310 QList<QVariant> ql;
311
312 // Convert the low-level key event to
313 // a DirectInput key ID.
314 unsigned int keyid = static_cast<unsigned int>((scancode << 8) | 0x4);
315 if (extended) {
316 keyid |= 0x8000U;
317 }
318
319 // NumLock and Pause need special handling.
320 // For those keys, the method above of setting
321 // bit 15 high when the LLKHF_EXTENDED flag is
322 // set on the low-level key event does not work.
323 //
324 // When we receive a low-level Windows
325 // Pause key event, the extended flag isn't
326 // set, but DirectInput expects it to be.
327 //
328 // The opposite is true for NumLock key,
329 // where the extended flag for the low-level
330 // Windows event is set, but DirectInput expects
331 // it not to be.
332 //
333 // Without this fix-up, we would emit Pause as
334 // NumLock, and NumLock as pause. That was
335 // problematic, because at the same time,
336 // DirectInput would emit the correct key.
337 // This meant that when pressing one of Pause
338 // and NumLock, shortcut actions for both keys
339 // would be triggered.
340 //
341 // Originally reported in mumble-voip/mumble#1353
342 if (vkcode == VK_PAUSE) {
343 // Always set the extended bit for Pause.
344 keyid |= 0x8000U;
345 } else if (vkcode == VK_NUMLOCK) {
346 // Never set the extended bit for NumLock.
347 keyid &= ~0x8000U;
348 }
349
350 ql << keyid;
351 ql << QVariant(QUuid(GUID_SysKeyboard));
352 bool suppress = gsw->handleButton(ql, down);
353
354 return suppress;
355 }
356
handleMouseMessage(unsigned int btn,bool down)357 bool GlobalShortcutWin::handleMouseMessage(unsigned int btn, bool down) {
358 GlobalShortcutWin *gsw = static_cast<GlobalShortcutWin *>(engine);
359
360 bool suppress = false;
361
362 if (btn > 0) {
363 QList<QVariant> ql;
364 ql << static_cast<unsigned int>((btn << 8) | 0x4);
365 ql << QVariant(QUuid(GUID_SysMouse));
366
367 // Do not suppress LBUTTONUP though (so suppression can be deactivated via mouse).
368 bool wantsuppress = gsw->handleButton(ql, down);
369 suppress = wantsuppress && (btn != 3);
370 }
371
372 return suppress;
373 }
374
HookKeyboard(int nCode,WPARAM wParam,LPARAM lParam)375 LRESULT CALLBACK GlobalShortcutWin::HookKeyboard(int nCode, WPARAM wParam, LPARAM lParam) {
376 GlobalShortcutWin *gsw=static_cast<GlobalShortcutWin *>(engine);
377 KBDLLHOOKSTRUCT *key=reinterpret_cast<KBDLLHOOKSTRUCT *>(lParam);
378
379 #ifndef QT_NO_DEBUG
380 static int safety = 0;
381
382 if ((++safety < 100) && (nCode >= 0)) {
383 #else
384 if (nCode >= 0) {
385 #endif
386 DWORD scancode = key->scanCode;
387 DWORD vkcode = key->vkCode;
388 bool extended = !!(key->flags & LLKHF_EXTENDED);
389 bool up = !!(key->flags & LLKHF_UP);
390 bool suppress = handleKeyboardMessage(scancode, vkcode, extended, !up);
391 if (suppress) {
392 return 1;
393 }
394 }
395 return CallNextHookEx(gsw->hhKeyboard, nCode, wParam, lParam);
396 }
397
398 LRESULT CALLBACK GlobalShortcutWin::HookMouse(int nCode, WPARAM wParam, LPARAM lParam) {
399 GlobalShortcutWin *gsw=static_cast<GlobalShortcutWin *>(engine);
400 MSLLHOOKSTRUCT *mouse=reinterpret_cast<MSLLHOOKSTRUCT *>(lParam);
401
402 if (nCode >= 0) {
403 bool suppress = false;
404 UINT msg = wParam;
405 // Convert the hooked Windows mouse message into a DirectInput
406 // button index, and store the pressed state of the button.
407 bool down = false;
408 unsigned int btn = 0;
409 switch (msg) {
410 case WM_LBUTTONDOWN:
411 down = true;
412 case WM_LBUTTONUP:
413 btn = 3;
414 break;
415 case WM_RBUTTONDOWN:
416 down = true;
417 case WM_RBUTTONUP:
418 btn = 4;
419 break;
420 case WM_MBUTTONDOWN:
421 down = true;
422 case WM_MBUTTONUP:
423 btn = 5;
424 break;
425 case WM_XBUTTONDOWN:
426 down = true;
427 case WM_XBUTTONUP:
428 btn = 5 + (mouse->mouseData >> 16);
429 default:
430 break;
431 }
432 if (btn > 0) {
433 suppress = handleMouseMessage(btn, down);
434 if (suppress) {
435 return 1;
436 }
437 }
438 }
439 return CallNextHookEx(gsw->hhMouse, nCode, wParam, lParam);
440 }
441
442 BOOL CALLBACK GlobalShortcutWin::EnumDeviceObjectsCallback(LPCDIDEVICEOBJECTINSTANCE lpddoi, LPVOID pvRef) {
443 InputDevice *id=static_cast<InputDevice *>(pvRef);
444 QString name = QString::fromUtf16(reinterpret_cast<const ushort *>(lpddoi->tszName));
445 id->qhNames[lpddoi->dwType] = name;
446
447 if (g.s.bDirectInputVerboseLogging) {
448 qWarning("GlobalShortcutWin: EnumObjects: device %s %s object 0x%.8lx %s",
449 qPrintable(QUuid(id->guid).toString()),
450 qPrintable(id->name),
451 static_cast<unsigned long>(lpddoi->dwType),
452 qPrintable(name));
453 }
454
455 return DIENUM_CONTINUE;
456 }
457
458 BOOL GlobalShortcutWin::EnumDevicesCB(LPCDIDEVICEINSTANCE pdidi, LPVOID pContext) {
459 GlobalShortcutWin *cbgsw=static_cast<GlobalShortcutWin *>(pContext);
460 HRESULT hr;
461
462 QString name = QString::fromUtf16(reinterpret_cast<const ushort *>(pdidi->tszProductName));
463 QString sname = QString::fromUtf16(reinterpret_cast<const ushort *>(pdidi->tszInstanceName));
464
465 InputDevice *id = new InputDevice;
466
467 id->pDID = NULL;
468
469 id->name = name;
470
471 id->guid = pdidi->guidInstance;
472 id->vguid = QVariant(QUuid(id->guid).toString());
473
474 id->guidproduct = pdidi->guidProduct;
475 id->vguidproduct = QVariant(QUuid(id->guidproduct).toString());
476
477 // Is it an XInput device? Skip it.
478 //
479 // This check is not restricted to USE_XBOXINPUT because
480 // Windows 10 (10586.122, ~March 2016) has issues with
481 // using XInput devices via DirectInput.
482 //
483 // See issues mumble-voip/mumble#2104 and mumble-voip/mumble#2147
484 // for more information.
485 if (XInputCheck_IsGuidProductXInputDevice(&id->guidproduct)) {
486 cbgsw->nxboxinput += 1;
487
488 qWarning("GlobalShortcutWin: excluded XInput device '%s' (guid %s guid product %s) from DirectInput",
489 qPrintable(id->name),
490 qPrintable(id->vguid.toString()),
491 qPrintable(id->vguidproduct.toString()));
492 delete id;
493 return DIENUM_CONTINUE;
494 }
495
496 // Check for PIDVID at the end of the GUID, as
497 // per http://stackoverflow.com/q/25622780.
498 BYTE pidvid[8] = { 0, 0, 'P', 'I', 'D', 'V', 'I', 'D' };
499 if (memcmp(id->guidproduct.Data4, pidvid, 8) == 0) {
500 uint16_t vendor_id = id->guidproduct.Data1 & 0xffff;
501 uint16_t product_id = (id->guidproduct.Data1 >> 16) & 0xffff;
502
503 id->vendor_id = vendor_id;
504 id->product_id = product_id;
505 } else {
506 id->vendor_id = 0x00;
507 id->product_id = 0x00;
508 }
509
510 // Reject devices if they are blacklisted.
511 //
512 // Device Name: ODAC-revB
513 // Vendor/Product ID: 0x262A, 0x1048
514 // https://github.com/mumble-voip/mumble/issues/1977
515 //
516 // Device Name: Aune T1 MK2 - HID-compliant consumer control device
517 // Vendor/Product ID: 0x262A, 0x1168
518 // https://github.com/mumble-voip/mumble/issues/1880
519 //
520 // For now, we simply disable the 0x262A vendor ID.
521 //
522 // 0x26A is SAVITECH Corp.
523 // http://www.savitech-ic.com/, or
524 // http://www.saviaudio.com/product.html
525 // (via https://usb-ids.gowdy.us/read/UD/262a)
526 //
527 // In the future, if there are more devices in the
528 // blacklist, we need a more structured aproach.
529 {
530 if (id->vendor_id == 0x262A) {
531 qWarning("GlobalShortcutWin: rejected blacklisted device %s (GUID: %s, PGUID: %s, VID: 0x%.4x, PID: 0x%.4x, TYPE: 0x%.8lx)",
532 qPrintable(id->name),
533 qPrintable(id->vguid.toString()),
534 qPrintable(id->vguidproduct.toString()),
535 id->vendor_id,
536 id->product_id,
537 static_cast<unsigned long>(pdidi->dwDevType));
538 delete id;
539 return DIENUM_CONTINUE;
540 }
541 }
542
543 foreach(InputDevice *dev, cbgsw->qhInputDevices) {
544 if (dev->guid == id->guid) {
545 delete id;
546 return DIENUM_CONTINUE;
547 }
548 }
549
550 if (FAILED(hr = cbgsw->pDI->CreateDevice(pdidi->guidInstance, &id->pDID, NULL)))
551 qFatal("GlobalShortcutWin: CreateDevice: %lx", hr);
552
553 if (FAILED(hr = id->pDID->EnumObjects(EnumDeviceObjectsCallback, static_cast<void *>(id), DIDFT_BUTTON)))
554 qFatal("GlobalShortcutWin: EnumObjects: %lx", hr);
555
556 if (id->qhNames.count() > 0) {
557 QList<DWORD> types = id->qhNames.keys();
558 qSort(types);
559
560 int nbuttons = types.count();
561 STACKVAR(DIOBJECTDATAFORMAT, rgodf, nbuttons);
562 DIDATAFORMAT df;
563 ZeroMemory(&df, sizeof(df));
564 df.dwSize = sizeof(df);
565 df.dwObjSize = sizeof(DIOBJECTDATAFORMAT);
566 df.dwFlags=DIDF_ABSAXIS;
567 df.dwDataSize = (nbuttons + 3) & (~0x3);
568 df.dwNumObjs = nbuttons;
569 df.rgodf = rgodf;
570 for (int i=0;i<nbuttons;i++) {
571 ZeroMemory(& rgodf[i], sizeof(DIOBJECTDATAFORMAT));
572 DWORD dwType = types[i];
573 DWORD dwOfs = i;
574 rgodf[i].dwOfs = dwOfs;
575 rgodf[i].dwType = dwType;
576 id->qhOfsToType[dwOfs] = dwType;
577 id->qhTypeToOfs[dwType] = dwOfs;
578 }
579
580 if (FAILED(hr = id->pDID->SetCooperativeLevel(mumble_mw_hwnd, DISCL_NONEXCLUSIVE|DISCL_BACKGROUND)))
581 qFatal("GlobalShortcutWin: SetCooperativeLevel: %lx", hr);
582
583 if (FAILED(hr = id->pDID->SetDataFormat(&df)))
584 qFatal("GlobalShortcutWin: SetDataFormat: %lx", hr);
585
586 DIPROPDWORD dipdw;
587
588 dipdw.diph.dwSize = sizeof(DIPROPDWORD);
589 dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
590 dipdw.diph.dwObj = 0;
591 dipdw.diph.dwHow = DIPH_DEVICE;
592 dipdw.dwData = DX_SAMPLE_BUFFER_SIZE;
593
594 if (FAILED(hr = id->pDID->SetProperty(DIPROP_BUFFERSIZE, &dipdw.diph)))
595 qFatal("GlobalShortcutWin: SetProperty: %lx", hr);
596
597 qWarning("Adding device %s %s %s:%d type 0x%.8lx guid product %s",
598 qPrintable(QUuid(id->guid).toString()),
599 qPrintable(name),
600 qPrintable(sname),
601 id->qhNames.count(),
602 static_cast<unsigned long>(pdidi->dwDevType),
603 qPrintable(id->vguidproduct.toString()));
604
605 cbgsw->qhInputDevices[id->guid] = id;
606 } else {
607 id->pDID->Release();
608 delete id;
609 }
610
611 return DIENUM_CONTINUE;
612 }
613
614 void GlobalShortcutWin::timeTicked() {
615 if (g.mw->uiNewHardware != uiHardwareDevices) {
616 uiHardwareDevices = g.mw->uiNewHardware;
617
618 XInputCheck_ClearDeviceCache();
619 nxboxinput = 0;
620
621 pDI->EnumDevices(DI8DEVCLASS_ALL, EnumDevicesCB, static_cast<void *>(this), DIEDFL_ATTACHEDONLY);
622 }
623
624 if (bNeedRemap)
625 remap();
626
627 foreach(InputDevice *id, qhInputDevices) {
628 DIDEVICEOBJECTDATA rgdod[DX_SAMPLE_BUFFER_SIZE];
629 DWORD dwItems = DX_SAMPLE_BUFFER_SIZE;
630 HRESULT hr;
631
632 hr = id->pDID->Acquire();
633
634 switch (hr) {
635 case DI_OK:
636 case S_FALSE:
637 break;
638 case DIERR_UNPLUGGED:
639 case DIERR_GENERIC:
640 qWarning("Removing device %s", qPrintable(QUuid(id->guid).toString()));
641 id->pDID->Release();
642 qhInputDevices.remove(id->guid);
643 delete id;
644 return;
645 case DIERR_OTHERAPPHASPRIO:
646 continue;
647 default:
648 break;
649 }
650
651 {
652 QElapsedTimer timer;
653 timer.start();
654
655 id->pDID->Poll();
656
657 // If a call to Poll takes more than
658 // a second, warn the user that they
659 // might have a misbehaving device.
660 if (timer.elapsed() > 1000) {
661 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()));
662 }
663 }
664
665 hr = id->pDID->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), rgdod, &dwItems, 0);
666 if (FAILED(hr))
667 continue;
668
669 if (dwItems <= 0)
670 continue;
671
672 for (DWORD j=0; j<dwItems; j++) {
673 QList<QVariant> ql;
674
675 quint32 uiType = id->qhOfsToType.value(rgdod[j].dwOfs);
676 ql << uiType;
677 ql << id->vguid;
678 handleButton(ql, rgdod[j].dwData & 0x80);
679 }
680 }
681
682 #ifdef USE_GKEY
683 if (g.s.bEnableGKey && gkey != NULL && gkey->isValid()) {
684 for (int button = GKEY_MIN_MOUSE_BUTTON; button <= GKEY_MAX_MOUSE_BUTTON; button++) {
685 QList<QVariant> ql;
686 ql << button;
687 ql << GKeyLibrary::quMouse;
688 handleButton(ql, gkey->isMouseButtonPressed(button));
689 }
690 for (int mode = GKEY_MIN_KEYBOARD_MODE; mode <= GKEY_MAX_KEYBOARD_MODE; mode++) {
691 for (int key = GKEY_MIN_KEYBOARD_BUTTON; key <= GKEY_MAX_KEYBOARD_BUTTON; key++) {
692 QList<QVariant> ql;
693 // Store the key and mode in one int
694 // bit 0..15: mode, bit 16..31: key
695 ql << (key | (mode << 16));
696 ql << GKeyLibrary::quKeyboard;
697 handleButton(ql, gkey->isKeyboardGkeyPressed(key, mode));
698 }
699 }
700 }
701 #endif
702
703 #ifdef USE_XBOXINPUT
704 if (g.s.bEnableXboxInput && xboxinput != NULL && xboxinput->isValid() && nxboxinput > 0) {
705 XboxInputState state;
706 for (uint32_t i = 0; i < XBOXINPUT_MAX_DEVICES; i++) {
707 if (xboxinput->GetState(i, &state) == 0) {
708 // Skip the result of GetState() if the packet number hasn't changed,
709 // or if we're at the first packet.
710 if (xboxinputLastPacket[i] != 0 && state.packetNumber == xboxinputLastPacket[i]) {
711 continue;
712 }
713
714 // The buttons field of XboxInputState contains a bit
715 // for each button on the Xbox controller. The official
716 // headers enumerate the bits via XINPUT_GAMEPAD_*.
717 // The official mapping uses all 16-bits, but leaves
718 // bit 10 and 11 (counting from 0) undocumented.
719 //
720 // It turns out that bit 10 is the guide button,
721 // which can be queried using the non-public
722 // XInputGetStateEx() function.
723 //
724 // Our mapping uses the bit number as a button index.
725 // So 0x1 -> 0, 0x2 -> 1, 0x4 -> 2, and so on...
726 //
727 // However, since the buttons field is only a 16-bit value,
728 // and we also want to use the left and right triggers as
729 // buttons, we assign them the button indexes 16 and 17.
730 uint32_t buttonMask = state.buttons;
731 for (uint32_t j = 0; j < 18; j++) {
732 QList<QVariant> ql;
733
734 bool pressed = false;
735 if (j >= 16) {
736 if (j == 16) { // LeftTrigger
737 pressed = state.leftTrigger > XBOXINPUT_TRIGGER_THRESHOLD;
738 } else if (j == 17) { // RightTrigger
739 pressed = state.rightTrigger > XBOXINPUT_TRIGGER_THRESHOLD;
740 }
741 } else {
742 uint32_t currentButtonMask = (1 << j);
743 pressed = (buttonMask & currentButtonMask) != 0;
744 }
745
746 uint32_t type = (i << 24) | j;
747 ql << static_cast<uint>(type);
748 ql << XboxInput::s_XboxInputGuid;
749 handleButton(ql, pressed);
750 }
751
752 xboxinputLastPacket[i] = state.packetNumber;
753 }
754 }
755 }
756 #endif
757
758 // Initialize winhooks.
759 //
760 // We do this here, because at this point, we've just run our
761 // first timeTicked() slot. The GlobalShortcut_win thread's event
762 // loop has nothing else to do at this point, so there is nothing
763 // that blocks the callbacks of the hooks.
764 //
765 // That is, if we initialize here, our callbacks *can* be called
766 // immediately after initialization, which gives the best results
767 // as far as interactivity and user experience goes. The initialization
768 // cannot be "felt".
769 //
770 // Let me explain...
771 //
772 // Originally, this code lived in the body of run, ::run(), just
773 // before exec() was called.
774 //
775 // It turns out that if our hooks are initialized there, it can take
776 // a short while before the mouse and keyboard callbacks can be processed.
777 //
778 // During this time, where the mouse callback is not able to be called,
779 // the mouse in Windows becomes laggy. It makes the whole computer feel
780 // like it has locked up -- because input "stops".
781 //
782 // As explained above, initializing the hooks here yields a much superior
783 // experience, where this initialization has no observable effect on the
784 // behavior of the system's mouse input.
785 if (bHook && hhMouse == NULL && hhKeyboard == NULL) {
786 HMODULE hSelf;
787 GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, reinterpret_cast<LPCTSTR>(&HookKeyboard), &hSelf);
788 hhMouse = SetWindowsHookEx(WH_MOUSE_LL, HookMouse, hSelf, 0);
789 hhKeyboard = SetWindowsHookEx(WH_KEYBOARD_LL, HookKeyboard, hSelf, 0);
790 }
791 }
792
793 QString GlobalShortcutWin::buttonName(const QVariant &v) {
794 GlobalShortcutWin *gsw = static_cast<GlobalShortcutWin *>(GlobalShortcutEngine::engine);
795
796 const QList<QVariant> &sublist = v.toList();
797 if (sublist.count() != 2)
798 return QString();
799
800 bool ok = false;
801 DWORD type = sublist.at(0).toUInt(&ok);
802 QUuid guid(sublist.at(1).toString());
803
804 if (guid.isNull() || (!ok))
805 return QString();
806
807 QString device=guid.toString();
808 QString name=QLatin1String("Unknown");
809
810 #ifdef USE_GKEY
811 if (g.s.bEnableGKey && gkey != NULL && gkey->isValid()) {
812 bool isGKey = false;
813 if (guid == GKeyLibrary::quMouse) {
814 isGKey = true;
815 name = gkey->getMouseButtonString(type);
816 } else if (guid == GKeyLibrary::quKeyboard) {
817 isGKey = true;
818 name = gkey->getKeyboardGkeyString(type & 0xFFFF, type >> 16);
819 }
820 if (isGKey) {
821 device = QLatin1String("GKey:");
822 return device + name; // Example output: "Gkey:G6/M1"
823 }
824 }
825 #endif
826
827 #ifdef USE_XBOXINPUT
828 if (g.s.bEnableXboxInput && xboxinput != NULL && xboxinput->isValid() && guid == XboxInput::s_XboxInputGuid) {
829 uint32_t idx = (type >> 24) & 0xff;
830 uint32_t button = (type & 0x00ffffff);
831
832 // Translate from our own button index mapping to
833 // the actual Xbox controller button names.
834 // For a description of the mapping, see the state
835 // querying code in GlobalShortcutWin::timeTicked().
836 switch (button) {
837 case 0:
838 return QString::fromLatin1("Xbox%1:Up").arg(idx + 1);
839 case 1:
840 return QString::fromLatin1("Xbox%1:Down").arg(idx + 1);
841 case 2:
842 return QString::fromLatin1("Xbox%1:Left").arg(idx + 1);
843 case 3:
844 return QString::fromLatin1("Xbox%1:Right").arg(idx + 1);
845 case 4:
846 return QString::fromLatin1("Xbox%1:Start").arg(idx + 1);
847 case 5:
848 return QString::fromLatin1("Xbox%1:Back").arg(idx + 1);
849 case 6:
850 return QString::fromLatin1("Xbox%1:LeftThumb").arg(idx + 1);
851 case 7:
852 return QString::fromLatin1("Xbox%1:RightThumb").arg(idx + 1);
853 case 8:
854 return QString::fromLatin1("Xbox%1:LeftShoulder").arg(idx + 1);
855 case 9:
856 return QString::fromLatin1("Xbox%1:RightShoulder").arg(idx + 1);
857 case 10:
858 return QString::fromLatin1("Xbox%1:Guide").arg(idx + 1);
859 case 11:
860 return QString::fromLatin1("Xbox%1:11").arg(idx + 1);
861 case 12:
862 return QString::fromLatin1("Xbox%1:A").arg(idx + 1);
863 case 13:
864 return QString::fromLatin1("Xbox%1:B").arg(idx + 1);
865 case 14:
866 return QString::fromLatin1("Xbox%1:X").arg(idx + 1);
867 case 15:
868 return QString::fromLatin1("Xbox%1:Y").arg(idx + 1);
869 case 16:
870 return QString::fromLatin1("Xbox%1:LeftTrigger").arg(idx + 1);
871 case 17:
872 return QString::fromLatin1("Xbox%1:RightTrigger").arg(idx + 1);
873 }
874 }
875 #endif
876
877 InputDevice *id = gsw->qhInputDevices.value(guid);
878 if (guid == GUID_SysMouse)
879 device=QLatin1String("M:");
880 else if (guid == GUID_SysKeyboard)
881 device=QLatin1String("K:");
882 else if (id)
883 device=id->name+QLatin1String(":");
884 if (id) {
885 QString result = id->qhNames.value(type);
886 if (!result.isEmpty()) {
887 name = result;
888 }
889 }
890 return device+name;
891 }
892
893 bool GlobalShortcutWin::canSuppress() {
894 return bHook;
895 }
896