dev
cpp 288 lines 8.33 KB
Raw
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 }