diff --git a/.github/workflows/tvos_builds.yml b/.github/workflows/tvos_builds.yml
new file mode 100644
index 000000000000..65a23f8e9fb7
--- /dev/null
+++ b/.github/workflows/tvos_builds.yml
@@ -0,0 +1,38 @@
+name: 📺 tvOS Builds
+on: [push, pull_request]
+
+# Global Settings
+env:
+ GODOT_BASE_BRANCH: master
+ SCONSFLAGS: verbose=yes warnings=extra werror=yes debug_symbols=no module_text_server_fb_enabled=yes
+
+concurrency:
+ group: ci-${{github.actor}}-${{github.head_ref || github.run_number}}-${{github.ref}}-tvos
+ cancel-in-progress: true
+
+jobs:
+ tvos-template:
+ runs-on: "macos-latest"
+ name: Template (target=release, tools=no)
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Setup Godot build cache
+ uses: ./.github/actions/godot-cache
+ continue-on-error: true
+
+ - name: Setup python and scons
+ uses: ./.github/actions/godot-deps
+
+ - name: Compilation (armv7)
+ uses: ./.github/actions/godot-build
+ with:
+ sconsflags: ${{ env.SCONSFLAGS }}
+ platform: tvos
+ target: release
+ tools: false
+ tests: false
+
+ - name: Upload artifact
+ uses: ./.github/actions/upload-artifact
diff --git a/core/input/input_builders.py b/core/input/input_builders.py
index 748ec0613307..d2b07783c14b 100644
--- a/core/input/input_builders.py
+++ b/core/input/input_builders.py
@@ -50,6 +50,7 @@ def make_default_controller_mappings(target, source, env):
"Mac OS X": "#ifdef OSX_ENABLED",
"Android": "#if defined(__ANDROID__)",
"iOS": "#ifdef IPHONE_ENABLED",
+ "tvOS": "#ifdef TVOS_ENABLED",
"Javascript": "#ifdef JAVASCRIPT_ENABLED",
"UWP": "#ifdef UWP_ENABLED",
}
diff --git a/doc/classes/EditorExportPlugin.xml b/doc/classes/EditorExportPlugin.xml
index 8aa2db2cf80c..0b26ca839e28 100644
--- a/doc/classes/EditorExportPlugin.xml
+++ b/doc/classes/EditorExportPlugin.xml
@@ -115,6 +115,57 @@
In case of a directory code-sign will error if you place non code object in directory.
+
+
+
+
+ Adds an tvOS bundle file from the given [code]path[/code] to the exported project.
+
+
+
+
+
+
+ Adds a C++ code to the tvOS export. The final code is created from the code appended by each active export plugin.
+
+
+
+
+
+
+ Adds a dynamic library (*.dylib, *.framework) to Linking Phase in tvOS's Xcode project and embeds it into resulting binary.
+ [b]Note:[/b] For static libraries (*.a) works in same way as [code]add_ios_framework[/code].
+ This method should not be used for System libraries as they are already present on the device.
+
+
+
+
+
+
+ Adds a static library (*.a) or dynamic library (*.dylib, *.framework) to Linking Phase in tvOS's Xcode project.
+
+
+
+
+
+
+ Adds linker flags for the tvOS export.
+
+
+
+
+
+
+ Adds content for tvOS Property List files.
+
+
+
+
+
+
+ Adds a static lib from the given [code]path[/code] to the tvOS project.
+
+
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index a8b4129061ad..6424349ea6ba 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -774,6 +774,9 @@
Default delay for touch events. This only affects iOS devices.
+
+ Default delay for end press events. This only affects tvOS devices.
+
The locale to fall back to if a translation isn't available in a given language. If left empty, [code]en[/code] (English) will be used.
diff --git a/drivers/gles3/rasterizer_gles3.cpp b/drivers/gles3/rasterizer_gles3.cpp
index faadb2a4edfb..0b9ff1aa86dc 100644
--- a/drivers/gles3/rasterizer_gles3.cpp
+++ b/drivers/gles3/rasterizer_gles3.cpp
@@ -67,9 +67,9 @@
#endif
#endif
-#if !defined(IPHONE_ENABLED) && !defined(JAVASCRIPT_ENABLED)
+#if !defined(UIKIT_ENABLED) && !defined(JAVASCRIPT_ENABLED)
// We include EGL below to get debug callback on GLES2 platforms,
-// but EGL is not available on iOS.
+// but EGL is not available on iOS/tvOS.
#define CAN_DEBUG
#endif
diff --git a/drivers/unix/os_unix.cpp b/drivers/unix/os_unix.cpp
index 3b5e1bf91d53..6c1e54807102 100644
--- a/drivers/unix/os_unix.cpp
+++ b/drivers/unix/os_unix.cpp
@@ -294,6 +294,8 @@ Error OS_Unix::execute(const String &p_path, const List &p_arguments, St
// Don't compile this code at all to avoid undefined references.
// Actual virtual call goes to OS_JavaScript.
ERR_FAIL_V(ERR_BUG);
+#elif defined(TVOS_ENABLED)
+ ERR_FAIL_V(ERR_CANT_FORK);
#else
if (r_pipe) {
String command = "\"" + p_path + "\"";
@@ -363,6 +365,8 @@ Error OS_Unix::create_process(const String &p_path, const List &p_argume
// Don't compile this code at all to avoid undefined references.
// Actual virtual call goes to OS_JavaScript.
ERR_FAIL_V(ERR_BUG);
+#elif defined(TVOS_ENABLED)
+ ERR_FAIL_V(ERR_CANT_FORK);
#else
pid_t pid = fork();
ERR_FAIL_COND_V(pid < 0, ERR_CANT_FORK);
diff --git a/drivers/vulkan/SCsub b/drivers/vulkan/SCsub
index b6ceb1cdeafd..7ad7a755ee6d 100644
--- a/drivers/vulkan/SCsub
+++ b/drivers/vulkan/SCsub
@@ -15,7 +15,7 @@ if env["use_volk"]:
if env["platform"] == "android":
env.AppendUnique(CPPDEFINES=["VK_USE_PLATFORM_ANDROID_KHR"])
-elif env["platform"] == "iphone":
+elif env["platform"] == "iphone" or env["platform"] == "tvos":
env.AppendUnique(CPPDEFINES=["VK_USE_PLATFORM_IOS_MVK"])
elif env["platform"] == "linuxbsd":
env.AppendUnique(CPPDEFINES=["VK_USE_PLATFORM_XLIB_KHR"])
diff --git a/editor/editor_export.cpp b/editor/editor_export.cpp
index afb5bd9d4d4d..c659c26602ec 100644
--- a/editor/editor_export.cpp
+++ b/editor/editor_export.cpp
@@ -637,6 +637,65 @@ Vector EditorExportPlugin::get_ios_project_static_libs() const {
return ios_project_static_libs;
}
+void EditorExportPlugin::add_tvos_framework(const String &p_path) {
+ tvos_frameworks.push_back(p_path);
+}
+
+void EditorExportPlugin::add_tvos_embedded_framework(const String &p_path) {
+ tvos_embedded_frameworks.push_back(p_path);
+}
+
+Vector EditorExportPlugin::get_tvos_frameworks() const {
+ return tvos_frameworks;
+}
+
+Vector EditorExportPlugin::get_tvos_embedded_frameworks() const {
+ return tvos_embedded_frameworks;
+}
+
+void EditorExportPlugin::add_tvos_plist_content(const String &p_plist_content) {
+ tvos_plist_content += p_plist_content + "\n";
+}
+
+String EditorExportPlugin::get_tvos_plist_content() const {
+ return tvos_plist_content;
+}
+
+void EditorExportPlugin::add_tvos_linker_flags(const String &p_flags) {
+ if (tvos_linker_flags.length() > 0) {
+ tvos_linker_flags += ' ';
+ }
+ tvos_linker_flags += p_flags;
+}
+
+String EditorExportPlugin::get_tvos_linker_flags() const {
+ return tvos_linker_flags;
+}
+
+void EditorExportPlugin::add_tvos_bundle_file(const String &p_path) {
+ tvos_bundle_files.push_back(p_path);
+}
+
+Vector EditorExportPlugin::get_tvos_bundle_files() const {
+ return tvos_bundle_files;
+}
+
+void EditorExportPlugin::add_tvos_cpp_code(const String &p_code) {
+ tvos_cpp_code += p_code;
+}
+
+String EditorExportPlugin::get_tvos_cpp_code() const {
+ return tvos_cpp_code;
+}
+
+void EditorExportPlugin::add_tvos_project_static_lib(const String &p_path) {
+ tvos_project_static_libs.push_back(p_path);
+}
+
+Vector EditorExportPlugin::get_tvos_project_static_libs() const {
+ return tvos_project_static_libs;
+}
+
void EditorExportPlugin::_export_file_script(const String &p_path, const String &p_type, const Vector &p_features) {
GDVIRTUAL_CALL(_export_file, p_path, p_type, p_features);
}
@@ -670,6 +729,13 @@ void EditorExportPlugin::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_ios_bundle_file", "path"), &EditorExportPlugin::add_ios_bundle_file);
ClassDB::bind_method(D_METHOD("add_ios_cpp_code", "code"), &EditorExportPlugin::add_ios_cpp_code);
ClassDB::bind_method(D_METHOD("add_osx_plugin_file", "path"), &EditorExportPlugin::add_osx_plugin_file);
+ ClassDB::bind_method(D_METHOD("add_tvos_project_static_lib", "path"), &EditorExportPlugin::add_tvos_project_static_lib);
+ ClassDB::bind_method(D_METHOD("add_tvos_framework", "path"), &EditorExportPlugin::add_tvos_framework);
+ ClassDB::bind_method(D_METHOD("add_tvos_embedded_framework", "path"), &EditorExportPlugin::add_tvos_embedded_framework);
+ ClassDB::bind_method(D_METHOD("add_tvos_plist_content", "plist_content"), &EditorExportPlugin::add_tvos_plist_content);
+ ClassDB::bind_method(D_METHOD("add_tvos_linker_flags", "flags"), &EditorExportPlugin::add_tvos_linker_flags);
+ ClassDB::bind_method(D_METHOD("add_tvos_bundle_file", "path"), &EditorExportPlugin::add_tvos_bundle_file);
+ ClassDB::bind_method(D_METHOD("add_tvos_cpp_code", "code"), &EditorExportPlugin::add_tvos_cpp_code);
ClassDB::bind_method(D_METHOD("skip"), &EditorExportPlugin::skip);
GDVIRTUAL_BIND(_export_file, "path", "type", "features");
diff --git a/editor/editor_export.h b/editor/editor_export.h
index 108abab29bc8..f2a74ec44a76 100644
--- a/editor/editor_export.h
+++ b/editor/editor_export.h
@@ -311,6 +311,14 @@ class EditorExportPlugin : public RefCounted {
Vector osx_plugin_files;
+ Vector tvos_frameworks;
+ Vector tvos_embedded_frameworks;
+ Vector tvos_project_static_libs;
+ String tvos_plist_content;
+ String tvos_linker_flags;
+ Vector tvos_bundle_files;
+ String tvos_cpp_code;
+
_FORCE_INLINE_ void _clear() {
shared_objects.clear();
extra_files.clear();
@@ -325,6 +333,13 @@ class EditorExportPlugin : public RefCounted {
ios_linker_flags = "";
ios_cpp_code = "";
osx_plugin_files.clear();
+
+ tvos_frameworks.clear();
+ tvos_embedded_frameworks.clear();
+ tvos_bundle_files.clear();
+ tvos_plist_content = "";
+ tvos_linker_flags = "";
+ tvos_cpp_code = "";
}
void _export_file_script(const String &p_path, const String &p_type, const Vector &p_features);
@@ -347,6 +362,14 @@ class EditorExportPlugin : public RefCounted {
void add_ios_cpp_code(const String &p_code);
void add_osx_plugin_file(const String &p_path);
+ void add_tvos_framework(const String &p_path);
+ void add_tvos_embedded_framework(const String &p_path);
+ void add_tvos_project_static_lib(const String &p_path);
+ void add_tvos_plist_content(const String &p_plist_content);
+ void add_tvos_linker_flags(const String &p_flags);
+ void add_tvos_bundle_file(const String &p_path);
+ void add_tvos_cpp_code(const String &p_code);
+
void skip();
virtual void _export_file(const String &p_path, const String &p_type, const Set &p_features);
@@ -368,6 +391,14 @@ class EditorExportPlugin : public RefCounted {
String get_ios_cpp_code() const;
const Vector &get_osx_plugin_files() const;
+ Vector get_tvos_frameworks() const;
+ Vector get_tvos_embedded_frameworks() const;
+ Vector get_tvos_project_static_libs() const;
+ String get_tvos_plist_content() const;
+ String get_tvos_linker_flags() const;
+ Vector get_tvos_bundle_files() const;
+ String get_tvos_cpp_code() const;
+
EditorExportPlugin();
};
diff --git a/editor/plugins/gdextension_export_plugin.h b/editor/plugins/gdextension_export_plugin.h
index 8ed72b1c422b..d3eb1632d7ad 100644
--- a/editor/plugins/gdextension_export_plugin.h
+++ b/editor/plugins/gdextension_export_plugin.h
@@ -81,9 +81,9 @@ void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p
}
add_shared_object(library_path, tags);
- if (p_features.has("iOS") && (library_path.ends_with(".a") || library_path.ends_with(".xcframework"))) {
+ if ((p_features.has("iOS") || p_features.has("tvOS")) && (library_path.ends_with(".a") || library_path.ends_with(".xcframework"))) {
String additional_code = "extern void register_dynamic_symbol(char *name, void *address);\n"
- "extern void add_ios_init_callback(void (*cb)());\n"
+ "extern void add_$PLATFORM_init_callback(void (*cb)());\n"
"\n"
"extern \"C\" void $ENTRY();\n"
"void $ENTRY_init() {\n"
@@ -91,15 +91,22 @@ void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p
"}\n"
"struct $ENTRY_struct {\n"
" $ENTRY_struct() {\n"
- " add_ios_init_callback($ENTRY_init);\n"
+ " add_$PLATFORM_init_callback($ENTRY_init);\n"
" }\n"
"};\n"
"$ENTRY_struct $ENTRY_struct_instance;\n\n";
additional_code = additional_code.replace("$ENTRY", entry_symbol);
- add_ios_cpp_code(additional_code);
-
String linker_flags = "-Wl,-U,_" + entry_symbol;
- add_ios_linker_flags(linker_flags);
+
+ if (p_features.has("iOS")) {
+ additional_code = additional_code.replace("$PLATFORM", "ios");
+ add_ios_cpp_code(additional_code);
+ add_ios_linker_flags(linker_flags);
+ } else if (p_features.has("tvOS")) {
+ additional_code = additional_code.replace("$PLATFORM", "tvos");
+ add_tvos_cpp_code(additional_code);
+ add_tvos_linker_flags(linker_flags);
+ }
}
break;
}
diff --git a/main/main.cpp b/main/main.cpp
index f41fa136bac4..51756e050727 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -1487,6 +1487,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
GLOBAL_DEF("display/window/ios/hide_home_indicator", true);
GLOBAL_DEF("input_devices/pointing/ios/touch_delay", 0.150);
+ GLOBAL_DEF("input_devices/pointing/tvos/press_end_delay", 0.150);
Engine::get_singleton()->set_frame_delay(frame_delay);
diff --git a/methods.py b/methods.py
index fe84641e9d06..13b978d0510d 100644
--- a/methods.py
+++ b/methods.py
@@ -853,6 +853,12 @@ def detect_darwin_sdk_path(platform, env):
elif platform == "iphonesimulator":
sdk_name = "iphonesimulator"
var_name = "IPHONESDK"
+ elif platform == "tvos":
+ sdk_name = "appletvos"
+ var_name = "TVOSSDK"
+ elif platform == "tvossimulator":
+ sdk_name = "appletvsimulator"
+ var_name = "TVOSSDK"
else:
raise Exception("Invalid platform argument passed to detect_darwin_sdk_path")
diff --git a/misc/dist/tvos_xcode/data.pck b/misc/dist/tvos_xcode/data.pck
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/misc/dist/tvos_xcode/godot_tvos.xcodeproj/project.pbxproj b/misc/dist/tvos_xcode/godot_tvos.xcodeproj/project.pbxproj
new file mode 100644
index 000000000000..da6c721c37c0
--- /dev/null
+++ b/misc/dist/tvos_xcode/godot_tvos.xcodeproj/project.pbxproj
@@ -0,0 +1,374 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 52;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 1F1575721F582BE20003B888 /* dylibs in Resources */ = {isa = PBXBuildFile; fileRef = 1F1575711F582BE20003B888 /* dylibs */; };
+ 9088C79E25C98AF800FCAE9A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9088C79D25C98AF800FCAE9A /* Assets.xcassets */; };
+ 9088C7A125C98AF800FCAE9A /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9088C79F25C98AF800FCAE9A /* Launch Screen.storyboard */; };
+ 9088C7A425C98AF800FCAE9A /* dummy.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9088C7A325C98AF800FCAE9A /* dummy.cpp */; };
+ DEADBEEF2F582BE20003B888 /* $binary.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = DEADBEEF1F582BE20003B888 /* $binary.xcframework */; };
+ 90C4BA1125C9A60500CD5FD1 /* $binary.pck in Resources */ = {isa = PBXBuildFile; fileRef = 90C4BA1025C9A5FC00CD5FD1 /* $binary.pck */; };
+/* End PBXBuildFile section */
+
+
+/* Begin PBXCopyFilesBuildPhase section */
+ 90A13CD024AA68E500E8464F /* Embed Frameworks */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 10;
+ files = (
+ $pbx_embeded_frameworks
+ );
+ name = "Embed Frameworks";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+ 1F1575711F582BE20003B888 /* dylibs */ = {isa = PBXFileReference; lastKnownFileType = folder; name = dylibs; path = "$binary/dylibs"; sourceTree = ""; };
+ 9088C79125C98AF700FCAE9A /* $binary.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "$binary.app"; sourceTree = BUILT_PRODUCTS_DIR; };
+ 9088C79D25C98AF800FCAE9A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ 9088C7A025C98AF800FCAE9A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = "Base.lproj/Launch Screen.storyboard"; sourceTree = ""; };
+ 9088C7A225C98AF800FCAE9A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 9088C7A325C98AF800FCAE9A /* dummy.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = dummy.cpp; sourceTree = ""; };
+ DEADBEEF1F582BE20003B888 /* $binary.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = godot; path = "$binary.xcframework"; sourceTree = ""; };
+ 90C4BA1025C9A5FC00CD5FD1 /* $binary.pck */ = {isa = PBXFileReference; lastKnownFileType = file; path = "$binary.pck"; sourceTree = SOURCE_ROOT; };
+ $additional_pbx_files
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 9088C78E25C98AF700FCAE9A /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DEADBEEF2F582BE20003B888 /* $binary.xcframework */,
+ $additional_pbx_frameworks_build
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 9088C78825C98AF700FCAE9A = {
+ isa = PBXGroup;
+ children = (
+ 1F1575711F582BE20003B888 /* dylibs */,
+ 90C4BA1025C9A5FC00CD5FD1 /* $binary.pck */,
+ 9088C79325C98AF700FCAE9A /* $binary */,
+ D0BCFE3618AEBDA2004A7AAE /* Frameworks */,
+ 9088C79225C98AF700FCAE9A /* Products */,
+ $additional_pbx_resources_refs
+ );
+ sourceTree = "";
+ };
+ 9088C79225C98AF700FCAE9A /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 9088C79125C98AF700FCAE9A /* $binary.app */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ D0BCFE3618AEBDA2004A7AAE /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ DEADBEEF1F582BE20003B888 /* $binary.xcframework */,
+ $additional_pbx_frameworks_refs
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
+ 9088C79325C98AF700FCAE9A /* $binary */ = {
+ isa = PBXGroup;
+ children = (
+ 9088C79D25C98AF800FCAE9A /* Assets.xcassets */,
+ 9088C79F25C98AF800FCAE9A /* Launch Screen.storyboard */,
+ 90C4BA1025C9A5FC00CD5FD1 /* $binary.pck */,
+ 9088C7A225C98AF800FCAE9A /* Info.plist */,
+ 9088C7A325C98AF800FCAE9A /* dummy.cpp */,
+ );
+ path = "$binary";
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 9088C79025C98AF700FCAE9A /* $binary */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 9088C7A725C98AF800FCAE9A /* Build configuration list for PBXNativeTarget "$binary" */;
+ buildPhases = (
+ 9088C78D25C98AF700FCAE9A /* Sources */,
+ 9088C78E25C98AF700FCAE9A /* Frameworks */,
+ 9088C78F25C98AF700FCAE9A /* Resources */,
+ 90A13CD024AA68E500E8464F /* Embed Frameworks */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = "$binary";
+ productName = "$name";
+ productReference = 9088C79125C98AF700FCAE9A /* $binary.app */;
+ productType = "com.apple.product-type.application";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 9088C78925C98AF700FCAE9A /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ LastUpgradeCheck = 1240;
+ TargetAttributes = {
+ 9088C79025C98AF700FCAE9A = {
+ CreatedOnToolsVersion = 12.4;
+ };
+ };
+ };
+ buildConfigurationList = 9088C78C25C98AF700FCAE9A /* Build configuration list for PBXProject "$binary" */;
+ compatibilityVersion = "Xcode 9.3";
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = 9088C78825C98AF700FCAE9A;
+ productRefGroup = 9088C79225C98AF700FCAE9A /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 9088C79025C98AF700FCAE9A /* $binary */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 9088C78F25C98AF700FCAE9A /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 9088C7A125C98AF800FCAE9A /* Launch Screen.storyboard in Resources */,
+ 9088C79E25C98AF800FCAE9A /* Assets.xcassets in Resources */,
+ 90C4BA1125C9A60500CD5FD1 /* $binary.pck in Resources */,
+ $additional_pbx_resources_build
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 9088C78D25C98AF700FCAE9A /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 9088C7A425C98AF800FCAE9A /* dummy.cpp in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXVariantGroup section */
+ 9088C79F25C98AF800FCAE9A /* Launch Screen.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 9088C7A025C98AF800FCAE9A /* Base */,
+ );
+ name = "Launch Screen.storyboard";
+ sourceTree = "";
+ };
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+ 9088C7A525C98AF800FCAE9A /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ OTHER_LDFLAGS = "$linker_flags";
+ SDKROOT = appletvos;
+ TVOS_DEPLOYMENT_TARGET = 6;
+ };
+ name = Debug;
+ };
+ 9088C7A625C98AF800FCAE9A /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ MTL_FAST_MATH = YES;
+ OTHER_LDFLAGS = "$linker_flags";
+ SDKROOT = appletvos;
+ TVOS_DEPLOYMENT_TARGET = 6;
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ 9088C7A825C98AF800FCAE9A /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_STYLE = Automatic;
+ DEVELOPMENT_TEAM = "$team_id";
+ FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/**";
+ INFOPLIST_FILE = "$binary/Info.plist";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ LIBRARY_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(PROJECT_DIR)/**",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = "$bundle_identifier";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ TARGETED_DEVICE_FAMILY = 3;
+ TVOS_DEPLOYMENT_TARGET = 10.0;
+ };
+ name = Debug;
+ };
+ 9088C7A925C98AF800FCAE9A /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_STYLE = Automatic;
+ DEVELOPMENT_TEAM = "$team_id";
+ FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/**";
+ INFOPLIST_FILE = "$binary/Info.plist";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ LIBRARY_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(PROJECT_DIR)/**",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = "$bundle_identifier";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ TARGETED_DEVICE_FAMILY = 3;
+ TVOS_DEPLOYMENT_TARGET = 10.0;
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 9088C78C25C98AF700FCAE9A /* Build configuration list for PBXProject "$binary" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 9088C7A525C98AF800FCAE9A /* Debug */,
+ 9088C7A625C98AF800FCAE9A /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 9088C7A725C98AF800FCAE9A /* Build configuration list for PBXNativeTarget "$binary" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 9088C7A825C98AF800FCAE9A /* Debug */,
+ 9088C7A925C98AF800FCAE9A /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 9088C78925C98AF700FCAE9A /* Project object */;
+}
diff --git a/misc/dist/tvos_xcode/godot_tvos.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/misc/dist/tvos_xcode/godot_tvos.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 000000000000..c9c19829f4ae
--- /dev/null
+++ b/misc/dist/tvos_xcode/godot_tvos.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/misc/dist/tvos_xcode/godot_tvos.xcodeproj/xcshareddata/xcschemes/godot_tvos.xcscheme b/misc/dist/tvos_xcode/godot_tvos.xcodeproj/xcshareddata/xcschemes/godot_tvos.xcscheme
new file mode 100644
index 000000000000..b6beeb012fea
--- /dev/null
+++ b/misc/dist/tvos_xcode/godot_tvos.xcodeproj/xcshareddata/xcschemes/godot_tvos.xcscheme
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/AccentColor.colorset/Contents.json b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/AccentColor.colorset/Contents.json
new file mode 100644
index 000000000000..eb8789700816
--- /dev/null
+++ b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/AccentColor.colorset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "colors" : [
+ {
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 000000000000..2e003356c750
--- /dev/null
+++ b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json
new file mode 100644
index 000000000000..73c00596a7fc
--- /dev/null
+++ b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json
new file mode 100644
index 000000000000..de59d885ae8d
--- /dev/null
+++ b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json
@@ -0,0 +1,17 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "layers" : [
+ {
+ "filename" : "Front.imagestacklayer"
+ },
+ {
+ "filename" : "Middle.imagestacklayer"
+ },
+ {
+ "filename" : "Back.imagestacklayer"
+ }
+ ]
+}
diff --git a/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 000000000000..2e003356c750
--- /dev/null
+++ b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json
new file mode 100644
index 000000000000..73c00596a7fc
--- /dev/null
+++ b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 000000000000..2e003356c750
--- /dev/null
+++ b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json
new file mode 100644
index 000000000000..73c00596a7fc
--- /dev/null
+++ b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 000000000000..795cce17243c
--- /dev/null
+++ b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "tv",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json
new file mode 100644
index 000000000000..73c00596a7fc
--- /dev/null
+++ b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json
new file mode 100644
index 000000000000..de59d885ae8d
--- /dev/null
+++ b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json
@@ -0,0 +1,17 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "layers" : [
+ {
+ "filename" : "Front.imagestacklayer"
+ },
+ {
+ "filename" : "Middle.imagestacklayer"
+ },
+ {
+ "filename" : "Back.imagestacklayer"
+ }
+ ]
+}
diff --git a/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 000000000000..795cce17243c
--- /dev/null
+++ b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "tv",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json
new file mode 100644
index 000000000000..73c00596a7fc
--- /dev/null
+++ b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 000000000000..795cce17243c
--- /dev/null
+++ b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "tv",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json
new file mode 100644
index 000000000000..73c00596a7fc
--- /dev/null
+++ b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json
new file mode 100644
index 000000000000..f47ba43daac4
--- /dev/null
+++ b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json
@@ -0,0 +1,32 @@
+{
+ "assets" : [
+ {
+ "filename" : "App Icon - App Store.imagestack",
+ "idiom" : "tv",
+ "role" : "primary-app-icon",
+ "size" : "1280x768"
+ },
+ {
+ "filename" : "App Icon.imagestack",
+ "idiom" : "tv",
+ "role" : "primary-app-icon",
+ "size" : "400x240"
+ },
+ {
+ "filename" : "Top Shelf Image Wide.imageset",
+ "idiom" : "tv",
+ "role" : "top-shelf-image-wide",
+ "size" : "2320x720"
+ },
+ {
+ "filename" : "Top Shelf Image.imageset",
+ "idiom" : "tv",
+ "role" : "top-shelf-image",
+ "size" : "1920x720"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json
new file mode 100644
index 000000000000..b65f0cddcfcf
--- /dev/null
+++ b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json
@@ -0,0 +1,24 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "tv",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "tv-marketing",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "tv-marketing",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json
new file mode 100644
index 000000000000..b65f0cddcfcf
--- /dev/null
+++ b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json
@@ -0,0 +1,24 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "tv",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "tv-marketing",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "tv-marketing",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/Contents.json b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/Contents.json
new file mode 100644
index 000000000000..73c00596a7fc
--- /dev/null
+++ b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/misc/dist/tvos_xcode/godot_tvos/Base.lproj/Launch Screen.storyboard b/misc/dist/tvos_xcode/godot_tvos/Base.lproj/Launch Screen.storyboard
new file mode 100644
index 000000000000..660ba53de4f7
--- /dev/null
+++ b/misc/dist/tvos_xcode/godot_tvos/Base.lproj/Launch Screen.storyboard
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/misc/dist/tvos_xcode/godot_tvos/Info.plist b/misc/dist/tvos_xcode/godot_tvos/Info.plist
new file mode 100644
index 000000000000..daa9782324e2
--- /dev/null
+++ b/misc/dist/tvos_xcode/godot_tvos/Info.plist
@@ -0,0 +1,34 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ ITSAppUsesNonExemptEncryption
+
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ $(PRODUCT_BUNDLE_PACKAGE_TYPE)
+ CFBundleShortVersionString
+ $short_version
+ CFBundleVersion
+ $build_version
+ LSRequiresIPhoneOS
+
+ UILaunchStoryboardName
+ Launch Screen
+ UIRequiredDeviceCapabilities
+
+ arm64
+
+ UIUserInterfaceStyle
+ Automatic
+
+
diff --git a/misc/dist/tvos_xcode/godot_tvos/dummy.cpp b/misc/dist/tvos_xcode/godot_tvos/dummy.cpp
new file mode 100644
index 000000000000..de5b02dc9949
--- /dev/null
+++ b/misc/dist/tvos_xcode/godot_tvos/dummy.cpp
@@ -0,0 +1,31 @@
+/*************************************************************************/
+/* dummy.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+$cpp_code
diff --git a/misc/dist/tvos_xcode/godot_tvos/dylibs/empty b/misc/dist/tvos_xcode/godot_tvos/dylibs/empty
new file mode 100644
index 000000000000..bd3e89433361
--- /dev/null
+++ b/misc/dist/tvos_xcode/godot_tvos/dylibs/empty
@@ -0,0 +1 @@
+Dummy file to make dylibs folder exported
diff --git a/misc/dist/tvos_xcode/libgodot.tvos.debug.xcframework/Info.plist b/misc/dist/tvos_xcode/libgodot.tvos.debug.xcframework/Info.plist
new file mode 100644
index 000000000000..ea990d4c4388
--- /dev/null
+++ b/misc/dist/tvos_xcode/libgodot.tvos.debug.xcframework/Info.plist
@@ -0,0 +1,40 @@
+
+
+
+
+ AvailableLibraries
+
+
+ LibraryIdentifier
+ tvos-arm64
+ LibraryPath
+ libgodot.a
+ SupportedArchitectures
+
+ arm64
+
+ SupportedPlatform
+ tvos
+
+
+ LibraryIdentifier
+ tvos-arm64_x86_64-simulator
+ LibraryPath
+ libgodot.a
+ SupportedArchitectures
+
+ arm64
+ x86_64
+
+ SupportedPlatform
+ tvos
+ SupportedPlatformVariant
+ simulator
+
+
+ CFBundlePackageType
+ XFWK
+ XCFrameworkFormatVersion
+ 1.0
+
+
diff --git a/misc/dist/tvos_xcode/libgodot.tvos.debug.xcframework/tvos-arm64/empty b/misc/dist/tvos_xcode/libgodot.tvos.debug.xcframework/tvos-arm64/empty
new file mode 100644
index 000000000000..bd3e89433361
--- /dev/null
+++ b/misc/dist/tvos_xcode/libgodot.tvos.debug.xcframework/tvos-arm64/empty
@@ -0,0 +1 @@
+Dummy file to make dylibs folder exported
diff --git a/misc/dist/tvos_xcode/libgodot.tvos.debug.xcframework/tvos-arm64_x86_64-simulator/empty b/misc/dist/tvos_xcode/libgodot.tvos.debug.xcframework/tvos-arm64_x86_64-simulator/empty
new file mode 100644
index 000000000000..bd3e89433361
--- /dev/null
+++ b/misc/dist/tvos_xcode/libgodot.tvos.debug.xcframework/tvos-arm64_x86_64-simulator/empty
@@ -0,0 +1 @@
+Dummy file to make dylibs folder exported
diff --git a/misc/dist/tvos_xcode/libgodot.tvos.release.xcframework/Info.plist b/misc/dist/tvos_xcode/libgodot.tvos.release.xcframework/Info.plist
new file mode 100644
index 000000000000..ea990d4c4388
--- /dev/null
+++ b/misc/dist/tvos_xcode/libgodot.tvos.release.xcframework/Info.plist
@@ -0,0 +1,40 @@
+
+
+
+
+ AvailableLibraries
+
+
+ LibraryIdentifier
+ tvos-arm64
+ LibraryPath
+ libgodot.a
+ SupportedArchitectures
+
+ arm64
+
+ SupportedPlatform
+ tvos
+
+
+ LibraryIdentifier
+ tvos-arm64_x86_64-simulator
+ LibraryPath
+ libgodot.a
+ SupportedArchitectures
+
+ arm64
+ x86_64
+
+ SupportedPlatform
+ tvos
+ SupportedPlatformVariant
+ simulator
+
+
+ CFBundlePackageType
+ XFWK
+ XCFrameworkFormatVersion
+ 1.0
+
+
diff --git a/misc/dist/tvos_xcode/libgodot.tvos.release.xcframework/tvos-arm64/empty b/misc/dist/tvos_xcode/libgodot.tvos.release.xcframework/tvos-arm64/empty
new file mode 100644
index 000000000000..bd3e89433361
--- /dev/null
+++ b/misc/dist/tvos_xcode/libgodot.tvos.release.xcframework/tvos-arm64/empty
@@ -0,0 +1 @@
+Dummy file to make dylibs folder exported
diff --git a/misc/dist/tvos_xcode/libgodot.tvos.release.xcframework/tvos-arm64_x86_64-simulator/empty b/misc/dist/tvos_xcode/libgodot.tvos.release.xcframework/tvos-arm64_x86_64-simulator/empty
new file mode 100644
index 000000000000..bd3e89433361
--- /dev/null
+++ b/misc/dist/tvos_xcode/libgodot.tvos.release.xcframework/tvos-arm64_x86_64-simulator/empty
@@ -0,0 +1 @@
+Dummy file to make dylibs folder exported
diff --git a/platform/iphone/SCsub b/platform/iphone/SCsub
index 58b574a72ff8..c2a1ef75702c 100644
--- a/platform/iphone/SCsub
+++ b/platform/iphone/SCsub
@@ -3,18 +3,24 @@
Import("env")
iphone_lib = [
+ "#platform/uikit/uikit_app_delegate.m",
+ "#platform/uikit/uikit_display_layer.mm",
+ "#platform/uikit/uikit_display_server.mm",
+ "#platform/uikit/uikit_joypad.mm",
+ "#platform/uikit/uikit_os.mm",
+ "#platform/uikit/uikit_view_controller.mm",
+ "#platform/uikit/uikit_view_renderer.mm",
+ "#platform/uikit/uikit_view.mm",
+ "#platform/uikit/uikit_vulkan_context.mm",
"godot_iphone.mm",
"os_iphone.mm",
"main.m",
"app_delegate.mm",
- "view_controller.mm",
"ios.mm",
- "vulkan_context_iphone.mm",
"display_server_iphone.mm",
- "joypad_iphone.mm",
"godot_view.mm",
- "display_layer.mm",
"godot_app_delegate.m",
+ "godot_view_controller.mm",
"godot_view_renderer.mm",
"godot_view_gesture_recognizer.mm",
"device_metrics.m",
diff --git a/platform/iphone/app_delegate.h b/platform/iphone/app_delegate.h
index 0ec1dc071b68..343f75633b31 100644
--- a/platform/iphone/app_delegate.h
+++ b/platform/iphone/app_delegate.h
@@ -30,7 +30,7 @@
#import
-@class ViewController;
+@class GodotViewController;
// FIXME: Add support for both OpenGL and Vulkan when OpenGL is implemented again,
// so it can't be done with compilation time branching.
@@ -42,6 +42,6 @@
//#endif
@property(strong, nonatomic) UIWindow *window;
-@property(strong, class, readonly, nonatomic) ViewController *viewController;
+@property(strong, class, readonly, nonatomic) GodotViewController *viewController;
@end
diff --git a/platform/iphone/app_delegate.mm b/platform/iphone/app_delegate.mm
index c5c9b5a5f98f..4bfce09f125a 100644
--- a/platform/iphone/app_delegate.mm
+++ b/platform/iphone/app_delegate.mm
@@ -33,9 +33,9 @@
#include "core/config/project_settings.h"
#include "drivers/coreaudio/audio_driver_coreaudio.h"
#import "godot_view.h"
+#import "godot_view_controller.h"
#include "main/main.h"
#include "os_iphone.h"
-#import "view_controller.h"
#import
#import
@@ -50,9 +50,9 @@
@implementation AppDelegate
-static ViewController *mainViewController = nil;
+static GodotViewController *mainViewController = nil;
-+ (ViewController *)viewController {
++ (GodotViewController *)viewController {
return mainViewController;
}
@@ -79,7 +79,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
return NO;
}
- ViewController *viewController = [[ViewController alloc] init];
+ GodotViewController *viewController = [[GodotViewController alloc] init];
viewController.godotView.useCADisplayLink = bool(GLOBAL_DEF("display.iOS/use_cadisplaylink", true)) ? YES : NO;
viewController.godotView.renderingInterval = 1.0 / kRenderingFrequency;
diff --git a/platform/iphone/detect.py b/platform/iphone/detect.py
index f442235e7c48..c2479c47018b 100644
--- a/platform/iphone/detect.py
+++ b/platform/iphone/detect.py
@@ -139,7 +139,7 @@ def configure(env):
)
env.Prepend(CPPPATH=["#platform/iphone"])
- env.Append(CPPDEFINES=["IPHONE_ENABLED", "UNIX_ENABLED", "COREAUDIO_ENABLED"])
+ env.Append(CPPDEFINES=["UIKIT_ENABLED", "IPHONE_ENABLED", "UNIX_ENABLED", "COREAUDIO_ENABLED"])
if env["vulkan"]:
env.Append(CPPDEFINES=["VULKAN_ENABLED"])
diff --git a/platform/iphone/display_server_iphone.h b/platform/iphone/display_server_iphone.h
index 7441550f6762..3962c5aafebd 100644
--- a/platform/iphone/display_server_iphone.h
+++ b/platform/iphone/display_server_iphone.h
@@ -31,6 +31,8 @@
#ifndef display_server_iphone_h
#define display_server_iphone_h
+#include "platform/uikit/uikit_display_server.h"
+
#include "core/input/input.h"
#include "servers/display_server.h"
@@ -38,8 +40,6 @@
#include "drivers/vulkan/rendering_device_vulkan.h"
#include "servers/rendering/renderer_rd/renderer_compositor_rd.h"
-#include "vulkan_context_iphone.h"
-
#import
#ifdef USE_VOLK
#include
@@ -48,142 +48,41 @@
#endif
#endif
-class DisplayServerIPhone : public DisplayServer {
+CALayer *initialize_uikit_rendering_layer(const String &p_driver);
+
+class DisplayServerIPhone : public DisplayServerUIKit {
GDCLASS(DisplayServerIPhone, DisplayServer)
_THREAD_SAFE_CLASS_
-#if defined(VULKAN_ENABLED)
- VulkanContextIPhone *context_vulkan = nullptr;
- RenderingDeviceVulkan *rendering_device_vulkan = nullptr;
-#endif
-
DisplayServer::ScreenOrientation screen_orientation;
- ObjectID window_attached_instance_id;
-
- Callable window_event_callback;
- Callable window_resize_callback;
- Callable input_event_callback;
- Callable input_text_callback;
-
int virtual_keyboard_height = 0;
- void perform_event(const Ref &p_event);
-
DisplayServerIPhone(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error);
~DisplayServerIPhone();
public:
- String rendering_driver;
-
static DisplayServerIPhone *get_singleton();
static void register_iphone_driver();
static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error);
static Vector get_rendering_drivers_func();
- // MARK: - Events
-
- virtual void process_events() override;
-
- virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
- virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
- virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
- virtual void window_set_input_text_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
- virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
-
- static void _dispatch_input_events(const Ref &p_event);
- void send_input_event(const Ref &p_event) const;
- void send_input_text(const String &p_text) const;
- void send_window_event(DisplayServer::WindowEvent p_event) const;
- void _window_callback(const Callable &p_callable, const Variant &p_arg) const;
-
- // MARK: - Input
-
- // MARK: Touches
-
- void touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_double_click);
- void touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y);
- void touches_cancelled(int p_idx);
-
- // MARK: Keyboard
-
- void key(Key p_key, bool p_pressed);
-
- // MARK: Motion
-
- void update_gravity(float p_x, float p_y, float p_z);
- void update_accelerometer(float p_x, float p_y, float p_z);
- void update_magnetometer(float p_x, float p_y, float p_z);
- void update_gyroscope(float p_x, float p_y, float p_z);
-
// MARK: -
virtual bool has_feature(Feature p_feature) const override;
virtual String get_name() const override;
- virtual int get_screen_count() const override;
- virtual Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
- virtual float screen_get_scale(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
- virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
-
- virtual Vector get_window_list() const override;
-
- virtual WindowID
- get_window_at_screen_position(const Point2i &p_position) const override;
virtual int64_t window_get_native_handle(HandleType p_handle_type, WindowID p_window = MAIN_WINDOW_ID) const override;
- virtual void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID) override;
- virtual ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const override;
-
- virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) override;
-
- virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const override;
- virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override;
-
- virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const override;
- virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) override;
-
- virtual void window_set_transient(WindowID p_window, WindowID p_parent) override;
-
- virtual void window_set_max_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
- virtual Size2i window_get_max_size(WindowID p_window = MAIN_WINDOW_ID) const override;
-
- virtual void window_set_min_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
- virtual Size2i window_get_min_size(WindowID p_window = MAIN_WINDOW_ID) const override;
-
- virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
- virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const override;
- virtual Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const override;
-
- virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID) override;
- virtual WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const override;
-
- virtual bool window_is_maximize_allowed(WindowID p_window = MAIN_WINDOW_ID) const override;
-
- virtual void window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window = MAIN_WINDOW_ID) override;
- virtual bool window_get_flag(WindowFlags p_flag, WindowID p_window = MAIN_WINDOW_ID) const override;
-
- virtual void window_request_attention(WindowID p_window = MAIN_WINDOW_ID) override;
- virtual void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID) override;
-
- virtual float screen_get_max_scale() const override;
-
virtual void screen_set_orientation(DisplayServer::ScreenOrientation p_orientation, int p_screen) override;
virtual DisplayServer::ScreenOrientation screen_get_orientation(int p_screen) const override;
- virtual bool window_can_draw(WindowID p_window = MAIN_WINDOW_ID) const override;
-
- virtual bool can_any_window_draw() const override;
-
- virtual void window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window = MAIN_WINDOW_ID) override;
- virtual DisplayServer::VSyncMode window_get_vsync_mode(WindowID p_vsync_mode) const override;
-
virtual bool screen_is_touchscreen(int p_screen) const override;
virtual void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, bool p_multiline, int p_max_length, int p_cursor_start, int p_cursor_end) override;
@@ -194,11 +93,6 @@ class DisplayServerIPhone : public DisplayServer {
virtual void clipboard_set(const String &p_text) override;
virtual String clipboard_get() const override;
-
- virtual void screen_set_keep_on(bool p_enable) override;
- virtual bool screen_is_kept_on() const override;
-
- void resize_window(CGSize size);
};
#endif /* display_server_iphone_h */
diff --git a/platform/iphone/display_server_iphone.mm b/platform/iphone/display_server_iphone.mm
index a0f8daf5a021..9f03429e0ce4 100644
--- a/platform/iphone/display_server_iphone.mm
+++ b/platform/iphone/display_server_iphone.mm
@@ -35,117 +35,28 @@
#include "core/io/file_access_pack.h"
#import "device_metrics.h"
#import "godot_view.h"
+#import "godot_view_controller.h"
#include "ios.h"
#import "keyboard_input_view.h"
#include "os_iphone.h"
-#import "view_controller.h"
#import
#import
-static const float kDisplayServerIPhoneAcceleration = 1;
+CALayer *initialize_uikit_rendering_layer(const String &p_driver) {
+ NSString *driverName = [NSString stringWithUTF8String:p_driver.utf8().get_data()];
+ return [AppDelegate.viewController.godotView initializeRenderingForDriver:driverName];
+}
DisplayServerIPhone *DisplayServerIPhone::get_singleton() {
return (DisplayServerIPhone *)DisplayServer::get_singleton();
}
-DisplayServerIPhone::DisplayServerIPhone(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
- rendering_driver = p_rendering_driver;
-
-#if defined(GLES3_ENABLED)
- // FIXME: Add support for both OpenGL and Vulkan when OpenGL is implemented
- // again,
- // Note that we should be checking "opengl3" as the driver, might never enable this seeing OpenGL is deprecated on iOS
- // We are hardcoding the rendering_driver to "vulkan" down below
-
- if (rendering_driver == "opengl_es") {
- bool gl_initialization_error = false;
-
- // FIXME: Add Vulkan support via MoltenVK. Add fallback code back?
-
- if (RasterizerGLES3::is_viable() == OK) {
- RasterizerGLES3::register_config();
- RasterizerGLES3::make_current();
- } else {
- gl_initialization_error = true;
- }
-
- if (gl_initialization_error) {
- OS::get_singleton()->alert("Your device does not support any of the supported OpenGL versions.", "Unable to initialize video driver");
- // return ERR_UNAVAILABLE;
- }
-
- // rendering_server = memnew(RenderingServerDefault);
- // // FIXME: Reimplement threaded rendering
- // if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) {
- // rendering_server = memnew(RenderingServerWrapMT(rendering_server,
- // false));
- // }
- // rendering_server->init();
- // rendering_server->cursor_set_visible(false, 0);
-
- // reset this to what it should be, it will have been set to 0 after
- // rendering_server->init() is called
- // RasterizerStorageGLES3system_fbo = gl_view_base_fb;
- }
-#endif
-
-#if defined(VULKAN_ENABLED)
- rendering_driver = "vulkan";
-
- context_vulkan = nullptr;
- rendering_device_vulkan = nullptr;
-
- if (rendering_driver == "vulkan") {
- context_vulkan = memnew(VulkanContextIPhone);
- if (context_vulkan->initialize() != OK) {
- memdelete(context_vulkan);
- context_vulkan = nullptr;
- ERR_FAIL_MSG("Failed to initialize Vulkan context");
- }
-
- CALayer *layer = [AppDelegate.viewController.godotView initializeRenderingForDriver:@"vulkan"];
-
- if (!layer) {
- ERR_FAIL_MSG("Failed to create iOS rendering layer.");
- }
-
- Size2i size = Size2i(layer.bounds.size.width, layer.bounds.size.height) * screen_get_max_scale();
- if (context_vulkan->window_create(MAIN_WINDOW_ID, p_vsync_mode, layer, size.width, size.height) != OK) {
- memdelete(context_vulkan);
- context_vulkan = nullptr;
- ERR_FAIL_MSG("Failed to create Vulkan window.");
- }
-
- rendering_device_vulkan = memnew(RenderingDeviceVulkan);
- rendering_device_vulkan->initialize(context_vulkan);
-
- RendererCompositorRD::make_current();
- }
-#endif
-
- bool keep_screen_on = bool(GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true));
- screen_set_keep_on(keep_screen_on);
-
- Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events);
-
- r_error = OK;
+DisplayServerIPhone::DisplayServerIPhone(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) :
+ DisplayServerUIKit(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_resolution, r_error) {
}
DisplayServerIPhone::~DisplayServerIPhone() {
-#if defined(VULKAN_ENABLED)
- if (rendering_device_vulkan) {
- rendering_device_vulkan->finalize();
- memdelete(rendering_device_vulkan);
- rendering_device_vulkan = nullptr;
- }
-
- if (context_vulkan) {
- context_vulkan->window_destroy(MAIN_WINDOW_ID);
- memdelete(context_vulkan);
- context_vulkan = nullptr;
- }
-#endif
}
DisplayServer *DisplayServerIPhone::create_func(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
@@ -169,127 +80,6 @@
register_create_function("iphone", create_func, get_rendering_drivers_func);
}
-// MARK: Events
-
-void DisplayServerIPhone::window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window) {
- window_resize_callback = p_callable;
-}
-
-void DisplayServerIPhone::window_set_window_event_callback(const Callable &p_callable, WindowID p_window) {
- window_event_callback = p_callable;
-}
-void DisplayServerIPhone::window_set_input_event_callback(const Callable &p_callable, WindowID p_window) {
- input_event_callback = p_callable;
-}
-
-void DisplayServerIPhone::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) {
- input_text_callback = p_callable;
-}
-
-void DisplayServerIPhone::window_set_drop_files_callback(const Callable &p_callable, WindowID p_window) {
- // Probably not supported for iOS
-}
-
-void DisplayServerIPhone::process_events() {
-}
-
-void DisplayServerIPhone::_dispatch_input_events(const Ref &p_event) {
- DisplayServerIPhone::get_singleton()->send_input_event(p_event);
-}
-
-void DisplayServerIPhone::send_input_event(const Ref &p_event) const {
- _window_callback(input_event_callback, p_event);
-}
-
-void DisplayServerIPhone::send_input_text(const String &p_text) const {
- _window_callback(input_text_callback, p_text);
-}
-
-void DisplayServerIPhone::send_window_event(DisplayServer::WindowEvent p_event) const {
- _window_callback(window_event_callback, int(p_event));
-}
-
-void DisplayServerIPhone::_window_callback(const Callable &p_callable, const Variant &p_arg) const {
- if (!p_callable.is_null()) {
- const Variant *argp = &p_arg;
- Variant ret;
- Callable::CallError ce;
- p_callable.call((const Variant **)&argp, 1, ret, ce);
- }
-}
-
-// MARK: - Input
-
-// MARK: Touches
-
-void DisplayServerIPhone::touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_double_click) {
- if (!GLOBAL_DEF("debug/disable_touch", false)) {
- Ref ev;
- ev.instantiate();
-
- ev->set_index(p_idx);
- ev->set_pressed(p_pressed);
- ev->set_position(Vector2(p_x, p_y));
- perform_event(ev);
- }
-}
-
-void DisplayServerIPhone::touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y) {
- if (!GLOBAL_DEF("debug/disable_touch", false)) {
- Ref ev;
- ev.instantiate();
- ev->set_index(p_idx);
- ev->set_position(Vector2(p_x, p_y));
- ev->set_relative(Vector2(p_x - p_prev_x, p_y - p_prev_y));
- perform_event(ev);
- }
-}
-
-void DisplayServerIPhone::perform_event(const Ref &p_event) {
- Input::get_singleton()->parse_input_event(p_event);
-}
-
-void DisplayServerIPhone::touches_cancelled(int p_idx) {
- touch_press(p_idx, -1, -1, false, false);
-}
-
-// MARK: Keyboard
-
-void DisplayServerIPhone::key(Key p_key, bool p_pressed) {
- Ref ev;
- ev.instantiate();
- ev->set_echo(false);
- ev->set_pressed(p_pressed);
- ev->set_keycode(p_key);
- ev->set_physical_keycode(p_key);
- ev->set_unicode((char32_t)p_key);
- perform_event(ev);
-}
-
-// MARK: Motion
-
-void DisplayServerIPhone::update_gravity(float p_x, float p_y, float p_z) {
- Input::get_singleton()->set_gravity(Vector3(p_x, p_y, p_z));
-}
-
-void DisplayServerIPhone::update_accelerometer(float p_x, float p_y, float p_z) {
- // Found out the Z should not be negated! Pass as is!
- Vector3 v_accelerometer = Vector3(
- p_x / kDisplayServerIPhoneAcceleration,
- p_y / kDisplayServerIPhoneAcceleration,
- p_z / kDisplayServerIPhoneAcceleration);
-
- Input::get_singleton()->set_accelerometer(v_accelerometer);
-}
-
-void DisplayServerIPhone::update_magnetometer(float p_x, float p_y, float p_z) {
- Input::get_singleton()->set_magnetometer(Vector3(p_x, p_y, p_z));
-}
-
-void DisplayServerIPhone::update_gyroscope(float p_x, float p_y, float p_z) {
- Input::get_singleton()->set_gyroscope(Vector3(p_x, p_y, p_z));
-}
-
// MARK: -
bool DisplayServerIPhone::has_feature(Feature p_feature) const {
@@ -320,14 +110,6 @@
return "iPhone";
}
-int DisplayServerIPhone::get_screen_count() const {
- return 1;
-}
-
-Point2i DisplayServerIPhone::screen_get_position(int p_screen) const {
- return Size2i();
-}
-
Size2i DisplayServerIPhone::screen_get_size(int p_screen) const {
CALayer *layer = AppDelegate.viewController.godotView.renderingLayer;
@@ -394,24 +176,6 @@
}
}
-float DisplayServerIPhone::screen_get_refresh_rate(int p_screen) const {
- return [UIScreen mainScreen].maximumFramesPerSecond;
-}
-
-float DisplayServerIPhone::screen_get_scale(int p_screen) const {
- return [UIScreen mainScreen].nativeScale;
-}
-
-Vector DisplayServerIPhone::get_window_list() const {
- Vector list;
- list.push_back(MAIN_WINDOW_ID);
- return list;
-}
-
-DisplayServer::WindowID DisplayServerIPhone::get_window_at_screen_position(const Point2i &p_position) const {
- return MAIN_WINDOW_ID;
-}
-
int64_t DisplayServerIPhone::window_get_native_handle(HandleType p_handle_type, WindowID p_window) const {
ERR_FAIL_COND_V(p_window != MAIN_WINDOW_ID, 0);
switch (p_handle_type) {
@@ -430,99 +194,6 @@
}
}
-void DisplayServerIPhone::window_attach_instance_id(ObjectID p_instance, WindowID p_window) {
- window_attached_instance_id = p_instance;
-}
-
-ObjectID DisplayServerIPhone::window_get_attached_instance_id(WindowID p_window) const {
- return window_attached_instance_id;
-}
-
-void DisplayServerIPhone::window_set_title(const String &p_title, WindowID p_window) {
- // Probably not supported for iOS
-}
-
-int DisplayServerIPhone::window_get_current_screen(WindowID p_window) const {
- return SCREEN_OF_MAIN_WINDOW;
-}
-
-void DisplayServerIPhone::window_set_current_screen(int p_screen, WindowID p_window) {
- // Probably not supported for iOS
-}
-
-Point2i DisplayServerIPhone::window_get_position(WindowID p_window) const {
- return Point2i();
-}
-
-void DisplayServerIPhone::window_set_position(const Point2i &p_position, WindowID p_window) {
- // Probably not supported for single window iOS app
-}
-
-void DisplayServerIPhone::window_set_transient(WindowID p_window, WindowID p_parent) {
- // Probably not supported for iOS
-}
-
-void DisplayServerIPhone::window_set_max_size(const Size2i p_size, WindowID p_window) {
- // Probably not supported for iOS
-}
-
-Size2i DisplayServerIPhone::window_get_max_size(WindowID p_window) const {
- return Size2i();
-}
-
-void DisplayServerIPhone::window_set_min_size(const Size2i p_size, WindowID p_window) {
- // Probably not supported for iOS
-}
-
-Size2i DisplayServerIPhone::window_get_min_size(WindowID p_window) const {
- return Size2i();
-}
-
-void DisplayServerIPhone::window_set_size(const Size2i p_size, WindowID p_window) {
- // Probably not supported for iOS
-}
-
-Size2i DisplayServerIPhone::window_get_size(WindowID p_window) const {
- CGRect screenBounds = [UIScreen mainScreen].bounds;
- return Size2i(screenBounds.size.width, screenBounds.size.height) * screen_get_max_scale();
-}
-
-Size2i DisplayServerIPhone::window_get_real_size(WindowID p_window) const {
- return window_get_size(p_window);
-}
-
-void DisplayServerIPhone::window_set_mode(WindowMode p_mode, WindowID p_window) {
- // Probably not supported for iOS
-}
-
-DisplayServer::WindowMode DisplayServerIPhone::window_get_mode(WindowID p_window) const {
- return WindowMode::WINDOW_MODE_FULLSCREEN;
-}
-
-bool DisplayServerIPhone::window_is_maximize_allowed(WindowID p_window) const {
- return false;
-}
-
-void DisplayServerIPhone::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) {
- // Probably not supported for iOS
-}
-
-bool DisplayServerIPhone::window_get_flag(WindowFlags p_flag, WindowID p_window) const {
- return false;
-}
-
-void DisplayServerIPhone::window_request_attention(WindowID p_window) {
- // Probably not supported for iOS
-}
-
-void DisplayServerIPhone::window_move_to_foreground(WindowID p_window) {
- // Probably not supported for iOS
-}
-
-float DisplayServerIPhone::screen_get_max_scale() const {
- return screen_get_scale(SCREEN_OF_MAIN_WINDOW);
-}
-
void DisplayServerIPhone::screen_set_orientation(DisplayServer::ScreenOrientation p_orientation, int p_screen) {
screen_orientation = p_orientation;
}
@@ -531,14 +202,6 @@
return screen_orientation;
}
-bool DisplayServerIPhone::window_can_draw(WindowID p_window) const {
- return true;
-}
-
-bool DisplayServerIPhone::can_any_window_draw() const {
- return true;
-}
-
bool DisplayServerIPhone::screen_is_touchscreen(int p_screen) const {
return true;
}
@@ -574,40 +237,3 @@
return String::utf8([text UTF8String]);
}
-
-void DisplayServerIPhone::screen_set_keep_on(bool p_enable) {
- [UIApplication sharedApplication].idleTimerDisabled = p_enable;
-}
-
-bool DisplayServerIPhone::screen_is_kept_on() const {
- return [UIApplication sharedApplication].idleTimerDisabled;
-}
-
-void DisplayServerIPhone::resize_window(CGSize viewSize) {
- Size2i size = Size2i(viewSize.width, viewSize.height) * screen_get_max_scale();
-
-#if defined(VULKAN_ENABLED)
- if (context_vulkan) {
- context_vulkan->window_resize(MAIN_WINDOW_ID, size.x, size.y);
- }
-#endif
-
- Variant resize_rect = Rect2i(Point2i(), size);
- _window_callback(window_resize_callback, resize_rect);
-}
-
-void DisplayServerIPhone::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window) {
- _THREAD_SAFE_METHOD_
-#if defined(VULKAN_ENABLED)
- context_vulkan->set_vsync_mode(p_window, p_vsync_mode);
-#endif
-}
-
-DisplayServer::VSyncMode DisplayServerIPhone::window_get_vsync_mode(WindowID p_window) const {
- _THREAD_SAFE_METHOD_
-#if defined(VULKAN_ENABLED)
- return context_vulkan->get_vsync_mode(p_window);
-#else
- return DisplayServer::VSYNC_ENABLED;
-#endif
-}
diff --git a/platform/iphone/godot_app_delegate.h b/platform/iphone/godot_app_delegate.h
index 703a906bda96..f474b5f50f57 100644
--- a/platform/iphone/godot_app_delegate.h
+++ b/platform/iphone/godot_app_delegate.h
@@ -28,14 +28,8 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#import
+#import "platform/uikit/uikit_app_delegate.h"
-typedef NSObject ApplicationDelegateService;
-
-@interface GodotApplicalitionDelegate : NSObject
-
-@property(class, readonly, strong) NSArray *services;
-
-+ (void)addService:(ApplicationDelegateService *)service;
+@interface GodotApplicalitionDelegate : UIKitApplicalitionDelegate
@end
diff --git a/platform/iphone/godot_app_delegate.m b/platform/iphone/godot_app_delegate.m
index 84347f9a3073..b2c4859981a2 100644
--- a/platform/iphone/godot_app_delegate.m
+++ b/platform/iphone/godot_app_delegate.m
@@ -38,430 +38,8 @@ @interface GodotApplicalitionDelegate ()
@implementation GodotApplicalitionDelegate
-static NSMutableArray *services = nil;
-
-+ (NSArray *)services {
- return services;
-}
-
+ (void)load {
- services = [NSMutableArray new];
- [services addObject:[AppDelegate new]];
-}
-
-+ (void)addService:(ApplicationDelegateService *)service {
- if (!services || !service) {
- return;
- }
- [services addObject:service];
-}
-
-// UIApplicationDelegate documentation can be found here: https://developer.apple.com/documentation/uikit/uiapplicationdelegate
-
-// MARK: Window
-
-- (UIWindow *)window {
- UIWindow *result = nil;
-
- for (ApplicationDelegateService *service in services) {
- if (![service respondsToSelector:_cmd]) {
- continue;
- }
-
- UIWindow *value = [service window];
-
- if (value) {
- result = value;
- }
- }
-
- return result;
-}
-
-// MARK: Initializing
-
-- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
- BOOL result = NO;
-
- for (ApplicationDelegateService *service in services) {
- if (![service respondsToSelector:_cmd]) {
- continue;
- }
-
- if ([service application:application willFinishLaunchingWithOptions:launchOptions]) {
- result = YES;
- }
- }
-
- return result;
-}
-
-- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
- BOOL result = NO;
-
- for (ApplicationDelegateService *service in services) {
- if (![service respondsToSelector:_cmd]) {
- continue;
- }
-
- if ([service application:application didFinishLaunchingWithOptions:launchOptions]) {
- result = YES;
- }
- }
-
- return result;
-}
-
-/* Can be handled by Info.plist. Not yet supported by Godot.
-
-// MARK: Scene
-
-- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options {}
-
-- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet *)sceneSessions {}
-
-*/
-
-// MARK: Life-Cycle
-
-- (void)applicationDidBecomeActive:(UIApplication *)application {
- for (ApplicationDelegateService *service in services) {
- if (![service respondsToSelector:_cmd]) {
- continue;
- }
-
- [service applicationDidBecomeActive:application];
- }
-}
-
-- (void)applicationWillResignActive:(UIApplication *)application {
- for (ApplicationDelegateService *service in services) {
- if (![service respondsToSelector:_cmd]) {
- continue;
- }
-
- [service applicationWillResignActive:application];
- }
-}
-
-- (void)applicationDidEnterBackground:(UIApplication *)application {
- for (ApplicationDelegateService *service in services) {
- if (![service respondsToSelector:_cmd]) {
- continue;
- }
-
- [service applicationDidEnterBackground:application];
- }
-}
-
-- (void)applicationWillEnterForeground:(UIApplication *)application {
- for (ApplicationDelegateService *service in services) {
- if (![service respondsToSelector:_cmd]) {
- continue;
- }
-
- [service applicationWillEnterForeground:application];
- }
-}
-
-- (void)applicationWillTerminate:(UIApplication *)application {
- for (ApplicationDelegateService *service in services) {
- if (![service respondsToSelector:_cmd]) {
- continue;
- }
-
- [service applicationWillTerminate:application];
- }
-}
-
-// MARK: Environment Changes
-
-- (void)applicationProtectedDataDidBecomeAvailable:(UIApplication *)application {
- for (ApplicationDelegateService *service in services) {
- if (![service respondsToSelector:_cmd]) {
- continue;
- }
-
- [service applicationProtectedDataDidBecomeAvailable:application];
- }
-}
-
-- (void)applicationProtectedDataWillBecomeUnavailable:(UIApplication *)application {
- for (ApplicationDelegateService *service in services) {
- if (![service respondsToSelector:_cmd]) {
- continue;
- }
-
- [service applicationProtectedDataWillBecomeUnavailable:application];
- }
-}
-
-- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
- for (ApplicationDelegateService *service in services) {
- if (![service respondsToSelector:_cmd]) {
- continue;
- }
-
- [service applicationDidReceiveMemoryWarning:application];
- }
-}
-
-- (void)applicationSignificantTimeChange:(UIApplication *)application {
- for (ApplicationDelegateService *service in services) {
- if (![service respondsToSelector:_cmd]) {
- continue;
- }
-
- [service applicationSignificantTimeChange:application];
- }
-}
-
-// MARK: App State Restoration
-
-- (BOOL)application:(UIApplication *)application shouldSaveSecureApplicationState:(NSCoder *)coder API_AVAILABLE(ios(13.2)) {
- BOOL result = NO;
-
- for (ApplicationDelegateService *service in services) {
- if (![service respondsToSelector:_cmd]) {
- continue;
- }
-
- if ([service application:application shouldSaveSecureApplicationState:coder]) {
- result = YES;
- }
- }
-
- return result;
-}
-
-- (BOOL)application:(UIApplication *)application shouldRestoreSecureApplicationState:(NSCoder *)coder API_AVAILABLE(ios(13.2)) {
- BOOL result = NO;
-
- for (ApplicationDelegateService *service in services) {
- if (![service respondsToSelector:_cmd]) {
- continue;
- }
-
- if ([service application:application shouldRestoreSecureApplicationState:coder]) {
- result = YES;
- }
- }
-
- return result;
-}
-
-- (UIViewController *)application:(UIApplication *)application viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder {
- for (ApplicationDelegateService *service in services) {
- if (![service respondsToSelector:_cmd]) {
- continue;
- }
-
- UIViewController *controller = [service application:application viewControllerWithRestorationIdentifierPath:identifierComponents coder:coder];
-
- if (controller) {
- return controller;
- }
- }
-
- return nil;
-}
-
-- (void)application:(UIApplication *)application willEncodeRestorableStateWithCoder:(NSCoder *)coder {
- for (ApplicationDelegateService *service in services) {
- if (![service respondsToSelector:_cmd]) {
- continue;
- }
-
- [service application:application willEncodeRestorableStateWithCoder:coder];
- }
-}
-
-- (void)application:(UIApplication *)application didDecodeRestorableStateWithCoder:(NSCoder *)coder {
- for (ApplicationDelegateService *service in services) {
- if (![service respondsToSelector:_cmd]) {
- continue;
- }
-
- [service application:application didDecodeRestorableStateWithCoder:coder];
- }
-}
-
-// MARK: Download Data in Background
-
-- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler {
- for (ApplicationDelegateService *service in services) {
- if (![service respondsToSelector:_cmd]) {
- continue;
- }
-
- [service application:application handleEventsForBackgroundURLSession:identifier completionHandler:completionHandler];
- }
-
- completionHandler();
-}
-
-// MARK: Remote Notification
-
-// Moved to the iOS Plugin
-
-// MARK: User Activity and Handling Quick Actions
-
-- (BOOL)application:(UIApplication *)application willContinueUserActivityWithType:(NSString *)userActivityType {
- BOOL result = NO;
-
- for (ApplicationDelegateService *service in services) {
- if (![service respondsToSelector:_cmd]) {
- continue;
- }
-
- if ([service application:application willContinueUserActivityWithType:userActivityType]) {
- result = YES;
- }
- }
-
- return result;
-}
-
-- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray> *restorableObjects))restorationHandler {
- BOOL result = NO;
-
- for (ApplicationDelegateService *service in services) {
- if (![service respondsToSelector:_cmd]) {
- continue;
- }
-
- if ([service application:application continueUserActivity:userActivity restorationHandler:restorationHandler]) {
- result = YES;
- }
- }
-
- return result;
-}
-
-- (void)application:(UIApplication *)application didUpdateUserActivity:(NSUserActivity *)userActivity {
- for (ApplicationDelegateService *service in services) {
- if (![service respondsToSelector:_cmd]) {
- continue;
- }
-
- [service application:application didUpdateUserActivity:userActivity];
- }
-}
-
-- (void)application:(UIApplication *)application didFailToContinueUserActivityWithType:(NSString *)userActivityType error:(NSError *)error {
- for (ApplicationDelegateService *service in services) {
- if (![service respondsToSelector:_cmd]) {
- continue;
- }
-
- [service application:application didFailToContinueUserActivityWithType:userActivityType error:error];
- }
-}
-
-- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL succeeded))completionHandler {
- for (ApplicationDelegateService *service in services) {
- if (![service respondsToSelector:_cmd]) {
- continue;
- }
-
- [service application:application performActionForShortcutItem:shortcutItem completionHandler:completionHandler];
- }
-}
-
-// MARK: WatchKit
-
-- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *replyInfo))reply {
- for (ApplicationDelegateService *service in services) {
- if (![service respondsToSelector:_cmd]) {
- continue;
- }
-
- [service application:application handleWatchKitExtensionRequest:userInfo reply:reply];
- }
-}
-
-// MARK: HealthKit
-
-- (void)applicationShouldRequestHealthAuthorization:(UIApplication *)application {
- for (ApplicationDelegateService *service in services) {
- if (![service respondsToSelector:_cmd]) {
- continue;
- }
-
- [service applicationShouldRequestHealthAuthorization:application];
- }
+ [self addService:[AppDelegate new]];
}
-// MARK: Opening an URL
-
-- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options {
- for (ApplicationDelegateService *service in services) {
- if (![service respondsToSelector:_cmd]) {
- continue;
- }
-
- if ([service application:app openURL:url options:options]) {
- return YES;
- }
- }
-
- return NO;
-}
-
-// MARK: Disallowing Specified App Extension Types
-
-- (BOOL)application:(UIApplication *)application shouldAllowExtensionPointIdentifier:(UIApplicationExtensionPointIdentifier)extensionPointIdentifier {
- BOOL result = NO;
-
- for (ApplicationDelegateService *service in services) {
- if (![service respondsToSelector:_cmd]) {
- continue;
- }
-
- if ([service application:application shouldAllowExtensionPointIdentifier:extensionPointIdentifier]) {
- result = YES;
- }
- }
-
- return result;
-}
-
-// MARK: SiriKit
-
-- (id)application:(UIApplication *)application handlerForIntent:(INIntent *)intent API_AVAILABLE(ios(14.0)) {
- for (ApplicationDelegateService *service in services) {
- if (![service respondsToSelector:_cmd]) {
- continue;
- }
-
- id result = [service application:application handlerForIntent:intent];
-
- if (result) {
- return result;
- }
- }
-
- return nil;
-}
-
-// MARK: CloudKit
-
-- (void)application:(UIApplication *)application userDidAcceptCloudKitShareWithMetadata:(CKShareMetadata *)cloudKitShareMetadata {
- for (ApplicationDelegateService *service in services) {
- if (![service respondsToSelector:_cmd]) {
- continue;
- }
-
- [service application:application userDidAcceptCloudKitShareWithMetadata:cloudKitShareMetadata];
- }
-}
-
-/* Handled By Info.plist file for now
-
-// MARK: Interface Geometry
-
-- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {}
-
-*/
-
@end
diff --git a/platform/iphone/godot_view.h b/platform/iphone/godot_view.h
index fcb97fa63a80..497ebd8c9be6 100644
--- a/platform/iphone/godot_view.h
+++ b/platform/iphone/godot_view.h
@@ -28,36 +28,9 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#import
+#import "platform/uikit/uikit_view.h"
-class String;
-
-@class GodotView;
-@protocol DisplayLayer;
-@protocol GodotViewRendererProtocol;
-
-@protocol GodotViewDelegate
-
-- (BOOL)godotViewFinishedSetup:(GodotView *)view;
-
-@end
-
-@interface GodotView : UIView
-
-@property(assign, nonatomic) id renderer;
-@property(assign, nonatomic) id delegate;
-
-@property(assign, readonly, nonatomic) BOOL isActive;
-
-@property(assign, nonatomic) BOOL useCADisplayLink;
-@property(strong, readonly, nonatomic) CALayer *renderingLayer;
-@property(assign, readonly, nonatomic) BOOL canRender;
-
-@property(assign, nonatomic) NSTimeInterval renderingInterval;
-
-- (CALayer *)initializeRenderingForDriver:(NSString *)driverName;
-- (void)stopRendering;
-- (void)startRendering;
+@interface GodotView : UIKitView
- (void)godotTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)godotTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
diff --git a/platform/iphone/godot_view.mm b/platform/iphone/godot_view.mm
index e48dd2e5079b..30effec97b90 100644
--- a/platform/iphone/godot_view.mm
+++ b/platform/iphone/godot_view.mm
@@ -32,7 +32,6 @@
#include "core/os/keyboard.h"
#include "core/string/ustring.h"
-#import "display_layer.h"
#include "display_server_iphone.h"
#import "godot_view_gesture_recognizer.h"
#import "godot_view_renderer.h"
@@ -46,17 +45,6 @@ @interface GodotView () {
UITouch *godot_touches[max_touches];
}
-@property(assign, nonatomic) BOOL isActive;
-
-// CADisplayLink available on 3.1+ synchronizes the animation timer & drawing with the refresh rate of the display, only supports animation intervals of 1/60 1/30 & 1/15
-@property(strong, nonatomic) CADisplayLink *displayLink;
-
-// An animation timer that, when animation is started, will periodically call -drawView at the given rate.
-// Only used if CADisplayLink is not
-@property(strong, nonatomic) NSTimer *animationTimer;
-
-@property(strong, nonatomic) CALayer *renderingLayer;
-
@property(strong, nonatomic) CMMotionManager *motionManager;
@property(strong, nonatomic) GodotViewGestureRecognizer *delayGestureRecognizer;
@@ -65,39 +53,6 @@ @interface GodotView () {
@implementation GodotView
-- (CALayer *)initializeRenderingForDriver:(NSString *)driverName {
- if (self.renderingLayer) {
- return self.renderingLayer;
- }
-
- CALayer *layer;
-
- if ([driverName isEqualToString:@"vulkan"]) {
- layer = [GodotMetalLayer layer];
- } else if ([driverName isEqualToString:@"opengl_es"]) {
- if (@available(iOS 13, *)) {
- NSLog(@"OpenGL ES is deprecated on iOS 13");
- }
-#if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR
- return nil;
-#else
- layer = [GodotOpenGLLayer layer];
-#endif
- } else {
- return nil;
- }
-
- layer.frame = self.bounds;
- layer.contentsScale = self.contentScaleFactor;
-
- [self.layer addSublayer:layer];
- self.renderingLayer = layer;
-
- [layer initializeDisplayLayer];
-
- return self.renderingLayer;
-}
-
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
@@ -119,39 +74,17 @@ - (instancetype)initWithFrame:(CGRect)frame {
}
- (void)dealloc {
- [self stopRendering];
-
- self.renderer = nil;
- self.delegate = nil;
-
- if (self.renderingLayer) {
- [self.renderingLayer removeFromSuperlayer];
- self.renderingLayer = nil;
- }
-
if (self.motionManager) {
[self.motionManager stopDeviceMotionUpdates];
self.motionManager = nil;
}
- if (self.displayLink) {
- [self.displayLink invalidate];
- self.displayLink = nil;
- }
-
- if (self.animationTimer) {
- [self.animationTimer invalidate];
- self.animationTimer = nil;
- }
-
if (self.delayGestureRecognizer) {
self.delayGestureRecognizer = nil;
}
}
- (void)godot_commonInit {
- self.contentScaleFactor = [UIScreen mainScreen].nativeScale;
-
[self initTouches];
self.multipleTouchEnabled = YES;
@@ -174,119 +107,15 @@ - (void)godot_commonInit {
}
- (void)stopRendering {
- if (!self.isActive) {
- return;
- }
-
- self.isActive = NO;
-
- printf("******** stop animation!\n");
-
- if (self.useCADisplayLink) {
- [self.displayLink invalidate];
- self.displayLink = nil;
- } else {
- [self.animationTimer invalidate];
- self.animationTimer = nil;
- }
+ [super stopRendering];
[self clearTouches];
}
-- (void)startRendering {
- if (self.isActive) {
- return;
- }
-
- self.isActive = YES;
-
- printf("start animation!\n");
-
- if (self.useCADisplayLink) {
- self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawView)];
-
- // Approximate frame rate
- // assumes device refreshes at 60 fps
- int displayFPS = (NSInteger)(1.0 / self.renderingInterval);
-
- self.displayLink.preferredFramesPerSecond = displayFPS;
-
- // Setup DisplayLink in main thread
- [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
- } else {
- self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:self.renderingInterval target:self selector:@selector(drawView) userInfo:nil repeats:YES];
- }
-}
-
- (void)drawView {
- if (!self.isActive) {
- printf("draw view not active!\n");
- return;
- }
-
- if (self.useCADisplayLink) {
- // Pause the CADisplayLink to avoid recursion
- [self.displayLink setPaused:YES];
-
- // Process all input events
- while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, TRUE) == kCFRunLoopRunHandledSource) {
- // Continue.
- }
-
- // We are good to go, resume the CADisplayLink
- [self.displayLink setPaused:NO];
- }
-
- [self.renderingLayer renderDisplayLayer];
-
- if (!self.renderer) {
- return;
- }
-
- if ([self.renderer setupView:self]) {
- return;
- }
-
- if (self.delegate) {
- BOOL delegateFinishedSetup = [self.delegate godotViewFinishedSetup:self];
-
- if (!delegateFinishedSetup) {
- return;
- }
- }
+ [super drawView];
[self handleMotion];
- [self.renderer renderOnView:self];
-}
-
-- (BOOL)canRender {
- if (self.useCADisplayLink) {
- return self.displayLink != nil;
- } else {
- return self.animationTimer != nil;
- }
-}
-
-- (void)setRenderingInterval:(NSTimeInterval)renderingInterval {
- _renderingInterval = renderingInterval;
-
- if (self.canRender) {
- [self stopRendering];
- [self startRendering];
- }
-}
-
-- (void)layoutSubviews {
- if (self.renderingLayer) {
- self.renderingLayer.frame = self.bounds;
- [self.renderingLayer layoutDisplayLayer];
-
- if (DisplayServerIPhone::get_singleton()) {
- DisplayServerIPhone::get_singleton()->resize_window(self.bounds.size);
- }
- }
-
- [super layoutSubviews];
}
// MARK: - Input
diff --git a/platform/iphone/view_controller.h b/platform/iphone/godot_view_controller.h
similarity index 93%
rename from platform/iphone/view_controller.h
rename to platform/iphone/godot_view_controller.h
index c8b37a4d111b..3786cf63698e 100644
--- a/platform/iphone/view_controller.h
+++ b/platform/iphone/godot_view_controller.h
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* view_controller.h */
+/* godot_view_controller.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,13 +28,13 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
+#import "platform/uikit/uikit_view_controller.h"
#import
@class GodotView;
-@class GodotNativeVideoView;
@class GodotKeyboardInputView;
-@interface ViewController : UIViewController
+@interface GodotViewController : UIKitViewController
@property(nonatomic, readonly, strong) GodotView *godotView;
@property(nonatomic, readonly, strong) GodotKeyboardInputView *keyboardView;
diff --git a/platform/iphone/view_controller.mm b/platform/iphone/godot_view_controller.mm
similarity index 84%
rename from platform/iphone/view_controller.mm
rename to platform/iphone/godot_view_controller.mm
index 4f4ef4f04630..373b4c5e7910 100644
--- a/platform/iphone/view_controller.mm
+++ b/platform/iphone/godot_view_controller.mm
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* view_controller.mm */
+/* godot_view_controller.mm */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,7 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#import "view_controller.h"
+#import "godot_view_controller.h"
#include "core/config/project_settings.h"
#include "display_server_iphone.h"
#import "godot_view.h"
@@ -39,16 +39,14 @@
#import
#import
-@interface ViewController ()
+@interface GodotViewController ()
@property(strong, nonatomic) GodotViewRenderer *renderer;
@property(strong, nonatomic) GodotKeyboardInputView *keyboardView;
-@property(strong, nonatomic) UIView *godotLoadingOverlay;
-
@end
-@implementation ViewController
+@implementation GodotViewController
- (GodotView *)godotView {
return (GodotView *)self.view;
@@ -98,7 +96,6 @@ - (void)viewDidLoad {
[super viewDidLoad];
[self observeKeyboard];
- [self displayLoadingOverlay];
if (@available(iOS 11.0, *)) {
[self setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
@@ -123,41 +120,11 @@ - (void)observeKeyboard {
object:nil];
}
-- (void)displayLoadingOverlay {
- NSBundle *bundle = [NSBundle mainBundle];
- NSString *storyboardName = @"Launch Screen";
-
- if ([bundle pathForResource:storyboardName ofType:@"storyboardc"] == nil) {
- return;
- }
-
- UIStoryboard *launchStoryboard = [UIStoryboard storyboardWithName:storyboardName bundle:bundle];
-
- UIViewController *controller = [launchStoryboard instantiateInitialViewController];
- self.godotLoadingOverlay = controller.view;
- self.godotLoadingOverlay.frame = self.view.bounds;
- self.godotLoadingOverlay.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
-
- [self.view addSubview:self.godotLoadingOverlay];
-}
-
-- (BOOL)godotViewFinishedSetup:(GodotView *)view {
- [self.godotLoadingOverlay removeFromSuperview];
- self.godotLoadingOverlay = nil;
-
- return YES;
-}
-
- (void)dealloc {
self.keyboardView = nil;
self.renderer = nil;
- if (self.godotLoadingOverlay) {
- [self.godotLoadingOverlay removeFromSuperview];
- self.godotLoadingOverlay = nil;
- }
-
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
diff --git a/platform/iphone/godot_view_renderer.h b/platform/iphone/godot_view_renderer.h
index b3ee23ae4f90..30cb80b54bc3 100644
--- a/platform/iphone/godot_view_renderer.h
+++ b/platform/iphone/godot_view_renderer.h
@@ -28,17 +28,9 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
+#import "platform/uikit/uikit_view_renderer.h"
#import
-@protocol GodotViewRendererProtocol
-
-@property(assign, readonly, nonatomic) BOOL hasFinishedSetup;
-
-- (BOOL)setupView:(UIView *)view;
-- (void)renderOnView:(UIView *)view;
-
-@end
-
-@interface GodotViewRenderer : NSObject
+@interface GodotViewRenderer : UIKitViewRenderer
@end
diff --git a/platform/iphone/godot_view_renderer.mm b/platform/iphone/godot_view_renderer.mm
index 32477ae41cd0..c1e0fd282012 100644
--- a/platform/iphone/godot_view_renderer.mm
+++ b/platform/iphone/godot_view_renderer.mm
@@ -45,66 +45,13 @@
@interface GodotViewRenderer ()
-@property(assign, nonatomic) BOOL hasFinishedProjectDataSetup;
-@property(assign, nonatomic) BOOL hasStartedMain;
-@property(assign, nonatomic) BOOL hasFinishedSetup;
-
@end
@implementation GodotViewRenderer
-- (BOOL)setupView:(UIView *)view {
- if (self.hasFinishedSetup) {
- return NO;
- }
-
- if (!OS::get_singleton()) {
- exit(0);
- }
-
- if (!self.hasFinishedProjectDataSetup) {
- [self setupProjectData];
- return YES;
- }
-
- if (!self.hasStartedMain) {
- self.hasStartedMain = YES;
- OSIPhone::get_singleton()->start();
- return YES;
- }
-
- self.hasFinishedSetup = YES;
-
- return NO;
-}
-
-- (void)setupProjectData {
- self.hasFinishedProjectDataSetup = YES;
-
- Main::setup2();
-
- // this might be necessary before here
- NSDictionary *dict = [[NSBundle mainBundle] infoDictionary];
- for (NSString *key in dict) {
- NSObject *value = [dict objectForKey:key];
- String ukey = String::utf8([key UTF8String]);
-
- // we need a NSObject to Variant conversor
-
- if ([value isKindOfClass:[NSString class]]) {
- NSString *str = (NSString *)value;
- String uval = String::utf8([str UTF8String]);
-
- ProjectSettings::get_singleton()->set("Info.plist/" + ukey, uval);
-
- } else if ([value isKindOfClass:[NSNumber class]]) {
- NSNumber *n = (NSNumber *)value;
- double dval = [n doubleValue];
-
- ProjectSettings::get_singleton()->set("Info.plist/" + ukey, dval);
- }
- // do stuff
- }
+- (BOOL)startUIKitPlatform {
+ OSIPhone::get_singleton()->start();
+ return YES;
}
- (void)renderOnView:(UIView *)view {
diff --git a/platform/iphone/ios.mm b/platform/iphone/ios.mm
index ad1ea70c1052..6a131db9e5fb 100644
--- a/platform/iphone/ios.mm
+++ b/platform/iphone/ios.mm
@@ -31,7 +31,7 @@
#include "ios.h"
#import "app_delegate.h"
-#import "view_controller.h"
+#import "godot_view_controller.h"
#import
#include
diff --git a/platform/iphone/os_iphone.h b/platform/iphone/os_iphone.h
index 3281ff0cdb71..737f6f1f6ec7 100644
--- a/platform/iphone/os_iphone.h
+++ b/platform/iphone/os_iphone.h
@@ -28,24 +28,23 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifdef IPHONE_ENABLED
-
#ifndef OS_IPHONE_H
#define OS_IPHONE_H
+#include "platform/uikit/uikit_os.h"
+
#include "drivers/coreaudio/audio_driver_coreaudio.h"
#include "drivers/unix/os_unix.h"
#include "ios.h"
-#include "joypad_iphone.h"
#include "servers/audio_server.h"
#include "servers/rendering/renderer_compositor.h"
#if defined(VULKAN_ENABLED)
#include "drivers/vulkan/rendering_device_vulkan.h"
-#include "platform/iphone/vulkan_context_iphone.h"
+#include "platform/uikit/uikit_vulkan_context.h"
#endif
-class OSIPhone : public OS_Unix {
+class OSIPhone : public OS_UIKit {
private:
static HashMap dynamic_symbol_lookup_table;
friend void register_dynamic_symbol(char *name, void *address);
@@ -54,61 +53,25 @@ class OSIPhone : public OS_Unix {
iOS *ios;
- JoypadIPhone *joypad_iphone;
-
- MainLoop *main_loop;
-
- virtual void initialize_core() override;
virtual void initialize() override;
- virtual void initialize_joypads() override {
- }
-
- virtual void set_main_loop(MainLoop *p_main_loop) override;
- virtual MainLoop *get_main_loop() const override;
-
- virtual void delete_main_loop() override;
-
virtual void finalize() override;
- String user_data_dir;
- String cache_dir;
-
bool is_focused = false;
- void deinitialize_modules();
-
public:
static OSIPhone *get_singleton();
OSIPhone(String p_data_dir, String p_cache_dir);
~OSIPhone();
- void initialize_modules();
-
- bool iterate();
-
- void start();
-
virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override;
- virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false) override;
- virtual Error close_dynamic_library(void *p_library_handle) override;
virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional = false) override;
virtual String get_name() const override;
virtual String get_model_name() const override;
- virtual Error shell_open(String p_uri) override;
-
- void set_user_data_dir(String p_dir);
- virtual String get_user_data_dir() const override;
-
- virtual String get_cache_path() const override;
-
- virtual String get_locale() const override;
-
- virtual String get_unique_id() const override;
virtual String get_processor_name() const override;
virtual void vibrate_handheld(int p_duration_ms = 500) override;
@@ -117,8 +80,8 @@ class OSIPhone : public OS_Unix {
void on_focus_out();
void on_focus_in();
+
+ void initialize_modules();
};
#endif // OS_IPHONE_H
-
-#endif // IPHONE_ENABLED
diff --git a/platform/iphone/os_iphone.mm b/platform/iphone/os_iphone.mm
index 1d990b562549..b39718059ee0 100644
--- a/platform/iphone/os_iphone.mm
+++ b/platform/iphone/os_iphone.mm
@@ -28,8 +28,6 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifdef IPHONE_ENABLED
-
#include "os_iphone.h"
#import "app_delegate.h"
@@ -40,8 +38,8 @@
#include "display_server_iphone.h"
#include "drivers/unix/syslog_logger.h"
#import "godot_view.h"
+#import "godot_view_controller.h"
#include "main/main.h"
-#import "view_controller.h"
#import
#import
@@ -89,7 +87,8 @@ void register_dynamic_symbol(char *name, void *address) {
return (OSIPhone *)OS::get_singleton();
}
-OSIPhone::OSIPhone(String p_data_dir, String p_cache_dir) {
+OSIPhone::OSIPhone(String p_data_dir, String p_cache_dir) :
+ OS_UIKit(p_data_dir, p_cache_dir) {
for (int i = 0; i < ios_init_callbacks_count; ++i) {
ios_init_callbacks[i]();
}
@@ -98,24 +97,6 @@ void register_dynamic_symbol(char *name, void *address) {
ios_init_callbacks_count = 0;
ios_init_callbacks_capacity = 0;
- main_loop = nullptr;
-
- // can't call set_data_dir from here, since it requires DirAccess
- // which is initialized in initialize_core
- user_data_dir = p_data_dir;
- cache_dir = p_cache_dir;
-
- Vector loggers;
- loggers.push_back(memnew(SyslogLogger));
-#ifdef DEBUG_ENABLED
- // it seems iOS app's stdout/stderr is only obtainable if you launch it from
- // Xcode
- loggers.push_back(memnew(StdLogger));
-#endif
- _set_logger(memnew(CompositeLogger(loggers)));
-
- AudioDriverManager::add_driver(&audio_driver);
-
DisplayServerIPhone::register_iphone_driver();
}
@@ -127,98 +108,25 @@ void register_dynamic_symbol(char *name, void *address) {
iOS::alert(utf8_alert.get_data(), utf8_title.get_data());
}
-void OSIPhone::initialize_core() {
- OS_Unix::initialize_core();
-
- set_user_data_dir(user_data_dir);
-}
-
void OSIPhone::initialize() {
- initialize_core();
+ OS_UIKit::initialize();
}
void OSIPhone::initialize_modules() {
ios = memnew(iOS);
Engine::get_singleton()->add_singleton(Engine::Singleton("iOS", ios));
-
- joypad_iphone = memnew(JoypadIPhone);
}
-void OSIPhone::deinitialize_modules() {
- if (joypad_iphone) {
- memdelete(joypad_iphone);
- }
-
+void OSIPhone::finalize() {
if (ios) {
memdelete(ios);
}
-}
-
-void OSIPhone::set_main_loop(MainLoop *p_main_loop) {
- main_loop = p_main_loop;
-
- if (main_loop) {
- main_loop->initialize();
- }
-}
-
-MainLoop *OSIPhone::get_main_loop() const {
- return main_loop;
-}
-
-void OSIPhone::delete_main_loop() {
- if (main_loop) {
- main_loop->finalize();
- memdelete(main_loop);
- }
-
- main_loop = nullptr;
-}
-
-bool OSIPhone::iterate() {
- if (!main_loop) {
- return true;
- }
-
- if (DisplayServer::get_singleton()) {
- DisplayServer::get_singleton()->process_events();
- }
-
- return Main::iteration();
-}
-
-void OSIPhone::start() {
- Main::start();
-
- if (joypad_iphone) {
- joypad_iphone->start_processing();
- }
-}
-
-void OSIPhone::finalize() {
- deinitialize_modules();
- // Already gets called
- //delete_main_loop();
+ OS_UIKit::finalize();
}
// MARK: Dynamic Libraries
-Error OSIPhone::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) {
- if (p_path.length() == 0) {
- p_library_handle = RTLD_SELF;
- return OK;
- }
- return OS_Unix::open_dynamic_library(p_path, p_library_handle, p_also_set_library_path);
-}
-
-Error OSIPhone::close_dynamic_library(void *p_library_handle) {
- if (p_library_handle == RTLD_SELF) {
- return OK;
- }
- return OS_Unix::close_dynamic_library(p_library_handle);
-}
-
Error OSIPhone::get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional) {
if (p_library_handle == RTLD_SELF) {
void **ptr = OSIPhone::dynamic_symbol_lookup_table.getptr(p_name);
@@ -243,51 +151,6 @@ void register_dynamic_symbol(char *name, void *address) {
return OS_Unix::get_model_name();
}
-Error OSIPhone::shell_open(String p_uri) {
- NSString *urlPath = [[NSString alloc] initWithUTF8String:p_uri.utf8().get_data()];
- NSURL *url = [NSURL URLWithString:urlPath];
-
- if (![[UIApplication sharedApplication] canOpenURL:url]) {
- return ERR_CANT_OPEN;
- }
-
- printf("opening url %s\n", p_uri.utf8().get_data());
-
- [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
-
- return OK;
-}
-
-void OSIPhone::set_user_data_dir(String p_dir) {
- DirAccessRef da = DirAccess::open(p_dir);
- user_data_dir = da->get_current_dir();
- printf("setting data dir to %s from %s\n", user_data_dir.utf8().get_data(), p_dir.utf8().get_data());
-}
-
-String OSIPhone::get_user_data_dir() const {
- return user_data_dir;
-}
-
-String OSIPhone::get_cache_path() const {
- return cache_dir;
-}
-
-String OSIPhone::get_locale() const {
- NSString *preferedLanguage = [NSLocale preferredLanguages].firstObject;
-
- if (preferedLanguage) {
- return String::utf8([preferedLanguage UTF8String]).replace("-", "_");
- }
-
- NSString *localeIdentifier = [[NSLocale currentLocale] localeIdentifier];
- return String::utf8([localeIdentifier UTF8String]).replace("-", "_");
-}
-
-String OSIPhone::get_unique_id() const {
- NSString *uuid = [UIDevice currentDevice].identifierForVendor.UUIDString;
- return String::utf8([uuid UTF8String]);
-}
-
String OSIPhone::get_processor_name() const {
char buffer[256];
size_t buffer_len = 256;
@@ -333,5 +196,3 @@ void register_dynamic_symbol(char *name, void *address) {
audio_driver.start();
}
}
-
-#endif // IPHONE_ENABLED
diff --git a/platform/tvos/SCsub b/platform/tvos/SCsub
new file mode 100644
index 000000000000..fe106af1413f
--- /dev/null
+++ b/platform/tvos/SCsub
@@ -0,0 +1,47 @@
+#!/usr/bin/env python
+
+Import("env")
+
+tvos_lib = [
+ "#platform/uikit/uikit_app_delegate.m",
+ "#platform/uikit/uikit_display_layer.mm",
+ "#platform/uikit/uikit_display_server.mm",
+ "#platform/uikit/uikit_joypad.mm",
+ "#platform/uikit/uikit_os.mm",
+ "#platform/uikit/uikit_view_controller.mm",
+ "#platform/uikit/uikit_view_renderer.mm",
+ "#platform/uikit/uikit_view.mm",
+ "#platform/uikit/uikit_vulkan_context.mm",
+ "godot_tvos.mm",
+ "os_tvos.mm",
+ "main.m",
+ "app_delegate.mm",
+ "tvos.mm",
+ "display_server_tvos.mm",
+ "godot_view.mm",
+ "godot_app_delegate.m",
+ "godot_view_controller.mm",
+ "godot_view_renderer.mm",
+ "godot_view_gesture_recognizer.mm",
+ "keyboard_input_view.mm",
+]
+
+env_tvos = env.Clone()
+tvos_lib = env_tvos.add_library("tvos", tvos_lib)
+
+# (tvOS) Enable module support
+env_tvos.Append(CCFLAGS=["-fmodules", "-fcxx-modules"])
+
+
+def combine_libs(target=None, source=None, env=None):
+ lib_path = target[0].srcnode().abspath
+ if "osxcross" in env:
+ libtool = "$TVOSPATH/usr/bin/${tvos_triple}libtool"
+ else:
+ libtool = "$TVOSPATH/usr/bin/libtool"
+ env.Execute(
+ libtool + ' -static -o "' + lib_path + '" ' + " ".join([('"' + lib.srcnode().abspath + '"') for lib in source])
+ )
+
+
+combine_command = env_tvos.Command("#bin/libgodot" + env_tvos["LIBSUFFIX"], [tvos_lib] + env_tvos["LIBS"], combine_libs)
diff --git a/platform/tvos/api/api.cpp b/platform/tvos/api/api.cpp
new file mode 100644
index 000000000000..1a709c725c05
--- /dev/null
+++ b/platform/tvos/api/api.cpp
@@ -0,0 +1,48 @@
+/*************************************************************************/
+/* api.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "api.h"
+
+#if defined(TVOS_ENABLED)
+
+void register_tvos_api() {
+ godot_tvos_plugins_initialize();
+}
+
+void unregister_tvos_api() {
+ godot_tvos_plugins_deinitialize();
+}
+
+#else
+
+void register_tvos_api() {}
+void unregister_tvos_api() {}
+
+#endif
diff --git a/platform/tvos/api/api.h b/platform/tvos/api/api.h
new file mode 100644
index 000000000000..90a685015eed
--- /dev/null
+++ b/platform/tvos/api/api.h
@@ -0,0 +1,42 @@
+/*************************************************************************/
+/* api.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef TVOS_API_H
+#define TVOS_API_H
+
+#if defined(TVOS_ENABLED)
+extern void godot_tvos_plugins_initialize();
+extern void godot_tvos_plugins_deinitialize();
+#endif
+
+void register_tvos_api();
+void unregister_tvos_api();
+
+#endif // TVOS_API_H
diff --git a/platform/tvos/app_delegate.h b/platform/tvos/app_delegate.h
new file mode 100644
index 000000000000..343f75633b31
--- /dev/null
+++ b/platform/tvos/app_delegate.h
@@ -0,0 +1,47 @@
+/*************************************************************************/
+/* app_delegate.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#import
+
+@class GodotViewController;
+
+// FIXME: Add support for both OpenGL and Vulkan when OpenGL is implemented again,
+// so it can't be done with compilation time branching.
+//#if defined(GLES3_ENABLED)
+//@interface AppDelegate : NSObject {
+//#endif
+//#if defined(VULKAN_ENABLED)
+@interface AppDelegate : NSObject
+//#endif
+
+@property(strong, nonatomic) UIWindow *window;
+@property(strong, class, readonly, nonatomic) GodotViewController *viewController;
+
+@end
diff --git a/platform/tvos/app_delegate.mm b/platform/tvos/app_delegate.mm
new file mode 100644
index 000000000000..9779dcb06c52
--- /dev/null
+++ b/platform/tvos/app_delegate.mm
@@ -0,0 +1,135 @@
+/*************************************************************************/
+/* app_delegate.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#import "app_delegate.h"
+#include "core/config/project_settings.h"
+#include "drivers/coreaudio/audio_driver_coreaudio.h"
+#import "godot_view.h"
+#import "godot_view_controller.h"
+#include "main/main.h"
+#include "os_tvos.h"
+
+#import
+
+#define kRenderingFrequency 60
+
+extern int gargc;
+extern char **gargv;
+
+extern int appletv_main(int, char **, String, String);
+extern void appletv_finish();
+
+@implementation AppDelegate
+
+static GodotViewController *mainViewController = nil;
+
++ (GodotViewController *)viewController {
+ return mainViewController;
+}
+
+- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
+ // Create a full-screen window
+ CGRect windowBounds = [[UIScreen mainScreen] bounds];
+ self.window = [[UIWindow alloc] initWithFrame:windowBounds];
+
+ NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory,
+ NSUserDomainMask, YES);
+ NSString *documentsDirectory = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"GodotDocuments"];
+
+ if (![[NSFileManager defaultManager] fileExistsAtPath:documentsDirectory]) {
+ [[NSFileManager defaultManager] createDirectoryAtPath:documentsDirectory withIntermediateDirectories:YES attributes:nil error:nil];
+ }
+
+ paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
+ NSString *cacheDirectory = [paths objectAtIndex:0];
+
+ int err = appletv_main(gargc, gargv, String::utf8([documentsDirectory UTF8String]), String::utf8([cacheDirectory UTF8String]));
+
+ if (err != 0) {
+ // bail, things did not go very well for us, should probably output a message on screen with our error code...
+ exit(0);
+ return NO;
+ }
+
+ // WARNING: We must *always* create the GodotView after we have constructed the
+ // OS with appletv_main. This allows the GodotView to access project settings so
+ // it can properly initialize the OpenGL context
+
+ GodotViewController *viewController = [[GodotViewController alloc] init];
+ viewController.godotView.useCADisplayLink = bool(GLOBAL_DEF("display.iOS/use_cadisplaylink", true)) ? YES : NO;
+ viewController.godotView.renderingInterval = 1.0 / kRenderingFrequency;
+
+ self.window.rootViewController = viewController;
+
+ // Show the window
+ [self.window makeKeyAndVisible];
+
+ mainViewController = viewController;
+
+ // prevent to stop music in another background app
+ [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:nil];
+
+ return YES;
+}
+
+- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
+ if (OS::get_singleton()->get_main_loop()) {
+ OS::get_singleton()->get_main_loop()->notification(
+ MainLoop::NOTIFICATION_OS_MEMORY_WARNING);
+ }
+};
+
+- (void)applicationWillTerminate:(UIApplication *)application {
+ appletv_finish();
+};
+
+// When application goes to background (e.g. user switches to another app or presses Home),
+// then applicationWillResignActive -> applicationDidEnterBackground are called.
+// When user opens the inactive app again,
+// applicationWillEnterForeground -> applicationDidBecomeActive are called.
+
+// There are cases when applicationWillResignActive -> applicationDidBecomeActive
+// sequence is called without the app going to background. For example, that happens
+// if you open the app list without switching to another app or open/close the
+// notification panel by swiping from the upper part of the screen.
+
+- (void)applicationWillResignActive:(UIApplication *)application {
+ OSAppleTV::get_singleton()->on_focus_out();
+}
+
+- (void)applicationDidBecomeActive:(UIApplication *)application {
+ OSAppleTV::get_singleton()->on_focus_in();
+}
+
+- (void)dealloc {
+ self.window = nil;
+}
+
+@end
diff --git a/platform/tvos/detect.py b/platform/tvos/detect.py
new file mode 100644
index 000000000000..a93444cf04ab
--- /dev/null
+++ b/platform/tvos/detect.py
@@ -0,0 +1,182 @@
+import os
+import sys
+from methods import detect_darwin_sdk_path
+
+
+def is_active():
+ return True
+
+
+def get_name():
+ return "tvOS"
+
+
+def can_build():
+ if sys.platform == "darwin" or ("OSXCROSS_TVOS" in os.environ):
+ return True
+
+ return False
+
+
+def get_opts():
+ from SCons.Variables import BoolVariable
+
+ return [
+ (
+ "TVOSPATH",
+ "Path to tvOS toolchain",
+ "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain",
+ ),
+ ("TVOSSDK", "Path to the tvOS SDK", ""),
+ BoolVariable("simulator", "Build for simulator", False),
+ ("tvos_triple", "Triple for tvOS toolchain", ""),
+ ]
+
+
+def get_flags():
+ return [
+ ("tools", False),
+ ("use_volk", False),
+ ]
+
+
+def configure(env):
+ ## Build type
+
+ if env["target"].startswith("release"):
+ env.Append(CPPDEFINES=["NDEBUG", ("NS_BLOCK_ASSERTIONS", 1)])
+ if env["optimize"] == "speed": # optimize for speed (default)
+ env.Append(CCFLAGS=["-O2", "-ftree-vectorize", "-fomit-frame-pointer"])
+ env.Append(LINKFLAGS=["-O2"])
+ else: # optimize for size
+ env.Append(CCFLAGS=["-Os", "-ftree-vectorize"])
+ env.Append(LINKFLAGS=["-Os"])
+
+ if env["target"] == "release_debug":
+ env.Append(CPPDEFINES=["DEBUG_ENABLED"])
+
+ elif env["target"] == "debug":
+ env.Append(CCFLAGS=["-gdwarf-2", "-O0"])
+ env.Append(CPPDEFINES=["_DEBUG", ("DEBUG", 1), "DEBUG_ENABLED"])
+
+ if env["use_lto"]:
+ env.Append(CCFLAGS=["-flto=thin"])
+ env.Append(LINKFLAGS=["-flto=thin"])
+
+ ## Architecture
+ if env["arch"] == "x86": # i386
+ env["bits"] = "32"
+ elif env["arch"] == "x86_64":
+ env["bits"] = "64"
+ else: # armv64
+ env["arch"] = "arm64"
+ env["bits"] = "64"
+
+ ## Compiler configuration
+
+ # Save this in environment for use by other modules
+ if "OSXCROSS_IOS" in os.environ:
+ env["osxcross"] = True
+
+ env["ENV"]["PATH"] = env["TVOSSDK"] + "/Developer/usr/bin/:" + env["ENV"]["PATH"]
+
+ compiler_path = "$TVOSPATH/usr/bin/${tvos_triple}"
+ s_compiler_path = "$TVOSPATH/Developer/usr/bin/"
+
+ ccache_path = os.environ.get("CCACHE")
+ if ccache_path is None:
+ env["CC"] = compiler_path + "clang"
+ env["CXX"] = compiler_path + "clang++"
+ env["S_compiler"] = s_compiler_path + "gcc"
+ else:
+ # there aren't any ccache wrappers available for iOS,
+ # to enable caching we need to prepend the path to the ccache binary
+ env["CC"] = ccache_path + " " + compiler_path + "clang"
+ env["CXX"] = ccache_path + " " + compiler_path + "clang++"
+ env["S_compiler"] = ccache_path + " " + s_compiler_path + "gcc"
+ env["AR"] = compiler_path + "ar"
+ env["RANLIB"] = compiler_path + "ranlib"
+
+ ## Compile flags
+
+ if env["simulator"]:
+ detect_darwin_sdk_path("tvossimulator", env)
+ env.Append(CCFLAGS=("-stdlib=libc++ -isysroot $TVOSSDK -mappletvsimulator-version-min=10.0").split())
+ env.Append(LINKFLAGS=[" -mappletvsimulator-version-min=10.0"])
+ else:
+ detect_darwin_sdk_path("tvos", env)
+ env.Append(CCFLAGS=("-stdlib=libc++ -isysroot $TVOSSDK -mappletvos-version-min=10.0").split())
+ env.Append(LINKFLAGS=["-mappletvos-version-min=10.0"])
+
+ if env["arch"] == "x86" or env["arch"] == "x86_64":
+ env["ENV"]["MACOSX_DEPLOYMENT_TARGET"] = "10.9"
+ arch_flag = "i386" if env["arch"] == "x86" else env["arch"]
+ env.Append(
+ CCFLAGS=(
+ "-arch "
+ + arch_flag
+ + " -fobjc-arc -fobjc-abi-version=2 -fobjc-legacy-dispatch -fmessage-length=0 -fpascal-strings -fblocks -fasm-blocks"
+ ).split()
+ )
+ elif env["arch"] == "arm64":
+ env.Append(
+ CCFLAGS="-fobjc-arc -arch arm64 -fmessage-length=0 -fno-strict-aliasing -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits -fpascal-strings -fblocks -fvisibility=hidden -MMD -MT dependencies".split()
+ )
+ env.Append(CPPDEFINES=["NEED_LONG_INT"])
+ env.Append(CPPDEFINES=["LIBYUV_DISABLE_NEON"])
+
+ # Temp fix to silence 'aligned deallocation function' diagnostics
+ env.Append(CCFLAGS=["-faligned-allocation"])
+
+ # Temp fix for ABS/MAX/MIN macros in tvOS/iOS SDK blocking compilation
+ env.Append(CCFLAGS=["-Wno-ambiguous-macro"])
+
+ # tvOS requires Bitcode.
+ env.Append(CCFLAGS=["-fembed-bitcode"])
+ env.Append(LINKFLAGS=["-bitcode_bundle"])
+
+ ## Link flags
+
+ if env["arch"] == "x86" or env["arch"] == "x86_64":
+ arch_flag = "i386" if env["arch"] == "x86" else env["arch"]
+ env.Append(
+ LINKFLAGS=[
+ "-arch",
+ arch_flag,
+ "-isysroot",
+ "$TVOSSDK",
+ "-Xlinker",
+ "-objc_abi_version",
+ "-Xlinker",
+ "2",
+ "-F$TVOSSDK",
+ ]
+ )
+
+ if env["arch"] == "arm64":
+ env.Append(LINKFLAGS=["-arch", "arm64", "-Wl,-dead_strip"])
+
+ env.Append(
+ LINKFLAGS=[
+ "-isysroot",
+ "$TVOSSDK",
+ ]
+ )
+
+ env.Prepend(
+ CPPPATH=[
+ "$TVOSSDK/usr/include",
+ "$TVOSSDK/System/Library/Frameworks/OpenGLES.framework/Headers",
+ "$TVOSSDK/System/Library/Frameworks/AudioUnit.framework/Headers",
+ ]
+ )
+
+ env.Prepend(CPPPATH=["#platform/tvos"])
+ env.Append(CPPDEFINES=["UIKIT_ENABLED", "TVOS_ENABLED", "UNIX_ENABLED", "COREAUDIO_ENABLED"])
+
+ env.Append(CPPDEFINES=["VULKAN_ENABLED"])
+ env.Append(LINKFLAGS=["-framework", "IOSurface"])
+
+ # Use Static Vulkan for tvOS. Dynamic Framework works fine too.
+ env.Append(LINKFLAGS=["-framework", "MoltenVK"])
+ env["builtin_vulkan"] = False
diff --git a/platform/tvos/display_server_tvos.h b/platform/tvos/display_server_tvos.h
new file mode 100644
index 000000000000..1c0c4762c6a9
--- /dev/null
+++ b/platform/tvos/display_server_tvos.h
@@ -0,0 +1,83 @@
+/*************************************************************************/
+/* display_server_tvos.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef display_server_tvos_h
+#define display_server_tvos_h
+
+#include "platform/uikit/uikit_display_server.h"
+
+#include "core/input/input.h"
+#include "servers/display_server.h"
+
+#if defined(VULKAN_ENABLED)
+#include "drivers/vulkan/rendering_device_vulkan.h"
+#include "servers/rendering/renderer_rd/renderer_compositor_rd.h"
+
+#import
+#ifdef USE_VOLK
+#include
+#else
+#include
+#endif
+#endif
+
+CALayer *initialize_uikit_rendering_layer(const String &p_driver);
+
+class DisplayServerAppleTV : public DisplayServerUIKit {
+ GDCLASS(DisplayServerAppleTV, DisplayServer)
+
+ _THREAD_SAFE_CLASS_
+
+ DisplayServerAppleTV(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error);
+ ~DisplayServerAppleTV();
+
+public:
+ static DisplayServerAppleTV *get_singleton();
+
+ static void register_tvos_driver();
+ static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error);
+ static Vector get_rendering_drivers_func();
+
+ // MARK: -
+
+ virtual bool has_feature(Feature p_feature) const override;
+ virtual String get_name() const override;
+
+ virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+
+ virtual int64_t window_get_native_handle(HandleType p_handle_type, WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, bool p_multiline, int p_max_length, int p_cursor_start, int p_cursor_end) override;
+ virtual void virtual_keyboard_hide() override;
+};
+
+#endif /* display_server_tvos_h */
diff --git a/platform/tvos/display_server_tvos.mm b/platform/tvos/display_server_tvos.mm
new file mode 100644
index 000000000000..5b7cb2d270ee
--- /dev/null
+++ b/platform/tvos/display_server_tvos.mm
@@ -0,0 +1,176 @@
+/*************************************************************************/
+/* display_server_tvos.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "display_server_tvos.h"
+#import "app_delegate.h"
+#include "core/config/project_settings.h"
+#include "core/io/file_access_pack.h"
+#import "godot_view.h"
+#import "godot_view_controller.h"
+#import "keyboard_input_view.h"
+#include "os_tvos.h"
+#include "tvos.h"
+
+#import
+#import
+
+CALayer *initialize_uikit_rendering_layer(const String &p_driver) {
+ NSString *driverName = [NSString stringWithUTF8String:p_driver.utf8().get_data()];
+ return [AppDelegate.viewController.godotView initializeRenderingForDriver:driverName];
+}
+
+DisplayServerAppleTV *DisplayServerAppleTV::get_singleton() {
+ return (DisplayServerAppleTV *)DisplayServer::get_singleton();
+}
+
+DisplayServerAppleTV::DisplayServerAppleTV(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) :
+ DisplayServerUIKit(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_resolution, r_error) {
+}
+
+DisplayServerAppleTV::~DisplayServerAppleTV() {
+}
+
+DisplayServer *DisplayServerAppleTV::create_func(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
+ return memnew(DisplayServerAppleTV(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_resolution, r_error));
+}
+
+Vector DisplayServerAppleTV::get_rendering_drivers_func() {
+ Vector drivers;
+
+#if defined(VULKAN_ENABLED)
+ drivers.push_back("vulkan");
+#endif
+#if defined(GLES3_ENABLED)
+ drivers.push_back("opengl_es");
+#endif
+
+ return drivers;
+}
+
+void DisplayServerAppleTV::register_tvos_driver() {
+ register_create_function("tvos", create_func, get_rendering_drivers_func);
+}
+
+// MARK: -
+
+bool DisplayServerAppleTV::has_feature(Feature p_feature) const {
+ switch (p_feature) {
+ // case FEATURE_CONSOLE_WINDOW:
+ // case FEATURE_CURSOR_SHAPE:
+ // case FEATURE_CUSTOM_CURSOR_SHAPE:
+ // case FEATURE_GLOBAL_MENU:
+ // case FEATURE_HIDPI:
+ // case FEATURE_ICON:
+ // case FEATURE_IME:
+ // case FEATURE_MOUSE:
+ // case FEATURE_MOUSE_WARP:
+ // case FEATURE_NATIVE_DIALOG:
+ // case FEATURE_NATIVE_ICON:
+ // case FEATURE_NATIVE_VIDEO:
+ // case FEATURE_WINDOW_TRANSPARENCY:
+ // case FEATURE_CLIPBOARD:
+ case FEATURE_KEEP_SCREEN_ON:
+ // case FEATURE_ORIENTATION:
+ case FEATURE_TOUCHSCREEN:
+ case FEATURE_VIRTUAL_KEYBOARD:
+ return true;
+ default:
+ return false;
+ }
+}
+
+String DisplayServerAppleTV::get_name() const {
+ return "tvOS";
+}
+
+Size2i DisplayServerAppleTV::screen_get_size(int p_screen) const {
+ CALayer *layer = AppDelegate.viewController.godotView.renderingLayer;
+
+ if (!layer) {
+ return Size2i();
+ }
+
+ return Size2i(layer.bounds.size.width, layer.bounds.size.height) * screen_get_scale(p_screen);
+}
+
+Rect2i DisplayServerAppleTV::screen_get_usable_rect(int p_screen) const {
+ if (@available(tvOS 11, *)) {
+ UIEdgeInsets insets = UIEdgeInsetsZero;
+ UIView *view = AppDelegate.viewController.godotView;
+
+ if ([view respondsToSelector:@selector(safeAreaInsets)]) {
+ insets = [view safeAreaInsets];
+ }
+
+ float scale = screen_get_scale(p_screen);
+ Size2i insets_position = Size2i(insets.left, insets.top) * scale;
+ Size2i insets_size = Size2i(insets.left + insets.right, insets.top + insets.bottom) * scale;
+
+ return Rect2i(screen_get_position(p_screen) + insets_position, screen_get_size(p_screen) - insets_size);
+ } else {
+ return Rect2i(screen_get_position(p_screen), screen_get_size(p_screen));
+ }
+}
+
+int DisplayServerAppleTV::screen_get_dpi(int p_screen) const {
+ return 96;
+}
+
+int64_t DisplayServerAppleTV::window_get_native_handle(HandleType p_handle_type, WindowID p_window) const {
+ ERR_FAIL_COND_V(p_window != MAIN_WINDOW_ID, 0);
+ switch (p_handle_type) {
+ case DISPLAY_HANDLE: {
+ return 0; // Not supported.
+ }
+ case WINDOW_HANDLE: {
+ return (int64_t)AppDelegate.viewController;
+ }
+ case WINDOW_VIEW: {
+ return (int64_t)AppDelegate.viewController.godotView;
+ }
+ default: {
+ return 0;
+ }
+ }
+}
+
+void DisplayServerAppleTV::virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, bool p_multiline, int p_max_length, int p_cursor_start, int p_cursor_end) {
+ NSString *existingString = [[NSString alloc] initWithUTF8String:p_existing_text.utf8().get_data()];
+
+ [AppDelegate.viewController.keyboardView
+ becomeFirstResponderWithString:existingString
+ multiline:p_multiline
+ cursorStart:p_cursor_start
+ cursorEnd:p_cursor_end];
+}
+
+void DisplayServerAppleTV::virtual_keyboard_hide() {
+ [AppDelegate.viewController.keyboardView resignFirstResponder];
+}
diff --git a/platform/tvos/export/export.cpp b/platform/tvos/export/export.cpp
new file mode 100644
index 000000000000..0ff9e203611b
--- /dev/null
+++ b/platform/tvos/export/export.cpp
@@ -0,0 +1,1333 @@
+/*************************************************************************/
+/* export.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "export.h"
+
+#include "core/config/project_settings.h"
+#include "core/io/file_access.h"
+#include "core/io/image_loader.h"
+#include "core/io/marshalls.h"
+#include "core/io/resource_saver.h"
+#include "core/io/zip_io.h"
+#include "core/os/os.h"
+#include "core/templates/safe_refcount.h"
+#include "core/version.h"
+#include "editor/editor_export.h"
+#include "editor/editor_node.h"
+#include "editor/editor_settings.h"
+#include "main/splash.gen.h"
+#include "platform/tvos/logo.gen.h"
+#include "string.h"
+
+#include "godot_plugin_config.h"
+
+#include
+
+class EditorExportPlatformTVOS : public EditorExportPlatform {
+ GDCLASS(EditorExportPlatformTVOS, EditorExportPlatform);
+
+ int version_code;
+
+ Ref logo;
+
+ // Plugins
+ SafeFlag plugins_changed;
+ Thread check_for_changes_thread;
+ SafeFlag quit_request;
+ Mutex plugins_lock;
+ Vector plugins;
+
+ typedef Error (*FileHandler)(String p_file, void *p_userdata);
+ static Error _walk_dir_recursive(DirAccess *p_da, FileHandler p_handler, void *p_userdata);
+
+ struct TVOSConfigData {
+ String pkg_name;
+ String binary_name;
+ String plist_content;
+ String linker_flags;
+ String cpp_code;
+ String modules_buildfile;
+ String modules_fileref;
+ String modules_buildphase;
+ String modules_buildgrp;
+ Vector capabilities;
+ };
+ struct ExportArchitecture {
+ String name;
+ bool is_default;
+
+ ExportArchitecture() :
+ name(""),
+ is_default(false) {
+ }
+
+ ExportArchitecture(String p_name, bool p_is_default) {
+ name = p_name;
+ is_default = p_is_default;
+ }
+ };
+
+ struct TVOSExportAsset {
+ String exported_path;
+ bool is_framework; // framework is anything linked to the binary, otherwise it's a resource
+ bool should_embed;
+ };
+
+ String _get_additional_plist_content();
+ String _get_linker_flags();
+ String _get_cpp_code();
+ void _fix_config_file(const Ref &p_preset, Vector &pfile, const TVOSConfigData &p_config, bool p_debug);
+
+ void _add_assets_to_project(const Ref &p_preset, Vector &p_project_data, const Vector &p_additional_assets);
+ Error _copy_asset(const String &p_out_dir, const String &p_asset, const String *p_custom_file_name, bool p_is_framework, bool p_should_embed, Vector &r_exported_assets);
+ Error _export_additional_assets(const String &p_out_dir, const Vector &p_assets, bool p_is_framework, bool p_should_embed, Vector &r_exported_assets);
+ Error _export_additional_assets(const String &p_out_dir, const Vector &p_libraries, Vector &r_exported_assets);
+ Error _export_tvos_plugins(const Ref &p_preset, TVOSConfigData &p_config_data, const String &dest_dir, Vector &r_exported_assets, bool p_debug);
+
+ bool is_package_name_valid(const String &p_package, String *r_error = NULL) const {
+ String pname = p_package;
+
+ if (pname.length() == 0) {
+ if (r_error) {
+ *r_error = TTR("Identifier is missing.");
+ }
+ return false;
+ }
+
+ for (int i = 0; i < pname.length(); i++) {
+ char32_t c = pname[i];
+ if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '.')) {
+ if (r_error) {
+ *r_error = vformat(TTR("The character '%s' is not allowed in Identifier."), String::chr(c));
+ }
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ static void _check_for_changes_poll_thread(void *ud) {
+ EditorExportPlatformTVOS *ea = (EditorExportPlatformTVOS *)ud;
+
+ while (!ea->quit_request.is_set()) {
+ // Nothing to do if we already know the plugins have changed.
+ if (!ea->plugins_changed.is_set()) {
+ MutexLock lock(ea->plugins_lock);
+
+ Vector loaded_plugins = get_plugins();
+
+ if (ea->plugins.size() != loaded_plugins.size()) {
+ ea->plugins_changed.set();
+ } else {
+ for (int i = 0; i < ea->plugins.size(); i++) {
+ if (ea->plugins[i].name != loaded_plugins[i].name || ea->plugins[i].last_updated != loaded_plugins[i].last_updated) {
+ ea->plugins_changed.set();
+ break;
+ }
+ }
+ }
+ }
+
+ uint64_t wait = 3000000;
+ uint64_t time = OS::get_singleton()->get_ticks_usec();
+ while (OS::get_singleton()->get_ticks_usec() - time < wait) {
+ OS::get_singleton()->delay_usec(300000);
+
+ if (ea->quit_request.is_set()) {
+ break;
+ }
+ }
+ }
+ }
+
+protected:
+ virtual void get_preset_features(const Ref &p_preset, List *r_features) override;
+ virtual void get_export_options(List *r_options) override;
+
+public:
+ virtual String get_name() const override { return "tvOS"; }
+ virtual String get_os_name() const override { return "tvOS"; }
+ virtual Ref get_logo() const override { return logo; }
+
+ virtual bool should_update_export_options() override {
+ bool export_options_changed = plugins_changed.is_set();
+ if (export_options_changed) {
+ // don't clear unless we're reporting true, to avoid race
+ plugins_changed.clear();
+ }
+ return export_options_changed;
+ }
+
+ virtual List get_binary_extensions(const Ref &p_preset) const override {
+ List list;
+ list.push_back("ipa");
+ return list;
+ }
+
+ virtual Error export_project(const Ref &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
+
+ virtual bool can_export(const Ref &p_preset, String &r_error, bool &r_missing_templates) const override;
+
+ virtual void get_platform_features(List *r_features) override {
+ r_features->push_back("mobile");
+ r_features->push_back("tvOS");
+ }
+
+ virtual void resolve_platform_feature_priorities(const Ref &p_preset, Set &p_features) override {
+ }
+
+ EditorExportPlatformTVOS();
+ ~EditorExportPlatformTVOS();
+
+ /// List the gdip files in the directory specified by the p_path parameter.
+ static Vector list_plugin_config_files(const String &p_path, bool p_check_directories) {
+ Vector dir_files;
+ DirAccessRef da = DirAccess::open(p_path);
+ if (da) {
+ da->list_dir_begin();
+ while (true) {
+ String file = da->get_next();
+ if (file.is_empty()) {
+ break;
+ }
+
+ if (file == "." || file == "..") {
+ continue;
+ }
+
+ if (da->current_is_hidden()) {
+ continue;
+ }
+
+ if (da->current_is_dir()) {
+ if (p_check_directories) {
+ Vector directory_files = list_plugin_config_files(p_path.plus_file(file), false);
+ for (int i = 0; i < directory_files.size(); ++i) {
+ dir_files.push_back(file.plus_file(directory_files[i]));
+ }
+ }
+
+ continue;
+ }
+
+ if (file.ends_with(PluginConfigTVOS::PLUGIN_CONFIG_EXT)) {
+ dir_files.push_back(file);
+ }
+ }
+ da->list_dir_end();
+ }
+
+ return dir_files;
+ }
+
+ static Vector get_plugins() {
+ Vector loaded_plugins;
+
+ String plugins_dir = ProjectSettings::get_singleton()->get_resource_path().plus_file("tvos/plugins");
+
+ if (DirAccess::exists(plugins_dir)) {
+ Vector plugins_filenames = list_plugin_config_files(plugins_dir, true);
+
+ if (!plugins_filenames.is_empty()) {
+ Ref config_file = memnew(ConfigFile);
+ for (int i = 0; i < plugins_filenames.size(); i++) {
+ PluginConfigTVOS config = load_plugin_config(config_file, plugins_dir.plus_file(plugins_filenames[i]));
+ if (config.valid_config) {
+ loaded_plugins.push_back(config);
+ } else {
+ print_error("Invalid plugin config file " + plugins_filenames[i]);
+ }
+ }
+ }
+ }
+
+ return loaded_plugins;
+ }
+
+ static Vector get_enabled_plugins(const Ref &p_presets) {
+ Vector enabled_plugins;
+ Vector all_plugins = get_plugins();
+ for (int i = 0; i < all_plugins.size(); i++) {
+ PluginConfigTVOS plugin = all_plugins[i];
+ bool enabled = p_presets->get("plugins/" + plugin.name);
+ if (enabled) {
+ enabled_plugins.push_back(plugin);
+ }
+ }
+
+ return enabled_plugins;
+ }
+};
+
+void EditorExportPlatformTVOS::get_preset_features(const Ref &p_preset, List *r_features) {
+ String driver = ProjectSettings::get_singleton()->get("rendering/quality/driver/driver_name");
+ r_features->push_back("pvrtc");
+ if (driver == "GLES3") {
+ r_features->push_back("etc2");
+ }
+
+ r_features->push_back("arm64");
+}
+
+void EditorExportPlatformTVOS::get_export_options(List *r_options) {
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), ""));
+
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/app_store_team_id"), ""));
+
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/bundle_identifier", PROPERTY_HINT_PLACEHOLDER_TEXT, "com.example.game"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/short_version"), "1.0"));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/build_version"), "1"));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/copyright"), ""));
+
+ Vector found_plugins = get_plugins();
+ for (int i = 0; i < found_plugins.size(); i++) {
+ r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "plugins/" + found_plugins[i].name), false));
+ }
+
+ Set plist_keys;
+
+ for (int i = 0; i < found_plugins.size(); i++) {
+ // Editable plugin plist values
+ PluginConfigTVOS plugin = found_plugins[i];
+ const String *K = nullptr;
+
+ while ((K = plugin.plist.next(K))) {
+ String key = *K;
+ PluginConfigTVOS::PlistItem item = plugin.plist[key];
+ switch (item.type) {
+ case PluginConfigTVOS::PlistItemType::STRING_INPUT: {
+ String preset_name = "plugins_plist/" + key;
+ if (!plist_keys.has(preset_name)) {
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, preset_name), item.value));
+ plist_keys.insert(preset_name);
+ }
+ } break;
+ default:
+ continue;
+ }
+ }
+ }
+
+ plugins_changed.clear();
+ plugins = found_plugins;
+}
+
+void EditorExportPlatformTVOS::_fix_config_file(const Ref &p_preset, Vector &pfile, const TVOSConfigData &p_config, bool p_debug) {
+ String str;
+ String strnew;
+ str.parse_utf8((const char *)pfile.ptr(), pfile.size());
+ Vector lines = str.split("\n");
+ for (int i = 0; i < lines.size(); i++) {
+ if (lines[i].find("$binary") != -1) {
+ strnew += lines[i].replace("$binary", p_config.binary_name) + "\n";
+ } else if (lines[i].find("$modules_buildfile") != -1) {
+ strnew += lines[i].replace("$modules_buildfile", p_config.modules_buildfile) + "\n";
+ } else if (lines[i].find("$modules_fileref") != -1) {
+ strnew += lines[i].replace("$modules_fileref", p_config.modules_fileref) + "\n";
+ } else if (lines[i].find("$modules_buildphase") != -1) {
+ strnew += lines[i].replace("$modules_buildphase", p_config.modules_buildphase) + "\n";
+ } else if (lines[i].find("$modules_buildgrp") != -1) {
+ strnew += lines[i].replace("$modules_buildgrp", p_config.modules_buildgrp) + "\n";
+ } else if (lines[i].find("$name") != -1) {
+ strnew += lines[i].replace("$name", p_config.pkg_name) + "\n";
+ } else if (lines[i].find("$info") != -1) {
+ strnew += lines[i].replace("$info", p_preset->get("application/info")) + "\n";
+ } else if (lines[i].find("$bundle_identifier") != -1) {
+ strnew += lines[i].replace("$bundle_identifier", p_preset->get("application/bundle_identifier")) + "\n";
+ } else if (lines[i].find("$short_version") != -1) {
+ strnew += lines[i].replace("$short_version", p_preset->get("application/short_version")) + "\n";
+ } else if (lines[i].find("$build_version") != -1) {
+ strnew += lines[i].replace("$build_version", p_preset->get("application/build_version")) + "\n";
+ } else if (lines[i].find("$copyright") != -1) {
+ strnew += lines[i].replace("$copyright", p_preset->get("application/copyright")) + "\n";
+ } else if (lines[i].find("$team_id") != -1) {
+ strnew += lines[i].replace("$team_id", p_preset->get("application/app_store_team_id")) + "\n";
+ } else if (lines[i].find("$additional_plist_content") != -1) {
+ strnew += lines[i].replace("$additional_plist_content", p_config.plist_content) + "\n";
+ } else if (lines[i].find("$linker_flags") != -1) {
+ strnew += lines[i].replace("$linker_flags", p_config.linker_flags) + "\n";
+ } else if (lines[i].find("$cpp_code") != -1) {
+ strnew += lines[i].replace("$cpp_code", p_config.cpp_code) + "\n";
+ } else {
+ strnew += lines[i] + "\n";
+ }
+ }
+
+ // !BAS! I'm assuming the 9 in the original code was a typo. I've added -1 or else it seems to also be adding our terminating zero...
+ // should apply the same fix in our OSX export.
+ CharString cs = strnew.utf8();
+ pfile.resize(cs.size() - 1);
+ for (int i = 0; i < cs.size() - 1; i++) {
+ pfile.write[i] = cs[i];
+ }
+}
+
+String EditorExportPlatformTVOS::_get_additional_plist_content() {
+ Vector[> export_plugins = EditorExport::get_singleton()->get_export_plugins();
+ String result;
+ for (int i = 0; i < export_plugins.size(); ++i) {
+ result += export_plugins[i]->get_tvos_plist_content();
+ }
+ return result;
+}
+
+String EditorExportPlatformTVOS::_get_linker_flags() {
+ Vector][> export_plugins = EditorExport::get_singleton()->get_export_plugins();
+ String result;
+ for (int i = 0; i < export_plugins.size(); ++i) {
+ String flags = export_plugins[i]->get_tvos_linker_flags();
+ if (flags.length() == 0)
+ continue;
+ if (result.length() > 0) {
+ result += ' ';
+ }
+ result += flags;
+ }
+ // the flags will be enclosed in quotes, so need to escape them
+ return result.replace("\"", "\\\"");
+}
+
+String EditorExportPlatformTVOS::_get_cpp_code() {
+ Vector][> export_plugins = EditorExport::get_singleton()->get_export_plugins();
+ String result;
+ for (int i = 0; i < export_plugins.size(); ++i) {
+ result += export_plugins[i]->get_tvos_cpp_code();
+ }
+ return result;
+}
+
+Error EditorExportPlatformTVOS::_walk_dir_recursive(DirAccess *p_da, FileHandler p_handler, void *p_userdata) {
+ Vector dirs;
+ String current_dir = p_da->get_current_dir();
+ p_da->list_dir_begin();
+
+ String path = p_da->get_next();
+ while (!path.is_empty()) {
+ if (p_da->current_is_dir()) {
+ if (path != "." && path != "..") {
+ dirs.push_back(path);
+ }
+ } else {
+ Error err = p_handler(current_dir.plus_file(path), p_userdata);
+ if (err) {
+ p_da->list_dir_end();
+ return err;
+ }
+ }
+
+ path = p_da->get_next();
+ }
+ p_da->list_dir_end();
+
+ for (int i = 0; i < dirs.size(); ++i) {
+ String dir = dirs[i];
+ p_da->change_dir(dir);
+ Error err = _walk_dir_recursive(p_da, p_handler, p_userdata);
+ p_da->change_dir("..");
+ if (err) {
+ return err;
+ }
+ }
+
+ return OK;
+}
+
+struct PbxId {
+private:
+ static char _hex_char(uint8_t four_bits) {
+ if (four_bits < 10) {
+ return ('0' + four_bits);
+ }
+ return 'A' + (four_bits - 10);
+ }
+
+ static String _hex_pad(uint32_t num) {
+ Vector ret;
+ ret.resize(sizeof(num) * 2);
+ for (uint64_t i = 0; i < sizeof(num) * 2; ++i) {
+ uint8_t four_bits = (num >> (sizeof(num) * 8 - (i + 1) * 4)) & 0xF;
+ ret.write[i] = _hex_char(four_bits);
+ }
+ return String::utf8(ret.ptr(), ret.size());
+ }
+
+public:
+ uint32_t high_bits;
+ uint32_t mid_bits;
+ uint32_t low_bits;
+
+ String str() const {
+ return _hex_pad(high_bits) + _hex_pad(mid_bits) + _hex_pad(low_bits);
+ }
+
+ PbxId &operator++() {
+ low_bits++;
+ if (!low_bits) {
+ mid_bits++;
+ if (!mid_bits) {
+ high_bits++;
+ }
+ }
+
+ return *this;
+ }
+};
+
+struct ExportLibsData {
+ Vector lib_paths;
+ String dest_dir;
+};
+
+void EditorExportPlatformTVOS::_add_assets_to_project(const Ref &p_preset, Vector &p_project_data, const Vector &p_additional_assets) {
+ // that is just a random number, we just need Godot IDs not to clash with
+ // existing IDs in the project.
+ PbxId current_id = { 0x58938401, 0, 0 };
+ String pbx_files;
+ String pbx_frameworks_build;
+ String pbx_frameworks_refs;
+ String pbx_resources_build;
+ String pbx_resources_refs;
+ String pbx_embeded_frameworks;
+
+ const String file_info_format = String("$build_id = {isa = PBXBuildFile; fileRef = $ref_id; };\n") +
+ "$ref_id = {isa = PBXFileReference; lastKnownFileType = $file_type; name = \"$name\"; path = \"$file_path\"; sourceTree = \"\"; };\n";
+
+ for (int i = 0; i < p_additional_assets.size(); ++i) {
+ String additional_asset_info_format = file_info_format;
+
+ String build_id = (++current_id).str();
+ String ref_id = (++current_id).str();
+ String framework_id = "";
+
+ const TVOSExportAsset &asset = p_additional_assets[i];
+
+ String type;
+ if (asset.exported_path.ends_with(".framework")) {
+ if (asset.should_embed) {
+ additional_asset_info_format += "$framework_id = {isa = PBXBuildFile; fileRef = $ref_id; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };\n";
+ framework_id = (++current_id).str();
+ pbx_embeded_frameworks += framework_id + ",\n";
+ }
+
+ type = "wrapper.framework";
+ } else if (asset.exported_path.ends_with(".xcframework")) {
+ if (asset.should_embed) {
+ additional_asset_info_format += "$framework_id = {isa = PBXBuildFile; fileRef = $ref_id; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };\n";
+ framework_id = (++current_id).str();
+ pbx_embeded_frameworks += framework_id + ",\n";
+ }
+
+ type = "wrapper.xcframework";
+ } else if (asset.exported_path.ends_with(".dylib")) {
+ type = "compiled.mach-o.dylib";
+ } else if (asset.exported_path.ends_with(".a")) {
+ type = "archive.ar";
+ } else {
+ type = "file";
+ }
+
+ String &pbx_build = asset.is_framework ? pbx_frameworks_build : pbx_resources_build;
+ String &pbx_refs = asset.is_framework ? pbx_frameworks_refs : pbx_resources_refs;
+
+ if (pbx_build.length() > 0) {
+ pbx_build += ",\n";
+ pbx_refs += ",\n";
+ }
+ pbx_build += build_id;
+ pbx_refs += ref_id;
+
+ Dictionary format_dict;
+ format_dict["build_id"] = build_id;
+ format_dict["ref_id"] = ref_id;
+ format_dict["name"] = asset.exported_path.get_file();
+ format_dict["file_path"] = asset.exported_path;
+ format_dict["file_type"] = type;
+ if (framework_id.length() > 0) {
+ format_dict["framework_id"] = framework_id;
+ }
+ pbx_files += additional_asset_info_format.format(format_dict, "$_");
+ }
+
+ // Note, frameworks like gamekit are always included in our project.pbxprof file
+ // even if turned off in capabilities.
+
+ String str = String::utf8((const char *)p_project_data.ptr(), p_project_data.size());
+ str = str.replace("$additional_pbx_files", pbx_files);
+ str = str.replace("$additional_pbx_frameworks_build", pbx_frameworks_build);
+ str = str.replace("$additional_pbx_frameworks_refs", pbx_frameworks_refs);
+ str = str.replace("$additional_pbx_resources_build", pbx_resources_build);
+ str = str.replace("$additional_pbx_resources_refs", pbx_resources_refs);
+ str = str.replace("$pbx_embeded_frameworks", pbx_embeded_frameworks);
+
+ CharString cs = str.utf8();
+ p_project_data.resize(cs.size() - 1);
+ for (int i = 0; i < cs.size() - 1; i++) {
+ p_project_data.write[i] = cs[i];
+ }
+}
+
+Error EditorExportPlatformTVOS::_copy_asset(const String &p_out_dir, const String &p_asset, const String *p_custom_file_name, bool p_is_framework, bool p_should_embed, Vector &r_exported_assets) {
+ DirAccess *filesystem_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ ERR_FAIL_COND_V_MSG(!filesystem_da, ERR_CANT_CREATE, "Cannot create DirAccess for path '" + p_out_dir + "'.");
+
+ String binary_name = p_out_dir.get_file().get_basename();
+
+ DirAccess *da = DirAccess::create_for_path(p_asset);
+ if (!da) {
+ memdelete(filesystem_da);
+ ERR_FAIL_V_MSG(ERR_CANT_CREATE, "Can't create directory: " + p_asset + ".");
+ }
+ bool file_exists = da->file_exists(p_asset);
+ bool dir_exists = da->dir_exists(p_asset);
+ if (!file_exists && !dir_exists) {
+ memdelete(da);
+ memdelete(filesystem_da);
+ return ERR_FILE_NOT_FOUND;
+ }
+
+ String base_dir = p_asset.get_base_dir().replace("res://", "");
+ String destination_dir;
+ String destination;
+ String asset_path;
+
+ bool create_framework = false;
+
+ if (p_is_framework && p_asset.ends_with(".dylib")) {
+ // For tvOS we need to turn .dylib into .framework
+ // to be able to send application to AppStore
+ asset_path = String("dylibs").plus_file(base_dir);
+
+ String file_name;
+
+ if (!p_custom_file_name) {
+ file_name = p_asset.get_basename().get_file();
+ } else {
+ file_name = *p_custom_file_name;
+ }
+
+ String framework_name = file_name + ".framework";
+
+ asset_path = asset_path.plus_file(framework_name);
+ destination_dir = p_out_dir.plus_file(asset_path);
+ destination = destination_dir.plus_file(file_name);
+ create_framework = true;
+ } else if (p_is_framework && (p_asset.ends_with(".framework") || p_asset.ends_with(".xcframework"))) {
+ asset_path = String("dylibs").plus_file(base_dir);
+
+ String file_name;
+
+ if (!p_custom_file_name) {
+ file_name = p_asset.get_file();
+ } else {
+ file_name = *p_custom_file_name;
+ }
+
+ asset_path = asset_path.plus_file(file_name);
+ destination_dir = p_out_dir.plus_file(asset_path);
+ destination = destination_dir;
+ } else {
+ asset_path = base_dir;
+
+ String file_name;
+
+ if (!p_custom_file_name) {
+ file_name = p_asset.get_file();
+ } else {
+ file_name = *p_custom_file_name;
+ }
+
+ destination_dir = p_out_dir.plus_file(asset_path);
+ asset_path = asset_path.plus_file(file_name);
+ destination = p_out_dir.plus_file(asset_path);
+ }
+
+ if (!filesystem_da->dir_exists(destination_dir)) {
+ Error make_dir_err = filesystem_da->make_dir_recursive(destination_dir);
+ if (make_dir_err) {
+ memdelete(da);
+ memdelete(filesystem_da);
+ return make_dir_err;
+ }
+ }
+
+ Error err = dir_exists ? da->copy_dir(p_asset, destination) : da->copy(p_asset, destination);
+ memdelete(da);
+ if (err) {
+ memdelete(filesystem_da);
+ return err;
+ }
+ TVOSExportAsset exported_asset = { binary_name.plus_file(asset_path), p_is_framework, p_should_embed };
+ r_exported_assets.push_back(exported_asset);
+
+ if (create_framework) {
+ String file_name;
+
+ if (!p_custom_file_name) {
+ file_name = p_asset.get_basename().get_file();
+ } else {
+ file_name = *p_custom_file_name;
+ }
+
+ String framework_name = file_name + ".framework";
+
+ // Performing `install_name_tool -id @rpath/{name}.framework/{name} ./{name}` on dylib
+ {
+ List install_name_args;
+ install_name_args.push_back("-id");
+ install_name_args.push_back(String("@rpath").plus_file(framework_name).plus_file(file_name));
+ install_name_args.push_back(destination);
+
+ OS::get_singleton()->execute("install_name_tool", install_name_args);
+ }
+
+ // Creating Info.plist
+ {
+ String info_plist_format = "\n"
+ "\n"
+ "\n"
+ "\n"
+ "CFBundleShortVersionString\n"
+ "1.0\n"
+ "CFBundleIdentifier\n"
+ "com.gdnative.framework.$name\n"
+ "CFBundleName\n"
+ "$name\n"
+ "CFBundleExecutable\n"
+ "$name\n"
+ "DTPlatformName\n"
+ "appletvos\n"
+ "CFBundleInfoDictionaryVersion\n"
+ "6.0\n"
+ "CFBundleVersion\n"
+ "1\n"
+ "CFBundlePackageType\n"
+ "FMWK\n"
+ "MinimumOSVersion\n"
+ "10.0\n"
+ "\n"
+ "";
+
+ String info_plist = info_plist_format.replace("$name", file_name);
+
+ FileAccess *f = FileAccess::open(destination_dir.plus_file("Info.plist"), FileAccess::WRITE);
+ if (f) {
+ f->store_string(info_plist);
+ f->close();
+ memdelete(f);
+ }
+ }
+ }
+
+ memdelete(filesystem_da);
+
+ return OK;
+}
+
+Error EditorExportPlatformTVOS::_export_additional_assets(const String &p_out_dir, const Vector &p_assets, bool p_is_framework, bool p_should_embed, Vector &r_exported_assets) {
+ for (int f_idx = 0; f_idx < p_assets.size(); ++f_idx) {
+ String asset = p_assets[f_idx];
+ if (!asset.begins_with("res://")) {
+ // either SDK-builtin or already a part of the export template
+ TVOSExportAsset exported_asset = { asset, p_is_framework, p_should_embed };
+ r_exported_assets.push_back(exported_asset);
+ } else {
+ Error err = _copy_asset(p_out_dir, asset, nullptr, p_is_framework, p_should_embed, r_exported_assets);
+ ERR_FAIL_COND_V(err, err);
+ }
+ }
+
+ return OK;
+}
+
+Error EditorExportPlatformTVOS::_export_additional_assets(const String &p_out_dir, const Vector &p_libraries, Vector &r_exported_assets) {
+ Vector][> export_plugins = EditorExport::get_singleton()->get_export_plugins();
+ for (int i = 0; i < export_plugins.size(); i++) {
+ Vector linked_frameworks = export_plugins[i]->get_tvos_frameworks();
+ Error err = _export_additional_assets(p_out_dir, linked_frameworks, true, false, r_exported_assets);
+ ERR_FAIL_COND_V(err, err);
+
+ Vector embedded_frameworks = export_plugins[i]->get_tvos_embedded_frameworks();
+ err = _export_additional_assets(p_out_dir, embedded_frameworks, true, true, r_exported_assets);
+ ERR_FAIL_COND_V(err, err);
+
+ Vector project_static_libs = export_plugins[i]->get_tvos_project_static_libs();
+ for (int j = 0; j < project_static_libs.size(); j++)
+ project_static_libs.write[j] = project_static_libs[j].get_file(); // Only the file name as it's copied to the project
+ err = _export_additional_assets(p_out_dir, project_static_libs, true, true, r_exported_assets);
+ ERR_FAIL_COND_V(err, err);
+
+ Vector tvos_bundle_files = export_plugins[i]->get_tvos_bundle_files();
+ err = _export_additional_assets(p_out_dir, tvos_bundle_files, false, false, r_exported_assets);
+ ERR_FAIL_COND_V(err, err);
+ }
+
+ Vector library_paths;
+ for (int i = 0; i < p_libraries.size(); ++i) {
+ library_paths.push_back(p_libraries[i].path);
+ }
+ Error err = _export_additional_assets(p_out_dir, library_paths, true, true, r_exported_assets);
+ ERR_FAIL_COND_V(err, err);
+
+ return OK;
+}
+
+Error EditorExportPlatformTVOS::_export_tvos_plugins(const Ref &p_preset, TVOSConfigData &p_config_data, const String &dest_dir, Vector &r_exported_assets, bool p_debug) {
+ String plugin_definition_cpp_code;
+ String plugin_initialization_cpp_code;
+ String plugin_deinitialization_cpp_code;
+
+ Vector plugin_linked_dependencies;
+ Vector plugin_embedded_dependencies;
+ Vector plugin_files;
+
+ Vector enabled_plugins = get_enabled_plugins(p_preset);
+
+ Vector added_linked_dependenciy_names;
+ Vector added_embedded_dependenciy_names;
+ HashMap plist_values;
+
+ Set plugin_linker_flags;
+
+ Error err;
+
+ for (int i = 0; i < enabled_plugins.size(); i++) {
+ PluginConfigTVOS plugin = enabled_plugins[i];
+
+ // Export plugin binary.
+ String plugin_main_binary = get_plugin_main_binary(plugin, p_debug);
+ String plugin_binary_result_file = plugin.binary.get_file();
+ // We shouldn't embed .xcframework that contains static libraries.
+ // Static libraries are not embedded anyway.
+ err = _copy_asset(dest_dir, plugin_main_binary, &plugin_binary_result_file, true, false, r_exported_assets);
+
+ ERR_FAIL_COND_V(err, err);
+
+ // Adding dependencies.
+ // Use separate container for names to check for duplicates.
+ for (int j = 0; j < plugin.linked_dependencies.size(); j++) {
+ String dependency = plugin.linked_dependencies[j];
+ String name = dependency.get_file();
+
+ if (added_linked_dependenciy_names.find(name) != -1) {
+ continue;
+ }
+
+ added_linked_dependenciy_names.push_back(name);
+ plugin_linked_dependencies.push_back(dependency);
+ }
+
+ for (int j = 0; j < plugin.system_dependencies.size(); j++) {
+ String dependency = plugin.system_dependencies[j];
+ String name = dependency.get_file();
+
+ if (added_linked_dependenciy_names.find(name) != -1) {
+ continue;
+ }
+
+ added_linked_dependenciy_names.push_back(name);
+ plugin_linked_dependencies.push_back(dependency);
+ }
+
+ for (int j = 0; j < plugin.embedded_dependencies.size(); j++) {
+ String dependency = plugin.embedded_dependencies[j];
+ String name = dependency.get_file();
+
+ if (added_embedded_dependenciy_names.find(name) != -1) {
+ continue;
+ }
+
+ added_embedded_dependenciy_names.push_back(name);
+ plugin_embedded_dependencies.push_back(dependency);
+ }
+
+ plugin_files.append_array(plugin.files_to_copy);
+
+ // Capabilities
+ // Also checking for duplicates.
+ for (int j = 0; j < plugin.capabilities.size(); j++) {
+ String capability = plugin.capabilities[j];
+
+ if (p_config_data.capabilities.find(capability) != -1) {
+ continue;
+ }
+
+ p_config_data.capabilities.push_back(capability);
+ }
+
+ // Linker flags
+ // Checking duplicates
+ for (int j = 0; j < plugin.linker_flags.size(); j++) {
+ String linker_flag = plugin.linker_flags[j];
+ plugin_linker_flags.insert(linker_flag);
+ }
+
+ // Plist
+ // Using hash map container to remove duplicates
+ const String *K = nullptr;
+
+ while ((K = plugin.plist.next(K))) {
+ String key = *K;
+ PluginConfigTVOS::PlistItem item = plugin.plist[key];
+
+ String value;
+
+ switch (item.type) {
+ case PluginConfigTVOS::PlistItemType::STRING_INPUT: {
+ String preset_name = "plugins_plist/" + key;
+ String input_value = p_preset->get(preset_name);
+ value = "" + input_value + "";
+ } break;
+ default:
+ value = item.value;
+ break;
+ }
+
+ if (key.is_empty() || value.is_empty()) {
+ continue;
+ }
+
+ String plist_key = "" + key + "";
+
+ plist_values[plist_key] = value;
+ }
+
+ // CPP Code
+ String definition_comment = "// Plugin: " + plugin.name + "\n";
+ String initialization_method = plugin.initialization_method + "();\n";
+ String deinitialization_method = plugin.deinitialization_method + "();\n";
+
+ plugin_definition_cpp_code += definition_comment +
+ "extern void " + initialization_method +
+ "extern void " + deinitialization_method + "\n";
+
+ plugin_initialization_cpp_code += "\t" + initialization_method;
+ plugin_deinitialization_cpp_code += "\t" + deinitialization_method;
+ }
+
+ // Updating `Info.plist`
+ {
+ const String *K = nullptr;
+ while ((K = plist_values.next(K))) {
+ String key = *K;
+ String value = plist_values[key];
+
+ if (key.is_empty() || value.is_empty()) {
+ continue;
+ }
+
+ p_config_data.plist_content += key + value + "\n";
+ }
+ }
+
+ // Export files
+ {
+ // Export linked plugin dependency
+ err = _export_additional_assets(dest_dir, plugin_linked_dependencies, true, false, r_exported_assets);
+ ERR_FAIL_COND_V(err, err);
+
+ // Export embedded plugin dependency
+ err = _export_additional_assets(dest_dir, plugin_embedded_dependencies, true, true, r_exported_assets);
+ ERR_FAIL_COND_V(err, err);
+
+ // Export plugin files
+ err = _export_additional_assets(dest_dir, plugin_files, false, false, r_exported_assets);
+ ERR_FAIL_COND_V(err, err);
+ }
+
+ // Update CPP
+ {
+ Dictionary plugin_format;
+ plugin_format["definition"] = plugin_definition_cpp_code;
+ plugin_format["initialization"] = plugin_initialization_cpp_code;
+ plugin_format["deinitialization"] = plugin_deinitialization_cpp_code;
+
+ String plugin_cpp_code = "\n// Godot Plugins\n"
+ "void godot_tvos_plugins_initialize();\n"
+ "void godot_tvos_plugins_deinitialize();\n"
+ "// Exported Plugins\n\n"
+ "$definition"
+ "// Use Plugins\n"
+ "void godot_tvos_plugins_initialize() {\n"
+ "$initialization"
+ "}\n\n"
+ "void godot_tvos_plugins_deinitialize() {\n"
+ "$deinitialization"
+ "}\n";
+
+ p_config_data.cpp_code += plugin_cpp_code.format(plugin_format, "$_");
+ }
+
+ // Update Linker Flag Values
+ {
+ String result_linker_flags = " ";
+ for (Set::Element *E = plugin_linker_flags.front(); E; E = E->next()) {
+ const String &flag = E->get();
+
+ if (flag.length() == 0) {
+ continue;
+ }
+
+ if (result_linker_flags.length() > 0) {
+ result_linker_flags += ' ';
+ }
+
+ result_linker_flags += flag;
+ }
+ result_linker_flags = result_linker_flags.replace("\"", "\\\"");
+ p_config_data.linker_flags += result_linker_flags;
+ }
+
+ return OK;
+}
+
+Error EditorExportPlatformTVOS::export_project(const Ref &p_preset, bool p_debug, const String &p_path, int p_flags) {
+ ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
+
+ String src_pkg_name;
+ String dest_dir = p_path.get_base_dir() + "/";
+ String binary_name = p_path.get_file().get_basename();
+
+ EditorProgress ep("export", "Exporting for tvOS", 5, true);
+
+ String team_id = p_preset->get("application/app_store_team_id");
+
+ if (p_debug)
+ src_pkg_name = p_preset->get("custom_template/debug");
+ else
+ src_pkg_name = p_preset->get("custom_template/release");
+
+ if (src_pkg_name == "") {
+ String err;
+ src_pkg_name = find_export_template("tvos.zip", &err);
+ if (src_pkg_name == "") {
+ EditorNode::add_io_error(err);
+ return ERR_FILE_NOT_FOUND;
+ }
+ }
+
+ if (!DirAccess::exists(dest_dir)) {
+ return ERR_FILE_BAD_PATH;
+ }
+
+ DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ if (da) {
+ String current_dir = da->get_current_dir();
+
+ // remove leftovers from last export so they don't interfere
+ // in case some files are no longer needed
+ if (da->change_dir(dest_dir + binary_name + ".xcodeproj") == OK) {
+ da->erase_contents_recursive();
+ }
+ if (da->change_dir(dest_dir + binary_name) == OK) {
+ da->erase_contents_recursive();
+ }
+
+ da->change_dir(current_dir);
+
+ if (!da->dir_exists(dest_dir + binary_name)) {
+ Error err = da->make_dir(dest_dir + binary_name);
+ if (err) {
+ memdelete(da);
+ return err;
+ }
+ }
+ memdelete(da);
+ }
+
+ if (ep.step("Making .pck", 0)) {
+ return ERR_SKIP;
+ }
+ String pack_path = dest_dir + binary_name + ".pck";
+ Vector libraries;
+ Error err = save_pack(p_preset, p_debug, pack_path, &libraries);
+ if (err)
+ return err;
+
+ if (ep.step("Extracting and configuring Xcode project", 1)) {
+ return ERR_SKIP;
+ }
+
+ String library_to_use = "libgodot.tvos." + String(p_debug ? "debug" : "release") + ".xcframework";
+
+ print_line("Static library: " + library_to_use);
+ String pkg_name;
+ if (p_preset->get("application/name") != "")
+ pkg_name = p_preset->get("application/name"); // app_name
+ else if (String(ProjectSettings::get_singleton()->get("application/config/name")) != "")
+ pkg_name = String(ProjectSettings::get_singleton()->get("application/config/name"));
+ else
+ pkg_name = "Unnamed";
+
+ bool found_library = false;
+ int total_size = 0;
+
+ const String project_file = "godot_tvos.xcodeproj/project.pbxproj";
+ Set files_to_parse;
+ files_to_parse.insert("godot_tvos/Info.plist");
+ files_to_parse.insert(project_file);
+ files_to_parse.insert("godot_tvos/dummy.cpp");
+ files_to_parse.insert("godot_tvos.xcodeproj/project.xcworkspace/contents.xcworkspacedata");
+ files_to_parse.insert("godot_tvos.xcodeproj/xcshareddata/xcschemes/godot_tvos.xcscheme");
+ files_to_parse.insert("godot_tvos/Launch Screen.storyboard");
+
+ TVOSConfigData config_data = {
+ pkg_name,
+ binary_name,
+ _get_additional_plist_content(),
+ _get_linker_flags(),
+ _get_cpp_code(),
+ "",
+ "",
+ "",
+ "",
+ Vector()
+ };
+
+ Vector assets;
+
+ DirAccess *tmp_app_path = DirAccess::create_for_path(dest_dir);
+ ERR_FAIL_COND_V(!tmp_app_path, ERR_CANT_CREATE);
+
+ print_line("Unzipping...");
+ FileAccess *src_f = NULL;
+ zlib_filefunc_def io = zipio_create_io_from_file(&src_f);
+ unzFile src_pkg_zip = unzOpen2(src_pkg_name.utf8().get_data(), &io);
+ if (!src_pkg_zip) {
+ EditorNode::add_io_error("Could not open export template (not a zip file?):\n" + src_pkg_name);
+ return ERR_CANT_OPEN;
+ }
+
+ err = _export_tvos_plugins(p_preset, config_data, dest_dir + binary_name, assets, p_debug);
+ ERR_FAIL_COND_V(err, err);
+
+ //export rest of the files
+ int ret = unzGoToFirstFile(src_pkg_zip);
+ Vector project_file_data;
+ while (ret == UNZ_OK) {
+#if defined(OSX_ENABLED) || defined(X11_ENABLED)
+ bool is_execute = false;
+#endif
+
+ //get filename
+ unz_file_info info;
+ char fname[16384];
+ ret = unzGetCurrentFileInfo(src_pkg_zip, &info, fname, 16384, NULL, 0, NULL, 0);
+
+ String file = fname;
+
+ print_line("READ: " + file);
+ Vector data;
+ data.resize(info.uncompressed_size);
+
+ //read
+ unzOpenCurrentFile(src_pkg_zip);
+ unzReadCurrentFile(src_pkg_zip, data.ptrw(), data.size());
+ unzCloseCurrentFile(src_pkg_zip);
+
+ //write
+
+ file = file.replace_first("tvos/", "");
+
+ if (files_to_parse.has(file)) {
+ _fix_config_file(p_preset, data, config_data, p_debug);
+ } else if (file.begins_with("libgodot.tvos")) {
+ if (!file.begins_with(library_to_use) || file.ends_with(String("/empty"))) {
+ ret = unzGoToNextFile(src_pkg_zip);
+ continue; //ignore!
+ }
+ found_library = true;
+#if defined(OSX_ENABLED) || defined(X11_ENABLED)
+ is_execute = true;
+#endif
+ file = file.replace(library_to_use, binary_name + ".xcframework");
+ }
+
+ if (file == project_file) {
+ project_file_data = data;
+ }
+
+ ///@TODO need to parse logo files
+
+ if (data.size() > 0) {
+ file = file.replace("godot_tvos", binary_name);
+
+ print_line("ADDING: " + file + " size: " + itos(data.size()));
+ total_size += data.size();
+
+ /* write it into our folder structure */
+ file = dest_dir + file;
+
+ /* make sure this folder exists */
+ String dir_name = file.get_base_dir();
+ if (!tmp_app_path->dir_exists(dir_name)) {
+ print_line("Creating " + dir_name);
+ Error dir_err = tmp_app_path->make_dir_recursive(dir_name);
+ if (dir_err) {
+ ERR_PRINT("Can't create '" + dir_name + "'.");
+ unzClose(src_pkg_zip);
+ memdelete(tmp_app_path);
+ return ERR_CANT_CREATE;
+ }
+ }
+
+ /* write the file */
+ FileAccess *f = FileAccess::open(file, FileAccess::WRITE);
+ if (!f) {
+ ERR_PRINT("Can't write '" + file + "'.");
+ unzClose(src_pkg_zip);
+ memdelete(tmp_app_path);
+ return ERR_CANT_CREATE;
+ };
+ f->store_buffer(data.ptr(), data.size());
+ f->close();
+ memdelete(f);
+
+#if defined(OSX_ENABLED) || defined(X11_ENABLED)
+ if (is_execute) {
+ // we need execute rights on this file
+ chmod(file.utf8().get_data(), 0755);
+ }
+#endif
+ }
+
+ ret = unzGoToNextFile(src_pkg_zip);
+ }
+
+ /* we're done with our source zip */
+ unzClose(src_pkg_zip);
+
+ if (!found_library) {
+ ERR_PRINT("Requested template library '" + library_to_use + "' not found. It might be missing from your template archive.");
+ memdelete(tmp_app_path);
+ return ERR_FILE_NOT_FOUND;
+ }
+
+ // Copy project static libs to the project
+ Vector][> export_plugins = EditorExport::get_singleton()->get_export_plugins();
+ for (int i = 0; i < export_plugins.size(); i++) {
+ Vector project_static_libs = export_plugins[i]->get_tvos_project_static_libs();
+ for (int j = 0; j < project_static_libs.size(); j++) {
+ const String &static_lib_path = project_static_libs[j];
+ String dest_lib_file_path = dest_dir + static_lib_path.get_file();
+ Error lib_copy_err = tmp_app_path->copy(static_lib_path, dest_lib_file_path);
+ if (lib_copy_err != OK) {
+ ERR_PRINT("Can't copy '" + static_lib_path + "'.");
+ memdelete(tmp_app_path);
+ return lib_copy_err;
+ }
+ }
+ }
+
+ print_line("Exporting additional assets");
+ _export_additional_assets(dest_dir + binary_name, libraries, assets);
+ _add_assets_to_project(p_preset, project_file_data, assets);
+ String project_file_name = dest_dir + binary_name + ".xcodeproj/project.pbxproj";
+ FileAccess *f = FileAccess::open(project_file_name, FileAccess::WRITE);
+ if (!f) {
+ ERR_PRINT("Can't write '" + project_file_name + "'.");
+ return ERR_CANT_CREATE;
+ };
+ f->store_buffer(project_file_data.ptr(), project_file_data.size());
+ f->close();
+ memdelete(f);
+
+ return OK;
+}
+
+bool EditorExportPlatformTVOS::can_export(const Ref &p_preset, String &r_error, bool &r_missing_templates) const {
+ String err;
+ bool valid = false;
+
+ // Look for export templates (first official, and if defined custom templates).
+
+ bool dvalid = exists_export_template("tvos.zip", &err);
+ bool rvalid = dvalid; // Both in the same ZIP.
+
+ if (p_preset->get("custom_template/debug") != "") {
+ dvalid = FileAccess::exists(p_preset->get("custom_template/debug"));
+ if (!dvalid) {
+ err += TTR("Custom debug template not found.") + "\n";
+ }
+ }
+ if (p_preset->get("custom_template/release") != "") {
+ rvalid = FileAccess::exists(p_preset->get("custom_template/release"));
+ if (!rvalid) {
+ err += TTR("Custom release template not found.") + "\n";
+ }
+ }
+
+ valid = dvalid || rvalid;
+ r_missing_templates = !valid;
+
+ // Validate the rest of the configuration.
+
+ String identifier = p_preset->get("application/bundle_identifier");
+ String pn_err;
+ if (!is_package_name_valid(identifier, &pn_err)) {
+ err += TTR("Invalid Identifier:") + " " + pn_err + "\n";
+ valid = false;
+ }
+
+ String etc_error = test_etc2();
+ if (etc_error != String()) {
+ valid = false;
+ err += etc_error;
+ }
+
+ if (!err.is_empty())
+ r_error = err;
+
+ return valid;
+}
+
+EditorExportPlatformTVOS::EditorExportPlatformTVOS() {
+ Ref img = memnew(Image(_tvos_logo));
+ logo.instantiate();
+ logo->create_from_image(img);
+
+ plugins_changed.set();
+
+ check_for_changes_thread.start(_check_for_changes_poll_thread, this);
+}
+
+EditorExportPlatformTVOS::~EditorExportPlatformTVOS() {
+ quit_request.set();
+ check_for_changes_thread.wait_to_finish();
+}
+
+void register_tvos_exporter() {
+ Ref platform;
+ platform.instantiate();
+
+ EditorExport::get_singleton()->add_export_platform(platform);
+}
diff --git a/platform/tvos/export/export.h b/platform/tvos/export/export.h
new file mode 100644
index 000000000000..e645e40b44ca
--- /dev/null
+++ b/platform/tvos/export/export.h
@@ -0,0 +1,36 @@
+/*************************************************************************/
+/* export.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef TVOS_EXPORT_H
+#define TVOS_EXPORT_H
+
+void register_tvos_exporter();
+
+#endif // TVOS_EXPORT_H
diff --git a/platform/tvos/export/godot_plugin_config.h b/platform/tvos/export/godot_plugin_config.h
new file mode 100644
index 000000000000..7d74c42474f4
--- /dev/null
+++ b/platform/tvos/export/godot_plugin_config.h
@@ -0,0 +1,363 @@
+/*************************************************************************/
+/* godot_plugin_config.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef GODOT_PLUGIN_CONFIG_H
+#define GODOT_PLUGIN_CONFIG_H
+
+#include "core/error/error_list.h"
+#include "core/io/config_file.h"
+#include "core/string/ustring.h"
+
+/*
+ The `config` section and fields are required and defined as follow:
+- **name**: name of the plugin
+- **binary**: path to static `.a` library
+The `dependencies` and fields are optional.
+- **linked**: dependencies that should only be linked.
+- **embedded**: dependencies that should be linked and embedded into application.
+- **system**: system dependencies that should be linked.
+- **capabilities**: capabilities that would be used for `UIRequiredDeviceCapabilities` options in Info.plist file.
+- **files**: files that would be copied into application
+The `plist` section are optional.
+- **key**: key and value that would be added in Info.plist file.
+ */
+
+struct PluginConfigTVOS {
+ inline static const char *PLUGIN_CONFIG_EXT = ".gdatvp";
+
+ inline static const char *CONFIG_SECTION = "config";
+ inline static const char *CONFIG_NAME_KEY = "name";
+ inline static const char *CONFIG_BINARY_KEY = "binary";
+ inline static const char *CONFIG_INITIALIZE_KEY = "initialization";
+ inline static const char *CONFIG_DEINITIALIZE_KEY = "deinitialization";
+
+ inline static const char *DEPENDENCIES_SECTION = "dependencies";
+ inline static const char *DEPENDENCIES_LINKED_KEY = "linked";
+ inline static const char *DEPENDENCIES_EMBEDDED_KEY = "embedded";
+ inline static const char *DEPENDENCIES_SYSTEM_KEY = "system";
+ inline static const char *DEPENDENCIES_CAPABILITIES_KEY = "capabilities";
+ inline static const char *DEPENDENCIES_FILES_KEY = "files";
+ inline static const char *DEPENDENCIES_LINKER_FLAGS = "linker_flags";
+
+ inline static const char *PLIST_SECTION = "plist";
+
+ enum PlistItemType {
+ UNKNOWN,
+ STRING,
+ INTEGER,
+ BOOLEAN,
+ RAW,
+ STRING_INPUT,
+ };
+
+ struct PlistItem {
+ PlistItemType type;
+ String value;
+ };
+
+ // Set to true when the config file is properly loaded.
+ bool valid_config = false;
+ bool supports_targets = false;
+ // Unix timestamp of last change to this plugin.
+ uint64_t last_updated = 0;
+
+ // Required config section
+ String name;
+ String binary;
+ String initialization_method;
+ String deinitialization_method;
+
+ // Optional dependencies section
+ Vector linked_dependencies;
+ Vector embedded_dependencies;
+ Vector system_dependencies;
+
+ Vector files_to_copy;
+ Vector capabilities;
+
+ Vector linker_flags;
+
+ // Optional plist section
+ // String value is default value.
+ // Currently supports `string`, `boolean`, `integer`, `raw`, `string_input` types
+ // : =
+ HashMap plist;
+};
+
+static inline String resolve_local_dependency_path(String plugin_config_dir, String dependency_path) {
+ String absolute_path;
+
+ if (dependency_path.is_empty()) {
+ return absolute_path;
+ }
+
+ if (dependency_path.is_absolute_path()) {
+ return dependency_path;
+ }
+
+ String res_path = ProjectSettings::get_singleton()->globalize_path("res://");
+ absolute_path = plugin_config_dir.plus_file(dependency_path);
+
+ return absolute_path.replace(res_path, "res://");
+}
+
+static inline String resolve_system_dependency_path(String dependency_path) {
+ String absolute_path;
+
+ if (dependency_path.is_empty()) {
+ return absolute_path;
+ }
+
+ if (dependency_path.is_absolute_path()) {
+ return dependency_path;
+ }
+
+ String system_path = "/System/Library/Frameworks";
+
+ return system_path.plus_file(dependency_path);
+}
+
+static inline Vector resolve_local_dependencies(String plugin_config_dir, Vector p_paths) {
+ Vector paths;
+
+ for (int i = 0; i < p_paths.size(); i++) {
+ String path = resolve_local_dependency_path(plugin_config_dir, p_paths[i]);
+
+ if (path.is_empty()) {
+ continue;
+ }
+
+ paths.push_back(path);
+ }
+
+ return paths;
+}
+
+static inline Vector resolve_system_dependencies(Vector p_paths) {
+ Vector paths;
+
+ for (int i = 0; i < p_paths.size(); i++) {
+ String path = resolve_system_dependency_path(p_paths[i]);
+
+ if (path.is_empty()) {
+ continue;
+ }
+
+ paths.push_back(path);
+ }
+
+ return paths;
+}
+
+static inline bool validate_plugin(PluginConfigTVOS &plugin_config) {
+ bool valid_name = !plugin_config.name.is_empty();
+ bool valid_binary_name = !plugin_config.binary.is_empty();
+ bool valid_initialize = !plugin_config.initialization_method.is_empty();
+ bool valid_deinitialize = !plugin_config.deinitialization_method.is_empty();
+
+ bool fields_value = valid_name && valid_binary_name && valid_initialize && valid_deinitialize;
+
+ if (!fields_value) {
+ return false;
+ }
+
+ String plugin_extension = plugin_config.binary.get_extension().to_lower();
+
+ if ((plugin_extension == "a" && FileAccess::exists(plugin_config.binary)) ||
+ (plugin_extension == "xcframework" && DirAccess::exists(plugin_config.binary))) {
+ plugin_config.valid_config = true;
+ plugin_config.supports_targets = false;
+ } else {
+ String file_path = plugin_config.binary.get_base_dir();
+ String file_name = plugin_config.binary.get_basename().get_file();
+ String file_extension = plugin_config.binary.get_extension();
+ String release_file_name = file_path.plus_file(file_name + ".release." + file_extension);
+ String debug_file_name = file_path.plus_file(file_name + ".debug." + file_extension);
+
+ if ((plugin_extension == "a" && FileAccess::exists(release_file_name) && FileAccess::exists(debug_file_name)) ||
+ (plugin_extension == "xcframework" && DirAccess::exists(release_file_name) && DirAccess::exists(debug_file_name))) {
+ plugin_config.valid_config = true;
+ plugin_config.supports_targets = true;
+ }
+ }
+
+ return plugin_config.valid_config;
+}
+
+static inline String get_plugin_main_binary(PluginConfigTVOS &plugin_config, bool p_debug) {
+ if (!plugin_config.supports_targets) {
+ return plugin_config.binary;
+ }
+
+ String plugin_binary_dir = plugin_config.binary.get_base_dir();
+ String plugin_name_prefix = plugin_config.binary.get_basename().get_file();
+ String plugin_extension = plugin_config.binary.get_extension();
+ String plugin_file = plugin_name_prefix + "." + (p_debug ? "debug" : "release") + "." + plugin_extension;
+
+ return plugin_binary_dir.plus_file(plugin_file);
+}
+
+static inline uint64_t get_plugin_modification_time(const PluginConfigTVOS &plugin_config, const String &config_path) {
+ uint64_t last_updated = FileAccess::get_modified_time(config_path);
+
+ if (!plugin_config.supports_targets) {
+ last_updated = MAX(last_updated, FileAccess::get_modified_time(plugin_config.binary));
+ } else {
+ String file_path = plugin_config.binary.get_base_dir();
+ String file_name = plugin_config.binary.get_basename().get_file();
+ String release_file_name = file_path.plus_file(file_name + ".release.a");
+ String debug_file_name = file_path.plus_file(file_name + ".debug.a");
+
+ last_updated = MAX(last_updated, FileAccess::get_modified_time(release_file_name));
+ last_updated = MAX(last_updated, FileAccess::get_modified_time(debug_file_name));
+ }
+
+ return last_updated;
+}
+
+static inline PluginConfigTVOS load_plugin_config(Ref config_file, const String &path) {
+ PluginConfigTVOS plugin_config = {};
+
+ if (!config_file.is_valid()) {
+ return plugin_config;
+ }
+
+ config_file->clear();
+
+ Error err = config_file->load(path);
+
+ if (err != OK) {
+ return plugin_config;
+ }
+
+ String config_base_dir = path.get_base_dir();
+
+ plugin_config.name = config_file->get_value(PluginConfigTVOS::CONFIG_SECTION, PluginConfigTVOS::CONFIG_NAME_KEY, String());
+ plugin_config.initialization_method = config_file->get_value(PluginConfigTVOS::CONFIG_SECTION, PluginConfigTVOS::CONFIG_INITIALIZE_KEY, String());
+ plugin_config.deinitialization_method = config_file->get_value(PluginConfigTVOS::CONFIG_SECTION, PluginConfigTVOS::CONFIG_DEINITIALIZE_KEY, String());
+
+ String binary_path = config_file->get_value(PluginConfigTVOS::CONFIG_SECTION, PluginConfigTVOS::CONFIG_BINARY_KEY, String());
+ plugin_config.binary = resolve_local_dependency_path(config_base_dir, binary_path);
+
+ if (config_file->has_section(PluginConfigTVOS::DEPENDENCIES_SECTION)) {
+ Vector linked_dependencies = config_file->get_value(PluginConfigTVOS::DEPENDENCIES_SECTION, PluginConfigTVOS::DEPENDENCIES_LINKED_KEY, Vector());
+ Vector embedded_dependencies = config_file->get_value(PluginConfigTVOS::DEPENDENCIES_SECTION, PluginConfigTVOS::DEPENDENCIES_EMBEDDED_KEY, Vector());
+ Vector system_dependencies = config_file->get_value(PluginConfigTVOS::DEPENDENCIES_SECTION, PluginConfigTVOS::DEPENDENCIES_SYSTEM_KEY, Vector());
+ Vector files = config_file->get_value(PluginConfigTVOS::DEPENDENCIES_SECTION, PluginConfigTVOS::DEPENDENCIES_FILES_KEY, Vector());
+
+ plugin_config.linked_dependencies = resolve_local_dependencies(config_base_dir, linked_dependencies);
+ plugin_config.embedded_dependencies = resolve_local_dependencies(config_base_dir, embedded_dependencies);
+ plugin_config.system_dependencies = resolve_system_dependencies(system_dependencies);
+
+ plugin_config.files_to_copy = resolve_local_dependencies(config_base_dir, files);
+
+ plugin_config.capabilities = config_file->get_value(PluginConfigTVOS::DEPENDENCIES_SECTION, PluginConfigTVOS::DEPENDENCIES_CAPABILITIES_KEY, Vector());
+
+ plugin_config.linker_flags = config_file->get_value(PluginConfigTVOS::DEPENDENCIES_SECTION, PluginConfigTVOS::DEPENDENCIES_LINKER_FLAGS, Vector());
+ }
+
+ if (config_file->has_section(PluginConfigTVOS::PLIST_SECTION)) {
+ List keys;
+ config_file->get_section_keys(PluginConfigTVOS::PLIST_SECTION, &keys);
+
+ for (int i = 0; i < keys.size(); i++) {
+ Vector key_components = keys[i].split(":");
+
+ String key_value = "";
+ PluginConfigTVOS::PlistItemType key_type = PluginConfigTVOS::PlistItemType::UNKNOWN;
+
+ if (key_components.size() == 1) {
+ key_value = key_components[0];
+ key_type = PluginConfigTVOS::PlistItemType::STRING;
+ } else if (key_components.size() == 2) {
+ key_value = key_components[0];
+
+ if (key_components[1].to_lower() == "string") {
+ key_type = PluginConfigTVOS::PlistItemType::STRING;
+ } else if (key_components[1].to_lower() == "integer") {
+ key_type = PluginConfigTVOS::PlistItemType::INTEGER;
+ } else if (key_components[1].to_lower() == "boolean") {
+ key_type = PluginConfigTVOS::PlistItemType::BOOLEAN;
+ } else if (key_components[1].to_lower() == "raw") {
+ key_type = PluginConfigTVOS::PlistItemType::RAW;
+ } else if (key_components[1].to_lower() == "string_input") {
+ key_type = PluginConfigTVOS::PlistItemType::STRING_INPUT;
+ }
+ }
+
+ if (key_value.is_empty() || key_type == PluginConfigTVOS::PlistItemType::UNKNOWN) {
+ continue;
+ }
+
+ String value;
+
+ switch (key_type) {
+ case PluginConfigTVOS::PlistItemType::STRING: {
+ String raw_value = config_file->get_value(PluginConfigTVOS::PLIST_SECTION, keys[i], String());
+ value = "" + raw_value + "";
+ } break;
+ case PluginConfigTVOS::PlistItemType::INTEGER: {
+ int raw_value = config_file->get_value(PluginConfigTVOS::PLIST_SECTION, keys[i], 0);
+ Dictionary value_dictionary;
+ String value_format = "$value";
+ value_dictionary["value"] = raw_value;
+ value = value_format.format(value_dictionary, "$_");
+ } break;
+ case PluginConfigTVOS::PlistItemType::BOOLEAN:
+ if (config_file->get_value(PluginConfigTVOS::PLIST_SECTION, keys[i], false)) {
+ value = "";
+ } else {
+ value = "";
+ }
+ break;
+ case PluginConfigTVOS::PlistItemType::RAW: {
+ String raw_value = config_file->get_value(PluginConfigTVOS::PLIST_SECTION, keys[i], String());
+ value = raw_value;
+ } break;
+ case PluginConfigTVOS::PlistItemType::STRING_INPUT: {
+ String raw_value = config_file->get_value(PluginConfigTVOS::PLIST_SECTION, keys[i], String());
+ value = raw_value;
+ } break;
+ default:
+ continue;
+ }
+
+ plugin_config.plist[key_value] = PluginConfigTVOS::PlistItem{ key_type, value };
+ }
+ }
+
+ if (validate_plugin(plugin_config)) {
+ plugin_config.last_updated = get_plugin_modification_time(plugin_config, path);
+ }
+
+ return plugin_config;
+}
+
+#endif // GODOT_PLUGIN_CONFIG_H
diff --git a/platform/tvos/godot_app_delegate.h b/platform/tvos/godot_app_delegate.h
new file mode 100644
index 000000000000..f474b5f50f57
--- /dev/null
+++ b/platform/tvos/godot_app_delegate.h
@@ -0,0 +1,35 @@
+/*************************************************************************/
+/* godot_app_delegate.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#import "platform/uikit/uikit_app_delegate.h"
+
+@interface GodotApplicalitionDelegate : UIKitApplicalitionDelegate
+
+@end
diff --git a/platform/tvos/godot_app_delegate.m b/platform/tvos/godot_app_delegate.m
new file mode 100644
index 000000000000..b2c4859981a2
--- /dev/null
+++ b/platform/tvos/godot_app_delegate.m
@@ -0,0 +1,45 @@
+/*************************************************************************/
+/* godot_app_delegate.m */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#import "godot_app_delegate.h"
+
+#import "app_delegate.h"
+
+@interface GodotApplicalitionDelegate ()
+
+@end
+
+@implementation GodotApplicalitionDelegate
+
++ (void)load {
+ [self addService:[AppDelegate new]];
+}
+
+@end
diff --git a/platform/tvos/godot_tvos.mm b/platform/tvos/godot_tvos.mm
new file mode 100644
index 000000000000..24b887725bcd
--- /dev/null
+++ b/platform/tvos/godot_tvos.mm
@@ -0,0 +1,124 @@
+/*************************************************************************/
+/* godot_tvos.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "core/string/ustring.h"
+#include "main/main.h"
+#include "os_tvos.h"
+
+#include
+#include
+#include
+
+static OSAppleTV *os = NULL;
+
+int add_path(int p_argc, char **p_args) {
+ NSString *str = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_path"];
+ if (!str) {
+ return p_argc;
+ }
+
+ p_args[p_argc++] = (char *)"--path";
+ p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding];
+ p_args[p_argc] = NULL;
+
+ return p_argc;
+}
+
+int add_cmdline(int p_argc, char **p_args) {
+ NSArray *arr = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_cmdline"];
+ if (!arr) {
+ return p_argc;
+ }
+
+ for (NSUInteger i = 0; i < [arr count]; i++) {
+ NSString *str = [arr objectAtIndex:i];
+ if (!str) {
+ continue;
+ }
+ p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding];
+ }
+
+ p_args[p_argc] = NULL;
+
+ return p_argc;
+}
+
+int appletv_main(int argc, char **argv, String data_dir, String cache_dir) {
+ size_t len = strlen(argv[0]);
+
+ while (len--) {
+ if (argv[0][len] == '/') {
+ break;
+ }
+ }
+
+ if (len >= 0) {
+ char path[512];
+ memcpy(path, argv[0], len > sizeof(path) ? sizeof(path) : len);
+ path[len] = 0;
+ printf("Path: %s\n", path);
+ chdir(path);
+ }
+
+ printf("godot_appletv %s\n", argv[0]);
+ char cwd[512];
+ getcwd(cwd, sizeof(cwd));
+ printf("cwd %s\n", cwd);
+ os = new OSAppleTV(data_dir, cache_dir);
+
+ // We must override main when testing is enabled
+ TEST_MAIN_OVERRIDE
+
+ char *fargv[64];
+ for (int i = 0; i < argc; i++) {
+ fargv[i] = argv[i];
+ };
+ fargv[argc] = nullptr;
+ argc = add_path(argc, fargv);
+ argc = add_cmdline(argc, fargv);
+
+ printf("os created\n");
+
+ Error err = Main::setup(fargv[0], argc - 1, &fargv[1], false);
+ printf("setup %i\n", err);
+ if (err != OK) {
+ return 255;
+ }
+
+ os->initialize_modules();
+
+ return 0;
+}
+
+void appletv_finish() {
+ printf("appletv_finish\n");
+ Main::cleanup();
+ delete os;
+}
diff --git a/platform/tvos/godot_view.h b/platform/tvos/godot_view.h
new file mode 100644
index 000000000000..1062241d3e61
--- /dev/null
+++ b/platform/tvos/godot_view.h
@@ -0,0 +1,35 @@
+/*************************************************************************/
+/* godot_view.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#import "platform/uikit/uikit_view.h"
+
+@interface GodotView : UIKitView
+
+@end
diff --git a/platform/tvos/godot_view.mm b/platform/tvos/godot_view.mm
new file mode 100644
index 000000000000..129a2e14067e
--- /dev/null
+++ b/platform/tvos/godot_view.mm
@@ -0,0 +1,135 @@
+/*************************************************************************/
+/* godot_view.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#import "godot_view.h"
+#include "core/os/keyboard.h"
+#include "core/string/ustring.h"
+#include "display_server_tvos.h"
+#import "godot_view_gesture_recognizer.h"
+#import "godot_view_renderer.h"
+#import "os_tvos.h"
+
+@interface GodotView ()
+
+@property(strong, nonatomic) GodotViewGestureRecognizer *delayGestureRecognizer;
+
+@end
+
+@implementation GodotView
+
+- (instancetype)initWithCoder:(NSCoder *)coder {
+ self = [super initWithCoder:coder];
+
+ if (self) {
+ [self godot_commonInit];
+ }
+
+ return self;
+}
+
+- (instancetype)initWithFrame:(CGRect)frame {
+ self = [super initWithFrame:frame];
+
+ if (self) {
+ [self godot_commonInit];
+ }
+
+ return self;
+}
+
+- (void)dealloc {
+ if (self.delayGestureRecognizer) {
+ self.delayGestureRecognizer = nil;
+ }
+}
+
+- (void)godot_commonInit {
+ // Initialize delay gesture recognizer
+ GodotViewGestureRecognizer *gestureRecognizer = [[GodotViewGestureRecognizer alloc] init];
+ self.delayGestureRecognizer = gestureRecognizer;
+ [self addGestureRecognizer:self.delayGestureRecognizer];
+}
+
+// MARK: - Input
+
+// MARK: Menu Button
+
+- (void)pressesBegan:(NSSet *)presses withEvent:(UIPressesEvent *)event {
+ if (!self.delayGestureRecognizer.overridesRemoteButtons) {
+ return [super pressesEnded:presses withEvent:event];
+ }
+
+ NSArray *tlist = [event.allPresses allObjects];
+
+ for (UIPress *press in tlist) {
+ if ([presses containsObject:press] && press.type == UIPressTypeMenu) {
+ int joy_id = OSAppleTV::get_singleton()->joy_id_for_name("Remote");
+ Input::get_singleton()->joy_button(joy_id, JoyButton::START, true);
+ } else {
+ [super pressesBegan:presses withEvent:event];
+ }
+ }
+}
+
+- (void)pressesEnded:(NSSet *)presses withEvent:(UIPressesEvent *)event {
+ if (!self.delayGestureRecognizer.overridesRemoteButtons) {
+ return [super pressesEnded:presses withEvent:event];
+ }
+
+ NSArray *tlist = [presses allObjects];
+
+ for (UIPress *press in tlist) {
+ if ([presses containsObject:press] && press.type == UIPressTypeMenu) {
+ int joy_id = OSAppleTV::get_singleton()->joy_id_for_name("Remote");
+ Input::get_singleton()->joy_button(joy_id, JoyButton::START, false);
+ } else {
+ [super pressesEnded:presses withEvent:event];
+ }
+ }
+}
+
+- (void)pressesCancelled:(NSSet *)presses withEvent:(UIPressesEvent *)event {
+ if (!self.delayGestureRecognizer.overridesRemoteButtons) {
+ return [super pressesEnded:presses withEvent:event];
+ }
+
+ NSArray *tlist = [event.allPresses allObjects];
+
+ for (UIPress *press in tlist) {
+ if ([presses containsObject:press] && press.type == UIPressTypeMenu) {
+ int joy_id = OSAppleTV::get_singleton()->joy_id_for_name("Remote");
+ Input::get_singleton()->joy_button(joy_id, JoyButton::START, false);
+ } else {
+ [super pressesCancelled:presses withEvent:event];
+ }
+ }
+}
+
+@end
diff --git a/platform/tvos/godot_view_controller.h b/platform/tvos/godot_view_controller.h
new file mode 100644
index 000000000000..3786cf63698e
--- /dev/null
+++ b/platform/tvos/godot_view_controller.h
@@ -0,0 +1,42 @@
+/*************************************************************************/
+/* godot_view_controller.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#import "platform/uikit/uikit_view_controller.h"
+#import
+
+@class GodotView;
+@class GodotKeyboardInputView;
+
+@interface GodotViewController : UIKitViewController
+
+@property(nonatomic, readonly, strong) GodotView *godotView;
+@property(nonatomic, readonly, strong) GodotKeyboardInputView *keyboardView;
+
+@end
diff --git a/platform/tvos/godot_view_controller.mm b/platform/tvos/godot_view_controller.mm
new file mode 100644
index 000000000000..4020dac723b6
--- /dev/null
+++ b/platform/tvos/godot_view_controller.mm
@@ -0,0 +1,116 @@
+/*************************************************************************/
+/* godot_view_controller.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#import "godot_view_controller.h"
+#include "core/config/project_settings.h"
+#include "display_server_tvos.h"
+#import "godot_view.h"
+#import "godot_view_renderer.h"
+#import "keyboard_input_view.h"
+#include "os_tvos.h"
+
+#import
+#import
+
+@interface GodotViewController ()
+
+@property(strong, nonatomic) GodotViewRenderer *renderer;
+@property(strong, nonatomic) GodotKeyboardInputView *keyboardView;
+
+@end
+
+@implementation GodotViewController
+
+- (GodotView *)godotView {
+ return (GodotView *)self.view;
+}
+
+- (void)loadView {
+ GodotView *view = [[GodotView alloc] init];
+ GodotViewRenderer *renderer = [[GodotViewRenderer alloc] init];
+
+ self.renderer = renderer;
+ self.view = view;
+
+ view.renderer = self.renderer;
+ view.delegate = self;
+}
+
+- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
+ self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
+
+ if (self) {
+ [self godot_commonInit];
+ }
+
+ return self;
+}
+
+- (instancetype)initWithCoder:(NSCoder *)coder {
+ self = [super initWithCoder:coder];
+
+ if (self) {
+ [self godot_commonInit];
+ }
+
+ return self;
+}
+
+- (void)godot_commonInit {
+ // Initialize view controller values.
+}
+
+- (void)didReceiveMemoryWarning {
+ [super didReceiveMemoryWarning];
+ printf("*********** did receive memory warning!\n");
+}
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+
+ [self observeKeyboard];
+}
+
+- (void)observeKeyboard {
+ printf("******** setting up keyboard input view\n");
+ self.keyboardView = [GodotKeyboardInputView new];
+ [self.view addSubview:self.keyboardView];
+}
+
+- (void)dealloc {
+ [self.keyboardView removeFromSuperview];
+ self.keyboardView = nil;
+
+ self.renderer = nil;
+
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+}
+
+@end
diff --git a/platform/tvos/godot_view_gesture_recognizer.h b/platform/tvos/godot_view_gesture_recognizer.h
new file mode 100644
index 000000000000..da9e87d3ebbf
--- /dev/null
+++ b/platform/tvos/godot_view_gesture_recognizer.h
@@ -0,0 +1,47 @@
+/*************************************************************************/
+/* godot_view_gesture_recognizer.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+// GLViewGestureRecognizer allows iOS gestures to work correctly by
+// emulating UIScrollView's UIScrollViewDelayedTouchesBeganGestureRecognizer.
+// It catches all gestures incoming to UIView and delays them for 150ms
+// (the same value used by UIScrollViewDelayedTouchesBeganGestureRecognizer)
+// If touch cancellation or end message is fired it fires delayed
+// begin touch immediately as well as last touch signal
+
+#import
+
+@interface GodotViewGestureRecognizer : UIGestureRecognizer
+
+@property(nonatomic, readonly, assign) NSTimeInterval delayTimeInterval;
+@property(nonatomic, readonly, assign) BOOL overridesRemoteButtons;
+
+- (instancetype)init;
+
+@end
diff --git a/platform/tvos/godot_view_gesture_recognizer.mm b/platform/tvos/godot_view_gesture_recognizer.mm
new file mode 100644
index 000000000000..d87770c73edb
--- /dev/null
+++ b/platform/tvos/godot_view_gesture_recognizer.mm
@@ -0,0 +1,124 @@
+/*************************************************************************/
+/* godot_view_gesture_recognizer.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#import "godot_view_gesture_recognizer.h"
+
+#include "core/config/project_settings.h"
+
+#import "os_tvos.h"
+
+@interface GodotViewGestureRecognizer ()
+
+// Timer used to delay end press message.
+@property(nonatomic, readwrite, strong) NSTimer *delayTimer;
+
+// Delayed touch parameters
+@property(nonatomic, readwrite, copy) NSSet *delayedPresses;
+@property(nonatomic, readwrite, strong) UIPressesEvent *delayedEvent;
+
+@property(nonatomic, readwrite, assign) NSTimeInterval delayTimeInterval;
+
+@end
+
+@implementation GodotViewGestureRecognizer
+
+- (instancetype)init {
+ self = [super init];
+
+ self.delayTimeInterval = GLOBAL_GET("input_devices/pointing/tvos/press_end_delay");
+ self.allowedPressTypes = @[ @(UIPressTypeMenu) ];
+
+ return self;
+}
+
+- (void)delayPresses:(NSSet *)presses andEvent:(UIPressesEvent *)event {
+ [self.delayTimer fire];
+
+ self.delayedPresses = presses;
+ self.delayedEvent = event;
+
+ self.delayTimer = [NSTimer scheduledTimerWithTimeInterval:self.delayTimeInterval target:self selector:@selector(fireDelayedPress:) userInfo:nil repeats:NO];
+}
+
+- (void)fireDelayedPress:(id)timer {
+ [self.delayTimer invalidate];
+ self.delayTimer = nil;
+
+ if (self.delayedPresses) {
+ [self.view pressesEnded:self.delayedPresses withEvent:self.delayedEvent];
+ }
+
+ self.delayedPresses = nil;
+ self.delayedEvent = nil;
+}
+
+- (BOOL)overridesRemoteButtons {
+ return OSAppleTV::get_singleton()->get_overrides_menu_button();
+}
+
+- (BOOL)shouldReceiveEvent:(UIEvent *)event {
+ return self.overridesRemoteButtons;
+}
+
+- (void)pressesBegan:(NSSet *)presses withEvent:(UIPressesEvent *)event {
+ [self.delayTimer fire];
+ [self.view pressesBegan:presses withEvent:event];
+}
+
+- (void)pressesEnded:(NSSet *)presses withEvent:(UIPressesEvent *)event {
+ NSSet *cleared = [self copyClearedPresses:presses type:UIPressTypeMenu phase:UIPressPhaseEnded];
+ [self delayPresses:cleared andEvent:event];
+}
+
+- (void)pressesCancelled:(NSSet *)presses withEvent:(UIPressesEvent *)event {
+ [self cancelDelayTimer];
+ [self.view pressesCancelled:presses withEvent:event];
+};
+
+- (void)cancelDelayTimer {
+ [self.delayTimer invalidate];
+ self.delayTimer = nil;
+ self.delayedPresses = nil;
+ self.delayedEvent = nil;
+}
+
+- (NSSet *)copyClearedPresses:(NSSet *)presses type:(UIPressType)phaseToSave phase:(UIPressPhase)phase {
+ NSMutableSet *cleared = [NSMutableSet new];
+
+ for (UIPress *press in presses) {
+ if (press.type == phaseToSave && press.phase == phase) {
+ [cleared addObject:press];
+ }
+ }
+
+ return cleared;
+}
+
+@end
diff --git a/platform/tvos/godot_view_renderer.h b/platform/tvos/godot_view_renderer.h
new file mode 100644
index 000000000000..30cb80b54bc3
--- /dev/null
+++ b/platform/tvos/godot_view_renderer.h
@@ -0,0 +1,36 @@
+/*************************************************************************/
+/* godot_view_renderer.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#import "platform/uikit/uikit_view_renderer.h"
+#import
+
+@interface GodotViewRenderer : UIKitViewRenderer
+
+@end
diff --git a/platform/tvos/godot_view_renderer.mm b/platform/tvos/godot_view_renderer.mm
new file mode 100644
index 000000000000..b80f9e8f84c5
--- /dev/null
+++ b/platform/tvos/godot_view_renderer.mm
@@ -0,0 +1,60 @@
+/*************************************************************************/
+/* godot_view_renderer.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#import "godot_view_renderer.h"
+#include "core/config/project_settings.h"
+#include "core/os/keyboard.h"
+#import "display_server_tvos.h"
+#include "main/main.h"
+#include "os_tvos.h"
+#include "servers/audio_server.h"
+
+#import
+
+@interface GodotViewRenderer ()
+
+@end
+
+@implementation GodotViewRenderer
+
+- (BOOL)startUIKitPlatform {
+ OSAppleTV::get_singleton()->start();
+ return YES;
+}
+
+- (void)renderOnView:(UIView *)view {
+ if (!OSAppleTV::get_singleton()) {
+ return;
+ }
+
+ OSAppleTV::get_singleton()->iterate();
+}
+
+@end
diff --git a/platform/tvos/keyboard_input_view.h b/platform/tvos/keyboard_input_view.h
new file mode 100644
index 000000000000..b9cae4de90c2
--- /dev/null
+++ b/platform/tvos/keyboard_input_view.h
@@ -0,0 +1,37 @@
+/*************************************************************************/
+/* keyboard_input_view.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#import
+
+@interface GodotKeyboardInputView : UITextField
+
+- (BOOL)becomeFirstResponderWithString:(NSString *)existingString multiline:(BOOL)flag cursorStart:(NSInteger)start cursorEnd:(NSInteger)end;
+
+@end
diff --git a/platform/tvos/keyboard_input_view.mm b/platform/tvos/keyboard_input_view.mm
new file mode 100644
index 000000000000..2289bf782082
--- /dev/null
+++ b/platform/tvos/keyboard_input_view.mm
@@ -0,0 +1,217 @@
+/*************************************************************************/
+/* keyboard_input_view.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#import "keyboard_input_view.h"
+
+#include "core/os/keyboard.h"
+#include "display_server_tvos.h"
+#include "os_tvos.h"
+
+@interface GodotKeyboardInputView ()
+
+@property(nonatomic, copy) NSString *previousText;
+@property(nonatomic, assign) NSRange previousSelectedRange;
+
+@end
+
+@implementation GodotKeyboardInputView
+
+- (instancetype)initWithCoder:(NSCoder *)coder {
+ self = [super initWithCoder:coder];
+
+ if (self) {
+ [self godot_commonInit];
+ }
+
+ return self;
+}
+
+- (instancetype)initWithFrame:(CGRect)frame {
+ self = [super initWithFrame:frame];
+
+ if (self) {
+ [self godot_commonInit];
+ }
+
+ return self;
+}
+
+- (void)godot_commonInit {
+ self.hidden = YES;
+ self.delegate = self;
+
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(observeTextChange:)
+ name:UITextFieldTextDidChangeNotification
+ object:self];
+}
+
+- (void)setSelectedRange:(NSRange)range {
+ UITextPosition *beginning = self.beginningOfDocument;
+ UITextPosition *start = [self positionFromPosition:beginning offset:range.location];
+ UITextPosition *end = [self positionFromPosition:start offset:range.length];
+ UITextRange *textRange = [self textRangeFromPosition:start toPosition:end];
+
+ self.selectedTextRange = textRange;
+}
+
+- (NSRange)selectedRange {
+ UITextPosition *beginning = self.beginningOfDocument;
+
+ UITextRange *selectedRange = self.selectedTextRange;
+ UITextPosition *selectionStart = selectedRange.start;
+ UITextPosition *selectionEnd = selectedRange.end;
+
+ const NSInteger location = [self offsetFromPosition:beginning toPosition:selectionStart];
+ const NSInteger length = [self offsetFromPosition:selectionStart toPosition:selectionEnd];
+
+ return NSMakeRange(location, length);
+}
+
+- (void)dealloc {
+ self.delegate = nil;
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+}
+
+// MARK: Keyboard
+
+- (BOOL)canBecomeFirstResponder {
+ return YES;
+}
+
+- (BOOL)becomeFirstResponderWithString:(NSString *)existingString multiline:(BOOL)flag cursorStart:(NSInteger)start cursorEnd:(NSInteger)end {
+ self.text = existingString;
+ self.previousText = existingString;
+
+ NSRange textRange;
+
+ // Either a simple cursor or a selection.
+ if (end > 0) {
+ textRange = NSMakeRange(start, end - start);
+ } else {
+ textRange = NSMakeRange(start, 0);
+ }
+
+ self.selectedRange = textRange;
+ self.previousSelectedRange = textRange;
+
+ return [self becomeFirstResponder];
+}
+
+- (BOOL)resignFirstResponder {
+ self.text = nil;
+ self.previousText = nil;
+ return [super resignFirstResponder];
+}
+
+// MARK: OS Messages
+
+- (void)deleteText:(NSInteger)charactersToDelete {
+ for (int i = 0; i < charactersToDelete; i++) {
+ DisplayServerAppleTV::get_singleton()->key(Key::BACKSPACE, true);
+ DisplayServerAppleTV::get_singleton()->key(Key::BACKSPACE, false);
+ }
+}
+
+- (void)enterText:(NSString *)substring {
+ String characters;
+ characters.parse_utf8([substring UTF8String]);
+
+ for (int i = 0; i < characters.size(); i++) {
+ int character = characters[i];
+
+ switch (character) {
+ case 10:
+ character = (int)Key::ENTER;
+ break;
+ case 8198:
+ character = (int)Key::SPACE;
+ break;
+ default:
+ break;
+ }
+
+ DisplayServerAppleTV::get_singleton()->key((Key)character, true);
+ DisplayServerAppleTV::get_singleton()->key((Key)character, false);
+ }
+}
+
+// MARK: Observer
+
+- (void)observeTextChange:(NSNotification *)notification {
+ if (notification.object != self) {
+ return;
+ }
+
+ if (self.previousSelectedRange.length == 0) {
+ // We are deleting all text before cursor if no range was selected.
+ // This way any inserted or changed text will be updated.
+ NSString *substringToDelete = [self.previousText substringToIndex:self.previousSelectedRange.location];
+ [self deleteText:substringToDelete.length];
+ } else {
+ // If text was previously selected
+ // we are sending only one `backspace`.
+ // It will remove all text from text input.
+ [self deleteText:1];
+ }
+
+ NSString *substringToEnter;
+
+ if (self.selectedRange.length == 0) {
+ // If previous cursor had a selection
+ // we have to calculate an inserted text.
+ if (self.previousSelectedRange.length != 0) {
+ NSInteger rangeEnd = self.selectedRange.location + self.selectedRange.length;
+ NSInteger rangeStart = MIN(self.previousSelectedRange.location, self.selectedRange.location);
+ NSInteger rangeLength = MAX(0, rangeEnd - rangeStart);
+
+ NSRange calculatedRange;
+
+ if (rangeLength >= 0) {
+ calculatedRange = NSMakeRange(rangeStart, rangeLength);
+ } else {
+ calculatedRange = NSMakeRange(rangeStart, 0);
+ }
+
+ substringToEnter = [self.text substringWithRange:calculatedRange];
+ } else {
+ substringToEnter = [self.text substringToIndex:self.selectedRange.location];
+ }
+ } else {
+ substringToEnter = [self.text substringWithRange:self.selectedRange];
+ }
+
+ [self enterText:substringToEnter];
+
+ self.previousText = self.text;
+ self.previousSelectedRange = self.selectedRange;
+}
+
+@end
diff --git a/platform/tvos/logo.png b/platform/tvos/logo.png
new file mode 100644
index 000000000000..012e75dbe7f4
Binary files /dev/null and b/platform/tvos/logo.png differ
diff --git a/platform/tvos/main.m b/platform/tvos/main.m
new file mode 100644
index 000000000000..acfa7ab7319a
--- /dev/null
+++ b/platform/tvos/main.m
@@ -0,0 +1,56 @@
+/*************************************************************************/
+/* main.m */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#import "godot_app_delegate.h"
+
+#import
+#include
+
+int gargc;
+char **gargv;
+
+int main(int argc, char *argv[]) {
+#if defined(VULKAN_ENABLED)
+ //MoltenVK - enable full component swizzling support
+ setenv("MVK_CONFIG_FULL_IMAGE_VIEW_SWIZZLE", "1", 1);
+#endif
+
+ printf("*********** main.m\n");
+ gargc = argc;
+ gargv = argv;
+
+ printf("running app main\n");
+ @autoreleasepool {
+ NSString *className = NSStringFromClass([GodotApplicalitionDelegate class]);
+ UIApplicationMain(argc, argv, nil, className);
+ }
+ printf("main done\n");
+ return 0;
+}
diff --git a/platform/tvos/os_tvos.h b/platform/tvos/os_tvos.h
new file mode 100644
index 000000000000..bc0c30ace76a
--- /dev/null
+++ b/platform/tvos/os_tvos.h
@@ -0,0 +1,88 @@
+/*************************************************************************/
+/* os_tvos.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef OS_TVOS_H
+#define OS_TVOS_H
+
+#include "platform/uikit/uikit_os.h"
+
+#include "drivers/coreaudio/audio_driver_coreaudio.h"
+#include "drivers/unix/os_unix.h"
+#include "servers/audio_server.h"
+#include "servers/rendering/renderer_compositor.h"
+#include "tvos.h"
+
+#if defined(VULKAN_ENABLED)
+#include "drivers/vulkan/rendering_device_vulkan.h"
+#include "platform/uikit/uikit_vulkan_context.h"
+#endif
+
+class OSAppleTV : public OS_UIKit {
+private:
+ static HashMap dynamic_symbol_lookup_table;
+ friend void register_dynamic_symbol(char *name, void *address);
+
+ AudioDriverCoreAudio audio_driver;
+
+ tvOS *tvos;
+
+ virtual void initialize() override;
+
+ virtual void finalize() override;
+
+ bool is_focused = false;
+
+ bool overrides_menu_button = true;
+
+public:
+ static OSAppleTV *get_singleton();
+
+ OSAppleTV(String p_data_dir, String p_cache_dir);
+ ~OSAppleTV();
+
+ virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override;
+
+ virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional = false) override;
+
+ virtual String get_name() const override;
+ virtual String get_model_name() const override;
+
+ virtual bool _check_internal_feature_support(const String &p_feature) override;
+
+ void on_focus_out();
+ void on_focus_in();
+
+ bool get_overrides_menu_button() const;
+ void set_overrides_menu_button(bool p_flag);
+
+ void initialize_modules();
+};
+
+#endif // OS_TVOS_H
diff --git a/platform/tvos/os_tvos.mm b/platform/tvos/os_tvos.mm
new file mode 100644
index 000000000000..068718673742
--- /dev/null
+++ b/platform/tvos/os_tvos.mm
@@ -0,0 +1,189 @@
+/*************************************************************************/
+/* os_tvos.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "os_tvos.h"
+#import "app_delegate.h"
+#include "core/config/project_settings.h"
+#include "core/io/dir_access.h"
+#include "core/io/file_access.h"
+#include "core/io/file_access_pack.h"
+#include "display_server_tvos.h"
+#include "drivers/unix/syslog_logger.h"
+#import "godot_view.h"
+#import "godot_view_controller.h"
+#include "main/main.h"
+
+#import
+#import
+#import
+
+#if defined(VULKAN_ENABLED)
+#include "servers/rendering/renderer_rd/renderer_compositor_rd.h"
+#import
+#ifdef USE_VOLK
+#include
+#else
+#include
+#endif
+#endif
+
+// Initialization order between compilation units is not guaranteed,
+// so we use this as a hack to ensure certain code is called before
+// everything else, but after all units are initialized.
+typedef void (*init_callback)();
+static init_callback *tvos_init_callbacks = NULL;
+static int tvos_init_callbacks_count = 0;
+static int tvos_init_callbacks_capacity = 0;
+HashMap OSAppleTV::dynamic_symbol_lookup_table;
+
+void add_tvos_init_callback(init_callback cb) {
+ if (tvos_init_callbacks_count == tvos_init_callbacks_capacity) {
+ void *new_ptr = realloc(tvos_init_callbacks, sizeof(cb) * 32);
+ if (new_ptr) {
+ tvos_init_callbacks = (init_callback *)(new_ptr);
+ tvos_init_callbacks_capacity += 32;
+ }
+ }
+ if (tvos_init_callbacks_capacity > tvos_init_callbacks_count) {
+ tvos_init_callbacks[tvos_init_callbacks_count] = cb;
+ ++tvos_init_callbacks_count;
+ }
+}
+
+void register_dynamic_symbol(char *name, void *address) {
+ OSAppleTV::dynamic_symbol_lookup_table[String(name)] = address;
+}
+
+OSAppleTV *OSAppleTV::get_singleton() {
+ return (OSAppleTV *)OS::get_singleton();
+}
+
+OSAppleTV::OSAppleTV(String p_data_dir, String p_cache_dir) :
+ OS_UIKit(p_data_dir, p_cache_dir) {
+ for (int i = 0; i < tvos_init_callbacks_count; ++i) {
+ tvos_init_callbacks[i]();
+ }
+ free(tvos_init_callbacks);
+ tvos_init_callbacks = nullptr;
+ tvos_init_callbacks_count = 0;
+ tvos_init_callbacks_capacity = 0;
+
+ DisplayServerAppleTV::register_tvos_driver();
+}
+
+OSAppleTV::~OSAppleTV() {}
+
+void OSAppleTV::alert(const String &p_alert, const String &p_title) {
+ const CharString utf8_alert = p_alert.utf8();
+ const CharString utf8_title = p_title.utf8();
+ tvOS::alert(utf8_alert.get_data(), utf8_title.get_data());
+}
+
+void OSAppleTV::initialize() {
+ OS_UIKit::initialize();
+}
+
+void OSAppleTV::initialize_modules() {
+ tvos = memnew(tvOS);
+ Engine::get_singleton()->add_singleton(Engine::Singleton("tvOS", tvos));
+}
+
+void OSAppleTV::finalize() {
+ if (tvos) {
+ memdelete(tvos);
+ }
+
+ OS_UIKit::finalize();
+}
+
+// MARK: Dynamic Libraries
+
+Error OSAppleTV::get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional) {
+ if (p_library_handle == RTLD_SELF) {
+ void **ptr = OSAppleTV::dynamic_symbol_lookup_table.getptr(p_name);
+ if (ptr) {
+ p_symbol_handle = *ptr;
+ return OK;
+ }
+ }
+ return OS_Unix::get_dynamic_library_symbol_handle(p_library_handle, p_name, p_symbol_handle, p_optional);
+}
+
+String OSAppleTV::get_name() const {
+ return "tvOS";
+};
+
+String OSAppleTV::get_model_name() const {
+ String model = tvos->get_model();
+ if (model != "")
+ return model;
+
+ return OS_Unix::get_model_name();
+}
+
+bool OSAppleTV::_check_internal_feature_support(const String &p_feature) {
+ return p_feature == "mobile";
+}
+
+void OSAppleTV::on_focus_out() {
+ if (is_focused) {
+ is_focused = false;
+
+ if (DisplayServerAppleTV::get_singleton()) {
+ DisplayServerAppleTV::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_FOCUS_OUT);
+ }
+
+ [AppDelegate.viewController.godotView stopRendering];
+
+ audio_driver.stop();
+ }
+}
+
+void OSAppleTV::on_focus_in() {
+ if (!is_focused) {
+ is_focused = true;
+
+ if (DisplayServerAppleTV::get_singleton()) {
+ DisplayServerAppleTV::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_FOCUS_IN);
+ }
+
+ [AppDelegate.viewController.godotView startRendering];
+
+ audio_driver.start();
+ }
+}
+
+bool OSAppleTV::get_overrides_menu_button() const {
+ return overrides_menu_button;
+}
+
+void OSAppleTV::set_overrides_menu_button(bool p_flag) {
+ overrides_menu_button = p_flag;
+}
diff --git a/platform/tvos/platform_config.h b/platform/tvos/platform_config.h
new file mode 100644
index 000000000000..fed77d893253
--- /dev/null
+++ b/platform/tvos/platform_config.h
@@ -0,0 +1,44 @@
+/*************************************************************************/
+/* platform_config.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include
+
+#define OPENGL_INCLUDE_H
+
+#define PLATFORM_REFCOUNT
+
+#define PTHREAD_RENAME_SELF
+
+#define _weakify(var) __weak typeof(var) GDWeak_##var = var;
+#define _strongify(var) \
+ _Pragma("clang diagnostic push") \
+ _Pragma("clang diagnostic ignored \"-Wshadow\"") \
+ __strong typeof(var) var = GDWeak_##var; \
+ _Pragma("clang diagnostic pop")
diff --git a/platform/tvos/tvos.h b/platform/tvos/tvos.h
new file mode 100644
index 000000000000..740b8a4ddf70
--- /dev/null
+++ b/platform/tvos/tvos.h
@@ -0,0 +1,53 @@
+/*************************************************************************/
+/* tvos.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef TVOS_H
+#define TVOS_H
+
+#include "core/object/class_db.h"
+
+class tvOS : public Object {
+ GDCLASS(tvOS, Object);
+
+ static void _bind_methods();
+
+public:
+ static void alert(const char *p_alert, const char *p_title);
+
+ String get_model() const;
+ String get_rate_url(int p_app_id) const;
+
+ bool get_overrides_menu_button() const;
+ void set_overrides_menu_button(bool p_flag);
+
+ tvOS();
+};
+
+#endif
diff --git a/platform/tvos/tvos.mm b/platform/tvos/tvos.mm
new file mode 100644
index 000000000000..a4ac436389b2
--- /dev/null
+++ b/platform/tvos/tvos.mm
@@ -0,0 +1,103 @@
+/*************************************************************************/
+/* tvos.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "tvos.h"
+
+#import "app_delegate.h"
+#import "godot_view_controller.h"
+#include "os_tvos.h"
+
+#import
+#include
+
+void tvOS::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("get_rate_url", "app_id"), &tvOS::get_rate_url);
+
+ ClassDB::bind_method(D_METHOD("get_overrides_menu_button"), &tvOS::get_overrides_menu_button);
+ ClassDB::bind_method(D_METHOD("set_overrides_menu_button", "p_flag"), &tvOS::set_overrides_menu_button);
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "overrides_menu_button"), "set_overrides_menu_button", "get_overrides_menu_button");
+};
+
+void tvOS::alert(const char *p_alert, const char *p_title) {
+ NSString *title = [NSString stringWithUTF8String:p_title];
+ NSString *message = [NSString stringWithUTF8String:p_alert];
+
+ UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
+ UIAlertAction *button = [UIAlertAction actionWithTitle:@"OK"
+ style:UIAlertActionStyleCancel
+ handler:^(id){
+ }];
+
+ [alert addAction:button];
+
+ [AppDelegate.viewController presentViewController:alert animated:YES completion:nil];
+}
+
+String tvOS::get_model() const {
+ size_t size;
+ sysctlbyname("hw.machine", NULL, &size, NULL, 0);
+ char *model = (char *)malloc(size);
+ if (model == NULL) {
+ return "";
+ }
+ sysctlbyname("hw.machine", model, &size, NULL, 0);
+ NSString *platform = [NSString stringWithCString:model encoding:NSUTF8StringEncoding];
+ free(model);
+ const char *str = [platform UTF8String];
+ return String(str != NULL ? str : "");
+}
+
+String tvOS::get_rate_url(int p_app_id) const {
+ String app_url_path = "itms-apps://itunes.apple.com/app/idAPP_ID";
+
+ String ret = app_url_path.replace("APP_ID", String::num(p_app_id));
+
+ printf("returning rate url %s\n", ret.utf8().get_data());
+
+ return ret;
+};
+
+bool tvOS::get_overrides_menu_button() const {
+ if (!OSAppleTV::get_singleton()) {
+ return false;
+ }
+
+ return OSAppleTV::get_singleton()->get_overrides_menu_button();
+}
+
+void tvOS::set_overrides_menu_button(bool p_flag) {
+ if (!OSAppleTV::get_singleton()) {
+ return;
+ }
+
+ OSAppleTV::get_singleton()->set_overrides_menu_button(p_flag);
+}
+
+tvOS::tvOS(){};
diff --git a/platform/uikit/uikit_app_delegate.h b/platform/uikit/uikit_app_delegate.h
new file mode 100644
index 000000000000..198faabfb12b
--- /dev/null
+++ b/platform/uikit/uikit_app_delegate.h
@@ -0,0 +1,41 @@
+/*************************************************************************/
+/* uikit_app_delegate.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#import
+
+typedef NSObject ApplicationDelegateService;
+
+@interface UIKitApplicalitionDelegate : NSObject
+
+@property(class, readonly, strong) NSArray]