Files
vnc_viewer/windows/runner/flutter_window.cpp
2025-08-16 12:37:47 +09:00

314 lines
9.1 KiB
C++

#include "flutter_window.h"
#include <optional>
#include <flutter/generated_plugin_registrant.h>
#include <flutter/method_channel.h>
#include <flutter/standard_method_codec.h>
#include <windows.h>
#include <iostream>
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<flutter::MethodChannel<flutter::EncodableValue>>(
messenger,
"com.example.vnc_viewer/keyboard",
&flutter::StandardMethodCodec::GetInstance());
auto args = std::make_unique<flutter::EncodableValue>(
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<KBDLLHOOKSTRUCT*>(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::MethodChannel<flutter::EncodableValue>>(
flutter_controller_->engine()->messenger(),
"com.example.vnc_viewer/keyboard",
&flutter::StandardMethodCodec::GetInstance());
message_channel_->SetMethodCallHandler(&FlutterWindow::HandleMethodCall);
}
void FlutterWindow::HandleMethodCall(
const flutter::MethodCall<flutter::EncodableValue>& method_call,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> 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<flutter::FlutterViewController>(
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<LRESULT> 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);
}