From 116a72ed56c9649915574145ae71ee0658e7116c Mon Sep 17 00:00:00 2001 From: Matej Knopp Date: Mon, 12 May 2025 14:18:55 +0200 Subject: [PATCH 01/44] multiwindow: engine --- .../flutter/ci/licenses_golden/excluded_files | 1 + .../ci/licenses_golden/licenses_flutter | 20 + .../flutter/shell/platform/common/BUILD.gn | 13 + .../flutter/shell/platform/common/geometry.h | 1 + .../shell/platform/common/isolate_scope.cc | 40 ++ .../shell/platform/common/isolate_scope.h | 44 ++ .../flutter/shell/platform/common/windowing.h | 30 + .../shell/platform/darwin/macos/BUILD.gn | 7 + .../macos/framework/Source/FlutterEngine.mm | 145 ++++- .../framework/Source/FlutterEngine_Internal.h | 21 + .../Source/FlutterPlatformViewController.h | 2 +- .../framework/Source/FlutterVSyncWaiter.mm | 9 +- .../framework/Source/FlutterViewController.mm | 7 +- .../Source/FlutterViewController_Internal.h | 8 + .../Source/FlutterWindowController.h | 83 +++ .../Source/FlutterWindowController.mm | 236 ++++++++ .../Source/FlutterWindowControllerTest.mm | 189 ++++++ .../Source/fixtures/flutter_desktop_test.dart | 8 + .../flutter/shell/platform/windows/BUILD.gn | 6 + .../shell/platform/windows/fixtures/main.dart | 5 + .../platform/windows/flutter_host_window.cc | 569 ++++++++++++++++++ .../platform/windows/flutter_host_window.h | 95 +++ .../windows/flutter_host_window_controller.cc | 193 ++++++ .../windows/flutter_host_window_controller.h | 142 +++++ ...lutter_host_window_controller_unittests.cc | 172 ++++++ .../windows/flutter_windows_engine.cc | 31 +- .../platform/windows/flutter_windows_engine.h | 13 + 27 files changed, 2072 insertions(+), 18 deletions(-) create mode 100644 engine/src/flutter/shell/platform/common/isolate_scope.cc create mode 100644 engine/src/flutter/shell/platform/common/isolate_scope.h create mode 100644 engine/src/flutter/shell/platform/common/windowing.h create mode 100644 engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowController.h create mode 100644 engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowController.mm create mode 100644 engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowControllerTest.mm create mode 100644 engine/src/flutter/shell/platform/windows/flutter_host_window.cc create mode 100644 engine/src/flutter/shell/platform/windows/flutter_host_window.h create mode 100644 engine/src/flutter/shell/platform/windows/flutter_host_window_controller.cc create mode 100644 engine/src/flutter/shell/platform/windows/flutter_host_window_controller.h create mode 100644 engine/src/flutter/shell/platform/windows/flutter_host_window_controller_unittests.cc diff --git a/engine/src/flutter/ci/licenses_golden/excluded_files b/engine/src/flutter/ci/licenses_golden/excluded_files index d6c6f639e005d..a15699bb42d62 100644 --- a/engine/src/flutter/ci/licenses_golden/excluded_files +++ b/engine/src/flutter/ci/licenses_golden/excluded_files @@ -416,6 +416,7 @@ ../../../flutter/shell/platform/windows/direct_manipulation_unittests.cc ../../../flutter/shell/platform/windows/dpi_utils_unittests.cc ../../../flutter/shell/platform/windows/fixtures +../../../flutter/shell/platform/windows/flutter_host_window_controller_unittests.cc ../../../flutter/shell/platform/windows/flutter_project_bundle_unittests.cc ../../../flutter/shell/platform/windows/flutter_window_unittests.cc ../../../flutter/shell/platform/windows/flutter_windows_engine_unittests.cc diff --git a/engine/src/flutter/ci/licenses_golden/licenses_flutter b/engine/src/flutter/ci/licenses_golden/licenses_flutter index e6fc889d9e26b..c7877ffd2944d 100644 --- a/engine/src/flutter/ci/licenses_golden/licenses_flutter +++ b/engine/src/flutter/ci/licenses_golden/licenses_flutter @@ -52793,6 +52793,8 @@ ORIGIN: ../../../flutter/shell/platform/common/flutter_platform_node_delegate.h ORIGIN: ../../../flutter/shell/platform/common/geometry.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/common/incoming_message_dispatcher.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/common/incoming_message_dispatcher.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/common/isolate_scope.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/common/isolate_scope.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/common/json_message_codec.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/common/json_message_codec.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/common/json_method_codec.cc + ../../../flutter/LICENSE @@ -52813,6 +52815,7 @@ ORIGIN: ../../../flutter/shell/platform/common/text_input_model.cc + ../../../fl ORIGIN: ../../../flutter/shell/platform/common/text_input_model.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/common/text_range.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/common/InternalFlutterSwiftCommon-Bridging-Header.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/common/windowing.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/common/availability_version_check.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/common/availability_version_check.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/common/buffer_conversions.h + ../../../flutter/LICENSE @@ -53091,6 +53094,9 @@ ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterVie ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterViewEngineProviderTest.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterViewProvider.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterViewTest.mm + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowController.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowController.mm + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowControllerTest.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/KeyCodeMap.g.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/KeyCodeMapTest.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/KeyCodeMap_Internal.h + ../../../flutter/LICENSE @@ -53549,6 +53555,10 @@ ORIGIN: ../../../flutter/shell/platform/windows/external_texture_d3d.h + ../../. ORIGIN: ../../../flutter/shell/platform/windows/external_texture_pixelbuffer.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/windows/external_texture_pixelbuffer.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/windows/flutter_desktop_messenger.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/windows/flutter_host_window.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/windows/flutter_host_window.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/windows/flutter_host_window_controller.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/windows/flutter_host_window_controller.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/windows/flutter_key_map.g.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/windows/flutter_platform_node_delegate_windows.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/windows/flutter_platform_node_delegate_windows.h + ../../../flutter/LICENSE @@ -55832,6 +55842,8 @@ FILE: ../../../flutter/shell/platform/common/flutter_platform_node_delegate.h FILE: ../../../flutter/shell/platform/common/geometry.h FILE: ../../../flutter/shell/platform/common/incoming_message_dispatcher.cc FILE: ../../../flutter/shell/platform/common/incoming_message_dispatcher.h +FILE: ../../../flutter/shell/platform/common/isolate_scope.cc +FILE: ../../../flutter/shell/platform/common/isolate_scope.h FILE: ../../../flutter/shell/platform/common/json_message_codec.cc FILE: ../../../flutter/shell/platform/common/json_message_codec.h FILE: ../../../flutter/shell/platform/common/json_method_codec.cc @@ -55854,6 +55866,7 @@ FILE: ../../../flutter/shell/platform/common/text_range.h FILE: ../../../flutter/shell/platform/darwin/common/InternalFlutterSwiftCommon-Bridging-Header.h FILE: ../../../flutter/shell/platform/darwin/common/SwiftTestingMain.swift FILE: ../../../flutter/shell/platform/darwin/common/SwiftTestingRunner.swift +FILE: ../../../flutter/shell/platform/common/windowing.h FILE: ../../../flutter/shell/platform/darwin/common/availability_version_check.cc FILE: ../../../flutter/shell/platform/darwin/common/availability_version_check.h FILE: ../../../flutter/shell/platform/darwin/common/buffer_conversions.h @@ -56142,6 +56155,9 @@ FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterViewE FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterViewEngineProviderTest.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterViewProvider.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterViewTest.mm +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowController.h +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowController.mm +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowControllerTest.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/KeyCodeMap.g.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/KeyCodeMapTest.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/KeyCodeMap_Internal.h @@ -56606,6 +56622,10 @@ FILE: ../../../flutter/shell/platform/windows/external_texture_d3d.h FILE: ../../../flutter/shell/platform/windows/external_texture_pixelbuffer.cc FILE: ../../../flutter/shell/platform/windows/external_texture_pixelbuffer.h FILE: ../../../flutter/shell/platform/windows/flutter_desktop_messenger.h +FILE: ../../../flutter/shell/platform/windows/flutter_host_window.cc +FILE: ../../../flutter/shell/platform/windows/flutter_host_window.h +FILE: ../../../flutter/shell/platform/windows/flutter_host_window_controller.cc +FILE: ../../../flutter/shell/platform/windows/flutter_host_window_controller.h FILE: ../../../flutter/shell/platform/windows/flutter_key_map.g.cc FILE: ../../../flutter/shell/platform/windows/flutter_platform_node_delegate_windows.cc FILE: ../../../flutter/shell/platform/windows/flutter_platform_node_delegate_windows.h diff --git a/engine/src/flutter/shell/platform/common/BUILD.gn b/engine/src/flutter/shell/platform/common/BUILD.gn index 5ebfb2236d2c5..b27be6bfa0388 100644 --- a/engine/src/flutter/shell/platform/common/BUILD.gn +++ b/engine/src/flutter/shell/platform/common/BUILD.gn @@ -57,6 +57,16 @@ source_set("common_cpp_input") { deps = [ "//flutter/fml:fml" ] } +source_set("common_cpp_isolate_scope") { + public = [ "isolate_scope.h" ] + sources = [ "isolate_scope.cc" ] + + deps = [ + "$dart_src/runtime:dart_api", + "//flutter/fml:fml", + ] +} + source_set("common_cpp_enums") { public = [ "app_lifecycle_state.h", @@ -142,11 +152,14 @@ source_set("common_cpp_core") { public = [ "geometry.h", "path_utils.h", + "windowing.h", ] sources = [ "path_utils.cc" ] public_configs = [ "//flutter:config" ] + + deps = [ "//flutter/fml:fml" ] } if (enable_unittests) { diff --git a/engine/src/flutter/shell/platform/common/geometry.h b/engine/src/flutter/shell/platform/common/geometry.h index 4d6e8daded041..89441f9710bba 100644 --- a/engine/src/flutter/shell/platform/common/geometry.h +++ b/engine/src/flutter/shell/platform/common/geometry.h @@ -45,6 +45,7 @@ class Size { bool operator==(const Size& other) const { return width_ == other.width_ && height_ == other.height_; } + bool operator!=(const Size& other) const { return !(*this == other); } private: double width_ = 0.0; diff --git a/engine/src/flutter/shell/platform/common/isolate_scope.cc b/engine/src/flutter/shell/platform/common/isolate_scope.cc new file mode 100644 index 0000000000000..9d1f537876f1e --- /dev/null +++ b/engine/src/flutter/shell/platform/common/isolate_scope.cc @@ -0,0 +1,40 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/shell/platform/common/isolate_scope.h" + +namespace flutter { + +Isolate Isolate::Current() { + Dart_Isolate isolate = Dart_CurrentIsolate(); + return Isolate(isolate); +} + +IsolateScope::IsolateScope(const Isolate& isolate) { + isolate_ = isolate.isolate_; + previous_ = Dart_CurrentIsolate(); + if (previous_ == isolate_) { + return; + } + if (previous_) { + Dart_ExitIsolate(); + } + Dart_EnterIsolate(isolate_); +}; + +IsolateScope::~IsolateScope() { + Dart_Isolate current = Dart_CurrentIsolate(); + FML_DCHECK(!current || current == isolate_); + if (previous_ == isolate_) { + return; + } + if (current) { + Dart_ExitIsolate(); + } + if (previous_) { + Dart_EnterIsolate(previous_); + } +} + +} // namespace flutter diff --git a/engine/src/flutter/shell/platform/common/isolate_scope.h b/engine/src/flutter/shell/platform/common/isolate_scope.h new file mode 100644 index 0000000000000..e4d84889466ac --- /dev/null +++ b/engine/src/flutter/shell/platform/common/isolate_scope.h @@ -0,0 +1,44 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/fml/logging.h" +#include "third_party/dart/runtime/include/dart_api.h" + +#ifndef FLUTTER_SHELL_PLATFORM_COMMON_ISOLATE_SCOPE_H_ +#define FLUTTER_SHELL_PLATFORM_COMMON_ISOLATE_SCOPE_H_ + +namespace flutter { + +/// This class is a thin wrapper around dart isolate. It can be used +/// as argument to IsolateScope constructor to enter and exit the isolate. +class Isolate { + public: + static Isolate Current(); + + ~Isolate() = default; + + private: + explicit Isolate(Dart_Isolate isolate) : isolate_(isolate) { + FML_DCHECK(isolate_ != nullptr); + } + + friend class IsolateScope; + Dart_Isolate isolate_; +}; + +// Enters provided isolate for as long as the scope is alive. +class IsolateScope { + public: + explicit IsolateScope(const Isolate& isolate); + ~IsolateScope(); + + private: + Dart_Isolate isolate_; + Dart_Isolate previous_; + IsolateScope() = delete; + IsolateScope(IsolateScope const&) = delete; +}; +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_COMMON_ISOLATE_SCOPE_H_ diff --git a/engine/src/flutter/shell/platform/common/windowing.h b/engine/src/flutter/shell/platform/common/windowing.h new file mode 100644 index 0000000000000..e6cc8e4ef9cc3 --- /dev/null +++ b/engine/src/flutter/shell/platform/common/windowing.h @@ -0,0 +1,30 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_PLATFORM_COMMON_WINDOWING_H_ +#define FLUTTER_SHELL_PLATFORM_COMMON_WINDOWING_H_ + +namespace flutter { + +// Types of windows. +// The value must match value from WindowType in the Dart code. +enum class WindowArchetype { + // Regular top-level window. + kRegular, +}; + +// Possible states a window can be in. +// The values must match values from WindowState in the Dart code. +enum class WindowState { + // Normal state, neither maximized, nor minimized. + kRestored, + // Maximized, occupying the full screen but still showing the system UI. + kMaximized, + // Minimized and not visible on the screen. + kMinimized, +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_COMMON_WINDOWING_H_ diff --git a/engine/src/flutter/shell/platform/darwin/macos/BUILD.gn b/engine/src/flutter/shell/platform/darwin/macos/BUILD.gn index dbfec091477ba..4f445e75b0aef 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/BUILD.gn +++ b/engine/src/flutter/shell/platform/darwin/macos/BUILD.gn @@ -133,6 +133,8 @@ source_set("flutter_framework_source") { "framework/Source/FlutterViewEngineProvider.h", "framework/Source/FlutterViewEngineProvider.mm", "framework/Source/FlutterViewProvider.h", + "framework/Source/FlutterWindowController.h", + "framework/Source/FlutterWindowController.mm", "framework/Source/KeyCodeMap.g.mm", ] @@ -143,8 +145,10 @@ source_set("flutter_framework_source") { "//flutter/flow:flow", "//flutter/fml", "//flutter/shell/platform/common:common_cpp_accessibility", + "//flutter/shell/platform/common:common_cpp_core", "//flutter/shell/platform/common:common_cpp_enums", "//flutter/shell/platform/common:common_cpp_input", + "//flutter/shell/platform/common:common_cpp_isolate_scope", "//flutter/shell/platform/common:common_cpp_switches", "//flutter/shell/platform/darwin/common:availability_version_check", "//flutter/shell/platform/darwin/common:framework_common", @@ -238,6 +242,7 @@ executable("flutter_desktop_darwin_unittests") { "framework/Source/KeyCodeMapTest.mm", "framework/Source/TestFlutterPlatformView.h", "framework/Source/TestFlutterPlatformView.mm", + "framework/source/FlutterWindowControllerTest.mm", ] deps = [ @@ -245,7 +250,9 @@ executable("flutter_desktop_darwin_unittests") { ":flutter_framework_source", "//flutter/fml", "//flutter/shell/platform/common:common_cpp_accessibility", + "//flutter/shell/platform/common:common_cpp_core", "//flutter/shell/platform/common:common_cpp_enums", + "//flutter/shell/platform/common:common_cpp_isolate_scope", "//flutter/shell/platform/darwin/common:framework_common", "//flutter/shell/platform/darwin/common:test_utils_swift", "//flutter/shell/platform/darwin/graphics", diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm index 1deb212fb3f3c..20abea06fe654 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm @@ -452,6 +452,9 @@ @implementation FlutterEngine { // factories. Lifecycle is tied to the engine. FlutterPlatformViewController* _platformViewController; + // Used to manage Flutter windows. + FlutterWindowController* _windowController; + // A message channel for sending user settings to the flutter engine. FlutterBasicMessageChannel* _settingsChannel; @@ -483,8 +486,18 @@ @implementation FlutterEngine { // The text input plugin that handles text editing state for text fields. FlutterTextInputPlugin* _textInputPlugin; + + // Whether the engine is running in multi-window mode. This affects behavior + // when adding view controller (it will fail when calling multiple times without + // _multiviewEnabled). + BOOL _multiviewEnabled; + + // View identifier for the next view to be created. + FlutterViewIdentifier _nextViewIdentifier; } +@synthesize windowController = _windowController; + - (instancetype)initWithName:(NSString*)labelPrefix project:(FlutterDartProject*)project { return [self initWithName:labelPrefix project:project allowHeadlessExecution:YES]; } @@ -528,6 +541,8 @@ - (instancetype)initWithName:(NSString*)labelPrefix [_isResponseValid addObject:@YES]; _keyboardManager = [[FlutterKeyboardManager alloc] initWithDelegate:self]; _textInputPlugin = [[FlutterTextInputPlugin alloc] initWithDelegate:self]; + _multiviewEnabled = NO; + _nextViewIdentifier = 1; _embedderAPI.struct_size = sizeof(FlutterEngineProcTable); FlutterEngineGetProcAddresses(&_embedderAPI); @@ -549,6 +564,10 @@ - (instancetype)initWithName:(NSString*)labelPrefix [[FlutterTimeConverter alloc] initWithEngine:self], _platformViewController); [self setUpPlatformViewChannel]; + + _windowController = [[FlutterWindowController alloc] init]; + _windowController.engine = self; + [self setUpAccessibilityChannel]; [self setUpNotificationCenterListeners]; id appDelegate = [[NSApplication sharedApplication] delegate]; @@ -623,6 +642,16 @@ - (FlutterTaskRunnerDescription)createPlatformThreadTaskDescription { return cocoa_task_runner_description; } +- (void)onFocusChangeRequest:(const FlutterViewFocusChangeRequest*)request { + FlutterViewController* controller = [self viewControllerForIdentifier:request->view_id]; + if (controller == nil) { + return; + } + if (request->state == kFocused) { + [controller.flutterView.window makeFirstResponder:controller.flutterView]; + } +} + - (BOOL)runWithEntrypoint:(NSString*)entrypoint { if (self.running) { return NO; @@ -732,6 +761,12 @@ - (BOOL)runWithEntrypoint:(NSString*)entrypoint { [engine onVSync:baton]; }; + flutterArguments.view_focus_change_request_callback = + [](const FlutterViewFocusChangeRequest* request, void* user_data) { + FlutterEngine* engine = (__bridge FlutterEngine*)user_data; + [engine onFocusChangeRequest:request]; + }; + FlutterRendererConfig rendererConfig = [_renderer createRendererConfig]; FlutterEngineResult result = _embedderAPI.Initialize( FLUTTER_ENGINE_VERSION, &rendererConfig, &flutterArguments, (__bridge void*)(self), &_engine); @@ -792,10 +827,12 @@ - (void)registerViewController:(FlutterViewController*)controller forIdentifier:(FlutterViewIdentifier)viewIdentifier { _macOSCompositor->AddView(viewIdentifier); NSAssert(controller != nil, @"The controller must not be nil."); - NSAssert(controller.engine == nil, - @"The FlutterViewController is unexpectedly attached to " - @"engine %@ before initialization.", - controller.engine); + if (!_multiviewEnabled) { + NSAssert(controller.engine == nil, + @"The FlutterViewController is unexpectedly attached to " + @"engine %@ before initialization.", + controller.engine); + } NSAssert([_viewControllers objectForKey:@(viewIdentifier)] == nil, @"The requested view ID is occupied."); [_viewControllers setObject:controller forKey:@(viewIdentifier)]; @@ -813,6 +850,32 @@ - (void)registerViewController:(FlutterViewController*)controller if (controller.viewLoaded) { [self viewControllerViewDidLoad:controller]; } + + if (viewIdentifier != kFlutterImplicitViewId) { + // These will be overriden immediately after the FlutterView is created + // by actual values. + FlutterWindowMetricsEvent metrics{ + .struct_size = sizeof(FlutterWindowMetricsEvent), + .width = 0, + .height = 0, + .pixel_ratio = 1.0, + }; + bool added = false; + FlutterAddViewInfo info{.struct_size = sizeof(FlutterAddViewInfo), + .view_id = viewIdentifier, + .view_metrics = &metrics, + .user_data = &added, + .add_view_callback = [](const FlutterAddViewResult* r) { + auto added = reinterpret_cast(r->user_data); + *added = true; + }}; + // The callback should be called synchronously from platform thread. + _embedderAPI.AddView(_engine, &info); + FML_DCHECK(added); + if (!added) { + NSLog(@"Failed to add view with ID %llu", viewIdentifier); + } + } } - (void)viewControllerViewDidLoad:(FlutterViewController*)viewController { @@ -830,14 +893,33 @@ - (void)viewControllerViewDidLoad:(FlutterViewController*)viewController { engine->_embedderAPI.OnVsync(_engine, baton, timeNanos, targetTimeNanos); } }]; - FML_DCHECK([_vsyncWaiters objectForKey:@(viewController.viewIdentifier)] == nil); @synchronized(_vsyncWaiters) { + FML_DCHECK([_vsyncWaiters objectForKey:@(viewController.viewIdentifier)] == nil); [_vsyncWaiters setObject:waiter forKey:@(viewController.viewIdentifier)]; } } - (void)deregisterViewControllerForIdentifier:(FlutterViewIdentifier)viewIdentifier { + if (viewIdentifier != kFlutterImplicitViewId) { + bool removed = false; + FlutterRemoveViewInfo info; + info.struct_size = sizeof(FlutterRemoveViewInfo); + info.view_id = viewIdentifier; + info.user_data = &removed; + info.remove_view_callback = [](const FlutterRemoveViewResult* r) { + auto removed = reinterpret_cast(r->user_data); + [FlutterRunLoop.mainRunLoop performBlock:^{ + *removed = true; + }]; + }; + _embedderAPI.RemoveView(_engine, &info); + while (!removed) { + [[FlutterRunLoop mainRunLoop] pollFlutterMessagesOnce]; + } + } + _macOSCompositor->RemoveView(viewIdentifier); + FlutterViewController* controller = [self viewControllerForIdentifier:viewIdentifier]; // The controller can be nil. The engine stores only a weak ref, and this // method could have been called from the controller's dealloc. @@ -938,11 +1020,44 @@ - (FlutterCompositor*)createFlutterCompositor { #pragma mark - Framework-internal methods - (void)addViewController:(FlutterViewController*)controller { - // FlutterEngine can only handle the implicit view for now. Adding more views - // throws an assertion. - NSAssert(self.viewController == nil, - @"The engine already has a view controller for the implicit view."); - self.viewController = controller; + if (!_multiviewEnabled) { + // FlutterEngine can only handle the implicit view for now. Adding more views + // throws an assertion. + NSAssert(self.viewController == nil, + @"The engine already has a view controller for the implicit view."); + self.viewController = controller; + } else { + FlutterViewIdentifier viewIdentifier = _nextViewIdentifier++; + [self registerViewController:controller forIdentifier:viewIdentifier]; + } +} + +- (void)enableMultiView { + if (!_multiviewEnabled) { + NSAssert(self.viewController == nil, + @"Multiview can only be enabled before adding any view controllers."); + _multiviewEnabled = YES; + } +} + +- (void)windowDidBecomeKey:(FlutterViewIdentifier)viewIdentifier { + FlutterViewFocusEvent event{ + .struct_size = sizeof(FlutterViewFocusEvent), + .view_id = viewIdentifier, + .state = kFocused, + .direction = kUndefined, + }; + _embedderAPI.SendViewFocusEvent(_engine, &event); +} + +- (void)windowDidResignKey:(FlutterViewIdentifier)viewIdentifier { + FlutterViewFocusEvent event{ + .struct_size = sizeof(FlutterViewFocusEvent), + .view_id = viewIdentifier, + .state = kUnfocused, + .direction = kUndefined, + }; + _embedderAPI.SendViewFocusEvent(_engine, &event); } - (void)removeViewController:(nonnull FlutterViewController*)viewController { @@ -1166,8 +1281,13 @@ - (void)onVSync:(uintptr_t)baton { auto block = ^{ // TODO(knopp): Use vsync waiter for correct view. // https://github.com/flutter/flutter/issues/142845 - FlutterVSyncWaiter* waiter = [_vsyncWaiters objectForKey:@(kFlutterImplicitViewId)]; - [waiter waitForVSync:baton]; + FlutterVSyncWaiter* waiter = + [_vsyncWaiters objectForKey:[_vsyncWaiters.keyEnumerator nextObject]]; + if (waiter != nil) { + [waiter waitForVSync:baton]; + } else { + self.embedderAPI.OnVsync(_engine, baton, 0, 0); + } }; if ([NSThread isMainThread]) { block(); @@ -1249,6 +1369,7 @@ - (void)addInternalPlugins { [FlutterMouseCursorPlugin registerWithRegistrar:[self registrarForPlugin:@"mousecursor"] delegate:self]; [FlutterMenuPlugin registerWithRegistrar:[self registrarForPlugin:@"menu"]]; + _settingsChannel = [FlutterBasicMessageChannel messageChannelWithName:kFlutterSettingsChannel binaryMessenger:self.binaryMessenger diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h index ec34b267a9246..3c8baaae0d088 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h @@ -18,6 +18,7 @@ #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterRenderer.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowController.h" NS_ASSUME_NONNULL_BEGIN @@ -223,6 +224,25 @@ typedef NS_ENUM(NSInteger, FlutterAppExitResponse) { */ @property(nonatomic, readonly) FlutterTextInputPlugin* textInputPlugin; +@property(nonatomic, readonly) FlutterWindowController* windowController; + +/** + * Toggles multi-view support. Called by [FlutterWindowController] before + * creating a new window. This allows registering multiple view controllers + * with the engine. + */ +- (void)enableMultiView; + +/** + * Notifies the engine that window with the given identifier has been made key. + */ +- (void)windowDidBecomeKey:(FlutterViewIdentifier)viewIdentifier; + +/** + * Notifies the engine that window with the given identifier has resigned being key. + */ +- (void)windowDidResignKey:(FlutterViewIdentifier)viewIdentifier; + /** * Returns an array of screen objects representing all of the screens available on the system. */ @@ -238,6 +258,7 @@ typedef NS_ENUM(NSInteger, FlutterAppExitResponse) { * This function must be called on the main thread. */ + (nullable FlutterEngine*)engineForIdentifier:(int64_t)identifier; + @end NS_ASSUME_NONNULL_END diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController.h b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController.h index a784a69b94d0b..db89d04f5faa7 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController.h +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController.h @@ -14,7 +14,7 @@ #include #include -@interface FlutterPlatformViewController : NSViewController +@interface FlutterPlatformViewController : NSObject @end @interface FlutterPlatformViewController () diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterVSyncWaiter.mm b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterVSyncWaiter.mm index 0af31382a4a87..20c69899d2f40 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterVSyncWaiter.mm +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterVSyncWaiter.mm @@ -115,7 +115,7 @@ - (void)waitForVSync:(uintptr_t)baton { TRACE_VSYNC("VSyncRequest", _pendingBaton.value_or(0)); CFTimeInterval tick_interval = _displayLink.nominalOutputRefreshPeriod; - if (_displayLink.paused || tick_interval == 0) { + if (_displayLink.paused || tick_interval == 0 || _lastTargetTimestamp == 0) { // When starting display link the first notification will come in the middle // of next frame, which would incur a whole frame period of latency. // To avoid that, first vsync notification will be fired using a timer @@ -152,8 +152,13 @@ - (void)waitForVSync:(uintptr_t)baton { } - (void)dealloc { + // It is possible that there is pending vsync request while the view for which + // this waiter belongs is being destroyed. In that case trigger the vsync + // immediately to avoid deadlock. if (_pendingBaton.has_value()) { - [FlutterLogger logWarning:@"Deallocating FlutterVSyncWaiter with a pending vsync"]; + CFTimeInterval now = CACurrentMediaTime(); + _block(now, now, _pendingBaton.value()); + _pendingBaton = std::nullopt; } // It is possible that block running on UI thread held the last reference to // the waiter, in which case reschedule to main thread. diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm index 5346c452dae70..4111bc52937ab 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm @@ -327,6 +327,7 @@ static void CommonInit(FlutterViewController* controller, FlutterEngine* engine) @"engine %@ before initialization.", controller.engine); [engine addViewController:controller]; + NSCAssert(controller.engine != nil, @"The FlutterViewController unexpectedly stays unattached after initialization. " @"In unit tests, this is likely because either the FlutterViewController or " @@ -418,13 +419,17 @@ - (void)viewWillDisappear { _keyUpMonitor = nil; } -- (void)dealloc { +- (void)dispose { if ([self attached]) { [_engine removeViewController:self]; } [self.flutterView shutDown]; } +- (void)dealloc { + [self dispose]; +} + #pragma mark - Public methods - (void)setMouseTrackingMode:(FlutterMouseTrackingMode)mode { diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h index 301d584fdf61e..d2d8434ddf355 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h @@ -53,6 +53,14 @@ */ - (void)updateSemantics:(nonnull const FlutterSemanticsUpdate2*)update; +/** + * Removes this controller from the engine. The controller is removed from the engine + * on dealloc, however in multi-window scenario the controller needs to be unregistered + * from the engine eagerly - because the FlutterView needs to be removed from the + * Flutter isolate before destroying the controller and window. + */ +- (void)dispose; + @end // Private methods made visible for testing diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowController.h b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowController.h new file mode 100644 index 0000000000000..0fce98cf1f27e --- /dev/null +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowController.h @@ -0,0 +1,83 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_PLATFORM_DARWIN_MACOS_FRAMEWORK_SOURCE_FLUTTERWINDOWCONTROLLER_H_ +#define FLUTTER_SHELL_PLATFORM_DARWIN_MACOS_FRAMEWORK_SOURCE_FLUTTERWINDOWCONTROLLER_H_ + +#import + +#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h" +#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterViewController.h" + +@class FlutterEngine; + +@interface FlutterWindowController : NSObject + +@property(nonatomic, weak) FlutterEngine* engine; + +@end + +@interface FlutterWindowController (Testing) + +- (void)closeAllWindows; + +@end + +struct FlutterWindowSizing { + bool has_size; + double width; + double height; + bool has_constraints; + double min_width; + double min_height; + double max_width; + double max_height; +}; + +struct FlutterWindowCreationRequest { + FlutterWindowSizing contentSize; + void (*on_close)(); + void (*on_size_change)(); +}; + +struct FlutterWindowSize { + double width; + double height; +}; + +extern "C" { + +// NOLINTBEGIN(google-objc-function-naming) + +FLUTTER_DARWIN_EXPORT +int64_t FlutterCreateRegularWindow(int64_t engine_id, const FlutterWindowCreationRequest* request); + +FLUTTER_DARWIN_EXPORT +void FlutterDestroyWindow(int64_t engine_id, void* window); + +FLUTTER_DARWIN_EXPORT +void* FlutterGetWindowHandle(int64_t engine_id, FlutterViewIdentifier view_id); + +FLUTTER_DARWIN_EXPORT +void* FlutterGetWindowHandle(int64_t engine_id, FlutterViewIdentifier view_id); + +FLUTTER_DARWIN_EXPORT +FlutterWindowSize FlutterGetWindowContentSize(void* window); + +FLUTTER_DARWIN_EXPORT +void FlutterSetWindowContentSize(void* window, const FlutterWindowSizing* size); + +FLUTTER_DARWIN_EXPORT +void FlutterSetWindowTitle(void* window, const char* title); + +FLUTTER_DARWIN_EXPORT +int64_t FlutterGetWindowState(void* window); + +FLUTTER_DARWIN_EXPORT +void FlutterSetWindowState(void* window, int64_t state); + +// NOLINTEND(google-objc-function-naming) +} + +#endif // FLUTTER_SHELL_PLATFORM_DARWIN_MACOS_FRAMEWORK_SOURCE_FLUTTERWINDOWCONTROLLER_H_ diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowController.mm b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowController.mm new file mode 100644 index 0000000000000..46363074e8df9 --- /dev/null +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowController.mm @@ -0,0 +1,236 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowController.h" + +#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h" +#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterCodecs.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h" + +#include "flutter/shell/platform/common/isolate_scope.h" +#include "flutter/shell/platform/common/windowing.h" + +/// A delegate for a Flutter managed window. +@interface FlutterWindowOwner : NSObject { + /// Strong reference to the window. This is the only strong reference to the + /// window. + NSWindow* _window; + FlutterViewController* _flutterViewController; + std::optional _isolate; + FlutterWindowCreationRequest _creationRequest; +} + +@property(readonly, nonatomic) NSWindow* window; +@property(readonly, nonatomic) FlutterViewController* flutterViewController; + +- (instancetype)initWithWindow:(NSWindow*)window + flutterViewController:(FlutterViewController*)viewController + creationRequest:(const FlutterWindowCreationRequest&)creationRequest; + +@end + +@interface NSWindow (FlutterWindowSizing) + +- (void)flutterSetContentSize:(FlutterWindowSizing)contentSize; + +@end + +@implementation NSWindow (FlutterWindowSizing) +- (void)flutterSetContentSize:(FlutterWindowSizing)contentSize { + if (contentSize.has_size) { + [self setContentSize:NSMakeSize(contentSize.width, contentSize.height)]; + } + if (contentSize.has_constraints) { + [self setContentMinSize:NSMakeSize(contentSize.min_width, contentSize.min_height)]; + if (contentSize.max_width > 0 && contentSize.max_height > 0) { + [self setContentMaxSize:NSMakeSize(contentSize.max_width, contentSize.max_height)]; + } else { + [self setContentMaxSize:NSMakeSize(CGFLOAT_MAX, CGFLOAT_MAX)]; + } + } +} + +@end + +@implementation FlutterWindowOwner + +@synthesize window = _window; +@synthesize flutterViewController = _flutterViewController; + +- (instancetype)initWithWindow:(NSWindow*)window + flutterViewController:(FlutterViewController*)viewController + creationRequest:(const FlutterWindowCreationRequest&)creationRequest { + if (self = [super init]) { + _window = window; + _flutterViewController = viewController; + _creationRequest = creationRequest; + _isolate = flutter::Isolate::Current(); + } + return self; +} + +- (void)windowDidBecomeKey:(NSNotification*)notification { + [_flutterViewController.engine windowDidBecomeKey:_flutterViewController.viewIdentifier]; +} + +- (void)windowDidResignKey:(NSNotification*)notification { + [_flutterViewController.engine windowDidResignKey:_flutterViewController.viewIdentifier]; +} + +- (BOOL)windowShouldClose:(NSWindow*)sender { + flutter::IsolateScope isolate_scope(*_isolate); + _creationRequest.on_close(); + return NO; +} + +- (void)windowDidResize:(NSNotification*)notification { + flutter::IsolateScope isolate_scope(*_isolate); + _creationRequest.on_size_change(); +} + +@end + +@interface FlutterWindowController () { + NSMutableArray* _windows; +} + +@end + +@implementation FlutterWindowController + +- (instancetype)init { + self = [super init]; + if (self != nil) { + _windows = [NSMutableArray array]; + } + return self; +} + +- (FlutterViewIdentifier)createRegularWindow:(const FlutterWindowCreationRequest*)request { + FlutterViewController* c = [[FlutterViewController alloc] initWithEngine:_engine + nibName:nil + bundle:nil]; + + NSWindow* window = [[NSWindow alloc] init]; + // If this is not set there will be double free on window close when + // using ARC. + [window setReleasedWhenClosed:NO]; + + window.contentViewController = c; + window.styleMask = NSWindowStyleMaskResizable | NSWindowStyleMaskTitled | + NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable; + [window flutterSetContentSize:request->contentSize]; + [window setIsVisible:YES]; + [window makeKeyAndOrderFront:nil]; + + FlutterWindowOwner* w = [[FlutterWindowOwner alloc] initWithWindow:window + flutterViewController:c + creationRequest:*request]; + window.delegate = w; + [_windows addObject:w]; + + return c.viewIdentifier; +} + +- (void)destroyWindow:(NSWindow*)window { + FlutterWindowOwner* owner = nil; + for (FlutterWindowOwner* o in _windows) { + if (o.window == window) { + owner = o; + break; + } + } + if (owner != nil) { + [_windows removeObject:owner]; + // Make sure to unregister the controller from the engine and remove the FlutterView + // before destroying the window and Flutter NSView. + [owner.flutterViewController dispose]; + owner.window.delegate = nil; + [owner.window close]; + } +} + +- (void)closeAllWindows { + for (FlutterWindowOwner* owner in _windows) { + [owner.flutterViewController dispose]; + [owner.window close]; + } + [_windows removeAllObjects]; +} + +@end + +// NOLINTBEGIN(google-objc-function-naming) + +int64_t FlutterCreateRegularWindow(int64_t engine_id, const FlutterWindowCreationRequest* request) { + FlutterEngine* engine = [FlutterEngine engineForIdentifier:engine_id]; + [engine enableMultiView]; + return [engine.windowController createRegularWindow:request]; +} + +void FlutterDestroyWindow(int64_t engine_id, void* window) { + NSWindow* w = (__bridge NSWindow*)window; + FlutterEngine* engine = [FlutterEngine engineForIdentifier:engine_id]; + [engine.windowController destroyWindow:w]; +} + +void* FlutterGetWindowHandle(int64_t engine_id, FlutterViewIdentifier view_id) { + FlutterEngine* engine = [FlutterEngine engineForIdentifier:engine_id]; + FlutterViewController* controller = [engine viewControllerForIdentifier:view_id]; + return (__bridge void*)controller.view.window; +} + +FlutterWindowSize FlutterGetWindowContentSize(void* window) { + NSWindow* w = (__bridge NSWindow*)window; + return { + .width = w.frame.size.width, + .height = w.frame.size.height, + }; +} + +void FlutterSetWindowContentSize(void* window, const FlutterWindowSizing* size) { + NSWindow* w = (__bridge NSWindow*)window; + [w flutterSetContentSize:*size]; +} + +void FlutterSetWindowTitle(void* window, const char* title) { + NSWindow* w = (__bridge NSWindow*)window; + w.title = [NSString stringWithUTF8String:title]; +} + +int64_t FlutterGetWindowState(void* window) { + NSWindow* w = (__bridge NSWindow*)window; + if (w.isMiniaturized) { + return static_cast(flutter::WindowState::kMinimized); + } else if (w.isZoomed) { + return static_cast(flutter::WindowState::kMaximized); + } else { + return static_cast(flutter::WindowState::kRestored); + } +} + +void FlutterSetWindowState(void* window, int64_t state) { + flutter::WindowState windowState = static_cast(state); + NSWindow* w = (__bridge NSWindow*)window; + if (windowState == flutter::WindowState::kMaximized) { + [w deminiaturize:nil]; + [w zoom:nil]; + } else if (state == static_cast(flutter::WindowState::kMinimized)) { + [w miniaturize:nil]; + } else { + if (w.isMiniaturized) { + [w deminiaturize:nil]; + } else if (w.isZoomed) { + [w zoom:nil]; + } else { + bool isFullScreen = (w.styleMask & NSWindowStyleMaskFullScreen) != 0; + if (isFullScreen) { + [w toggleFullScreen:nil]; + } + } + } +} + +// NOLINTEND(google-objc-function-naming) diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowControllerTest.mm b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowControllerTest.mm new file mode 100644 index 0000000000000..23c2a8cf05b44 --- /dev/null +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowControllerTest.mm @@ -0,0 +1,189 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "flutter/shell/platform/common/isolate_scope.h" +#import "flutter/shell/platform/common/windowing.h" + +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject_Internal.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineTestUtils.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowController.h" +#import "flutter/testing/testing.h" +#import "third_party/googletest/googletest/include/gtest/gtest.h" + +namespace flutter::testing { + +class FlutterWindowControllerTest : public FlutterEngineTest { + public: + FlutterWindowControllerTest() = default; + + void SetUp() { + FlutterEngineTest::SetUp(); + + [GetFlutterEngine() runWithEntrypoint:@"testWindowController"]; + + bool signalled = false; + + AddNativeCallback("SignalNativeTest", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { + isolate_ = Isolate::Current(); + signalled = true; + })); + + while (!signalled) { + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, false); + } + } + + void TearDown() { + [GetFlutterEngine().windowController closeAllWindows]; + FlutterEngineTest::TearDown(); + } + + protected: + flutter::Isolate& isolate() { + if (isolate_) { + return *isolate_; + } else { + FML_LOG(ERROR) << "Isolate is not set."; + FML_UNREACHABLE(); + } + } + + std::optional isolate_; +}; + +class FlutterWindowControllerRetainTest : public ::testing::Test {}; + +TEST_F(FlutterWindowControllerTest, CreateRegularWindow) { + FlutterWindowCreationRequest request{ + .contentSize = {.has_size = true, .width = 800, .height = 600}, + .on_close = [] {}, + .on_size_change = [] {}, + }; + + FlutterEngine* engine = GetFlutterEngine(); + int64_t engineId = reinterpret_cast(engine); + + { + IsolateScope isolate_scope(isolate()); + int64_t handle = FlutterCreateRegularWindow(engineId, &request); + EXPECT_EQ(handle, 1); + + FlutterViewController* viewController = [engine viewControllerForIdentifier:handle]; + EXPECT_NE(viewController, nil); + CGSize size = viewController.view.frame.size; + EXPECT_EQ(size.width, 800); + EXPECT_EQ(size.height, 600); + } +} + +TEST_F(FlutterWindowControllerRetainTest, WindowControllerDoesNotRetainEngine) { + FlutterWindowCreationRequest request{ + .contentSize = {.has_size = true, .width = 800, .height = 600}, + .on_close = [] {}, + .on_size_change = [] {}, + }; + + __weak FlutterEngine* weakEngine = nil; + @autoreleasepool { + NSString* fixtures = @(flutter::testing::GetFixturesPath()); + NSLog(@"Fixtures path: %@", fixtures); + FlutterDartProject* project = [[FlutterDartProject alloc] + initWithAssetsPath:fixtures + ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]]; + + static std::optional isolate; + isolate = std::nullopt; + + project.rootIsolateCreateCallback = [](void*) { isolate = flutter::Isolate::Current(); }; + FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test" + project:project + allowHeadlessExecution:YES]; + weakEngine = engine; + [engine runWithEntrypoint:@"testWindowControllerRetainCycle"]; + + int64_t engineId = reinterpret_cast(engine); + + { + FML_DCHECK(isolate.has_value()); + // NOLINTNEXTLINE(bugprone-unchecked-optional-access) + IsolateScope isolateScope(*isolate); + int64_t handle = FlutterCreateRegularWindow(engineId, &request); + EXPECT_EQ(handle, 1); + } + + [engine.windowController closeAllWindows]; + [engine shutDownEngine]; + } + EXPECT_EQ(weakEngine, nil); +} + +TEST_F(FlutterWindowControllerTest, DestroyRegularWindow) { + FlutterWindowCreationRequest request{ + .contentSize = {.has_size = true, .width = 800, .height = 600}, + .on_close = [] {}, + .on_size_change = [] {}, + }; + + FlutterEngine* engine = GetFlutterEngine(); + int64_t engine_id = reinterpret_cast(engine); + + IsolateScope isolate_scope(isolate()); + int64_t handle = FlutterCreateRegularWindow(engine_id, &request); + FlutterViewController* viewController = [engine viewControllerForIdentifier:handle]; + + FlutterDestroyWindow(engine_id, (__bridge void*)viewController.view.window); + viewController = [engine viewControllerForIdentifier:handle]; + EXPECT_EQ(viewController, nil); +} + +TEST_F(FlutterWindowControllerTest, FlutterGetWindowHandle) { + FlutterWindowCreationRequest request{ + .contentSize = {.has_size = true, .width = 800, .height = 600}, + .on_close = [] {}, + .on_size_change = [] {}, + }; + + FlutterEngine* engine = GetFlutterEngine(); + int64_t engine_id = reinterpret_cast(engine); + + IsolateScope isolate_scope(isolate()); + int64_t handle = FlutterCreateRegularWindow(engine_id, &request); + FlutterViewController* viewController = [engine viewControllerForIdentifier:handle]; + + void* window_handle = FlutterGetWindowHandle(engine_id, handle); + EXPECT_EQ(window_handle, (__bridge void*)viewController.view.window); +} + +TEST_F(FlutterWindowControllerTest, FlutterSetWindowState) { + FlutterWindowCreationRequest request{ + .contentSize = {.has_size = true, .width = 800, .height = 600}, + .on_close = [] {}, + .on_size_change = [] {}, + }; + + FlutterEngine* engine = GetFlutterEngine(); + int64_t engine_id = reinterpret_cast(engine); + + IsolateScope isolate_scope(isolate()); + int64_t handle = FlutterCreateRegularWindow(engine_id, &request); + + const std::array kWindowStates = { + static_cast(WindowState::kRestored), // + static_cast(WindowState::kMaximized), // + static_cast(WindowState::kMinimized), // + static_cast(WindowState::kMaximized), // + static_cast(WindowState::kRestored), // + }; + FlutterViewController* viewController = [engine viewControllerForIdentifier:handle]; + void* windowHandle = (__bridge void*)viewController.view.window; + + for (const auto requestedState : kWindowStates) { + FlutterSetWindowState(windowHandle, requestedState); + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0, false); + const int64_t actualState = FlutterGetWindowState(windowHandle); + EXPECT_EQ(actualState, requestedState); + } +} +} // namespace flutter::testing diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/fixtures/flutter_desktop_test.dart b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/fixtures/flutter_desktop_test.dart index 208d466cbf487..a58f2eecb94e6 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/fixtures/flutter_desktop_test.dart +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/fixtures/flutter_desktop_test.dart @@ -93,3 +93,11 @@ external void notifyEngineId(int? engineId); void testEngineId() { notifyEngineId(PlatformDispatcher.instance.engineId); } + +@pragma('vm:entry-point') +void testWindowController() { + signalNativeTest(); +} + +@pragma('vm:entry-point') +void testWindowControllerRetainCycle() {} diff --git a/engine/src/flutter/shell/platform/windows/BUILD.gn b/engine/src/flutter/shell/platform/windows/BUILD.gn index e80662e592a72..7625f8beae96f 100644 --- a/engine/src/flutter/shell/platform/windows/BUILD.gn +++ b/engine/src/flutter/shell/platform/windows/BUILD.gn @@ -73,6 +73,10 @@ source_set("flutter_windows_source") { "external_texture_d3d.h", "external_texture_pixelbuffer.cc", "external_texture_pixelbuffer.h", + "flutter_host_window.cc", + "flutter_host_window.h", + "flutter_host_window_controller.cc", + "flutter_host_window_controller.h", "flutter_key_map.g.cc", "flutter_platform_node_delegate_windows.cc", "flutter_platform_node_delegate_windows.h", @@ -158,6 +162,7 @@ source_set("flutter_windows_source") { "//flutter/impeller/renderer/backend/gles", "//flutter/shell/platform/common:common_cpp", "//flutter/shell/platform/common:common_cpp_input", + "//flutter/shell/platform/common:common_cpp_isolate_scope", "//flutter/shell/platform/common:common_cpp_switches", "//flutter/shell/platform/common/client_wrapper:client_wrapper", "//flutter/shell/platform/embedder:embedder_as_internal_library", @@ -202,6 +207,7 @@ executable("flutter_windows_unittests") { "cursor_handler_unittests.cc", "direct_manipulation_unittests.cc", "dpi_utils_unittests.cc", + "flutter_host_window_controller_unittests.cc", "flutter_project_bundle_unittests.cc", "flutter_window_unittests.cc", "flutter_windows_engine_unittests.cc", diff --git a/engine/src/flutter/shell/platform/windows/fixtures/main.dart b/engine/src/flutter/shell/platform/windows/fixtures/main.dart index b811fab5b03b8..4ae7fd3d59b1c 100644 --- a/engine/src/flutter/shell/platform/windows/fixtures/main.dart +++ b/engine/src/flutter/shell/platform/windows/fixtures/main.dart @@ -405,6 +405,11 @@ void testEngineId() { notifyEngineId(ui.PlatformDispatcher.instance.engineId); } +@pragma('vm:entry-point') +void testWindowController() { + signal(); +} + @pragma('vm:entry-point') Future sendSemanticsTreeInfo() async { // Wait until semantics are enabled. diff --git a/engine/src/flutter/shell/platform/windows/flutter_host_window.cc b/engine/src/flutter/shell/platform/windows/flutter_host_window.cc new file mode 100644 index 0000000000000..4ae52566e1584 --- /dev/null +++ b/engine/src/flutter/shell/platform/windows/flutter_host_window.cc @@ -0,0 +1,569 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/shell/platform/windows/flutter_host_window.h" + +#include + +#include "flutter/shell/platform/windows/dpi_utils.h" +#include "flutter/shell/platform/windows/flutter_host_window_controller.h" +#include "flutter/shell/platform/windows/flutter_window.h" +#include "flutter/shell/platform/windows/flutter_windows_view_controller.h" + +namespace { + +constexpr wchar_t kWindowClassName[] = L"FLUTTER_HOST_WINDOW"; + +// Clamps |size| to the size of the virtual screen. Both the parameter and +// return size are in physical coordinates. +flutter::Size ClampToVirtualScreen(flutter::Size size) { + double const virtual_screen_width = GetSystemMetrics(SM_CXVIRTUALSCREEN); + double const virtual_screen_height = GetSystemMetrics(SM_CYVIRTUALSCREEN); + + return flutter::Size(std::clamp(size.width(), 0.0, virtual_screen_width), + std::clamp(size.height(), 0.0, virtual_screen_height)); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module +// so that the non-client area automatically responds to changes in DPI. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + + using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + + FreeLibrary(user32_module); +} + +// Dynamically loads |SetWindowCompositionAttribute| from the User32 module to +// make the window's background transparent. +void EnableTransparentWindowBackground(HWND hwnd) { + HMODULE const user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + + enum WINDOWCOMPOSITIONATTRIB { WCA_ACCENT_POLICY = 19 }; + + struct WINDOWCOMPOSITIONATTRIBDATA { + WINDOWCOMPOSITIONATTRIB Attrib; + PVOID pvData; + SIZE_T cbData; + }; + + using SetWindowCompositionAttribute = + BOOL(__stdcall*)(HWND, WINDOWCOMPOSITIONATTRIBDATA*); + + auto set_window_composition_attribute = + reinterpret_cast( + GetProcAddress(user32_module, "SetWindowCompositionAttribute")); + if (set_window_composition_attribute != nullptr) { + enum ACCENT_STATE { ACCENT_DISABLED = 0 }; + + struct ACCENT_POLICY { + ACCENT_STATE AccentState; + DWORD AccentFlags; + DWORD GradientColor; + DWORD AnimationId; + }; + + // Set the accent policy to disable window composition. + ACCENT_POLICY accent = {ACCENT_DISABLED, 2, static_cast(0), 0}; + WINDOWCOMPOSITIONATTRIBDATA data = {.Attrib = WCA_ACCENT_POLICY, + .pvData = &accent, + .cbData = sizeof(accent)}; + set_window_composition_attribute(hwnd, &data); + + // Extend the frame into the client area and set the window's system + // backdrop type for visual effects. + MARGINS const margins = {-1}; + ::DwmExtendFrameIntoClientArea(hwnd, &margins); + INT effect_value = 1; + ::DwmSetWindowAttribute(hwnd, DWMWA_SYSTEMBACKDROP_TYPE, &effect_value, + sizeof(BOOL)); + } + + FreeLibrary(user32_module); +} + +// Retrieves the calling thread's last-error code message as a string, +// or a fallback message if the error message cannot be formatted. +std::string GetLastErrorAsString() { + LPWSTR message_buffer = nullptr; + + if (DWORD const size = FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast(&message_buffer), 0, nullptr)) { + std::wstring const wide_message(message_buffer, size); + LocalFree(message_buffer); + message_buffer = nullptr; + + if (int const buffer_size = + WideCharToMultiByte(CP_UTF8, 0, wide_message.c_str(), -1, nullptr, + 0, nullptr, nullptr)) { + std::string message(buffer_size, 0); + WideCharToMultiByte(CP_UTF8, 0, wide_message.c_str(), -1, &message[0], + buffer_size, nullptr, nullptr); + return message; + } + } + + if (message_buffer) { + LocalFree(message_buffer); + } + std::ostringstream oss; + oss << "Format message failed with 0x" << std::hex << std::setfill('0') + << std::setw(8) << GetLastError(); + return oss.str(); +} + +// Calculates the required window size, in physical coordinates, to +// accommodate the given |client_size|, in logical coordinates, constrained by +// optional |min_size| and |max_size|, for a window with the specified +// |window_style| and |extended_window_style|. If |owner_hwnd| is not null, the +// DPI of the display with the largest area of intersection with |owner_hwnd| is +// used for the calculation; otherwise, the primary display's DPI is used. The +// resulting size includes window borders, non-client areas, and drop shadows. +// On error, returns std::nullopt and logs an error message. +std::optional GetWindowSizeForClientSize( + flutter::Size const& client_size, + std::optional min_size, + std::optional max_size, + DWORD window_style, + DWORD extended_window_style, + HWND owner_hwnd) { + UINT const dpi = flutter::GetDpiForHWND(owner_hwnd); + double const scale_factor = + static_cast(dpi) / USER_DEFAULT_SCREEN_DPI; + RECT rect = { + .right = static_cast(client_size.width() * scale_factor), + .bottom = static_cast(client_size.height() * scale_factor)}; + + HMODULE const user32_raw = LoadLibraryA("User32.dll"); + auto free_user32_module = [](HMODULE module) { FreeLibrary(module); }; + std::unique_ptr, decltype(free_user32_module)> + user32_module(user32_raw, free_user32_module); + if (!user32_module) { + FML_LOG(ERROR) << "Failed to load User32.dll.\n"; + return std::nullopt; + } + + using AdjustWindowRectExForDpi = BOOL __stdcall( + LPRECT lpRect, DWORD dwStyle, BOOL bMenu, DWORD dwExStyle, UINT dpi); + auto* const adjust_window_rect_ext_for_dpi = + reinterpret_cast( + GetProcAddress(user32_raw, "AdjustWindowRectExForDpi")); + if (!adjust_window_rect_ext_for_dpi) { + FML_LOG(ERROR) << "Failed to retrieve AdjustWindowRectExForDpi address " + "from User32.dll."; + return std::nullopt; + } + + if (!adjust_window_rect_ext_for_dpi(&rect, window_style, FALSE, + extended_window_style, dpi)) { + FML_LOG(ERROR) << "Failed to run AdjustWindowRectExForDpi: " + << GetLastErrorAsString(); + return std::nullopt; + } + + double width = static_cast(rect.right - rect.left); + double height = static_cast(rect.bottom - rect.top); + + // Apply size constraints + double const non_client_width = width - (client_size.width() * scale_factor); + double const non_client_height = + height - (client_size.height() * scale_factor); + if (min_size) { + flutter::Size min_physical_size = ClampToVirtualScreen( + flutter::Size(min_size->width() * scale_factor + non_client_width, + min_size->height() * scale_factor + non_client_height)); + width = std::max(width, min_physical_size.width()); + height = std::max(height, min_physical_size.height()); + } + if (max_size) { + flutter::Size max_physical_size = ClampToVirtualScreen( + flutter::Size(max_size->width() * scale_factor + non_client_width, + max_size->height() * scale_factor + non_client_height)); + width = std::min(width, max_physical_size.width()); + height = std::min(height, max_physical_size.height()); + } + + return flutter::Size{width, height}; +} + +// Checks whether the window class of name |class_name| is registered for the +// current application. +bool IsClassRegistered(LPCWSTR class_name) { + WNDCLASSEX window_class = {}; + return GetClassInfoEx(GetModuleHandle(nullptr), class_name, &window_class) != + 0; +} + +// Converts std::string to std::wstring. +std::wstring StringToWstring(std::string_view str) { + if (str.empty()) { + return {}; + } + if (int buffer_size = + MultiByteToWideChar(CP_UTF8, 0, str.data(), str.size(), nullptr, 0)) { + std::wstring wide_str(buffer_size, L'\0'); + if (MultiByteToWideChar(CP_UTF8, 0, str.data(), str.size(), &wide_str[0], + buffer_size)) { + return wide_str; + } + } + return {}; +} + +// Window attribute that enables dark mode window decorations. +// +// Redefined in case the developer's machine has a Windows SDK older than +// version 10.0.22000.0. +// See: +// https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +// Updates the window frame's theme to match the system theme. +void UpdateTheme(HWND window) { + // Registry key for app theme preference. + const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; + const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + + // A value of 0 indicates apps should use dark mode. A non-zero or missing + // value indicates apps should use light mode. + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS const result = + RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, RRF_RT_REG_DWORD, nullptr, + &light_mode, &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} + +} // namespace + +namespace flutter { + +FlutterHostWindow::FlutterHostWindow(FlutterHostWindowController* controller, + WindowArchetype archetype, + const FlutterWindowSizing& content_size) + : window_controller_(controller), archetype_(archetype) { + // Check preconditions and set window styles based on window type. + DWORD window_style = 0; + DWORD extended_window_style = 0; + switch (archetype_) { + case WindowArchetype::kRegular: + window_style |= WS_OVERLAPPEDWINDOW; + break; + default: + FML_UNREACHABLE(); + } + + if (content_size.has_constraints) { + min_size_ = Size(content_size.min_width, content_size.min_height); + if (content_size.max_width > 0 && content_size.max_height > 0) { + max_size_ = Size(content_size.max_width, content_size.max_height); + } + } + + // TODO(knopp): What about windows sized to content? + assert(content_size.has_size); + + // Calculate the screen space window rectangle for the new window. + // Default positioning values (CW_USEDEFAULT) are used + // if the window has no owner. + Rect const initial_window_rect = [&]() -> Rect { + std::optional const window_size = GetWindowSizeForClientSize( + Size(content_size.width, content_size.height), min_size_, max_size_, + window_style, extended_window_style, nullptr); + return {{CW_USEDEFAULT, CW_USEDEFAULT}, + window_size ? *window_size : Size{CW_USEDEFAULT, CW_USEDEFAULT}}; + }(); + + // Set up the view. + FlutterWindowsEngine* const engine = window_controller_->engine(); + auto view_window = std::make_unique( + initial_window_rect.width(), initial_window_rect.height(), + engine->windows_proc_table()); + + std::unique_ptr view = + engine->CreateView(std::move(view_window)); + if (!view) { + FML_LOG(ERROR) << "Failed to create view"; + return; + } + + view_controller_ = + std::make_unique(nullptr, std::move(view)); + FML_CHECK(engine->running()); + // Must happen after engine is running. + view_controller_->view()->SendInitialBounds(); + // The Windows embedder listens to accessibility updates using the + // view's HWND. The embedder's accessibility features may be stale if + // the app was in headless mode. + view_controller_->engine()->UpdateAccessibilityFeatures(); + + // Ensure that basic setup of the view controller was successful. + if (!view_controller_->view()) { + FML_LOG(ERROR) << "Failed to set up the view controller"; + return; + } + + // Register the window class. + if (!IsClassRegistered(kWindowClassName)) { + auto const idi_app_icon = 101; + WNDCLASSEX window_class = {}; + window_class.cbSize = sizeof(WNDCLASSEX); + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.lpfnWndProc = FlutterHostWindow::WndProc; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(idi_app_icon)); + if (!window_class.hIcon) { + window_class.hIcon = LoadIcon(nullptr, IDI_APPLICATION); + } + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + + if (!RegisterClassEx(&window_class)) { + FML_LOG(ERROR) << "Cannot register window class " << kWindowClassName + << ": " << GetLastErrorAsString(); + return; + } + } + + // Create the native window. + HWND hwnd = + CreateWindowEx(extended_window_style, kWindowClassName, L"", window_style, + initial_window_rect.left(), initial_window_rect.top(), + initial_window_rect.width(), initial_window_rect.height(), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!hwnd) { + FML_LOG(ERROR) << "Cannot create window: " << GetLastErrorAsString(); + return; + } + + // Adjust the window position so its origin aligns with the top-left corner + // of the window frame, not the window rectangle (which includes the + // drop-shadow). This adjustment must be done post-creation since the frame + // rectangle is only available after the window has been created. + RECT frame_rect; + DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &frame_rect, + sizeof(frame_rect)); + RECT window_rect; + GetWindowRect(hwnd, &window_rect); + LONG const left_dropshadow_width = frame_rect.left - window_rect.left; + LONG const top_dropshadow_height = window_rect.top - frame_rect.top; + SetWindowPos(hwnd, nullptr, window_rect.left - left_dropshadow_width, + window_rect.top - top_dropshadow_height, 0, 0, + SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); + + UpdateTheme(hwnd); + + SetChildContent(view_controller_->view()->GetWindowHandle()); + + // TODO(loicsharma): Hide the window until the first frame is rendered. + // Single window apps use the engine's next frame callback to show the + // window. This doesn't work for multi window apps as the engine cannot have + // multiple next frame callbacks. If multiple windows are created, only the + // last one will be shown. + ShowWindow(hwnd, SW_SHOWNORMAL); +} + +FlutterHostWindow::~FlutterHostWindow() { + if (view_controller_) { + // Unregister the window class. Fail silently if other windows are still + // using the class, as only the last window can successfully unregister it. + if (!UnregisterClass(kWindowClassName, GetModuleHandle(nullptr))) { + // Clear the error state after the failed unregistration. + SetLastError(ERROR_SUCCESS); + } + } +} + +FlutterHostWindow* FlutterHostWindow::GetThisFromHandle(HWND hwnd) { + return reinterpret_cast( + GetWindowLongPtr(hwnd, GWLP_USERDATA)); +} + +HWND FlutterHostWindow::GetWindowHandle() const { + return window_handle_; +} + +void FlutterHostWindow::FocusViewOf(FlutterHostWindow* window) { + if (window != nullptr && window->child_content_ != nullptr) { + SetFocus(window->child_content_); + } +}; + +LRESULT FlutterHostWindow::WndProc(HWND hwnd, + UINT message, + WPARAM wparam, + LPARAM lparam) { + if (message == WM_NCCREATE) { + auto* const create_struct = reinterpret_cast(lparam); + auto* const window = + static_cast(create_struct->lpCreateParams); + SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast(window)); + window->window_handle_ = hwnd; + + EnableFullDpiSupportIfAvailable(hwnd); + EnableTransparentWindowBackground(hwnd); + } else if (FlutterHostWindow* const window = GetThisFromHandle(hwnd)) { + return window->HandleMessage(hwnd, message, wparam, lparam); + } + + return DefWindowProc(hwnd, message, wparam, lparam); +} + +LRESULT FlutterHostWindow::HandleMessage(HWND hwnd, + UINT message, + WPARAM wparam, + LPARAM lparam) { + auto result = + view_controller_->engine() + ->window_proc_delegate_manager() + ->OnTopLevelWindowProc(window_handle_, message, wparam, lparam); + if (result) { + return *result; + } + + switch (message) { + case WM_DPICHANGED: { + auto* const new_scaled_window_rect = reinterpret_cast(lparam); + LONG const width = + new_scaled_window_rect->right - new_scaled_window_rect->left; + LONG const height = + new_scaled_window_rect->bottom - new_scaled_window_rect->top; + SetWindowPos(hwnd, nullptr, new_scaled_window_rect->left, + new_scaled_window_rect->top, width, height, + SWP_NOZORDER | SWP_NOACTIVATE); + return 0; + } + + case WM_GETMINMAXINFO: { + RECT window_rect; + GetWindowRect(hwnd, &window_rect); + RECT client_rect; + GetClientRect(hwnd, &client_rect); + LONG const non_client_width = (window_rect.right - window_rect.left) - + (client_rect.right - client_rect.left); + LONG const non_client_height = (window_rect.bottom - window_rect.top) - + (client_rect.bottom - client_rect.top); + + UINT const dpi = flutter::GetDpiForHWND(hwnd); + double const scale_factor = + static_cast(dpi) / USER_DEFAULT_SCREEN_DPI; + + MINMAXINFO* info = reinterpret_cast(lparam); + if (min_size_) { + Size const min_physical_size = ClampToVirtualScreen( + Size(min_size_->width() * scale_factor + non_client_width, + min_size_->height() * scale_factor + non_client_height)); + + info->ptMinTrackSize.x = min_physical_size.width(); + info->ptMinTrackSize.y = min_physical_size.height(); + } + if (max_size_) { + Size const max_physical_size = ClampToVirtualScreen( + Size(max_size_->width() * scale_factor + non_client_width, + max_size_->height() * scale_factor + non_client_height)); + + info->ptMaxTrackSize.x = max_physical_size.width(); + info->ptMaxTrackSize.y = max_physical_size.height(); + } + return 0; + } + + case WM_SIZE: { + if (child_content_ != nullptr) { + // Resize and reposition the child content window. + RECT client_rect; + GetClientRect(hwnd, &client_rect); + MoveWindow(child_content_, client_rect.left, client_rect.top, + client_rect.right - client_rect.left, + client_rect.bottom - client_rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + FocusViewOf(this); + return 0; + + case WM_MOUSEACTIVATE: + FocusViewOf(this); + return MA_ACTIVATE; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + + default: + break; + } + + if (!view_controller_) { + return 0; + } + + return DefWindowProc(hwnd, message, wparam, lparam); +} + +void FlutterHostWindow::SetContentSize(const FlutterWindowSizing& size) { + WINDOWINFO window_info = {.cbSize = sizeof(WINDOWINFO)}; + GetWindowInfo(window_handle_, &window_info); + + if (size.has_constraints) { + min_size_ = Size(size.min_width, size.min_height); + if (size.max_width > 0 && size.max_height > 0) { + max_size_ = Size(size.max_width, size.max_height); + } + } + + if (size.has_size) { + std::optional const window_size = GetWindowSizeForClientSize( + Size(size.width, size.height), min_size_, max_size_, + window_info.dwStyle, window_info.dwExStyle, nullptr); + + if (window_size) { + SetWindowPos(window_handle_, NULL, 0, 0, window_size->width(), + window_size->height(), + SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE); + } + } +} + +void FlutterHostWindow::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT client_rect; + GetClientRect(window_handle_, &client_rect); + MoveWindow(content, client_rect.left, client_rect.top, + client_rect.right - client_rect.left, + client_rect.bottom - client_rect.top, true); +} + +} // namespace flutter diff --git a/engine/src/flutter/shell/platform/windows/flutter_host_window.h b/engine/src/flutter/shell/platform/windows/flutter_host_window.h new file mode 100644 index 0000000000000..db4ebd1a446a5 --- /dev/null +++ b/engine/src/flutter/shell/platform/windows/flutter_host_window.h @@ -0,0 +1,95 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_FLUTTER_HOST_WINDOW_H_ +#define FLUTTER_SHELL_PLATFORM_WINDOWS_FLUTTER_HOST_WINDOW_H_ + +#include +#include +#include + +#include "flutter/fml/macros.h" +#include "flutter/shell/platform/common/geometry.h" +#include "flutter/shell/platform/common/windowing.h" +#include "flutter/shell/platform/windows/flutter_host_window_controller.h" + +namespace flutter { + +class FlutterHostWindowController; +class FlutterWindowsView; +class FlutterWindowsViewController; + +// A Win32 window that hosts a |FlutterWindow| in its client area. +class FlutterHostWindow { + public: + // Creates a native Win32 window with a child view confined to its client + // area. |controller| is a pointer to the controller that manages the + // |FlutterHostWindow|. On success, a valid window handle can be retrieved + // via |FlutterHostWindow::GetWindowHandle|. + FlutterHostWindow(FlutterHostWindowController* controller, + WindowArchetype archetype, + const FlutterWindowSizing& content_size); + + virtual ~FlutterHostWindow(); + + // Returns the instance pointer for |hwnd| or nullptr if invalid. + static FlutterHostWindow* GetThisFromHandle(HWND hwnd); + + // Returns the backing window handle, or nullptr if the native window is not + // created or has already been destroyed. + HWND GetWindowHandle() const; + + // Resizes the window to accommodate a client area of the given + // |size|. + void SetContentSize(const FlutterWindowSizing& size); + + private: + friend FlutterHostWindowController; + + // Sets the focus to the child view window of |window|. + static void FocusViewOf(FlutterHostWindow* window); + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. Delegates other messages to the controller. + static LRESULT WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam); + + // Processes and routes salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + LRESULT HandleMessage(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Controller for this window. + FlutterHostWindowController* const window_controller_ = nullptr; + + // Controller for the view hosted in this window. Value-initialized if the + // window is created from an existing top-level native window created by the + // runner. + std::unique_ptr view_controller_; + + // The window archetype. + WindowArchetype archetype_ = WindowArchetype::kRegular; + + // Backing handle for this window. + HWND window_handle_ = nullptr; + + // Backing handle for the hosted view window. + HWND child_content_ = nullptr; + + // The minimum size of the window's client area, if defined. + std::optional min_size_; + + // The maximum size of the window's client area, if defined. + std::optional max_size_; + + FML_DISALLOW_COPY_AND_ASSIGN(FlutterHostWindow); +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_FLUTTER_HOST_WINDOW_H_ diff --git a/engine/src/flutter/shell/platform/windows/flutter_host_window_controller.cc b/engine/src/flutter/shell/platform/windows/flutter_host_window_controller.cc new file mode 100644 index 0000000000000..c922028350269 --- /dev/null +++ b/engine/src/flutter/shell/platform/windows/flutter_host_window_controller.cc @@ -0,0 +1,193 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/shell/platform/windows/flutter_host_window_controller.h" + +#include +#include +#include + +#include "embedder.h" +#include "flutter/shell/platform/common/windowing.h" +#include "flutter/shell/platform/windows/flutter_host_window.h" +#include "flutter/shell/platform/windows/flutter_windows_engine.h" +#include "flutter/shell/platform/windows/flutter_windows_view_controller.h" +#include "fml/logging.h" +#include "shell/platform/windows/client_wrapper/include/flutter/flutter_view.h" +#include "shell/platform/windows/flutter_host_window.h" +#include "shell/platform/windows/flutter_windows_view.h" + +namespace flutter { + +FlutterHostWindowController::FlutterHostWindowController( + FlutterWindowsEngine* engine) + : engine_(engine) {} + +void FlutterHostWindowController::Initialize( + const WindowingInitRequest* request) { + on_message_ = request->on_message; + isolate_ = Isolate::Current(); + + // Send messages accumulated before isolate called this method. + for (WindowsMessage& message : pending_messages_) { + IsolateScope scope(*isolate_); + on_message_(&message); + } + pending_messages_.clear(); +} + +bool FlutterHostWindowController::HasTopLevelWindows() const { + return !active_windows_.empty(); +} + +FlutterViewId FlutterHostWindowController::CreateRegularWindow( + const WindowCreationRequest* request) { + auto window = std::make_unique( + this, WindowArchetype::kRegular, request->content_size); + if (!window->GetWindowHandle()) { + FML_LOG(ERROR) << "Failed to create host window"; + return 0; + } + FlutterViewId const view_id = window->view_controller_->view()->view_id(); + active_windows_[window->GetWindowHandle()] = std::move(window); + return view_id; +} + +void FlutterHostWindowController::OnEngineShutdown() { + // Don't send any more messages to isolate. + on_message_ = nullptr; + std::vector active_handles; + active_handles.reserve(active_windows_.size()); + for (auto& [hwnd, window] : active_windows_) { + active_handles.push_back(hwnd); + } + for (auto hwnd : active_handles) { + // This will destroy the window, which will in turn remove the + // FlutterHostWindow from map when handling WM_NCDESTROY inside + // HandleMessage. + DestroyWindow(hwnd); + } +} + +std::optional FlutterHostWindowController::HandleMessage( + HWND hwnd, + UINT message, + WPARAM wparam, + LPARAM lparam) { + if (message == WM_NCDESTROY) { + active_windows_.erase(hwnd); + } + + FlutterWindowsView* view = engine_->GetViewFromTopLevelWindow(hwnd); + if (!view) { + FML_LOG(WARNING) << "Received message for unknown view"; + return std::nullopt; + } + + WindowsMessage message_struct = {.view_id = view->view_id(), + .hwnd = hwnd, + .message = message, + .wParam = wparam, + .lParam = lparam, + .result = 0, + .handled = false}; + + // Not initialized yet. + if (!isolate_) { + pending_messages_.push_back(message_struct); + return std::nullopt; + } + + IsolateScope scope(*isolate_); + on_message_(&message_struct); + if (message_struct.handled) { + return message_struct.result; + } else { + return std::nullopt; + } +} + +FlutterWindowsEngine* FlutterHostWindowController::engine() const { + return engine_; +} + +} // namespace flutter + +void FlutterWindowingInitialize(int64_t engine_id, + const flutter::WindowingInitRequest* request) { + flutter::FlutterWindowsEngine* engine = + flutter::FlutterWindowsEngine::GetEngineForId(engine_id); + engine->get_host_window_controller()->Initialize(request); +} + +bool FlutterWindowingHasTopLevelWindows(int64_t engine_id) { + flutter::FlutterWindowsEngine* engine = + flutter::FlutterWindowsEngine::GetEngineForId(engine_id); + return engine->get_host_window_controller()->HasTopLevelWindows(); +} + +int64_t FlutterCreateRegularWindow( + int64_t engine_id, + const flutter::WindowCreationRequest* request) { + flutter::FlutterWindowsEngine* engine = + flutter::FlutterWindowsEngine::GetEngineForId(engine_id); + return engine->get_host_window_controller()->CreateRegularWindow(request); +} + +HWND FlutterGetWindowHandle(int64_t engine_id, FlutterViewId view_id) { + flutter::FlutterWindowsEngine* engine = + flutter::FlutterWindowsEngine::GetEngineForId(engine_id); + flutter::FlutterWindowsView* view = engine->view(view_id); + if (view == nullptr) { + return nullptr; + } else { + return GetAncestor(view->GetWindowHandle(), GA_ROOT); + } +} + +FlutterWindowSize FlutterGetWindowContentSize(HWND hwnd) { + RECT rect; + GetClientRect(hwnd, &rect); + double const dpr = FlutterDesktopGetDpiForHWND(hwnd) / + static_cast(USER_DEFAULT_SCREEN_DPI); + double const width = rect.right / dpr; + double const height = rect.bottom / dpr; + return { + .width = rect.right / dpr, + .height = rect.bottom / dpr, + }; +} + +int64_t FlutterGetWindowState(HWND hwnd) { + if (IsIconic(hwnd)) { + return static_cast(flutter::WindowState::kMinimized); + } else if (IsZoomed(hwnd)) { + return static_cast(flutter::WindowState::kMaximized); + } else { + return static_cast(flutter::WindowState::kRestored); + } +} + +void FlutterSetWindowState(HWND hwnd, int64_t state) { + switch (static_cast(state)) { + case flutter::WindowState::kRestored: + ShowWindow(hwnd, SW_RESTORE); + break; + case flutter::WindowState::kMaximized: + ShowWindow(hwnd, SW_MAXIMIZE); + break; + case flutter::WindowState::kMinimized: + ShowWindow(hwnd, SW_MINIMIZE); + break; + } +} + +void FlutterSetWindowContentSize(HWND hwnd, + const flutter::FlutterWindowSizing* size) { + flutter::FlutterHostWindow* window = + flutter::FlutterHostWindow::GetThisFromHandle(hwnd); + if (window) { + window->SetContentSize(*size); + } +} diff --git a/engine/src/flutter/shell/platform/windows/flutter_host_window_controller.h b/engine/src/flutter/shell/platform/windows/flutter_host_window_controller.h new file mode 100644 index 0000000000000..36127619a6638 --- /dev/null +++ b/engine/src/flutter/shell/platform/windows/flutter_host_window_controller.h @@ -0,0 +1,142 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_FLUTTER_HOST_WINDOW_CONTROLLER_H_ +#define FLUTTER_SHELL_PLATFORM_WINDOWS_FLUTTER_HOST_WINDOW_CONTROLLER_H_ + +#include +#include +#include +#include + +#include "flutter/shell/platform/common/public/flutter_export.h" + +#include "flutter/fml/macros.h" +#include "flutter/shell/platform/common/isolate_scope.h" +#include "flutter/shell/platform/embedder/embedder.h" + +namespace flutter { + +class FlutterWindowsEngine; +class FlutterHostWindow; +struct WindowingInitRequest; + +struct WindowsMessage { + int64_t view_id; + HWND hwnd; + UINT message; + WPARAM wParam; + LPARAM lParam; + LRESULT result; + bool handled; +}; + +struct FlutterWindowSizing { + bool has_size; + double width; + double height; + bool has_constraints; + double min_width; + double min_height; + double max_width; + double max_height; +}; + +struct WindowingInitRequest { + void (*on_message)(WindowsMessage*); +}; + +struct WindowCreationRequest { + FlutterWindowSizing content_size; +}; + +// A controller class for managing |FlutterHostWindow| instances. +// A unique instance of this class is owned by |FlutterWindowsEngine| and used +// in |WindowingHandler| to handle methods and messages enabling multi-window +// support. +class FlutterHostWindowController { + public: + explicit FlutterHostWindowController(FlutterWindowsEngine* engine); + virtual ~FlutterHostWindowController() = default; + + void Initialize(const WindowingInitRequest* request); + + bool HasTopLevelWindows() const; + + FlutterViewId CreateRegularWindow(const WindowCreationRequest* request); + + // Message handler called by |FlutterHostWindow::WndProc| to process window + // messages before delegating them to the host window. This allows the + // controller to process messages that affect the state of other host windows. + std::optional HandleMessage(HWND hwnd, + UINT message, + WPARAM wparam, + LPARAM lparam); + + // Gets the engine that owns this controller. + FlutterWindowsEngine* engine() const; + + void OnEngineShutdown(); + + private: + // The Flutter engine that owns this controller. + FlutterWindowsEngine* const engine_; + + // Callback that relays windows messages to the isolate. Set + // during Initialize(). + void (*on_message_)(WindowsMessage*) = nullptr; + + // Isolate that runs the Dart code. Set during Initialize(). + std::optional isolate_; + + // Messages received before the controller is initialized from dart + // code. Buffered until Initialize() is called. + std::vector pending_messages_; + + // A map of active windows. Used to destroy remaining windows on engine + // shutdown. + std::unordered_map> active_windows_; + + FML_DISALLOW_COPY_AND_ASSIGN(FlutterHostWindowController); +}; + +} // namespace flutter + +extern "C" { + +FLUTTER_EXPORT +void FlutterWindowingInitialize(int64_t engine_id, + const flutter::WindowingInitRequest* request); + +FLUTTER_EXPORT +bool FlutterWindowingHasTopLevelWindows(int64_t engine_id); + +FLUTTER_EXPORT +int64_t FlutterCreateRegularWindow( + int64_t engine_id, + const flutter::WindowCreationRequest* request); + +FLUTTER_EXPORT +HWND FlutterGetWindowHandle(int64_t engine_id, FlutterViewId view_id); + +struct FlutterWindowSize { + double width; + double height; +}; + +FLUTTER_EXPORT +FlutterWindowSize FlutterGetWindowContentSize(HWND hwnd); + +FLUTTER_EXPORT +int64_t FlutterGetWindowState(HWND hwnd); + +FLUTTER_EXPORT +void FlutterSetWindowState(HWND hwnd, int64_t state); + +FLUTTER_EXPORT +void FlutterSetWindowContentSize(HWND hwnd, + const flutter::FlutterWindowSizing* size); +} + +#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_FLUTTER_HOST_WINDOW_CONTROLLER_H_ diff --git a/engine/src/flutter/shell/platform/windows/flutter_host_window_controller_unittests.cc b/engine/src/flutter/shell/platform/windows/flutter_host_window_controller_unittests.cc new file mode 100644 index 0000000000000..b854762b999c8 --- /dev/null +++ b/engine/src/flutter/shell/platform/windows/flutter_host_window_controller_unittests.cc @@ -0,0 +1,172 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/shell/platform/windows/flutter_host_window_controller.h" +#include "flutter/shell/platform/windows/testing/flutter_windows_engine_builder.h" +#include "flutter/shell/platform/windows/testing/windows_test.h" +#include "gtest/gtest.h" + +namespace flutter { +namespace testing { + +namespace { + +class FlutterHostWindowControllerTest : public WindowsTest { + public: + FlutterHostWindowControllerTest() = default; + virtual ~FlutterHostWindowControllerTest() = default; + + protected: + void SetUp() override { + auto& context = GetContext(); + FlutterWindowsEngineBuilder builder(context); + builder.SetSwitches({"--enable-windowing=true"}); + + engine_ = builder.Build(); + ASSERT_TRUE(engine_); + + engine_->SetRootIsolateCreateCallback(context.GetRootIsolateCallback()); + ASSERT_TRUE(engine_->Run("testWindowController")); + + bool signalled = false; + context.AddNativeFunction( + "Signal", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { + isolate_ = flutter::Isolate::Current(); + signalled = true; + })); + while (!signalled) { + engine_->task_runner()->ProcessTasks(); + } + } + + void TearDown() override { engine_->Stop(); } + + int64_t engine_id() { return reinterpret_cast(engine_.get()); } + flutter::Isolate& isolate() { return *isolate_; } + WindowCreationRequest* creation_request() { return &creation_request_; } + + private: + std::unique_ptr engine_; + std::optional isolate_; + WindowCreationRequest creation_request_{ + .content_size = + { + .has_size = true, + .width = 800, + .height = 600, + }, + }; + + FML_DISALLOW_COPY_AND_ASSIGN(FlutterHostWindowControllerTest); +}; + +} // namespace + +TEST_F(FlutterHostWindowControllerTest, WindowingInitialize) { + IsolateScope isolate_scope(isolate()); + + static bool received_message = false; + WindowingInitRequest init_request{ + .on_message = [](WindowsMessage* message) { received_message = true; }}; + + FlutterWindowingInitialize(engine_id(), &init_request); + const int64_t view_id = + FlutterCreateRegularWindow(engine_id(), creation_request()); + DestroyWindow(FlutterGetWindowHandle(engine_id(), view_id)); + + EXPECT_TRUE(received_message); +} + +TEST_F(FlutterHostWindowControllerTest, HasTopLevelWindows) { + IsolateScope isolate_scope(isolate()); + + bool has_top_level_windows = FlutterWindowingHasTopLevelWindows(engine_id()); + EXPECT_FALSE(has_top_level_windows); + + FlutterCreateRegularWindow(engine_id(), creation_request()); + has_top_level_windows = FlutterWindowingHasTopLevelWindows(engine_id()); + EXPECT_TRUE(has_top_level_windows); +} + +TEST_F(FlutterHostWindowControllerTest, CreateRegularWindow) { + IsolateScope isolate_scope(isolate()); + + const int64_t view_id = + FlutterCreateRegularWindow(engine_id(), creation_request()); + EXPECT_EQ(view_id, 0); +} + +TEST_F(FlutterHostWindowControllerTest, GetWindowHandle) { + IsolateScope isolate_scope(isolate()); + + const int64_t view_id = + FlutterCreateRegularWindow(engine_id(), creation_request()); + const HWND window_handle = FlutterGetWindowHandle(engine_id(), view_id); + EXPECT_NE(window_handle, nullptr); +} + +TEST_F(FlutterHostWindowControllerTest, GetWindowSize) { + IsolateScope isolate_scope(isolate()); + + const int64_t view_id = + FlutterCreateRegularWindow(engine_id(), creation_request()); + const HWND window_handle = FlutterGetWindowHandle(engine_id(), view_id); + + FlutterWindowSize size = FlutterGetWindowContentSize(window_handle); + + EXPECT_EQ(size.width, creation_request()->content_size.width); + EXPECT_EQ(size.height, creation_request()->content_size.height); +} + +TEST_F(FlutterHostWindowControllerTest, GetWindowState) { + IsolateScope isolate_scope(isolate()); + + const int64_t view_id = + FlutterCreateRegularWindow(engine_id(), creation_request()); + const HWND window_handle = FlutterGetWindowHandle(engine_id(), view_id); + const int64_t window_state = FlutterGetWindowState(window_handle); + EXPECT_EQ(window_state, static_cast(WindowState::kRestored)); +} + +TEST_F(FlutterHostWindowControllerTest, SetWindowState) { + IsolateScope isolate_scope(isolate()); + + const int64_t view_id = + FlutterCreateRegularWindow(engine_id(), creation_request()); + const HWND window_handle = FlutterGetWindowHandle(engine_id(), view_id); + + const std::array kWindowStates = { + static_cast(WindowState::kRestored), + static_cast(WindowState::kMaximized), + static_cast(WindowState::kMinimized), + }; + + for (const auto requested_state : kWindowStates) { + FlutterSetWindowState(window_handle, requested_state); + const int64_t actual_state = FlutterGetWindowState(window_handle); + EXPECT_EQ(actual_state, requested_state); + } +} + +TEST_F(FlutterHostWindowControllerTest, SetWindowSize) { + IsolateScope isolate_scope(isolate()); + + const int64_t view_id = + FlutterCreateRegularWindow(engine_id(), creation_request()); + const HWND window_handle = FlutterGetWindowHandle(engine_id(), view_id); + + FlutterWindowSizing requestedSize{ + .has_size = true, + .width = 640, + .height = 480, + }; + FlutterSetWindowContentSize(window_handle, &requestedSize); + + FlutterWindowSize actual_size = FlutterGetWindowContentSize(window_handle); + EXPECT_EQ(actual_size.width, 640); + EXPECT_EQ(actual_size.height, 480); +} + +} // namespace testing +} // namespace flutter diff --git a/engine/src/flutter/shell/platform/windows/flutter_windows_engine.cc b/engine/src/flutter/shell/platform/windows/flutter_windows_engine.cc index 3b6d76db1852a..17af5b0a6dc59 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_windows_engine.cc +++ b/engine/src/flutter/shell/platform/windows/flutter_windows_engine.cc @@ -21,6 +21,7 @@ #include "flutter/shell/platform/windows/accessibility_bridge_windows.h" #include "flutter/shell/platform/windows/compositor_opengl.h" #include "flutter/shell/platform/windows/compositor_software.h" +#include "flutter/shell/platform/windows/flutter_host_window_controller.h" #include "flutter/shell/platform/windows/flutter_windows_view.h" #include "flutter/shell/platform/windows/keyboard_key_channel_handler.h" #include "flutter/shell/platform/windows/system_utils.h" @@ -205,8 +206,18 @@ FlutterWindowsEngine::FlutterWindowsEngine( FlutterWindowsEngine* that = static_cast(user_data); BASE_DCHECK(that->lifecycle_manager_); - return that->lifecycle_manager_->WindowProc(hwnd, msg, wpar, lpar, - result); + bool handled = + that->lifecycle_manager_->WindowProc(hwnd, msg, wpar, lpar, result); + if (handled) { + return true; + } + auto message_result = + that->host_window_controller_->HandleMessage(hwnd, msg, wpar, lpar); + if (message_result) { + *result = *message_result; + return true; + } + return false; }, static_cast(this)); @@ -224,6 +235,7 @@ FlutterWindowsEngine::FlutterWindowsEngine( std::make_unique(messenger_wrapper_.get(), this); platform_handler_ = std::make_unique(messenger_wrapper_.get(), this); + host_window_controller_ = std::make_unique(this); settings_plugin_ = std::make_unique(messenger_wrapper_.get(), task_runner_.get()); } @@ -499,6 +511,7 @@ bool FlutterWindowsEngine::Run(std::string_view entrypoint) { bool FlutterWindowsEngine::Stop() { if (engine_) { + host_window_controller_->OnEngineShutdown(); for (const auto& [callback, registrar] : plugin_registrar_destruction_callbacks_) { callback(registrar); @@ -839,6 +852,20 @@ HCURSOR FlutterWindowsEngine::GetCursorByName( return windows_proc_table_->LoadCursor(nullptr, idc_name); } +FlutterWindowsView* FlutterWindowsEngine::GetViewFromTopLevelWindow( + HWND hwnd) const { + std::shared_lock read_lock(views_mutex_); + auto const iterator = + std::find_if(views_.begin(), views_.end(), [hwnd](auto const& pair) { + FlutterWindowsView* const view = pair.second; + return GetAncestor(view->GetWindowHandle(), GA_ROOT) == hwnd; + }); + if (iterator != views_.end()) { + return iterator->second; + } + return nullptr; +} + void FlutterWindowsEngine::SendSystemLocales() { std::vector languages = GetPreferredLanguageInfo(*windows_proc_table_); diff --git a/engine/src/flutter/shell/platform/windows/flutter_windows_engine.h b/engine/src/flutter/shell/platform/windows/flutter_windows_engine.h index 2fa8784054606..a90220bd811f8 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_windows_engine.h +++ b/engine/src/flutter/shell/platform/windows/flutter_windows_engine.h @@ -30,6 +30,7 @@ #include "flutter/shell/platform/windows/egl/manager.h" #include "flutter/shell/platform/windows/egl/proc_table.h" #include "flutter/shell/platform/windows/flutter_desktop_messenger.h" +#include "flutter/shell/platform/windows/flutter_host_window.h" #include "flutter/shell/platform/windows/flutter_project_bundle.h" #include "flutter/shell/platform/windows/flutter_windows_texture_registrar.h" #include "flutter/shell/platform/windows/keyboard_handler_base.h" @@ -315,6 +316,14 @@ class FlutterWindowsEngine { // Sets the cursor directly from a cursor handle. void SetFlutterCursor(HCURSOR cursor) const; + FlutterHostWindowController* get_host_window_controller() { + return host_window_controller_.get(); + } + + // Returns the root view associated with the top-level window with |hwnd| as + // the window handle. + FlutterWindowsView* GetViewFromTopLevelWindow(HWND hwnd) const; + protected: // Creates the keyboard key handler. // @@ -453,6 +462,10 @@ class FlutterWindowsEngine { // Handlers for keyboard events from Windows. std::unique_ptr keyboard_key_handler_; + // The controller that manages the lifecycle of |FlutterHostWindow|s, native + // Win32 windows hosting a Flutter view in their client area. + std::unique_ptr host_window_controller_; + // Handlers for text events from Windows. std::unique_ptr text_input_plugin_; From 6409a212f468e1e0dfdc16d77ca2daf97fe7f62d Mon Sep 17 00:00:00 2001 From: Matej Knopp Date: Mon, 12 May 2025 14:20:13 +0200 Subject: [PATCH 02/44] multiwindow: framework --- dev/bots/allowlist.dart | 1 + .../flutter/lib/src/widgets/_window_ffi.dart | 21 ++ .../flutter/lib/src/widgets/_window_web.dart | 11 + packages/flutter/lib/src/widgets/binding.dart | 17 + packages/flutter/lib/src/widgets/window.dart | 310 ++++++++++++++++- .../flutter/lib/src/widgets/window_macos.dart | 228 ++++++++++++ .../flutter/lib/src/widgets/window_win32.dart | 325 ++++++++++++++++++ packages/flutter/lib/widgets.dart | 1 + packages/flutter/pubspec.yaml | 1 + packages/flutter_test/lib/src/binding.dart | 6 + packages/flutter_test/lib/src/windowing.dart | 21 ++ 11 files changed, 940 insertions(+), 2 deletions(-) create mode 100644 packages/flutter/lib/src/widgets/_window_ffi.dart create mode 100644 packages/flutter/lib/src/widgets/_window_web.dart create mode 100644 packages/flutter/lib/src/widgets/window_macos.dart create mode 100644 packages/flutter/lib/src/widgets/window_win32.dart create mode 100644 packages/flutter_test/lib/src/windowing.dart diff --git a/dev/bots/allowlist.dart b/dev/bots/allowlist.dart index 91f5da871f202..24fc2cb6d62c2 100644 --- a/dev/bots/allowlist.dart +++ b/dev/bots/allowlist.dart @@ -23,6 +23,7 @@ const Set kCorePackageAllowList = { 'clock', 'collection', 'fake_async', + 'ffi', 'file', 'flutter', 'flutter_driver', diff --git a/packages/flutter/lib/src/widgets/_window_ffi.dart b/packages/flutter/lib/src/widgets/_window_ffi.dart new file mode 100644 index 0000000000000..897ba1d321876 --- /dev/null +++ b/packages/flutter/lib/src/widgets/_window_ffi.dart @@ -0,0 +1,21 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:io'; + +import 'window.dart'; +import 'window_macos.dart'; +import 'window_win32.dart'; + +/// Creates a default [WindowingOwner] for current platform. +/// Only supported on desktop platforms. +WindowingOwner? createDefaultOwner() { + if (Platform.isMacOS) { + return WindowingOwnerMacOS(); + } else if (Platform.isWindows) { + return WindowingOwnerWin32(); + } else { + return null; + } +} diff --git a/packages/flutter/lib/src/widgets/_window_web.dart b/packages/flutter/lib/src/widgets/_window_web.dart new file mode 100644 index 0000000000000..160eaeb192f27 --- /dev/null +++ b/packages/flutter/lib/src/widgets/_window_web.dart @@ -0,0 +1,11 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'window.dart'; + +/// Creates a default [WindowingOwner] for web. Returns `null` as web does not +/// support multiple windows. +WindowingOwner? createDefaultOwner() { + return null; +} diff --git a/packages/flutter/lib/src/widgets/binding.dart b/packages/flutter/lib/src/widgets/binding.dart index 8ed41cbbd8e74..9b3df9b790e00 100644 --- a/packages/flutter/lib/src/widgets/binding.dart +++ b/packages/flutter/lib/src/widgets/binding.dart @@ -44,6 +44,7 @@ import 'router.dart'; import 'service_extensions.dart'; import 'view.dart'; import 'widget_inspector.dart'; +import 'window.dart'; export 'dart:ui' show AppLifecycleState, Locale; @@ -459,6 +460,7 @@ mixin WidgetsBinding return true; }()); platformMenuDelegate = DefaultPlatformMenuDelegate(); + _windowingOwner = createWindowingOwner(); } /// The current [WidgetsBinding], if one has been created. @@ -1438,6 +1440,21 @@ mixin WidgetsBinding Locale? computePlatformResolvedLocale(List supportedLocales) { return platformDispatcher.computePlatformResolvedLocale(supportedLocales); } + + /// The [WindowingOwner] is responsible for creating and managing [WindowController]s. + /// Default [WindowingOwner] supports standard Flutter desktop embedders. + /// + /// Custom [WindowingOwner] can be provided by overriding [createWindowingOwner]. + WindowingOwner get windowingOwner => _windowingOwner; + late WindowingOwner _windowingOwner; + + /// Creates the [WindowingOwner] instance available via [windowingOwner]. + /// Can be overriden in subclasses to create embedder-specific [WindowingOwner] + /// implementation. + @protected + WindowingOwner createWindowingOwner() { + return WindowingOwner.createDefaultOwner(); + } } /// Inflate the given widget and attach it to the view. diff --git a/packages/flutter/lib/src/widgets/window.dart b/packages/flutter/lib/src/widgets/window.dart index a9e827862b0e0..0206eccbf00fe 100644 --- a/packages/flutter/lib/src/widgets/window.dart +++ b/packages/flutter/lib/src/widgets/window.dart @@ -2,5 +2,311 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -/// Placeholder to be used in a future version of Flutter. -abstract final class Window {} +import 'dart:ui' show AppExitType, FlutterView; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; + +import '_window_ffi.dart' if (dart.library.js_util) '_window_web.dart' as window_impl; +import 'binding.dart'; +import 'framework.dart'; +import 'view.dart'; + +/// Defines the possible archetypes for a window. +enum WindowArchetype { + /// Defines a traditional window + regular, +} + +/// Defines the possible states that a window can be in. +enum WindowState { + /// Window is in its normal state, neither maximized, nor minimized. + restored, + + /// Window is maximized, occupying the full screen but still showing the system UI. + maximized, + + /// Window is minimized and not visible on the screen. + minimized, +} + +/// Defines sizing request for a window. +class WindowSizing { + /// Creates a new [WindowSizing] object. + WindowSizing({this.preferredSize, this.constraints}); + + /// Preferred size of the window. This may not be honored by the platform. + final Size? preferredSize; + + /// Constraints for the window. This may not be honored by the platform. + final BoxConstraints? constraints; +} + +/// Base class for window controllers. +/// +/// A window controller must provide a [future] that resolves to a +/// a [WindowCreationResult] object. This object contains the view +/// associated with the window, the archetype of the window, the size +/// of the window, and the state of the window. +/// +/// The caller may also provide a callback to be called when the window +/// is destroyed, and a callback to be called when an error is encountered +/// during the creation of the window. +/// +/// Each [WindowController] is associated with exactly one root [FlutterView]. +/// +/// When the window is destroyed for any reason (either by the caller or by the +/// platform), the content of the controller will thereafter be invalid. Callers +/// may check if this content is invalid via the [isReady] property. +/// +/// This class implements the [Listenable] interface, so callers can listen +/// for changes to the window's properties. +abstract class WindowController with ChangeNotifier { + @protected + /// Sets the view associated with this window. + // ignore: use_setters_to_change_properties + void setView(FlutterView view) { + _view = view; + } + + /// The archetype of the window. + WindowArchetype get type; + + /// The current size of the window. This may differ from the requested size. + Size get contentSize; + + /// Destroys this window. It is permissible to call this method multiple times. + void destroy(); + + /// The root view associated to this window, which is unique to each window. + FlutterView get rootView => _view; + late final FlutterView _view; +} + +/// Delegate class for regular window controller. +mixin class RegularWindowControllerDelegate { + /// Invoked when user attempts to close the window. Default implementation + /// destroys the window. Subclass can override the behavior to delay + /// or prevent the window from closing. + void onWindowCloseRequested(RegularWindowController controller) { + controller.destroy(); + } + + /// Invoked when the window is closed. Default implementation exits the + /// application if this was the last top-level window. + void onWindowDestroyed() { + final WindowingOwner owner = WidgetsBinding.instance.windowingOwner; + if (!owner.hasTopLevelWindows()) { + // No more top-level windows, exit the application. + ServicesBinding.instance.exitApplication(AppExitType.cancelable); + } + } +} + +/// A controller for a regular window. +/// +/// A regular window is a traditional window that can be resized, minimized, +/// maximized, and closed. Upon construction, the window is created for the +/// platform with the provided properties. +/// +/// This class does not interact with the widget tree. Instead, it is typically +/// provided to the [RegularWindow] widget, who does the work of rendering the +/// content inside of this window. +/// +/// An example usage might look like: +/// ```dart +/// final RegularWindowController controller = RegularWindowController( +/// contentSize: const WindowSizing( +/// size: Size(800, 600), +/// constraints: BoxConstraints(minWidth: 640, minHeight: 480), +/// ), +/// title: "Example Window", +/// ); +/// runWidget(RegularWindow( +/// controller: controller, +/// child: MaterialApp(home: Container()))); +/// ``` +/// +/// When provided to a [RegularWindow] widget, widgets inside of the [child] +/// parameter will have access to the [RegularWindowController] via the +/// [WindowControllerContext] widget. +abstract class RegularWindowController extends WindowController { + /// Creates a [RegularWindowController] with the provided properties. + /// Upon construction, the window is created for the platform. + /// + /// [contentSize] sizing requests for the window. This may not be honored by the platform + /// [title] the title of the window + /// [state] the initial state of the window + /// [delegate] optional delegate for the controller controller. + factory RegularWindowController({ + required WindowSizing contentSize, + String? title, + WindowState? state, + RegularWindowControllerDelegate? delegate, + }) { + WidgetsFlutterBinding.ensureInitialized(); + final WindowingOwner owner = WidgetsBinding.instance.windowingOwner; + final RegularWindowController controller = owner.createRegularWindowController( + contentSize: contentSize, + delegate: delegate ?? RegularWindowControllerDelegate(), + ); + if (title != null) { + controller.setTitle(title); + } + if (state != null) { + controller.setState(state); + } + return controller; + } + + @protected + /// Creates an empty [RegularWindowController]. + RegularWindowController.empty(); + + @override + WindowArchetype get type => WindowArchetype.regular; + + /// The current state of the window. + WindowState get state; + + /// Request change for the window content size. + /// + /// [contentSize] describes the new requested window size. The properties + /// of this object are applied independently of each other. For example, + /// setting [WindowSizing.preferredSize] does not affect the [WindowSizing.constraints] + /// set previously. + /// + /// System compositor is free to ignore the request. + void updateContentSize(WindowSizing sizing); + + /// Request change for the window title. + /// [title] new title of the window. + void setTitle(String title); + + /// Request change for the window state. + /// [state] new state of the window. + void setState(WindowState state); +} + +/// [WindowingOwner] is responsible for creating and managing window controllers. +/// +/// Custom subclass can be provided by subclassing [WidgetsBinding] and +/// overriding the [createWindowingOwner] method. +abstract class WindowingOwner { + /// Creates a [RegularWindowController] with the provided properties. + RegularWindowController createRegularWindowController({ + required WindowSizing contentSize, + required RegularWindowControllerDelegate delegate, + }); + + /// Returns whether application has any top level windows created by this + /// windowing owner. + bool hasTopLevelWindows(); + + /// Creates default windowing owner for standard desktop embedders. + static WindowingOwner createDefaultOwner() { + return window_impl.createDefaultOwner() ?? _FallbackWindowingOwner(); + } +} + +/// Windowing delegate used on platforms that do not support windowing. +class _FallbackWindowingOwner extends WindowingOwner { + @override + RegularWindowController createRegularWindowController({ + required WindowSizing contentSize, + required RegularWindowControllerDelegate delegate, + }) { + throw UnsupportedError( + 'Current platform does not support windowing.\n' + 'Implement a WindowingDelegate for this platform.', + ); + } + + @override + bool hasTopLevelWindows() { + return false; + } +} + +/// The [RegularWindow] widget provides a way to render a regular window in the +/// widget tree. The provided [controller] creates the native window that backs +/// the widget. The [child] widget is rendered into this newly created window. +/// +/// While the window is being created, the [RegularWindow] widget will render +/// an empty [ViewCollection] widget. Once the window is created, the [child] +/// widget will be rendered into the window inside of a [View]. +/// +/// An example usage might look like: +/// ```dart +/// final RegularWindowController controller = RegularWindowController( +/// contentSize: const WindowSizing( +/// size: Size(800, 600), +/// constraints: BoxConstraints(minWidth: 640, minHeight: 480), +/// ), +/// title: "Example Window", +/// ); +/// runApp(RegularWindow( +/// controller: controller, +/// child: MaterialApp(home: Container()))); +/// ``` +/// +/// When a [RegularWindow] widget is removed from the tree, the window that was created +/// by the [controller] is automatically destroyed if it has not yet been destroyed. +/// +/// Widgets in the same tree as the [child] widget will have access to the +/// [RegularWindowController] via the [WindowControllerContext] widget. +class RegularWindow extends StatefulWidget { + /// Creates a regular window widget. + /// [controller] the controller for this window + /// [child] the content to render into this window + /// [key] the key for this widget + const RegularWindow({super.key, required this.controller, required this.child}); + + /// Controller for this widget. + final RegularWindowController controller; + + /// The content rendered into this window. + final Widget child; + + @override + State createState() => _RegularWindowState(); +} + +class _RegularWindowState extends State { + @override + void dispose() { + super.dispose(); + widget.controller.destroy(); + } + + @override + Widget build(BuildContext context) { + return View( + view: widget.controller.rootView, + child: WindowControllerContext(controller: widget.controller, child: widget.child), + ); + } +} + +/// Provides descendants with access to the [WindowController] associated with +/// the window that is being rendered. +class WindowControllerContext extends InheritedWidget { + /// Creates a new [WindowControllerContext] + /// [controller] the controller associated with this window + /// [child] the child widget + const WindowControllerContext({super.key, required this.controller, required super.child}); + + /// The controller associated with this window. + final WindowController controller; + + /// Returns the [WindowContext] if any + static WindowController? of(BuildContext context) { + return context.dependOnInheritedWidgetOfExactType()?.controller; + } + + @override + bool updateShouldNotify(WindowControllerContext oldWidget) { + return controller != oldWidget.controller; + } +} diff --git a/packages/flutter/lib/src/widgets/window_macos.dart b/packages/flutter/lib/src/widgets/window_macos.dart new file mode 100644 index 0000000000000..fd082ffed639e --- /dev/null +++ b/packages/flutter/lib/src/widgets/window_macos.dart @@ -0,0 +1,228 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:ffi' hide Size; +import 'dart:ui' show FlutterView; + +import 'package:ffi/ffi.dart' as ffi; +import 'package:flutter/foundation.dart'; +import 'package:flutter/rendering.dart'; + +import 'binding.dart'; +import 'window.dart'; + +/// The macOS implementation of the windowing API. +class WindowingOwnerMacOS extends WindowingOwner { + @override + RegularWindowController createRegularWindowController({ + required WindowSizing contentSize, + required RegularWindowControllerDelegate delegate, + }) { + final RegularWindowControllerMacOS res = RegularWindowControllerMacOS( + owner: this, + delegate: delegate, + contentSize: contentSize, + ); + _activeControllers.add(res); + return res; + } + + @override + bool hasTopLevelWindows() { + return _activeControllers.isNotEmpty; + } + + final List _activeControllers = []; + + /// Returns the window handle for the given [view], or null is the window + /// handle is not available. + /// The window handle is a pointer to NSWindow instance. + static Pointer getWindowHandle(FlutterView view) { + return _getWindowHandle(PlatformDispatcher.instance.engineId!, view.viewId); + } + + @Native Function(Int64, Int64)>(symbol: 'FlutterGetWindowHandle') + external static Pointer _getWindowHandle(int engineId, int viewId); +} + +/// The macOS implementation of the regular window controller. +class RegularWindowControllerMacOS extends RegularWindowController { + /// Creates a new regular window controller for macOS. When this constructor + /// completes the FlutterView is created and framework is aware of it. + RegularWindowControllerMacOS({ + required WindowingOwnerMacOS owner, + required RegularWindowControllerDelegate delegate, + required WindowSizing contentSize, + String? title, + }) : _owner = owner, + _delegate = delegate, + super.empty() { + _onClose = NativeCallable.isolateLocal(_handleOnClose); + _onResize = NativeCallable.isolateLocal(_handleOnResize); + final Pointer<_WindowCreationRequest> request = + ffi.calloc<_WindowCreationRequest>() + ..ref.contentSize.set(contentSize) + ..ref.onClose = _onClose.nativeFunction + ..ref.onSizeChange = _onResize.nativeFunction; + + final int viewId = _createWindow(PlatformDispatcher.instance.engineId!, request); + ffi.calloc.free(request); + final FlutterView flutterView = WidgetsBinding.instance.platformDispatcher.views.firstWhere( + (FlutterView view) => view.viewId == viewId, + ); + setView(flutterView); + if (title != null) { + setTitle(title); + } + } + + /// Returns window handle for the current window. + /// The handle is a pointer to NSWindow instance. + Pointer getWindowHandle() { + return WindowingOwnerMacOS.getWindowHandle(rootView); + } + + bool _destroyed = false; + + @override + void destroy() { + if (_destroyed) { + return; + } + _destroyed = true; + _owner._activeControllers.remove(this); + _destroyWindow(PlatformDispatcher.instance.engineId!, getWindowHandle()); + _delegate.onWindowDestroyed(); + _onClose.close(); + _onResize.close(); + } + + void _handleOnClose() { + _delegate.onWindowCloseRequested(this); + } + + void _handleOnResize() { + notifyListeners(); + } + + @override + void updateContentSize(WindowSizing sizing) { + final Pointer<_Sizing> ffiSizing = ffi.calloc<_Sizing>(); + ffiSizing.ref.set(sizing); + _setWindowContentSize(getWindowHandle(), ffiSizing); + ffi.calloc.free(ffiSizing); + } + + @override + void setTitle(String title) { + final Pointer titlePointer = title.toNativeUtf8(); + _setWindowTitle(getWindowHandle(), titlePointer); + ffi.calloc.free(titlePointer); + } + + final WindowingOwnerMacOS _owner; + final RegularWindowControllerDelegate _delegate; + late final NativeCallable _onClose; + late final NativeCallable _onResize; + + @override + Size get contentSize { + final _Size size = _getWindowContentSize(getWindowHandle()); + return Size(size.width, size.height); + } + + @override + WindowState get state => WindowState.values[_getWindowState(getWindowHandle())]; + + @override + void setState(WindowState state) { + _setWindowState(getWindowHandle(), state.index); + } + + @Native)>( + symbol: 'FlutterCreateRegularWindow', + ) + external static int _createWindow(int engineId, Pointer<_WindowCreationRequest> request); + + @Native)>(symbol: 'FlutterDestroyWindow') + external static void _destroyWindow(int engineId, Pointer handle); + + @Native<_Size Function(Pointer)>(symbol: 'FlutterGetWindowContentSize') + external static _Size _getWindowContentSize(Pointer windowHandle); + + @Native, Pointer<_Sizing>)>(symbol: 'FlutterSetWindowContentSize') + external static void _setWindowContentSize(Pointer windowHandle, Pointer<_Sizing> size); + + @Native, Pointer)>(symbol: 'FlutterSetWindowTitle') + external static void _setWindowTitle(Pointer windowHandle, Pointer title); + + @Native)>(symbol: 'FlutterGetWindowState') + external static int _getWindowState(Pointer windowHandle); + + @Native, Int64)>(symbol: 'FlutterSetWindowState') + external static void _setWindowState(Pointer windowHandle, int state); +} + +final class _Sizing extends Struct { + @Bool() + external bool hasSize; + + @Double() + external double width; + + @Double() + external double height; + + @Bool() + external bool hasConstraints; + + @Double() + external double minWidth; + + @Double() + external double minHeight; + + @Double() + external double maxWidth; + + @Double() + external double maxHeight; + + void set(WindowSizing sizing) { + final Size? size = sizing.preferredSize; + if (size != null) { + hasSize = true; + width = size.width; + height = size.height; + } else { + hasSize = false; + } + + final BoxConstraints? constraints = sizing.constraints; + if (constraints != null) { + hasConstraints = true; + minWidth = constraints.minWidth; + minHeight = constraints.minHeight; + maxWidth = constraints.maxWidth; + maxHeight = constraints.maxHeight; + } else { + hasConstraints = false; + } + } +} + +final class _WindowCreationRequest extends Struct { + external _Sizing contentSize; + + external Pointer> onClose; + external Pointer> onSizeChange; +} + +final class _Size extends Struct { + @Double() + external double width; + + @Double() + external double height; +} diff --git a/packages/flutter/lib/src/widgets/window_win32.dart b/packages/flutter/lib/src/widgets/window_win32.dart new file mode 100644 index 0000000000000..5b2ab30aa5e37 --- /dev/null +++ b/packages/flutter/lib/src/widgets/window_win32.dart @@ -0,0 +1,325 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:ffi' hide Size; +import 'dart:ui' show FlutterView; +import 'package:ffi/ffi.dart' as ffi; +import 'package:flutter/foundation.dart'; +import 'package:flutter/rendering.dart'; + +import 'binding.dart'; +import 'window.dart'; + +/// Handler for Win32 messages. +abstract class WindowsMessageHandler { + /// Handles a window message. Returned value, if not null will be + /// returned to the system as LRESULT and will stop all other + /// handlers from being called. + int? handleWindowsMessage( + FlutterView view, + Pointer windowHandle, + int message, + int wParam, + int lParam, + ); +} + +/// Windowing owner implementation for Windows. +class WindowingOwnerWin32 extends WindowingOwner { + /// Creates a new [WindowingOwnerWin32] instance. + WindowingOwnerWin32() { + final Pointer<_WindowingInitRequest> request = + ffi.calloc<_WindowingInitRequest>() + ..ref.onMessage = + NativeCallable)>.isolateLocal( + _onMessage, + ).nativeFunction; + _initializeWindowing(PlatformDispatcher.instance.engineId!, request); + ffi.calloc.free(request); + } + + @override + RegularWindowController createRegularWindowController({ + required WindowSizing contentSize, + required RegularWindowControllerDelegate delegate, + }) { + return RegularWindowControllerWin32(owner: this, delegate: delegate, contentSize: contentSize); + } + + /// Register new message handler. The handler will be called for unhandled + /// messages for all top level windows. + void addMessageHandler(WindowsMessageHandler handler) { + _messageHandlers.add(handler); + } + + /// Unregister message handler. + void removeMessageHandler(WindowsMessageHandler handler) { + _messageHandlers.remove(handler); + } + + final List _messageHandlers = []; + + void _onMessage(Pointer<_WindowsMessage> message) { + final List handlers = List.from(_messageHandlers); + final FlutterView flutterView = WidgetsBinding.instance.platformDispatcher.views.firstWhere( + (FlutterView view) => view.viewId == message.ref.viewId, + ); + for (final WindowsMessageHandler handler in handlers) { + final int? result = handler.handleWindowsMessage( + flutterView, + message.ref.windowHandle, + message.ref.message, + message.ref.wParam, + message.ref.lParam, + ); + if (result != null) { + message.ref.handled = true; + message.ref.lResult = result; + return; + } + } + } + + @override + bool hasTopLevelWindows() { + return _hasTopLevelWindows(PlatformDispatcher.instance.engineId!); + } + + @Native(symbol: 'FlutterWindowingHasTopLevelWindows') + external static bool _hasTopLevelWindows(int engineId); + + @Native)>( + symbol: 'FlutterWindowingInitialize', + ) + external static void _initializeWindowing(int engineId, Pointer<_WindowingInitRequest> request); +} + +/// The Win32 implementation of the regular window controller. +class RegularWindowControllerWin32 extends RegularWindowController + implements WindowsMessageHandler { + /// Creates a new regular window controller for Win32. When this constructor + /// completes the FlutterView is created and framework is aware of it. + RegularWindowControllerWin32({ + required WindowingOwnerWin32 owner, + required RegularWindowControllerDelegate delegate, + required WindowSizing contentSize, + }) : _owner = owner, + _delegate = delegate, + super.empty() { + owner.addMessageHandler(this); + final Pointer<_WindowCreationRequest> request = + ffi.calloc<_WindowCreationRequest>()..ref.contentSize.set(contentSize); + final int viewId = _createWindow(PlatformDispatcher.instance.engineId!, request); + ffi.calloc.free(request); + final FlutterView flutterView = WidgetsBinding.instance.platformDispatcher.views.firstWhere( + (FlutterView view) => view.viewId == viewId, + ); + setView(flutterView); + } + + @override + Size get contentSize { + _ensureNotDestroyed(); + final _Size size = _getWindowContentSize(getWindowHandle()); + final Size result = Size(size.width, size.height); + return result; + } + + @override + WindowState get state { + _ensureNotDestroyed(); + final int state = _getWindowState(getWindowHandle()); + return WindowState.values[state]; + } + + @override + void setState(WindowState state) { + _ensureNotDestroyed(); + _setWindowState(getWindowHandle(), state.index); + } + + @override + void setTitle(String title) { + _ensureNotDestroyed(); + final Pointer titlePointer = title.toNativeUtf16(); + _setWindowTitle(getWindowHandle(), titlePointer); + ffi.calloc.free(titlePointer); + } + + @override + void updateContentSize(WindowSizing sizing) { + _ensureNotDestroyed(); + final Pointer<_Sizing> ffiSizing = ffi.calloc<_Sizing>(); + ffiSizing.ref.set(sizing); + _setWindowContentSize(getWindowHandle(), ffiSizing); + ffi.calloc.free(ffiSizing); + } + + /// Returns HWND pointer to the top level window. + Pointer getWindowHandle() { + _ensureNotDestroyed(); + return _getWindowHandle(PlatformDispatcher.instance.engineId!, rootView.viewId); + } + + void _ensureNotDestroyed() { + if (_destroyed) { + throw StateError('Window has been destroyed.'); + } + } + + final RegularWindowControllerDelegate _delegate; + bool _destroyed = false; + + @override + void destroy() { + if (_destroyed) { + return; + } + _destroyWindow(getWindowHandle()); + _destroyed = true; + _delegate.onWindowDestroyed(); + _owner.removeMessageHandler(this); + } + + static const int _WM_SIZE = 0x0005; + static const int _WM_CLOSE = 0x0010; + + @override + int? handleWindowsMessage( + FlutterView view, + Pointer windowHandle, + int message, + int wParam, + int lParam, + ) { + if (view.viewId != rootView.viewId) { + return null; + } + + if (message == _WM_CLOSE) { + _delegate.onWindowCloseRequested(this); + return 0; + } else if (message == _WM_SIZE) { + notifyListeners(); + } + return null; + } + + final WindowingOwnerWin32 _owner; + + @Native)>( + symbol: 'FlutterCreateRegularWindow', + ) + external static int _createWindow(int engineId, Pointer<_WindowCreationRequest> request); + + @Native Function(Int64, Int64)>(symbol: 'FlutterGetWindowHandle') + external static Pointer _getWindowHandle(int engineId, int viewId); + + @Native)>(symbol: 'DestroyWindow') + external static void _destroyWindow(Pointer windowHandle); + + @Native<_Size Function(Pointer)>(symbol: 'FlutterGetWindowContentSize') + external static _Size _getWindowContentSize(Pointer windowHandle); + + @Native)>(symbol: 'FlutterGetWindowState') + external static int _getWindowState(Pointer windowHandle); + + @Native, Int64)>(symbol: 'FlutterSetWindowState') + external static void _setWindowState(Pointer windowHandle, int state); + + @Native, Pointer)>(symbol: 'SetWindowTextW') + external static void _setWindowTitle(Pointer windowHandle, Pointer title); + + @Native, Pointer<_Sizing>)>(symbol: 'FlutterSetWindowContentSize') + external static void _setWindowContentSize(Pointer windowHandle, Pointer<_Sizing> size); +} + +/// Request to initialize windowing system. +final class _WindowingInitRequest extends Struct { + external Pointer)>> onMessage; +} + +final class _Sizing extends Struct { + @Bool() + external bool hasSize; + + @Double() + external double width; + + @Double() + external double height; + + @Bool() + external bool hasConstraints; + + @Double() + external double minWidth; + + @Double() + external double minHeight; + + @Double() + external double maxWidth; + + @Double() + external double maxHeight; + + void set(WindowSizing sizing) { + final Size? size = sizing.preferredSize; + if (size != null) { + hasSize = true; + width = size.width; + height = size.height; + } else { + hasSize = false; + } + + final BoxConstraints? constraints = sizing.constraints; + if (constraints != null) { + hasConstraints = true; + minWidth = constraints.minWidth; + minHeight = constraints.minHeight; + maxWidth = constraints.maxWidth; + maxHeight = constraints.maxHeight; + } else { + hasConstraints = false; + } + } +} + +final class _WindowCreationRequest extends Struct { + external _Sizing contentSize; +} + +/// Windows message received for all top level windows (regardless whether +/// they are created using a windowing controller). +final class _WindowsMessage extends Struct { + @Int64() + external int viewId; + + external Pointer windowHandle; + + @Int32() + external int message; + + @Int64() + external int wParam; + + @Int64() + external int lParam; + + @Int64() + external int lResult; + + @Bool() + external bool handled; +} + +final class _Size extends Struct { + @Double() + external double width; + + @Double() + external double height; +} diff --git a/packages/flutter/lib/widgets.dart b/packages/flutter/lib/widgets.dart index efe2858c67443..5104162087656 100644 --- a/packages/flutter/lib/widgets.dart +++ b/packages/flutter/lib/widgets.dart @@ -180,3 +180,4 @@ export 'src/widgets/widget_inspector.dart'; export 'src/widgets/widget_span.dart'; export 'src/widgets/widget_state.dart'; export 'src/widgets/will_pop_scope.dart'; +export 'src/widgets/window.dart'; diff --git a/packages/flutter/pubspec.yaml b/packages/flutter/pubspec.yaml index 3654490d76367..a8e79a218a818 100644 --- a/packages/flutter/pubspec.yaml +++ b/packages/flutter/pubspec.yaml @@ -19,6 +19,7 @@ dependencies: vector_math: 2.2.0 sky_engine: sdk: flutter + ffi: ^2.1.4 dev_dependencies: flutter_driver: diff --git a/packages/flutter_test/lib/src/binding.dart b/packages/flutter_test/lib/src/binding.dart index 5808af6386397..07903161d26ec 100644 --- a/packages/flutter_test/lib/src/binding.dart +++ b/packages/flutter_test/lib/src/binding.dart @@ -35,6 +35,7 @@ import 'test_default_binary_messenger.dart'; import 'test_exception_reporter.dart'; import 'test_text_input.dart'; import 'window.dart'; +import 'windowing.dart'; /// Phases that can be reached by [WidgetTester.pumpWidget] and /// [TestWidgetsFlutterBinding.pump]. @@ -204,6 +205,11 @@ abstract class TestWidgetsFlutterBinding extends BindingBase debugDisableShadows = disableShadows; } + @override + WindowingOwner createWindowingOwner() { + return TestWindowingOwner(); + } + /// Deprecated. Will be removed in a future version of Flutter. /// /// This property has been deprecated to prepare for Flutter's upcoming diff --git a/packages/flutter_test/lib/src/windowing.dart b/packages/flutter_test/lib/src/windowing.dart new file mode 100644 index 0000000000000..b1b82cf112c8e --- /dev/null +++ b/packages/flutter_test/lib/src/windowing.dart @@ -0,0 +1,21 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/widgets.dart'; + +/// WindowingOwner used in Flutter Tester. +class TestWindowingOwner extends WindowingOwner { + @override + RegularWindowController createRegularWindowController({ + required WindowSizing contentSize, + required RegularWindowControllerDelegate delegate, + }) { + throw UnsupportedError('Current platform does not support windowing.\n'); + } + + @override + bool hasTopLevelWindows() { + return false; + } +} From 3a2ef5445a6bd4d5d66f6520cd28232103e7db52 Mon Sep 17 00:00:00 2001 From: Matej Knopp Date: Mon, 12 May 2025 14:21:41 +0200 Subject: [PATCH 03/44] multiwindow: ref app multiwindow: framework feedback (ref app) --- examples/multi_window_ref_app/.gitignore | 45 ++ examples/multi_window_ref_app/.metadata | 30 + .../multi_window_ref_app/.vscode/launch.json | 35 + examples/multi_window_ref_app/README.md | 5 + .../analysis_options.yaml | 28 + .../lib/app/main_window.dart | 273 +++++++ .../lib/app/regular_window_content.dart | 214 ++++++ .../lib/app/regular_window_edit_dialog.dart | 90 +++ .../lib/app/window_controller_render.dart | 40 + .../lib/app/window_manager_model.dart | 55 ++ .../lib/app/window_settings.dart | 17 + .../lib/app/window_settings_dialog.dart | 94 +++ examples/multi_window_ref_app/lib/main.dart | 22 + .../multi_window_ref_app/macos/.gitignore | 7 + .../macos/Flutter/Flutter-Debug.xcconfig | 1 + .../macos/Flutter/Flutter-Release.xcconfig | 1 + .../macos/Runner.xcodeproj/project.pbxproj | 705 ++++++++++++++++++ .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 99 +++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../macos/Runner/AppDelegate.swift | 25 + .../Runner/Assets.xcassets/Contents.json | 6 + .../macos/Runner/Base.lproj/MainMenu.xib | 332 +++++++++ .../macos/Runner/Configs/AppInfo.xcconfig | 14 + .../macos/Runner/Configs/Debug.xcconfig | 2 + .../macos/Runner/Configs/Release.xcconfig | 2 + .../macos/Runner/Configs/Warnings.xcconfig | 13 + .../macos/Runner/DebugProfile.entitlements | 12 + .../macos/Runner/Info.plist | 32 + .../macos/Runner/MainFlutterWindow.swift | 19 + .../macos/Runner/Release.entitlements | 8 + .../macos/RunnerTests/RunnerTests.swift | 16 + examples/multi_window_ref_app/pubspec.yaml | 48 ++ .../test/widget_test.dart | 9 + .../multi_window_ref_app/windows/.gitignore | 17 + .../windows/CMakeLists.txt | 108 +++ .../windows/flutter/CMakeLists.txt | 109 +++ .../windows/runner/CMakeLists.txt | 37 + .../windows/runner/Runner.rc | 111 +++ .../windows/runner/main.cpp | 41 + .../windows/runner/resource.h | 19 + .../windows/runner/runner.exe.manifest | 14 + .../windows/runner/utils.cpp | 68 ++ .../windows/runner/utils.h | 23 + 45 files changed, 2869 insertions(+) create mode 100644 examples/multi_window_ref_app/.gitignore create mode 100644 examples/multi_window_ref_app/.metadata create mode 100644 examples/multi_window_ref_app/.vscode/launch.json create mode 100644 examples/multi_window_ref_app/README.md create mode 100644 examples/multi_window_ref_app/analysis_options.yaml create mode 100644 examples/multi_window_ref_app/lib/app/main_window.dart create mode 100644 examples/multi_window_ref_app/lib/app/regular_window_content.dart create mode 100644 examples/multi_window_ref_app/lib/app/regular_window_edit_dialog.dart create mode 100644 examples/multi_window_ref_app/lib/app/window_controller_render.dart create mode 100644 examples/multi_window_ref_app/lib/app/window_manager_model.dart create mode 100644 examples/multi_window_ref_app/lib/app/window_settings.dart create mode 100644 examples/multi_window_ref_app/lib/app/window_settings_dialog.dart create mode 100644 examples/multi_window_ref_app/lib/main.dart create mode 100644 examples/multi_window_ref_app/macos/.gitignore create mode 100644 examples/multi_window_ref_app/macos/Flutter/Flutter-Debug.xcconfig create mode 100644 examples/multi_window_ref_app/macos/Flutter/Flutter-Release.xcconfig create mode 100644 examples/multi_window_ref_app/macos/Runner.xcodeproj/project.pbxproj create mode 100644 examples/multi_window_ref_app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 examples/multi_window_ref_app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 examples/multi_window_ref_app/macos/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 examples/multi_window_ref_app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 examples/multi_window_ref_app/macos/Runner/AppDelegate.swift create mode 100644 examples/multi_window_ref_app/macos/Runner/Assets.xcassets/Contents.json create mode 100644 examples/multi_window_ref_app/macos/Runner/Base.lproj/MainMenu.xib create mode 100644 examples/multi_window_ref_app/macos/Runner/Configs/AppInfo.xcconfig create mode 100644 examples/multi_window_ref_app/macos/Runner/Configs/Debug.xcconfig create mode 100644 examples/multi_window_ref_app/macos/Runner/Configs/Release.xcconfig create mode 100644 examples/multi_window_ref_app/macos/Runner/Configs/Warnings.xcconfig create mode 100644 examples/multi_window_ref_app/macos/Runner/DebugProfile.entitlements create mode 100644 examples/multi_window_ref_app/macos/Runner/Info.plist create mode 100644 examples/multi_window_ref_app/macos/Runner/MainFlutterWindow.swift create mode 100644 examples/multi_window_ref_app/macos/Runner/Release.entitlements create mode 100644 examples/multi_window_ref_app/macos/RunnerTests/RunnerTests.swift create mode 100644 examples/multi_window_ref_app/pubspec.yaml create mode 100644 examples/multi_window_ref_app/test/widget_test.dart create mode 100644 examples/multi_window_ref_app/windows/.gitignore create mode 100644 examples/multi_window_ref_app/windows/CMakeLists.txt create mode 100644 examples/multi_window_ref_app/windows/flutter/CMakeLists.txt create mode 100644 examples/multi_window_ref_app/windows/runner/CMakeLists.txt create mode 100644 examples/multi_window_ref_app/windows/runner/Runner.rc create mode 100644 examples/multi_window_ref_app/windows/runner/main.cpp create mode 100644 examples/multi_window_ref_app/windows/runner/resource.h create mode 100644 examples/multi_window_ref_app/windows/runner/runner.exe.manifest create mode 100644 examples/multi_window_ref_app/windows/runner/utils.cpp create mode 100644 examples/multi_window_ref_app/windows/runner/utils.h diff --git a/examples/multi_window_ref_app/.gitignore b/examples/multi_window_ref_app/.gitignore new file mode 100644 index 0000000000000..79c113f9b5017 --- /dev/null +++ b/examples/multi_window_ref_app/.gitignore @@ -0,0 +1,45 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/examples/multi_window_ref_app/.metadata b/examples/multi_window_ref_app/.metadata new file mode 100644 index 0000000000000..c9660e951c922 --- /dev/null +++ b/examples/multi_window_ref_app/.metadata @@ -0,0 +1,30 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "f07dbe9f9b40ecc5557632d6feb70a198dab5668" + channel: "[user-branch]" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: f07dbe9f9b40ecc5557632d6feb70a198dab5668 + base_revision: f07dbe9f9b40ecc5557632d6feb70a198dab5668 + - platform: macos + create_revision: f07dbe9f9b40ecc5557632d6feb70a198dab5668 + base_revision: f07dbe9f9b40ecc5557632d6feb70a198dab5668 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/examples/multi_window_ref_app/.vscode/launch.json b/examples/multi_window_ref_app/.vscode/launch.json new file mode 100644 index 0000000000000..c846434f923e5 --- /dev/null +++ b/examples/multi_window_ref_app/.vscode/launch.json @@ -0,0 +1,35 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "multi_window_ref_app", + "request": "launch", + "type": "dart", + "program": "lib/main.dart", + "toolArgs": [ + "--local-engine=host_debug_unopt_arm64", + "--local-engine-host=host_debug_unopt_arm64", + ] + }, + { + "name": "multi_window_ref_app (profile mode)", + "request": "launch", + "type": "dart", + "flutterMode": "profile" + }, + { + "name": "multi_window_ref_app (release mode)", + "request": "launch", + "type": "dart", + "flutterMode": "release" + }, + { + "name": "(Windows) Attach", + "type": "cppvsdbg", + "request": "attach", + } + ] +} \ No newline at end of file diff --git a/examples/multi_window_ref_app/README.md b/examples/multi_window_ref_app/README.md new file mode 100644 index 0000000000000..39b39942c528b --- /dev/null +++ b/examples/multi_window_ref_app/README.md @@ -0,0 +1,5 @@ +# multi_window_ref_app + +A reference application demonstrating multi-window support for Flutter using a +rich semantics windowing API. At the moment, only the Windows platform is +supported. \ No newline at end of file diff --git a/examples/multi_window_ref_app/analysis_options.yaml b/examples/multi_window_ref_app/analysis_options.yaml new file mode 100644 index 0000000000000..0d2902135caec --- /dev/null +++ b/examples/multi_window_ref_app/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/examples/multi_window_ref_app/lib/app/main_window.dart b/examples/multi_window_ref_app/lib/app/main_window.dart new file mode 100644 index 0000000000000..c4d39eaf80897 --- /dev/null +++ b/examples/multi_window_ref_app/lib/app/main_window.dart @@ -0,0 +1,273 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:multi_window_ref_app/app/window_controller_render.dart'; + +import 'regular_window_content.dart'; +import 'window_settings.dart'; +import 'window_settings_dialog.dart'; +import 'window_manager_model.dart'; +import 'regular_window_edit_dialog.dart'; + +class MainWindow extends StatefulWidget { + MainWindow({super.key, required WindowController mainController}) { + _windowManagerModel.add( + KeyedWindowController(isMainWindow: true, key: UniqueKey(), controller: mainController)); + } + + final WindowManagerModel _windowManagerModel = WindowManagerModel(); + final WindowSettings _settings = WindowSettings(); + + @override + State createState() => _MainWindowState(); +} + +class _MainWindowState extends State { + @override + Widget build(BuildContext context) { + final child = Scaffold( + appBar: AppBar( + title: const Text('Multi Window Reference App'), + ), + body: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + flex: 60, + child: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: _ActiveWindowsTable(windowManagerModel: widget._windowManagerModel), + ), + ), + Expanded( + flex: 40, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + ListenableBuilder( + listenable: widget._windowManagerModel, + builder: (BuildContext context, Widget? child) { + return _WindowCreatorCard( + selectedWindow: widget._windowManagerModel.selected, + windowManagerModel: widget._windowManagerModel, + windowSettings: widget._settings); + }) + ], + ), + ), + ], + ), + ); + + return ViewAnchor( + view: ListenableBuilder( + listenable: widget._windowManagerModel, + builder: (BuildContext context, Widget? _) { + final List childViews = []; + for (final KeyedWindowController controller in widget._windowManagerModel.windows) { + if (controller.parent == null && !controller.isMainWindow) { + childViews.add(WindowControllerRender( + controller: controller.controller, + key: controller.key, + windowSettings: widget._settings, + windowManagerModel: widget._windowManagerModel, + onDestroyed: () => widget._windowManagerModel.remove(controller.key), + onError: () => widget._windowManagerModel.remove(controller.key), + )); + } + } + + return ViewCollection(views: childViews); + }), + child: child); + } +} + +class _ActiveWindowsTable extends StatelessWidget { + const _ActiveWindowsTable({required this.windowManagerModel}); + + final WindowManagerModel windowManagerModel; + + @override + Widget build(BuildContext context) { + return ListenableBuilder( + listenable: windowManagerModel, + builder: (BuildContext context, Widget? widget) { + return DataTable( + showBottomBorder: true, + onSelectAll: (selected) { + windowManagerModel.select(null); + }, + columns: const [ + DataColumn( + label: SizedBox( + width: 20, + child: Text( + 'ID', + style: TextStyle( + fontSize: 16, + ), + ), + ), + ), + DataColumn( + label: SizedBox( + width: 120, + child: Text( + 'Type', + style: TextStyle( + fontSize: 16, + ), + ), + ), + ), + DataColumn( + label: SizedBox( + width: 20, + child: Text(''), + ), + numeric: true), + ], + rows: (windowManagerModel.windows).map((KeyedWindowController controller) { + return DataRow( + key: controller.key, + color: WidgetStateColor.resolveWith((states) { + if (states.contains(WidgetState.selected)) { + return Theme.of(context).colorScheme.primary.withAlpha(20); + } + return Colors.transparent; + }), + selected: controller.controller == windowManagerModel.selected, + onSelectChanged: (selected) { + if (selected != null) { + windowManagerModel + .select(selected ? controller.controller.rootView.viewId : null); + } + }, + cells: [ + DataCell(Text('$controller.controller.rootView.viewId')), + DataCell( + ListenableBuilder( + listenable: controller.controller, + builder: (BuildContext context, Widget? _) => Text(controller + .controller.type + .toString() + .replaceFirst('WindowArchetype.', ''))), + ), + DataCell( + ListenableBuilder( + listenable: controller.controller, + builder: (BuildContext context, Widget? _) => Row(children: [ + IconButton( + icon: const Icon(Icons.edit_outlined), + onPressed: () { + if (controller.controller.type == WindowArchetype.regular) { + showRegularWindowEditDialog(context, + initialWidth: controller.controller.contentSize.width, + initialHeight: controller.controller.contentSize.height, + initialTitle: "", + initialState: + (controller.controller as RegularWindowController) + .state, onSave: (double? width, double? height, + String? title, WindowState? state) { + final regularController = + controller.controller as RegularWindowController; + if (width != null && height != null) { + regularController.updateContentSize( + WindowSizing(preferredSize: Size(width, height)), + ); + } + if (title != null) { + regularController.setTitle(title); + } + if (state != null) { + regularController.setState(state); + } + }); + } + }, + ), + IconButton( + icon: const Icon(Icons.delete_outlined), + onPressed: () async { + controller.controller.destroy(); + }, + ) + ])), + ), + ], + ); + }).toList(), + ); + }); + } +} + +class _WindowCreatorCard extends StatelessWidget { + const _WindowCreatorCard( + {required this.selectedWindow, + required this.windowManagerModel, + required this.windowSettings}); + + final WindowController? selectedWindow; + final WindowManagerModel windowManagerModel; + final WindowSettings windowSettings; + + @override + Widget build(BuildContext context) { + return Card.outlined( + margin: const EdgeInsets.symmetric(horizontal: 25), + child: Padding( + padding: const EdgeInsets.fromLTRB(25, 0, 25, 5), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Padding( + padding: EdgeInsets.only(top: 10, bottom: 10), + child: Text( + 'New Window', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16.0, + ), + ), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + OutlinedButton( + onPressed: () async { + final UniqueKey key = UniqueKey(); + windowManagerModel.add(KeyedWindowController( + key: key, + controller: RegularWindowController( + delegate: WindowControllerDelegate( + onDestroyed: () => windowManagerModel.remove(key), + ), + title: "Regular", + contentSize: WindowSizing(preferredSize: windowSettings.regularSize), + ))); + }, + child: const Text('Regular'), + ), + const SizedBox(height: 8), + Container( + alignment: Alignment.bottomRight, + child: TextButton( + child: const Text('SETTINGS'), + onPressed: () { + windowSettingsDialog(context, windowSettings); + }, + ), + ), + const SizedBox(width: 8), + ], + ), + ], + ), + ), + ); + } +} diff --git a/examples/multi_window_ref_app/lib/app/regular_window_content.dart b/examples/multi_window_ref_app/lib/app/regular_window_content.dart new file mode 100644 index 0000000000000..981598f82c940 --- /dev/null +++ b/examples/multi_window_ref_app/lib/app/regular_window_content.dart @@ -0,0 +1,214 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:multi_window_ref_app/app/window_controller_render.dart'; +import 'package:multi_window_ref_app/app/window_manager_model.dart'; +import 'package:multi_window_ref_app/app/window_settings.dart'; +import 'dart:math'; +import 'package:vector_math/vector_math_64.dart'; + +class RegularWindowContent extends StatefulWidget { + const RegularWindowContent( + {super.key, + required this.window, + required this.windowSettings, + required this.windowManagerModel}); + + final RegularWindowController window; + final WindowSettings windowSettings; + final WindowManagerModel windowManagerModel; + + @override + State createState() => _RegularWindowContentState(); +} + +class WindowControllerDelegate extends RegularWindowControllerDelegate { + WindowControllerDelegate({required this.onDestroyed}); + + @override + void onWindowDestroyed() { + onDestroyed(); + super.onWindowDestroyed(); + } + + final VoidCallback onDestroyed; +} + +class _RegularWindowContentState extends State + with SingleTickerProviderStateMixin { + late final AnimationController _animation; + late final Color cubeColor; + + @override + void initState() { + super.initState(); + _animation = AnimationController( + vsync: this, + lowerBound: 0, + upperBound: 2 * pi, + duration: const Duration(seconds: 15), + )..repeat(); + cubeColor = _generateRandomDarkColor(); + } + + @override + void dispose() { + _animation.dispose(); + super.dispose(); + } + + Color _generateRandomDarkColor() { + final random = Random(); + const int lowerBound = 32; + const int span = 160; + int red = lowerBound + random.nextInt(span); + int green = lowerBound + random.nextInt(span); + int blue = lowerBound + random.nextInt(span); + return Color.fromARGB(255, red, green, blue); + } + + @override + Widget build(BuildContext context) { + final dpr = MediaQuery.of(context).devicePixelRatio; + + final child = Scaffold( + appBar: AppBar(title: Text('${widget.window.type}')), + body: Center( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + AnimatedBuilder( + animation: _animation, + builder: (context, child) { + return CustomPaint( + size: const Size(200, 200), + painter: _RotatedWireCube( + angle: _animation.value, color: cubeColor), + ); + }, + ), + ], + ), + Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + ElevatedButton( + onPressed: () { + final UniqueKey key = UniqueKey(); + widget.windowManagerModel.add(KeyedWindowController( + key: key, + controller: RegularWindowController( + contentSize: WindowSizing( + preferredSize: widget.windowSettings.regularSize), + delegate: WindowControllerDelegate( + onDestroyed: () => + widget.windowManagerModel.remove(key), + ), + title: "Regular", + ))); + }, + child: const Text('Create Regular Window'), + ), + const SizedBox(height: 20), + ListenableBuilder( + listenable: widget.window, + builder: (BuildContext context, Widget? _) { + return Text( + 'View #${widget.window.rootView.viewId}\n' + 'Size: ${(widget.window.contentSize.width).toStringAsFixed(1)}\u00D7${(widget.window.contentSize.height).toStringAsFixed(1)}\n' + 'Device Pixel Ratio: $dpr', + textAlign: TextAlign.center, + ); + }) + ], + ), + ], + )), + ); + + return ViewAnchor( + view: ListenableBuilder( + listenable: widget.windowManagerModel, + builder: (BuildContext context, Widget? _) { + final List childViews = []; + for (final KeyedWindowController controller + in widget.windowManagerModel.windows) { + if (controller.parent == widget.window) { + childViews.add(WindowControllerRender( + controller: controller.controller, + key: controller.key, + windowSettings: widget.windowSettings, + windowManagerModel: widget.windowManagerModel, + onDestroyed: () => + widget.windowManagerModel.remove(controller.key), + onError: () => + widget.windowManagerModel.remove(controller.key), + )); + } + } + + return ViewCollection(views: childViews); + }), + child: child); + } +} + +class _RotatedWireCube extends CustomPainter { + static List vertices = [ + Vector3(-0.5, -0.5, -0.5), + Vector3(0.5, -0.5, -0.5), + Vector3(0.5, 0.5, -0.5), + Vector3(-0.5, 0.5, -0.5), + Vector3(-0.5, -0.5, 0.5), + Vector3(0.5, -0.5, 0.5), + Vector3(0.5, 0.5, 0.5), + Vector3(-0.5, 0.5, 0.5), + ]; + + static const List> edges = [ + [0, 1], [1, 2], [2, 3], [3, 0], // Front face + [4, 5], [5, 6], [6, 7], [7, 4], // Back face + [0, 4], [1, 5], [2, 6], [3, 7], // Connecting front and back + ]; + + final double angle; + final Color color; + + _RotatedWireCube({required this.angle, required this.color}); + + Offset scaleAndCenter(Vector3 point, double size, Offset center) { + final scale = size / 2; + return Offset(center.dx + point.x * scale, center.dy - point.y * scale); + } + + @override + void paint(Canvas canvas, Size size) { + final rotatedVertices = vertices + .map((vertex) => Matrix4.rotationX(angle).transformed3(vertex)) + .map((vertex) => Matrix4.rotationY(angle).transformed3(vertex)) + .map((vertex) => Matrix4.rotationZ(angle).transformed3(vertex)) + .toList(); + + final center = Offset(size.width / 2, size.height / 2); + + final paint = Paint() + ..color = color + ..style = PaintingStyle.stroke + ..strokeWidth = 2; + + for (var edge in edges) { + final p1 = scaleAndCenter(rotatedVertices[edge[0]], size.width, center); + final p2 = scaleAndCenter(rotatedVertices[edge[1]], size.width, center); + canvas.drawLine(p1, p2, paint); + } + } + + @override + bool shouldRepaint(_RotatedWireCube oldDelegate) => true; +} diff --git a/examples/multi_window_ref_app/lib/app/regular_window_edit_dialog.dart b/examples/multi_window_ref_app/lib/app/regular_window_edit_dialog.dart new file mode 100644 index 0000000000000..64a603c2809d0 --- /dev/null +++ b/examples/multi_window_ref_app/lib/app/regular_window_edit_dialog.dart @@ -0,0 +1,90 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; + +void showRegularWindowEditDialog(BuildContext context, + {double? initialWidth, + double? initialHeight, + String? initialTitle, + WindowState? initialState, + Function(double?, double?, String?, WindowState)? onSave}) { + final TextEditingController widthController = + TextEditingController(text: initialWidth?.toString() ?? ''); + final TextEditingController heightController = + TextEditingController(text: initialHeight?.toString() ?? ''); + final TextEditingController titleController = + TextEditingController(text: initialTitle ?? ''); + + showDialog( + context: context, + builder: (context) { + WindowState selectedState = initialState ?? WindowState.restored; + + return AlertDialog( + title: Text("Edit Window Properties"), + content: StatefulBuilder( + builder: (context, setState) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + controller: widthController, + keyboardType: TextInputType.number, + decoration: InputDecoration(labelText: "Width"), + ), + TextField( + controller: heightController, + keyboardType: TextInputType.number, + decoration: InputDecoration(labelText: "Height"), + ), + TextField( + controller: titleController, + decoration: InputDecoration(labelText: "Title"), + ), + DropdownButton( + value: selectedState, + onChanged: (WindowState? newState) { + if (newState != null) { + setState(() { + selectedState = newState; + }); + } + }, + items: WindowState.values.map((WindowState state) { + return DropdownMenuItem( + value: state, + child: Text( + state.toString().split('.').last[0].toUpperCase() + + state.toString().split('.').last.substring(1), + ), + ); + }).toList(), + ), + ], + ); + }, + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text("Cancel"), + ), + TextButton( + onPressed: () { + double? width = double.tryParse(widthController.text); + double? height = double.tryParse(heightController.text); + String? title = + titleController.text.isEmpty ? null : titleController.text; + + onSave?.call(width, height, title, selectedState); + Navigator.of(context).pop(); + }, + child: Text("Save"), + ), + ], + ); + }, + ); +} diff --git a/examples/multi_window_ref_app/lib/app/window_controller_render.dart b/examples/multi_window_ref_app/lib/app/window_controller_render.dart new file mode 100644 index 0000000000000..1e2ab569f8cb6 --- /dev/null +++ b/examples/multi_window_ref_app/lib/app/window_controller_render.dart @@ -0,0 +1,40 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'regular_window_content.dart'; +import 'window_manager_model.dart'; +import 'window_settings.dart'; + +class WindowControllerRender extends StatelessWidget { + const WindowControllerRender({ + required this.controller, + required this.onDestroyed, + required this.onError, + required this.windowSettings, + required this.windowManagerModel, + required super.key, + }); + + final WindowController controller; + final VoidCallback onDestroyed; + final VoidCallback onError; + final WindowSettings windowSettings; + final WindowManagerModel windowManagerModel; + + @override + Widget build(BuildContext context) { + switch (controller.type) { + case WindowArchetype.regular: + return RegularWindow( + key: key, + controller: controller as RegularWindowController, + child: RegularWindowContent( + window: controller as RegularWindowController, + windowSettings: windowSettings, + windowManagerModel: windowManagerModel), + ); + } + } +} diff --git a/examples/multi_window_ref_app/lib/app/window_manager_model.dart b/examples/multi_window_ref_app/lib/app/window_manager_model.dart new file mode 100644 index 0000000000000..741f3a74ad94a --- /dev/null +++ b/examples/multi_window_ref_app/lib/app/window_manager_model.dart @@ -0,0 +1,55 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/widgets.dart'; + +class KeyedWindowController { + KeyedWindowController( + {this.parent, + this.isMainWindow = false, + required this.key, + required this.controller}); + + final WindowController? parent; + final bool isMainWindow; + final UniqueKey key; + final WindowController controller; +} + +/// Manages a flat list of all of the [WindowController]s that have been +/// created by the application as well as which controller is currently +/// selected by the UI. +class WindowManagerModel extends ChangeNotifier { + final List _windows = []; + List get windows => _windows; + int? _selectedViewId; + WindowController? get selected { + if (_selectedViewId == null) { + return null; + } + + for (final KeyedWindowController controller in _windows) { + if (controller.controller.rootView.viewId == _selectedViewId) { + return controller.controller; + } + } + + return null; + } + + void add(KeyedWindowController window) { + _windows.add(window); + notifyListeners(); + } + + void remove(UniqueKey key) { + _windows.removeWhere((KeyedWindowController window) => window.key == key); + notifyListeners(); + } + + void select(int? viewId) { + _selectedViewId = viewId; + notifyListeners(); + } +} diff --git a/examples/multi_window_ref_app/lib/app/window_settings.dart b/examples/multi_window_ref_app/lib/app/window_settings.dart new file mode 100644 index 0000000000000..70aa13db5fbaa --- /dev/null +++ b/examples/multi_window_ref_app/lib/app/window_settings.dart @@ -0,0 +1,17 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; + +class WindowSettings extends ChangeNotifier { + WindowSettings({Size regularSize = const Size(400, 300)}) + : _regularSize = regularSize; + + Size _regularSize; + Size get regularSize => _regularSize; + set regularSize(Size value) { + _regularSize = value; + notifyListeners(); + } +} diff --git a/examples/multi_window_ref_app/lib/app/window_settings_dialog.dart b/examples/multi_window_ref_app/lib/app/window_settings_dialog.dart new file mode 100644 index 0000000000000..481f1a8c068ea --- /dev/null +++ b/examples/multi_window_ref_app/lib/app/window_settings_dialog.dart @@ -0,0 +1,94 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:multi_window_ref_app/app/window_settings.dart'; + +Future windowSettingsDialog( + BuildContext context, WindowSettings settings) async { + return await showDialog( + barrierDismissible: true, + context: context, + builder: (BuildContext ctx) { + return SimpleDialog( + contentPadding: const EdgeInsets.all(4), + titlePadding: const EdgeInsets.fromLTRB(24, 10, 24, 0), + title: const Center( + child: Text('Window Settings'), + ), + children: [ + SizedBox( + width: 600, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + Expanded( + child: ListTile( + title: const Text('Regular'), + subtitle: ListenableBuilder( + listenable: settings, + builder: (BuildContext ctx, Widget? _) { + return Row( + children: [ + Expanded( + child: TextFormField( + initialValue: settings.regularSize.width + .toString(), + decoration: const InputDecoration( + labelText: 'Initial width', + ), + onChanged: (String value) => + settings.regularSize = Size( + double.tryParse(value) ?? 0, + settings.regularSize.height), + ), + ), + const SizedBox( + width: 20, + ), + Expanded( + child: TextFormField( + initialValue: settings + .regularSize.height + .toString(), + decoration: const InputDecoration( + labelText: 'Initial height', + ), + onChanged: (String value) => + settings.regularSize = Size( + settings.regularSize.width, + double.tryParse(value) ?? 0), + ), + ), + ], + ); + }), + ), + ), + const SizedBox( + width: 10, + ), + ], + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: TextButton( + onPressed: () { + Navigator.of(context, rootNavigator: true).pop(); + }, + child: const Text('Apply'), + ), + ), + const SizedBox( + height: 2, + ), + ], + ); + }); +} diff --git a/examples/multi_window_ref_app/lib/main.dart b/examples/multi_window_ref_app/lib/main.dart new file mode 100644 index 0000000000000..21056d60e2226 --- /dev/null +++ b/examples/multi_window_ref_app/lib/main.dart @@ -0,0 +1,22 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'app/main_window.dart'; + +void main() { + final RegularWindowController controller = RegularWindowController( + contentSize: WindowSizing( + preferredSize: const Size(800, 600), + constraints: const BoxConstraints(minWidth: 640, minHeight: 480), + ), + title: "Multi-Window Reference Application", + ); + runWidget( + RegularWindow( + controller: controller, + child: MaterialApp(home: MainWindow(mainController: controller)), + ), + ); +} diff --git a/examples/multi_window_ref_app/macos/.gitignore b/examples/multi_window_ref_app/macos/.gitignore new file mode 100644 index 0000000000000..746adbb6b9e14 --- /dev/null +++ b/examples/multi_window_ref_app/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/examples/multi_window_ref_app/macos/Flutter/Flutter-Debug.xcconfig b/examples/multi_window_ref_app/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000000000..c2efd0b608ba8 --- /dev/null +++ b/examples/multi_window_ref_app/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/examples/multi_window_ref_app/macos/Flutter/Flutter-Release.xcconfig b/examples/multi_window_ref_app/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000000000..c2efd0b608ba8 --- /dev/null +++ b/examples/multi_window_ref_app/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/examples/multi_window_ref_app/macos/Runner.xcodeproj/project.pbxproj b/examples/multi_window_ref_app/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000000000..c1e14c02ab186 --- /dev/null +++ b/examples/multi_window_ref_app/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,705 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* multi_window_ref_app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "multi_window_ref_app.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* multi_window_ref_app.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* multi_window_ref_app.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.multiWindowRefApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/multi_window_ref_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/multi_window_ref_app"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.multiWindowRefApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/multi_window_ref_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/multi_window_ref_app"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.multiWindowRefApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/multi_window_ref_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/multi_window_ref_app"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/examples/multi_window_ref_app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/examples/multi_window_ref_app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000000..18d981003d68d --- /dev/null +++ b/examples/multi_window_ref_app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/examples/multi_window_ref_app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/examples/multi_window_ref_app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000000000..e7813cac2f1bf --- /dev/null +++ b/examples/multi_window_ref_app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/multi_window_ref_app/macos/Runner.xcworkspace/contents.xcworkspacedata b/examples/multi_window_ref_app/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000000..1d526a16ed0f1 --- /dev/null +++ b/examples/multi_window_ref_app/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples/multi_window_ref_app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/examples/multi_window_ref_app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000000..18d981003d68d --- /dev/null +++ b/examples/multi_window_ref_app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/examples/multi_window_ref_app/macos/Runner/AppDelegate.swift b/examples/multi_window_ref_app/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000000000..84cd1f9d64f7b --- /dev/null +++ b/examples/multi_window_ref_app/macos/Runner/AppDelegate.swift @@ -0,0 +1,25 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import Cocoa +import FlutterMacOS + +@main +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } + + var engine : FlutterEngine?; + + + override func applicationDidFinishLaunching(_ notification: Notification) { + engine = FlutterEngine(name: "project", project: nil); + engine?.run(withEntrypoint:nil); + } +} diff --git a/examples/multi_window_ref_app/macos/Runner/Assets.xcassets/Contents.json b/examples/multi_window_ref_app/macos/Runner/Assets.xcassets/Contents.json new file mode 100644 index 0000000000000..73c00596a7fca --- /dev/null +++ b/examples/multi_window_ref_app/macos/Runner/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/examples/multi_window_ref_app/macos/Runner/Base.lproj/MainMenu.xib b/examples/multi_window_ref_app/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 0000000000000..a62d87856b51b --- /dev/null +++ b/examples/multi_window_ref_app/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,332 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/multi_window_ref_app/macos/Runner/Configs/AppInfo.xcconfig b/examples/multi_window_ref_app/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000000000..35840c478829e --- /dev/null +++ b/examples/multi_window_ref_app/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = multi_window_ref_app + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.multiWindowRefApp + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2025 com.example. All rights reserved. diff --git a/examples/multi_window_ref_app/macos/Runner/Configs/Debug.xcconfig b/examples/multi_window_ref_app/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000000000..36b0fd9464f45 --- /dev/null +++ b/examples/multi_window_ref_app/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/examples/multi_window_ref_app/macos/Runner/Configs/Release.xcconfig b/examples/multi_window_ref_app/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000000000..dff4f49561c81 --- /dev/null +++ b/examples/multi_window_ref_app/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/examples/multi_window_ref_app/macos/Runner/Configs/Warnings.xcconfig b/examples/multi_window_ref_app/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000000000..42bcbf4780b18 --- /dev/null +++ b/examples/multi_window_ref_app/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/examples/multi_window_ref_app/macos/Runner/DebugProfile.entitlements b/examples/multi_window_ref_app/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000000000..dddb8a30c851e --- /dev/null +++ b/examples/multi_window_ref_app/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/examples/multi_window_ref_app/macos/Runner/Info.plist b/examples/multi_window_ref_app/macos/Runner/Info.plist new file mode 100644 index 0000000000000..4789daa6a443e --- /dev/null +++ b/examples/multi_window_ref_app/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/examples/multi_window_ref_app/macos/Runner/MainFlutterWindow.swift b/examples/multi_window_ref_app/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000000000..a6a6b9af83072 --- /dev/null +++ b/examples/multi_window_ref_app/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,19 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/examples/multi_window_ref_app/macos/Runner/Release.entitlements b/examples/multi_window_ref_app/macos/Runner/Release.entitlements new file mode 100644 index 0000000000000..852fa1a4728ae --- /dev/null +++ b/examples/multi_window_ref_app/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/examples/multi_window_ref_app/macos/RunnerTests/RunnerTests.swift b/examples/multi_window_ref_app/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000000000..eeb5f29483661 --- /dev/null +++ b/examples/multi_window_ref_app/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,16 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/examples/multi_window_ref_app/pubspec.yaml b/examples/multi_window_ref_app/pubspec.yaml new file mode 100644 index 0000000000000..ec5a4e3c2f4fc --- /dev/null +++ b/examples/multi_window_ref_app/pubspec.yaml @@ -0,0 +1,48 @@ +name: multi_window_ref_app +description: "Reference app for the multi-view windowing API." +version: 1.0.0+1 + +environment: + sdk: '>=3.5.0-180.0.dev <4.0.0' + +dependencies: + flutter: + sdk: flutter + stack_trace: 1.12.1 + vector_math: 2.1.4 + + characters: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + collection: 1.19.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + ffi: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + material_color_utilities: 0.11.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.16.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + path: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: 5.0.0 + + async: 2.13.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + boolean_selector: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + clock: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + crypto: 3.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + file: 7.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + intl: 0.20.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.17 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + platform: 3.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + process: 5.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + source_span: 1.10.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + stream_channel: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + string_scanner: 1.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + sync_http: 0.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + term_glyph: 1.2.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + typed_data: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 15.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + webdriver: 3.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + +flutter: + uses-material-design: true + +# PUBSPEC CHECKSUM: 78a6 diff --git a/examples/multi_window_ref_app/test/widget_test.dart b/examples/multi_window_ref_app/test/widget_test.dart new file mode 100644 index 0000000000000..31030bc0daaa8 --- /dev/null +++ b/examples/multi_window_ref_app/test/widget_test.dart @@ -0,0 +1,9 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async {}); +} diff --git a/examples/multi_window_ref_app/windows/.gitignore b/examples/multi_window_ref_app/windows/.gitignore new file mode 100644 index 0000000000000..d492d0d98c8fd --- /dev/null +++ b/examples/multi_window_ref_app/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/examples/multi_window_ref_app/windows/CMakeLists.txt b/examples/multi_window_ref_app/windows/CMakeLists.txt new file mode 100644 index 0000000000000..4450980427578 --- /dev/null +++ b/examples/multi_window_ref_app/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(multi_window_ref_app LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "multi_window_ref_app") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/examples/multi_window_ref_app/windows/flutter/CMakeLists.txt b/examples/multi_window_ref_app/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000000000..a71c6e2c5e4f3 --- /dev/null +++ b/examples/multi_window_ref_app/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") +set(CMAKE_CXX_STANDARD 20) + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/examples/multi_window_ref_app/windows/runner/CMakeLists.txt b/examples/multi_window_ref_app/windows/runner/CMakeLists.txt new file mode 100644 index 0000000000000..697f43451ac08 --- /dev/null +++ b/examples/multi_window_ref_app/windows/runner/CMakeLists.txt @@ -0,0 +1,37 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "main.cpp" + "utils.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/examples/multi_window_ref_app/windows/runner/Runner.rc b/examples/multi_window_ref_app/windows/runner/Runner.rc new file mode 100644 index 0000000000000..909820ff45c09 --- /dev/null +++ b/examples/multi_window_ref_app/windows/runner/Runner.rc @@ -0,0 +1,111 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "The Flutter Authors" "\0" + VALUE "FileDescription", "A reference application demonstrating Flutter's multi-window API." "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "Flutter Multi-Window Reference App" "\0" + VALUE "LegalCopyright", "Copyright 2014 The Flutter Authors. All rights reserved." "\0" + VALUE "OriginalFilename", "multi_window_ref_app.exe" "\0" + VALUE "ProductName", "Flutter Multi-Window Reference App" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/examples/multi_window_ref_app/windows/runner/main.cpp b/examples/multi_window_ref_app/windows/runner/main.cpp new file mode 100644 index 0000000000000..11a76c8ceffa2 --- /dev/null +++ b/examples/multi_window_ref_app/windows/runner/main.cpp @@ -0,0 +1,41 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include + +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t* command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + auto command_line_arguments{GetCommandLineArguments()}; + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + auto const engine{std::make_shared(project)}; + RegisterPlugins(engine.get()); + engine->Run(); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/examples/multi_window_ref_app/windows/runner/resource.h b/examples/multi_window_ref_app/windows/runner/resource.h new file mode 100644 index 0000000000000..69cacf3cead96 --- /dev/null +++ b/examples/multi_window_ref_app/windows/runner/resource.h @@ -0,0 +1,19 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/examples/multi_window_ref_app/windows/runner/runner.exe.manifest b/examples/multi_window_ref_app/windows/runner/runner.exe.manifest new file mode 100644 index 0000000000000..153653e8d67f8 --- /dev/null +++ b/examples/multi_window_ref_app/windows/runner/runner.exe.manifest @@ -0,0 +1,14 @@ + + + + + PerMonitorV2 + + + + + + + + + diff --git a/examples/multi_window_ref_app/windows/runner/utils.cpp b/examples/multi_window_ref_app/windows/runner/utils.cpp new file mode 100644 index 0000000000000..6abcd65042070 --- /dev/null +++ b/examples/multi_window_ref_app/windows/runner/utils.cpp @@ -0,0 +1,68 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + unsigned int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + std::string utf8_string; + if (target_length == 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/examples/multi_window_ref_app/windows/runner/utils.h b/examples/multi_window_ref_app/windows/runner/utils.h new file mode 100644 index 0000000000000..54414c989ba71 --- /dev/null +++ b/examples/multi_window_ref_app/windows/runner/utils.h @@ -0,0 +1,23 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ From e0ade0335cda0916accde28d8d8f76f40eae4307 Mon Sep 17 00:00:00 2001 From: Matej Knopp Date: Sun, 1 Jun 2025 16:27:02 +0200 Subject: [PATCH 04/44] windowstate: framework --- packages/flutter/lib/src/widgets/window.dart | 52 +++++++----- .../flutter/lib/src/widgets/window_macos.dart | 83 +++++++++++++++++-- .../flutter/lib/src/widgets/window_win32.dart | 78 ++++++++++++----- 3 files changed, 163 insertions(+), 50 deletions(-) diff --git a/packages/flutter/lib/src/widgets/window.dart b/packages/flutter/lib/src/widgets/window.dart index 0206eccbf00fe..3e290f9b6430d 100644 --- a/packages/flutter/lib/src/widgets/window.dart +++ b/packages/flutter/lib/src/widgets/window.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:ui' show AppExitType, FlutterView; +import 'dart:ui' show AppExitType, FlutterView, Display; import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; @@ -19,18 +19,6 @@ enum WindowArchetype { regular, } -/// Defines the possible states that a window can be in. -enum WindowState { - /// Window is in its normal state, neither maximized, nor minimized. - restored, - - /// Window is maximized, occupying the full screen but still showing the system UI. - maximized, - - /// Window is minimized and not visible on the screen. - minimized, -} - /// Defines sizing request for a window. class WindowSizing { /// Creates a new [WindowSizing] object. @@ -142,7 +130,6 @@ abstract class RegularWindowController extends WindowController { factory RegularWindowController({ required WindowSizing contentSize, String? title, - WindowState? state, RegularWindowControllerDelegate? delegate, }) { WidgetsFlutterBinding.ensureInitialized(); @@ -154,9 +141,6 @@ abstract class RegularWindowController extends WindowController { if (title != null) { controller.setTitle(title); } - if (state != null) { - controller.setState(state); - } return controller; } @@ -167,9 +151,6 @@ abstract class RegularWindowController extends WindowController { @override WindowArchetype get type => WindowArchetype.regular; - /// The current state of the window. - WindowState get state; - /// Request change for the window content size. /// /// [contentSize] describes the new requested window size. The properties @@ -184,9 +165,34 @@ abstract class RegularWindowController extends WindowController { /// [title] new title of the window. void setTitle(String title); - /// Request change for the window state. - /// [state] new state of the window. - void setState(WindowState state); + /// Requests that the window be displayed in its current size and position. + /// If the window is minimized or maximized, the window returns to the size + /// and position that it had before that state was applied. + void activate(); + + /// Requests the window to be maximized. This has no effect + /// if the window is currently full screen or minimized, but may + /// affect the window size upon restoring it from minimized or + /// full screen state. + void setMaximized(bool maximized); + + /// Returns whether window is currently maximized. + bool isMaximized(); + + /// Requests window to be minimized. + void setMinimized(bool minimized); + + /// Returns whether window is currently minimized. + bool isMinimized(); + + /// Request change for the window to enter or exit fullscreen state. + /// [fullscreen] whether to enter or exit fullscreen state. + /// [displayId] optional [Display] identifier to use for fullscreen mode. + /// Specifying the [displayId] might not be supported on all platforms. + void setFullscreen(bool fullscreen, {int? displayId}); + + /// Returns whether window is currently in fullscreen mode. + bool isFullscreen(); } /// [WindowingOwner] is responsible for creating and managing window controllers. diff --git a/packages/flutter/lib/src/widgets/window_macos.dart b/packages/flutter/lib/src/widgets/window_macos.dart index fd082ffed639e..c1e87f735dd86 100644 --- a/packages/flutter/lib/src/widgets/window_macos.dart +++ b/packages/flutter/lib/src/widgets/window_macos.dart @@ -80,6 +80,7 @@ class RegularWindowControllerMacOS extends RegularWindowController { /// Returns window handle for the current window. /// The handle is a pointer to NSWindow instance. Pointer getWindowHandle() { + _ensureNotDestroyed(); return WindowingOwnerMacOS.getWindowHandle(rootView); } @@ -90,9 +91,10 @@ class RegularWindowControllerMacOS extends RegularWindowController { if (_destroyed) { return; } + final Pointer handle = getWindowHandle(); _destroyed = true; _owner._activeControllers.remove(this); - _destroyWindow(PlatformDispatcher.instance.engineId!, getWindowHandle()); + _destroyWindow(PlatformDispatcher.instance.engineId!, handle); _delegate.onWindowDestroyed(); _onClose.close(); _onResize.close(); @@ -108,6 +110,7 @@ class RegularWindowControllerMacOS extends RegularWindowController { @override void updateContentSize(WindowSizing sizing) { + _ensureNotDestroyed(); final Pointer<_Sizing> ffiSizing = ffi.calloc<_Sizing>(); ffiSizing.ref.set(sizing); _setWindowContentSize(getWindowHandle(), ffiSizing); @@ -116,6 +119,7 @@ class RegularWindowControllerMacOS extends RegularWindowController { @override void setTitle(String title) { + _ensureNotDestroyed(); final Pointer titlePointer = title.toNativeUtf8(); _setWindowTitle(getWindowHandle(), titlePointer); ffi.calloc.free(titlePointer); @@ -128,16 +132,61 @@ class RegularWindowControllerMacOS extends RegularWindowController { @override Size get contentSize { + _ensureNotDestroyed(); final _Size size = _getWindowContentSize(getWindowHandle()); return Size(size.width, size.height); } @override - WindowState get state => WindowState.values[_getWindowState(getWindowHandle())]; + void activate() { + _ensureNotDestroyed(); + _activate(getWindowHandle()); + } + + @override + void setMaximized(bool maximized) { + _ensureNotDestroyed(); + _setMaximized(getWindowHandle(), maximized); + } + + @override + bool isMaximized() { + _ensureNotDestroyed(); + return _isMaximized(getWindowHandle()); + } + + @override + void setMinimized(bool minimized) { + _ensureNotDestroyed(); + if (minimized) { + _minimize(getWindowHandle()); + } else { + _unminimize(getWindowHandle()); + } + } + + @override + bool isMinimized() { + _ensureNotDestroyed(); + return _isMinimized(getWindowHandle()); + } + + @override + void setFullscreen(bool fullscreen, {int? displayId}) { + _ensureNotDestroyed(); + _setFullscreen(getWindowHandle(), fullscreen); + } @override - void setState(WindowState state) { - _setWindowState(getWindowHandle(), state.index); + bool isFullscreen() { + _ensureNotDestroyed(); + return _isFullscreen(getWindowHandle()); + } + + void _ensureNotDestroyed() { + if (_destroyed) { + throw StateError('Window has been destroyed.'); + } } @Native)>( @@ -157,11 +206,29 @@ class RegularWindowControllerMacOS extends RegularWindowController { @Native, Pointer)>(symbol: 'FlutterSetWindowTitle') external static void _setWindowTitle(Pointer windowHandle, Pointer title); - @Native)>(symbol: 'FlutterGetWindowState') - external static int _getWindowState(Pointer windowHandle); + @Native, Bool)>(symbol: 'FlutterWindowSetMaximized') + external static void _setMaximized(Pointer windowHandle, bool maximized); + + @Native)>(symbol: 'FlutterWindowIsMaximized') + external static bool _isMaximized(Pointer windowHandle); + + @Native)>(symbol: 'FlutterWindowMinimize') + external static void _minimize(Pointer windowHandle); + + @Native)>(symbol: 'FlutterWindowUnminimize') + external static void _unminimize(Pointer windowHandle); + + @Native)>(symbol: 'FlutterWindowIsMinimized') + external static bool _isMinimized(Pointer windowHandle); + + @Native, Bool)>(symbol: 'FlutterWindowSetFullScreen') + external static void _setFullscreen(Pointer windowHandle, bool fullscreen); + + @Native)>(symbol: 'FlutterWindowIsFullScreen') + external static bool _isFullscreen(Pointer windowHandle); - @Native, Int64)>(symbol: 'FlutterSetWindowState') - external static void _setWindowState(Pointer windowHandle, int state); + @Native)>(symbol: 'FlutterWindowActivate') + external static void _activate(Pointer windowHandle); } final class _Sizing extends Struct { diff --git a/packages/flutter/lib/src/widgets/window_win32.dart b/packages/flutter/lib/src/widgets/window_win32.dart index 5b2ab30aa5e37..61bff21593275 100644 --- a/packages/flutter/lib/src/widgets/window_win32.dart +++ b/packages/flutter/lib/src/widgets/window_win32.dart @@ -126,19 +126,6 @@ class RegularWindowControllerWin32 extends RegularWindowController return result; } - @override - WindowState get state { - _ensureNotDestroyed(); - final int state = _getWindowState(getWindowHandle()); - return WindowState.values[state]; - } - - @override - void setState(WindowState state) { - _ensureNotDestroyed(); - _setWindowState(getWindowHandle(), state.index); - } - @override void setTitle(String title) { _ensureNotDestroyed(); @@ -156,6 +143,52 @@ class RegularWindowControllerWin32 extends RegularWindowController ffi.calloc.free(ffiSizing); } + @override + void activate() { + _ensureNotDestroyed(); + _showWindow(getWindowHandle(), SW_RESTORE); + } + + @override + bool isFullscreen() { + return false; + } + + @override + void setFullscreen(bool fullscreen, {int? displayId}) {} + + @override + bool isMaximized() { + _ensureNotDestroyed(); + return _isZoomed(getWindowHandle()) != 0; + } + + @override + bool isMinimized() { + _ensureNotDestroyed(); + return _isIconic(getWindowHandle()) != 0; + } + + @override + void setMinimized(bool minimized) { + _ensureNotDestroyed(); + if (minimized) { + _showWindow(getWindowHandle(), SW_MINIMIZE); + } else { + _showWindow(getWindowHandle(), SW_RESTORE); + } + } + + @override + void setMaximized(bool maximized) { + _ensureNotDestroyed(); + if (maximized) { + _showWindow(getWindowHandle(), SW_MAXIMIZE); + } else { + _showWindow(getWindowHandle(), SW_RESTORE); + } + } + /// Returns HWND pointer to the top level window. Pointer getWindowHandle() { _ensureNotDestroyed(); @@ -185,6 +218,10 @@ class RegularWindowControllerWin32 extends RegularWindowController static const int _WM_SIZE = 0x0005; static const int _WM_CLOSE = 0x0010; + static const int SW_RESTORE = 9; + static const int SW_MAXIMIZE = 3; + static const int SW_MINIMIZE = 6; + @override int? handleWindowsMessage( FlutterView view, @@ -222,17 +259,20 @@ class RegularWindowControllerWin32 extends RegularWindowController @Native<_Size Function(Pointer)>(symbol: 'FlutterGetWindowContentSize') external static _Size _getWindowContentSize(Pointer windowHandle); - @Native)>(symbol: 'FlutterGetWindowState') - external static int _getWindowState(Pointer windowHandle); - - @Native, Int64)>(symbol: 'FlutterSetWindowState') - external static void _setWindowState(Pointer windowHandle, int state); - @Native, Pointer)>(symbol: 'SetWindowTextW') external static void _setWindowTitle(Pointer windowHandle, Pointer title); @Native, Pointer<_Sizing>)>(symbol: 'FlutterSetWindowContentSize') external static void _setWindowContentSize(Pointer windowHandle, Pointer<_Sizing> size); + + @Native, Int32)>(symbol: 'ShowWindow') + external static void _showWindow(Pointer windowHandle, int command); + + @Native)>(symbol: 'IsIconic') + external static int _isIconic(Pointer windowHandle); + + @Native)>(symbol: 'IsZoomed') + external static int _isZoomed(Pointer windowHandle); } /// Request to initialize windowing system. From f938b4b9a911690c172b347003b06181336d7401 Mon Sep 17 00:00:00 2001 From: Matej Knopp Date: Sun, 1 Jun 2025 16:27:18 +0200 Subject: [PATCH 05/44] windowstate: engine --- .../flutter/shell/platform/common/windowing.h | 11 --- .../Source/FlutterWindowController.h | 22 ++++- .../Source/FlutterWindowController.mm | 83 +++++++++++++------ .../Source/FlutterWindowControllerTest.mm | 35 ++++---- .../windows/flutter_host_window_controller.cc | 24 ------ .../windows/flutter_host_window_controller.h | 6 -- ...lutter_host_window_controller_unittests.cc | 30 ------- 7 files changed, 97 insertions(+), 114 deletions(-) diff --git a/engine/src/flutter/shell/platform/common/windowing.h b/engine/src/flutter/shell/platform/common/windowing.h index e6cc8e4ef9cc3..be864a5b3e160 100644 --- a/engine/src/flutter/shell/platform/common/windowing.h +++ b/engine/src/flutter/shell/platform/common/windowing.h @@ -14,17 +14,6 @@ enum class WindowArchetype { kRegular, }; -// Possible states a window can be in. -// The values must match values from WindowState in the Dart code. -enum class WindowState { - // Normal state, neither maximized, nor minimized. - kRestored, - // Maximized, occupying the full screen but still showing the system UI. - kMaximized, - // Minimized and not visible on the screen. - kMinimized, -}; - } // namespace flutter #endif // FLUTTER_SHELL_PLATFORM_COMMON_WINDOWING_H_ diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowController.h b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowController.h index 0fce98cf1f27e..0fa008984fb09 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowController.h +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowController.h @@ -72,10 +72,28 @@ FLUTTER_DARWIN_EXPORT void FlutterSetWindowTitle(void* window, const char* title); FLUTTER_DARWIN_EXPORT -int64_t FlutterGetWindowState(void* window); +void FlutterWindowSetMaximized(void* window, bool maximized); FLUTTER_DARWIN_EXPORT -void FlutterSetWindowState(void* window, int64_t state); +bool FlutterWindowIsMaximized(void* window); + +FLUTTER_DARWIN_EXPORT +void FlutterWindowMinimize(void* window); + +FLUTTER_DARWIN_EXPORT +void FlutterWindowUnminimize(void* window); + +FLUTTER_DARWIN_EXPORT +bool FlutterWindowIsMinimized(void* window); + +FLUTTER_DARWIN_EXPORT +void FlutterWindowSetFullScreen(void* window, bool fullScreen); + +FLUTTER_DARWIN_EXPORT +bool FlutterWindowIsFullScreen(void* window); + +FLUTTER_DARWIN_EXPORT +void FlutterWindowActivate(void* window); // NOLINTEND(google-objc-function-naming) } diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowController.mm b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowController.mm index 46363074e8df9..7780419d1095e 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowController.mm +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowController.mm @@ -3,6 +3,7 @@ // found in the LICENSE file. #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowController.h" +#include #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h" #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterCodecs.h" @@ -10,7 +11,6 @@ #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h" #include "flutter/shell/platform/common/isolate_scope.h" -#include "flutter/shell/platform/common/windowing.h" /// A delegate for a Flutter managed window. @interface FlutterWindowOwner : NSObject { @@ -90,6 +90,20 @@ - (void)windowDidResize:(NSNotification*)notification { _creationRequest.on_size_change(); } +// Miniaturize does not trigger resize event, but for now there +// is no other way to get notification about the state change. +- (void)windowDidMiniaturize:(NSNotification*)notification { + flutter::IsolateScope isolate_scope(*_isolate); + _creationRequest.on_size_change(); +} + +// Deminiaturize does not trigger resize event, but for now there +// is no other way to get notification about the state change. +- (void)windowDidDeminiaturize:(NSNotification*)notification { + flutter::IsolateScope isolate_scope(*_isolate); + _creationRequest.on_size_change(); +} + @end @interface FlutterWindowController () { @@ -200,37 +214,54 @@ void FlutterSetWindowTitle(void* window, const char* title) { w.title = [NSString stringWithUTF8String:title]; } -int64_t FlutterGetWindowState(void* window) { +void FlutterWindowSetMaximized(void* window, bool maximized) { NSWindow* w = (__bridge NSWindow*)window; - if (w.isMiniaturized) { - return static_cast(flutter::WindowState::kMinimized); - } else if (w.isZoomed) { - return static_cast(flutter::WindowState::kMaximized); - } else { - return static_cast(flutter::WindowState::kRestored); + if (maximized & !w.isZoomed) { + [w zoom:nil]; + } else if (!maximized && w.isZoomed) { + [w zoom:nil]; } } -void FlutterSetWindowState(void* window, int64_t state) { - flutter::WindowState windowState = static_cast(state); +bool FlutterWindowIsMaximized(void* window) { NSWindow* w = (__bridge NSWindow*)window; - if (windowState == flutter::WindowState::kMaximized) { - [w deminiaturize:nil]; - [w zoom:nil]; - } else if (state == static_cast(flutter::WindowState::kMinimized)) { - [w miniaturize:nil]; - } else { - if (w.isMiniaturized) { - [w deminiaturize:nil]; - } else if (w.isZoomed) { - [w zoom:nil]; - } else { - bool isFullScreen = (w.styleMask & NSWindowStyleMaskFullScreen) != 0; - if (isFullScreen) { - [w toggleFullScreen:nil]; - } - } + return w.isZoomed; +} + +void FlutterWindowMinimize(void* window) { + NSWindow* w = (__bridge NSWindow*)window; + [w miniaturize:nil]; +} + +void FlutterWindowUnminimize(void* window) { + NSWindow* w = (__bridge NSWindow*)window; + [w deminiaturize:nil]; +} + +bool FlutterWindowIsMinimized(void* window) { + NSWindow* w = (__bridge NSWindow*)window; + return w.isMiniaturized; +} + +void FlutterWindowSetFullScreen(void* window, bool fullScreen) { + NSWindow* w = (__bridge NSWindow*)window; + bool isFullScreen = (w.styleMask & NSWindowStyleMaskFullScreen) != 0; + if (fullScreen && !isFullScreen) { + [w toggleFullScreen:nil]; + } else if (!fullScreen && isFullScreen) { + [w toggleFullScreen:nil]; } } +bool FlutterWindowIsFullScreen(void* window) { + NSWindow* w = (__bridge NSWindow*)window; + return (w.styleMask & NSWindowStyleMaskFullScreen) != 0; +} + +void FlutterWindowActivate(void* window) { + NSWindow* w = (__bridge NSWindow*)window; + [NSApplication.sharedApplication activateIgnoringOtherApps:YES]; + [w makeKeyAndOrderFront:nil]; +} + // NOLINTEND(google-objc-function-naming) diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowControllerTest.mm b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowControllerTest.mm index 23c2a8cf05b44..0611b92f9d27d 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowControllerTest.mm +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowControllerTest.mm @@ -156,7 +156,7 @@ void TearDown() { EXPECT_EQ(window_handle, (__bridge void*)viewController.view.window); } -TEST_F(FlutterWindowControllerTest, FlutterSetWindowState) { +TEST_F(FlutterWindowControllerTest, WindowStates) { FlutterWindowCreationRequest request{ .contentSize = {.has_size = true, .width = 800, .height = 600}, .on_close = [] {}, @@ -169,21 +169,26 @@ void TearDown() { IsolateScope isolate_scope(isolate()); int64_t handle = FlutterCreateRegularWindow(engine_id, &request); - const std::array kWindowStates = { - static_cast(WindowState::kRestored), // - static_cast(WindowState::kMaximized), // - static_cast(WindowState::kMinimized), // - static_cast(WindowState::kMaximized), // - static_cast(WindowState::kRestored), // - }; FlutterViewController* viewController = [engine viewControllerForIdentifier:handle]; - void* windowHandle = (__bridge void*)viewController.view.window; + NSWindow* window = viewController.view.window; + void* windowHandle = (__bridge void*)window; - for (const auto requestedState : kWindowStates) { - FlutterSetWindowState(windowHandle, requestedState); - CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0, false); - const int64_t actualState = FlutterGetWindowState(windowHandle); - EXPECT_EQ(actualState, requestedState); - } + EXPECT_EQ(window.zoomed, NO); + EXPECT_EQ(window.miniaturized, NO); + EXPECT_EQ(window.styleMask & NSWindowStyleMaskFullScreen, 0u); + + FlutterWindowSetMaximized(windowHandle, true); + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.5, false); + EXPECT_EQ(window.zoomed, YES); + + FlutterWindowSetMaximized(windowHandle, false); + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.5, false); + EXPECT_EQ(window.zoomed, NO); + + // FullScreen toggle does not seem to work when the application is not run from a bundle. + + FlutterWindowMinimize(windowHandle); + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.5, false); + EXPECT_EQ(window.miniaturized, YES); } } // namespace flutter::testing diff --git a/engine/src/flutter/shell/platform/windows/flutter_host_window_controller.cc b/engine/src/flutter/shell/platform/windows/flutter_host_window_controller.cc index c922028350269..27f1b460c9cf9 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_host_window_controller.cc +++ b/engine/src/flutter/shell/platform/windows/flutter_host_window_controller.cc @@ -159,30 +159,6 @@ FlutterWindowSize FlutterGetWindowContentSize(HWND hwnd) { }; } -int64_t FlutterGetWindowState(HWND hwnd) { - if (IsIconic(hwnd)) { - return static_cast(flutter::WindowState::kMinimized); - } else if (IsZoomed(hwnd)) { - return static_cast(flutter::WindowState::kMaximized); - } else { - return static_cast(flutter::WindowState::kRestored); - } -} - -void FlutterSetWindowState(HWND hwnd, int64_t state) { - switch (static_cast(state)) { - case flutter::WindowState::kRestored: - ShowWindow(hwnd, SW_RESTORE); - break; - case flutter::WindowState::kMaximized: - ShowWindow(hwnd, SW_MAXIMIZE); - break; - case flutter::WindowState::kMinimized: - ShowWindow(hwnd, SW_MINIMIZE); - break; - } -} - void FlutterSetWindowContentSize(HWND hwnd, const flutter::FlutterWindowSizing* size) { flutter::FlutterHostWindow* window = diff --git a/engine/src/flutter/shell/platform/windows/flutter_host_window_controller.h b/engine/src/flutter/shell/platform/windows/flutter_host_window_controller.h index 36127619a6638..a9b16f233fdbc 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_host_window_controller.h +++ b/engine/src/flutter/shell/platform/windows/flutter_host_window_controller.h @@ -128,12 +128,6 @@ struct FlutterWindowSize { FLUTTER_EXPORT FlutterWindowSize FlutterGetWindowContentSize(HWND hwnd); -FLUTTER_EXPORT -int64_t FlutterGetWindowState(HWND hwnd); - -FLUTTER_EXPORT -void FlutterSetWindowState(HWND hwnd, int64_t state); - FLUTTER_EXPORT void FlutterSetWindowContentSize(HWND hwnd, const flutter::FlutterWindowSizing* size); diff --git a/engine/src/flutter/shell/platform/windows/flutter_host_window_controller_unittests.cc b/engine/src/flutter/shell/platform/windows/flutter_host_window_controller_unittests.cc index b854762b999c8..04ab83b37959f 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_host_window_controller_unittests.cc +++ b/engine/src/flutter/shell/platform/windows/flutter_host_window_controller_unittests.cc @@ -119,36 +119,6 @@ TEST_F(FlutterHostWindowControllerTest, GetWindowSize) { EXPECT_EQ(size.height, creation_request()->content_size.height); } -TEST_F(FlutterHostWindowControllerTest, GetWindowState) { - IsolateScope isolate_scope(isolate()); - - const int64_t view_id = - FlutterCreateRegularWindow(engine_id(), creation_request()); - const HWND window_handle = FlutterGetWindowHandle(engine_id(), view_id); - const int64_t window_state = FlutterGetWindowState(window_handle); - EXPECT_EQ(window_state, static_cast(WindowState::kRestored)); -} - -TEST_F(FlutterHostWindowControllerTest, SetWindowState) { - IsolateScope isolate_scope(isolate()); - - const int64_t view_id = - FlutterCreateRegularWindow(engine_id(), creation_request()); - const HWND window_handle = FlutterGetWindowHandle(engine_id(), view_id); - - const std::array kWindowStates = { - static_cast(WindowState::kRestored), - static_cast(WindowState::kMaximized), - static_cast(WindowState::kMinimized), - }; - - for (const auto requested_state : kWindowStates) { - FlutterSetWindowState(window_handle, requested_state); - const int64_t actual_state = FlutterGetWindowState(window_handle); - EXPECT_EQ(actual_state, requested_state); - } -} - TEST_F(FlutterHostWindowControllerTest, SetWindowSize) { IsolateScope isolate_scope(isolate()); From 63b51cb617e5784ebb4b19f9d6621ac8ade60f59 Mon Sep 17 00:00:00 2001 From: Matej Knopp Date: Sun, 1 Jun 2025 16:27:28 +0200 Subject: [PATCH 06/44] windowstate: ref app --- .../lib/app/main_window.dart | 82 +++--- .../lib/app/regular_window_edit_dialog.dart | 250 ++++++++++++------ 2 files changed, 210 insertions(+), 122 deletions(-) diff --git a/examples/multi_window_ref_app/lib/app/main_window.dart b/examples/multi_window_ref_app/lib/app/main_window.dart index c4d39eaf80897..0073ab0566c11 100644 --- a/examples/multi_window_ref_app/lib/app/main_window.dart +++ b/examples/multi_window_ref_app/lib/app/main_window.dart @@ -13,8 +13,8 @@ import 'regular_window_edit_dialog.dart'; class MainWindow extends StatefulWidget { MainWindow({super.key, required WindowController mainController}) { - _windowManagerModel.add( - KeyedWindowController(isMainWindow: true, key: UniqueKey(), controller: mainController)); + _windowManagerModel.add(KeyedWindowController( + isMainWindow: true, key: UniqueKey(), controller: mainController)); } final WindowManagerModel _windowManagerModel = WindowManagerModel(); @@ -38,7 +38,8 @@ class _MainWindowState extends State { flex: 60, child: SingleChildScrollView( scrollDirection: Axis.vertical, - child: _ActiveWindowsTable(windowManagerModel: widget._windowManagerModel), + child: _ActiveWindowsTable( + windowManagerModel: widget._windowManagerModel), ), ), Expanded( @@ -66,15 +67,18 @@ class _MainWindowState extends State { listenable: widget._windowManagerModel, builder: (BuildContext context, Widget? _) { final List childViews = []; - for (final KeyedWindowController controller in widget._windowManagerModel.windows) { + for (final KeyedWindowController controller + in widget._windowManagerModel.windows) { if (controller.parent == null && !controller.isMainWindow) { childViews.add(WindowControllerRender( controller: controller.controller, key: controller.key, windowSettings: widget._settings, windowManagerModel: widget._windowManagerModel, - onDestroyed: () => widget._windowManagerModel.remove(controller.key), - onError: () => widget._windowManagerModel.remove(controller.key), + onDestroyed: () => + widget._windowManagerModel.remove(controller.key), + onError: () => + widget._windowManagerModel.remove(controller.key), )); } } @@ -130,7 +134,8 @@ class _ActiveWindowsTable extends StatelessWidget { ), numeric: true), ], - rows: (windowManagerModel.windows).map((KeyedWindowController controller) { + rows: (windowManagerModel.windows) + .map((KeyedWindowController controller) { return DataRow( key: controller.key, color: WidgetStateColor.resolveWith((states) { @@ -142,53 +147,30 @@ class _ActiveWindowsTable extends StatelessWidget { selected: controller.controller == windowManagerModel.selected, onSelectChanged: (selected) { if (selected != null) { - windowManagerModel - .select(selected ? controller.controller.rootView.viewId : null); + windowManagerModel.select(selected + ? controller.controller.rootView.viewId + : null); } }, cells: [ - DataCell(Text('$controller.controller.rootView.viewId')), + DataCell(Text('${controller.controller.rootView.viewId}')), DataCell( ListenableBuilder( listenable: controller.controller, - builder: (BuildContext context, Widget? _) => Text(controller - .controller.type - .toString() - .replaceFirst('WindowArchetype.', ''))), + builder: (BuildContext context, Widget? _) => Text( + controller.controller.type + .toString() + .replaceFirst('WindowArchetype.', ''))), ), DataCell( ListenableBuilder( listenable: controller.controller, - builder: (BuildContext context, Widget? _) => Row(children: [ + builder: (BuildContext context, Widget? _) => + Row(children: [ IconButton( - icon: const Icon(Icons.edit_outlined), - onPressed: () { - if (controller.controller.type == WindowArchetype.regular) { - showRegularWindowEditDialog(context, - initialWidth: controller.controller.contentSize.width, - initialHeight: controller.controller.contentSize.height, - initialTitle: "", - initialState: - (controller.controller as RegularWindowController) - .state, onSave: (double? width, double? height, - String? title, WindowState? state) { - final regularController = - controller.controller as RegularWindowController; - if (width != null && height != null) { - regularController.updateContentSize( - WindowSizing(preferredSize: Size(width, height)), - ); - } - if (title != null) { - regularController.setTitle(title); - } - if (state != null) { - regularController.setState(state); - } - }); - } - }, - ), + icon: const Icon(Icons.edit_outlined), + onPressed: () => _showWindowEditDialog( + controller, context)), IconButton( icon: const Icon(Icons.delete_outlined), onPressed: () async { @@ -203,6 +185,17 @@ class _ActiveWindowsTable extends StatelessWidget { ); }); } + + void _showWindowEditDialog( + KeyedWindowController controller, BuildContext context) { + if (controller.controller.type != WindowArchetype.regular) { + return; + } + + showRegularWindowEditDialog( + context: context, + controller: controller.controller as RegularWindowController); + } } class _WindowCreatorCard extends StatelessWidget { @@ -247,7 +240,8 @@ class _WindowCreatorCard extends StatelessWidget { onDestroyed: () => windowManagerModel.remove(key), ), title: "Regular", - contentSize: WindowSizing(preferredSize: windowSettings.regularSize), + contentSize: WindowSizing( + preferredSize: windowSettings.regularSize), ))); }, child: const Text('Regular'), diff --git a/examples/multi_window_ref_app/lib/app/regular_window_edit_dialog.dart b/examples/multi_window_ref_app/lib/app/regular_window_edit_dialog.dart index 64a603c2809d0..64b2bca18a688 100644 --- a/examples/multi_window_ref_app/lib/app/regular_window_edit_dialog.dart +++ b/examples/multi_window_ref_app/lib/app/regular_window_edit_dialog.dart @@ -4,87 +4,181 @@ import 'package:flutter/material.dart'; -void showRegularWindowEditDialog(BuildContext context, - {double? initialWidth, - double? initialHeight, - String? initialTitle, - WindowState? initialState, - Function(double?, double?, String?, WindowState)? onSave}) { - final TextEditingController widthController = - TextEditingController(text: initialWidth?.toString() ?? ''); - final TextEditingController heightController = - TextEditingController(text: initialHeight?.toString() ?? ''); - final TextEditingController titleController = - TextEditingController(text: initialTitle ?? ''); - +void showRegularWindowEditDialog( + {required BuildContext context, + required RegularWindowController controller}) { showDialog( - context: context, - builder: (context) { - WindowState selectedState = initialState ?? WindowState.restored; - - return AlertDialog( - title: Text("Edit Window Properties"), - content: StatefulBuilder( - builder: (context, setState) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - TextField( - controller: widthController, - keyboardType: TextInputType.number, - decoration: InputDecoration(labelText: "Width"), - ), - TextField( - controller: heightController, - keyboardType: TextInputType.number, - decoration: InputDecoration(labelText: "Height"), - ), - TextField( - controller: titleController, - decoration: InputDecoration(labelText: "Title"), - ), - DropdownButton( - value: selectedState, - onChanged: (WindowState? newState) { - if (newState != null) { - setState(() { - selectedState = newState; - }); - } - }, - items: WindowState.values.map((WindowState state) { - return DropdownMenuItem( - value: state, - child: Text( - state.toString().split('.').last[0].toUpperCase() + - state.toString().split('.').last.substring(1), - ), - ); - }).toList(), - ), - ], - ); - }, - ), - actions: [ - TextButton( - onPressed: () => Navigator.of(context).pop(), - child: Text("Cancel"), + context: context, + builder: (context) => _RegularWindowEditDialog( + controller: controller, onClose: () => Navigator.pop(context))); +} + +class _RegularWindowEditDialog extends StatefulWidget { + const _RegularWindowEditDialog( + {required this.controller, required this.onClose}); + + final RegularWindowController controller; + final VoidCallback onClose; + + @override + State createState() => _RegularWindowEditDialogState(); +} + +class _RegularWindowEditDialogState extends State<_RegularWindowEditDialog> { + late Size initialSize; + late String initialTitle; + late bool initialFullscreen; + late bool initialMaximized; + late bool initialMinimized; + + late final TextEditingController widthController; + late final TextEditingController heightController; + late final TextEditingController titleController; + + bool? nextIsFullscreen; + bool? nextIsMaximized; + bool? nextIsMinized; + + @override + void initState() { + super.initState(); + initialSize = widget.controller.contentSize; + initialTitle = ""; // TODO: Get the title + initialFullscreen = widget.controller.isFullscreen(); + initialMaximized = widget.controller.isMaximized(); + initialMinimized = widget.controller.isMinimized(); + + widthController = TextEditingController(text: initialSize.width.toString()); + heightController = + TextEditingController(text: initialSize.height.toString()); + titleController = TextEditingController(text: initialTitle); + + widget.controller.addListener(_onNotification); + } + + void _onNotification() { + // We listen on the state of the controller. If a value that the user + // can edit changes from what it was initially set to, we invalidate + // their current change and store the new "initial" value. + if (widget.controller.contentSize != initialSize) { + initialSize = widget.controller.contentSize; + widthController.text = widget.controller.contentSize.width.toString(); + heightController.text = widget.controller.contentSize.height.toString(); + } + if (widget.controller.isFullscreen() != initialFullscreen) { + setState(() { + initialFullscreen = widget.controller.isFullscreen(); + nextIsFullscreen = null; + }); + } + if (widget.controller.isMaximized() != initialMaximized) { + setState(() { + initialMaximized = widget.controller.isMaximized(); + nextIsMaximized = null; + }); + } + if (widget.controller.isMinimized() != initialMinimized) { + setState(() { + initialMinimized = widget.controller.isMinimized(); + nextIsMinized = null; + }); + } + } + + @override + void dispose() { + super.dispose(); + widget.controller.removeListener(_onNotification); + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Text("Edit Window Properties"), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + controller: widthController, + keyboardType: TextInputType.number, + decoration: InputDecoration(labelText: "Width"), ), - TextButton( - onPressed: () { - double? width = double.tryParse(widthController.text); - double? height = double.tryParse(heightController.text); - String? title = - titleController.text.isEmpty ? null : titleController.text; - - onSave?.call(width, height, title, selectedState); - Navigator.of(context).pop(); - }, - child: Text("Save"), + TextField( + controller: heightController, + keyboardType: TextInputType.number, + decoration: InputDecoration(labelText: "Height"), ), + TextField( + controller: titleController, + decoration: InputDecoration(labelText: "Title"), + ), + CheckboxListTile( + title: const Text('Fullscreen'), + value: nextIsFullscreen ?? initialFullscreen, + onChanged: (bool? value) { + if (value != null) { + setState(() => nextIsFullscreen = value); + } + }), + CheckboxListTile( + title: const Text('Maximized'), + value: nextIsMaximized ?? initialMaximized, + onChanged: (bool? value) { + if (value != null) { + setState(() => nextIsMaximized = value); + } + }), + CheckboxListTile( + title: const Text('Minimized'), + value: nextIsMinized ?? initialMinimized, + onChanged: (bool? value) { + if (value != null) { + setState(() => nextIsMinized = value); + } + }) ], + ), + actions: [ + TextButton( + onPressed: () => widget.onClose(), + child: Text("Cancel"), + ), + TextButton( + onPressed: () => _onSave(), + child: Text("Save"), + ), + ], + ); + } + + void _onSave() { + double? width = double.tryParse(widthController.text); + double? height = double.tryParse(heightController.text); + String? title = titleController.text.isEmpty ? null : titleController.text; + if (width != null && height != null) { + widget.controller.updateContentSize( + WindowSizing(preferredSize: Size(width, height)), ); - }, - ); + } + if (title != null) { + widget.controller.setTitle(title); + } + if (nextIsFullscreen != null) { + if (widget.controller.isFullscreen() != nextIsFullscreen) { + widget.controller.setFullscreen(nextIsFullscreen!); + } + } + if (nextIsMaximized != null) { + if (widget.controller.isMaximized() != nextIsMaximized) { + widget.controller.setMaximized(nextIsMaximized!); + } + } + if (nextIsMinized != null) { + if (widget.controller.isMinimized() != nextIsMinized) { + widget.controller.setMinimized(nextIsMinized!); + } + } + + widget.onClose(); + } } From 6fe7149003300abe10cc098b648175cbc61ffa91 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Fri, 13 Jun 2025 11:43:03 -0400 Subject: [PATCH 07/44] refactor: change how FlutterHostWindow construction is done --- .../platform/windows/flutter_host_window.cc | 115 +++++++++--------- .../platform/windows/flutter_host_window.h | 35 ++++-- .../windows/flutter_host_window_controller.cc | 10 +- .../windows/flutter_host_window_controller.h | 3 - 4 files changed, 83 insertions(+), 80 deletions(-) diff --git a/engine/src/flutter/shell/platform/windows/flutter_host_window.cc b/engine/src/flutter/shell/platform/windows/flutter_host_window.cc index 4ae52566e1584..7137f5a52ca5e 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_host_window.cc +++ b/engine/src/flutter/shell/platform/windows/flutter_host_window.cc @@ -261,29 +261,33 @@ void UpdateTheme(HWND window) { } } +// Inserts |content| into the window tree. +void SetChildContent(HWND content, HWND window) { + SetParent(content, window); + RECT client_rect; + GetClientRect(window, &client_rect); + MoveWindow(content, client_rect.left, client_rect.top, + client_rect.right - client_rect.left, + client_rect.bottom - client_rect.top, true); +} + } // namespace namespace flutter { -FlutterHostWindow::FlutterHostWindow(FlutterHostWindowController* controller, - WindowArchetype archetype, - const FlutterWindowSizing& content_size) - : window_controller_(controller), archetype_(archetype) { - // Check preconditions and set window styles based on window type. - DWORD window_style = 0; +std::unique_ptr FlutterHostWindow::createRegularWindow( + FlutterHostWindowController* controller, + FlutterWindowsEngine* engine, + const FlutterWindowSizing& content_size) { + DWORD window_style = WS_OVERLAPPEDWINDOW; DWORD extended_window_style = 0; - switch (archetype_) { - case WindowArchetype::kRegular: - window_style |= WS_OVERLAPPEDWINDOW; - break; - default: - FML_UNREACHABLE(); - } + std::optional min_size = std::nullopt; + std::optional max_size = std::nullopt; if (content_size.has_constraints) { - min_size_ = Size(content_size.min_width, content_size.min_height); + min_size = Size(content_size.min_width, content_size.min_height); if (content_size.max_width > 0 && content_size.max_height > 0) { - max_size_ = Size(content_size.max_width, content_size.max_height); + max_size = Size(content_size.max_width, content_size.max_height); } } @@ -295,40 +299,33 @@ FlutterHostWindow::FlutterHostWindow(FlutterHostWindowController* controller, // if the window has no owner. Rect const initial_window_rect = [&]() -> Rect { std::optional const window_size = GetWindowSizeForClientSize( - Size(content_size.width, content_size.height), min_size_, max_size_, + Size(content_size.width, content_size.height), min_size, max_size, window_style, extended_window_style, nullptr); return {{CW_USEDEFAULT, CW_USEDEFAULT}, window_size ? *window_size : Size{CW_USEDEFAULT, CW_USEDEFAULT}}; }(); // Set up the view. - FlutterWindowsEngine* const engine = window_controller_->engine(); auto view_window = std::make_unique( initial_window_rect.width(), initial_window_rect.height(), engine->windows_proc_table()); std::unique_ptr view = engine->CreateView(std::move(view_window)); - if (!view) { + if (view == nullptr) { FML_LOG(ERROR) << "Failed to create view"; - return; + return nullptr; } - view_controller_ = + std::unique_ptr view_controller = std::make_unique(nullptr, std::move(view)); FML_CHECK(engine->running()); // Must happen after engine is running. - view_controller_->view()->SendInitialBounds(); + view_controller->view()->SendInitialBounds(); // The Windows embedder listens to accessibility updates using the // view's HWND. The embedder's accessibility features may be stale if // the app was in headless mode. - view_controller_->engine()->UpdateAccessibilityFeatures(); - - // Ensure that basic setup of the view controller was successful. - if (!view_controller_->view()) { - FML_LOG(ERROR) << "Failed to set up the view controller"; - return; - } + engine->UpdateAccessibilityFeatures(); // Register the window class. if (!IsClassRegistered(kWindowClassName)) { @@ -349,7 +346,7 @@ FlutterHostWindow::FlutterHostWindow(FlutterHostWindowController* controller, if (!RegisterClassEx(&window_class)) { FML_LOG(ERROR) << "Cannot register window class " << kWindowClassName << ": " << GetLastErrorAsString(); - return; + return nullptr; } } @@ -358,11 +355,10 @@ FlutterHostWindow::FlutterHostWindow(FlutterHostWindowController* controller, CreateWindowEx(extended_window_style, kWindowClassName, L"", window_style, initial_window_rect.left(), initial_window_rect.top(), initial_window_rect.width(), initial_window_rect.height(), - nullptr, nullptr, GetModuleHandle(nullptr), this); - + nullptr, nullptr, GetModuleHandle(nullptr), nullptr); if (!hwnd) { FML_LOG(ERROR) << "Cannot create window: " << GetLastErrorAsString(); - return; + return nullptr; } // Adjust the window position so its origin aligns with the top-left corner @@ -382,7 +378,7 @@ FlutterHostWindow::FlutterHostWindow(FlutterHostWindowController* controller, UpdateTheme(hwnd); - SetChildContent(view_controller_->view()->GetWindowHandle()); + SetChildContent(view_controller->view()->GetWindowHandle(), hwnd); // TODO(loicsharma): Hide the window until the first frame is rendered. // Single window apps use the engine's next frame callback to show the @@ -390,6 +386,27 @@ FlutterHostWindow::FlutterHostWindow(FlutterHostWindowController* controller, // multiple next frame callbacks. If multiple windows are created, only the // last one will be shown. ShowWindow(hwnd, SW_SHOWNORMAL); + return std::unique_ptr(new FlutterHostWindow( + controller, engine, WindowArchetype::kRegular, std::move(view_controller), + min_size, max_size, hwnd)); +} + +FlutterHostWindow::FlutterHostWindow( + FlutterHostWindowController* controller, + FlutterWindowsEngine* engine, + WindowArchetype archetype, + std::unique_ptr view_controller, + const std::optional& min_size, + const std::optional& max_size, + HWND hwnd) + : window_controller_(controller), + engine_(engine), + archetype_(archetype), + view_controller_(std::move(view_controller)), + min_size_(min_size), + max_size_(max_size), + window_handle_(hwnd) { + SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast(this)); } FlutterHostWindow::~FlutterHostWindow() { @@ -413,8 +430,9 @@ HWND FlutterHostWindow::GetWindowHandle() const { } void FlutterHostWindow::FocusViewOf(FlutterHostWindow* window) { - if (window != nullptr && window->child_content_ != nullptr) { - SetFocus(window->child_content_); + auto child_content = window->view_controller_->view()->GetWindowHandle(); + if (window != nullptr && child_content != nullptr) { + SetFocus(child_content); } }; @@ -423,12 +441,6 @@ LRESULT FlutterHostWindow::WndProc(HWND hwnd, WPARAM wparam, LPARAM lparam) { if (message == WM_NCCREATE) { - auto* const create_struct = reinterpret_cast(lparam); - auto* const window = - static_cast(create_struct->lpCreateParams); - SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast(window)); - window->window_handle_ = hwnd; - EnableFullDpiSupportIfAvailable(hwnd); EnableTransparentWindowBackground(hwnd); } else if (FlutterHostWindow* const window = GetThisFromHandle(hwnd)) { @@ -442,10 +454,8 @@ LRESULT FlutterHostWindow::HandleMessage(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) { - auto result = - view_controller_->engine() - ->window_proc_delegate_manager() - ->OnTopLevelWindowProc(window_handle_, message, wparam, lparam); + auto result = engine_->window_proc_delegate_manager()->OnTopLevelWindowProc( + window_handle_, message, wparam, lparam); if (result) { return *result; } @@ -498,11 +508,12 @@ LRESULT FlutterHostWindow::HandleMessage(HWND hwnd, } case WM_SIZE: { - if (child_content_ != nullptr) { + auto child_content = view_controller_->view()->GetWindowHandle(); + if (child_content != nullptr) { // Resize and reposition the child content window. RECT client_rect; GetClientRect(hwnd, &client_rect); - MoveWindow(child_content_, client_rect.left, client_rect.top, + MoveWindow(child_content, client_rect.left, client_rect.top, client_rect.right - client_rect.left, client_rect.bottom - client_rect.top, TRUE); } @@ -556,14 +567,4 @@ void FlutterHostWindow::SetContentSize(const FlutterWindowSizing& size) { } } -void FlutterHostWindow::SetChildContent(HWND content) { - child_content_ = content; - SetParent(content, window_handle_); - RECT client_rect; - GetClientRect(window_handle_, &client_rect); - MoveWindow(content, client_rect.left, client_rect.top, - client_rect.right - client_rect.left, - client_rect.bottom - client_rect.top, true); -} - } // namespace flutter diff --git a/engine/src/flutter/shell/platform/windows/flutter_host_window.h b/engine/src/flutter/shell/platform/windows/flutter_host_window.h index db4ebd1a446a5..065bc59ff0c89 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_host_window.h +++ b/engine/src/flutter/shell/platform/windows/flutter_host_window.h @@ -23,15 +23,18 @@ class FlutterWindowsViewController; // A Win32 window that hosts a |FlutterWindow| in its client area. class FlutterHostWindow { public: + virtual ~FlutterHostWindow(); + // Creates a native Win32 window with a child view confined to its client // area. |controller| is a pointer to the controller that manages the - // |FlutterHostWindow|. On success, a valid window handle can be retrieved - // via |FlutterHostWindow::GetWindowHandle|. - FlutterHostWindow(FlutterHostWindowController* controller, - WindowArchetype archetype, - const FlutterWindowSizing& content_size); - - virtual ~FlutterHostWindow(); + // |FlutterHostWindow|. |engine| is a pointer to the engine thaat manages + // the controller On success, a valid window handle can be retrieved + // via |FlutterHostWindow::GetWindowHandle|. |nullptr| will be returned + // on failure. + static std::unique_ptr createRegularWindow( + FlutterHostWindowController* controller, + FlutterWindowsEngine* engine, + const FlutterWindowSizing& content_size); // Returns the instance pointer for |hwnd| or nullptr if invalid. static FlutterHostWindow* GetThisFromHandle(HWND hwnd); @@ -47,6 +50,15 @@ class FlutterHostWindow { private: friend FlutterHostWindowController; + FlutterHostWindow( + FlutterHostWindowController* controller, + FlutterWindowsEngine* engine, + WindowArchetype archetype, + std::unique_ptr view_controller, + const std::optional& min_size, + const std::optional& max_size, + HWND hwnd); + // Sets the focus to the child view window of |window|. static void FocusViewOf(FlutterHostWindow* window); @@ -61,12 +73,12 @@ class FlutterHostWindow { // inheriting classes can handle. LRESULT HandleMessage(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam); - // Inserts |content| into the window tree. - void SetChildContent(HWND content); - // Controller for this window. FlutterHostWindowController* const window_controller_ = nullptr; + // The Flutter engine that owns this window. + FlutterWindowsEngine* engine_; + // Controller for the view hosted in this window. Value-initialized if the // window is created from an existing top-level native window created by the // runner. @@ -78,9 +90,6 @@ class FlutterHostWindow { // Backing handle for this window. HWND window_handle_ = nullptr; - // Backing handle for the hosted view window. - HWND child_content_ = nullptr; - // The minimum size of the window's client area, if defined. std::optional min_size_; diff --git a/engine/src/flutter/shell/platform/windows/flutter_host_window_controller.cc b/engine/src/flutter/shell/platform/windows/flutter_host_window_controller.cc index 27f1b460c9cf9..82410e190f3dc 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_host_window_controller.cc +++ b/engine/src/flutter/shell/platform/windows/flutter_host_window_controller.cc @@ -43,9 +43,9 @@ bool FlutterHostWindowController::HasTopLevelWindows() const { FlutterViewId FlutterHostWindowController::CreateRegularWindow( const WindowCreationRequest* request) { - auto window = std::make_unique( - this, WindowArchetype::kRegular, request->content_size); - if (!window->GetWindowHandle()) { + auto window = FlutterHostWindow::createRegularWindow(this, engine_, + request->content_size); + if (!window || !window->GetWindowHandle()) { FML_LOG(ERROR) << "Failed to create host window"; return 0; } @@ -108,10 +108,6 @@ std::optional FlutterHostWindowController::HandleMessage( } } -FlutterWindowsEngine* FlutterHostWindowController::engine() const { - return engine_; -} - } // namespace flutter void FlutterWindowingInitialize(int64_t engine_id, diff --git a/engine/src/flutter/shell/platform/windows/flutter_host_window_controller.h b/engine/src/flutter/shell/platform/windows/flutter_host_window_controller.h index a9b16f233fdbc..73b9ff360cca8 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_host_window_controller.h +++ b/engine/src/flutter/shell/platform/windows/flutter_host_window_controller.h @@ -74,9 +74,6 @@ class FlutterHostWindowController { WPARAM wparam, LPARAM lparam); - // Gets the engine that owns this controller. - FlutterWindowsEngine* engine() const; - void OnEngineShutdown(); private: From ccd2f808269072abeb128a0535673289e85e58e3 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Fri, 13 Jun 2025 14:01:13 -0400 Subject: [PATCH 08/44] minor: remove enable-windowing flag --- .../platform/windows/flutter_host_window_controller_unittests.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/engine/src/flutter/shell/platform/windows/flutter_host_window_controller_unittests.cc b/engine/src/flutter/shell/platform/windows/flutter_host_window_controller_unittests.cc index 04ab83b37959f..3b71750ed4cc6 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_host_window_controller_unittests.cc +++ b/engine/src/flutter/shell/platform/windows/flutter_host_window_controller_unittests.cc @@ -21,7 +21,6 @@ class FlutterHostWindowControllerTest : public WindowsTest { void SetUp() override { auto& context = GetContext(); FlutterWindowsEngineBuilder builder(context); - builder.SetSwitches({"--enable-windowing=true"}); engine_ = builder.Build(); ASSERT_TRUE(engine_); From e4b6492e398fb1666cec826a2a6cfeada4f2b0ef Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Fri, 13 Jun 2025 14:03:02 -0400 Subject: [PATCH 09/44] minor: add path to dart file for WindowArchetype comment --- engine/src/flutter/shell/platform/common/windowing.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/engine/src/flutter/shell/platform/common/windowing.h b/engine/src/flutter/shell/platform/common/windowing.h index be864a5b3e160..5c37df323c59f 100644 --- a/engine/src/flutter/shell/platform/common/windowing.h +++ b/engine/src/flutter/shell/platform/common/windowing.h @@ -8,7 +8,8 @@ namespace flutter { // Types of windows. -// The value must match value from WindowType in the Dart code. +// The value must match value from WindowType in the Dart code +// in packages/flutter/lib/src/widgets/window.dart enum class WindowArchetype { // Regular top-level window. kRegular, From 992fa18f46f1aed06107e44fa3c9ed7e007ca91e Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Fri, 13 Jun 2025 14:22:47 -0400 Subject: [PATCH 10/44] refactor: add BoxConstraints abstraction on win32 platform --- .../flutter/shell/platform/common/geometry.h | 24 ++++++++++++ .../platform/windows/flutter_host_window.cc | 38 +++++++++++-------- .../platform/windows/flutter_host_window.h | 10 ++--- 3 files changed, 49 insertions(+), 23 deletions(-) diff --git a/engine/src/flutter/shell/platform/common/geometry.h b/engine/src/flutter/shell/platform/common/geometry.h index 89441f9710bba..32542e11642bc 100644 --- a/engine/src/flutter/shell/platform/common/geometry.h +++ b/engine/src/flutter/shell/platform/common/geometry.h @@ -6,6 +6,7 @@ #define FLUTTER_SHELL_PLATFORM_COMMON_GEOMETRY_H_ #include +#include namespace flutter { @@ -79,6 +80,29 @@ class Rect { Size size_; }; +// Encapsulates a min and max size that represents the constraints that some +// arbitrary box is able to take up. +class BoxConstraints { + public: + BoxConstraints() = default; + BoxConstraints(const std::optional& min_size, + const std::optional& max_size) + : min_size_(min_size), max_size_(max_size) {} + static BoxConstraints tight(const Size& size) { + return BoxConstraints(size, size); + } + static BoxConstraints loose(const Size& size) { + return BoxConstraints(Size(0, 0), size); + } + BoxConstraints(const BoxConstraints& other) = default; + std::optional max_size() const { return max_size_; } + std::optional min_size() const { return min_size_; } + + private: + std::optional min_size_; + std::optional max_size_; +}; + } // namespace flutter #endif // FLUTTER_SHELL_PLATFORM_COMMON_GEOMETRY_H_ diff --git a/engine/src/flutter/shell/platform/windows/flutter_host_window.cc b/engine/src/flutter/shell/platform/windows/flutter_host_window.cc index 7137f5a52ca5e..b0c2533ca4d5a 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_host_window.cc +++ b/engine/src/flutter/shell/platform/windows/flutter_host_window.cc @@ -388,7 +388,7 @@ std::unique_ptr FlutterHostWindow::createRegularWindow( ShowWindow(hwnd, SW_SHOWNORMAL); return std::unique_ptr(new FlutterHostWindow( controller, engine, WindowArchetype::kRegular, std::move(view_controller), - min_size, max_size, hwnd)); + BoxConstraints(min_size, max_size), hwnd)); } FlutterHostWindow::FlutterHostWindow( @@ -396,16 +396,14 @@ FlutterHostWindow::FlutterHostWindow( FlutterWindowsEngine* engine, WindowArchetype archetype, std::unique_ptr view_controller, - const std::optional& min_size, - const std::optional& max_size, + const BoxConstraints& box_constraints, HWND hwnd) : window_controller_(controller), engine_(engine), archetype_(archetype), view_controller_(std::move(view_controller)), - min_size_(min_size), - max_size_(max_size), - window_handle_(hwnd) { + window_handle_(hwnd), + box_constraints_(box_constraints) { SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast(this)); } @@ -488,18 +486,22 @@ LRESULT FlutterHostWindow::HandleMessage(HWND hwnd, static_cast(dpi) / USER_DEFAULT_SCREEN_DPI; MINMAXINFO* info = reinterpret_cast(lparam); - if (min_size_) { + if (box_constraints_.min_size()) { Size const min_physical_size = ClampToVirtualScreen( - Size(min_size_->width() * scale_factor + non_client_width, - min_size_->height() * scale_factor + non_client_height)); + Size(box_constraints_.min_size()->width() * scale_factor + + non_client_width, + box_constraints_.min_size()->height() * scale_factor + + non_client_height)); info->ptMinTrackSize.x = min_physical_size.width(); info->ptMinTrackSize.y = min_physical_size.height(); } - if (max_size_) { + if (box_constraints_.max_size()) { Size const max_physical_size = ClampToVirtualScreen( - Size(max_size_->width() * scale_factor + non_client_width, - max_size_->height() * scale_factor + non_client_height)); + Size(box_constraints_.max_size()->width() * scale_factor + + non_client_width, + box_constraints_.max_size()->height() * scale_factor + + non_client_height)); info->ptMaxTrackSize.x = max_physical_size.width(); info->ptMaxTrackSize.y = max_physical_size.height(); @@ -547,17 +549,21 @@ void FlutterHostWindow::SetContentSize(const FlutterWindowSizing& size) { WINDOWINFO window_info = {.cbSize = sizeof(WINDOWINFO)}; GetWindowInfo(window_handle_, &window_info); + std::optional min_size, max_size; if (size.has_constraints) { - min_size_ = Size(size.min_width, size.min_height); + min_size = Size(size.min_width, size.min_height); if (size.max_width > 0 && size.max_height > 0) { - max_size_ = Size(size.max_width, size.max_height); + max_size = Size(size.max_width, size.max_height); } } + box_constraints_ = BoxConstraints(min_size, max_size); + if (size.has_size) { std::optional const window_size = GetWindowSizeForClientSize( - Size(size.width, size.height), min_size_, max_size_, - window_info.dwStyle, window_info.dwExStyle, nullptr); + Size(size.width, size.height), box_constraints_.min_size(), + box_constraints_.max_size(), window_info.dwStyle, window_info.dwExStyle, + nullptr); if (window_size) { SetWindowPos(window_handle_, NULL, 0, 0, window_size->width(), diff --git a/engine/src/flutter/shell/platform/windows/flutter_host_window.h b/engine/src/flutter/shell/platform/windows/flutter_host_window.h index 065bc59ff0c89..3e87d0656804b 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_host_window.h +++ b/engine/src/flutter/shell/platform/windows/flutter_host_window.h @@ -55,8 +55,7 @@ class FlutterHostWindow { FlutterWindowsEngine* engine, WindowArchetype archetype, std::unique_ptr view_controller, - const std::optional& min_size, - const std::optional& max_size, + const BoxConstraints& constraints, HWND hwnd); // Sets the focus to the child view window of |window|. @@ -90,11 +89,8 @@ class FlutterHostWindow { // Backing handle for this window. HWND window_handle_ = nullptr; - // The minimum size of the window's client area, if defined. - std::optional min_size_; - - // The maximum size of the window's client area, if defined. - std::optional max_size_; + // The constraints on the window's client area. + BoxConstraints box_constraints_; FML_DISALLOW_COPY_AND_ASSIGN(FlutterHostWindow); }; From e55b799acf284c9e8093c7354ba7ab25ab1c5368 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Fri, 13 Jun 2025 14:40:40 -0400 Subject: [PATCH 11/44] task: add max pending_messages count --- .../platform/windows/flutter_host_window_controller.cc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/engine/src/flutter/shell/platform/windows/flutter_host_window_controller.cc b/engine/src/flutter/shell/platform/windows/flutter_host_window_controller.cc index 82410e190f3dc..2fb1cc471227c 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_host_window_controller.cc +++ b/engine/src/flutter/shell/platform/windows/flutter_host_window_controller.cc @@ -95,6 +95,12 @@ std::optional FlutterHostWindowController::HandleMessage( // Not initialized yet. if (!isolate_) { + if (pending_messages_.size() > 1024) { + FML_LOG(ERROR) << "The pending message cache has been maxed out, " + "something must be going wrong."; + return std::nullopt; + } + pending_messages_.push_back(message_struct); return std::nullopt; } From 76fc387c36957543095ea4b8aeedcbb5cc642877 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Fri, 13 Jun 2025 15:10:43 -0400 Subject: [PATCH 12/44] rename: exported WindowManager methods --- .../windows/flutter_host_window_controller.cc | 22 +++++--- .../windows/flutter_host_window_controller.h | 21 +++++--- ...lutter_host_window_controller_unittests.cc | 50 +++++++++++++------ 3 files changed, 61 insertions(+), 32 deletions(-) diff --git a/engine/src/flutter/shell/platform/windows/flutter_host_window_controller.cc b/engine/src/flutter/shell/platform/windows/flutter_host_window_controller.cc index 2fb1cc471227c..67127d2e03b0e 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_host_window_controller.cc +++ b/engine/src/flutter/shell/platform/windows/flutter_host_window_controller.cc @@ -116,20 +116,22 @@ std::optional FlutterHostWindowController::HandleMessage( } // namespace flutter -void FlutterWindowingInitialize(int64_t engine_id, - const flutter::WindowingInitRequest* request) { +void InternalFlutterWindows_WindowManager_Initialize( + int64_t engine_id, + const flutter::WindowingInitRequest* request) { flutter::FlutterWindowsEngine* engine = flutter::FlutterWindowsEngine::GetEngineForId(engine_id); engine->get_host_window_controller()->Initialize(request); } -bool FlutterWindowingHasTopLevelWindows(int64_t engine_id) { +bool InternalFlutterWindows_WindowManager_HasTopLevelWindows( + int64_t engine_id) { flutter::FlutterWindowsEngine* engine = flutter::FlutterWindowsEngine::GetEngineForId(engine_id); return engine->get_host_window_controller()->HasTopLevelWindows(); } -int64_t FlutterCreateRegularWindow( +int64_t InternalFlutterWindows_WindowManager_CreateRegularWindow( int64_t engine_id, const flutter::WindowCreationRequest* request) { flutter::FlutterWindowsEngine* engine = @@ -137,7 +139,9 @@ int64_t FlutterCreateRegularWindow( return engine->get_host_window_controller()->CreateRegularWindow(request); } -HWND FlutterGetWindowHandle(int64_t engine_id, FlutterViewId view_id) { +HWND InternalFlutterWindows_WindowManager_GetWindowHandle( + int64_t engine_id, + FlutterViewId view_id) { flutter::FlutterWindowsEngine* engine = flutter::FlutterWindowsEngine::GetEngineForId(engine_id); flutter::FlutterWindowsView* view = engine->view(view_id); @@ -148,7 +152,8 @@ HWND FlutterGetWindowHandle(int64_t engine_id, FlutterViewId view_id) { } } -FlutterWindowSize FlutterGetWindowContentSize(HWND hwnd) { +FlutterWindowSize InternalFlutterWindows_WindowManager_GetWindowContentSize( + HWND hwnd) { RECT rect; GetClientRect(hwnd, &rect); double const dpr = FlutterDesktopGetDpiForHWND(hwnd) / @@ -161,8 +166,9 @@ FlutterWindowSize FlutterGetWindowContentSize(HWND hwnd) { }; } -void FlutterSetWindowContentSize(HWND hwnd, - const flutter::FlutterWindowSizing* size) { +void InternalFlutterWindows_WindowManager_SetWindowContentSize( + HWND hwnd, + const flutter::FlutterWindowSizing* size) { flutter::FlutterHostWindow* window = flutter::FlutterHostWindow::GetThisFromHandle(hwnd); if (window) { diff --git a/engine/src/flutter/shell/platform/windows/flutter_host_window_controller.h b/engine/src/flutter/shell/platform/windows/flutter_host_window_controller.h index 73b9ff360cca8..729360dfd034a 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_host_window_controller.h +++ b/engine/src/flutter/shell/platform/windows/flutter_host_window_controller.h @@ -103,19 +103,22 @@ class FlutterHostWindowController { extern "C" { FLUTTER_EXPORT -void FlutterWindowingInitialize(int64_t engine_id, - const flutter::WindowingInitRequest* request); +void InternalFlutterWindows_WindowManager_Initialize( + int64_t engine_id, + const flutter::WindowingInitRequest* request); FLUTTER_EXPORT -bool FlutterWindowingHasTopLevelWindows(int64_t engine_id); +bool InternalFlutterWindows_WindowManager_HasTopLevelWindows(int64_t engine_id); FLUTTER_EXPORT -int64_t FlutterCreateRegularWindow( +int64_t InternalFlutterWindows_WindowManager_CreateRegularWindow( int64_t engine_id, const flutter::WindowCreationRequest* request); FLUTTER_EXPORT -HWND FlutterGetWindowHandle(int64_t engine_id, FlutterViewId view_id); +HWND InternalFlutterWindows_WindowManager_GetWindowHandle( + int64_t engine_id, + FlutterViewId view_id); struct FlutterWindowSize { double width; @@ -123,11 +126,13 @@ struct FlutterWindowSize { }; FLUTTER_EXPORT -FlutterWindowSize FlutterGetWindowContentSize(HWND hwnd); +FlutterWindowSize InternalFlutterWindows_WindowManager_GetWindowContentSize( + HWND hwnd); FLUTTER_EXPORT -void FlutterSetWindowContentSize(HWND hwnd, - const flutter::FlutterWindowSizing* size); +void InternalFlutterWindows_WindowManager_SetWindowContentSize( + HWND hwnd, + const flutter::FlutterWindowSizing* size); } #endif // FLUTTER_SHELL_PLATFORM_WINDOWS_FLUTTER_HOST_WINDOW_CONTROLLER_H_ diff --git a/engine/src/flutter/shell/platform/windows/flutter_host_window_controller_unittests.cc b/engine/src/flutter/shell/platform/windows/flutter_host_window_controller_unittests.cc index 3b71750ed4cc6..a4de4c74c1f3f 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_host_window_controller_unittests.cc +++ b/engine/src/flutter/shell/platform/windows/flutter_host_window_controller_unittests.cc @@ -69,10 +69,12 @@ TEST_F(FlutterHostWindowControllerTest, WindowingInitialize) { WindowingInitRequest init_request{ .on_message = [](WindowsMessage* message) { received_message = true; }}; - FlutterWindowingInitialize(engine_id(), &init_request); + InternalFlutterWindows_WindowManager_Initialize(engine_id(), &init_request); const int64_t view_id = - FlutterCreateRegularWindow(engine_id(), creation_request()); - DestroyWindow(FlutterGetWindowHandle(engine_id(), view_id)); + InternalFlutterWindows_WindowManager_CreateRegularWindow( + engine_id(), creation_request()); + DestroyWindow(InternalFlutterWindows_WindowManager_GetWindowHandle( + engine_id(), view_id)); EXPECT_TRUE(received_message); } @@ -80,11 +82,14 @@ TEST_F(FlutterHostWindowControllerTest, WindowingInitialize) { TEST_F(FlutterHostWindowControllerTest, HasTopLevelWindows) { IsolateScope isolate_scope(isolate()); - bool has_top_level_windows = FlutterWindowingHasTopLevelWindows(engine_id()); + bool has_top_level_windows = + InternalFlutterWindows_WindowManager_HasTopLevelWindows(engine_id()); EXPECT_FALSE(has_top_level_windows); - FlutterCreateRegularWindow(engine_id(), creation_request()); - has_top_level_windows = FlutterWindowingHasTopLevelWindows(engine_id()); + InternalFlutterWindows_WindowManager_CreateRegularWindow(engine_id(), + creation_request()); + has_top_level_windows = + InternalFlutterWindows_WindowManager_HasTopLevelWindows(engine_id()); EXPECT_TRUE(has_top_level_windows); } @@ -92,7 +97,8 @@ TEST_F(FlutterHostWindowControllerTest, CreateRegularWindow) { IsolateScope isolate_scope(isolate()); const int64_t view_id = - FlutterCreateRegularWindow(engine_id(), creation_request()); + InternalFlutterWindows_WindowManager_CreateRegularWindow( + engine_id(), creation_request()); EXPECT_EQ(view_id, 0); } @@ -100,8 +106,11 @@ TEST_F(FlutterHostWindowControllerTest, GetWindowHandle) { IsolateScope isolate_scope(isolate()); const int64_t view_id = - FlutterCreateRegularWindow(engine_id(), creation_request()); - const HWND window_handle = FlutterGetWindowHandle(engine_id(), view_id); + InternalFlutterWindows_WindowManager_CreateRegularWindow( + engine_id(), creation_request()); + const HWND window_handle = + InternalFlutterWindows_WindowManager_GetWindowHandle(engine_id(), + view_id); EXPECT_NE(window_handle, nullptr); } @@ -109,10 +118,14 @@ TEST_F(FlutterHostWindowControllerTest, GetWindowSize) { IsolateScope isolate_scope(isolate()); const int64_t view_id = - FlutterCreateRegularWindow(engine_id(), creation_request()); - const HWND window_handle = FlutterGetWindowHandle(engine_id(), view_id); + InternalFlutterWindows_WindowManager_CreateRegularWindow( + engine_id(), creation_request()); + const HWND window_handle = + InternalFlutterWindows_WindowManager_GetWindowHandle(engine_id(), + view_id); - FlutterWindowSize size = FlutterGetWindowContentSize(window_handle); + FlutterWindowSize size = + InternalFlutterWindows_WindowManager_GetWindowContentSize(window_handle); EXPECT_EQ(size.width, creation_request()->content_size.width); EXPECT_EQ(size.height, creation_request()->content_size.height); @@ -122,17 +135,22 @@ TEST_F(FlutterHostWindowControllerTest, SetWindowSize) { IsolateScope isolate_scope(isolate()); const int64_t view_id = - FlutterCreateRegularWindow(engine_id(), creation_request()); - const HWND window_handle = FlutterGetWindowHandle(engine_id(), view_id); + InternalFlutterWindows_WindowManager_CreateRegularWindow( + engine_id(), creation_request()); + const HWND window_handle = + InternalFlutterWindows_WindowManager_GetWindowHandle(engine_id(), + view_id); FlutterWindowSizing requestedSize{ .has_size = true, .width = 640, .height = 480, }; - FlutterSetWindowContentSize(window_handle, &requestedSize); + InternalFlutterWindows_WindowManager_SetWindowContentSize(window_handle, + &requestedSize); - FlutterWindowSize actual_size = FlutterGetWindowContentSize(window_handle); + FlutterWindowSize actual_size = + InternalFlutterWindows_WindowManager_GetWindowContentSize(window_handle); EXPECT_EQ(actual_size.width, 640); EXPECT_EQ(actual_size.height, 480); } From edf3b88924c236f9a2f2cce9134698467092654e Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Fri, 13 Jun 2025 15:21:18 -0400 Subject: [PATCH 13/44] rename FlutterHostWindowController to WindowManager --- .../flutter/shell/platform/windows/BUILD.gn | 6 ++-- .../platform/windows/flutter_host_window.cc | 12 ++++---- .../platform/windows/flutter_host_window.h | 12 ++++---- .../windows/flutter_windows_engine.cc | 8 ++--- .../platform/windows/flutter_windows_engine.h | 8 ++--- ...window_controller.cc => window_manager.cc} | 30 ++++++++----------- ...t_window_controller.h => window_manager.h} | 14 ++++----- ...ittests.cc => window_manager_unittests.cc} | 22 +++++++------- 8 files changed, 53 insertions(+), 59 deletions(-) rename engine/src/flutter/shell/platform/windows/{flutter_host_window_controller.cc => window_manager.cc} (86%) rename engine/src/flutter/shell/platform/windows/{flutter_host_window_controller.h => window_manager.h} (88%) rename engine/src/flutter/shell/platform/windows/{flutter_host_window_controller_unittests.cc => window_manager_unittests.cc} (87%) diff --git a/engine/src/flutter/shell/platform/windows/BUILD.gn b/engine/src/flutter/shell/platform/windows/BUILD.gn index 7625f8beae96f..64ae49d7b5d3d 100644 --- a/engine/src/flutter/shell/platform/windows/BUILD.gn +++ b/engine/src/flutter/shell/platform/windows/BUILD.gn @@ -75,8 +75,6 @@ source_set("flutter_windows_source") { "external_texture_pixelbuffer.h", "flutter_host_window.cc", "flutter_host_window.h", - "flutter_host_window_controller.cc", - "flutter_host_window_controller.h", "flutter_key_map.g.cc", "flutter_platform_node_delegate_windows.cc", "flutter_platform_node_delegate_windows.h", @@ -126,6 +124,8 @@ source_set("flutter_windows_source") { "text_input_plugin.h", "window_binding_handler.h", "window_binding_handler_delegate.h", + "window_manager.cc", + "window_manager.h", "window_proc_delegate_manager.cc", "window_proc_delegate_manager.h", "window_state.h", @@ -207,7 +207,6 @@ executable("flutter_windows_unittests") { "cursor_handler_unittests.cc", "direct_manipulation_unittests.cc", "dpi_utils_unittests.cc", - "flutter_host_window_controller_unittests.cc", "flutter_project_bundle_unittests.cc", "flutter_window_unittests.cc", "flutter_windows_engine_unittests.cc", @@ -253,6 +252,7 @@ executable("flutter_windows_unittests") { "testing/wm_builders.cc", "testing/wm_builders.h", "text_input_plugin_unittest.cc", + "window_manager_unittests.cc", "window_proc_delegate_manager_unittests.cc", "window_unittests.cc", "windows_lifecycle_manager_unittests.cc", diff --git a/engine/src/flutter/shell/platform/windows/flutter_host_window.cc b/engine/src/flutter/shell/platform/windows/flutter_host_window.cc index b0c2533ca4d5a..b3187d0aec912 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_host_window.cc +++ b/engine/src/flutter/shell/platform/windows/flutter_host_window.cc @@ -7,9 +7,9 @@ #include #include "flutter/shell/platform/windows/dpi_utils.h" -#include "flutter/shell/platform/windows/flutter_host_window_controller.h" #include "flutter/shell/platform/windows/flutter_window.h" #include "flutter/shell/platform/windows/flutter_windows_view_controller.h" +#include "flutter/shell/platform/windows/window_manager.h" namespace { @@ -276,7 +276,7 @@ void SetChildContent(HWND content, HWND window) { namespace flutter { std::unique_ptr FlutterHostWindow::createRegularWindow( - FlutterHostWindowController* controller, + WindowManager* window_manager, FlutterWindowsEngine* engine, const FlutterWindowSizing& content_size) { DWORD window_style = WS_OVERLAPPEDWINDOW; @@ -387,18 +387,18 @@ std::unique_ptr FlutterHostWindow::createRegularWindow( // last one will be shown. ShowWindow(hwnd, SW_SHOWNORMAL); return std::unique_ptr(new FlutterHostWindow( - controller, engine, WindowArchetype::kRegular, std::move(view_controller), - BoxConstraints(min_size, max_size), hwnd)); + window_manager, engine, WindowArchetype::kRegular, + std::move(view_controller), BoxConstraints(min_size, max_size), hwnd)); } FlutterHostWindow::FlutterHostWindow( - FlutterHostWindowController* controller, + WindowManager* window_manager, FlutterWindowsEngine* engine, WindowArchetype archetype, std::unique_ptr view_controller, const BoxConstraints& box_constraints, HWND hwnd) - : window_controller_(controller), + : window_manager_(window_manager), engine_(engine), archetype_(archetype), view_controller_(std::move(view_controller)), diff --git a/engine/src/flutter/shell/platform/windows/flutter_host_window.h b/engine/src/flutter/shell/platform/windows/flutter_host_window.h index 3e87d0656804b..0e4b643ca8bcd 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_host_window.h +++ b/engine/src/flutter/shell/platform/windows/flutter_host_window.h @@ -12,11 +12,11 @@ #include "flutter/fml/macros.h" #include "flutter/shell/platform/common/geometry.h" #include "flutter/shell/platform/common/windowing.h" -#include "flutter/shell/platform/windows/flutter_host_window_controller.h" +#include "flutter/shell/platform/windows/window_manager.h" namespace flutter { -class FlutterHostWindowController; +class WindowManager; class FlutterWindowsView; class FlutterWindowsViewController; @@ -32,7 +32,7 @@ class FlutterHostWindow { // via |FlutterHostWindow::GetWindowHandle|. |nullptr| will be returned // on failure. static std::unique_ptr createRegularWindow( - FlutterHostWindowController* controller, + WindowManager* controller, FlutterWindowsEngine* engine, const FlutterWindowSizing& content_size); @@ -48,10 +48,10 @@ class FlutterHostWindow { void SetContentSize(const FlutterWindowSizing& size); private: - friend FlutterHostWindowController; + friend WindowManager; FlutterHostWindow( - FlutterHostWindowController* controller, + WindowManager* controller, FlutterWindowsEngine* engine, WindowArchetype archetype, std::unique_ptr view_controller, @@ -73,7 +73,7 @@ class FlutterHostWindow { LRESULT HandleMessage(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam); // Controller for this window. - FlutterHostWindowController* const window_controller_ = nullptr; + WindowManager* const window_manager_ = nullptr; // The Flutter engine that owns this window. FlutterWindowsEngine* engine_; diff --git a/engine/src/flutter/shell/platform/windows/flutter_windows_engine.cc b/engine/src/flutter/shell/platform/windows/flutter_windows_engine.cc index 17af5b0a6dc59..7a80bbd889ec7 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_windows_engine.cc +++ b/engine/src/flutter/shell/platform/windows/flutter_windows_engine.cc @@ -21,11 +21,11 @@ #include "flutter/shell/platform/windows/accessibility_bridge_windows.h" #include "flutter/shell/platform/windows/compositor_opengl.h" #include "flutter/shell/platform/windows/compositor_software.h" -#include "flutter/shell/platform/windows/flutter_host_window_controller.h" #include "flutter/shell/platform/windows/flutter_windows_view.h" #include "flutter/shell/platform/windows/keyboard_key_channel_handler.h" #include "flutter/shell/platform/windows/system_utils.h" #include "flutter/shell/platform/windows/task_runner.h" +#include "flutter/shell/platform/windows/window_manager.h" #include "flutter/third_party/accessibility/ax/ax_node.h" #include "shell/platform/windows/flutter_project_bundle.h" @@ -212,7 +212,7 @@ FlutterWindowsEngine::FlutterWindowsEngine( return true; } auto message_result = - that->host_window_controller_->HandleMessage(hwnd, msg, wpar, lpar); + that->window_manager_->HandleMessage(hwnd, msg, wpar, lpar); if (message_result) { *result = *message_result; return true; @@ -235,7 +235,7 @@ FlutterWindowsEngine::FlutterWindowsEngine( std::make_unique(messenger_wrapper_.get(), this); platform_handler_ = std::make_unique(messenger_wrapper_.get(), this); - host_window_controller_ = std::make_unique(this); + window_manager_ = std::make_unique(this); settings_plugin_ = std::make_unique(messenger_wrapper_.get(), task_runner_.get()); } @@ -511,7 +511,7 @@ bool FlutterWindowsEngine::Run(std::string_view entrypoint) { bool FlutterWindowsEngine::Stop() { if (engine_) { - host_window_controller_->OnEngineShutdown(); + window_manager_->OnEngineShutdown(); for (const auto& [callback, registrar] : plugin_registrar_destruction_callbacks_) { callback(registrar); diff --git a/engine/src/flutter/shell/platform/windows/flutter_windows_engine.h b/engine/src/flutter/shell/platform/windows/flutter_windows_engine.h index a90220bd811f8..f9a46da4fb894 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_windows_engine.h +++ b/engine/src/flutter/shell/platform/windows/flutter_windows_engine.h @@ -316,9 +316,7 @@ class FlutterWindowsEngine { // Sets the cursor directly from a cursor handle. void SetFlutterCursor(HCURSOR cursor) const; - FlutterHostWindowController* get_host_window_controller() { - return host_window_controller_.get(); - } + WindowManager* get_window_manager() { return window_manager_.get(); } // Returns the root view associated with the top-level window with |hwnd| as // the window handle. @@ -462,9 +460,9 @@ class FlutterWindowsEngine { // Handlers for keyboard events from Windows. std::unique_ptr keyboard_key_handler_; - // The controller that manages the lifecycle of |FlutterHostWindow|s, native + // The manager that manages the lifecycle of |FlutterHostWindow|s, native // Win32 windows hosting a Flutter view in their client area. - std::unique_ptr host_window_controller_; + std::unique_ptr window_manager_; // Handlers for text events from Windows. std::unique_ptr text_input_plugin_; diff --git a/engine/src/flutter/shell/platform/windows/flutter_host_window_controller.cc b/engine/src/flutter/shell/platform/windows/window_manager.cc similarity index 86% rename from engine/src/flutter/shell/platform/windows/flutter_host_window_controller.cc rename to engine/src/flutter/shell/platform/windows/window_manager.cc index 67127d2e03b0e..9cab737b2b936 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_host_window_controller.cc +++ b/engine/src/flutter/shell/platform/windows/window_manager.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "flutter/shell/platform/windows/flutter_host_window_controller.h" +#include "flutter/shell/platform/windows/window_manager.h" #include #include @@ -20,12 +20,9 @@ namespace flutter { -FlutterHostWindowController::FlutterHostWindowController( - FlutterWindowsEngine* engine) - : engine_(engine) {} +WindowManager::WindowManager(FlutterWindowsEngine* engine) : engine_(engine) {} -void FlutterHostWindowController::Initialize( - const WindowingInitRequest* request) { +void WindowManager::Initialize(const WindowingInitRequest* request) { on_message_ = request->on_message; isolate_ = Isolate::Current(); @@ -37,11 +34,11 @@ void FlutterHostWindowController::Initialize( pending_messages_.clear(); } -bool FlutterHostWindowController::HasTopLevelWindows() const { +bool WindowManager::HasTopLevelWindows() const { return !active_windows_.empty(); } -FlutterViewId FlutterHostWindowController::CreateRegularWindow( +FlutterViewId WindowManager::CreateRegularWindow( const WindowCreationRequest* request) { auto window = FlutterHostWindow::createRegularWindow(this, engine_, request->content_size); @@ -54,7 +51,7 @@ FlutterViewId FlutterHostWindowController::CreateRegularWindow( return view_id; } -void FlutterHostWindowController::OnEngineShutdown() { +void WindowManager::OnEngineShutdown() { // Don't send any more messages to isolate. on_message_ = nullptr; std::vector active_handles; @@ -70,11 +67,10 @@ void FlutterHostWindowController::OnEngineShutdown() { } } -std::optional FlutterHostWindowController::HandleMessage( - HWND hwnd, - UINT message, - WPARAM wparam, - LPARAM lparam) { +std::optional WindowManager::HandleMessage(HWND hwnd, + UINT message, + WPARAM wparam, + LPARAM lparam) { if (message == WM_NCDESTROY) { active_windows_.erase(hwnd); } @@ -121,14 +117,14 @@ void InternalFlutterWindows_WindowManager_Initialize( const flutter::WindowingInitRequest* request) { flutter::FlutterWindowsEngine* engine = flutter::FlutterWindowsEngine::GetEngineForId(engine_id); - engine->get_host_window_controller()->Initialize(request); + engine->get_window_manager()->Initialize(request); } bool InternalFlutterWindows_WindowManager_HasTopLevelWindows( int64_t engine_id) { flutter::FlutterWindowsEngine* engine = flutter::FlutterWindowsEngine::GetEngineForId(engine_id); - return engine->get_host_window_controller()->HasTopLevelWindows(); + return engine->get_window_manager()->HasTopLevelWindows(); } int64_t InternalFlutterWindows_WindowManager_CreateRegularWindow( @@ -136,7 +132,7 @@ int64_t InternalFlutterWindows_WindowManager_CreateRegularWindow( const flutter::WindowCreationRequest* request) { flutter::FlutterWindowsEngine* engine = flutter::FlutterWindowsEngine::GetEngineForId(engine_id); - return engine->get_host_window_controller()->CreateRegularWindow(request); + return engine->get_window_manager()->CreateRegularWindow(request); } HWND InternalFlutterWindows_WindowManager_GetWindowHandle( diff --git a/engine/src/flutter/shell/platform/windows/flutter_host_window_controller.h b/engine/src/flutter/shell/platform/windows/window_manager.h similarity index 88% rename from engine/src/flutter/shell/platform/windows/flutter_host_window_controller.h rename to engine/src/flutter/shell/platform/windows/window_manager.h index 729360dfd034a..a856a12f5a332 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_host_window_controller.h +++ b/engine/src/flutter/shell/platform/windows/window_manager.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_FLUTTER_HOST_WINDOW_CONTROLLER_H_ -#define FLUTTER_SHELL_PLATFORM_WINDOWS_FLUTTER_HOST_WINDOW_CONTROLLER_H_ +#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_WINDOW_MANAGER_H_ +#define FLUTTER_SHELL_PLATFORM_WINDOWS_WINDOW_MANAGER_H_ #include #include @@ -55,10 +55,10 @@ struct WindowCreationRequest { // A unique instance of this class is owned by |FlutterWindowsEngine| and used // in |WindowingHandler| to handle methods and messages enabling multi-window // support. -class FlutterHostWindowController { +class WindowManager { public: - explicit FlutterHostWindowController(FlutterWindowsEngine* engine); - virtual ~FlutterHostWindowController() = default; + explicit WindowManager(FlutterWindowsEngine* engine); + virtual ~WindowManager() = default; void Initialize(const WindowingInitRequest* request); @@ -95,7 +95,7 @@ class FlutterHostWindowController { // shutdown. std::unordered_map> active_windows_; - FML_DISALLOW_COPY_AND_ASSIGN(FlutterHostWindowController); + FML_DISALLOW_COPY_AND_ASSIGN(WindowManager); }; } // namespace flutter @@ -135,4 +135,4 @@ void InternalFlutterWindows_WindowManager_SetWindowContentSize( const flutter::FlutterWindowSizing* size); } -#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_FLUTTER_HOST_WINDOW_CONTROLLER_H_ +#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_WINDOW_MANAGER_H_ diff --git a/engine/src/flutter/shell/platform/windows/flutter_host_window_controller_unittests.cc b/engine/src/flutter/shell/platform/windows/window_manager_unittests.cc similarity index 87% rename from engine/src/flutter/shell/platform/windows/flutter_host_window_controller_unittests.cc rename to engine/src/flutter/shell/platform/windows/window_manager_unittests.cc index a4de4c74c1f3f..9342cb6cdea8a 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_host_window_controller_unittests.cc +++ b/engine/src/flutter/shell/platform/windows/window_manager_unittests.cc @@ -2,9 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "flutter/shell/platform/windows/flutter_host_window_controller.h" #include "flutter/shell/platform/windows/testing/flutter_windows_engine_builder.h" #include "flutter/shell/platform/windows/testing/windows_test.h" +#include "flutter/shell/platform/windows/window_manager.h" #include "gtest/gtest.h" namespace flutter { @@ -12,10 +12,10 @@ namespace testing { namespace { -class FlutterHostWindowControllerTest : public WindowsTest { +class WindowManagerTest : public WindowsTest { public: - FlutterHostWindowControllerTest() = default; - virtual ~FlutterHostWindowControllerTest() = default; + WindowManagerTest() = default; + virtual ~WindowManagerTest() = default; protected: void SetUp() override { @@ -57,12 +57,12 @@ class FlutterHostWindowControllerTest : public WindowsTest { }, }; - FML_DISALLOW_COPY_AND_ASSIGN(FlutterHostWindowControllerTest); + FML_DISALLOW_COPY_AND_ASSIGN(WindowManagerTest); }; } // namespace -TEST_F(FlutterHostWindowControllerTest, WindowingInitialize) { +TEST_F(WindowManagerTest, WindowingInitialize) { IsolateScope isolate_scope(isolate()); static bool received_message = false; @@ -79,7 +79,7 @@ TEST_F(FlutterHostWindowControllerTest, WindowingInitialize) { EXPECT_TRUE(received_message); } -TEST_F(FlutterHostWindowControllerTest, HasTopLevelWindows) { +TEST_F(WindowManagerTest, HasTopLevelWindows) { IsolateScope isolate_scope(isolate()); bool has_top_level_windows = @@ -93,7 +93,7 @@ TEST_F(FlutterHostWindowControllerTest, HasTopLevelWindows) { EXPECT_TRUE(has_top_level_windows); } -TEST_F(FlutterHostWindowControllerTest, CreateRegularWindow) { +TEST_F(WindowManagerTest, CreateRegularWindow) { IsolateScope isolate_scope(isolate()); const int64_t view_id = @@ -102,7 +102,7 @@ TEST_F(FlutterHostWindowControllerTest, CreateRegularWindow) { EXPECT_EQ(view_id, 0); } -TEST_F(FlutterHostWindowControllerTest, GetWindowHandle) { +TEST_F(WindowManagerTest, GetWindowHandle) { IsolateScope isolate_scope(isolate()); const int64_t view_id = @@ -114,7 +114,7 @@ TEST_F(FlutterHostWindowControllerTest, GetWindowHandle) { EXPECT_NE(window_handle, nullptr); } -TEST_F(FlutterHostWindowControllerTest, GetWindowSize) { +TEST_F(WindowManagerTest, GetWindowSize) { IsolateScope isolate_scope(isolate()); const int64_t view_id = @@ -131,7 +131,7 @@ TEST_F(FlutterHostWindowControllerTest, GetWindowSize) { EXPECT_EQ(size.height, creation_request()->content_size.height); } -TEST_F(FlutterHostWindowControllerTest, SetWindowSize) { +TEST_F(WindowManagerTest, SetWindowSize) { IsolateScope isolate_scope(isolate()); const int64_t view_id = From 4a4d8d7c5861006c3f9feee70c534a5194101b3d Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Fri, 13 Jun 2025 15:29:16 -0400 Subject: [PATCH 14/44] nit: using FML_CHECK over assert for has_size check --- .../src/flutter/shell/platform/windows/flutter_host_window.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/src/flutter/shell/platform/windows/flutter_host_window.cc b/engine/src/flutter/shell/platform/windows/flutter_host_window.cc index b3187d0aec912..80a6f403c160a 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_host_window.cc +++ b/engine/src/flutter/shell/platform/windows/flutter_host_window.cc @@ -292,7 +292,7 @@ std::unique_ptr FlutterHostWindow::createRegularWindow( } // TODO(knopp): What about windows sized to content? - assert(content_size.has_size); + FML_CHECK(content_size.has_size); // Calculate the screen space window rectangle for the new window. // Default positioning values (CW_USEDEFAULT) are used From a4ca74b1468210ad0da25b3a783e08d0d2d73358 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Fri, 13 Jun 2025 15:31:26 -0400 Subject: [PATCH 15/44] doc: fix bad window manager docstring --- engine/src/flutter/shell/platform/windows/window_manager.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/engine/src/flutter/shell/platform/windows/window_manager.h b/engine/src/flutter/shell/platform/windows/window_manager.h index a856a12f5a332..1df0ec4fa0d14 100644 --- a/engine/src/flutter/shell/platform/windows/window_manager.h +++ b/engine/src/flutter/shell/platform/windows/window_manager.h @@ -51,10 +51,8 @@ struct WindowCreationRequest { FlutterWindowSizing content_size; }; -// A controller class for managing |FlutterHostWindow| instances. -// A unique instance of this class is owned by |FlutterWindowsEngine| and used -// in |WindowingHandler| to handle methods and messages enabling multi-window -// support. +// A manager class for managing |FlutterHostWindow| instances. +// A unique instance of this class is owned by |FlutterWindowsEngine|. class WindowManager { public: explicit WindowManager(FlutterWindowsEngine* engine); From 4b46e131e993b90cad5200400de730434393a8b7 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Fri, 13 Jun 2025 15:36:57 -0400 Subject: [PATCH 16/44] rename: get_window_manager to window_manager --- .../flutter/shell/platform/windows/flutter_windows_engine.h | 2 +- engine/src/flutter/shell/platform/windows/window_manager.cc | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/engine/src/flutter/shell/platform/windows/flutter_windows_engine.h b/engine/src/flutter/shell/platform/windows/flutter_windows_engine.h index f9a46da4fb894..ff207dac9b42a 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_windows_engine.h +++ b/engine/src/flutter/shell/platform/windows/flutter_windows_engine.h @@ -316,7 +316,7 @@ class FlutterWindowsEngine { // Sets the cursor directly from a cursor handle. void SetFlutterCursor(HCURSOR cursor) const; - WindowManager* get_window_manager() { return window_manager_.get(); } + WindowManager* window_manager() { return window_manager_.get(); } // Returns the root view associated with the top-level window with |hwnd| as // the window handle. diff --git a/engine/src/flutter/shell/platform/windows/window_manager.cc b/engine/src/flutter/shell/platform/windows/window_manager.cc index 9cab737b2b936..7737f3e250c95 100644 --- a/engine/src/flutter/shell/platform/windows/window_manager.cc +++ b/engine/src/flutter/shell/platform/windows/window_manager.cc @@ -117,14 +117,14 @@ void InternalFlutterWindows_WindowManager_Initialize( const flutter::WindowingInitRequest* request) { flutter::FlutterWindowsEngine* engine = flutter::FlutterWindowsEngine::GetEngineForId(engine_id); - engine->get_window_manager()->Initialize(request); + engine->window_manager()->Initialize(request); } bool InternalFlutterWindows_WindowManager_HasTopLevelWindows( int64_t engine_id) { flutter::FlutterWindowsEngine* engine = flutter::FlutterWindowsEngine::GetEngineForId(engine_id); - return engine->get_window_manager()->HasTopLevelWindows(); + return engine->window_manager()->HasTopLevelWindows(); } int64_t InternalFlutterWindows_WindowManager_CreateRegularWindow( @@ -132,7 +132,7 @@ int64_t InternalFlutterWindows_WindowManager_CreateRegularWindow( const flutter::WindowCreationRequest* request) { flutter::FlutterWindowsEngine* engine = flutter::FlutterWindowsEngine::GetEngineForId(engine_id); - return engine->get_window_manager()->CreateRegularWindow(request); + return engine->window_manager()->CreateRegularWindow(request); } HWND InternalFlutterWindows_WindowManager_GetWindowHandle( From 35d91d23b11d4ea9f492135bbe8d382c4b908d9a Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Fri, 13 Jun 2025 15:44:35 -0400 Subject: [PATCH 17/44] nit: return FlutterViewId from create regular window FFI call --- engine/src/flutter/shell/platform/windows/window_manager.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/src/flutter/shell/platform/windows/window_manager.h b/engine/src/flutter/shell/platform/windows/window_manager.h index 1df0ec4fa0d14..05f61502ab64d 100644 --- a/engine/src/flutter/shell/platform/windows/window_manager.h +++ b/engine/src/flutter/shell/platform/windows/window_manager.h @@ -109,7 +109,7 @@ FLUTTER_EXPORT bool InternalFlutterWindows_WindowManager_HasTopLevelWindows(int64_t engine_id); FLUTTER_EXPORT -int64_t InternalFlutterWindows_WindowManager_CreateRegularWindow( +FlutterViewId InternalFlutterWindows_WindowManager_CreateRegularWindow( int64_t engine_id, const flutter::WindowCreationRequest* request); From 0f7087feec1b942aa2a280cd075d0b8adf08a79a Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Fri, 13 Jun 2025 15:49:11 -0400 Subject: [PATCH 18/44] nit: use FlutterViewId in WindowsMessage --- engine/src/flutter/shell/platform/windows/window_manager.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/src/flutter/shell/platform/windows/window_manager.h b/engine/src/flutter/shell/platform/windows/window_manager.h index 05f61502ab64d..e6afe22a298bb 100644 --- a/engine/src/flutter/shell/platform/windows/window_manager.h +++ b/engine/src/flutter/shell/platform/windows/window_manager.h @@ -23,7 +23,7 @@ class FlutterHostWindow; struct WindowingInitRequest; struct WindowsMessage { - int64_t view_id; + FlutterViewId view_id; HWND hwnd; UINT message; WPARAM wParam; From 4d749221f909e1a4319996f0527822c0d35dbb87 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Fri, 13 Jun 2025 15:55:52 -0400 Subject: [PATCH 19/44] nit: use FlutterViewId in the definition as well --- engine/src/flutter/shell/platform/windows/window_manager.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/src/flutter/shell/platform/windows/window_manager.cc b/engine/src/flutter/shell/platform/windows/window_manager.cc index 7737f3e250c95..47c4ab48929e6 100644 --- a/engine/src/flutter/shell/platform/windows/window_manager.cc +++ b/engine/src/flutter/shell/platform/windows/window_manager.cc @@ -127,7 +127,7 @@ bool InternalFlutterWindows_WindowManager_HasTopLevelWindows( return engine->window_manager()->HasTopLevelWindows(); } -int64_t InternalFlutterWindows_WindowManager_CreateRegularWindow( +FlutterViewId InternalFlutterWindows_WindowManager_CreateRegularWindow( int64_t engine_id, const flutter::WindowCreationRequest* request) { flutter::FlutterWindowsEngine* engine = From 8ae391227fea29cccef7ec6b70b565302a5b23b8 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Fri, 13 Jun 2025 15:56:18 -0400 Subject: [PATCH 20/44] docs: adding a comment to InternalFlutterWindows_WindowManager_GetWindowHandle --- engine/src/flutter/shell/platform/windows/window_manager.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/engine/src/flutter/shell/platform/windows/window_manager.h b/engine/src/flutter/shell/platform/windows/window_manager.h index e6afe22a298bb..0796ce7b24b50 100644 --- a/engine/src/flutter/shell/platform/windows/window_manager.h +++ b/engine/src/flutter/shell/platform/windows/window_manager.h @@ -113,6 +113,8 @@ FlutterViewId InternalFlutterWindows_WindowManager_CreateRegularWindow( int64_t engine_id, const flutter::WindowCreationRequest* request); +// Retrives the HWND associated with this |engine_id| and |view_id|. Returns +// NULL if the HWND cannot be found FLUTTER_EXPORT HWND InternalFlutterWindows_WindowManager_GetWindowHandle( int64_t engine_id, From 30cf11764633f8b087283305b43979ac8c6651f8 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Fri, 13 Jun 2025 16:47:20 -0400 Subject: [PATCH 21/44] rename: to get top level window handle --- .../shell/platform/windows/window_manager.cc | 2 +- .../shell/platform/windows/window_manager.h | 2 +- .../platform/windows/window_manager_unittests.cc | 14 +++++++------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/engine/src/flutter/shell/platform/windows/window_manager.cc b/engine/src/flutter/shell/platform/windows/window_manager.cc index 47c4ab48929e6..a4a8e3eaf93ab 100644 --- a/engine/src/flutter/shell/platform/windows/window_manager.cc +++ b/engine/src/flutter/shell/platform/windows/window_manager.cc @@ -135,7 +135,7 @@ FlutterViewId InternalFlutterWindows_WindowManager_CreateRegularWindow( return engine->window_manager()->CreateRegularWindow(request); } -HWND InternalFlutterWindows_WindowManager_GetWindowHandle( +HWND InternalFlutterWindows_WindowManager_GetTopLevelWindowHandle( int64_t engine_id, FlutterViewId view_id) { flutter::FlutterWindowsEngine* engine = diff --git a/engine/src/flutter/shell/platform/windows/window_manager.h b/engine/src/flutter/shell/platform/windows/window_manager.h index 0796ce7b24b50..64eb16f22f3ce 100644 --- a/engine/src/flutter/shell/platform/windows/window_manager.h +++ b/engine/src/flutter/shell/platform/windows/window_manager.h @@ -116,7 +116,7 @@ FlutterViewId InternalFlutterWindows_WindowManager_CreateRegularWindow( // Retrives the HWND associated with this |engine_id| and |view_id|. Returns // NULL if the HWND cannot be found FLUTTER_EXPORT -HWND InternalFlutterWindows_WindowManager_GetWindowHandle( +HWND InternalFlutterWindows_WindowManager_GetTopLevelWindowHandle( int64_t engine_id, FlutterViewId view_id); diff --git a/engine/src/flutter/shell/platform/windows/window_manager_unittests.cc b/engine/src/flutter/shell/platform/windows/window_manager_unittests.cc index 9342cb6cdea8a..a8728a9ed3145 100644 --- a/engine/src/flutter/shell/platform/windows/window_manager_unittests.cc +++ b/engine/src/flutter/shell/platform/windows/window_manager_unittests.cc @@ -73,7 +73,7 @@ TEST_F(WindowManagerTest, WindowingInitialize) { const int64_t view_id = InternalFlutterWindows_WindowManager_CreateRegularWindow( engine_id(), creation_request()); - DestroyWindow(InternalFlutterWindows_WindowManager_GetWindowHandle( + DestroyWindow(InternalFlutterWindows_WindowManager_GetTopLevelWindowHandle( engine_id(), view_id)); EXPECT_TRUE(received_message); @@ -109,8 +109,8 @@ TEST_F(WindowManagerTest, GetWindowHandle) { InternalFlutterWindows_WindowManager_CreateRegularWindow( engine_id(), creation_request()); const HWND window_handle = - InternalFlutterWindows_WindowManager_GetWindowHandle(engine_id(), - view_id); + InternalFlutterWindows_WindowManager_GetTopLevelWindowHandle(engine_id(), + view_id); EXPECT_NE(window_handle, nullptr); } @@ -121,8 +121,8 @@ TEST_F(WindowManagerTest, GetWindowSize) { InternalFlutterWindows_WindowManager_CreateRegularWindow( engine_id(), creation_request()); const HWND window_handle = - InternalFlutterWindows_WindowManager_GetWindowHandle(engine_id(), - view_id); + InternalFlutterWindows_WindowManager_GetTopLevelWindowHandle(engine_id(), + view_id); FlutterWindowSize size = InternalFlutterWindows_WindowManager_GetWindowContentSize(window_handle); @@ -138,8 +138,8 @@ TEST_F(WindowManagerTest, SetWindowSize) { InternalFlutterWindows_WindowManager_CreateRegularWindow( engine_id(), creation_request()); const HWND window_handle = - InternalFlutterWindows_WindowManager_GetWindowHandle(engine_id(), - view_id); + InternalFlutterWindows_WindowManager_GetTopLevelWindowHandle(engine_id(), + view_id); FlutterWindowSizing requestedSize{ .has_size = true, From 2355a12203e119732cfc6788fc96295d5bf9d001 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Mon, 16 Jun 2025 15:37:39 -0400 Subject: [PATCH 22/44] refactor: move user32 methods into the WindowsProcTable abstraction --- .../platform/windows/flutter_host_window.cc | 143 ++++++------------ .../platform/windows/windows_proc_table.cc | 53 +++++++ .../platform/windows/windows_proc_table.h | 65 ++++++++ 3 files changed, 161 insertions(+), 100 deletions(-) diff --git a/engine/src/flutter/shell/platform/windows/flutter_host_window.cc b/engine/src/flutter/shell/platform/windows/flutter_host_window.cc index 80a6f403c160a..eb1aeda3f703e 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_host_window.cc +++ b/engine/src/flutter/shell/platform/windows/flutter_host_window.cc @@ -25,76 +25,34 @@ flutter::Size ClampToVirtualScreen(flutter::Size size) { std::clamp(size.height(), 0.0, virtual_screen_height)); } -// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module -// so that the non-client area automatically responds to changes in DPI. -// This API is only needed for PerMonitor V1 awareness mode. -void EnableFullDpiSupportIfAvailable(HWND hwnd) { - HMODULE user32_module = LoadLibraryA("User32.dll"); - if (!user32_module) { - return; - } - - using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); - - auto enable_non_client_dpi_scaling = - reinterpret_cast( - GetProcAddress(user32_module, "EnableNonClientDpiScaling")); - if (enable_non_client_dpi_scaling != nullptr) { - enable_non_client_dpi_scaling(hwnd); - } - - FreeLibrary(user32_module); -} - -// Dynamically loads |SetWindowCompositionAttribute| from the User32 module to -// make the window's background transparent. -void EnableTransparentWindowBackground(HWND hwnd) { - HMODULE const user32_module = LoadLibraryA("User32.dll"); - if (!user32_module) { - return; - } - - enum WINDOWCOMPOSITIONATTRIB { WCA_ACCENT_POLICY = 19 }; - - struct WINDOWCOMPOSITIONATTRIBDATA { - WINDOWCOMPOSITIONATTRIB Attrib; - PVOID pvData; - SIZE_T cbData; +void EnableTransparentWindowBackground( + HWND hwnd, + flutter::WindowsProcTable const& proc_table) { + enum ACCENT_STATE { ACCENT_DISABLED = 0 }; + + struct ACCENT_POLICY { + ACCENT_STATE AccentState; + DWORD AccentFlags; + DWORD GradientColor; + DWORD AnimationId; }; - using SetWindowCompositionAttribute = - BOOL(__stdcall*)(HWND, WINDOWCOMPOSITIONATTRIBDATA*); - - auto set_window_composition_attribute = - reinterpret_cast( - GetProcAddress(user32_module, "SetWindowCompositionAttribute")); - if (set_window_composition_attribute != nullptr) { - enum ACCENT_STATE { ACCENT_DISABLED = 0 }; - - struct ACCENT_POLICY { - ACCENT_STATE AccentState; - DWORD AccentFlags; - DWORD GradientColor; - DWORD AnimationId; - }; - - // Set the accent policy to disable window composition. - ACCENT_POLICY accent = {ACCENT_DISABLED, 2, static_cast(0), 0}; - WINDOWCOMPOSITIONATTRIBDATA data = {.Attrib = WCA_ACCENT_POLICY, - .pvData = &accent, - .cbData = sizeof(accent)}; - set_window_composition_attribute(hwnd, &data); - - // Extend the frame into the client area and set the window's system - // backdrop type for visual effects. - MARGINS const margins = {-1}; - ::DwmExtendFrameIntoClientArea(hwnd, &margins); - INT effect_value = 1; - ::DwmSetWindowAttribute(hwnd, DWMWA_SYSTEMBACKDROP_TYPE, &effect_value, - sizeof(BOOL)); - } - - FreeLibrary(user32_module); + // Set the accent policy to disable window composition. + ACCENT_POLICY accent = {ACCENT_DISABLED, 2, static_cast(0), 0}; + flutter::WindowsProcTable::WINDOWCOMPOSITIONATTRIBDATA data = { + .Attrib = + flutter::WindowsProcTable::WINDOWCOMPOSITIONATTRIB::WCA_ACCENT_POLICY, + .pvData = &accent, + .cbData = sizeof(accent)}; + proc_table.SetWindowCompositionAttribute(hwnd, &data); + + // Extend the frame into the client area and set the window's system + // backdrop type for visual effects. + MARGINS const margins = {-1}; + proc_table.DwmExtendFrameIntoClientArea(hwnd, &margins); + INT effect_value = 1; + proc_table.DwmSetWindowAttribute(hwnd, DWMWA_SYSTEMBACKDROP_TYPE, + &effect_value, sizeof(BOOL)); } // Retrieves the calling thread's last-error code message as a string, @@ -139,6 +97,7 @@ std::string GetLastErrorAsString() { // resulting size includes window borders, non-client areas, and drop shadows. // On error, returns std::nullopt and logs an error message. std::optional GetWindowSizeForClientSize( + flutter::WindowsProcTable const& proc_table, flutter::Size const& client_size, std::optional min_size, std::optional max_size, @@ -152,28 +111,8 @@ std::optional GetWindowSizeForClientSize( .right = static_cast(client_size.width() * scale_factor), .bottom = static_cast(client_size.height() * scale_factor)}; - HMODULE const user32_raw = LoadLibraryA("User32.dll"); - auto free_user32_module = [](HMODULE module) { FreeLibrary(module); }; - std::unique_ptr, decltype(free_user32_module)> - user32_module(user32_raw, free_user32_module); - if (!user32_module) { - FML_LOG(ERROR) << "Failed to load User32.dll.\n"; - return std::nullopt; - } - - using AdjustWindowRectExForDpi = BOOL __stdcall( - LPRECT lpRect, DWORD dwStyle, BOOL bMenu, DWORD dwExStyle, UINT dpi); - auto* const adjust_window_rect_ext_for_dpi = - reinterpret_cast( - GetProcAddress(user32_raw, "AdjustWindowRectExForDpi")); - if (!adjust_window_rect_ext_for_dpi) { - FML_LOG(ERROR) << "Failed to retrieve AdjustWindowRectExForDpi address " - "from User32.dll."; - return std::nullopt; - } - - if (!adjust_window_rect_ext_for_dpi(&rect, window_style, FALSE, - extended_window_style, dpi)) { + if (!proc_table.AdjustWindowRectExForDpi(&rect, window_style, FALSE, + extended_window_style, dpi)) { FML_LOG(ERROR) << "Failed to run AdjustWindowRectExForDpi: " << GetLastErrorAsString(); return std::nullopt; @@ -299,6 +238,7 @@ std::unique_ptr FlutterHostWindow::createRegularWindow( // if the window has no owner. Rect const initial_window_rect = [&]() -> Rect { std::optional const window_size = GetWindowSizeForClientSize( + *engine->windows_proc_table(), Size(content_size.width, content_size.height), min_size, max_size, window_style, extended_window_style, nullptr); return {{CW_USEDEFAULT, CW_USEDEFAULT}, @@ -351,11 +291,11 @@ std::unique_ptr FlutterHostWindow::createRegularWindow( } // Create the native window. - HWND hwnd = - CreateWindowEx(extended_window_style, kWindowClassName, L"", window_style, - initial_window_rect.left(), initial_window_rect.top(), - initial_window_rect.width(), initial_window_rect.height(), - nullptr, nullptr, GetModuleHandle(nullptr), nullptr); + HWND hwnd = CreateWindowEx( + extended_window_style, kWindowClassName, L"", window_style, + initial_window_rect.left(), initial_window_rect.top(), + initial_window_rect.width(), initial_window_rect.height(), nullptr, + nullptr, GetModuleHandle(nullptr), engine->windows_proc_table().get()); if (!hwnd) { FML_LOG(ERROR) << "Cannot create window: " << GetLastErrorAsString(); return nullptr; @@ -439,8 +379,11 @@ LRESULT FlutterHostWindow::WndProc(HWND hwnd, WPARAM wparam, LPARAM lparam) { if (message == WM_NCCREATE) { - EnableFullDpiSupportIfAvailable(hwnd); - EnableTransparentWindowBackground(hwnd); + auto* const create_struct = reinterpret_cast(lparam); + auto* const windows_proc_table = + static_cast(create_struct->lpCreateParams); + windows_proc_table->EnableNonClientDpiScaling(hwnd); + EnableTransparentWindowBackground(hwnd, *windows_proc_table); } else if (FlutterHostWindow* const window = GetThisFromHandle(hwnd)) { return window->HandleMessage(hwnd, message, wparam, lparam); } @@ -561,9 +504,9 @@ void FlutterHostWindow::SetContentSize(const FlutterWindowSizing& size) { if (size.has_size) { std::optional const window_size = GetWindowSizeForClientSize( - Size(size.width, size.height), box_constraints_.min_size(), - box_constraints_.max_size(), window_info.dwStyle, window_info.dwExStyle, - nullptr); + *engine_->windows_proc_table(), Size(size.width, size.height), + box_constraints_.min_size(), box_constraints_.max_size(), + window_info.dwStyle, window_info.dwExStyle, nullptr); if (window_size) { SetWindowPos(window_handle_, NULL, 0, 0, window_size->width(), diff --git a/engine/src/flutter/shell/platform/windows/windows_proc_table.cc b/engine/src/flutter/shell/platform/windows/windows_proc_table.cc index 2cb8e26fc1c66..2be3c056c03db 100644 --- a/engine/src/flutter/shell/platform/windows/windows_proc_table.cc +++ b/engine/src/flutter/shell/platform/windows/windows_proc_table.cc @@ -13,6 +13,15 @@ WindowsProcTable::WindowsProcTable() { user32_ = fml::NativeLibrary::Create("user32.dll"); get_pointer_type_ = user32_->ResolveFunction("GetPointerType"); + enable_non_client_dpi_scaling_ = + user32_->ResolveFunction( + "EnableNonClientDpiScaling"); + set_window_composition_attribute_ = + user32_->ResolveFunction( + "SetWindowCompositionAttribute"); + adjust_window_rect_ext_for_dpi_ = + user32_->ResolveFunction( + "AdjustWindowRectExForDpi"); } WindowsProcTable::~WindowsProcTable() { @@ -67,4 +76,48 @@ HCURSOR WindowsProcTable::SetCursor(HCURSOR cursor) const { return ::SetCursor(cursor); } +BOOL WindowsProcTable::EnableNonClientDpiScaling(HWND hwnd) const { + if (!enable_non_client_dpi_scaling_.has_value()) { + return FALSE; + } + + return enable_non_client_dpi_scaling_.value()(hwnd); +} + +BOOL WindowsProcTable::SetWindowCompositionAttribute( + HWND hwnd, + WINDOWCOMPOSITIONATTRIBDATA* data) const { + if (!set_window_composition_attribute_.has_value()) { + return FALSE; + } + + return set_window_composition_attribute_.value()(hwnd, data); +} + +HRESULT WindowsProcTable::DwmExtendFrameIntoClientArea( + HWND hwnd, + const MARGINS* pMarInset) const { + return ::DwmExtendFrameIntoClientArea(hwnd, pMarInset); +} + +HRESULT WindowsProcTable::DwmSetWindowAttribute(HWND hwnd, + DWORD dwAttribute, + LPCVOID pvAttribute, + DWORD cbAttribute) const { + return ::DwmSetWindowAttribute(hwnd, dwAttribute, pvAttribute, cbAttribute); +} + +BOOL WindowsProcTable::AdjustWindowRectExForDpi(LPRECT lpRect, + DWORD dwStyle, + BOOL bMenu, + DWORD dwExStyle, + UINT dpi) const { + if (!adjust_window_rect_ext_for_dpi_.has_value()) { + return FALSE; + } + + return adjust_window_rect_ext_for_dpi_.value()(lpRect, dwStyle, bMenu, + dwExStyle, dpi); +} + } // namespace flutter diff --git a/engine/src/flutter/shell/platform/windows/windows_proc_table.h b/engine/src/flutter/shell/platform/windows/windows_proc_table.h index dd1190a7f245d..9290e1302d1eb 100644 --- a/engine/src/flutter/shell/platform/windows/windows_proc_table.h +++ b/engine/src/flutter/shell/platform/windows/windows_proc_table.h @@ -5,6 +5,7 @@ #ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_WINDOWS_PROC_TABLE_H_ #define FLUTTER_SHELL_PLATFORM_WINDOWS_WINDOWS_PROC_TABLE_H_ +#include #include #include "flutter/fml/macros.h" @@ -16,6 +17,14 @@ namespace flutter { // Windows, or for mocking Windows API calls. class WindowsProcTable { public: + enum WINDOWCOMPOSITIONATTRIB { WCA_ACCENT_POLICY = 19 }; + + struct WINDOWCOMPOSITIONATTRIBDATA { + WINDOWCOMPOSITIONATTRIB Attrib; + PVOID pvData; + SIZE_T cbData; + }; + WindowsProcTable(); virtual ~WindowsProcTable(); @@ -70,14 +79,70 @@ class WindowsProcTable { // https://learn.microsoft.com/windows/win32/api/winuser/nf-winuser-setcursor virtual HCURSOR SetCursor(HCURSOR cursor) const; + // Enables automatic display scaling of the non-client area portions of the + // specified top-level window. + // + // See: + // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enablenonclientdpiscaling + virtual BOOL EnableNonClientDpiScaling(HWND hwnd) const; + + // Sets the current value of a specified Desktop Window Manager (DWM) + // attribute applied to a window. + // + // See: + // https://learn.microsoft.com/en-us/windows/win32/dwm/setwindowcompositionattribute + virtual BOOL SetWindowCompositionAttribute( + HWND hwnd, + WINDOWCOMPOSITIONATTRIBDATA* data) const; + + // Extends the window frame into the client area. + // + // See: + // https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmextendframeintoclientarea + virtual HRESULT DwmExtendFrameIntoClientArea(HWND hwnd, + const MARGINS* pMarInset) const; + + // Sets the value of Desktop Window Manager (DWM) non-client rendering + // attributes for a window. + // + // See: + // https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmsetwindowattribute + virtual HRESULT DwmSetWindowAttribute(HWND hwnd, + DWORD dwAttribute, + LPCVOID pvAttribute, + DWORD cbAttribute) const; + + // Calculates the required size of the window rectangle, based on the desired + // size of the client rectangle and the provided DPI. + // + // See: + // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-adjustwindowrectexfordpi + virtual BOOL AdjustWindowRectExForDpi(LPRECT lpRect, + DWORD dwStyle, + BOOL bMenu, + DWORD dwExStyle, + UINT dpi) const; + private: using GetPointerType_ = BOOL __stdcall(UINT32 pointerId, POINTER_INPUT_TYPE* pointerType); + using EnableNonClientDpiScaling_ = BOOL __stdcall(HWND hwnd); + using SetWindowCompositionAttribute_ = + BOOL __stdcall(HWND, WINDOWCOMPOSITIONATTRIBDATA*); + using AdjustWindowRectExForDpi_ = BOOL __stdcall(LPRECT lpRect, + DWORD dwStyle, + BOOL bMenu, + DWORD dwExStyle, + UINT dpi); // The User32.dll library, used to resolve functions at runtime. fml::RefPtr user32_; std::optional get_pointer_type_; + std::optional enable_non_client_dpi_scaling_; + std::optional + set_window_composition_attribute_; + std::optional adjust_window_rect_ext_for_dpi_; FML_DISALLOW_COPY_AND_ASSIGN(WindowsProcTable); }; From 26aa9c14199128a3ff7867a064674162a452e360 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Mon, 16 Jun 2025 15:39:08 -0400 Subject: [PATCH 23/44] style: fix IsolateScope nit --- engine/src/flutter/shell/platform/common/isolate_scope.h | 1 + 1 file changed, 1 insertion(+) diff --git a/engine/src/flutter/shell/platform/common/isolate_scope.h b/engine/src/flutter/shell/platform/common/isolate_scope.h index e4d84889466ac..69147d17d6b23 100644 --- a/engine/src/flutter/shell/platform/common/isolate_scope.h +++ b/engine/src/flutter/shell/platform/common/isolate_scope.h @@ -39,6 +39,7 @@ class IsolateScope { IsolateScope() = delete; IsolateScope(IsolateScope const&) = delete; }; + } // namespace flutter #endif // FLUTTER_SHELL_PLATFORM_COMMON_ISOLATE_SCOPE_H_ From 9ed43d0d5f00a11f0ba39032913773a423d94c9c Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Mon, 16 Jun 2025 18:43:48 -0400 Subject: [PATCH 24/44] Update win32 API per latest FFI --- examples/multi_window_ref_app/pubspec.yaml | 2 +- packages/flutter/lib/src/widgets/window_win32.dart | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/multi_window_ref_app/pubspec.yaml b/examples/multi_window_ref_app/pubspec.yaml index ec5a4e3c2f4fc..fd7fc9122bdce 100644 --- a/examples/multi_window_ref_app/pubspec.yaml +++ b/examples/multi_window_ref_app/pubspec.yaml @@ -9,7 +9,7 @@ dependencies: flutter: sdk: flutter stack_trace: 1.12.1 - vector_math: 2.1.4 + vector_math: 2.2.0 characters: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.19.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" diff --git a/packages/flutter/lib/src/widgets/window_win32.dart b/packages/flutter/lib/src/widgets/window_win32.dart index 61bff21593275..ce9572cba4c9c 100644 --- a/packages/flutter/lib/src/widgets/window_win32.dart +++ b/packages/flutter/lib/src/widgets/window_win32.dart @@ -86,11 +86,11 @@ class WindowingOwnerWin32 extends WindowingOwner { return _hasTopLevelWindows(PlatformDispatcher.instance.engineId!); } - @Native(symbol: 'FlutterWindowingHasTopLevelWindows') + @Native(symbol: 'InternalFlutterWindows_WindowManager_HasTopLevelWindows') external static bool _hasTopLevelWindows(int engineId); @Native)>( - symbol: 'FlutterWindowingInitialize', + symbol: 'InternalFlutterWindows_WindowManager_Initialize', ) external static void _initializeWindowing(int engineId, Pointer<_WindowingInitRequest> request); } @@ -246,23 +246,23 @@ class RegularWindowControllerWin32 extends RegularWindowController final WindowingOwnerWin32 _owner; @Native)>( - symbol: 'FlutterCreateRegularWindow', + symbol: 'InternalFlutterWindows_WindowManager_CreateRegularWindow', ) external static int _createWindow(int engineId, Pointer<_WindowCreationRequest> request); - @Native Function(Int64, Int64)>(symbol: 'FlutterGetWindowHandle') + @Native Function(Int64, Int64)>(symbol: 'InternalFlutterWindows_WindowManager_GetTopLevelWindowHandle') external static Pointer _getWindowHandle(int engineId, int viewId); @Native)>(symbol: 'DestroyWindow') external static void _destroyWindow(Pointer windowHandle); - @Native<_Size Function(Pointer)>(symbol: 'FlutterGetWindowContentSize') + @Native<_Size Function(Pointer)>(symbol: 'InternalFlutterWindows_WindowManager_GetWindowContentSize') external static _Size _getWindowContentSize(Pointer windowHandle); @Native, Pointer)>(symbol: 'SetWindowTextW') external static void _setWindowTitle(Pointer windowHandle, Pointer title); - @Native, Pointer<_Sizing>)>(symbol: 'FlutterSetWindowContentSize') + @Native, Pointer<_Sizing>)>(symbol: 'InternalFlutterWindows_WindowManager_SetWindowContentSize') external static void _setWindowContentSize(Pointer windowHandle, Pointer<_Sizing> size); @Native, Int32)>(symbol: 'ShowWindow') From a72cd927f89e2b90757bb04986dec1d70e6f2450 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Tue, 17 Jun 2025 08:45:18 -0400 Subject: [PATCH 25/44] refactor: improve BoxConstraints constructor --- .../flutter/shell/platform/common/geometry.h | 25 +++++---- .../platform/windows/flutter_host_window.cc | 52 +++++++++---------- 2 files changed, 38 insertions(+), 39 deletions(-) diff --git a/engine/src/flutter/shell/platform/common/geometry.h b/engine/src/flutter/shell/platform/common/geometry.h index 32542e11642bc..9ca17c7852a8c 100644 --- a/engine/src/flutter/shell/platform/common/geometry.h +++ b/engine/src/flutter/shell/platform/common/geometry.h @@ -6,6 +6,7 @@ #define FLUTTER_SHELL_PLATFORM_COMMON_GEOMETRY_H_ #include +#include #include namespace flutter { @@ -85,22 +86,20 @@ class Rect { class BoxConstraints { public: BoxConstraints() = default; - BoxConstraints(const std::optional& min_size, - const std::optional& max_size) - : min_size_(min_size), max_size_(max_size) {} - static BoxConstraints tight(const Size& size) { - return BoxConstraints(size, size); - } - static BoxConstraints loose(const Size& size) { - return BoxConstraints(Size(0, 0), size); - } + BoxConstraints(const std::optional& smallest, + const std::optional& biggest) + : smallest_(smallest.value_or(Size(0, 0))), + biggest_( + biggest.value_or(Size(std::numeric_limits::infinity(), + std::numeric_limits::infinity()))) {} BoxConstraints(const BoxConstraints& other) = default; - std::optional max_size() const { return max_size_; } - std::optional min_size() const { return min_size_; } + std::optional biggest() const { return biggest_; } + std::optional smallest() const { return smallest_; } private: - std::optional min_size_; - std::optional max_size_; + Size smallest_ = Size(0, 0); + Size biggest_ = Size(std::numeric_limits::infinity(), + std::numeric_limits::infinity()); }; } // namespace flutter diff --git a/engine/src/flutter/shell/platform/windows/flutter_host_window.cc b/engine/src/flutter/shell/platform/windows/flutter_host_window.cc index eb1aeda3f703e..a07b030ed1d99 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_host_window.cc +++ b/engine/src/flutter/shell/platform/windows/flutter_host_window.cc @@ -90,7 +90,7 @@ std::string GetLastErrorAsString() { // Calculates the required window size, in physical coordinates, to // accommodate the given |client_size|, in logical coordinates, constrained by -// optional |min_size| and |max_size|, for a window with the specified +// optional |smallest| and |biggest|, for a window with the specified // |window_style| and |extended_window_style|. If |owner_hwnd| is not null, the // DPI of the display with the largest area of intersection with |owner_hwnd| is // used for the calculation; otherwise, the primary display's DPI is used. The @@ -99,8 +99,8 @@ std::string GetLastErrorAsString() { std::optional GetWindowSizeForClientSize( flutter::WindowsProcTable const& proc_table, flutter::Size const& client_size, - std::optional min_size, - std::optional max_size, + std::optional smallest, + std::optional biggest, DWORD window_style, DWORD extended_window_style, HWND owner_hwnd) { @@ -125,17 +125,17 @@ std::optional GetWindowSizeForClientSize( double const non_client_width = width - (client_size.width() * scale_factor); double const non_client_height = height - (client_size.height() * scale_factor); - if (min_size) { + if (smallest) { flutter::Size min_physical_size = ClampToVirtualScreen( - flutter::Size(min_size->width() * scale_factor + non_client_width, - min_size->height() * scale_factor + non_client_height)); + flutter::Size(smallest->width() * scale_factor + non_client_width, + smallest->height() * scale_factor + non_client_height)); width = std::max(width, min_physical_size.width()); height = std::max(height, min_physical_size.height()); } - if (max_size) { + if (biggest) { flutter::Size max_physical_size = ClampToVirtualScreen( - flutter::Size(max_size->width() * scale_factor + non_client_width, - max_size->height() * scale_factor + non_client_height)); + flutter::Size(biggest->width() * scale_factor + non_client_width, + biggest->height() * scale_factor + non_client_height)); width = std::min(width, max_physical_size.width()); height = std::min(height, max_physical_size.height()); } @@ -220,13 +220,13 @@ std::unique_ptr FlutterHostWindow::createRegularWindow( const FlutterWindowSizing& content_size) { DWORD window_style = WS_OVERLAPPEDWINDOW; DWORD extended_window_style = 0; - std::optional min_size = std::nullopt; - std::optional max_size = std::nullopt; + std::optional smallest = std::nullopt; + std::optional biggest = std::nullopt; if (content_size.has_constraints) { - min_size = Size(content_size.min_width, content_size.min_height); + smallest = Size(content_size.min_width, content_size.min_height); if (content_size.max_width > 0 && content_size.max_height > 0) { - max_size = Size(content_size.max_width, content_size.max_height); + biggest = Size(content_size.max_width, content_size.max_height); } } @@ -239,7 +239,7 @@ std::unique_ptr FlutterHostWindow::createRegularWindow( Rect const initial_window_rect = [&]() -> Rect { std::optional const window_size = GetWindowSizeForClientSize( *engine->windows_proc_table(), - Size(content_size.width, content_size.height), min_size, max_size, + Size(content_size.width, content_size.height), smallest, biggest, window_style, extended_window_style, nullptr); return {{CW_USEDEFAULT, CW_USEDEFAULT}, window_size ? *window_size : Size{CW_USEDEFAULT, CW_USEDEFAULT}}; @@ -328,7 +328,7 @@ std::unique_ptr FlutterHostWindow::createRegularWindow( ShowWindow(hwnd, SW_SHOWNORMAL); return std::unique_ptr(new FlutterHostWindow( window_manager, engine, WindowArchetype::kRegular, - std::move(view_controller), BoxConstraints(min_size, max_size), hwnd)); + std::move(view_controller), BoxConstraints(smallest, biggest), hwnd)); } FlutterHostWindow::FlutterHostWindow( @@ -429,21 +429,21 @@ LRESULT FlutterHostWindow::HandleMessage(HWND hwnd, static_cast(dpi) / USER_DEFAULT_SCREEN_DPI; MINMAXINFO* info = reinterpret_cast(lparam); - if (box_constraints_.min_size()) { + if (box_constraints_.smallest()) { Size const min_physical_size = ClampToVirtualScreen( - Size(box_constraints_.min_size()->width() * scale_factor + + Size(box_constraints_.smallest()->width() * scale_factor + non_client_width, - box_constraints_.min_size()->height() * scale_factor + + box_constraints_.smallest()->height() * scale_factor + non_client_height)); info->ptMinTrackSize.x = min_physical_size.width(); info->ptMinTrackSize.y = min_physical_size.height(); } - if (box_constraints_.max_size()) { + if (box_constraints_.biggest()) { Size const max_physical_size = ClampToVirtualScreen( - Size(box_constraints_.max_size()->width() * scale_factor + + Size(box_constraints_.biggest()->width() * scale_factor + non_client_width, - box_constraints_.max_size()->height() * scale_factor + + box_constraints_.biggest()->height() * scale_factor + non_client_height)); info->ptMaxTrackSize.x = max_physical_size.width(); @@ -492,20 +492,20 @@ void FlutterHostWindow::SetContentSize(const FlutterWindowSizing& size) { WINDOWINFO window_info = {.cbSize = sizeof(WINDOWINFO)}; GetWindowInfo(window_handle_, &window_info); - std::optional min_size, max_size; + std::optional smallest, biggest; if (size.has_constraints) { - min_size = Size(size.min_width, size.min_height); + smallest = Size(size.min_width, size.min_height); if (size.max_width > 0 && size.max_height > 0) { - max_size = Size(size.max_width, size.max_height); + biggest = Size(size.max_width, size.max_height); } } - box_constraints_ = BoxConstraints(min_size, max_size); + box_constraints_ = BoxConstraints(smallest, biggest); if (size.has_size) { std::optional const window_size = GetWindowSizeForClientSize( *engine_->windows_proc_table(), Size(size.width, size.height), - box_constraints_.min_size(), box_constraints_.max_size(), + box_constraints_.smallest(), box_constraints_.biggest(), window_info.dwStyle, window_info.dwExStyle, nullptr); if (window_size) { From ddb942a7ed50f630b6189f6211188898967b3541 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Wed, 18 Jun 2025 14:05:51 -0400 Subject: [PATCH 26/44] rename: FlutterHostWindow -> HostWindow --- .../flutter/shell/platform/windows/BUILD.gn | 4 +- .../platform/windows/flutter_windows_engine.h | 4 +- ...{flutter_host_window.cc => host_window.cc} | 41 +++++++++---------- .../{flutter_host_window.h => host_window.h} | 35 ++++++++-------- .../shell/platform/windows/window_manager.cc | 13 +++--- .../shell/platform/windows/window_manager.h | 8 ++-- 6 files changed, 51 insertions(+), 54 deletions(-) rename engine/src/flutter/shell/platform/windows/{flutter_host_window.cc => host_window.cc} (94%) rename engine/src/flutter/shell/platform/windows/{flutter_host_window.h => host_window.h} (75%) diff --git a/engine/src/flutter/shell/platform/windows/BUILD.gn b/engine/src/flutter/shell/platform/windows/BUILD.gn index 64ae49d7b5d3d..7b31e8570d1f6 100644 --- a/engine/src/flutter/shell/platform/windows/BUILD.gn +++ b/engine/src/flutter/shell/platform/windows/BUILD.gn @@ -73,8 +73,6 @@ source_set("flutter_windows_source") { "external_texture_d3d.h", "external_texture_pixelbuffer.cc", "external_texture_pixelbuffer.h", - "flutter_host_window.cc", - "flutter_host_window.h", "flutter_key_map.g.cc", "flutter_platform_node_delegate_windows.cc", "flutter_platform_node_delegate_windows.h", @@ -91,6 +89,8 @@ source_set("flutter_windows_source") { "flutter_windows_view.h", "flutter_windows_view_controller.cc", "flutter_windows_view_controller.h", + "host_window.cc", + "host_window.h", "keyboard_handler_base.h", "keyboard_key_channel_handler.cc", "keyboard_key_channel_handler.h", diff --git a/engine/src/flutter/shell/platform/windows/flutter_windows_engine.h b/engine/src/flutter/shell/platform/windows/flutter_windows_engine.h index ff207dac9b42a..ed95016f660a1 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_windows_engine.h +++ b/engine/src/flutter/shell/platform/windows/flutter_windows_engine.h @@ -30,9 +30,9 @@ #include "flutter/shell/platform/windows/egl/manager.h" #include "flutter/shell/platform/windows/egl/proc_table.h" #include "flutter/shell/platform/windows/flutter_desktop_messenger.h" -#include "flutter/shell/platform/windows/flutter_host_window.h" #include "flutter/shell/platform/windows/flutter_project_bundle.h" #include "flutter/shell/platform/windows/flutter_windows_texture_registrar.h" +#include "flutter/shell/platform/windows/host_window.h" #include "flutter/shell/platform/windows/keyboard_handler_base.h" #include "flutter/shell/platform/windows/keyboard_key_embedder_handler.h" #include "flutter/shell/platform/windows/platform_handler.h" @@ -460,7 +460,7 @@ class FlutterWindowsEngine { // Handlers for keyboard events from Windows. std::unique_ptr keyboard_key_handler_; - // The manager that manages the lifecycle of |FlutterHostWindow|s, native + // The manager that manages the lifecycle of |HostWindow|s, native // Win32 windows hosting a Flutter view in their client area. std::unique_ptr window_manager_; diff --git a/engine/src/flutter/shell/platform/windows/flutter_host_window.cc b/engine/src/flutter/shell/platform/windows/host_window.cc similarity index 94% rename from engine/src/flutter/shell/platform/windows/flutter_host_window.cc rename to engine/src/flutter/shell/platform/windows/host_window.cc index a07b030ed1d99..ec5f236dcc17f 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_host_window.cc +++ b/engine/src/flutter/shell/platform/windows/host_window.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "flutter/shell/platform/windows/flutter_host_window.h" +#include "flutter/shell/platform/windows/host_window.h" #include @@ -214,7 +214,7 @@ void SetChildContent(HWND content, HWND window) { namespace flutter { -std::unique_ptr FlutterHostWindow::createRegularWindow( +std::unique_ptr HostWindow::createRegularWindow( WindowManager* window_manager, FlutterWindowsEngine* engine, const FlutterWindowSizing& content_size) { @@ -273,7 +273,7 @@ std::unique_ptr FlutterHostWindow::createRegularWindow( WNDCLASSEX window_class = {}; window_class.cbSize = sizeof(WNDCLASSEX); window_class.style = CS_HREDRAW | CS_VREDRAW; - window_class.lpfnWndProc = FlutterHostWindow::WndProc; + window_class.lpfnWndProc = HostWindow::WndProc; window_class.hInstance = GetModuleHandle(nullptr); window_class.hIcon = LoadIcon(window_class.hInstance, MAKEINTRESOURCE(idi_app_icon)); @@ -326,12 +326,12 @@ std::unique_ptr FlutterHostWindow::createRegularWindow( // multiple next frame callbacks. If multiple windows are created, only the // last one will be shown. ShowWindow(hwnd, SW_SHOWNORMAL); - return std::unique_ptr(new FlutterHostWindow( + return std::unique_ptr(new HostWindow( window_manager, engine, WindowArchetype::kRegular, std::move(view_controller), BoxConstraints(smallest, biggest), hwnd)); } -FlutterHostWindow::FlutterHostWindow( +HostWindow::HostWindow( WindowManager* window_manager, FlutterWindowsEngine* engine, WindowArchetype archetype, @@ -347,7 +347,7 @@ FlutterHostWindow::FlutterHostWindow( SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast(this)); } -FlutterHostWindow::~FlutterHostWindow() { +HostWindow::~HostWindow() { if (view_controller_) { // Unregister the window class. Fail silently if other windows are still // using the class, as only the last window can successfully unregister it. @@ -358,43 +358,42 @@ FlutterHostWindow::~FlutterHostWindow() { } } -FlutterHostWindow* FlutterHostWindow::GetThisFromHandle(HWND hwnd) { - return reinterpret_cast( - GetWindowLongPtr(hwnd, GWLP_USERDATA)); +HostWindow* HostWindow::GetThisFromHandle(HWND hwnd) { + return reinterpret_cast(GetWindowLongPtr(hwnd, GWLP_USERDATA)); } -HWND FlutterHostWindow::GetWindowHandle() const { +HWND HostWindow::GetWindowHandle() const { return window_handle_; } -void FlutterHostWindow::FocusViewOf(FlutterHostWindow* window) { +void HostWindow::FocusViewOf(HostWindow* window) { auto child_content = window->view_controller_->view()->GetWindowHandle(); if (window != nullptr && child_content != nullptr) { SetFocus(child_content); } }; -LRESULT FlutterHostWindow::WndProc(HWND hwnd, - UINT message, - WPARAM wparam, - LPARAM lparam) { +LRESULT HostWindow::WndProc(HWND hwnd, + UINT message, + WPARAM wparam, + LPARAM lparam) { if (message == WM_NCCREATE) { auto* const create_struct = reinterpret_cast(lparam); auto* const windows_proc_table = static_cast(create_struct->lpCreateParams); windows_proc_table->EnableNonClientDpiScaling(hwnd); EnableTransparentWindowBackground(hwnd, *windows_proc_table); - } else if (FlutterHostWindow* const window = GetThisFromHandle(hwnd)) { + } else if (HostWindow* const window = GetThisFromHandle(hwnd)) { return window->HandleMessage(hwnd, message, wparam, lparam); } return DefWindowProc(hwnd, message, wparam, lparam); } -LRESULT FlutterHostWindow::HandleMessage(HWND hwnd, - UINT message, - WPARAM wparam, - LPARAM lparam) { +LRESULT HostWindow::HandleMessage(HWND hwnd, + UINT message, + WPARAM wparam, + LPARAM lparam) { auto result = engine_->window_proc_delegate_manager()->OnTopLevelWindowProc( window_handle_, message, wparam, lparam); if (result) { @@ -488,7 +487,7 @@ LRESULT FlutterHostWindow::HandleMessage(HWND hwnd, return DefWindowProc(hwnd, message, wparam, lparam); } -void FlutterHostWindow::SetContentSize(const FlutterWindowSizing& size) { +void HostWindow::SetContentSize(const FlutterWindowSizing& size) { WINDOWINFO window_info = {.cbSize = sizeof(WINDOWINFO)}; GetWindowInfo(window_handle_, &window_info); diff --git a/engine/src/flutter/shell/platform/windows/flutter_host_window.h b/engine/src/flutter/shell/platform/windows/host_window.h similarity index 75% rename from engine/src/flutter/shell/platform/windows/flutter_host_window.h rename to engine/src/flutter/shell/platform/windows/host_window.h index 0e4b643ca8bcd..479226590ed47 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_host_window.h +++ b/engine/src/flutter/shell/platform/windows/host_window.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_FLUTTER_HOST_WINDOW_H_ -#define FLUTTER_SHELL_PLATFORM_WINDOWS_FLUTTER_HOST_WINDOW_H_ +#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_HOST_WINDOW_H_ +#define FLUTTER_SHELL_PLATFORM_WINDOWS_HOST_WINDOW_H_ #include #include @@ -21,23 +21,23 @@ class FlutterWindowsView; class FlutterWindowsViewController; // A Win32 window that hosts a |FlutterWindow| in its client area. -class FlutterHostWindow { +class HostWindow { public: - virtual ~FlutterHostWindow(); + virtual ~HostWindow(); // Creates a native Win32 window with a child view confined to its client // area. |controller| is a pointer to the controller that manages the - // |FlutterHostWindow|. |engine| is a pointer to the engine thaat manages + // |HostWindow|. |engine| is a pointer to the engine thaat manages // the controller On success, a valid window handle can be retrieved - // via |FlutterHostWindow::GetWindowHandle|. |nullptr| will be returned + // via |HostWindow::GetWindowHandle|. |nullptr| will be returned // on failure. - static std::unique_ptr createRegularWindow( + static std::unique_ptr createRegularWindow( WindowManager* controller, FlutterWindowsEngine* engine, const FlutterWindowSizing& content_size); // Returns the instance pointer for |hwnd| or nullptr if invalid. - static FlutterHostWindow* GetThisFromHandle(HWND hwnd); + static HostWindow* GetThisFromHandle(HWND hwnd); // Returns the backing window handle, or nullptr if the native window is not // created or has already been destroyed. @@ -50,16 +50,15 @@ class FlutterHostWindow { private: friend WindowManager; - FlutterHostWindow( - WindowManager* controller, - FlutterWindowsEngine* engine, - WindowArchetype archetype, - std::unique_ptr view_controller, - const BoxConstraints& constraints, - HWND hwnd); + HostWindow(WindowManager* controller, + FlutterWindowsEngine* engine, + WindowArchetype archetype, + std::unique_ptr view_controller, + const BoxConstraints& constraints, + HWND hwnd); // Sets the focus to the child view window of |window|. - static void FocusViewOf(FlutterHostWindow* window); + static void FocusViewOf(HostWindow* window); // OS callback called by message pump. Handles the WM_NCCREATE message which // is passed when the non-client area is being created and enables automatic @@ -92,9 +91,9 @@ class FlutterHostWindow { // The constraints on the window's client area. BoxConstraints box_constraints_; - FML_DISALLOW_COPY_AND_ASSIGN(FlutterHostWindow); + FML_DISALLOW_COPY_AND_ASSIGN(HostWindow); }; } // namespace flutter -#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_FLUTTER_HOST_WINDOW_H_ +#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_HOST_WINDOW_H_ diff --git a/engine/src/flutter/shell/platform/windows/window_manager.cc b/engine/src/flutter/shell/platform/windows/window_manager.cc index a4a8e3eaf93ab..cfd631f257198 100644 --- a/engine/src/flutter/shell/platform/windows/window_manager.cc +++ b/engine/src/flutter/shell/platform/windows/window_manager.cc @@ -10,13 +10,13 @@ #include "embedder.h" #include "flutter/shell/platform/common/windowing.h" -#include "flutter/shell/platform/windows/flutter_host_window.h" #include "flutter/shell/platform/windows/flutter_windows_engine.h" #include "flutter/shell/platform/windows/flutter_windows_view_controller.h" +#include "flutter/shell/platform/windows/host_window.h" #include "fml/logging.h" #include "shell/platform/windows/client_wrapper/include/flutter/flutter_view.h" -#include "shell/platform/windows/flutter_host_window.h" #include "shell/platform/windows/flutter_windows_view.h" +#include "shell/platform/windows/host_window.h" namespace flutter { @@ -40,8 +40,8 @@ bool WindowManager::HasTopLevelWindows() const { FlutterViewId WindowManager::CreateRegularWindow( const WindowCreationRequest* request) { - auto window = FlutterHostWindow::createRegularWindow(this, engine_, - request->content_size); + auto window = + HostWindow::createRegularWindow(this, engine_, request->content_size); if (!window || !window->GetWindowHandle()) { FML_LOG(ERROR) << "Failed to create host window"; return 0; @@ -61,7 +61,7 @@ void WindowManager::OnEngineShutdown() { } for (auto hwnd : active_handles) { // This will destroy the window, which will in turn remove the - // FlutterHostWindow from map when handling WM_NCDESTROY inside + // HostWindow from map when handling WM_NCDESTROY inside // HandleMessage. DestroyWindow(hwnd); } @@ -165,8 +165,7 @@ FlutterWindowSize InternalFlutterWindows_WindowManager_GetWindowContentSize( void InternalFlutterWindows_WindowManager_SetWindowContentSize( HWND hwnd, const flutter::FlutterWindowSizing* size) { - flutter::FlutterHostWindow* window = - flutter::FlutterHostWindow::GetThisFromHandle(hwnd); + flutter::HostWindow* window = flutter::HostWindow::GetThisFromHandle(hwnd); if (window) { window->SetContentSize(*size); } diff --git a/engine/src/flutter/shell/platform/windows/window_manager.h b/engine/src/flutter/shell/platform/windows/window_manager.h index 64eb16f22f3ce..c8842e5a90264 100644 --- a/engine/src/flutter/shell/platform/windows/window_manager.h +++ b/engine/src/flutter/shell/platform/windows/window_manager.h @@ -19,7 +19,7 @@ namespace flutter { class FlutterWindowsEngine; -class FlutterHostWindow; +class HostWindow; struct WindowingInitRequest; struct WindowsMessage { @@ -51,7 +51,7 @@ struct WindowCreationRequest { FlutterWindowSizing content_size; }; -// A manager class for managing |FlutterHostWindow| instances. +// A manager class for managing |HostWindow| instances. // A unique instance of this class is owned by |FlutterWindowsEngine|. class WindowManager { public: @@ -64,7 +64,7 @@ class WindowManager { FlutterViewId CreateRegularWindow(const WindowCreationRequest* request); - // Message handler called by |FlutterHostWindow::WndProc| to process window + // Message handler called by |HostWindow::WndProc| to process window // messages before delegating them to the host window. This allows the // controller to process messages that affect the state of other host windows. std::optional HandleMessage(HWND hwnd, @@ -91,7 +91,7 @@ class WindowManager { // A map of active windows. Used to destroy remaining windows on engine // shutdown. - std::unordered_map> active_windows_; + std::unordered_map> active_windows_; FML_DISALLOW_COPY_AND_ASSIGN(WindowManager); }; From 6b32581fa8a63dec688af6ba86943e8b7d8e83cd Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Wed, 18 Jun 2025 14:12:23 -0400 Subject: [PATCH 27/44] rename: createRegularWindow -> CreateRegularWindow --- engine/src/flutter/shell/platform/windows/host_window.cc | 2 +- engine/src/flutter/shell/platform/windows/host_window.h | 2 +- engine/src/flutter/shell/platform/windows/window_manager.cc | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/engine/src/flutter/shell/platform/windows/host_window.cc b/engine/src/flutter/shell/platform/windows/host_window.cc index ec5f236dcc17f..f895e025209a1 100644 --- a/engine/src/flutter/shell/platform/windows/host_window.cc +++ b/engine/src/flutter/shell/platform/windows/host_window.cc @@ -214,7 +214,7 @@ void SetChildContent(HWND content, HWND window) { namespace flutter { -std::unique_ptr HostWindow::createRegularWindow( +std::unique_ptr HostWindow::CreateRegularWindow( WindowManager* window_manager, FlutterWindowsEngine* engine, const FlutterWindowSizing& content_size) { diff --git a/engine/src/flutter/shell/platform/windows/host_window.h b/engine/src/flutter/shell/platform/windows/host_window.h index 479226590ed47..9f045fd1fbdf7 100644 --- a/engine/src/flutter/shell/platform/windows/host_window.h +++ b/engine/src/flutter/shell/platform/windows/host_window.h @@ -31,7 +31,7 @@ class HostWindow { // the controller On success, a valid window handle can be retrieved // via |HostWindow::GetWindowHandle|. |nullptr| will be returned // on failure. - static std::unique_ptr createRegularWindow( + static std::unique_ptr CreateRegularWindow( WindowManager* controller, FlutterWindowsEngine* engine, const FlutterWindowSizing& content_size); diff --git a/engine/src/flutter/shell/platform/windows/window_manager.cc b/engine/src/flutter/shell/platform/windows/window_manager.cc index cfd631f257198..4b03ddbbc2ed3 100644 --- a/engine/src/flutter/shell/platform/windows/window_manager.cc +++ b/engine/src/flutter/shell/platform/windows/window_manager.cc @@ -41,7 +41,7 @@ bool WindowManager::HasTopLevelWindows() const { FlutterViewId WindowManager::CreateRegularWindow( const WindowCreationRequest* request) { auto window = - HostWindow::createRegularWindow(this, engine_, request->content_size); + HostWindow::CreateRegularWindow(this, engine_, request->content_size); if (!window || !window->GetWindowHandle()) { FML_LOG(ERROR) << "Failed to create host window"; return 0; From 7b79835c5b22b13152d0f715e2835cb38d4f22e0 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Wed, 18 Jun 2025 14:16:29 -0400 Subject: [PATCH 28/44] rename: proc_table -> win32 --- .../shell/platform/windows/host_window.cc | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/engine/src/flutter/shell/platform/windows/host_window.cc b/engine/src/flutter/shell/platform/windows/host_window.cc index f895e025209a1..43f79ec65f2d0 100644 --- a/engine/src/flutter/shell/platform/windows/host_window.cc +++ b/engine/src/flutter/shell/platform/windows/host_window.cc @@ -25,9 +25,8 @@ flutter::Size ClampToVirtualScreen(flutter::Size size) { std::clamp(size.height(), 0.0, virtual_screen_height)); } -void EnableTransparentWindowBackground( - HWND hwnd, - flutter::WindowsProcTable const& proc_table) { +void EnableTransparentWindowBackground(HWND hwnd, + flutter::WindowsProcTable const& win32) { enum ACCENT_STATE { ACCENT_DISABLED = 0 }; struct ACCENT_POLICY { @@ -44,15 +43,15 @@ void EnableTransparentWindowBackground( flutter::WindowsProcTable::WINDOWCOMPOSITIONATTRIB::WCA_ACCENT_POLICY, .pvData = &accent, .cbData = sizeof(accent)}; - proc_table.SetWindowCompositionAttribute(hwnd, &data); + win32.SetWindowCompositionAttribute(hwnd, &data); // Extend the frame into the client area and set the window's system // backdrop type for visual effects. MARGINS const margins = {-1}; - proc_table.DwmExtendFrameIntoClientArea(hwnd, &margins); + win32.DwmExtendFrameIntoClientArea(hwnd, &margins); INT effect_value = 1; - proc_table.DwmSetWindowAttribute(hwnd, DWMWA_SYSTEMBACKDROP_TYPE, - &effect_value, sizeof(BOOL)); + win32.DwmSetWindowAttribute(hwnd, DWMWA_SYSTEMBACKDROP_TYPE, &effect_value, + sizeof(BOOL)); } // Retrieves the calling thread's last-error code message as a string, @@ -97,7 +96,7 @@ std::string GetLastErrorAsString() { // resulting size includes window borders, non-client areas, and drop shadows. // On error, returns std::nullopt and logs an error message. std::optional GetWindowSizeForClientSize( - flutter::WindowsProcTable const& proc_table, + flutter::WindowsProcTable const& win32, flutter::Size const& client_size, std::optional smallest, std::optional biggest, @@ -111,8 +110,8 @@ std::optional GetWindowSizeForClientSize( .right = static_cast(client_size.width() * scale_factor), .bottom = static_cast(client_size.height() * scale_factor)}; - if (!proc_table.AdjustWindowRectExForDpi(&rect, window_style, FALSE, - extended_window_style, dpi)) { + if (!win32.AdjustWindowRectExForDpi(&rect, window_style, FALSE, + extended_window_style, dpi)) { FML_LOG(ERROR) << "Failed to run AdjustWindowRectExForDpi: " << GetLastErrorAsString(); return std::nullopt; From 8c3a89a8085fbe0307b47a58587837686af2bc73 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Wed, 18 Jun 2025 14:20:08 -0400 Subject: [PATCH 29/44] refactor: do not send windows metrics event twice --- engine/src/flutter/shell/platform/windows/host_window.cc | 2 -- 1 file changed, 2 deletions(-) diff --git a/engine/src/flutter/shell/platform/windows/host_window.cc b/engine/src/flutter/shell/platform/windows/host_window.cc index 43f79ec65f2d0..46bdce972635f 100644 --- a/engine/src/flutter/shell/platform/windows/host_window.cc +++ b/engine/src/flutter/shell/platform/windows/host_window.cc @@ -259,8 +259,6 @@ std::unique_ptr HostWindow::CreateRegularWindow( std::unique_ptr view_controller = std::make_unique(nullptr, std::move(view)); FML_CHECK(engine->running()); - // Must happen after engine is running. - view_controller->view()->SendInitialBounds(); // The Windows embedder listens to accessibility updates using the // view's HWND. The embedder's accessibility features may be stale if // the app was in headless mode. From 1600f14bb9514ec0a6b04d6810a19fbbecda5fe3 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Wed, 18 Jun 2025 14:22:30 -0400 Subject: [PATCH 30/44] rename: FlutterWindowSizing -> WindowSizing --- engine/src/flutter/shell/platform/windows/host_window.cc | 4 ++-- engine/src/flutter/shell/platform/windows/host_window.h | 4 ++-- engine/src/flutter/shell/platform/windows/window_manager.cc | 2 +- engine/src/flutter/shell/platform/windows/window_manager.h | 6 +++--- .../shell/platform/windows/window_manager_unittests.cc | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/engine/src/flutter/shell/platform/windows/host_window.cc b/engine/src/flutter/shell/platform/windows/host_window.cc index 46bdce972635f..da5228ad8af7a 100644 --- a/engine/src/flutter/shell/platform/windows/host_window.cc +++ b/engine/src/flutter/shell/platform/windows/host_window.cc @@ -216,7 +216,7 @@ namespace flutter { std::unique_ptr HostWindow::CreateRegularWindow( WindowManager* window_manager, FlutterWindowsEngine* engine, - const FlutterWindowSizing& content_size) { + const WindowSizing& content_size) { DWORD window_style = WS_OVERLAPPEDWINDOW; DWORD extended_window_style = 0; std::optional smallest = std::nullopt; @@ -484,7 +484,7 @@ LRESULT HostWindow::HandleMessage(HWND hwnd, return DefWindowProc(hwnd, message, wparam, lparam); } -void HostWindow::SetContentSize(const FlutterWindowSizing& size) { +void HostWindow::SetContentSize(const WindowSizing& size) { WINDOWINFO window_info = {.cbSize = sizeof(WINDOWINFO)}; GetWindowInfo(window_handle_, &window_info); diff --git a/engine/src/flutter/shell/platform/windows/host_window.h b/engine/src/flutter/shell/platform/windows/host_window.h index 9f045fd1fbdf7..c357d93ebab5c 100644 --- a/engine/src/flutter/shell/platform/windows/host_window.h +++ b/engine/src/flutter/shell/platform/windows/host_window.h @@ -34,7 +34,7 @@ class HostWindow { static std::unique_ptr CreateRegularWindow( WindowManager* controller, FlutterWindowsEngine* engine, - const FlutterWindowSizing& content_size); + const WindowSizing& content_size); // Returns the instance pointer for |hwnd| or nullptr if invalid. static HostWindow* GetThisFromHandle(HWND hwnd); @@ -45,7 +45,7 @@ class HostWindow { // Resizes the window to accommodate a client area of the given // |size|. - void SetContentSize(const FlutterWindowSizing& size); + void SetContentSize(const WindowSizing& size); private: friend WindowManager; diff --git a/engine/src/flutter/shell/platform/windows/window_manager.cc b/engine/src/flutter/shell/platform/windows/window_manager.cc index 4b03ddbbc2ed3..03e6bfffad3af 100644 --- a/engine/src/flutter/shell/platform/windows/window_manager.cc +++ b/engine/src/flutter/shell/platform/windows/window_manager.cc @@ -164,7 +164,7 @@ FlutterWindowSize InternalFlutterWindows_WindowManager_GetWindowContentSize( void InternalFlutterWindows_WindowManager_SetWindowContentSize( HWND hwnd, - const flutter::FlutterWindowSizing* size) { + const flutter::WindowSizing* size) { flutter::HostWindow* window = flutter::HostWindow::GetThisFromHandle(hwnd); if (window) { window->SetContentSize(*size); diff --git a/engine/src/flutter/shell/platform/windows/window_manager.h b/engine/src/flutter/shell/platform/windows/window_manager.h index c8842e5a90264..1283c3ad6c2ab 100644 --- a/engine/src/flutter/shell/platform/windows/window_manager.h +++ b/engine/src/flutter/shell/platform/windows/window_manager.h @@ -32,7 +32,7 @@ struct WindowsMessage { bool handled; }; -struct FlutterWindowSizing { +struct WindowSizing { bool has_size; double width; double height; @@ -48,7 +48,7 @@ struct WindowingInitRequest { }; struct WindowCreationRequest { - FlutterWindowSizing content_size; + WindowSizing content_size; }; // A manager class for managing |HostWindow| instances. @@ -132,7 +132,7 @@ FlutterWindowSize InternalFlutterWindows_WindowManager_GetWindowContentSize( FLUTTER_EXPORT void InternalFlutterWindows_WindowManager_SetWindowContentSize( HWND hwnd, - const flutter::FlutterWindowSizing* size); + const flutter::WindowSizing* size); } #endif // FLUTTER_SHELL_PLATFORM_WINDOWS_WINDOW_MANAGER_H_ diff --git a/engine/src/flutter/shell/platform/windows/window_manager_unittests.cc b/engine/src/flutter/shell/platform/windows/window_manager_unittests.cc index a8728a9ed3145..7021f88835c96 100644 --- a/engine/src/flutter/shell/platform/windows/window_manager_unittests.cc +++ b/engine/src/flutter/shell/platform/windows/window_manager_unittests.cc @@ -141,7 +141,7 @@ TEST_F(WindowManagerTest, SetWindowSize) { InternalFlutterWindows_WindowManager_GetTopLevelWindowHandle(engine_id(), view_id); - FlutterWindowSizing requestedSize{ + WindowSizing requestedSize{ .has_size = true, .width = 640, .height = 480, From 0d7f90a51d661080009157ce1d91651664840f71 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Fri, 20 Jun 2025 14:07:26 -0400 Subject: [PATCH 31/44] refactor: make the BoxConstraints biggest/smallest accessors non-optional --- .../flutter/shell/platform/common/geometry.h | 4 +-- .../shell/platform/windows/host_window.cc | 34 ++++++++----------- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/engine/src/flutter/shell/platform/common/geometry.h b/engine/src/flutter/shell/platform/common/geometry.h index 9ca17c7852a8c..19d61dd947987 100644 --- a/engine/src/flutter/shell/platform/common/geometry.h +++ b/engine/src/flutter/shell/platform/common/geometry.h @@ -93,8 +93,8 @@ class BoxConstraints { biggest.value_or(Size(std::numeric_limits::infinity(), std::numeric_limits::infinity()))) {} BoxConstraints(const BoxConstraints& other) = default; - std::optional biggest() const { return biggest_; } - std::optional smallest() const { return smallest_; } + Size biggest() const { return biggest_; } + Size smallest() const { return smallest_; } private: Size smallest_ = Size(0, 0); diff --git a/engine/src/flutter/shell/platform/windows/host_window.cc b/engine/src/flutter/shell/platform/windows/host_window.cc index da5228ad8af7a..62ac440b57755 100644 --- a/engine/src/flutter/shell/platform/windows/host_window.cc +++ b/engine/src/flutter/shell/platform/windows/host_window.cc @@ -425,26 +425,20 @@ LRESULT HostWindow::HandleMessage(HWND hwnd, static_cast(dpi) / USER_DEFAULT_SCREEN_DPI; MINMAXINFO* info = reinterpret_cast(lparam); - if (box_constraints_.smallest()) { - Size const min_physical_size = ClampToVirtualScreen( - Size(box_constraints_.smallest()->width() * scale_factor + - non_client_width, - box_constraints_.smallest()->height() * scale_factor + - non_client_height)); - - info->ptMinTrackSize.x = min_physical_size.width(); - info->ptMinTrackSize.y = min_physical_size.height(); - } - if (box_constraints_.biggest()) { - Size const max_physical_size = ClampToVirtualScreen( - Size(box_constraints_.biggest()->width() * scale_factor + - non_client_width, - box_constraints_.biggest()->height() * scale_factor + - non_client_height)); - - info->ptMaxTrackSize.x = max_physical_size.width(); - info->ptMaxTrackSize.y = max_physical_size.height(); - } + Size const min_physical_size = ClampToVirtualScreen(Size( + box_constraints_.smallest().width() * scale_factor + non_client_width, + box_constraints_.smallest().height() * scale_factor + + non_client_height)); + + info->ptMinTrackSize.x = min_physical_size.width(); + info->ptMinTrackSize.y = min_physical_size.height(); + Size const max_physical_size = ClampToVirtualScreen(Size( + box_constraints_.biggest().width() * scale_factor + non_client_width, + box_constraints_.biggest().height() * scale_factor + + non_client_height)); + + info->ptMaxTrackSize.x = max_physical_size.width(); + info->ptMaxTrackSize.y = max_physical_size.height(); return 0; } From caecbf949d7b044943e0575d6f523b34b9795676 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Mon, 23 Jun 2025 15:24:10 -0400 Subject: [PATCH 32/44] docstrings: fix typos on HostWindow --- engine/src/flutter/shell/platform/windows/host_window.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/src/flutter/shell/platform/windows/host_window.h b/engine/src/flutter/shell/platform/windows/host_window.h index c357d93ebab5c..8a6875e83f45a 100644 --- a/engine/src/flutter/shell/platform/windows/host_window.h +++ b/engine/src/flutter/shell/platform/windows/host_window.h @@ -27,8 +27,8 @@ class HostWindow { // Creates a native Win32 window with a child view confined to its client // area. |controller| is a pointer to the controller that manages the - // |HostWindow|. |engine| is a pointer to the engine thaat manages - // the controller On success, a valid window handle can be retrieved + // |HostWindow|. |engine| is a pointer to the engine that manages + // the controller. On success, a valid window handle can be retrieved // via |HostWindow::GetWindowHandle|. |nullptr| will be returned // on failure. static std::unique_ptr CreateRegularWindow( From 1b228be27597b5950efce532b88213b5630e2d7e Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Tue, 24 Jun 2025 09:56:25 -0400 Subject: [PATCH 33/44] docs: on GetViewFromTopLevelWindow --- .../src/flutter/shell/platform/windows/flutter_windows_engine.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/src/flutter/shell/platform/windows/flutter_windows_engine.h b/engine/src/flutter/shell/platform/windows/flutter_windows_engine.h index ed95016f660a1..40a8f87387340 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_windows_engine.h +++ b/engine/src/flutter/shell/platform/windows/flutter_windows_engine.h @@ -319,7 +319,7 @@ class FlutterWindowsEngine { WindowManager* window_manager() { return window_manager_.get(); } // Returns the root view associated with the top-level window with |hwnd| as - // the window handle. + // the window handle or nullptr if no such view could be found. FlutterWindowsView* GetViewFromTopLevelWindow(HWND hwnd) const; protected: From f626f805df9751287f1ec9e8c8f76fafed6b2c7a Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Tue, 24 Jun 2025 09:58:15 -0400 Subject: [PATCH 34/44] docs: controller -> manager --- engine/src/flutter/shell/platform/windows/window_manager.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/src/flutter/shell/platform/windows/window_manager.h b/engine/src/flutter/shell/platform/windows/window_manager.h index 1283c3ad6c2ab..172dca2edcc1f 100644 --- a/engine/src/flutter/shell/platform/windows/window_manager.h +++ b/engine/src/flutter/shell/platform/windows/window_manager.h @@ -75,7 +75,7 @@ class WindowManager { void OnEngineShutdown(); private: - // The Flutter engine that owns this controller. + // The Flutter engine that owns this manager. FlutterWindowsEngine* const engine_; // Callback that relays windows messages to the isolate. Set From 8ecdd02bf7e3872f7aa603e743fd6b28e7ad03a2 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Tue, 24 Jun 2025 09:58:38 -0400 Subject: [PATCH 35/44] docs: controller -> manager again --- engine/src/flutter/shell/platform/windows/window_manager.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/src/flutter/shell/platform/windows/window_manager.h b/engine/src/flutter/shell/platform/windows/window_manager.h index 172dca2edcc1f..2b05462b6bc1c 100644 --- a/engine/src/flutter/shell/platform/windows/window_manager.h +++ b/engine/src/flutter/shell/platform/windows/window_manager.h @@ -66,7 +66,7 @@ class WindowManager { // Message handler called by |HostWindow::WndProc| to process window // messages before delegating them to the host window. This allows the - // controller to process messages that affect the state of other host windows. + // manager to process messages that affect the state of other host windows. std::optional HandleMessage(HWND hwnd, UINT message, WPARAM wparam, From 6deff8ecf4fbfb90c6a7a1006649ab9d939d661c Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Tue, 24 Jun 2025 10:05:47 -0400 Subject: [PATCH 36/44] task: use std::function instead of C style pointer for on_message_ --- engine/src/flutter/shell/platform/windows/window_manager.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/engine/src/flutter/shell/platform/windows/window_manager.h b/engine/src/flutter/shell/platform/windows/window_manager.h index 2b05462b6bc1c..154976f124d77 100644 --- a/engine/src/flutter/shell/platform/windows/window_manager.h +++ b/engine/src/flutter/shell/platform/windows/window_manager.h @@ -6,6 +6,7 @@ #define FLUTTER_SHELL_PLATFORM_WINDOWS_WINDOW_MANAGER_H_ #include +#include #include #include #include @@ -80,7 +81,7 @@ class WindowManager { // Callback that relays windows messages to the isolate. Set // during Initialize(). - void (*on_message_)(WindowsMessage*) = nullptr; + std::function on_message_; // Isolate that runs the Dart code. Set during Initialize(). std::optional isolate_; From 88a7f26e22d0938cb44cf6296ffa2e4f70c1ef78 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Tue, 24 Jun 2025 10:09:49 -0400 Subject: [PATCH 37/44] docs: documenting Isolate::Current() --- engine/src/flutter/shell/platform/common/isolate_scope.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/engine/src/flutter/shell/platform/common/isolate_scope.h b/engine/src/flutter/shell/platform/common/isolate_scope.h index 69147d17d6b23..91b50b50598c0 100644 --- a/engine/src/flutter/shell/platform/common/isolate_scope.h +++ b/engine/src/flutter/shell/platform/common/isolate_scope.h @@ -14,6 +14,8 @@ namespace flutter { /// as argument to IsolateScope constructor to enter and exit the isolate. class Isolate { public: + /// Retrieve the current Dart Isolate. If no isolate is current, this + /// results in a crash. static Isolate Current(); ~Isolate() = default; From b60341e9ed519c32f9b439199d2b516140a04ad2 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Tue, 24 Jun 2025 10:10:56 -0400 Subject: [PATCH 38/44] task: WindowManager::CreateRegularWindow returns -1 if creation fails --- engine/src/flutter/shell/platform/windows/window_manager.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/src/flutter/shell/platform/windows/window_manager.cc b/engine/src/flutter/shell/platform/windows/window_manager.cc index 03e6bfffad3af..ad0b07b993b34 100644 --- a/engine/src/flutter/shell/platform/windows/window_manager.cc +++ b/engine/src/flutter/shell/platform/windows/window_manager.cc @@ -44,7 +44,7 @@ FlutterViewId WindowManager::CreateRegularWindow( HostWindow::CreateRegularWindow(this, engine_, request->content_size); if (!window || !window->GetWindowHandle()) { FML_LOG(ERROR) << "Failed to create host window"; - return 0; + return -1; } FlutterViewId const view_id = window->view_controller_->view()->view_id(); active_windows_[window->GetWindowHandle()] = std::move(window); From c3efe1f57bcecd2c66c331a6508ce8ec9dc5f63a Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Tue, 24 Jun 2025 10:32:48 -0400 Subject: [PATCH 39/44] task: remove unused function --- .../shell/platform/windows/host_window.cc | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/engine/src/flutter/shell/platform/windows/host_window.cc b/engine/src/flutter/shell/platform/windows/host_window.cc index 62ac440b57755..bd3174e26ea19 100644 --- a/engine/src/flutter/shell/platform/windows/host_window.cc +++ b/engine/src/flutter/shell/platform/windows/host_window.cc @@ -150,22 +150,6 @@ bool IsClassRegistered(LPCWSTR class_name) { 0; } -// Converts std::string to std::wstring. -std::wstring StringToWstring(std::string_view str) { - if (str.empty()) { - return {}; - } - if (int buffer_size = - MultiByteToWideChar(CP_UTF8, 0, str.data(), str.size(), nullptr, 0)) { - std::wstring wide_str(buffer_size, L'\0'); - if (MultiByteToWideChar(CP_UTF8, 0, str.data(), str.size(), &wide_str[0], - buffer_size)) { - return wide_str; - } - } - return {}; -} - // Window attribute that enables dark mode window decorations. // // Redefined in case the developer's machine has a Windows SDK older than From 1fa3caaa83ca740786951dc043bc8f99be4eca21 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Tue, 24 Jun 2025 10:39:43 -0400 Subject: [PATCH 40/44] task: remove pending_messages --- .../shell/platform/windows/window_manager.cc | 14 -------------- .../shell/platform/windows/window_manager.h | 4 ---- 2 files changed, 18 deletions(-) diff --git a/engine/src/flutter/shell/platform/windows/window_manager.cc b/engine/src/flutter/shell/platform/windows/window_manager.cc index ad0b07b993b34..11ff976bc3ba6 100644 --- a/engine/src/flutter/shell/platform/windows/window_manager.cc +++ b/engine/src/flutter/shell/platform/windows/window_manager.cc @@ -25,13 +25,6 @@ WindowManager::WindowManager(FlutterWindowsEngine* engine) : engine_(engine) {} void WindowManager::Initialize(const WindowingInitRequest* request) { on_message_ = request->on_message; isolate_ = Isolate::Current(); - - // Send messages accumulated before isolate called this method. - for (WindowsMessage& message : pending_messages_) { - IsolateScope scope(*isolate_); - on_message_(&message); - } - pending_messages_.clear(); } bool WindowManager::HasTopLevelWindows() const { @@ -91,13 +84,6 @@ std::optional WindowManager::HandleMessage(HWND hwnd, // Not initialized yet. if (!isolate_) { - if (pending_messages_.size() > 1024) { - FML_LOG(ERROR) << "The pending message cache has been maxed out, " - "something must be going wrong."; - return std::nullopt; - } - - pending_messages_.push_back(message_struct); return std::nullopt; } diff --git a/engine/src/flutter/shell/platform/windows/window_manager.h b/engine/src/flutter/shell/platform/windows/window_manager.h index 154976f124d77..e0c0379a6ab4f 100644 --- a/engine/src/flutter/shell/platform/windows/window_manager.h +++ b/engine/src/flutter/shell/platform/windows/window_manager.h @@ -86,10 +86,6 @@ class WindowManager { // Isolate that runs the Dart code. Set during Initialize(). std::optional isolate_; - // Messages received before the controller is initialized from dart - // code. Buffered until Initialize() is called. - std::vector pending_messages_; - // A map of active windows. Used to destroy remaining windows on engine // shutdown. std::unordered_map> active_windows_; From 703b21ae3df7d848f004600527f2f4290489eeaa Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Wed, 25 Jun 2025 14:43:31 -0400 Subject: [PATCH 41/44] rename: WindowSizing members --- .../shell/platform/windows/host_window.cc | 28 ++++++++++--------- .../shell/platform/windows/window_manager.h | 16 +++++------ .../windows/window_manager_unittests.cc | 17 +++++------ 3 files changed, 32 insertions(+), 29 deletions(-) diff --git a/engine/src/flutter/shell/platform/windows/host_window.cc b/engine/src/flutter/shell/platform/windows/host_window.cc index bd3174e26ea19..2723bb99857f9 100644 --- a/engine/src/flutter/shell/platform/windows/host_window.cc +++ b/engine/src/flutter/shell/platform/windows/host_window.cc @@ -206,15 +206,15 @@ std::unique_ptr HostWindow::CreateRegularWindow( std::optional smallest = std::nullopt; std::optional biggest = std::nullopt; - if (content_size.has_constraints) { - smallest = Size(content_size.min_width, content_size.min_height); - if (content_size.max_width > 0 && content_size.max_height > 0) { - biggest = Size(content_size.max_width, content_size.max_height); + if (content_size.has_view_constraints) { + smallest = Size(content_size.view_min_width, content_size.view_min_height); + if (content_size.view_max_width > 0 && content_size.view_max_height > 0) { + biggest = Size(content_size.view_max_width, content_size.view_max_height); } } // TODO(knopp): What about windows sized to content? - FML_CHECK(content_size.has_size); + FML_CHECK(content_size.has_preferred_view_size); // Calculate the screen space window rectangle for the new window. // Default positioning values (CW_USEDEFAULT) are used @@ -222,8 +222,9 @@ std::unique_ptr HostWindow::CreateRegularWindow( Rect const initial_window_rect = [&]() -> Rect { std::optional const window_size = GetWindowSizeForClientSize( *engine->windows_proc_table(), - Size(content_size.width, content_size.height), smallest, biggest, - window_style, extended_window_style, nullptr); + Size(content_size.preferred_view_width, + content_size.preferred_view_height), + smallest, biggest, window_style, extended_window_style, nullptr); return {{CW_USEDEFAULT, CW_USEDEFAULT}, window_size ? *window_size : Size{CW_USEDEFAULT, CW_USEDEFAULT}}; }(); @@ -467,18 +468,19 @@ void HostWindow::SetContentSize(const WindowSizing& size) { GetWindowInfo(window_handle_, &window_info); std::optional smallest, biggest; - if (size.has_constraints) { - smallest = Size(size.min_width, size.min_height); - if (size.max_width > 0 && size.max_height > 0) { - biggest = Size(size.max_width, size.max_height); + if (size.has_view_constraints) { + smallest = Size(size.view_min_width, size.view_min_height); + if (size.view_max_width > 0 && size.view_max_height > 0) { + biggest = Size(size.view_max_width, size.view_max_height); } } box_constraints_ = BoxConstraints(smallest, biggest); - if (size.has_size) { + if (size.has_preferred_view_size) { std::optional const window_size = GetWindowSizeForClientSize( - *engine_->windows_proc_table(), Size(size.width, size.height), + *engine_->windows_proc_table(), + Size(size.preferred_view_width, size.preferred_view_height), box_constraints_.smallest(), box_constraints_.biggest(), window_info.dwStyle, window_info.dwExStyle, nullptr); diff --git a/engine/src/flutter/shell/platform/windows/window_manager.h b/engine/src/flutter/shell/platform/windows/window_manager.h index e0c0379a6ab4f..675b470c0681f 100644 --- a/engine/src/flutter/shell/platform/windows/window_manager.h +++ b/engine/src/flutter/shell/platform/windows/window_manager.h @@ -34,14 +34,14 @@ struct WindowsMessage { }; struct WindowSizing { - bool has_size; - double width; - double height; - bool has_constraints; - double min_width; - double min_height; - double max_width; - double max_height; + bool has_preferred_view_size; + double preferred_view_width; + double preferred_view_height; + bool has_view_constraints; + double view_min_width; + double view_min_height; + double view_max_width; + double view_max_height; }; struct WindowingInitRequest { diff --git a/engine/src/flutter/shell/platform/windows/window_manager_unittests.cc b/engine/src/flutter/shell/platform/windows/window_manager_unittests.cc index 7021f88835c96..02c943b8e227f 100644 --- a/engine/src/flutter/shell/platform/windows/window_manager_unittests.cc +++ b/engine/src/flutter/shell/platform/windows/window_manager_unittests.cc @@ -51,9 +51,9 @@ class WindowManagerTest : public WindowsTest { WindowCreationRequest creation_request_{ .content_size = { - .has_size = true, - .width = 800, - .height = 600, + .has_preferred_view_size = true, + .preferred_view_width = 800, + .preferred_view_height = 600, }, }; @@ -127,8 +127,9 @@ TEST_F(WindowManagerTest, GetWindowSize) { FlutterWindowSize size = InternalFlutterWindows_WindowManager_GetWindowContentSize(window_handle); - EXPECT_EQ(size.width, creation_request()->content_size.width); - EXPECT_EQ(size.height, creation_request()->content_size.height); + EXPECT_EQ(size.width, creation_request()->content_size.preferred_view_width); + EXPECT_EQ(size.height, + creation_request()->content_size.preferred_view_height); } TEST_F(WindowManagerTest, SetWindowSize) { @@ -142,9 +143,9 @@ TEST_F(WindowManagerTest, SetWindowSize) { view_id); WindowSizing requestedSize{ - .has_size = true, - .width = 640, - .height = 480, + .has_preferred_view_size = true, + .preferred_view_width = 640, + .preferred_view_height = 480, }; InternalFlutterWindows_WindowManager_SetWindowContentSize(window_handle, &requestedSize); From 21a31c42b4de16be755022c2f03fb1c01c517a99 Mon Sep 17 00:00:00 2001 From: Matej Knopp Date: Wed, 2 Jul 2025 20:51:41 +0200 Subject: [PATCH 42/44] engine: macOS feedback --- .../macos/framework/Source/FlutterEngine.mm | 17 +++++++++++++++-- .../framework/Source/FlutterEngine_Internal.h | 15 ++++++++++++--- .../macos/framework/Source/FlutterVSyncWaiter.h | 4 ++++ .../framework/Source/FlutterVSyncWaiter.mm | 15 ++++++++------- .../framework/Source/FlutterVSyncWaiterTest.mm | 5 +++++ .../framework/Source/FlutterWindowController.mm | 6 +++--- 6 files changed, 47 insertions(+), 15 deletions(-) diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm index 20abea06fe654..a9ebb3ad41477 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm @@ -493,6 +493,7 @@ @implementation FlutterEngine { BOOL _multiviewEnabled; // View identifier for the next view to be created. + // Only used when multiview is enabled. FlutterViewIdentifier _nextViewIdentifier; } @@ -906,6 +907,9 @@ - (void)deregisterViewControllerForIdentifier:(FlutterViewIdentifier)viewIdentif info.struct_size = sizeof(FlutterRemoveViewInfo); info.view_id = viewIdentifier; info.user_data = &removed; + // RemoveViewCallback is not finished synchronously, the remove_view_callback + // is called from raster thread when the engine knows for sure that the resources + // associated with the view are no longer needed. info.remove_view_callback = [](const FlutterRemoveViewResult* r) { auto removed = reinterpret_cast(r->user_data); [FlutterRunLoop.mainRunLoop performBlock:^{ @@ -931,9 +935,13 @@ - (void)deregisterViewControllerForIdentifier:(FlutterViewIdentifier)viewIdentif @"the FlutterEngine is mocked. Please subclass these classes instead."); } [_viewControllers removeObjectForKey:@(viewIdentifier)]; + + FlutterVSyncWaiter* waiter = nil; @synchronized(_vsyncWaiters) { + waiter = [_vsyncWaiters objectForKey:@(viewIdentifier)]; [_vsyncWaiters removeObjectForKey:@(viewIdentifier)]; } + [waiter invalidate]; } - (void)shutDownIfNeeded { @@ -1021,12 +1029,14 @@ - (FlutterCompositor*)createFlutterCompositor { - (void)addViewController:(FlutterViewController*)controller { if (!_multiviewEnabled) { - // FlutterEngine can only handle the implicit view for now. Adding more views - // throws an assertion. + // When multiview is disabled, the engine will only assign views to the implicit view ID. + // The implicit view ID can be reused if and only if the implicit view is unassigned. NSAssert(self.viewController == nil, @"The engine already has a view controller for the implicit view."); self.viewController = controller; } else { + // When multiview is enabled, the engine will assign views to a self-incrementing ID. + // The implicit view ID can not be reused. FlutterViewIdentifier viewIdentifier = _nextViewIdentifier++; [self registerViewController:controller forIdentifier:viewIdentifier]; } @@ -1286,6 +1296,9 @@ - (void)onVSync:(uintptr_t)baton { if (waiter != nil) { [waiter waitForVSync:baton]; } else { + // Sometimes there is a vsync request right after the last view is removed. + // It still need to be handled, otherwise the engine will stop producing frames + // even if a new view is added later. self.embedderAPI.OnVsync(_engine, baton, 0, 0); } }; diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h index 3c8baaae0d088..7e562522f6121 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h @@ -227,9 +227,18 @@ typedef NS_ENUM(NSInteger, FlutterAppExitResponse) { @property(nonatomic, readonly) FlutterWindowController* windowController; /** - * Toggles multi-view support. Called by [FlutterWindowController] before - * creating a new window. This allows registering multiple view controllers - * with the engine. + * Enables multi-view support. + * + * Called by [FlutterWindowController] before the first view is added. This + * affects the behavior when adding view controllers: + * + * - When multiview is disabled, the engine will only assign views to the + * implicit view ID. The implicit view ID can be reused if and only if the + * implicit view ID is unassigned. + * - When multiview is enabled, the engine will assign views to a + * self-incrementing ID. + * + * Calling enableMultiView when multiview is already enabled is a noop. */ - (void)enableMultiView; diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterVSyncWaiter.h b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterVSyncWaiter.h index 712a7bcfefc3f..0b0cfc4d847ed 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterVSyncWaiter.h +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterVSyncWaiter.h @@ -20,6 +20,10 @@ /// This function must be called on the main thread. - (void)waitForVSync:(uintptr_t)baton; +/// Invalidates the waiter. This must be called on the main thread +/// before the instance is deallocated. +- (void)invalidate; + @end #endif // FLUTTER_SHELL_PLATFORM_DARWIN_MACOS_FRAMEWORK_SOURCE_FLUTTERVSYNCWAITER_H_ diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterVSyncWaiter.mm b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterVSyncWaiter.mm index 20c69899d2f40..d95c4af9f61e6 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterVSyncWaiter.mm +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterVSyncWaiter.mm @@ -151,7 +151,7 @@ - (void)waitForVSync:(uintptr_t)baton { } } -- (void)dealloc { +- (void)invalidate { // It is possible that there is pending vsync request while the view for which // this waiter belongs is being destroyed. In that case trigger the vsync // immediately to avoid deadlock. @@ -160,12 +160,13 @@ - (void)dealloc { _block(now, now, _pendingBaton.value()); _pendingBaton = std::nullopt; } - // It is possible that block running on UI thread held the last reference to - // the waiter, in which case reschedule to main thread. - FlutterDisplayLink* link = _displayLink; - dispatch_async(dispatch_get_main_queue(), ^{ - [link invalidate]; - }); + + [_displayLink invalidate]; + _displayLink = nil; +} + +- (void)dealloc { + FML_DCHECK(_displayLink == nil); } @end diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterVSyncWaiterTest.mm b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterVSyncWaiterTest.mm index 4fdd063deee89..fe68abaf2e783 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterVSyncWaiterTest.mm +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterVSyncWaiterTest.mm @@ -55,6 +55,7 @@ - (void)invalidate { [displayLink tickWithTimestamp:CACurrentMediaTime() targetTimestamp:CACurrentMediaTime() + 1.0 / 60.0]; EXPECT_TRUE(displayLink.paused); + [waiter invalidate]; } static void BusyWait(CFTimeInterval duration) { @@ -111,6 +112,8 @@ static void BusyWait(CFTimeInterval duration) { EXPECT_DOUBLE_EQ(timestamp, expectedTimestamp); EXPECT_DOUBLE_EQ(targetTimestamp, expectedTimestamp + displayLink.nominalOutputRefreshPeriod); EXPECT_EQ(baton, size_t(1)); + + [waiter invalidate]; }; // First argument if the wait duration after reference vsync. @@ -204,4 +207,6 @@ static void BusyWait(CFTimeInterval duration) { EXPECT_DOUBLE_EQ(entries[3].timestamp, now + 3 * displayLink.nominalOutputRefreshPeriod); EXPECT_DOUBLE_EQ(entries[3].targetTimestamp, now + 4 * displayLink.nominalOutputRefreshPeriod); EXPECT_EQ(entries[3].baton, size_t(3)); + + [waiter invalidate]; } diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowController.mm b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowController.mm index 7780419d1095e..5416b0135e4f0 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowController.mm +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowController.mm @@ -12,10 +12,10 @@ #include "flutter/shell/platform/common/isolate_scope.h" -/// A delegate for a Flutter managed window. +// A delegate for a Flutter managed window. @interface FlutterWindowOwner : NSObject { - /// Strong reference to the window. This is the only strong reference to the - /// window. + // Strong reference to the window. This is the only strong reference to the + // window. NSWindow* _window; FlutterViewController* _flutterViewController; std::optional _isolate; From 15f12fba1bdbee2319109cf690a692cd58f6297c Mon Sep 17 00:00:00 2001 From: Matej Knopp Date: Mon, 7 Jul 2025 16:47:28 +0200 Subject: [PATCH 43/44] engine: rename ffi methods --- .../Source/FlutterWindowController.h | 32 ++++++++++--------- .../Source/FlutterWindowController.mm | 30 +++++++++-------- .../Source/FlutterWindowControllerTest.mm | 22 ++++++------- 3 files changed, 44 insertions(+), 40 deletions(-) diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowController.h b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowController.h index 0fa008984fb09..d32c226a5da9d 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowController.h +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowController.h @@ -51,49 +51,51 @@ extern "C" { // NOLINTBEGIN(google-objc-function-naming) FLUTTER_DARWIN_EXPORT -int64_t FlutterCreateRegularWindow(int64_t engine_id, const FlutterWindowCreationRequest* request); +int64_t InternalFlutter_WindowController_CreateRegularWindow( + int64_t engine_id, + const FlutterWindowCreationRequest* request); FLUTTER_DARWIN_EXPORT -void FlutterDestroyWindow(int64_t engine_id, void* window); +void InternalFlutter_Window_Destroy(int64_t engine_id, void* window); FLUTTER_DARWIN_EXPORT -void* FlutterGetWindowHandle(int64_t engine_id, FlutterViewIdentifier view_id); +void* InternalFlutter_Window_GetHandle(int64_t engine_id, FlutterViewIdentifier view_id); FLUTTER_DARWIN_EXPORT -void* FlutterGetWindowHandle(int64_t engine_id, FlutterViewIdentifier view_id); +void* InternalFlutter_Window_GetHandle(int64_t engine_id, FlutterViewIdentifier view_id); FLUTTER_DARWIN_EXPORT -FlutterWindowSize FlutterGetWindowContentSize(void* window); +FlutterWindowSize InternalFlutter_Window_GetContentSize(void* window); FLUTTER_DARWIN_EXPORT -void FlutterSetWindowContentSize(void* window, const FlutterWindowSizing* size); +void InternalFlutter_Window_SetContentSize(void* window, const FlutterWindowSizing* size); FLUTTER_DARWIN_EXPORT -void FlutterSetWindowTitle(void* window, const char* title); +void InternalFlutter_Window_SetTitle(void* window, const char* title); FLUTTER_DARWIN_EXPORT -void FlutterWindowSetMaximized(void* window, bool maximized); +void InternalFlutter_Window_SetMaximized(void* window, bool maximized); FLUTTER_DARWIN_EXPORT -bool FlutterWindowIsMaximized(void* window); +bool InternalFlutter_Window_IsMaximized(void* window); FLUTTER_DARWIN_EXPORT -void FlutterWindowMinimize(void* window); +void InternalFlutter_Window_Minimize(void* window); FLUTTER_DARWIN_EXPORT -void FlutterWindowUnminimize(void* window); +void InternalFlutter_Window_Unminimize(void* window); FLUTTER_DARWIN_EXPORT -bool FlutterWindowIsMinimized(void* window); +bool InternalFlutter_Window_IsMinimized(void* window); FLUTTER_DARWIN_EXPORT -void FlutterWindowSetFullScreen(void* window, bool fullScreen); +void InternalFlutter_Window_SetFullScreen(void* window, bool fullScreen); FLUTTER_DARWIN_EXPORT -bool FlutterWindowIsFullScreen(void* window); +bool InternalFlutter_Window_IsFullScreen(void* window); FLUTTER_DARWIN_EXPORT -void FlutterWindowActivate(void* window); +void InternalFlutter_Window_Activate(void* window); // NOLINTEND(google-objc-function-naming) } diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowController.mm b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowController.mm index 5416b0135e4f0..95aa3548ea598 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowController.mm +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowController.mm @@ -178,25 +178,27 @@ - (void)closeAllWindows { // NOLINTBEGIN(google-objc-function-naming) -int64_t FlutterCreateRegularWindow(int64_t engine_id, const FlutterWindowCreationRequest* request) { +int64_t InternalFlutter_WindowController_CreateRegularWindow( + int64_t engine_id, + const FlutterWindowCreationRequest* request) { FlutterEngine* engine = [FlutterEngine engineForIdentifier:engine_id]; [engine enableMultiView]; return [engine.windowController createRegularWindow:request]; } -void FlutterDestroyWindow(int64_t engine_id, void* window) { +void InternalFlutter_Window_Destroy(int64_t engine_id, void* window) { NSWindow* w = (__bridge NSWindow*)window; FlutterEngine* engine = [FlutterEngine engineForIdentifier:engine_id]; [engine.windowController destroyWindow:w]; } -void* FlutterGetWindowHandle(int64_t engine_id, FlutterViewIdentifier view_id) { +void* InternalFlutter_Window_GetHandle(int64_t engine_id, FlutterViewIdentifier view_id) { FlutterEngine* engine = [FlutterEngine engineForIdentifier:engine_id]; FlutterViewController* controller = [engine viewControllerForIdentifier:view_id]; return (__bridge void*)controller.view.window; } -FlutterWindowSize FlutterGetWindowContentSize(void* window) { +FlutterWindowSize InternalFlutter_Window_GetContentSize(void* window) { NSWindow* w = (__bridge NSWindow*)window; return { .width = w.frame.size.width, @@ -204,17 +206,17 @@ FlutterWindowSize FlutterGetWindowContentSize(void* window) { }; } -void FlutterSetWindowContentSize(void* window, const FlutterWindowSizing* size) { +void InternalFlutter_Window_SetContentSize(void* window, const FlutterWindowSizing* size) { NSWindow* w = (__bridge NSWindow*)window; [w flutterSetContentSize:*size]; } -void FlutterSetWindowTitle(void* window, const char* title) { +void InternalFlutter_Window_SetTitle(void* window, const char* title) { NSWindow* w = (__bridge NSWindow*)window; w.title = [NSString stringWithUTF8String:title]; } -void FlutterWindowSetMaximized(void* window, bool maximized) { +void InternalFlutter_Window_SetMaximized(void* window, bool maximized) { NSWindow* w = (__bridge NSWindow*)window; if (maximized & !w.isZoomed) { [w zoom:nil]; @@ -223,27 +225,27 @@ void FlutterWindowSetMaximized(void* window, bool maximized) { } } -bool FlutterWindowIsMaximized(void* window) { +bool InternalFlutter_Window_IsMaximized(void* window) { NSWindow* w = (__bridge NSWindow*)window; return w.isZoomed; } -void FlutterWindowMinimize(void* window) { +void InternalFlutter_Window_Minimize(void* window) { NSWindow* w = (__bridge NSWindow*)window; [w miniaturize:nil]; } -void FlutterWindowUnminimize(void* window) { +void InternalFlutter_Window_Unminimize(void* window) { NSWindow* w = (__bridge NSWindow*)window; [w deminiaturize:nil]; } -bool FlutterWindowIsMinimized(void* window) { +bool InternalFlutter_Window_IsMinimized(void* window) { NSWindow* w = (__bridge NSWindow*)window; return w.isMiniaturized; } -void FlutterWindowSetFullScreen(void* window, bool fullScreen) { +void InternalFlutter_Window_SetFullScreen(void* window, bool fullScreen) { NSWindow* w = (__bridge NSWindow*)window; bool isFullScreen = (w.styleMask & NSWindowStyleMaskFullScreen) != 0; if (fullScreen && !isFullScreen) { @@ -253,12 +255,12 @@ void FlutterWindowSetFullScreen(void* window, bool fullScreen) { } } -bool FlutterWindowIsFullScreen(void* window) { +bool InternalFlutter_Window_IsFullScreen(void* window) { NSWindow* w = (__bridge NSWindow*)window; return (w.styleMask & NSWindowStyleMaskFullScreen) != 0; } -void FlutterWindowActivate(void* window) { +void InternalFlutter_Window_Activate(void* window) { NSWindow* w = (__bridge NSWindow*)window; [NSApplication.sharedApplication activateIgnoringOtherApps:YES]; [w makeKeyAndOrderFront:nil]; diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowControllerTest.mm b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowControllerTest.mm index 0611b92f9d27d..36068f950f8c4 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowControllerTest.mm +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowControllerTest.mm @@ -67,7 +67,7 @@ void TearDown() { { IsolateScope isolate_scope(isolate()); - int64_t handle = FlutterCreateRegularWindow(engineId, &request); + int64_t handle = InternalFlutter_WindowController_CreateRegularWindow(engineId, &request); EXPECT_EQ(handle, 1); FlutterViewController* viewController = [engine viewControllerForIdentifier:handle]; @@ -109,7 +109,7 @@ void TearDown() { FML_DCHECK(isolate.has_value()); // NOLINTNEXTLINE(bugprone-unchecked-optional-access) IsolateScope isolateScope(*isolate); - int64_t handle = FlutterCreateRegularWindow(engineId, &request); + int64_t handle = InternalFlutter_WindowController_CreateRegularWindow(engineId, &request); EXPECT_EQ(handle, 1); } @@ -130,15 +130,15 @@ void TearDown() { int64_t engine_id = reinterpret_cast(engine); IsolateScope isolate_scope(isolate()); - int64_t handle = FlutterCreateRegularWindow(engine_id, &request); + int64_t handle = InternalFlutter_WindowController_CreateRegularWindow(engine_id, &request); FlutterViewController* viewController = [engine viewControllerForIdentifier:handle]; - FlutterDestroyWindow(engine_id, (__bridge void*)viewController.view.window); + InternalFlutter_Window_Destroy(engine_id, (__bridge void*)viewController.view.window); viewController = [engine viewControllerForIdentifier:handle]; EXPECT_EQ(viewController, nil); } -TEST_F(FlutterWindowControllerTest, FlutterGetWindowHandle) { +TEST_F(FlutterWindowControllerTest, InternalFlutter_Window_GetHandle) { FlutterWindowCreationRequest request{ .contentSize = {.has_size = true, .width = 800, .height = 600}, .on_close = [] {}, @@ -149,10 +149,10 @@ void TearDown() { int64_t engine_id = reinterpret_cast(engine); IsolateScope isolate_scope(isolate()); - int64_t handle = FlutterCreateRegularWindow(engine_id, &request); + int64_t handle = InternalFlutter_WindowController_CreateRegularWindow(engine_id, &request); FlutterViewController* viewController = [engine viewControllerForIdentifier:handle]; - void* window_handle = FlutterGetWindowHandle(engine_id, handle); + void* window_handle = InternalFlutter_Window_GetHandle(engine_id, handle); EXPECT_EQ(window_handle, (__bridge void*)viewController.view.window); } @@ -167,7 +167,7 @@ void TearDown() { int64_t engine_id = reinterpret_cast(engine); IsolateScope isolate_scope(isolate()); - int64_t handle = FlutterCreateRegularWindow(engine_id, &request); + int64_t handle = InternalFlutter_WindowController_CreateRegularWindow(engine_id, &request); FlutterViewController* viewController = [engine viewControllerForIdentifier:handle]; NSWindow* window = viewController.view.window; @@ -177,17 +177,17 @@ void TearDown() { EXPECT_EQ(window.miniaturized, NO); EXPECT_EQ(window.styleMask & NSWindowStyleMaskFullScreen, 0u); - FlutterWindowSetMaximized(windowHandle, true); + InternalFlutter_Window_SetMaximized(windowHandle, true); CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.5, false); EXPECT_EQ(window.zoomed, YES); - FlutterWindowSetMaximized(windowHandle, false); + InternalFlutter_Window_SetMaximized(windowHandle, false); CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.5, false); EXPECT_EQ(window.zoomed, NO); // FullScreen toggle does not seem to work when the application is not run from a bundle. - FlutterWindowMinimize(windowHandle); + InternalFlutter_Window_Minimize(windowHandle); CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.5, false); EXPECT_EQ(window.miniaturized, YES); } From 045cccc8d8fad54d42c122278de7cfc85d36279e Mon Sep 17 00:00:00 2001 From: Matej Knopp Date: Mon, 7 Jul 2025 16:47:35 +0200 Subject: [PATCH 44/44] framework: rename ffi methods --- .../flutter/lib/src/widgets/window_macos.dart | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/packages/flutter/lib/src/widgets/window_macos.dart b/packages/flutter/lib/src/widgets/window_macos.dart index c1e87f735dd86..7637b5ca48af4 100644 --- a/packages/flutter/lib/src/widgets/window_macos.dart +++ b/packages/flutter/lib/src/widgets/window_macos.dart @@ -42,7 +42,7 @@ class WindowingOwnerMacOS extends WindowingOwner { return _getWindowHandle(PlatformDispatcher.instance.engineId!, view.viewId); } - @Native Function(Int64, Int64)>(symbol: 'FlutterGetWindowHandle') + @Native Function(Int64, Int64)>(symbol: 'InternalFlutter_Window_GetHandle') external static Pointer _getWindowHandle(int engineId, int viewId); } @@ -190,44 +190,48 @@ class RegularWindowControllerMacOS extends RegularWindowController { } @Native)>( - symbol: 'FlutterCreateRegularWindow', + symbol: 'InternalFlutter_WindowController_CreateRegularWindow', ) external static int _createWindow(int engineId, Pointer<_WindowCreationRequest> request); - @Native)>(symbol: 'FlutterDestroyWindow') + @Native)>(symbol: 'InternalFlutter_Window_Destroy') external static void _destroyWindow(int engineId, Pointer handle); - @Native<_Size Function(Pointer)>(symbol: 'FlutterGetWindowContentSize') + @Native<_Size Function(Pointer)>(symbol: 'InternalFlutter_Window_GetContentSize') external static _Size _getWindowContentSize(Pointer windowHandle); - @Native, Pointer<_Sizing>)>(symbol: 'FlutterSetWindowContentSize') + @Native, Pointer<_Sizing>)>( + symbol: 'InternalFlutter_Window_SetContentSize', + ) external static void _setWindowContentSize(Pointer windowHandle, Pointer<_Sizing> size); - @Native, Pointer)>(symbol: 'FlutterSetWindowTitle') + @Native, Pointer)>( + symbol: 'InternalFlutter_Window_SetTitle', + ) external static void _setWindowTitle(Pointer windowHandle, Pointer title); - @Native, Bool)>(symbol: 'FlutterWindowSetMaximized') + @Native, Bool)>(symbol: 'InternalFlutter_Window_SetMaximized') external static void _setMaximized(Pointer windowHandle, bool maximized); - @Native)>(symbol: 'FlutterWindowIsMaximized') + @Native)>(symbol: 'InternalFlutter_Window_IsMaximized') external static bool _isMaximized(Pointer windowHandle); - @Native)>(symbol: 'FlutterWindowMinimize') + @Native)>(symbol: 'InternalFlutter_Window_Minimize') external static void _minimize(Pointer windowHandle); - @Native)>(symbol: 'FlutterWindowUnminimize') + @Native)>(symbol: 'InternalFlutter_Window_Unminimize') external static void _unminimize(Pointer windowHandle); - @Native)>(symbol: 'FlutterWindowIsMinimized') + @Native)>(symbol: 'InternalFlutter_Window_IsMinimized') external static bool _isMinimized(Pointer windowHandle); - @Native, Bool)>(symbol: 'FlutterWindowSetFullScreen') + @Native, Bool)>(symbol: 'InternalFlutter_Window_SetFullScreen') external static void _setFullscreen(Pointer windowHandle, bool fullscreen); - @Native)>(symbol: 'FlutterWindowIsFullScreen') + @Native)>(symbol: 'InternalFlutter_Window_IsFullScreen') external static bool _isFullscreen(Pointer windowHandle); - @Native)>(symbol: 'FlutterWindowActivate') + @Native)>(symbol: 'InternalFlutter_Window_Activate') external static void _activate(Pointer windowHandle); }