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