19 constexpr
wchar_t kWindowClassName[] = L
"FLUTTER_HOST_WINDOW";
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);
27 return flutter::Size(std::clamp(size.width(), 0.0, virtual_screen_width),
28 std::clamp(size.height(), 0.0, virtual_screen_height));
31 void EnableTransparentWindowBackground(HWND hwnd,
33 enum ACCENT_STATE { ACCENT_DISABLED = 0 };
35 struct ACCENT_POLICY {
36 ACCENT_STATE AccentState;
43 ACCENT_POLICY accent = {ACCENT_DISABLED, 2,
static_cast<DWORD
>(0), 0};
46 flutter::WindowsProcTable::WINDOWCOMPOSITIONATTRIB::WCA_ACCENT_POLICY,
48 .cbData =
sizeof(accent)};
53 MARGINS
const margins = {-1};
62 std::string GetLastErrorAsString() {
63 LPWSTR message_buffer =
nullptr;
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;
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);
85 LocalFree(message_buffer);
87 std::ostringstream oss;
88 oss <<
"Format message failed with 0x" << std::hex << std::setfill(
'0')
89 << std::setw(8) << GetLastError();
101 std::optional<flutter::Size> GetWindowSizeForClientSize(
103 flutter::Size
const& client_size,
104 std::optional<flutter::Size> smallest,
105 std::optional<flutter::Size> biggest,
107 DWORD extended_window_style,
110 double const scale_factor =
111 static_cast<double>(dpi) / USER_DEFAULT_SCREEN_DPI;
113 .right =
static_cast<LONG
>(client_size.width() * scale_factor),
114 .bottom =
static_cast<LONG
>(client_size.height() * scale_factor)};
117 extended_window_style, dpi)) {
118 FML_LOG(ERROR) <<
"Failed to run AdjustWindowRectExForDpi: "
119 << GetLastErrorAsString();
123 double width =
static_cast<double>(rect.right - rect.left);
124 double height =
static_cast<double>(rect.bottom - rect.top);
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);
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());
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());
145 return flutter::Size{width, height};
150 bool IsClassRegistered(LPCWSTR class_name) {
151 WNDCLASSEX window_class = {};
152 return GetClassInfoEx(GetModuleHandle(
nullptr), class_name, &window_class) !=
162 #ifndef DWMWA_USE_IMMERSIVE_DARK_MODE
163 #define DWMWA_USE_IMMERSIVE_DARK_MODE 20
167 void UpdateTheme(HWND window) {
169 const wchar_t kGetPreferredBrightnessRegKey[] =
170 L
"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
171 const wchar_t kGetPreferredBrightnessRegValue[] = L
"AppsUseLightTheme";
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);
182 if (result == ERROR_SUCCESS) {
183 BOOL enable_dark_mode = light_mode == 0;
185 &enable_dark_mode,
sizeof(enable_dark_mode));
190 void SetChildContent(HWND
content, HWND window) {
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);
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;
219 *origin = std::min(dst_origin + dst_size, *origin + *size) - *size;
222 RECT AdjustToFit(
const RECT& parent,
const RECT& child) {
223 auto new_x = child.left;
224 auto new_y = child.top;
231 result.right = new_x + new_width;
233 result.bottom = new_y + new_height;
237 flutter::BoxConstraints FromWindowConstraints(
239 std::optional<flutter::Size> smallest, biggest;
250 return flutter::BoxConstraints(smallest, biggest);
263 DWORD window_style = WS_OVERLAPPEDWINDOW;
264 DWORD extended_window_style = 0;
265 auto const box_constraints = FromWindowConstraints(preferred_constraints);
273 Rect
const initial_window_rect = [&]() -> Rect {
274 std::optional<Size>
const window_size = GetWindowSizeForClientSize(
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}};
285 auto view_window = std::make_unique<FlutterWindow>(
286 initial_window_rect.width(), initial_window_rect.height(),
289 std::unique_ptr<FlutterWindowsView> view =
291 if (view ==
nullptr) {
292 FML_LOG(ERROR) <<
"Failed to create view";
296 std::unique_ptr<FlutterWindowsViewController> view_controller =
297 std::make_unique<FlutterWindowsViewController>(
nullptr, std::move(view));
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);
313 LoadIcon(window_class.hInstance, MAKEINTRESOURCE(idi_app_icon));
314 if (!window_class.hIcon) {
315 window_class.hIcon = LoadIcon(
nullptr, IDI_APPLICATION);
317 window_class.hCursor = LoadCursor(
nullptr, IDC_ARROW);
318 window_class.lpszClassName = kWindowClassName;
320 if (!RegisterClassEx(&window_class)) {
321 FML_LOG(ERROR) <<
"Cannot register window class "
323 << GetLastErrorAsString();
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,
335 FML_LOG(ERROR) <<
"Cannot create window: " << GetLastErrorAsString();
344 DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &frame_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);
356 SetChildContent(view_controller->view()->GetWindowHandle(), hwnd);
363 ShowWindow(hwnd, SW_SHOWNORMAL);
364 return std::unique_ptr<HostWindow>(
366 std::move(view_controller), box_constraints, hwnd));
369 HostWindow::HostWindow(
373 std::unique_ptr<FlutterWindowsViewController> view_controller,
374 const BoxConstraints& box_constraints,
376 : window_manager_(window_manager),
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));
386 if (view_controller_) {
389 if (!UnregisterClass(kWindowClassName, GetModuleHandle(
nullptr))) {
391 SetLastError(ERROR_SUCCESS);
397 return reinterpret_cast<HostWindow*
>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
401 return window_handle_;
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);
411 LRESULT HostWindow::WndProc(HWND hwnd,
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);
422 return window->HandleMessage(hwnd,
message, wparam, lparam);
425 return DefWindowProc(hwnd,
message, wparam, lparam);
428 LRESULT HostWindow::HandleMessage(HWND hwnd,
433 window_handle_,
message, wparam, lparam);
439 case WM_DPICHANGED: {
440 auto*
const new_scaled_window_rect =
reinterpret_cast<RECT*
>(lparam);
442 new_scaled_window_rect->right - new_scaled_window_rect->left;
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);
451 case WM_GETMINMAXINFO: {
453 GetWindowRect(hwnd, &window_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);
462 double const scale_factor =
463 static_cast<double>(dpi) / USER_DEFAULT_SCREEN_DPI;
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 +
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 +
478 info->ptMaxTrackSize.x = max_physical_size.width();
479 info->ptMaxTrackSize.y = max_physical_size.height();
484 auto child_content = view_controller_->view()->GetWindowHandle();
485 if (child_content !=
nullptr) {
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);
500 case WM_MOUSEACTIVATE:
504 case WM_DWMCOLORIZATIONCOLORCHANGED:
512 if (!view_controller_) {
516 return DefWindowProc(hwnd,
message, wparam, lparam);
525 std::optional<Size>
const window_size = GetWindowSizeForClientSize(
528 box_constraints_.smallest(), box_constraints_.biggest(),
529 saved_window_info_.style, saved_window_info_.ex_style,
nullptr);
534 saved_window_info_.client_size =
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());
542 WINDOWINFO window_info = {.cbSize =
sizeof(WINDOWINFO)};
543 GetWindowInfo(window_handle_, &window_info);
545 std::optional<Size>
const window_size = GetWindowSizeForClientSize(
548 box_constraints_.smallest(), box_constraints_.biggest(),
549 window_info.dwStyle, window_info.dwExStyle,
nullptr);
555 SetWindowPos(window_handle_, NULL, 0, 0, window_size->width(),
556 window_size->height(),
557 SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
562 box_constraints_ = FromWindowConstraints(constraints);
565 std::optional<Size>
const window_size = GetWindowSizeForClientSize(
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);
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());
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(
586 box_constraints_.smallest(), box_constraints_.biggest(),
587 window_info.dwStyle, window_info.dwExStyle,
nullptr);
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);
604 std::optional<FlutterEngineDisplayId> display_id) {
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;
616 ::GetWindowRect(window_handle_, &saved_window_info_.rect);
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);
630 MonitorFromWindow(window_handle_, MONITOR_DEFAULTTONEAREST);
632 if (
auto const display =
634 monitor =
reinterpret_cast<HMONITOR
>(display->display_id);
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 "
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);
651 SetWindowLong(window_handle_, GWL_STYLE,
652 saved_window_info_.style & ~(WS_CAPTION | WS_THICKFRAME));
654 window_handle_, GWL_EXSTYLE,
655 saved_window_info_.ex_style & ~(WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE |
656 WS_EX_CLIENTEDGE | WS_EX_STATICEDGE));
661 SetWindowPos(window_handle_, NULL, 0, 0, 0, 0,
662 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
664 SetWindowPos(window_handle_,
nullptr, monitor_info.rcMonitor.left,
665 monitor_info.rcMonitor.top, width, height,
666 SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
672 SetWindowLong(window_handle_, GWL_STYLE,
673 saved_window_info_.style | WS_VISIBLE);
674 SetWindowLong(window_handle_, GWL_EXSTYLE, saved_window_info_.ex_style);
679 SetWindowPos(window_handle_, NULL, 0, 0, 0, 0,
680 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
683 MonitorFromRect(&saved_window_info_.rect, MONITOR_DEFAULTTONEAREST);
684 MONITORINFO monitor_info;
685 monitor_info.cbSize =
sizeof(monitor_info);
686 GetMonitorInfo(monitor, &monitor_info);
688 auto window_rect = saved_window_info_.rect;
692 if (monitor != saved_window_info_.monitor ||
694 monitor_info.rcWork)) {
695 window_rect = AdjustToFit(monitor_info.rcWork, window_rect);
699 SetWindowPos(window_handle_,
nullptr, window_rect.left, window_rect.top,
701 SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
703 if (final_dpi != saved_window_info_.dpi || final_dpi != fullscreen_dpi) {
711 if (final_dpi != saved_window_info_.dpi) {
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);
721 SetWindowPos(window_handle_,
nullptr, window_rect.left, window_rect.top,
723 SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
727 if (!task_bar_list_) {
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;
741 if (task_bar_list_) {
742 task_bar_list_->MarkFullscreenWindow(window_handle_, !!fullscreen);
745 is_fullscreen_ = fullscreen;
749 return is_fullscreen_;
754 GetClientRect(hwnd, &rect);
756 static_cast<double>(USER_DEFAULT_SCREEN_DPI);
757 double const width = rect.right / dpr;
758 double const height = rect.bottom / dpr;
760 .width = rect.right / dpr,
761 .height = rect.bottom / dpr,
std::shared_ptr< WindowsProcTable > windows_proc_table()
void UpdateAccessibilityFeatures()
WindowProcDelegateManager * window_proc_delegate_manager()
std::shared_ptr< DisplayManagerWin32 > display_manager()
virtual bool running() const
std::unique_ptr< FlutterWindowsView > CreateView(std::unique_ptr< WindowBindingHandler > window)
HWND GetWindowHandle() const
static std::unique_ptr< HostWindow > CreateRegularWindow(WindowManager *controller, FlutterWindowsEngine *engine, const WindowSizeRequest &preferred_size, const WindowConstraints &preferred_constraints, LPCWSTR title)
static ActualWindowSize GetWindowContentSize(HWND hwnd)
void SetContentSize(const WindowSizeRequest &size)
static HostWindow * GetThisFromHandle(HWND hwnd)
bool GetFullscreen() const
void SetConstraints(const WindowConstraints &constraints)
void SetFullscreen(bool fullscreen, std::optional< FlutterEngineDisplayId > display_id)
std::optional< LRESULT > OnTopLevelWindowProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) const
virtual BOOL AdjustWindowRectExForDpi(LPRECT lpRect, DWORD dwStyle, BOOL bMenu, DWORD dwExStyle, UINT dpi) const
virtual HRESULT DwmExtendFrameIntoClientArea(HWND hwnd, const MARGINS *pMarInset) const
virtual HRESULT DwmSetWindowAttribute(HWND hwnd, DWORD dwAttribute, LPCVOID pvAttribute, DWORD cbAttribute) const
virtual BOOL SetWindowCompositionAttribute(HWND hwnd, WINDOWCOMPOSITIONATTRIBDATA *data) const
UINT FlutterDesktopGetDpiForHWND(HWND hwnd)
#define DWMWA_USE_IMMERSIVE_DARK_MODE
union flutter::testing::@93::KeyboardChange::@0 content
UINT GetDpiForHWND(HWND hwnd)
std::string WCharBufferToString(const wchar_t *wstr)
Convert a null terminated wchar_t buffer to a std::string using Windows utilities.
LONG RectWidth(const RECT &r)
bool AreRectsEqual(const RECT &a, const RECT &b)
LONG RectHeight(const RECT &r)
bool has_view_constraints
double preferred_view_height
double preferred_view_width
bool has_preferred_view_size
WINDOWCOMPOSITIONATTRIB Attrib