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