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