#include "flutter_window.h" #include #include #include #include #include #include static HHOOK g_hHook = nullptr; constexpr wchar_t kFlutterTitleName[] = L"Desktop"; FlutterWindow* g_flutter_window = nullptr; enum KeyState { kNone, kWinPressed, kOtherKeyPressed }; KeyState g_keyState = kNone; bool g_isLeftShiftPressed = false; bool g_isRightShiftPressed = false; void UpdateShiftState(UINT vkCode, bool isKeyDown) { if (vkCode == VK_LSHIFT) { g_isLeftShiftPressed = isKeyDown; } else if (vkCode == VK_RSHIFT) { g_isRightShiftPressed = isKeyDown; } } bool IsShiftPressed() { return g_isLeftShiftPressed || g_isRightShiftPressed; } int GetFlutterKeyId(UINT vkCode) { bool shiftPressed = IsShiftPressed(); // std::wcout << L"[GetFlutterKeyId] shiftPressed=" << shiftPressed // << L", vkCode=" << vkCode // << L"\n" << std::flush; if(shiftPressed) { if (vkCode >= 0x41 && vkCode <= 0x5A) { return vkCode + 0x20; // 'A' = 0x0041 } if (vkCode >= 0x61 && vkCode <= 0x7A) { return vkCode - 0x20; // 'a' ~ 'z' } } else { if (vkCode >= 0x41 && vkCode <= 0x5A) { return vkCode; // 'A' ~ 'Z' = 0x0041 } if (vkCode >= 0x61 && vkCode <= 0x7A) { return vkCode; // 'a' ~ 'z' } } if (vkCode >= 0x30 && vkCode <= 0x39) return vkCode; // '0'~'9' if (vkCode >= 0x60 && vkCode <= 0x69) { return 0xFFB0 + (vkCode - 0x60); } if (vkCode >= 0x70 && vkCode <= 0x7B) { return 0xFFBE + (vkCode - 0x70); } switch (vkCode) { // Modifier Keys case 0x25: return 0xFF51; // Left case 0x26: return 0xFF52; // Up case 0x27: return 0xFF53; // Right case 0x28: return 0xFF54; // Down case 0x08: return 0xFF08; // Backspace case 0x09: return 0xFF09; // Tab case 0x0D: return 0xFF0D; // Enter case 0x1B: return 0xFF1B; // Escape case 0x20: return 0x0020; // Space case 0x2D: return 0xFF63; // Insert case 0x2E: return 0xFFFF; // Delete case 0x24: return 0xFF50; // Home case 0x23: return 0xFF57; // End case 0x21: return 0xFF55; // Page Up case 0x22: return 0xFF56; // Page Down case 0xBA: return 0x003B; // Semicolon (;) case 0xBB: return 0x003D; // Equal (=) case 0xBC: return 0x002C; // Comma (,) case 0xBD: return 0x002D; // Minus (-) case 0xBE: return 0x002E; // Period (.) case 0xBF: return 0x002F; // Slash (/) case 0xC0: return 0x0060; // Backquote (`) or ~ case 0xDB: return 0x005B; // Open Bracket ([) case 0xDC: return 0x005C; // Backslash (\) case 0xDD: return 0x005D; // Close Bracket (]) case 0xDE: return 0x0027; // Quote (') case 0xA0: return 0xFFE1; // Left Shift case 0xA1: return 0xFFE2; // Right Shift case 0xA2: return 0xFFE3; // Left Control case 0xA3: return 0xFFE4; // Right Control case 0xA4: return 0xFFE9; // Left Alt case 0xA5: return 0xFFEA; // Right Alt case 0x5B: return 0xFFEB; // Left Meta (Win) case 0x5C: return 0xFFEC; // Right Meta (Win) case 0x5D: return 0xFF67; // Context Menu case 0x90: return 0xFF7F; // Num Lock case 0x91: return 0xFF14; // Scroll Lock case 0x14: return 0xFFE5; // Caps Lock case 0xAD: return 0xFF12; // Mute case 0xAE: return 0xFF11; // Volume Down case 0xAF: return 0xFF13; // Volume Up case 0xB0: return 0xFF16; // Media Previous case 0xB1: return 0xFF17; // Media Next case 0xB2: return 0xFF14; // Play/Pause case 0xB3: return 0xFF15; // Stop default: return 0x0000; // Unknown } } void SendKeyEventToDart(UINT vkCode, bool down) { if (g_flutter_window) { auto messenger = g_flutter_window->GetMessenger(); if (messenger) { int flutterKeyId = GetFlutterKeyId(vkCode); if (flutterKeyId == 0) return; auto channel = std::make_unique>( messenger, "com.example.vnc_viewer/keyboard", &flutter::StandardMethodCodec::GetInstance()); auto args = std::make_unique( flutter::EncodableMap{ {"key", flutter::EncodableValue(flutterKeyId)}, {"vkCode", flutter::EncodableValue(vkCode)}, {"down", flutter::EncodableValue(down)}, } ); // std::wcout << L"[send] Pressed=" << g_keyState // << L", args=" << args // << L"\n" << std::flush; channel->InvokeMethod("onPhysicalKeyEvent", std::move(args)); } } } static LRESULT CALLBACK LLKeyProc(int nCode, WPARAM w, LPARAM l) { if (nCode == HC_ACTION) { std::string type = ""; auto* kbd = reinterpret_cast(l); HWND hwndFore = GetForegroundWindow(); if (hwndFore == nullptr) return CallNextHookEx(nullptr, nCode, w, l); wchar_t titleName[256] = {0}; GetWindowText(hwndFore, titleName, 256); // std::wcout << L"[g_keyState] Pressed=" << g_keyState // << L", kFlutterTitleName=" << kFlutterTitleName // << L", titleName=" << titleName // << L", compare=" << wcscmp(titleName, kFlutterTitleName) // << L"\n" << std::flush; if (wcscmp(titleName, kFlutterTitleName) == 0) { bool isKeyDown = (w == WM_KEYDOWN || w == WM_SYSKEYDOWN); if (kbd->vkCode == VK_LSHIFT || kbd->vkCode == VK_RSHIFT) { UpdateShiftState(kbd->vkCode, isKeyDown); } // std::wcout << L"[g_keyState] Pressed=" << g_keyState // << L", kWinPressed=" << kWinPressed // << L", vkCode=" << kbd->vkCode // << L"\n" << std::flush; if(kbd->vkCode == 20 && !isKeyDown) { return 1; } SendKeyEventToDart(kbd->vkCode, isKeyDown); return 1; } else { return CallNextHookEx(nullptr, nCode, w, l); } } return CallNextHookEx(nullptr, nCode, w, l); } static void InstallLLHook() { if (!g_hHook) { g_hHook = SetWindowsHookEx( WH_KEYBOARD_LL, LLKeyProc, GetModuleHandle(nullptr), 0 ); } } static void RemoveLLHook() { if (g_hHook) { UnhookWindowsHookEx(g_hHook); g_hHook = nullptr; } } void FlutterWindow::CreateMessageChannel() { if (!flutter_controller_ || !flutter_controller_->engine()) { return; } message_channel_ = std::make_unique>( flutter_controller_->engine()->messenger(), "com.example.vnc_viewer/keyboard", &flutter::StandardMethodCodec::GetInstance()); message_channel_->SetMethodCallHandler(&FlutterWindow::HandleMethodCall); } void FlutterWindow::HandleMethodCall( const flutter::MethodCall& method_call, std::unique_ptr> result) { result->NotImplemented(); } // ---------------------------------------------------- FlutterWindow::FlutterWindow(const flutter::DartProject& project) : project_(project) { g_flutter_window = this; } FlutterWindow::~FlutterWindow() { // RemoveLLHook(); } bool FlutterWindow::OnCreate() { if (!Win32Window::OnCreate()) { return false; } RECT frame = GetClientArea(); // The size here must match the window dimensions to avoid unnecessary surface // creation / destruction in the startup path. flutter_controller_ = std::make_unique( frame.right - frame.left, frame.bottom - frame.top, project_); // Ensure that basic setup of the controller was successful. if (!flutter_controller_->engine() || !flutter_controller_->view()) { return false; } RegisterPlugins(flutter_controller_->engine()); SetChildContent(flutter_controller_->view()->GetNativeWindow()); flutter_controller_->engine()->SetNextFrameCallback([&]() { this->Show(); }); CreateMessageChannel(); // Flutter can complete the first frame before the "show window" callback is // registered. The following call ensures a frame is pending to ensure the // window is shown. It is a no-op if the first frame hasn't completed yet. flutter_controller_->ForceRedraw(); InstallLLHook(); return true; } void FlutterWindow::OnDestroy() { RemoveLLHook(); if (flutter_controller_) { flutter_controller_ = nullptr; } Win32Window::OnDestroy(); } LRESULT FlutterWindow::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { // Give Flutter, including plugins, an opportunity to handle window messages. if (flutter_controller_) { std::optional result = flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, lparam); if (result) { return *result; } } switch (message) { case WM_FONTCHANGE: flutter_controller_->engine()->ReloadSystemFonts(); break; } return Win32Window::MessageHandler(hwnd, message, wparam, lparam); }