/ windows / runner / win32_window.cpp
win32_window.cpp
  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  }