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
1112constexpr char kHotkeyChannel [] = " ykd/native_hotkeys" ;
1213constexpr char kOverlayChannel [] = " ykd/overlay" ;
14+ constexpr UINT kHotkeyMessage = WM_APP + 0x101 ;
1315constexpr UINT kOutsideClickMessage = WM_APP + 0x102 ;
1416
1517FlutterWindow* 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
2944FlutterWindow::FlutterWindow (const flutter::DartProject& project)
@@ -70,6 +85,7 @@ bool FlutterWindow::OnCreate() {
7085}
7186
7287void 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+
116175void 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 (
0 commit comments