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