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