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