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