Skip to content

Commit 7c8174d

Browse files
committed
Fix keyboard hooks
1 parent 20f3972 commit 7c8174d

4 files changed

Lines changed: 98 additions & 10 deletions

File tree

lib/src/app/bootstrap.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,7 @@ class _AppState extends State<App>
418418
await windowManager.setSize(size);
419419
await windowManager.setAlwaysOnTop(true);
420420
await windowManager.center();
421+
await WidgetsBinding.instance.endOfFrame;
421422
await _showOverlayWindowInactive(widget.overlayGateway);
422423
}
423424

pubspec.lock

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ packages:
2929
dependency: transitive
3030
description:
3131
name: characters
32-
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
32+
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
3333
url: "https://pub.dev"
3434
source: hosted
35-
version: "1.4.1"
35+
version: "1.4.0"
3636
clock:
3737
dependency: transitive
3838
description:
@@ -271,18 +271,18 @@ packages:
271271
dependency: transitive
272272
description:
273273
name: matcher
274-
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
274+
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
275275
url: "https://pub.dev"
276276
source: hosted
277-
version: "0.12.19"
277+
version: "0.12.17"
278278
material_color_utilities:
279279
dependency: transitive
280280
description:
281281
name: material_color_utilities
282-
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
282+
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
283283
url: "https://pub.dev"
284284
source: hosted
285-
version: "0.13.0"
285+
version: "0.11.1"
286286
menu_base:
287287
dependency: transitive
288288
description:
@@ -295,10 +295,10 @@ packages:
295295
dependency: transitive
296296
description:
297297
name: meta
298-
sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
298+
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
299299
url: "https://pub.dev"
300300
source: hosted
301-
version: "1.18.0"
301+
version: "1.17.0"
302302
nested:
303303
dependency: transitive
304304
description:
@@ -492,10 +492,10 @@ packages:
492492
dependency: transitive
493493
description:
494494
name: test_api
495-
sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
495+
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
496496
url: "https://pub.dev"
497497
source: hosted
498-
version: "0.7.11"
498+
version: "0.7.7"
499499
tray_manager:
500500
dependency: "direct main"
501501
description:

windows/runner/flutter_window.cpp

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include <flutter/standard_method_codec.h>
44

5+
#include <algorithm>
56
#include <optional>
67

78
#include "flutter/generated_plugin_registrant.h"
@@ -10,6 +11,7 @@ namespace {
1011

1112
constexpr char kHotkeyChannel[] = "ykd/native_hotkeys";
1213
constexpr char kOverlayChannel[] = "ykd/overlay";
14+
constexpr UINT kHotkeyMessage = WM_APP + 0x101;
1315
constexpr UINT kOutsideClickMessage = WM_APP + 0x102;
1416

1517
FlutterWindow* g_hotkey_window = nullptr;
@@ -24,6 +26,19 @@ int ReadInt(const flutter::EncodableMap& map, const char* key) {
2426
return 0;
2527
}
2628

29+
bool ModifierDown(int virtual_key) {
30+
return (::GetAsyncKeyState(virtual_key) & 0x8000) != 0;
31+
}
32+
33+
UINT CurrentModifiers() {
34+
UINT modifiers = 0;
35+
if (ModifierDown(VK_MENU)) modifiers |= MOD_ALT;
36+
if (ModifierDown(VK_CONTROL)) modifiers |= MOD_CONTROL;
37+
if (ModifierDown(VK_SHIFT)) modifiers |= MOD_SHIFT;
38+
if (ModifierDown(VK_LWIN) || ModifierDown(VK_RWIN)) modifiers |= MOD_WIN;
39+
return modifiers;
40+
}
41+
2742
}
2843

2944
FlutterWindow::FlutterWindow(const flutter::DartProject& project)
@@ -70,6 +85,7 @@ bool FlutterWindow::OnCreate() {
7085
}
7186

