1 // This file is part of Desktop App Toolkit,
2 // a set of libraries for developing nice desktop applications.
3 //
4 // For license and copyright information please follow this link:
5 // https://github.com/desktop-app/legal/blob/master/LEGAL
6 //
7 #include "base/platform/win/base_global_shortcuts_win.h"
8 
9 #include "base/platform/win/base_windows_h.h"
10 
11 namespace base::Platform::GlobalShortcuts {
12 namespace {
13 
14 constexpr auto kShiftMouseButton = std::numeric_limits<uint64>::max() - 100;
15 
16 HHOOK GlobalHookKeyboard = nullptr;
17 HHOOK GlobalHookMouse = nullptr;
18 HANDLE ThreadHandle = nullptr;
19 HANDLE ThreadEvent = nullptr;
20 DWORD ThreadId = 0;
21 Fn<void(GlobalShortcutKeyGeneric descriptor, bool down)> ProcessCallback;
22 
MakeDescriptor(uint32 virtualKeyCode,uint32 lParam)23 [[nodiscard]] GlobalShortcutKeyGeneric MakeDescriptor(
24 		uint32 virtualKeyCode,
25 		uint32 lParam) {
26 	return GlobalShortcutKeyGeneric(
27 		(uint64(virtualKeyCode) << 32) | uint64(lParam));
28 }
29 
MakeMouseDescriptor(uint8 button)30 [[nodiscard]] GlobalShortcutKeyGeneric MakeMouseDescriptor(uint8 button) {
31 	Expects(button > 0 && button < 100);
32 
33 	return kShiftMouseButton + button;
34 }
35 
GetVirtualKeyCode(GlobalShortcutKeyGeneric descriptor)36 [[nodiscard]] uint32 GetVirtualKeyCode(GlobalShortcutKeyGeneric descriptor) {
37 	return uint32(uint64(descriptor) >> 32);
38 }
39 
GetLParam(GlobalShortcutKeyGeneric descriptor)40 [[nodiscard]] uint32 GetLParam(GlobalShortcutKeyGeneric descriptor) {
41 	return uint32(uint64(descriptor) & 0xFFFFFFFFULL);
42 }
43 
ProcessHookedKeyboardEvent(WPARAM wParam,LPARAM lParam)44 void ProcessHookedKeyboardEvent(WPARAM wParam, LPARAM lParam) {
45 	const auto press = (PKBDLLHOOKSTRUCT)lParam;
46 	const auto repeatCount = uint32(0);
47 	const auto extendedBit = ((press->flags & LLKHF_EXTENDED) != 0);
48 	const auto contextBit = ((press->flags & LLKHF_ALTDOWN) != 0);
49 	const auto transitionState = ((press->flags & LLKHF_UP) != 0);
50 	const auto lParamForEvent = (repeatCount & 0x0000FFFFU)
51 		| ((uint32(press->scanCode) & 0xFFU) << 16)
52 		| (extendedBit ? (uint32(KF_EXTENDED) << 16) : 0);
53 		//| (contextBit ? (uint32(KF_ALTDOWN) << 16) : 0); // Alt pressed.
54 		//| (transitionState ? (uint32(KF_UP) << 16) : 0); // Is pressed.
55 	const auto descriptor = MakeDescriptor(press->vkCode, lParamForEvent);
56 	const auto down = (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN);
57 
58 	ProcessCallback(descriptor, down);
59 }
60 
ProcessHookedMouseEvent(WPARAM wParam,LPARAM lParam)61 void ProcessHookedMouseEvent(WPARAM wParam, LPARAM lParam) {
62 	if (wParam != WM_RBUTTONDOWN
63 		&& wParam != WM_RBUTTONUP
64 		&& wParam != WM_MBUTTONDOWN
65 		&& wParam != WM_MBUTTONUP
66 		&& wParam != WM_XBUTTONDOWN
67 		&& wParam != WM_XBUTTONUP) {
68 		return;
69 	}
70 	const auto button = [&] {
71 		if (wParam == WM_RBUTTONDOWN || wParam == WM_RBUTTONUP) {
72 			return 2;
73 		} else if (wParam == WM_MBUTTONDOWN || wParam == WM_MBUTTONUP) {
74 			return 3;
75 		}
76 		const auto press = (PMSLLHOOKSTRUCT)lParam;
77 		const auto xbutton = ((press->mouseData >> 16) & 0xFFU);
78 		return (xbutton >= 0x01 && xbutton <= 0x18) ? int(xbutton + 3) : 0;
79 	}();
80 	if (!button) {
81 		return;
82 	}
83 	const auto descriptor = MakeMouseDescriptor(button);
84 	const auto down = (wParam == WM_RBUTTONDOWN)
85 		|| (wParam == WM_MBUTTONDOWN)
86 		|| (wParam == WM_XBUTTONDOWN);
87 
88 	ProcessCallback(descriptor, down);
89 }
90 
LowLevelKeyboardProc(_In_ int nCode,_In_ WPARAM wParam,_In_ LPARAM lParam)91 LRESULT CALLBACK LowLevelKeyboardProc(
92 		_In_ int nCode,
93 		_In_ WPARAM wParam,
94 		_In_ LPARAM lParam) {
95 	if (nCode == HC_ACTION) {
96 		ProcessHookedKeyboardEvent(wParam, lParam);
97 	}
98 	return CallNextHookEx(GlobalHookKeyboard, nCode, wParam, lParam);
99 }
100 
LowLevelMouseProc(_In_ int nCode,_In_ WPARAM wParam,_In_ LPARAM lParam)101 LRESULT CALLBACK LowLevelMouseProc(
102 		_In_ int nCode,
103 		_In_ WPARAM wParam,
104 		_In_ LPARAM lParam) {
105 	if (nCode == HC_ACTION) {
106 		ProcessHookedMouseEvent(wParam, lParam);
107 	}
108 	return CallNextHookEx(GlobalHookMouse, nCode, wParam, lParam);
109 }
110 
RunThread(LPVOID)111 DWORD WINAPI RunThread(LPVOID) {
112 	auto message = MSG();
113 
114 	// Force message loop creation.
115 	PeekMessage(&message, nullptr, WM_USER, WM_USER, PM_NOREMOVE);
116 	SetEvent(ThreadEvent);
117 
118 	const auto guard = gsl::finally([&] {
119 		if (GlobalHookKeyboard) {
120 			UnhookWindowsHookEx(GlobalHookKeyboard);
121 			GlobalHookKeyboard = nullptr;
122 		}
123 		if (GlobalHookMouse) {
124 			UnhookWindowsHookEx(GlobalHookMouse);
125 			GlobalHookMouse = nullptr;
126 		}
127 	});
128 	GlobalHookKeyboard = SetWindowsHookEx(
129 		WH_KEYBOARD_LL,
130 		LowLevelKeyboardProc,
131 		nullptr,
132 		0);
133 	GlobalHookMouse = SetWindowsHookEx(
134 		WH_MOUSE_LL,
135 		LowLevelMouseProc,
136 		nullptr,
137 		0);
138 	if (!GlobalHookKeyboard || !GlobalHookMouse) {
139 		return -1;
140 	}
141 
142 	while (GetMessage(&message, nullptr, 0, 0)) {
143 		if (message.message == WM_QUIT) {
144 			break;
145 		}
146 		TranslateMessage(&message);
147 		DispatchMessage(&message);
148 	}
149 	return 0;
150 }
151 
152 } // namespace
153 
Available()154 bool Available() {
155 	return true;
156 }
157 
Allowed()158 bool Allowed() {
159 	return true;
160 }
161 
Start(Fn<void (GlobalShortcutKeyGeneric descriptor,bool down)> process)162 void Start(Fn<void(GlobalShortcutKeyGeneric descriptor, bool down)> process) {
163 	Expects(!ThreadHandle);
164 	Expects(!ThreadId);
165 
166 	ProcessCallback = std::move(process);
167 	ThreadEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
168 	if (!ThreadEvent) {
169 		ProcessCallback = nullptr;
170 		return;
171 	}
172 	ThreadHandle = CreateThread(
173 		nullptr,
174 		0,
175 		&RunThread,
176 		nullptr,
177 		0,
178 		&ThreadId);
179 	if (!ThreadHandle) {
180 		CloseHandle(ThreadEvent);
181 		ThreadEvent = nullptr;
182 		ThreadId = 0;
183 		ProcessCallback = nullptr;
184 	}
185 }
186 
Stop()187 void Stop() {
188 	if (!ThreadHandle) {
189 		return;
190 	}
191 	WaitForSingleObject(ThreadEvent, INFINITE);
192 	PostThreadMessage(ThreadId, WM_QUIT, 0, 0);
193 	WaitForSingleObject(ThreadHandle, INFINITE);
194 	CloseHandle(ThreadHandle);
195 	CloseHandle(ThreadEvent);
196 	ThreadHandle = ThreadEvent = nullptr;
197 	ThreadId = 0;
198 	ProcessCallback = nullptr;
199 }
200 
KeyName(GlobalShortcutKeyGeneric descriptor)201 QString KeyName(GlobalShortcutKeyGeneric descriptor) {
202 	if (descriptor > kShiftMouseButton) {
203 		return QString("Mouse %1").arg(descriptor - kShiftMouseButton);
204 	}
205 
206 	constexpr auto kLimit = 1024;
207 
208 	WCHAR buffer[kLimit + 1] = { 0 };
209 
210 	// Remove 25 bit, we want to differentiate between left and right Ctrl-s.
211 	auto lParam = LONG(GetLParam(descriptor) & ~(1U << 25));
212 
213 	return GetKeyNameText(lParam, buffer, kLimit)
214 		? QString::fromWCharArray(buffer)
215 		: (GetVirtualKeyCode(descriptor) == VK_RSHIFT)
216 		? QString("Right Shift")
217 		: QString("\\x%1").arg(GetVirtualKeyCode(descriptor), 0, 16);
218 }
219 
220 } // namespace base::Platform::GlobalShortcuts
221