win32_window.cpp 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. #include "win32_window.h"
  2. #include <dwmapi.h>
  3. #include <flutter_windows.h>
  4. #include "resource.h"
  5. namespace {
  6. /// Window attribute that enables dark mode window decorations.
  7. ///
  8. /// Redefined in case the developer's machine has a Windows SDK older than
  9. /// version 10.0.22000.0.
  10. /// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
  11. #ifndef DWMWA_USE_IMMERSIVE_DARK_MODE
  12. #define DWMWA_USE_IMMERSIVE_DARK_MODE 20
  13. #endif
  14. constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
  15. /// Registry key for app theme preference.
  16. ///
  17. /// A value of 0 indicates apps should use dark mode. A non-zero or missing
  18. /// value indicates apps should use light mode.
  19. constexpr const wchar_t kGetPreferredBrightnessRegKey[] =
  20. L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
  21. constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme";
  22. // The number of Win32Window objects that currently exist.
  23. static int g_active_window_count = 0;
  24. using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
  25. // Scale helper to convert logical scaler values to physical using passed in
  26. // scale factor
  27. int Scale(int source, double scale_factor) {
  28. return static_cast<int>(source * scale_factor);
  29. }
  30. // Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
  31. // This API is only needed for PerMonitor V1 awareness mode.
  32. void EnableFullDpiSupportIfAvailable(HWND hwnd) {
  33. HMODULE user32_module = LoadLibraryA("User32.dll");
  34. if (!user32_module) {
  35. return;
  36. }
  37. auto enable_non_client_dpi_scaling =
  38. reinterpret_cast<EnableNonClientDpiScaling*>(
  39. GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
  40. if (enable_non_client_dpi_scaling != nullptr) {
  41. enable_non_client_dpi_scaling(hwnd);
  42. }
  43. FreeLibrary(user32_module);
  44. }
  45. } // namespace
  46. // Manages the Win32Window's window class registration.
  47. class WindowClassRegistrar {
  48. public:
  49. ~WindowClassRegistrar() = default;
  50. // Returns the singleton registrar instance.
  51. static WindowClassRegistrar* GetInstance() {
  52. if (!instance_) {
  53. instance_ = new WindowClassRegistrar();
  54. }
  55. return instance_;
  56. }
  57. // Returns the name of the window class, registering the class if it hasn't
  58. // previously been registered.
  59. const wchar_t* GetWindowClass();
  60. // Unregisters the window class. Should only be called if there are no
  61. // instances of the window.
  62. void UnregisterWindowClass();
  63. private:
  64. WindowClassRegistrar() = default;
  65. static WindowClassRegistrar* instance_;
  66. bool class_registered_ = false;
  67. };
  68. WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
  69. const wchar_t* WindowClassRegistrar::GetWindowClass() {
  70. if (!class_registered_) {
  71. WNDCLASS window_class{};
  72. window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
  73. window_class.lpszClassName = kWindowClassName;
  74. window_class.style = CS_HREDRAW | CS_VREDRAW;
  75. window_class.cbClsExtra = 0;
  76. window_class.cbWndExtra = 0;
  77. window_class.hInstance = GetModuleHandle(nullptr);
  78. window_class.hIcon =
  79. LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON));
  80. window_class.hbrBackground = 0;
  81. window_class.lpszMenuName = nullptr;
  82. window_class.lpfnWndProc = Win32Window::WndProc;
  83. RegisterClass(&window_class);
  84. class_registered_ = true;
  85. }
  86. return kWindowClassName;
  87. }
  88. void WindowClassRegistrar::UnregisterWindowClass() {
  89. UnregisterClass(kWindowClassName, nullptr);
  90. class_registered_ = false;
  91. }
  92. Win32Window::Win32Window() {
  93. ++g_active_window_count;
  94. }
  95. Win32Window::~Win32Window() {
  96. --g_active_window_count;
  97. Destroy();
  98. }
  99. bool Win32Window::Create(const std::wstring& title,
  100. const Point& origin,
  101. const Size& size) {
  102. Destroy();
  103. const wchar_t* window_class =
  104. WindowClassRegistrar::GetInstance()->GetWindowClass();
  105. const POINT target_point = {static_cast<LONG>(origin.x),
  106. static_cast<LONG>(origin.y)};
  107. HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
  108. UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
  109. double scale_factor = dpi / 96.0;
  110. HWND window = CreateWindow(
  111. window_class, title.c_str(), WS_OVERLAPPEDWINDOW,
  112. Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
  113. Scale(size.width, scale_factor), Scale(size.height, scale_factor),
  114. nullptr, nullptr, GetModuleHandle(nullptr), this);
  115. if (!window) {
  116. return false;
  117. }
  118. UpdateTheme(window);
  119. return OnCreate();
  120. }
  121. bool Win32Window::Show() {
  122. return ShowWindow(window_handle_, SW_SHOWNORMAL);
  123. }
  124. // static
  125. LRESULT CALLBACK Win32Window::WndProc(HWND const window,
  126. UINT const message,
  127. WPARAM const wparam,
  128. LPARAM const lparam) noexcept {
  129. if (message == WM_NCCREATE) {
  130. auto window_struct = reinterpret_cast<CREATESTRUCT*>(lparam);
  131. SetWindowLongPtr(window, GWLP_USERDATA,
  132. reinterpret_cast<LONG_PTR>(window_struct->lpCreateParams));
  133. auto that = static_cast<Win32Window*>(window_struct->lpCreateParams);
  134. EnableFullDpiSupportIfAvailable(window);
  135. that->window_handle_ = window;
  136. } else if (Win32Window* that = GetThisFromHandle(window)) {
  137. return that->MessageHandler(window, message, wparam, lparam);
  138. }
  139. return DefWindowProc(window, message, wparam, lparam);
  140. }
  141. LRESULT
  142. Win32Window::MessageHandler(HWND hwnd,
  143. UINT const message,
  144. WPARAM const wparam,
  145. LPARAM const lparam) noexcept {
  146. switch (message) {
  147. case WM_DESTROY:
  148. window_handle_ = nullptr;
  149. Destroy();
  150. if (quit_on_close_) {
  151. PostQuitMessage(0);
  152. }
  153. return 0;
  154. case WM_DPICHANGED: {
  155. auto newRectSize = reinterpret_cast<RECT*>(lparam);
  156. LONG newWidth = newRectSize->right - newRectSize->left;
  157. LONG newHeight = newRectSize->bottom - newRectSize->top;
  158. SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
  159. newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
  160. return 0;
  161. }
  162. case WM_SIZE: {
  163. RECT rect = GetClientArea();
  164. if (child_content_ != nullptr) {
  165. // Size and position the child window.
  166. MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
  167. rect.bottom - rect.top, TRUE);
  168. }
  169. return 0;
  170. }
  171. case WM_ACTIVATE:
  172. if (child_content_ != nullptr) {
  173. SetFocus(child_content_);
  174. }
  175. return 0;
  176. case WM_DWMCOLORIZATIONCOLORCHANGED:
  177. UpdateTheme(hwnd);
  178. return 0;
  179. }
  180. return DefWindowProc(window_handle_, message, wparam, lparam);
  181. }
  182. void Win32Window::Destroy() {
  183. OnDestroy();
  184. if (window_handle_) {
  185. DestroyWindow(window_handle_);
  186. window_handle_ = nullptr;
  187. }
  188. if (g_active_window_count == 0) {
  189. WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
  190. }
  191. }
  192. Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
  193. return reinterpret_cast<Win32Window*>(
  194. GetWindowLongPtr(window, GWLP_USERDATA));
  195. }
  196. void Win32Window::SetChildContent(HWND content) {
  197. child_content_ = content;
  198. SetParent(content, window_handle_);
  199. RECT frame = GetClientArea();
  200. MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
  201. frame.bottom - frame.top, true);
  202. SetFocus(child_content_);
  203. }
  204. RECT Win32Window::GetClientArea() {
  205. RECT frame;
  206. GetClientRect(window_handle_, &frame);
  207. return frame;
  208. }
  209. HWND Win32Window::GetHandle() {
  210. return window_handle_;
  211. }
  212. void Win32Window::SetQuitOnClose(bool quit_on_close) {
  213. quit_on_close_ = quit_on_close;
  214. }
  215. bool Win32Window::OnCreate() {
  216. // No-op; provided for subclasses.
  217. return true;
  218. }
  219. void Win32Window::OnDestroy() {
  220. // No-op; provided for subclasses.
  221. }
  222. void Win32Window::UpdateTheme(HWND const window) {
  223. DWORD light_mode;
  224. DWORD light_mode_size = sizeof(light_mode);
  225. LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey,
  226. kGetPreferredBrightnessRegValue,
  227. RRF_RT_REG_DWORD, nullptr, &light_mode,
  228. &light_mode_size);
  229. if (result == ERROR_SUCCESS) {
  230. BOOL enable_dark_mode = light_mode == 0;
  231. DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE,
  232. &enable_dark_mode, sizeof(enable_dark_mode));
  233. }
  234. }