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
9
10#include <dwmapi.h>
11
19
20namespace {
21
22constexpr 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.
26flutter::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
34void 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 =
50 .pvData = &accent,
51 .cbData = sizeof(accent)};
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.
65std::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.
98bool 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.
115void 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.
138void 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: {[----]------}
162void 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
170RECT 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
185flutter::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
203namespace flutter {
204
205std::unique_ptr<HostWindow> HostWindow::CreateRegularWindow(
206 WindowManager* window_manager,
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
216std::unique_ptr<HostWindow> HostWindow::CreateDialogWindow(
217 WindowManager* window_manager,
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
229std::unique_ptr<HostWindow> HostWindow::CreateTooltipWindow(
230 WindowManager* window_manager,
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
243 : window_manager_(window_manager), engine_(engine) {}
244
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));
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.
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
357
359 auto child_content = window->view_controller_->view()->GetWindowHandle();
360 if (window != nullptr && child_content != nullptr) {
361 SetFocus(child_content);
362 }
363};
364
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
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(
499 Size(size.preferred_view_width, size.preferred_view_height),
502 if (!window_size) {
503 return;
504 }
505
507 ActualWindowSize{.width = size.preferred_view_width,
508 .height = size.preferred_view_height};
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(
519 Size(size.preferred_view_width, size.preferred_view_height),
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(
543 if (!window_size) {
544 return;
545 }
546
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,
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
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
806std::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
Size smallest() const
Definition geometry.h:94
Size biggest() const
Definition geometry.h:93
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()
WindowProcDelegateManager * window_proc_delegate_manager()
Microsoft::WRL::ComPtr< ITaskbarList2 > task_bar_list_
HWND GetWindowHandle() const
BoxConstraints box_constraints_
void InitializeFlutterView(HostWindowInitializationParams const &params)
SavedWindowInfo saved_window_info_
std::unique_ptr< FlutterWindowsViewController > view_controller_
static LRESULT WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
HostWindow * GetOwnerWindow() const
static ActualWindowSize GetWindowContentSize(HWND hwnd)
void EnableRecursively(bool enable)
void SetContentSize(const WindowSizeRequest &size)
static void FocusRootViewOf(HostWindow *window)
HWND GetFlutterViewWindowHandle() const
FlutterWindowsEngine * engine_
static HostWindow * GetThisFromHandle(HWND hwnd)
HostWindow(WindowManager *window_manager, FlutterWindowsEngine *engine)
std::vector< HostWindow * > GetOwnedWindows() const
virtual bool GetFullscreen() const
void SetConstraints(const WindowConstraints &constraints)
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)
virtual void SetFullscreen(bool fullscreen, std::optional< FlutterEngineDisplayId > display_id)
virtual LRESULT HandleMessage(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
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::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)
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
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
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::@2836::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
std::shared_ptr< const fml::Mapping > data
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