/ app / windows / runner / win32_window.cpp
win32_window.cpp
  1  #include "win32_window.h"
  2  
  3  #include <dwmapi.h>
  4  #include <flutter_windows.h>
  5  
  6  #include "resource.h"
  7  
  8  namespace {
  9  
 10  /// Window attribute that enables dark mode window decorations.
 11  ///
 12  /// Redefined in case the developer's machine has a Windows SDK older than
 13  /// version 10.0.22000.0.
 14  /// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
 15  #ifndef DWMWA_USE_IMMERSIVE_DARK_MODE
 16  #define DWMWA_USE_IMMERSIVE_DARK_MODE 20
 17  #endif
 18  
 19  constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
 20  
 21  /// Registry key for app theme preference.
 22  ///
 23  /// A value of 0 indicates apps should use dark mode. A non-zero or missing
 24  /// value indicates apps should use light mode.
 25  constexpr const wchar_t kGetPreferredBrightnessRegKey[] =
 26    L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
 27  constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme";
 28  
 29  // The number of Win32Window objects that currently exist.
 30  static int g_active_window_count = 0;
 31  
 32  using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
 33  
 34  // Scale helper to convert logical scaler values to physical using passed in
 35  // scale factor
 36  int Scale(int source, double scale_factor) {
 37    return static_cast<int>(source * scale_factor);
 38  }
 39  
 40  // Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
 41  // This API is only needed for PerMonitor V1 awareness mode.
 42  void EnableFullDpiSupportIfAvailable(HWND hwnd) {
 43    HMODULE user32_module = LoadLibraryA("User32.dll");
 44    if (!user32_module) {
 45      return;
 46    }
 47    auto enable_non_client_dpi_scaling =
 48        reinterpret_cast<EnableNonClientDpiScaling*>(
 49            GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
 50    if (enable_non_client_dpi_scaling != nullptr) {
 51      enable_non_client_dpi_scaling(hwnd);
 52    }
 53    FreeLibrary(user32_module);
 54  }
 55  
 56  }  // namespace
 57  
 58  // Manages the Win32Window's window class registration.
 59  class WindowClassRegistrar {
 60   public:
 61    ~WindowClassRegistrar() = default;
 62  
 63    // Returns the singleton registrar instance.
 64    static WindowClassRegistrar* GetInstance() {
 65      if (!instance_) {
 66        instance_ = new WindowClassRegistrar();
 67      }
 68      return instance_;
 69    }
 70  
 71    // Returns the name of the window class, registering the class if it hasn't
 72    // previously been registered.
 73    const wchar_t* GetWindowClass();
 74  
 75    // Unregisters the window class. Should only be called if there are no
 76    // instances of the window.
 77    void UnregisterWindowClass();
 78  
 79   private:
 80    WindowClassRegistrar() = default;
 81  
 82    static WindowClassRegistrar* instance_;
 83  
 84    bool class_registered_ = false;
 85  };
 86  
 87  WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
 88  
 89  const wchar_t* WindowClassRegistrar::GetWindowClass() {
 90    if (!class_registered_) {
 91      WNDCLASS window_class{};
 92      window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
 93      window_class.lpszClassName = kWindowClassName;
 94      window_class.style = CS_HREDRAW | CS_VREDRAW;
 95      window_class.cbClsExtra = 0;
 96      window_class.cbWndExtra = 0;
 97      window_class.hInstance = GetModuleHandle(nullptr);
 98      window_class.hIcon =
 99          LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON));
100      window_class.hbrBackground = 0;
101      window_class.lpszMenuName = nullptr;
102      window_class.lpfnWndProc = Win32Window::WndProc;
103      RegisterClass(&window_class);
104      class_registered_ = true;
105    }
106    return kWindowClassName;
107  }
108  
109  void WindowClassRegistrar::UnregisterWindowClass() {
110    UnregisterClass(kWindowClassName, nullptr);
111    class_registered_ = false;
112  }
113  
114  Win32Window::Win32Window() {
115    ++g_active_window_count;
116  }
117  
118  Win32Window::~Win32Window() {
119    --g_active_window_count;
120    Destroy();
121  }
122  
123  bool Win32Window::Create(const std::wstring& title,
124                           const Point& origin,
125                           const Size& size) {
126    Destroy();
127  
128    const wchar_t* window_class =
129        WindowClassRegistrar::GetInstance()->GetWindowClass();
130  
131    const POINT target_point = {static_cast<LONG>(origin.x),
132                                static_cast<LONG>(origin.y)};
133    HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
134    UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
135    double scale_factor = dpi / 96.0;
136  
137    HWND window = CreateWindow(
138        window_class, title.c_str(), WS_OVERLAPPEDWINDOW,
139        Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
140        Scale(size.width, scale_factor), Scale(size.height, scale_factor),
141        nullptr, nullptr, GetModuleHandle(nullptr), this);
142  
143    if (!window) {
144      return false;
145    }
146  
147    UpdateTheme(window);
148  
149    return OnCreate();
150  }
151  
152  bool Win32Window::Show() {
153    return ShowWindow(window_handle_, SW_SHOWNORMAL);
154  }
155  
156  // static
157  LRESULT CALLBACK Win32Window::WndProc(HWND const window,
158                                        UINT const message,
159                                        WPARAM const wparam,
160                                        LPARAM const lparam) noexcept {
161    if (message == WM_NCCREATE) {
162      auto window_struct = reinterpret_cast<CREATESTRUCT*>(lparam);
163      SetWindowLongPtr(window, GWLP_USERDATA,
164                       reinterpret_cast<LONG_PTR>(window_struct->lpCreateParams));
165  
166      auto that = static_cast<Win32Window*>(window_struct->lpCreateParams);
167      EnableFullDpiSupportIfAvailable(window);
168      that->window_handle_ = window;
169    } else if (Win32Window* that = GetThisFromHandle(window)) {
170      return that->MessageHandler(window, message, wparam, lparam);
171    }
172  
173    return DefWindowProc(window, message, wparam, lparam);
174  }
175  
176  LRESULT
177  Win32Window::MessageHandler(HWND hwnd,
178                              UINT const message,
179                              WPARAM const wparam,
180                              LPARAM const lparam) noexcept {
181    switch (message) {
182      case WM_DESTROY:
183        window_handle_ = nullptr;
184        Destroy();
185        if (quit_on_close_) {
186          PostQuitMessage(0);
187        }
188        return 0;
189  
190      case WM_DPICHANGED: {
191        auto newRectSize = reinterpret_cast<RECT*>(lparam);
192        LONG newWidth = newRectSize->right - newRectSize->left;
193        LONG newHeight = newRectSize->bottom - newRectSize->top;
194  
195        SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
196                     newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
197  
198        return 0;
199      }
200      case WM_SIZE: {
201        RECT rect = GetClientArea();
202        if (child_content_ != nullptr) {
203          // Size and position the child window.
204          MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
205                     rect.bottom - rect.top, TRUE);
206        }
207        return 0;
208      }
209  
210      case WM_ACTIVATE:
211        if (child_content_ != nullptr) {
212          SetFocus(child_content_);
213        }
214        return 0;
215  
216      case WM_DWMCOLORIZATIONCOLORCHANGED:
217        UpdateTheme(hwnd);
218        return 0;
219    }
220  
221    return DefWindowProc(window_handle_, message, wparam, lparam);
222  }
223  
224  void Win32Window::Destroy() {
225    OnDestroy();
226  
227    if (window_handle_) {
228      DestroyWindow(window_handle_);
229      window_handle_ = nullptr;
230    }
231    if (g_active_window_count == 0) {
232      WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
233    }
234  }
235  
236  Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
237    return reinterpret_cast<Win32Window*>(
238        GetWindowLongPtr(window, GWLP_USERDATA));
239  }
240  
241  void Win32Window::SetChildContent(HWND content) {
242    child_content_ = content;
243    SetParent(content, window_handle_);
244    RECT frame = GetClientArea();
245  
246    MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
247               frame.bottom - frame.top, true);
248  
249    SetFocus(child_content_);
250  }
251  
252  RECT Win32Window::GetClientArea() {
253    RECT frame;
254    GetClientRect(window_handle_, &frame);
255    return frame;
256  }
257  
258  HWND Win32Window::GetHandle() {
259    return window_handle_;
260  }
261  
262  void Win32Window::SetQuitOnClose(bool quit_on_close) {
263    quit_on_close_ = quit_on_close;
264  }
265  
266  bool Win32Window::OnCreate() {
267    // No-op; provided for subclasses.
268    return true;
269  }
270  
271  void Win32Window::OnDestroy() {
272    // No-op; provided for subclasses.
273  }
274  
275  void Win32Window::UpdateTheme(HWND const window) {
276    DWORD light_mode;
277    DWORD light_mode_size = sizeof(light_mode);
278    LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey,
279                                 kGetPreferredBrightnessRegValue,
280                                 RRF_RT_REG_DWORD, nullptr, &light_mode,
281                                 &light_mode_size);
282  
283    if (result == ERROR_SUCCESS) {
284      BOOL enable_dark_mode = light_mode == 0;
285      DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE,
286                            &enable_dark_mode, sizeof(enable_dark_mode));
287    }
288  }