13 #include "flutter/common/constants.h"
16 #include "flutter/shell/platform/embedder/embedder.h"
18 #import "flutter/shell/platform/darwin/common/InternalFlutterSwiftCommon/InternalFlutterSwiftCommon.h"
20 #import "flutter/shell/platform/darwin/macos/InternalFlutterSwift/InternalFlutterSwift.h"
36 #import <CoreVideo/CoreVideo.h>
37 #import <IOSurface/IOSurface.h>
45 using flutter::kFlutterImplicitViewId;
52 FlutterLocale flutterLocale = {};
53 flutterLocale.struct_size =
sizeof(FlutterLocale);
54 flutterLocale.language_code = [[locale objectForKey:NSLocaleLanguageCode] UTF8String];
55 flutterLocale.country_code = [[locale objectForKey:NSLocaleCountryCode] UTF8String];
56 flutterLocale.script_code = [[locale objectForKey:NSLocaleScriptCode] UTF8String];
57 flutterLocale.variant_code = [[locale objectForKey:NSLocaleVariantCode] UTF8String];
63 @"NSApplicationDidChangeAccessibilityEnhancedUserInterfaceNotification";
75 - (instancetype)initWithConnection:(NSNumber*)connection
84 - (instancetype)initWithConnection:(NSNumber*)connection
87 NSAssert(
self,
@"Super init cannot be nil");
109 @property(nonatomic, strong) NSMutableArray<NSNumber*>* isResponseValid;
114 @property(nonatomic, strong) NSPointerArray* pluginAppDelegates;
119 @property(nonatomic, readonly)
120 NSMutableDictionary<NSString*, FlutterEngineRegistrar*>* pluginRegistrars;
147 - (void)shutDownIfNeeded;
152 - (void)sendUserLocales;
157 - (void)engineCallbackOnPlatformMessage:(const FlutterPlatformMessage*)message;
163 - (void)postMainThreadTask:(FlutterTask)task targetTimeInNanoseconds:(uint64_t)targetTime;
169 - (void)loadAOTData:(NSString*)assetsDir;
174 - (void)setUpPlatformViewChannel;
179 - (void)setUpAccessibilityChannel;
198 _acceptingRequests = NO;
200 _terminator = terminator ? terminator : ^(
id sender) {
203 [[NSApplication sharedApplication] terminate:sender];
205 id<NSApplicationDelegate> appDelegate = [[NSApplication sharedApplication] delegate];
206 if ([appDelegate respondsToSelector:
@selector(setTerminationHandler:)]) {
208 flutterAppDelegate.terminationHandler =
self;
215 - (void)handleRequestAppExitMethodCall:(NSDictionary<NSString*,
id>*)arguments
217 NSString* type = arguments[@"type"];
223 FlutterAppExitType exitType =
224 [type isEqualTo:@"cancelable"] ? kFlutterAppExitTypeCancelable : kFlutterAppExitTypeRequired;
226 [
self requestApplicationTermination:[NSApplication sharedApplication]
233 - (void)requestApplicationTermination:(
id)sender
234 exitType:(FlutterAppExitType)type
236 _shouldTerminate = YES;
237 if (![
self acceptingRequests]) {
240 type = kFlutterAppExitTypeRequired;
243 case kFlutterAppExitTypeCancelable: {
247 [_engine sendOnChannel:kFlutterPlatformChannel
248 message:[codec encodeMethodCall:methodCall]
249 binaryReply:^(NSData* _Nullable reply) {
250 NSAssert(_terminator, @"terminator shouldn't be nil");
251 id decoded_reply = [codec decodeEnvelope:reply];
252 if ([decoded_reply isKindOfClass:[
FlutterError class]]) {
254 NSLog(@"Method call returned error[%@]: %@ %@", [error code], [error message],
259 if (![decoded_reply isKindOfClass:[NSDictionary class]]) {
260 NSLog(@"Call to System.requestAppExit returned an unexpected object: %@",
265 NSDictionary* replyArgs = (NSDictionary*)decoded_reply;
266 if ([replyArgs[@"response"] isEqual:@"exit"]) {
268 } else if ([replyArgs[@"response"] isEqual:@"cancel"]) {
269 _shouldTerminate = NO;
277 case kFlutterAppExitTypeRequired:
278 NSAssert(
_terminator,
@"terminator shouldn't be nil");
291 return [[NSPasteboard generalPasteboard] clearContents];
294 - (NSString*)stringForType:(NSPasteboardType)dataType {
295 return [[NSPasteboard generalPasteboard] stringForType:dataType];
298 - (BOOL)setString:(nonnull NSString*)string forType:(nonnull NSPasteboardType)dataType {
299 return [[NSPasteboard generalPasteboard] setString:string forType:dataType];
310 - (instancetype)initWithPlugin:(nonnull NSString*)pluginKey
324 NSString* _pluginKey;
330 - (instancetype)initWithPlugin:(NSString*)pluginKey flutterEngine:(
FlutterEngine*)flutterEngine {
333 _pluginKey = [pluginKey copy];
335 _publishedValue = [NSNull null];
340 #pragma mark - FlutterPluginRegistrar
351 return [
self viewForIdentifier:kFlutterImplicitViewId];
356 if (controller == nil) {
359 if (!controller.viewLoaded) {
360 [controller loadView];
362 return controller.flutterView;
365 - (NSViewController*)viewController {
366 return [_flutterEngine viewControllerForIdentifier:kFlutterImplicitViewId];
369 - (void)addMethodCallDelegate:(nonnull
id<
FlutterPlugin>)delegate
377 id<NSApplicationDelegate> appDelegate = [[NSApplication sharedApplication] delegate];
379 id<FlutterAppLifecycleProvider> lifeCycleProvider =
380 static_cast<id<FlutterAppLifecycleProvider>
>(appDelegate);
381 [lifeCycleProvider addApplicationLifecycleDelegate:delegate];
382 [_flutterEngine.pluginAppDelegates addPointer:(__bridge void*)delegate];
387 withId:(nonnull NSString*)factoryId {
388 [[_flutterEngine platformViewController] registerViewFactory:factory withId:factoryId];
391 - (void)publish:(NSObject*)value {
392 _publishedValue = value;
395 - (NSString*)lookupKeyForAsset:(NSString*)asset {
399 - (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package {
406 #pragma mark - Static methods provided to engine configuration
410 [engine engineCallbackOnPlatformMessage:message];
506 - (instancetype)initWithName:(NSString*)labelPrefix project:(
FlutterDartProject*)project {
507 return [
self initWithName:labelPrefix project:project allowHeadlessExecution:YES];
512 static void SetThreadPriority(FlutterThreadPriority priority) {
513 if (priority == kDisplay || priority == kRaster) {
514 pthread_t thread = pthread_self();
517 if (!pthread_getschedparam(thread, &policy, ¶m)) {
519 pthread_setschedparam(thread, policy, ¶m);
521 pthread_set_qos_class_self_np(QOS_CLASS_USER_INTERACTIVE, 0);
525 - (instancetype)initWithName:(NSString*)labelPrefix
527 allowHeadlessExecution:(BOOL)allowHeadlessExecution {
529 NSAssert(
self,
@"Super init cannot be nil");
531 [FlutterRunLoop ensureMainLoopInitialized];
538 _pluginAppDelegates = [NSPointerArray weakObjectsPointerArray];
539 _pluginRegistrars = [[NSMutableDictionary alloc] init];
542 _semanticsEnabled = NO;
544 _isResponseValid = [[NSMutableArray alloc] initWithCapacity:1];
545 [_isResponseValid addObject:@YES];
551 _embedderAPI.struct_size =
sizeof(FlutterEngineProcTable);
552 FlutterEngineGetProcAddresses(&_embedderAPI);
557 NSNotificationCenter* notificationCenter = [NSNotificationCenter defaultCenter];
558 [notificationCenter addObserver:self
559 selector:@selector(sendUserLocales)
560 name:NSCurrentLocaleDidChangeNotification
570 [
self setUpPlatformViewChannel];
575 [
self setUpAccessibilityChannel];
576 [
self setUpNotificationCenterListeners];
577 id<NSApplicationDelegate> appDelegate = [[NSApplication sharedApplication] delegate];
581 id<FlutterAppLifecycleProvider> lifecycleProvider =
582 static_cast<id<FlutterAppLifecycleProvider>
>(appDelegate);
583 [lifecycleProvider addApplicationLifecycleDelegate:self];
585 _terminationHandler = nil;
594 id<NSApplicationDelegate> appDelegate = [[NSApplication sharedApplication] delegate];
596 id<FlutterAppLifecycleProvider> lifecycleProvider =
597 static_cast<id<FlutterAppLifecycleProvider>
>(appDelegate);
598 [lifecycleProvider removeApplicationLifecycleDelegate:self];
603 for (id<FlutterAppLifecycleDelegate> delegate in _pluginAppDelegates) {
605 [lifecycleProvider removeApplicationLifecycleDelegate:delegate];
611 for (NSString* pluginName in _pluginRegistrars) {
612 [_pluginRegistrars[pluginName] publish:[NSNull null]];
614 @
synchronized(_isResponseValid) {
615 [_isResponseValid removeAllObjects];
616 [_isResponseValid addObject:@NO];
618 [
self shutDownEngine];
620 _embedderAPI.CollectAOTData(
_aotData);
624 - (FlutterTaskRunnerDescription)createPlatformThreadTaskDescription {
625 static size_t sTaskRunnerIdentifiers = 0;
626 FlutterTaskRunnerDescription cocoa_task_runner_description = {
627 .struct_size =
sizeof(FlutterTaskRunnerDescription),
629 .
user_data = (__bridge_retained
void*)
self,
630 .runs_task_on_current_thread_callback = [](
void*
user_data) ->
bool {
631 return [[NSThread currentThread] isMainThread];
633 .post_task_callback = [](FlutterTask task, uint64_t target_time_nanos,
636 [engine postMainThreadTask:task targetTimeInNanoseconds:target_time_nanos];
638 .identifier = ++sTaskRunnerIdentifiers,
639 .destruction_callback =
646 return cocoa_task_runner_description;
649 - (void)onFocusChangeRequest:(const FlutterViewFocusChangeRequest*)request {
651 if (controller == nil) {
654 if (request->state == kFocused) {
655 [controller.flutterView.window makeFirstResponder:controller.flutterView];
659 - (BOOL)runWithEntrypoint:(NSString*)entrypoint {
665 NSLog(
@"Attempted to run an engine with no view controller without headless mode enabled.");
669 [
self addInternalPlugins];
672 std::vector<const char*> argv = {[
self.executableName UTF8String]};
673 std::vector<std::string> switches =
self.switches;
677 std::find(switches.begin(), switches.end(),
"--enable-impeller=true") != switches.end()) {
678 switches.push_back(
"--enable-impeller=true");
682 std::find(switches.begin(), switches.end(),
"--enable-flutter-gpu=true") != switches.end()) {
683 switches.push_back(
"--enable-flutter-gpu=true");
686 std::transform(switches.begin(), switches.end(), std::back_inserter(argv),
687 [](
const std::string& arg) ->
const char* { return arg.c_str(); });
689 std::vector<const char*> dartEntrypointArgs;
690 for (NSString* argument in [
_project dartEntrypointArguments]) {
691 dartEntrypointArgs.push_back([argument UTF8String]);
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,
707 [[engine viewControllerForIdentifier:kFlutterImplicitViewId] updateSemantics:update];
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,
716 std::stringstream stream;
718 stream << tag <<
": ";
721 std::string log = stream.str();
722 [FlutterLogger logDirect:[NSString stringWithUTF8String:log.c_str()]];
725 flutterArguments.engine_id =
reinterpret_cast<int64_t
>((__bridge
void*)
self);
726 flutterArguments.enable_wide_gamut =
_project.enableWideGamut;
728 BOOL mergedPlatformUIThread = YES;
729 NSNumber* enableMergedPlatformUIThread =
730 [[NSBundle mainBundle] objectForInfoDictionaryKey:@"FLTEnableMergedPlatformUIThread"];
731 if (enableMergedPlatformUIThread != nil) {
732 mergedPlatformUIThread = enableMergedPlatformUIThread.boolValue;
735 if (mergedPlatformUIThread) {
736 NSLog(
@"Running with merged UI and platform thread. Experimental.");
742 FlutterTaskRunnerDescription platformTaskRunnerDescription =
743 [
self createPlatformThreadTaskDescription];
744 std::optional<FlutterTaskRunnerDescription> uiTaskRunnerDescription;
745 if (mergedPlatformUIThread) {
746 uiTaskRunnerDescription = [
self createPlatformThreadTaskDescription];
749 const FlutterCustomTaskRunners custom_task_runners = {
750 .struct_size =
sizeof(FlutterCustomTaskRunners),
751 .platform_task_runner = &platformTaskRunnerDescription,
752 .thread_priority_setter = SetThreadPriority,
753 .ui_task_runner = uiTaskRunnerDescription ? &uiTaskRunnerDescription.value() :
nullptr,
755 flutterArguments.custom_task_runners = &custom_task_runners;
757 [
self loadAOTData:_project.assetsPath];
759 flutterArguments.aot_data =
_aotData;
762 flutterArguments.compositor = [
self createFlutterCompositor];
764 flutterArguments.on_pre_engine_restart_callback = [](
void*
user_data) {
766 [engine engineCallbackOnPreEngineRestart];
769 flutterArguments.vsync_callback = [](
void*
user_data, intptr_t baton) {
771 [engine onVSync:baton];
774 flutterArguments.view_focus_change_request_callback =
775 [](
const FlutterViewFocusChangeRequest* request,
void*
user_data) {
777 [engine onFocusChangeRequest:request];
780 FlutterRendererConfig rendererConfig = [_renderer createRendererConfig];
781 FlutterEngineResult result = _embedderAPI.Initialize(
782 FLUTTER_ENGINE_VERSION, &rendererConfig, &flutterArguments, (__bridge
void*)(
self), &_engine);
783 if (result != kSuccess) {
784 NSLog(
@"Failed to initialize Flutter engine: error %d", result);
788 result = _embedderAPI.RunInitialized(_engine);
789 if (result != kSuccess) {
790 NSLog(
@"Failed to run an initialized engine: error %d", result);
794 [
self sendUserLocales];
797 NSEnumerator* viewControllerEnumerator = [_viewControllers objectEnumerator];
799 while ((nextViewController = [viewControllerEnumerator nextObject])) {
800 [
self updateWindowMetricsForViewController:nextViewController];
803 [
self updateDisplayConfig];
806 [
self sendInitialSettings];
810 - (void)loadAOTData:(NSString*)assetsDir {
811 if (!_embedderAPI.RunsAOTCompiledDartCode()) {
815 BOOL isDirOut =
false;
816 NSFileManager* fileManager = [NSFileManager defaultManager];
820 NSString* elfPath = [NSString pathWithComponents:@[ assetsDir, @"app_elf_snapshot.so" ]];
822 if (![fileManager fileExistsAtPath:elfPath isDirectory:&isDirOut]) {
826 FlutterEngineAOTDataSource source = {};
827 source.type = kFlutterEngineAOTDataSourceTypeElfPath;
828 source.elf_path = [elfPath cStringUsingEncoding:NSUTF8StringEncoding];
830 auto result = _embedderAPI.CreateAOTData(&source, &
_aotData);
831 if (result != kSuccess) {
832 NSLog(
@"Failed to load AOT data from: %@", elfPath);
839 NSAssert(controller != nil,
@"The controller must not be nil.");
841 NSAssert(controller.
engine == nil,
842 @"The FlutterViewController is unexpectedly attached to "
843 @"engine %@ before initialization.",
847 @"The requested view ID is occupied.");
848 [_viewControllers setObject:controller forKey:@(viewIdentifier)];
849 [controller setUpWithEngine:self viewIdentifier:viewIdentifier];
850 NSAssert(controller.
viewIdentifier == viewIdentifier,
@"Failed to assign view ID.");
854 NSAssert(controller.
attached,
@"The FlutterViewController should switch to the attached mode "
855 @"after it is added to a FlutterEngine.");
856 NSAssert(controller.
engine ==
self,
857 @"The FlutterViewController was added to %@, but its engine unexpectedly became %@.",
860 if (controller.viewLoaded) {
861 [
self viewControllerViewDidLoad:controller];
864 if (viewIdentifier != kFlutterImplicitViewId) {
867 FlutterWindowMetricsEvent metrics{
868 .struct_size =
sizeof(FlutterWindowMetricsEvent),
874 FlutterAddViewInfo info{.struct_size =
sizeof(FlutterAddViewInfo),
875 .view_id = viewIdentifier,
876 .view_metrics = &metrics,
878 .add_view_callback = [](
const FlutterAddViewResult* r) {
879 auto added =
reinterpret_cast<bool*
>(r->user_data);
883 _embedderAPI.AddView(_engine, &info);
886 NSLog(
@"Failed to add view with ID %llu", viewIdentifier);
896 block:^(CFTimeInterval timestamp, CFTimeInterval targetTimestamp,
899 uint64_t targetTimeNanos =
901 FlutterEngine* engine = weakSelf;
903 engine->_embedderAPI.OnVsync(_engine, baton, timeNanos, targetTimeNanos);
908 [_vsyncWaiters setObject:waiter forKey:@(viewController.viewIdentifier)];
913 if (viewIdentifier != kFlutterImplicitViewId) {
914 bool removed =
false;
915 FlutterRemoveViewInfo info;
916 info.struct_size =
sizeof(FlutterRemoveViewInfo);
917 info.view_id = viewIdentifier;
918 info.user_data = &removed;
922 info.remove_view_callback = [](
const FlutterRemoveViewResult* r) {
923 auto removed =
reinterpret_cast<bool*
>(r->user_data);
924 [FlutterRunLoop.mainRunLoop performBlock:^{
928 _embedderAPI.RemoveView(_engine, &info);
930 [[FlutterRunLoop mainRunLoop] pollFlutterMessagesOnce];
939 if (controller != nil) {
940 [controller detachFromEngine];
942 @"The FlutterViewController unexpectedly stays attached after being removed. "
943 @"In unit tests, this is likely because either the FlutterViewController or "
944 @"the FlutterEngine is mocked. Please subclass these classes instead.");
946 [_viewControllers removeObjectForKey:@(viewIdentifier)];
950 waiter = [_vsyncWaiters objectForKey:@(viewIdentifier)];
951 [_vsyncWaiters removeObjectForKey:@(viewIdentifier)];
956 - (void)shutDownIfNeeded {
958 [
self shutDownEngine];
964 NSAssert(controller == nil || controller.
viewIdentifier == viewIdentifier,
965 @"The stored controller has unexpected view ID.");
971 [_viewControllers objectForKey:@(kFlutterImplicitViewId)];
972 if (currentController == controller) {
976 if (currentController == nil && controller != nil) {
978 NSAssert(controller.
engine == nil,
979 @"Failed to set view controller to the engine: "
980 @"The given FlutterViewController is already attached to an engine %@. "
981 @"If you wanted to create an FlutterViewController and set it to an existing engine, "
982 @"you should use FlutterViewController#init(engine:, nibName, bundle:) instead.",
984 [
self registerViewController:controller forIdentifier:kFlutterImplicitViewId];
985 }
else if (currentController != nil && controller == nil) {
986 NSAssert(currentController.
viewIdentifier == kFlutterImplicitViewId,
987 @"The default controller has an unexpected ID %llu", currentController.
viewIdentifier);
989 [
self deregisterViewControllerForIdentifier:kFlutterImplicitViewId];
990 [
self shutDownIfNeeded];
994 @"Failed to set view controller to the engine: "
995 @"The engine already has an implicit view controller %@. "
996 @"If you wanted to make the implicit view render in a different window, "
997 @"you should attach the current view controller to the window instead.",
1003 return [
self viewControllerForIdentifier:kFlutterImplicitViewId];
1006 - (FlutterCompositor*)createFlutterCompositor {
1008 _compositor.struct_size =
sizeof(FlutterCompositor);
1011 _compositor.create_backing_store_callback = [](
const FlutterBackingStoreConfig* config,
1012 FlutterBackingStore* backing_store_out,
1016 config, backing_store_out);
1019 _compositor.collect_backing_store_callback = [](
const FlutterBackingStore* backing_store,
1023 _compositor.present_view_callback = [](
const FlutterPresentViewInfo* info) {
1025 ->Present(info->view_id, info->layers, info->layers_count);
1037 #pragma mark - Framework-internal methods
1043 NSAssert(
self.viewController == nil,
1044 @"The engine already has a view controller for the implicit view.");
1045 self.viewController = controller;
1050 [
self registerViewController:controller forIdentifier:viewIdentifier];
1054 - (void)enableMultiView {
1056 NSAssert(
self.viewController == nil,
1057 @"Multiview can only be enabled before adding any view controllers.");
1063 FlutterViewFocusEvent
event{
1064 .struct_size =
sizeof(FlutterViewFocusEvent),
1065 .view_id = viewIdentifier,
1067 .direction = kUndefined,
1069 _embedderAPI.SendViewFocusEvent(_engine, &event);
1073 FlutterViewFocusEvent
event{
1074 .struct_size =
sizeof(FlutterViewFocusEvent),
1075 .view_id = viewIdentifier,
1076 .state = kUnfocused,
1077 .direction = kUndefined,
1079 _embedderAPI.SendViewFocusEvent(_engine, &event);
1083 [
self deregisterViewControllerForIdentifier:viewController.viewIdentifier];
1084 [
self shutDownIfNeeded];
1088 return _engine !=
nullptr;
1091 - (void)updateDisplayConfig:(NSNotification*)notification {
1092 [
self updateDisplayConfig];
1095 - (NSArray<NSScreen*>*)screens {
1096 return [NSScreen screens];
1099 - (void)updateDisplayConfig {
1104 std::vector<FlutterEngineDisplay> displays;
1105 for (NSScreen* screen : [
self screens]) {
1106 CGDirectDisplayID displayID =
1107 static_cast<CGDirectDisplayID
>([screen.deviceDescription[@"NSScreenNumber"] integerValue]);
1109 double devicePixelRatio = screen.backingScaleFactor;
1110 FlutterEngineDisplay display;
1111 display.struct_size =
sizeof(display);
1112 display.display_id = displayID;
1113 display.single_display =
false;
1114 display.width =
static_cast<size_t>(screen.frame.size.width) * devicePixelRatio;
1115 display.height =
static_cast<size_t>(screen.frame.size.height) * devicePixelRatio;
1116 display.device_pixel_ratio = devicePixelRatio;
1118 CVDisplayLinkRef displayLinkRef = nil;
1119 CVReturn error = CVDisplayLinkCreateWithCGDisplay(displayID, &displayLinkRef);
1122 CVTime nominal = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(displayLinkRef);
1123 if (!(nominal.flags & kCVTimeIsIndefinite)) {
1124 double refreshRate =
static_cast<double>(nominal.timeScale) / nominal.timeValue;
1125 display.refresh_rate = round(refreshRate);
1127 CVDisplayLinkRelease(displayLinkRef);
1129 display.refresh_rate = 0;
1132 displays.push_back(display);
1134 _embedderAPI.NotifyDisplayUpdate(_engine, kFlutterEngineDisplaysUpdateTypeStartup,
1135 displays.data(), displays.size());
1138 - (void)onSettingsChanged:(NSNotification*)notification {
1140 NSString* brightness =
1141 [[NSUserDefaults standardUserDefaults] stringForKey:@"AppleInterfaceStyle"];
1142 [_settingsChannel sendMessage:@{
1143 @"platformBrightness" : [brightness isEqualToString:@"Dark"] ? @"dark" : @"light",
1145 @"textScaleFactor" : @1.0,
1150 - (void)sendInitialSettings {
1152 [[NSDistributedNotificationCenter defaultCenter]
1154 selector:@selector(onSettingsChanged:)
1155 name:@"AppleInterfaceThemeChangedNotification"
1157 [
self onSettingsChanged:nil];
1160 - (FlutterEngineProcTable&)embedderAPI {
1161 return _embedderAPI;
1164 - (nonnull NSString*)executableName {
1165 return [[[NSProcessInfo processInfo] arguments] firstObject] ?:
@"Flutter";
1169 if (!_engine || !viewController || !viewController.viewLoaded) {
1172 NSAssert([
self viewControllerForIdentifier:viewController.
viewIdentifier] == viewController,
1173 @"The provided view controller is not attached to this engine.");
1175 CGRect scaledBounds = [view convertRectToBacking:view.bounds];
1176 CGSize scaledSize = scaledBounds.size;
1177 double pixelRatio = view.layer.contentsScale;
1178 auto displayId = [view.window.screen.deviceDescription[@"NSScreenNumber"] integerValue];
1179 FlutterWindowMetricsEvent windowMetricsEvent = {
1180 .struct_size =
sizeof(windowMetricsEvent),
1181 .width =
static_cast<size_t>(scaledSize.width),
1182 .height =
static_cast<size_t>(scaledSize.height),
1183 .pixel_ratio = pixelRatio,
1184 .left =
static_cast<size_t>(scaledBounds.origin.x),
1185 .top =
static_cast<size_t>(scaledBounds.origin.y),
1186 .display_id =
static_cast<uint64_t
>(displayId),
1190 CGSize maximumContentSize = [view convertSizeToBacking:view.maximumContentSize];
1191 CGSize minimumContentSize = [view convertSizeToBacking:view.minimumContentSize];
1192 windowMetricsEvent.has_constraints =
true;
1193 windowMetricsEvent.min_width_constraint =
static_cast<size_t>(minimumContentSize.width);
1194 windowMetricsEvent.min_height_constraint =
static_cast<size_t>(minimumContentSize.height);
1195 windowMetricsEvent.max_width_constraint =
static_cast<size_t>(maximumContentSize.width);
1196 windowMetricsEvent.max_height_constraint =
static_cast<size_t>(maximumContentSize.height);
1198 windowMetricsEvent.min_width_constraint =
static_cast<size_t>(scaledSize.width);
1199 windowMetricsEvent.min_height_constraint =
static_cast<size_t>(scaledSize.height);
1200 windowMetricsEvent.max_width_constraint =
static_cast<size_t>(scaledSize.width);
1201 windowMetricsEvent.max_height_constraint =
static_cast<size_t>(scaledSize.height);
1203 _embedderAPI.SendWindowMetricsEvent(_engine, &windowMetricsEvent);
1206 - (void)sendPointerEvent:(const FlutterPointerEvent&)event {
1207 _embedderAPI.SendPointerEvent(_engine, &event, 1);
1211 - (void)setSemanticsEnabled:(BOOL)enabled {
1212 if (_semanticsEnabled == enabled) {
1215 _semanticsEnabled = enabled;
1218 NSEnumerator* viewControllerEnumerator = [_viewControllers objectEnumerator];
1220 while ((nextViewController = [viewControllerEnumerator nextObject])) {
1221 [nextViewController notifySemanticsEnabledChanged];
1224 _embedderAPI.UpdateSemanticsEnabled(_engine, _semanticsEnabled);
1227 - (void)dispatchSemanticsAction:(FlutterSemanticsAction)action
1228 toTarget:(uint16_t)target
1229 withData:(fml::MallocMapping)data {
1230 _embedderAPI.DispatchSemanticsAction(_engine, target, action, data.GetMapping(), data.GetSize());
1237 #pragma mark - Private methods
1239 - (void)sendUserLocales {
1240 if (!
self.running) {
1245 NSMutableArray<NSLocale*>* locales = [NSMutableArray array];
1246 std::vector<FlutterLocale> flutterLocales;
1247 flutterLocales.reserve(locales.count);
1248 for (NSString* localeID in [NSLocale preferredLanguages]) {
1249 NSLocale* locale = [[NSLocale alloc] initWithLocaleIdentifier:localeID];
1250 [locales addObject:locale];
1254 std::vector<const FlutterLocale*> flutterLocaleList;
1255 flutterLocaleList.reserve(flutterLocales.size());
1256 std::transform(flutterLocales.begin(), flutterLocales.end(),
1257 std::back_inserter(flutterLocaleList),
1258 [](
const auto& arg) ->
const auto* { return &arg; });
1259 _embedderAPI.UpdateLocales(_engine, flutterLocaleList.data(), flutterLocaleList.size());
1262 - (void)engineCallbackOnPlatformMessage:(const FlutterPlatformMessage*)message {
1263 NSData* messageData = nil;
1264 if (message->message_size > 0) {
1265 messageData = [NSData dataWithBytesNoCopy:(void*)message->message
1266 length:message->message_size
1269 NSString* channel = @(message->channel);
1270 __block
const FlutterPlatformMessageResponseHandle* responseHandle = message->response_handle;
1272 NSMutableArray* isResponseValid =
self.isResponseValid;
1273 FlutterEngineSendPlatformMessageResponseFnPtr sendPlatformMessageResponse =
1274 _embedderAPI.SendPlatformMessageResponse;
1276 @
synchronized(isResponseValid) {
1277 if (![isResponseValid[0] boolValue]) {
1281 if (responseHandle) {
1282 sendPlatformMessageResponse(weakSelf->_engine, responseHandle,
1283 static_cast<const uint8_t*
>(response.bytes), response.length);
1284 responseHandle = NULL;
1286 NSLog(
@"Error: Message responses can be sent only once. Ignoring duplicate response "
1295 handlerInfo.
handler(messageData, binaryResponseHandler);
1297 binaryResponseHandler(nil);
1301 - (void)engineCallbackOnPreEngineRestart {
1302 NSEnumerator* viewControllerEnumerator = [_viewControllers objectEnumerator];
1304 while ((nextViewController = [viewControllerEnumerator nextObject])) {
1307 [_windowController closeAllWindows];
1308 [_platformViewController reset];
1314 - (void)onVSync:(uintptr_t)baton {
1319 [_vsyncWaiters objectForKey:[_vsyncWaiters.keyEnumerator nextObject]];
1320 if (waiter != nil) {
1326 self.embedderAPI.OnVsync(_engine, baton, 0, 0);
1329 if ([NSThread isMainThread]) {
1332 [FlutterRunLoop.mainRunLoop performBlock:block];
1339 - (void)shutDownEngine {
1340 if (_engine ==
nullptr) {
1344 FlutterEngineResult result = _embedderAPI.Deinitialize(_engine);
1345 if (result != kSuccess) {
1346 NSLog(
@"Could not de-initialize the Flutter engine: error %d", result);
1349 result = _embedderAPI.Shutdown(_engine);
1350 if (result != kSuccess) {
1351 NSLog(
@"Failed to shut down Flutter engine: error %d", result);
1357 NSAssert([[NSThread currentThread] isMainThread],
@"Must be called on the main thread.");
1358 return (__bridge
FlutterEngine*)
reinterpret_cast<void*
>(identifier);
1361 - (void)setUpPlatformViewChannel {
1368 [_platformViewsChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
1369 [[weakSelf platformViewController] handleMethodCall:call result:result];
1373 - (void)setUpAccessibilityChannel {
1379 [_accessibilityChannel setMessageHandler:^(id message, FlutterReply reply) {
1380 [weakSelf handleAccessibilityEvent:message];
1383 - (void)setUpNotificationCenterListeners {
1384 NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
1386 [center addObserver:self
1387 selector:@selector(onAccessibilityStatusChanged:)
1388 name:kEnhancedUserInterfaceNotification
1390 [center addObserver:self
1391 selector:@selector(applicationWillTerminate:)
1392 name:NSApplicationWillTerminateNotification
1394 [center addObserver:self
1395 selector:@selector(windowDidChangeScreen:)
1396 name:NSWindowDidChangeScreenNotification
1398 [center addObserver:self
1399 selector:@selector(updateDisplayConfig:)
1400 name:NSApplicationDidChangeScreenParametersNotification
1404 - (void)addInternalPlugins {
1418 [_platformChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
1419 [weakSelf handleMethodCall:call result:result];
1426 [_screenshotChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
1430 message:@"Engine deallocated."
1434 FlutterViewController* viewController =
1435 [strongSelf viewControllerForIdentifier:flutter::kFlutterImplicitViewId];
1436 if (!viewController) {
1438 message:@"No view controller."
1442 NSArray<FlutterSurface*>* frontSurfaces =
1443 viewController.flutterView.surfaceManager.frontSurfaces;
1444 if (frontSurfaces.count == 0) {
1451 FlutterSurface* surface = frontSurfaces.firstObject;
1452 IOSurfaceRef ioSurface = surface.ioSurface;
1454 size_t width = IOSurfaceGetWidth(ioSurface);
1455 size_t height = IOSurfaceGetHeight(ioSurface);
1456 size_t bytesPerRow = IOSurfaceGetBytesPerRow(ioSurface);
1457 size_t bytesPerElement = IOSurfaceGetBytesPerElement(ioSurface);
1458 uint32_t pixelFormat = (uint32_t)IOSurfaceGetPixelFormat(ioSurface);
1460 NSString* formatString;
1461 switch (pixelFormat) {
1462 case kCVPixelFormatType_40ARGBLEWideGamut:
1463 formatString = @"MTLPixelFormatBGRA10_XR";
1465 case kCVPixelFormatType_32BGRA:
1466 formatString = @"MTLPixelFormatBGRA8Unorm";
1469 formatString = [NSString stringWithFormat:@"Unknown(%u)", pixelFormat];
1473 IOSurfaceLock(ioSurface, kIOSurfaceLockReadOnly, nil);
1474 void* baseAddress = IOSurfaceGetBaseAddress(ioSurface);
1477 size_t packedBytesPerRow = width * bytesPerElement;
1478 NSMutableData* packedData = [NSMutableData dataWithLength:packedBytesPerRow * height];
1479 uint8_t* dest = (uint8_t*)packedData.mutableBytes;
1480 for (size_t row = 0; row < height; row++) {
1481 memcpy(dest + row * packedBytesPerRow, (uint8_t*)baseAddress + row * bytesPerRow,
1485 IOSurfaceUnlock(ioSurface, kIOSurfaceLockReadOnly, nil);
1496 - (void)didUpdateMouseCursor:(NSCursor*)cursor {
1500 [_lastViewWithPointerEvent didUpdateMouseCursor:cursor];
1503 - (void)applicationWillTerminate:(NSNotification*)notification {
1504 [
self shutDownEngine];
1507 - (void)windowDidChangeScreen:(NSNotification*)notification {
1510 NSEnumerator* viewControllerEnumerator = [_viewControllers objectEnumerator];
1512 while ((nextViewController = [viewControllerEnumerator nextObject])) {
1513 [
self updateWindowMetricsForViewController:nextViewController];
1514 [nextViewController updateWideGamutForScreen];
1518 - (void)onAccessibilityStatusChanged:(NSNotification*)notification {
1519 BOOL enabled = [notification.userInfo[kEnhancedUserInterfaceKey] boolValue];
1520 NSEnumerator* viewControllerEnumerator = [_viewControllers objectEnumerator];
1522 while ((nextViewController = [viewControllerEnumerator nextObject])) {
1526 self.semanticsEnabled = enabled;
1528 - (void)handleAccessibilityEvent:(NSDictionary<NSString*,
id>*)annotatedEvent {
1529 NSString* type = annotatedEvent[@"type"];
1530 if ([type isEqualToString:
@"announce"]) {
1531 NSString* message = annotatedEvent[@"data"][@"message"];
1532 NSNumber* assertiveness = annotatedEvent[@"data"][@"assertiveness"];
1533 if (message == nil) {
1537 NSAccessibilityPriorityLevel priority = [assertiveness isEqualToNumber:@1]
1538 ? NSAccessibilityPriorityHigh
1539 : NSAccessibilityPriorityMedium;
1541 [
self announceAccessibilityMessage:message withPriority:priority];
1545 - (void)announceAccessibilityMessage:(NSString*)message
1546 withPriority:(NSAccessibilityPriorityLevel)priority {
1547 NSAccessibilityPostNotificationWithUserInfo(
1548 [
self viewControllerForIdentifier:kFlutterImplicitViewId].flutterView,
1549 NSAccessibilityAnnouncementRequestedNotification,
1550 @{NSAccessibilityAnnouncementKey : message, NSAccessibilityPriorityKey : @(priority)});
1553 if ([call.
method isEqualToString:
@"SystemNavigator.pop"]) {
1554 [[NSApplication sharedApplication] terminate:self];
1556 }
else if ([call.
method isEqualToString:
@"SystemSound.play"]) {
1557 [
self playSystemSound:call.arguments];
1559 }
else if ([call.
method isEqualToString:
@"Clipboard.getData"]) {
1560 result([
self getClipboardData:call.
arguments]);
1561 }
else if ([call.
method isEqualToString:
@"Clipboard.setData"]) {
1562 [
self setClipboardData:call.arguments];
1564 }
else if ([call.
method isEqualToString:
@"Clipboard.hasStrings"]) {
1565 result(@{
@"value" : @([
self clipboardHasStrings])});
1566 }
else if ([call.
method isEqualToString:
@"System.exitApplication"]) {
1567 if ([
self terminationHandler] == nil) {
1572 [NSApp terminate:self];
1575 [[
self terminationHandler] handleRequestAppExitMethodCall:call.arguments result:result];
1577 }
else if ([call.
method isEqualToString:
@"System.initializationComplete"]) {
1578 if ([
self terminationHandler] != nil) {
1579 [
self terminationHandler].acceptingRequests = YES;
1587 - (void)playSystemSound:(NSString*)soundType {
1588 if ([soundType isEqualToString:
@"SystemSoundType.alert"]) {
1593 - (NSDictionary*)getClipboardData:(NSString*)format {
1595 NSString* stringInPasteboard = [
self.pasteboard stringForType:NSPasteboardTypeString];
1596 return stringInPasteboard == nil ? nil : @{
@"text" : stringInPasteboard};
1601 - (void)setClipboardData:(NSDictionary*)data {
1602 NSString* text = data[@"text"];
1603 [
self.pasteboard clearContents];
1604 if (text && ![text isEqual:[NSNull
null]]) {
1605 [
self.pasteboard setString:text forType:NSPasteboardTypeString];
1609 - (BOOL)clipboardHasStrings {
1610 return [
self.pasteboard stringForType:NSPasteboardTypeString].length > 0;
1613 - (std::vector<std::string>)switches {
1617 #pragma mark - FlutterAppLifecycleDelegate
1620 NSString* nextState =
1621 [[NSString alloc] initWithCString:flutter::AppLifecycleStateToString(state)];
1622 [
self sendOnChannel:kFlutterLifecycleChannel
1623 message:[nextState dataUsingEncoding:NSUTF8StringEncoding]];
1630 - (void)handleWillBecomeActive:(NSNotification*)notification {
1633 [
self setApplicationState:flutter::AppLifecycleState::kHidden];
1635 [
self setApplicationState:flutter::AppLifecycleState::kResumed];
1643 - (void)handleWillResignActive:(NSNotification*)notification {
1646 [
self setApplicationState:flutter::AppLifecycleState::kHidden];
1648 [
self setApplicationState:flutter::AppLifecycleState::kInactive];
1656 - (void)handleDidChangeOcclusionState:(NSNotification*)notification {
1657 NSApplicationOcclusionState occlusionState = [[NSApplication sharedApplication] occlusionState];
1658 if (occlusionState & NSApplicationOcclusionStateVisible) {
1661 [
self setApplicationState:flutter::AppLifecycleState::kResumed];
1663 [
self setApplicationState:flutter::AppLifecycleState::kInactive];
1667 [
self setApplicationState:flutter::AppLifecycleState::kHidden];
1671 #pragma mark - FlutterBinaryMessenger
1673 - (void)sendOnChannel:(nonnull NSString*)channel message:(nullable NSData*)message {
1674 [
self sendOnChannel:channel message:message binaryReply:nil];
1677 - (void)sendOnChannel:(NSString*)channel
1678 message:(NSData* _Nullable)message
1680 FlutterPlatformMessageResponseHandle* response_handle =
nullptr;
1685 auto captures = std::make_unique<Captures>();
1686 captures->reply = callback;
1687 auto message_reply = [](
const uint8_t* data,
size_t data_size,
void*
user_data) {
1688 auto captures =
reinterpret_cast<Captures*
>(
user_data);
1689 NSData* reply_data = nil;
1690 if (data !=
nullptr && data_size > 0) {
1691 reply_data = [NSData dataWithBytes:static_cast<const void*>(data) length:data_size];
1693 captures->reply(reply_data);
1697 FlutterEngineResult create_result = _embedderAPI.PlatformMessageCreateResponseHandle(
1698 _engine, message_reply, captures.get(), &response_handle);
1699 if (create_result != kSuccess) {
1700 NSLog(
@"Failed to create a FlutterPlatformMessageResponseHandle (%d)", create_result);
1706 FlutterPlatformMessage platformMessage = {
1707 .struct_size =
sizeof(FlutterPlatformMessage),
1708 .channel = [channel UTF8String],
1709 .message =
static_cast<const uint8_t*
>(message.bytes),
1710 .message_size = message.length,
1711 .response_handle = response_handle,
1714 FlutterEngineResult message_result = _embedderAPI.SendPlatformMessage(_engine, &platformMessage);
1715 if (message_result != kSuccess) {
1716 NSLog(
@"Failed to send message to Flutter engine on channel '%@' (%d).", channel,
1720 if (response_handle !=
nullptr) {
1721 FlutterEngineResult release_result =
1722 _embedderAPI.PlatformMessageReleaseResponseHandle(_engine, response_handle);
1723 if (release_result != kSuccess) {
1724 NSLog(
@"Failed to release the response handle (%d).", release_result);
1730 binaryMessageHandler:
1735 handler:[handler copy]];
1742 NSString* foundChannel = nil;
1745 if ([handlerInfo.
connection isEqual:@(connection)]) {
1751 [_messengerHandlers removeObjectForKey:foundChannel];
1755 #pragma mark - FlutterPluginRegistry
1758 id<FlutterPluginRegistrar> registrar =
self.pluginRegistrars[pluginName];
1762 self.pluginRegistrars[pluginName] = registrarImpl;
1763 registrar = registrarImpl;
1768 - (nullable NSObject*)valuePublishedByPlugin:(NSString*)pluginName {
1772 #pragma mark - FlutterTextureRegistrar
1775 return [_renderer registerTexture:texture];
1778 - (BOOL)registerTextureWithID:(int64_t)textureId {
1779 return _embedderAPI.RegisterExternalTexture(_engine, textureId) == kSuccess;
1782 - (void)textureFrameAvailable:(int64_t)textureID {
1783 [_renderer textureFrameAvailable:textureID];
1786 - (BOOL)markTextureFrameAvailable:(int64_t)textureID {
1787 return _embedderAPI.MarkExternalTextureFrameAvailable(_engine, textureID) == kSuccess;
1790 - (void)unregisterTexture:(int64_t)textureID {
1791 [_renderer unregisterTexture:textureID];
1794 - (BOOL)unregisterTextureWithID:(int64_t)textureID {
1795 return _embedderAPI.UnregisterExternalTexture(_engine, textureID) == kSuccess;
1798 #pragma mark - Task runner integration
1800 - (void)postMainThreadTask:(FlutterTask)task targetTimeInNanoseconds:(uint64_t)targetTime {
1803 const auto engine_time = _embedderAPI.GetCurrentTime();
1804 [FlutterRunLoop.mainRunLoop
1805 performAfterDelay:(targetTime - (double)engine_time) / NSEC_PER_SEC
1808 if (self != nil && self->_engine != nil) {
1809 auto result = _embedderAPI.RunTask(self->_engine, &task);
1810 if (result != kSuccess) {
1811 NSLog(@"Could not post a task to the Flutter engine.");
1818 - (
flutter::FlutterCompositor*)macOSCompositor {
1822 #pragma mark - FlutterKeyboardManagerDelegate
1827 - (void)sendKeyEvent:(const FlutterKeyEvent&)event
1828 callback:(FlutterKeyEventCallback)callback
1829 userData:(
void*)userData {
1830 _embedderAPI.SendKeyEvent(_engine, &event, callback, userData);
NS_ASSUME_NONNULL_BEGIN typedef void(^ FlutterBinaryReply)(NSData *_Nullable reply)
void(^ FlutterBinaryMessageHandler)(NSData *_Nullable message, FlutterBinaryReply reply)
int64_t FlutterBinaryMessengerConnection
void(^ FlutterResult)(id _Nullable result)
FLUTTER_DARWIN_EXPORT NSObject const * FlutterMethodNotImplemented
FlutterBinaryMessengerConnection _connection
FlutterMethodChannel * _platformViewsChannel
_FlutterEngineAOTData * _aotData
std::unique_ptr< flutter::FlutterCompositor > _macOSCompositor
static const int kMainThreadPriority
static void OnPlatformMessage(const FlutterPlatformMessage *message, void *user_data)
FlutterPlatformViewController * _platformViewController
FlutterBasicMessageChannel * _accessibilityChannel
FlutterBasicMessageChannel * _settingsChannel
static FlutterLocale FlutterLocaleFromNSLocale(NSLocale *locale)
BOOL _allowHeadlessExecution
NSMutableDictionary< NSString *, FlutterEngineHandlerInfo * > * _messengerHandlers
FlutterBinaryMessengerConnection _currentMessengerConnection
FlutterMethodChannel * _platformChannel
NSString *const kFlutterLifecycleChannel
static NSString *const kEnhancedUserInterfaceNotification
The private notification for voice over.
NSMapTable< NSNumber *, FlutterVSyncWaiter * > * _vsyncWaiters
FlutterViewIdentifier _nextViewIdentifier
NSString *const kFlutterPlatformChannel
FlutterMethodChannel * _screenshotChannel
FlutterTextInputPlugin * _textInputPlugin
FlutterDartProject * _project
NSMapTable * _viewControllers
FlutterCompositor _compositor
__weak FlutterView * _lastViewWithPointerEvent
FlutterKeyboardManager * _keyboardManager
FlutterWindowController * _windowController
FlutterTerminationCallback _terminator
constexpr char kTextPlainFormat[]
Clipboard plain text format.
__weak FlutterEngine * _flutterEngine
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)
instancetype displayLinkWithView:(NSView *view)
FlutterBinaryMessageHandler handler
id< FlutterBinaryMessenger > binaryMessenger
NSObject * publishedValue
instancetype errorWithCode:message:details:(NSString *code,[message] NSString *_Nullable message,[details] id _Nullable details)
instancetype methodCallWithMethodName:arguments:(NSString *method,[arguments] id _Nullable arguments)
void setMethodCallHandler:(FlutterMethodCallHandler _Nullable handler)
instancetype methodChannelWithName:binaryMessenger:codec:(NSString *name,[binaryMessenger] NSObject< FlutterBinaryMessenger > *messenger,[codec] NSObject< FlutterMethodCodec > *codec)
void registerWithRegistrar:delegate:(nonnull id< FlutterPluginRegistrar > registrar,[delegate] nullable id< FlutterMouseCursorPluginDelegate > delegate)
instancetype typedDataWithBytes:(NSData *data)
Converts between the time representation used by Flutter Engine and CAMediaTime.
uint64_t CAMediaTimeToEngineTime:(CFTimeInterval time)
void waitForVSync:(uintptr_t baton)
void onPreEngineRestart()
FlutterViewIdentifier viewIdentifier
void onAccessibilityStatusChanged:(BOOL enabled)
std::vector< std::string > GetSwitchesFromEnvironment()
instancetype sharedInstance()
void handleMethodCall:result:(FlutterMethodCall *call,[result] FlutterResult result)