at main 8.5 kB view raw
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}