win32_window.cpp (8534B)
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 }