#include "flutter_window.h" #include #include #include #include #include #include #include #include #include #include "flutter/generated_plugin_registrant.h" // GET_X_LPARAM / GET_Y_LPARAM from windowsx.h // Defined inline to avoid potential macro conflicts with plugin code #ifndef GET_X_LPARAM #define GET_X_LPARAM(lp) ((int)(short)LOWORD(lp)) #endif #ifndef GET_Y_LPARAM #define GET_Y_LPARAM(lp) ((int)(short)HIWORD(lp)) #endif // ============================================================ // debug instrumentation for window-drag-lag (flutter_window side) // ============================================================ namespace { std::mutex g_debug_log_mutex2; std::wstring g_debug_log_path2; void DebugLog(const std::string& tag, const std::string& detail) { // 已禁用文件日志以排除 I/O 对消息循环的干扰。 (void)tag; (void)detail; #if 0 auto now = std::chrono::system_clock::now(); auto us = std::chrono::duration_cast( now.time_since_epoch()) .count(); std::lock_guard lock(g_debug_log_mutex2); if (g_debug_log_path2.empty()) { g_debug_log_path2 = L"E:\\project\\flutter\\f\\xianyan\\xianyan_drag_debug.log"; } std::wofstream ofs(g_debug_log_path2, std::ios::app); if (!ofs.is_open()) return; ofs << us << L" [" << std::wstring(tag.begin(), tag.end()) << L"] " << std::wstring(detail.begin(), detail.end()) << L"\n"; #endif } std::string MsgName(UINT msg) { switch (msg) { case WM_NCHITTEST: return "WM_NCHITTEST"; case WM_NCLBUTTONDOWN: return "WM_NCLBUTTONDOWN"; case WM_NCLBUTTONUP: return "WM_NCLBUTTONUP"; case WM_NCMOUSEMOVE: return "WM_NCMOUSEMOVE"; case WM_LBUTTONDOWN: return "WM_LBUTTONDOWN"; case WM_LBUTTONUP: return "WM_LBUTTONUP"; case WM_MOUSEMOVE: return "WM_MOUSEMOVE"; case WM_MOVE: return "WM_MOVE"; case WM_MOVING: return "WM_MOVING"; case WM_SIZE: return "WM_SIZE"; case WM_WINDOWPOSCHANGING: return "WM_WINDOWPOSCHANGING"; case WM_WINDOWPOSCHANGED: return "WM_WINDOWPOSCHANGED"; case WM_SYSCOMMAND: return "WM_SYSCOMMAND"; case WM_ENTERSIZEMOVE: return "WM_ENTERSIZEMOVE"; case WM_EXITSIZEMOVE: return "WM_EXITSIZEMOVE"; case WM_PAINT: return "WM_PAINT"; case WM_ERASEBKGND: return "WM_ERASEBKGND"; default: return "MSG_" + std::to_string(msg); } } } // namespace // ============================================================ // 静态成员初始化 // ============================================================ bool FlutterWindow::is_in_native_drag_ = false; FlutterWindow::FlutterWindow(const flutter::DartProject& project) : project_(project) {} FlutterWindow::~FlutterWindow() {} 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 子窗口:标题栏命中测试穿透 // Flutter 子窗口覆盖整个客户区,拦截了所有鼠标消息。 // 通过子类化,在标题栏可拖拽区域返回 HTTRANSPARENT,让父窗口 // 的 WM_NCHITTEST 有机会返回 HTCAPTION,从而触发 Windows 原生 // 模态拖拽循环。原生拖拽由 DWM 直接处理,延迟最低。 // 标题栏右侧控制按钮区域返回 HTCLIENT,保证按钮可点击。 // ============================================================ HWND child_hwnd = flutter_controller_->view()->GetNativeWindow(); SetWindowSubclass(child_hwnd, ChildWndProc, kChildSubclassId, reinterpret_cast(GetHandle())); flutter_controller_->engine()->SetNextFrameCallback([&]() { this->Show(); }); // 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(); // Register Windows platform MethodChannel for theme control const static std::string channel_name("apps.xy.xianyan/windows"); platform_channel_ = std::make_unique>( flutter_controller_->engine()->messenger(), channel_name, &flutter::StandardMethodCodec::GetInstance()); platform_channel_->SetMethodCallHandler( [this](const flutter::MethodCall<>& call, std::unique_ptr> result) { const std::string& method = call.method_name(); if (method == "setDarkMode") { bool is_dark = false; if (const auto* args = std::get_if(call.arguments())) { auto it = args->find(flutter::EncodableValue("isDark")); if (it != args->end() && std::holds_alternative(it->second)) { is_dark = std::get(it->second); } } Win32Window::SetDarkMode(GetHandle(), is_dark); result->Success(); } else if (method == "setWindowTitle") { std::string title; if (const auto* args = std::get_if(call.arguments())) { auto it = args->find(flutter::EncodableValue("title")); if (it != args->end() && std::holds_alternative(it->second)) { title = std::get(it->second); } } std::wstring wide_title(title.begin(), title.end()); Win32Window::SetWindowTitle(GetHandle(), wide_title); result->Success(); } else if (method == "setFullscreen") { bool fullscreen = false; if (const auto* args = std::get_if(call.arguments())) { auto it = args->find(flutter::EncodableValue("fullscreen")); if (it != args->end() && std::holds_alternative(it->second)) { fullscreen = std::get(it->second); } } Win32Window::SetFullscreen(GetHandle(), fullscreen); result->Success(); } else if (method == "isFullscreen") { bool is_fullscreen = Win32Window::IsFullscreen(GetHandle()); result->Success(flutter::EncodableValue(is_fullscreen)); } else if (method == "setMinSize") { unsigned int width = 0; unsigned int height = 0; if (const auto* args = std::get_if(call.arguments())) { auto it_w = args->find(flutter::EncodableValue("width")); auto it_h = args->find(flutter::EncodableValue("height")); if (it_w != args->end() && std::holds_alternative(it_w->second)) { width = static_cast(std::get(it_w->second)); } if (it_h != args->end() && std::holds_alternative(it_h->second)) { height = static_cast(std::get(it_h->second)); } } Win32Window::SetMinSize(GetHandle(), width, height); result->Success(); } else if (method == "performHapticFeedback") { int feedback_type = 0; if (const auto* args = std::get_if(call.arguments())) { auto it = args->find(flutter::EncodableValue("type")); if (it != args->end() && std::holds_alternative(it->second)) { feedback_type = std::get(it->second); } } Win32Window::PerformHapticFeedback(GetHandle(), feedback_type); result->Success(); } else if (method == "getSystemAppearance") { std::string appearance = Win32Window::GetSystemAppearance(); result->Success(flutter::EncodableValue(appearance)); } else { result->NotImplemented(); } }); // ============================================================ // 窗口控制 MethodChannel(窗口大小预设菜单等) // ============================================================ window_control_channel_ = std::make_unique>( flutter_controller_->engine()->messenger(), "xianyan/window_control", &flutter::StandardMethodCodec::GetInstance()); window_control_channel_->SetMethodCallHandler( [this](const flutter::MethodCall<>& call, std::unique_ptr> result) { const std::string& method = call.method_name(); if (method == "showWindowSizeMenu") { // 使用 PostMessage 异步弹出菜单,避免 TrackPopupMenuEx // 阻塞 MethodChannel 回调线程(Flutter UI 线程) PostMessage(GetHandle(), WM_USER + 1000, 0, 0); result->Success(flutter::EncodableValue(true)); } else { result->NotImplemented(); } }); return true; } void FlutterWindow::OnDestroy() { if (flutter_controller_) { HWND child_hwnd = flutter_controller_->view()->GetNativeWindow(); RemoveWindowSubclass(child_hwnd, ChildWndProc, kChildSubclassId); flutter_controller_ = nullptr; } Win32Window::OnDestroy(); } // ============================================================ // 原生窗口大小预设菜单 // // 由于 Mica/Acrylic 导致 DefWindowProc 边框命中测试失效, // 手动实现的边框命中测试在某些环境下仍无法调整窗口大小。 // 这里提供点击"口"按钮弹出原生菜单选择窗口大小的替代方案。 // // 使用 Win32 TrackPopupMenuEx 弹出原生菜单,符合 Windows 原生风格。 // 菜单项包括:最大化/还原、几种预设尺寸。 // ============================================================ bool FlutterWindow::ShowWindowSizeMenu(HWND hwnd) { if (!hwnd) return false; // 创建弹出菜单 HMENU hMenu = CreatePopupMenu(); if (!hMenu) return false; // 检查当前是否最大化 bool is_maximized = (::IsZoomed(hwnd) != 0); // 菜单项 ID(从 1001 开始,避免与系统命令冲突) enum MenuId { ID_TOGGLE_MAXIMIZE = 1001, ID_SIZE_800x600, ID_SIZE_1024x768, ID_SIZE_1280x720, ID_SIZE_1440x900, ID_SIZE_1920x1080, }; // 添加菜单项 AppendMenuW(hMenu, MF_STRING, ID_TOGGLE_MAXIMIZE, is_maximized ? L"🔽 还原窗口" : L"⬜ 最大化"); AppendMenuW(hMenu, MF_SEPARATOR, 0, nullptr); AppendMenuW(hMenu, MF_STRING, ID_SIZE_800x600, L"📱 小窗 800 × 600"); AppendMenuW(hMenu, MF_STRING, ID_SIZE_1024x768, L"💻 标准 1024 × 768"); AppendMenuW(hMenu, MF_STRING, ID_SIZE_1280x720, L"🖥️ 宽屏 1280 × 720"); AppendMenuW(hMenu, MF_STRING, ID_SIZE_1440x900, L"🖥️ 大屏 1440 × 900"); AppendMenuW(hMenu, MF_STRING, ID_SIZE_1920x1080, L"📺 全高清 1920 × 1080"); // 获取鼠标位置(菜单显示在鼠标位置) POINT cursor_pos; GetCursorPos(&cursor_pos); // 设置菜单为右对齐、右键选择 UINT flags = TPM_LEFTALIGN | TPM_TOPALIGN | TPM_LEFTBUTTON | TPM_RIGHTBUTTON | TPM_RETURNCMD; // 弹出菜单(同步阻塞,返回选择的菜单项 ID,0 表示取消) int cmd = TrackPopupMenuEx(hMenu, flags, cursor_pos.x, cursor_pos.y, hwnd, nullptr); DestroyMenu(hMenu); if (cmd == 0) return false; // 用户取消 // 处理用户选择 int width = 0, height = 0; switch (cmd) { case ID_TOGGLE_MAXIMIZE: if (is_maximized) { PostMessage(hwnd, WM_SYSCOMMAND, SC_RESTORE, 0); } else { PostMessage(hwnd, WM_SYSCOMMAND, SC_MAXIMIZE, 0); } return true; case ID_SIZE_800x600: width = 800; height = 600; break; case ID_SIZE_1024x768: width = 1024; height = 768; break; case ID_SIZE_1280x720: width = 1280; height = 720; break; case ID_SIZE_1440x900: width = 1440; height = 900; break; case ID_SIZE_1920x1080: width = 1920; height = 1080; break; default: return false; } // 取消最大化(如果当前是最大化状态) if (is_maximized) { ShowWindow(hwnd, SW_RESTORE); } // 获取窗口当前位置,保持左上角不变 RECT wr; GetWindowRect(hwnd, &wr); // 调整为指定大小(考虑 DPI 缩放) UINT dpi = GetDpiForWindow(hwnd); int physical_width = static_cast(width * dpi / 96.0); int physical_height = static_cast(height * dpi / 96.0); SetWindowPos(hwnd, nullptr, wr.left, wr.top, physical_width, physical_height, SWP_NOZORDER | SWP_NOACTIVATE); return true; } // ============================================================ // Flutter 子窗口子类化回调 // // 核心思路: // 1. 标题栏可拖拽区域返回 HTTRANSPARENT,让父窗口的 WM_NCHITTEST // 返回 HTCAPTION,Windows 随后进入原生模态拖拽循环。 // 2. 标题栏右侧控制按钮区域返回 HTCLIENT,保证 Flutter 按钮可点击。 // 3. 其他区域返回 DefSubclassProc,由 Flutter 正常处理。 // // 注意:窗口边框缩放由父窗口原生非客户区处理,不需要子窗口干预。 // ============================================================ LRESULT CALLBACK FlutterWindow::ChildWndProc( HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam, UINT_PTR subclass_id, DWORD_PTR ref_data) { HWND top_hwnd = reinterpret_cast(ref_data); // ============================================================ // 拖拽期间:拦截 Flutter 子窗口的所有消息(除了 WM_NCHITTEST) // // Flutter 子窗口覆盖整个客户区,在拖拽期间会持续接收消息 // (WM_PAINT/WM_MOVE/WM_SIZE/WM_WINDOWPOSCHANGED 等)并触发 // Flutter 引擎重绘,阻塞 Win32 消息循环(= UI 线程),导致 // DWM 无法应用 live-drag 优化。 // // 拖拽期间所有消息直接交给 DefWindowProc(不经过 Flutter 引擎的 // WndProc),阻止 Flutter 重绘。WM_NCHITTEST 仍需正常处理, // 否则鼠标在标题栏的命中测试会失败。 // ============================================================ if (is_in_native_drag_ && message != WM_NCHITTEST) { return DefWindowProc(hwnd, message, wparam, lparam); } // region debug-point child-msg if (message == WM_NCHITTEST || message == WM_NCLBUTTONDOWN || message == WM_NCLBUTTONUP || message == WM_NCMOUSEMOVE || message == WM_LBUTTONDOWN || message == WM_LBUTTONUP || message == WM_MOUSEMOVE || message == WM_MOVE || message == WM_MOVING || message == WM_SIZE || message == WM_WINDOWPOSCHANGING || message == WM_WINDOWPOSCHANGED || message == WM_SYSCOMMAND || message == WM_ENTERSIZEMOVE || message == WM_EXITSIZEMOVE || message == WM_PAINT || message == WM_ERASEBKGND) { std::ostringstream oss; oss << "hwnd=" << hwnd << " msg=" << MsgName(message) << " wp=" << wparam << " lp=" << lparam; DebugLog("child", oss.str()); } // endregion debug-point child-msg switch (message) { case WM_NCHITTEST: { POINT pt = {GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam)}; ScreenToClient(hwnd, &pt); UINT dpi = GetDpiForWindow(top_hwnd); double scale = static_cast(dpi) / 96.0; int title_h = static_cast(36 * scale); int btn_w = static_cast(138 * scale); RECT cr; GetClientRect(top_hwnd, &cr); { std::ostringstream oss; oss << "WM_NCHITTEST raw_screen=(" << GET_X_LPARAM(lparam) << "," << GET_Y_LPARAM(lparam) << ") client=(" << pt.x << "," << pt.y << ") dpi=" << dpi << " scale=" << scale << " title_h=" << title_h << " btn_w=" << btn_w << " cr=(" << cr.right << "," << cr.bottom << ")"; DebugLog("child", oss.str()); } if (cr.right <= 0 || cr.bottom <= 0) { DebugLog("child", "WM_NCHITTEST fallback: empty client rect"); return DefSubclassProc(hwnd, message, wparam, lparam); } // 标题栏可拖拽区域:穿透到父窗口,父窗口会返回 HTCAPTION if (pt.y >= 0 && pt.y < title_h && pt.x >= 0 && pt.x < cr.right - btn_w) { std::ostringstream oss; oss << "WM_NCHITTEST -> HTTRANSPARENT pt=(" << pt.x << "," << pt.y << ")"; DebugLog("child", oss.str()); return HTTRANSPARENT; } // 标题栏右侧控制按钮区域:保持 HTCLIENT,让 Flutter 处理点击 if (pt.y >= 0 && pt.y < title_h && pt.x >= cr.right - btn_w && pt.x <= cr.right) { DebugLog("child", "WM_NCHITTEST -> HTCLIENT (buttons)"); return HTCLIENT; } DebugLog("child", "WM_NCHITTEST -> DefSubclassProc"); return DefSubclassProc(hwnd, message, wparam, lparam); } } return DefSubclassProc(hwnd, message, wparam, lparam); } LRESULT FlutterWindow::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { // ============================================================ // 异步弹出窗口大小预设菜单 // MethodChannel 回调中通过 PostMessage 触发,避免 TrackPopupMenuEx // 阻塞 Flutter UI 线程 // ============================================================ if (message == WM_USER + 1000) { ShowWindowSizeMenu(hwnd); return 0; } // ============================================================ // 拖拽卡顿修复 v9:WM_NCLBUTTONDOWN 时禁用 Mica + SetWindowCompositionAttribute // // 根因链(经 v1~v8 验证): // 1. 手动 SetWindowPos 拖拽不触发 DWM "live drag" 优化 // 2. 完全禁用 Mica 能解决卡顿(v8 验证),但临时禁用 Mica(v4~v7)失败 // 3. v4~v7 失败的原因:EnterSizeMove 遗漏了 SetWindowCompositionAttribute // (ACCENT_DISABLED) 调用——这是 flutter_acrylic 的 setEffect(disabled) // 能立即禁用 Mica 的关键。仅靠 DwmSetWindowAttribute 是异步的,不生效。 // // v9 修复: // - WM_NCLBUTTONDOWN(HTCAPTION) 时(拖拽还没开始)调用 EnterSizeMove 禁用 Mica // (现在包含 SetWindowCompositionAttribute(ACCENT_DISABLED),能立即生效) // - SWP_FRAMECHANGED 强制 DWM 同步应用变更 // - DefWindowProc 进入 SC_MOVE 模态循环 // - DefWindowProc 返回后(拖拽已结束)调用 ExitSizeMove 恢复 Mica // - 模态循环期间绕过插件链 + 子窗口消息拦截(v8) // ============================================================ // --- 标题栏 + 边框命中测试:直接返回,绕过插件 --- // flutter_acrylic 的 DwmExtendFrameIntoClientArea({-1,-1,-1,-1}) 和 // window_manager 的 TitleBarStyle.hidden 会导致 DefWindowProc 的边框 // 命中测试失效。这里手动实现边框命中测试,恢复窗口边缘调整大小功能。 if (message == WM_NCHITTEST) { POINT pt = {GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam)}; UINT dpi = GetDpiForWindow(hwnd); double scale = static_cast(dpi) / 96.0; int title_h = static_cast(36.0 * scale); int btn_w = static_cast(138.0 * scale); // 边框命中测试宽度(稍大于实际边框,方便用户操作) int border_w = static_cast(6.0 * scale); // 使用窗口坐标(而非客户区坐标),避免客户区边距导致的负坐标问题 RECT wr; GetWindowRect(hwnd, &wr); bool maximized = (::IsZoomed(hwnd) != 0); if (!maximized) { bool on_left = pt.x >= wr.left && pt.x < wr.left + border_w; bool on_right = pt.x >= wr.right - border_w && pt.x < wr.right; bool on_top = pt.y >= wr.top && pt.y < wr.top + border_w; bool on_bottom = pt.y >= wr.bottom - border_w && pt.y < wr.bottom; if (on_top && on_left) return HTTOPLEFT; if (on_top && on_right) return HTTOPRIGHT; if (on_bottom && on_left) return HTBOTTOMLEFT; if (on_bottom && on_right) return HTBOTTOMRIGHT; if (on_left) return HTLEFT; if (on_right) return HTRIGHT; if (on_top) return HTTOP; if (on_bottom) return HTBOTTOM; } // 标题栏拖拽区域(使用客户区坐标判断) POINT client_pt = pt; ScreenToClient(hwnd, &client_pt); RECT cr; GetClientRect(hwnd, &cr); if (cr.right > 0 && cr.bottom > 0) { if (client_pt.y >= 0 && client_pt.y < title_h && client_pt.x >= 0 && client_pt.x < cr.right - btn_w) { return HTCAPTION; } } // 非标题栏/边框区域:继续交给插件处理(返回 HTCLIENT) } // --- 标题栏拖拽启动:提前禁用 Mica,再进入原生 SC_MOVE 模态循环 --- // 关键:在 WM_NCLBUTTONDOWN 时(拖拽还没开始)禁用 Mica。 // EnterSizeMove 现在包含 SetWindowCompositionAttribute(ACCENT_DISABLED), // 能立即禁用 Mica(与 flutter_acrylic 的 setEffect(disabled) 一致)。 if (message == WM_NCLBUTTONDOWN && wparam == HTCAPTION) { // 1. 拖拽开始前禁用 Mica/Acrylic backdrop(立即生效) EnterSizeMove(hwnd); // 2. SWP_FRAMECHANGED 触发 WM_NCCALCSIZE,强制 DWM 同步应用变更 SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); // 3. 进入原生 SC_MOVE 模态循环(同步阻塞,返回时拖拽已结束) LRESULT drag_result = DefWindowProc(hwnd, message, wparam, lparam); // 4. 拖拽结束后恢复 Mica/Acrylic backdrop ExitSizeMove(hwnd); // 5. SWP_FRAMECHANGED 强制 DWM 同步应用 Mica 恢复 SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); return drag_result; } // --- 原生拖拽/调整大小模态循环开始 --- // 无论是拖拽标题栏(已在 WM_NCLBUTTONDOWN 提前禁用 Mica)还是从边框调整大小, // 都会触发 WM_ENTERSIZEMOVE。对于边框调整大小的情况,这里禁用 Mica。 if (message == WM_ENTERSIZEMOVE) { is_in_native_drag_ = true; // 如果还没有禁用 Mica(边框调整大小的情况),现在禁用 if (!is_in_size_move_loop_) { EnterSizeMove(hwnd); SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); } return DefWindowProc(hwnd, message, wparam, lparam); } // --- 原生拖拽/调整大小模态循环结束 --- if (message == WM_EXITSIZEMOVE) { is_in_native_drag_ = false; // 如果之前禁用了 Mica(边框调整大小的情况),现在恢复 if (is_in_size_move_loop_) { ExitSizeMove(hwnd); SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); } return DefWindowProc(hwnd, message, wparam, lparam); } // ============================================================ // 原生拖拽期间:完全绕过插件链 // // window_manager 插件在 WM_MOVING 中调用 _EmitEvent("move"), // 通过 MethodChannel 通知 Dart 侧。Dart 侧的 listener 可能触发 // setState → Flutter 重建 → 阻塞 UI 线程(= Win32 消息循环线程)。 // 22 个插件的 delegate 分发也会累积延迟。 // // 拖拽期间所有消息直接交给 DefWindowProc,让 DWM live-drag 优化 // 不受任何干扰。同时 ChildWndProc 也会拦截 Flutter 子窗口的消息, // 阻止 Flutter 引擎重绘。此时 Mica 已被 EnterSizeMove 禁用。 // ============================================================ if (is_in_native_drag_) { return DefWindowProc(hwnd, message, wparam, lparam); } // --- 非拖拽期间:正常消息流转 --- // 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); }