mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Multi-window support (engine) (#168728)
<!-- Thanks for filing a pull request! Reviewers are typically assigned within a week of filing a request. To learn more about code review, see our documentation on Tree Hygiene: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md --> *Replace this paragraph with a description of what this PR is changing or adding, and why. Consider including before/after screenshots.* *List which issues are fixed by this PR. You must list at least one issue. An issue is not required if the PR fixes something trivial like a typo.* *If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].* ## Pre-launch Checklist - [X] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [X] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [X] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [X] I signed the [CLA]. - [X] I listed at least one issue that this PR fixes in the description above. - [X] I updated/added relevant documentation (doc comments with `///`). - [X] I added new tests to check the change I am making, or this PR is [test-exempt]. - [X] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [X] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --------- Co-authored-by: Matthew Kosarek <matt.kosarek@canonical.com> Co-authored-by: Loïc Sharma <737941+loic-sharma@users.noreply.github.com>
This commit is contained in:
parent
ae3823c2e8
commit
fc286d0530
@ -435,6 +435,7 @@
|
||||
../../../flutter/shell/platform/windows/task_runner_unittests.cc
|
||||
../../../flutter/shell/platform/windows/testing
|
||||
../../../flutter/shell/platform/windows/text_input_plugin_unittest.cc
|
||||
../../../flutter/shell/platform/windows/window_manager_unittests.cc
|
||||
../../../flutter/shell/platform/windows/window_proc_delegate_manager_unittests.cc
|
||||
../../../flutter/shell/platform/windows/window_unittests.cc
|
||||
../../../flutter/shell/platform/windows/windows_lifecycle_manager_unittests.cc
|
||||
|
||||
@ -53288,6 +53288,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
|
||||
@ -53307,6 +53309,7 @@ ORIGIN: ../../../flutter/shell/platform/common/text_editing_delta.h + ../../../f
|
||||
ORIGIN: ../../../flutter/shell/platform/common/text_input_model.cc + ../../../flutter/LICENSE
|
||||
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/common/windowing.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/darwin/common/InternalFlutterSwiftCommon-Bridging-Header.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/darwin/common/SwiftTestingMain.swift + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/darwin/common/SwiftTestingRunner.swift + ../../../flutter/LICENSE
|
||||
@ -53594,6 +53597,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
|
||||
@ -54071,6 +54077,8 @@ ORIGIN: ../../../flutter/shell/platform/windows/flutter_windows_view.cc + ../../
|
||||
ORIGIN: ../../../flutter/shell/platform/windows/flutter_windows_view.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/windows/flutter_windows_view_controller.cc + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/windows/flutter_windows_view_controller.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/windows/host_window.cc + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/windows/host_window.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/windows/keyboard_handler_base.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/windows/keyboard_key_channel_handler.cc + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/windows/keyboard_key_channel_handler.h + ../../../flutter/LICENSE
|
||||
@ -54105,6 +54113,8 @@ ORIGIN: ../../../flutter/shell/platform/windows/text_input_plugin.cc + ../../../
|
||||
ORIGIN: ../../../flutter/shell/platform/windows/text_input_plugin.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/windows/window_binding_handler.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/windows/window_binding_handler_delegate.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/windows/window_manager.cc + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/windows/window_manager.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/windows/window_proc_delegate_manager.cc + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/windows/window_proc_delegate_manager.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/windows/window_state.h + ../../../flutter/LICENSE
|
||||
@ -56349,6 +56359,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
|
||||
@ -56368,6 +56380,7 @@ FILE: ../../../flutter/shell/platform/common/text_editing_delta.h
|
||||
FILE: ../../../flutter/shell/platform/common/text_input_model.cc
|
||||
FILE: ../../../flutter/shell/platform/common/text_input_model.h
|
||||
FILE: ../../../flutter/shell/platform/common/text_range.h
|
||||
FILE: ../../../flutter/shell/platform/common/windowing.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
|
||||
@ -56660,6 +56673,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
|
||||
@ -57141,6 +57157,8 @@ FILE: ../../../flutter/shell/platform/windows/flutter_windows_view.cc
|
||||
FILE: ../../../flutter/shell/platform/windows/flutter_windows_view.h
|
||||
FILE: ../../../flutter/shell/platform/windows/flutter_windows_view_controller.cc
|
||||
FILE: ../../../flutter/shell/platform/windows/flutter_windows_view_controller.h
|
||||
FILE: ../../../flutter/shell/platform/windows/host_window.cc
|
||||
FILE: ../../../flutter/shell/platform/windows/host_window.h
|
||||
FILE: ../../../flutter/shell/platform/windows/keyboard_handler_base.h
|
||||
FILE: ../../../flutter/shell/platform/windows/keyboard_key_channel_handler.cc
|
||||
FILE: ../../../flutter/shell/platform/windows/keyboard_key_channel_handler.h
|
||||
@ -57175,6 +57193,8 @@ FILE: ../../../flutter/shell/platform/windows/text_input_plugin.cc
|
||||
FILE: ../../../flutter/shell/platform/windows/text_input_plugin.h
|
||||
FILE: ../../../flutter/shell/platform/windows/window_binding_handler.h
|
||||
FILE: ../../../flutter/shell/platform/windows/window_binding_handler_delegate.h
|
||||
FILE: ../../../flutter/shell/platform/windows/window_manager.cc
|
||||
FILE: ../../../flutter/shell/platform/windows/window_manager.h
|
||||
FILE: ../../../flutter/shell/platform/windows/window_proc_delegate_manager.cc
|
||||
FILE: ../../../flutter/shell/platform/windows/window_proc_delegate_manager.h
|
||||
FILE: ../../../flutter/shell/platform/windows/window_state.h
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -6,6 +6,8 @@
|
||||
#define FLUTTER_SHELL_PLATFORM_COMMON_GEOMETRY_H_
|
||||
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
|
||||
namespace flutter {
|
||||
|
||||
@ -45,6 +47,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;
|
||||
@ -78,6 +81,27 @@ 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<Size>& smallest,
|
||||
const std::optional<Size>& biggest)
|
||||
: smallest_(smallest.value_or(Size(0, 0))),
|
||||
biggest_(
|
||||
biggest.value_or(Size(std::numeric_limits<double>::infinity(),
|
||||
std::numeric_limits<double>::infinity()))) {}
|
||||
BoxConstraints(const BoxConstraints& other) = default;
|
||||
Size biggest() const { return biggest_; }
|
||||
Size smallest() const { return smallest_; }
|
||||
|
||||
private:
|
||||
Size smallest_ = Size(0, 0);
|
||||
Size biggest_ = Size(std::numeric_limits<double>::infinity(),
|
||||
std::numeric_limits<double>::infinity());
|
||||
};
|
||||
|
||||
} // namespace flutter
|
||||
|
||||
#endif // FLUTTER_SHELL_PLATFORM_COMMON_GEOMETRY_H_
|
||||
|
||||
40
engine/src/flutter/shell/platform/common/isolate_scope.cc
Normal file
40
engine/src/flutter/shell/platform/common/isolate_scope.cc
Normal file
@ -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
|
||||
47
engine/src/flutter/shell/platform/common/isolate_scope.h
Normal file
47
engine/src/flutter/shell/platform/common/isolate_scope.h
Normal file
@ -0,0 +1,47 @@
|
||||
// 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:
|
||||
/// Retrieve the current Dart Isolate. If no isolate is current, this
|
||||
/// results in a crash.
|
||||
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_;
|
||||
|
||||
FML_DISALLOW_COPY_AND_ASSIGN(IsolateScope);
|
||||
};
|
||||
|
||||
} // namespace flutter
|
||||
|
||||
#endif // FLUTTER_SHELL_PLATFORM_COMMON_ISOLATE_SCOPE_H_
|
||||
20
engine/src/flutter/shell/platform/common/windowing.h
Normal file
20
engine/src/flutter/shell/platform/common/windowing.h
Normal file
@ -0,0 +1,20 @@
|
||||
// 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
|
||||
// in packages/flutter/lib/src/widgets/window.dart
|
||||
enum class WindowArchetype {
|
||||
// Regular top-level window.
|
||||
kRegular,
|
||||
};
|
||||
|
||||
} // namespace flutter
|
||||
|
||||
#endif // FLUTTER_SHELL_PLATFORM_COMMON_WINDOWING_H_
|
||||
@ -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",
|
||||
|
||||
@ -452,6 +452,9 @@ static void OnPlatformMessage(const FlutterPlatformMessage* message, void* user_
|
||||
// factories. Lifecycle is tied to the engine.
|
||||
FlutterPlatformViewController* _platformViewController;
|
||||
|
||||
// Used to manage Flutter windows created by the Dart application
|
||||
FlutterWindowController* _windowController;
|
||||
|
||||
// A message channel for sending user settings to the flutter engine.
|
||||
FlutterBasicMessageChannel* _settingsChannel;
|
||||
|
||||
@ -483,8 +486,19 @@ static void OnPlatformMessage(const FlutterPlatformMessage* message, void* user_
|
||||
|
||||
// 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.
|
||||
// Only used when multiview is enabled.
|
||||
FlutterViewIdentifier _nextViewIdentifier;
|
||||
}
|
||||
|
||||
@synthesize windowController = _windowController;
|
||||
|
||||
- (instancetype)initWithName:(NSString*)labelPrefix project:(FlutterDartProject*)project {
|
||||
return [self initWithName:labelPrefix project:project allowHeadlessExecution:YES];
|
||||
}
|
||||
@ -528,6 +542,8 @@ static void SetThreadPriority(FlutterThreadPriority priority) {
|
||||
[_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 +565,10 @@ static void SetThreadPriority(FlutterThreadPriority priority) {
|
||||
[[FlutterTimeConverter alloc] initWithEngine:self], _platformViewController);
|
||||
|
||||
[self setUpPlatformViewChannel];
|
||||
|
||||
_windowController = [[FlutterWindowController alloc] init];
|
||||
_windowController.engine = self;
|
||||
|
||||
[self setUpAccessibilityChannel];
|
||||
[self setUpNotificationCenterListeners];
|
||||
id<NSApplicationDelegate> appDelegate = [[NSApplication sharedApplication] delegate];
|
||||
@ -623,6 +643,16 @@ static void SetThreadPriority(FlutterThreadPriority priority) {
|
||||
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;
|
||||
@ -737,6 +767,12 @@ static void SetThreadPriority(FlutterThreadPriority priority) {
|
||||
[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);
|
||||
@ -797,10 +833,12 @@ static void SetThreadPriority(FlutterThreadPriority priority) {
|
||||
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)];
|
||||
@ -818,6 +856,32 @@ static void SetThreadPriority(FlutterThreadPriority priority) {
|
||||
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<bool*>(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 {
|
||||
@ -835,14 +899,36 @@ static void SetThreadPriority(FlutterThreadPriority priority) {
|
||||
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;
|
||||
// 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<bool*>(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.
|
||||
@ -854,9 +940,13 @@ static void SetThreadPriority(FlutterThreadPriority priority) {
|
||||
@"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 {
|
||||
@ -943,11 +1033,46 @@ static void SetThreadPriority(FlutterThreadPriority priority) {
|
||||
#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) {
|
||||
// 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];
|
||||
}
|
||||
}
|
||||
|
||||
- (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 {
|
||||
@ -1171,8 +1296,16 @@ static void SetThreadPriority(FlutterThreadPriority priority) {
|
||||
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 {
|
||||
// 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);
|
||||
}
|
||||
};
|
||||
if ([NSThread isMainThread]) {
|
||||
block();
|
||||
@ -1254,6 +1387,7 @@ static void SetThreadPriority(FlutterThreadPriority priority) {
|
||||
[FlutterMouseCursorPlugin registerWithRegistrar:[self registrarForPlugin:@"mousecursor"]
|
||||
delegate:self];
|
||||
[FlutterMenuPlugin registerWithRegistrar:[self registrarForPlugin:@"menu"]];
|
||||
|
||||
_settingsChannel =
|
||||
[FlutterBasicMessageChannel messageChannelWithName:kFlutterSettingsChannel
|
||||
binaryMessenger:self.binaryMessenger
|
||||
|
||||
@ -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,34 @@ typedef NS_ENUM(NSInteger, FlutterAppExitResponse) {
|
||||
*/
|
||||
@property(nonatomic, readonly) FlutterTextInputPlugin* textInputPlugin;
|
||||
|
||||
@property(nonatomic, readonly) FlutterWindowController* windowController;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 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 +267,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
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
#include <map>
|
||||
#include <unordered_set>
|
||||
|
||||
@interface FlutterPlatformViewController : NSViewController
|
||||
@interface FlutterPlatformViewController : NSObject
|
||||
@end
|
||||
|
||||
@interface FlutterPlatformViewController ()
|
||||
|
||||
@ -24,6 +24,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_
|
||||
|
||||
@ -118,7 +118,7 @@ static const CFTimeInterval kTimerLatencyCompensation = 0.001;
|
||||
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
|
||||
@ -154,16 +154,22 @@ static const CFTimeInterval kTimerLatencyCompensation = 0.001;
|
||||
}
|
||||
}
|
||||
|
||||
- (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.
|
||||
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.
|
||||
FlutterDisplayLink* link = _displayLink;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[link invalidate];
|
||||
});
|
||||
|
||||
[_displayLink invalidate];
|
||||
_displayLink = nil;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
FML_DCHECK(_displayLink == nil);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@ -55,6 +55,7 @@ TEST_F(FlutterVSyncWaiterTest, RequestsInitialVSync) {
|
||||
[displayLink tickWithTimestamp:CACurrentMediaTime()
|
||||
targetTimestamp:CACurrentMediaTime() + 1.0 / 60.0];
|
||||
EXPECT_TRUE(displayLink.paused);
|
||||
[waiter invalidate];
|
||||
}
|
||||
|
||||
static void BusyWait(CFTimeInterval duration) {
|
||||
@ -111,6 +112,8 @@ TEST_F(FlutterVSyncWaiterTest, FirstVSyncIsSynthesized) {
|
||||
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 @@ TEST_F(FlutterVSyncWaiterTest, VSyncWorks) {
|
||||
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];
|
||||
}
|
||||
|
||||
@ -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 @@ static void CommonInit(FlutterViewController* controller, FlutterEngine* engine)
|
||||
_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 {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -0,0 +1,100 @@
|
||||
// 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 <Cocoa/Cocoa.h>
|
||||
|
||||
#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 InternalFlutter_WindowController_CreateRegularWindow(
|
||||
int64_t engine_id,
|
||||
const FlutterWindowCreationRequest* request);
|
||||
|
||||
FLUTTER_DARWIN_EXPORT
|
||||
void InternalFlutter_Window_Destroy(int64_t engine_id, void* window);
|
||||
|
||||
FLUTTER_DARWIN_EXPORT
|
||||
void* InternalFlutter_Window_GetHandle(int64_t engine_id, FlutterViewIdentifier view_id);
|
||||
|
||||
FLUTTER_DARWIN_EXPORT
|
||||
FlutterWindowSize InternalFlutter_Window_GetContentSize(void* window);
|
||||
|
||||
FLUTTER_DARWIN_EXPORT
|
||||
void InternalFlutter_Window_SetContentSize(void* window, const FlutterWindowSizing* size);
|
||||
|
||||
FLUTTER_DARWIN_EXPORT
|
||||
void InternalFlutter_Window_SetTitle(void* window, const char* title);
|
||||
|
||||
FLUTTER_DARWIN_EXPORT
|
||||
void InternalFlutter_Window_SetMaximized(void* window, bool maximized);
|
||||
|
||||
FLUTTER_DARWIN_EXPORT
|
||||
bool InternalFlutter_Window_IsMaximized(void* window);
|
||||
|
||||
FLUTTER_DARWIN_EXPORT
|
||||
void InternalFlutter_Window_Minimize(void* window);
|
||||
|
||||
FLUTTER_DARWIN_EXPORT
|
||||
void InternalFlutter_Window_Unminimize(void* window);
|
||||
|
||||
FLUTTER_DARWIN_EXPORT
|
||||
bool InternalFlutter_Window_IsMinimized(void* window);
|
||||
|
||||
FLUTTER_DARWIN_EXPORT
|
||||
void InternalFlutter_Window_SetFullScreen(void* window, bool fullScreen);
|
||||
|
||||
FLUTTER_DARWIN_EXPORT
|
||||
bool InternalFlutter_Window_IsFullScreen(void* window);
|
||||
|
||||
FLUTTER_DARWIN_EXPORT
|
||||
void InternalFlutter_Window_Activate(void* window);
|
||||
|
||||
// NOLINTEND(google-objc-function-naming)
|
||||
}
|
||||
|
||||
#endif // FLUTTER_SHELL_PLATFORM_DARWIN_MACOS_FRAMEWORK_SOURCE_FLUTTERWINDOWCONTROLLER_H_
|
||||
@ -0,0 +1,269 @@
|
||||
// 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"
|
||||
#include <Foundation/Foundation.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"
|
||||
|
||||
// A delegate for a Flutter managed window.
|
||||
@interface FlutterWindowOwner : NSObject <NSWindowDelegate> {
|
||||
// Strong reference to the window. This is the only strong reference to the
|
||||
// window.
|
||||
NSWindow* _window;
|
||||
FlutterViewController* _flutterViewController;
|
||||
std::optional<flutter::Isolate> _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();
|
||||
}
|
||||
|
||||
// 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 () {
|
||||
NSMutableArray<FlutterWindowOwner*>* _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 InternalFlutter_WindowController_CreateRegularWindow(
|
||||
int64_t engine_id,
|
||||
const FlutterWindowCreationRequest* request) {
|
||||
FlutterEngine* engine = [FlutterEngine engineForIdentifier:engine_id];
|
||||
[engine enableMultiView];
|
||||
return [engine.windowController createRegularWindow:request];
|
||||
}
|
||||
|
||||
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* 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 InternalFlutter_Window_GetContentSize(void* window) {
|
||||
NSWindow* w = (__bridge NSWindow*)window;
|
||||
return {
|
||||
.width = w.frame.size.width,
|
||||
.height = w.frame.size.height,
|
||||
};
|
||||
}
|
||||
|
||||
void InternalFlutter_Window_SetContentSize(void* window, const FlutterWindowSizing* size) {
|
||||
NSWindow* w = (__bridge NSWindow*)window;
|
||||
[w flutterSetContentSize:*size];
|
||||
}
|
||||
|
||||
void InternalFlutter_Window_SetTitle(void* window, const char* title) {
|
||||
NSWindow* w = (__bridge NSWindow*)window;
|
||||
w.title = [NSString stringWithUTF8String:title];
|
||||
}
|
||||
|
||||
void InternalFlutter_Window_SetMaximized(void* window, bool maximized) {
|
||||
NSWindow* w = (__bridge NSWindow*)window;
|
||||
if (maximized & !w.isZoomed) {
|
||||
[w zoom:nil];
|
||||
} else if (!maximized && w.isZoomed) {
|
||||
[w zoom:nil];
|
||||
}
|
||||
}
|
||||
|
||||
bool InternalFlutter_Window_IsMaximized(void* window) {
|
||||
NSWindow* w = (__bridge NSWindow*)window;
|
||||
return w.isZoomed;
|
||||
}
|
||||
|
||||
void InternalFlutter_Window_Minimize(void* window) {
|
||||
NSWindow* w = (__bridge NSWindow*)window;
|
||||
[w miniaturize:nil];
|
||||
}
|
||||
|
||||
void InternalFlutter_Window_Unminimize(void* window) {
|
||||
NSWindow* w = (__bridge NSWindow*)window;
|
||||
[w deminiaturize:nil];
|
||||
}
|
||||
|
||||
bool InternalFlutter_Window_IsMinimized(void* window) {
|
||||
NSWindow* w = (__bridge NSWindow*)window;
|
||||
return w.isMiniaturized;
|
||||
}
|
||||
|
||||
void InternalFlutter_Window_SetFullScreen(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 InternalFlutter_Window_IsFullScreen(void* window) {
|
||||
NSWindow* w = (__bridge NSWindow*)window;
|
||||
return (w.styleMask & NSWindowStyleMaskFullScreen) != 0;
|
||||
}
|
||||
|
||||
void InternalFlutter_Window_Activate(void* window) {
|
||||
NSWindow* w = (__bridge NSWindow*)window;
|
||||
[NSApplication.sharedApplication activateIgnoringOtherApps:YES];
|
||||
[w makeKeyAndOrderFront:nil];
|
||||
}
|
||||
|
||||
// NOLINTEND(google-objc-function-naming)
|
||||
@ -0,0 +1,194 @@
|
||||
// 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<flutter::Isolate> 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<int64_t>(engine);
|
||||
|
||||
{
|
||||
IsolateScope isolate_scope(isolate());
|
||||
int64_t handle = InternalFlutter_WindowController_CreateRegularWindow(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<flutter::Isolate> 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<int64_t>(engine);
|
||||
|
||||
{
|
||||
FML_DCHECK(isolate.has_value());
|
||||
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
|
||||
IsolateScope isolateScope(*isolate);
|
||||
int64_t handle = InternalFlutter_WindowController_CreateRegularWindow(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<int64_t>(engine);
|
||||
|
||||
IsolateScope isolate_scope(isolate());
|
||||
int64_t handle = InternalFlutter_WindowController_CreateRegularWindow(engine_id, &request);
|
||||
FlutterViewController* viewController = [engine viewControllerForIdentifier:handle];
|
||||
|
||||
InternalFlutter_Window_Destroy(engine_id, (__bridge void*)viewController.view.window);
|
||||
viewController = [engine viewControllerForIdentifier:handle];
|
||||
EXPECT_EQ(viewController, nil);
|
||||
}
|
||||
|
||||
TEST_F(FlutterWindowControllerTest, InternalFlutter_Window_GetHandle) {
|
||||
FlutterWindowCreationRequest request{
|
||||
.contentSize = {.has_size = true, .width = 800, .height = 600},
|
||||
.on_close = [] {},
|
||||
.on_size_change = [] {},
|
||||
};
|
||||
|
||||
FlutterEngine* engine = GetFlutterEngine();
|
||||
int64_t engine_id = reinterpret_cast<int64_t>(engine);
|
||||
|
||||
IsolateScope isolate_scope(isolate());
|
||||
int64_t handle = InternalFlutter_WindowController_CreateRegularWindow(engine_id, &request);
|
||||
FlutterViewController* viewController = [engine viewControllerForIdentifier:handle];
|
||||
|
||||
void* window_handle = InternalFlutter_Window_GetHandle(engine_id, handle);
|
||||
EXPECT_EQ(window_handle, (__bridge void*)viewController.view.window);
|
||||
}
|
||||
|
||||
TEST_F(FlutterWindowControllerTest, WindowStates) {
|
||||
FlutterWindowCreationRequest request{
|
||||
.contentSize = {.has_size = true, .width = 800, .height = 600},
|
||||
.on_close = [] {},
|
||||
.on_size_change = [] {},
|
||||
};
|
||||
|
||||
FlutterEngine* engine = GetFlutterEngine();
|
||||
int64_t engine_id = reinterpret_cast<int64_t>(engine);
|
||||
|
||||
IsolateScope isolate_scope(isolate());
|
||||
int64_t handle = InternalFlutter_WindowController_CreateRegularWindow(engine_id, &request);
|
||||
|
||||
FlutterViewController* viewController = [engine viewControllerForIdentifier:handle];
|
||||
NSWindow* window = viewController.view.window;
|
||||
void* windowHandle = (__bridge void*)window;
|
||||
|
||||
EXPECT_EQ(window.zoomed, NO);
|
||||
EXPECT_EQ(window.miniaturized, NO);
|
||||
EXPECT_EQ(window.styleMask & NSWindowStyleMaskFullScreen, 0u);
|
||||
|
||||
InternalFlutter_Window_SetMaximized(windowHandle, true);
|
||||
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.5, false);
|
||||
EXPECT_EQ(window.zoomed, YES);
|
||||
|
||||
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.
|
||||
|
||||
InternalFlutter_Window_Minimize(windowHandle);
|
||||
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.5, false);
|
||||
EXPECT_EQ(window.miniaturized, YES);
|
||||
}
|
||||
} // namespace flutter::testing
|
||||
@ -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() {}
|
||||
|
||||
@ -89,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",
|
||||
@ -122,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",
|
||||
@ -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",
|
||||
@ -247,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",
|
||||
|
||||
@ -404,6 +404,11 @@ void testEngineId() {
|
||||
notifyEngineId(ui.PlatformDispatcher.instance.engineId);
|
||||
}
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
void testWindowController() {
|
||||
signal();
|
||||
}
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
Future<void> sendSemanticsTreeInfo() async {
|
||||
// Wait until semantics are enabled.
|
||||
|
||||
@ -25,6 +25,7 @@
|
||||
#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"
|
||||
|
||||
@ -205,8 +206,18 @@ FlutterWindowsEngine::FlutterWindowsEngine(
|
||||
FlutterWindowsEngine* that =
|
||||
static_cast<FlutterWindowsEngine*>(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->window_manager_->HandleMessage(hwnd, msg, wpar, lpar);
|
||||
if (message_result) {
|
||||
*result = *message_result;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
static_cast<void*>(this));
|
||||
|
||||
@ -224,6 +235,7 @@ FlutterWindowsEngine::FlutterWindowsEngine(
|
||||
std::make_unique<CursorHandler>(messenger_wrapper_.get(), this);
|
||||
platform_handler_ =
|
||||
std::make_unique<PlatformHandler>(messenger_wrapper_.get(), this);
|
||||
window_manager_ = std::make_unique<WindowManager>(this);
|
||||
settings_plugin_ = std::make_unique<SettingsPlugin>(messenger_wrapper_.get(),
|
||||
task_runner_.get());
|
||||
}
|
||||
@ -499,6 +511,7 @@ bool FlutterWindowsEngine::Run(std::string_view entrypoint) {
|
||||
|
||||
bool FlutterWindowsEngine::Stop() {
|
||||
if (engine_) {
|
||||
window_manager_->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<LanguageInfo> languages =
|
||||
GetPreferredLanguageInfo(*windows_proc_table_);
|
||||
|
||||
@ -32,6 +32,7 @@
|
||||
#include "flutter/shell/platform/windows/flutter_desktop_messenger.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"
|
||||
@ -315,6 +316,12 @@ class FlutterWindowsEngine {
|
||||
// Sets the cursor directly from a cursor handle.
|
||||
void SetFlutterCursor(HCURSOR cursor) const;
|
||||
|
||||
WindowManager* window_manager() { return window_manager_.get(); }
|
||||
|
||||
// Returns the root view associated with the top-level window with |hwnd| as
|
||||
// the window handle or nullptr if no such view could be found.
|
||||
FlutterWindowsView* GetViewFromTopLevelWindow(HWND hwnd) const;
|
||||
|
||||
protected:
|
||||
// Creates the keyboard key handler.
|
||||
//
|
||||
@ -453,6 +460,10 @@ class FlutterWindowsEngine {
|
||||
// Handlers for keyboard events from Windows.
|
||||
std::unique_ptr<KeyboardHandlerBase> keyboard_key_handler_;
|
||||
|
||||
// The manager that manages the lifecycle of |HostWindow|s, native
|
||||
// Win32 windows hosting a Flutter view in their client area.
|
||||
std::unique_ptr<WindowManager> window_manager_;
|
||||
|
||||
// Handlers for text events from Windows.
|
||||
std::unique_ptr<TextInputPlugin> text_input_plugin_;
|
||||
|
||||
|
||||
495
engine/src/flutter/shell/platform/windows/host_window.cc
Normal file
495
engine/src/flutter/shell/platform/windows/host_window.cc
Normal file
@ -0,0 +1,495 @@
|
||||
// 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/host_window.h"
|
||||
|
||||
#include <dwmapi.h>
|
||||
|
||||
#include "flutter/shell/platform/windows/dpi_utils.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 {
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
void EnableTransparentWindowBackground(HWND hwnd,
|
||||
flutter::WindowsProcTable const& win32) {
|
||||
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<DWORD>(0), 0};
|
||||
flutter::WindowsProcTable::WINDOWCOMPOSITIONATTRIBDATA data = {
|
||||
.Attrib =
|
||||
flutter::WindowsProcTable::WINDOWCOMPOSITIONATTRIB::WCA_ACCENT_POLICY,
|
||||
.pvData = &accent,
|
||||
.cbData = sizeof(accent)};
|
||||
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};
|
||||
win32.DwmExtendFrameIntoClientArea(hwnd, &margins);
|
||||
INT effect_value = 1;
|
||||
win32.DwmSetWindowAttribute(hwnd, DWMWA_SYSTEMBACKDROP_TYPE, &effect_value,
|
||||
sizeof(BOOL));
|
||||
}
|
||||
|
||||
// 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<LPTSTR>(&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 |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
|
||||
// resulting size includes window borders, non-client areas, and drop shadows.
|
||||
// On error, returns std::nullopt and logs an error message.
|
||||
std::optional<flutter::Size> GetWindowSizeForClientSize(
|
||||
flutter::WindowsProcTable const& win32,
|
||||
flutter::Size const& client_size,
|
||||
std::optional<flutter::Size> smallest,
|
||||
std::optional<flutter::Size> biggest,
|
||||
DWORD window_style,
|
||||
DWORD extended_window_style,
|
||||
HWND owner_hwnd) {
|
||||
UINT const dpi = flutter::GetDpiForHWND(owner_hwnd);
|
||||
double const scale_factor =
|
||||
static_cast<double>(dpi) / USER_DEFAULT_SCREEN_DPI;
|
||||
RECT rect = {
|
||||
.right = static_cast<LONG>(client_size.width() * scale_factor),
|
||||
.bottom = static_cast<LONG>(client_size.height() * scale_factor)};
|
||||
|
||||
if (!win32.AdjustWindowRectExForDpi(&rect, window_style, FALSE,
|
||||
extended_window_style, dpi)) {
|
||||
FML_LOG(ERROR) << "Failed to run AdjustWindowRectExForDpi: "
|
||||
<< GetLastErrorAsString();
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
double width = static_cast<double>(rect.right - rect.left);
|
||||
double height = static_cast<double>(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 (smallest) {
|
||||
flutter::Size min_physical_size = ClampToVirtualScreen(
|
||||
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 (biggest) {
|
||||
flutter::Size max_physical_size = ClampToVirtualScreen(
|
||||
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());
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
|
||||
std::unique_ptr<HostWindow> HostWindow::CreateRegularWindow(
|
||||
WindowManager* window_manager,
|
||||
FlutterWindowsEngine* engine,
|
||||
const WindowSizing& content_size) {
|
||||
DWORD window_style = WS_OVERLAPPEDWINDOW;
|
||||
DWORD extended_window_style = 0;
|
||||
std::optional<Size> smallest = std::nullopt;
|
||||
std::optional<Size> biggest = std::nullopt;
|
||||
|
||||
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_preferred_view_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<Size> const window_size = GetWindowSizeForClientSize(
|
||||
*engine->windows_proc_table(),
|
||||
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}};
|
||||
}();
|
||||
|
||||
// Set up the view.
|
||||
auto view_window = std::make_unique<FlutterWindow>(
|
||||
initial_window_rect.width(), initial_window_rect.height(),
|
||||
engine->windows_proc_table());
|
||||
|
||||
std::unique_ptr<FlutterWindowsView> view =
|
||||
engine->CreateView(std::move(view_window));
|
||||
if (view == nullptr) {
|
||||
FML_LOG(ERROR) << "Failed to create view";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<FlutterWindowsViewController> view_controller =
|
||||
std::make_unique<FlutterWindowsViewController>(nullptr, std::move(view));
|
||||
FML_CHECK(engine->running());
|
||||
// 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.
|
||||
engine->UpdateAccessibilityFeatures();
|
||||
|
||||
// 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 = HostWindow::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 nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// 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), engine->windows_proc_table().get());
|
||||
if (!hwnd) {
|
||||
FML_LOG(ERROR) << "Cannot create window: " << GetLastErrorAsString();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 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(), 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
|
||||
// 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);
|
||||
return std::unique_ptr<HostWindow>(new HostWindow(
|
||||
window_manager, engine, WindowArchetype::kRegular,
|
||||
std::move(view_controller), BoxConstraints(smallest, biggest), hwnd));
|
||||
}
|
||||
|
||||
HostWindow::HostWindow(
|
||||
WindowManager* window_manager,
|
||||
FlutterWindowsEngine* engine,
|
||||
WindowArchetype archetype,
|
||||
std::unique_ptr<FlutterWindowsViewController> view_controller,
|
||||
const BoxConstraints& box_constraints,
|
||||
HWND hwnd)
|
||||
: window_manager_(window_manager),
|
||||
engine_(engine),
|
||||
archetype_(archetype),
|
||||
view_controller_(std::move(view_controller)),
|
||||
window_handle_(hwnd),
|
||||
box_constraints_(box_constraints) {
|
||||
SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
|
||||
}
|
||||
|
||||
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.
|
||||
if (!UnregisterClass(kWindowClassName, GetModuleHandle(nullptr))) {
|
||||
// Clear the error state after the failed unregistration.
|
||||
SetLastError(ERROR_SUCCESS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HostWindow* HostWindow::GetThisFromHandle(HWND hwnd) {
|
||||
return reinterpret_cast<HostWindow*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
|
||||
}
|
||||
|
||||
HWND HostWindow::GetWindowHandle() const {
|
||||
return window_handle_;
|
||||
}
|
||||
|
||||
void HostWindow::FocusViewOf(HostWindow* window) {
|
||||
auto child_content = window->view_controller_->view()->GetWindowHandle();
|
||||
if (window != nullptr && child_content != nullptr) {
|
||||
SetFocus(child_content);
|
||||
}
|
||||
};
|
||||
|
||||
LRESULT HostWindow::WndProc(HWND hwnd,
|
||||
UINT message,
|
||||
WPARAM wparam,
|
||||
LPARAM lparam) {
|
||||
if (message == WM_NCCREATE) {
|
||||
auto* const create_struct = reinterpret_cast<CREATESTRUCT*>(lparam);
|
||||
auto* const windows_proc_table =
|
||||
static_cast<WindowsProcTable*>(create_struct->lpCreateParams);
|
||||
windows_proc_table->EnableNonClientDpiScaling(hwnd);
|
||||
EnableTransparentWindowBackground(hwnd, *windows_proc_table);
|
||||
} else if (HostWindow* const window = GetThisFromHandle(hwnd)) {
|
||||
return window->HandleMessage(hwnd, message, wparam, lparam);
|
||||
}
|
||||
|
||||
return DefWindowProc(hwnd, message, wparam, 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) {
|
||||
return *result;
|
||||
}
|
||||
|
||||
switch (message) {
|
||||
case WM_DPICHANGED: {
|
||||
auto* const new_scaled_window_rect = reinterpret_cast<RECT*>(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<double>(dpi) / USER_DEFAULT_SCREEN_DPI;
|
||||
|
||||
MINMAXINFO* info = reinterpret_cast<MINMAXINFO*>(lparam);
|
||||
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;
|
||||
}
|
||||
|
||||
case WM_SIZE: {
|
||||
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,
|
||||
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 HostWindow::SetContentSize(const WindowSizing& size) {
|
||||
WINDOWINFO window_info = {.cbSize = sizeof(WINDOWINFO)};
|
||||
GetWindowInfo(window_handle_, &window_info);
|
||||
|
||||
std::optional<Size> smallest, biggest;
|
||||
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_preferred_view_size) {
|
||||
std::optional<Size> const window_size = GetWindowSizeForClientSize(
|
||||
*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);
|
||||
|
||||
if (window_size) {
|
||||
SetWindowPos(window_handle_, NULL, 0, 0, window_size->width(),
|
||||
window_size->height(),
|
||||
SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace flutter
|
||||
99
engine/src/flutter/shell/platform/windows/host_window.h
Normal file
99
engine/src/flutter/shell/platform/windows/host_window.h
Normal file
@ -0,0 +1,99 @@
|
||||
// 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_HOST_WINDOW_H_
|
||||
#define FLUTTER_SHELL_PLATFORM_WINDOWS_HOST_WINDOW_H_
|
||||
|
||||
#include <windows.h>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
#include "flutter/fml/macros.h"
|
||||
#include "flutter/shell/platform/common/geometry.h"
|
||||
#include "flutter/shell/platform/common/windowing.h"
|
||||
#include "flutter/shell/platform/windows/window_manager.h"
|
||||
|
||||
namespace flutter {
|
||||
|
||||
class WindowManager;
|
||||
class FlutterWindowsView;
|
||||
class FlutterWindowsViewController;
|
||||
|
||||
// A Win32 window that hosts a |FlutterWindow| in its client area.
|
||||
class HostWindow {
|
||||
public:
|
||||
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
|
||||
// |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<HostWindow> CreateRegularWindow(
|
||||
WindowManager* controller,
|
||||
FlutterWindowsEngine* engine,
|
||||
const WindowSizing& content_size);
|
||||
|
||||
// Returns the instance pointer for |hwnd| or nullptr if invalid.
|
||||
static HostWindow* 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 WindowSizing& size);
|
||||
|
||||
private:
|
||||
friend WindowManager;
|
||||
|
||||
HostWindow(WindowManager* controller,
|
||||
FlutterWindowsEngine* engine,
|
||||
WindowArchetype archetype,
|
||||
std::unique_ptr<FlutterWindowsViewController> view_controller,
|
||||
const BoxConstraints& constraints,
|
||||
HWND hwnd);
|
||||
|
||||
// Sets the focus to the child view window of |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
|
||||
// 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);
|
||||
|
||||
// Controller for this window.
|
||||
WindowManager* const window_manager_ = 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.
|
||||
std::unique_ptr<FlutterWindowsViewController> view_controller_;
|
||||
|
||||
// The window archetype.
|
||||
WindowArchetype archetype_ = WindowArchetype::kRegular;
|
||||
|
||||
// Backing handle for this window.
|
||||
HWND window_handle_ = nullptr;
|
||||
|
||||
// The constraints on the window's client area.
|
||||
BoxConstraints box_constraints_;
|
||||
|
||||
FML_DISALLOW_COPY_AND_ASSIGN(HostWindow);
|
||||
};
|
||||
|
||||
} // namespace flutter
|
||||
|
||||
#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_HOST_WINDOW_H_
|
||||
158
engine/src/flutter/shell/platform/windows/window_manager.cc
Normal file
158
engine/src/flutter/shell/platform/windows/window_manager.cc
Normal file
@ -0,0 +1,158 @@
|
||||
// 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/window_manager.h"
|
||||
|
||||
#include <dwmapi.h>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include "embedder.h"
|
||||
#include "flutter/shell/platform/common/windowing.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_windows_view.h"
|
||||
#include "shell/platform/windows/host_window.h"
|
||||
|
||||
namespace flutter {
|
||||
|
||||
WindowManager::WindowManager(FlutterWindowsEngine* engine) : engine_(engine) {}
|
||||
|
||||
void WindowManager::Initialize(const WindowingInitRequest* request) {
|
||||
on_message_ = request->on_message;
|
||||
isolate_ = Isolate::Current();
|
||||
}
|
||||
|
||||
bool WindowManager::HasTopLevelWindows() const {
|
||||
return !active_windows_.empty();
|
||||
}
|
||||
|
||||
FlutterViewId WindowManager::CreateRegularWindow(
|
||||
const WindowCreationRequest* request) {
|
||||
auto window =
|
||||
HostWindow::CreateRegularWindow(this, engine_, request->content_size);
|
||||
if (!window || !window->GetWindowHandle()) {
|
||||
FML_LOG(ERROR) << "Failed to create host window";
|
||||
return -1;
|
||||
}
|
||||
FlutterViewId const view_id = window->view_controller_->view()->view_id();
|
||||
active_windows_[window->GetWindowHandle()] = std::move(window);
|
||||
return view_id;
|
||||
}
|
||||
|
||||
void WindowManager::OnEngineShutdown() {
|
||||
// Don't send any more messages to isolate.
|
||||
on_message_ = nullptr;
|
||||
std::vector<HWND> 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
|
||||
// HostWindow from map when handling WM_NCDESTROY inside
|
||||
// HandleMessage.
|
||||
DestroyWindow(hwnd);
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<LRESULT> WindowManager::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_) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
IsolateScope scope(*isolate_);
|
||||
on_message_(&message_struct);
|
||||
if (message_struct.handled) {
|
||||
return message_struct.result;
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace flutter
|
||||
|
||||
void InternalFlutterWindows_WindowManager_Initialize(
|
||||
int64_t engine_id,
|
||||
const flutter::WindowingInitRequest* request) {
|
||||
flutter::FlutterWindowsEngine* engine =
|
||||
flutter::FlutterWindowsEngine::GetEngineForId(engine_id);
|
||||
engine->window_manager()->Initialize(request);
|
||||
}
|
||||
|
||||
bool InternalFlutterWindows_WindowManager_HasTopLevelWindows(
|
||||
int64_t engine_id) {
|
||||
flutter::FlutterWindowsEngine* engine =
|
||||
flutter::FlutterWindowsEngine::GetEngineForId(engine_id);
|
||||
return engine->window_manager()->HasTopLevelWindows();
|
||||
}
|
||||
|
||||
FlutterViewId InternalFlutterWindows_WindowManager_CreateRegularWindow(
|
||||
int64_t engine_id,
|
||||
const flutter::WindowCreationRequest* request) {
|
||||
flutter::FlutterWindowsEngine* engine =
|
||||
flutter::FlutterWindowsEngine::GetEngineForId(engine_id);
|
||||
return engine->window_manager()->CreateRegularWindow(request);
|
||||
}
|
||||
|
||||
HWND InternalFlutterWindows_WindowManager_GetTopLevelWindowHandle(
|
||||
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 InternalFlutterWindows_WindowManager_GetWindowContentSize(
|
||||
HWND hwnd) {
|
||||
RECT rect;
|
||||
GetClientRect(hwnd, &rect);
|
||||
double const dpr = FlutterDesktopGetDpiForHWND(hwnd) /
|
||||
static_cast<double>(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,
|
||||
};
|
||||
}
|
||||
|
||||
void InternalFlutterWindows_WindowManager_SetWindowContentSize(
|
||||
HWND hwnd,
|
||||
const flutter::WindowSizing* size) {
|
||||
flutter::HostWindow* window = flutter::HostWindow::GetThisFromHandle(hwnd);
|
||||
if (window) {
|
||||
window->SetContentSize(*size);
|
||||
}
|
||||
}
|
||||
135
engine/src/flutter/shell/platform/windows/window_manager.h
Normal file
135
engine/src/flutter/shell/platform/windows/window_manager.h
Normal file
@ -0,0 +1,135 @@
|
||||
// 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_WINDOW_MANAGER_H_
|
||||
#define FLUTTER_SHELL_PLATFORM_WINDOWS_WINDOW_MANAGER_H_
|
||||
|
||||
#include <windows.h>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#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 HostWindow;
|
||||
struct WindowingInitRequest;
|
||||
|
||||
struct WindowsMessage {
|
||||
FlutterViewId view_id;
|
||||
HWND hwnd;
|
||||
UINT message;
|
||||
WPARAM wParam;
|
||||
LPARAM lParam;
|
||||
LRESULT result;
|
||||
bool handled;
|
||||
};
|
||||
|
||||
struct WindowSizing {
|
||||
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 {
|
||||
void (*on_message)(WindowsMessage*);
|
||||
};
|
||||
|
||||
struct WindowCreationRequest {
|
||||
WindowSizing content_size;
|
||||
};
|
||||
|
||||
// A manager class for managing |HostWindow| instances.
|
||||
// A unique instance of this class is owned by |FlutterWindowsEngine|.
|
||||
class WindowManager {
|
||||
public:
|
||||
explicit WindowManager(FlutterWindowsEngine* engine);
|
||||
virtual ~WindowManager() = default;
|
||||
|
||||
void Initialize(const WindowingInitRequest* request);
|
||||
|
||||
bool HasTopLevelWindows() const;
|
||||
|
||||
FlutterViewId CreateRegularWindow(const WindowCreationRequest* request);
|
||||
|
||||
// Message handler called by |HostWindow::WndProc| to process window
|
||||
// messages before delegating them to the host window. This allows the
|
||||
// manager to process messages that affect the state of other host windows.
|
||||
std::optional<LRESULT> HandleMessage(HWND hwnd,
|
||||
UINT message,
|
||||
WPARAM wparam,
|
||||
LPARAM lparam);
|
||||
|
||||
void OnEngineShutdown();
|
||||
|
||||
private:
|
||||
// The Flutter engine that owns this manager.
|
||||
FlutterWindowsEngine* const engine_;
|
||||
|
||||
// Callback that relays windows messages to the isolate. Set
|
||||
// during Initialize().
|
||||
std::function<void(WindowsMessage*)> on_message_;
|
||||
|
||||
// Isolate that runs the Dart code. Set during Initialize().
|
||||
std::optional<Isolate> isolate_;
|
||||
|
||||
// A map of active windows. Used to destroy remaining windows on engine
|
||||
// shutdown.
|
||||
std::unordered_map<HWND, std::unique_ptr<HostWindow>> active_windows_;
|
||||
|
||||
FML_DISALLOW_COPY_AND_ASSIGN(WindowManager);
|
||||
};
|
||||
|
||||
} // namespace flutter
|
||||
|
||||
extern "C" {
|
||||
|
||||
FLUTTER_EXPORT
|
||||
void InternalFlutterWindows_WindowManager_Initialize(
|
||||
int64_t engine_id,
|
||||
const flutter::WindowingInitRequest* request);
|
||||
|
||||
FLUTTER_EXPORT
|
||||
bool InternalFlutterWindows_WindowManager_HasTopLevelWindows(int64_t engine_id);
|
||||
|
||||
FLUTTER_EXPORT
|
||||
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_GetTopLevelWindowHandle(
|
||||
int64_t engine_id,
|
||||
FlutterViewId view_id);
|
||||
|
||||
struct FlutterWindowSize {
|
||||
double width;
|
||||
double height;
|
||||
};
|
||||
|
||||
FLUTTER_EXPORT
|
||||
FlutterWindowSize InternalFlutterWindows_WindowManager_GetWindowContentSize(
|
||||
HWND hwnd);
|
||||
|
||||
FLUTTER_EXPORT
|
||||
void InternalFlutterWindows_WindowManager_SetWindowContentSize(
|
||||
HWND hwnd,
|
||||
const flutter::WindowSizing* size);
|
||||
}
|
||||
|
||||
#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_WINDOW_MANAGER_H_
|
||||
@ -0,0 +1,160 @@
|
||||
// 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/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 {
|
||||
namespace testing {
|
||||
|
||||
namespace {
|
||||
|
||||
class WindowManagerTest : public WindowsTest {
|
||||
public:
|
||||
WindowManagerTest() = default;
|
||||
virtual ~WindowManagerTest() = default;
|
||||
|
||||
protected:
|
||||
void SetUp() override {
|
||||
auto& context = GetContext();
|
||||
FlutterWindowsEngineBuilder builder(context);
|
||||
|
||||
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<int64_t>(engine_.get()); }
|
||||
flutter::Isolate& isolate() { return *isolate_; }
|
||||
WindowCreationRequest* creation_request() { return &creation_request_; }
|
||||
|
||||
private:
|
||||
std::unique_ptr<FlutterWindowsEngine> engine_;
|
||||
std::optional<flutter::Isolate> isolate_;
|
||||
WindowCreationRequest creation_request_{
|
||||
.content_size =
|
||||
{
|
||||
.has_preferred_view_size = true,
|
||||
.preferred_view_width = 800,
|
||||
.preferred_view_height = 600,
|
||||
},
|
||||
};
|
||||
|
||||
FML_DISALLOW_COPY_AND_ASSIGN(WindowManagerTest);
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_F(WindowManagerTest, WindowingInitialize) {
|
||||
IsolateScope isolate_scope(isolate());
|
||||
|
||||
static bool received_message = false;
|
||||
WindowingInitRequest init_request{
|
||||
.on_message = [](WindowsMessage* message) { received_message = true; }};
|
||||
|
||||
InternalFlutterWindows_WindowManager_Initialize(engine_id(), &init_request);
|
||||
const int64_t view_id =
|
||||
InternalFlutterWindows_WindowManager_CreateRegularWindow(
|
||||
engine_id(), creation_request());
|
||||
DestroyWindow(InternalFlutterWindows_WindowManager_GetTopLevelWindowHandle(
|
||||
engine_id(), view_id));
|
||||
|
||||
EXPECT_TRUE(received_message);
|
||||
}
|
||||
|
||||
TEST_F(WindowManagerTest, HasTopLevelWindows) {
|
||||
IsolateScope isolate_scope(isolate());
|
||||
|
||||
bool has_top_level_windows =
|
||||
InternalFlutterWindows_WindowManager_HasTopLevelWindows(engine_id());
|
||||
EXPECT_FALSE(has_top_level_windows);
|
||||
|
||||
InternalFlutterWindows_WindowManager_CreateRegularWindow(engine_id(),
|
||||
creation_request());
|
||||
has_top_level_windows =
|
||||
InternalFlutterWindows_WindowManager_HasTopLevelWindows(engine_id());
|
||||
EXPECT_TRUE(has_top_level_windows);
|
||||
}
|
||||
|
||||
TEST_F(WindowManagerTest, CreateRegularWindow) {
|
||||
IsolateScope isolate_scope(isolate());
|
||||
|
||||
const int64_t view_id =
|
||||
InternalFlutterWindows_WindowManager_CreateRegularWindow(
|
||||
engine_id(), creation_request());
|
||||
EXPECT_EQ(view_id, 0);
|
||||
}
|
||||
|
||||
TEST_F(WindowManagerTest, GetWindowHandle) {
|
||||
IsolateScope isolate_scope(isolate());
|
||||
|
||||
const int64_t view_id =
|
||||
InternalFlutterWindows_WindowManager_CreateRegularWindow(
|
||||
engine_id(), creation_request());
|
||||
const HWND window_handle =
|
||||
InternalFlutterWindows_WindowManager_GetTopLevelWindowHandle(engine_id(),
|
||||
view_id);
|
||||
EXPECT_NE(window_handle, nullptr);
|
||||
}
|
||||
|
||||
TEST_F(WindowManagerTest, GetWindowSize) {
|
||||
IsolateScope isolate_scope(isolate());
|
||||
|
||||
const int64_t view_id =
|
||||
InternalFlutterWindows_WindowManager_CreateRegularWindow(
|
||||
engine_id(), creation_request());
|
||||
const HWND window_handle =
|
||||
InternalFlutterWindows_WindowManager_GetTopLevelWindowHandle(engine_id(),
|
||||
view_id);
|
||||
|
||||
FlutterWindowSize size =
|
||||
InternalFlutterWindows_WindowManager_GetWindowContentSize(window_handle);
|
||||
|
||||
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) {
|
||||
IsolateScope isolate_scope(isolate());
|
||||
|
||||
const int64_t view_id =
|
||||
InternalFlutterWindows_WindowManager_CreateRegularWindow(
|
||||
engine_id(), creation_request());
|
||||
const HWND window_handle =
|
||||
InternalFlutterWindows_WindowManager_GetTopLevelWindowHandle(engine_id(),
|
||||
view_id);
|
||||
|
||||
WindowSizing requestedSize{
|
||||
.has_preferred_view_size = true,
|
||||
.preferred_view_width = 640,
|
||||
.preferred_view_height = 480,
|
||||
};
|
||||
InternalFlutterWindows_WindowManager_SetWindowContentSize(window_handle,
|
||||
&requestedSize);
|
||||
|
||||
FlutterWindowSize actual_size =
|
||||
InternalFlutterWindows_WindowManager_GetWindowContentSize(window_handle);
|
||||
EXPECT_EQ(actual_size.width, 640);
|
||||
EXPECT_EQ(actual_size.height, 480);
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
} // namespace flutter
|
||||
@ -13,6 +13,15 @@ WindowsProcTable::WindowsProcTable() {
|
||||
user32_ = fml::NativeLibrary::Create("user32.dll");
|
||||
get_pointer_type_ =
|
||||
user32_->ResolveFunction<GetPointerType_*>("GetPointerType");
|
||||
enable_non_client_dpi_scaling_ =
|
||||
user32_->ResolveFunction<EnableNonClientDpiScaling_*>(
|
||||
"EnableNonClientDpiScaling");
|
||||
set_window_composition_attribute_ =
|
||||
user32_->ResolveFunction<SetWindowCompositionAttribute_*>(
|
||||
"SetWindowCompositionAttribute");
|
||||
adjust_window_rect_ext_for_dpi_ =
|
||||
user32_->ResolveFunction<AdjustWindowRectExForDpi_*>(
|
||||
"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
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_WINDOWS_PROC_TABLE_H_
|
||||
#define FLUTTER_SHELL_PLATFORM_WINDOWS_WINDOWS_PROC_TABLE_H_
|
||||
|
||||
#include <dwmapi.h>
|
||||
#include <optional>
|
||||
|
||||
#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<fml::NativeLibrary> user32_;
|
||||
|
||||
std::optional<GetPointerType_*> get_pointer_type_;
|
||||
std::optional<EnableNonClientDpiScaling_*> enable_non_client_dpi_scaling_;
|
||||
std::optional<SetWindowCompositionAttribute_*>
|
||||
set_window_composition_attribute_;
|
||||
std::optional<AdjustWindowRectExForDpi_*> adjust_window_rect_ext_for_dpi_;
|
||||
|
||||
FML_DISALLOW_COPY_AND_ASSIGN(WindowsProcTable);
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user