win32_window.cpp 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  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. window_class, title.c_str(),
  113. WS_OVERLAPPEDWINDOW,
  114. Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
  115. Scale(size.width, scale_factor), Scale(size.height, scale_factor),
  116. nullptr, nullptr, GetModuleHandle(nullptr), this);
  117. if (!window) {
  118. return false;
  119. }
  120. UpdateTheme(window);
  121. return OnCreate();
  122. }
  123. bool Win32Window::Show() {
  124. return ShowWindow(window_handle_, SW_SHOWNORMAL);
  125. }
  126. // static
  127. LRESULT CALLBACK Win32Window::WndProc(HWND const window,
  128. UINT const message,
  129. WPARAM const wparam,
  130. LPARAM const lparam) noexcept {
  131. if (message == WM_NCCREATE) {
  132. auto window_struct = reinterpret_cast<CREATESTRUCT*>(lparam);
  133. SetWindowLongPtr(window, GWLP_USERDATA,
  134. reinterpret_cast<LONG_PTR>(window_struct->lpCreateParams));
  135. auto that = static_cast<Win32Window*>(window_struct->lpCreateParams);
  136. EnableFullDpiSupportIfAvailable(window);
  137. that->window_handle_ = window;
  138. } else if (Win32Window* that = GetThisFromHandle(window)) {
  139. return that->MessageHandler(window, message, wparam, lparam);
  140. }
  141. return DefWindowProc(window, message, wparam, lparam);
  142. }
  143. LRESULT
  144. Win32Window::MessageHandler(HWND hwnd,
  145. UINT const message,
  146. WPARAM const wparam,
  147. LPARAM const lparam) noexcept {
  148. switch (message) {
  149. case WM_DESTROY:
  150. window_handle_ = nullptr;
  151. Destroy();
  152. if (quit_on_close_) {
  153. PostQuitMessage(0);
  154. }
  155. return 0;
  156. case WM_DPICHANGED: {
  157. auto newRectSize = reinterpret_cast<RECT*>(lparam);
  158. LONG newWidth = newRectSize->right - newRectSize->left;
  159. LONG newHeight = newRectSize->bottom - newRectSize->top;
  160. SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
  161. newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
  162. return 0;
  163. }
  164. case WM_SIZE: {
  165. RECT rect = GetClientArea();
  166. if (child_content_ != nullptr) {
  167. // Size and position the child window.
  168. MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
  169. rect.bottom - rect.top, TRUE);
  170. }
  171. return 0;
  172. }
  173. case WM_ACTIVATE:
  174. if (child_content_ != nullptr) {
  175. SetFocus(child_content_);
  176. }
  177. return 0;
  178. case WM_DWMCOLORIZATIONCOLORCHANGED:
  179. UpdateTheme(hwnd);
  180. return 0;
  181. }
  182. return DefWindowProc(window_handle_, message, wparam, lparam);
  183. }
  184. void Win32Window::Destroy() {
  185. OnDestroy();
  186. if (window_handle_) {
  187. DestroyWindow(window_handle_);
  188. window_handle_ = nullptr;
  189. }
  190. if (g_active_window_count == 0) {
  191. WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
  192. }
  193. }
  194. Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
  195. return reinterpret_cast<Win32Window*>(
  196. GetWindowLongPtr(window, GWLP_USERDATA));
  197. }
  198. void Win32Window::SetChildContent(HWND content) {
  199. child_content_ = content;
  200. SetParent(content, window_handle_);
  201. RECT frame = GetClientArea();
  202. MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
  203. frame.bottom - frame.top, true);
  204. SetFocus(child_content_);
  205. }
  206. RECT Win32Window::GetClientArea() {
  207. RECT frame;
  208. GetClientRect(window_handle_, &frame);
  209. return frame;
  210. }
  211. HWND Win32Window::GetHandle() {
  212. return window_handle_;
  213. }
  214. void Win32Window::SetQuitOnClose(bool quit_on_close) {
  215. quit_on_close_ = quit_on_close;
  216. }
  217. bool Win32Window::OnCreate() {
  218. // No-op; provided for subclasses.
  219. return true;
  220. }
  221. void Win32Window::OnDestroy() {
  222. // No-op; provided for subclasses.
  223. }
  224. void Win32Window::UpdateTheme(HWND const window) {
  225. DWORD light_mode;
  226. DWORD light_mode_size = sizeof(light_mode);
  227. LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey,
  228. kGetPreferredBrightnessRegValue,
  229. RRF_RT_REG_DWORD, nullptr, &light_mode,
  230. &light_mode_size);
  231. if (result == ERROR_SUCCESS) {
  232. BOOL enable_dark_mode = light_mode == 0;
  233. DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE,
  234. &enable_dark_mode, sizeof(enable_dark_mode));
  235. }
  236. }