Main coves client
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 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
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}