diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 1418af878d6d9..f355ef5d891d1 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -739,6 +739,7 @@ FILE: ../../../flutter/shell/platform/darwin/ios/ios_surface_software.mm FILE: ../../../flutter/shell/platform/darwin/ios/platform_view_ios.h FILE: ../../../flutter/shell/platform/darwin/ios/platform_view_ios.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/FlutterMacOS.podspec +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FLEEngine.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FLEOpenGLContextHandling.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FLEReshapeListener.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FLEView.h @@ -747,6 +748,8 @@ FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterMacO FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterPluginMacOS.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterPluginRegistrarMacOS.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Info.plist +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FLEEngine.mm +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FLEEngine_Internal.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FLETextInputModel.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FLETextInputModel.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FLETextInputPlugin.h diff --git a/shell/platform/darwin/macos/BUILD.gn b/shell/platform/darwin/macos/BUILD.gn index dceb7419b9dd0..f5bb498a0fb97 100644 --- a/shell/platform/darwin/macos/BUILD.gn +++ b/shell/platform/darwin/macos/BUILD.gn @@ -33,6 +33,7 @@ _flutter_framework_headers = [ "framework/Headers/FlutterMacOS.h", "framework/Headers/FlutterPluginMacOS.h", "framework/Headers/FlutterPluginRegistrarMacOS.h", + "framework/Headers/FLEEngine.h", "framework/Headers/FLEOpenGLContextHandling.h", "framework/Headers/FLEReshapeListener.h", "framework/Headers/FLEView.h", @@ -48,6 +49,8 @@ shared_library("create_flutter_framework_dylib") { output_name = "$_flutter_framework_name" sources = [ + "framework/Source/FLEEngine.mm", + "framework/Source/FLEEngine_Internal.h", "framework/Source/FLETextInputModel.h", "framework/Source/FLETextInputModel.mm", "framework/Source/FLETextInputPlugin.h", diff --git a/shell/platform/darwin/macos/framework/Headers/FLEEngine.h b/shell/platform/darwin/macos/framework/Headers/FLEEngine.h new file mode 100644 index 0000000000000..17053caa8ece1 --- /dev/null +++ b/shell/platform/darwin/macos/framework/Headers/FLEEngine.h @@ -0,0 +1,59 @@ +// 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_FLEENGINE_H_ +#define FLUTTER_FLEENGINE_H_ + +#import + +#include "FlutterBinaryMessenger.h" +#include "FlutterMacros.h" +#include "FlutterPluginRegistrarMacOS.h" + +@class FLEViewController; + +/** + * Coordinates a single instance of execution of a Flutter engine. + * + * TODO(stuartmorgan): Finish aligning this (and ideally merging) with FlutterEngine. Currently + * this is largely usable only as an implementation detail of FLEViewController. + */ +FLUTTER_EXPORT +@interface FLEEngine : NSObject + +/** + * Initializes an engine with the given viewController. + * + * @param viewController The view controller associated with this engine. If nil, the engine + * will be run headless. + */ +- (nonnull instancetype)initWithViewController:(nullable FLEViewController*)viewController; + +/** + * Launches the Flutter engine with the provided configuration. + * + * @param assets The path to the flutter_assets folder for the Flutter application to be run. + * @param arguments Arguments to pass to the Flutter engine. See + * https://github.com/flutter/engine/blob/master/shell/common/switches.h + * for details. Not all arguments will apply to embedding mode. + * Note: This API layer will abstract arguments in the future, instead of + * providing a direct passthrough. + * @return YES if the engine launched successfully. + */ +- (BOOL)launchEngineWithAssetsPath:(nonnull NSURL*)assets + commandLineArguments:(nullable NSArray*)arguments; + +/** + * The `FLEViewController` associated with this engine, if any. + */ +@property(nonatomic, nullable, readonly, weak) FLEViewController* viewController; + +/** + * The `FlutterBinaryMessenger` for communicating with this engine. + */ +@property(nonatomic, nonnull, readonly) id binaryMessenger; + +@end + +#endif // FLUTTER_FLEENGINE_H_ diff --git a/shell/platform/darwin/macos/framework/Headers/FLEViewController.h b/shell/platform/darwin/macos/framework/Headers/FLEViewController.h index 54d05dc3d009a..18b5731f98b0f 100644 --- a/shell/platform/darwin/macos/framework/Headers/FLEViewController.h +++ b/shell/platform/darwin/macos/framework/Headers/FLEViewController.h @@ -4,9 +4,9 @@ #import +#import "FLEEngine.h" #import "FLEOpenGLContextHandling.h" #import "FLEReshapeListener.h" -#import "FlutterBinaryMessenger.h" #import "FlutterMacros.h" #import "FlutterPluginRegistrarMacOS.h" @@ -29,15 +29,19 @@ typedef NS_ENUM(NSInteger, FlutterMouseTrackingMode) { * Flutter engine in non-interactive mode, or with a drawable Flutter canvas. */ FLUTTER_EXPORT -@interface FLEViewController - : NSViewController +@interface FLEViewController : NSViewController /** - * The view this controller manages when launched in interactive mode (headless set to false). Must - * be capable of handling text input events, and the OpenGL context handling protocols. + * The view this controller manages. Must be capable of handling text input events, and the OpenGL + * context handling protocols. */ @property(nullable) NSView* view; +/** + * The Flutter engine associated with this view controller. + */ +@property(nonatomic, nonnull, readonly) FLEEngine* engine; + /** * The style of mouse tracking to use for the view. Defaults to * FlutterMouseTrackingModeInKeyWindow. @@ -49,28 +53,13 @@ FLUTTER_EXPORT * * @param assets The path to the flutter_assets folder for the Flutter application to be run. * @param arguments Arguments to pass to the Flutter engine. See - * https://github.com/flutter/engine/blob/master/shell/common/switches.h - * for details. Not all arguments will apply to embedding mode. - * Note: This API layer will likely abstract arguments in the future, instead of - * providing a direct passthrough. + * https://github.com/flutter/engine/blob/master/shell/common/switches.h + * for details. Not all arguments will apply to embedding mode. + * Note: This API layer will abstract in the future, instead of providing a direct + * passthrough. * @return YES if the engine launched successfully. */ - (BOOL)launchEngineWithAssetsPath:(nonnull NSURL*)assets commandLineArguments:(nullable NSArray*)arguments; -/** - * Launches the Flutter engine in headless mode with the provided configuration. In headless mode, - * this controller's view should not be displayed. - * - * See launcheEngineWithAssetsPath:commandLineArguments: for details. - */ -- (BOOL)launchHeadlessEngineWithAssetsPath:(nonnull NSURL*)assets - commandLineArguments:(nullable NSArray*)arguments; - -/** - * The `FlutterBinaryMessenger` associated with this FLEViewController (used for communicating - * with channels). - */ -@property(nonatomic, readonly) NSObject* _Nonnull binaryMessenger; - @end diff --git a/shell/platform/darwin/macos/framework/Headers/FlutterMacOS.h b/shell/platform/darwin/macos/framework/Headers/FlutterMacOS.h index 8237ecfa676fc..317370ea203a1 100644 --- a/shell/platform/darwin/macos/framework/Headers/FlutterMacOS.h +++ b/shell/platform/darwin/macos/framework/Headers/FlutterMacOS.h @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#import "FLEEngine.h" #import "FLEOpenGLContextHandling.h" #import "FLEReshapeListener.h" #import "FLEView.h" diff --git a/shell/platform/darwin/macos/framework/Source/FLEEngine.mm b/shell/platform/darwin/macos/framework/Source/FLEEngine.mm new file mode 100644 index 0000000000000..7e26e5fc2b91a --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/FLEEngine.mm @@ -0,0 +1,304 @@ +// 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/Headers/FLEEngine.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FLEEngine_Internal.h" + +#include + +#import "flutter/shell/platform/darwin/macos/framework/Source/FLEViewController_Internal.h" +#import "flutter/shell/platform/embedder/embedder.h" + +static NSString* const kICUBundlePath = @"icudtl.dat"; + +/** + * Private interface declaration for FLEEngine. + */ +@interface FLEEngine () + +/** + * Called by the engine to make the context the engine should draw into current. + */ +- (bool)engineCallbackOnMakeCurrent; + +/** + * Called by the engine to clear the context the engine should draw into. + */ +- (bool)engineCallbackOnClearCurrent; + +/** + * Called by the engine when the context's buffers should be swapped. + */ +- (bool)engineCallbackOnPresent; + +/** + * Makes the resource context the current context. + */ +- (bool)engineCallbackOnMakeResourceCurrent; + +/** + * Handles a platform message from the engine. + */ +- (void)engineCallbackOnPlatformMessage:(const FlutterPlatformMessage*)message; + +@end + +#pragma mark - + +/** + * `FlutterPluginRegistrar` implementation handling a single plugin. + */ +@interface FlutterEngineRegistrar : NSObject +- (instancetype)initWithPlugin:(nonnull NSString*)pluginKey + flutterEngine:(nonnull FLEEngine*)flutterEngine; +@end + +@implementation FlutterEngineRegistrar { + NSString* _pluginKey; + FLEEngine* _flutterEngine; +} + +- (instancetype)initWithPlugin:(NSString*)pluginKey flutterEngine:(FLEEngine*)flutterEngine { + self = [super init]; + if (self) { + _pluginKey = [pluginKey copy]; + _flutterEngine = flutterEngine; + } + return self; +} + +#pragma mark - FlutterPluginRegistrar + +- (id)messenger { + return _flutterEngine.binaryMessenger; +} + +- (NSView*)view { + return _flutterEngine.viewController.view; +} + +- (void)addMethodCallDelegate:(nonnull id)delegate + channel:(nonnull FlutterMethodChannel*)channel { + [channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) { + [delegate handleMethodCall:call result:result]; + }]; +} + +@end + +// Callbacks provided to the engine. See the called methods for documentation. +#pragma mark - Static methods provided to engine configuration + +static bool OnMakeCurrent(FLEEngine* engine) { + return [engine engineCallbackOnMakeCurrent]; +} + +static bool OnClearCurrent(FLEEngine* engine) { + return [engine engineCallbackOnClearCurrent]; +} + +static bool OnPresent(FLEEngine* engine) { + return [engine engineCallbackOnPresent]; +} + +static uint32_t OnFBO(FLEEngine* engine) { + // There is currently no case where a different FBO is used, so no need to forward. + return 0; +} + +static bool OnMakeResourceCurrent(FLEEngine* engine) { + return [engine engineCallbackOnMakeResourceCurrent]; +} + +static void OnPlatformMessage(const FlutterPlatformMessage* message, FLEEngine* engine) { + [engine engineCallbackOnPlatformMessage:message]; +} + +#pragma mark - FLEEngine implementation + +@implementation FLEEngine { + // The embedding-API-level engine object. + FlutterEngine _engine; + + // A mapping of channel names to the registered handlers for those channels. + NSMutableDictionary* _messageHandlers; +} + +- (instancetype)init { + return [self initWithViewController:nil]; +} + +- (instancetype)initWithViewController:(FLEViewController*)viewController { + self = [super init]; + if (self != nil) { + _viewController = viewController; + _messageHandlers = [[NSMutableDictionary alloc] init]; + } + return self; +} + +- (void)dealloc { + if (FlutterEngineShutdown(_engine) == kSuccess) { + _engine = NULL; + } +} + +- (BOOL)launchEngineWithAssetsPath:(NSURL*)assets + commandLineArguments:(NSArray*)arguments { + if (_engine != NULL) { + return NO; + } + + const FlutterRendererConfig rendererConfig = { + .type = kOpenGL, + .open_gl.struct_size = sizeof(FlutterOpenGLRendererConfig), + .open_gl.make_current = (BoolCallback)OnMakeCurrent, + .open_gl.clear_current = (BoolCallback)OnClearCurrent, + .open_gl.present = (BoolCallback)OnPresent, + .open_gl.fbo_callback = (UIntCallback)OnFBO, + .open_gl.make_resource_current = (BoolCallback)OnMakeResourceCurrent, + }; + + // TODO(stuartmorgan): Move internal channel registration from FLEViewController to here. + + // FlutterProjectArgs is expecting a full argv, so when processing it for flags the first + // item is treated as the executable and ignored. Add a dummy value so that all provided arguments + // are used. + std::vector argv = {"placeholder"}; + for (NSUInteger i = 0; i < arguments.count; ++i) { + argv.push_back([arguments[i] UTF8String]); + } + + NSString* icuData = [[NSBundle bundleForClass:[self class]] pathForResource:kICUBundlePath + ofType:nil]; + + FlutterProjectArgs flutterArguments = {}; + flutterArguments.struct_size = sizeof(FlutterProjectArgs); + flutterArguments.assets_path = assets.fileSystemRepresentation; + flutterArguments.icu_data_path = icuData.UTF8String; + flutterArguments.command_line_argc = static_cast(argv.size()); + flutterArguments.command_line_argv = &argv[0]; + flutterArguments.platform_message_callback = (FlutterPlatformMessageCallback)OnPlatformMessage; + + FlutterEngineResult result = FlutterEngineRun( + FLUTTER_ENGINE_VERSION, &rendererConfig, &flutterArguments, (__bridge void*)(self), &_engine); + if (result != kSuccess) { + NSLog(@"Failed to start Flutter engine: error %d", result); + return NO; + } + return YES; +} + +- (id)binaryMessenger { + // TODO(stuartmorgan): Switch to FlutterBinaryMessengerRelay to avoid plugins + // keeping the engine alive. + return self; +} + +#pragma mark - Framework-internal methods + +- (void)updateWindowMetricsWithSize:(CGSize)size pixelRatio:(double)pixelRatio { + const FlutterWindowMetricsEvent event = { + .struct_size = sizeof(event), + .width = static_cast(size.width), + .height = static_cast(size.height), + .pixel_ratio = pixelRatio, + }; + FlutterEngineSendWindowMetricsEvent(_engine, &event); +} + +- (void)sendPointerEvent:(const FlutterPointerEvent&)event { + FlutterEngineSendPointerEvent(_engine, &event, 1); +} + +#pragma mark - Private methods + +- (bool)engineCallbackOnMakeCurrent { + if (!_viewController.view) { + return false; + } + [_viewController.view makeCurrentContext]; + return true; +} + +- (bool)engineCallbackOnClearCurrent { + if (!_viewController.view) { + return false; + } + [NSOpenGLContext clearCurrentContext]; + return true; +} + +- (bool)engineCallbackOnPresent { + if (!_viewController.view) { + return false; + } + [_viewController.view onPresent]; + return true; +} + +- (bool)engineCallbackOnMakeResourceCurrent { + if (!_viewController.view) { + return false; + } + [_viewController makeResourceContextCurrent]; + return true; +} + +- (void)engineCallbackOnPlatformMessage:(const FlutterPlatformMessage*)message { + NSData* messageData = [NSData dataWithBytesNoCopy:(void*)message->message + length:message->message_size + freeWhenDone:NO]; + NSString* channel = @(message->channel); + __block const FlutterPlatformMessageResponseHandle* responseHandle = message->response_handle; + + FlutterBinaryReply binaryResponseHandler = ^(NSData* response) { + if (responseHandle) { + FlutterEngineSendPlatformMessageResponse(self->_engine, responseHandle, + static_cast(response.bytes), + response.length); + responseHandle = NULL; + } else { + NSLog(@"Error: Message responses can be sent only once. Ignoring duplicate response " + "on channel '%@'.", + channel); + } + }; + + FlutterBinaryMessageHandler channelHandler = _messageHandlers[channel]; + if (channelHandler) { + channelHandler(messageData, binaryResponseHandler); + } else { + binaryResponseHandler(nil); + } +} + +#pragma mark - FlutterBinaryMessenger + +- (void)sendOnChannel:(nonnull NSString*)channel message:(nullable NSData*)message { + FlutterPlatformMessage platformMessage = { + .struct_size = sizeof(FlutterPlatformMessage), + .channel = [channel UTF8String], + .message = static_cast(message.bytes), + .message_size = message.length, + }; + + FlutterEngineResult result = FlutterEngineSendPlatformMessage(_engine, &platformMessage); + if (result != kSuccess) { + NSLog(@"Failed to send message to Flutter engine on channel '%@' (%d).", channel, result); + } +} + +- (void)setMessageHandlerOnChannel:(nonnull NSString*)channel + binaryMessageHandler:(nullable FlutterBinaryMessageHandler)handler { + _messageHandlers[channel] = [handler copy]; +} + +#pragma mark - FlutterPluginRegistry + +- (id)registrarForPlugin:(NSString*)pluginName { + return [[FlutterEngineRegistrar alloc] initWithPlugin:pluginName flutterEngine:self]; +} + +@end diff --git a/shell/platform/darwin/macos/framework/Source/FLEEngine_Internal.h b/shell/platform/darwin/macos/framework/Source/FLEEngine_Internal.h new file mode 100644 index 0000000000000..80112e205c9e0 --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/FLEEngine_Internal.h @@ -0,0 +1,24 @@ +// 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/Headers/FLEEngine.h" + +#import "flutter/shell/platform/embedder/embedder.h" + +@interface FLEEngine () + +/** + * Informs the engine that the display region's size has changed. + * + * @param size The size of the display, in pixels. + * @param pixelRatio The number of pixels per screen coordinate. + */ +- (void)updateWindowMetricsWithSize:(CGSize)size pixelRatio:(double)pixelRatio; + +/** + * Dispatches the given pointer event data to engine. + */ +- (void)sendPointerEvent:(const FlutterPointerEvent&)event; + +@end diff --git a/shell/platform/darwin/macos/framework/Source/FLETextInputPlugin.mm b/shell/platform/darwin/macos/framework/Source/FLETextInputPlugin.mm index eeb1ebd9d193f..158d359028c34 100644 --- a/shell/platform/darwin/macos/framework/Source/FLETextInputPlugin.mm +++ b/shell/platform/darwin/macos/framework/Source/FLETextInputPlugin.mm @@ -72,7 +72,7 @@ - (instancetype)initWithViewController:(FLEViewController*)viewController { if (self != nil) { _flutterViewController = viewController; _channel = [FlutterMethodChannel methodChannelWithName:kTextInputChannel - binaryMessenger:viewController.binaryMessenger + binaryMessenger:viewController.engine.binaryMessenger codec:[FlutterJSONMethodCodec sharedInstance]]; __weak FLETextInputPlugin* weakSelf = self; [_channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) { diff --git a/shell/platform/darwin/macos/framework/Source/FLEViewController.mm b/shell/platform/darwin/macos/framework/Source/FLEViewController.mm index e0cdeaa4b37b5..a7872ac9ae55c 100644 --- a/shell/platform/darwin/macos/framework/Source/FLEViewController.mm +++ b/shell/platform/darwin/macos/framework/Source/FLEViewController.mm @@ -7,15 +7,13 @@ #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/Headers/FLEEngine.h" #import "flutter/shell/platform/darwin/macos/framework/Headers/FLEReshapeListener.h" #import "flutter/shell/platform/darwin/macos/framework/Headers/FLEView.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FLEEngine_Internal.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FLETextInputPlugin.h" #import "flutter/shell/platform/embedder/embedder.h" -static NSString* const kICUBundlePath = @"icudtl.dat"; - -static const int kDefaultWindowFramebuffer = 0; - namespace { /// Clipboard plain text format. @@ -70,7 +68,7 @@ void Reset() { /** * Private interface declaration for FLEViewController. */ -@interface FLEViewController () +@interface FLEViewController () /** * A list of additional responders to keyboard events. Keybord events are forwarded to all of them. @@ -98,35 +96,11 @@ - (void)configureTrackingArea; */ - (void)addInternalPlugins; -/** - * Shared implementation of the regular and headless public APIs. - */ -- (BOOL)launchEngineInternalWithAssetsPath:(nonnull NSURL*)assets - headless:(BOOL)headless - commandLineArguments:(nullable NSArray*)arguments; - -/** - * Creates a render config with callbacks based on whether the embedder is being run as a headless - * server. - */ -+ (FlutterRendererConfig)createRenderConfigHeadless:(BOOL)headless; - /** * Creates the OpenGL context used as the resource context by the engine. */ - (void)createResourceContext; -/** - * Makes the OpenGL context used by the engine for rendering optimization the - * current context. - */ -- (void)makeResourceContextCurrent; - -/** - * Responds to system messages sent to this controller from the Flutter engine. - */ -- (void)handlePlatformMessage:(const FlutterPlatformMessage*)message; - /** * Calls dispatchMouseEvent:phase: with a phase determined by self.mouseState. * @@ -173,88 +147,12 @@ - (void)setClipboardData:(NSDictionary*)data; @end -#pragma mark - Static methods provided to engine configuration - -/** - * Makes the owned FlutterView the current context. - */ -static bool OnMakeCurrent(FLEViewController* controller) { - [controller.view makeCurrentContext]; - return true; -} - -/** - * Clears the current context. - */ -static bool OnClearCurrent(FLEViewController* controller) { - [NSOpenGLContext clearCurrentContext]; - return true; -} - -/** - * Flushes the GL context as part of the Flutter rendering pipeline. - */ -static bool OnPresent(FLEViewController* controller) { - [controller.view onPresent]; - return true; -} - -/** - * Returns the framebuffer object whose color attachment the engine should render into. - */ -static uint32_t OnFBO(FLEViewController* controller) { - return kDefaultWindowFramebuffer; -} - -/** - * Handles the given platform message by dispatching to the controller. - */ -static void OnPlatformMessage(const FlutterPlatformMessage* message, - FLEViewController* controller) { - [controller handlePlatformMessage:message]; -} - -/** - * Makes the resource context the current context. - */ -static bool OnMakeResourceCurrent(FLEViewController* controller) { - [controller makeResourceContextCurrent]; - return true; -} - -#pragma mark Static methods provided for headless engine configuration - -static bool HeadlessOnMakeCurrent(FLEViewController* controller) { - return false; -} - -static bool HeadlessOnClearCurrent(FLEViewController* controller) { - return false; -} - -static bool HeadlessOnPresent(FLEViewController* controller) { - return false; -} - -static uint32_t HeadlessOnFBO(FLEViewController* controller) { - return kDefaultWindowFramebuffer; -} - -static bool HeadlessOnMakeResourceCurrent(FLEViewController* controller) { - return false; -} - #pragma mark - FLEViewController implementation. @implementation FLEViewController { - FlutterEngine _engine; - // The additional context provided to the Flutter engine for resource loading. NSOpenGLContext* _resourceContext; - // A mapping of channel names to the registered handlers for those channels. - NSMutableDictionary* _messageHandlers; - // The plugin used to handle text input. This is not an FlutterPlugin, so must be owned // separately. FLETextInputPlugin* _textInputPlugin; @@ -276,7 +174,7 @@ @implementation FLEViewController { * Performs initialization that's common between the different init paths. */ static void CommonInit(FLEViewController* controller) { - controller->_messageHandlers = [[NSMutableDictionary alloc] init]; + controller->_engine = [[FLEEngine alloc] initWithViewController:controller]; controller->_additionalKeyResponders = [[NSMutableOrderedSet alloc] init]; controller->_mouseTrackingMode = FlutterMouseTrackingModeInKeyWindow; } @@ -297,12 +195,6 @@ - (instancetype)initWithNibName:(NSString*)nibNameOrNil bundle:(NSBundle*)nibBun return self; } -- (void)dealloc { - if (FlutterEngineShutdown(_engine) == kSuccess) { - _engine = NULL; - } -} - - (void)setView:(NSView*)view { if (_trackingArea) { [self.view removeTrackingArea:_trackingArea]; @@ -327,16 +219,21 @@ - (void)setMouseTrackingMode:(FlutterMouseTrackingMode)mode { - (BOOL)launchEngineWithAssetsPath:(NSURL*)assets commandLineArguments:(NSArray*)arguments { - return [self launchEngineInternalWithAssetsPath:assets - headless:NO - commandLineArguments:arguments]; -} + // Set up the resource context. This is done here rather than in viewDidLoad as there's no + // guarantee that viewDidLoad will be called before the engine is started, and the context must + // be valid by that point. + [self createResourceContext]; + + // Register internal plugins before starting the engine. + [self addInternalPlugins]; -- (BOOL)launchHeadlessEngineWithAssetsPath:(NSURL*)assets - commandLineArguments:(NSArray*)arguments { - return [self launchEngineInternalWithAssetsPath:assets - headless:YES - commandLineArguments:arguments]; + if (![_engine launchEngineWithAssetsPath:assets commandLineArguments:arguments]) { + return NO; + } + // Send the initial user settings such as brightness and text scale factor + // to the engine. + [self sendInitialSettings]; + return YES; } #pragma mark - Framework-internal methods @@ -349,6 +246,10 @@ - (void)removeKeyResponder:(NSResponder*)responder { [self.additionalKeyResponders removeObject:responder]; } +- (void)makeResourceContextCurrent { + [_resourceContext makeCurrentContext]; +} + #pragma mark - Private methods - (void)configureTrackingArea { @@ -384,15 +285,15 @@ - (void)addInternalPlugins { _textInputPlugin = [[FLETextInputPlugin alloc] initWithViewController:self]; _keyEventChannel = [FlutterBasicMessageChannel messageChannelWithName:@"flutter/keyevent" - binaryMessenger:self + binaryMessenger:_engine.binaryMessenger codec:[FlutterJSONMessageCodec sharedInstance]]; _settingsChannel = [FlutterBasicMessageChannel messageChannelWithName:@"flutter/settings" - binaryMessenger:self + binaryMessenger:_engine.binaryMessenger codec:[FlutterJSONMessageCodec sharedInstance]]; _platformChannel = [FlutterMethodChannel methodChannelWithName:@"flutter/platform" - binaryMessenger:self + binaryMessenger:_engine.binaryMessenger codec:[FlutterJSONMethodCodec sharedInstance]]; __weak FLEViewController* weakSelf = self; [_platformChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) { @@ -400,119 +301,12 @@ - (void)addInternalPlugins { }]; } -- (BOOL)launchEngineInternalWithAssetsPath:(NSURL*)assets - headless:(BOOL)headless - commandLineArguments:(NSArray*)arguments { - if (_engine != NULL) { - return NO; - } - - // Set up the resource context. This is done here rather than in viewDidLoad as there's no - // guarantee that viewDidLoad will be called before the engine is started, and the context must - // be valid by that point. - [self createResourceContext]; - - const FlutterRendererConfig config = [FLEViewController createRenderConfigHeadless:headless]; - - // Register internal plugins before starting the engine. - [self addInternalPlugins]; - - // FlutterProjectArgs is expecting a full argv, so when processing it for flags the first - // item is treated as the executable and ignored. Add a dummy value so that all provided arguments - // are used. - const unsigned long argc = arguments.count + 1; - const char** argv = (const char**)malloc(argc * sizeof(const char*)); - argv[0] = "placeholder"; - for (NSUInteger i = 0; i < arguments.count; ++i) { - argv[i + 1] = [arguments[i] UTF8String]; - } - - NSString* icuData = [[NSBundle bundleForClass:[self class]] pathForResource:kICUBundlePath - ofType:nil]; - - FlutterProjectArgs flutterArguments = {}; - flutterArguments.struct_size = sizeof(FlutterProjectArgs); - flutterArguments.assets_path = assets.fileSystemRepresentation; - flutterArguments.icu_data_path = icuData.UTF8String; - flutterArguments.command_line_argc = (int)(argc); - flutterArguments.command_line_argv = argv; - flutterArguments.platform_message_callback = (FlutterPlatformMessageCallback)OnPlatformMessage; - - FlutterEngineResult result = FlutterEngineRun(FLUTTER_ENGINE_VERSION, &config, &flutterArguments, - (__bridge void*)(self), &_engine); - free(argv); - if (result != kSuccess) { - NSLog(@"Failed to start Flutter engine: error %d", result); - return NO; - } - // Send the initial user settings such as brightness and text scale factor - // to the engine. - [self sendInitialSettings]; - return YES; -} - -+ (FlutterRendererConfig)createRenderConfigHeadless:(BOOL)headless { - if (headless) { - const FlutterRendererConfig config = { - .type = kOpenGL, - .open_gl.struct_size = sizeof(FlutterOpenGLRendererConfig), - .open_gl.make_current = (BoolCallback)HeadlessOnMakeCurrent, - .open_gl.clear_current = (BoolCallback)HeadlessOnClearCurrent, - .open_gl.present = (BoolCallback)HeadlessOnPresent, - .open_gl.fbo_callback = (UIntCallback)HeadlessOnFBO, - .open_gl.make_resource_current = (BoolCallback)HeadlessOnMakeResourceCurrent}; - return config; - } else { - const FlutterRendererConfig config = { - .type = kOpenGL, - .open_gl.struct_size = sizeof(FlutterOpenGLRendererConfig), - .open_gl.make_current = (BoolCallback)OnMakeCurrent, - .open_gl.clear_current = (BoolCallback)OnClearCurrent, - .open_gl.present = (BoolCallback)OnPresent, - .open_gl.fbo_callback = (UIntCallback)OnFBO, - .open_gl.make_resource_current = (BoolCallback)OnMakeResourceCurrent}; - return config; - } -} - - (void)createResourceContext { NSOpenGLContext* viewContext = ((NSOpenGLView*)self.view).openGLContext; _resourceContext = [[NSOpenGLContext alloc] initWithFormat:viewContext.pixelFormat shareContext:viewContext]; } -- (void)makeResourceContextCurrent { - [_resourceContext makeCurrentContext]; -} - -- (void)handlePlatformMessage:(const FlutterPlatformMessage*)message { - NSData* messageData = [NSData dataWithBytesNoCopy:(void*)message->message - length:message->message_size - freeWhenDone:NO]; - NSString* channel = @(message->channel); - __block const FlutterPlatformMessageResponseHandle* responseHandle = message->response_handle; - - FlutterBinaryReply binaryResponseHandler = ^(NSData* response) { - if (responseHandle) { - FlutterEngineSendPlatformMessageResponse(self->_engine, responseHandle, - static_cast(response.bytes), - response.length); - responseHandle = NULL; - } else { - NSLog(@"Error: Message responses can be sent only once. Ignoring duplicate response " - "on channel '%@'.", - channel); - } - }; - - FlutterBinaryMessageHandler channelHandler = _messageHandlers[channel]; - if (channelHandler) { - channelHandler(messageData, binaryResponseHandler); - } else { - binaryResponseHandler(nil); - } -} - - (void)dispatchMouseEvent:(nonnull NSEvent*)event { FlutterPointerPhase phase = _mouseState.buttons == 0 ? (_mouseState.flutter_state_is_down ? kUp : kHover) @@ -572,7 +366,7 @@ - (void)dispatchMouseEvent:(NSEvent*)event phase:(FlutterPointerPhase)phase { flutterEvent.scroll_delta_x = -event.scrollingDeltaX * pixelsPerLine * scaleFactor; flutterEvent.scroll_delta_y = -event.scrollingDeltaY * pixelsPerLine * scaleFactor; } - FlutterEngineSendPointerEvent(_engine, &flutterEvent, 1); + [_engine sendPointerEvent:flutterEvent]; // Update tracking of state as reported to Flutter. if (phase == kDown) { @@ -661,61 +455,15 @@ - (void)setClipboardData:(NSDictionary*)data { * Responds to view reshape by notifying the engine of the change in dimensions. */ - (void)viewDidReshape:(NSOpenGLView*)view { - CGRect scaledBounds = [view convertRectToBacking:view.bounds]; - const FlutterWindowMetricsEvent event = { - .struct_size = sizeof(event), - .width = static_cast(scaledBounds.size.width), - .height = static_cast(scaledBounds.size.height), - .pixel_ratio = scaledBounds.size.width / view.bounds.size.width, - }; - FlutterEngineSendWindowMetricsEvent(_engine, &event); -} - -#pragma mark - FlutterBinaryMessengerContainer -- (NSObject*)binaryMessenger { - return self; -} - -#pragma mark - FlutterBinaryMessenger - -- (void)sendOnChannel:(nonnull NSString*)channel message:(nullable NSData*)message { - FlutterPlatformMessage platformMessage = { - .struct_size = sizeof(FlutterPlatformMessage), - .channel = [channel UTF8String], - .message = static_cast(message.bytes), - .message_size = message.length, - }; - - FlutterEngineResult result = FlutterEngineSendPlatformMessage(_engine, &platformMessage); - if (result != kSuccess) { - NSLog(@"Failed to send message to Flutter engine on channel '%@' (%d).", channel, result); - } -} - -- (void)setMessageHandlerOnChannel:(nonnull NSString*)channel - binaryMessageHandler:(nullable FlutterBinaryMessageHandler)handler { - _messageHandlers[channel] = [handler copy]; -} - -#pragma mark - FlutterPluginRegistrar - -- (id)messenger { - return self; -} - -- (void)addMethodCallDelegate:(nonnull id)delegate - channel:(nonnull FlutterMethodChannel*)channel { - [channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) { - [delegate handleMethodCall:call result:result]; - }]; + CGSize scaledSize = [view convertRectToBacking:view.bounds].size; + double pixelRatio = view.bounds.size.width == 0 ? 1 : scaledSize.width / view.bounds.size.width; + [_engine updateWindowMetricsWithSize:scaledSize pixelRatio:pixelRatio]; } #pragma mark - FlutterPluginRegistry - (id)registrarForPlugin:(NSString*)pluginName { - // Currently, the view controller acts as the registrar for all plugins, so the - // name is ignored. - return self; + return [_engine registrarForPlugin:pluginName]; } #pragma mark - NSResponder diff --git a/shell/platform/darwin/macos/framework/Source/FLEViewController_Internal.h b/shell/platform/darwin/macos/framework/Source/FLEViewController_Internal.h index 8fd3fb26519c8..540010db478e7 100644 --- a/shell/platform/darwin/macos/framework/Source/FLEViewController_Internal.h +++ b/shell/platform/darwin/macos/framework/Source/FLEViewController_Internal.h @@ -17,4 +17,10 @@ */ - (void)removeKeyResponder:(nonnull NSResponder*)responder; +/** + * Called when the engine wants to make the resource context current. This must be a context + * that is in the same share group as this controller's view. + */ +- (void)makeResourceContextCurrent; + @end