| Flutter macOS Embedder |

FlutterEngine.mm
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h"
6 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h"
7
8 #include <algorithm>
9 #include <iostream>
10 #include <sstream>
11 #include <vector>
12
13 #include "flutter/common/constants.h"
14 #include "flutter/shell/platform/common/app_lifecycle_state.h"
15 #include "flutter/shell/platform/common/engine_switches.h"
16 #include "flutter/shell/platform/embedder/embedder.h"
17
18 #import "flutter/shell/platform/darwin/common/InternalFlutterSwiftCommon/InternalFlutterSwiftCommon.h"
19 #import "flutter/shell/platform/darwin/common/framework/Source/FlutterBinaryMessengerRelay.h"
20 #import "flutter/shell/platform/darwin/macos/InternalFlutterSwift/InternalFlutterSwift.h"
21 #import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h"
22 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate_Internal.h"
23 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterCompositor.h"
24 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject_Internal.h"
25 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterDisplayLink.h"
26 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterMenuPlugin.h"
27 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.h"
28 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController.h"
29 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterRenderer.h"
30 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterTimeConverter.h"
31 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterVSyncWaiter.h"
32 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterView.h"
33 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h"
34 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewEngineProvider.h"
35
36 #import <CoreVideo/CoreVideo.h>
37 #import <IOSurface/IOSurface.h>
38
39 @class FlutterEngineRegistrar;
40
41 NSString* const kFlutterPlatformChannel = @"flutter/platform";
42 NSString* const kFlutterSettingsChannel = @"flutter/settings";
43 NSString* const kFlutterLifecycleChannel = @"flutter/lifecycle";
44
45 using flutter::kFlutterImplicitViewId;
46
47 /**
48 * Constructs and returns a FlutterLocale struct corresponding to |locale|, which must outlive
49 * the returned struct.
50 */
51 static FlutterLocale FlutterLocaleFromNSLocale(NSLocale* locale) {
52 FlutterLocale flutterLocale = {};
53 flutterLocale.struct_size = sizeof(FlutterLocale);
54 flutterLocale.language_code = [[locale objectForKey:NSLocaleLanguageCode] UTF8String];
55 flutterLocale.country_code = [[locale objectForKey:NSLocaleCountryCode] UTF8String];
56 flutterLocale.script_code = [[locale objectForKey:NSLocaleScriptCode] UTF8String];
57 flutterLocale.variant_code = [[locale objectForKey:NSLocaleVariantCode] UTF8String];
58 return flutterLocale;
59 }
60
61 /// The private notification for voice over.
62 static NSString* const kEnhancedUserInterfaceNotification =
63 @"NSApplicationDidChangeAccessibilityEnhancedUserInterfaceNotification";
64 static NSString* const kEnhancedUserInterfaceKey = @"AXEnhancedUserInterface";
65
66 /// Clipboard plain text format.
67 constexpr char kTextPlainFormat[] = "text/plain";
68
69 #pragma mark -
70
71 // Records an active handler of the messenger (FlutterEngine) that listens to
72 // platform messages on a given channel.
73 @interface FlutterEngineHandlerInfo : NSObject
74
75 - (instancetype)initWithConnection:(NSNumber*)connection
76 handler:(FlutterBinaryMessageHandler)handler;
77
78 @property(nonatomic, readonly) FlutterBinaryMessageHandler handler;
79 @property(nonatomic, readonly) NSNumber* connection;
80
81 @end
82
83 @implementation FlutterEngineHandlerInfo
84 - (instancetype)initWithConnection:(NSNumber*)connection
85 handler:(FlutterBinaryMessageHandler)handler {
86 self = [super init];
87 NSAssert(self, @"Super init cannot be nil");
88 _connection = connection;
89 _handler = handler;
90 return self;
91 }
92 @end
93
94 #pragma mark -
95
96 /**
97 * Private interface declaration for FlutterEngine.
98 */
99 @interface FlutterEngine () <FlutterBinaryMessenger,
100 FlutterMouseCursorPluginDelegate,
101 FlutterKeyboardManagerDelegate,
102 FlutterTextInputPluginDelegate>
103
104 /**
105 * A mutable array that holds one bool value that determines if responses to platform messages are
106 * clear to execute. This value should be read or written only inside of a synchronized block and
107 * will return `NO` after the FlutterEngine has been dealloc'd.
108 */
109 @property(nonatomic, strong) NSMutableArray<NSNumber*>* isResponseValid;
110
111 /**
112 * All delegates added via plugin calls to addApplicationDelegate.
113 */
114 @property(nonatomic, strong) NSPointerArray* pluginAppDelegates;
115
116 /**
117 * All registrars returned from registrarForPlugin:
118 */
119 @property(nonatomic, readonly)
120 NSMutableDictionary<NSString*, FlutterEngineRegistrar*>* pluginRegistrars;
121
122 - (nullable FlutterViewController*)viewControllerForIdentifier:
123 (FlutterViewIdentifier)viewIdentifier;
124
125 /**
126 * An internal method that adds the view controller with the given ID.
127 *
128 * This method assigns the controller with the ID, puts the controller into the
129 * map, and does assertions related to the implicit view ID.
130 */
131 - (void)registerViewController:(FlutterViewController*)controller
132 forIdentifier:(FlutterViewIdentifier)viewIdentifier;
133
134 /**
135 * An internal method that removes the view controller with the given ID.
136 *
137 * This method clears the ID of the controller, removes the controller from the
138 * map. This is an no-op if the view ID is not associated with any view
139 * controllers.
140 */
141 - (void)deregisterViewControllerForIdentifier:(FlutterViewIdentifier)viewIdentifier;
142
143 /**
144 * Shuts down the engine if view requirement is not met, and headless execution
145 * is not allowed.
146 */
147 - (void)shutDownIfNeeded;
148
149 /**
150 * Sends the list of user-preferred locales to the Flutter engine.
151 */
152 - (void)sendUserLocales;
153
154 /**
155 * Handles a platform message from the engine.
156 */
157 - (void)engineCallbackOnPlatformMessage:(const FlutterPlatformMessage*)message;
158
159 /**
160 * Requests that the task be posted back the to the Flutter engine at the target time. The target
161 * time is in the clock used by the Flutter engine.
162 */
163 - (void)postMainThreadTask:(FlutterTask)task targetTimeInNanoseconds:(uint64_t)targetTime;
164
165 /**
166 * Loads the AOT snapshots and instructions from the elf bundle (app_elf_snapshot.so) into _aotData,
167 * if it is present in the assets directory.
168 */
169 - (void)loadAOTData:(NSString*)assetsDir;
170
171 /**
172 * Creates a platform view channel and sets up the method handler.
173 */
174 - (void)setUpPlatformViewChannel;
175
176 /**
177 * Creates an accessibility channel and sets up the message handler.
178 */
179 - (void)setUpAccessibilityChannel;
180
181 /**
182 * Handles messages received from the Flutter engine on the _*Channel channels.
183 */
184 - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result;
185
186 @end
187
188 #pragma mark -
189
190 @implementation FlutterEngineTerminationHandler {
191 __weak FlutterEngine* _engine;
192 FlutterTerminationCallback _terminator;
193 }
194
195 - (instancetype)initWithEngine:(FlutterEngine*)engine
196 terminator:(FlutterTerminationCallback)terminator {
197 self = [super init];
198 _acceptingRequests = NO;
199 _engine = engine;
200 _terminator = terminator ? terminator : ^(id sender) {
201 // Default to actually terminating the application. The terminator exists to
202 // allow tests to override it so that an actual exit doesn't occur.
203 [[NSApplication sharedApplication] terminate:sender];
204 };
205 id<NSApplicationDelegate> appDelegate = [[NSApplication sharedApplication] delegate];
206 if ([appDelegate respondsToSelector:@selector(setTerminationHandler:)]) {
207 FlutterAppDelegate* flutterAppDelegate = reinterpret_cast<FlutterAppDelegate*>(appDelegate);
208 flutterAppDelegate.terminationHandler = self;
209 }
210 return self;
211 }
212
213 // This is called by the method call handler in the engine when the application
214 // requests termination itself.
215 - (void)handleRequestAppExitMethodCall:(NSDictionary<NSString*, id>*)arguments
216 result:(FlutterResult)result {
217 NSString* type = arguments[@"type"];
218 // Ignore the "exitCode" value in the arguments because AppKit doesn't have
219 // any good way to set the process exit code other than calling exit(), and
220 // that bypasses all of the native applicationShouldExit shutdown events,
221 // etc., which we don't want to skip.
222
223 FlutterAppExitType exitType =
224 [type isEqualTo:@"cancelable"] ? kFlutterAppExitTypeCancelable : kFlutterAppExitTypeRequired;
225
226 [self requestApplicationTermination:[NSApplication sharedApplication]
227 exitType:exitType
228 result:result];
229 }
230
231 // This is called by the FlutterAppDelegate whenever any termination request is
232 // received.
233 - (void)requestApplicationTermination:(id)sender
234 exitType:(FlutterAppExitType)type
235 result:(nullable FlutterResult)result {
236 _shouldTerminate = YES;
237 if (![self acceptingRequests]) {
238 // Until the Dart application has signaled that it is ready to handle
239 // termination requests, the app will just terminate when asked.
240 type = kFlutterAppExitTypeRequired;
241 }
242 switch (type) {
243 case kFlutterAppExitTypeCancelable: {
244 FlutterJSONMethodCodec* codec = [FlutterJSONMethodCodec sharedInstance];
245 FlutterMethodCall* methodCall =
246 [FlutterMethodCall methodCallWithMethodName:@"System.requestAppExit" arguments:nil];
247 [_engine sendOnChannel:kFlutterPlatformChannel
248 message:[codec encodeMethodCall:methodCall]
249 binaryReply:^(NSData* _Nullable reply) {
250 NSAssert(_terminator, @"terminator shouldn't be nil");
251 id decoded_reply = [codec decodeEnvelope:reply];
252 if ([decoded_reply isKindOfClass:[FlutterError class]]) {
253 FlutterError* error = (FlutterError*)decoded_reply;
254 NSLog(@"Method call returned error[%@]: %@ %@", [error code], [error message],
255 [error details]);
256 _terminator(sender);
257 return;
258 }
259 if (![decoded_reply isKindOfClass:[NSDictionary class]]) {
260 NSLog(@"Call to System.requestAppExit returned an unexpected object: %@",
261 decoded_reply);
262 _terminator(sender);
263 return;
264 }
265 NSDictionary* replyArgs = (NSDictionary*)decoded_reply;
266 if ([replyArgs[@"response"] isEqual:@"exit"]) {
267 _terminator(sender);
268 } else if ([replyArgs[@"response"] isEqual:@"cancel"]) {
269 _shouldTerminate = NO;
270 }
271 if (result != nil) {
272 result(replyArgs);
273 }
274 }];
275 break;
276 }
277 case kFlutterAppExitTypeRequired:
278 NSAssert(_terminator, @"terminator shouldn't be nil");
279 _terminator(sender);
280 break;
281 }
282 }
283
284 @end
285
286 #pragma mark -
287
288 @implementation FlutterPasteboard
289
290 - (NSInteger)clearContents {
291 return [[NSPasteboard generalPasteboard] clearContents];
292 }
293
294 - (NSString*)stringForType:(NSPasteboardType)dataType {
295 return [[NSPasteboard generalPasteboard] stringForType:dataType];
296 }
297
298 - (BOOL)setString:(nonnull NSString*)string forType:(nonnull NSPasteboardType)dataType {
299 return [[NSPasteboard generalPasteboard] setString:string forType:dataType];
300 }
301
302 @end
303
304 #pragma mark -
305
306 /**
307 * `FlutterPluginRegistrar` implementation handling a single plugin.
308 */
309 @interface FlutterEngineRegistrar : NSObject <FlutterPluginRegistrar>
310 - (instancetype)initWithPlugin:(nonnull NSString*)pluginKey
311 flutterEngine:(nonnull FlutterEngine*)flutterEngine;
312
313 - (nullable NSView*)viewForIdentifier:(FlutterViewIdentifier)viewIdentifier;
314
315 /**
316 * The value published by this plugin, or NSNull if nothing has been published.
317 *
318 * The unusual NSNull is for the documented behavior of valuePublishedByPlugin:.
319 */
320 @property(nonatomic, readonly, nonnull) NSObject* publishedValue;
321 @end
322
323 @implementation FlutterEngineRegistrar {
324 NSString* _pluginKey;
325 __weak FlutterEngine* _flutterEngine;
326 }
327
328 @dynamic view;
329
330 - (instancetype)initWithPlugin:(NSString*)pluginKey flutterEngine:(FlutterEngine*)flutterEngine {
331 self = [super init];
332 if (self) {
333 _pluginKey = [pluginKey copy];
334 _flutterEngine = flutterEngine;
335 _publishedValue = [NSNull null];
336 }
337 return self;
338 }
339
340 #pragma mark - FlutterPluginRegistrar
341
342 - (id<FlutterBinaryMessenger>)messenger {
343 return _flutterEngine.binaryMessenger;
344 }
345
346 - (id<FlutterTextureRegistry>)textures {
347 return _flutterEngine.renderer;
348 }
349
350 - (NSView*)view {
351 return [self viewForIdentifier:kFlutterImplicitViewId];
352 }
353
354 - (NSView*)viewForIdentifier:(FlutterViewIdentifier)viewIdentifier {
355 FlutterViewController* controller = [_flutterEngine viewControllerForIdentifier:viewIdentifier];
356 if (controller == nil) {
357 return nil;
358 }
359 if (!controller.viewLoaded) {
360 [controller loadView];
361 }
362 return controller.flutterView;
363 }
364
365 - (NSViewController*)viewController {
366 return [_flutterEngine viewControllerForIdentifier:kFlutterImplicitViewId];
367 }
368
369 - (void)addMethodCallDelegate:(nonnull id<FlutterPlugin>)delegate
370 channel:(nonnull FlutterMethodChannel*)channel {
371 [channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
372 [delegate handleMethodCall:call result:result];
373 }];
374 }
375
376 - (void)addApplicationDelegate:(NSObject<FlutterAppLifecycleDelegate>*)delegate {
377 id<NSApplicationDelegate> appDelegate = [[NSApplication sharedApplication] delegate];
378 if ([appDelegate conformsToProtocol:@protocol(FlutterAppLifecycleProvider)]) {
379 id<FlutterAppLifecycleProvider> lifeCycleProvider =
380 static_cast<id<FlutterAppLifecycleProvider>>(appDelegate);
381 [lifeCycleProvider addApplicationLifecycleDelegate:delegate];
382 [_flutterEngine.pluginAppDelegates addPointer:(__bridge void*)delegate];
383 }
384 }
385
386 - (void)registerViewFactory:(nonnull NSObject<FlutterPlatformViewFactory>*)factory
387 withId:(nonnull NSString*)factoryId {
388 [[_flutterEngine platformViewController] registerViewFactory:factory withId:factoryId];
389 }
390
391 - (void)publish:(NSObject*)value {
392 _publishedValue = value;
393 }
394
395 - (NSString*)lookupKeyForAsset:(NSString*)asset {
396 return [FlutterDartProject lookupKeyForAsset:asset];
397 }
398
399 - (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package {
400 return [FlutterDartProject lookupKeyForAsset:asset fromPackage:package];
401 }
402
403 @end
404
405 // Callbacks provided to the engine. See the called methods for documentation.
406 #pragma mark - Static methods provided to engine configuration
407
408 static void OnPlatformMessage(const FlutterPlatformMessage* message, void* user_data) {
409 FlutterEngine* engine = (__bridge FlutterEngine*)user_data;
410 [engine engineCallbackOnPlatformMessage:message];
411 }
412
413 #pragma mark -
414
415 @implementation FlutterEngine {
416 // The embedding-API-level engine object.
417 FLUTTER_API_SYMBOL(FlutterEngine) _engine;
418
419 // The project being run by this engine.
420 FlutterDartProject* _project;
421
422 // A mapping of channel names to the registered information for those channels.
423 NSMutableDictionary<NSString*, FlutterEngineHandlerInfo*>* _messengerHandlers;
424
425 // A self-incremental integer to assign to newly assigned channels as
426 // identification.
427 FlutterBinaryMessengerConnection _currentMessengerConnection;
428
429 // Whether the engine can continue running after the view controller is removed.
430 BOOL _allowHeadlessExecution;
431
432 // Pointer to the Dart AOT snapshot and instruction data.
433 _FlutterEngineAOTData* _aotData;
434
435 // _macOSCompositor is created when the engine is created and its destruction is handled by ARC
436 // when the engine is destroyed.
437 std::unique_ptr<flutter::FlutterCompositor> _macOSCompositor;
438
439 // The information of all views attached to this engine mapped from IDs.
440 //
441 // It can't use NSDictionary, because the values need to be weak references.
442 NSMapTable* _viewControllers;
443
444 // FlutterCompositor is copied and used in embedder.cc.
445 FlutterCompositor _compositor;
446
447 // Method channel for platform view functions. These functions include creating, disposing and
448 // mutating a platform view.
449 FlutterMethodChannel* _platformViewsChannel;
450
451 // Used to support creation and deletion of platform views and registering platform view
452 // factories. Lifecycle is tied to the engine.
453 FlutterPlatformViewController* _platformViewController;
454
455 // Used to manage Flutter windows created by the Dart application
456 FlutterWindowController* _windowController;
457
458 // A message channel for sending user settings to the flutter engine.
459 FlutterBasicMessageChannel* _settingsChannel;
460
461 // A message channel for accessibility.
462 FlutterBasicMessageChannel* _accessibilityChannel;
463
464 // A method channel for miscellaneous platform functionality.
465 FlutterMethodChannel* _platformChannel;
466
467 // A method channel for taking screenshots via the rasterizer.
468 FlutterMethodChannel* _screenshotChannel;
469
470 // Whether the application is currently the active application.
471 BOOL _active;
472
473 // Whether any portion of the application is currently visible.
474 BOOL _visible;
475
476 // Proxy to allow plugins, channels to hold a weak reference to the binary messenger (self).
477 FlutterBinaryMessengerRelay* _binaryMessenger;
478
479 // Map from ViewId to vsync waiter. Note that this is modified on main thread
480 // but accessed on UI thread, so access must be @synchronized.
481 NSMapTable<NSNumber*, FlutterVSyncWaiter*>* _vsyncWaiters;
482
483 // Weak reference to last view that received a pointer event. This is used to
484 // pair cursor change with a view.
485 __weak FlutterView* _lastViewWithPointerEvent;
486
487 // Pointer to a keyboard manager.
488 FlutterKeyboardManager* _keyboardManager;
489
490 // The text input plugin that handles text editing state for text fields.
491 FlutterTextInputPlugin* _textInputPlugin;
492
493 // Whether the engine is running in multi-window mode. This affects behavior
494 // when adding view controller (it will fail when calling multiple times without
495 // _multiviewEnabled).
496 BOOL _multiViewEnabled;
497
498 // View identifier for the next view to be created.
499 // Only used when multiview is enabled.
500 FlutterViewIdentifier _nextViewIdentifier;
501 }
502
503 @synthesize windowController = _windowController;
504 @synthesize project = _project;
505
506 - (instancetype)initWithName:(NSString*)labelPrefix project:(FlutterDartProject*)project {
507 return [self initWithName:labelPrefix project:project allowHeadlessExecution:YES];
508 }
509
510 static const int kMainThreadPriority = 47;
511
512 static void SetThreadPriority(FlutterThreadPriority priority) {
513 if (priority == kDisplay || priority == kRaster) {
514 pthread_t thread = pthread_self();
515 sched_param param;
516 int policy;
517 if (!pthread_getschedparam(thread, &policy, &param)) {
518 param.sched_priority = kMainThreadPriority;
519 pthread_setschedparam(thread, policy, &param);
520 }
521 pthread_set_qos_class_self_np(QOS_CLASS_USER_INTERACTIVE, 0);
522 }
523 }
524
525 - (instancetype)initWithName:(NSString*)labelPrefix
526 project:(FlutterDartProject*)project
527 allowHeadlessExecution:(BOOL)allowHeadlessExecution {
528 self = [super init];
529 NSAssert(self, @"Super init cannot be nil");
530
531 [FlutterRunLoop ensureMainLoopInitialized];
532
533 _pasteboard = [[FlutterPasteboard alloc] init];
534 _active = NO;
535 _visible = NO;
536 _project = project ?: [[FlutterDartProject alloc] init];
537 _messengerHandlers = [[NSMutableDictionary alloc] init];
538 _pluginAppDelegates = [NSPointerArray weakObjectsPointerArray];
539 _pluginRegistrars = [[NSMutableDictionary alloc] init];
540 _currentMessengerConnection = 1;
541 _allowHeadlessExecution = allowHeadlessExecution;
542 _semanticsEnabled = NO;
543 _binaryMessenger = [[FlutterBinaryMessengerRelay alloc] initWithParent:self];
544 _isResponseValid = [[NSMutableArray alloc] initWithCapacity:1];
545 [_isResponseValid addObject:@YES];
546 _keyboardManager = [[FlutterKeyboardManager alloc] initWithDelegate:self];
547 _textInputPlugin = [[FlutterTextInputPlugin alloc] initWithDelegate:self];
548 _multiViewEnabled = NO;
549 _nextViewIdentifier = 1;
550
551 _embedderAPI.struct_size = sizeof(FlutterEngineProcTable);
552 FlutterEngineGetProcAddresses(&_embedderAPI);
553
554 _viewControllers = [NSMapTable weakToWeakObjectsMapTable];
555 _renderer = [[FlutterRenderer alloc] initWithFlutterEngine:self];
556
557 NSNotificationCenter* notificationCenter = [NSNotificationCenter defaultCenter];
558 [notificationCenter addObserver:self
559 selector:@selector(sendUserLocales)
560 name:NSCurrentLocaleDidChangeNotification
561 object:nil];
562
563 _platformViewController = [[FlutterPlatformViewController alloc] init];
564 // The macOS compositor must be initialized in the initializer because it is
565 // used when adding views, which might happen before runWithEntrypoint.
566 _macOSCompositor = std::make_unique<flutter::FlutterCompositor>(
567 [[FlutterViewEngineProvider alloc] initWithEngine:self],
568 [[FlutterTimeConverter alloc] initWithEngine:self], _platformViewController);
569
570 [self setUpPlatformViewChannel];
571
572 _windowController = [[FlutterWindowController alloc] init];
573 _windowController.engine = self;
574
575 [self setUpAccessibilityChannel];
576 [self setUpNotificationCenterListeners];
577 id<NSApplicationDelegate> appDelegate = [[NSApplication sharedApplication] delegate];
578 if ([appDelegate conformsToProtocol:@protocol(FlutterAppLifecycleProvider)]) {
579 _terminationHandler = [[FlutterEngineTerminationHandler alloc] initWithEngine:self
580 terminator:nil];
581 id<FlutterAppLifecycleProvider> lifecycleProvider =
582 static_cast<id<FlutterAppLifecycleProvider>>(appDelegate);
583 [lifecycleProvider addApplicationLifecycleDelegate:self];
584 } else {
585 _terminationHandler = nil;
586 }
587
588 _vsyncWaiters = [NSMapTable strongToStrongObjectsMapTable];
589
590 return self;
591 }
592
593 - (void)dealloc {
594 id<NSApplicationDelegate> appDelegate = [[NSApplication sharedApplication] delegate];
595 if ([appDelegate conformsToProtocol:@protocol(FlutterAppLifecycleProvider)]) {
596 id<FlutterAppLifecycleProvider> lifecycleProvider =
597 static_cast<id<FlutterAppLifecycleProvider>>(appDelegate);
598 [lifecycleProvider removeApplicationLifecycleDelegate:self];
599
600 // Unregister any plugins that registered as app delegates, since they are not guaranteed to
601 // live after the engine is destroyed, and their delegation registration is intended to be bound
602 // to the engine and its lifetime.
603 for (id<FlutterAppLifecycleDelegate> delegate in _pluginAppDelegates) {
604 if (delegate) {
605 [lifecycleProvider removeApplicationLifecycleDelegate:delegate];
606 }
607 }
608 }
609 // Clear any published values, just in case a plugin has created a retain cycle with the
610 // registrar.
611 for (NSString* pluginName in _pluginRegistrars) {
612 [_pluginRegistrars[pluginName] publish:[NSNull null]];
613 }
614 @synchronized(_isResponseValid) {
615 [_isResponseValid removeAllObjects];
616 [_isResponseValid addObject:@NO];
617 }
618 [self shutDownEngine];
619 if (_aotData) {
620 _embedderAPI.CollectAOTData(_aotData);
621 }
622 }
623
624 - (FlutterTaskRunnerDescription)createPlatformThreadTaskDescription {
625 static size_t sTaskRunnerIdentifiers = 0;
626 FlutterTaskRunnerDescription cocoa_task_runner_description = {
627 .struct_size = sizeof(FlutterTaskRunnerDescription),
628 // Retain for use in post_task_callback. Released in destruction_callback.
629 .user_data = (__bridge_retained void*)self,
630 .runs_task_on_current_thread_callback = [](void* user_data) -> bool {
631 return [[NSThread currentThread] isMainThread];
632 },
633 .post_task_callback = [](FlutterTask task, uint64_t target_time_nanos,
634 void* user_data) -> void {
635 FlutterEngine* engine = (__bridge FlutterEngine*)user_data;
636 [engine postMainThreadTask:task targetTimeInNanoseconds:target_time_nanos];
637 },
638 .identifier = ++sTaskRunnerIdentifiers,
639 .destruction_callback =
640 [](void* user_data) {
641 // Balancing release for the retain when setting user_data above.
642 FlutterEngine* engine = (__bridge_transfer FlutterEngine*)user_data;
643 engine = nil;
644 },
645 };
646 return cocoa_task_runner_description;
647 }
648
649 - (void)onFocusChangeRequest:(const FlutterViewFocusChangeRequest*)request {
650 FlutterViewController* controller = [self viewControllerForIdentifier:request->view_id];
651 if (controller == nil) {
652 return;
653 }
654 if (request->state == kFocused) {
655 [controller.flutterView.window makeFirstResponder:controller.flutterView];
656 }
657 }
658
659 - (BOOL)runWithEntrypoint:(NSString*)entrypoint {
660 if (self.running) {
661 return NO;
662 }
663
664 if (!_allowHeadlessExecution && [_viewControllers count] == 0) {
665 NSLog(@"Attempted to run an engine with no view controller without headless mode enabled.");
666 return NO;
667 }
668
669 [self addInternalPlugins];
670
671 // The first argument of argv is required to be the executable name.
672 std::vector<const char*> argv = {[self.executableName UTF8String]};
673 std::vector<std::string> switches = self.switches;
674
675 // Enable Impeller only if specifically asked for from the project or cmdline arguments.
676 if (_project.enableImpeller ||
677 std::find(switches.begin(), switches.end(), "--enable-impeller=true") != switches.end()) {
678 switches.push_back("--enable-impeller=true");
679 }
680 switches.push_back(_project.enableSDFs ? "--impeller-use-sdfs=true"
681 : "--impeller-use-sdfs=false");
682
683 if (_project.enableFlutterGPU ||
684 std::find(switches.begin(), switches.end(), "--enable-flutter-gpu=true") != switches.end()) {
685 switches.push_back("--enable-flutter-gpu=true");
686 }
687
688 std::transform(switches.begin(), switches.end(), std::back_inserter(argv),
689 [](const std::string& arg) -> const char* { return arg.c_str(); });
690
691 std::vector<const char*> dartEntrypointArgs;
692 for (NSString* argument in [_project dartEntrypointArguments]) {
693 dartEntrypointArgs.push_back([argument UTF8String]);
694 }
695
696 FlutterProjectArgs flutterArguments = {};
697 flutterArguments.struct_size = sizeof(FlutterProjectArgs);
698 flutterArguments.assets_path = _project.assetsPath.UTF8String;
699 flutterArguments.icu_data_path = _project.ICUDataPath.UTF8String;
700 flutterArguments.command_line_argc = static_cast<int>(argv.size());
701 flutterArguments.command_line_argv = argv.empty() ? nullptr : argv.data();
702 flutterArguments.platform_message_callback = (FlutterPlatformMessageCallback)OnPlatformMessage;
703 flutterArguments.update_semantics_callback2 = [](const FlutterSemanticsUpdate2* update,
704 void* user_data) {
705 // TODO(dkwingsmt): This callback only supports single-view, therefore it
706 // only operates on the implicit view. To support multi-view, we need a
707 // way to pass in the ID (probably through FlutterSemanticsUpdate).
708 FlutterEngine* engine = (__bridge FlutterEngine*)user_data;
709 [[engine viewControllerForIdentifier:kFlutterImplicitViewId] updateSemantics:update];
710 };
711 flutterArguments.custom_dart_entrypoint = entrypoint.UTF8String;
712 flutterArguments.shutdown_dart_vm_when_done = true;
713 flutterArguments.dart_entrypoint_argc = dartEntrypointArgs.size();
714 flutterArguments.dart_entrypoint_argv = dartEntrypointArgs.data();
715 flutterArguments.root_isolate_create_callback = _project.rootIsolateCreateCallback;
716 flutterArguments.log_message_callback = [](const char* tag, const char* message,
717 void* user_data) {
718 std::stringstream stream;
719 if (tag && tag[0]) {
720 stream << tag << ": ";
721 }
722 stream << message;
723 std::string log = stream.str();
724 [FlutterLogger logDirect:[NSString stringWithUTF8String:log.c_str()]];
725 };
726
727 flutterArguments.engine_id = reinterpret_cast<int64_t>((__bridge void*)self);
728 flutterArguments.enable_wide_gamut = _project.enableWideGamut;
729
730 BOOL mergedPlatformUIThread = YES;
731 NSNumber* enableMergedPlatformUIThread =
732 [[NSBundle mainBundle] objectForInfoDictionaryKey:@"FLTEnableMergedPlatformUIThread"];
733 if (enableMergedPlatformUIThread != nil) {
734 mergedPlatformUIThread = enableMergedPlatformUIThread.boolValue;
735 }
736
737 if (mergedPlatformUIThread) {
738 NSLog(@"Running with merged UI and platform thread. Experimental.");
739 }
740
741 // The task description needs to be created separately for platform task
742 // runner and UI task runner because each one has their own __bridge_retained
743 // engine user data.
744 FlutterTaskRunnerDescription platformTaskRunnerDescription =
745 [self createPlatformThreadTaskDescription];
746 std::optional<FlutterTaskRunnerDescription> uiTaskRunnerDescription;
747 if (mergedPlatformUIThread) {
748 uiTaskRunnerDescription = [self createPlatformThreadTaskDescription];
749 }
750
751 const FlutterCustomTaskRunners custom_task_runners = {
752 .struct_size = sizeof(FlutterCustomTaskRunners),
753 .platform_task_runner = &platformTaskRunnerDescription,
754 .thread_priority_setter = SetThreadPriority,
755 .ui_task_runner = uiTaskRunnerDescription ? &uiTaskRunnerDescription.value() : nullptr,
756 };
757 flutterArguments.custom_task_runners = &custom_task_runners;
758
759 [self loadAOTData:_project.assetsPath];
760 if (_aotData) {
761 flutterArguments.aot_data = _aotData;
762 }
763
764 flutterArguments.compositor = [self createFlutterCompositor];
765
766 flutterArguments.on_pre_engine_restart_callback = [](void* user_data) {
767 FlutterEngine* engine = (__bridge FlutterEngine*)user_data;
768 [engine engineCallbackOnPreEngineRestart];
769 };
770
771 flutterArguments.vsync_callback = [](void* user_data, intptr_t baton) {
772 FlutterEngine* engine = (__bridge FlutterEngine*)user_data;
773 [engine onVSync:baton];
774 };
775
776 flutterArguments.view_focus_change_request_callback =
777 [](const FlutterViewFocusChangeRequest* request, void* user_data) {
778 FlutterEngine* engine = (__bridge FlutterEngine*)user_data;
779 [engine onFocusChangeRequest:request];
780 };
781
782 FlutterRendererConfig rendererConfig = [_renderer createRendererConfig];
783 FlutterEngineResult result = _embedderAPI.Initialize(
784 FLUTTER_ENGINE_VERSION, &rendererConfig, &flutterArguments, (__bridge void*)(self), &_engine);
785 if (result != kSuccess) {
786 NSLog(@"Failed to initialize Flutter engine: error %d", result);
787 return NO;
788 }
789
790 result = _embedderAPI.RunInitialized(_engine);
791 if (result != kSuccess) {
792 NSLog(@"Failed to run an initialized engine: error %d", result);
793 return NO;
794 }
795
796 [self sendUserLocales];
797
798 // Update window metric for all view controllers.
799 NSEnumerator* viewControllerEnumerator = [_viewControllers objectEnumerator];
800 FlutterViewController* nextViewController;
801 while ((nextViewController = [viewControllerEnumerator nextObject])) {
802 [self updateWindowMetricsForViewController:nextViewController];
803 }
804
805 [self updateDisplayConfig];
806 // Send the initial user settings such as brightness and text scale factor
807 // to the engine.
808 [self sendInitialSettings];
809 return YES;
810 }
811
812 - (void)loadAOTData:(NSString*)assetsDir {
813 if (!_embedderAPI.RunsAOTCompiledDartCode()) {
814 return;
815 }
816
817 BOOL isDirOut = false; // required for NSFileManager fileExistsAtPath.
818 NSFileManager* fileManager = [NSFileManager defaultManager];
819
820 // This is the location where the test fixture places the snapshot file.
821 // For applications built by Flutter tool, this is in "App.framework".
822 NSString* elfPath = [NSString pathWithComponents:@[ assetsDir, @"app_elf_snapshot.so" ]];
823
824 if (![fileManager fileExistsAtPath:elfPath isDirectory:&isDirOut]) {
825 return;
826 }
827
828 FlutterEngineAOTDataSource source = {};
829 source.type = kFlutterEngineAOTDataSourceTypeElfPath;
830 source.elf_path = [elfPath cStringUsingEncoding:NSUTF8StringEncoding];
831
832 auto result = _embedderAPI.CreateAOTData(&source, &_aotData);
833 if (result != kSuccess) {
834 NSLog(@"Failed to load AOT data from: %@", elfPath);
835 }
836 }
837
838 - (void)registerViewController:(FlutterViewController*)controller
839 forIdentifier:(FlutterViewIdentifier)viewIdentifier {
840 _macOSCompositor->AddView(viewIdentifier);
841 NSAssert(controller != nil, @"The controller must not be nil.");
842 if (!_multiViewEnabled) {
843 NSAssert(controller.engine == nil,
844 @"The FlutterViewController is unexpectedly attached to "
845 @"engine %@ before initialization.",
846 controller.engine);
847 }
848 NSAssert([_viewControllers objectForKey:@(viewIdentifier)] == nil,
849 @"The requested view ID is occupied.");
850 [_viewControllers setObject:controller forKey:@(viewIdentifier)];
851 [controller setUpWithEngine:self viewIdentifier:viewIdentifier];
852 NSAssert(controller.viewIdentifier == viewIdentifier, @"Failed to assign view ID.");
853 // Verify that the controller's property are updated accordingly. Failing the
854 // assertions is likely because either the FlutterViewController or the
855 // FlutterEngine is mocked. Please subclass these classes instead.
856 NSAssert(controller.attached, @"The FlutterViewController should switch to the attached mode "
857 @"after it is added to a FlutterEngine.");
858 NSAssert(controller.engine == self,
859 @"The FlutterViewController was added to %@, but its engine unexpectedly became %@.",
860 self, controller.engine);
861
862 if (controller.viewLoaded) {
863 [self viewControllerViewDidLoad:controller];
864 }
865
866 if (viewIdentifier != kFlutterImplicitViewId) {
867 // These will be overriden immediately after the FlutterView is created
868 // by actual values.
869 FlutterWindowMetricsEvent metrics{
870 .struct_size = sizeof(FlutterWindowMetricsEvent),
871 .width = 0,
872 .height = 0,
873 .pixel_ratio = 1.0,
874 };
875 bool added = false;
876 FlutterAddViewInfo info{.struct_size = sizeof(FlutterAddViewInfo),
877 .view_id = viewIdentifier,
878 .view_metrics = &metrics,
879 .user_data = &added,
880 .add_view_callback = [](const FlutterAddViewResult* r) {
881 auto added = reinterpret_cast<bool*>(r->user_data);
882 *added = true;
883 }};
884 // The callback should be called synchronously from platform thread.
885 _embedderAPI.AddView(_engine, &info);
886 FML_DCHECK(added);
887 if (!added) {
888 NSLog(@"Failed to add view with ID %llu", viewIdentifier);
889 }
890 }
891 }
892
893 - (void)viewControllerViewDidLoad:(FlutterViewController*)viewController {
894 __weak FlutterEngine* weakSelf = self;
895 FlutterTimeConverter* timeConverter = [[FlutterTimeConverter alloc] initWithEngine:self];
896 FlutterVSyncWaiter* waiter = [[FlutterVSyncWaiter alloc]
897 initWithDisplayLink:[FlutterDisplayLink displayLinkWithView:viewController.view]
898 block:^(CFTimeInterval timestamp, CFTimeInterval targetTimestamp,
899 uintptr_t baton) {
900 uint64_t timeNanos = [timeConverter CAMediaTimeToEngineTime:timestamp];
901 uint64_t targetTimeNanos =
902 [timeConverter CAMediaTimeToEngineTime:targetTimestamp];
903 FlutterEngine* engine = weakSelf;
904 if (engine) {
905 engine->_embedderAPI.OnVsync(_engine, baton, timeNanos, targetTimeNanos);
906 }
907 }];
908 @synchronized(_vsyncWaiters) {
909 FML_DCHECK([_vsyncWaiters objectForKey:@(viewController.viewIdentifier)] == nil);
910 [_vsyncWaiters setObject:waiter forKey:@(viewController.viewIdentifier)];
911 }
912 }
913
914 - (void)deregisterViewControllerForIdentifier:(FlutterViewIdentifier)viewIdentifier {
915 if (viewIdentifier != kFlutterImplicitViewId) {
916 bool removed = false;
917 FlutterRemoveViewInfo info;
918 info.struct_size = sizeof(FlutterRemoveViewInfo);
919 info.view_id = viewIdentifier;
920 info.user_data = &removed;
921 // RemoveViewCallback is not finished synchronously, the remove_view_callback
922 // is called from raster thread when the engine knows for sure that the resources
923 // associated with the view are no longer needed.
924 info.remove_view_callback = [](const FlutterRemoveViewResult* r) {
925 auto removed = reinterpret_cast<bool*>(r->user_data);
926 [FlutterRunLoop.mainRunLoop performBlock:^{
927 *removed = true;
928 }];
929 };
930 _embedderAPI.RemoveView(_engine, &info);
931 while (!removed) {
932 [[FlutterRunLoop mainRunLoop] pollFlutterMessagesOnce];
933 }
934 }
935
936 _macOSCompositor->RemoveView(viewIdentifier);
937
938 FlutterViewController* controller = [self viewControllerForIdentifier:viewIdentifier];
939 // The controller can be nil. The engine stores only a weak ref, and this
940 // method could have been called from the controller's dealloc.
941 if (controller != nil) {
942 [controller detachFromEngine];
943 NSAssert(!controller.attached,
944 @"The FlutterViewController unexpectedly stays attached after being removed. "
945 @"In unit tests, this is likely because either the FlutterViewController or "
946 @"the FlutterEngine is mocked. Please subclass these classes instead.");
947 }
948 [_viewControllers removeObjectForKey:@(viewIdentifier)];
949
950 FlutterVSyncWaiter* waiter = nil;
951 @synchronized(_vsyncWaiters) {
952 waiter = [_vsyncWaiters objectForKey:@(viewIdentifier)];
953 [_vsyncWaiters removeObjectForKey:@(viewIdentifier)];
954 }
955 [waiter invalidate];
956 }
957
958 - (void)shutDownIfNeeded {
959 if ([_viewControllers count] == 0 && !_allowHeadlessExecution) {
960 [self shutDownEngine];
961 }
962 }
963
964 - (FlutterViewController*)viewControllerForIdentifier:(FlutterViewIdentifier)viewIdentifier {
965 FlutterViewController* controller = [_viewControllers objectForKey:@(viewIdentifier)];
966 NSAssert(controller == nil || controller.viewIdentifier == viewIdentifier,
967 @"The stored controller has unexpected view ID.");
968 return controller;
969 }
970
971 - (void)setViewController:(FlutterViewController*)controller {
972 FlutterViewController* currentController =
973 [_viewControllers objectForKey:@(kFlutterImplicitViewId)];
974 if (currentController == controller) {
975 // From nil to nil, or from non-nil to the same controller.
976 return;
977 }
978 if (currentController == nil && controller != nil) {
979 // From nil to non-nil.
980 NSAssert(controller.engine == nil,
981 @"Failed to set view controller to the engine: "
982 @"The given FlutterViewController is already attached to an engine %@. "
983 @"If you wanted to create an FlutterViewController and set it to an existing engine, "
984 @"you should use FlutterViewController#init(engine:, nibName, bundle:) instead.",
985 controller.engine);
986 [self registerViewController:controller forIdentifier:kFlutterImplicitViewId];
987 } else if (currentController != nil && controller == nil) {
988 NSAssert(currentController.viewIdentifier == kFlutterImplicitViewId,
989 @"The default controller has an unexpected ID %llu", currentController.viewIdentifier);
990 // From non-nil to nil.
991 [self deregisterViewControllerForIdentifier:kFlutterImplicitViewId];
992 [self shutDownIfNeeded];
993 } else {
994 // From non-nil to a different non-nil view controller.
995 NSAssert(NO,
996 @"Failed to set view controller to the engine: "
997 @"The engine already has an implicit view controller %@. "
998 @"If you wanted to make the implicit view render in a different window, "
999 @"you should attach the current view controller to the window instead.",
1000 [_viewControllers objectForKey:@(kFlutterImplicitViewId)]);
1001 }
1002 }
1003
1004 - (FlutterViewController*)viewController {
1005 return [self viewControllerForIdentifier:kFlutterImplicitViewId];
1006 }
1007
1008 - (FlutterCompositor*)createFlutterCompositor {
1009 _compositor = {};
1010 _compositor.struct_size = sizeof(FlutterCompositor);
1011 _compositor.user_data = _macOSCompositor.get();
1012
1013 _compositor.create_backing_store_callback = [](const FlutterBackingStoreConfig* config, //
1014 FlutterBackingStore* backing_store_out, //
1015 void* user_data //
1016 ) {
1017 return reinterpret_cast<flutter::FlutterCompositor*>(user_data)->CreateBackingStore(
1018 config, backing_store_out);
1019 };
1020
1021 _compositor.collect_backing_store_callback = [](const FlutterBackingStore* backing_store, //
1022 void* user_data //
1023 ) { return true; };
1024
1025 _compositor.present_view_callback = [](const FlutterPresentViewInfo* info) {
1026 return reinterpret_cast<flutter::FlutterCompositor*>(info->user_data)
1027 ->Present(info->view_id, info->layers, info->layers_count);
1028 };
1029
1030 _compositor.avoid_backing_store_cache = true;
1031
1032 return &_compositor;
1033 }
1034
1035 - (id<FlutterBinaryMessenger>)binaryMessenger {
1036 return _binaryMessenger;
1037 }
1038
1039 #pragma mark - Framework-internal methods
1040
1041 - (void)addViewController:(FlutterViewController*)controller {
1042 if (!_multiViewEnabled) {
1043 // When multiview is disabled, the engine will only assign views to the implicit view ID.
1044 // The implicit view ID can be reused if and only if the implicit view is unassigned.
1045 NSAssert(self.viewController == nil,
1046 @"The engine already has a view controller for the implicit view.");
1047 self.viewController = controller;
1048 } else {
1049 // When multiview is enabled, the engine will assign views to a self-incrementing ID.
1050 // The implicit view ID can not be reused.
1051 FlutterViewIdentifier viewIdentifier = _nextViewIdentifier++;
1052 [self registerViewController:controller forIdentifier:viewIdentifier];
1053 }
1054 }
1055
1056 - (void)enableMultiView {
1057 if (!_multiViewEnabled) {
1058 NSAssert(self.viewController == nil,
1059 @"Multiview can only be enabled before adding any view controllers.");
1060 _multiViewEnabled = YES;
1061 }
1062 }
1063
1064 - (void)windowDidBecomeKey:(FlutterViewIdentifier)viewIdentifier {
1065 FlutterViewFocusEvent event{
1066 .struct_size = sizeof(FlutterViewFocusEvent),
1067 .view_id = viewIdentifier,
1068 .state = kFocused,
1069 .direction = kUndefined,
1070 };
1071 _embedderAPI.SendViewFocusEvent(_engine, &event);
1072 }
1073
1074 - (void)windowDidResignKey:(FlutterViewIdentifier)viewIdentifier {
1075 FlutterViewFocusEvent event{
1076 .struct_size = sizeof(FlutterViewFocusEvent),
1077 .view_id = viewIdentifier,
1078 .state = kUnfocused,
1079 .direction = kUndefined,
1080 };
1081 _embedderAPI.SendViewFocusEvent(_engine, &event);
1082 }
1083
1084 - (void)removeViewController:(nonnull FlutterViewController*)viewController {
1085 [self deregisterViewControllerForIdentifier:viewController.viewIdentifier];
1086 [self shutDownIfNeeded];
1087 }
1088
1089 - (BOOL)running {
1090 return _engine != nullptr;
1091 }
1092
1093 - (void)updateDisplayConfig:(NSNotification*)notification {
1094 [self updateDisplayConfig];
1095 }
1096
1097 - (NSArray<NSScreen*>*)screens {
1098 return [NSScreen screens];
1099 }
1100
1101 - (void)updateDisplayConfig {
1102 if (!_engine) {
1103 return;
1104 }
1105
1106 std::vector<FlutterEngineDisplay> displays;
1107 for (NSScreen* screen : [self screens]) {
1108 CGDirectDisplayID displayID =
1109 static_cast<CGDirectDisplayID>([screen.deviceDescription[@"NSScreenNumber"] integerValue]);
1110
1111 double devicePixelRatio = screen.backingScaleFactor;
1112 FlutterEngineDisplay display;
1113 display.struct_size = sizeof(display);
1114 display.display_id = displayID;
1115 display.single_display = false;
1116 display.width = static_cast<size_t>(screen.frame.size.width) * devicePixelRatio;
1117 display.height = static_cast<size_t>(screen.frame.size.height) * devicePixelRatio;
1118 display.device_pixel_ratio = devicePixelRatio;
1119
1120 CVDisplayLinkRef displayLinkRef = nil;
1121 CVReturn error = CVDisplayLinkCreateWithCGDisplay(displayID, &displayLinkRef);
1122
1123 if (error == 0) {
1124 CVTime nominal = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(displayLinkRef);
1125 if (!(nominal.flags & kCVTimeIsIndefinite)) {
1126 double refreshRate = static_cast<double>(nominal.timeScale) / nominal.timeValue;
1127 display.refresh_rate = round(refreshRate);
1128 }
1129 CVDisplayLinkRelease(displayLinkRef);
1130 } else {
1131 display.refresh_rate = 0;
1132 }
1133
1134 displays.push_back(display);
1135 }
1136 _embedderAPI.NotifyDisplayUpdate(_engine, kFlutterEngineDisplaysUpdateTypeStartup,
1137 displays.data(), displays.size());
1138 }
1139
1140 - (void)onSettingsChanged:(NSNotification*)notification {
1141 // TODO(jonahwilliams): https://github.com/flutter/flutter/issues/32015.
1142 NSString* brightness =
1143 [[NSUserDefaults standardUserDefaults] stringForKey:@"AppleInterfaceStyle"];
1144 [_settingsChannel sendMessage:@{
1145 @"platformBrightness" : [brightness isEqualToString:@"Dark"] ? @"dark" : @"light",
1146 // TODO(jonahwilliams): https://github.com/flutter/flutter/issues/32006.
1147 @"textScaleFactor" : @1.0,
1148 @"alwaysUse24HourFormat" : @([FlutterHourFormat isAlwaysUse24HourFormat]),
1149 }];
1150 }
1151
1152 - (void)sendInitialSettings {
1153 // TODO(jonahwilliams): https://github.com/flutter/flutter/issues/32015.
1154 [[NSDistributedNotificationCenter defaultCenter]
1155 addObserver:self
1156 selector:@selector(onSettingsChanged:)
1157 name:@"AppleInterfaceThemeChangedNotification"
1158 object:nil];
1159 [self onSettingsChanged:nil];
1160 }
1161
1162 - (FlutterEngineProcTable&)embedderAPI {
1163 return _embedderAPI;
1164 }
1165
1166 - (nonnull NSString*)executableName {
1167 return [[[NSProcessInfo processInfo] arguments] firstObject] ?: @"Flutter";
1168 }
1169
1170 - (void)updateWindowMetricsForViewController:(FlutterViewController*)viewController {
1171 if (!_engine || !viewController || !viewController.viewLoaded) {
1172 return;
1173 }
1174 NSAssert([self viewControllerForIdentifier:viewController.viewIdentifier] == viewController,
1175 @"The provided view controller is not attached to this engine.");
1176 FlutterView* view = viewController.flutterView;
1177 CGRect scaledBounds = [view convertRectToBacking:view.bounds];
1178 CGSize scaledSize = scaledBounds.size;
1179 double pixelRatio = view.layer.contentsScale;
1180 auto displayId = [view.window.screen.deviceDescription[@"NSScreenNumber"] integerValue];
1181 FlutterWindowMetricsEvent windowMetricsEvent = {
1182 .struct_size = sizeof(windowMetricsEvent),
1183 .width = static_cast<size_t>(scaledSize.width),
1184 .height = static_cast<size_t>(scaledSize.height),
1185 .pixel_ratio = pixelRatio,
1186 .left = static_cast<size_t>(scaledBounds.origin.x),
1187 .top = static_cast<size_t>(scaledBounds.origin.y),
1188 .display_id = static_cast<uint64_t>(displayId),
1189 .view_id = viewController.viewIdentifier,
1190 };
1191 if (view.sizedToContents) {
1192 CGSize maximumContentSize = [view convertSizeToBacking:view.maximumContentSize];
1193 CGSize minimumContentSize = [view convertSizeToBacking:view.minimumContentSize];
1194 windowMetricsEvent.has_constraints = true;
1195 windowMetricsEvent.min_width_constraint = static_cast<size_t>(minimumContentSize.width);
1196 windowMetricsEvent.min_height_constraint = static_cast<size_t>(minimumContentSize.height);
1197 windowMetricsEvent.max_width_constraint = static_cast<size_t>(maximumContentSize.width);
1198 windowMetricsEvent.max_height_constraint = static_cast<size_t>(maximumContentSize.height);
1199 } else {
1200 windowMetricsEvent.min_width_constraint = static_cast<size_t>(scaledSize.width);
1201 windowMetricsEvent.min_height_constraint = static_cast<size_t>(scaledSize.height);
1202 windowMetricsEvent.max_width_constraint = static_cast<size_t>(scaledSize.width);
1203 windowMetricsEvent.max_height_constraint = static_cast<size_t>(scaledSize.height);
1204 }
1205 _embedderAPI.SendWindowMetricsEvent(_engine, &windowMetricsEvent);
1206 }
1207
1208 - (void)sendPointerEvent:(const FlutterPointerEvent&)event {
1209 _embedderAPI.SendPointerEvent(_engine, &event, 1);
1210 _lastViewWithPointerEvent = [self viewControllerForIdentifier:kFlutterImplicitViewId].flutterView;
1211 }
1212
1213 - (void)setSemanticsEnabled:(BOOL)enabled {
1214 if (_semanticsEnabled == enabled) {
1215 return;
1216 }
1217 _semanticsEnabled = enabled;
1218
1219 // Update all view controllers' bridges.
1220 NSEnumerator* viewControllerEnumerator = [_viewControllers objectEnumerator];
1221 FlutterViewController* nextViewController;
1222 while ((nextViewController = [viewControllerEnumerator nextObject])) {
1223 [nextViewController notifySemanticsEnabledChanged];
1224 }
1225
1226 _embedderAPI.UpdateSemanticsEnabled(_engine, _semanticsEnabled);
1227 }
1228
1229 - (void)dispatchSemanticsAction:(FlutterSemanticsAction)action
1230 toTarget:(uint16_t)target
1231 withData:(fml::MallocMapping)data {
1232 _embedderAPI.DispatchSemanticsAction(_engine, target, action, data.GetMapping(), data.GetSize());
1233 }
1234
1235 - (FlutterPlatformViewController*)platformViewController {
1236 return _platformViewController;
1237 }
1238
1239 #pragma mark - Private methods
1240
1241 - (void)sendUserLocales {
1242 if (!self.running) {
1243 return;
1244 }
1245
1246 // Create a list of FlutterLocales corresponding to the preferred languages.
1247 NSMutableArray<NSLocale*>* locales = [NSMutableArray array];
1248 std::vector<FlutterLocale> flutterLocales;
1249 flutterLocales.reserve(locales.count);
1250 for (NSString* localeID in [NSLocale preferredLanguages]) {
1251 NSLocale* locale = [[NSLocale alloc] initWithLocaleIdentifier:localeID];
1252 [locales addObject:locale];
1253 flutterLocales.push_back(FlutterLocaleFromNSLocale(locale));
1254 }
1255 // Convert to a list of pointers, and send to the engine.
1256 std::vector<const FlutterLocale*> flutterLocaleList;
1257 flutterLocaleList.reserve(flutterLocales.size());
1258 std::transform(flutterLocales.begin(), flutterLocales.end(),
1259 std::back_inserter(flutterLocaleList),
1260 [](const auto& arg) -> const auto* { return &arg; });
1261 _embedderAPI.UpdateLocales(_engine, flutterLocaleList.data(), flutterLocaleList.size());
1262 }
1263
1264 - (void)engineCallbackOnPlatformMessage:(const FlutterPlatformMessage*)message {
1265 NSData* messageData = nil;
1266 if (message->message_size > 0) {
1267 messageData = [NSData dataWithBytesNoCopy:(void*)message->message
1268 length:message->message_size
1269 freeWhenDone:NO];
1270 }
1271 NSString* channel = @(message->channel);
1272 __block const FlutterPlatformMessageResponseHandle* responseHandle = message->response_handle;
1273 __block FlutterEngine* weakSelf = self;
1274 NSMutableArray* isResponseValid = self.isResponseValid;
1275 FlutterEngineSendPlatformMessageResponseFnPtr sendPlatformMessageResponse =
1276 _embedderAPI.SendPlatformMessageResponse;
1277 FlutterBinaryReply binaryResponseHandler = ^(NSData* response) {
1278 @synchronized(isResponseValid) {
1279 if (![isResponseValid[0] boolValue]) {
1280 // Ignore, engine was killed.
1281 return;
1282 }
1283 if (responseHandle) {
1284 sendPlatformMessageResponse(weakSelf->_engine, responseHandle,
1285 static_cast<const uint8_t*>(response.bytes), response.length);
1286 responseHandle = NULL;
1287 } else {
1288 NSLog(@"Error: Message responses can be sent only once. Ignoring duplicate response "
1289 "on channel '%@'.",
1290 channel);
1291 }
1292 }
1293 };
1294
1295 FlutterEngineHandlerInfo* handlerInfo = _messengerHandlers[channel];
1296 if (handlerInfo) {
1297 handlerInfo.handler(messageData, binaryResponseHandler);
1298 } else {
1299 binaryResponseHandler(nil);
1300 }
1301 }
1302
1303 - (void)engineCallbackOnPreEngineRestart {
1304 NSEnumerator* viewControllerEnumerator = [_viewControllers objectEnumerator];
1305 FlutterViewController* nextViewController;
1306 while ((nextViewController = [viewControllerEnumerator nextObject])) {
1307 [nextViewController onPreEngineRestart];
1308 }
1309 [_windowController closeAllWindows];
1310 [_platformViewController reset];
1311 _keyboardManager = [[FlutterKeyboardManager alloc] initWithDelegate:self];
1312 }
1313
1314 // This will be called on UI thread, which maybe or may not be platform thread,
1315 // depending on the configuration.
1316 - (void)onVSync:(uintptr_t)baton {
1317 auto block = ^{
1318 // TODO(knopp): Use vsync waiter for correct view.
1319 // https://github.com/flutter/flutter/issues/142845
1320 FlutterVSyncWaiter* waiter =
1321 [_vsyncWaiters objectForKey:[_vsyncWaiters.keyEnumerator nextObject]];
1322 if (waiter != nil) {
1323 [waiter waitForVSync:baton];
1324 } else {
1325 // Sometimes there is a vsync request right after the last view is removed.
1326 // It still need to be handled, otherwise the engine will stop producing frames
1327 // even if a new view is added later.
1328 self.embedderAPI.OnVsync(_engine, baton, 0, 0);
1329 }
1330 };
1331 if ([NSThread isMainThread]) {
1332 block();
1333 } else {
1334 [FlutterRunLoop.mainRunLoop performBlock:block];
1335 }
1336 }
1337
1338 /**
1339 * Note: Called from dealloc. Should not use accessors or other methods.
1340 */
1341 - (void)shutDownEngine {
1342 if (_engine == nullptr) {
1343 return;
1344 }
1345
1346 FlutterEngineResult result = _embedderAPI.Deinitialize(_engine);
1347 if (result != kSuccess) {
1348 NSLog(@"Could not de-initialize the Flutter engine: error %d", result);
1349 }
1350
1351 result = _embedderAPI.Shutdown(_engine);
1352 if (result != kSuccess) {
1353 NSLog(@"Failed to shut down Flutter engine: error %d", result);
1354 }
1355 _engine = nullptr;
1356 }
1357
1358 + (FlutterEngine*)engineForIdentifier:(int64_t)identifier {
1359 NSAssert([[NSThread currentThread] isMainThread], @"Must be called on the main thread.");
1360 return (__bridge FlutterEngine*)reinterpret_cast<void*>(identifier);
1361 }
1362
1363 - (void)setUpPlatformViewChannel {
1364 _platformViewsChannel =
1365 [FlutterMethodChannel methodChannelWithName:@"flutter/platform_views"
1366 binaryMessenger:self.binaryMessenger
1367 codec:[FlutterStandardMethodCodec sharedInstance]];
1368
1369 __weak FlutterEngine* weakSelf = self;
1370 [_platformViewsChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
1371 [[weakSelf platformViewController] handleMethodCall:call result:result];
1372 }];
1373 }
1374
1375 - (void)setUpAccessibilityChannel {
1376 _accessibilityChannel = [FlutterBasicMessageChannel
1377 messageChannelWithName:@"flutter/accessibility"
1378 binaryMessenger:self.binaryMessenger
1379 codec:[FlutterStandardMessageCodec sharedInstance]];
1380 __weak FlutterEngine* weakSelf = self;
1381 [_accessibilityChannel setMessageHandler:^(id message, FlutterReply reply) {
1382 [weakSelf handleAccessibilityEvent:message];
1383 }];
1384 }
1385 - (void)setUpNotificationCenterListeners {
1386 NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
1387 // macOS fires this private message when VoiceOver turns on or off.
1388 [center addObserver:self
1389 selector:@selector(onAccessibilityStatusChanged:)
1390 name:kEnhancedUserInterfaceNotification
1391 object:nil];
1392 [center addObserver:self
1393 selector:@selector(applicationWillTerminate:)
1394 name:NSApplicationWillTerminateNotification
1395 object:nil];
1396 [center addObserver:self
1397 selector:@selector(windowDidChangeScreen:)
1398 name:NSWindowDidChangeScreenNotification
1399 object:nil];
1400 [center addObserver:self
1401 selector:@selector(updateDisplayConfig:)
1402 name:NSApplicationDidChangeScreenParametersNotification
1403 object:nil];
1404 }
1405
1406 - (void)addInternalPlugins {
1407 __weak FlutterEngine* weakSelf = self;
1408 [FlutterMouseCursorPlugin registerWithRegistrar:[self registrarForPlugin:@"mousecursor"]
1409 delegate:self];
1410 [FlutterMenuPlugin registerWithRegistrar:[self registrarForPlugin:@"menu"]];
1411
1412 _settingsChannel =
1413 [FlutterBasicMessageChannel messageChannelWithName:kFlutterSettingsChannel
1414 binaryMessenger:self.binaryMessenger
1415 codec:[FlutterJSONMessageCodec sharedInstance]];
1416 _platformChannel =
1417 [FlutterMethodChannel methodChannelWithName:kFlutterPlatformChannel
1418 binaryMessenger:self.binaryMessenger
1419 codec:[FlutterJSONMethodCodec sharedInstance]];
1420 [_platformChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
1421 [weakSelf handleMethodCall:call result:result];
1422 }];
1423
1424 _screenshotChannel =
1425 [FlutterMethodChannel methodChannelWithName:@"flutter/screenshot"
1426 binaryMessenger:self.binaryMessenger
1427 codec:[FlutterStandardMethodCodec sharedInstance]];
1428 [_screenshotChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
1429 FlutterEngine* strongSelf = weakSelf;
1430 if (!strongSelf) {
1431 return result([FlutterError errorWithCode:@"invalid_state"
1432 message:@"Engine deallocated."
1433 details:nil]);
1434 }
1435
1436 FlutterViewController* viewController =
1437 [strongSelf viewControllerForIdentifier:flutter::kFlutterImplicitViewId];
1438 if (!viewController) {
1439 return result([FlutterError errorWithCode:@"failure"
1440 message:@"No view controller."
1441 details:nil]);
1442 }
1443
1444 NSArray<FlutterSurface*>* frontSurfaces =
1445 viewController.flutterView.surfaceManager.frontSurfaces;
1446 if (frontSurfaces.count == 0) {
1447 return result([FlutterError errorWithCode:@"failure"
1448 message:@"No front surfaces."
1449 details:nil]);
1450 }
1451
1452 // Use the first front surface (the main backing store).
1453 FlutterSurface* surface = frontSurfaces.firstObject;
1454 IOSurfaceRef ioSurface = surface.ioSurface;
1455
1456 size_t width = IOSurfaceGetWidth(ioSurface);
1457 size_t height = IOSurfaceGetHeight(ioSurface);
1458 size_t bytesPerRow = IOSurfaceGetBytesPerRow(ioSurface);
1459 size_t bytesPerElement = IOSurfaceGetBytesPerElement(ioSurface);
1460 uint32_t pixelFormat = (uint32_t)IOSurfaceGetPixelFormat(ioSurface);
1461
1462 NSString* formatString;
1463 switch (pixelFormat) {
1464 case kCVPixelFormatType_40ARGBLEWideGamut:
1465 formatString = @"MTLPixelFormatBGRA10_XR";
1466 break;
1467 case kCVPixelFormatType_32BGRA:
1468 formatString = @"MTLPixelFormatBGRA8Unorm";
1469 break;
1470 default:
1471 formatString = [NSString stringWithFormat:@"Unknown(%u)", pixelFormat];
1472 break;
1473 }
1474
1475 IOSurfaceLock(ioSurface, kIOSurfaceLockReadOnly, nil);
1476 void* baseAddress = IOSurfaceGetBaseAddress(ioSurface);
1477
1478 // Copy pixel data row by row into a tightly-packed buffer.
1479 size_t packedBytesPerRow = width * bytesPerElement;
1480 NSMutableData* packedData = [NSMutableData dataWithLength:packedBytesPerRow * height];
1481 uint8_t* dest = (uint8_t*)packedData.mutableBytes;
1482 for (size_t row = 0; row < height; row++) {
1483 memcpy(dest + row * packedBytesPerRow, (uint8_t*)baseAddress + row * bytesPerRow,
1484 packedBytesPerRow);
1485 }
1486
1487 IOSurfaceUnlock(ioSurface, kIOSurfaceLockReadOnly, nil);
1488
1489 return result(@[
1490 @(width),
1491 @(height),
1492 formatString,
1493 [FlutterStandardTypedData typedDataWithBytes:packedData],
1494 ]);
1495 }];
1496 }
1497
1498 - (void)didUpdateMouseCursor:(NSCursor*)cursor {
1499 // Mouse cursor plugin does not specify which view is responsible for changing the cursor,
1500 // so the reasonable assumption here is that cursor change is a result of a mouse movement
1501 // and thus the cursor will be paired with last Flutter view that reveived mouse event.
1502 [_lastViewWithPointerEvent didUpdateMouseCursor:cursor];
1503 }
1504
1505 - (void)applicationWillTerminate:(NSNotification*)notification {
1506 [self shutDownEngine];
1507 }
1508
1509 - (void)windowDidChangeScreen:(NSNotification*)notification {
1510 // Update window metric for all view controllers since the display_id has
1511 // changed.
1512 NSEnumerator* viewControllerEnumerator = [_viewControllers objectEnumerator];
1513 FlutterViewController* nextViewController;
1514 while ((nextViewController = [viewControllerEnumerator nextObject])) {
1515 [self updateWindowMetricsForViewController:nextViewController];
1516 [nextViewController updateWideGamutForScreen];
1517 }
1518 }
1519
1520 - (void)onAccessibilityStatusChanged:(NSNotification*)notification {
1521 BOOL enabled = [notification.userInfo[kEnhancedUserInterfaceKey] boolValue];
1522 NSEnumerator* viewControllerEnumerator = [_viewControllers objectEnumerator];
1523 FlutterViewController* nextViewController;
1524 while ((nextViewController = [viewControllerEnumerator nextObject])) {
1525 [nextViewController onAccessibilityStatusChanged:enabled];
1526 }
1527
1528 self.semanticsEnabled = enabled;
1529 }
1530 - (void)handleAccessibilityEvent:(NSDictionary<NSString*, id>*)annotatedEvent {
1531 NSString* type = annotatedEvent[@"type"];
1532 if ([type isEqualToString:@"announce"]) {
1533 NSString* message = annotatedEvent[@"data"][@"message"];
1534 NSNumber* assertiveness = annotatedEvent[@"data"][@"assertiveness"];
1535 if (message == nil) {
1536 return;
1537 }
1538
1539 NSAccessibilityPriorityLevel priority = [assertiveness isEqualToNumber:@1]
1540 ? NSAccessibilityPriorityHigh
1541 : NSAccessibilityPriorityMedium;
1542
1543 [self announceAccessibilityMessage:message withPriority:priority];
1544 }
1545 }
1546
1547 - (void)announceAccessibilityMessage:(NSString*)message
1548 withPriority:(NSAccessibilityPriorityLevel)priority {
1549 NSAccessibilityPostNotificationWithUserInfo(
1550 [self viewControllerForIdentifier:kFlutterImplicitViewId].flutterView,
1551 NSAccessibilityAnnouncementRequestedNotification,
1552 @{NSAccessibilityAnnouncementKey : message, NSAccessibilityPriorityKey : @(priority)});
1553 }
1554 - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
1555 if ([call.method isEqualToString:@"SystemNavigator.pop"]) {
1556 [[NSApplication sharedApplication] terminate:self];
1557 result(nil);
1558 } else if ([call.method isEqualToString:@"SystemSound.play"]) {
1559 [self playSystemSound:call.arguments];
1560 result(nil);
1561 } else if ([call.method isEqualToString:@"Clipboard.getData"]) {
1562 result([self getClipboardData:call.arguments]);
1563 } else if ([call.method isEqualToString:@"Clipboard.setData"]) {
1564 [self setClipboardData:call.arguments];
1565 result(nil);
1566 } else if ([call.method isEqualToString:@"Clipboard.hasStrings"]) {
1567 result(@{@"value" : @([self clipboardHasStrings])});
1568 } else if ([call.method isEqualToString:@"System.exitApplication"]) {
1569 if ([self terminationHandler] == nil) {
1570 // If the termination handler isn't set, then either we haven't
1571 // initialized it yet, or (more likely) the NSApp delegate isn't a
1572 // FlutterAppDelegate, so it can't cancel requests to exit. So, in that
1573 // case, just terminate when requested.
1574 [NSApp terminate:self];
1575 result(nil);
1576 } else {
1577 [[self terminationHandler] handleRequestAppExitMethodCall:call.arguments result:result];
1578 }
1579 } else if ([call.method isEqualToString:@"System.initializationComplete"]) {
1580 if ([self terminationHandler] != nil) {
1581 [self terminationHandler].acceptingRequests = YES;
1582 }
1583 result(nil);
1584 } else {
1585 result(FlutterMethodNotImplemented);
1586 }
1587 }
1588
1589 - (void)playSystemSound:(NSString*)soundType {
1590 if ([soundType isEqualToString:@"SystemSoundType.alert"]) {
1591 NSBeep();
1592 }
1593 }
1594
1595 - (NSDictionary*)getClipboardData:(NSString*)format {
1596 if ([format isEqualToString:@(kTextPlainFormat)]) {
1597 NSString* stringInPasteboard = [self.pasteboard stringForType:NSPasteboardTypeString];
1598 return stringInPasteboard == nil ? nil : @{@"text" : stringInPasteboard};
1599 }
1600 return nil;
1601 }
1602
1603 - (void)setClipboardData:(NSDictionary*)data {
1604 NSString* text = data[@"text"];
1605 [self.pasteboard clearContents];
1606 if (text && ![text isEqual:[NSNull null]]) {
1607 [self.pasteboard setString:text forType:NSPasteboardTypeString];
1608 }
1609 }
1610
1611 - (BOOL)clipboardHasStrings {
1612 return [self.pasteboard stringForType:NSPasteboardTypeString].length > 0;
1613 }
1614
1615 - (std::vector<std::string>)switches {
1616 return flutter::GetSwitchesFromEnvironment();
1617 }
1618
1619 #pragma mark - FlutterAppLifecycleDelegate
1620
1621 - (void)setApplicationState:(flutter::AppLifecycleState)state {
1622 NSString* nextState =
1623 [[NSString alloc] initWithCString:flutter::AppLifecycleStateToString(state)];
1624 [self sendOnChannel:kFlutterLifecycleChannel
1625 message:[nextState dataUsingEncoding:NSUTF8StringEncoding]];
1626 }
1627
1628 /**
1629 * Called when the |FlutterAppDelegate| gets the applicationWillBecomeActive
1630 * notification.
1631 */
1632 - (void)handleWillBecomeActive:(NSNotification*)notification {
1633 _active = YES;
1634 if (!_visible) {
1635 [self setApplicationState:flutter::AppLifecycleState::kHidden];
1636 } else {
1637 [self setApplicationState:flutter::AppLifecycleState::kResumed];
1638 }
1639 }
1640
1641 /**
1642 * Called when the |FlutterAppDelegate| gets the applicationWillResignActive
1643 * notification.
1644 */
1645 - (void)handleWillResignActive:(NSNotification*)notification {
1646 _active = NO;
1647 if (!_visible) {
1648 [self setApplicationState:flutter::AppLifecycleState::kHidden];
1649 } else {
1650 [self setApplicationState:flutter::AppLifecycleState::kInactive];
1651 }
1652 }
1653
1654 /**
1655 * Called when the |FlutterAppDelegate| gets the applicationDidUnhide
1656 * notification.
1657 */
1658 - (void)handleDidChangeOcclusionState:(NSNotification*)notification {
1659 NSApplicationOcclusionState occlusionState = [[NSApplication sharedApplication] occlusionState];
1660 if (occlusionState & NSApplicationOcclusionStateVisible) {
1661 _visible = YES;
1662 if (_active) {
1663 [self setApplicationState:flutter::AppLifecycleState::kResumed];
1664 } else {
1665 [self setApplicationState:flutter::AppLifecycleState::kInactive];
1666 }
1667 } else {
1668 _visible = NO;
1669 [self setApplicationState:flutter::AppLifecycleState::kHidden];
1670 }
1671 }
1672
1673 #pragma mark - FlutterBinaryMessenger
1674
1675 - (void)sendOnChannel:(nonnull NSString*)channel message:(nullable NSData*)message {
1676 [self sendOnChannel:channel message:message binaryReply:nil];
1677 }
1678
1679 - (void)sendOnChannel:(NSString*)channel
1680 message:(NSData* _Nullable)message
1681 binaryReply:(FlutterBinaryReply _Nullable)callback {
1682 FlutterPlatformMessageResponseHandle* response_handle = nullptr;
1683 if (callback) {
1684 struct Captures {
1685 FlutterBinaryReply reply;
1686 };
1687 auto captures = std::make_unique<Captures>();
1688 captures->reply = callback;
1689 auto message_reply = [](const uint8_t* data, size_t data_size, void* user_data) {
1690 auto captures = reinterpret_cast<Captures*>(user_data);
1691 NSData* reply_data = nil;
1692 if (data != nullptr && data_size > 0) {
1693 reply_data = [NSData dataWithBytes:static_cast<const void*>(data) length:data_size];
1694 }
1695 captures->reply(reply_data);
1696 delete captures;
1697 };
1698
1699 FlutterEngineResult create_result = _embedderAPI.PlatformMessageCreateResponseHandle(
1700 _engine, message_reply, captures.get(), &response_handle);
1701 if (create_result != kSuccess) {
1702 NSLog(@"Failed to create a FlutterPlatformMessageResponseHandle (%d)", create_result);
1703 return;
1704 }
1705 captures.release();
1706 }
1707
1708 FlutterPlatformMessage platformMessage = {
1709 .struct_size = sizeof(FlutterPlatformMessage),
1710 .channel = [channel UTF8String],
1711 .message = static_cast<const uint8_t*>(message.bytes),
1712 .message_size = message.length,
1713 .response_handle = response_handle,
1714 };
1715
1716 FlutterEngineResult message_result = _embedderAPI.SendPlatformMessage(_engine, &platformMessage);
1717 if (message_result != kSuccess) {
1718 NSLog(@"Failed to send message to Flutter engine on channel '%@' (%d).", channel,
1719 message_result);
1720 }
1721
1722 if (response_handle != nullptr) {
1723 FlutterEngineResult release_result =
1724 _embedderAPI.PlatformMessageReleaseResponseHandle(_engine, response_handle);
1725 if (release_result != kSuccess) {
1726 NSLog(@"Failed to release the response handle (%d).", release_result);
1727 };
1728 }
1729 }
1730
1731 - (FlutterBinaryMessengerConnection)setMessageHandlerOnChannel:(nonnull NSString*)channel
1732 binaryMessageHandler:
1733 (nullable FlutterBinaryMessageHandler)handler {
1734 _currentMessengerConnection += 1;
1735 _messengerHandlers[channel] =
1736 [[FlutterEngineHandlerInfo alloc] initWithConnection:@(_currentMessengerConnection)
1737 handler:[handler copy]];
1738 return _currentMessengerConnection;
1739 }
1740
1741 - (void)cleanUpConnection:(FlutterBinaryMessengerConnection)connection {
1742 // Find the _messengerHandlers that has the required connection, and record its
1743 // channel.
1744 NSString* foundChannel = nil;
1745 for (NSString* key in [_messengerHandlers allKeys]) {
1746 FlutterEngineHandlerInfo* handlerInfo = [_messengerHandlers objectForKey:key];
1747 if ([handlerInfo.connection isEqual:@(connection)]) {
1748 foundChannel = key;
1749 break;
1750 }
1751 }
1752 if (foundChannel) {
1753 [_messengerHandlers removeObjectForKey:foundChannel];
1754 }
1755 }
1756
1757 #pragma mark - FlutterPluginRegistry
1758
1759 - (id<FlutterPluginRegistrar>)registrarForPlugin:(NSString*)pluginName {
1760 id<FlutterPluginRegistrar> registrar = self.pluginRegistrars[pluginName];
1761 if (!registrar) {
1762 FlutterEngineRegistrar* registrarImpl =
1763 [[FlutterEngineRegistrar alloc] initWithPlugin:pluginName flutterEngine:self];
1764 self.pluginRegistrars[pluginName] = registrarImpl;
1765 registrar = registrarImpl;
1766 }
1767 return registrar;
1768 }
1769
1770 - (nullable NSObject*)valuePublishedByPlugin:(NSString*)pluginName {
1771 return self.pluginRegistrars[pluginName].publishedValue;
1772 }
1773
1774 #pragma mark - FlutterTextureRegistrar
1775
1776 - (int64_t)registerTexture:(id<FlutterTexture>)texture {
1777 return [_renderer registerTexture:texture];
1778 }
1779
1780 - (BOOL)registerTextureWithID:(int64_t)textureId {
1781 return _embedderAPI.RegisterExternalTexture(_engine, textureId) == kSuccess;
1782 }
1783
1784 - (void)textureFrameAvailable:(int64_t)textureID {
1785 [_renderer textureFrameAvailable:textureID];
1786 }
1787
1788 - (BOOL)markTextureFrameAvailable:(int64_t)textureID {
1789 return _embedderAPI.MarkExternalTextureFrameAvailable(_engine, textureID) == kSuccess;
1790 }
1791
1792 - (void)unregisterTexture:(int64_t)textureID {
1793 [_renderer unregisterTexture:textureID];
1794 }
1795
1796 - (BOOL)unregisterTextureWithID:(int64_t)textureID {
1797 return _embedderAPI.UnregisterExternalTexture(_engine, textureID) == kSuccess;
1798 }
1799
1800 #pragma mark - Task runner integration
1801
1802 - (void)postMainThreadTask:(FlutterTask)task targetTimeInNanoseconds:(uint64_t)targetTime {
1803 __weak FlutterEngine* weakSelf = self;
1804
1805 const auto engine_time = _embedderAPI.GetCurrentTime();
1806 [FlutterRunLoop.mainRunLoop
1807 performAfterDelay:(targetTime - (double)engine_time) / NSEC_PER_SEC
1808 block:^{
1809 FlutterEngine* self = weakSelf;
1810 if (self != nil && self->_engine != nil) {
1811 auto result = _embedderAPI.RunTask(self->_engine, &task);
1812 if (result != kSuccess) {
1813 NSLog(@"Could not post a task to the Flutter engine.");
1814 }
1815 }
1816 }];
1817 }
1818
1819 // Getter used by test harness, only exposed through the FlutterEngine(Test) category
1820 - (flutter::FlutterCompositor*)macOSCompositor {
1821 return _macOSCompositor.get();
1822 }
1823
1824 #pragma mark - FlutterKeyboardManagerDelegate
1825
1826 /**
1827 * Dispatches the given pointer event data to engine.
1828 */
1829 - (void)sendKeyEvent:(const FlutterKeyEvent&)event
1830 callback:(FlutterKeyEventCallback)callback
1831 userData:(void*)userData {
1832 _embedderAPI.SendKeyEvent(_engine, &event, callback, userData);
1833 }
1834
1835 @end
FlutterAppDelegate.h
FlutterAppDelegate_Internal.h
FlutterBinaryReply
NS_ASSUME_NONNULL_BEGIN typedef void(^ FlutterBinaryReply)(NSData *_Nullable reply)
FlutterBinaryMessageHandler
void(^ FlutterBinaryMessageHandler)(NSData *_Nullable message, FlutterBinaryReply reply)
Definition: FlutterBinaryMessenger.h:30
FlutterBinaryMessengerConnection
int64_t FlutterBinaryMessengerConnection
Definition: FlutterBinaryMessenger.h:32
FlutterBinaryMessengerRelay.h
FlutterResult
void(^ FlutterResult)(id _Nullable result)
Definition: FlutterChannels.h:194
FlutterMethodNotImplemented
FLUTTER_DARWIN_EXPORT NSObject const * FlutterMethodNotImplemented
_connection
FlutterBinaryMessengerConnection _connection
Definition: FlutterChannels.mm:74
FlutterCompositor.h
FlutterDartProject_Internal.h
FlutterDisplayLink.h
FlutterEngine.h
_platformViewsChannel
FlutterMethodChannel * _platformViewsChannel
Definition: FlutterEngine.mm:449
_aotData
_FlutterEngineAOTData * _aotData
Definition: FlutterEngine.mm:433
_macOSCompositor
std::unique_ptr< flutter::FlutterCompositor > _macOSCompositor
Definition: FlutterEngine.mm:437
kMainThreadPriority
static const int kMainThreadPriority
Definition: FlutterEngine.mm:510
OnPlatformMessage
static void OnPlatformMessage(const FlutterPlatformMessage *message, void *user_data)
Definition: FlutterEngine.mm:408
_platformViewController
FlutterPlatformViewController * _platformViewController
Definition: FlutterEngine.mm:453
_accessibilityChannel
FlutterBasicMessageChannel * _accessibilityChannel
Definition: FlutterEngine.mm:462
_settingsChannel
FlutterBasicMessageChannel * _settingsChannel
Definition: FlutterEngine.mm:459
FlutterLocaleFromNSLocale
static FlutterLocale FlutterLocaleFromNSLocale(NSLocale *locale)
Definition: FlutterEngine.mm:51
_allowHeadlessExecution
BOOL _allowHeadlessExecution
Definition: FlutterEngine.mm:430
_messengerHandlers
NSMutableDictionary< NSString *, FlutterEngineHandlerInfo * > * _messengerHandlers
Definition: FlutterEngine.mm:423
_currentMessengerConnection
FlutterBinaryMessengerConnection _currentMessengerConnection
Definition: FlutterEngine.mm:427
_platformChannel
FlutterMethodChannel * _platformChannel
Definition: FlutterEngine.mm:465
kFlutterLifecycleChannel
NSString *const kFlutterLifecycleChannel
Definition: FlutterEngine.mm:43
kEnhancedUserInterfaceNotification
static NSString *const kEnhancedUserInterfaceNotification
The private notification for voice over.
Definition: FlutterEngine.mm:62
_vsyncWaiters
NSMapTable< NSNumber *, FlutterVSyncWaiter * > * _vsyncWaiters
Definition: FlutterEngine.mm:481
_nextViewIdentifier
FlutterViewIdentifier _nextViewIdentifier
Definition: FlutterEngine.mm:500
kFlutterPlatformChannel
NSString *const kFlutterPlatformChannel
Definition: FlutterEngine.mm:41
_screenshotChannel
FlutterMethodChannel * _screenshotChannel
Definition: FlutterEngine.mm:468
_textInputPlugin
FlutterTextInputPlugin * _textInputPlugin
Definition: FlutterEngine.mm:491
_project
FlutterDartProject * _project
Definition: FlutterEngine.mm:415
_viewControllers
NSMapTable * _viewControllers
Definition: FlutterEngine.mm:442
_multiViewEnabled
BOOL _multiViewEnabled
Definition: FlutterEngine.mm:496
_compositor
FlutterCompositor _compositor
Definition: FlutterEngine.mm:445
_lastViewWithPointerEvent
__weak FlutterView * _lastViewWithPointerEvent
Definition: FlutterEngine.mm:485
_keyboardManager
FlutterKeyboardManager * _keyboardManager
Definition: FlutterEngine.mm:488
_windowController
FlutterWindowController * _windowController
Definition: FlutterEngine.mm:456
_terminator
FlutterTerminationCallback _terminator
Definition: FlutterEngine.mm:190
kTextPlainFormat
constexpr char kTextPlainFormat[]
Clipboard plain text format.
Definition: FlutterEngine.mm:67
_flutterEngine
__weak FlutterEngine * _flutterEngine
Definition: FlutterEngine.mm:323
_visible
BOOL _visible
Definition: FlutterEngine.mm:474
_active
BOOL _active
Definition: FlutterEngine.mm:471
_binaryMessenger
FlutterBinaryMessengerRelay * _binaryMessenger
Definition: FlutterEngine.mm:477
kEnhancedUserInterfaceKey
static NSString *const kEnhancedUserInterfaceKey
Definition: FlutterEngine.mm:64
kFlutterSettingsChannel
NSString *const kFlutterSettingsChannel
Definition: FlutterEngine.mm:42
FlutterEngine_Internal.h
FlutterTerminationCallback
NS_ASSUME_NONNULL_BEGIN typedef void(^ FlutterTerminationCallback)(id _Nullable sender)
FlutterMouseCursorPlugin.h
FlutterPlatformViewController.h
FlutterRenderer.h
FlutterTimeConverter.h
FlutterVSyncWaiter.h
FlutterView.h
FlutterViewIdentifier
int64_t FlutterViewIdentifier
Definition: FlutterViewController.h:21
FlutterViewController_Internal.h
FlutterViewEngineProvider.h
app_lifecycle_state.h
FlutterDartProject
Definition: FlutterDartProject.mm:39
+[FlutterDartProject lookupKeyForAsset:fromPackage:]
NSString * lookupKeyForAsset:fromPackage:(NSString *asset,[fromPackage] NSString *package)
Definition: FlutterDartProject.mm:166
+[FlutterDartProject lookupKeyForAsset:]
NSString * lookupKeyForAsset:(NSString *asset)
Definition: FlutterDartProject.mm:157
FlutterEngineTerminationHandler
Definition: FlutterEngine.mm:190
FlutterPasteboard
Definition: FlutterEngine.mm:288
-[FlutterPasteboard clearContents]
NSInteger clearContents()
Definition: FlutterEngine.mm:290
flutter::FlutterCompositor
Definition: FlutterCompositor.h:36
engine_switches.h
FlutterAppDelegate
Definition: FlutterAppDelegate.h:54
FlutterBasicMessageChannel
Definition: FlutterChannels.h:38
+[FlutterBasicMessageChannel messageChannelWithName:binaryMessenger:codec:]
instancetype messageChannelWithName:binaryMessenger:codec:(NSString *name,[binaryMessenger] NSObject< FlutterBinaryMessenger > *messenger,[codec] NSObject< FlutterMessageCodec > *codec)
Definition: FlutterChannels.mm:84
FlutterBinaryMessengerRelay
Definition: FlutterBinaryMessengerRelay.h:14
FlutterDisplayLink
Definition: FlutterDisplayLink.h:22
+[FlutterDisplayLink displayLinkWithView:]
instancetype displayLinkWithView:(NSView *view)
Definition: FlutterDisplayLink.mm:292
FlutterEngineHandlerInfo
Definition: FlutterEngine.mm:74
FlutterEngineHandlerInfo::connection
NSNumber * connection
Definition: FlutterEngine.mm:79
FlutterEngineHandlerInfo::handler
FlutterBinaryMessageHandler handler
Definition: FlutterEngine.mm:78
FlutterEngine
Definition: FlutterEngine.h:32
FlutterEngine::binaryMessenger
id< FlutterBinaryMessenger > binaryMessenger
Definition: FlutterEngine.h:92
FlutterEngineRegistrar
Definition: FlutterEngine.mm:309
FlutterEngineRegistrar::publishedValue
NSObject * publishedValue
Definition: FlutterEngine.mm:320
FlutterError
Definition: FlutterCodecs.h:247
+[FlutterError errorWithCode:message:details:]
instancetype errorWithCode:message:details:(NSString *code,[message] NSString *_Nullable message,[details] id _Nullable details)
FlutterHourFormat
Definition: FlutterHourFormat.h:11
+[FlutterHourFormat isAlwaysUse24HourFormat]
BOOL isAlwaysUse24HourFormat()
Definition: FlutterHourFormat.mm:8
FlutterJSONMessageCodec
Definition: FlutterCodecs.h:81
FlutterJSONMethodCodec
Definition: FlutterCodecs.h:455
FlutterKeyboardManager
Definition: FlutterKeyboardManager.h:79
FlutterMethodCall
Definition: FlutterCodecs.h:221
+[FlutterMethodCall methodCallWithMethodName:arguments:]
instancetype methodCallWithMethodName:arguments:(NSString *method,[arguments] id _Nullable arguments)
FlutterMethodCall::method
NSString * method
Definition: FlutterCodecs.h:233
FlutterMethodCall::arguments
id arguments
Definition: FlutterCodecs.h:238
FlutterMethodChannel
Definition: FlutterChannels.h:221
-[FlutterMethodChannel setMethodCallHandler:]
void setMethodCallHandler:(FlutterMethodCallHandler _Nullable handler)
+[FlutterMethodChannel methodChannelWithName:binaryMessenger:codec:]
instancetype methodChannelWithName:binaryMessenger:codec:(NSString *name,[binaryMessenger] NSObject< FlutterBinaryMessenger > *messenger,[codec] NSObject< FlutterMethodCodec > *codec)
FlutterMouseCursorPlugin
Definition: FlutterMouseCursorPlugin.h:23
+[FlutterMouseCursorPlugin registerWithRegistrar:delegate:]
void registerWithRegistrar:delegate:(nonnull id< FlutterPluginRegistrar > registrar,[delegate] nullable id< FlutterMouseCursorPluginDelegate > delegate)
FlutterPlatformViewController
Definition: FlutterPlatformViewController.h:18
FlutterRenderer
Definition: FlutterRenderer.h:19
FlutterStandardMessageCodec
Definition: FlutterCodecs.h:209
FlutterStandardMethodCodec
Definition: FlutterCodecs.h:469
FlutterStandardTypedData
Definition: FlutterCodecs.h:301
+[FlutterStandardTypedData typedDataWithBytes:]
instancetype typedDataWithBytes:(NSData *data)
Definition: FlutterStandardCodec.mm:146
FlutterTextInputPlugin
Definition: FlutterTextInputPlugin.h:42
FlutterTimeConverter
Converts between the time representation used by Flutter Engine and CAMediaTime.
Definition: FlutterTimeConverter.h:14
-[FlutterTimeConverter CAMediaTimeToEngineTime:]
uint64_t CAMediaTimeToEngineTime:(CFTimeInterval time)
Definition: FlutterTimeConverter.mm:26
FlutterVSyncWaiter
Definition: FlutterVSyncWaiter.h:13
-[FlutterVSyncWaiter invalidate]
void invalidate()
Definition: FlutterVSyncWaiter.mm:155
-[FlutterVSyncWaiter waitForVSync:]
void waitForVSync:(uintptr_t baton)
Definition: FlutterVSyncWaiter.mm:98
FlutterViewController
Definition: FlutterViewController.h:73
-[FlutterViewController onPreEngineRestart]
void onPreEngineRestart()
Definition: FlutterViewController.mm:455
FlutterViewController::engine
FlutterEngine * engine
Definition: FlutterViewController.h:78
-[FlutterViewController attached]
BOOL attached()
Definition: FlutterViewController.mm:490
FlutterViewController::viewIdentifier
FlutterViewIdentifier viewIdentifier
Definition: FlutterViewController.h:130
-[FlutterViewController onAccessibilityStatusChanged:]
void onAccessibilityStatusChanged:(BOOL enabled)
FlutterViewEngineProvider
Definition: FlutterViewEngineProvider.h:18
FlutterView
Definition: FlutterView.h:69
FlutterView::sizedToContents
BOOL sizedToContents
Definition: FlutterView.h:121
FlutterWindowController
Definition: FlutterWindowController.h:16
FlutterWindowController::engine
FlutterEngine * engine
Definition: FlutterWindowController.h:17
flutter
Definition: AccessibilityBridgeMac.h:16
flutter::GetSwitchesFromEnvironment
std::vector< std::string > GetSwitchesFromEnvironment()
Definition: engine_switches.cc:14
flutter::AppLifecycleState
AppLifecycleState
Definition: app_lifecycle_state.h:32
FlutterAppLifecycleDelegate-p
Definition: FlutterAppLifecycleDelegate.h:21
FlutterAppLifecycleProvider-p
Definition: FlutterAppDelegate.h:21
FlutterBinaryMessenger-p
Definition: FlutterBinaryMessenger.h:49
FlutterKeyboardManagerDelegate-p
Definition: FlutterKeyboardManager.h:13
+[FlutterMessageCodec-p sharedInstance]
instancetype sharedInstance()
FlutterMouseCursorPluginDelegate-p
Definition: FlutterMouseCursorPlugin.h:13
FlutterPlatformViewFactory-p
Definition: FlutterPlatformViews.h:13
FlutterPlugin-p
Definition: FlutterPluginMacOS.h:29
-[FlutterPlugin-p handleMethodCall:result:]
void handleMethodCall:result:(FlutterMethodCall *call,[result] FlutterResult result)
FlutterPluginRegistrar-p
Definition: FlutterPluginRegistrarMacOS.h:28
FlutterTextInputPluginDelegate-p
Definition: FlutterTextInputPlugin.h:18
FlutterTexture-p
Definition: FlutterTexture.h:21
FlutterTextureRegistry-p
Definition: FlutterTexture.h:45
user_data
void * user_data
Definition: texture_registrar_unittests.cc:27