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