diff --git a/.github/workflows/tvos_builds.yml b/.github/workflows/tvos_builds.yml
new file mode 100644
index 000000000000..90d255a6dd7c
--- /dev/null
+++ b/.github/workflows/tvos_builds.yml
@@ -0,0 +1,37 @@
+name: 📺 tvOS Builds
+on: [push, pull_request]
+
+# Global Settings
+env:
+ GODOT_BASE_BRANCH: 3.x
+ SCONSFLAGS: verbose=yes warnings=all werror=yes debug_symbols=no
+
+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 (arm64)
+ uses: ./.github/actions/godot-build
+ with:
+ sconsflags: ${{ env.SCONSFLAGS }}
+ platform: tvos
+ target: release
+ tools: false
+
+ - name: Upload artifact
+ uses: ./.github/actions/upload-artifact
diff --git a/doc/classes/EditorExportPlugin.xml b/doc/classes/EditorExportPlugin.xml
index d4d5c8a74927..92869d311724 100644
--- a/doc/classes/EditorExportPlugin.xml
+++ b/doc/classes/EditorExportPlugin.xml
@@ -114,6 +114,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 [method add_ios_framework].
+ 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 3247ad4e893c..00afa397f4d7 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -634,6 +634,9 @@
Default delay for touch events. This only affects iOS devices.
+
+ Default delay for end press events. This only affects tvOS devices.
+
Optional name for the 2D physics layer 1.
diff --git a/drivers/gles2/rasterizer_gles2.cpp b/drivers/gles2/rasterizer_gles2.cpp
index f53524761d86..3c4dde931cb1 100644
--- a/drivers/gles2/rasterizer_gles2.cpp
+++ b/drivers/gles2/rasterizer_gles2.cpp
@@ -65,9 +65,9 @@
#endif
#endif
-#ifndef IPHONE_ENABLED
+#ifndef UIKIT_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/gles2/rasterizer_scene_gles2.cpp b/drivers/gles2/rasterizer_scene_gles2.cpp
index a85264835b32..7487fe2b04bb 100644
--- a/drivers/gles2/rasterizer_scene_gles2.cpp
+++ b/drivers/gles2/rasterizer_scene_gles2.cpp
@@ -44,7 +44,7 @@
#endif
#ifndef GLES_OVER_GL
-#ifdef IPHONE_ENABLED
+#ifdef UIKIT_ENABLED
#include
//void *glResolveMultisampleFramebufferAPPLE;
@@ -2824,7 +2824,7 @@ void RasterizerSceneGLES2::_post_process(Environment *env, const CameraMatrix &p
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
-#elif IPHONE_ENABLED
+#elif UIKIT_ENABLED
glBindFramebuffer(GL_READ_FRAMEBUFFER, storage->frame.current_rt->multisample_fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, next_buffer);
@@ -3561,7 +3561,7 @@ void RasterizerSceneGLES2::render_scene(const Transform &p_cam_transform, const
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
-#elif IPHONE_ENABLED
+#elif UIKIT_ENABLED
glBindFramebuffer(GL_READ_FRAMEBUFFER, storage->frame.current_rt->multisample_fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, storage->frame.current_rt->fbo);
diff --git a/drivers/gles2/rasterizer_storage_gles2.cpp b/drivers/gles2/rasterizer_storage_gles2.cpp
index 1bf07eea7150..221510450216 100644
--- a/drivers/gles2/rasterizer_storage_gles2.cpp
+++ b/drivers/gles2/rasterizer_storage_gles2.cpp
@@ -94,7 +94,7 @@ GLuint RasterizerStorageGLES2::system_fbo = 0;
#include // needed to load extensions
#endif
-#ifdef IPHONE_ENABLED
+#ifdef UIKIT_ENABLED
#include
//void *glRenderbufferStorageMultisampleAPPLE;
@@ -5120,7 +5120,7 @@ void RasterizerStorageGLES2::_render_target_allocate(RenderTarget *rt) {
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rt->multisample_depth);
-#if defined(GLES_OVER_GL) || defined(IPHONE_ENABLED)
+#if defined(GLES_OVER_GL) || defined(UIKIT_ENABLED)
glGenRenderbuffers(1, &rt->multisample_color);
glBindRenderbuffer(GL_RENDERBUFFER, rt->multisample_color);
@@ -6249,7 +6249,7 @@ void RasterizerStorageGLES2::initialize() {
#ifndef GLES_OVER_GL
//Manually load extensions for android and ios
-#ifdef IPHONE_ENABLED
+#ifdef UIKIT_ENABLED
// appears that IPhone doesn't need to dlopen TODO: test this rigorously before removing
//void *gles2_lib = dlopen(NULL, RTLD_LAZY);
//glRenderbufferStorageMultisampleAPPLE = dlsym(gles2_lib, "glRenderbufferStorageMultisampleAPPLE");
diff --git a/drivers/gles3/rasterizer_storage_gles3.cpp b/drivers/gles3/rasterizer_storage_gles3.cpp
index d8b1a9242daf..b00136bee3c2 100644
--- a/drivers/gles3/rasterizer_storage_gles3.cpp
+++ b/drivers/gles3/rasterizer_storage_gles3.cpp
@@ -38,7 +38,7 @@
#include "rasterizer_scene_gles3.h"
#include "servers/visual_server.h"
-#if defined(IPHONE_ENABLED) || defined(ANDROID_ENABLED)
+#if defined(UIKIT_ENABLED) || defined(ANDROID_ENABLED)
#include
#endif
@@ -8104,10 +8104,10 @@ void RasterizerStorageGLES3::initialize() {
glMaxShaderCompilerThreadsKHR(ShaderGLES3::max_simultaneous_compiles);
}
#else
-#if defined(IPHONE_ENABLED) || defined(ANDROID_ENABLED) // TODO: Consider more platforms?
+#if defined(UIKIT_ENABLED) || defined(ANDROID_ENABLED) // TODO: Consider more platforms?
void *gles3_lib = nullptr;
void (*MaxShaderCompilerThreads)(GLuint) = nullptr;
-#if defined(IPHONE_ENABLED)
+#if defined(UIKIT_ENABLED)
gles3_lib = dlopen(nullptr, RTLD_LAZY);
#elif defined(ANDROID_ENABLED)
gles3_lib = dlopen("libGLESv3.so", RTLD_LAZY);
diff --git a/drivers/unix/os_unix.cpp b/drivers/unix/os_unix.cpp
index 3d54c1ca1aa2..0ed3a005b6b2 100644
--- a/drivers/unix/os_unix.cpp
+++ b/drivers/unix/os_unix.cpp
@@ -265,6 +265,8 @@ Error OS_Unix::execute(const String &p_path, const List &p_arguments, bo
// 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 (p_blocking && r_pipe) {
String argss;
diff --git a/editor/editor_export.cpp b/editor/editor_export.cpp
index ad95e26aa758..dc04cfb113dd 100644
--- a/editor/editor_export.cpp
+++ b/editor/editor_export.cpp
@@ -566,6 +566,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 PoolVector &p_features) {
if (get_script_instance()) {
get_script_instance()->call("_export_file", p_path, p_type, p_features);
@@ -596,15 +655,28 @@ void EditorExportPlugin::skip() {
void EditorExportPlugin::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_shared_object", "path", "tags"), &EditorExportPlugin::add_shared_object);
- ClassDB::bind_method(D_METHOD("add_ios_project_static_lib", "path"), &EditorExportPlugin::add_ios_project_static_lib);
ClassDB::bind_method(D_METHOD("add_file", "path", "file", "remap"), &EditorExportPlugin::add_file);
+
+ // iOS specific methods
+ ClassDB::bind_method(D_METHOD("add_ios_project_static_lib", "path"), &EditorExportPlugin::add_ios_project_static_lib);
ClassDB::bind_method(D_METHOD("add_ios_framework", "path"), &EditorExportPlugin::add_ios_framework);
ClassDB::bind_method(D_METHOD("add_ios_embedded_framework", "path"), &EditorExportPlugin::add_ios_embedded_framework);
ClassDB::bind_method(D_METHOD("add_ios_plist_content", "plist_content"), &EditorExportPlugin::add_ios_plist_content);
ClassDB::bind_method(D_METHOD("add_ios_linker_flags", "flags"), &EditorExportPlugin::add_ios_linker_flags);
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);
+
+ // tvOS specific methods
+ 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("add_osx_plugin_file", "path"), &EditorExportPlugin::add_osx_plugin_file);
+
ClassDB::bind_method(D_METHOD("skip"), &EditorExportPlugin::skip);
BIND_VMETHOD(MethodInfo("_export_file", PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::STRING, "type"), PropertyInfo(Variant::POOL_STRING_ARRAY, "features")));
diff --git a/editor/editor_export.h b/editor/editor_export.h
index 80926c1e7f6a..cad63a54047e 100644
--- a/editor/editor_export.h
+++ b/editor/editor_export.h
@@ -290,6 +290,14 @@ class EditorExportPlugin : public Reference {
Vector ios_bundle_files;
String ios_cpp_code;
+ 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;
+
Vector osx_plugin_files;
_FORCE_INLINE_ void _clear() {
@@ -305,6 +313,14 @@ class EditorExportPlugin : public Reference {
ios_plist_content = "";
ios_linker_flags = "";
ios_cpp_code = "";
+
+ tvos_frameworks.clear();
+ tvos_embedded_frameworks.clear();
+ tvos_bundle_files.clear();
+ tvos_plist_content = "";
+ tvos_linker_flags = "";
+ tvos_cpp_code = "";
+
osx_plugin_files.clear();
}
@@ -327,6 +343,14 @@ class EditorExportPlugin : public Reference {
void add_ios_bundle_file(const String &p_path);
void add_ios_cpp_code(const String &p_code);
+ 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 add_osx_plugin_file(const String &p_path);
void skip();
@@ -345,6 +369,14 @@ class EditorExportPlugin : public Reference {
Vector get_ios_bundle_files() const;
String get_ios_cpp_code() 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;
+
const Vector &get_osx_plugin_files() const;
EditorExportPlugin();
diff --git a/main/main.cpp b/main/main.cpp
index eb1654b9153a..279979ffdb88 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -1245,6 +1245,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/main/main_builders.py b/main/main_builders.py
index 6e5a5ceede4f..f3846cb71d85 100644
--- a/main/main_builders.py
+++ b/main/main_builders.py
@@ -110,6 +110,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/methods.py b/methods.py
index a8255fb67700..d9470efcb697 100644
--- a/methods.py
+++ b/methods.py
@@ -861,6 +861,10 @@ def get_darwin_sdk_version(platform):
sdk_name = "iphoneos"
elif platform == "iphonesimulator":
sdk_name = "iphonesimulator"
+ elif platform == "tvos":
+ sdk_name = "appletvos"
+ elif platform == "tvossimulator":
+ sdk_name = "appletvsimulator"
else:
raise Exception("Invalid platform argument passed to get_darwin_sdk_version")
@@ -882,6 +886,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/modules/gdnative/doc_classes/GDNativeLibrary.xml b/modules/gdnative/doc_classes/GDNativeLibrary.xml
index bf5dcd38fc13..2cf9d8fa526b 100644
--- a/modules/gdnative/doc_classes/GDNativeLibrary.xml
+++ b/modules/gdnative/doc_classes/GDNativeLibrary.xml
@@ -42,7 +42,7 @@
The prefix this library's entry point functions begin with. For example, a GDNativeLibrary would declare its [code]gdnative_init[/code] function as [code]godot_gdnative_init[/code] by default.
- On platforms that require statically linking libraries (currently only iOS), each library must have a different [code]symbol_prefix[/code].
+ On platforms that require statically linking libraries (currently iOS and tvOS), each library must have a different [code]symbol_prefix[/code].
diff --git a/modules/gdnative/gdnative.cpp b/modules/gdnative/gdnative.cpp
index 8b9c77f9a64f..7d151d4bf7f9 100644
--- a/modules/gdnative/gdnative.cpp
+++ b/modules/gdnative/gdnative.cpp
@@ -293,11 +293,11 @@ bool GDNative::initialize() {
ERR_PRINT("No library set for this platform");
return false;
}
-#ifdef IPHONE_ENABLED
- // On iOS we use static linking by default.
+#if defined(IPHONE_ENABLED) || defined(TVOS_ENABLED)
+ // On iOS and tvOS we use static linking by default.
String path = "";
- // On iOS dylibs is not allowed, but can be replaced with .framework or .xcframework.
+ // On iOS and tvOS dylibs is not allowed, but can be replaced with .framework or .xcframework.
// If they are used, we can run dlopen on them.
// They should be located under Frameworks directory, so we need to replace library path.
if (!lib_path.ends_with(".a")) {
diff --git a/modules/gdnative/gdnative_library_editor_plugin.cpp b/modules/gdnative/gdnative_library_editor_plugin.cpp
index ac2879f72f2a..e5d721b250a9 100644
--- a/modules/gdnative/gdnative_library_editor_plugin.cpp
+++ b/modules/gdnative/gdnative_library_editor_plugin.cpp
@@ -139,7 +139,7 @@ void GDNativeLibraryEditor::_on_item_button(Object *item, int column, int id) {
if (id == BUTTON_SELECT_DEPENDENCES) {
mode = EditorFileDialog::MODE_OPEN_FILES;
- } else if (treeItem->get_text(0) == "iOS" || treeItem->get_text(0) == "macOS") {
+ } else if (treeItem->get_text(0) == "iOS" || treeItem->get_text(0) == "macOS" || treeItem->get_text(0) == "tvOS") {
mode = EditorFileDialog::MODE_OPEN_ANY;
}
@@ -330,6 +330,15 @@ GDNativeLibraryEditor::GDNativeLibraryEditor() {
// Frameworks is actually a folder with files.
platform_ios.library_extension = "*.framework; Framework, *.xcframework; Binary Framework, *.a; Static Library, *.dylib; Dynamic Library";
platforms["iOS"] = platform_ios;
+
+ NativePlatformConfig platform_tvos;
+ platform_tvos.name = "tvOS";
+ platform_tvos.entries.push_back("arm64");
+ platform_tvos.entries.push_back("x86_64");
+ // tvOS can use both Static and Dynamic libraries.
+ // Frameworks is actually a folder with files.
+ platform_tvos.library_extension = "*.framework; Framework, *.xcframework; Binary Framework, *.a; Static Library, *.dylib; Dynamic Library";
+ platforms["tvOS"] = platform_tvos;
}
VBoxContainer *container = memnew(VBoxContainer);
diff --git a/modules/gdnative/include/gdnative/gdnative.h b/modules/gdnative/include/gdnative/gdnative.h
index a5815f8ed418..c1843eefc6d2 100644
--- a/modules/gdnative/include/gdnative/gdnative.h
+++ b/modules/gdnative/include/gdnative/gdnative.h
@@ -43,6 +43,9 @@ extern "C" {
#if TARGET_OS_IPHONE
#define GDCALLINGCONV __attribute__((visibility("default")))
#define GDAPI GDCALLINGCONV
+#elif TARGET_OS_TV
+#define GDCALLINGCONV __attribute__((visibility("default")))
+#define GDAPI GDCALLINGCONV
#elif TARGET_OS_MAC
#define GDCALLINGCONV __attribute__((sysv_abi))
#define GDAPI GDCALLINGCONV
diff --git a/modules/gdnative/nativescript/SCsub b/modules/gdnative/nativescript/SCsub
index b1ddb2489c6e..144836acf3cc 100644
--- a/modules/gdnative/nativescript/SCsub
+++ b/modules/gdnative/nativescript/SCsub
@@ -5,5 +5,5 @@ Import("env_gdnative")
env_gdnative.add_source_files(env.modules_sources, "*.cpp")
-if "platform" in env and env["platform"] in ["x11", "iphone"]:
+if "platform" in env and env["platform"] in ["x11", "iphone", "tvos"]:
env.Append(LINKFLAGS=["-rdynamic"])
diff --git a/modules/gdnative/register_types.cpp b/modules/gdnative/register_types.cpp
index aacd0d58e1ef..bbc138396edd 100644
--- a/modules/gdnative/register_types.cpp
+++ b/modules/gdnative/register_types.cpp
@@ -142,8 +142,8 @@ void GDNativeExportPlugin::_export_file(const String &p_path, const String &p_ty
}
}
- // Add symbols for staticaly linked libraries on iOS
- if (p_features.has("iOS")) {
+ // Add symbols for staticaly linked libraries on iOS and tvOS
+ if (p_features.has("iOS") || p_features.has("tvOS")) {
bool should_fake_dynamic = false;
List entry_keys;
@@ -180,7 +180,7 @@ void GDNativeExportPlugin::_export_file(const String &p_path, const String &p_ty
}
if (should_fake_dynamic) {
- // Register symbols in the "fake" dynamic lookup table, because dlsym does not work well on iOS.
+ // Register symbols in the "fake" dynamic lookup table, because dlsym does not work well on iOS and tvOS.
LibrarySymbol expected_symbols[] = {
{ "gdnative_init", true },
{ "gdnative_terminate", false },
@@ -192,7 +192,7 @@ void GDNativeExportPlugin::_export_file(const String &p_path, const String &p_ty
};
String declare_pattern = "extern \"C\" void $name(void)$weak;\n";
String additional_code = "extern void register_dynamic_symbol(char *name, void *address);\n"
- "extern void add_ios_init_callback(void (*cb)());\n";
+ "extern void $callback_registration(void (*cb)());\n";
String linker_flags = "";
for (unsigned long i = 0; i < sizeof(expected_symbols) / sizeof(expected_symbols[0]); ++i) {
String full_name = lib->get_symbol_prefix() + expected_symbols[i].name;
@@ -215,11 +215,22 @@ void GDNativeExportPlugin::_export_file(const String &p_path, const String &p_ty
additional_code += register_pattern.replace("$name", full_name);
}
additional_code += "}\n";
- additional_code += String("struct $prefixstruct {$prefixstruct() {add_ios_init_callback($prefixinit);}};\n").replace("$prefix", lib->get_symbol_prefix());
+ additional_code += String("struct $prefixstruct {$prefixstruct() {$callback_registration($prefixinit);}};\n").replace("$prefix", lib->get_symbol_prefix());
additional_code += String("$prefixstruct $prefixstruct_instance;\n").replace("$prefix", lib->get_symbol_prefix());
- add_ios_cpp_code(additional_code);
- add_ios_linker_flags(linker_flags);
+ if (p_features.has("iOS")) {
+ const String callback_registration_string = "add_ios_init_callback";
+ additional_code = additional_code.replace("$callback_registration", callback_registration_string);
+
+ add_ios_cpp_code(additional_code);
+ add_ios_linker_flags(linker_flags);
+ } else if (p_features.has("tvOS")) {
+ const String callback_registration_string = "add_tvos_init_callback";
+ additional_code = additional_code.replace("$callback_registration", callback_registration_string);
+
+ add_tvos_cpp_code(additional_code);
+ add_tvos_linker_flags(linker_flags);
+ }
}
}
}
diff --git a/modules/webm/config.py b/modules/webm/config.py
index 0bedfa07ef82..6ce526f41740 100644
--- a/modules/webm/config.py
+++ b/modules/webm/config.py
@@ -1,7 +1,7 @@
def can_build(env, platform):
if env["arch"].startswith("rv"):
return False
- return platform not in ["iphone"]
+ return platform not in ["iphone", "tvos"]
def configure(env):
diff --git a/platform/iphone/SCsub b/platform/iphone/SCsub
index 983bfad6305d..4fcda8fb8a97 100644
--- a/platform/iphone/SCsub
+++ b/platform/iphone/SCsub
@@ -3,16 +3,21 @@
Import("env")
iphone_lib = [
+ "#platform/uikit/uikit_app_delegate.m",
+ "#platform/uikit/uikit_display_layer.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",
"godot_iphone.mm",
"os_iphone.mm",
"main.m",
"app_delegate.mm",
- "view_controller.mm",
"ios.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 0a5b72672648..f23ccd684834 100644
--- a/platform/iphone/app_delegate.h
+++ b/platform/iphone/app_delegate.h
@@ -30,11 +30,11 @@
#import
-@class ViewController;
+@class GodotViewController;
@interface AppDelegate : NSObject
@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 46e5067f3b8c..2fdcaa11cd79 100644
--- a/platform/iphone/app_delegate.mm
+++ b/platform/iphone/app_delegate.mm
@@ -33,9 +33,9 @@
#include "core/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
@@ -49,9 +49,9 @@
@implementation AppDelegate
-static ViewController *mainViewController = nil;
+static GodotViewController *mainViewController = nil;
-+ (ViewController *)viewController {
++ (GodotViewController *)viewController {
return mainViewController;
}
@@ -78,7 +78,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
// OS with iphone_main. This allows the GodotView to access project settings so
// it can properly initialize the OpenGL context
- 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 e292d6a514f7..4a827c43843f 100644
--- a/platform/iphone/detect.py
+++ b/platform/iphone/detect.py
@@ -187,4 +187,4 @@ def configure(env):
env["ENV"]["CODESIGN_ALLOCATE"] = "/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/codesign_allocate"
env.Prepend(CPPPATH=["#platform/iphone"])
- env.Append(CPPDEFINES=["IPHONE_ENABLED", "UNIX_ENABLED", "GLES_ENABLED", "COREAUDIO_ENABLED"])
+ env.Append(CPPDEFINES=["UIKIT_ENABLED", "IPHONE_ENABLED", "UNIX_ENABLED", "GLES_ENABLED", "COREAUDIO_ENABLED"])
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 76316b918028..497ebd8c9be6 100644
--- a/platform/iphone/godot_view.h
+++ b/platform/iphone/godot_view.h
@@ -28,42 +28,9 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#import
-#import
-#import
-#import
-#import
-#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(strong, readonly, nonatomic) CALayer *renderingLayer;
-@property(assign, readonly, nonatomic) BOOL canRender;
-
-@property(assign, nonatomic) NSTimeInterval renderingInterval;
-
-- (CALayer *)initializeRendering;
-- (void)stopRendering;
-- (void)startRendering;
-
-@property(nonatomic, assign) BOOL useCADisplayLink;
+@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 5cf914176d07..56516922323b 100644
--- a/platform/iphone/godot_view.mm
+++ b/platform/iphone/godot_view.mm
@@ -37,9 +37,7 @@
#import
#import
-#import "display_layer.h"
#import "godot_view_gesture_recognizer.h"
-#import "godot_view_renderer.h"
#import
@@ -49,17 +47,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;
@@ -68,26 +55,6 @@ @interface GodotView () {
@implementation GodotView
-// Implement this to override the default layer class (which is [CALayer class]).
-// We do this so that our view will be backed by a layer that is capable of OpenGL ES rendering.
-- (CALayer *)initializeRendering {
- if (self.renderingLayer) {
- return self.renderingLayer;
- }
-
- CALayer *layer = [GodotOpenGLLayer layer];
-
- 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];
@@ -110,39 +77,17 @@ - (instancetype)initWithFrame:(CGRect)frame {
// Stop animating and release resources when they are no longer needed.
- (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;
@@ -164,122 +109,16 @@ - (void)godot_commonInit {
[self addGestureRecognizer:self.delayGestureRecognizer];
}
-- (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)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;
- }
-
- [self clearTouches];
-}
-
-// Updates the OpenGL view when the timer fires
- (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)
- ;
-
- // We are good to go, resume the CADisplayLink
- [self.displayLink setPaused:NO];
- }
-
- [self.renderingLayer startRenderDisplayLayer];
-
- 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];
-
- [self.renderingLayer stopRenderDisplayLayer];
-}
-
-- (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];
- }
}
-// If our view is resized, we'll be asked to layout subviews.
-// This is the perfect opportunity to also update the framebuffer so that it is
-// the same size as our display area.
-
-- (void)layoutSubviews {
- if (self.renderingLayer) {
- self.renderingLayer.frame = self.bounds;
- [self.renderingLayer layoutDisplayLayer];
- }
+- (void)stopRendering {
+ [super stopRendering];
- [super layoutSubviews];
+ [self clearTouches];
}
// MARK: - Input
diff --git a/platform/iphone/view_controller.h b/platform/iphone/godot_view_controller.h
similarity index 94%
rename from platform/iphone/view_controller.h
rename to platform/iphone/godot_view_controller.h
index c99ca931c796..d49c43f02147 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,14 @@
/* 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) GodotNativeVideoView *videoView;
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 417f411c9665..6f08b2e2f280 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/project_settings.h"
#import "godot_view.h"
@@ -37,17 +37,15 @@
#import "native_video_view.h"
#include "os_iphone.h"
-@interface ViewController ()
+@interface GodotViewController ()
@property(strong, nonatomic) GodotViewRenderer *renderer;
@property(strong, nonatomic) GodotNativeVideoView *videoView;
@property(strong, nonatomic) GodotKeyboardInputView *keyboardView;
-@property(strong, nonatomic) UIView *godotLoadingOverlay;
-
@end
-@implementation ViewController
+@implementation GodotViewController
- (GodotView *)godotView {
return (GodotView *)self.view;
@@ -90,16 +88,10 @@ - (void)godot_commonInit {
// Initialize view controller values.
}
-- (void)didReceiveMemoryWarning {
- [super didReceiveMemoryWarning];
- printf("*********** did receive memory warning!\n");
-};
-
- (void)viewDidLoad {
[super viewDidLoad];
[self observeKeyboard];
- [self displayLoadingOverlay];
if (@available(iOS 11.0, *)) {
[self setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
@@ -124,31 +116,6 @@ - (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.videoView stopVideo];
self.videoView = nil;
@@ -157,11 +124,6 @@ - (void)dealloc {
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 da988296484f..0f41a8d08f4e 100644
--- a/platform/iphone/godot_view_renderer.mm
+++ b/platform/iphone/godot_view_renderer.mm
@@ -30,80 +30,19 @@
#import "godot_view_renderer.h"
-#include "core/os/keyboard.h"
-#include "core/project_settings.h"
-#include "main/main.h"
#include "os_iphone.h"
-#include "servers/audio_server.h"
-#import
-#import
-#import
-#import
#import
@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 8c9f5cbae819..c96a26d33c01 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 c2fdd1bcd0ba..8cdfb063e1bd 100644
--- a/platform/iphone/os_iphone.h
+++ b/platform/iphone/os_iphone.h
@@ -28,83 +28,31 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifdef IPHONE_ENABLED
-
#ifndef OS_IPHONE_H
#define OS_IPHONE_H
-#include "core/os/input.h"
-#include "drivers/coreaudio/audio_driver_coreaudio.h"
-#include "drivers/unix/os_unix.h"
-#include "joypad_iphone.h"
+#include "platform/uikit/uikit_os.h"
#include "ios.h"
-#include "main/input_default.h"
-#include "servers/audio_server.h"
-#include "servers/visual/rasterizer.h"
-#include "servers/visual_server.h"
-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);
- VisualServer *visual_server;
-
- AudioDriverCoreAudio audio_driver;
-
iOS *ios;
- JoypadIPhone *joypad_iphone;
-
- MainLoop *main_loop;
-
- VideoMode video_mode;
-
- EAGLContext *offscreen_gl_context;
-
- virtual int get_video_driver_count() const;
- virtual const char *get_video_driver_name(int p_driver) const;
-
- virtual int get_current_video_driver() const;
+ int virtual_keyboard_height = 0;
- virtual void initialize_core();
virtual Error initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver);
-
- virtual void set_main_loop(MainLoop *p_main_loop);
- virtual MainLoop *get_main_loop() const;
-
- virtual void delete_main_loop();
-
virtual void finalize();
- void perform_event(const Ref &p_event);
-
- void set_data_dir(String p_dir);
-
- String data_dir;
- String cache_dir;
-
- InputDefault *input;
-
- int virtual_keyboard_height = 0;
-
- int video_driver_index;
-
- bool is_focused = false;
-
public:
static OSIPhone *get_singleton();
OSIPhone(String p_data_dir, String p_cache_dir);
~OSIPhone();
- bool iterate();
-
- void start();
-
- virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false);
- virtual Error close_dynamic_library(void *p_library_handle);
virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional = false);
virtual void alert(const String &p_alert, const String &p_title = "ALERT!");
@@ -115,14 +63,6 @@ class OSIPhone : public OS_Unix {
virtual void set_clipboard(const String &p_text);
virtual String get_clipboard() const;
- Error shell_open(String p_uri);
-
- String get_user_data_dir() const;
- String get_cache_path() const;
-
- String get_locale() const;
-
- String get_unique_id() const;
virtual String get_processor_name() const;
virtual void vibrate_handheld(int p_duration_ms = 500);
@@ -132,54 +72,13 @@ class OSIPhone : public OS_Unix {
virtual int get_screen_dpi(int p_screen = -1) const;
virtual float get_screen_refresh_rate(int p_screen = -1) const;
- void pencil_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick);
- void touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick);
- void pencil_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y, float p_force);
- 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);
- void pencil_cancelled(int p_idx);
- void key(uint32_t p_key, bool p_pressed);
void set_virtual_keyboard_height(int p_height);
- int set_base_framebuffer(int p_fb);
-
- 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);
-
- int get_unused_joy_id();
- void joy_connection_changed(int p_idx, bool p_connected, String p_name);
- void joy_button(int p_device, int p_button, bool p_pressed);
- void joy_axis(int p_device, int p_axis, float p_value);
-
- virtual void set_mouse_show(bool p_show);
- virtual void set_mouse_grab(bool p_grab);
- virtual bool is_mouse_grab_enabled() const;
- virtual Point2 get_mouse_position() const;
- virtual int get_mouse_button_state() const;
-
- virtual void set_window_title(const String &p_title);
-
- virtual void set_video_mode(const VideoMode &p_video_mode, int p_screen = 0);
- virtual VideoMode get_video_mode(int p_screen = 0) const;
-
- virtual void get_fullscreen_mode_list(List *p_list, int p_screen = 0) const;
-
- void set_offscreen_gl_context(EAGLContext *p_context);
- virtual bool is_offscreen_gl_available() const;
- virtual void set_offscreen_gl_current(bool p_current);
-
- virtual void set_keep_screen_on(bool p_enabled);
-
- virtual bool can_draw() const;
-
virtual bool has_virtual_keyboard() const;
virtual void show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), bool p_multiline = false, int p_max_input_length = -1, int p_cursor_start = -1, int p_cursor_end = -1);
virtual void hide_virtual_keyboard();
virtual int get_virtual_keyboard_height() const;
- virtual Size2 get_window_size() const;
virtual Rect2 get_window_safe_area() const;
virtual bool has_touchscreen_ui_hint() const;
@@ -196,5 +95,3 @@ class OSIPhone : public OS_Unix {
};
#endif // OS_IPHONE_H
-
-#endif
diff --git a/platform/iphone/os_iphone.mm b/platform/iphone/os_iphone.mm
index 4936eed0e674..0fc1c470164d 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"
#include "drivers/gles2/rasterizer_gles2.h"
@@ -48,18 +46,15 @@
#import "app_delegate.h"
#import "device_metrics.h"
#import "godot_view.h"
+#import "godot_view_controller.h"
#import "keyboard_input_view.h"
#import "native_video_view.h"
-#import "view_controller.h"
#import
#include
#include
#import
-extern int gl_view_base_fb; // from gl_view.mm
-extern bool gles3_available; // from gl_view.mm
-
// 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.
@@ -69,303 +64,43 @@
static int ios_init_callbacks_capacity = 0;
HashMap OSIPhone::dynamic_symbol_lookup_table;
-int OSIPhone::get_video_driver_count() const {
- return 2;
-};
-
-const char *OSIPhone::get_video_driver_name(int p_driver) const {
- switch (p_driver) {
- case VIDEO_DRIVER_GLES3:
- return "GLES3";
- case VIDEO_DRIVER_GLES2:
- return "GLES2";
+void add_ios_init_callback(init_callback cb) {
+ if (ios_init_callbacks_count == ios_init_callbacks_capacity) {
+ void *new_ptr = realloc(ios_init_callbacks, sizeof(cb) * 32);
+ if (new_ptr) {
+ ios_init_callbacks = (init_callback *)(new_ptr);
+ ios_init_callbacks_capacity += 32;
+ }
}
- ERR_FAIL_V_MSG(NULL, "Invalid video driver index: " + itos(p_driver) + ".");
-};
+ if (ios_init_callbacks_capacity > ios_init_callbacks_count) {
+ ios_init_callbacks[ios_init_callbacks_count] = cb;
+ ++ios_init_callbacks_count;
+ }
+}
OSIPhone *OSIPhone::get_singleton() {
return (OSIPhone *)OS::get_singleton();
-};
-
-void OSIPhone::set_data_dir(String p_dir) {
- DirAccess *da = DirAccess::open(p_dir);
-
- data_dir = da->get_current_dir();
- printf("setting data dir to %ls from %ls\n", data_dir.c_str(), p_dir.c_str());
- memdelete(da);
-};
-
-String OSIPhone::get_unique_id() const {
- NSString *uuid = [UIDevice currentDevice].identifierForVendor.UUIDString;
- return String::utf8([uuid UTF8String]);
-};
-
-void OSIPhone::initialize_core() {
- OS_Unix::initialize_core();
-
- set_data_dir(data_dir);
-};
-
-int OSIPhone::get_current_video_driver() const {
- return video_driver_index;
-}
-
-void OSIPhone::start() {
- Main::start();
-
- if (joypad_iphone) {
- joypad_iphone->start_processing();
- }
}
Error OSIPhone::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) {
- bool use_gl3 = GLOBAL_GET("rendering/quality/driver/driver_name") == "GLES3";
- bool gl_initialization_error = false;
-
- while (true) {
- if (use_gl3) {
- if (RasterizerGLES3::is_viable() == OK && gles3_available) {
- RasterizerGLES3::register_config();
- RasterizerGLES3::make_current();
- break;
- } else {
- if (GLOBAL_GET("rendering/quality/driver/fallback_to_gles2")) {
- p_video_driver = VIDEO_DRIVER_GLES2;
- use_gl3 = false;
- continue;
- } else {
- gl_initialization_error = true;
- break;
- }
- }
- } else {
- if (RasterizerGLES2::is_viable() == OK) {
- RasterizerGLES2::register_config();
- RasterizerGLES2::make_current();
- break;
- } else {
- gl_initialization_error = true;
- break;
- }
- }
- }
-
- 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;
- }
-
- video_driver_index = p_video_driver;
- visual_server = memnew(VisualServerRaster);
- // FIXME: Reimplement threaded rendering
- if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) {
- visual_server = memnew(VisualServerWrapMT(visual_server, false));
- }
-
- visual_server->init();
- //visual_server->cursor_set_visible(false, 0);
+ Error result = OS_UIKit::initialize(p_desired, p_video_driver, p_audio_driver);
- // reset this to what it should be, it will have been set to 0 after visual_server->init() is called
- if (use_gl3) {
- RasterizerStorageGLES3::system_fbo = gl_view_base_fb;
- } else {
- RasterizerStorageGLES2::system_fbo = gl_view_base_fb;
+ if (result != OK) {
+ return result;
}
- AudioDriverManager::initialize(p_audio_driver);
-
- input = memnew(InputDefault);
-
ios = memnew(iOS);
Engine::get_singleton()->add_singleton(Engine::Singleton("iOS", ios));
- joypad_iphone = memnew(JoypadIPhone);
-
return OK;
-};
-
-MainLoop *OSIPhone::get_main_loop() const {
- return main_loop;
-};
-
-void OSIPhone::set_main_loop(MainLoop *p_main_loop) {
- main_loop = p_main_loop;
-
- if (main_loop) {
- input->set_main_loop(p_main_loop);
- main_loop->init();
- }
-};
-
-bool OSIPhone::iterate() {
- if (!main_loop) {
- return true;
- }
-
- return Main::iteration();
-};
-
-void OSIPhone::key(uint32_t p_key, bool p_pressed) {
- Ref ev;
- ev.instance();
- ev->set_echo(false);
- ev->set_pressed(p_pressed);
- ev->set_scancode(p_key);
- ev->set_physical_scancode(p_key);
- ev->set_unicode(p_key);
- perform_event(ev);
-};
-
-void OSIPhone::pencil_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick) {
- Ref ev;
- ev.instance();
- ev->set_button_index(1);
- ev->set_pressed(p_pressed);
- ev->set_position(Vector2(p_x, p_y));
- ev->set_global_position(Vector2(p_x, p_y));
- ev->set_doubleclick(p_doubleclick);
- perform_event(ev);
-};
-
-void OSIPhone::pencil_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y, float p_force) {
- Ref ev;
- ev.instance();
- ev->set_pressure(p_force);
- ev->set_position(Vector2(p_x, p_y));
- ev->set_global_position(Vector2(p_x, p_y));
- ev->set_relative(Vector2(p_x - p_prev_x, p_y - p_prev_y));
- perform_event(ev);
-};
-
-void OSIPhone::pencil_cancelled(int p_idx) {
- pencil_press(p_idx, -1, -1, false, false);
-}
-
-void OSIPhone::touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick) {
- if (GLOBAL_DEF("debug/disable_touch", false)) {
- return;
- }
-
- Ref ev;
- ev.instance();
-
- ev->set_index(p_idx);
- ev->set_pressed(p_pressed);
- ev->set_position(Vector2(p_x, p_y));
- perform_event(ev);
-};
-
-void OSIPhone::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)) {
- return;
- }
-
- Ref ev;
- ev.instance();
- 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 OSIPhone::perform_event(const Ref &p_event) {
- input->parse_input_event(p_event);
-}
-
-void OSIPhone::touches_cancelled(int p_idx) {
- touch_press(p_idx, -1, -1, false, false);
-}
-
-static const float ACCEL_RANGE = 1;
-
-void OSIPhone::update_gravity(float p_x, float p_y, float p_z) {
- input->set_gravity(Vector3(p_x, p_y, p_z));
-};
-
-void OSIPhone::update_accelerometer(float p_x, float p_y, float p_z) {
- // Found out the Z should not be negated! Pass as is!
- input->set_accelerometer(Vector3(p_x / (float)ACCEL_RANGE, p_y / (float)ACCEL_RANGE, p_z / (float)ACCEL_RANGE));
-};
-
-void OSIPhone::update_magnetometer(float p_x, float p_y, float p_z) {
- input->set_magnetometer(Vector3(p_x, p_y, p_z));
-};
-
-void OSIPhone::update_gyroscope(float p_x, float p_y, float p_z) {
- input->set_gyroscope(Vector3(p_x, p_y, p_z));
-};
-
-int OSIPhone::get_unused_joy_id() {
- return input->get_unused_joy_id();
-};
-
-void OSIPhone::joy_connection_changed(int p_idx, bool p_connected, String p_name) {
- input->joy_connection_changed(p_idx, p_connected, p_name);
-};
-
-void OSIPhone::joy_button(int p_device, int p_button, bool p_pressed) {
- input->joy_button(p_device, p_button, p_pressed);
-};
-
-void OSIPhone::joy_axis(int p_device, int p_axis, float p_value) {
- input->joy_axis(p_device, p_axis, p_value);
-};
-
-void OSIPhone::delete_main_loop() {
- if (main_loop) {
- main_loop->finish();
- memdelete(main_loop);
- };
-
- main_loop = NULL;
-};
-
void OSIPhone::finalize() {
- delete_main_loop();
-
- if (joypad_iphone) {
- memdelete(joypad_iphone);
- }
-
- if (input) {
- memdelete(input);
- }
-
if (ios) {
memdelete(ios);
}
- visual_server->finish();
- memdelete(visual_server);
- // memdelete(rasterizer);
-}
-
-void OSIPhone::set_mouse_show(bool p_show) {
- // Not supported for iOS
-}
-
-void OSIPhone::set_mouse_grab(bool p_grab) {
- // Not supported for iOS
-}
-
-bool OSIPhone::is_mouse_grab_enabled() const {
- // Not supported for iOS
- return true;
-}
-
-Point2 OSIPhone::get_mouse_position() const {
- // Not supported for iOS
- return Point2();
-}
-
-int OSIPhone::get_mouse_button_state() const {
- // Not supported for iOS
- return 0;
-}
-
-void OSIPhone::set_window_title(const String &p_title) {
- // Not supported for iOS
+ OS_UIKit::finalize();
}
void OSIPhone::alert(const String &p_alert, const String &p_title) {
@@ -374,79 +109,6 @@
iOS::alert(utf8_alert.get_data(), utf8_title.get_data());
}
-// 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);
-}
-
-void register_dynamic_symbol(char *name, void *address) {
- OSIPhone::dynamic_symbol_lookup_table[String(name)] = address;
-}
-
-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);
- 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);
-}
-
-void OSIPhone::set_video_mode(const VideoMode &p_video_mode, int p_screen) {
- video_mode = p_video_mode;
-}
-
-OS::VideoMode OSIPhone::get_video_mode(int p_screen) const {
- return video_mode;
-}
-
-void OSIPhone::get_fullscreen_mode_list(List *p_list, int p_screen) const {
- p_list->push_back(video_mode);
-}
-
-void OSIPhone::set_offscreen_gl_context(EAGLContext *p_context) {
- offscreen_gl_context = p_context;
-}
-
-bool OSIPhone::is_offscreen_gl_available() const {
- return offscreen_gl_context;
-}
-
-void OSIPhone::set_offscreen_gl_current(bool p_current) {
- if (p_current) {
- [EAGLContext setCurrentContext:offscreen_gl_context];
- } else {
- [EAGLContext setCurrentContext:nil];
- }
-}
-
-bool OSIPhone::can_draw() const {
- if (native_video_is_playing())
- return false;
- return true;
-}
-
-int OSIPhone::set_base_framebuffer(int p_fb) {
- // gl_view_base_fb has not been updated yet
- RasterizerStorageGLES3::system_fbo = p_fb;
-
- return 0;
-}
-
bool OSIPhone::has_virtual_keyboard() const {
return true;
};
@@ -473,30 +135,6 @@ void register_dynamic_symbol(char *name, void *address) {
return virtual_keyboard_height;
}
-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_keep_screen_on(bool p_enabled) {
- OS::set_keep_screen_on(p_enabled);
- [UIApplication sharedApplication].idleTimerDisabled = p_enabled;
-};
-
-String OSIPhone::get_user_data_dir() const {
- return data_dir;
-}
-
String OSIPhone::get_name() const {
return "iOS";
}
@@ -511,10 +149,6 @@ void register_dynamic_symbol(char *name, void *address) {
return String::utf8([text UTF8String]);
}
-String OSIPhone::get_cache_path() const {
- return cache_dir;
-}
-
String OSIPhone::get_model_name() const {
String model = ios->get_model();
if (model != "") {
@@ -524,10 +158,6 @@ void register_dynamic_symbol(char *name, void *address) {
return OS_Unix::get_model_name();
}
-Size2 OSIPhone::get_window_size() const {
- return Vector2(video_mode.width, video_mode.height);
-}
-
int OSIPhone::get_screen_dpi(int p_screen) const {
struct utsname systemInfo;
uname(&systemInfo);
@@ -592,17 +222,6 @@ void register_dynamic_symbol(char *name, void *address) {
return true;
}
-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("-", "_");
-}
-
Error OSIPhone::native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track) {
FileAccess *f = FileAccess::open(p_path, FileAccess::READ);
bool exists = f && f->is_open();
@@ -688,21 +307,23 @@ void register_dynamic_symbol(char *name, void *address) {
return p_feature == "mobile";
}
-void add_ios_init_callback(init_callback cb) {
- if (ios_init_callbacks_count == ios_init_callbacks_capacity) {
- void *new_ptr = realloc(ios_init_callbacks, sizeof(cb) * 32);
- if (new_ptr) {
- ios_init_callbacks = (init_callback *)(new_ptr);
- ios_init_callbacks_capacity += 32;
+void register_dynamic_symbol(char *name, void *address) {
+ OSIPhone::dynamic_symbol_lookup_table[String(name)] = address;
+}
+
+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);
+ if (ptr) {
+ p_symbol_handle = *ptr;
+ return OK;
}
}
- if (ios_init_callbacks_capacity > ios_init_callbacks_count) {
- ios_init_callbacks[ios_init_callbacks_count] = cb;
- ++ios_init_callbacks_count;
- }
+ return OS_Unix::get_dynamic_library_symbol_handle(p_library_handle, p_name, p_symbol_handle, p_optional);
}
-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]();
}
@@ -710,25 +331,6 @@ void add_ios_init_callback(init_callback cb) {
ios_init_callbacks = NULL;
ios_init_callbacks_count = 0;
ios_init_callbacks_capacity = 0;
-
- main_loop = NULL;
- visual_server = NULL;
- offscreen_gl_context = NULL;
-
- // can't call set_data_dir from here, since it requires DirAccess
- // which is initialized in initialize_core
- 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);
};
OSIPhone::~OSIPhone() {
@@ -769,5 +371,3 @@ void add_ios_init_callback(init_callback cb) {
audio_driver.start();
}
}
-
-#endif
diff --git a/platform/iphone/platform_config.h b/platform/iphone/platform_config.h
index a1b598fe2aba..e2d339f72c38 100644
--- a/platform/iphone/platform_config.h
+++ b/platform/iphone/platform_config.h
@@ -28,18 +28,4 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#include
-
-#define GLES2_INCLUDE_H
-#define GLES3_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")
+#import "platform/uikit/uikit_platform_config.h"
diff --git a/platform/tvos/SCsub b/platform/tvos/SCsub
new file mode 100644
index 000000000000..e71c9572bb78
--- /dev/null
+++ b/platform/tvos/SCsub
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+
+Import("env")
+
+tvos_lib = [
+ "#platform/uikit/uikit_app_delegate.m",
+ "#platform/uikit/uikit_display_layer.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",
+ "godot_tvos.mm",
+ "os_tvos.mm",
+ "main.m",
+ "app_delegate.mm",
+ "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..ce79d4faecd8
--- /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 // IPHONE_API_H
diff --git a/platform/tvos/app_delegate.h b/platform/tvos/app_delegate.h
new file mode 100644
index 000000000000..f23ccd684834
--- /dev/null
+++ b/platform/tvos/app_delegate.h
@@ -0,0 +1,40 @@
+/*************************************************************************/
+/* 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;
+
+@interface AppDelegate : NSObject
+
+@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..98514800024d
--- /dev/null
+++ b/platform/tvos/app_delegate.mm
@@ -0,0 +1,137 @@
+/*************************************************************************/
+/* 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/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"
+
+#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 FALSE;
+ }
+
+ // WARNING: We must *always* create the GodotView after we have constructed the
+ // OS with tvos_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];
+
+ bool keep_screen_on = bool(GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true));
+ OSAppleTV::get_singleton()->set_keep_screen_on(keep_screen_on);
+
+ return TRUE;
+}
+
+- (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..db8b97bf0d29
--- /dev/null
+++ b/platform/tvos/detect.py
@@ -0,0 +1,176 @@
+import os
+import sys
+from methods import detect_darwin_sdk_path, get_darwin_sdk_version
+
+
+def is_active():
+ return True
+
+
+def get_name():
+ return "tvOS"
+
+
+def can_build():
+ if sys.platform == "darwin":
+ if get_darwin_sdk_version("tvos") < 13.0:
+ print("Detected tvOS SDK version older than 13")
+ return False
+ return True
+ elif "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),
+ ]
+
+
+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_TVOS" 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=("-isysroot $TVOSSDK -mappletvsimulator-version-min=10.0").split())
+ env.Append(LINKFLAGS=["-mappletvsimulator-version-min=10.0"])
+ env["LIBSUFFIX"] = ".simulator" + env["LIBSUFFIX"]
+ else:
+ detect_darwin_sdk_path("tvos", env)
+ env.Append(CCFLAGS=("-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 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", "GLES_ENABLED", "COREAUDIO_ENABLED"])
diff --git a/platform/tvos/export/export.cpp b/platform/tvos/export/export.cpp
new file mode 100644
index 000000000000..7f3df76b793b
--- /dev/null
+++ b/platform/tvos/export/export.cpp
@@ -0,0 +1,1329 @@
+/*************************************************************************/
+/* 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/io/marshalls.h"
+#include "core/io/resource_saver.h"
+#include "core/io/zip_io.h"
+#include "core/os/file_access.h"
+#include "core/os/os.h"
+#include "core/project_settings.h"
+#include "core/version.h"
+#include "editor/editor_export.h"
+#include "editor/editor_node.h"
+#include "editor/editor_settings.h"
+#include "godot_plugin_config.h"
+#include "main/splash.gen.h"
+#include "platform/tvos/logo.gen.h"
+#include "string.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++) {
+ CharType 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()) {
+ ea->plugins_lock.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;
+ }
+ }
+ }
+
+ ea->plugins_lock.unlock();
+ }
+
+ 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);
+ virtual void get_export_options(List *r_options);
+
+public:
+ virtual String get_name() const { return "tvOS"; }
+ virtual String get_os_name() const { return "tvOS"; }
+ virtual Ref get_logo() const { return logo; }
+
+ virtual bool should_update_export_options() {
+ 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 {
+ 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);
+
+ virtual bool can_export(const Ref &p_preset, String &r_error, bool &r_missing_templates) const;
+
+ virtual void get_platform_features(List *r_features) {
+ r_features->push_back("mobile");
+ r_features->push_back("tvOS");
+ }
+
+ virtual void resolve_platform_feature_priorities(const Ref &p_preset, Set &p_features) {
+ }
+
+ 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.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.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 path;
+ String current_dir = p_da->get_current_dir();
+ p_da->list_dir_begin();
+ while ((path = p_da->get_next()).length() != 0) {
+ 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;
+ }
+ }
+ }
+ 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, true);
+ }
+
+ // 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.empty() || value.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.empty() || value.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, 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 framework: " + 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_or_pvrtc();
+ if (etc_error != String()) {
+ valid = false;
+ err += etc_error;
+ }
+
+ if (!err.empty())
+ r_error = err;
+
+ return valid;
+}
+
+EditorExportPlatformTVOS::EditorExportPlatformTVOS() {
+ Ref img = memnew(Image(_tvos_logo));
+ logo.instance();
+ 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.instance();
+
+ 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..eadca57f8cf5
--- /dev/null
+++ b/platform/tvos/export/godot_plugin_config.h
@@ -0,0 +1,380 @@
+/*************************************************************************/
+/* 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_list.h"
+#include "core/io/config_file.h"
+#include "core/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 {
+ static const char *PLUGIN_CONFIG_EXT;
+
+ static const char *CONFIG_SECTION;
+ static const char *CONFIG_NAME_KEY;
+ static const char *CONFIG_BINARY_KEY;
+ static const char *CONFIG_INITIALIZE_KEY;
+ static const char *CONFIG_DEINITIALIZE_KEY;
+
+ static const char *DEPENDENCIES_SECTION;
+ static const char *DEPENDENCIES_LINKED_KEY;
+ static const char *DEPENDENCIES_EMBEDDED_KEY;
+ static const char *DEPENDENCIES_SYSTEM_KEY;
+ static const char *DEPENDENCIES_CAPABILITIES_KEY;
+ static const char *DEPENDENCIES_FILES_KEY;
+ static const char *DEPENDENCIES_LINKER_FLAGS;
+
+ static const char *PLIST_SECTION;
+
+ 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;
+};
+
+const char *PluginConfigTVOS::PLUGIN_CONFIG_EXT = ".gdatvp";
+
+const char *PluginConfigTVOS::CONFIG_SECTION = "config";
+const char *PluginConfigTVOS::CONFIG_NAME_KEY = "name";
+const char *PluginConfigTVOS::CONFIG_BINARY_KEY = "binary";
+const char *PluginConfigTVOS::CONFIG_INITIALIZE_KEY = "initialization";
+const char *PluginConfigTVOS::CONFIG_DEINITIALIZE_KEY = "deinitialization";
+
+const char *PluginConfigTVOS::DEPENDENCIES_SECTION = "dependencies";
+const char *PluginConfigTVOS::DEPENDENCIES_LINKED_KEY = "linked";
+const char *PluginConfigTVOS::DEPENDENCIES_EMBEDDED_KEY = "embedded";
+const char *PluginConfigTVOS::DEPENDENCIES_SYSTEM_KEY = "system";
+const char *PluginConfigTVOS::DEPENDENCIES_CAPABILITIES_KEY = "capabilities";
+const char *PluginConfigTVOS::DEPENDENCIES_FILES_KEY = "files";
+const char *PluginConfigTVOS::DEPENDENCIES_LINKER_FLAGS = "linker_flags";
+
+const char *PluginConfigTVOS::PLIST_SECTION = "plist";
+
+static inline String resolve_local_dependency_path(String plugin_config_dir, String dependency_path) {
+ String absolute_path;
+
+ if (dependency_path.empty()) {
+ return absolute_path;
+ }
+
+ if (dependency_path.is_abs_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.empty()) {
+ return absolute_path;
+ }
+
+ if (dependency_path.is_abs_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.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.empty()) {
+ continue;
+ }
+
+ paths.push_back(path);
+ }
+
+ return paths;
+}
+
+static inline bool validate_plugin(PluginConfigTVOS &plugin_config) {
+ bool valid_name = !plugin_config.name.empty();
+ bool valid_binary_name = !plugin_config.binary.empty();
+ bool valid_initialize = !plugin_config.initialization_method.empty();
+ bool valid_deinitialize = !plugin_config.deinitialization_method.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.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..0f89200c1ca6
--- /dev/null
+++ b/platform/tvos/godot_tvos.mm
@@ -0,0 +1,117 @@
+/*************************************************************************/
+/* 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/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);
+
+ char *fargv[64];
+ for (int i = 0; i < argc; i++) {
+ fargv[i] = argv[i];
+ }
+ fargv[argc] = NULL;
+ 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;
+ }
+
+ 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..b96b046ac720
--- /dev/null
+++ b/platform/tvos/godot_view.mm
@@ -0,0 +1,139 @@
+/*************************************************************************/
+/* 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/project_settings.h"
+#include "os_tvos.h"
+#include "servers/audio_server.h"
+
+#import
+#import
+
+#import "godot_view_gesture_recognizer.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;
+}
+
+// Stop animating and release resources when they are no longer needed.
+- (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");
+ OSAppleTV::get_singleton()->joy_button(joy_id, JOY_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");
+ OSAppleTV::get_singleton()->joy_button(joy_id, JOY_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");
+ OSAppleTV::get_singleton()->joy_button(joy_id, JOY_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..a66f5f0ea456
--- /dev/null
+++ b/platform/tvos/godot_view_controller.mm
@@ -0,0 +1,110 @@
+/*************************************************************************/
+/* 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/project_settings.h"
+#import "godot_view.h"
+#import "godot_view_renderer.h"
+#import "keyboard_input_view.h"
+#include "os_tvos.h"
+
+@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];
+ [view initializeRendering];
+
+ 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)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..8708a70b9aec
--- /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. */
+/*************************************************************************/
+
+// GodotViewGestureRecognizer allows tvOS 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..91bd96cb615f
--- /dev/null
+++ b/platform/tvos/godot_view_gesture_recognizer.mm
@@ -0,0 +1,123 @@
+/*************************************************************************/
+/* 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/project_settings.h"
+#include "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..72b9607b3da4
--- /dev/null
+++ b/platform/tvos/godot_view_renderer.mm
@@ -0,0 +1,56 @@
+/*************************************************************************/
+/* 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 "os_tvos.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..a596e01671d7
--- /dev/null
+++ b/platform/tvos/keyboard_input_view.mm
@@ -0,0 +1,216 @@
+/*************************************************************************/
+/* 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 "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++) {
+ OSAppleTV::get_singleton()->key(KEY_BACKSPACE, true);
+ OSAppleTV::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 = KEY_ENTER;
+ break;
+ case 8198:
+ character = KEY_SPACE;
+ break;
+ default:
+ break;
+ }
+
+ OSAppleTV::get_singleton()->key(character, true);
+ OSAppleTV::get_singleton()->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..d7dd6ac6b789
--- /dev/null
+++ b/platform/tvos/main.m
@@ -0,0 +1,51 @@
+/*************************************************************************/
+/* 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[]) {
+ 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..be7d28c8b753
--- /dev/null
+++ b/platform/tvos/os_tvos.h
@@ -0,0 +1,83 @@
+/*************************************************************************/
+/* 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 "tvos.h"
+
+class OSAppleTV : public OS_UIKit {
+private:
+ static HashMap dynamic_symbol_lookup_table;
+ friend void register_dynamic_symbol(char *name, void *address);
+
+ tvOS *tvos;
+
+ bool overrides_menu_button = true;
+
+ virtual Error initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver);
+ virtual void finalize();
+
+public:
+ static OSAppleTV *get_singleton();
+
+ OSAppleTV(String p_data_dir, String p_cache_dir);
+ ~OSAppleTV();
+
+ virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional = false);
+
+ virtual void alert(const String &p_alert, const String &p_title = "ALERT!");
+
+ virtual String get_name() const;
+ virtual String get_model_name() const;
+
+ virtual bool _check_internal_feature_support(const String &p_feature);
+
+ virtual int get_screen_dpi(int p_screen = -1) const;
+
+ virtual bool has_virtual_keyboard() const;
+ virtual void show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), bool p_multiline = false, int p_max_input_length = -1, int p_cursor_start = -1, int p_cursor_end = -1);
+ virtual void hide_virtual_keyboard();
+ virtual int get_virtual_keyboard_height() const;
+
+ virtual Rect2 get_window_safe_area() const;
+
+ virtual bool has_touchscreen_ui_hint() const;
+
+ void on_focus_out();
+ void on_focus_in();
+
+ bool get_overrides_menu_button() const;
+ void set_overrides_menu_button(bool p_flag);
+};
+
+#endif // OS_IPHONE_H
diff --git a/platform/tvos/os_tvos.mm b/platform/tvos/os_tvos.mm
new file mode 100644
index 000000000000..89a1761c74e3
--- /dev/null
+++ b/platform/tvos/os_tvos.mm
@@ -0,0 +1,238 @@
+/*************************************************************************/
+/* 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"
+
+#include "drivers/gles2/rasterizer_gles2.h"
+#include "drivers/gles3/rasterizer_gles3.h"
+#include "servers/visual/visual_server_raster.h"
+#include "servers/visual/visual_server_wrap_mt.h"
+
+#include "main/main.h"
+
+#include "core/io/file_access_pack.h"
+#include "core/os/dir_access.h"
+#include "core/os/file_access.h"
+#include "core/project_settings.h"
+#include "drivers/unix/syslog_logger.h"
+
+#import "app_delegate.h"
+#import "godot_view.h"
+#import "godot_view_controller.h"
+#import "keyboard_input_view.h"
+
+#import
+#include
+#import
+
+// 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;
+ }
+}
+
+OSAppleTV *OSAppleTV::get_singleton() {
+ return (OSAppleTV *)OS::get_singleton();
+}
+
+Error OSAppleTV::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) {
+ Error result = OS_UIKit::initialize(p_desired, p_video_driver, p_audio_driver);
+
+ if (result != OK) {
+ return result;
+ }
+
+ tvos = memnew(tvOS);
+ Engine::get_singleton()->add_singleton(Engine::Singleton("tvOS", tvos));
+
+ return OK;
+}
+
+void OSAppleTV::finalize() {
+ if (tvos) {
+ memdelete(tvos);
+ }
+
+ OS_UIKit::finalize();
+}
+
+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());
+}
+
+bool OSAppleTV::has_virtual_keyboard() const {
+ return true;
+};
+
+void OSAppleTV::show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect, bool p_multiline, int p_max_input_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 OSAppleTV::hide_virtual_keyboard() {
+ [AppDelegate.viewController.keyboardView resignFirstResponder];
+}
+
+int OSAppleTV::get_virtual_keyboard_height() const {
+ return 0;
+}
+
+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();
+}
+
+int OSAppleTV::get_screen_dpi(int p_screen) const {
+ return 96;
+}
+
+Rect2 OSAppleTV::get_window_safe_area() const {
+ if (@available(tvOS 11, *)) {
+ UIEdgeInsets insets = UIEdgeInsetsZero;
+ UIView *view = AppDelegate.viewController.godotView;
+
+ if ([view respondsToSelector:@selector(safeAreaInsets)]) {
+ insets = [view safeAreaInsets];
+ }
+
+ float scale = [UIScreen mainScreen].nativeScale;
+ Size2i insets_position = Size2i(insets.left, insets.top) * scale;
+ Size2i insets_size = Size2i(insets.left + insets.right, insets.top + insets.bottom) * scale;
+
+ return Rect2i(insets_position, get_window_size() - insets_size);
+ } else {
+ return Rect2i(Size2i(0, 0), get_window_size());
+ }
+}
+
+bool OSAppleTV::has_touchscreen_ui_hint() const {
+ return true;
+}
+
+bool OSAppleTV::_check_internal_feature_support(const String &p_feature) {
+ return p_feature == "mobile";
+}
+
+void register_dynamic_symbol(char *name, void *address) {
+ OSAppleTV::dynamic_symbol_lookup_table[String(name)] = address;
+}
+
+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);
+}
+
+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 = NULL;
+ tvos_init_callbacks_count = 0;
+ tvos_init_callbacks_capacity = 0;
+};
+
+OSAppleTV::~OSAppleTV() {
+}
+
+void OSAppleTV::on_focus_out() {
+ if (is_focused) {
+ is_focused = false;
+
+ if (get_main_loop()) {
+ get_main_loop()->notification(MainLoop::NOTIFICATION_WM_FOCUS_OUT);
+ }
+
+ [AppDelegate.viewController.godotView stopRendering];
+
+ audio_driver.stop();
+ }
+}
+
+void OSAppleTV::on_focus_in() {
+ if (!is_focused) {
+ is_focused = true;
+
+ if (get_main_loop()) {
+ get_main_loop()->notification(MainLoop::NOTIFICATION_WM_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..e2d339f72c38
--- /dev/null
+++ b/platform/tvos/platform_config.h
@@ -0,0 +1,31 @@
+/*************************************************************************/
+/* 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. */
+/*************************************************************************/
+
+#import "platform/uikit/uikit_platform_config.h"
diff --git a/platform/tvos/tvos.h b/platform/tvos/tvos.h
new file mode 100644
index 000000000000..b81ce3bbb626
--- /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.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..7bd9283c76cd
--- /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 %ls\n", ret.c_str());
+
+ 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 *services;
+
++ (void)addService:(ApplicationDelegateService *)service;
+
+@end
diff --git a/platform/uikit/uikit_app_delegate.m b/platform/uikit/uikit_app_delegate.m
new file mode 100644
index 000000000000..8914200667ce
--- /dev/null
+++ b/platform/uikit/uikit_app_delegate.m
@@ -0,0 +1,466 @@
+/*************************************************************************/
+/* uikit_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 "uikit_app_delegate.h"
+
+@interface UIKitApplicalitionDelegate ()
+
+@end
+
+@implementation UIKitApplicalitionDelegate
+
+static NSMutableArray]