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