Flutter Windows Embedder
host_window.cc
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
8 
9 #include <dwmapi.h>
10 
18 
19 namespace {
20 
21 constexpr wchar_t kWindowClassName[] = L"FLUTTER_HOST_WINDOW";
22 
23 // Clamps |size| to the size of the virtual screen. Both the parameter and
24 // return size are in physical coordinates.
25 flutter::Size ClampToVirtualScreen(flutter::Size size) {
26  double const virtual_screen_width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
27  double const virtual_screen_height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
28 
29  return flutter::Size(std::clamp(size.width(), 0.0, virtual_screen_width),
30  std::clamp(size.height(), 0.0, virtual_screen_height));
31 }
32 
33 void EnableTransparentWindowBackground(HWND hwnd,
34  flutter::WindowsProcTable const& win32) {
35  enum ACCENT_STATE { ACCENT_DISABLED = 0 };
36 
37  struct ACCENT_POLICY {
38  ACCENT_STATE AccentState;
39  DWORD AccentFlags;
40  DWORD GradientColor;
41  DWORD AnimationId;
42  };
43 
44  // Set the accent policy to disable window composition.
45  ACCENT_POLICY accent = {ACCENT_DISABLED, 2, static_cast<DWORD>(0), 0};
47  .Attrib =
48  flutter::WindowsProcTable::WINDOWCOMPOSITIONATTRIB::WCA_ACCENT_POLICY,
49  .pvData = &accent,
50  .cbData = sizeof(accent)};
51  win32.SetWindowCompositionAttribute(hwnd, &data);
52 
53  // Extend the frame into the client area and set the window's system
54  // backdrop type for visual effects.
55  MARGINS const margins = {-1};
56  win32.DwmExtendFrameIntoClientArea(hwnd, &margins);
57  INT effect_value = 1;
58  win32.DwmSetWindowAttribute(hwnd, DWMWA_SYSTEMBACKDROP_TYPE, &effect_value,
59  sizeof(BOOL));
60 }
61 
62 // Retrieves the calling thread's last-error code message as a string,
63 // or a fallback message if the error message cannot be formatted.
64 std::string GetLastErrorAsString() {
65  LPWSTR message_buffer = nullptr;
66 
67  if (DWORD const size = FormatMessage(
68  FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
69  FORMAT_MESSAGE_IGNORE_INSERTS,
70  nullptr, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
71  reinterpret_cast<LPTSTR>(&message_buffer), 0, nullptr)) {
72  std::wstring const wide_message(message_buffer, size);
73  LocalFree(message_buffer);
74  message_buffer = nullptr;
75 
76  if (int const buffer_size =
77  WideCharToMultiByte(CP_UTF8, 0, wide_message.c_str(), -1, nullptr,
78  0, nullptr, nullptr)) {
79  std::string message(buffer_size, 0);
80  WideCharToMultiByte(CP_UTF8, 0, wide_message.c_str(), -1, &message[0],
81  buffer_size, nullptr, nullptr);
82  return message;
83  }
84  }
85 
86  if (message_buffer) {
87  LocalFree(message_buffer);
88  }
89  std::ostringstream oss;
90  oss << "Format message failed with 0x" << std::hex << std::setfill('0')
91  << std::setw(8) << GetLastError();
92  return oss.str();
93 }
94 
95 // Checks whether the window class of name |class_name| is registered for the
96 // current application.
97 bool IsClassRegistered(LPCWSTR class_name) {
98  WNDCLASSEX window_class = {};
99  return GetClassInfoEx(GetModuleHandle(nullptr), class_name, &window_class) !=
100  0;
101 }
102 
103 // Window attribute that enables dark mode window decorations.
104 //
105 // Redefined in case the developer's machine has a Windows SDK older than
106 // version 10.0.22000.0.
107 // See:
108 // https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
109 #ifndef DWMWA_USE_IMMERSIVE_DARK_MODE
110 #define DWMWA_USE_IMMERSIVE_DARK_MODE 20
111 #endif
112 
113 // Updates the window frame's theme to match the system theme.
114 void UpdateTheme(HWND window) {
115  // Registry key for app theme preference.
116  const wchar_t kGetPreferredBrightnessRegKey[] =
117  L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
118  const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme";
119 
120  // A value of 0 indicates apps should use dark mode. A non-zero or missing
121  // value indicates apps should use light mode.
122  DWORD light_mode;
123  DWORD light_mode_size = sizeof(light_mode);
124  LSTATUS const result =
125  RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey,
126  kGetPreferredBrightnessRegValue, RRF_RT_REG_DWORD, nullptr,
127  &light_mode, &light_mode_size);
128 
129  if (result == ERROR_SUCCESS) {
130  BOOL enable_dark_mode = light_mode == 0;
131  DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE,
132  &enable_dark_mode, sizeof(enable_dark_mode));
133  }
134 }
135 
136 // Inserts |content| into the window tree.
137 void SetChildContent(HWND content, HWND window) {
138  SetParent(content, window);
139  RECT client_rect;
140  GetClientRect(window, &client_rect);
141  MoveWindow(content, client_rect.left, client_rect.top,
142  client_rect.right - client_rect.left,
143  client_rect.bottom - client_rect.top, true);
144 }
145 
146 // Adjusts a 1D segment (defined by origin and size) to fit entirely within
147 // a destination segment. If the segment is larger than the destination, it is
148 // first shrunk to fit. Then, it's shifted to be within the bounds.
149 //
150 // Let the destination be "{...}" and the segment to adjust be "[...]".
151 //
152 // Case 1: The segment sticks out to the right.
153 //
154 // Before: {------[----}------]
155 // After: {------[----]}
156 //
157 // Case 2: The segment sticks out to the left.
158 //
159 // Before: [------{----]------}
160 // After: {[----]------}
161 void AdjustAlongAxis(LONG dst_origin, LONG dst_size, LONG* origin, LONG* size) {
162  *size = std::min(dst_size, *size);
163  if (*origin < dst_origin)
164  *origin = dst_origin;
165  else
166  *origin = std::min(dst_origin + dst_size, *origin + *size) - *size;
167 }
168 
169 RECT AdjustToFit(const RECT& parent, const RECT& child) {
170  auto new_x = child.left;
171  auto new_y = child.top;
172  auto new_width = flutter::RectWidth(child);
173  auto new_height = flutter::RectHeight(child);
174  AdjustAlongAxis(parent.left, flutter::RectWidth(parent), &new_x, &new_width);
175  AdjustAlongAxis(parent.top, flutter::RectHeight(parent), &new_y, &new_height);
176  RECT result;
177  result.left = new_x;
178  result.right = new_x + new_width;
179  result.top = new_y;
180  result.bottom = new_y + new_height;
181  return result;
182 }
183 
184 flutter::BoxConstraints FromWindowConstraints(
185  const flutter::WindowConstraints& preferred_constraints) {
186  std::optional<flutter::Size> smallest, biggest;
187  if (preferred_constraints.has_view_constraints) {
188  smallest = flutter::Size(preferred_constraints.view_min_width,
189  preferred_constraints.view_min_height);
190  if (preferred_constraints.view_max_width > 0 &&
191  preferred_constraints.view_max_height > 0) {
192  biggest = flutter::Size(preferred_constraints.view_max_width,
193  preferred_constraints.view_max_height);
194  }
195  }
196 
197  return flutter::BoxConstraints(smallest, biggest);
198 }
199 
200 } // namespace
201 
202 namespace flutter {
203 
204 std::unique_ptr<HostWindow> HostWindow::CreateRegularWindow(
205  WindowManager* window_manager,
206  FlutterWindowsEngine* engine,
207  const WindowSizeRequest& preferred_size,
208  const WindowConstraints& preferred_constraints,
209  LPCWSTR title) {
210  return std::unique_ptr<HostWindow>(new HostWindowRegular(
211  window_manager, engine, preferred_size,
212  FromWindowConstraints(preferred_constraints), title));
213 }
214 
215 std::unique_ptr<HostWindow> HostWindow::CreateDialogWindow(
216  WindowManager* window_manager,
217  FlutterWindowsEngine* engine,
218  const WindowSizeRequest& preferred_size,
219  const WindowConstraints& preferred_constraints,
220  LPCWSTR title,
221  HWND parent) {
222  return std::unique_ptr<HostWindow>(
223  new HostWindowDialog(window_manager, engine, preferred_size,
224  FromWindowConstraints(preferred_constraints), title,
225  parent ? parent : std::optional<HWND>()));
226 }
227 
229  FlutterWindowsEngine* engine,
230  WindowArchetype archetype,
231  DWORD window_style,
232  DWORD extended_window_style,
233  const BoxConstraints& box_constraints,
234  Rect const initial_window_rect,
235  LPCWSTR title,
236  std::optional<HWND> const& owner_window)
237  : window_manager_(window_manager),
238  engine_(engine),
239  archetype_(archetype),
240  box_constraints_(box_constraints) {
241  // Set up the view.
242  auto view_window = std::make_unique<FlutterWindow>(
243  initial_window_rect.width(), initial_window_rect.height(),
244  engine->display_manager(), engine->windows_proc_table());
245 
246  std::unique_ptr<FlutterWindowsView> view =
247  engine->CreateView(std::move(view_window));
248  FML_CHECK(view != nullptr);
249 
251  std::make_unique<FlutterWindowsViewController>(nullptr, std::move(view));
252  FML_CHECK(engine->running());
253  // The Windows embedder listens to accessibility updates using the
254  // view's HWND. The embedder's accessibility features may be stale if
255  // the app was in headless mode.
256  engine->UpdateAccessibilityFeatures();
257 
258  // Register the window class.
259  if (!IsClassRegistered(kWindowClassName)) {
260  auto const idi_app_icon = 101;
261  WNDCLASSEX window_class = {};
262  window_class.cbSize = sizeof(WNDCLASSEX);
263  window_class.style = CS_HREDRAW | CS_VREDRAW;
264  window_class.lpfnWndProc = HostWindow::WndProc;
265  window_class.hInstance = GetModuleHandle(nullptr);
266  window_class.hIcon =
267  LoadIcon(window_class.hInstance, MAKEINTRESOURCE(idi_app_icon));
268  if (!window_class.hIcon) {
269  window_class.hIcon = LoadIcon(nullptr, IDI_APPLICATION);
270  }
271  window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
272  window_class.lpszClassName = kWindowClassName;
273 
274  FML_CHECK(RegisterClassEx(&window_class));
275  }
276 
277  // Create the native window.
278  window_handle_ = CreateWindowEx(
279  extended_window_style, kWindowClassName, title, window_style,
280  initial_window_rect.left(), initial_window_rect.top(),
281  initial_window_rect.width(), initial_window_rect.height(),
282  owner_window ? *owner_window : nullptr, nullptr, GetModuleHandle(nullptr),
283  engine->windows_proc_table().get());
284  FML_CHECK(window_handle_ != nullptr);
285 
286  // Adjust the window position so its origin aligns with the top-left corner
287  // of the window frame, not the window rectangle (which includes the
288  // drop-shadow). This adjustment must be done post-creation since the frame
289  // rectangle is only available after the window has been created.
290  RECT frame_rect;
291  DwmGetWindowAttribute(window_handle_, DWMWA_EXTENDED_FRAME_BOUNDS,
292  &frame_rect, sizeof(frame_rect));
293  RECT window_rect;
294  GetWindowRect(window_handle_, &window_rect);
295  LONG const left_dropshadow_width = frame_rect.left - window_rect.left;
296  LONG const top_dropshadow_height = window_rect.top - frame_rect.top;
297  SetWindowPos(window_handle_, nullptr,
298  window_rect.left - left_dropshadow_width,
299  window_rect.top - top_dropshadow_height, 0, 0,
300  SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
301 
302  UpdateTheme(window_handle_);
303 
304  SetChildContent(view_controller_->view()->GetWindowHandle(), window_handle_);
305 
306  // TODO(loicsharma): Hide the window until the first frame is rendered.
307  // Single window apps use the engine's next frame callback to show the
308  // window. This doesn't work for multi window apps as the engine cannot have
309  // multiple next frame callbacks. If multiple windows are created, only the
310  // last one will be shown.
311  ShowWindow(window_handle_, SW_SHOWNORMAL);
312  SetWindowLongPtr(window_handle_, GWLP_USERDATA,
313  reinterpret_cast<LONG_PTR>(this));
314 }
315 
317  if (view_controller_) {
318  // Unregister the window class. Fail silently if other windows are still
319  // using the class, as only the last window can successfully unregister it.
320  if (!UnregisterClass(kWindowClassName, GetModuleHandle(nullptr))) {
321  // Clear the error state after the failed unregistration.
322  SetLastError(ERROR_SUCCESS);
323  }
324  }
325 }
326 
328  wchar_t class_name[256];
329  if (!GetClassName(hwnd, class_name, sizeof(class_name) / sizeof(wchar_t))) {
330  FML_LOG(ERROR) << "Failed to get class name for window handle " << hwnd
331  << ": " << GetLastErrorAsString();
332  return nullptr;
333  }
334  // Ignore window handles that do not match the expected class name.
335  if (wcscmp(class_name, kWindowClassName) != 0) {
336  return nullptr;
337  }
338 
339  return reinterpret_cast<HostWindow*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
340 }
341 
343  return window_handle_;
344 }
345 
347  auto child_content = window->view_controller_->view()->GetWindowHandle();
348  if (window != nullptr && child_content != nullptr) {
349  SetFocus(child_content);
350  }
351 };
352 
353 LRESULT HostWindow::WndProc(HWND hwnd,
354  UINT message,
355  WPARAM wparam,
356  LPARAM lparam) {
357  if (message == WM_NCCREATE) {
358  auto* const create_struct = reinterpret_cast<CREATESTRUCT*>(lparam);
359  auto* const windows_proc_table =
360  static_cast<WindowsProcTable*>(create_struct->lpCreateParams);
361  windows_proc_table->EnableNonClientDpiScaling(hwnd);
362  EnableTransparentWindowBackground(hwnd, *windows_proc_table);
363  } else if (HostWindow* const window = GetThisFromHandle(hwnd)) {
364  return window->HandleMessage(hwnd, message, wparam, lparam);
365  }
366 
367  return DefWindowProc(hwnd, message, wparam, lparam);
368 }
369 
370 LRESULT HostWindow::HandleMessage(HWND hwnd,
371  UINT message,
372  WPARAM wparam,
373  LPARAM lparam) {
375  window_handle_, message, wparam, lparam);
376  if (result) {
377  return *result;
378  }
379 
380  switch (message) {
381  case WM_DESTROY:
382  is_being_destroyed_ = true;
383  break;
384 
385  case WM_NCLBUTTONDOWN: {
386  // Fix for 500ms hang after user clicks on the title bar, but before
387  // moving mouse. Reference:
388  // https://gamedev.net/forums/topic/672094-keeping-things-moving-during-win32-moveresize-events/5254386/
389  if (SendMessage(window_handle_, WM_NCHITTEST, wparam, lparam) ==
390  HTCAPTION) {
391  POINT cursorPos;
392  // Get the current cursor position and synthesize WM_MOUSEMOVE to
393  // unblock default window proc implementation for WM_NCLBUTTONDOWN at
394  // HTCAPTION.
395  GetCursorPos(&cursorPos);
396  ScreenToClient(window_handle_, &cursorPos);
397  PostMessage(window_handle_, WM_MOUSEMOVE, 0,
398  MAKELPARAM(cursorPos.x, cursorPos.y));
399  }
400  break;
401  }
402 
403  case WM_DPICHANGED: {
404  auto* const new_scaled_window_rect = reinterpret_cast<RECT*>(lparam);
405  LONG const width =
406  new_scaled_window_rect->right - new_scaled_window_rect->left;
407  LONG const height =
408  new_scaled_window_rect->bottom - new_scaled_window_rect->top;
409  SetWindowPos(hwnd, nullptr, new_scaled_window_rect->left,
410  new_scaled_window_rect->top, width, height,
411  SWP_NOZORDER | SWP_NOACTIVATE);
412  return 0;
413  }
414 
415  case WM_GETMINMAXINFO: {
416  RECT window_rect;
417  GetWindowRect(hwnd, &window_rect);
418  RECT client_rect;
419  GetClientRect(hwnd, &client_rect);
420  LONG const non_client_width = (window_rect.right - window_rect.left) -
421  (client_rect.right - client_rect.left);
422  LONG const non_client_height = (window_rect.bottom - window_rect.top) -
423  (client_rect.bottom - client_rect.top);
424 
425  UINT const dpi = flutter::GetDpiForHWND(hwnd);
426  double const scale_factor =
427  static_cast<double>(dpi) / USER_DEFAULT_SCREEN_DPI;
428 
429  MINMAXINFO* info = reinterpret_cast<MINMAXINFO*>(lparam);
430  Size const min_physical_size = ClampToVirtualScreen(Size(
431  box_constraints_.smallest().width() * scale_factor + non_client_width,
432  box_constraints_.smallest().height() * scale_factor +
433  non_client_height));
434 
435  info->ptMinTrackSize.x = min_physical_size.width();
436  info->ptMinTrackSize.y = min_physical_size.height();
437  Size const max_physical_size = ClampToVirtualScreen(Size(
438  box_constraints_.biggest().width() * scale_factor + non_client_width,
439  box_constraints_.biggest().height() * scale_factor +
440  non_client_height));
441 
442  info->ptMaxTrackSize.x = max_physical_size.width();
443  info->ptMaxTrackSize.y = max_physical_size.height();
444  return 0;
445  }
446 
447  case WM_SIZE: {
448  auto child_content = view_controller_->view()->GetWindowHandle();
449  if (child_content != nullptr) {
450  // Resize and reposition the child content window.
451  RECT client_rect;
452  GetClientRect(hwnd, &client_rect);
453  MoveWindow(child_content, client_rect.left, client_rect.top,
454  client_rect.right - client_rect.left,
455  client_rect.bottom - client_rect.top, TRUE);
456  }
457  return 0;
458  }
459 
460  case WM_ACTIVATE:
461  FocusRootViewOf(this);
462  return 0;
463 
464  case WM_DWMCOLORIZATIONCOLORCHANGED:
465  UpdateTheme(hwnd);
466  return 0;
467 
468  default:
469  break;
470  }
471 
472  if (!view_controller_) {
473  return 0;
474  }
475 
476  return DefWindowProc(hwnd, message, wparam, lparam);
477 }
478 
480  if (!size.has_preferred_view_size) {
481  return;
482  }
483 
484  if (GetFullscreen()) {
485  std::optional<Size> const window_size = GetWindowSizeForClientSize(
488  box_constraints_.smallest(), box_constraints_.biggest(),
490  if (!window_size) {
491  return;
492  }
493 
496  .height = size.preferred_view_height};
497  saved_window_info_.rect.right =
498  saved_window_info_.rect.left + static_cast<LONG>(window_size->width());
499  saved_window_info_.rect.bottom =
500  saved_window_info_.rect.top + static_cast<LONG>(window_size->height());
501  } else {
502  WINDOWINFO window_info = {.cbSize = sizeof(WINDOWINFO)};
503  GetWindowInfo(window_handle_, &window_info);
504 
505  std::optional<Size> const window_size = GetWindowSizeForClientSize(
508  box_constraints_.smallest(), box_constraints_.biggest(),
509  window_info.dwStyle, window_info.dwExStyle, nullptr);
510 
511  if (!window_size) {
512  return;
513  }
514 
515  SetWindowPos(window_handle_, NULL, 0, 0, window_size->width(),
516  window_size->height(),
517  SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
518  }
519 }
520 
522  box_constraints_ = FromWindowConstraints(constraints);
523 
524  if (GetFullscreen()) {
525  std::optional<Size> const window_size = GetWindowSizeForClientSize(
529  box_constraints_.smallest(), box_constraints_.biggest(),
531  if (!window_size) {
532  return;
533  }
534 
535  saved_window_info_.rect.right =
536  saved_window_info_.rect.left + static_cast<LONG>(window_size->width());
537  saved_window_info_.rect.bottom =
538  saved_window_info_.rect.top + static_cast<LONG>(window_size->height());
539  } else {
540  auto const client_size = GetWindowContentSize(window_handle_);
541  auto const current_size = Size(client_size.width, client_size.height);
542  WINDOWINFO window_info = {.cbSize = sizeof(WINDOWINFO)};
543  GetWindowInfo(window_handle_, &window_info);
544  std::optional<Size> const window_size = GetWindowSizeForClientSize(
545  *engine_->windows_proc_table(), current_size,
546  box_constraints_.smallest(), box_constraints_.biggest(),
547  window_info.dwStyle, window_info.dwExStyle, nullptr);
548 
549  if (window_size && current_size != window_size) {
550  SetWindowPos(window_handle_, NULL, 0, 0, window_size->width(),
551  window_size->height(),
552  SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
553  }
554  }
555 }
556 
557 // The fullscreen method is largely adapted from the method found in chromium:
558 // See:
559 //
560 // * https://chromium.googlesource.com/chromium/src/+/refs/heads/main/ui/views/win/fullscreen_handler.h
561 // * https://chromium.googlesource.com/chromium/src/+/refs/heads/main/ui/views/win/fullscreen_handler.cc
563  bool fullscreen,
564  std::optional<FlutterEngineDisplayId> display_id) {
565  if (fullscreen == GetFullscreen()) {
566  return;
567  }
568 
569  if (fullscreen) {
570  WINDOWINFO window_info = {.cbSize = sizeof(WINDOWINFO)};
571  GetWindowInfo(window_handle_, &window_info);
572  saved_window_info_.style = window_info.dwStyle;
573  saved_window_info_.ex_style = window_info.dwExStyle;
574  // Store the original window rect, DPI, and monitor info to detect changes
575  // and more accurately restore window placements when exiting fullscreen.
576  ::GetWindowRect(window_handle_, &saved_window_info_.rect);
580  MonitorFromWindow(window_handle_, MONITOR_DEFAULTTONEAREST);
583  GetMonitorInfo(saved_window_info_.monitor,
585  }
586 
587  if (fullscreen) {
588  // Next, get the raw HMONITOR that we want to be fullscreened on
589  HMONITOR monitor =
590  MonitorFromWindow(window_handle_, MONITOR_DEFAULTTONEAREST);
591  if (display_id) {
592  if (auto const display =
593  engine_->display_manager()->FindById(display_id.value())) {
594  monitor = reinterpret_cast<HMONITOR>(display->display_id);
595  }
596  }
597 
598  MONITORINFO monitor_info;
599  monitor_info.cbSize = sizeof(monitor_info);
600  if (!GetMonitorInfo(monitor, &monitor_info)) {
601  FML_LOG(ERROR) << "Cannot set window fullscreen because the monitor info "
602  "was not found";
603  }
604 
605  auto const width = RectWidth(monitor_info.rcMonitor);
606  auto const height = RectHeight(monitor_info.rcMonitor);
607  WINDOWINFO window_info = {.cbSize = sizeof(WINDOWINFO)};
608  GetWindowInfo(window_handle_, &window_info);
609 
610  // Set new window style and size.
611  SetWindowLong(window_handle_, GWL_STYLE,
612  saved_window_info_.style & ~(WS_CAPTION | WS_THICKFRAME));
613  SetWindowLong(
614  window_handle_, GWL_EXSTYLE,
615  saved_window_info_.ex_style & ~(WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE |
616  WS_EX_CLIENTEDGE | WS_EX_STATICEDGE));
617 
618  // We call SetWindowPos first to set the window flags immediately. This
619  // makes it so that the WM_GETMINMAXINFO gets called with the correct window
620  // and content sizes.
621  SetWindowPos(window_handle_, NULL, 0, 0, 0, 0,
622  SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
623 
624  SetWindowPos(window_handle_, nullptr, monitor_info.rcMonitor.left,
625  monitor_info.rcMonitor.top, width, height,
626  SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
627  } else {
628  // Restore the window style and bounds saved prior to entering fullscreen.
629  // Use WS_VISIBLE for windows shown after SetFullscreen: crbug.com/1062251.
630  // Making multiple window adjustments here is ugly, but if SetWindowPos()
631  // doesn't redraw, the taskbar won't be repainted.
632  SetWindowLong(window_handle_, GWL_STYLE,
633  saved_window_info_.style | WS_VISIBLE);
634  SetWindowLong(window_handle_, GWL_EXSTYLE, saved_window_info_.ex_style);
635 
636  // We call SetWindowPos first to set the window flags immediately. This
637  // makes it so that the WM_GETMINMAXINFO gets called with the correct window
638  // and content sizes.
639  SetWindowPos(window_handle_, NULL, 0, 0, 0, 0,
640  SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
641 
642  HMONITOR monitor =
643  MonitorFromRect(&saved_window_info_.rect, MONITOR_DEFAULTTONEAREST);
644  MONITORINFO monitor_info;
645  monitor_info.cbSize = sizeof(monitor_info);
646  GetMonitorInfo(monitor, &monitor_info);
647 
648  auto window_rect = saved_window_info_.rect;
649 
650  // Adjust the window bounds to restore, if displays were disconnected,
651  // virtually rearranged, or otherwise changed metrics during fullscreen.
652  if (monitor != saved_window_info_.monitor ||
654  monitor_info.rcWork)) {
655  window_rect = AdjustToFit(monitor_info.rcWork, window_rect);
656  }
657 
658  auto const fullscreen_dpi = GetDpiForHWND(window_handle_);
659  SetWindowPos(window_handle_, nullptr, window_rect.left, window_rect.top,
660  RectWidth(window_rect), RectHeight(window_rect),
661  SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
662  auto const final_dpi = GetDpiForHWND(window_handle_);
663  if (final_dpi != saved_window_info_.dpi || final_dpi != fullscreen_dpi) {
664  // Reissue SetWindowPos if the DPI changed from saved or fullscreen DPIs.
665  // The first call may misinterpret bounds spanning displays, if the
666  // fullscreen display's DPI does not match the target display's DPI.
667  //
668  // Scale and clamp the bounds if the final DPI changed from the saved DPI.
669  // This more accurately matches the original placement, while avoiding
670  // unexpected offscreen placement in a recongifured multi-screen space.
671  if (final_dpi != saved_window_info_.dpi) {
672  auto const scale =
673  final_dpi / static_cast<float>(saved_window_info_.dpi);
674  auto const width = static_cast<LONG>(scale * RectWidth(window_rect));
675  auto const height = static_cast<LONG>(scale * RectHeight(window_rect));
676  window_rect.right = window_rect.left + width;
677  window_rect.bottom = window_rect.top + height;
678  window_rect = AdjustToFit(monitor_info.rcWork, window_rect);
679  }
680 
681  SetWindowPos(window_handle_, nullptr, window_rect.left, window_rect.top,
682  RectWidth(window_rect), RectHeight(window_rect),
683  SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
684  }
685  }
686 
687  if (!task_bar_list_) {
688  HRESULT hr =
689  ::CoCreateInstance(CLSID_TaskbarList, nullptr, CLSCTX_INPROC_SERVER,
690  IID_PPV_ARGS(&task_bar_list_));
691  if (SUCCEEDED(hr) && FAILED(task_bar_list_->HrInit())) {
692  task_bar_list_ = nullptr;
693  }
694  }
695 
696  // As per MSDN marking the window as fullscreen should ensure that the
697  // taskbar is moved to the bottom of the Z-order when the fullscreen window
698  // is activated. If the window is not fullscreen, the Shell falls back to
699  // heuristics to determine how the window should be treated, which means
700  // that it could still consider the window as fullscreen. :(
701  if (task_bar_list_) {
702  task_bar_list_->MarkFullscreenWindow(window_handle_, !!fullscreen);
703  }
704 
705  is_fullscreen_ = fullscreen;
706 }
707 
709  return is_fullscreen_;
710 }
711 
713  RECT rect;
714  GetClientRect(hwnd, &rect);
715  double const dpr = FlutterDesktopGetDpiForHWND(hwnd) /
716  static_cast<double>(USER_DEFAULT_SCREEN_DPI);
717  double const width = rect.right / dpr;
718  double const height = rect.bottom / dpr;
719  return {
720  .width = rect.right / dpr,
721  .height = rect.bottom / dpr,
722  };
723 }
724 
726  WindowsProcTable const& win32,
727  Size const& client_size,
728  std::optional<Size> smallest,
729  std::optional<Size> biggest,
730  DWORD window_style,
731  DWORD extended_window_style,
732  std::optional<HWND> const& owner_hwnd) {
733  UINT const dpi = GetDpiForHWND(owner_hwnd ? *owner_hwnd : nullptr);
734  double const scale_factor =
735  static_cast<double>(dpi) / USER_DEFAULT_SCREEN_DPI;
736  RECT rect = {
737  .right = static_cast<LONG>(client_size.width() * scale_factor),
738  .bottom = static_cast<LONG>(client_size.height() * scale_factor)};
739 
740  if (!win32.AdjustWindowRectExForDpi(&rect, window_style, FALSE,
741  extended_window_style, dpi)) {
742  FML_LOG(ERROR) << "Failed to run AdjustWindowRectExForDpi: "
743  << GetLastErrorAsString();
744  return std::nullopt;
745  }
746 
747  double width = static_cast<double>(rect.right - rect.left);
748  double height = static_cast<double>(rect.bottom - rect.top);
749 
750  // Apply size constraints.
751  double const non_client_width = width - (client_size.width() * scale_factor);
752  double const non_client_height =
753  height - (client_size.height() * scale_factor);
754  if (smallest) {
755  flutter::Size min_physical_size = ClampToVirtualScreen(
756  flutter::Size(smallest->width() * scale_factor + non_client_width,
757  smallest->height() * scale_factor + non_client_height));
758  width = std::max(width, min_physical_size.width());
759  height = std::max(height, min_physical_size.height());
760  }
761  if (biggest) {
762  flutter::Size max_physical_size = ClampToVirtualScreen(
763  flutter::Size(biggest->width() * scale_factor + non_client_width,
764  biggest->height() * scale_factor + non_client_height));
765  width = std::min(width, max_physical_size.width());
766  height = std::min(height, max_physical_size.height());
767  }
768 
769  return flutter::Size{width, height};
770 }
771 
772 void HostWindow::EnableRecursively(bool enable) {
773  EnableWindow(window_handle_, enable);
774 
775  for (HostWindow* const owned : GetOwnedWindows()) {
776  owned->EnableRecursively(enable);
777  }
778 }
779 
781  if (IsWindowEnabled(window_handle_)) {
782  return const_cast<HostWindow*>(this);
783  }
784 
785  for (HostWindow* const owned : GetOwnedWindows()) {
786  if (HostWindow* const result = owned->FindFirstEnabledDescendant()) {
787  return result;
788  }
789  }
790 
791  return nullptr;
792 }
793 
794 std::vector<HostWindow*> HostWindow::GetOwnedWindows() const {
795  std::vector<HostWindow*> owned_windows;
796  struct EnumData {
797  HWND owner_window_handle;
798  std::vector<HostWindow*>* owned_windows;
799  } data{window_handle_, &owned_windows};
800 
801  EnumWindows(
802  [](HWND hwnd, LPARAM lparam) -> BOOL {
803  auto* const data = reinterpret_cast<EnumData*>(lparam);
804  if (GetWindow(hwnd, GW_OWNER) == data->owner_window_handle) {
805  HostWindow* const window = GetThisFromHandle(hwnd);
806  if (window && !window->is_being_destroyed_) {
807  data->owned_windows->push_back(window);
808  }
809  }
810  return TRUE;
811  },
812  reinterpret_cast<LPARAM>(&data));
813 
814  return owned_windows;
815 }
816 
818  if (HWND const owner_window_handle = GetWindow(GetWindowHandle(), GW_OWNER)) {
819  return GetThisFromHandle(owner_window_handle);
820  }
821  return nullptr;
822 };
823 
825  // Disable the window itself.
826  EnableWindow(window_handle_, false);
827 
828  for (HostWindow* const owned : GetOwnedWindows()) {
829  owned->DisableRecursively();
830  }
831 }
832 
834  auto children = GetOwnedWindows();
835  if (children.empty()) {
836  // Leaf window in the active path, enable it.
837  EnableWindow(window_handle_, true);
838  } else {
839  // Non-leaf window in the active path, disable it and process children.
840  EnableWindow(window_handle_, false);
841 
842  // On same level of window hierarchy the most recently created window
843  // will remain enabled.
844  auto latest_child = *std::max_element(
845  children.begin(), children.end(), [](HostWindow* a, HostWindow* b) {
846  return a->view_controller_->view()->view_id() <
847  b->view_controller_->view()->view_id();
848  });
849 
850  for (HostWindow* const child : children) {
851  if (child == latest_child) {
852  child->UpdateModalStateLayer();
853  } else {
854  child->DisableRecursively();
855  }
856  }
857  }
858 }
859 
860 } // namespace flutter
std::shared_ptr< WindowsProcTable > windows_proc_table()
WindowProcDelegateManager * window_proc_delegate_manager()
std::shared_ptr< DisplayManagerWin32 > display_manager()
std::unique_ptr< FlutterWindowsView > CreateView(std::unique_ptr< WindowBindingHandler > window)
Microsoft::WRL::ComPtr< ITaskbarList2 > task_bar_list_
Definition: host_window.h:214
HWND GetWindowHandle() const
Definition: host_window.cc:342
BoxConstraints box_constraints_
Definition: host_window.h:202
SavedWindowInfo saved_window_info_
Definition: host_window.h:211
std::unique_ptr< FlutterWindowsViewController > view_controller_
Definition: host_window.h:193
static LRESULT WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
Definition: host_window.cc:353
HostWindow * GetOwnerWindow() const
Definition: host_window.cc:817
static ActualWindowSize GetWindowContentSize(HWND hwnd)
Definition: host_window.cc:712
void EnableRecursively(bool enable)
Definition: host_window.cc:772
void SetContentSize(const WindowSizeRequest &size)
Definition: host_window.cc:479
void UpdateModalStateLayer()
Definition: host_window.cc:833
static void FocusRootViewOf(HostWindow *window)
Definition: host_window.cc:346
HostWindow(WindowManager *window_manager, FlutterWindowsEngine *engine, WindowArchetype archetype, DWORD window_style, DWORD extended_window_style, const BoxConstraints &box_constraints, Rect const initial_window_rect, LPCWSTR title, std::optional< HWND > const &owner_window)
Definition: host_window.cc:228
FlutterWindowsEngine * engine_
Definition: host_window.h:188
static HostWindow * GetThisFromHandle(HWND hwnd)
Definition: host_window.cc:327
std::vector< HostWindow * > GetOwnedWindows() const
Definition: host_window.cc:794
virtual bool GetFullscreen() const
Definition: host_window.cc:708
void SetConstraints(const WindowConstraints &constraints)
Definition: host_window.cc:521
virtual void SetFullscreen(bool fullscreen, std::optional< FlutterEngineDisplayId > display_id)
Definition: host_window.cc:562
virtual LRESULT HandleMessage(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
Definition: host_window.cc:370
static std::unique_ptr< HostWindow > CreateDialogWindow(WindowManager *window_manager, FlutterWindowsEngine *engine, const WindowSizeRequest &preferred_size, const WindowConstraints &preferred_constraints, LPCWSTR title, HWND parent)
Definition: host_window.cc:215
static std::optional< Size > GetWindowSizeForClientSize(WindowsProcTable const &win32, Size const &client_size, std::optional< Size > smallest, std::optional< Size > biggest, DWORD window_style, DWORD extended_window_style, std::optional< HWND > const &owner_hwnd)
Definition: host_window.cc:725
static std::unique_ptr< HostWindow > CreateRegularWindow(WindowManager *window_manager, FlutterWindowsEngine *engine, const WindowSizeRequest &preferred_size, const WindowConstraints &preferred_constraints, LPCWSTR title)
Definition: host_window.cc:204
HostWindow * FindFirstEnabledDescendant() const
Definition: host_window.cc:780
std::optional< LRESULT > OnTopLevelWindowProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) const
virtual BOOL AdjustWindowRectExForDpi(LPRECT lpRect, DWORD dwStyle, BOOL bMenu, DWORD dwExStyle, UINT dpi) const
virtual BOOL EnableNonClientDpiScaling(HWND hwnd) const
virtual HRESULT DwmExtendFrameIntoClientArea(HWND hwnd, const MARGINS *pMarInset) const
virtual HRESULT DwmSetWindowAttribute(HWND hwnd, DWORD dwAttribute, LPCVOID pvAttribute, DWORD cbAttribute) const
virtual BOOL SetWindowCompositionAttribute(HWND hwnd, WINDOWCOMPOSITIONATTRIBDATA *data) const
UINT FlutterDesktopGetDpiForHWND(HWND hwnd)
#define DWMWA_USE_IMMERSIVE_DARK_MODE
Definition: host_window.cc:110
union flutter::testing::@98::KeyboardChange::@0 content
Win32Message message
UINT GetDpiForHWND(HWND hwnd)
Definition: dpi_utils.cc:128
WindowArchetype
Definition: windowing.h:11
LONG RectWidth(const RECT &r)
Definition: rect_helper.h:11
bool AreRectsEqual(const RECT &a, const RECT &b)
Definition: rect_helper.h:19
LONG RectHeight(const RECT &r)
Definition: rect_helper.h:15