mirror of
https://git.hmsn.ink/flutter/vnc_viewer.git
synced 2026-03-20 00:02:22 +09:00
314 lines
9.1 KiB
C++
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);
|
|
}
|