From 4e1532af3e50ebb5ef1ecdb380aba9b1d2378ad2 Mon Sep 17 00:00:00 2001 From: Lucas Date: Thu, 7 Nov 2024 15:55:38 +0800 Subject: [PATCH] fix: application window is not visible on Windows (#6740) * fix: try to fix the windows not visible issue * chore: update win32_window * chore: adjust the new api --- .../windows/runner/flutter_window.cpp | 12 +- .../appflowy_flutter/windows/runner/main.cpp | 5 +- .../windows/runner/win32_window.cpp | 405 +++++++++--------- .../windows/runner/win32_window.h | 45 +- 4 files changed, 244 insertions(+), 223 deletions(-) diff --git a/frontend/appflowy_flutter/windows/runner/flutter_window.cpp b/frontend/appflowy_flutter/windows/runner/flutter_window.cpp index 8e9deabc69..955ee3038f 100644 --- a/frontend/appflowy_flutter/windows/runner/flutter_window.cpp +++ b/frontend/appflowy_flutter/windows/runner/flutter_window.cpp @@ -17,7 +17,7 @@ bool FlutterWindow::OnCreate() { RECT frame = GetClientArea(); // The size here must match the window dimensions to avoid unnecessary surface -// creation / destruction in the startup path. + // 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. @@ -26,6 +26,16 @@ bool FlutterWindow::OnCreate() { } RegisterPlugins(flutter_controller_->engine()); SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + 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(); + return true; } diff --git a/frontend/appflowy_flutter/windows/runner/main.cpp b/frontend/appflowy_flutter/windows/runner/main.cpp index 8ac91fd693..b1fff72b84 100644 --- a/frontend/appflowy_flutter/windows/runner/main.cpp +++ b/frontend/appflowy_flutter/windows/runner/main.cpp @@ -47,9 +47,12 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, FlutterWindow window(project); Win32Window::Point origin(10, 10); Win32Window::Size size(1280, 720); - if (!window.CreateAndShow(L"AppFlowy", origin, size)) { + + if (!window.Create(L"AppFlowy", origin, size)) { return EXIT_FAILURE; } + + window.Show(); window.SetQuitOnClose(true); ::MSG msg; diff --git a/frontend/appflowy_flutter/windows/runner/win32_window.cpp b/frontend/appflowy_flutter/windows/runner/win32_window.cpp index a46adb6af5..2f78196d35 100644 --- a/frontend/appflowy_flutter/windows/runner/win32_window.cpp +++ b/frontend/appflowy_flutter/windows/runner/win32_window.cpp @@ -1,60 +1,70 @@ #include "win32_window.h" +#include #include #include "resource.h" #include "app_links/app_links_plugin_c_api.h" -namespace -{ +namespace { - constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif - // The number of Win32Window objects that currently exist. - static int g_active_window_count = 0; +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; - using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; - // Scale helper to convert logical scaler values to physical using passed in - // scale factor - int Scale(int source, double scale_factor) - { - return static_cast(source * scale_factor); +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; } - - // Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. - // This API is only needed for PerMonitor V1 awareness mode. - void EnableFullDpiSupportIfAvailable(HWND hwnd) - { - HMODULE user32_module = LoadLibraryA("User32.dll"); - if (!user32_module) - { - return; - } - auto enable_non_client_dpi_scaling = - reinterpret_cast( - GetProcAddress(user32_module, "EnableNonClientDpiScaling")); - if (enable_non_client_dpi_scaling != nullptr) - { - enable_non_client_dpi_scaling(hwnd); - FreeLibrary(user32_module); - } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); } + FreeLibrary(user32_module); +} -} // namespace +} // namespace // Manages the Win32Window's window class registration. -class WindowClassRegistrar -{ -public: +class WindowClassRegistrar { + public: ~WindowClassRegistrar() = default; - // Returns the singleton registar instance. - static WindowClassRegistrar *GetInstance() - { - if (!instance_) - { + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { instance_ = new WindowClassRegistrar(); } return instance_; @@ -62,26 +72,24 @@ public: // Returns the name of the window class, registering the class if it hasn't // previously been registered. - const wchar_t *GetWindowClass(); + const wchar_t* GetWindowClass(); // Unregisters the window class. Should only be called if there are no // instances of the window. void UnregisterWindowClass(); -private: + private: WindowClassRegistrar() = default; - static WindowClassRegistrar *instance_; + static WindowClassRegistrar* instance_; bool class_registered_ = false; }; -WindowClassRegistrar *WindowClassRegistrar::instance_ = nullptr; +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; -const wchar_t *WindowClassRegistrar::GetWindowClass() -{ - if (!class_registered_) - { +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { WNDCLASS window_class{}; window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); window_class.lpszClassName = kWindowClassName; @@ -100,35 +108,31 @@ const wchar_t *WindowClassRegistrar::GetWindowClass() return kWindowClassName; } -void WindowClassRegistrar::UnregisterWindowClass() -{ +void WindowClassRegistrar::UnregisterWindowClass() { UnregisterClass(kWindowClassName, nullptr); class_registered_ = false; } -Win32Window::Win32Window() -{ +Win32Window::Win32Window() { ++g_active_window_count; } -Win32Window::~Win32Window() -{ +Win32Window::~Win32Window() { --g_active_window_count; Destroy(); } -bool Win32Window::CreateAndShow(const std::wstring &title, - const Point &origin, - const Size &size) -{ +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + if (SendAppLinkToInstance(title)) { return false; } - Destroy(); - - const wchar_t *window_class = + const wchar_t* window_class = WindowClassRegistrar::GetInstance()->GetWindowClass(); const POINT target_point = {static_cast(origin.x), @@ -138,19 +142,158 @@ bool Win32Window::CreateAndShow(const std::wstring &title, double scale_factor = dpi / 96.0; HWND window = CreateWindow( - window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), Scale(size.width, scale_factor), Scale(size.height, scale_factor), nullptr, nullptr, GetModuleHandle(nullptr), this); - if (!window) - { + if (!window) { return false; } + UpdateTheme(window); + return OnCreate(); } +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} + bool Win32Window::SendAppLinkToInstance(const std::wstring &title) { // Find our exact window @@ -186,140 +329,4 @@ bool Win32Window::SendAppLinkToInstance(const std::wstring &title) } return false; -} - -// static -LRESULT CALLBACK Win32Window::WndProc(HWND const window, - UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept -{ - if (message == WM_NCCREATE) - { - auto window_struct = reinterpret_cast(lparam); - SetWindowLongPtr(window, GWLP_USERDATA, - reinterpret_cast(window_struct->lpCreateParams)); - - auto that = static_cast(window_struct->lpCreateParams); - EnableFullDpiSupportIfAvailable(window); - that->window_handle_ = window; - } - else if (Win32Window *that = GetThisFromHandle(window)) - { - return that->MessageHandler(window, message, wparam, lparam); - } - - return DefWindowProc(window, message, wparam, lparam); -} - -LRESULT -Win32Window::MessageHandler(HWND hwnd, - UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept -{ - switch (message) - { - case WM_DESTROY: - window_handle_ = nullptr; - Destroy(); - if (quit_on_close_) - { - PostQuitMessage(0); - } - return 0; - - case WM_DPICHANGED: - { - auto newRectSize = reinterpret_cast(lparam); - LONG newWidth = newRectSize->right - newRectSize->left; - LONG newHeight = newRectSize->bottom - newRectSize->top; - - SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, - newHeight, SWP_NOZORDER | SWP_NOACTIVATE); - - return 0; - } - case WM_SIZE: - { - RECT rect = GetClientArea(); - if (child_content_ != nullptr) - { - // Size and position the child window. - MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, - rect.bottom - rect.top, TRUE); - } - return 0; - } - - case WM_ACTIVATE: - if (child_content_ != nullptr) - { - SetFocus(child_content_); - } - return 0; - } - - return DefWindowProc(window_handle_, message, wparam, lparam); -} - -void Win32Window::Destroy() -{ - OnDestroy(); - - if (window_handle_) - { - DestroyWindow(window_handle_); - window_handle_ = nullptr; - } - if (g_active_window_count == 0) - { - WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); - } -} - -Win32Window *Win32Window::GetThisFromHandle(HWND const window) noexcept -{ - return reinterpret_cast( - GetWindowLongPtr(window, GWLP_USERDATA)); -} - -void Win32Window::SetChildContent(HWND content) -{ - child_content_ = content; - SetParent(content, window_handle_); - RECT frame = GetClientArea(); - - MoveWindow(content, frame.left, frame.top, frame.right - frame.left, - frame.bottom - frame.top, true); - - SetFocus(child_content_); -} - -RECT Win32Window::GetClientArea() -{ - RECT frame; - GetClientRect(window_handle_, &frame); - return frame; -} - -HWND Win32Window::GetHandle() -{ - return window_handle_; -} - -void Win32Window::SetQuitOnClose(bool quit_on_close) -{ - quit_on_close_ = quit_on_close; -} - -bool Win32Window::OnCreate() -{ - // No-op; provided for subclasses. - return true; -} - -void Win32Window::OnDestroy() -{ - // No-op; provided for subclasses. -} +} \ No newline at end of file diff --git a/frontend/appflowy_flutter/windows/runner/win32_window.h b/frontend/appflowy_flutter/windows/runner/win32_window.h index 4d717b053d..fae0d8a741 100644 --- a/frontend/appflowy_flutter/windows/runner/win32_window.h +++ b/frontend/appflowy_flutter/windows/runner/win32_window.h @@ -10,18 +10,15 @@ // A class abstraction for a high DPI-aware Win32 Window. Intended to be // inherited from by classes that wish to specialize with custom // rendering and input handling -class Win32Window -{ -public: - struct Point - { +class Win32Window { + public: + struct Point { unsigned int x; unsigned int y; Point(unsigned int x, unsigned int y) : x(x), y(y) {} }; - struct Size - { + struct Size { unsigned int width; unsigned int height; Size(unsigned int width, unsigned int height) @@ -31,19 +28,16 @@ public: Win32Window(); virtual ~Win32Window(); - // Creates and shows a win32 window with |title| and position and size using + // Creates a win32 window with |title| that is positioned and sized using // |origin| and |size|. New windows are created on the default monitor. Window // sizes are specified to the OS in physical pixels, hence to ensure a - // consistent size to will treat the width height passed in to this function - // as logical pixels and scale to appropriate for the default monitor. Returns - // true if the window was created successfully. - bool CreateAndShow(const std::wstring &title, - const Point &origin, - const Size &size); + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); - // Dispatches link if any. - // This method enables our app to be with a single instance too. - bool SendAppLinkToInstance(const std::wstring &title); + // Show the current window. Returns true if the window was successfully shown. + bool Show(); // Release OS resources associated with window. void Destroy(); @@ -61,7 +55,11 @@ public: // Return a RECT representing the bounds of the current client area. RECT GetClientArea(); -protected: + // Dispatches link if any. + // This method enables our app to be with a single instance too. + bool SendAppLinkToInstance(const std::wstring &title); + + protected: // Processes and route salient window messages for mouse handling, // size change and DPI. Delegates handling of these to member overloads that // inheriting classes can handle. @@ -77,13 +75,13 @@ protected: // Called when Destroy is called. virtual void OnDestroy(); -private: + private: friend class WindowClassRegistrar; // OS callback called by message pump. Handles the WM_NCCREATE message which // is passed when the non-client area is being created and enables automatic // non-client DPI scaling so that the non-client area automatically - // responsponds to changes in DPI. All other messages are handled by + // responds to changes in DPI. All other messages are handled by // MessageHandler. static LRESULT CALLBACK WndProc(HWND const window, UINT const message, @@ -91,7 +89,10 @@ private: LPARAM const lparam) noexcept; // Retrieves a class instance pointer for |window| - static Win32Window *GetThisFromHandle(HWND const window) noexcept; + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); bool quit_on_close_ = false; @@ -102,4 +103,4 @@ private: HWND child_content_ = nullptr; }; -#endif // RUNNER_WIN32_WINDOW_H_ +#endif // RUNNER_WIN32_WINDOW_H_