| Flutter Windows Embedder |

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
5 #include "flutter/shell/platform/windows/host_window.h"
6 #include "flutter/shell/platform/windows/host_window_dialog.h"
7 #include "flutter/shell/platform/windows/host_window_regular.h"
8
9 #include <dwmapi.h>
10
11 #include "flutter/shell/platform/windows/display_manager.h"
12 #include "flutter/shell/platform/windows/dpi_utils.h"
13 #include "flutter/shell/platform/windows/flutter_window.h"
14 #include "flutter/shell/platform/windows/flutter_windows_view_controller.h"
15 #include "flutter/shell/platform/windows/rect_helper.h"
16 #include "flutter/shell/platform/windows/wchar_util.h"
17 #include "flutter/shell/platform/windows/window_manager.h"
18
19 namespace {
20
21 constexpr 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.
25 flutter::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
33 void 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};
46 flutter::WindowsProcTable::WINDOWCOMPOSITIONATTRIBDATA data = {
47 .Attrib =
48 flutter::WindowsProcTable::WINDOWCOMPOSITIONATTRIB::WCA_ACCENT_POLICY,
49 .pvData = &accent,
50 .cbData = sizeof(accent)};
51 win32.SetWindowCompositionAttribute(hwnd, &data);
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.
64 std::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.
97 bool 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.
114 void 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.
137 void 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: {[----]------}
161 void 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
169 RECT 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
184 flutter::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
202 namespace flutter {
203
204 std::unique_ptr<HostWindow> HostWindow::CreateRegularWindow(
205 WindowManager* window_manager,
206 FlutterWindowsEngine* engine,
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
215 std::unique_ptr<HostWindow> HostWindow::CreateDialogWindow(
216 WindowManager* window_manager,
217 FlutterWindowsEngine* engine,
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
228 HostWindow::HostWindow(WindowManager* window_manager,
229 FlutterWindowsEngine* engine,
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
250 view_controller_ =
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
316 HostWindow::~HostWindow() {
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.
322 SetLastError(ERROR_SUCCESS);
323 }
324 }
325 }
326
327 HostWindow* HostWindow::GetThisFromHandle(HWND hwnd) {
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
342 HWND HostWindow::GetWindowHandle() const {
343 return window_handle_;
344 }
345
346 void HostWindow::FocusRootViewOf(HostWindow* window) {
347 auto child_content = window->view_controller_->view()->GetWindowHandle();
348 if (window != nullptr && child_content != nullptr) {
349 SetFocus(child_content);
350 }
351 };
352
353 LRESULT HostWindow::WndProc(HWND hwnd,
354 UINT message,
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
370 LRESULT HostWindow::HandleMessage(HWND hwnd,
371 UINT message,
372 WPARAM wparam,
373 LPARAM lparam) {
374 auto result = engine_->window_proc_delegate_manager()->OnTopLevelWindowProc(
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
479 void HostWindow::SetContentSize(const WindowSizeRequest& size) {
480 if (!size.has_preferred_view_size) {
481 return;
482 }
483
484 if (GetFullscreen()) {
485 std::optional<Size> const window_size = GetWindowSizeForClientSize(
486 *engine_->windows_proc_table(),
487 Size(size.preferred_view_width, size.preferred_view_height),
488 box_constraints_.smallest(), box_constraints_.biggest(),
489 saved_window_info_.style, saved_window_info_.ex_style, nullptr);
490 if (!window_size) {
491 return;
492 }
493
494 saved_window_info_.client_size =
495 ActualWindowSize{.width = size.preferred_view_width,
496 .height = size.preferred_view_height};
497 saved_window_info_.rect.right =
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(
506 *engine_->windows_proc_table(),
507 Size(size.preferred_view_width, size.preferred_view_height),
508 box_constraints_.smallest(), box_constraints_.biggest(),
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
521 void HostWindow::SetConstraints(const WindowConstraints& constraints) {
522 box_constraints_ = FromWindowConstraints(constraints);
523
524 if (GetFullscreen()) {
525 std::optional<Size> const window_size = GetWindowSizeForClientSize(
526 *engine_->windows_proc_table(),
527 Size(saved_window_info_.client_size.width,
528 saved_window_info_.client_size.height),
529 box_constraints_.smallest(), box_constraints_.biggest(),
530 saved_window_info_.style, saved_window_info_.ex_style, nullptr);
531 if (!window_size) {
532 return;
533 }
534
535 saved_window_info_.rect.right =
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,
546 box_constraints_.smallest(), box_constraints_.biggest(),
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
562 void HostWindow::SetFullscreen(
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);
577 saved_window_info_.client_size = GetWindowContentSize(window_handle_);
578 saved_window_info_.dpi = GetDpiForHWND(window_handle_);
579 saved_window_info_.monitor =
580 MonitorFromWindow(window_handle_, MONITOR_DEFAULTTONEAREST);
581 saved_window_info_.monitor_info.cbSize =
582 sizeof(saved_window_info_.monitor_info);
583 GetMonitorInfo(saved_window_info_.monitor,
584 &saved_window_info_.monitor_info);
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 ||
653 !AreRectsEqual(saved_window_info_.monitor_info.rcWork,
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
708 bool HostWindow::GetFullscreen() const {
709 return is_fullscreen_;
710 }
711
712 ActualWindowSize HostWindow::GetWindowContentSize(HWND hwnd) {
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
725 std::optional<Size> HostWindow::GetWindowSizeForClientSize(
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
772 void HostWindow::EnableRecursively(bool enable) {
773 EnableWindow(window_handle_, enable);
774
775 for (HostWindow* const owned : GetOwnedWindows()) {
776 owned->EnableRecursively(enable);
777 }
778 }
779
780 HostWindow* HostWindow::FindFirstEnabledDescendant() const {
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
794 std::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
817 HostWindow* HostWindow::GetOwnerWindow() const {
818 if (HWND const owner_window_handle = GetWindow(GetWindowHandle(), GW_OWNER)) {
819 return GetThisFromHandle(owner_window_handle);
820 }
821 return nullptr;
822 };
823
824 void HostWindow::DisableRecursively() {
825 // Disable the window itself.
826 EnableWindow(window_handle_, false);
827
828 for (HostWindow* const owned : GetOwnedWindows()) {
829 owned->DisableRecursively();
830 }
831 }
832
833 void HostWindow::UpdateModalStateLayer() {
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
flutter::FlutterWindowsEngine
Definition: flutter_windows_engine.h:92
flutter::FlutterWindowsEngine::windows_proc_table
std::shared_ptr< WindowsProcTable > windows_proc_table()
Definition: flutter_windows_engine.h:316
flutter::FlutterWindowsEngine::UpdateAccessibilityFeatures
void UpdateAccessibilityFeatures()
Definition: flutter_windows_engine.cc:1017
flutter::FlutterWindowsEngine::window_proc_delegate_manager
WindowProcDelegateManager * window_proc_delegate_manager()
Definition: flutter_windows_engine.h:182
flutter::FlutterWindowsEngine::display_manager
std::shared_ptr< DisplayManagerWin32 > display_manager()
Definition: flutter_windows_engine.h:163
flutter::FlutterWindowsEngine::running
virtual bool running() const
Definition: flutter_windows_engine.h:125
flutter::FlutterWindowsEngine::CreateView
std::unique_ptr< FlutterWindowsView > CreateView(std::unique_ptr< WindowBindingHandler > window)
Definition: flutter_windows_engine.cc:526
flutter::HostWindowDialog
Definition: host_window_dialog.h:11
flutter::HostWindow
Definition: host_window.h:27
flutter::HostWindow::task_bar_list_
Microsoft::WRL::ComPtr< ITaskbarList2 > task_bar_list_
Definition: host_window.h:214
flutter::HostWindow::GetWindowHandle
HWND GetWindowHandle() const
Definition: host_window.cc:342
flutter::HostWindow::box_constraints_
BoxConstraints box_constraints_
Definition: host_window.h:202
flutter::HostWindow::window_handle_
HWND window_handle_
Definition: host_window.h:199
flutter::HostWindow::saved_window_info_
SavedWindowInfo saved_window_info_
Definition: host_window.h:211
flutter::HostWindow::is_fullscreen_
bool is_fullscreen_
Definition: host_window.h:208
flutter::HostWindow::view_controller_
std::unique_ptr< FlutterWindowsViewController > view_controller_
Definition: host_window.h:193
flutter::HostWindow::DisableRecursively
void DisableRecursively()
Definition: host_window.cc:824
flutter::HostWindow::WndProc
static LRESULT WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
Definition: host_window.cc:353
flutter::HostWindow::GetOwnerWindow
HostWindow * GetOwnerWindow() const
Definition: host_window.cc:817
flutter::HostWindow::GetWindowContentSize
static ActualWindowSize GetWindowContentSize(HWND hwnd)
Definition: host_window.cc:712
flutter::HostWindow::EnableRecursively
void EnableRecursively(bool enable)
Definition: host_window.cc:772
flutter::HostWindow::SetContentSize
void SetContentSize(const WindowSizeRequest &size)
Definition: host_window.cc:479
flutter::HostWindow::is_being_destroyed_
bool is_being_destroyed_
Definition: host_window.h:205
flutter::HostWindow::UpdateModalStateLayer
void UpdateModalStateLayer()
Definition: host_window.cc:833
flutter::HostWindow::FocusRootViewOf
static void FocusRootViewOf(HostWindow *window)
Definition: host_window.cc:346
flutter::HostWindow::HostWindow
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)
Definition: host_window.cc:228
flutter::HostWindow::engine_
FlutterWindowsEngine * engine_
Definition: host_window.h:188
flutter::HostWindow::~HostWindow
virtual ~HostWindow()
Definition: host_window.cc:316
flutter::HostWindow::GetThisFromHandle
static HostWindow * GetThisFromHandle(HWND hwnd)
Definition: host_window.cc:327
flutter::HostWindow::GetOwnedWindows
std::vector< HostWindow * > GetOwnedWindows() const
Definition: host_window.cc:794
flutter::HostWindow::GetFullscreen
virtual bool GetFullscreen() const
Definition: host_window.cc:708
flutter::HostWindow::SetConstraints
void SetConstraints(const WindowConstraints &constraints)
Definition: host_window.cc:521
flutter::HostWindow::SetFullscreen
virtual void SetFullscreen(bool fullscreen, std::optional< FlutterEngineDisplayId > display_id)
Definition: host_window.cc:562
flutter::HostWindow::HandleMessage
virtual LRESULT HandleMessage(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
Definition: host_window.cc:370
flutter::HostWindow::CreateDialogWindow
static std::unique_ptr< HostWindow > CreateDialogWindow(WindowManager *window_manager, FlutterWindowsEngine *engine, const WindowSizeRequest &preferred_size, const WindowConstraints &preferred_constraints, LPCWSTR title, HWND parent)
Definition: host_window.cc:215
flutter::HostWindow::GetWindowSizeForClientSize
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)
Definition: host_window.cc:725
flutter::HostWindow::CreateRegularWindow
static std::unique_ptr< HostWindow > CreateRegularWindow(WindowManager *window_manager, FlutterWindowsEngine *engine, const WindowSizeRequest &preferred_size, const WindowConstraints &preferred_constraints, LPCWSTR title)
Definition: host_window.cc:204
flutter::HostWindow::FindFirstEnabledDescendant
HostWindow * FindFirstEnabledDescendant() const
Definition: host_window.cc:780
flutter::HostWindowRegular
Definition: host_window_regular.h:11
flutter::WindowManager
Definition: window_manager.h:84
flutter::WindowProcDelegateManager::OnTopLevelWindowProc
std::optional< LRESULT > OnTopLevelWindowProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) const
Definition: window_proc_delegate_manager.cc:37
flutter::WindowsProcTable
Definition: windows_proc_table.h:18
flutter::WindowsProcTable::AdjustWindowRectExForDpi
virtual BOOL AdjustWindowRectExForDpi(LPRECT lpRect, DWORD dwStyle, BOOL bMenu, DWORD dwExStyle, UINT dpi) const
Definition: windows_proc_table.cc:110
flutter::WindowsProcTable::EnableNonClientDpiScaling
virtual BOOL EnableNonClientDpiScaling(HWND hwnd) const
Definition: windows_proc_table.cc:79
flutter::WindowsProcTable::DwmExtendFrameIntoClientArea
virtual HRESULT DwmExtendFrameIntoClientArea(HWND hwnd, const MARGINS *pMarInset) const
Definition: windows_proc_table.cc:97
flutter::WindowsProcTable::DwmSetWindowAttribute
virtual HRESULT DwmSetWindowAttribute(HWND hwnd, DWORD dwAttribute, LPCVOID pvAttribute, DWORD cbAttribute) const
Definition: windows_proc_table.cc:103
flutter::WindowsProcTable::SetWindowCompositionAttribute
virtual BOOL SetWindowCompositionAttribute(HWND hwnd, WINDOWCOMPOSITIONATTRIBDATA *data) const
Definition: windows_proc_table.cc:87
display_manager.h
dpi_utils.h
flutter_window.h
FlutterDesktopGetDpiForHWND
UINT FlutterDesktopGetDpiForHWND(HWND hwnd)
Definition: flutter_windows.cc:323
flutter_windows_view_controller.h
DWMWA_USE_IMMERSIVE_DARK_MODE
#define DWMWA_USE_IMMERSIVE_DARK_MODE
Definition: host_window.cc:110
host_window.h
host_window_dialog.h
host_window_regular.h
content
union flutter::testing::@97::KeyboardChange::@0 content
message
Win32Message message
Definition: keyboard_unittests.cc:137
flutter
Definition: accessibility_bridge_windows.cc:11
flutter::GetDpiForHWND
UINT GetDpiForHWND(HWND hwnd)
Definition: dpi_utils.cc:128
flutter::WindowArchetype
WindowArchetype
Definition: windowing.h:11
flutter::RectWidth
LONG RectWidth(const RECT &r)
Definition: rect_helper.h:11
flutter::AreRectsEqual
bool AreRectsEqual(const RECT &a, const RECT &b)
Definition: rect_helper.h:19
flutter::RectHeight
LONG RectHeight(const RECT &r)
Definition: rect_helper.h:15
rect_helper.h
flutter::ActualWindowSize
Definition: window_manager.h:71
flutter::ActualWindowSize::width
double width
Definition: window_manager.h:72
flutter::ActualWindowSize::height
double height
Definition: window_manager.h:73
flutter::HostWindow::SavedWindowInfo::client_size
ActualWindowSize client_size
Definition: host_window.h:114
flutter::HostWindow::SavedWindowInfo::dpi
int dpi
Definition: host_window.h:115
flutter::HostWindow::SavedWindowInfo::ex_style
LONG ex_style
Definition: host_window.h:112
flutter::HostWindow::SavedWindowInfo::style
LONG style
Definition: host_window.h:111
flutter::HostWindow::SavedWindowInfo::monitor_info
MONITORINFO monitor_info
Definition: host_window.h:117
flutter::HostWindow::SavedWindowInfo::monitor
HMONITOR monitor
Definition: host_window.h:116
flutter::HostWindow::SavedWindowInfo::rect
RECT rect
Definition: host_window.h:113
flutter::WindowConstraints
Definition: window_manager.h:33
flutter::WindowConstraints::view_max_width
double view_max_width
Definition: window_manager.h:37
flutter::WindowConstraints::view_min_width
double view_min_width
Definition: window_manager.h:35
flutter::WindowConstraints::has_view_constraints
bool has_view_constraints
Definition: window_manager.h:34
flutter::WindowConstraints::view_max_height
double view_max_height
Definition: window_manager.h:38
flutter::WindowConstraints::view_min_height
double view_min_height
Definition: window_manager.h:36
flutter::WindowSizeRequest
Definition: window_manager.h:26
flutter::WindowSizeRequest::preferred_view_height
double preferred_view_height
Definition: window_manager.h:29
flutter::WindowSizeRequest::preferred_view_width
double preferred_view_width
Definition: window_manager.h:28
flutter::WindowSizeRequest::has_preferred_view_size
bool has_preferred_view_size
Definition: window_manager.h:27
flutter::WindowsProcTable::WINDOWCOMPOSITIONATTRIBDATA
Definition: windows_proc_table.h:22
flutter::WindowsProcTable::WINDOWCOMPOSITIONATTRIBDATA::Attrib
WINDOWCOMPOSITIONATTRIB Attrib
Definition: windows_proc_table.h:23
wchar_util.h
window_manager.h