diff --git a/BUILD.gn b/BUILD.gn index b5720381d1148..24f49477c73cd 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -177,7 +177,7 @@ group("unittests") { } if (is_ios) { - public_deps += [ "//flutter/shell/platform/darwin/ios:ios_test_flutter" ] + # public_deps += [ "//flutter/shell/platform/darwin/ios:ios_test_flutter" ] } # Compile all unittests targets if enabled. diff --git a/DEPS b/DEPS index 46438c50e89fa..e22686ff45722 100644 --- a/DEPS +++ b/DEPS @@ -152,7 +152,7 @@ vars = { "upstream_benchmark": "https://github.com/google/benchmark.git", "upstream_boringssl": "https://github.com/openssl/openssl.git", "upstream_brotli": "https://github.com/google/brotli.git", - "upstream_buildroot": "https://github.com/flutter/buildroot.git", + "upstream_buildroot": "https://github.com/easion/buildroot_tvos.git", "upstream_dart_style": "https://github.com/dart-lang/dart_style.git", "upstream_dartdoc": "https://github.com/dart-lang/dartdoc.git", "upstream_equatable": "https://github.com/felangel/equatable.git", @@ -253,7 +253,7 @@ allowed_hosts = [ ] deps = { - 'src': 'https://github.com/flutter/buildroot.git' + '@' + 'cc9bcddf1524812c80ef741191d5db7469e705de', + 'src': 'https://github.com/easion/buildroot_tvos.git' + '@' + 'master', 'src/flutter/third_party/depot_tools': Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '580b4ff3f5cd0dcaa2eacda28cefe0f45320e8f7', diff --git a/build/secondary/flutter/third_party/ocmock/BUILD.gn b/build/secondary/flutter/third_party/ocmock/BUILD.gn index 5f1a172b8b255..cc75f7d69f9bb 100644 --- a/build/secondary/flutter/third_party/ocmock/BUILD.gn +++ b/build/secondary/flutter/third_party/ocmock/BUILD.gn @@ -23,7 +23,7 @@ source_set("ocmock_src") { "-Wno-misleading-indentation", ] if (is_ios) { - cflags += [ "-mios-simulator-version-min=$ios_testing_deployment_target" ] + cflags += [ "-mtvos-simulator-version-min=$ios_testing_deployment_target" ] } sources = [ @@ -115,7 +115,7 @@ static_library("ocmock") { # Force the static lib to include code from dependencies complete_static_lib = true if (is_ios) { - cflags = [ "-mios-simulator-version-min=$ios_testing_deployment_target" ] + cflags = [ "-mtvos-simulator-version-min=$ios_testing_deployment_target" ] } public_deps = [ ":ocmock_src" ] } diff --git a/fml/build_config.h b/fml/build_config.h index d42a88e5bbce0..a31db05a60941 100644 --- a/fml/build_config.h +++ b/fml/build_config.h @@ -26,10 +26,10 @@ // is really mac/ios. #include #define FML_OS_MACOSX 1 -#if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE +#if (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) || (defined(TARGET_OS_TV) && TARGET_OS_TV) #define FML_OS_IOS 1 #endif // defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE -#if defined(TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR +#if (defined(TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR) || (defined(TARGET_TV_SIMULATOR) && TARGET_TV_SIMULATOR) #define FML_OS_IOS_SIMULATOR 1 #endif // defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE #elif defined(__linux__) diff --git a/get_source.sh b/get_source.sh new file mode 100644 index 0000000000000..4c2955ed119a7 --- /dev/null +++ b/get_source.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +get_depot_tools(){ + git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git -b main + export PATH=$PATH:${PWD}/depot_tools +} + +ENGINE_VERSION=$(curl -s https://raw.githubusercontent.com/flutter/flutter/stable/bin/internal/engine.version) + +echo "ENGINE_VERSION=${ENGINE_VERSION}" + +export DEPOT_TOOLS_UPDATE=0 +export GCLIENT_PY3=1 +gclient --version + +cat << EOF > .gclient +solutions = [ + { + "managed": False, + "name": "src/flutter", + "url": "git@github.com:easion/engine.git@master", + "custom_deps": {}, + "deps_file": "DEPS", + "safesync_url": "", + }, +] +EOF + +gclient sync --no-history --revision ${FLUTTER_ENGINE_SHA} -R -D -j ${NUM_PROC} diff --git a/impeller/renderer/backend/metal/allocator_mtl.mm b/impeller/renderer/backend/metal/allocator_mtl.mm index 56cd82791255c..701479bafa1be 100644 --- a/impeller/renderer/backend/metal/allocator_mtl.mm +++ b/impeller/renderer/backend/metal/allocator_mtl.mm @@ -19,6 +19,7 @@ static bool DeviceSupportsDeviceTransientTargets(id device) { // Refer to the "Memoryless render targets" feature in the table below: // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) if (@available(ios 13.0, tvos 13.0, macos 10.15, *)) { return [device supportsFamily:MTLGPUFamilyApple2]; } else { @@ -34,21 +35,29 @@ static bool DeviceSupportsDeviceTransientTargets(id device) { return false; #endif } +#else + return [device supportsFeatureSet:MTLFeatureSet_tvOS_GPUFamily1_v1];; +#endif FML_UNREACHABLE(); } static bool DeviceHasUnifiedMemoryArchitecture(id device) { - if (@available(ios 13.0, tvos 13.0, macOS 10.15, *)) { - return [device hasUnifiedMemory]; - } else { -#if FML_OS_IOS - // iOS devices where the availability check can fail always have had UMA. - return true; +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) + if (@available(ios 13.0, tvos 13.0, macOS 10.15, *)) { + return [device hasUnifiedMemory]; + } else { + #if FML_OS_IOS + // iOS devices where the availability check can fail always have had UMA. + return true; + #else + // Mac devices where the availability check can fail have never had UMA. + return false; + #endif + } #else - // Mac devices where the availability check can fail have never had UMA. - return false; + return false; #endif - } + FML_UNREACHABLE(); } @@ -59,27 +68,36 @@ ISize DeviceMaxTextureSizeSupported(id device) { // According to the feature set table, there are two supported max sizes : // 16384 and 8192 for devices flutter support. The former is used on macs and // latest ios devices. The latter is used on old ios devices. - if (@available(macOS 10.15, iOS 13, tvOS 13, *)) { - if ([device supportsFamily:MTLGPUFamilyApple3] || - [device supportsFamily:MTLGPUFamilyMacCatalyst1] || - [device supportsFamily:MTLGPUFamilyMac1]) { - return {16384, 16384}; - } - return {8192, 8192}; - } else { -#if FML_OS_IOS - if ([device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily4_v1] || - [device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily3_v1]) { - return {16384, 16384}; +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) + if (@available(macOS 10.15, iOS 13, tvOS 13, *)) { + if ([device supportsFamily:MTLGPUFamilyApple3] || + [device supportsFamily:MTLGPUFamilyMacCatalyst1] || + [device supportsFamily:MTLGPUFamilyMac1]) { + return {16384, 16384}; + } + return {8192, 8192}; + } else { + #if FML_OS_IOS + if ([device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily4_v1] || + [device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily3_v1]) { + return {16384, 16384}; + } + #endif + #if FML_OS_MACOSX + return {16384, 16384}; + #endif + return {8192, 8192}; } -#endif -#if FML_OS_MACOSX +#else + if ([device supportsFeatureSet:MTLFeatureSet_tvOS_GPUFamily2_v1]) return {16384, 16384}; -#endif + else return {8192, 8192}; - } +#endif + } +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) static bool SupportsLossyTextureCompression(id device) { #ifdef FML_OS_IOS_SIMULATOR return false; @@ -90,6 +108,7 @@ static bool SupportsLossyTextureCompression(id device) { return false; #endif } +#endif void DebugAllocatorStats::Increment(size_t size) { size_.fetch_add(size, std::memory_order_relaxed); @@ -219,14 +238,14 @@ static MTLStorageMode ToMTLStorageMode(StorageMode mode, mtl_texture_desc.storageMode = ToMTLStorageMode( desc.storage_mode, supports_memoryless_targets_, supports_uma_); - +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) if (@available(macOS 12.5, ios 15.0, *)) { if (desc.compression_type == CompressionType::kLossy && SupportsLossyTextureCompression(device_)) { mtl_texture_desc.compressionType = MTLTextureCompressionTypeLossy; } } - +#endif #ifdef IMPELLER_DEBUG if (desc.storage_mode != StorageMode::kDeviceTransient) { debug_allocater_->Increment(desc.GetByteSizeOfAllMipLevels()); diff --git a/impeller/renderer/backend/metal/command_buffer_mtl.mm b/impeller/renderer/backend/metal/command_buffer_mtl.mm index 6eb9f2ea050ee..3a6f39d9788a0 100644 --- a/impeller/renderer/backend/metal/command_buffer_mtl.mm +++ b/impeller/renderer/backend/metal/command_buffer_mtl.mm @@ -14,6 +14,8 @@ namespace impeller { +// NOLINTEND(readability-identifier-naming) +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) API_AVAILABLE(ios(14.0), macos(11.0)) static NSString* MTLCommandEncoderErrorStateToString( MTLCommandEncoderErrorState state) { @@ -31,6 +33,9 @@ } return @"unknown"; } +#endif + +// NOLINTEND(readability-identifier-naming) static NSString* MTLCommandBufferErrorToString(MTLCommandBufferError code) { switch (code) { @@ -84,7 +89,7 @@ static bool LogMTLCommandBufferErrorIfPresent(id buffer) { .UTF8String << std::endl; } - +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) if (@available(iOS 14.0, macOS 11.0, *)) { NSArray>* infos = buffer.error.userInfo[MTLCommandBufferEncoderInfoErrorKey]; @@ -108,14 +113,14 @@ static bool LogMTLCommandBufferErrorIfPresent(id buffer) { } } } - +#endif stream << "<<<<<<<"; VALIDATION_LOG << stream.str(); return false; } static id CreateCommandBuffer(id queue) { -#ifndef FLUTTER_RELEASE +#if !defined(FLUTTER_RELEASE) && !(defined(TARGET_OS_TV) && TARGET_OS_TV) if (@available(iOS 14.0, macOS 11.0, *)) { auto desc = [[MTLCommandBufferDescriptor alloc] init]; // Degrades CPU performance slightly but is well worth the cost for typical diff --git a/impeller/renderer/backend/metal/context_mtl.mm b/impeller/renderer/backend/metal/context_mtl.mm index a4083bfa63eec..c921d3a34347a 100644 --- a/impeller/renderer/backend/metal/context_mtl.mm +++ b/impeller/renderer/backend/metal/context_mtl.mm @@ -27,14 +27,20 @@ static bool DeviceSupportsFramebufferFetch(id device) { return false; #else // FML_OS_IOS_SIMULATOR +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) if (@available(macOS 10.15, iOS 13, tvOS 13, *)) { return [device supportsFamily:MTLGPUFamilyApple2]; } +#endif // According to // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf , Apple2 // corresponds to iOS GPU family 2, which supports A8 devices. #if FML_OS_IOS - return [device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily2_v1]; + #if !(defined(TARGET_OS_TV) && TARGET_OS_TV) + return [device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily2_v1]; + #else + return [device supportsFeatureSet:MTLFeatureSet_tvOS_GPUFamily1_v1]; + #endif #else return false; #endif // FML_OS_IOS @@ -43,12 +49,15 @@ static bool DeviceSupportsFramebufferFetch(id device) { static bool DeviceSupportsComputeSubgroups(id device) { bool supports_subgroups = false; - // Refer to the "SIMD-scoped reduction operations" feature in the table - // below: https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf - if (@available(ios 13.0, tvos 13.0, macos 10.15, *)) { - supports_subgroups = [device supportsFamily:MTLGPUFamilyApple7] || - [device supportsFamily:MTLGPUFamilyMac2]; - } +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) + // Refer to the "SIMD-scoped reduction operations" feature in the table + // below: https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf + if (@available(ios 13.0, tvos 13.0, macos 10.15, *)) { + supports_subgroups = [device supportsFamily:MTLGPUFamilyApple7] || + [device supportsFamily:MTLGPUFamilyMac2]; + } +#endif + return supports_subgroups; } diff --git a/impeller/renderer/backend/metal/sampler_library_mtl.mm b/impeller/renderer/backend/metal/sampler_library_mtl.mm index 65caef79baee6..e70f6d2a05d06 100644 --- a/impeller/renderer/backend/metal/sampler_library_mtl.mm +++ b/impeller/renderer/backend/metal/sampler_library_mtl.mm @@ -31,9 +31,11 @@ desc.sAddressMode = ToMTLSamplerAddressMode(descriptor.width_address_mode); desc.tAddressMode = ToMTLSamplerAddressMode(descriptor.height_address_mode); desc.rAddressMode = ToMTLSamplerAddressMode(descriptor.depth_address_mode); +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) if (@available(iOS 14.0, macos 10.12, *)) { desc.borderColor = MTLSamplerBorderColorTransparentBlack; } +#endif #ifdef IMPELLER_DEBUG if (!descriptor.label.empty()) { desc.label = @(descriptor.label.data()); diff --git a/ninja_build.sh b/ninja_build.sh new file mode 100644 index 0000000000000..f87bc1d902fcd --- /dev/null +++ b/ninja_build.sh @@ -0,0 +1,50 @@ +#!/bin/bash +set -e +RED='\033[0;31m' +NOCOLOR='\033[0m' + +if [[ $(uname -m) == "arm64" ]]; then + echo "Host: arm64" + GN_SIM_ARGS="--simulator-cpu=arm64" + GN_ARGS="--mac-cpu=arm64" + OUTPUT_POSTFIX="_arm64" +else + echo "Host: x64" + GN_SIM_ARGS="" + GN_ARGS="" + OUTPUT_POSTFIX="" +fi + +if [[ "$1" == "clean" ]]; then + echo "Clean build ..." + rm -irf ./out/ios_debug_sim_unopt$OUTPUT_POSTFIX + rm -rf ./out/ios_debug_unopt$OUTPUT_POSTFIX + rm -rf ./out/ios_release$OUTPUT_POSTFIX + rm -rf ./out/host_debug_unopt$OUTPUT_POSTFIX + rm -rf ./out/host_release$OUTPUT_POSTFIX +fi + +if [[ "$1" == "clean" ]] || [[ ! -d ./out/ios_debug_sim_unopt$OUTPUT_POSTFIX ]]; then + ./flutter/tools/gn --ios --no-goma --simulator --unoptimized $GN_SIM_ARGS +fi +ninja -C out/ios_debug_sim_unopt$OUTPUT_POSTFIX + +if [[ "$1" == "clean" ]] || [[ ! -d ./out/ios_debug_unopt$OUTPUT_POSTFIX ]]; then + ./flutter/tools/gn --ios --no-goma --unoptimized $GN_ARGS +fi +ninja -C out/ios_debug_unopt$OUTPUT_POSTFIX + +if [[ "$1" == "clean" ]] || [[ ! -d ./out/ios_release$OUTPUT_POSTFIX ]]; then + ./flutter/tools/gn --ios --no-goma --runtime-mode=release $GN_ARGS +fi +ninja -C out/ios_release$OUTPUT_POSTFIX + +if [[ "$1" == "clean" ]] || [[ ! -d ./out/host_debug_unopt$OUTPUT_POSTFIX ]]; then + ./flutter/tools/gn --no-goma --unoptimized --no-prebuilt-dart-sdk $GN_ARGS +fi +ninja -C out/host_debug_unopt$OUTPUT_POSTFIX + +if [[ "$1" == "clean" ]] || [[ ! -d ./out/host_release$OUTPUT_POSTFIX ]]; then + ./flutter/tools/gn --no-goma --no-lto --runtime-mode=release --no-prebuilt-dart-sdk $GN_ARGS +fi +ninja -C out/host_release$OUTPUT_POSTFIX \ No newline at end of file diff --git a/shell/platform/darwin/ios/BUILD.gn b/shell/platform/darwin/ios/BUILD.gn index 6020365844afb..380873551fcc4 100644 --- a/shell/platform/darwin/ios/BUILD.gn +++ b/shell/platform/darwin/ios/BUILD.gn @@ -163,15 +163,17 @@ source_set("flutter_framework_source") { "AudioToolbox.framework", "CoreMedia.framework", "CoreVideo.framework", - "IOSurface.framework", "QuartzCore.framework", - "WebKit.framework", + "MediaPlayer.framework", + "IOSurface.framework", + #"WebKit.framework", "UIKit.framework", + "GameController.framework", ] if (flutter_runtime_mode == "profile" || flutter_runtime_mode == "debug") { # This is required by the profiler_metrics_ios.mm to get GPU statistics. # Usage in release builds will cause rejection from the App Store. - frameworks += [ "IOKit.framework" ] + # frameworks += [ "IOKit.framework" ] } deps = [ @@ -205,7 +207,7 @@ shared_library("ios_test_flutter") { cflags = [ "-fvisibility=default", "-F$platform_frameworks_path", - "-mios-simulator-version-min=$ios_testing_deployment_target", + "-mtvos-simulator-version-min=$ios_testing_deployment_target", ] ldflags = [ diff --git a/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h b/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h index 8ab46cfe80ac9..e41c338158782 100644 --- a/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h +++ b/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h @@ -69,11 +69,13 @@ NS_ASSUME_NONNULL_BEGIN /** * Called if this has been registered for `UIApplicationDelegate` callbacks. */ +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) - (void)application:(UIApplication*)application didRegisterUserNotificationSettings:(UIUserNotificationSettings*)notificationSettings API_DEPRECATED( "See -[UIApplicationDelegate application:didRegisterUserNotificationSettings:] deprecation", ios(8.0, 10.0)); +#endif /** * Called if this has been registered for `UIApplicationDelegate` callbacks. @@ -99,11 +101,13 @@ NS_ASSUME_NONNULL_BEGIN /** * Calls all plugins registered for `UIApplicationDelegate` callbacks. */ +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) - (void)application:(UIApplication*)application didReceiveLocalNotification:(UILocalNotification*)notification API_DEPRECATED( "See -[UIApplicationDelegate application:didReceiveLocalNotification:] deprecation", ios(4.0, 10.0)); +#endif /** * Called if this has been registered for `UIApplicationDelegate` callbacks. @@ -136,10 +140,12 @@ NS_ASSUME_NONNULL_BEGIN * * @return `YES` if this handles the request. */ +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) - (BOOL)application:(UIApplication*)application performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem completionHandler:(void (^)(BOOL succeeded))completionHandler API_AVAILABLE(ios(9.0)); +#endif /** * Called if this has been registered for `UIApplicationDelegate` callbacks. diff --git a/shell/platform/darwin/ios/framework/Headers/FlutterPluginAppLifeCycleDelegate.h b/shell/platform/darwin/ios/framework/Headers/FlutterPluginAppLifeCycleDelegate.h index 95d1e611e4950..bad13d426c007 100644 --- a/shell/platform/darwin/ios/framework/Headers/FlutterPluginAppLifeCycleDelegate.h +++ b/shell/platform/darwin/ios/framework/Headers/FlutterPluginAppLifeCycleDelegate.h @@ -42,11 +42,13 @@ FLUTTER_DARWIN_EXPORT /** * Called if this plugin has been registered for `UIApplicationDelegate` callbacks. */ +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) - (void)application:(UIApplication*)application didRegisterUserNotificationSettings:(UIUserNotificationSettings*)notificationSettings API_DEPRECATED( "See -[UIApplicationDelegate application:didRegisterUserNotificationSettings:] deprecation", ios(8.0, 10.0)); +#endif /** * Calls all plugins registered for `UIApplicationDelegate` callbacks. @@ -70,11 +72,13 @@ FLUTTER_DARWIN_EXPORT /** * Calls all plugins registered for `UIApplicationDelegate` callbacks. */ +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) - (void)application:(UIApplication*)application didReceiveLocalNotification:(UILocalNotification*)notification API_DEPRECATED( "See -[UIApplicationDelegate application:didReceiveLocalNotification:] deprecation", ios(4.0, 10.0)); +#endif /** * Calls all plugins registered for `UIApplicationDelegate` callbacks in order of registration until @@ -108,10 +112,12 @@ FLUTTER_DARWIN_EXPORT /** * Calls all plugins registered for `UIApplicationDelegate` callbacks. */ +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) - (void)application:(UIApplication*)application performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem completionHandler:(void (^)(BOOL succeeded))completionHandler API_AVAILABLE(ios(9.0)); +#endif /** * Calls all plugins registered for `UIApplicationDelegate` callbacks in order of registration until diff --git a/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm b/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm index e23d61dccab66..b221712cdb58a 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm @@ -78,6 +78,7 @@ - (void)applicationDidBecomeActive:(UIApplication*)application { - (void)applicationWillTerminate:(UIApplication*)application { } +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" - (void)application:(UIApplication*)application @@ -86,6 +87,7 @@ - (void)application:(UIApplication*)application didRegisterUserNotificationSettings:notificationSettings]; } #pragma GCC diagnostic pop +#endif - (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken { @@ -99,6 +101,7 @@ - (void)application:(UIApplication*)application didFailToRegisterForRemoteNotificationsWithError:error]; } +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" - (void)application:(UIApplication*)application @@ -106,6 +109,7 @@ - (void)application:(UIApplication*)application [self.lifeCycleDelegate application:application didReceiveLocalNotification:notification]; } #pragma GCC diagnostic pop +#endif - (void)userNotificationCenter:(UNUserNotificationCenter*)center willPresentNotification:(UNNotification*)notification @@ -121,6 +125,7 @@ - (void)userNotificationCenter:(UNUserNotificationCenter*)center /** * Calls all plugins registered for `UNUserNotificationCenterDelegate` callbacks. */ +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) - (void)userNotificationCenter:(UNUserNotificationCenter*)center didReceiveNotificationResponse:(UNNotificationResponse*)response withCompletionHandler:(void (^)(void))completionHandler { @@ -130,6 +135,7 @@ - (void)userNotificationCenter:(UNUserNotificationCenter*)center withCompletionHandler:completionHandler]; } } +#endif - (BOOL)isFlutterDeepLinkingEnabled { NSNumber* isDeepLinkingEnabled = @@ -188,6 +194,7 @@ - (BOOL)application:(UIApplication*)application annotation:annotation]; } +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) - (void)application:(UIApplication*)application performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem completionHandler:(void (^)(BOOL succeeded))completionHandler { @@ -195,6 +202,7 @@ - (void)application:(UIApplication*)application performActionForShortcutItem:shortcutItem completionHandler:completionHandler]; } +#endif - (void)application:(UIApplication*)application handleEventsForBackgroundURLSession:(nonnull NSString*)identifier diff --git a/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm b/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm index 3f31c181f3d17..71f15106549cb 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm @@ -33,6 +33,7 @@ static BOOL DoesHardwareSupportWideGamut() { static dispatch_once_t once_token = 0; dispatch_once(&once_token, ^{ id device = MTLCreateSystemDefaultDevice(); +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) if (@available(iOS 13.0, *)) { // MTLGPUFamilyApple2 = A9/A10 result = [device supportsFamily:MTLGPUFamilyApple2]; @@ -40,6 +41,9 @@ static BOOL DoesHardwareSupportWideGamut() { // A9/A10 on iOS 10+ result = [device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily3_v2]; } +#else + result = [device supportsFamily:MTLGPUFamilyApple2]; +#endif }); return result; } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm index 6a662381fdf03..41038c734deea 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm @@ -19,7 +19,9 @@ namespace { +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) constexpr char kTextPlainFormat[] = "text/plain"; +#endif const UInt32 kKeyPressClickSoundId = 1306; #if not APPLICATION_EXTENSION_API_ONLY @@ -44,6 +46,7 @@ using namespace flutter; +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) static void SetStatusBarHiddenForSharedApplication(BOOL hidden) { #if not APPLICATION_EXTENSION_API_ONLY [UIApplication sharedApplication].statusBarHidden = hidden; @@ -61,6 +64,7 @@ static void SetStatusBarStyleForSharedApplication(UIStatusBarStyle style) { FML_LOG(WARNING) << "Application based status bar styling is not available in app extension."; #endif } +#endif @interface FlutterPlatformPlugin () @@ -176,13 +180,16 @@ - (void)showSystemContextMenu:(NSDictionary*)args { } - (void)hideSystemContextMenu { +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) if (@available(iOS 16.0, *)) { FlutterTextInputPlugin* textInputPlugin = [self.engine textInputPlugin]; [textInputPlugin hideEditMenu]; } +#endif } - (void)showShareViewController:(NSString*)content { +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) UIViewController* engineViewController = [self.engine viewController]; NSArray* itemsToShare = @[ content ?: [NSNull null] ]; @@ -217,6 +224,7 @@ - (void)showShareViewController:(NSString*)content { } [engineViewController presentViewController:activityViewController animated:YES completion:nil]; +#endif } - (void)searchWeb:(NSString*)searchTerm { @@ -248,6 +256,7 @@ - (void)vibrateHapticFeedback:(NSString*)feedbackType { return; } +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) if ([@"HapticFeedbackType.lightImpact" isEqualToString:feedbackType]) { [[[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleLight] impactOccurred]; } else if ([@"HapticFeedbackType.mediumImpact" isEqualToString:feedbackType]) { @@ -257,11 +266,13 @@ - (void)vibrateHapticFeedback:(NSString*)feedbackType { } else if ([@"HapticFeedbackType.selectionClick" isEqualToString:feedbackType]) { [[[UISelectionFeedbackGenerator alloc] init] selectionChanged]; } +#endif } - (void)setSystemChromePreferredOrientations:(NSArray*)orientations { UIInterfaceOrientationMask mask = 0; +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) if (orientations.count == 0) { mask |= UIInterfaceOrientationMaskAll; } else { @@ -277,6 +288,7 @@ - (void)setSystemChromePreferredOrientations:(NSArray*)orientations { } } } +#endif if (!mask) { return; @@ -292,6 +304,14 @@ - (void)setSystemChromeApplicationSwitcherDescription:(NSDictionary*)object { } - (void)setSystemChromeEnabledSystemUIOverlays:(NSArray*)overlays { + // Checks if the top status bar should be visible. This platform ignores all + // other overlays + + // We opt out of view controller based status bar visibility since we want + // to be able to modify this on the fly. The key used is + // UIViewControllerBasedStatusBarAppearance +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) + [UIApplication sharedApplication].statusBarHidden = BOOL statusBarShouldBeHidden = ![overlays containsObject:@"SystemUiOverlay.top"]; if ([overlays containsObject:@"SystemUiOverlay.bottom"]) { [[NSNotificationCenter defaultCenter] @@ -313,12 +333,16 @@ - (void)setSystemChromeEnabledSystemUIOverlays:(NSArray*)overlays { // UIViewControllerBasedStatusBarAppearance. SetStatusBarHiddenForSharedApplication(statusBarShouldBeHidden); } + #endif } + - (void)setSystemChromeEnabledSystemUIMode:(NSString*)mode { BOOL edgeToEdge = [mode isEqualToString:@"SystemUiMode.edgeToEdge"]; if (self.enableViewControllerBasedStatusBarAppearance) { +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) [self.engine viewController].prefersStatusBarHidden = !edgeToEdge; +#endif } else { // Checks if the top status bar should be visible, reflected by edge to edge setting. This // platform ignores all other system ui modes. @@ -326,7 +350,9 @@ - (void)setSystemChromeEnabledSystemUIMode:(NSString*)mode { // We opt out of view controller based status bar visibility since we want // to be able to modify this on the fly. The key used is // UIViewControllerBasedStatusBarAppearance. + #if !(defined(TARGET_OS_TV) && TARGET_OS_TV) SetStatusBarHiddenForSharedApplication(!edgeToEdge); + #endif } [[NSNotificationCenter defaultCenter] postNotificationName:edgeToEdge ? FlutterViewControllerShowHomeIndicator @@ -344,6 +370,7 @@ - (void)setSystemChromeSystemUIOverlayStyle:(NSDictionary*)message { return; } +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) UIStatusBarStyle statusBarStyle; if ([brightness isEqualToString:@"Brightness.dark"]) { statusBarStyle = UIStatusBarStyleLightContent; @@ -366,6 +393,7 @@ - (void)setSystemChromeSystemUIOverlayStyle:(NSDictionary*)message { } else { SetStatusBarStyleForSharedApplication(statusBarStyle); } +#endif } - (void)popSystemNavigator:(BOOL)isAnimated { @@ -399,16 +427,19 @@ - (void)popSystemNavigator:(BOOL)isAnimated { } - (NSDictionary*)getClipboardData:(NSString*)format { +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) UIPasteboard* pasteboard = [UIPasteboard generalPasteboard]; if (!format || [format isEqualToString:@(kTextPlainFormat)]) { NSString* stringInPasteboard = pasteboard.string; // The pasteboard may contain an item but it may not be a string (an image for instance). return stringInPasteboard == nil ? nil : @{@"text" : stringInPasteboard}; } +#endif return nil; } - (void)setClipboardData:(NSDictionary*)data { +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) UIPasteboard* pasteboard = [UIPasteboard generalPasteboard]; id copyText = data[@"text"]; if ([copyText isKindOfClass:[NSString class]]) { @@ -416,10 +447,15 @@ - (void)setClipboardData:(NSDictionary*)data { } else { pasteboard.string = @"null"; } +#endif } - (NSDictionary*)clipboardHasStrings { - return @{@"value" : @([UIPasteboard generalPasteboard].hasStrings)}; + bool hasStrings = false; +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) + hasStrings = [UIPasteboard generalPasteboard].hasStrings; +#endif + return @{@"value" : @(hasStrings)}; } - (BOOL)isLiveTextInputAvailable { @@ -427,12 +463,14 @@ - (BOOL)isLiveTextInputAvailable { } - (void)showLookUpViewController:(NSString*)term { +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) UIViewController* engineViewController = [self.engine viewController]; UIReferenceLibraryViewController* referenceLibraryViewController = [[UIReferenceLibraryViewController alloc] initWithTerm:term]; [engineViewController presentViewController:referenceLibraryViewController animated:YES completion:nil]; +#endif } - (UITextField*)textField { @@ -443,3 +481,5 @@ - (UITextField*)textField { } @end + + diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm index 16ce9ba0ddc13..7b6c929e2c2df 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm @@ -4,7 +4,9 @@ #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h" +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) #import +#endif #include "flutter/display_list/effects/dl_image_filter.h" #include "flutter/fml/platform/darwin/cf_utils.h" @@ -525,7 +527,11 @@ - (instancetype)initWithEmbeddedView:(UIView*)embeddedView (FlutterPlatformViewGestureRecognizersBlockingPolicy)blockingPolicy { self = [super initWithFrame:embeddedView.frame]; if (self) { +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) self.multipleTouchEnabled = YES; +#else + self.userInteractionEnabled = NO; +#endif _embeddedView = embeddedView; embeddedView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight); @@ -567,6 +573,7 @@ - (void)releaseGesture { } - (BOOL)containsWebView:(UIView*)view remainingSubviewDepth:(int)remainingSubviewDepth { +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) if (remainingSubviewDepth < 0) { return NO; } @@ -578,6 +585,7 @@ - (BOOL)containsWebView:(UIView*)view remainingSubviewDepth:(int)remainingSubvie return YES; } } +#endif return NO; } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm index 98bbf6b85e886..a761100cf3047 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm @@ -6,7 +6,9 @@ #import #import +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) #import +#endif #import #include diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate.mm b/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate.mm index 11451d38e481d..b8392d4bc9980 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate.mm @@ -216,7 +216,7 @@ - (void)handleWillTerminate:(NSNotification*)notification } } } - +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" - (void)application:(UIApplication*)application @@ -231,6 +231,7 @@ - (void)application:(UIApplication*)application } } #pragma GCC diagnostic pop +#endif - (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken { @@ -274,6 +275,7 @@ - (void)application:(UIApplication*)application } } +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" - (void)application:(UIApplication*)application @@ -288,6 +290,7 @@ - (void)application:(UIApplication*)application } } #pragma GCC diagnostic pop +#endif - (void)userNotificationCenter:(UNUserNotificationCenter*)center willPresentNotification:(UNNotification*)notification @@ -302,6 +305,7 @@ - (void)userNotificationCenter:(UNUserNotificationCenter*)center } } +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) - (void)userNotificationCenter:(UNUserNotificationCenter*)center didReceiveNotificationResponse:(UNNotificationResponse*)response withCompletionHandler:(void (^)(void))completionHandler { @@ -313,6 +317,7 @@ - (void)userNotificationCenter:(UNUserNotificationCenter*)center } } } +#endif - (BOOL)application:(UIApplication*)application openURL:(NSURL*)url @@ -364,6 +369,7 @@ - (BOOL)application:(UIApplication*)application return NO; } +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) - (void)application:(UIApplication*)application performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem completionHandler:(void (^)(BOOL succeeded))completionHandler { @@ -380,6 +386,7 @@ - (void)application:(UIApplication*)application } } } +#endif - (BOOL)application:(UIApplication*)application handleEventsForBackgroundURLSession:(nonnull NSString*)identifier diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h index 3f30494457300..93ea5426d6b9f 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h @@ -30,8 +30,13 @@ typedef NS_ENUM(NSInteger, FlutterScribbleInteractionStatus) { // NOLINTEND(readability-identifier-naming) }; +#ifdef TARGET_OS_TV +@interface FlutterTextInputPlugin + : NSObject +#else @interface FlutterTextInputPlugin : NSObject +#endif @property(nonatomic, weak) UIViewController* viewController; @property(nonatomic, weak) id indirectScribbleDelegate; @@ -130,8 +135,11 @@ API_AVAILABLE(ios(13.0)) @interface FlutterTextPlaceholder : UITextPlaceholder #if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG FLUTTER_DARWIN_EXPORT #endif -@interface FlutterTextInputView - : UIView +#ifdef TARGET_OS_TV +@interface FlutterTextInputView : UIView +#else +@interface FlutterTextInputView : UIView +#endif // UITextInput @property(nonatomic, readonly) NSMutableString* text; @@ -162,10 +170,11 @@ FLUTTER_DARWIN_EXPORT @property(nonatomic) FlutterScribbleFocusStatus scribbleFocusStatus; @property(nonatomic, strong) NSArray* selectionRects; -@property(nonatomic, strong) UIEditMenuInteraction* editMenuInteraction API_AVAILABLE(ios(16.0)); - (void)resetScribbleInteractionStatusIfEnding; +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) +@property(nonatomic, strong) UIEditMenuInteraction* editMenuInteraction API_AVAILABLE(ios(16.0)); - (BOOL)isScribbleAvailable; - +#endif - (instancetype)init NS_UNAVAILABLE; + (instancetype)new NS_UNAVAILABLE; - (instancetype)initWithCoder:(NSCoder*)aDecoder NS_UNAVAILABLE; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 9358431cf5a6f..bd876e4d87f17 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. + #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h" #import "flutter/shell/platform/darwin/ios/framework/Source/UIViewController+FlutterScreenAndSceneIfLoaded.h" @@ -623,6 +624,7 @@ - (UITextRange*)rangeEnclosingPosition:(UITextPosition*)position - (UITextRange*)lineEnclosingPosition:(UITextPosition*)position inDirection:(UITextDirection)direction { +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) // TODO(hellohuanlin): remove iOS 17 check. The same logic should apply to older iOS version. if (@available(iOS 17.0, *)) { // According to the API doc if the text position is at a text-unit boundary, it is considered @@ -635,7 +637,7 @@ - (UITextRange*)lineEnclosingPosition:(UITextPosition*)position return nil; } } - +#endif // Gets the first line break position after the input position. NSString* textAfter = [_textInputView textInRange:[_textInputView textRangeFromPosition:position @@ -818,7 +820,9 @@ @implementation FlutterTextInputView { bool _isFloatingCursorActive; CGPoint _floatingCursorOffset; bool _enableInteractiveSelection; + #if !(defined(TARGET_OS_TV) && TARGET_OS_TV) UITextInteraction* _textInteraction API_AVAILABLE(ios(13.0)); + #endif } @synthesize tokenizer = _tokenizer; @@ -858,20 +862,24 @@ - (instancetype)initWithOwner:(FlutterTextInputPlugin*)textInputPlugin { _smartDashesType = UITextSmartDashesTypeYes; _selectionRects = [[NSArray alloc] init]; + #if !(defined(TARGET_OS_TV) && TARGET_OS_TV) if (@available(iOS 14.0, *)) { UIScribbleInteraction* interaction = [[UIScribbleInteraction alloc] initWithDelegate:self]; [self addInteraction:interaction]; } + #endif } +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) if (@available(iOS 16.0, *)) { _editMenuInteraction = [[UIEditMenuInteraction alloc] initWithDelegate:self]; [self addInteraction:_editMenuInteraction]; } - +#endif return self; } +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) - (UIMenu*)editMenuInteraction:(UIEditMenuInteraction*)interaction menuForConfiguration:(UIEditMenuConfiguration*)configuration suggestedActions:(NSArray*)suggestedActions API_AVAILABLE(ios(16.0)) { @@ -901,6 +909,7 @@ - (void)showEditMenuWithTargetRect:(CGRect)targetRect API_AVAILABLE(ios(16.0)) { - (void)hideEditMenu API_AVAILABLE(ios(16.0)) { [self.editMenuInteraction dismissMenu]; } +#endif - (void)configureWithDictionary:(NSDictionary*)configuration { NSDictionary* inputType = configuration[kKeyboardType]; @@ -1005,6 +1014,7 @@ - (void)setTextInputClient:(int)client { _hasPlaceholder = NO; } +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) - (UITextInteraction*)textInteraction API_AVAILABLE(ios(13.0)) { if (!_textInteraction) { _textInteraction = [UITextInteraction textInteractionForMode:UITextInteractionModeEditable]; @@ -1012,8 +1022,10 @@ - (UITextInteraction*)textInteraction API_AVAILABLE(ios(13.0)) { } return _textInteraction; } +#endif - (void)setTextInputState:(NSDictionary*)state { +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) if (@available(iOS 13.0, *)) { // [UITextInteraction willMoveToView:] sometimes sets the textInput's inputDelegate // to nil. This is likely a bug in UIKit. In order to inform the keyboard of text @@ -1024,6 +1036,7 @@ - (void)setTextInputState:(NSDictionary*)state { [self addInteraction:self.textInteraction]; } } +#endif NSString* newText = state[@"text"]; BOOL textChanged = ![self.text isEqualToString:newText]; @@ -1060,12 +1073,13 @@ - (void)setTextInputState:(NSDictionary*)state { if (textChanged) { [self.inputDelegate textDidChange:self]; } - +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) if (@available(iOS 13.0, *)) { if (_textInteraction) { [self removeInteraction:_textInteraction]; } } +#endif } // Forward touches to the viewResponder to allow tapping inside the UITextField as normal. @@ -1132,6 +1146,7 @@ - (void)setIsVisibleToAutofill:(BOOL)isVisibleToAutofill { // Checks whether Scribble features are possibly available – meaning this is an iPad running iOS // 14 or higher. +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) - (BOOL)isScribbleAvailable { if (@available(iOS 14.0, *)) { if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { @@ -1140,7 +1155,9 @@ - (BOOL)isScribbleAvailable { } return NO; } +#endif +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) - (void)scribbleInteractionWillBeginWriting:(UIScribbleInteraction*)interaction API_AVAILABLE(ios(14.0)) { _scribbleInteractionStatus = FlutterScribbleInteractionStatusStarted; @@ -1162,6 +1179,7 @@ - (BOOL)scribbleInteractionShouldDelayFocus:(UIScribbleInteraction*)interaction API_AVAILABLE(ios(14.0)) { return NO; } +#endif #pragma mark - UIResponder Overrides @@ -1185,6 +1203,7 @@ - (BOOL)resignFirstResponder { } - (BOOL)canPerformAction:(SEL)action withSender:(id)sender { +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) if (action == @selector(paste:)) { // Forbid pasting images, memojis, or other non-string content. return [UIPasteboard generalPasteboard].hasStrings; @@ -1194,25 +1213,32 @@ - (BOOL)canPerformAction:(SEL)action withSender:(id)sender { } else if (action == @selector(selectAll:)) { return self.hasText; } +#endif return [super canPerformAction:action withSender:sender]; } #pragma mark - UIResponderStandardEditActions Overrides - (void)cut:(id)sender { +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) [UIPasteboard generalPasteboard].string = [self textInRange:_selectedTextRange]; [self replaceRange:_selectedTextRange withText:@""]; +#endif } - (void)copy:(id)sender { +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) [UIPasteboard generalPasteboard].string = [self textInRange:_selectedTextRange]; +#endif } - (void)paste:(id)sender { +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) NSString* pasteboardString = [UIPasteboard generalPasteboard].string; if (pasteboardString != nil) { [self insertText:pasteboardString]; } +#endif } - (void)delete:(id)sender { @@ -1712,6 +1738,7 @@ - (CGRect)firstRectForRange:(UITextRange*)range { if (_scribbleInteractionStatus == FlutterScribbleInteractionStatusNone && _scribbleFocusStatus == FlutterScribbleFocusStatusUnfocused) { +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) if (@available(iOS 17.0, *)) { // Disable auto-correction highlight feature for iOS 17+. // In iOS 17+, whenever a character is inserted or deleted, the system will always query @@ -1727,8 +1754,9 @@ - (CGRect)firstRectForRange:(UITextRange*)range { end:end withClient:_textInputClient]; } +#endif } - +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) // The iOS 16 system highlight does not repect the height returned by `firstRectForRange` // API (unlike iOS 17). So we return CGRectZero to hide it (unless if scribble is enabled). // To support scribble's advanced gestures (e.g. insert a space with a vertical bar), @@ -1738,7 +1766,7 @@ - (CGRect)firstRectForRange:(UITextRange*)range { } else if (![self isScribbleAvailable]) { return CGRectZero; } - +#endif NSUInteger first = start; if (end < start) { first = end; @@ -2361,20 +2389,23 @@ - (instancetype)initWithDelegate:(id)textInputDelegate _inputHider = [[FlutterTextInputViewAccessibilityHider alloc] init]; _scribbleElements = [[NSMutableDictionary alloc] init]; _keyboardViewContainer = [[UIView alloc] init]; - +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleKeyboardWillShow:) name:UIKeyboardWillShowNotification object:nil]; +#endif } return self; } - (void)handleKeyboardWillShow:(NSNotification*)notification { +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) NSDictionary* keyboardInfo = [notification userInfo]; NSValue* keyboardFrameEnd = [keyboardInfo valueForKey:UIKeyboardFrameEndUserInfoKey]; _keyboardRect = [keyboardFrameEnd CGRectValue]; +#endif } - (void)dealloc { @@ -2559,6 +2590,7 @@ - (BOOL)showEditMenu:(NSDictionary*)args API_AVAILABLE(ios(16.0)) { if (!self.activeView.isFirstResponder) { return NO; } +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) NSDictionary* encodedTargetRect = args[@"targetRect"]; CGRect globalTargetRect = CGRectMake( [encodedTargetRect[@"x"] doubleValue], [encodedTargetRect[@"y"] doubleValue], @@ -2566,15 +2598,21 @@ - (BOOL)showEditMenu:(NSDictionary*)args API_AVAILABLE(ios(16.0)) { CGRect localTargetRect = [self.hostView convertRect:globalTargetRect toView:self.activeView]; [self.activeView showEditMenuWithTargetRect:localTargetRect]; return YES; +#else + return NO; +#endif } - (void)hideEditMenu { +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) [self.activeView hideEditMenu]; +#endif } - (void)setEditableSizeAndTransform:(NSDictionary*)dictionary { NSArray* transform = dictionary[@"transform"]; [_activeView setEditableTransform:transform]; +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) const int leftIndex = 12; const int topIndex = 13; if ([_activeView isScribbleAvailable]) { @@ -2600,6 +2638,7 @@ - (void)setEditableSizeAndTransform:(NSDictionary*)dictionary { CGRectMake([transform[leftIndex] intValue], [transform[topIndex] intValue], 0, 0); } } +#endif } - (void)updateMarkedRect:(NSDictionary*)dictionary { @@ -2634,12 +2673,14 @@ - (void)setSelectionRects:(NSArray*)encodedRects { } - (void)startLiveTextInput { +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) if (@available(iOS 15.0, *)) { if (_activeView == nil || !_activeView.isFirstResponder) { return; } [_activeView captureTextFromCamera:nil]; } +#endif } - (void)showTextInput { @@ -2946,6 +2987,7 @@ - (void)updateConfig:(NSDictionary*)dictionary { } #pragma mark UIIndirectScribbleInteractionDelegate +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) - (BOOL)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction isElementFocused:(UIScribbleElementIdentifier)elementIdentifier @@ -3022,11 +3064,13 @@ - (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction completion(elements); }]; } +#endif #pragma mark - Methods related to Scribble support - (void)setUpIndirectScribbleInteraction:(id)viewResponder { if (_viewResponder != viewResponder) { +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) if (@available(iOS 14.0, *)) { UIView* parentView = viewResponder.view; if (parentView != nil) { @@ -3035,6 +3079,7 @@ - (void)setUpIndirectScribbleInteraction:(id)viewResponder [parentView addInteraction:scribbleInteraction]; } } +#endif } _viewResponder = viewResponder; } @@ -3072,3 +3117,7 @@ - (id)flutterFirstResponder { return nil; } @end + + + + diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm index be00f6e1e63d4..62a6d410b1564 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm @@ -101,8 +101,10 @@ - (void)setUp { viewController = [[FlutterViewController alloc] init]; textInputPlugin.viewController = viewController; +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) // Clear pasteboard between tests. UIPasteboard.generalPasteboard.items = @[]; +#endif } - (void)tearDown { @@ -416,10 +418,10 @@ - (void)testAutocorrectionPromptRectDoesNotAppearDuringScribble { [inputView firstRectForRange:[FlutterTextRange rangeWithNSRange:NSMakeRange(0, 1)]]; // showAutocorrectionPromptRectForStart fires in response to firstRectForRange XCTAssertEqual(callCount, 1); - +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) UIScribbleInteraction* scribbleInteraction = [[UIScribbleInteraction alloc] initWithDelegate:inputView]; - +#endif [inputView scribbleInteractionWillBeginWriting:scribbleInteraction]; [inputView firstRectForRange:[FlutterTextRange rangeWithNSRange:NSMakeRange(0, 1)]]; // showAutocorrectionPromptRectForStart does not fire in response to setMarkedText during a @@ -653,9 +655,10 @@ - (void)testPastingNonTextDisallowed { [self setClientId:123 configuration:config]; NSArray* inputFields = self.installedInputViews; FlutterTextInputView* inputView = inputFields[0]; - +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) UIPasteboard.generalPasteboard.color = UIColor.redColor; XCTAssertNil(UIPasteboard.generalPasteboard.string); +#endif XCTAssertFalse([inputView canPerformAction:@selector(paste:) withSender:nil]); [inputView paste:nil]; @@ -1356,10 +1359,10 @@ - (void)testSetMarkedTextDuringScribbleDoesNotTriggerUpdateEditingClient { [inputView setMarkedText:@"marked text" selectedRange:NSMakeRange(0, 1)]; // updateEditingClient fires in response to setMarkedText. XCTAssertEqual(updateCount, 1); - +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) UIScribbleInteraction* scribbleInteraction = [[UIScribbleInteraction alloc] initWithDelegate:inputView]; - +#endif [inputView scribbleInteractionWillBeginWriting:scribbleInteraction]; [inputView setMarkedText:@"during writing" selectedRange:NSMakeRange(1, 2)]; // updateEditingClient does not fire in response to setMarkedText during a scribble interaction. diff --git a/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerPlugin.mm index 32810dcacc9c1..b315315692517 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerPlugin.mm @@ -97,11 +97,13 @@ - (void)setUndoState:(NSDictionary*)dictionary { } UIView* textInputView = self.undoManagerDelegate.activeTextInputView; if (textInputView != nil) { +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) // This is needed to notify the iPadOS keyboard that it needs to update the // state of the UIBarButtons. Otherwise, the state changes to NSUndoManager // will not show up until the next keystroke (or other trigger). UITextInputAssistantItem* assistantItem = textInputView.inputAssistantItem; assistantItem.leadingBarButtonGroups = assistantItem.leadingBarButtonGroups; +#endif } undoManager.groupsByEvent = groupsByEvent; } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index ad6eedc910c50..d7917540f1488 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -10,6 +10,11 @@ #include #include "flutter/common/constants.h" + +#ifdef TARGET_OS_TV +#include +#include +#endif #include "flutter/fml/memory/weak_ptr.h" #include "flutter/fml/message_loop.h" #include "flutter/fml/platform/darwin/platform_version.h" @@ -59,7 +64,13 @@ // This is left a FlutterBinaryMessenger privately for now to give people a chance to notice the // change. Unfortunately unless you have Werror turned on, incompatible pointers as arguments are // just a warning. +#ifdef TARGET_OS_TV +@interface FlutterViewController () +@property(nonatomic, strong) FlutterBasicMessageChannel* keyEventChannel; +@property(nonatomic, strong) FlutterBasicMessageChannel* gamepadTouchEventChannel; +#else @interface FlutterViewController () +#endif // TODO(dkwingsmt): Make the view ID property public once the iOS shell // supports multiple views. // https://github.com/flutter/flutter/issues/138168 @@ -71,7 +82,9 @@ @interface FlutterViewController () 0){ + [self sendTap:0x5A withType:@"android" ofType:@"keydown"]; + [self sendTap:0x5A withType:@"android" ofType:@"keyup"]; + } else if (((MPChangePlaybackRateCommandEvent *)event).playbackRate < 0){ + [self sendTap:0x59 withType:@"android" ofType:@"keydown"]; + [self sendTap:0x59 withType:@"android" ofType:@"keyup"]; + } + return MPRemoteCommandHandlerStatusSuccess; +} +]; + +} + +- (void)controllerConnected:(NSNotification*)notification { + [self setupControllers]; +} +#endif //ifdef TARGET_OS_TV #pragma mark - UIViewController lifecycle notifications - (void)viewDidLoad { TRACE_EVENT0("flutter", "viewDidLoad"); +#ifdef TARGET_OS_TV + [self createRecognizerFor:UIPressTypeUpArrow action:@selector(handleUpTap:)]; + [self createRecognizerFor:UIPressTypeDownArrow action:@selector(handleDownTap:)]; + [self createRecognizerFor:UIPressTypeLeftArrow action:@selector(handleLeftTap:)]; + [self createRecognizerFor:UIPressTypeRightArrow action:@selector(handleRightTap:)]; + [self createRecognizerFor:UIPressTypeSelect action:@selector(handleCenterTap:)]; + [self createRecognizerFor:UIPressTypePlayPause action:@selector(handlePlayPauseTap:)]; + [self createRecognizerFor:UIPressTypeMenu action:@selector(handleMenuTap:)]; + + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wunguarded-availability-new" + + if ([self respondsToSelector:@selector(handlePageUpTap:)]) { + [self createRecognizerFor:UIPressTypePageUp action:@selector(handlePageUpTap:)]; + } + + if ([self respondsToSelector:@selector(handlePageDownTap:)]) { + [self createRecognizerFor:UIPressTypePageDown action:@selector(handlePageDownTap:)]; + } + + #pragma clang diagnostic pop + + [self setupControllers]; + [self setupWalnutController]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(controllerConnected:) name:GCControllerDidConnectNotification object:nil]; + + self.keyEventChannel = + [FlutterBasicMessageChannel messageChannelWithName:@"flutter/keyevent" + binaryMessenger:[self binaryMessenger] + codec:[FlutterJSONMessageCodec sharedInstance] + ]; + self.gamepadTouchEventChannel = + [FlutterBasicMessageChannel messageChannelWithName:@"flutter/gamepadtouchevent" + binaryMessenger:[self binaryMessenger] + codec:[FlutterJSONMessageCodec sharedInstance] + ]; +#endif //#ifdef TARGET_OS_TV if (self.engine && self.engineNeedsLaunch) { [self.engine launchEngine:nil libraryURI:nil entrypointArgs:nil]; [self.engine setViewController:self]; @@ -764,6 +998,7 @@ - (void)viewDidLoad { // Create a vsync client to correct delivery frame rate of touch events if needed. [self createTouchRateCorrectionVSyncClientIfNeeded]; +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) if (@available(iOS 13.4, *)) { _hoverGestureRecognizer = [[UIHoverGestureRecognizer alloc] initWithTarget:self action:@selector(hoverEvent:)]; @@ -797,6 +1032,7 @@ - (void)viewDidLoad { _rotationGestureRecognizer.delegate = self; [self.flutterView addGestureRecognizer:_rotationGestureRecognizer]; } +#endif [super viewDidLoad]; } @@ -876,8 +1112,10 @@ - (void)viewWillDisappear:(BOOL)animated { - (void)viewDidDisappear:(BOOL)animated { TRACE_EVENT0("flutter", "viewDidDisappear"); if (self.engine.viewController == self) { +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) [self invalidateKeyboardAnimationVSyncClient]; [self ensureViewportMetricsIsCorrect]; +#endif [self surfaceUpdated:NO]; [self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.paused"]; [self flushOngoingTouches]; @@ -965,18 +1203,21 @@ - (void)dealloc { // Eliminate method calls in initializers and dealloc. [self removeInternalPlugins]; [self deregisterNotifications]; - +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) [self invalidateKeyboardAnimationVSyncClient]; +#endif [self invalidateTouchRateCorrectionVSyncClient]; // TODO(cbracken): https://github.com/flutter/flutter/issues/156222 // Ensure all delegates are weak and remove this. _scrollView.delegate = nil; +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) _hoverGestureRecognizer.delegate = nil; _discreteScrollingPanGestureRecognizer.delegate = nil; _continuousScrollingPanGestureRecognizer.delegate = nil; _pinchGestureRecognizer.delegate = nil; _rotationGestureRecognizer.delegate = nil; +#endif } #pragma mark - Application lifecycle notifications @@ -1258,7 +1499,7 @@ - (void)dispatchTouches:(NSSet*)touches // Discussion: // Sweep direction is the same. Phase of M_PI_2. pointer_data.orientation = [touch azimuthAngleInView:nil] - M_PI_2; - + #if !(defined(TARGET_OS_TV) && TARGET_OS_TV) if (@available(iOS 13.4, *)) { if (event != nullptr) { pointer_data.buttons = (((event.buttonMask & UIEventButtonMaskPrimary) > 0) @@ -1269,6 +1510,7 @@ - (void)dispatchTouches:(NSSet*)touches : 0); } } + #endif packet->SetPointerData(pointer_index++, pointer_data); @@ -1283,19 +1525,47 @@ - (void)dispatchTouches:(NSSet*)touches } - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event { + #ifdef TARGET_OS_TV + for (UITouch* touch in touches) { + CGPoint location = [touch locationInView:self.view]; + [self sendGamepadTouchesWithType:@"started" x:location.x y:location.y]; + } + #else [self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event]; + #endif } - (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event { + #ifdef TARGET_OS_TV + for (UITouch* touch in touches) { + CGPoint location = [touch locationInView:self.view]; + [self sendGamepadTouchesWithType:@"move" x:location.x y:location.y]; + } + #else [self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event]; + #endif } - (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event { + #ifdef TARGET_OS_TV + for (UITouch* touch in touches) { + CGPoint location = [touch locationInView:self.view]; + [self sendGamepadTouchesWithType:@"ended" x:location.x y:location.y]; + } + #else [self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event]; + #endif } - (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event { + #ifdef TARGET_OS_TV + for (UITouch* touch in touches) { + CGPoint location = [touch locationInView:self.view]; + [self sendGamepadTouchesWithType:@"cancelled" x:location.x y:location.y]; + } + #else [self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event]; + #endif } - (void)forceTouchesCancelled:(NSSet*)touches { @@ -1454,6 +1724,7 @@ - (void)setViewportMetricsPaddings { } #pragma mark - Keyboard events +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) - (void)keyboardWillShowNotification:(NSNotification*)notification { // Immediately prior to a docked keyboard being shown or when a keyboard goes from @@ -1835,6 +2106,7 @@ - (void)ensureViewportMetricsIsCorrect { [self updateViewportMetricsIfNeeded]; } } +#endif - (void)handlePressEvent:(FlutterUIPressProxy*)press nextAction:(void (^)())next API_AVAILABLE(ios(13.4)) { @@ -1908,6 +2180,7 @@ - (void)superPressesCancelled:(NSSet*)presses withEvent:(UIPressesEven // both places to capture keys both inside and outside of a text field, but have // slightly different implementations. +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) - (void)pressesBegan:(NSSet*)presses withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) { if (@available(iOS 13.4, *)) { @@ -1968,8 +2241,31 @@ - (void)pressesCancelled:(NSSet*)presses } } +#else + +- (void)pressesBegan:(NSSet*)presses + withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0),tvos(10.0)) { + [super pressesBegan:presses withEvent:event]; +} + +- (void)pressesChanged:(NSSet*)presses + withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0),tvos(10.0)) { + [super pressesChanged:presses withEvent:event]; +} + +- (void)pressesEnded:(NSSet*)presses + withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0),tvos(10.0)) { + [super pressesEnded:presses withEvent:event]; +} + +- (void)pressesCancelled:(NSSet*)presses + withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0),tvos(10.0)) { + [super pressesCancelled:presses withEvent:event]; +} +#endif #pragma mark - Orientation updates +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) - (void)onOrientationPreferencesUpdated:(NSNotification*)notification { // Notifications may not be on the iOS UI thread __weak FlutterViewController* weakSelf = self; @@ -2078,6 +2374,7 @@ - (void)setIsHomeIndicatorHidden:(BOOL)hideHomeIndicator { - (BOOL)prefersHomeIndicatorAutoHidden { return self.isHomeIndicatorHidden; } +#endif - (BOOL)shouldAutorotate { return YES; @@ -2142,11 +2439,15 @@ - (BOOL)accessibilityPerformEscape { } + (BOOL)accessibilityIsOnOffSwitchLabelsEnabled { + #if !(defined(TARGET_OS_TV) && TARGET_OS_TV) if (@available(iOS 13, *)) { return UIAccessibilityIsOnOffSwitchLabelsEnabled(); } else { return NO; - } + } +#else + return NO; +#endif } #pragma mark - Set user settings @@ -2254,6 +2555,9 @@ - (NSString*)brightnessMode { // understood by the Flutter framework. See the settings system channel for more // information. - (NSString*)contrastMode { +#ifdef TARGET_OS_TV + return @"normal"; +#else if (@available(iOS 13, *)) { UIAccessibilityContrast contrast = self.traitCollection.accessibilityContrast; @@ -2265,10 +2569,11 @@ - (NSString*)contrastMode { } else { return @"normal"; } +#endif } #pragma mark - Status bar style - +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) - (UIStatusBarStyle)preferredStatusBarStyle { return self.statusBarStyle; } @@ -2307,6 +2612,8 @@ - (BOOL)prefersStatusBarHidden { return self.flutterPrefersStatusBarHidden; } +#endif + #pragma mark - Platform views - (FlutterPlatformViewsController*)platformViewsController { @@ -2417,6 +2724,7 @@ - (BOOL)isPresentingViewController { return self.presentedViewController != nil || self.isPresentingViewControllerAnimating; } +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) - (flutter::PointerData)updateMousePointerDataFrom:(UIGestureRecognizer*)gestureRecognizer API_AVAILABLE(ios(13.4)) { CGPoint location = [gestureRecognizer locationInView:self.view]; @@ -2461,6 +2769,7 @@ - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer } - (void)hoverEvent:(UIHoverGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) { + #if !(defined(TARGET_OS_TV) && TARGET_OS_TV) CGPoint oldLocation = _mouseState.location; flutter::PointerData pointer_data = [self updateMousePointerDataFrom:recognizer]; @@ -2514,6 +2823,7 @@ - (void)hoverEvent:(UIHoverGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4) packet->SetPointerData(/*i=*/0, pointer_data); [self.engine dispatchPointerDataPacket:std::move(packet)]; } + #endif } - (void)discreteScrollEvent:(UIPanGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) { @@ -2627,6 +2937,7 @@ - (void)pinchEvent:(UIPinchGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4) packet->SetPointerData(/*i=*/0, pointer_data); [self.engine dispatchPointerDataPacket:std::move(packet)]; } +#endif #pragma mark - State Restoration @@ -2651,3 +2962,4 @@ - (FlutterRestorationPlugin*)restorationPlugin { } @end + diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h b/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h index 3645d2c376f65..4fb43c2505a86 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h @@ -55,7 +55,12 @@ typedef void (^FlutterKeyboardAnimationCallback)(fml::TimePoint); * This is ignored when `UIViewControllerBasedStatusBarAppearance` in info.plist * of the app project is `false`. */ + +#if defined(TARGET_OS_TV) +//@property(nonatomic, readonly) BOOL prefersStatusBarHidden; +#else @property(nonatomic, assign, readwrite) BOOL prefersStatusBarHidden; +#endif @property(nonatomic, readonly) FlutterPlatformViewsController* platformViewsController; diff --git a/shell/platform/darwin/ios/framework/Source/SemanticsObject.h b/shell/platform/darwin/ios/framework/Source/SemanticsObject.h index 2c9e0516112ea..a349c1275160f 100644 --- a/shell/platform/darwin/ios/framework/Source/SemanticsObject.h +++ b/shell/platform/darwin/ios/framework/Source/SemanticsObject.h @@ -180,9 +180,11 @@ constexpr float kScrollExtentMaxForInf = 1000; /// The semantics object for switch buttons. This class creates an UISwitch to interact with the /// iOS. +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) @interface FlutterSwitchSemanticsObject : SemanticsObject @end +#endif /// The semantics object for scrollable. This class creates an UIScrollView to interact with the /// iOS. diff --git a/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm b/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm index 39882196e8e99..625edd943d26e 100644 --- a/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm +++ b/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm @@ -93,11 +93,13 @@ CGRect ConvertRectToGlobal(SemanticsObject* reference, CGRect local_rect) { } } // namespace - +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) @interface FlutterSwitchSemanticsObject () @property(nonatomic, retain, readonly) UISwitch* nativeSwitch; @end +#endif +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) @implementation FlutterSwitchSemanticsObject - (instancetype)initWithBridge:(fml::WeakPtr)bridge @@ -140,6 +142,7 @@ - (UIAccessibilityTraits)accessibilityTraits { } @end // FlutterSwitchSemanticsObject +#endif @interface FlutterScrollableSemanticsObject () @property(nonatomic) FlutterSemanticsScrollView* scrollView; @@ -420,12 +423,14 @@ - (NSAttributedString*)createAttributedStringFromString:(NSString*)string break; } case flutter::StringAttributeType::kSpellOut: { - if (@available(iOS 13.0, *)) { + #if !(defined(TARGET_OS_TV) && TARGET_OS_TV) + if (@available(iOS 13.0, tvOS 13.0, *)) { NSDictionary* attributeDict = @{ UIAccessibilitySpeechAttributeSpellOut : @YES, }; [attributedString setAttributes:attributeDict range:range]; } + #endif break; } } diff --git a/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm b/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm index daf6389834c81..673e192ff0583 100644 --- a/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm +++ b/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm @@ -264,7 +264,7 @@ static void ReplaceSemanticsObject(SemanticsObject* oldObject, !node.HasFlag(flutter::SemanticsFlags::kIsReadOnly)) { // Text fields are backed by objects that implement UITextInput. return [[TextInputSemanticsObject alloc] initWithBridge:weak_ptr uid:node.id]; - } else if (!node.HasFlag(flutter::SemanticsFlags::kIsInMutuallyExclusiveGroup) && +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) (node.HasFlag(flutter::SemanticsFlags::kHasToggledState) || node.HasFlag(flutter::SemanticsFlags::kHasCheckedState))) { return [[FlutterSwitchSemanticsObject alloc] initWithBridge:weak_ptr uid:node.id]; @@ -278,6 +278,7 @@ static void ReplaceSemanticsObject(SemanticsObject* oldObject, return [[FlutterPlatformViewSemanticsContainer alloc] initWithBridge:weak_ptr uid:node.id platformView:touchInterceptingView]; +#endif } else { return [[FlutterSemanticsObject alloc] initWithBridge:weak_ptr uid:node.id]; } diff --git a/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.mm b/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.mm index 0a4ba99a0dd1d..fb9a63096176b 100644 --- a/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.mm +++ b/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.mm @@ -58,6 +58,7 @@ #if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG || \ FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_PROFILE +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) std::optional FindGpuUsageInfo(io_iterator_t iterator) { for (fml::CFRef reg_entry(IOIteratorNext(iterator)); reg_entry.Get(); reg_entry.Reset(IOIteratorNext(iterator))) { @@ -77,18 +78,22 @@ } return std::nullopt; } +#endif [[maybe_unused]] std::optional FindSimulatorGpuUsageInfo() { +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) io_iterator_t io_iterator; if (IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceNameMatching("IntelAccelerator"), &io_iterator) == kIOReturnSuccess) { fml::CFRef iterator(io_iterator); return FindGpuUsageInfo(iterator.Get()); } +#endif return std::nullopt; } [[maybe_unused]] std::optional FindDeviceGpuUsageInfo() { +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) io_iterator_t io_iterator; if (IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceNameMatching("sgx"), &io_iterator) == kIOReturnSuccess) { @@ -106,6 +111,7 @@ } } } +#endif return std::nullopt; } @@ -120,6 +126,8 @@ return FindSimulatorGpuUsageInfo(); #elif TARGET_OS_IOS return FindDeviceGpuUsageInfo(); +#elif TARGET_OS_TV + return FindDeviceGpuUsageInfo(); #endif // TARGET_IPHONE_SIMULATOR } } // namespace @@ -136,6 +144,8 @@ kernel_return_code = task_threads(mach_task_self(), &mach_threads.threads, &mach_threads.thread_count); if (kernel_return_code != KERN_SUCCESS) { + FML_LOG(ERROR) << "Error retrieving task information: " + << mach_error_string(kernel_return_code); return std::nullopt; } @@ -172,6 +182,8 @@ num_threads--; break; default: + FML_LOG(ERROR) << "Error retrieving thread information: " + << mach_error_string(kernel_return_code); return std::nullopt; } } @@ -190,6 +202,8 @@ task_info(mach_task_self(), TASK_VM_INFO, reinterpret_cast(&task_memory_info), &task_memory_info_count); if (kernel_return_code != KERN_SUCCESS) { + FML_LOG(ERROR) << " Error retrieving task memory information: " + << mach_error_string(kernel_return_code); return std::nullopt; } diff --git a/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.mm b/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.mm index e73ed315a7ffd..63da275fa9b0d 100644 --- a/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.mm +++ b/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.mm @@ -96,6 +96,7 @@ - (void)setMaxRefreshRate:(double)refreshRate { return; } double maxFrameRate = fmax(refreshRate, 60); + #if !(defined(TARGET_OS_TV) && TARGET_OS_TV) double minFrameRate = fmax(maxFrameRate / 2, 60); if (@available(iOS 15.0, *)) { _displayLink.preferredFrameRateRange = @@ -103,6 +104,9 @@ - (void)setMaxRefreshRate:(double)refreshRate { } else { _displayLink.preferredFramesPerSecond = maxFrameRate; } + #else + _displayLink.preferredFramesPerSecond = maxFrameRate; + #endif } - (void)await { diff --git a/shell/platform/darwin/ios/rendering_api_selection.mm b/shell/platform/darwin/ios/rendering_api_selection.mm index 81099d7df1f3f..47127f0aa2a51 100644 --- a/shell/platform/darwin/ios/rendering_api_selection.mm +++ b/shell/platform/darwin/ios/rendering_api_selection.mm @@ -21,10 +21,14 @@ bool ShouldUseMetalRenderer() { bool ios_version_supports_metal = false; +#ifdef TARGET_OS_TV + ios_version_supports_metal = true; // min tvos GPU Family is same as IOS GPU family 2 +#else if (@available(iOS METAL_IOS_VERSION_BASELINE, *)) { id device = MTLCreateSystemDefaultDevice(); ios_version_supports_metal = [device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily1_v3]; } +#endif return ios_version_supports_metal; } @@ -61,13 +65,17 @@ Class GetCoreAnimationLayerClassForRenderingAPI(IOSRenderingAPI rendering_api) { case IOSRenderingAPI::kSoftware: return [CALayer class]; case IOSRenderingAPI::kMetal: +#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) if (@available(iOS METAL_IOS_VERSION_BASELINE, *)) { if ([FlutterMetalLayer enabled]) { return [FlutterMetalLayer class]; } else { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunguarded-availability-new" return [CAMetalLayer class]; - } +#pragma GCC diagnostic pop } +#endif FML_CHECK(false) << "Metal availability should already have been checked"; break; default: diff --git a/tools/gn b/tools/gn index 325b5cbece147..54d30dad89c42 100755 --- a/tools/gn +++ b/tools/gn @@ -278,8 +278,8 @@ def setup_apple_sdks(): prefixes = [ ('MacOSX', 'mac_sdk_path'), - ('iPhoneOS', 'ios_device_sdk_path'), - ('iPhoneSimulator', 'ios_simulator_sdk_path'), + ('AppleTVOS', 'ios_device_sdk_path'), + ('AppleTVSimulator', 'ios_simulator_sdk_path'), ] sdks_path = os.path.join(SRC_ROOT, 'flutter', 'prebuilts', 'SDKs') sdk_entries = os.listdir(sdks_path) diff --git a/tvos_port/dart.diff b/tvos_port/dart.diff new file mode 100644 index 0000000000000..84fb12c416b92 --- /dev/null +++ b/tvos_port/dart.diff @@ -0,0 +1,102 @@ +diff --git a/runtime/bin/process_macos.cc b/runtime/bin/process_macos.cc +index d7eb45e4ae0..58f0ff268b7 100644 +--- a/runtime/bin/process_macos.cc ++++ b/runtime/bin/process_macos.cc +@@ -164,12 +164,12 @@ class ExitCodeHandler { + // Set terminate_done_ to false, so we can use it as a guard for our + // monitor. + running_ = false; +- ++#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) + // Fork to wake up waitpid. + if (TEMP_FAILURE_RETRY(fork()) == 0) { + _Exit(0); + } +- ++#endif + monitor_->Notify(); + + while (!terminate_done_) { +@@ -299,6 +299,7 @@ class ProcessStarter { + } + + int Start() { ++#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) + // Create pipes required. + int err = CreatePipes(); + if (err != 0) { +@@ -388,6 +389,7 @@ class ProcessStarter { + ASSERT(exec_control_[1] == -1); + + *id_ = pid; ++#endif + return 0; + } + +@@ -446,6 +448,7 @@ class ProcessStarter { + } + + void ExecProcess() { ++#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) + if (mode_ == kNormal) { + if (TEMP_FAILURE_RETRY(dup2(write_out_[0], STDIN_FILENO)) == -1) { + ReportChildError(); +@@ -476,9 +479,11 @@ class ProcessStarter { + + execvp(path_, const_cast(program_arguments_)); + ReportChildError(); ++#endif + } + + void ExecDetachedProcess() { ++#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) + if (mode_ == kDetached) { + ASSERT(write_out_[0] == -1); + ASSERT(write_out_[1] == -1); +@@ -541,6 +546,7 @@ class ProcessStarter { + // to prevent deadlocks. + _Exit(0); + } ++#endif + } + + int RegisterProcess(pid_t pid) { +diff --git a/runtime/platform/globals.h b/runtime/platform/globals.h +index 124b85be8d6..8d59f0538e0 100644 +--- a/runtime/platform/globals.h ++++ b/runtime/platform/globals.h +@@ -119,7 +119,7 @@ + // Define the flavor of Mac OS we are running on. + #include + #define DART_HOST_OS_MACOS 1 +-#if TARGET_OS_IPHONE ++#if defined(TARGET_OS_IPHONE) || defined(TARGET_OS_TV) + #define DART_HOST_OS_IOS 1 + #endif + +diff --git a/runtime/vm/compiler/ffi/abi.cc b/runtime/vm/compiler/ffi/abi.cc +index 66fa5a8d564..8f0a4458794 100644 +--- a/runtime/vm/compiler/ffi/abi.cc ++++ b/runtime/vm/compiler/ffi/abi.cc +@@ -54,7 +54,7 @@ static_assert(offsetof(AbiAlignmentUint64, i) == 8, + #elif defined(DART_TARGET_OS_LINUX) + #define DART_TARGET_OS_NAME Linux + #elif defined(DART_TARGET_OS_MACOS) +-#if DART_TARGET_OS_MACOS_IOS ++#if defined(DART_TARGET_OS_MACOS_IOS) || defined(TARGET_OS_TV) + #define DART_TARGET_OS_NAME IOS + #else + #define DART_TARGET_OS_NAME MacOS +diff --git a/runtime/vm/cpu_arm.cc b/runtime/vm/cpu_arm.cc +index d40147b2ea0..5e970fe5113 100644 +--- a/runtime/vm/cpu_arm.cc ++++ b/runtime/vm/cpu_arm.cc +@@ -54,7 +54,7 @@ DEFINE_FLAG(bool, + "Use integer division instruction if supported"); + + #if defined(TARGET_HOST_MISMATCH) +-#if defined(DART_TARGET_OS_ANDROID) || defined(DART_TARGET_OS_MACOS_IOS) ++#if defined(DART_TARGET_OS_ANDROID) || defined(DART_TARGET_OS_MACOS_IOS) || defined(TARGET_OS_TV) + DEFINE_FLAG(bool, sim_use_hardfp, false, "Use the hardfp ABI."); + #else + DEFINE_FLAG(bool, sim_use_hardfp, true, "Use the hardfp ABI."); diff --git a/tvos_port/skia.diff b/tvos_port/skia.diff new file mode 100644 index 0000000000000..51dd13fe5ef3d --- /dev/null +++ b/tvos_port/skia.diff @@ -0,0 +1,172 @@ +diff --git a/src/gpu/ganesh/mtl/GrMtlCaps.mm b/src/gpu/ganesh/mtl/GrMtlCaps.mm +index 8d0bc6e364..1403609f89 100644 +--- a/src/gpu/ganesh/mtl/GrMtlCaps.mm ++++ b/src/gpu/ganesh/mtl/GrMtlCaps.mm +@@ -102,7 +102,11 @@ + return true; + } + } +-#elif defined(SK_BUILD_FOR_IOS) ++#endif ++ ++#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) ++ ++#if defined(SK_BUILD_FOR_IOS) + // TODO: support tvOS + *gpuFamily = GPUFamily::kApple; + // iOS 12 +@@ -164,6 +168,34 @@ + } + // We don't support earlier OSes + #endif ++#else ++ *gpuFamily = GPUFamily::kApple; ++ ++ if ([device supportsFeatureSet:MTLFeatureSet_tvOS_GPUFamily2_v2]) { ++ *group = 2; ++ return true; ++ } ++ if ([device supportsFeatureSet:MTLFeatureSet_tvOS_GPUFamily2_v1]) { ++ *group = 2; ++ return true; ++ } ++ if ([device supportsFeatureSet:MTLFeatureSet_tvOS_GPUFamily1_v4]) { ++ *group = 1; ++ return true; ++ } ++ if ([device supportsFeatureSet:MTLFeatureSet_tvOS_GPUFamily1_v3]) { ++ *group = 1; ++ return true; ++ } ++ if ([device supportsFeatureSet:MTLFeatureSet_tvOS_GPUFamily1_v2]) { ++ *group = 1; ++ return true; ++ } ++ if ([device supportsFeatureSet:MTLFeatureSet_tvOS_GPUFamily1_v1]) { ++ *group = 1; ++ return true; ++ } ++#endif //tvos + + #endif // GR_METAL_SDK_VERSION < 300 + +@@ -171,9 +203,16 @@ + return false; + } + ++#if defined(TARGET_OS_TV) && (__TV_OS_VERSION_MAX_ALLOWED < 130000) ++//#error "__TV_OS_VERSION_MAX_ALLOWED baddd" ++#endif ++ + bool GrMtlCaps::getGPUFamily(id device, GPUFamily* gpuFamily, int* group) { + #if GR_METAL_SDK_VERSION >= 220 +- if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) { ++#if !defined(TARGET_OS_TV) ++ if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) ++#endif ++ { + // Apple Silicon + #if GR_METAL_SDK_VERSION >= 230 + if ([device supportsFamily:MTLGPUFamilyApple7]) { +@@ -239,15 +278,21 @@ + } + + void GrMtlCaps::initGPUFamily(id device) { +- if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) { ++#if !defined(TARGET_OS_TV) ++ if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) ++#endif ++ { + if (this->getGPUFamily(device, &fGPUFamily, &fFamilyGroup)) { + return; + } +- } else { ++ } ++#if !defined(TARGET_OS_TV) ++ else { + if (this->getGPUFamilyFromFeatureSet(device, &fGPUFamily, &fFamilyGroup)) { + return; + } + } ++#endif + // We don't know what this is, fall back to minimum defaults + #ifdef SK_BUILD_FOR_MAC + fGPUFamily = GPUFamily::kMac; +diff --git a/src/gpu/ganesh/mtl/GrMtlCommandBuffer.mm b/src/gpu/ganesh/mtl/GrMtlCommandBuffer.mm +index 3bd7a192c1..df51a54d4b 100644 +--- a/src/gpu/ganesh/mtl/GrMtlCommandBuffer.mm ++++ b/src/gpu/ganesh/mtl/GrMtlCommandBuffer.mm +@@ -23,7 +23,7 @@ + + sk_sp GrMtlCommandBuffer::Make(id queue) { + id mtlCommandBuffer; +-#if GR_METAL_SDK_VERSION >= 230 ++#if GR_METAL_SDK_VERSION >= 230 && !(defined(TARGET_OS_TV)) + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { + MTLCommandBufferDescriptor* desc = [[MTLCommandBufferDescriptor alloc] init]; + desc.errorOptions = MTLCommandBufferErrorOptionEncoderExecutionStatus; +diff --git a/src/gpu/ganesh/mtl/GrMtlGpu.mm b/src/gpu/ganesh/mtl/GrMtlGpu.mm +index 0cac6cb18e..1862e55e0a 100644 +--- a/src/gpu/ganesh/mtl/GrMtlGpu.mm ++++ b/src/gpu/ganesh/mtl/GrMtlGpu.mm +@@ -1815,10 +1815,12 @@ void copy_src_data(char* dst, + } + #endif // SK_BUILD_FOR_MAC + #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101500 || __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 ++#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) + if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) { + writer->appendBool("hasUnifiedMemory", fDevice.hasUnifiedMemory); + } + #endif ++#endif + #ifdef SK_BUILD_FOR_MAC + #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101500 + if (@available(macOS 10.15, *)) { +@@ -1899,9 +1901,11 @@ void copy_src_data(char* dst, + writer->appendU64("maxArgumentBufferSamplerCount", fDevice.maxArgumentBufferSamplerCount); + } + #ifdef SK_BUILD_FOR_IOS ++#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) + if (@available(iOS 13.0, tvOS 13.0, *)) { + writer->appendU64("sparseTileSizeInBytes", fDevice.sparseTileSizeInBytes); + } ++#endif + #endif + writer->endObject(); + +diff --git a/src/gpu/graphite/mtl/MtlCaps.mm b/src/gpu/graphite/mtl/MtlCaps.mm +index 871575ff10..63b5de5514 100644 +--- a/src/gpu/graphite/mtl/MtlCaps.mm ++++ b/src/gpu/graphite/mtl/MtlCaps.mm +@@ -40,6 +40,7 @@ + } + + bool MtlCaps::GetGPUFamily(id device, GPUFamily* gpuFamily, int* group) { ++#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) + #if SKGPU_GRAPHITE_METAL_SDK_VERSION >= 220 + if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) { + // Apple Silicon +@@ -50,6 +51,7 @@ + return true; + } + #endif ++#if !(defined(TARGET_OS_TV) && TARGET_OS_TV) + #ifdef SK_BUILD_FOR_IOS + if ([device supportsFamily:MTLGPUFamilyApple6]) { + *gpuFamily = GPUFamily::kApple; +@@ -82,6 +84,7 @@ + return true; + } + #endif ++#endif //tvos + + // Older Macs + // MTLGPUFamilyMac1, MTLGPUFamilyMacCatalyst1, and MTLGPUFamilyMacCatalyst2 are deprecated. +@@ -101,6 +104,7 @@ + } + } + #endif ++#endif //tvos + + // No supported GPU families were found + return false;