7287
void FlutterWindow::OnDestroy() {
88+
ReleaseLowLevelHook();
7389
SetOutsideClickWatch(false);
7490
for (const int id : hotkey_ids_) {
7591
::UnregisterHotKey(GetHandle(), id);
@@ -113,6 +129,49 @@ LRESULT CALLBACK FlutterWindow::LowLevelMouseProc(int code, WPARAM wparam,
113129
return ::CallNextHookEx(nullptr, code, wparam, lparam);
114130
}
115131

132+
bool FlutterWindow::EnsureLowLevelHook() {
133+
if (keyboard_hook_ != nullptr) return true;
134+
keyboard_hook_ = ::SetWindowsHookExW(
135+
WH_KEYBOARD_LL, &FlutterWindow::LowLevelKeyboardProc,
136+
::GetModuleHandleW(nullptr), 0);
137+
return keyboard_hook_ != nullptr;
138+
}
139+
140+
void FlutterWindow::ReleaseLowLevelHook() {
141+
if (keyboard_hook_ != nullptr) {
142+
::UnhookWindowsHookEx(keyboard_hook_);
143+
keyboard_hook_ = nullptr;
144+
}
145+
low_level_hotkeys_.clear();
146+
}
147+
148+
LRESULT CALLBACK FlutterWindow::LowLevelKeyboardProc(int code, WPARAM wparam,
149+
LPARAM lparam) {
150+
FlutterWindow* window = g_hotkey_window;
151+
if (code == HC_ACTION && window != nullptr) {
152+
const auto* event = reinterpret_cast<const KBDLLHOOKSTRUCT*>(lparam);
153+
const bool key_down = wparam == WM_KEYDOWN || wparam == WM_SYSKEYDOWN;
154+
const bool key_up = wparam == WM_KEYUP || wparam == WM_SYSKEYUP;
155+
const UINT modifiers = CurrentModifiers();
156+
for (auto& hotkey : window->low_level_hotkeys_) {
157+
if (hotkey.key != event->vkCode) continue;
158+
if (key_up) {
159+
hotkey.active = false;
160+
continue;
161+
}
162+
if (key_down && hotkey.modifiers == modifiers) {
163+
if (!hotkey.active) {
164+
hotkey.active = true;
165+
::PostMessageW(window->GetHandle(), kHotkeyMessage,
166+
static_cast<WPARAM>(hotkey.id), 0);
167+
}
168+
return 1;
169+
}
170+
}
171+
}
172+
return ::CallNextHookEx(nullptr, code, wparam, lparam);
173+
}
174+
116175
void FlutterWindow::DispatchHotkey(int id) {
117176
if (hotkey_channel_) {
118177
hotkey_channel_->InvokeMethod(
@@ -138,6 +197,11 @@ void FlutterWindow::HandleHotkeyMethodCall(
138197
result->Success(flutter::EncodableValue(true));
139198
return;
140199
}
200+
if (EnsureLowLevelHook()) {
201+
low_level_hotkeys_.push_back({id, modifiers, key, false});
202+
result->Success(flutter::EncodableValue(true));
203+
return;
204+
}
141205
result->Success(flutter::EncodableValue(false));
142206
return;
143207
}
@@ -149,6 +213,10 @@ void FlutterWindow::HandleHotkeyMethodCall(
149213
const int id = ReadInt(*arguments, "id");
150214
const bool unregistered = ::UnregisterHotKey(GetHandle(), id) != 0;
151215
hotkey_ids_.erase(id);
216+
low_level_hotkeys_.erase(
217+
std::remove_if(low_level_hotkeys_.begin(), low_level_hotkeys_.end(),
218+
[id](const LowLevelHotkey& h) { return h.id == id; }),
219+
low_level_hotkeys_.end());
152220
result->Success(flutter::EncodableValue(unregistered));
153221
return;
154222
}
@@ -158,6 +226,7 @@ void FlutterWindow::HandleHotkeyMethodCall(
158226
all_released = (::UnregisterHotKey(GetHandle(), id) != 0) && all_released;
159227
}
160228
hotkey_ids_.clear();
229+
ReleaseLowLevelHook();
161230
result->Success(flutter::EncodableValue(all_released));
162231
return;
163232
}
@@ -202,6 +271,9 @@ FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
202271
case WM_HOTKEY:
203272
DispatchHotkey(static_cast<int>(wparam));
204273
return 0;
274+
case kHotkeyMessage:
275+
DispatchHotkey(static_cast<int>(wparam));
276+
return 0;
205277
case kOutsideClickMessage:
206278
if (overlay_channel_) {
207279
overlay_channel_->InvokeMethod(

windows/runner/flutter_window.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
#include <memory>
1212
#include <set>
13+
#include <vector>
1314

1415
#include "win32_window.h"
1516

@@ -25,15 +26,26 @@ class FlutterWindow : public Win32Window {
2526
LPARAM const lparam) noexcept override;
2627

2728
private:
29+
struct LowLevelHotkey {
30+
int id;
31+
UINT modifiers;
32+
UINT key;
33+
bool active;
34+
};
35+
2836
void HandleHotkeyMethodCall(
2937
const flutter::MethodCall<flutter::EncodableValue>& call,
3038
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result);
3139
void HandleOverlayMethodCall(
3240
const flutter::MethodCall<flutter::EncodableValue>& call,
3341
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result);
42+
bool EnsureLowLevelHook();
43+
void ReleaseLowLevelHook();
3444
void DispatchHotkey(int id);
3545
void SetOutsideClickWatch(bool enabled);
3646

47+
static LRESULT CALLBACK LowLevelKeyboardProc(int code, WPARAM wparam,
48+
LPARAM lparam);
3749
static LRESULT CALLBACK LowLevelMouseProc(int code, WPARAM wparam,
3850
LPARAM lparam);
3951

@@ -48,6 +60,9 @@ class FlutterWindow : public Win32Window {
4860
std::unique_ptr<flutter::MethodChannel<flutter::EncodableValue>>
4961
overlay_channel_;
5062

63+
std::vector<LowLevelHotkey> low_level_hotkeys_;
64+
HHOOK keyboard_hook_ = nullptr;
65+
5166
HHOOK mouse_hook_ = nullptr;
5267
};
5368

0 commit comments

Comments
 (0)