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