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
356void HostWindow::HandleWindowActivation(HWND hwnd, WPARAM wparam) {
357 if (LOWORD(wparam) == WA_INACTIVE) {
358 // The window is being deactivated; leave focus untouched. Focusing this
359 // window's view would call SetFocus on its content, which reactivates the
360 // window (SetFocus activates the parent of the focused window) and pulls it
361 // back to the top of the z-order, taking activation from the window that is
362 // being activated.
363 return;
364 }
365
366 if (!IsWindowEnabled(hwnd)) {
367 // Prevent a disabled window (e.g. the owner of a modal dialog) from being
368 // activated using the task switcher by redirecting focus and activation to
369 // the first enabled descendant.
370 if (HostWindow* const enabled_descendant = FindFirstEnabledDescendant()) {
371 SetActiveWindow(enabled_descendant->GetWindowHandle());
372 FocusRootViewOf(this);
373 }
374 return;
375 }
376
377 FocusRootViewOf(this);
378}
379
380LRESULT HostWindow::WndProc(HWND hwnd,
382 WPARAM wparam,
383 LPARAM lparam) {
384 if (message == WM_NCCREATE) {
385 auto* const create_struct = reinterpret_cast<CREATESTRUCT*>(lparam);
386 auto* const windows_proc_table =
387 static_cast<WindowsProcTable*>(create_struct->lpCreateParams);
388 windows_proc_table->EnableNonClientDpiScaling(hwnd);
389 } else if (HostWindow* const window = GetThisFromHandle(hwnd)) {
390 return window->HandleMessage(hwnd, message, wparam, lparam);
391 }
392
393 return DefWindowProc(hwnd, message, wparam, lparam);
394}
395
396LRESULT HostWindow::HandleMessage(HWND hwnd,
398 WPARAM wparam,
399 LPARAM lparam) {
400 auto result = engine_->window_proc_delegate_manager()->OnTopLevelWindowProc(
401 window_handle_, message, wparam, lparam);
402 if (result) {
403 return *result;
404 }
405
406 switch (message) {
407 case WM_DESTROY:
408 is_being_destroyed_ = true;
409 break;
410
411 case WM_NCLBUTTONDOWN: {
412 // Fix for 500ms hang after user clicks on the title bar, but before
413 // moving mouse. Reference:
414 // https://gamedev.net/forums/topic/672094-keeping-things-moving-during-win32-moveresize-events/5254386/
415 if (SendMessage(window_handle_, WM_NCHITTEST, wparam, lparam) ==
416 HTCAPTION) {
417 POINT cursorPos;
418 // Get the current cursor position and synthesize WM_MOUSEMOVE to
419 // unblock default window proc implementation for WM_NCLBUTTONDOWN at
420 // HTCAPTION.
421 GetCursorPos(&cursorPos);
422 ScreenToClient(window_handle_, &cursorPos);
423 PostMessage(window_handle_, WM_MOUSEMOVE, 0,
424 MAKELPARAM(cursorPos.x, cursorPos.y));
425 }
426 break;
427 }
428
429 case WM_DPICHANGED: {
430 auto* const new_scaled_window_rect = reinterpret_cast<RECT*>(lparam);
431 LONG const width =
432 new_scaled_window_rect->right - new_scaled_window_rect->left;
433 LONG const height =
434 new_scaled_window_rect->bottom - new_scaled_window_rect->top;
435 SetWindowPos(hwnd, nullptr, new_scaled_window_rect->left,
436 new_scaled_window_rect->top, width, height,
437 SWP_NOZORDER | SWP_NOACTIVATE);
438 return 0;
439 }
440
441 case WM_GETMINMAXINFO: {
442 RECT window_rect;
443 GetWindowRect(hwnd, &window_rect);
444 RECT client_rect;
445 GetClientRect(hwnd, &client_rect);
446 LONG const non_client_width = (window_rect.right - window_rect.left) -
447 (client_rect.right - client_rect.left);
448 LONG const non_client_height = (window_rect.bottom - window_rect.top) -
449 (client_rect.bottom - client_rect.top);
450
451 UINT const dpi = flutter::GetDpiForHWND(hwnd);
452 double const scale_factor =
453 static_cast<double>(dpi) / USER_DEFAULT_SCREEN_DPI;
454
455 MINMAXINFO* info = reinterpret_cast<MINMAXINFO*>(lparam);
456 Size const min_physical_size = ClampToVirtualScreen(Size(
457 box_constraints_.smallest().width() * scale_factor + non_client_width,
458 box_constraints_.smallest().height() * scale_factor +
459 non_client_height));
460
461 info->ptMinTrackSize.x = min_physical_size.width();
462 info->ptMinTrackSize.y = min_physical_size.height();
463 Size const max_physical_size = ClampToVirtualScreen(Size(
464 box_constraints_.biggest().width() * scale_factor + non_client_width,
465 box_constraints_.biggest().height() * scale_factor +
466 non_client_height));
467
468 info->ptMaxTrackSize.x = max_physical_size.width();
469 info->ptMaxTrackSize.y = max_physical_size.height();
470 return 0;
471 }
472
473 case WM_SIZE: {
474 auto child_content = view_controller_->view()->GetWindowHandle();
475 if (child_content != nullptr) {
476 // Resize and reposition the child content window.
477 RECT client_rect;
478 GetClientRect(hwnd, &client_rect);
479 MoveWindow(child_content, client_rect.left, client_rect.top,
480 client_rect.right - client_rect.left,
481 client_rect.bottom - client_rect.top, TRUE);
482 }
483 return 0;
484 }
485
486 case WM_ACTIVATE:
487 HandleWindowActivation(hwnd, wparam);
488 return 0;
489
490 case WM_DWMCOLORIZATIONCOLORCHANGED:
491 UpdateTheme(hwnd);
492 return 0;
493
494 default:
495 break;
496 }
497
498 if (!view_controller_) {
499 return 0;
500 }
501
502 return DefWindowProc(hwnd, message, wparam, lparam);
503}
504
505void HostWindow::SetContentSize(const WindowSizeRequest& size) {
506 if (!size.has_preferred_view_size) {
507 return;
508 }
509
510 if (GetFullscreen()) {
511 std::optional<Size> const window_size = GetWindowSizeForClientSize(
512 *engine_->windows_proc_table(),
513 Size(size.preferred_view_width, size.preferred_view_height),
514 box_constraints_.smallest(), box_constraints_.biggest(),
515 saved_window_info_.style, saved_window_info_.ex_style, nullptr);
516 if (!window_size) {
517 return;
518 }
519
520 saved_window_info_.client_size =
521 ActualWindowSize{.width = size.preferred_view_width,
522 .height = size.preferred_view_height};
523 saved_window_info_.rect.right =
524 saved_window_info_.rect.left + static_cast<LONG>(window_size->width());
525 saved_window_info_.rect.bottom =
526 saved_window_info_.rect.top + static_cast<LONG>(window_size->height());
527 } else {
528 WINDOWINFO window_info = {.cbSize = sizeof(WINDOWINFO)};
529 GetWindowInfo(window_handle_, &window_info);
530
531 std::optional<Size> const window_size = GetWindowSizeForClientSize(
532 *engine_->windows_proc_table(),
533 Size(size.preferred_view_width, size.preferred_view_height),
534 box_constraints_.smallest(), box_constraints_.biggest(),
535 window_info.dwStyle, window_info.dwExStyle, nullptr);
536
537 if (!window_size) {
538 return;
539 }
540
541 SetWindowPos(window_handle_, NULL, 0, 0, window_size->width(),
542 window_size->height(),
543 SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
544 }
545}
546
547void HostWindow::SetConstraints(const WindowConstraints& constraints) {
548 box_constraints_ = FromWindowConstraints(constraints);
549
550 if (GetFullscreen()) {
551 std::optional<Size> const window_size = GetWindowSizeForClientSize(
552 *engine_->windows_proc_table(),
553 Size(saved_window_info_.client_size.width,
554 saved_window_info_.client_size.height),
555 box_constraints_.smallest(), box_constraints_.biggest(),
556 saved_window_info_.style, saved_window_info_.ex_style, nullptr);
557 if (!window_size) {
558 return;
559 }
560
561 saved_window_info_.rect.right =
562 saved_window_info_.rect.left + static_cast<LONG>(window_size->width());
563 saved_window_info_.rect.bottom =
564 saved_window_info_.rect.top + static_cast<LONG>(window_size->height());
565 } else {
566 auto const client_size = GetWindowContentSize(window_handle_);
567 auto const current_size = Size(client_size.width, client_size.height);
568 WINDOWINFO window_info = {.cbSize = sizeof(WINDOWINFO)};
569 GetWindowInfo(window_handle_, &window_info);
570 std::optional<Size> const window_size = GetWindowSizeForClientSize(
571 *engine_->windows_proc_table(), current_size,
572 box_constraints_.smallest(), box_constraints_.biggest(),
573 window_info.dwStyle, window_info.dwExStyle, nullptr);
574
575 if (window_size && current_size != window_size) {
576 SetWindowPos(window_handle_, NULL, 0, 0, window_size->width(),
577 window_size->height(),
578 SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
579 }
580 }
581}
582
583// The fullscreen method is largely adapted from the method found in chromium:
584// See:
585//
586// * https://chromium.googlesource.com/chromium/src/+/refs/heads/main/ui/views/win/fullscreen_handler.h
587// * https://chromium.googlesource.com/chromium/src/+/refs/heads/main/ui/views/win/fullscreen_handler.cc
588void HostWindow::SetFullscreen(
589 bool fullscreen,
590 std::optional<FlutterEngineDisplayId> display_id) {
591 if (fullscreen == GetFullscreen()) {
592 return;
593 }
594
595 if (fullscreen) {
596 WINDOWINFO window_info = {.cbSize = sizeof(WINDOWINFO)};
597 GetWindowInfo(window_handle_, &window_info);
598 saved_window_info_.style = window_info.dwStyle;
599 saved_window_info_.ex_style = window_info.dwExStyle;
600 // Store the original window rect, DPI, and monitor info to detect changes
601 // and more accurately restore window placements when exiting fullscreen.
602 ::GetWindowRect(window_handle_, &saved_window_info_.rect);
603 saved_window_info_.client_size = GetWindowContentSize(window_handle_);
604 saved_window_info_.dpi = GetDpiForHWND(window_handle_);
605 saved_window_info_.monitor =
606 MonitorFromWindow(window_handle_, MONITOR_DEFAULTTONEAREST);
607 saved_window_info_.monitor_info.cbSize =
608 sizeof(saved_window_info_.monitor_info);
609 GetMonitorInfo(saved_window_info_.monitor,
610 &saved_window_info_.monitor_info);
611 }
612
613 if (fullscreen) {
614 // Next, get the raw HMONITOR that we want to be fullscreened on
615 HMONITOR monitor =
616 MonitorFromWindow(window_handle_, MONITOR_DEFAULTTONEAREST);
617 if (display_id) {
618 if (auto const display =
619 engine_->display_manager()->FindById(display_id.value())) {
620 monitor = reinterpret_cast<HMONITOR>(display->display_id);
621 }
622 }
623
624 MONITORINFO monitor_info;
625 monitor_info.cbSize = sizeof(monitor_info);
626 if (!GetMonitorInfo(monitor, &monitor_info)) {
627 FML_LOG(ERROR) << "Cannot set window fullscreen because the monitor info "
628 "was not found";
629 }
630
631 auto const width = RectWidth(monitor_info.rcMonitor);
632 auto const height = RectHeight(monitor_info.rcMonitor);
633 WINDOWINFO window_info = {.cbSize = sizeof(WINDOWINFO)};
634 GetWindowInfo(window_handle_, &window_info);
635
636 // Set new window style and size.
637 SetWindowLong(window_handle_, GWL_STYLE,
638 saved_window_info_.style & ~(WS_CAPTION | WS_THICKFRAME));
639 SetWindowLong(
640 window_handle_, GWL_EXSTYLE,
641 saved_window_info_.ex_style & ~(WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE |
642 WS_EX_CLIENTEDGE | WS_EX_STATICEDGE));
643
644 // We call SetWindowPos first to set the window flags immediately. This
645 // makes it so that the WM_GETMINMAXINFO gets called with the correct window
646 // and content sizes.
647 SetWindowPos(window_handle_, NULL, 0, 0, 0, 0,
648 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
649
650 SetWindowPos(window_handle_, nullptr, monitor_info.rcMonitor.left,
651 monitor_info.rcMonitor.top, width, height,
652 SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
653 } else {
654 // Restore the window style and bounds saved prior to entering fullscreen.
655 // Use WS_VISIBLE for windows shown after SetFullscreen: crbug.com/1062251.
656 // Making multiple window adjustments here is ugly, but if SetWindowPos()
657 // doesn't redraw, the taskbar won't be repainted.
658 SetWindowLong(window_handle_, GWL_STYLE,
659 saved_window_info_.style | WS_VISIBLE);
660 SetWindowLong(window_handle_, GWL_EXSTYLE, saved_window_info_.ex_style);
661
662 // We call SetWindowPos first to set the window flags immediately. This
663 // makes it so that the WM_GETMINMAXINFO gets called with the correct window
664 // and content sizes.
665 SetWindowPos(window_handle_, NULL, 0, 0, 0, 0,
666 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
667
668 HMONITOR monitor =
669 MonitorFromRect(&saved_window_info_.rect, MONITOR_DEFAULTTONEAREST);
670 MONITORINFO monitor_info;
671 monitor_info.cbSize = sizeof(monitor_info);
672 GetMonitorInfo(monitor, &monitor_info);
673
674 auto window_rect = saved_window_info_.rect;
675
676 // Adjust the window bounds to restore, if displays were disconnected,
677 // virtually rearranged, or otherwise changed metrics during fullscreen.
678 if (monitor != saved_window_info_.monitor ||
679 !AreRectsEqual(saved_window_info_.monitor_info.rcWork,
680 monitor_info.rcWork)) {
681 window_rect = AdjustToFit(monitor_info.rcWork, window_rect);
682 }
683
684 auto const fullscreen_dpi = GetDpiForHWND(window_handle_);
685 SetWindowPos(window_handle_, nullptr, window_rect.left, window_rect.top,
686 RectWidth(window_rect), RectHeight(window_rect),
687 SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
688 auto const final_dpi = GetDpiForHWND(window_handle_);
689 if (final_dpi != saved_window_info_.dpi || final_dpi != fullscreen_dpi) {
690 // Reissue SetWindowPos if the DPI changed from saved or fullscreen DPIs.
691 // The first call may misinterpret bounds spanning displays, if the
692 // fullscreen display's DPI does not match the target display's DPI.
693 //
694 // Scale and clamp the bounds if the final DPI changed from the saved DPI.
695 // This more accurately matches the original placement, while avoiding
696 // unexpected offscreen placement in a recongifured multi-screen space.
697 if (final_dpi != saved_window_info_.dpi) {
698 auto const scale =
699 final_dpi / static_cast<float>(saved_window_info_.dpi);
700 auto const width = static_cast<LONG>(scale * RectWidth(window_rect));
701 auto const height = static_cast<LONG>(scale * RectHeight(window_rect));
702 window_rect.right = window_rect.left + width;
703 window_rect.bottom = window_rect.top + height;
704 window_rect = AdjustToFit(monitor_info.rcWork, window_rect);
705 }
706
707 SetWindowPos(window_handle_, nullptr, window_rect.left, window_rect.top,
708 RectWidth(window_rect), RectHeight(window_rect),
709 SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
710 }
711 }
712
713 if (!task_bar_list_) {
714 HRESULT hr =
715 ::CoCreateInstance(CLSID_TaskbarList, nullptr, CLSCTX_INPROC_SERVER,
716 IID_PPV_ARGS(&task_bar_list_));
717 if (SUCCEEDED(hr) && FAILED(task_bar_list_->HrInit())) {
718 task_bar_list_ = nullptr;
719 }
720 }
721
722 // As per MSDN marking the window as fullscreen should ensure that the
723 // taskbar is moved to the bottom of the Z-order when the fullscreen window
724 // is activated. If the window is not fullscreen, the Shell falls back to
725 // heuristics to determine how the window should be treated, which means
726 // that it could still consider the window as fullscreen. :(
727 if (task_bar_list_) {
728 task_bar_list_->MarkFullscreenWindow(window_handle_, !!fullscreen);
729 }
730
731 is_fullscreen_ = fullscreen;
732}
733
734bool HostWindow::GetFullscreen() const {
735 return is_fullscreen_;
736}
737
738ActualWindowSize HostWindow::GetWindowContentSize(HWND hwnd) {
739 RECT rect;
740 GetClientRect(hwnd, &rect);
741 double const dpr = FlutterDesktopGetDpiForHWND(hwnd) /
742 static_cast<double>(USER_DEFAULT_SCREEN_DPI);
743 double const width = rect.right / dpr;
744 double const height = rect.bottom / dpr;
745 return {
746 .width = rect.right / dpr,
747 .height = rect.bottom / dpr,
748 };
749}
750
751std::optional<Size> HostWindow::GetWindowSizeForClientSize(
752 WindowsProcTable const& win32,
753 Size const& client_size,
754 std::optional<Size> smallest,
755 std::optional<Size> biggest,
756 DWORD window_style,
757 DWORD extended_window_style,
758 std::optional<HWND> const& owner_hwnd) {
759 UINT const dpi = GetDpiForHWND(owner_hwnd ? *owner_hwnd : nullptr);
760 double const scale_factor =
761 static_cast<double>(dpi) / USER_DEFAULT_SCREEN_DPI;
762 RECT rect = {
763 .right = static_cast<LONG>(client_size.width() * scale_factor),
764 .bottom = static_cast<LONG>(client_size.height() * scale_factor)};
765
766 if (!win32.AdjustWindowRectExForDpi(&rect, window_style, FALSE,
767 extended_window_style, dpi)) {
768 FML_LOG(ERROR) << "Failed to run AdjustWindowRectExForDpi: "
769 << GetLastErrorAsString();
770 return std::nullopt;
771 }
772
773 double width = static_cast<double>(rect.right - rect.left);
774 double height = static_cast<double>(rect.bottom - rect.top);
775
776 // Apply size constraints.
777 double const non_client_width = width - (client_size.width() * scale_factor);
778 double const non_client_height =
779 height - (client_size.height() * scale_factor);
780 if (smallest) {
781 flutter::Size min_physical_size = ClampToVirtualScreen(
782 flutter::Size(smallest->width() * scale_factor + non_client_width,
783 smallest->height() * scale_factor + non_client_height));
784 width = std::max(width, min_physical_size.width());
785 height = std::max(height, min_physical_size.height());
786 }
787 if (biggest) {
788 flutter::Size max_physical_size = ClampToVirtualScreen(
789 flutter::Size(biggest->width() * scale_factor + non_client_width,
790 biggest->height() * scale_factor + non_client_height));
791 width = std::min(width, max_physical_size.width());
792 height = std::min(height, max_physical_size.height());
793 }
794
795 return flutter::Size{width, height};
796}
797
798void HostWindow::EnableRecursively(bool enable) {
799 EnableWindow(window_handle_, enable);
800
801 for (HostWindow* const owned : GetOwnedWindows()) {
802 owned->EnableRecursively(enable);
803 }
804}
805
806HostWindow* HostWindow::FindFirstEnabledDescendant() const {
807 if (IsWindowEnabled(window_handle_)) {
808 return const_cast<HostWindow*>(this);
809 }
810
811 for (HostWindow* const owned : GetOwnedWindows()) {
812 if (HostWindow* const result = owned->FindFirstEnabledDescendant()) {
813 return result;
814 }
815 }
816
817 return nullptr;
818}
819
820std::vector<HostWindow*> HostWindow::GetOwnedWindows() const {
821 std::vector<HostWindow*> owned_windows;
822 struct EnumData {
823 HWND owner_window_handle;
824 std::vector<HostWindow*>* owned_windows;
825 } data{window_handle_, &owned_windows};
826
827 EnumWindows(
828 [](HWND hwnd, LPARAM lparam) -> BOOL {
829 auto* const data = reinterpret_cast<EnumData*>(lparam);
830 if (GetWindow(hwnd, GW_OWNER) == data->owner_window_handle) {
831 HostWindow* const window = GetThisFromHandle(hwnd);
832 if (window && !window->is_being_destroyed_) {
833 data->owned_windows->push_back(window);
834 }
835 }
836 return TRUE;
837 },
838 reinterpret_cast<LPARAM>(&data));
839
840 return owned_windows;
841}
842
843HostWindow* HostWindow::GetOwnerWindow() const {
844 if (HWND const owner_window_handle = GetWindow(GetWindowHandle(), GW_OWNER)) {
845 return GetThisFromHandle(owner_window_handle);
846 }
847 return nullptr;
848};
849
850void HostWindow::DisableRecursively() {
851 // Disable the window itself.
852 EnableWindow(window_handle_, false);
853
854 for (HostWindow* const owned : GetOwnedWindows()) {
855 owned->DisableRecursively();
856 }
857}
858
859void HostWindow::UpdateModalStateLayer() {
860 auto children = GetOwnedWindows();
861
862 if (children.empty()) {
863 // Leaf window in the active path, enable it.
864 EnableWindow(window_handle_, true);
865 } else {
866 // Non-leaf window in the active path, disable it and process children.
867 EnableWindow(window_handle_, false);
868
869 // On same level of window hierarchy the most recently created window
870 // will remain enabled.
871 auto latest_child = *std::max_element(
872 children.begin(), children.end(), [](HostWindow* a, HostWindow* b) {
873 return a->view_controller_->view()->view_id() <
874 b->view_controller_->view()->view_id();
875 });
876
877 for (HostWindow* const child : children) {
878 if (child == latest_child) {
879 child->UpdateModalStateLayer();
880 } else {
881 child->DisableRecursively();
882 }
883 }
884 }
885}
886
887} // 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
G_BEGIN_DECLS gboolean sized_to_content
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::@2903::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
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