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