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