main
  1#include "win32_window.h"
  2
  3#include <dwmapi.h>
  4#include <flutter_windows.h>
  5
  6#include "resource.h"
  7
  8namespace {
  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
 19constexpr 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.
 25constexpr const wchar_t kGetPreferredBrightnessRegKey[] =
 26  L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
 27constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme";
 28
 29// The number of Win32Window objects that currently exist.
 30static int g_active_window_count = 0;
 31
 32using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
 33
 34// Scale helper to convert logical scaler values to physical using passed in
 35// scale factor
 36int 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.
 42void 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.
 59class WindowClassRegistrar {
 60 public:
 61  ~WindowClassRegistrar() = default;
 62
 63  // Returns the singleton registar 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
 87WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
 88
 89const 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
109void WindowClassRegistrar::UnregisterWindowClass() {
110  UnregisterClass(kWindowClassName, nullptr);
111  class_registered_ = false;
112}
113
114Win32Window::Win32Window() {
115  ++g_active_window_count;
116}
117
118Win32Window::~Win32Window() {
119  --g_active_window_count;
120  Destroy();
121}
122
123bool 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
152bool Win32Window::Show() {
153  return ShowWindow(window_handle_, SW_SHOWNORMAL);
154}
155
156// static
157LRESULT 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
176LRESULT
177Win32Window::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
224void 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
236Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
237  return reinterpret_cast<Win32Window*>(
238      GetWindowLongPtr(window, GWLP_USERDATA));
239}
240
241void 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
252RECT Win32Window::GetClientArea() {
253  RECT frame;
254  GetClientRect(window_handle_, &frame);
255  return frame;
256}
257
258HWND Win32Window::GetHandle() {
259  return window_handle_;
260}
261
262void Win32Window::SetQuitOnClose(bool quit_on_close) {
263  quit_on_close_ = quit_on_close;
264}
265
266bool Win32Window::OnCreate() {
267  // No-op; provided for subclasses.
268  return true;
269}
270
271void Win32Window::OnDestroy() {
272  // No-op; provided for subclasses.
273}
274
275void 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}