diff --git a/SConstruct b/SConstruct index ce49308d101a..1e76c431b79a 100644 --- a/SConstruct +++ b/SConstruct @@ -647,7 +647,11 @@ if env["scu_build"]: # are actually handled to change compile options, etc. detect.configure(env) -print(f'Building for platform "{env["platform"]}", architecture "{env["arch"]}", target "{env["target"]}".') +platform_string = env["platform"] +if env.get("simulator"): + platform_string += " (simulator)" +print(f'Building for platform "{platform_string}", architecture "{env["arch"]}", target "{env["target"]}".') + if env.dev_build: print_info("Developer build, with debug optimization level and debug symbols (unless overridden).") diff --git a/core/input/input_builders.py b/core/input/input_builders.py index c63f595fced6..92b6495d8835 100644 --- a/core/input/input_builders.py +++ b/core/input/input_builders.py @@ -48,7 +48,7 @@ def make_default_controller_mappings(target, source, env): "Windows": "WINDOWS", "Mac OS X": "MACOS", "Android": "ANDROID", - "iOS": "IOS", + "iOS": "APPLE_EMBEDDED", "Web": "WEB", } diff --git a/core/os/os.cpp b/core/os/os.cpp index c4828fe1275a..29e49f8f65fe 100644 --- a/core/os/os.cpp +++ b/core/os/os.cpp @@ -585,7 +585,7 @@ bool OS::has_feature(const String &p_feature) { } #endif -#if defined(IOS_SIMULATOR) +#if defined(IOS_SIMULATOR) || defined(VISIONOS_SIMULATOR) if (p_feature == "simulator") { return true; } diff --git a/doc/classes/EditorExportPlatformAppleEmbedded.xml b/doc/classes/EditorExportPlatformAppleEmbedded.xml new file mode 100644 index 000000000000..790ef3a88daa --- /dev/null +++ b/doc/classes/EditorExportPlatformAppleEmbedded.xml @@ -0,0 +1,13 @@ + + + + Base class for the Apple embedded platform exporters (iOS and visionOS). + + + The base class for Apple embedded platform exporters. These include iOS and visionOS, but not macOS. See the classes inheriting from this one for more details. + + + $DOCS_URL/tutorials/export/exporting_for_ios.html + $DOCS_URL/tutorials/platform/ios/index.html + + diff --git a/doc/classes/EditorExportPlatformPC.xml b/doc/classes/EditorExportPlatformPC.xml index def14e595524..07e46c857c20 100644 --- a/doc/classes/EditorExportPlatformPC.xml +++ b/doc/classes/EditorExportPlatformPC.xml @@ -4,7 +4,7 @@ Base class for the desktop platform exporter (Windows and Linux/BSD). - The base class for the desktop platform exporters. These include Windows and Linux/BSD, but not macOS. See the classes inheriting this one for more details. + The base class for the desktop platform exporters. These include Windows and Linux/BSD, but not macOS. See the classes inheriting from this one for more details. $DOCS_URL/tutorials/export/exporting_for_windows.html diff --git a/doc/classes/EditorExportPlugin.xml b/doc/classes/EditorExportPlugin.xml index 01dff2b53a1b..c9e34d464020 100644 --- a/doc/classes/EditorExportPlugin.xml +++ b/doc/classes/EditorExportPlugin.xml @@ -251,6 +251,57 @@ If no modifications are needed, then an empty [PackedByteArray] should be returned. + + + + + Adds an Apple embedded platform bundle file from the given [param path] to the exported project. + + + + + + + Adds C++ code to the Apple embedded platform export. The final code is created from the code appended by each active export plugin. + + + + + + + Adds a dynamic library (*.dylib, *.framework) to the Linking Phase in the Apple embedded platform's Xcode project and embeds it into the resulting binary. + [b]Note:[/b] For static libraries (*.a), this works in the same way as [method add_apple_embedded_platform_framework]. + [b]Note:[/b] This method should not be used for System libraries as they are already present on the device. + + + + + + + Adds a static library (*.a) or a dynamic library (*.dylib, *.framework) to the Linking Phase to the Apple embedded platform's Xcode project. + + + + + + + Adds linker flags for the Apple embedded platform export. + + + + + + + Adds additional fields to the Apple embedded platform's project Info.plist file. + + + + + + + Adds a static library from the given [param path] to the Apple embedded platform project. + + @@ -262,55 +313,55 @@ [param file] will not be imported, so consider using [method _customize_resource] to remap imported resources. - + Adds an iOS bundle file from the given [param path] to the exported project. - + - Adds a C++ code to the iOS export. The final code is created from the code appended by each active export plugin. + Adds C++ code to the iOS 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 iOS'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]. + [b]Note:[/b] For static libraries (*.a), this works the in same way as [method add_apple_embedded_platform_framework]. [b]Note:[/b] 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 iOS's Xcode project. + Adds a static library (*.a) or a dynamic library (*.dylib, *.framework) to the Linking Phase to the iOS Xcode project. - + Adds linker flags for the iOS export. - + - Adds content for iOS Property List files. + Adds additional fields to the iOS project Info.plist file. - + - Adds a static lib from the given [param path] to the iOS project. + Adds a static library from the given [param path] to the iOS project. diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index cef1253b6ddd..df7401ccd67e 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -900,6 +900,9 @@ MacOS override for [member display/display_server/driver]. + + visionOS override for [member display/display_server/driver]. + Windows override for [member display/display_server/driver]. @@ -3163,6 +3166,11 @@ - [code]metal[/code] (default), Metal from native drivers, only supported on Apple Silicon Macs. On Intel Macs, it will automatically fall back to [code]vulkan[/code] as Metal support is not implemented. - [code]vulkan[/code], Vulkan over Metal via MoltenVK, supported on both Apple Silicon and Intel Macs. + + visionOS override for [member rendering/rendering_device/driver]. + Only one option is supported: + - [code]metal[/code] (default), Metal from native drivers. + Windows override for [member rendering/rendering_device/driver]. Two options are supported: diff --git a/drivers/SCsub b/drivers/SCsub index b5624b177809..5b891631ce8d 100644 --- a/drivers/SCsub +++ b/drivers/SCsub @@ -14,8 +14,6 @@ SConscript("windows/SCsub") # Sounds drivers SConscript("alsa/SCsub") -if env["platform"] == "ios" or env["platform"] == "macos": - SConscript("coreaudio/SCsub") SConscript("pulseaudio/SCsub") if env["platform"] == "windows": SConscript("wasapi/SCsub") @@ -28,8 +26,11 @@ if env["xaudio2"]: SConscript("xaudio2/SCsub") # Shared Apple platform drivers -if env["platform"] in ["macos", "ios"]: +if env["platform"] in ["macos", "ios", "visionos"]: SConscript("apple/SCsub") + SConscript("coreaudio/SCsub") +if env["platform"] in ["ios", "visionos"]: + SConscript("apple_embedded/SCsub") # Accessibility if env["accesskit"] and env["platform"] in ["macos", "windows", "linuxbsd"]: @@ -37,7 +38,7 @@ if env["accesskit"] and env["platform"] in ["macos", "windows", "linuxbsd"]: # Midi drivers SConscript("alsamidi/SCsub") -if env["platform"] in ["macos", "ios"]: +if env["platform"] in ["macos"]: SConscript("coremidi/SCsub") SConscript("winmidi/SCsub") diff --git a/drivers/apple_embedded/SCsub b/drivers/apple_embedded/SCsub new file mode 100644 index 000000000000..7a4b82bd9e08 --- /dev/null +++ b/drivers/apple_embedded/SCsub @@ -0,0 +1,16 @@ +#!/usr/bin/env python +from misc.utility.scons_hints import * + +Import("env") + +env_apple_embedded = env.Clone() + +# Enable module support +env_apple_embedded.Append(CCFLAGS=["-fmodules", "-fcxx-modules"]) + +# Use bundled Vulkan headers +vulkan_dir = "#thirdparty/vulkan" +env_apple_embedded.Prepend(CPPPATH=[vulkan_dir, vulkan_dir + "/include"]) + +# Driver source files +env_apple_embedded.add_source_files(env.drivers_sources, "*.mm") diff --git a/platform/ios/app_delegate.h b/drivers/apple_embedded/app_delegate_service.h similarity index 81% rename from platform/ios/app_delegate.h rename to drivers/apple_embedded/app_delegate_service.h index 2f29e0871da7..6314d6060b3a 100644 --- a/platform/ios/app_delegate.h +++ b/drivers/apple_embedded/app_delegate_service.h @@ -1,5 +1,5 @@ /**************************************************************************/ -/* app_delegate.h */ +/* app_delegate_service.h */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -32,18 +32,11 @@ #import -@class ViewController; +@class GDTViewController; -// FIXME: Add support for both OpenGL and Vulkan when OpenGL is implemented again, -// so it can't be done with compilation time branching. -//#if defined(GLES3_ENABLED) -//@interface AppDelegate : NSObject { -//#endif -//#if defined(VULKAN_ENABLED) -@interface AppDelegate : NSObject -//#endif +@interface GDTAppDelegateService : NSObject @property(strong, nonatomic) UIWindow *window; -@property(strong, class, readonly, nonatomic) ViewController *viewController; +@property(strong, class, readonly, nonatomic) GDTViewController *viewController; @end diff --git a/platform/ios/app_delegate.mm b/drivers/apple_embedded/app_delegate_service.mm similarity index 88% rename from platform/ios/app_delegate.mm rename to drivers/apple_embedded/app_delegate_service.mm index 37d269643488..07b52b9d9c66 100644 --- a/platform/ios/app_delegate.mm +++ b/drivers/apple_embedded/app_delegate_service.mm @@ -1,5 +1,5 @@ /**************************************************************************/ -/* app_delegate.mm */ +/* app_delegate_service.mm */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,10 +28,10 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#import "app_delegate.h" +#import "app_delegate_service.h" -#import "godot_view.h" -#import "os_ios.h" +#import "godot_view_apple_embedded.h" +#import "os_apple_embedded.h" #import "view_controller.h" #include "core/config/project_settings.h" @@ -46,10 +46,10 @@ extern int gargc; extern char **gargv; -extern int ios_main(int, char **); -extern void ios_finish(); +extern int apple_embedded_main(int, char **); +extern void apple_embedded_finish(); -@implementation AppDelegate +@implementation GDTAppDelegateService enum { SESSION_CATEGORY_AMBIENT, @@ -60,9 +60,9 @@ @implementation AppDelegate SESSION_CATEGORY_SOLO_AMBIENT }; -static ViewController *mainViewController = nil; +static GDTViewController *mainViewController = nil; -+ (ViewController *)viewController { ++ (GDTViewController *)viewController { return mainViewController; } @@ -71,12 +71,15 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( // TODO: logo screen is not displayed while shaders are compiling // DummyViewController(Splash/LoadingViewController) -> setup -> GodotViewController - CGRect windowBounds = [[UIScreen mainScreen] bounds]; - +#if !defined(VISIONOS_ENABLED) // Create a full-screen window + CGRect windowBounds = [[UIScreen mainScreen] bounds]; self.window = [[UIWindow alloc] initWithFrame:windowBounds]; +#else + self.window = [[UIWindow alloc] init]; +#endif - int err = ios_main(gargc, gargv); + int err = apple_embedded_main(gargc, gargv); if (err != 0) { // bail, things did not go very well for us, should probably output a message on screen with our error code... @@ -84,7 +87,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( return NO; } - ViewController *viewController = [[ViewController alloc] init]; + GDTViewController *viewController = [[GDTViewController alloc] init]; viewController.godotView.useCADisplayLink = bool(GLOBAL_DEF("display.iOS/use_cadisplaylink", true)) ? YES : NO; viewController.godotView.renderingInterval = 1.0 / kRenderingFrequency; @@ -135,10 +138,10 @@ - (void)onAudioInterruption:(NSNotification *)notification { if ([notification.name isEqualToString:AVAudioSessionInterruptionNotification]) { if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeBegan]]) { NSLog(@"Audio interruption began"); - OS_IOS::get_singleton()->on_focus_out(); + OS_AppleEmbedded::get_singleton()->on_focus_out(); } else if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeEnded]]) { NSLog(@"Audio interruption ended"); - OS_IOS::get_singleton()->on_focus_in(); + OS_AppleEmbedded::get_singleton()->on_focus_in(); } } } @@ -150,7 +153,7 @@ - (void)applicationDidReceiveMemoryWarning:(UIApplication *)application { } - (void)applicationWillTerminate:(UIApplication *)application { - ios_finish(); + apple_embedded_finish(); } // When application goes to background (e.g. user switches to another app or presses Home), @@ -164,19 +167,19 @@ - (void)applicationWillTerminate:(UIApplication *)application { // notification panel by swiping from the upper part of the screen. - (void)applicationWillResignActive:(UIApplication *)application { - OS_IOS::get_singleton()->on_focus_out(); + OS_AppleEmbedded::get_singleton()->on_focus_out(); } - (void)applicationDidBecomeActive:(UIApplication *)application { - OS_IOS::get_singleton()->on_focus_in(); + OS_AppleEmbedded::get_singleton()->on_focus_in(); } - (void)applicationDidEnterBackground:(UIApplication *)application { - OS_IOS::get_singleton()->on_enter_background(); + OS_AppleEmbedded::get_singleton()->on_enter_background(); } - (void)applicationWillEnterForeground:(UIApplication *)application { - OS_IOS::get_singleton()->on_exit_background(); + OS_AppleEmbedded::get_singleton()->on_exit_background(); } - (void)dealloc { diff --git a/drivers/apple_embedded/apple_embedded.h b/drivers/apple_embedded/apple_embedded.h new file mode 100644 index 000000000000..f06d5796baa8 --- /dev/null +++ b/drivers/apple_embedded/apple_embedded.h @@ -0,0 +1,59 @@ +/**************************************************************************/ +/* apple_embedded.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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. */ +/**************************************************************************/ + +#pragma once + +#include "core/object/class_db.h" + +#import + +class AppleEmbedded : public Object { + GDCLASS(AppleEmbedded, Object); + + static void _bind_methods(); + +private: + CHHapticEngine *haptic_engine API_AVAILABLE(ios(13)) = nullptr; + + CHHapticEngine *get_haptic_engine_instance() API_AVAILABLE(ios(13)); + void start_haptic_engine(); + void stop_haptic_engine(); + +public: + static void alert(const char *p_alert, const char *p_title); + + bool supports_haptic_engine(); + void vibrate_haptic_engine(float p_duration_seconds, float p_amplitude); + + String get_model() const; + String get_rate_url(int p_app_id) const; + + AppleEmbedded(); +}; diff --git a/platform/ios/ios.mm b/drivers/apple_embedded/apple_embedded.mm similarity index 82% rename from platform/ios/ios.mm rename to drivers/apple_embedded/apple_embedded.mm index e257b506a946..33d23061f071 100644 --- a/platform/ios/ios.mm +++ b/drivers/apple_embedded/apple_embedded.mm @@ -1,5 +1,5 @@ /**************************************************************************/ -/* ios.mm */ +/* apple_embedded.mm */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,23 +28,23 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#import "ios.h" +#import "apple_embedded.h" -#import "app_delegate.h" +#import "app_delegate_service.h" #import "view_controller.h" #import #import #include -void iOS::_bind_methods() { - ClassDB::bind_method(D_METHOD("get_rate_url", "app_id"), &iOS::get_rate_url); - ClassDB::bind_method(D_METHOD("supports_haptic_engine"), &iOS::supports_haptic_engine); - ClassDB::bind_method(D_METHOD("start_haptic_engine"), &iOS::start_haptic_engine); - ClassDB::bind_method(D_METHOD("stop_haptic_engine"), &iOS::stop_haptic_engine); +void AppleEmbedded::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_rate_url", "app_id"), &AppleEmbedded::get_rate_url); + ClassDB::bind_method(D_METHOD("supports_haptic_engine"), &AppleEmbedded::supports_haptic_engine); + ClassDB::bind_method(D_METHOD("start_haptic_engine"), &AppleEmbedded::start_haptic_engine); + ClassDB::bind_method(D_METHOD("stop_haptic_engine"), &AppleEmbedded::stop_haptic_engine); } -bool iOS::supports_haptic_engine() { +bool AppleEmbedded::supports_haptic_engine() { if (@available(iOS 13, *)) { id capabilities = [CHHapticEngine capabilitiesForHardware]; return capabilities.supportsHaptics; @@ -53,7 +53,7 @@ return false; } -CHHapticEngine *iOS::get_haptic_engine_instance() API_AVAILABLE(ios(13)) { +CHHapticEngine *AppleEmbedded::get_haptic_engine_instance() API_AVAILABLE(ios(13)) { if (haptic_engine == nullptr) { NSError *error = nullptr; haptic_engine = [[CHHapticEngine alloc] initAndReturnError:&error]; @@ -69,7 +69,7 @@ return haptic_engine; } -void iOS::vibrate_haptic_engine(float p_duration_seconds, float p_amplitude) API_AVAILABLE(ios(13)) { +void AppleEmbedded::vibrate_haptic_engine(float p_duration_seconds, float p_amplitude) API_AVAILABLE(ios(13)) { if (@available(iOS 13, *)) { // We need the @available check every time to make the compiler happy... if (supports_haptic_engine()) { CHHapticEngine *cur_haptic_engine = get_haptic_engine_instance(); @@ -117,10 +117,10 @@ } } - NSLog(@"Haptic engine is not supported in this version of iOS"); + NSLog(@"Haptic engine is not supported"); } -void iOS::start_haptic_engine() { +void AppleEmbedded::start_haptic_engine() { if (@available(iOS 13, *)) { if (supports_haptic_engine()) { CHHapticEngine *cur_haptic_engine = get_haptic_engine_instance(); @@ -136,10 +136,10 @@ } } - NSLog(@"Haptic engine is not supported in this version of iOS"); + NSLog(@"Haptic engine is not supported"); } -void iOS::stop_haptic_engine() { +void AppleEmbedded::stop_haptic_engine() { if (@available(iOS 13, *)) { if (supports_haptic_engine()) { CHHapticEngine *cur_haptic_engine = get_haptic_engine_instance(); @@ -155,10 +155,10 @@ } } - NSLog(@"Haptic engine is not supported in this version of iOS"); + NSLog(@"Haptic engine is not supported"); } -void iOS::alert(const char *p_alert, const char *p_title) { +void AppleEmbedded::alert(const char *p_alert, const char *p_title) { NSString *title = [NSString stringWithUTF8String:p_title]; NSString *message = [NSString stringWithUTF8String:p_alert]; @@ -170,10 +170,10 @@ [alert addAction:button]; - [AppDelegate.viewController presentViewController:alert animated:YES completion:nil]; + [GDTAppDelegateService.viewController presentViewController:alert animated:YES completion:nil]; } -String iOS::get_model() const { +String AppleEmbedded::get_model() const { // [[UIDevice currentDevice] model] only returns "iPad" or "iPhone". size_t size; sysctlbyname("hw.machine", nullptr, &size, nullptr, 0); @@ -188,7 +188,7 @@ return String::utf8(str != nullptr ? str : ""); } -String iOS::get_rate_url(int p_app_id) const { +String AppleEmbedded::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_int64(p_app_id)); @@ -197,4 +197,4 @@ return ret; } -iOS::iOS() {} +AppleEmbedded::AppleEmbedded() {} diff --git a/drivers/apple_embedded/display_layer_apple_embedded.h b/drivers/apple_embedded/display_layer_apple_embedded.h new file mode 100644 index 000000000000..552b115a0843 --- /dev/null +++ b/drivers/apple_embedded/display_layer_apple_embedded.h @@ -0,0 +1,42 @@ +/**************************************************************************/ +/* display_layer_apple_embedded.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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. */ +/**************************************************************************/ + +#pragma once + +#import + +@protocol GDTDisplayLayer + +- (void)startRenderDisplayLayer; +- (void)stopRenderDisplayLayer; +- (void)initializeDisplayLayer; +- (void)layoutDisplayLayer; + +@end diff --git a/drivers/apple_embedded/display_server_apple_embedded.h b/drivers/apple_embedded/display_server_apple_embedded.h new file mode 100644 index 000000000000..df7bcd9b345f --- /dev/null +++ b/drivers/apple_embedded/display_server_apple_embedded.h @@ -0,0 +1,233 @@ +/**************************************************************************/ +/* display_server_apple_embedded.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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. */ +/**************************************************************************/ + +#pragma once + +#include "core/input/input.h" +#include "servers/display_server.h" + +#if defined(RD_ENABLED) +#include "servers/rendering/renderer_rd/renderer_compositor_rd.h" +#include "servers/rendering/rendering_device.h" + +#if defined(VULKAN_ENABLED) +#import "rendering_context_driver_vulkan_apple_embedded.h" + +#include "drivers/vulkan/godot_vulkan.h" +#endif // VULKAN_ENABLED + +#if defined(METAL_ENABLED) +#import "drivers/metal/rendering_context_driver_metal.h" +#endif // METAL_ENABLED +#endif // RD_ENABLED + +#if defined(GLES3_ENABLED) +#include "drivers/gles3/rasterizer_gles3.h" +#endif // GLES3_ENABLED + +#import +#import + +class DisplayServerAppleEmbedded : public DisplayServer { + GDSOFTCLASS(DisplayServerAppleEmbedded, DisplayServer); + + _THREAD_SAFE_CLASS_ + +#if defined(RD_ENABLED) + RenderingContextDriver *rendering_context = nullptr; + RenderingDevice *rendering_device = nullptr; +#endif + NativeMenu *native_menu = nullptr; + + id tts = nullptr; + + DisplayServer::ScreenOrientation screen_orientation; + + ObjectID window_attached_instance_id; + + Callable window_event_callback; + Callable window_resize_callback; + Callable input_event_callback; + Callable input_text_callback; + + Callable system_theme_changed; + + int virtual_keyboard_height = 0; + + void perform_event(const Ref &p_event); + + void initialize_tts() const; + +protected: + DisplayServerAppleEmbedded(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error); + ~DisplayServerAppleEmbedded(); + +public: + String rendering_driver; + + static DisplayServerAppleEmbedded *get_singleton(); + + static Vector get_rendering_drivers_func(); + + // MARK: - Events + + virtual void process_events() override; + + virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; + virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; + virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; + virtual void window_set_input_text_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; + virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; + + static void _dispatch_input_events(const Ref &p_event); + void send_input_event(const Ref &p_event) const; + void send_input_text(const String &p_text) const; + void send_window_event(DisplayServer::WindowEvent p_event) const; + void _window_callback(const Callable &p_callable, const Variant &p_arg) const; + + void emit_system_theme_changed(); + + // MARK: - Input + + // MARK: Touches and Apple Pencil + + void touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_double_click); + void touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y, float p_pressure, Vector2 p_tilt); + void touches_canceled(int p_idx); + + // MARK: Keyboard + + void key(Key p_key, char32_t p_char, Key p_unshifted, Key p_physical, NSInteger p_modifier, bool p_pressed, KeyLocation p_location); + bool is_keyboard_active() const; + + // MARK: Motion + + void update_gravity(const Vector3 &p_gravity); + void update_accelerometer(const Vector3 &p_accelerometer); + void update_magnetometer(const Vector3 &p_magnetometer); + void update_gyroscope(const Vector3 &p_gyroscope); + + // MARK: - + + virtual bool has_feature(Feature p_feature) const override; + + virtual bool tts_is_speaking() const override; + virtual bool tts_is_paused() const override; + virtual TypedArray tts_get_voices() const override; + + virtual void tts_speak(const String &p_text, const String &p_voice, int p_volume = 50, float p_pitch = 1.f, float p_rate = 1.f, int p_utterance_id = 0, bool p_interrupt = false) override; + virtual void tts_pause() override; + virtual void tts_resume() override; + virtual void tts_stop() override; + + virtual bool is_dark_mode_supported() const override; + virtual bool is_dark_mode() const override; + virtual void set_system_theme_change_callback(const Callable &p_callable) override; + + virtual Rect2i get_display_safe_area() const override; + + virtual int get_screen_count() const override; + virtual int get_primary_screen() const override; + virtual Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + + virtual Vector get_window_list() const override; + + virtual WindowID get_window_at_screen_position(const Point2i &p_position) const override; + + virtual int64_t window_get_native_handle(HandleType p_handle_type, WindowID p_window = MAIN_WINDOW_ID) const override; + + virtual void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID) override; + virtual ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const override; + + virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) override; + + virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const override; + virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override; + + virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const override; + virtual Point2i window_get_position_with_decorations(WindowID p_window = MAIN_WINDOW_ID) const override; + virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) override; + + virtual void window_set_transient(WindowID p_window, WindowID p_parent) override; + + virtual void window_set_max_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; + virtual Size2i window_get_max_size(WindowID p_window = MAIN_WINDOW_ID) const override; + + virtual void window_set_min_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; + virtual Size2i window_get_min_size(WindowID p_window = MAIN_WINDOW_ID) const override; + + virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; + virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const override; + virtual Size2i window_get_size_with_decorations(WindowID p_window = MAIN_WINDOW_ID) const override; + + virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID) override; + virtual WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const override; + + virtual bool window_is_maximize_allowed(WindowID p_window = MAIN_WINDOW_ID) const override; + + virtual void window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window = MAIN_WINDOW_ID) override; + virtual bool window_get_flag(WindowFlags p_flag, WindowID p_window = MAIN_WINDOW_ID) const override; + + virtual void window_request_attention(WindowID p_window = MAIN_WINDOW_ID) override; + virtual void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID) override; + virtual bool window_is_focused(WindowID p_window = MAIN_WINDOW_ID) const override; + + virtual float screen_get_max_scale() const override; + + virtual void screen_set_orientation(DisplayServer::ScreenOrientation p_orientation, int p_screen) override; + virtual DisplayServer::ScreenOrientation screen_get_orientation(int p_screen) const override; + + virtual bool window_can_draw(WindowID p_window = MAIN_WINDOW_ID) const override; + + virtual bool can_any_window_draw() const override; + + virtual void window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window = MAIN_WINDOW_ID) override; + virtual DisplayServer::VSyncMode window_get_vsync_mode(WindowID p_vsync_mode) const override; + + virtual bool is_touchscreen_available() const override; + + virtual void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, VirtualKeyboardType p_type, int p_max_length, int p_cursor_start, int p_cursor_end) override; + virtual void virtual_keyboard_hide() override; + + void virtual_keyboard_set_height(int height); + virtual int virtual_keyboard_get_height() const override; + virtual bool has_hardware_keyboard() const override; + + virtual void clipboard_set(const String &p_text) override; + virtual String clipboard_get() const override; + + virtual void screen_set_keep_on(bool p_enable) override; + virtual bool screen_is_kept_on() const override; + + void resize_window(CGSize size); + virtual void swap_buffers() override {} +}; diff --git a/drivers/apple_embedded/display_server_apple_embedded.mm b/drivers/apple_embedded/display_server_apple_embedded.mm new file mode 100644 index 000000000000..3f13b23b9ffa --- /dev/null +++ b/drivers/apple_embedded/display_server_apple_embedded.mm @@ -0,0 +1,794 @@ +/**************************************************************************/ +/* display_server_apple_embedded.mm */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 "display_server_apple_embedded.h" + +#import "app_delegate_service.h" +#import "apple_embedded.h" +#import "godot_view_apple_embedded.h" +#import "key_mapping_apple_embedded.h" +#import "keyboard_input_view.h" +#import "os_apple_embedded.h" +#import "tts_apple_embedded.h" +#import "view_controller.h" + +#include "core/config/project_settings.h" +#include "core/io/file_access_pack.h" + +#import + +static const float kDisplayServerIOSAcceleration = 1.f; + +DisplayServerAppleEmbedded *DisplayServerAppleEmbedded::get_singleton() { + return (DisplayServerAppleEmbedded *)DisplayServer::get_singleton(); +} + +DisplayServerAppleEmbedded::DisplayServerAppleEmbedded(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error) { + KeyMappingAppleEmbedded::initialize(); + + rendering_driver = p_rendering_driver; + + // Init TTS + bool tts_enabled = GLOBAL_GET("audio/general/text_to_speech"); + if (tts_enabled) { + initialize_tts(); + } + native_menu = memnew(NativeMenu); + + bool has_made_render_compositor_current = false; + +#if defined(RD_ENABLED) + rendering_context = nullptr; + rendering_device = nullptr; + + CALayer *layer = nullptr; + + union { +#ifdef VULKAN_ENABLED + RenderingContextDriverVulkanAppleEmbedded::WindowPlatformData vulkan; +#endif +#ifdef METAL_ENABLED + GODOT_CLANG_WARNING_PUSH_AND_IGNORE("-Wunguarded-availability") + // Eliminate "RenderingContextDriverMetal is only available on iOS 14.0 or newer". + RenderingContextDriverMetal::WindowPlatformData metal; + GODOT_CLANG_WARNING_POP +#endif + } wpd; + +#if defined(VULKAN_ENABLED) + if (rendering_driver == "vulkan") { + layer = [GDTAppDelegateService.viewController.godotView initializeRenderingForDriver:@"vulkan"]; + if (!layer) { + ERR_FAIL_MSG("Failed to create iOS Vulkan rendering layer."); + } + wpd.vulkan.layer_ptr = (CAMetalLayer *const *)&layer; + rendering_context = memnew(RenderingContextDriverVulkanAppleEmbedded); + } +#endif +#ifdef METAL_ENABLED + if (rendering_driver == "metal") { + if (@available(iOS 14.0, *)) { + layer = [GDTAppDelegateService.viewController.godotView initializeRenderingForDriver:@"metal"]; + wpd.metal.layer = (CAMetalLayer *)layer; + rendering_context = memnew(RenderingContextDriverMetal); + } else { + OS::get_singleton()->alert("Metal is only supported on iOS 14.0 and later."); + r_error = ERR_UNAVAILABLE; + return; + } + } +#endif + if (rendering_context) { + if (rendering_context->initialize() != OK) { + memdelete(rendering_context); + rendering_context = nullptr; +#if defined(GLES3_ENABLED) + bool fallback_to_opengl3 = GLOBAL_GET("rendering/rendering_device/fallback_to_opengl3"); + if (fallback_to_opengl3 && rendering_driver != "opengl3") { + WARN_PRINT("Your device seem not to support MoltenVK or Metal, switching to OpenGL 3."); + rendering_driver = "opengl3"; + OS::get_singleton()->set_current_rendering_method("gl_compatibility"); + OS::get_singleton()->set_current_rendering_driver_name(rendering_driver); + } else +#endif + { + ERR_PRINT(vformat("Failed to initialize %s context", rendering_driver)); + r_error = ERR_UNAVAILABLE; + return; + } + } + } + + if (rendering_context) { + if (rendering_context->window_create(MAIN_WINDOW_ID, &wpd) != OK) { + ERR_PRINT(vformat("Failed to create %s window.", rendering_driver)); + memdelete(rendering_context); + rendering_context = nullptr; + r_error = ERR_UNAVAILABLE; + return; + } + + Size2i size = Size2i(layer.bounds.size.width, layer.bounds.size.height) * screen_get_max_scale(); + rendering_context->window_set_size(MAIN_WINDOW_ID, size.width, size.height); + rendering_context->window_set_vsync_mode(MAIN_WINDOW_ID, p_vsync_mode); + + rendering_device = memnew(RenderingDevice); + if (rendering_device->initialize(rendering_context, MAIN_WINDOW_ID) != OK) { + rendering_device = nullptr; + memdelete(rendering_context); + rendering_context = nullptr; + r_error = ERR_UNAVAILABLE; + return; + } + rendering_device->screen_create(MAIN_WINDOW_ID); + + RendererCompositorRD::make_current(); + has_made_render_compositor_current = true; + } +#endif + +#if defined(GLES3_ENABLED) + if (rendering_driver == "opengl3") { + CALayer *layer = [GDTAppDelegateService.viewController.godotView initializeRenderingForDriver:@"opengl3"]; + + if (!layer) { + ERR_FAIL_MSG("Failed to create iOS OpenGLES rendering layer."); + } + + RasterizerGLES3::make_current(false); + has_made_render_compositor_current = true; + } +#endif + + ERR_FAIL_COND_MSG(!has_made_render_compositor_current, vformat("Failed to make RendererCompositor current for rendering driver %s", rendering_driver)); + + bool keep_screen_on = bool(GLOBAL_GET("display/window/energy_saving/keep_screen_on")); + screen_set_keep_on(keep_screen_on); + + Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events); + + r_error = OK; +} + +DisplayServerAppleEmbedded::~DisplayServerAppleEmbedded() { + if (native_menu) { + memdelete(native_menu); + native_menu = nullptr; + } + +#if defined(RD_ENABLED) + if (rendering_device) { + rendering_device->screen_free(MAIN_WINDOW_ID); + memdelete(rendering_device); + rendering_device = nullptr; + } + + if (rendering_context) { + rendering_context->window_destroy(MAIN_WINDOW_ID); + memdelete(rendering_context); + rendering_context = nullptr; + } +#endif +} + +Vector DisplayServerAppleEmbedded::get_rendering_drivers_func() { + Vector drivers; + +#if defined(VULKAN_ENABLED) + drivers.push_back("vulkan"); +#endif +#if defined(METAL_ENABLED) + if (@available(ios 14.0, *)) { + drivers.push_back("metal"); + } +#endif +#if defined(GLES3_ENABLED) + drivers.push_back("opengl3"); +#endif + + return drivers; +} + +// MARK: Events + +void DisplayServerAppleEmbedded::window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window) { + window_resize_callback = p_callable; +} + +void DisplayServerAppleEmbedded::window_set_window_event_callback(const Callable &p_callable, WindowID p_window) { + window_event_callback = p_callable; +} +void DisplayServerAppleEmbedded::window_set_input_event_callback(const Callable &p_callable, WindowID p_window) { + input_event_callback = p_callable; +} + +void DisplayServerAppleEmbedded::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) { + input_text_callback = p_callable; +} + +void DisplayServerAppleEmbedded::window_set_drop_files_callback(const Callable &p_callable, WindowID p_window) { + // Probably not supported for iOS +} + +void DisplayServerAppleEmbedded::process_events() { + Input::get_singleton()->flush_buffered_events(); +} + +void DisplayServerAppleEmbedded::_dispatch_input_events(const Ref &p_event) { + DisplayServerAppleEmbedded::get_singleton()->send_input_event(p_event); +} + +void DisplayServerAppleEmbedded::send_input_event(const Ref &p_event) const { + _window_callback(input_event_callback, p_event); +} + +void DisplayServerAppleEmbedded::send_input_text(const String &p_text) const { + _window_callback(input_text_callback, p_text); +} + +void DisplayServerAppleEmbedded::send_window_event(DisplayServer::WindowEvent p_event) const { + _window_callback(window_event_callback, int(p_event)); +} + +void DisplayServerAppleEmbedded::_window_callback(const Callable &p_callable, const Variant &p_arg) const { + if (p_callable.is_valid()) { + p_callable.call(p_arg); + } +} + +// MARK: - Input + +// MARK: Touches + +void DisplayServerAppleEmbedded::touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_double_click) { + Ref ev; + ev.instantiate(); + + ev->set_index(p_idx); + ev->set_pressed(p_pressed); + ev->set_position(Vector2(p_x, p_y)); + ev->set_double_tap(p_double_click); + perform_event(ev); +} + +void DisplayServerAppleEmbedded::touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y, float p_pressure, Vector2 p_tilt) { + Ref ev; + ev.instantiate(); + ev->set_index(p_idx); + ev->set_pressure(p_pressure); + ev->set_tilt(p_tilt); + ev->set_position(Vector2(p_x, p_y)); + ev->set_relative(Vector2(p_x - p_prev_x, p_y - p_prev_y)); + ev->set_relative_screen_position(ev->get_relative()); + perform_event(ev); +} + +void DisplayServerAppleEmbedded::perform_event(const Ref &p_event) { + Input::get_singleton()->parse_input_event(p_event); +} + +void DisplayServerAppleEmbedded::touches_canceled(int p_idx) { + touch_press(p_idx, -1, -1, false, false); +} + +// MARK: Keyboard + +void DisplayServerAppleEmbedded::key(Key p_key, char32_t p_char, Key p_unshifted, Key p_physical, NSInteger p_modifier, bool p_pressed, KeyLocation p_location) { + Ref ev; + ev.instantiate(); + ev->set_echo(false); + ev->set_pressed(p_pressed); + ev->set_keycode(fix_keycode(p_char, p_key)); + if (@available(iOS 13.4, *)) { + if (p_key != Key::SHIFT) { + ev->set_shift_pressed(p_modifier & UIKeyModifierShift); + } + if (p_key != Key::CTRL) { + ev->set_ctrl_pressed(p_modifier & UIKeyModifierControl); + } + if (p_key != Key::ALT) { + ev->set_alt_pressed(p_modifier & UIKeyModifierAlternate); + } + if (p_key != Key::META) { + ev->set_meta_pressed(p_modifier & UIKeyModifierCommand); + } + } + ev->set_key_label(p_unshifted); + ev->set_physical_keycode(p_physical); + ev->set_unicode(fix_unicode(p_char)); + ev->set_location(p_location); + perform_event(ev); +} + +// MARK: Motion + +void DisplayServerAppleEmbedded::update_gravity(const Vector3 &p_gravity) { + Input::get_singleton()->set_gravity(p_gravity); +} + +void DisplayServerAppleEmbedded::update_accelerometer(const Vector3 &p_accelerometer) { + Input::get_singleton()->set_accelerometer(p_accelerometer / kDisplayServerIOSAcceleration); +} + +void DisplayServerAppleEmbedded::update_magnetometer(const Vector3 &p_magnetometer) { + Input::get_singleton()->set_magnetometer(p_magnetometer); +} + +void DisplayServerAppleEmbedded::update_gyroscope(const Vector3 &p_gyroscope) { + Input::get_singleton()->set_gyroscope(p_gyroscope); +} + +// MARK: - + +bool DisplayServerAppleEmbedded::has_feature(Feature p_feature) const { + switch (p_feature) { +#ifndef DISABLE_DEPRECATED + case FEATURE_GLOBAL_MENU: { + return (native_menu && native_menu->has_feature(NativeMenu::FEATURE_GLOBAL_MENU)); + } break; +#endif + // case FEATURE_CURSOR_SHAPE: + // case FEATURE_CUSTOM_CURSOR_SHAPE: + // case FEATURE_HIDPI: + // case FEATURE_ICON: + // case FEATURE_IME: + // case FEATURE_MOUSE: + // case FEATURE_MOUSE_WARP: + // case FEATURE_NATIVE_DIALOG: + // case FEATURE_NATIVE_DIALOG_INPUT: + // case FEATURE_NATIVE_DIALOG_FILE: + // case FEATURE_NATIVE_DIALOG_FILE_EXTRA: + // case FEATURE_NATIVE_DIALOG_FILE_MIME: + // case FEATURE_NATIVE_ICON: + // case FEATURE_WINDOW_TRANSPARENCY: + case FEATURE_CLIPBOARD: + case FEATURE_KEEP_SCREEN_ON: + case FEATURE_ORIENTATION: + case FEATURE_TOUCHSCREEN: + case FEATURE_VIRTUAL_KEYBOARD: + case FEATURE_TEXT_TO_SPEECH: + return true; + default: + return false; + } +} + +void DisplayServerAppleEmbedded::initialize_tts() const { + const_cast(this)->tts = [[GDTTTS alloc] init]; +} + +bool DisplayServerAppleEmbedded::tts_is_speaking() const { + if (unlikely(!tts)) { + initialize_tts(); + } + ERR_FAIL_NULL_V(tts, false); + return [tts isSpeaking]; +} + +bool DisplayServerAppleEmbedded::tts_is_paused() const { + if (unlikely(!tts)) { + initialize_tts(); + } + ERR_FAIL_NULL_V(tts, false); + return [tts isPaused]; +} + +TypedArray DisplayServerAppleEmbedded::tts_get_voices() const { + if (unlikely(!tts)) { + initialize_tts(); + } + ERR_FAIL_NULL_V(tts, TypedArray()); + return [tts getVoices]; +} + +void DisplayServerAppleEmbedded::tts_speak(const String &p_text, const String &p_voice, int p_volume, float p_pitch, float p_rate, int p_utterance_id, bool p_interrupt) { + if (unlikely(!tts)) { + initialize_tts(); + } + ERR_FAIL_NULL(tts); + [tts speak:p_text voice:p_voice volume:p_volume pitch:p_pitch rate:p_rate utterance_id:p_utterance_id interrupt:p_interrupt]; +} + +void DisplayServerAppleEmbedded::tts_pause() { + if (unlikely(!tts)) { + initialize_tts(); + } + ERR_FAIL_NULL(tts); + [tts pauseSpeaking]; +} + +void DisplayServerAppleEmbedded::tts_resume() { + if (unlikely(!tts)) { + initialize_tts(); + } + ERR_FAIL_NULL(tts); + [tts resumeSpeaking]; +} + +void DisplayServerAppleEmbedded::tts_stop() { + if (unlikely(!tts)) { + initialize_tts(); + } + ERR_FAIL_NULL(tts); + [tts stopSpeaking]; +} + +bool DisplayServerAppleEmbedded::is_dark_mode_supported() const { + if (@available(iOS 13.0, *)) { + return true; + } else { + return false; + } +} + +bool DisplayServerAppleEmbedded::is_dark_mode() const { + if (@available(iOS 13.0, *)) { + return [UITraitCollection currentTraitCollection].userInterfaceStyle == UIUserInterfaceStyleDark; + } else { + return false; + } +} + +void DisplayServerAppleEmbedded::set_system_theme_change_callback(const Callable &p_callable) { + system_theme_changed = p_callable; +} + +void DisplayServerAppleEmbedded::emit_system_theme_changed() { + if (system_theme_changed.is_valid()) { + Variant ret; + Callable::CallError ce; + system_theme_changed.callp(nullptr, 0, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat("Failed to execute system theme changed callback: %s.", Variant::get_callable_error_text(system_theme_changed, nullptr, 0, ce))); + } + } +} + +Rect2i DisplayServerAppleEmbedded::get_display_safe_area() const { + UIEdgeInsets insets = UIEdgeInsetsZero; + UIView *view = GDTAppDelegateService.viewController.godotView; + if ([view respondsToSelector:@selector(safeAreaInsets)]) { + insets = [view safeAreaInsets]; + } + float scale = screen_get_scale(); + Size2i insets_position = Size2i(insets.left, insets.top) * scale; + Size2i insets_size = Size2i(insets.left + insets.right, insets.top + insets.bottom) * scale; + return Rect2i(screen_get_position() + insets_position, screen_get_size() - insets_size); +} + +int DisplayServerAppleEmbedded::get_screen_count() const { + return 1; +} + +int DisplayServerAppleEmbedded::get_primary_screen() const { + return 0; +} + +Point2i DisplayServerAppleEmbedded::screen_get_position(int p_screen) const { + return Size2i(); +} + +Size2i DisplayServerAppleEmbedded::screen_get_size(int p_screen) const { + CALayer *layer = GDTAppDelegateService.viewController.godotView.renderingLayer; + + if (!layer) { + return Size2i(); + } + + return Size2i(layer.bounds.size.width, layer.bounds.size.height) * screen_get_scale(p_screen); +} + +Rect2i DisplayServerAppleEmbedded::screen_get_usable_rect(int p_screen) const { + return Rect2i(screen_get_position(p_screen), screen_get_size(p_screen)); +} + +Vector DisplayServerAppleEmbedded::get_window_list() const { + Vector list; + list.push_back(MAIN_WINDOW_ID); + return list; +} + +DisplayServer::WindowID DisplayServerAppleEmbedded::get_window_at_screen_position(const Point2i &p_position) const { + return MAIN_WINDOW_ID; +} + +int64_t DisplayServerAppleEmbedded::window_get_native_handle(HandleType p_handle_type, WindowID p_window) const { + ERR_FAIL_COND_V(p_window != MAIN_WINDOW_ID, 0); + switch (p_handle_type) { + case DISPLAY_HANDLE: { + return 0; // Not supported. + } + case WINDOW_HANDLE: { + return (int64_t)GDTAppDelegateService.viewController; + } + case WINDOW_VIEW: { + return (int64_t)GDTAppDelegateService.viewController.godotView; + } + default: { + return 0; + } + } +} + +void DisplayServerAppleEmbedded::window_attach_instance_id(ObjectID p_instance, WindowID p_window) { + window_attached_instance_id = p_instance; +} + +ObjectID DisplayServerAppleEmbedded::window_get_attached_instance_id(WindowID p_window) const { + return window_attached_instance_id; +} + +void DisplayServerAppleEmbedded::window_set_title(const String &p_title, WindowID p_window) { + // Probably not supported for iOS +} + +int DisplayServerAppleEmbedded::window_get_current_screen(WindowID p_window) const { + return SCREEN_OF_MAIN_WINDOW; +} + +void DisplayServerAppleEmbedded::window_set_current_screen(int p_screen, WindowID p_window) { + // Probably not supported for iOS +} + +Point2i DisplayServerAppleEmbedded::window_get_position(WindowID p_window) const { + return Point2i(); +} + +Point2i DisplayServerAppleEmbedded::window_get_position_with_decorations(WindowID p_window) const { + return Point2i(); +} + +void DisplayServerAppleEmbedded::window_set_position(const Point2i &p_position, WindowID p_window) { + // Probably not supported for single window iOS app +} + +void DisplayServerAppleEmbedded::window_set_transient(WindowID p_window, WindowID p_parent) { + // Probably not supported for iOS +} + +void DisplayServerAppleEmbedded::window_set_max_size(const Size2i p_size, WindowID p_window) { + // Probably not supported for iOS +} + +Size2i DisplayServerAppleEmbedded::window_get_max_size(WindowID p_window) const { + return Size2i(); +} + +void DisplayServerAppleEmbedded::window_set_min_size(const Size2i p_size, WindowID p_window) { + // Probably not supported for iOS +} + +Size2i DisplayServerAppleEmbedded::window_get_min_size(WindowID p_window) const { + return Size2i(); +} + +void DisplayServerAppleEmbedded::window_set_size(const Size2i p_size, WindowID p_window) { + // Probably not supported for iOS +} + +Size2i DisplayServerAppleEmbedded::window_get_size(WindowID p_window) const { + id appDelegate = [[UIApplication sharedApplication] delegate]; + CGRect windowBounds = appDelegate.window.bounds; + return Size2i(windowBounds.size.width, windowBounds.size.height) * screen_get_max_scale(); +} + +Size2i DisplayServerAppleEmbedded::window_get_size_with_decorations(WindowID p_window) const { + return window_get_size(p_window); +} + +void DisplayServerAppleEmbedded::window_set_mode(WindowMode p_mode, WindowID p_window) { + // Probably not supported for iOS +} + +DisplayServer::WindowMode DisplayServerAppleEmbedded::window_get_mode(WindowID p_window) const { + return WindowMode::WINDOW_MODE_FULLSCREEN; +} + +bool DisplayServerAppleEmbedded::window_is_maximize_allowed(WindowID p_window) const { + return false; +} + +void DisplayServerAppleEmbedded::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) { + // Probably not supported for iOS +} + +bool DisplayServerAppleEmbedded::window_get_flag(WindowFlags p_flag, WindowID p_window) const { + return false; +} + +void DisplayServerAppleEmbedded::window_request_attention(WindowID p_window) { + // Probably not supported for iOS +} + +void DisplayServerAppleEmbedded::window_move_to_foreground(WindowID p_window) { + // Probably not supported for iOS +} + +bool DisplayServerAppleEmbedded::window_is_focused(WindowID p_window) const { + return true; +} + +float DisplayServerAppleEmbedded::screen_get_max_scale() const { + return screen_get_scale(SCREEN_OF_MAIN_WINDOW); +} + +void DisplayServerAppleEmbedded::screen_set_orientation(DisplayServer::ScreenOrientation p_orientation, int p_screen) { + screen_orientation = p_orientation; + if (@available(iOS 16.0, *)) { + [GDTAppDelegateService.viewController setNeedsUpdateOfSupportedInterfaceOrientations]; + } +#if !defined(VISIONOS_ENABLED) + else { + [UIViewController attemptRotationToDeviceOrientation]; + } +#endif +} + +DisplayServer::ScreenOrientation DisplayServerAppleEmbedded::screen_get_orientation(int p_screen) const { + return screen_orientation; +} + +bool DisplayServerAppleEmbedded::window_can_draw(WindowID p_window) const { + return true; +} + +bool DisplayServerAppleEmbedded::can_any_window_draw() const { + return true; +} + +bool DisplayServerAppleEmbedded::is_touchscreen_available() const { + return true; +} + +_FORCE_INLINE_ int _convert_utf32_offset_to_utf16(const String &p_existing_text, int p_pos) { + int limit = p_pos; + for (int i = 0; i < MIN(p_existing_text.length(), p_pos); i++) { + if (p_existing_text[i] > 0xffff) { + limit++; + } + } + return limit; +} + +void DisplayServerAppleEmbedded::virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, VirtualKeyboardType p_type, int p_max_length, int p_cursor_start, int p_cursor_end) { + NSString *existingString = [[NSString alloc] initWithUTF8String:p_existing_text.utf8().get_data()]; + + GDTAppDelegateService.viewController.keyboardView.keyboardType = UIKeyboardTypeDefault; + GDTAppDelegateService.viewController.keyboardView.textContentType = nil; + switch (p_type) { + case KEYBOARD_TYPE_DEFAULT: { + GDTAppDelegateService.viewController.keyboardView.keyboardType = UIKeyboardTypeDefault; + } break; + case KEYBOARD_TYPE_MULTILINE: { + GDTAppDelegateService.viewController.keyboardView.keyboardType = UIKeyboardTypeDefault; + } break; + case KEYBOARD_TYPE_NUMBER: { + GDTAppDelegateService.viewController.keyboardView.keyboardType = UIKeyboardTypeNumberPad; + } break; + case KEYBOARD_TYPE_NUMBER_DECIMAL: { + GDTAppDelegateService.viewController.keyboardView.keyboardType = UIKeyboardTypeDecimalPad; + } break; + case KEYBOARD_TYPE_PHONE: { + GDTAppDelegateService.viewController.keyboardView.keyboardType = UIKeyboardTypePhonePad; + GDTAppDelegateService.viewController.keyboardView.textContentType = UITextContentTypeTelephoneNumber; + } break; + case KEYBOARD_TYPE_EMAIL_ADDRESS: { + GDTAppDelegateService.viewController.keyboardView.keyboardType = UIKeyboardTypeEmailAddress; + GDTAppDelegateService.viewController.keyboardView.textContentType = UITextContentTypeEmailAddress; + } break; + case KEYBOARD_TYPE_PASSWORD: { + GDTAppDelegateService.viewController.keyboardView.keyboardType = UIKeyboardTypeDefault; + GDTAppDelegateService.viewController.keyboardView.textContentType = UITextContentTypePassword; + } break; + case KEYBOARD_TYPE_URL: { + GDTAppDelegateService.viewController.keyboardView.keyboardType = UIKeyboardTypeWebSearch; + GDTAppDelegateService.viewController.keyboardView.textContentType = UITextContentTypeURL; + } break; + } + + [GDTAppDelegateService.viewController.keyboardView + becomeFirstResponderWithString:existingString + cursorStart:_convert_utf32_offset_to_utf16(p_existing_text, p_cursor_start) + cursorEnd:_convert_utf32_offset_to_utf16(p_existing_text, p_cursor_end)]; +} + +bool DisplayServerAppleEmbedded::is_keyboard_active() const { + return [GDTAppDelegateService.viewController.keyboardView isFirstResponder]; +} + +void DisplayServerAppleEmbedded::virtual_keyboard_hide() { + [GDTAppDelegateService.viewController.keyboardView resignFirstResponder]; +} + +void DisplayServerAppleEmbedded::virtual_keyboard_set_height(int height) { + virtual_keyboard_height = height * screen_get_max_scale(); +} + +int DisplayServerAppleEmbedded::virtual_keyboard_get_height() const { + return virtual_keyboard_height; +} + +bool DisplayServerAppleEmbedded::has_hardware_keyboard() const { + if (@available(iOS 14.0, *)) { + return [GCKeyboard coalescedKeyboard]; + } else { + return false; + } +} + +void DisplayServerAppleEmbedded::clipboard_set(const String &p_text) { + [UIPasteboard generalPasteboard].string = [NSString stringWithUTF8String:p_text.utf8().get_data()]; +} + +String DisplayServerAppleEmbedded::clipboard_get() const { + NSString *text = [UIPasteboard generalPasteboard].string; + + return String::utf8([text UTF8String]); +} + +void DisplayServerAppleEmbedded::screen_set_keep_on(bool p_enable) { + [UIApplication sharedApplication].idleTimerDisabled = p_enable; +} + +bool DisplayServerAppleEmbedded::screen_is_kept_on() const { + return [UIApplication sharedApplication].idleTimerDisabled; +} + +void DisplayServerAppleEmbedded::resize_window(CGSize viewSize) { + Size2i size = Size2i(viewSize.width, viewSize.height) * screen_get_max_scale(); + +#if defined(RD_ENABLED) + if (rendering_context) { + rendering_context->window_set_size(MAIN_WINDOW_ID, size.x, size.y); + } +#endif + + Variant resize_rect = Rect2i(Point2i(), size); + _window_callback(window_resize_callback, resize_rect); +} + +void DisplayServerAppleEmbedded::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window) { + _THREAD_SAFE_METHOD_ +#if defined(RD_ENABLED) + if (rendering_context) { + rendering_context->window_set_vsync_mode(p_window, p_vsync_mode); + } +#endif +} + +DisplayServer::VSyncMode DisplayServerAppleEmbedded::window_get_vsync_mode(WindowID p_window) const { + _THREAD_SAFE_METHOD_ +#if defined(RD_ENABLED) + if (rendering_context) { + return rendering_context->window_get_vsync_mode(p_window); + } +#endif + return DisplayServer::VSYNC_ENABLED; +} diff --git a/platform/ios/godot_app_delegate.h b/drivers/apple_embedded/godot_app_delegate.h similarity index 88% rename from platform/ios/godot_app_delegate.h rename to drivers/apple_embedded/godot_app_delegate.h index 35a90171108a..7c1287189268 100644 --- a/platform/ios/godot_app_delegate.h +++ b/drivers/apple_embedded/godot_app_delegate.h @@ -32,12 +32,12 @@ #import -typedef NSObject ApplicationDelegateService; +typedef NSObject GDTAppDelegateServiceProtocol; -@interface GodotApplicationDelegate : NSObject +@interface GDTApplicationDelegate : NSObject -@property(class, readonly, strong) NSArray *services; +@property(class, readonly, strong) NSArray *services; -+ (void)addService:(ApplicationDelegateService *)service; ++ (void)addService:(GDTAppDelegateServiceProtocol *)service; @end diff --git a/platform/ios/godot_app_delegate.m b/drivers/apple_embedded/godot_app_delegate.mm similarity index 84% rename from platform/ios/godot_app_delegate.m rename to drivers/apple_embedded/godot_app_delegate.mm index 53e53cd0c658..7e78b3fc3ba1 100644 --- a/platform/ios/godot_app_delegate.m +++ b/drivers/apple_embedded/godot_app_delegate.mm @@ -1,5 +1,5 @@ /**************************************************************************/ -/* godot_app_delegate.m */ +/* godot_app_delegate.mm */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -30,26 +30,22 @@ #import "godot_app_delegate.h" -#import "app_delegate.h" +#import "app_delegate_service.h" -@interface GodotApplicationDelegate () +@implementation GDTApplicationDelegate -@end - -@implementation GodotApplicationDelegate - -static NSMutableArray *services = nil; +static NSMutableArray *services = nil; -+ (NSArray *)services { ++ (NSArray *)services { return services; } + (void)load { services = [NSMutableArray new]; - [services addObject:[AppDelegate new]]; + [services addObject:[GDTAppDelegateService new]]; } -+ (void)addService:(ApplicationDelegateService *)service { ++ (void)addService:(GDTAppDelegateServiceProtocol *)service { if (!services || !service) { return; } @@ -63,7 +59,7 @@ + (void)addService:(ApplicationDelegateService *)service { - (UIWindow *)window { UIWindow *result = nil; - for (ApplicationDelegateService *service in services) { + for (GDTAppDelegateServiceProtocol *service in services) { if (![service respondsToSelector:_cmd]) { continue; } @@ -83,7 +79,7 @@ - (UIWindow *)window { - (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions { BOOL result = NO; - for (ApplicationDelegateService *service in services) { + for (GDTAppDelegateServiceProtocol *service in services) { if (![service respondsToSelector:_cmd]) { continue; } @@ -99,7 +95,7 @@ - (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions: - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { BOOL result = NO; - for (ApplicationDelegateService *service in services) { + for (GDTAppDelegateServiceProtocol *service in services) { if (![service respondsToSelector:_cmd]) { continue; } @@ -125,7 +121,7 @@ - (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet< // MARK: Life-Cycle - (void)applicationDidBecomeActive:(UIApplication *)application { - for (ApplicationDelegateService *service in services) { + for (GDTAppDelegateServiceProtocol *service in services) { if (![service respondsToSelector:_cmd]) { continue; } @@ -135,7 +131,7 @@ - (void)applicationDidBecomeActive:(UIApplication *)application { } - (void)applicationWillResignActive:(UIApplication *)application { - for (ApplicationDelegateService *service in services) { + for (GDTAppDelegateServiceProtocol *service in services) { if (![service respondsToSelector:_cmd]) { continue; } @@ -145,7 +141,7 @@ - (void)applicationWillResignActive:(UIApplication *)application { } - (void)applicationDidEnterBackground:(UIApplication *)application { - for (ApplicationDelegateService *service in services) { + for (GDTAppDelegateServiceProtocol *service in services) { if (![service respondsToSelector:_cmd]) { continue; } @@ -155,7 +151,7 @@ - (void)applicationDidEnterBackground:(UIApplication *)application { } - (void)applicationWillEnterForeground:(UIApplication *)application { - for (ApplicationDelegateService *service in services) { + for (GDTAppDelegateServiceProtocol *service in services) { if (![service respondsToSelector:_cmd]) { continue; } @@ -165,7 +161,7 @@ - (void)applicationWillEnterForeground:(UIApplication *)application { } - (void)applicationWillTerminate:(UIApplication *)application { - for (ApplicationDelegateService *service in services) { + for (GDTAppDelegateServiceProtocol *service in services) { if (![service respondsToSelector:_cmd]) { continue; } @@ -177,7 +173,7 @@ - (void)applicationWillTerminate:(UIApplication *)application { // MARK: Environment Changes - (void)applicationProtectedDataDidBecomeAvailable:(UIApplication *)application { - for (ApplicationDelegateService *service in services) { + for (GDTAppDelegateServiceProtocol *service in services) { if (![service respondsToSelector:_cmd]) { continue; } @@ -187,7 +183,7 @@ - (void)applicationProtectedDataDidBecomeAvailable:(UIApplication *)application } - (void)applicationProtectedDataWillBecomeUnavailable:(UIApplication *)application { - for (ApplicationDelegateService *service in services) { + for (GDTAppDelegateServiceProtocol *service in services) { if (![service respondsToSelector:_cmd]) { continue; } @@ -197,7 +193,7 @@ - (void)applicationProtectedDataWillBecomeUnavailable:(UIApplication *)applicati } - (void)applicationDidReceiveMemoryWarning:(UIApplication *)application { - for (ApplicationDelegateService *service in services) { + for (GDTAppDelegateServiceProtocol *service in services) { if (![service respondsToSelector:_cmd]) { continue; } @@ -207,7 +203,7 @@ - (void)applicationDidReceiveMemoryWarning:(UIApplication *)application { } - (void)applicationSignificantTimeChange:(UIApplication *)application { - for (ApplicationDelegateService *service in services) { + for (GDTAppDelegateServiceProtocol *service in services) { if (![service respondsToSelector:_cmd]) { continue; } @@ -221,7 +217,7 @@ - (void)applicationSignificantTimeChange:(UIApplication *)application { - (BOOL)application:(UIApplication *)application shouldSaveSecureApplicationState:(NSCoder *)coder API_AVAILABLE(ios(13.2)) { BOOL result = NO; - for (ApplicationDelegateService *service in services) { + for (GDTAppDelegateServiceProtocol *service in services) { if (![service respondsToSelector:_cmd]) { continue; } @@ -237,7 +233,7 @@ - (BOOL)application:(UIApplication *)application shouldSaveSecureApplicationStat - (BOOL)application:(UIApplication *)application shouldRestoreSecureApplicationState:(NSCoder *)coder API_AVAILABLE(ios(13.2)) { BOOL result = NO; - for (ApplicationDelegateService *service in services) { + for (GDTAppDelegateServiceProtocol *service in services) { if (![service respondsToSelector:_cmd]) { continue; } @@ -251,7 +247,7 @@ - (BOOL)application:(UIApplication *)application shouldRestoreSecureApplicationS } - (UIViewController *)application:(UIApplication *)application viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder { - for (ApplicationDelegateService *service in services) { + for (GDTAppDelegateServiceProtocol *service in services) { if (![service respondsToSelector:_cmd]) { continue; } @@ -267,7 +263,7 @@ - (UIViewController *)application:(UIApplication *)application viewControllerWit } - (void)application:(UIApplication *)application willEncodeRestorableStateWithCoder:(NSCoder *)coder { - for (ApplicationDelegateService *service in services) { + for (GDTAppDelegateServiceProtocol *service in services) { if (![service respondsToSelector:_cmd]) { continue; } @@ -277,7 +273,7 @@ - (void)application:(UIApplication *)application willEncodeRestorableStateWithCo } - (void)application:(UIApplication *)application didDecodeRestorableStateWithCoder:(NSCoder *)coder { - for (ApplicationDelegateService *service in services) { + for (GDTAppDelegateServiceProtocol *service in services) { if (![service respondsToSelector:_cmd]) { continue; } @@ -289,7 +285,7 @@ - (void)application:(UIApplication *)application didDecodeRestorableStateWithCod // MARK: Download Data in Background - (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler { - for (ApplicationDelegateService *service in services) { + for (GDTAppDelegateServiceProtocol *service in services) { if (![service respondsToSelector:_cmd]) { continue; } @@ -309,7 +305,7 @@ - (void)application:(UIApplication *)application handleEventsForBackgroundURLSes - (BOOL)application:(UIApplication *)application willContinueUserActivityWithType:(NSString *)userActivityType { BOOL result = NO; - for (ApplicationDelegateService *service in services) { + for (GDTAppDelegateServiceProtocol *service in services) { if (![service respondsToSelector:_cmd]) { continue; } @@ -325,7 +321,7 @@ - (BOOL)application:(UIApplication *)application willContinueUserActivityWithTyp - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray> *restorableObjects))restorationHandler { BOOL result = NO; - for (ApplicationDelegateService *service in services) { + for (GDTAppDelegateServiceProtocol *service in services) { if (![service respondsToSelector:_cmd]) { continue; } @@ -339,7 +335,7 @@ - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserAct } - (void)application:(UIApplication *)application didUpdateUserActivity:(NSUserActivity *)userActivity { - for (ApplicationDelegateService *service in services) { + for (GDTAppDelegateServiceProtocol *service in services) { if (![service respondsToSelector:_cmd]) { continue; } @@ -349,7 +345,7 @@ - (void)application:(UIApplication *)application didUpdateUserActivity:(NSUserAc } - (void)application:(UIApplication *)application didFailToContinueUserActivityWithType:(NSString *)userActivityType error:(NSError *)error { - for (ApplicationDelegateService *service in services) { + for (GDTAppDelegateServiceProtocol *service in services) { if (![service respondsToSelector:_cmd]) { continue; } @@ -359,7 +355,7 @@ - (void)application:(UIApplication *)application didFailToContinueUserActivityWi } - (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL succeeded))completionHandler { - for (ApplicationDelegateService *service in services) { + for (GDTAppDelegateServiceProtocol *service in services) { if (![service respondsToSelector:_cmd]) { continue; } @@ -371,7 +367,7 @@ - (void)application:(UIApplication *)application performActionForShortcutItem:(U // MARK: WatchKit - (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *replyInfo))reply { - for (ApplicationDelegateService *service in services) { + for (GDTAppDelegateServiceProtocol *service in services) { if (![service respondsToSelector:_cmd]) { continue; } @@ -383,7 +379,7 @@ - (void)application:(UIApplication *)application handleWatchKitExtensionRequest: // MARK: HealthKit - (void)applicationShouldRequestHealthAuthorization:(UIApplication *)application { - for (ApplicationDelegateService *service in services) { + for (GDTAppDelegateServiceProtocol *service in services) { if (![service respondsToSelector:_cmd]) { continue; } @@ -395,7 +391,7 @@ - (void)applicationShouldRequestHealthAuthorization:(UIApplication *)application // MARK: Opening an URL - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options { - for (ApplicationDelegateService *service in services) { + for (GDTAppDelegateServiceProtocol *service in services) { if (![service respondsToSelector:_cmd]) { continue; } @@ -413,7 +409,7 @@ - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDiction - (BOOL)application:(UIApplication *)application shouldAllowExtensionPointIdentifier:(UIApplicationExtensionPointIdentifier)extensionPointIdentifier { BOOL result = NO; - for (ApplicationDelegateService *service in services) { + for (GDTAppDelegateServiceProtocol *service in services) { if (![service respondsToSelector:_cmd]) { continue; } @@ -429,7 +425,7 @@ - (BOOL)application:(UIApplication *)application shouldAllowExtensionPointIdenti // MARK: SiriKit - (id)application:(UIApplication *)application handlerForIntent:(INIntent *)intent API_AVAILABLE(ios(14.0)) { - for (ApplicationDelegateService *service in services) { + for (GDTAppDelegateServiceProtocol *service in services) { if (![service respondsToSelector:_cmd]) { continue; } @@ -447,7 +443,7 @@ - (id)application:(UIApplication *)application handlerForIntent:(INIntent *)inte // MARK: CloudKit - (void)application:(UIApplication *)application userDidAcceptCloudKitShareWithMetadata:(CKShareMetadata *)cloudKitShareMetadata { - for (ApplicationDelegateService *service in services) { + for (GDTAppDelegateServiceProtocol *service in services) { if (![service respondsToSelector:_cmd]) { continue; } diff --git a/platform/ios/godot_view.h b/drivers/apple_embedded/godot_view_apple_embedded.h similarity index 78% rename from platform/ios/godot_view.h rename to drivers/apple_embedded/godot_view_apple_embedded.h index ca774be339c4..0694d3a4c900 100644 --- a/platform/ios/godot_view.h +++ b/drivers/apple_embedded/godot_view_apple_embedded.h @@ -1,5 +1,5 @@ /**************************************************************************/ -/* godot_view.h */ +/* godot_view_apple_embedded.h */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -34,31 +34,39 @@ class String; -@class GodotView; -@protocol DisplayLayer; -@protocol GodotViewRendererProtocol; +@class GDTView; +@protocol GDTDisplayLayer; +@protocol GDTViewRendererProtocol; -@protocol GodotViewDelegate +@protocol GDTViewDelegate -- (BOOL)godotViewFinishedSetup:(GodotView *)view; +- (BOOL)godotViewFinishedSetup:(GDTView *)view; @end -@interface GodotView : UIView +@interface GDTView : UIView -@property(assign, nonatomic) id renderer; -@property(assign, nonatomic) id delegate; +@property(assign, nonatomic) id renderer; +@property(assign, nonatomic) id delegate; @property(assign, readonly, nonatomic) BOOL isActive; @property(assign, nonatomic) BOOL useCADisplayLink; -@property(strong, readonly, nonatomic) CALayer *renderingLayer; +@property(strong, readonly, nonatomic) CALayer *renderingLayer; @property(assign, readonly, nonatomic) BOOL canRender; @property(assign, nonatomic) NSTimeInterval renderingInterval; -- (CALayer *)initializeRenderingForDriver:(NSString *)driverName; +// Can be extended by subclasses +- (void)godot_commonInit; + +// Implemented in subclasses +- (CALayer *)initializeRenderingForDriver:(NSString *)driverName; + - (void)stopRendering; - (void)startRendering; @end + +// Implemented in subclasses +extern GDTView *GDTViewCreate(); diff --git a/platform/ios/godot_view.mm b/drivers/apple_embedded/godot_view_apple_embedded.mm similarity index 72% rename from platform/ios/godot_view.mm rename to drivers/apple_embedded/godot_view_apple_embedded.mm index f2416f2fbfdf..dd6ba3f5d2b7 100644 --- a/platform/ios/godot_view.mm +++ b/drivers/apple_embedded/godot_view_apple_embedded.mm @@ -1,5 +1,5 @@ /**************************************************************************/ -/* godot_view.mm */ +/* godot_view_apple_embedded.mm */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,10 +28,10 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#import "godot_view.h" +#import "godot_view_apple_embedded.h" -#import "display_layer.h" -#import "display_server_ios.h" +#import "display_layer_apple_embedded.h" +#import "display_server_apple_embedded.h" #import "godot_view_renderer.h" #include "core/config/project_settings.h" @@ -43,7 +43,7 @@ static const int max_touches = 32; static const float earth_gravity = 9.80665; -@interface GodotView () { +@interface GDTView () { UITouch *godot_touches[max_touches]; } @@ -56,48 +56,17 @@ @interface GodotView () { // Only used if CADisplayLink is not @property(strong, nonatomic) NSTimer *animationTimer; -@property(strong, nonatomic) CALayer *renderingLayer; +@property(strong, nonatomic) CALayer *renderingLayer; @property(strong, nonatomic) CMMotionManager *motionManager; @end -@implementation GodotView +@implementation GDTView -- (CALayer *)initializeRenderingForDriver:(NSString *)driverName { - if (self.renderingLayer) { - return self.renderingLayer; - } - - CALayer *layer; - - if ([driverName isEqualToString:@"vulkan"] || [driverName isEqualToString:@"metal"]) { -#if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR - if (@available(iOS 13, *)) { - layer = [GodotMetalLayer layer]; - } else { - return nil; - } -#else - layer = [GodotMetalLayer layer]; -#endif - } else if ([driverName isEqualToString:@"opengl3"]) { - GODOT_CLANG_WARNING_PUSH_AND_IGNORE("-Wdeprecated-declarations") // OpenGL is deprecated in iOS 12.0. - layer = [GodotOpenGLLayer layer]; - GODOT_CLANG_WARNING_POP - } else { - return nil; - } - - layer.frame = self.bounds; - layer.contentsScale = self.contentScaleFactor; - - [self.layer addSublayer:layer]; - self.renderingLayer = layer; - - [layer initializeDisplayLayer]; - - return self.renderingLayer; +// Implemented in subclasses +- (CALayer *)initializeRenderingForDriver:(NSString *)driverName { + return nil; } - (instancetype)initWithCoder:(NSCoder *)coder { @@ -148,7 +117,13 @@ - (void)dealloc { } - (void)godot_commonInit { +#if !defined(VISIONOS_ENABLED) self.contentScaleFactor = [UIScreen mainScreen].scale; +#endif + + if (@available(iOS 17.0, *)) { + [self registerForTraitChanges:@[ [UITraitUserInterfaceStyle class] ] withTarget:self action:@selector(traitCollectionDidChangeWithView:previousTraitCollection:)]; + } [self initTouches]; @@ -167,7 +142,7 @@ - (void)godot_commonInit { } - (void)system_theme_changed { - DisplayServerIOS *ds = (DisplayServerIOS *)DisplayServer::get_singleton(); + DisplayServerAppleEmbedded *ds = (DisplayServerAppleEmbedded *)DisplayServer::get_singleton(); if (ds) { ds->emit_system_theme_changed(); } @@ -175,8 +150,16 @@ - (void)system_theme_changed { - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection { if (@available(iOS 13.0, *)) { +#if !defined(VISIONOS_ENABLED) [super traitCollectionDidChange:previousTraitCollection]; +#endif + [self traitCollectionDidChangeWithView:self + previousTraitCollection:previousTraitCollection]; + } +} +- (void)traitCollectionDidChangeWithView:(UIView *)view previousTraitCollection:(UITraitCollection *)previousTraitCollection { + if (@available(iOS 13.0, *)) { if ([UITraitCollection currentTraitCollection].userInterfaceStyle != previousTraitCollection.userInterfaceStyle) { [self system_theme_changed]; } @@ -293,8 +276,8 @@ - (void)layoutSubviews { self.renderingLayer.frame = self.bounds; [self.renderingLayer layoutDisplayLayer]; - if (DisplayServerIOS::get_singleton()) { - DisplayServerIOS::get_singleton()->resize_window(self.bounds.size); + if (DisplayServerAppleEmbedded::get_singleton()) { + DisplayServerAppleEmbedded::get_singleton()->resize_window(self.bounds.size); } } @@ -357,7 +340,7 @@ - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { int tid = [self getTouchIDForTouch:touch]; ERR_FAIL_COND(tid == -1); CGPoint touchPoint = [touch locationInView:self]; - DisplayServerIOS::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, true, touch.tapCount > 1); + DisplayServerAppleEmbedded::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, true, touch.tapCount > 1); } } @@ -369,7 +352,7 @@ - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { CGPoint prev_point = [touch previousLocationInView:self]; CGFloat alt = [touch altitudeAngle]; CGVector azim = [touch azimuthUnitVectorInView:self]; - DisplayServerIOS::get_singleton()->touch_drag(tid, prev_point.x * self.contentScaleFactor, prev_point.y * self.contentScaleFactor, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, [touch force] / [touch maximumPossibleForce], Vector2(azim.dx, azim.dy) * Math::cos(alt)); + DisplayServerAppleEmbedded::get_singleton()->touch_drag(tid, prev_point.x * self.contentScaleFactor, prev_point.y * self.contentScaleFactor, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, [touch force] / [touch maximumPossibleForce], Vector2(azim.dx, azim.dy) * Math::cos(alt)); } } @@ -379,7 +362,7 @@ - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { ERR_FAIL_COND(tid == -1); [self removeTouch:touch]; CGPoint touchPoint = [touch locationInView:self]; - DisplayServerIOS::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, false, false); + DisplayServerAppleEmbedded::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, false, false); } } @@ -387,7 +370,7 @@ - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { for (UITouch *touch in touches) { int tid = [self getTouchIDForTouch:touch]; ERR_FAIL_COND(tid == -1); - DisplayServerIOS::get_singleton()->touches_canceled(tid); + DisplayServerAppleEmbedded::get_singleton()->touches_canceled(tid); } [self clearTouches]; } @@ -440,6 +423,7 @@ - (void)handleMotion { UIInterfaceOrientation interfaceOrientation = UIInterfaceOrientationUnknown; +#if !defined(VISIONOS_ENABLED) #if __IPHONE_OS_VERSION_MAX_ALLOWED < 140000 interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation]; #else @@ -451,31 +435,34 @@ - (void)handleMotion { #endif } #endif +#else + interfaceOrientation = [UIApplication sharedApplication].delegate.window.windowScene.interfaceOrientation; +#endif switch (interfaceOrientation) { case UIInterfaceOrientationLandscapeLeft: { - DisplayServerIOS::get_singleton()->update_gravity(Vector3(gravity.x, gravity.y, gravity.z).rotated(Vector3(0, 0, 1), -Math::PI * 0.5)); - DisplayServerIOS::get_singleton()->update_accelerometer(Vector3(acceleration.x + gravity.x, acceleration.y + gravity.y, acceleration.z + gravity.z).rotated(Vector3(0, 0, 1), -Math::PI * 0.5)); - DisplayServerIOS::get_singleton()->update_magnetometer(Vector3(magnetic.x, magnetic.y, magnetic.z).rotated(Vector3(0, 0, 1), -Math::PI * 0.5)); - DisplayServerIOS::get_singleton()->update_gyroscope(Vector3(rotation.x, rotation.y, rotation.z).rotated(Vector3(0, 0, 1), -Math::PI * 0.5)); + DisplayServerAppleEmbedded::get_singleton()->update_gravity(Vector3(gravity.x, gravity.y, gravity.z).rotated(Vector3(0, 0, 1), -Math::PI * 0.5)); + DisplayServerAppleEmbedded::get_singleton()->update_accelerometer(Vector3(acceleration.x + gravity.x, acceleration.y + gravity.y, acceleration.z + gravity.z).rotated(Vector3(0, 0, 1), -Math::PI * 0.5)); + DisplayServerAppleEmbedded::get_singleton()->update_magnetometer(Vector3(magnetic.x, magnetic.y, magnetic.z).rotated(Vector3(0, 0, 1), -Math::PI * 0.5)); + DisplayServerAppleEmbedded::get_singleton()->update_gyroscope(Vector3(rotation.x, rotation.y, rotation.z).rotated(Vector3(0, 0, 1), -Math::PI * 0.5)); } break; case UIInterfaceOrientationLandscapeRight: { - DisplayServerIOS::get_singleton()->update_gravity(Vector3(gravity.x, gravity.y, gravity.z).rotated(Vector3(0, 0, 1), Math::PI * 0.5)); - DisplayServerIOS::get_singleton()->update_accelerometer(Vector3(acceleration.x + gravity.x, acceleration.y + gravity.y, acceleration.z + gravity.z).rotated(Vector3(0, 0, 1), Math::PI * 0.5)); - DisplayServerIOS::get_singleton()->update_magnetometer(Vector3(magnetic.x, magnetic.y, magnetic.z).rotated(Vector3(0, 0, 1), Math::PI * 0.5)); - DisplayServerIOS::get_singleton()->update_gyroscope(Vector3(rotation.x, rotation.y, rotation.z).rotated(Vector3(0, 0, 1), Math::PI * 0.5)); + DisplayServerAppleEmbedded::get_singleton()->update_gravity(Vector3(gravity.x, gravity.y, gravity.z).rotated(Vector3(0, 0, 1), Math::PI * 0.5)); + DisplayServerAppleEmbedded::get_singleton()->update_accelerometer(Vector3(acceleration.x + gravity.x, acceleration.y + gravity.y, acceleration.z + gravity.z).rotated(Vector3(0, 0, 1), Math::PI * 0.5)); + DisplayServerAppleEmbedded::get_singleton()->update_magnetometer(Vector3(magnetic.x, magnetic.y, magnetic.z).rotated(Vector3(0, 0, 1), Math::PI * 0.5)); + DisplayServerAppleEmbedded::get_singleton()->update_gyroscope(Vector3(rotation.x, rotation.y, rotation.z).rotated(Vector3(0, 0, 1), Math::PI * 0.5)); } break; case UIInterfaceOrientationPortraitUpsideDown: { - DisplayServerIOS::get_singleton()->update_gravity(Vector3(gravity.x, gravity.y, gravity.z).rotated(Vector3(0, 0, 1), Math::PI)); - DisplayServerIOS::get_singleton()->update_accelerometer(Vector3(acceleration.x + gravity.x, acceleration.y + gravity.y, acceleration.z + gravity.z).rotated(Vector3(0, 0, 1), Math::PI)); - DisplayServerIOS::get_singleton()->update_magnetometer(Vector3(magnetic.x, magnetic.y, magnetic.z).rotated(Vector3(0, 0, 1), Math::PI)); - DisplayServerIOS::get_singleton()->update_gyroscope(Vector3(rotation.x, rotation.y, rotation.z).rotated(Vector3(0, 0, 1), Math::PI)); + DisplayServerAppleEmbedded::get_singleton()->update_gravity(Vector3(gravity.x, gravity.y, gravity.z).rotated(Vector3(0, 0, 1), Math::PI)); + DisplayServerAppleEmbedded::get_singleton()->update_accelerometer(Vector3(acceleration.x + gravity.x, acceleration.y + gravity.y, acceleration.z + gravity.z).rotated(Vector3(0, 0, 1), Math::PI)); + DisplayServerAppleEmbedded::get_singleton()->update_magnetometer(Vector3(magnetic.x, magnetic.y, magnetic.z).rotated(Vector3(0, 0, 1), Math::PI)); + DisplayServerAppleEmbedded::get_singleton()->update_gyroscope(Vector3(rotation.x, rotation.y, rotation.z).rotated(Vector3(0, 0, 1), Math::PI)); } break; default: { // assume portrait - DisplayServerIOS::get_singleton()->update_gravity(Vector3(gravity.x, gravity.y, gravity.z)); - DisplayServerIOS::get_singleton()->update_accelerometer(Vector3(acceleration.x + gravity.x, acceleration.y + gravity.y, acceleration.z + gravity.z)); - DisplayServerIOS::get_singleton()->update_magnetometer(Vector3(magnetic.x, magnetic.y, magnetic.z)); - DisplayServerIOS::get_singleton()->update_gyroscope(Vector3(rotation.x, rotation.y, rotation.z)); + DisplayServerAppleEmbedded::get_singleton()->update_gravity(Vector3(gravity.x, gravity.y, gravity.z)); + DisplayServerAppleEmbedded::get_singleton()->update_accelerometer(Vector3(acceleration.x + gravity.x, acceleration.y + gravity.y, acceleration.z + gravity.z)); + DisplayServerAppleEmbedded::get_singleton()->update_magnetometer(Vector3(magnetic.x, magnetic.y, magnetic.z)); + DisplayServerAppleEmbedded::get_singleton()->update_gyroscope(Vector3(rotation.x, rotation.y, rotation.z)); } break; } } diff --git a/platform/ios/godot_view_renderer.h b/drivers/apple_embedded/godot_view_renderer.h similarity index 95% rename from platform/ios/godot_view_renderer.h rename to drivers/apple_embedded/godot_view_renderer.h index b6a78ffbf57b..1d409d9b4500 100644 --- a/platform/ios/godot_view_renderer.h +++ b/drivers/apple_embedded/godot_view_renderer.h @@ -32,7 +32,7 @@ #import -@protocol GodotViewRendererProtocol +@protocol GDTViewRendererProtocol @property(assign, readonly, nonatomic) BOOL hasFinishedSetup; @@ -41,6 +41,6 @@ @end -@interface GodotViewRenderer : NSObject +@interface GDTViewRenderer : NSObject @end diff --git a/platform/ios/godot_view_renderer.mm b/drivers/apple_embedded/godot_view_renderer.mm similarity index 93% rename from platform/ios/godot_view_renderer.mm rename to drivers/apple_embedded/godot_view_renderer.mm index 9c56ca342fb8..95a41e7bd4ff 100644 --- a/platform/ios/godot_view_renderer.mm +++ b/drivers/apple_embedded/godot_view_renderer.mm @@ -30,8 +30,8 @@ #import "godot_view_renderer.h" -#import "display_server_ios.h" -#import "os_ios.h" +#import "display_server_apple_embedded.h" +#import "os_apple_embedded.h" #include "core/config/project_settings.h" #include "core/os/keyboard.h" @@ -44,7 +44,7 @@ #import #import -@interface GodotViewRenderer () +@interface GDTViewRenderer () @property(assign, nonatomic) BOOL hasFinishedProjectDataSetup; @property(assign, nonatomic) BOOL hasStartedMain; @@ -52,7 +52,7 @@ @interface GodotViewRenderer () @end -@implementation GodotViewRenderer +@implementation GDTViewRenderer - (BOOL)setupView:(UIView *)view { if (self.hasFinishedSetup) { @@ -70,7 +70,7 @@ - (BOOL)setupView:(UIView *)view { if (!self.hasStartedMain) { self.hasStartedMain = YES; - OS_IOS::get_singleton()->start(); + OS_AppleEmbedded::get_singleton()->start(); return YES; } @@ -109,11 +109,11 @@ - (void)setupProjectData { } - (void)renderOnView:(UIView *)view { - if (!OS_IOS::get_singleton()) { + if (!OS_AppleEmbedded::get_singleton()) { return; } - OS_IOS::get_singleton()->iterate(); + OS_AppleEmbedded::get_singleton()->iterate(); } @end diff --git a/platform/ios/key_mapping_ios.h b/drivers/apple_embedded/key_mapping_apple_embedded.h similarity index 94% rename from platform/ios/key_mapping_ios.h rename to drivers/apple_embedded/key_mapping_apple_embedded.h index 81abee3ce995..184c628a74da 100644 --- a/platform/ios/key_mapping_ios.h +++ b/drivers/apple_embedded/key_mapping_apple_embedded.h @@ -1,5 +1,5 @@ /**************************************************************************/ -/* key_mapping_ios.h */ +/* key_mapping_apple_embedded.h */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -34,8 +34,8 @@ #import -class KeyMappingIOS { - KeyMappingIOS() {} +class KeyMappingAppleEmbedded { + KeyMappingAppleEmbedded() {} public: static void initialize(); diff --git a/platform/ios/key_mapping_ios.mm b/drivers/apple_embedded/key_mapping_apple_embedded.mm similarity index 97% rename from platform/ios/key_mapping_ios.mm rename to drivers/apple_embedded/key_mapping_apple_embedded.mm index 61f28aa84bf7..137328b43f2c 100644 --- a/platform/ios/key_mapping_ios.mm +++ b/drivers/apple_embedded/key_mapping_apple_embedded.mm @@ -1,5 +1,5 @@ /**************************************************************************/ -/* key_mapping_ios.mm */ +/* key_mapping_apple_embedded.mm */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,7 +28,7 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#import "key_mapping_ios.h" +#import "key_mapping_apple_embedded.h" #include "core/templates/hash_map.h" @@ -40,7 +40,7 @@ HashMap keyusage_map; HashMap location_map; -void KeyMappingIOS::initialize() { +void KeyMappingAppleEmbedded::initialize() { if (@available(iOS 13.4, *)) { keyusage_map[UIKeyboardHIDUsageKeyboardA] = Key::A; keyusage_map[UIKeyboardHIDUsageKeyboardB] = Key::B; @@ -185,7 +185,7 @@ } } -Key KeyMappingIOS::remap_key(CFIndex p_keycode) { +Key KeyMappingAppleEmbedded::remap_key(CFIndex p_keycode) { if (@available(iOS 13.4, *)) { const Key *key = keyusage_map.getptr(p_keycode); if (key) { @@ -195,7 +195,7 @@ return Key::NONE; } -KeyLocation KeyMappingIOS::key_location(CFIndex p_keycode) { +KeyLocation KeyMappingAppleEmbedded::key_location(CFIndex p_keycode) { if (@available(iOS 13.4, *)) { const KeyLocation *location = location_map.getptr(p_keycode); if (location) { diff --git a/platform/ios/keyboard_input_view.h b/drivers/apple_embedded/keyboard_input_view.h similarity index 98% rename from platform/ios/keyboard_input_view.h rename to drivers/apple_embedded/keyboard_input_view.h index c2e789f53d00..3f57d6d2687b 100644 --- a/platform/ios/keyboard_input_view.h +++ b/drivers/apple_embedded/keyboard_input_view.h @@ -32,7 +32,7 @@ #import -@interface GodotKeyboardInputView : UITextView +@interface GDTKeyboardInputView : UITextView - (BOOL)becomeFirstResponderWithString:(NSString *)existingString cursorStart:(NSInteger)start cursorEnd:(NSInteger)end; diff --git a/platform/ios/keyboard_input_view.mm b/drivers/apple_embedded/keyboard_input_view.mm similarity index 90% rename from platform/ios/keyboard_input_view.mm rename to drivers/apple_embedded/keyboard_input_view.mm index a2e58efbdc4f..3079d5a5235f 100644 --- a/platform/ios/keyboard_input_view.mm +++ b/drivers/apple_embedded/keyboard_input_view.mm @@ -30,19 +30,19 @@ #import "keyboard_input_view.h" -#import "display_server_ios.h" -#import "os_ios.h" +#import "display_server_apple_embedded.h" +#import "os_apple_embedded.h" #include "core/os/keyboard.h" -@interface GodotKeyboardInputView () +@interface GDTKeyboardInputView () @property(nonatomic, copy) NSString *previousText; @property(nonatomic, assign) NSRange previousSelectedRange; @end -@implementation GodotKeyboardInputView +@implementation GDTKeyboardInputView - (instancetype)initWithCoder:(NSCoder *)coder { self = [super initWithCoder:coder]; @@ -116,8 +116,8 @@ - (BOOL)resignFirstResponder { - (void)deleteText:(NSInteger)charactersToDelete { for (int i = 0; i < charactersToDelete; i++) { - DisplayServerIOS::get_singleton()->key(Key::BACKSPACE, 0, Key::BACKSPACE, Key::NONE, 0, true, KeyLocation::UNSPECIFIED); - DisplayServerIOS::get_singleton()->key(Key::BACKSPACE, 0, Key::BACKSPACE, Key::NONE, 0, false, KeyLocation::UNSPECIFIED); + DisplayServerAppleEmbedded::get_singleton()->key(Key::BACKSPACE, 0, Key::BACKSPACE, Key::NONE, 0, true, KeyLocation::UNSPECIFIED); + DisplayServerAppleEmbedded::get_singleton()->key(Key::BACKSPACE, 0, Key::BACKSPACE, Key::NONE, 0, false, KeyLocation::UNSPECIFIED); } } @@ -136,8 +136,8 @@ - (void)enterText:(NSString *)substring { key = Key::SPACE; } - DisplayServerIOS::get_singleton()->key(key, character, key, Key::NONE, 0, true, KeyLocation::UNSPECIFIED); - DisplayServerIOS::get_singleton()->key(key, character, key, Key::NONE, 0, false, KeyLocation::UNSPECIFIED); + DisplayServerAppleEmbedded::get_singleton()->key(key, character, key, Key::NONE, 0, true, KeyLocation::UNSPECIFIED); + DisplayServerAppleEmbedded::get_singleton()->key(key, character, key, Key::NONE, 0, false, KeyLocation::UNSPECIFIED); } } diff --git a/drivers/apple_embedded/main_utilities.h b/drivers/apple_embedded/main_utilities.h new file mode 100644 index 000000000000..441a7dddf36b --- /dev/null +++ b/drivers/apple_embedded/main_utilities.h @@ -0,0 +1,35 @@ +/**************************************************************************/ +/* main_utilities.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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. */ +/**************************************************************************/ + +#pragma once + +void change_to_launch_dir(char **p_args); + +int process_args(int p_argc, char **p_args, char **r_args); diff --git a/platform/ios/godot_ios.mm b/drivers/apple_embedded/main_utilities.mm similarity index 77% rename from platform/ios/godot_ios.mm rename to drivers/apple_embedded/main_utilities.mm index 42b9658fbf99..608232625023 100644 --- a/platform/ios/godot_ios.mm +++ b/drivers/apple_embedded/main_utilities.mm @@ -1,5 +1,5 @@ /**************************************************************************/ -/* godot_ios.mm */ +/* main_utilities.mm */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,15 +28,29 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#import "os_ios.h" - #include "core/string/ustring.h" -#include "main/main.h" + +#import #include #include -static OS_IOS *os = nullptr; +void change_to_launch_dir(char **p_args) { + size_t len = strlen(p_args[0]); + + while (len--) { + if (p_args[0][len] == '/') { + break; + } + } + + if (len >= 0) { + char path[512]; + memcpy(path, p_args[0], len > sizeof(path) ? sizeof(path) : len); + path[len] = 0; + chdir(path); + } +} int add_path(int p_argc, char **p_args) { NSString *str = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_path"]; @@ -70,50 +84,12 @@ int add_cmdline(int p_argc, char **p_args) { return p_argc; } -int ios_main(int argc, char **argv) { - size_t len = strlen(argv[0]); - - while (len--) { - if (argv[0][len] == '/') { - break; - } +int process_args(int p_argc, char **p_args, char **r_args) { + for (int i = 0; i < p_argc; i++) { + r_args[i] = p_args[i]; } - - if (len >= 0) { - char path[512]; - memcpy(path, argv[0], len > sizeof(path) ? sizeof(path) : len); - path[len] = 0; - chdir(path); - } - - os = new OS_IOS(); - - // We must override main when testing is enabled - TEST_MAIN_OVERRIDE - - char *fargv[64]; - for (int i = 0; i < argc; i++) { - fargv[i] = argv[i]; - } - fargv[argc] = nullptr; - argc = add_path(argc, fargv); - argc = add_cmdline(argc, fargv); - - Error err = Main::setup(fargv[0], argc - 1, &fargv[1], false); - - if (err != OK) { - if (err == ERR_HELP) { // Returned by --help and --version, so success. - return EXIT_SUCCESS; - } - return EXIT_FAILURE; - } - - os->initialize_modules(); - - return os->get_exit_code(); -} - -void ios_finish() { - Main::cleanup(); - delete os; + r_args[p_argc] = nullptr; + p_argc = add_path(p_argc, r_args); + p_argc = add_cmdline(p_argc, r_args); + return p_argc; } diff --git a/drivers/apple_embedded/os_apple_embedded.h b/drivers/apple_embedded/os_apple_embedded.h new file mode 100644 index 000000000000..6bbf31852315 --- /dev/null +++ b/drivers/apple_embedded/os_apple_embedded.h @@ -0,0 +1,137 @@ +/**************************************************************************/ +/* os_apple_embedded.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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. */ +/**************************************************************************/ + +#pragma once + +#ifdef APPLE_EMBEDDED_ENABLED + +#import "apple_embedded.h" + +#import "drivers/apple/joypad_apple.h" +#import "drivers/coreaudio/audio_driver_coreaudio.h" +#include "drivers/unix/os_unix.h" +#include "servers/audio_server.h" +#include "servers/rendering/renderer_compositor.h" + +#if defined(RD_ENABLED) +#include "servers/rendering/rendering_device.h" + +#if defined(VULKAN_ENABLED) +#import "rendering_context_driver_vulkan_apple_embedded.h" +#endif +#endif + +class OS_AppleEmbedded : public OS_Unix { +private: + static HashMap dynamic_symbol_lookup_table; + friend void register_dynamic_symbol(char *name, void *address); + + AudioDriverCoreAudio audio_driver; + + AppleEmbedded *apple_embedded = nullptr; + + JoypadApple *joypad_apple = nullptr; + + MainLoop *main_loop = nullptr; + + virtual void initialize_core() override; + virtual void initialize() override; + + virtual void initialize_joypads() override; + + virtual void set_main_loop(MainLoop *p_main_loop) override; + virtual MainLoop *get_main_loop() const override; + + virtual void delete_main_loop() override; + + virtual void finalize() override; + + bool is_focused = false; + + CGFloat _weight_to_ct(int p_weight) const; + CGFloat _stretch_to_ct(int p_stretch) const; + String _get_default_fontname(const String &p_font_name) const; + + static _FORCE_INLINE_ String get_framework_executable(const String &p_path); + + void deinitialize_modules(); + +public: + static OS_AppleEmbedded *get_singleton(); + + OS_AppleEmbedded(); + ~OS_AppleEmbedded(); + + void initialize_modules(); + + bool iterate(); + + void start(); + + virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override; + + virtual Vector get_system_fonts() const override; + virtual Vector get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale = String(), const String &p_script = String(), int p_weight = 400, int p_stretch = 100, bool p_italic = false) const override; + virtual String get_system_font_path(const String &p_font_name, int p_weight = 400, int p_stretch = 100, bool p_italic = false) const override; + + virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data = nullptr) override; + virtual Error close_dynamic_library(void *p_library_handle) override; + virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String &p_name, void *&p_symbol_handle, bool p_optional = false) override; + + virtual String get_distribution_name() const override; + virtual String get_version() const override; + virtual String get_model_name() const override; + + virtual Error shell_open(const String &p_uri) override; + + virtual String get_user_data_dir(const String &p_user_dir) const override; + + virtual String get_cache_path() const override; + virtual String get_temp_path() const override; + + virtual String get_locale() const override; + + virtual String get_unique_id() const override; + virtual String get_processor_name() const override; + + virtual void vibrate_handheld(int p_duration_ms = 500, float p_amplitude = -1.0) override; + + virtual bool _check_internal_feature_support(const String &p_feature) override; + + void on_focus_out(); + void on_focus_in(); + + void on_enter_background(); + void on_exit_background(); + + virtual Rect2 calculate_boot_screen_rect(const Size2 &p_window_size, const Size2 &p_imgrect_size) const override; +}; + +#endif // APPLE_EMBEDDED_ENABLED diff --git a/drivers/apple_embedded/os_apple_embedded.mm b/drivers/apple_embedded/os_apple_embedded.mm new file mode 100644 index 000000000000..fa71c96e100f --- /dev/null +++ b/drivers/apple_embedded/os_apple_embedded.mm @@ -0,0 +1,717 @@ +/**************************************************************************/ +/* os_apple_embedded.mm */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 "os_apple_embedded.h" + +#ifdef APPLE_EMBEDDED_ENABLED + +#import "app_delegate_service.h" +#import "display_server_apple_embedded.h" +#import "godot_view_apple_embedded.h" +#import "terminal_logger_apple_embedded.h" +#import "view_controller.h" + +#include "core/config/project_settings.h" +#include "core/io/dir_access.h" +#include "core/io/file_access.h" +#include "core/io/file_access_pack.h" +#include "drivers/unix/syslog_logger.h" +#include "main/main.h" + +#import +#import +#import +#import +#include + +#if defined(RD_ENABLED) +#include "servers/rendering/renderer_rd/renderer_compositor_rd.h" +#import + +#if defined(VULKAN_ENABLED) +#include "drivers/vulkan/godot_vulkan.h" +#endif // VULKAN_ENABLED +#endif + +// Initialization order between compilation units is not guaranteed, +// so we use this as a hack to ensure certain code is called before +// everything else, but after all units are initialized. +typedef void (*init_callback)(); +static init_callback *apple_init_callbacks = nullptr; +static int apple_embedded_platform_init_callbacks_count = 0; +static int apple_embedded_platform_init_callbacks_capacity = 0; +HashMap OS_AppleEmbedded::dynamic_symbol_lookup_table; + +void add_apple_embedded_platform_init_callback(init_callback cb) { + if (apple_embedded_platform_init_callbacks_count == apple_embedded_platform_init_callbacks_capacity) { + void *new_ptr = realloc(apple_init_callbacks, sizeof(cb) * (apple_embedded_platform_init_callbacks_capacity + 32)); + if (new_ptr) { + apple_init_callbacks = (init_callback *)(new_ptr); + apple_embedded_platform_init_callbacks_capacity += 32; + } else { + ERR_FAIL_MSG("Unable to allocate memory for extension callbacks."); + } + } + apple_init_callbacks[apple_embedded_platform_init_callbacks_count++] = cb; +} + +void register_dynamic_symbol(char *name, void *address) { + OS_AppleEmbedded::dynamic_symbol_lookup_table[String(name)] = address; +} + +Rect2 fit_keep_aspect_centered(const Vector2 &p_container, const Vector2 &p_rect) { + real_t available_ratio = p_container.width / p_container.height; + real_t fit_ratio = p_rect.width / p_rect.height; + Rect2 result; + if (fit_ratio < available_ratio) { + // Fit height - we'll have horizontal gaps + result.size.height = p_container.height; + result.size.width = p_container.height * fit_ratio; + result.position.y = 0; + result.position.x = (p_container.width - result.size.width) * 0.5f; + } else { + // Fit width - we'll have vertical gaps + result.size.width = p_container.width; + result.size.height = p_container.width / fit_ratio; + result.position.x = 0; + result.position.y = (p_container.height - result.size.height) * 0.5f; + } + return result; +} + +Rect2 fit_keep_aspect_covered(const Vector2 &p_container, const Vector2 &p_rect) { + real_t available_ratio = p_container.width / p_container.height; + real_t fit_ratio = p_rect.width / p_rect.height; + Rect2 result; + if (fit_ratio < available_ratio) { + // Need to scale up to fit width, and crop height + result.size.width = p_container.width; + result.size.height = p_container.width / fit_ratio; + result.position.x = 0; + result.position.y = (p_container.height - result.size.height) * 0.5f; + } else { + // Need to scale up to fit height, and crop width + result.size.width = p_container.height * fit_ratio; + result.size.height = p_container.height; + result.position.x = (p_container.width - result.size.width) * 0.5f; + result.position.y = 0; + } + return result; +} + +OS_AppleEmbedded *OS_AppleEmbedded::get_singleton() { + return (OS_AppleEmbedded *)OS::get_singleton(); +} + +OS_AppleEmbedded::OS_AppleEmbedded() { + for (int i = 0; i < apple_embedded_platform_init_callbacks_count; ++i) { + apple_init_callbacks[i](); + } + free(apple_init_callbacks); + apple_init_callbacks = nullptr; + apple_embedded_platform_init_callbacks_count = 0; + apple_embedded_platform_init_callbacks_capacity = 0; + + main_loop = nullptr; + + Vector loggers; + loggers.push_back(memnew(TerminalLoggerAppleEmbedded)); + _set_logger(memnew(CompositeLogger(loggers))); + + AudioDriverManager::add_driver(&audio_driver); +} + +OS_AppleEmbedded::~OS_AppleEmbedded() {} + +void OS_AppleEmbedded::alert(const String &p_alert, const String &p_title) { + const CharString utf8_alert = p_alert.utf8(); + const CharString utf8_title = p_title.utf8(); + AppleEmbedded::alert(utf8_alert.get_data(), utf8_title.get_data()); +} + +void OS_AppleEmbedded::initialize_core() { + OS_Unix::initialize_core(); +} + +void OS_AppleEmbedded::initialize() { + initialize_core(); +} + +void OS_AppleEmbedded::initialize_joypads() { + joypad_apple = memnew(JoypadApple); +} + +void OS_AppleEmbedded::initialize_modules() { + apple_embedded = memnew(AppleEmbedded); + Engine::get_singleton()->add_singleton(Engine::Singleton("AppleEmbedded", apple_embedded)); +} + +void OS_AppleEmbedded::deinitialize_modules() { + if (joypad_apple) { + memdelete(joypad_apple); + } + + if (apple_embedded) { + memdelete(apple_embedded); + } +} + +void OS_AppleEmbedded::set_main_loop(MainLoop *p_main_loop) { + main_loop = p_main_loop; +} + +MainLoop *OS_AppleEmbedded::get_main_loop() const { + return main_loop; +} + +void OS_AppleEmbedded::delete_main_loop() { + if (main_loop) { + main_loop->finalize(); + memdelete(main_loop); + } + + main_loop = nullptr; +} + +bool OS_AppleEmbedded::iterate() { + if (!main_loop) { + return true; + } + + if (DisplayServer::get_singleton()) { + DisplayServer::get_singleton()->process_events(); + } + + joypad_apple->process_joypads(); + + return Main::iteration(); +} + +void OS_AppleEmbedded::start() { + if (Main::start() == EXIT_SUCCESS) { + main_loop->initialize(); + } +} + +void OS_AppleEmbedded::finalize() { + deinitialize_modules(); + + // Already gets called + //delete_main_loop(); +} + +// MARK: Dynamic Libraries + +_FORCE_INLINE_ String OS_AppleEmbedded::get_framework_executable(const String &p_path) { + Ref da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + + // Read framework bundle to get executable name. + NSURL *url = [NSURL fileURLWithPath:@(p_path.utf8().get_data())]; + NSBundle *bundle = [NSBundle bundleWithURL:url]; + if (bundle) { + String exe_path = String::utf8([[bundle executablePath] UTF8String]); + if (da->file_exists(exe_path)) { + return exe_path; + } + } + + // Try default executable name (invalid framework). + if (da->dir_exists(p_path) && da->file_exists(p_path.path_join(p_path.get_file().get_basename()))) { + return p_path.path_join(p_path.get_file().get_basename()); + } + + // Not a framework, try loading as .dylib. + return p_path; +} + +Error OS_AppleEmbedded::open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data) { + if (p_path.length() == 0) { + // Static xcframework. + p_library_handle = RTLD_SELF; + + if (p_data != nullptr && p_data->r_resolved_path != nullptr) { + *p_data->r_resolved_path = p_path; + } + + return OK; + } + + String path = get_framework_executable(p_path); + + if (!FileAccess::exists(path)) { + // Load .dylib or framework from within the executable path. + path = get_framework_executable(get_executable_path().get_base_dir().path_join(p_path.get_file())); + } + + if (!FileAccess::exists(path)) { + // Load .dylib converted to framework from within the executable path. + path = get_framework_executable(get_executable_path().get_base_dir().path_join(p_path.get_file().get_basename() + ".framework")); + } + + if (!FileAccess::exists(path)) { + // Load .dylib or framework from a standard iOS location. + path = get_framework_executable(get_executable_path().get_base_dir().path_join("Frameworks").path_join(p_path.get_file())); + } + + if (!FileAccess::exists(path)) { + // Load .dylib converted to framework from a standard iOS location. + path = get_framework_executable(get_executable_path().get_base_dir().path_join("Frameworks").path_join(p_path.get_file().get_basename() + ".framework")); + } + + ERR_FAIL_COND_V(!FileAccess::exists(path), ERR_FILE_NOT_FOUND); + + p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW); + ERR_FAIL_NULL_V_MSG(p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", p_path, dlerror())); + + if (p_data != nullptr && p_data->r_resolved_path != nullptr) { + *p_data->r_resolved_path = path; + } + + return OK; +} + +Error OS_AppleEmbedded::close_dynamic_library(void *p_library_handle) { + if (p_library_handle == RTLD_SELF) { + return OK; + } + return OS_Unix::close_dynamic_library(p_library_handle); +} + +Error OS_AppleEmbedded::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 = OS_AppleEmbedded::dynamic_symbol_lookup_table.getptr(p_name); + if (ptr) { + p_symbol_handle = *ptr; + return OK; + } + } + return OS_Unix::get_dynamic_library_symbol_handle(p_library_handle, p_name, p_symbol_handle, p_optional); +} + +String OS_AppleEmbedded::get_distribution_name() const { + return get_name(); +} + +String OS_AppleEmbedded::get_version() const { + NSOperatingSystemVersion ver = [NSProcessInfo processInfo].operatingSystemVersion; + return vformat("%d.%d.%d", (int64_t)ver.majorVersion, (int64_t)ver.minorVersion, (int64_t)ver.patchVersion); +} + +String OS_AppleEmbedded::get_model_name() const { + String model = apple_embedded->get_model(); + if (model != "") { + return model; + } + + return OS_Unix::get_model_name(); +} + +Error OS_AppleEmbedded::shell_open(const 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; + } + + print_verbose(vformat("Opening URL %s", p_uri)); + + [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil]; + + return OK; +} + +String OS_AppleEmbedded::get_user_data_dir(const String &p_user_dir) const { + static String ret; + if (ret.is_empty()) { + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + if (paths && [paths count] >= 1) { + ret.append_utf8([[paths firstObject] UTF8String]); + } + } + return ret; +} + +String OS_AppleEmbedded::get_cache_path() const { + static String ret; + if (ret.is_empty()) { + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); + if (paths && [paths count] >= 1) { + ret.append_utf8([[paths firstObject] UTF8String]); + } + } + return ret; +} + +String OS_AppleEmbedded::get_temp_path() const { + static String ret; + if (ret.is_empty()) { + NSURL *url = [NSURL fileURLWithPath:NSTemporaryDirectory() + isDirectory:YES]; + if (url) { + ret = String::utf8([url.path UTF8String]); + ret = ret.trim_prefix("file://"); + } + } + return ret; +} + +String OS_AppleEmbedded::get_locale() const { + NSString *preferredLanguage = [NSLocale preferredLanguages].firstObject; + + if (preferredLanguage) { + return String::utf8([preferredLanguage UTF8String]).replace_char('-', '_'); + } + + NSString *localeIdentifier = [[NSLocale currentLocale] localeIdentifier]; + return String::utf8([localeIdentifier UTF8String]).replace_char('-', '_'); +} + +String OS_AppleEmbedded::get_unique_id() const { + NSString *uuid = [UIDevice currentDevice].identifierForVendor.UUIDString; + return String::utf8([uuid UTF8String]); +} + +String OS_AppleEmbedded::get_processor_name() const { + char buffer[256]; + size_t buffer_len = 256; + if (sysctlbyname("machdep.cpu.brand_string", &buffer, &buffer_len, nullptr, 0) == 0) { + return String::utf8(buffer, buffer_len); + } + ERR_FAIL_V_MSG("", String("Couldn't get the CPU model name. Returning an empty string.")); +} + +Vector OS_AppleEmbedded::get_system_fonts() const { + HashSet font_names; + CFArrayRef fonts = CTFontManagerCopyAvailableFontFamilyNames(); + if (fonts) { + for (CFIndex i = 0; i < CFArrayGetCount(fonts); i++) { + CFStringRef cf_name = (CFStringRef)CFArrayGetValueAtIndex(fonts, i); + if (cf_name && (CFStringGetLength(cf_name) > 0) && (CFStringCompare(cf_name, CFSTR("LastResort"), kCFCompareCaseInsensitive) != kCFCompareEqualTo) && (CFStringGetCharacterAtIndex(cf_name, 0) != '.')) { + NSString *ns_name = (__bridge NSString *)cf_name; + font_names.insert(String::utf8([ns_name UTF8String])); + } + } + CFRelease(fonts); + } + + Vector ret; + for (const String &E : font_names) { + ret.push_back(E); + } + return ret; +} + +String OS_AppleEmbedded::_get_default_fontname(const String &p_font_name) const { + String font_name = p_font_name; + if (font_name.to_lower() == "sans-serif") { + font_name = "Helvetica"; + } else if (font_name.to_lower() == "serif") { + font_name = "Times"; + } else if (font_name.to_lower() == "monospace") { + font_name = "Courier"; + } else if (font_name.to_lower() == "fantasy") { + font_name = "Papyrus"; + } else if (font_name.to_lower() == "cursive") { + font_name = "Apple Chancery"; + }; + return font_name; +} + +CGFloat OS_AppleEmbedded::_weight_to_ct(int p_weight) const { + if (p_weight < 150) { + return -0.80; + } else if (p_weight < 250) { + return -0.60; + } else if (p_weight < 350) { + return -0.40; + } else if (p_weight < 450) { + return 0.0; + } else if (p_weight < 550) { + return 0.23; + } else if (p_weight < 650) { + return 0.30; + } else if (p_weight < 750) { + return 0.40; + } else if (p_weight < 850) { + return 0.56; + } else if (p_weight < 925) { + return 0.62; + } else { + return 1.00; + } +} + +CGFloat OS_AppleEmbedded::_stretch_to_ct(int p_stretch) const { + if (p_stretch < 56) { + return -0.5; + } else if (p_stretch < 69) { + return -0.37; + } else if (p_stretch < 81) { + return -0.25; + } else if (p_stretch < 93) { + return -0.13; + } else if (p_stretch < 106) { + return 0.0; + } else if (p_stretch < 137) { + return 0.13; + } else if (p_stretch < 144) { + return 0.25; + } else if (p_stretch < 162) { + return 0.37; + } else { + return 0.5; + } +} + +Vector OS_AppleEmbedded::get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale, const String &p_script, int p_weight, int p_stretch, bool p_italic) const { + Vector ret; + String font_name = _get_default_fontname(p_font_name); + + CFStringRef name = CFStringCreateWithCString(kCFAllocatorDefault, font_name.utf8().get_data(), kCFStringEncodingUTF8); + CTFontSymbolicTraits traits = 0; + if (p_weight >= 700) { + traits |= kCTFontBoldTrait; + } + if (p_italic) { + traits |= kCTFontItalicTrait; + } + if (p_stretch < 100) { + traits |= kCTFontCondensedTrait; + } else if (p_stretch > 100) { + traits |= kCTFontExpandedTrait; + } + + CFNumberRef sym_traits = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &traits); + CFMutableDictionaryRef traits_dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr); + CFDictionaryAddValue(traits_dict, kCTFontSymbolicTrait, sym_traits); + + CGFloat weight = _weight_to_ct(p_weight); + CFNumberRef font_weight = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &weight); + CFDictionaryAddValue(traits_dict, kCTFontWeightTrait, font_weight); + + CGFloat stretch = _stretch_to_ct(p_stretch); + CFNumberRef font_stretch = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &stretch); + CFDictionaryAddValue(traits_dict, kCTFontWidthTrait, font_stretch); + + CFMutableDictionaryRef attributes = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr); + CFDictionaryAddValue(attributes, kCTFontFamilyNameAttribute, name); + CFDictionaryAddValue(attributes, kCTFontTraitsAttribute, traits_dict); + + CTFontDescriptorRef font = CTFontDescriptorCreateWithAttributes(attributes); + if (font) { + CTFontRef family = CTFontCreateWithFontDescriptor(font, 0, nullptr); + if (family) { + CFStringRef string = CFStringCreateWithCString(kCFAllocatorDefault, p_text.utf8().get_data(), kCFStringEncodingUTF8); + CFRange range = CFRangeMake(0, CFStringGetLength(string)); + CTFontRef fallback_family = CTFontCreateForString(family, string, range); + if (fallback_family) { + CTFontDescriptorRef fallback_font = CTFontCopyFontDescriptor(fallback_family); + if (fallback_font) { + CFURLRef url = (CFURLRef)CTFontDescriptorCopyAttribute(fallback_font, kCTFontURLAttribute); + if (url) { + NSString *font_path = [NSString stringWithString:[(__bridge NSURL *)url path]]; + ret.push_back(String::utf8([font_path UTF8String])); + CFRelease(url); + } + CFRelease(fallback_font); + } + CFRelease(fallback_family); + } + CFRelease(string); + CFRelease(family); + } + CFRelease(font); + } + + CFRelease(attributes); + CFRelease(traits_dict); + CFRelease(sym_traits); + CFRelease(font_stretch); + CFRelease(font_weight); + CFRelease(name); + + return ret; +} + +String OS_AppleEmbedded::get_system_font_path(const String &p_font_name, int p_weight, int p_stretch, bool p_italic) const { + String ret; + String font_name = _get_default_fontname(p_font_name); + + CFStringRef name = CFStringCreateWithCString(kCFAllocatorDefault, font_name.utf8().get_data(), kCFStringEncodingUTF8); + + CTFontSymbolicTraits traits = 0; + if (p_weight >= 700) { + traits |= kCTFontBoldTrait; + } + if (p_italic) { + traits |= kCTFontItalicTrait; + } + if (p_stretch < 100) { + traits |= kCTFontCondensedTrait; + } else if (p_stretch > 100) { + traits |= kCTFontExpandedTrait; + } + + CFNumberRef sym_traits = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &traits); + CFMutableDictionaryRef traits_dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr); + CFDictionaryAddValue(traits_dict, kCTFontSymbolicTrait, sym_traits); + + CGFloat weight = _weight_to_ct(p_weight); + CFNumberRef font_weight = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &weight); + CFDictionaryAddValue(traits_dict, kCTFontWeightTrait, font_weight); + + CGFloat stretch = _stretch_to_ct(p_stretch); + CFNumberRef font_stretch = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &stretch); + CFDictionaryAddValue(traits_dict, kCTFontWidthTrait, font_stretch); + + CFMutableDictionaryRef attributes = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr); + CFDictionaryAddValue(attributes, kCTFontFamilyNameAttribute, name); + CFDictionaryAddValue(attributes, kCTFontTraitsAttribute, traits_dict); + + CTFontDescriptorRef font = CTFontDescriptorCreateWithAttributes(attributes); + if (font) { + CFURLRef url = (CFURLRef)CTFontDescriptorCopyAttribute(font, kCTFontURLAttribute); + if (url) { + NSString *font_path = [NSString stringWithString:[(__bridge NSURL *)url path]]; + ret = String::utf8([font_path UTF8String]); + CFRelease(url); + } + CFRelease(font); + } + + CFRelease(attributes); + CFRelease(traits_dict); + CFRelease(sym_traits); + CFRelease(font_stretch); + CFRelease(font_weight); + CFRelease(name); + + return ret; +} + +void OS_AppleEmbedded::vibrate_handheld(int p_duration_ms, float p_amplitude) { + if (apple_embedded->supports_haptic_engine()) { + if (p_amplitude > 0.0) { + p_amplitude = CLAMP(p_amplitude, 0.0, 1.0); + } + + apple_embedded->vibrate_haptic_engine((float)p_duration_ms / 1000.f, p_amplitude); + } else { + // iOS <13 does not support duration for vibration + AudioServicesPlaySystemSound(kSystemSoundID_Vibrate); + } +} + +bool OS_AppleEmbedded::_check_internal_feature_support(const String &p_feature) { + if (p_feature == "system_fonts") { + return true; + } + if (p_feature == "mobile") { + return true; + } + + return false; +} + +void OS_AppleEmbedded::on_focus_out() { + if (is_focused) { + is_focused = false; + + if (DisplayServerAppleEmbedded::get_singleton()) { + DisplayServerAppleEmbedded::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_FOCUS_OUT); + } + + if (OS::get_singleton()->get_main_loop()) { + OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT); + } + + [GDTAppDelegateService.viewController.godotView stopRendering]; + + audio_driver.stop(); + } +} + +void OS_AppleEmbedded::on_focus_in() { + if (!is_focused) { + is_focused = true; + + if (DisplayServerAppleEmbedded::get_singleton()) { + DisplayServerAppleEmbedded::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_FOCUS_IN); + } + + if (OS::get_singleton()->get_main_loop()) { + OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN); + } + + [GDTAppDelegateService.viewController.godotView startRendering]; + + audio_driver.start(); + } +} + +void OS_AppleEmbedded::on_enter_background() { + // Do not check for is_focused, because on_focus_out will always be fired first by applicationWillResignActive. + + if (OS::get_singleton()->get_main_loop()) { + OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_PAUSED); + } + + on_focus_out(); +} + +void OS_AppleEmbedded::on_exit_background() { + if (!is_focused) { + on_focus_in(); + + if (OS::get_singleton()->get_main_loop()) { + OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_RESUMED); + } + } +} + +Rect2 OS_AppleEmbedded::calculate_boot_screen_rect(const Size2 &p_window_size, const Size2 &p_imgrect_size) const { + String scalemodestr = GLOBAL_GET("ios/launch_screen_image_mode"); + + if (scalemodestr == "scaleAspectFit") { + return fit_keep_aspect_centered(p_window_size, p_imgrect_size); + } else if (scalemodestr == "scaleAspectFill") { + return fit_keep_aspect_covered(p_window_size, p_imgrect_size); + } else if (scalemodestr == "scaleToFill") { + return Rect2(Point2(), p_window_size); + } else if (scalemodestr == "center") { + return OS_Unix::calculate_boot_screen_rect(p_window_size, p_imgrect_size); + } else { + WARN_PRINT(vformat("Boot screen scale mode mismatch between iOS and Godot: %s not supported", scalemodestr)); + return OS_Unix::calculate_boot_screen_rect(p_window_size, p_imgrect_size); + } +} + +#endif // APPLE_EMBEDDED_ENABLED diff --git a/drivers/apple_embedded/platform_config.h b/drivers/apple_embedded/platform_config.h new file mode 100644 index 000000000000..bc8b45603ab5 --- /dev/null +++ b/drivers/apple_embedded/platform_config.h @@ -0,0 +1,44 @@ +/**************************************************************************/ +/* platform_config.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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. */ +/**************************************************************************/ + +#pragma once + +#include + +#define PLATFORM_THREAD_OVERRIDE + +#define PTHREAD_RENAME_SELF + +#define _weakify(var) __weak typeof(var) GDWeak_##var = var; +#define _strongify(var) \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wshadow\"") \ + __strong typeof(var) var = GDWeak_##var; \ + _Pragma("clang diagnostic pop") diff --git a/platform/ios/rendering_context_driver_vulkan_ios.h b/drivers/apple_embedded/rendering_context_driver_vulkan_apple_embedded.h similarity index 91% rename from platform/ios/rendering_context_driver_vulkan_ios.h rename to drivers/apple_embedded/rendering_context_driver_vulkan_apple_embedded.h index 07a08cc8613c..8e12bc163a64 100644 --- a/platform/ios/rendering_context_driver_vulkan_ios.h +++ b/drivers/apple_embedded/rendering_context_driver_vulkan_apple_embedded.h @@ -1,5 +1,5 @@ /**************************************************************************/ -/* rendering_context_driver_vulkan_ios.h */ +/* rendering_context_driver_vulkan_apple_embedded.h */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -36,7 +36,7 @@ #import -class RenderingContextDriverVulkanIOS : public RenderingContextDriverVulkan { +class RenderingContextDriverVulkanAppleEmbedded : public RenderingContextDriverVulkan { private: virtual const char *_get_platform_surface_extension() const override final; @@ -48,8 +48,8 @@ class RenderingContextDriverVulkanIOS : public RenderingContextDriverVulkan { CAMetalLayer *const *layer_ptr; }; - RenderingContextDriverVulkanIOS(); - ~RenderingContextDriverVulkanIOS(); + RenderingContextDriverVulkanAppleEmbedded(); + ~RenderingContextDriverVulkanAppleEmbedded(); }; #endif // VULKAN_ENABLED diff --git a/platform/ios/rendering_context_driver_vulkan_ios.mm b/drivers/apple_embedded/rendering_context_driver_vulkan_apple_embedded.mm similarity index 85% rename from platform/ios/rendering_context_driver_vulkan_ios.mm rename to drivers/apple_embedded/rendering_context_driver_vulkan_apple_embedded.mm index 8747bfd76abd..f25931377663 100644 --- a/platform/ios/rendering_context_driver_vulkan_ios.mm +++ b/drivers/apple_embedded/rendering_context_driver_vulkan_apple_embedded.mm @@ -1,5 +1,5 @@ /**************************************************************************/ -/* rendering_context_driver_vulkan_ios.mm */ +/* rendering_context_driver_vulkan_apple_embedded.mm */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,7 +28,7 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#import "rendering_context_driver_vulkan_ios.h" +#import "rendering_context_driver_vulkan_apple_embedded.h" #ifdef VULKAN_ENABLED @@ -38,11 +38,11 @@ #include #endif -const char *RenderingContextDriverVulkanIOS::_get_platform_surface_extension() const { +const char *RenderingContextDriverVulkanAppleEmbedded::_get_platform_surface_extension() const { return VK_EXT_METAL_SURFACE_EXTENSION_NAME; } -RenderingContextDriver::SurfaceID RenderingContextDriverVulkanIOS::surface_create(const void *p_platform_data) { +RenderingContextDriver::SurfaceID RenderingContextDriverVulkanAppleEmbedded::surface_create(const void *p_platform_data) { const WindowPlatformData *wpd = (const WindowPlatformData *)(p_platform_data); VkMetalSurfaceCreateInfoEXT create_info = {}; @@ -58,11 +58,11 @@ return SurfaceID(surface); } -RenderingContextDriverVulkanIOS::RenderingContextDriverVulkanIOS() { +RenderingContextDriverVulkanAppleEmbedded::RenderingContextDriverVulkanAppleEmbedded() { // Does nothing. } -RenderingContextDriverVulkanIOS::~RenderingContextDriverVulkanIOS() { +RenderingContextDriverVulkanAppleEmbedded::~RenderingContextDriverVulkanAppleEmbedded() { // Does nothing. } diff --git a/platform/ios/ios_terminal_logger.h b/drivers/apple_embedded/terminal_logger_apple_embedded.h similarity index 93% rename from platform/ios/ios_terminal_logger.h rename to drivers/apple_embedded/terminal_logger_apple_embedded.h index 7f4f0445d28c..046d30017589 100644 --- a/platform/ios/ios_terminal_logger.h +++ b/drivers/apple_embedded/terminal_logger_apple_embedded.h @@ -1,5 +1,5 @@ /**************************************************************************/ -/* ios_terminal_logger.h */ +/* terminal_logger_apple_embedded.h */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -30,13 +30,13 @@ #pragma once -#ifdef IOS_ENABLED +#ifdef APPLE_EMBEDDED_ENABLED #include "core/io/logger.h" -class IOSTerminalLogger : public StdLogger { +class TerminalLoggerAppleEmbedded : public StdLogger { public: virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify = false, ErrorType p_type = ERR_ERROR, const Vector> &p_script_backtraces = {}) override; }; -#endif // IOS_ENABLED +#endif // APPLE_EMBEDDED_ENABLED diff --git a/platform/ios/ios_terminal_logger.mm b/drivers/apple_embedded/terminal_logger_apple_embedded.mm similarity index 88% rename from platform/ios/ios_terminal_logger.mm rename to drivers/apple_embedded/terminal_logger_apple_embedded.mm index 0dfd7b890fad..647d371c1190 100644 --- a/platform/ios/ios_terminal_logger.mm +++ b/drivers/apple_embedded/terminal_logger_apple_embedded.mm @@ -1,5 +1,5 @@ /**************************************************************************/ -/* ios_terminal_logger.mm */ +/* terminal_logger_apple_embedded.mm */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,13 +28,13 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#import "ios_terminal_logger.h" +#import "terminal_logger_apple_embedded.h" -#ifdef IOS_ENABLED +#ifdef APPLE_EMBEDDED_ENABLED #import -void IOSTerminalLogger::log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type, const Vector> &p_script_backtraces) { +void TerminalLoggerAppleEmbedded::log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type, const Vector> &p_script_backtraces) { if (!should_log(true)) { return; } @@ -77,4 +77,4 @@ } } -#endif // IOS_ENABLED +#endif // APPLE_EMBEDDED_ENABLED diff --git a/platform/ios/tts_ios.h b/drivers/apple_embedded/tts_apple_embedded.h similarity index 95% rename from platform/ios/tts_ios.h rename to drivers/apple_embedded/tts_apple_embedded.h index 0282a084dfcd..33103b2578cc 100644 --- a/platform/ios/tts_ios.h +++ b/drivers/apple_embedded/tts_apple_embedded.h @@ -1,5 +1,5 @@ /**************************************************************************/ -/* tts_ios.h */ +/* tts_apple_embedded.h */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -42,7 +42,7 @@ #import #endif -@interface TTS_IOS : NSObject { +@interface GDTTTS : NSObject { bool speaking; HashMap ids; diff --git a/platform/ios/tts_ios.mm b/drivers/apple_embedded/tts_apple_embedded.mm similarity index 98% rename from platform/ios/tts_ios.mm rename to drivers/apple_embedded/tts_apple_embedded.mm index 33b30b17c906..1cd67b0bcea8 100644 --- a/platform/ios/tts_ios.mm +++ b/drivers/apple_embedded/tts_apple_embedded.mm @@ -1,5 +1,5 @@ /**************************************************************************/ -/* tts_ios.mm */ +/* tts_apple_embedded.mm */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,9 +28,9 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#import "tts_ios.h" +#import "tts_apple_embedded.h" -@implementation TTS_IOS +@implementation GDTTTS - (id)init { self = [super init]; diff --git a/platform/ios/view_controller.h b/drivers/apple_embedded/view_controller.h similarity index 89% rename from platform/ios/view_controller.h rename to drivers/apple_embedded/view_controller.h index b39164ca1a5a..949e3040c906 100644 --- a/platform/ios/view_controller.h +++ b/drivers/apple_embedded/view_controller.h @@ -32,13 +32,12 @@ #import -@class GodotView; -@class GodotNativeVideoView; -@class GodotKeyboardInputView; +@class GDTView; +@class GDTKeyboardInputView; -@interface ViewController : UIViewController +@interface GDTViewController : UIViewController -@property(nonatomic, readonly, strong) GodotView *godotView; -@property(nonatomic, readonly, strong) GodotKeyboardInputView *keyboardView; +@property(nonatomic, readonly, strong) GDTView *godotView; +@property(nonatomic, readonly, strong) GDTKeyboardInputView *keyboardView; @end diff --git a/platform/ios/view_controller.mm b/drivers/apple_embedded/view_controller.mm similarity index 78% rename from platform/ios/view_controller.mm rename to drivers/apple_embedded/view_controller.mm index 787e767109d2..15dc1daf16e2 100644 --- a/platform/ios/view_controller.mm +++ b/drivers/apple_embedded/view_controller.mm @@ -30,44 +30,44 @@ #import "view_controller.h" -#import "display_server_ios.h" -#import "godot_view.h" +#import "display_server_apple_embedded.h" +#import "godot_view_apple_embedded.h" #import "godot_view_renderer.h" -#import "key_mapping_ios.h" +#import "key_mapping_apple_embedded.h" #import "keyboard_input_view.h" -#import "os_ios.h" +#import "os_apple_embedded.h" #include "core/config/project_settings.h" #import #import -@interface ViewController () +@interface GDTViewController () -@property(strong, nonatomic) GodotViewRenderer *renderer; -@property(strong, nonatomic) GodotKeyboardInputView *keyboardView; +@property(strong, nonatomic) GDTViewRenderer *renderer; +@property(strong, nonatomic) GDTKeyboardInputView *keyboardView; @property(strong, nonatomic) UIView *godotLoadingOverlay; @end -@implementation ViewController +@implementation GDTViewController -- (GodotView *)godotView { - return (GodotView *)self.view; +- (GDTView *)godotView { + return (GDTView *)self.view; } - (void)pressesBegan:(NSSet *)presses withEvent:(UIPressesEvent *)event { [super pressesBegan:presses withEvent:event]; - if (!DisplayServerIOS::get_singleton() || DisplayServerIOS::get_singleton()->is_keyboard_active()) { + if (!DisplayServerAppleEmbedded::get_singleton() || DisplayServerAppleEmbedded::get_singleton()->is_keyboard_active()) { return; } if (@available(iOS 13.4, *)) { for (UIPress *press in presses) { String u32lbl = String::utf8([press.key.charactersIgnoringModifiers UTF8String]); String u32text = String::utf8([press.key.characters UTF8String]); - Key key = KeyMappingIOS::remap_key(press.key.keyCode); + Key key = KeyMappingAppleEmbedded::remap_key(press.key.keyCode); if (press.key.keyCode == 0 && u32text.is_empty() && u32lbl.is_empty()) { continue; @@ -78,15 +78,15 @@ - (void)pressesBegan:(NSSet *)presses withEvent:(UIPressesEvent *)eve us = u32lbl[0]; } - KeyLocation location = KeyMappingIOS::key_location(press.key.keyCode); + KeyLocation location = KeyMappingAppleEmbedded::key_location(press.key.keyCode); if (!u32text.is_empty() && !u32text.begins_with("UIKey")) { for (int i = 0; i < u32text.length(); i++) { const char32_t c = u32text[i]; - DisplayServerIOS::get_singleton()->key(fix_keycode(us, key), c, fix_key_label(us, key), key, press.key.modifierFlags, true, location); + DisplayServerAppleEmbedded::get_singleton()->key(fix_keycode(us, key), c, fix_key_label(us, key), key, press.key.modifierFlags, true, location); } } else { - DisplayServerIOS::get_singleton()->key(fix_keycode(us, key), 0, fix_key_label(us, key), key, press.key.modifierFlags, true, location); + DisplayServerAppleEmbedded::get_singleton()->key(fix_keycode(us, key), 0, fix_key_label(us, key), key, press.key.modifierFlags, true, location); } } } @@ -95,13 +95,13 @@ - (void)pressesBegan:(NSSet *)presses withEvent:(UIPressesEvent *)eve - (void)pressesEnded:(NSSet *)presses withEvent:(UIPressesEvent *)event { [super pressesEnded:presses withEvent:event]; - if (!DisplayServerIOS::get_singleton() || DisplayServerIOS::get_singleton()->is_keyboard_active()) { + if (!DisplayServerAppleEmbedded::get_singleton() || DisplayServerAppleEmbedded::get_singleton()->is_keyboard_active()) { return; } if (@available(iOS 13.4, *)) { for (UIPress *press in presses) { String u32lbl = String::utf8([press.key.charactersIgnoringModifiers UTF8String]); - Key key = KeyMappingIOS::remap_key(press.key.keyCode); + Key key = KeyMappingAppleEmbedded::remap_key(press.key.keyCode); if (press.key.keyCode == 0 && u32lbl.is_empty()) { continue; @@ -112,16 +112,16 @@ - (void)pressesEnded:(NSSet *)presses withEvent:(UIPressesEvent *)eve us = u32lbl[0]; } - KeyLocation location = KeyMappingIOS::key_location(press.key.keyCode); + KeyLocation location = KeyMappingAppleEmbedded::key_location(press.key.keyCode); - DisplayServerIOS::get_singleton()->key(fix_keycode(us, key), 0, fix_key_label(us, key), key, press.key.modifierFlags, false, location); + DisplayServerAppleEmbedded::get_singleton()->key(fix_keycode(us, key), 0, fix_key_label(us, key), key, press.key.modifierFlags, false, location); } } } - (void)loadView { - GodotView *view = [[GodotView alloc] init]; - GodotViewRenderer *renderer = [[GodotViewRenderer alloc] init]; + GDTView *view = GDTViewCreate(); + GDTViewRenderer *renderer = [[GDTViewRenderer alloc] init]; self.renderer = renderer; self.view = view; @@ -170,7 +170,7 @@ - (void)viewDidLoad { - (void)observeKeyboard { print_verbose("Setting up keyboard input view."); - self.keyboardView = [GodotKeyboardInputView new]; + self.keyboardView = [GDTKeyboardInputView new]; [self.view addSubview:self.keyboardView]; print_verbose("Adding observer for keyboard show/hide."); @@ -187,6 +187,7 @@ - (void)observeKeyboard { } - (void)displayLoadingOverlay { +#if !defined(VISIONOS_ENABLED) NSBundle *bundle = [NSBundle mainBundle]; NSString *storyboardName = @"Launch Screen"; @@ -202,9 +203,10 @@ - (void)displayLoadingOverlay { self.godotLoadingOverlay.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; [self.view addSubview:self.godotLoadingOverlay]; +#endif } -- (BOOL)godotViewFinishedSetup:(GodotView *)view { +- (BOOL)godotViewFinishedSetup:(GDTView *)view { [self.godotLoadingOverlay removeFromSuperview]; self.godotLoadingOverlay = nil; @@ -235,11 +237,11 @@ - (UIRectEdge)preferredScreenEdgesDeferringSystemGestures { } - (BOOL)shouldAutorotate { - if (!DisplayServerIOS::get_singleton()) { + if (!DisplayServerAppleEmbedded::get_singleton()) { return NO; } - switch (DisplayServerIOS::get_singleton()->screen_get_orientation(DisplayServer::SCREEN_OF_MAIN_WINDOW)) { + switch (DisplayServerAppleEmbedded::get_singleton()->screen_get_orientation(DisplayServer::SCREEN_OF_MAIN_WINDOW)) { case DisplayServer::SCREEN_SENSOR: case DisplayServer::SCREEN_SENSOR_LANDSCAPE: case DisplayServer::SCREEN_SENSOR_PORTRAIT: @@ -250,11 +252,11 @@ - (BOOL)shouldAutorotate { } - (UIInterfaceOrientationMask)supportedInterfaceOrientations { - if (!DisplayServerIOS::get_singleton()) { + if (!DisplayServerAppleEmbedded::get_singleton()) { return UIInterfaceOrientationMaskAll; } - switch (DisplayServerIOS::get_singleton()->screen_get_orientation(DisplayServer::SCREEN_OF_MAIN_WINDOW)) { + switch (DisplayServerAppleEmbedded::get_singleton()->screen_get_orientation(DisplayServer::SCREEN_OF_MAIN_WINDOW)) { case DisplayServer::SCREEN_PORTRAIT: return UIInterfaceOrientationMaskPortrait; case DisplayServer::SCREEN_REVERSE_LANDSCAPE: @@ -305,14 +307,14 @@ - (void)keyboardOnScreen:(NSNotification *)notification { CGRect rawFrame = [value CGRectValue]; CGRect keyboardFrame = [self.view convertRect:rawFrame fromView:nil]; - if (DisplayServerIOS::get_singleton()) { - DisplayServerIOS::get_singleton()->virtual_keyboard_set_height(keyboardFrame.size.height); + if (DisplayServerAppleEmbedded::get_singleton()) { + DisplayServerAppleEmbedded::get_singleton()->virtual_keyboard_set_height(keyboardFrame.size.height); } } - (void)keyboardHidden:(NSNotification *)notification { - if (DisplayServerIOS::get_singleton()) { - DisplayServerIOS::get_singleton()->virtual_keyboard_set_height(0); + if (DisplayServerAppleEmbedded::get_singleton()) { + DisplayServerAppleEmbedded::get_singleton()->virtual_keyboard_set_height(0); } } diff --git a/drivers/coremidi/midi_driver_coremidi.mm b/drivers/coremidi/midi_driver_coremidi.mm index b280d20960ab..9c83556177f7 100644 --- a/drivers/coremidi/midi_driver_coremidi.mm +++ b/drivers/coremidi/midi_driver_coremidi.mm @@ -34,7 +34,6 @@ #include "core/string/print_string.h" -#import #import Mutex MIDIDriverCoreMidi::mutex; diff --git a/drivers/metal/SCsub b/drivers/metal/SCsub index ced6dfbbfdcf..a4c1c65b8297 100644 --- a/drivers/metal/SCsub +++ b/drivers/metal/SCsub @@ -39,6 +39,9 @@ if "-std=gnu++17" in env_metal["CXXFLAGS"]: env_metal["CXXFLAGS"].remove("-std=gnu++17") env_metal.Append(CXXFLAGS=["-std=c++20"]) +# Enable module support +env_metal.Append(CCFLAGS=["-fmodules", "-fcxx-modules"]) + # Driver source files driver_obj = [] diff --git a/drivers/metal/metal_device_properties.mm b/drivers/metal/metal_device_properties.mm index 713f69e41dfe..1d3b78a1b69f 100644 --- a/drivers/metal/metal_device_properties.mm +++ b/drivers/metal/metal_device_properties.mm @@ -50,6 +50,8 @@ #import "metal_device_properties.h" +#include "servers/rendering/renderer_rd/effects/metal_fx.h" + #import #import #import @@ -63,7 +65,7 @@ #define MTLGPUFamilyApple9 (MTLGPUFamily)1009 #endif -API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) +API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0), visionos(1.0)) MTLGPUFamily &operator--(MTLGPUFamily &p_family) { p_family = static_cast(static_cast(p_family) - 1); if (p_family < MTLGPUFamilyApple1) { @@ -126,7 +128,11 @@ if (@available(macOS 13.0, iOS 16.0, tvOS 16.0, *)) { features.metal_fx_spatial = [MTLFXSpatialScalerDescriptor supportsDevice:p_device]; +#ifdef METAL_MFXTEMPORAL_ENABLED features.metal_fx_temporal = [MTLFXTemporalScalerDescriptor supportsDevice:p_device]; +#else + features.metal_fx_temporal = false; +#endif } MTLCompileOptions *opts = [MTLCompileOptions new]; @@ -136,7 +142,7 @@ features.mslVersion = SPIRV_CROSS_NAMESPACE::CompilerMSL::Options::make_msl_version(m_maj, m_min) switch (features.mslVersionEnum) { -#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 150000 || __IPHONE_OS_VERSION_MAX_ALLOWED >= 180000 || __TV_OS_VERSION_MAX_ALLOWED >= 180000 +#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 150000 || __IPHONE_OS_VERSION_MAX_ALLOWED >= 180000 || __TV_OS_VERSION_MAX_ALLOWED >= 180000 || __VISION_OS_VERSION_MAX_ALLOWED >= 20000 case MTLLanguageVersion3_2: setMSLVersion(3, 2); break; @@ -170,7 +176,7 @@ case MTLLanguageVersion1_1: setMSLVersion(1, 1); break; -#if TARGET_OS_IPHONE && !TARGET_OS_MACCATALYST +#if TARGET_OS_IPHONE && !TARGET_OS_MACCATALYST && !TARGET_OS_VISION case MTLLanguageVersion1_0: setMSLVersion(1, 0); break; @@ -324,6 +330,7 @@ limits.maxDrawIndexedIndexValue = std::numeric_limits::max() - 1; +#ifdef METAL_MFXTEMPORAL_ENABLED if (@available(macOS 14.0, iOS 17.0, tvOS 17.0, *)) { limits.temporalScalerInputContentMinScale = (double)[MTLFXTemporalScalerDescriptor supportedInputContentMinScaleForDevice:p_device]; limits.temporalScalerInputContentMaxScale = (double)[MTLFXTemporalScalerDescriptor supportedInputContentMaxScaleForDevice:p_device]; @@ -332,6 +339,11 @@ limits.temporalScalerInputContentMinScale = 1.0; limits.temporalScalerInputContentMaxScale = 3.0; } +#else + // Defaults taken from macOS 14+ + limits.temporalScalerInputContentMinScale = 1.0; + limits.temporalScalerInputContentMaxScale = 3.0; +#endif } MetalDeviceProperties::MetalDeviceProperties(id p_device) { diff --git a/drivers/metal/pixel_formats.mm b/drivers/metal/pixel_formats.mm index 86924c6530ac..9ce9cdda733a 100644 --- a/drivers/metal/pixel_formats.mm +++ b/drivers/metal/pixel_formats.mm @@ -114,6 +114,9 @@ void clear(T *p_val, size_t p_count = 1) { } bool PixelFormats::isPVRTCFormat(MTLPixelFormat p_format) { +#if defined(VISIONOS_ENABLED) + return false; +#else switch (p_format) { case MTLPixelFormatPVRTC_RGBA_2BPP: case MTLPixelFormatPVRTC_RGBA_2BPP_sRGB: @@ -127,6 +130,7 @@ void clear(T *p_val, size_t p_count = 1) { default: return false; } +#endif } MTLFormatType PixelFormats::getFormatType(DataFormat p_format) { @@ -668,11 +672,13 @@ void clear(T *p_val, size_t p_count = 1) { addMTLPixelFormatDesc(RGBA32Sint, Color128, RWC); addMTLPixelFormatDesc(RGBA32Float, Color128, All); +#if !defined(VISIONOS_ENABLED) // Compressed pixel formats addMTLPixelFormatDesc(PVRTC_RGBA_2BPP, PVRTC_RGBA_2BPP, RF); addMTLPixelFormatDescSRGB(PVRTC_RGBA_2BPP_sRGB, PVRTC_RGBA_2BPP, RF, PVRTC_RGBA_2BPP); addMTLPixelFormatDesc(PVRTC_RGBA_4BPP, PVRTC_RGBA_4BPP, RF); addMTLPixelFormatDescSRGB(PVRTC_RGBA_4BPP_sRGB, PVRTC_RGBA_4BPP, RF, PVRTC_RGBA_4BPP); +#endif addMTLPixelFormatDesc(ETC2_RGB8, ETC2_RGB8, RF); addMTLPixelFormatDescSRGB(ETC2_RGB8_sRGB, ETC2_RGB8, RF, ETC2_RGB8); diff --git a/drivers/metal/rendering_device_driver_metal.mm b/drivers/metal/rendering_device_driver_metal.mm index 53af8e49c5ee..3d61c63c2db8 100644 --- a/drivers/metal/rendering_device_driver_metal.mm +++ b/drivers/metal/rendering_device_driver_metal.mm @@ -290,7 +290,11 @@ _FORCE_INLINE_ MTLSize mipmapLevelSizeFromSize(MTLSize p_size, NSUInteger p_leve // Usage. MTLResourceOptions options = 0; +#if defined(VISIONOS_ENABLED) + const bool supports_memoryless = true; +#else const bool supports_memoryless = (*device_properties).features.highestFamily >= MTLGPUFamilyApple2 && (*device_properties).features.highestFamily < MTLGPUFamilyMac1; +#endif if (supports_memoryless && p_format.usage_bits & TEXTURE_USAGE_TRANSIENT_BIT) { options = MTLResourceStorageModeMemoryless | MTLResourceHazardTrackingModeTracked; desc.storageMode = MTLStorageModeMemoryless; @@ -2493,7 +2497,11 @@ void deserialize(BufReader &p_reader) { cd->name = binary_data.shader_name; cd->stage = shader_data.stage; options.preserveInvariance = shader_data.is_position_invariant; +#if defined(VISIONOS_ENABLED) + options.mathMode = MTLMathModeFast; +#else options.fastMathEnabled = YES; +#endif MDLibrary *library = [MDLibrary newLibraryWithCacheEntry:cd device:device source:source @@ -4184,8 +4192,8 @@ bool isArrayTexture(MTLTextureType p_type) { error_string += "- No support for image cube arrays.\n"; } -#if defined(IOS_ENABLED) - // iOS platform ports currently don't exit themselves when this method returns `ERR_CANT_CREATE`. +#if defined(APPLE_EMBEDDED_ENABLED) + // Apple Embedded platforms exports currently don't exit themselves when this method returns `ERR_CANT_CREATE`. OS::get_singleton()->alert(error_string + "\nClick OK to exit (black screen will be visible)."); #else OS::get_singleton()->alert(error_string + "\nClick OK to exit."); diff --git a/editor/export/editor_export_platform_apple_embedded.cpp b/editor/export/editor_export_platform_apple_embedded.cpp new file mode 100644 index 000000000000..02e8b86609bd --- /dev/null +++ b/editor/export/editor_export_platform_apple_embedded.cpp @@ -0,0 +1,2757 @@ +/**************************************************************************/ +/* editor_export_platform_apple_embedded.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 "editor_export_platform_apple_embedded.h" + +#include "core/io/json.h" +#include "core/io/plist.h" +#include "core/string/translation.h" +#include "editor/editor_node.h" +#include "editor/editor_paths.h" +#include "editor/editor_string_names.h" +#include "editor/export/editor_export.h" +#include "editor/export/lipo.h" +#include "editor/export/macho.h" +#include "editor/import/resource_importer_texture_settings.h" +#include "editor/plugins/script_editor_plugin.h" +#include "editor/themes/editor_scale.h" + +#include "modules/modules_enabled.gen.h" // For mono. +#include "modules/svg/image_loader_svg.h" + +void EditorExportPlatformAppleEmbedded::get_preset_features(const Ref &p_preset, List *r_features) const { + // Vulkan and OpenGL ES 3.0 both mandate ETC2 support. + r_features->push_back("etc2"); + r_features->push_back("astc"); + + Vector architectures = _get_preset_architectures(p_preset); + for (int i = 0; i < architectures.size(); ++i) { + r_features->push_back(architectures[i]); + } +} + +Vector EditorExportPlatformAppleEmbedded::_get_supported_architectures() const { + Vector archs; + archs.push_back(ExportArchitecture("arm64", true)); + return archs; +} + +struct APIAccessInfo { + String prop_name; + String type_name; + Vector prop_flag_value; + Vector prop_flag_name; + int default_value; +}; + +static const APIAccessInfo api_info[] = { + { "file_timestamp", + "NSPrivacyAccessedAPICategoryFileTimestamp", + { "DDA9.1", "C617.1", "3B52.1" }, + { "Display to user on-device:", "Inside app or group container", "Files provided to app by user" }, + 3 }, + { "system_boot_time", + "NSPrivacyAccessedAPICategorySystemBootTime", + { "35F9.1", "8FFB.1", "3D61.1" }, + { "Measure time on-device", "Calculate absolute event timestamps", "User-initiated bug report" }, + 1 }, + { "disk_space", + "NSPrivacyAccessedAPICategoryDiskSpace", + { "E174.1", "85F4.1", "7D9E.1", "B728.1" }, + { "Write or delete file on-device", "Display to user on-device", "User-initiated bug report", "Health research app" }, + 3 }, + { "active_keyboard", + "NSPrivacyAccessedAPICategoryActiveKeyboards", + { "3EC4.1", "54BD.1" }, + { "Custom keyboard app on-device", "Customize UI on-device:2" }, + 0 }, + { "user_defaults", + "NSPrivacyAccessedAPICategoryUserDefaults", + { "1C8F.1", "AC6B.1", "CA92.1" }, + { "Access info from same App Group", "Access managed app configuration", "Access info from same app" }, + 0 }, +}; + +struct DataCollectionInfo { + String prop_name; + String type_name; +}; + +static const DataCollectionInfo data_collect_type_info[] = { + { "name", "NSPrivacyCollectedDataTypeName" }, + { "email_address", "NSPrivacyCollectedDataTypeEmailAddress" }, + { "phone_number", "NSPrivacyCollectedDataTypePhoneNumber" }, + { "physical_address", "NSPrivacyCollectedDataTypePhysicalAddress" }, + { "other_contact_info", "NSPrivacyCollectedDataTypeOtherUserContactInfo" }, + { "health", "NSPrivacyCollectedDataTypeHealth" }, + { "fitness", "NSPrivacyCollectedDataTypeFitness" }, + { "payment_info", "NSPrivacyCollectedDataTypePaymentInfo" }, + { "credit_info", "NSPrivacyCollectedDataTypeCreditInfo" }, + { "other_financial_info", "NSPrivacyCollectedDataTypeOtherFinancialInfo" }, + { "precise_location", "NSPrivacyCollectedDataTypePreciseLocation" }, + { "coarse_location", "NSPrivacyCollectedDataTypeCoarseLocation" }, + { "sensitive_info", "NSPrivacyCollectedDataTypeSensitiveInfo" }, + { "contacts", "NSPrivacyCollectedDataTypeContacts" }, + { "emails_or_text_messages", "NSPrivacyCollectedDataTypeEmailsOrTextMessages" }, + { "photos_or_videos", "NSPrivacyCollectedDataTypePhotosorVideos" }, + { "audio_data", "NSPrivacyCollectedDataTypeAudioData" }, + { "gameplay_content", "NSPrivacyCollectedDataTypeGameplayContent" }, + { "customer_support", "NSPrivacyCollectedDataTypeCustomerSupport" }, + { "other_user_content", "NSPrivacyCollectedDataTypeOtherUserContent" }, + { "browsing_history", "NSPrivacyCollectedDataTypeBrowsingHistory" }, + { "search_hhistory", "NSPrivacyCollectedDataTypeSearchHistory" }, + { "user_id", "NSPrivacyCollectedDataTypeUserID" }, + { "device_id", "NSPrivacyCollectedDataTypeDeviceID" }, + { "purchase_history", "NSPrivacyCollectedDataTypePurchaseHistory" }, + { "product_interaction", "NSPrivacyCollectedDataTypeProductInteraction" }, + { "advertising_data", "NSPrivacyCollectedDataTypeAdvertisingData" }, + { "other_usage_data", "NSPrivacyCollectedDataTypeOtherUsageData" }, + { "crash_data", "NSPrivacyCollectedDataTypeCrashData" }, + { "performance_data", "NSPrivacyCollectedDataTypePerformanceData" }, + { "other_diagnostic_data", "NSPrivacyCollectedDataTypeOtherDiagnosticData" }, + { "environment_scanning", "NSPrivacyCollectedDataTypeEnvironmentScanning" }, + { "hands", "NSPrivacyCollectedDataTypeHands" }, + { "head", "NSPrivacyCollectedDataTypeHead" }, + { "other_data_types", "NSPrivacyCollectedDataTypeOtherDataTypes" }, +}; + +static const DataCollectionInfo data_collect_purpose_info[] = { + { "Analytics", "NSPrivacyCollectedDataTypePurposeAnalytics" }, + { "App Functionality", "NSPrivacyCollectedDataTypePurposeAppFunctionality" }, + { "Developer Advertising", "NSPrivacyCollectedDataTypePurposeDeveloperAdvertising" }, + { "Third-party Advertising", "NSPrivacyCollectedDataTypePurposeThirdPartyAdvertising" }, + { "Product Personalization", "NSPrivacyCollectedDataTypePurposeProductPersonalization" }, + { "Other", "NSPrivacyCollectedDataTypePurposeOther" }, +}; + +static const String export_method_string[] = { + "app-store", + "development", + "ad-hoc", + "enterprise", +}; + +String EditorExportPlatformAppleEmbedded::get_export_option_warning(const EditorExportPreset *p_preset, const StringName &p_name) const { + if (p_preset) { + if (p_name == "application/app_store_team_id") { + String team_id = p_preset->get("application/app_store_team_id"); + if (team_id.is_empty()) { + return TTR("App Store Team ID not specified.") + "\n"; + } + } else if (p_name == "application/bundle_identifier") { + String identifier = p_preset->get("application/bundle_identifier"); + String pn_err; + if (!is_package_name_valid(identifier, &pn_err)) { + return TTR("Invalid Identifier:") + " " + pn_err; + } + } else if (p_name == "privacy/file_timestamp_access_reasons") { + int access = p_preset->get("privacy/file_timestamp_access_reasons"); + if (access == 0) { + return TTR("At least one file timestamp access reason should be selected."); + } + } else if (p_name == "privacy/disk_space_access_reasons") { + int access = p_preset->get("privacy/disk_space_access_reasons"); + if (access == 0) { + return TTR("At least one disk space access reason should be selected."); + } + } else if (p_name == "privacy/system_boot_time_access_reasons") { + int access = p_preset->get("privacy/system_boot_time_access_reasons"); + if (access == 0) { + return TTR("At least one system boot time access reason should be selected."); + } + } + } + return String(); +} + +void EditorExportPlatformAppleEmbedded::_notification(int p_what) { +#ifdef MACOS_ENABLED + if (p_what == NOTIFICATION_POSTINITIALIZE) { + if (EditorExport::get_singleton()) { + EditorExport::get_singleton()->connect_presets_runnable_updated(callable_mp(this, &EditorExportPlatformAppleEmbedded::_update_preset_status)); + } + } +#endif +} + +bool EditorExportPlatformAppleEmbedded::get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const { + // Hide unsupported .NET embedding option. + if (p_option == "dotnet/embed_build_outputs") { + return false; + } + + if (p_preset == nullptr) { + return true; + } + + bool advanced_options_enabled = p_preset->are_advanced_options_enabled(); + if (p_option.begins_with("privacy") || + (p_option.begins_with("icons/") && !p_option.begins_with("icons/icon") && !p_option.begins_with("icons/app_store")) || + p_option == "custom_template/debug" || + p_option == "custom_template/release" || + p_option == "application/additional_plist_content" || + p_option == "application/delete_old_export_files_unconditionally" || + p_option == "application/icon_interpolation" || + p_option == "application/signature") { + return advanced_options_enabled; + } + + return true; +} + +void EditorExportPlatformAppleEmbedded::get_export_options(List *r_options) const { + 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"), "")); + + Vector architectures = _get_supported_architectures(); + for (int i = 0; i < architectures.size(); ++i) { + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, vformat("%s/%s", PNAME("architectures"), architectures[i].name)), architectures[i].is_default)); + } + + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/app_store_team_id"), "", false, true)); + + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/export_method_debug", PROPERTY_HINT_ENUM, "App Store,Development,Ad-Hoc,Enterprise"), 1)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/code_sign_identity_debug", PROPERTY_HINT_PLACEHOLDER_TEXT, "Apple Development"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/code_sign_identity_release", PROPERTY_HINT_PLACEHOLDER_TEXT, "Apple Distribution"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/provisioning_profile_uuid_debug", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/provisioning_profile_uuid_release", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/provisioning_profile_specifier_debug", PROPERTY_HINT_PLACEHOLDER_TEXT, ""), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/provisioning_profile_specifier_release", PROPERTY_HINT_PLACEHOLDER_TEXT, ""), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/export_method_release", PROPERTY_HINT_ENUM, "App Store,Development,Ad-Hoc,Enterprise"), 0)); + + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/bundle_identifier", PROPERTY_HINT_PLACEHOLDER_TEXT, "com.example.game"), "", false, true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/signature"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/short_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "Leave empty to use project version"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/version", PROPERTY_HINT_PLACEHOLDER_TEXT, "Leave empty to use project version"), "")); + + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/additional_plist_content", PROPERTY_HINT_MULTILINE_TEXT), "")); + + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/icon_interpolation", PROPERTY_HINT_ENUM, "Nearest neighbor,Bilinear,Cubic,Trilinear,Lanczos"), 4)); + + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "application/export_project_only"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "application/delete_old_export_files_unconditionally"), false)); + + Vector found_plugins = get_plugins(get_platform_name()); + for (int i = 0; i < found_plugins.size(); i++) { + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, vformat("%s/%s", PNAME("plugins"), found_plugins[i].name)), false)); + } + + HashSet plist_keys; + + for (int i = 0; i < found_plugins.size(); i++) { + // Editable plugin plist values + PluginConfigAppleEmbedded plugin = found_plugins[i]; + + for (const KeyValue &E : plugin.plist) { + switch (E.value.type) { + case PluginConfigAppleEmbedded::PlistItemType::STRING_INPUT: { + String preset_name = "plugins_plist/" + E.key; + if (!plist_keys.has(preset_name)) { + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, preset_name), E.value.value)); + plist_keys.insert(preset_name); + } + } break; + default: + continue; + } + } + } + + plugins_changed.clear(); + plugins = found_plugins; + + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "entitlements/increased_memory_limit"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "entitlements/game_center"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "entitlements/push_notifications", PROPERTY_HINT_ENUM, "Disabled,Production,Development"), "Disabled")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "entitlements/additional", PROPERTY_HINT_MULTILINE_TEXT), "")); + + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/access_wifi"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/performance_gaming_tier"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/performance_a12"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "capabilities/additional"), PackedStringArray())); + + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "user_data/accessible_from_files_app"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "user_data/accessible_from_itunes_sharing"), false)); + + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/camera_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the camera"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/camera_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/microphone_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the microphone"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/microphone_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/photolibrary_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need access to the photo library"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/photolibrary_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); + + for (uint64_t i = 0; i < std::size(api_info); ++i) { + String prop_name = vformat("privacy/%s_access_reasons", api_info[i].prop_name); + String hint; + for (int j = 0; j < api_info[i].prop_flag_value.size(); j++) { + if (j != 0) { + hint += ","; + } + hint += vformat("%s - %s:%d", api_info[i].prop_flag_value[j], api_info[i].prop_flag_name[j], (1 << j)); + } + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, prop_name, PROPERTY_HINT_FLAGS, hint), api_info[i].default_value)); + } + + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "privacy/tracking_enabled"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "privacy/tracking_domains"), Vector())); + + { + String hint; + for (uint64_t i = 0; i < std::size(data_collect_purpose_info); ++i) { + if (i != 0) { + hint += ","; + } + hint += vformat("%s:%d", data_collect_purpose_info[i].prop_name, (1 << i)); + } + for (uint64_t i = 0; i < std::size(data_collect_type_info); ++i) { + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, vformat("privacy/collected_data/%s/collected", data_collect_type_info[i].prop_name)), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, vformat("privacy/collected_data/%s/linked_to_user", data_collect_type_info[i].prop_name)), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, vformat("privacy/collected_data/%s/used_for_tracking", data_collect_type_info[i].prop_name)), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, vformat("privacy/collected_data/%s/collection_purposes", data_collect_type_info[i].prop_name), PROPERTY_HINT_FLAGS, hint), 0)); + } + } + + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/icon_1024x1024", PROPERTY_HINT_FILE, "*.svg,*.png,*.webp,*.jpg,*.jpeg"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/icon_1024x1024_dark", PROPERTY_HINT_FILE, "*.svg,*.png,*.webp,*.jpg,*.jpeg"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/icon_1024x1024_tinted", PROPERTY_HINT_FILE, "*.svg,*.png,*.webp,*.jpg,*.jpeg"), "")); + + HashSet used_names; + + Vector icon_infos = get_icon_infos(); + for (int i = 0; i < icon_infos.size(); ++i) { + if (!used_names.has(icon_infos[i].preset_key)) { + used_names.insert(icon_infos[i].preset_key); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, String(icon_infos[i].preset_key), PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, String(icon_infos[i].preset_key) + "_dark", PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, String(icon_infos[i].preset_key) + "_tinted", PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), "")); + } + } +} + +void EditorExportPlatformAppleEmbedded::_fix_config_file(const Ref &p_preset, Vector &pfile, const AppleEmbeddedConfigData &p_config, bool p_debug) { + String dbg_sign_id = p_preset->get("application/code_sign_identity_debug").operator String().is_empty() ? "Apple Development" : p_preset->get("application/code_sign_identity_debug"); + String rel_sign_id = p_preset->get("application/code_sign_identity_release").operator String().is_empty() ? "Apple Distribution" : p_preset->get("application/code_sign_identity_release"); + bool dbg_manual = !p_preset->get_or_env("application/provisioning_profile_uuid_debug", ENV_APPLE_PLATFORM_PROFILE_UUID_DEBUG).operator String().is_empty() || (dbg_sign_id != "Apple Development" && dbg_sign_id != "Apple Distribution"); + bool rel_manual = !p_preset->get_or_env("application/provisioning_profile_uuid_release", ENV_APPLE_PLATFORM_PROFILE_UUID_RELEASE).operator String().is_empty() || (rel_sign_id != "Apple Development" && rel_sign_id != "Apple Distribution"); + + String provisioning_profile_specifier_dbg = p_preset->get_or_env("application/provisioning_profile_specifier_debug", ENV_APPLE_PLATFORM_PROFILE_SPECIFIER_DEBUG).operator String(); + bool valid_dbg_specifier = !provisioning_profile_specifier_dbg.is_empty(); + dbg_manual |= valid_dbg_specifier; + + String provisioning_profile_specifier_rel = p_preset->get_or_env("application/provisioning_profile_specifier_release", ENV_APPLE_PLATFORM_PROFILE_SPECIFIER_RELEASE).operator String(); + bool valid_rel_specifier = !provisioning_profile_specifier_rel.is_empty(); + rel_manual |= valid_rel_specifier; + + String str = String::utf8((const char *)pfile.ptr(), pfile.size()); + String strnew; + Vector lines = str.split("\n"); + for (int i = 0; i < lines.size(); i++) { + if (lines[i].contains("$binary")) { + strnew += lines[i].replace("$binary", p_config.binary_name) + "\n"; + } else if (lines[i].contains("$modules_buildfile")) { + strnew += lines[i].replace("$modules_buildfile", p_config.modules_buildfile) + "\n"; + } else if (lines[i].contains("$modules_fileref")) { + strnew += lines[i].replace("$modules_fileref", p_config.modules_fileref) + "\n"; + } else if (lines[i].contains("$modules_buildphase")) { + strnew += lines[i].replace("$modules_buildphase", p_config.modules_buildphase) + "\n"; + } else if (lines[i].contains("$modules_buildgrp")) { + strnew += lines[i].replace("$modules_buildgrp", p_config.modules_buildgrp) + "\n"; + } else if (lines[i].contains("$name")) { + strnew += lines[i].replace("$name", p_config.pkg_name) + "\n"; + } else if (lines[i].contains("$bundle_identifier")) { + strnew += lines[i].replace("$bundle_identifier", p_preset->get("application/bundle_identifier")) + "\n"; + } else if (lines[i].contains("$short_version")) { + strnew += lines[i].replace("$short_version", p_preset->get_version("application/short_version")) + "\n"; + } else if (lines[i].contains("$version")) { + strnew += lines[i].replace("$version", p_preset->get_version("application/version")) + "\n"; + } else if (lines[i].contains("$min_version")) { + strnew += lines[i].replace("$min_version", + p_preset->get("application/min_" + get_platform_name() + "_version")) + + "\n"; + } else if (lines[i].contains("$signature")) { + strnew += lines[i].replace("$signature", p_preset->get("application/signature")) + "\n"; + } else if (lines[i].contains("$team_id")) { + strnew += lines[i].replace("$team_id", p_preset->get("application/app_store_team_id")) + "\n"; + } else if (lines[i].contains("$default_build_config")) { + strnew += lines[i].replace("$default_build_config", p_debug ? "Debug" : "Release") + "\n"; + } else if (lines[i].contains("$export_method")) { + int export_method = p_preset->get(p_debug ? "application/export_method_debug" : "application/export_method_release"); + strnew += lines[i].replace("$export_method", export_method_string[export_method]) + "\n"; + } else if (lines[i].contains("$provisioning_profile_specifier_debug")) { + strnew += lines[i].replace("$provisioning_profile_specifier_debug", provisioning_profile_specifier_dbg) + "\n"; + } else if (lines[i].contains("$provisioning_profile_specifier_release")) { + strnew += lines[i].replace("$provisioning_profile_specifier_release", provisioning_profile_specifier_rel) + "\n"; + } else if (lines[i].contains("$provisioning_profile_specifier")) { + String specifier = p_debug ? provisioning_profile_specifier_dbg : provisioning_profile_specifier_rel; + strnew += lines[i].replace("$provisioning_profile_specifier", specifier) + "\n"; + } else if (lines[i].contains("$provisioning_profile_uuid_release")) { + strnew += lines[i].replace("$provisioning_profile_uuid_release", p_preset->get_or_env("application/provisioning_profile_uuid_release", ENV_APPLE_PLATFORM_PROFILE_UUID_RELEASE)) + "\n"; + } else if (lines[i].contains("$provisioning_profile_uuid_debug")) { + strnew += lines[i].replace("$provisioning_profile_uuid_debug", p_preset->get_or_env("application/provisioning_profile_uuid_debug", ENV_APPLE_PLATFORM_PROFILE_UUID_DEBUG)) + "\n"; + } else if (lines[i].contains("$code_sign_style_debug")) { + if (dbg_manual) { + strnew += lines[i].replace("$code_sign_style_debug", "Manual") + "\n"; + } else { + strnew += lines[i].replace("$code_sign_style_debug", "Automatic") + "\n"; + } + } else if (lines[i].contains("$code_sign_style_release")) { + if (rel_manual) { + strnew += lines[i].replace("$code_sign_style_release", "Manual") + "\n"; + } else { + strnew += lines[i].replace("$code_sign_style_release", "Automatic") + "\n"; + } + } else if (lines[i].contains("$provisioning_profile_uuid")) { + String uuid = p_debug ? p_preset->get_or_env("application/provisioning_profile_uuid_debug", ENV_APPLE_PLATFORM_PROFILE_UUID_DEBUG) : p_preset->get_or_env("application/provisioning_profile_uuid_release", ENV_APPLE_PLATFORM_PROFILE_UUID_RELEASE); + if (uuid.is_empty()) { + Variant variant = p_debug ? provisioning_profile_specifier_dbg : provisioning_profile_specifier_rel; + bool valid = p_debug ? valid_dbg_specifier : valid_rel_specifier; + uuid = valid ? variant : ""; + } + strnew += lines[i].replace("$provisioning_profile_uuid", uuid) + "\n"; + } else if (lines[i].contains("$code_sign_identity_debug")) { + strnew += lines[i].replace("$code_sign_identity_debug", dbg_sign_id) + "\n"; + } else if (lines[i].contains("$code_sign_identity_release")) { + strnew += lines[i].replace("$code_sign_identity_release", rel_sign_id) + "\n"; + } else if (lines[i].contains("$additional_plist_content")) { + strnew += lines[i].replace("$additional_plist_content", p_config.plist_content) + "\n"; + } else if (lines[i].contains("$godot_archs")) { + strnew += lines[i].replace("$godot_archs", p_config.architectures) + "\n"; + } else if (lines[i].contains("$linker_flags")) { + strnew += lines[i].replace("$linker_flags", p_config.linker_flags) + "\n"; + } else if (lines[i].contains("$targeted_device_family")) { + String xcode_value; + switch ((int)p_preset->get("application/targeted_device_family")) { + case 0: // iPhone + xcode_value = "1"; + break; + case 1: // iPad + xcode_value = "2"; + break; + case 2: // iPhone & iPad + xcode_value = "1,2"; + break; + } + strnew += lines[i].replace("$targeted_device_family", xcode_value) + "\n"; + } else if (lines[i].contains("$cpp_code")) { + strnew += lines[i].replace("$cpp_code", p_config.cpp_code) + "\n"; + } else if (lines[i].contains("$docs_in_place")) { + strnew += lines[i].replace("$docs_in_place", ((bool)p_preset->get("user_data/accessible_from_files_app")) ? "" : "") + "\n"; + } else if (lines[i].contains("$docs_sharing")) { + strnew += lines[i].replace("$docs_sharing", ((bool)p_preset->get("user_data/accessible_from_itunes_sharing")) ? "" : "") + "\n"; + } else if (lines[i].contains("$entitlements_full")) { + String entitlements; + if ((String)p_preset->get("entitlements/push_notifications") != "Disabled") { + entitlements += "aps-environment\n" + p_preset->get("entitlements/push_notifications").operator String().to_lower() + "" + "\n"; + } + if ((bool)p_preset->get("entitlements/game_center")) { + entitlements += "com.apple.developer.game-center\n\n"; + } + if ((bool)p_preset->get("entitlements/increased_memory_limit")) { + entitlements += "com.apple.developer.kernel.increased-memory-limit\n\n"; + } + entitlements += p_preset->get("entitlements/additional").operator String() + "\n"; + + strnew += lines[i].replace("$entitlements_full", entitlements); + } else if (lines[i].contains("$required_device_capabilities")) { + String capabilities; + + // I've removed armv7 as we can run on 64bit only devices + // Note that capabilities listed here are requirements for the app to be installed. + // They don't enable anything. + Vector capabilities_list = p_config.capabilities; + + if ((bool)p_preset->get("capabilities/access_wifi") && !capabilities_list.has("wifi")) { + capabilities_list.push_back("wifi"); + } + if ((bool)p_preset->get("capabilities/performance_gaming_tier") && !capabilities_list.has("iphone-performance-gaming-tier")) { + capabilities_list.push_back("iphone-performance-gaming-tier"); + } + if ((bool)p_preset->get("capabilities/performance_a12") && !capabilities_list.has("iphone-ipad-minimum-performance-a12")) { + capabilities_list.push_back("iphone-ipad-minimum-performance-a12"); + } + for (int idx = 0; idx < capabilities_list.size(); idx++) { + capabilities += "" + capabilities_list[idx] + "\n"; + } + for (const String &cap : p_preset->get("capabilities/additional").operator PackedStringArray()) { + capabilities += "" + cap + "\n"; + } + + strnew += lines[i].replace("$required_device_capabilities", capabilities); + } else if (lines[i].contains("$interface_orientations")) { + String orientations; + const DisplayServer::ScreenOrientation screen_orientation = + DisplayServer::ScreenOrientation(int(get_project_setting(p_preset, "display/window/handheld/orientation"))); + + switch (screen_orientation) { + case DisplayServer::SCREEN_LANDSCAPE: + orientations += "UIInterfaceOrientationLandscapeLeft\n"; + break; + case DisplayServer::SCREEN_PORTRAIT: + orientations += "UIInterfaceOrientationPortrait\n"; + break; + case DisplayServer::SCREEN_REVERSE_LANDSCAPE: + orientations += "UIInterfaceOrientationLandscapeRight\n"; + break; + case DisplayServer::SCREEN_REVERSE_PORTRAIT: + orientations += "UIInterfaceOrientationPortraitUpsideDown\n"; + break; + case DisplayServer::SCREEN_SENSOR_LANDSCAPE: + // Allow both landscape orientations depending on sensor direction. + orientations += "UIInterfaceOrientationLandscapeLeft\n"; + orientations += "UIInterfaceOrientationLandscapeRight\n"; + break; + case DisplayServer::SCREEN_SENSOR_PORTRAIT: + // Allow both portrait orientations depending on sensor direction. + orientations += "UIInterfaceOrientationPortrait\n"; + orientations += "UIInterfaceOrientationPortraitUpsideDown\n"; + break; + case DisplayServer::SCREEN_SENSOR: + // Allow all screen orientations depending on sensor direction. + orientations += "UIInterfaceOrientationLandscapeLeft\n"; + orientations += "UIInterfaceOrientationLandscapeRight\n"; + orientations += "UIInterfaceOrientationPortrait\n"; + orientations += "UIInterfaceOrientationPortraitUpsideDown\n"; + break; + } + + strnew += lines[i].replace("$interface_orientations", orientations); + } else if (lines[i].contains("$ipad_interface_orientations")) { + String orientations; + const DisplayServer::ScreenOrientation screen_orientation = + DisplayServer::ScreenOrientation(int(get_project_setting(p_preset, "display/window/handheld/orientation"))); + + switch (screen_orientation) { + case DisplayServer::SCREEN_LANDSCAPE: + orientations += "UIInterfaceOrientationLandscapeRight\n"; + break; + case DisplayServer::SCREEN_PORTRAIT: + orientations += "UIInterfaceOrientationPortrait\n"; + break; + case DisplayServer::SCREEN_REVERSE_LANDSCAPE: + orientations += "UIInterfaceOrientationLandscapeLeft\n"; + break; + case DisplayServer::SCREEN_REVERSE_PORTRAIT: + orientations += "UIInterfaceOrientationPortraitUpsideDown\n"; + break; + case DisplayServer::SCREEN_SENSOR_LANDSCAPE: + // Allow both landscape orientations depending on sensor direction. + orientations += "UIInterfaceOrientationLandscapeLeft\n"; + orientations += "UIInterfaceOrientationLandscapeRight\n"; + break; + case DisplayServer::SCREEN_SENSOR_PORTRAIT: + // Allow both portrait orientations depending on sensor direction. + orientations += "UIInterfaceOrientationPortrait\n"; + orientations += "UIInterfaceOrientationPortraitUpsideDown\n"; + break; + case DisplayServer::SCREEN_SENSOR: + // Allow all screen orientations depending on sensor direction. + orientations += "UIInterfaceOrientationLandscapeLeft\n"; + orientations += "UIInterfaceOrientationLandscapeRight\n"; + orientations += "UIInterfaceOrientationPortrait\n"; + orientations += "UIInterfaceOrientationPortraitUpsideDown\n"; + break; + } + + strnew += lines[i].replace("$ipad_interface_orientations", orientations); + } else if (lines[i].contains("$camera_usage_description")) { + String description = p_preset->get("privacy/camera_usage_description"); + strnew += lines[i].replace("$camera_usage_description", description) + "\n"; + } else if (lines[i].contains("$microphone_usage_description")) { + String description = p_preset->get("privacy/microphone_usage_description"); + strnew += lines[i].replace("$microphone_usage_description", description) + "\n"; + } else if (lines[i].contains("$photolibrary_usage_description")) { + String description = p_preset->get("privacy/photolibrary_usage_description"); + strnew += lines[i].replace("$photolibrary_usage_description", description) + "\n"; + } else if (lines[i].contains("$plist_launch_screen_name")) { + String value = "UILaunchStoryboardName\nLaunch Screen"; + strnew += lines[i].replace("$plist_launch_screen_name", value) + "\n"; + } else if (lines[i].contains("$pbx_launch_screen_file_reference")) { + String value = "90DD2D9D24B36E8000717FE1 = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = \"Launch Screen.storyboard\"; sourceTree = \"\"; };"; + strnew += lines[i].replace("$pbx_launch_screen_file_reference", value) + "\n"; + } else if (lines[i].contains("$pbx_launch_screen_copy_files")) { + String value = "90DD2D9D24B36E8000717FE1 /* Launch Screen.storyboard */,"; + strnew += lines[i].replace("$pbx_launch_screen_copy_files", value) + "\n"; + } else if (lines[i].contains("$pbx_launch_screen_build_phase")) { + String value = "90DD2D9E24B36E8000717FE1 /* Launch Screen.storyboard in Resources */,"; + strnew += lines[i].replace("$pbx_launch_screen_build_phase", value) + "\n"; + } else if (lines[i].contains("$pbx_launch_screen_build_reference")) { + String value = "90DD2D9E24B36E8000717FE1 /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 90DD2D9D24B36E8000717FE1 /* Launch Screen.storyboard */; };"; + strnew += lines[i].replace("$pbx_launch_screen_build_reference", value) + "\n"; +#ifndef DISABLE_DEPRECATED + } else if (lines[i].contains("$pbx_launch_image_usage_setting")) { + strnew += lines[i].replace("$pbx_launch_image_usage_setting", "") + "\n"; +#endif + } else if (lines[i].contains("$launch_screen_image_mode")) { + int image_scale_mode = p_preset->get("storyboard/image_scale_mode"); + String value; + + switch (image_scale_mode) { + case 0: { + String logo_path = get_project_setting(p_preset, "application/boot_splash/image"); + bool is_on = get_project_setting(p_preset, "application/boot_splash/fullsize"); + // If custom logo is not specified, Godot does not scale default one, so we should do the same. + value = (is_on && logo_path.length() > 0) ? "scaleAspectFit" : "center"; + } break; + default: { + value = storyboard_image_scale_mode[image_scale_mode - 1]; + } + } + + strnew += lines[i].replace("$launch_screen_image_mode", value) + "\n"; + } else if (lines[i].contains("$launch_screen_background_color")) { + bool use_custom = p_preset->get("storyboard/use_custom_bg_color"); + Color color = use_custom ? p_preset->get("storyboard/custom_bg_color") : get_project_setting(p_preset, "application/boot_splash/bg_color"); + const String value_format = "red=\"$red\" green=\"$green\" blue=\"$blue\" alpha=\"$alpha\""; + + Dictionary value_dictionary; + value_dictionary["red"] = color.r; + value_dictionary["green"] = color.g; + value_dictionary["blue"] = color.b; + value_dictionary["alpha"] = color.a; + String value = value_format.format(value_dictionary, "$_"); + + strnew += lines[i].replace("$launch_screen_background_color", value) + "\n"; + } else if (lines[i].contains("$pbx_locale_file_reference")) { + String locale_files; + Vector translations = get_project_setting(p_preset, "internationalization/locale/translations"); + if (translations.size() > 0) { + HashSet languages; + for (const String &E : translations) { + Ref tr = ResourceLoader::load(E); + if (tr.is_valid() && tr->get_locale() != "en") { + languages.insert(tr->get_locale()); + } + } + + int index = 0; + for (const String &lang : languages) { + locale_files += "D0BCFE4518AEBDA2004A" + itos(index).pad_zeros(4) + " /* " + lang + " */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = " + lang + "; path = " + lang + ".lproj/InfoPlist.strings; sourceTree = \"\"; };\n"; + index++; + } + } + strnew += lines[i].replace("$pbx_locale_file_reference", locale_files); + } else if (lines[i].contains("$pbx_locale_build_reference")) { + String locale_files; + Vector translations = get_project_setting(p_preset, "internationalization/locale/translations"); + if (translations.size() > 0) { + HashSet languages; + for (const String &E : translations) { + Ref tr = ResourceLoader::load(E); + if (tr.is_valid() && tr->get_locale() != "en") { + languages.insert(tr->get_locale()); + } + } + + int index = 0; + for (const String &lang : languages) { + locale_files += "D0BCFE4518AEBDA2004A" + itos(index).pad_zeros(4) + " /* " + lang + " */,\n"; + index++; + } + } + strnew += lines[i].replace("$pbx_locale_build_reference", locale_files); + } else if (lines[i].contains("$swift_runtime_migration")) { + String value = !p_config.use_swift_runtime ? "" : "LastSwiftMigration = 1250;"; + strnew += lines[i].replace("$swift_runtime_migration", value) + "\n"; + } else if (lines[i].contains("$swift_runtime_build_settings")) { + String value = !p_config.use_swift_runtime ? "" : R"( + CLANG_ENABLE_MODULES = YES; + SWIFT_OBJC_BRIDGING_HEADER = "$binary/dummy.h"; + SWIFT_VERSION = 5.0; + )"; + value = value.replace("$binary", p_config.binary_name); + strnew += lines[i].replace("$swift_runtime_build_settings", value) + "\n"; + } else if (lines[i].contains("$swift_runtime_fileref")) { + String value = !p_config.use_swift_runtime ? "" : R"( + 90B4C2AA2680BC560039117A /* dummy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "dummy.h"; sourceTree = ""; }; + 90B4C2B52680C7E90039117A /* dummy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "dummy.swift"; sourceTree = ""; }; + )"; + strnew += lines[i].replace("$swift_runtime_fileref", value) + "\n"; + } else if (lines[i].contains("$swift_runtime_binary_files")) { + String value = !p_config.use_swift_runtime ? "" : R"( + 90B4C2AA2680BC560039117A /* dummy.h */, + 90B4C2B52680C7E90039117A /* dummy.swift */, + )"; + strnew += lines[i].replace("$swift_runtime_binary_files", value) + "\n"; + } else if (lines[i].contains("$swift_runtime_buildfile")) { + String value = !p_config.use_swift_runtime ? "" : "90B4C2B62680C7E90039117A /* dummy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90B4C2B52680C7E90039117A /* dummy.swift */; };"; + strnew += lines[i].replace("$swift_runtime_buildfile", value) + "\n"; + } else if (lines[i].contains("$swift_runtime_build_phase")) { + String value = !p_config.use_swift_runtime ? "" : "90B4C2B62680C7E90039117A /* dummy.swift */,"; + strnew += lines[i].replace("$swift_runtime_build_phase", value) + "\n"; + } else if (lines[i].contains("$priv_collection")) { + bool section_opened = false; + for (uint64_t j = 0; j < std::size(data_collect_type_info); ++j) { + bool data_collected = p_preset->get(vformat("privacy/collected_data/%s/collected", data_collect_type_info[j].prop_name)); + bool linked = p_preset->get(vformat("privacy/collected_data/%s/linked_to_user", data_collect_type_info[j].prop_name)); + bool tracking = p_preset->get(vformat("privacy/collected_data/%s/used_for_tracking", data_collect_type_info[j].prop_name)); + int purposes = p_preset->get(vformat("privacy/collected_data/%s/collection_purposes", data_collect_type_info[j].prop_name)); + if (data_collected) { + if (!section_opened) { + section_opened = true; + strnew += "\tNSPrivacyCollectedDataTypes\n"; + strnew += "\t\n"; + } + strnew += "\t\t\n"; + strnew += "\t\t\tNSPrivacyCollectedDataType\n"; + strnew += vformat("\t\t\t%s\n", data_collect_type_info[j].type_name); + strnew += "\t\t\t\tNSPrivacyCollectedDataTypeLinked\n"; + if (linked) { + strnew += "\t\t\t\t\n"; + } else { + strnew += "\t\t\t\t\n"; + } + strnew += "\t\t\t\tNSPrivacyCollectedDataTypeTracking\n"; + if (tracking) { + strnew += "\t\t\t\t\n"; + } else { + strnew += "\t\t\t\t\n"; + } + if (purposes != 0) { + strnew += "\t\t\t\tNSPrivacyCollectedDataTypePurposes\n"; + strnew += "\t\t\t\t\n"; + for (uint64_t k = 0; k < std::size(data_collect_purpose_info); ++k) { + if (purposes & (1 << k)) { + strnew += vformat("\t\t\t\t\t%s\n", data_collect_purpose_info[k].type_name); + } + } + strnew += "\t\t\t\t\n"; + } + strnew += "\t\t\t\n"; + } + } + if (section_opened) { + strnew += "\t\n"; + } + } else if (lines[i].contains("$priv_tracking")) { + bool tracking = p_preset->get("privacy/tracking_enabled"); + strnew += "\tNSPrivacyTracking\n"; + if (tracking) { + strnew += "\t\n"; + } else { + strnew += "\t\n"; + } + Vector tracking_domains = p_preset->get("privacy/tracking_domains"); + if (!tracking_domains.is_empty()) { + strnew += "\tNSPrivacyTrackingDomains\n"; + strnew += "\t\n"; + for (const String &E : tracking_domains) { + strnew += "\t\t" + E + "\n"; + } + strnew += "\t\n"; + } + } else if (lines[i].contains("$priv_api_types")) { + strnew += "\t\n"; + for (uint64_t j = 0; j < std::size(api_info); ++j) { + int api_access = p_preset->get(vformat("privacy/%s_access_reasons", api_info[j].prop_name)); + if (api_access != 0) { + strnew += "\t\t\n"; + strnew += "\t\t\tNSPrivacyAccessedAPITypeReasons\n"; + strnew += "\t\t\t\n"; + for (int k = 0; k < api_info[j].prop_flag_value.size(); k++) { + if (api_access & (1 << k)) { + strnew += vformat("\t\t\t\t%s\n", api_info[j].prop_flag_value[k]); + } + } + strnew += "\t\t\t\n"; + strnew += "\t\t\tNSPrivacyAccessedAPIType\n"; + strnew += vformat("\t\t\t%s\n", api_info[j].type_name); + strnew += "\t\t\n"; + } + } + strnew += "\t\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 macOS 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 EditorExportPlatformAppleEmbedded::_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_apple_embedded_platform_plist_content(); + } + return result; +} + +String EditorExportPlatformAppleEmbedded::_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_apple_embedded_platform_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 EditorExportPlatformAppleEmbedded::_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_apple_embedded_platform_cpp_code(); + } + return result; +} + +void EditorExportPlatformAppleEmbedded::_blend_and_rotate(Ref &p_dst, Ref &p_src, bool p_rot) { + ERR_FAIL_COND(p_dst.is_null()); + ERR_FAIL_COND(p_src.is_null()); + + int sw = p_rot ? p_src->get_height() : p_src->get_width(); + int sh = p_rot ? p_src->get_width() : p_src->get_height(); + + int x_pos = (p_dst->get_width() - sw) / 2; + int y_pos = (p_dst->get_height() - sh) / 2; + + int xs = (x_pos >= 0) ? 0 : -x_pos; + int ys = (y_pos >= 0) ? 0 : -y_pos; + + if (sw + x_pos > p_dst->get_width()) { + sw = p_dst->get_width() - x_pos; + } + if (sh + y_pos > p_dst->get_height()) { + sh = p_dst->get_height() - y_pos; + } + + for (int y = ys; y < sh; y++) { + for (int x = xs; x < sw; x++) { + Color sc = p_rot ? p_src->get_pixel(p_src->get_width() - y - 1, x) : p_src->get_pixel(x, y); + Color dc = p_dst->get_pixel(x_pos + x, y_pos + y); + dc.r = (double)(sc.a * sc.r + dc.a * (1.0 - sc.a) * dc.r); + dc.g = (double)(sc.a * sc.g + dc.a * (1.0 - sc.a) * dc.g); + dc.b = (double)(sc.a * sc.b + dc.a * (1.0 - sc.a) * dc.b); + dc.a = (double)(sc.a + dc.a * (1.0 - sc.a)); + p_dst->set_pixel(x_pos + x, y_pos + y, dc); + } + } +} + +Error EditorExportPlatformAppleEmbedded::_walk_dir_recursive(Ref &p_da, FileHandler p_handler, void *p_userdata) { + Vector dirs; + String current_dir = p_da->get_current_dir(); + p_da->list_dir_begin(); + String path = p_da->get_next(); + while (!path.is_empty()) { + if (p_da->current_is_dir()) { + if (path != "." && path != "..") { + dirs.push_back(path); + } + } else { + Error err = p_handler(current_dir.path_join(path), p_userdata); + if (err) { + p_da->list_dir_end(); + return err; + } + } + path = p_da->get_next(); + } + p_da->list_dir_end(); + + for (int i = 0; i < dirs.size(); ++i) { + p_da->change_dir(dirs[i]); + Error err = _walk_dir_recursive(p_da, p_handler, p_userdata); + p_da->change_dir(".."); + if (err) { + return err; + } + } + + return OK; +} + +struct CodesignData { + const Ref &preset; + bool debug = false; + + CodesignData(const Ref &p_preset, bool p_debug) : + preset(p_preset), + debug(p_debug) { + } +}; + +Error EditorExportPlatformAppleEmbedded::_codesign(String p_file, void *p_userdata) { + if (p_file.ends_with(".dylib")) { + CodesignData *data = static_cast(p_userdata); + print_line(String("Signing ") + p_file); + + String sign_id; + if (data->debug) { + sign_id = data->preset->get("application/code_sign_identity_debug").operator String().is_empty() ? "Apple Development" : data->preset->get("application/code_sign_identity_debug"); + } else { + sign_id = data->preset->get("application/code_sign_identity_release").operator String().is_empty() ? "Apple Distribution" : data->preset->get("application/code_sign_identity_release"); + } + + List codesign_args; + codesign_args.push_back("-f"); + codesign_args.push_back("-s"); + codesign_args.push_back(sign_id); + codesign_args.push_back(p_file); + String str; + Error err = OS::get_singleton()->execute("codesign", codesign_args, &str, nullptr, true); + print_verbose("codesign (" + p_file + "):\n" + str); + + return err; + } + return OK; +} + +struct PbxId { +private: + static char _hex_char(uint8_t p_four_bits) { + if (p_four_bits < 10) { + return ('0' + p_four_bits); + } + return 'A' + (p_four_bits - 10); + } + + static String _hex_pad(uint32_t p_num) { + Vector ret; + ret.resize(sizeof(p_num) * 2); + for (uint64_t i = 0; i < sizeof(p_num) * 2; ++i) { + uint8_t four_bits = (p_num >> (sizeof(p_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 EditorExportPlatformAppleEmbedded::_check_xcframework_content(const String &p_path, int &r_total_libs, int &r_static_libs, int &r_dylibs, int &r_frameworks) const { + Ref plist; + plist.instantiate(); + plist->load_file(p_path.path_join("Info.plist")); + Ref root_node = plist->get_root(); + if (root_node.is_null()) { + return; + } + Dictionary root = root_node->get_value(); + if (!root.has("AvailableLibraries")) { + return; + } + Ref libs_node = root["AvailableLibraries"]; + if (libs_node.is_null()) { + return; + } + Array libs = libs_node->get_value(); + r_total_libs = libs.size(); + for (int j = 0; j < libs.size(); j++) { + Ref lib_node = libs[j]; + if (lib_node.is_null()) { + return; + } + Dictionary lib = lib_node->get_value(); + if (lib.has("BinaryPath")) { + Ref path_node = lib["BinaryPath"]; + if (path_node.is_valid()) { + String path = path_node->get_value(); + if (path.ends_with(".a")) { + r_static_libs++; + } + if (path.ends_with(".dylib")) { + r_dylibs++; + } + if (path.ends_with(".framework")) { + r_frameworks++; + } + } + } + } +} + +Error EditorExportPlatformAppleEmbedded::_convert_to_framework(const String &p_source, const String &p_destination, const String &p_id) const { + print_line("Converting to .framework", p_source, " -> ", p_destination); + + Ref da = DirAccess::create_for_path(p_source); + if (da.is_null()) { + return ERR_CANT_OPEN; + } + + Ref filesystem_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + if (filesystem_da.is_null()) { + return ERR_CANT_OPEN; + } + + if (!filesystem_da->dir_exists(p_destination)) { + Error make_dir_err = filesystem_da->make_dir_recursive(p_destination); + if (make_dir_err) { + return make_dir_err; + } + } + + String asset = p_source.ends_with("/") ? p_source.left(-1) : p_source; + if (asset.ends_with(".xcframework")) { + Ref plist; + plist.instantiate(); + plist->load_file(p_source.path_join("Info.plist")); + Ref root_node = plist->get_root(); + if (root_node.is_null()) { + return ERR_CANT_OPEN; + } + Dictionary root = root_node->get_value(); + if (!root.has("AvailableLibraries")) { + return ERR_CANT_OPEN; + } + Ref libs_node = root["AvailableLibraries"]; + if (libs_node.is_null()) { + return ERR_CANT_OPEN; + } + Array libs = libs_node->get_value(); + for (int j = 0; j < libs.size(); j++) { + Ref lib_node = libs[j]; + if (lib_node.is_null()) { + return ERR_CANT_OPEN; + } + Dictionary lib = lib_node->get_value(); + if (lib.has("BinaryPath") && lib.has("LibraryPath") && lib.has("LibraryIdentifier")) { + Ref bpath_node = lib["BinaryPath"]; + Ref lpath_node = lib["LibraryPath"]; + Ref lid_node = lib["LibraryIdentifier"]; + if (bpath_node.is_valid() && lpath_node.is_valid() && lid_node.is_valid()) { + String binary_path = bpath_node->get_value(); + String library_identifier = lid_node->get_value(); + + String file_name = binary_path.get_basename().get_file(); + String framework_name = file_name + ".framework"; + + bpath_node->data_string = framework_name.utf8(); + lpath_node->data_string = framework_name.utf8(); + if (!filesystem_da->dir_exists(p_destination.path_join(library_identifier))) { + filesystem_da->make_dir_recursive(p_destination.path_join(library_identifier)); + } + _convert_to_framework(p_source.path_join(library_identifier).path_join(binary_path), p_destination.path_join(library_identifier).path_join(framework_name), p_id); + if (lib.has("DebugSymbolsPath")) { + Ref dpath_node = lib["DebugSymbolsPath"]; + if (dpath_node.is_valid()) { + String dpath = dpath_node->get_value(); + if (da->dir_exists(p_source.path_join(library_identifier).path_join(dpath))) { + da->copy_dir(p_source.path_join(library_identifier).path_join(dpath), p_destination.path_join(library_identifier).path_join("dSYMs")); + } + } + } + } + } + } + String info_plist = plist->save_text(); + + Ref f = FileAccess::open(p_destination.path_join("Info.plist"), FileAccess::WRITE); + if (f.is_valid()) { + f->store_string(info_plist); + } + } else { + String file_name = p_destination.get_basename().get_file(); + String framework_name = file_name + ".framework"; + + da->copy(p_source, p_destination.path_join(file_name)); + + // 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").path_join(framework_name).path_join(file_name)); + install_name_args.push_back(p_destination.path_join(file_name)); + + OS::get_singleton()->execute("install_name_tool", install_name_args); + } + + // Creating Info.plist + { + String lib_clean_name = file_name; + for (int i = 0; i < lib_clean_name.length(); i++) { + if (!is_ascii_alphanumeric_char(lib_clean_name[i]) && lib_clean_name[i] != '.' && lib_clean_name[i] != '-') { + lib_clean_name[i] = '-'; + } + } + String info_plist_format = + "\n" + "\n" + "\n" + " \n" + " CFBundleShortVersionString\n" + " 1.0\n" + " CFBundleIdentifier\n" + " $id.framework.$cl_name\n" + " CFBundleName\n" + " $name\n" + " CFBundleExecutable\n" + " $name\n" + " DTPlatformName\n" + " " + + get_sdk_name() + + "\n" + " CFBundleInfoDictionaryVersion\n" + " 6.0\n" + " CFBundleVersion\n" + " 1\n" + " CFBundlePackageType\n" + " FMWK\n" + " MinimumOSVersion\n" + " " + + get_minimum_deployment_target() + + "\n" + " \n" + ""; + + String info_plist = info_plist_format.replace("$id", p_id).replace("$name", file_name).replace("$cl_name", lib_clean_name); + + Ref f = FileAccess::open(p_destination.path_join("Info.plist"), FileAccess::WRITE); + if (f.is_valid()) { + f->store_string(info_plist); + } + } + } + + return OK; +} + +void EditorExportPlatformAppleEmbedded::_add_assets_to_project(const String &p_out_dir, 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 AppleEmbeddedExportAsset &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")) { + int total_libs = 0; + int static_libs = 0; + int dylibs = 0; + int frameworks = 0; + _check_xcframework_content(p_out_dir.path_join(asset.exported_path), total_libs, static_libs, dylibs, frameworks); + if (asset.should_embed && static_libs != total_libs) { + 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 EditorExportPlatformAppleEmbedded::_copy_asset(const Ref &p_preset, 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) { + String binary_name = p_out_dir.get_file().get_basename(); + + Ref da = DirAccess::create_for_path(p_asset); + if (da.is_null()) { + ERR_FAIL_V_MSG(ERR_CANT_CREATE, "Can't open directory: " + p_asset + "."); + } + bool file_exists = da->file_exists(p_asset); + bool dir_exists = da->dir_exists(p_asset); + if (!file_exists && !dir_exists) { + return ERR_FILE_NOT_FOUND; + } + + String base_dir = p_asset.get_base_dir().replace("res://", "").replace(".godot/mono/temp/bin/", ""); + String asset = p_asset.ends_with("/") ? p_asset.left(-1) : p_asset; + String destination_dir; + String destination; + String asset_path; + + Ref filesystem_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + ERR_FAIL_COND_V_MSG(filesystem_da.is_null(), ERR_CANT_CREATE, "Cannot create DirAccess for path '" + p_out_dir + "'."); + + if (p_is_framework && asset.ends_with(".dylib")) { + // For Apple Embedded platforms we need to turn .dylib into .framework + // to be able to send application to AppStore + asset_path = String("dylibs").path_join(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.path_join(framework_name); + destination_dir = p_out_dir.path_join(asset_path); + destination = destination_dir; + + // Convert to framework and copy. + Error err = _convert_to_framework(p_asset, destination, p_preset->get("application/bundle_identifier")); + if (err) { + return err; + } + } else if (p_is_framework && asset.ends_with(".xcframework")) { + // For Apple Embedded platforms we need to turn .dylib inside .xcframework + // into .framework to be able to send application to AppStore + + int total_libs = 0; + int static_libs = 0; + int dylibs = 0; + int frameworks = 0; + _check_xcframework_content(p_asset, total_libs, static_libs, dylibs, frameworks); + + asset_path = String("dylibs").path_join(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.path_join(file_name); + destination_dir = p_out_dir.path_join(asset_path); + destination = destination_dir; + + if (dylibs > 0) { + // Convert to framework and copy. + Error err = _convert_to_framework(p_asset, destination, p_preset->get("application/bundle_identifier")); + if (err) { + return err; + } + } else { + // Copy as is. + if (!filesystem_da->dir_exists(destination_dir)) { + Error make_dir_err = filesystem_da->make_dir_recursive(destination_dir); + if (make_dir_err) { + return make_dir_err; + } + } + Error err = dir_exists ? da->copy_dir(p_asset, destination) : da->copy(p_asset, destination); + if (err) { + return err; + } + } + } else if (p_is_framework && asset.ends_with(".framework")) { + // Framework. + asset_path = String("dylibs").path_join(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.path_join(file_name); + destination_dir = p_out_dir.path_join(asset_path); + destination = destination_dir; + + // Copy as is. + if (!filesystem_da->dir_exists(destination_dir)) { + Error make_dir_err = filesystem_da->make_dir_recursive(destination_dir); + if (make_dir_err) { + return make_dir_err; + } + } + Error err = dir_exists ? da->copy_dir(p_asset, destination) : da->copy(p_asset, destination); + if (err) { + return err; + } + } else { + // Unknown resource. + 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.path_join(asset_path); + asset_path = asset_path.path_join(file_name); + destination = p_out_dir.path_join(asset_path); + + // Copy as is. + if (!filesystem_da->dir_exists(destination_dir)) { + Error make_dir_err = filesystem_da->make_dir_recursive(destination_dir); + if (make_dir_err) { + return make_dir_err; + } + } + Error err = dir_exists ? da->copy_dir(p_asset, destination) : da->copy(p_asset, destination); + if (err) { + return err; + } + } + + if (asset_path.ends_with("/")) { + asset_path = asset_path.left(-1); + } + AppleEmbeddedExportAsset exported_asset = { binary_name.path_join(asset_path), p_is_framework, p_should_embed }; + r_exported_assets.push_back(exported_asset); + + return OK; +} + +Error EditorExportPlatformAppleEmbedded::_export_additional_assets(const Ref &p_preset, 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) { + const String &asset = p_assets[f_idx]; + if (asset.begins_with("res://")) { + Error err = _copy_asset(p_preset, p_out_dir, asset, nullptr, p_is_framework, p_should_embed, r_exported_assets); + ERR_FAIL_COND_V(err != OK, err); + } else if (asset.is_absolute_path() && ProjectSettings::get_singleton()->localize_path(asset).begins_with("res://")) { + Error err = _copy_asset(p_preset, p_out_dir, ProjectSettings::get_singleton()->localize_path(asset), nullptr, p_is_framework, p_should_embed, r_exported_assets); + ERR_FAIL_COND_V(err != OK, err); + } else { + // either SDK-builtin or already a part of the export template + AppleEmbeddedExportAsset exported_asset = { asset, p_is_framework, p_should_embed }; + r_exported_assets.push_back(exported_asset); + } + } + + return OK; +} + +Error EditorExportPlatformAppleEmbedded::_export_additional_assets(const Ref &p_preset, 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_apple_embedded_platform_frameworks(); + Error err = _export_additional_assets(p_preset, p_out_dir, linked_frameworks, true, false, r_exported_assets); + ERR_FAIL_COND_V(err, err); + + Vector embedded_frameworks = export_plugins[i]->get_apple_embedded_platform_embedded_frameworks(); + err = _export_additional_assets(p_preset, p_out_dir, embedded_frameworks, true, true, r_exported_assets); + ERR_FAIL_COND_V(err, err); + + Vector project_static_libs = export_plugins[i]->get_apple_embedded_platform_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_preset, p_out_dir, project_static_libs, true, false, r_exported_assets); + ERR_FAIL_COND_V(err, err); + + Vector apple_embedded_platform_bundle_files = export_plugins[i]->get_apple_embedded_platform_bundle_files(); + err = _export_additional_assets(p_preset, p_out_dir, apple_embedded_platform_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_preset, p_out_dir, library_paths, true, true, r_exported_assets); + ERR_FAIL_COND_V(err, err); + + return OK; +} + +Vector EditorExportPlatformAppleEmbedded::_get_preset_architectures(const Ref &p_preset) const { + Vector all_archs = _get_supported_architectures(); + Vector enabled_archs; + for (int i = 0; i < all_archs.size(); ++i) { + bool is_enabled = p_preset->get("architectures/" + all_archs[i].name); + if (is_enabled) { + enabled_archs.push_back(all_archs[i].name); + } + } + return enabled_archs; +} + +Error EditorExportPlatformAppleEmbedded::_export_apple_embedded_plugins(const Ref &p_preset, AppleEmbeddedConfigData &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(get_platform_name(), p_preset); + + Vector added_linked_dependenciy_names; + Vector added_embedded_dependenciy_names; + HashMap plist_values; + + HashSet plugin_linker_flags; + + Error err; + + for (int i = 0; i < enabled_plugins.size(); i++) { + PluginConfigAppleEmbedded plugin = enabled_plugins[i]; + + // Export plugin binary. + String plugin_main_binary = PluginConfigAppleEmbedded::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(p_preset, dest_dir, plugin_main_binary, &plugin_binary_result_file, true, false, r_exported_assets); + ERR_FAIL_COND_V(err != OK, 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.has(name)) { + 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.has(name)) { + 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.has(name)) { + 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.has(capability)) { + 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 + + for (const KeyValue &E : plugin.plist) { + String key = E.key; + const PluginConfigAppleEmbedded::PlistItem &item = E.value; + + String value; + + switch (item.type) { + case PluginConfigAppleEmbedded::PlistItemType::STRING_INPUT: { + String preset_name = "plugins_plist/" + key; + String input_value = p_preset->get(preset_name); + value = "" + input_value + ""; + } break; + default: + value = item.value; + break; + } + + if (key.is_empty() || value.is_empty()) { + continue; + } + + String plist_key = "" + key + ""; + + plist_values[plist_key] = value; + } + + // CPP Code + String definition_comment = "// Plugin: " + plugin.name + "\n"; + String initialization_method = plugin.initialization_method + "();\n"; + String deinitialization_method = plugin.deinitialization_method + "();\n"; + + plugin_definition_cpp_code += definition_comment + + "extern void " + initialization_method + + "extern void " + deinitialization_method + "\n"; + + plugin_initialization_cpp_code += "\t" + initialization_method; + plugin_deinitialization_cpp_code += "\t" + deinitialization_method; + + if (plugin.use_swift_runtime) { + p_config_data.use_swift_runtime = true; + } + } + + // Updating `Info.plist` + { + for (const KeyValue &E : plist_values) { + String key = E.key; + String value = E.value; + + if (key.is_empty() || value.is_empty()) { + continue; + } + + p_config_data.plist_content += key + value + "\n"; + } + } + + // Export files + { + // Export linked plugin dependency + err = _export_additional_assets(p_preset, dest_dir, plugin_linked_dependencies, true, false, r_exported_assets); + ERR_FAIL_COND_V(err != OK, err); + + // Export embedded plugin dependency + err = _export_additional_assets(p_preset, dest_dir, plugin_embedded_dependencies, true, true, r_exported_assets); + ERR_FAIL_COND_V(err != OK, err); + + // Export plugin files + err = _export_additional_assets(p_preset, dest_dir, plugin_files, false, false, r_exported_assets); + ERR_FAIL_COND_V(err != OK, 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_apple_embedded_plugins_initialize();\n" + "void godot_apple_embedded_plugins_deinitialize();\n" + "// Exported Plugins\n\n" + "$definition" + "// Use Plugins\n" + "void godot_apple_embedded_plugins_initialize() {\n" + "$initialization" + "}\n\n" + "void godot_apple_embedded_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 (const String &E : plugin_linker_flags) { + const String &flag = E; + + 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 EditorExportPlatformAppleEmbedded::export_project(const Ref &p_preset, bool p_debug, const String &p_path, BitField p_flags) { + return _export_project_helper(p_preset, p_debug, p_path, p_flags, false); +} + +Error EditorExportPlatformAppleEmbedded::_export_project_helper(const Ref &p_preset, bool p_debug, const String &p_path, BitField p_flags, bool p_oneclick) { + ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); + + const String dest_dir = p_path.get_base_dir() + "/"; + const String binary_name = p_path.get_file().get_basename(); + const String binary_dir = dest_dir + binary_name; + + if (!DirAccess::exists(dest_dir)) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Target folder does not exist or is inaccessible: \"%s\""), dest_dir)); + return ERR_FILE_BAD_PATH; + } + + bool export_project_only = p_preset->get("application/export_project_only"); + if (p_oneclick) { + export_project_only = false; // Skip for one-click deploy. + } + + EditorProgress ep("export", export_project_only ? TTR("Exporting for " + get_name() + " (Project Files Only)") : TTR("Exporting for " + get_name() + ""), export_project_only ? 2 : 5, true); + + String team_id = p_preset->get("application/app_store_team_id"); + ERR_FAIL_COND_V_MSG(team_id.length() == 0, ERR_CANT_OPEN, "App Store Team ID not specified - cannot configure the project."); + + String src_pkg_name; + 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.is_empty()) { + String err; + src_pkg_name = find_export_template(get_platform_name() + ".zip", &err); + if (src_pkg_name.is_empty()) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), TTR("Export template not found.")); + return ERR_FILE_NOT_FOUND; + } + } + + { + bool delete_old = p_preset->get("application/delete_old_export_files_unconditionally"); + Ref da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + if (da.is_valid()) { + 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(binary_dir + ".xcodeproj") == OK) { + // Check directory content before deleting. + int expected_files = 0; + int total_files = 0; + if (!delete_old) { + da->list_dir_begin(); + for (String n = da->get_next(); !n.is_empty(); n = da->get_next()) { + if (!n.begins_with(".")) { // Ignore ".", ".." and hidden files. + if (da->current_is_dir()) { + if (n == "xcshareddata" || n == "project.xcworkspace") { + expected_files++; + } + } else { + if (n == "project.pbxproj") { + expected_files++; + } + } + total_files++; + } + } + da->list_dir_end(); + } + if ((total_files == 0) || (expected_files >= Math::floor(total_files * 0.8))) { + da->erase_contents_recursive(); + } else { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Unexpected files found in the export destination directory \"%s.xcodeproj\", delete it manually or select another destination."), binary_dir)); + return ERR_CANT_CREATE; + } + } + da->change_dir(current_dir); + + if (da->change_dir(binary_dir) == OK) { + // Check directory content before deleting. + int expected_files = 0; + int total_files = 0; + if (!delete_old) { + da->list_dir_begin(); + for (String n = da->get_next(); !n.is_empty(); n = da->get_next()) { + if (!n.begins_with(".")) { // Ignore ".", ".." and hidden files. + if (da->current_is_dir()) { + if (n == "dylibs" || n == "Images.xcassets" || n.ends_with(".lproj") || n == "godot-publish-dotnet" || n.ends_with(".xcframework") || n.ends_with(".framework")) { + expected_files++; + } + } else { + if (n == binary_name + "-Info.plist" || n == binary_name + ".entitlements" || n == "Launch Screen.storyboard" || n == "export_options.plist" || n.begins_with("dummy.") || n.ends_with(".gdip")) { + expected_files++; + } + } + total_files++; + } + } + da->list_dir_end(); + } + if ((total_files == 0) || (expected_files >= Math::floor(total_files * 0.8))) { + da->erase_contents_recursive(); + } else { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Unexpected files found in the export destination directory \"%s\", delete it manually or select another destination."), binary_dir)); + return ERR_CANT_CREATE; + } + } + da->change_dir(current_dir); + + if (!da->dir_exists(binary_dir)) { + Error err = da->make_dir(binary_dir); + if (err != OK) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Failed to create the directory: \"%s\""), binary_dir)); + return err; + } + } + } + } + + if (ep.step("Making .pck", 0)) { + return ERR_SKIP; + } + String pack_path = binary_dir + ".pck"; + Vector libraries; + Error err = save_pack(p_preset, p_debug, pack_path, &libraries); + if (err) { + // Message is supplied by the subroutine method. + return err; + } + + if (ep.step("Extracting and configuring Xcode project", 1)) { + return ERR_SKIP; + } + + String library_to_use = "libgodot." + get_platform_name() + "." + String(p_debug ? "debug" : "release") + ".xcframework"; + + print_line("Static framework: " + library_to_use); + String pkg_name; + if (String(get_project_setting(p_preset, "application/config/name")) != "") { + pkg_name = String(get_project_setting(p_preset, "application/config/name")); + } else { + pkg_name = "Unnamed"; + } + + bool found_library = false; + + const String godot_platform = "godot_" + get_platform_name(); + const String project_file = godot_platform + ".xcodeproj/project.pbxproj"; + HashSet files_to_parse; + files_to_parse.insert(godot_platform + "/godot_" + get_platform_name() + "-Info.plist"); + files_to_parse.insert(project_file); + files_to_parse.insert(godot_platform + "/export_options.plist"); + files_to_parse.insert(godot_platform + "/dummy.cpp"); + files_to_parse.insert(godot_platform + ".xcodeproj/project.xcworkspace/contents.xcworkspacedata"); + files_to_parse.insert(godot_platform + ".xcodeproj/xcshareddata/xcschemes/godot_" + get_platform_name() + ".xcscheme"); + files_to_parse.insert(godot_platform + "/godot_" + get_platform_name() + ".entitlements"); + files_to_parse.insert(godot_platform + "/Launch Screen.storyboard"); + files_to_parse.insert("PrivacyInfo.xcprivacy"); + + AppleEmbeddedConfigData config_data = { + pkg_name, + binary_name, + _get_additional_plist_content(), + String(" ").join(_get_preset_architectures(p_preset)), + _get_linker_flags(), + _get_cpp_code(), + "", + "", + "", + "", + Vector(), + false + }; + + config_data.plist_content += p_preset->get("application/additional_plist_content").operator String() + "\n"; + + Vector assets; + + Ref tmp_app_path = DirAccess::create_for_path(dest_dir); + if (tmp_app_path.is_null()) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), vformat(TTR("Could not create and open the directory: \"%s\""), dest_dir)); + return ERR_CANT_CREATE; + } + + print_line("Unzipping..."); + Ref io_fa; + zlib_filefunc_def io = zipio_create_io(&io_fa); + unzFile src_pkg_zip = unzOpen2(src_pkg_name.utf8().get_data(), &io); + if (!src_pkg_zip) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), TTR("Could not open export template (not a zip file?): \"%s\".", src_pkg_name)); + return ERR_CANT_OPEN; + } + + err = _export_apple_embedded_plugins(p_preset, config_data, binary_dir, assets, p_debug); + if (err != OK) { + // TODO: Improve error reporting by using `add_message` throughout all methods called via `_export_apple_embedded_plugins`. + // For now a generic top level message would be fine, but we're ought to use proper reporting here instead of + // just fail macros and non-descriptive error return values. + add_message(EXPORT_MESSAGE_ERROR, TTR("Apple Embedded Plugins"), vformat(TTR("Failed to export Apple Embedded plugins with code %d. Please check the output log."), err)); + return err; + } + + //export rest of the files + int ret = unzGoToFirstFile(src_pkg_zip); + Vector project_file_data; + while (ret == UNZ_OK) { +#if defined(MACOS_ENABLED) || defined(LINUXBSD_ENABLED) + bool is_execute = false; +#endif + + //get filename + unz_file_info info; + char fname[16384]; + ret = unzGetCurrentFileInfo(src_pkg_zip, &info, fname, 16384, nullptr, 0, nullptr, 0); + if (ret != UNZ_OK) { + break; + } + + String file = String::utf8(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 + + if (files_to_parse.has(file)) { + _fix_config_file(p_preset, data, config_data, p_debug); + } else if (file.begins_with("libgodot." + get_platform_name())) { + if (!file.begins_with(library_to_use) || file.ends_with(String("/empty"))) { + ret = unzGoToNextFile(src_pkg_zip); + continue; //ignore! + } + found_library = true; +#if defined(MACOS_ENABLED) || defined(LINUXBSD_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_" + get_platform_name(), binary_name); + + print_line("ADDING: " + file + " size: " + itos(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) { + unzClose(src_pkg_zip); + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not create a directory at path \"%s\"."), dir_name)); + return ERR_CANT_CREATE; + } + } + + /* write the file */ + { + Ref f = FileAccess::open(file, FileAccess::WRITE); + if (f.is_null()) { + unzClose(src_pkg_zip); + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not write to a file at path \"%s\"."), file)); + return ERR_CANT_CREATE; + }; + f->store_buffer(data.ptr(), data.size()); + } + +#if defined(MACOS_ENABLED) || defined(LINUXBSD_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) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Requested template library '%s' not found. It might be missing from your template archive."), library_to_use)); + return ERR_FILE_NOT_FOUND; + } + + Dictionary appnames = get_project_setting(p_preset, "application/config/name_localized"); + Dictionary camera_usage_descriptions = p_preset->get("privacy/camera_usage_description_localized"); + Dictionary microphone_usage_descriptions = p_preset->get("privacy/microphone_usage_description_localized"); + Dictionary photolibrary_usage_descriptions = p_preset->get("privacy/photolibrary_usage_description_localized"); + + Vector translations = get_project_setting(p_preset, "internationalization/locale/translations"); + if (translations.size() > 0) { + { + String fname = binary_dir + "/en.lproj"; + tmp_app_path->make_dir_recursive(fname); + Ref f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE); + f->store_line("/* Localized versions of Info.plist keys */"); + f->store_line(""); + f->store_line("CFBundleDisplayName = \"" + get_project_setting(p_preset, "application/config/name").operator String() + "\";"); + f->store_line("NSCameraUsageDescription = \"" + p_preset->get("privacy/camera_usage_description").operator String() + "\";"); + f->store_line("NSMicrophoneUsageDescription = \"" + p_preset->get("privacy/microphone_usage_description").operator String() + "\";"); + f->store_line("NSPhotoLibraryUsageDescription = \"" + p_preset->get("privacy/photolibrary_usage_description").operator String() + "\";"); + } + + HashSet languages; + for (const String &E : translations) { + Ref tr = ResourceLoader::load(E); + if (tr.is_valid() && tr->get_locale() != "en") { + languages.insert(tr->get_locale()); + } + } + + for (const String &lang : languages) { + String fname = binary_dir + "/" + lang + ".lproj"; + tmp_app_path->make_dir_recursive(fname); + Ref f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE); + f->store_line("/* Localized versions of Info.plist keys */"); + f->store_line(""); + if (appnames.has(lang)) { + f->store_line("CFBundleDisplayName = \"" + appnames[lang].operator String() + "\";"); + } + if (camera_usage_descriptions.has(lang)) { + f->store_line("NSCameraUsageDescription = \"" + camera_usage_descriptions[lang].operator String() + "\";"); + } + if (microphone_usage_descriptions.has(lang)) { + f->store_line("NSMicrophoneUsageDescription = \"" + microphone_usage_descriptions[lang].operator String() + "\";"); + } + if (photolibrary_usage_descriptions.has(lang)) { + f->store_line("NSPhotoLibraryUsageDescription = \"" + photolibrary_usage_descriptions[lang].operator String() + "\";"); + } + } + } + + // 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_apple_embedded_platform_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) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not copy a file at path \"%s\" to \"%s\"."), static_lib_path, dest_lib_file_path)); + return lib_copy_err; + } + } + } + + String iconset_dir = binary_dir + "/Images.xcassets/AppIcon.appiconset/"; + err = OK; + if (!tmp_app_path->dir_exists(iconset_dir)) { + err = tmp_app_path->make_dir_recursive(iconset_dir); + } + if (err != OK) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not create a directory at path \"%s\"."), iconset_dir)); + return err; + } + + err = _export_icons(p_preset, iconset_dir); + if (err != OK) { + // Message is supplied by the subroutine method. + return err; + } + + { + String splash_image_path = binary_dir + "/Images.xcassets/SplashImage.imageset/"; + + Ref launch_screen_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + if (launch_screen_da.is_null()) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Could not access the filesystem.")); + return ERR_CANT_CREATE; + } + + print_line("Exporting launch screen storyboard"); + + err = _export_loading_screen_file(p_preset, splash_image_path); + if (err != OK) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Failed to create a file at path \"%s\" with code %d."), splash_image_path, err)); + } + } + + if (err != OK) { + return err; + } + + print_line("Exporting additional assets"); + _export_additional_assets(p_preset, binary_dir, libraries, assets); + _add_assets_to_project(dest_dir, p_preset, project_file_data, assets); + String project_file_name = binary_dir + ".xcodeproj/project.pbxproj"; + { + Ref f = FileAccess::open(project_file_name, FileAccess::WRITE); + if (f.is_null()) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not write to a file at path \"%s\"."), project_file_name)); + return ERR_CANT_CREATE; + }; + f->store_buffer(project_file_data.ptr(), project_file_data.size()); + } + +#ifdef MACOS_ENABLED + { + if (ep.step("Code-signing dylibs", 2)) { + return ERR_SKIP; + } + Ref dylibs_dir = DirAccess::open(binary_dir + "/dylibs"); + ERR_FAIL_COND_V(dylibs_dir.is_null(), ERR_CANT_OPEN); + CodesignData codesign_data(p_preset, p_debug); + err = _walk_dir_recursive(dylibs_dir, _codesign, &codesign_data); + if (err != OK) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Code Signing"), TTR("Code signing failed, see editor log for details.")); + return err; + } + } + + if (export_project_only) { + return OK; + } + + if (ep.step("Making .xcarchive", 3)) { + return ERR_SKIP; + } + + String platform_name = get_platform_name(); + + String archive_path = p_path.get_basename() + ".xcarchive"; + List archive_args; + archive_args.push_back("-project"); + archive_args.push_back(binary_dir + ".xcodeproj"); + archive_args.push_back("-scheme"); + archive_args.push_back(binary_name); + archive_args.push_back("-sdk"); + archive_args.push_back(get_sdk_name()); + archive_args.push_back("-configuration"); + archive_args.push_back(p_debug ? "Debug" : "Release"); + archive_args.push_back("-destination"); + archive_args.push_back("generic/platform=" + get_platform_name()); + archive_args.push_back("archive"); + archive_args.push_back("-allowProvisioningUpdates"); + archive_args.push_back("-archivePath"); + archive_args.push_back(archive_path); + + String archive_str; + err = OS::get_singleton()->execute("xcodebuild", archive_args, &archive_str, nullptr, true); + if (err != OK) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Xcode Build"), vformat(TTR("Failed to run xcodebuild with code %d"), err)); + return err; + } + + print_line("xcodebuild (.xcarchive):\n" + archive_str); + if (!archive_str.contains("** ARCHIVE SUCCEEDED **")) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Xcode Build"), TTR("Xcode project build failed, see editor log for details.")); + return FAILED; + } + + if (!p_oneclick) { + if (ep.step("Making .ipa", 4)) { + return ERR_SKIP; + } + + List export_args; + export_args.push_back("-exportArchive"); + export_args.push_back("-archivePath"); + export_args.push_back(archive_path); + export_args.push_back("-exportOptionsPlist"); + export_args.push_back(binary_dir + "/export_options.plist"); + export_args.push_back("-allowProvisioningUpdates"); + export_args.push_back("-exportPath"); + export_args.push_back(dest_dir); + + String export_str; + err = OS::get_singleton()->execute("xcodebuild", export_args, &export_str, nullptr, true); + if (err != OK) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Xcode Build"), vformat(TTR("Failed to run xcodebuild with code %d"), err)); + return err; + } + + print_line("xcodebuild (.ipa):\n" + export_str); + if (!export_str.contains("** EXPORT SUCCEEDED **")) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Xcode Build"), TTR(".ipa export failed, see editor log for details.")); + return FAILED; + } + } +#else + add_message(EXPORT_MESSAGE_WARNING, TTR("Xcode Build"), TTR(".ipa can only be built on macOS. Leaving Xcode project without building the package.")); +#endif + + return OK; +} + +bool EditorExportPlatformAppleEmbedded::has_valid_export_configuration(const Ref &p_preset, String &r_error, bool &r_missing_templates, bool p_debug) const { +#if defined(MODULE_MONO_ENABLED) && !defined(MACOS_ENABLED) + // TODO: Remove this restriction when we don't rely on macOS tools to package up the native libraries anymore. + r_error += TTR("Exporting to an Apple Embedded platform when using C#/.NET is experimental and requires macOS.") + "\n"; + return false; +#else + + String err; + bool valid = false; + +#if defined(MODULE_MONO_ENABLED) + // Apple Embedded export is still a work in progress, keep a message as a warning. + err += TTR("Exporting to an Apple Embedded platform when using C#/.NET is experimental.") + "\n"; +#endif + // Look for export templates (first official, and if defined custom templates). + + bool dvalid = exists_export_template(get_platform_name() + ".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; + + const String &additional_plist_content = p_preset->get("application/additional_plist_content"); + if (!additional_plist_content.is_empty()) { + const String &plist = vformat("\n" + "" + "" + "\n" + "%s\n" + "\n" + "\n", + additional_plist_content); + + String plist_err; + Ref plist_parser; + plist_parser.instantiate(); + if (!plist_parser->load_string(plist, plist_err)) { + err += TTR("Invalid additional PList content: ") + plist_err + "\n"; + valid = false; + } + } + + if (!err.is_empty()) { + r_error = err; + } + + return valid; +#endif // !(MODULE_MONO_ENABLED && !MACOS_ENABLED) +} + +bool EditorExportPlatformAppleEmbedded::has_valid_project_configuration(const Ref &p_preset, String &r_error) const { + String err; + bool valid = true; + + // Validate the project configuration. + + List options; + get_export_options(&options); + for (const EditorExportPlatform::ExportOption &E : options) { + if (get_export_option_visibility(p_preset.ptr(), E.option.name)) { + String warn = get_export_option_warning(p_preset.ptr(), E.option.name); + if (!warn.is_empty()) { + err += warn + "\n"; + if (E.required) { + valid = false; + } + } + } + } + + if (!ResourceImporterTextureSettings::should_import_etc2_astc()) { + valid = false; + } + + if (!err.is_empty()) { + r_error = err; + } + + return valid; +} + +int EditorExportPlatformAppleEmbedded::get_options_count() const { + MutexLock lock(device_lock); + return devices.size(); +} + +String EditorExportPlatformAppleEmbedded::get_options_tooltip() const { + return TTR("Select device from the list"); +} + +Ref EditorExportPlatformAppleEmbedded::get_option_icon(int p_index) const { + MutexLock lock(device_lock); + + Ref icon; + if (p_index >= 0 || p_index < devices.size()) { + Ref theme = EditorNode::get_singleton()->get_editor_theme(); + if (theme.is_valid()) { + if (devices[p_index].wifi) { + icon = theme->get_icon("IOSDeviceWireless", EditorStringName(EditorIcons)); + } else { + icon = theme->get_icon("IOSDeviceWired", EditorStringName(EditorIcons)); + } + } + } + return icon; +} + +String EditorExportPlatformAppleEmbedded::get_option_label(int p_index) const { + ERR_FAIL_INDEX_V(p_index, devices.size(), ""); + MutexLock lock(device_lock); + return devices[p_index].name; +} + +String EditorExportPlatformAppleEmbedded::get_option_tooltip(int p_index) const { + ERR_FAIL_INDEX_V(p_index, devices.size(), ""); + MutexLock lock(device_lock); + return "UUID: " + devices[p_index].id; +} + +bool EditorExportPlatformAppleEmbedded::is_package_name_valid(const String &p_package, String *r_error) const { + String pname = p_package; + + if (pname.length() == 0) { + if (r_error) { + *r_error = TTR("Identifier is missing."); + } + return false; + } + + for (int i = 0; i < pname.length(); i++) { + char32_t c = pname[i]; + if (!(is_ascii_alphanumeric_char(c) || c == '-' || c == '.')) { + if (r_error) { + *r_error = vformat(TTR("The character '%s' is not allowed in Identifier."), String::chr(c)); + } + return false; + } + } + + return true; +} + +#ifdef MACOS_ENABLED +bool EditorExportPlatformAppleEmbedded::_check_xcode_install() { + static bool xcode_found = false; + if (!xcode_found) { + Vector mdfind_paths; + List mdfind_args; + mdfind_args.push_back("kMDItemCFBundleIdentifier=com.apple.dt.Xcode"); + + String output; + Error err = OS::get_singleton()->execute("mdfind", mdfind_args, &output); + if (err == OK) { + mdfind_paths = output.split("\n"); + } + for (const String &found_path : mdfind_paths) { + xcode_found = !found_path.is_empty() && DirAccess::dir_exists_absolute(found_path.strip_edges()); + if (xcode_found) { + break; + } + } + } + return xcode_found; +} + +void EditorExportPlatformAppleEmbedded::_check_for_changes_poll_thread(void *ud) { + EditorExportPlatformAppleEmbedded *ea = static_cast(ud); + + while (!ea->quit_request.is_set()) { + // Nothing to do if we already know the plugins have changed. + if (!ea->plugins_changed.is_set()) { + MutexLock lock(ea->plugins_lock); + + Vector loaded_plugins = get_plugins(ea->get_platform_name()); + + 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; + } + } + } + } + + // Check for devices updates. + Vector ldevices; + + // Enum real devices (via ios_deploy, pre Xcode 15). + String ios_deploy_setting = "export/" + ea->get_platform_name() + "/ios_deploy"; + if (EditorSettings::get_singleton()->has_setting(ios_deploy_setting)) { + String idepl = EDITOR_GET(ios_deploy_setting); + if (ea->has_runnable_preset.is_set() && !idepl.is_empty()) { + String devices; + List args; + args.push_back("-c"); + args.push_back("-timeout"); + args.push_back("1"); + args.push_back("-j"); + args.push_back("-u"); + args.push_back("-I"); + + int ec = 0; + Error err = OS::get_singleton()->execute(idepl, args, &devices, &ec, true); + if (err == OK && ec == 0) { + Ref json; + json.instantiate(); + devices = "{ \"devices\":[" + devices.replace("}{", "},{") + "]}"; + err = json->parse(devices); + if (err == OK) { + Dictionary data = json->get_data(); + Array devices = data["devices"]; + for (int i = 0; i < devices.size(); i++) { + Dictionary device_event = devices[i]; + if (device_event["Event"] == "DeviceDetected") { + Dictionary device_info = device_event["Device"]; + Device nd; + nd.id = device_info["DeviceIdentifier"]; + nd.name = device_info["DeviceName"].operator String() + " (ios_deploy, " + ((device_event["Interface"] == "WIFI") ? "network" : "wired") + ")"; + nd.wifi = device_event["Interface"] == "WIFI"; + nd.use_ios_deploy = true; + ldevices.push_back(nd); + } + } + } + } + } + } + // Enum devices (via Xcode). + if (ea->has_runnable_preset.is_set() && _check_xcode_install() && (FileAccess::exists("/usr/bin/xcrun") || FileAccess::exists("/bin/xcrun"))) { + String devices; + List args; + args.push_back("devicectl"); + args.push_back("list"); + args.push_back("devices"); + args.push_back("-j"); + args.push_back("-"); + args.push_back("-q"); + int ec = 0; + Error err = OS::get_singleton()->execute("xcrun", args, &devices, &ec, true); + if (err == OK && ec == 0) { + Ref json; + json.instantiate(); + err = json->parse(devices); + if (err == OK) { + const Dictionary &data = json->get_data(); + const Dictionary &result = data["result"]; + const Array &devices = result["devices"]; + for (int i = 0; i < devices.size(); i++) { + const Dictionary &device_info = devices[i]; + const Dictionary &conn_props = device_info["connectionProperties"]; + const Dictionary &dev_props = device_info["deviceProperties"]; + if (conn_props["pairingState"] == "paired" && dev_props["developerModeStatus"] == "enabled") { + Device nd; + nd.id = device_info["identifier"]; + nd.name = dev_props["name"].operator String() + " (devicectl, " + ((conn_props["transportType"] == "localNetwork") ? "network" : "wired") + ")"; + nd.wifi = conn_props["transportType"] == "localNetwork"; + ldevices.push_back(nd); + } + } + } + } + } + + // Update device list. + { + MutexLock lock(ea->device_lock); + + bool different = false; + + if (ea->devices.size() != ldevices.size()) { + different = true; + } else { + for (int i = 0; i < ea->devices.size(); i++) { + if (ea->devices[i].id != ldevices[i].id) { + different = true; + break; + } + } + } + + if (different) { + ea->devices = ldevices; + ea->devices_changed.set(); + } + } + + uint64_t sleep = 200; + 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(1000 * sleep); + if (ea->quit_request.is_set()) { + break; + } + } + } +} + +void EditorExportPlatformAppleEmbedded::_update_preset_status() { + const int preset_count = EditorExport::get_singleton()->get_export_preset_count(); + bool has_runnable = false; + + for (int i = 0; i < preset_count; i++) { + const Ref &preset = EditorExport::get_singleton()->get_export_preset(i); + if (preset->get_platform() == this && preset->is_runnable()) { + has_runnable = true; + break; + } + } + + if (has_runnable) { + has_runnable_preset.set(); + } else { + has_runnable_preset.clear(); + } + devices_changed.set(); +} +#endif + +Error EditorExportPlatformAppleEmbedded::run(const Ref &p_preset, int p_device, BitField p_debug_flags) { +#ifdef MACOS_ENABLED + ERR_FAIL_INDEX_V(p_device, devices.size(), ERR_INVALID_PARAMETER); + + String can_export_error; + bool can_export_missing_templates; + if (!can_export(p_preset, can_export_error, can_export_missing_templates)) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Run"), can_export_error); + return ERR_UNCONFIGURED; + } + + MutexLock lock(device_lock); + + EditorProgress ep("run", vformat(TTR("Running on %s"), devices[p_device].name), 3); + + String id = "tmpexport." + uitos(OS::get_singleton()->get_unix_time()); + + Ref filesystem_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + ERR_FAIL_COND_V_MSG(filesystem_da.is_null(), ERR_CANT_CREATE, "Cannot create DirAccess for path '" + EditorPaths::get_singleton()->get_temp_dir() + "'."); + filesystem_da->make_dir_recursive(EditorPaths::get_singleton()->get_temp_dir().path_join(id)); + String tmp_export_path = EditorPaths::get_singleton()->get_temp_dir().path_join(id).path_join("export.ipa"); + +#define CLEANUP_AND_RETURN(m_err) \ + { \ + if (filesystem_da->change_dir(EditorPaths::get_singleton()->get_temp_dir().path_join(id)) == OK) { \ + filesystem_da->erase_contents_recursive(); \ + filesystem_da->change_dir(".."); \ + filesystem_da->remove(id); \ + } \ + return m_err; \ + } \ + ((void)0) + + Device dev = devices[p_device]; + + // Export before sending to device. + Error err = _export_project_helper(p_preset, true, tmp_export_path, p_debug_flags, true); + + if (err != OK) { + CLEANUP_AND_RETURN(err); + } + + Vector cmd_args_list; + String host = EDITOR_GET("network/debug/remote_host"); + int remote_port = (int)EDITOR_GET("network/debug/remote_port"); + + if (p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST)) { + host = "localhost"; + } + + if (p_debug_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT)) { + int port = EDITOR_GET("filesystem/file_server/port"); + String passwd = EDITOR_GET("filesystem/file_server/password"); + cmd_args_list.push_back("--remote-fs"); + cmd_args_list.push_back(host + ":" + itos(port)); + if (!passwd.is_empty()) { + cmd_args_list.push_back("--remote-fs-password"); + cmd_args_list.push_back(passwd); + } + } + + if (p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG)) { + cmd_args_list.push_back("--remote-debug"); + + cmd_args_list.push_back(get_debug_protocol() + host + ":" + String::num_int64(remote_port)); + + List breakpoints; + ScriptEditor::get_singleton()->get_breakpoints(&breakpoints); + + if (breakpoints.size()) { + cmd_args_list.push_back("--breakpoints"); + String bpoints; + for (const List::Element *E = breakpoints.front(); E; E = E->next()) { + bpoints += E->get().replace(" ", "%20"); + if (E->next()) { + bpoints += ","; + } + } + + cmd_args_list.push_back(bpoints); + } + } + + if (p_debug_flags.has_flag(DEBUG_FLAG_VIEW_COLLISIONS)) { + cmd_args_list.push_back("--debug-collisions"); + } + + if (p_debug_flags.has_flag(DEBUG_FLAG_VIEW_NAVIGATION)) { + cmd_args_list.push_back("--debug-navigation"); + } + + if (dev.use_ios_deploy) { + // Deploy and run on real device (via ios-deploy). + if (ep.step("Installing and running on device...", 4)) { + CLEANUP_AND_RETURN(ERR_SKIP); + } else { + List args; + args.push_back("-u"); + args.push_back("-I"); + args.push_back("--id"); + args.push_back(dev.id); + args.push_back("--justlaunch"); + args.push_back("--bundle"); + args.push_back(EditorPaths::get_singleton()->get_temp_dir().path_join(id).path_join("export.xcarchive/Products/Applications/export.app")); + String app_args; + for (const String &E : cmd_args_list) { + app_args += E + " "; + } + if (!app_args.is_empty()) { + args.push_back("--args"); + args.push_back(app_args); + } + + String idepl = EDITOR_GET("export/" + get_platform_name() + "/ios_deploy"); + if (idepl.is_empty()) { + idepl = "ios-deploy"; + } + String log; + int ec; + err = OS::get_singleton()->execute(idepl, args, &log, &ec, true); + if (err != OK) { + add_message(EXPORT_MESSAGE_WARNING, TTR("Run"), TTR("Could not start ios-deploy executable.")); + CLEANUP_AND_RETURN(err); + } + if (ec != 0) { + print_line("ios-deploy:\n" + log); + add_message(EXPORT_MESSAGE_ERROR, TTR("Run"), TTR("Installation/running failed, see editor log for details.")); + CLEANUP_AND_RETURN(ERR_UNCONFIGURED); + } + } + } else { + // Deploy and run on real device (via Xcode). + if (ep.step("Installing to device...", 3)) { + CLEANUP_AND_RETURN(ERR_SKIP); + } else { + List args; + args.push_back("devicectl"); + args.push_back("device"); + args.push_back("install"); + args.push_back("app"); + args.push_back("-d"); + args.push_back(dev.id); + args.push_back(EditorPaths::get_singleton()->get_temp_dir().path_join(id).path_join("export.xcarchive/Products/Applications/export.app")); + + String log; + int ec; + err = OS::get_singleton()->execute("xcrun", args, &log, &ec, true); + if (err != OK) { + add_message(EXPORT_MESSAGE_WARNING, TTR("Run"), TTR("Could not start device executable.")); + CLEANUP_AND_RETURN(err); + } + if (ec != 0) { + print_line("device install:\n" + log); + add_message(EXPORT_MESSAGE_ERROR, TTR("Run"), TTR("Installation failed, see editor log for details.")); + CLEANUP_AND_RETURN(ERR_UNCONFIGURED); + } + } + + if (ep.step("Running on device...", 4)) { + CLEANUP_AND_RETURN(ERR_SKIP); + } else { + List args; + args.push_back("devicectl"); + args.push_back("device"); + args.push_back("process"); + args.push_back("launch"); + args.push_back("--terminate-existing"); + args.push_back("-d"); + args.push_back(dev.id); + args.push_back(p_preset->get("application/bundle_identifier")); + for (const String &E : cmd_args_list) { + args.push_back(E); + } + + String log; + int ec; + err = OS::get_singleton()->execute("xcrun", args, &log, &ec, true); + if (err != OK) { + add_message(EXPORT_MESSAGE_WARNING, TTR("Run"), TTR("Could not start devicectl executable.")); + CLEANUP_AND_RETURN(err); + } + if (ec != 0) { + print_line("devicectl launch:\n" + log); + add_message(EXPORT_MESSAGE_ERROR, TTR("Run"), TTR("Running failed, see editor log for details.")); + } + } + } + + CLEANUP_AND_RETURN(OK); + +#undef CLEANUP_AND_RETURN +#else + return ERR_UNCONFIGURED; +#endif +} + +EditorExportPlatformAppleEmbedded::EditorExportPlatformAppleEmbedded(const char *p_platform_logo_svg, const char *p_run_icon_svg) { + if (EditorNode::get_singleton()) { + Ref img = memnew(Image); + const bool upsample = !Math::is_equal_approx(Math::round(EDSCALE), EDSCALE); + + ImageLoaderSVG::create_image_from_string(img, p_platform_logo_svg, EDSCALE, upsample, false); + logo = ImageTexture::create_from_image(img); + + ImageLoaderSVG::create_image_from_string(img, p_run_icon_svg, EDSCALE, upsample, false); + run_icon = ImageTexture::create_from_image(img); + + plugins_changed.set(); + devices_changed.set(); +#ifdef MACOS_ENABLED + _update_preset_status(); + check_for_changes_thread.start(_check_for_changes_poll_thread, this); +#endif + } +} + +EditorExportPlatformAppleEmbedded::~EditorExportPlatformAppleEmbedded() { +#ifdef MACOS_ENABLED + quit_request.set(); + if (check_for_changes_thread.is_started()) { + check_for_changes_thread.wait_to_finish(); + } +#endif +} diff --git a/editor/export/editor_export_platform_apple_embedded.h b/editor/export/editor_export_platform_apple_embedded.h new file mode 100644 index 000000000000..48c0c866eb83 --- /dev/null +++ b/editor/export/editor_export_platform_apple_embedded.h @@ -0,0 +1,319 @@ +/**************************************************************************/ +/* editor_export_platform_apple_embedded.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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. */ +/**************************************************************************/ + +#pragma once + +#include "plugin_config_apple_embedded.h" + +#include "core/config/project_settings.h" +#include "core/io/file_access.h" +#include "core/io/image_loader.h" +#include "core/io/marshalls.h" +#include "core/io/resource_saver.h" +#include "core/io/zip_io.h" +#include "core/os/os.h" +#include "core/templates/safe_refcount.h" +#include "editor/editor_settings.h" +#include "editor/export/editor_export_platform.h" +#include "main/splash.gen.h" +#include "scene/resources/image_texture.h" + +#include + +// Optional environment variables for defining confidential information. If any +// of these is set, they will override the values set in the credentials file. +const String ENV_APPLE_PLATFORM_PROFILE_UUID_DEBUG = "GODOT_APPLE_PLATFORM_PROVISIONING_PROFILE_UUID_DEBUG"; +const String ENV_APPLE_PLATFORM_PROFILE_UUID_RELEASE = "GODOT_APPLE_PLATFORM_PROVISIONING_PROFILE_UUID_RELEASE"; +const String ENV_APPLE_PLATFORM_PROFILE_SPECIFIER_DEBUG = "GODOT_APPLE_PLATFORM_PROFILE_SPECIFIER_DEBUG"; +const String ENV_APPLE_PLATFORM_PROFILE_SPECIFIER_RELEASE = "GODOT_APPLE_PLATFORM_PROFILE_SPECIFIER_RELEASE"; + +static const String storyboard_image_scale_mode[] = { + "center", + "scaleAspectFit", + "scaleAspectFill", + "scaleToFill", +}; + +class EditorExportPlatformAppleEmbedded : public EditorExportPlatform { + GDCLASS(EditorExportPlatformAppleEmbedded, EditorExportPlatform); + + Ref logo; + Ref run_icon; + + // Plugins + mutable SafeFlag plugins_changed; + SafeFlag devices_changed; + + struct Device { + String id; + String name; + bool wifi = false; + bool use_ios_deploy = false; + }; + + Vector devices; + Mutex device_lock; + + Mutex plugins_lock; + mutable Vector plugins; +#ifdef MACOS_ENABLED + Thread check_for_changes_thread; + SafeFlag quit_request; + SafeFlag has_runnable_preset; + + static bool _check_xcode_install(); + static void _check_for_changes_poll_thread(void *ud); + void _update_preset_status(); +#endif + + typedef Error (*FileHandler)(String p_file, void *p_userdata); + static Error _walk_dir_recursive(Ref &p_da, FileHandler p_handler, void *p_userdata); + static Error _codesign(String p_file, void *p_userdata); + + struct AppleEmbeddedConfigData { + String pkg_name; + String binary_name; + String plist_content; + String architectures; + String linker_flags; + String cpp_code; + String modules_buildfile; + String modules_fileref; + String modules_buildphase; + String modules_buildgrp; + Vector capabilities; + bool use_swift_runtime; + }; + + struct ExportArchitecture { + String name; + bool is_default = false; + + ExportArchitecture() {} + + ExportArchitecture(String p_name, bool p_is_default) { + name = p_name; + is_default = p_is_default; + } + }; + + struct AppleEmbeddedExportAsset { + String exported_path; + bool is_framework = false; // framework is anything linked to the binary, otherwise it's a resource + bool should_embed = false; + }; + + String _get_additional_plist_content(); + String _get_linker_flags(); + String _get_cpp_code(); + void _fix_config_file(const Ref &p_preset, Vector &pfile, const AppleEmbeddedConfigData &p_config, bool p_debug); + + Vector _get_supported_architectures() const; + Vector _get_preset_architectures(const Ref &p_preset) const; + + void _check_xcframework_content(const String &p_path, int &r_total_libs, int &r_static_libs, int &r_dylibs, int &r_frameworks) const; + Error _convert_to_framework(const String &p_source, const String &p_destination, const String &p_id) const; + + void _add_assets_to_project(const String &p_out_dir, const Ref &p_preset, Vector &p_project_data, const Vector &p_additional_assets); + Error _export_additional_assets(const Ref &p_preset, const String &p_out_dir, const Vector &p_assets, bool p_is_framework, bool p_should_embed, Vector &r_exported_assets); + Error _copy_asset(const Ref &p_preset, 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 Ref &p_preset, const String &p_out_dir, const Vector &p_libraries, Vector &r_exported_assets); + Error _export_apple_embedded_plugins(const Ref &p_preset, AppleEmbeddedConfigData &p_config_data, const String &dest_dir, Vector &r_exported_assets, bool p_debug); + + Error _export_project_helper(const Ref &p_preset, bool p_debug, const String &p_path, BitField p_flags, bool p_oneclick); + + bool is_package_name_valid(const String &p_package, String *r_error = nullptr) const; + +protected: + struct IconInfo { + const char *preset_key; + const char *idiom; + const char *export_name; + const char *actual_size_side; + const char *scale; + const char *unscaled_size; + bool force_opaque; + }; + + void _blend_and_rotate(Ref &p_dst, Ref &p_src, bool p_rot); + + virtual Error _export_loading_screen_file(const Ref &p_preset, const String &p_dest_dir) { return OK; } + virtual Error _export_icons(const Ref &p_preset, const String &p_iconset_dir) { return OK; } + + virtual String get_platform_name() const = 0; + virtual String get_sdk_name() const = 0; + virtual String get_minimum_deployment_target() const = 0; + + virtual void get_preset_features(const Ref &p_preset, List *r_features) const override; + virtual void get_export_options(List *r_options) const override; + virtual bool get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const override; + virtual String get_export_option_warning(const EditorExportPreset *p_preset, const StringName &p_name) const override; + + virtual Vector get_icon_infos() const = 0; + + void _notification(int p_what); + + virtual void get_platform_features(List *r_features) const override { + r_features->push_back("mobile"); + r_features->push_back("apple_embedded"); + } + +public: + virtual Ref get_logo() const override { return logo; } + virtual Ref get_run_icon() const override { return run_icon; } + + virtual int get_options_count() const override; + virtual String get_options_tooltip() const override; + virtual Ref get_option_icon(int p_index) const override; + virtual String get_option_label(int p_index) const override; + virtual String get_option_tooltip(int p_index) const override; + virtual Error run(const Ref &p_preset, int p_device, BitField p_debug_flags) override; + + virtual bool poll_export() override { + bool dc = devices_changed.is_set(); + if (dc) { + // don't clear unless we're reporting true, to avoid race + devices_changed.clear(); + } + return dc; + } + + virtual bool should_update_export_options() override { + bool export_options_changed = plugins_changed.is_set(); + if (export_options_changed) { + // don't clear unless we're reporting true, to avoid race + plugins_changed.clear(); + } + return export_options_changed; + } + + virtual List get_binary_extensions(const Ref &p_preset) const override { + List list; + if (p_preset.is_valid()) { + bool project_only = p_preset->get("application/export_project_only"); + if (project_only) { + list.push_back("xcodeproj"); + } else { + list.push_back("ipa"); + } + } + return list; + } + + virtual Error export_project(const Ref &p_preset, bool p_debug, const String &p_path, BitField p_flags = 0) override; + + virtual bool has_valid_export_configuration(const Ref &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override; + virtual bool has_valid_project_configuration(const Ref &p_preset, String &r_error) const override; + + virtual void resolve_platform_feature_priorities(const Ref &p_preset, HashSet &p_features) override { + } + + EditorExportPlatformAppleEmbedded(const char *p_platform_logo_svg, const char *p_run_icon_svg); + ~EditorExportPlatformAppleEmbedded(); + + /// 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; + Ref da = DirAccess::open(p_path); + if (da.is_valid()) { + da->list_dir_begin(); + while (true) { + String file = da->get_next(); + if (file.is_empty()) { + break; + } + + if (file == "." || file == "..") { + continue; + } + + if (da->current_is_hidden()) { + continue; + } + + if (da->current_is_dir()) { + if (p_check_directories) { + Vector directory_files = list_plugin_config_files(p_path.path_join(file), false); + for (int i = 0; i < directory_files.size(); ++i) { + dir_files.push_back(file.path_join(directory_files[i])); + } + } + + continue; + } + + if (file.ends_with(PluginConfigAppleEmbedded::PLUGIN_CONFIG_EXT)) { + dir_files.push_back(file); + } + } + da->list_dir_end(); + } + + return dir_files; + } + + static Vector get_plugins(const String &p_platform_name) { + Vector loaded_plugins; + + String plugins_dir = ProjectSettings::get_singleton()->get_resource_path().path_join(p_platform_name + "/plugins"); + + if (DirAccess::exists(plugins_dir)) { + Vector plugins_filenames = list_plugin_config_files(plugins_dir, true); + + if (!plugins_filenames.is_empty()) { + Ref config_file; + for (int i = 0; i < plugins_filenames.size(); i++) { + PluginConfigAppleEmbedded config = PluginConfigAppleEmbedded::load_plugin_config(config_file, plugins_dir.path_join(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 String &p_platform_name, const Ref &p_presets) { + Vector enabled_plugins; + Vector all_plugins = get_plugins(p_platform_name); + for (int i = 0; i < all_plugins.size(); i++) { + PluginConfigAppleEmbedded plugin = all_plugins[i]; + bool enabled = p_presets->get("plugins/" + plugin.name); + if (enabled) { + enabled_plugins.push_back(plugin); + } + } + + return enabled_plugins; + } +}; diff --git a/editor/export/editor_export_plugin.cpp b/editor/export/editor_export_plugin.cpp index dea7c45a7f84..fbabd7d88d3d 100644 --- a/editor/export/editor_export_plugin.cpp +++ b/editor/export/editor_export_plugin.cpp @@ -67,55 +67,55 @@ void EditorExportPlugin::_add_shared_object(const SharedObject &p_shared_object) shared_objects.push_back(p_shared_object); } -void EditorExportPlugin::add_ios_framework(const String &p_path) { - ios_frameworks.push_back(p_path); +void EditorExportPlugin::add_apple_embedded_platform_framework(const String &p_path) { + apple_embedded_platform_frameworks.push_back(p_path); } -void EditorExportPlugin::add_ios_embedded_framework(const String &p_path) { - ios_embedded_frameworks.push_back(p_path); +void EditorExportPlugin::add_apple_embedded_platform_embedded_framework(const String &p_path) { + apple_embedded_platform_embedded_frameworks.push_back(p_path); } -Vector EditorExportPlugin::get_ios_frameworks() const { - return ios_frameworks; +Vector EditorExportPlugin::get_apple_embedded_platform_frameworks() const { + return apple_embedded_platform_frameworks; } -Vector EditorExportPlugin::get_ios_embedded_frameworks() const { - return ios_embedded_frameworks; +Vector EditorExportPlugin::get_apple_embedded_platform_embedded_frameworks() const { + return apple_embedded_platform_embedded_frameworks; } -void EditorExportPlugin::add_ios_plist_content(const String &p_plist_content) { - ios_plist_content += p_plist_content + "\n"; +void EditorExportPlugin::add_apple_embedded_platform_plist_content(const String &p_plist_content) { + apple_embedded_platform_plist_content += p_plist_content + "\n"; } -String EditorExportPlugin::get_ios_plist_content() const { - return ios_plist_content; +String EditorExportPlugin::get_apple_embedded_platform_plist_content() const { + return apple_embedded_platform_plist_content; } -void EditorExportPlugin::add_ios_linker_flags(const String &p_flags) { - if (ios_linker_flags.length() > 0) { - ios_linker_flags += ' '; +void EditorExportPlugin::add_apple_embedded_platform_linker_flags(const String &p_flags) { + if (apple_embedded_platform_linker_flags.length() > 0) { + apple_embedded_platform_linker_flags += ' '; } - ios_linker_flags += p_flags; + apple_embedded_platform_linker_flags += p_flags; } -String EditorExportPlugin::get_ios_linker_flags() const { - return ios_linker_flags; +String EditorExportPlugin::get_apple_embedded_platform_linker_flags() const { + return apple_embedded_platform_linker_flags; } -void EditorExportPlugin::add_ios_bundle_file(const String &p_path) { - ios_bundle_files.push_back(p_path); +void EditorExportPlugin::add_apple_embedded_platform_bundle_file(const String &p_path) { + apple_embedded_platform_bundle_files.push_back(p_path); } -Vector EditorExportPlugin::get_ios_bundle_files() const { - return ios_bundle_files; +Vector EditorExportPlugin::get_apple_embedded_platform_bundle_files() const { + return apple_embedded_platform_bundle_files; } -void EditorExportPlugin::add_ios_cpp_code(const String &p_code) { - ios_cpp_code += p_code; +void EditorExportPlugin::add_apple_embedded_platform_cpp_code(const String &p_code) { + apple_embedded_platform_cpp_code += p_code; } -String EditorExportPlugin::get_ios_cpp_code() const { - return ios_cpp_code; +String EditorExportPlugin::get_apple_embedded_platform_cpp_code() const { + return apple_embedded_platform_cpp_code; } void EditorExportPlugin::add_macos_plugin_file(const String &p_path) { @@ -126,12 +126,12 @@ const Vector &EditorExportPlugin::get_macos_plugin_files() const { return macos_plugin_files; } -void EditorExportPlugin::add_ios_project_static_lib(const String &p_path) { - ios_project_static_libs.push_back(p_path); +void EditorExportPlugin::add_apple_embedded_platform_project_static_lib(const String &p_path) { + apple_embedded_platform_project_static_libs.push_back(p_path); } -Vector EditorExportPlugin::get_ios_project_static_libs() const { - return ios_project_static_libs; +Vector EditorExportPlugin::get_apple_embedded_platform_project_static_libs() const { + return apple_embedded_platform_project_static_libs; } Variant EditorExportPlugin::get_option(const StringName &p_name) const { @@ -328,14 +328,26 @@ void EditorExportPlugin::skip() { void EditorExportPlugin::_bind_methods() { ClassDB::bind_method(D_METHOD("add_shared_object", "path", "tags", "target"), &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); - 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); + + ClassDB::bind_method(D_METHOD("add_apple_embedded_platform_project_static_lib", "path"), &EditorExportPlugin::add_apple_embedded_platform_project_static_lib); + ClassDB::bind_method(D_METHOD("add_apple_embedded_platform_framework", "path"), &EditorExportPlugin::add_apple_embedded_platform_framework); + ClassDB::bind_method(D_METHOD("add_apple_embedded_platform_embedded_framework", "path"), &EditorExportPlugin::add_apple_embedded_platform_embedded_framework); + ClassDB::bind_method(D_METHOD("add_apple_embedded_platform_plist_content", "plist_content"), &EditorExportPlugin::add_apple_embedded_platform_plist_content); + ClassDB::bind_method(D_METHOD("add_apple_embedded_platform_linker_flags", "flags"), &EditorExportPlugin::add_apple_embedded_platform_linker_flags); + ClassDB::bind_method(D_METHOD("add_apple_embedded_platform_bundle_file", "path"), &EditorExportPlugin::add_apple_embedded_platform_bundle_file); + ClassDB::bind_method(D_METHOD("add_apple_embedded_platform_cpp_code", "code"), &EditorExportPlugin::add_apple_embedded_platform_cpp_code); + +#ifndef DISABLE_DEPRECATED + ClassDB::bind_method(D_METHOD("add_ios_project_static_lib", "path"), &EditorExportPlugin::add_apple_embedded_platform_project_static_lib); + ClassDB::bind_method(D_METHOD("add_ios_framework", "path"), &EditorExportPlugin::add_apple_embedded_platform_framework); + ClassDB::bind_method(D_METHOD("add_ios_embedded_framework", "path"), &EditorExportPlugin::add_apple_embedded_platform_embedded_framework); + ClassDB::bind_method(D_METHOD("add_ios_plist_content", "plist_content"), &EditorExportPlugin::add_apple_embedded_platform_plist_content); + ClassDB::bind_method(D_METHOD("add_ios_linker_flags", "flags"), &EditorExportPlugin::add_apple_embedded_platform_linker_flags); + ClassDB::bind_method(D_METHOD("add_ios_bundle_file", "path"), &EditorExportPlugin::add_apple_embedded_platform_bundle_file); + ClassDB::bind_method(D_METHOD("add_ios_cpp_code", "code"), &EditorExportPlugin::add_apple_embedded_platform_cpp_code); +#endif + ClassDB::bind_method(D_METHOD("add_macos_plugin_file", "path"), &EditorExportPlugin::add_macos_plugin_file); ClassDB::bind_method(D_METHOD("skip"), &EditorExportPlugin::skip); ClassDB::bind_method(D_METHOD("get_option", "name"), &EditorExportPlugin::get_option); diff --git a/editor/export/editor_export_plugin.h b/editor/export/editor_export_plugin.h index 5d60951b69c7..d721760d7296 100644 --- a/editor/export/editor_export_plugin.h +++ b/editor/export/editor_export_plugin.h @@ -53,13 +53,13 @@ class EditorExportPlugin : public RefCounted { Vector extra_files; bool skipped = false; - Vector ios_frameworks; - Vector ios_embedded_frameworks; - Vector ios_project_static_libs; - String ios_plist_content; - String ios_linker_flags; - Vector ios_bundle_files; - String ios_cpp_code; + Vector apple_embedded_platform_frameworks; + Vector apple_embedded_platform_embedded_frameworks; + Vector apple_embedded_platform_project_static_libs; + String apple_embedded_platform_plist_content; + String apple_embedded_platform_linker_flags; + Vector apple_embedded_platform_bundle_files; + String apple_embedded_platform_cpp_code; Vector macos_plugin_files; @@ -70,12 +70,12 @@ class EditorExportPlugin : public RefCounted { } _FORCE_INLINE_ void _export_end_clear() { - ios_frameworks.clear(); - ios_embedded_frameworks.clear(); - ios_bundle_files.clear(); - ios_plist_content = ""; - ios_linker_flags = ""; - ios_cpp_code = ""; + apple_embedded_platform_frameworks.clear(); + apple_embedded_platform_embedded_frameworks.clear(); + apple_embedded_platform_bundle_files.clear(); + apple_embedded_platform_plist_content = ""; + apple_embedded_platform_linker_flags = ""; + apple_embedded_platform_cpp_code = ""; macos_plugin_files.clear(); } @@ -95,13 +95,13 @@ class EditorExportPlugin : public RefCounted { void add_shared_object(const String &p_path, const Vector &tags, const String &p_target = String()); void _add_shared_object(const SharedObject &p_shared_object); - void add_ios_framework(const String &p_path); - void add_ios_embedded_framework(const String &p_path); - void add_ios_project_static_lib(const String &p_path); - void add_ios_plist_content(const String &p_plist_content); - void add_ios_linker_flags(const String &p_flags); - void add_ios_bundle_file(const String &p_path); - void add_ios_cpp_code(const String &p_code); + void add_apple_embedded_platform_framework(const String &p_path); + void add_apple_embedded_platform_embedded_framework(const String &p_path); + void add_apple_embedded_platform_project_static_lib(const String &p_path); + void add_apple_embedded_platform_plist_content(const String &p_plist_content); + void add_apple_embedded_platform_linker_flags(const String &p_flags); + void add_apple_embedded_platform_bundle_file(const String &p_path); + void add_apple_embedded_platform_cpp_code(const String &p_code); void add_macos_plugin_file(const String &p_path); void skip(); @@ -177,13 +177,13 @@ class EditorExportPlugin : public RefCounted { virtual String get_android_manifest_element_contents(const Ref &p_export_platform, bool p_debug) const; virtual PackedByteArray update_android_prebuilt_manifest(const Ref &p_export_platform, const PackedByteArray &p_manifest_data) const; - Vector get_ios_frameworks() const; - Vector get_ios_embedded_frameworks() const; - Vector get_ios_project_static_libs() const; - String get_ios_plist_content() const; - String get_ios_linker_flags() const; - Vector get_ios_bundle_files() const; - String get_ios_cpp_code() const; + Vector get_apple_embedded_platform_frameworks() const; + Vector get_apple_embedded_platform_embedded_frameworks() const; + Vector get_apple_embedded_platform_project_static_libs() const; + String get_apple_embedded_platform_plist_content() const; + String get_apple_embedded_platform_linker_flags() const; + Vector get_apple_embedded_platform_bundle_files() const; + String get_apple_embedded_platform_cpp_code() const; const Vector &get_macos_plugin_files() const; Variant get_option(const StringName &p_name) const; }; diff --git a/editor/export/macho.h b/editor/export/macho.h index 0932c2769668..4a1faa1f1fc6 100644 --- a/editor/export/macho.h +++ b/editor/export/macho.h @@ -111,6 +111,8 @@ class MachO : public RefCounted { PLATFORM_TVOSSIMULATOR = 8, PLATFORM_WATCHOSSIMULATOR = 9, PLATFORM_DRIVERKIT = 10, + PLATFORM_VISIONOS = 11, + PLATFORM_VISIONOSSIMULATOR = 12, }; struct LoadCommandHeader { diff --git a/platform/ios/export/godot_plugin_config.cpp b/editor/export/plugin_config_apple_embedded.cpp similarity index 67% rename from platform/ios/export/godot_plugin_config.cpp rename to editor/export/plugin_config_apple_embedded.cpp index 96938ff567e9..eb7b6ebd1427 100644 --- a/platform/ios/export/godot_plugin_config.cpp +++ b/editor/export/plugin_config_apple_embedded.cpp @@ -1,5 +1,5 @@ /**************************************************************************/ -/* godot_plugin_config.cpp */ +/* plugin_config_apple_embedded.cpp */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,13 +28,13 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#include "godot_plugin_config.h" +#include "plugin_config_apple_embedded.h" #include "core/config/project_settings.h" #include "core/io/dir_access.h" #include "core/io/file_access.h" -String PluginConfigIOS::resolve_local_dependency_path(String plugin_config_dir, String dependency_path) { +String PluginConfigAppleEmbedded::resolve_local_dependency_path(String plugin_config_dir, String dependency_path) { String absolute_path; if (dependency_path.is_empty()) { @@ -51,7 +51,7 @@ String PluginConfigIOS::resolve_local_dependency_path(String plugin_config_dir, return absolute_path.replace(res_path, "res://"); } -String PluginConfigIOS::resolve_system_dependency_path(String dependency_path) { +String PluginConfigAppleEmbedded::resolve_system_dependency_path(String dependency_path) { String absolute_path; if (dependency_path.is_empty()) { @@ -67,7 +67,7 @@ String PluginConfigIOS::resolve_system_dependency_path(String dependency_path) { return system_path.path_join(dependency_path); } -Vector PluginConfigIOS::resolve_local_dependencies(String plugin_config_dir, Vector p_paths) { +Vector PluginConfigAppleEmbedded::resolve_local_dependencies(String plugin_config_dir, Vector p_paths) { Vector paths; for (int i = 0; i < p_paths.size(); i++) { @@ -83,7 +83,7 @@ Vector PluginConfigIOS::resolve_local_dependencies(String plugin_config_ return paths; } -Vector PluginConfigIOS::resolve_system_dependencies(Vector p_paths) { +Vector PluginConfigAppleEmbedded::resolve_system_dependencies(Vector p_paths) { Vector paths; for (int i = 0; i < p_paths.size(); i++) { @@ -99,7 +99,7 @@ Vector PluginConfigIOS::resolve_system_dependencies(Vector p_pat return paths; } -bool PluginConfigIOS::validate_plugin(PluginConfigIOS &plugin_config) { +bool PluginConfigAppleEmbedded::validate_plugin(PluginConfigAppleEmbedded &plugin_config) { bool valid_name = !plugin_config.name.is_empty(); bool valid_binary_name = !plugin_config.binary.is_empty(); bool valid_initialize = !plugin_config.initialization_method.is_empty(); @@ -134,7 +134,7 @@ bool PluginConfigIOS::validate_plugin(PluginConfigIOS &plugin_config) { return plugin_config.valid_config; } -String PluginConfigIOS::get_plugin_main_binary(PluginConfigIOS &plugin_config, bool p_debug) { +String PluginConfigAppleEmbedded::get_plugin_main_binary(PluginConfigAppleEmbedded &plugin_config, bool p_debug) { if (!plugin_config.supports_targets) { return plugin_config.binary; } @@ -147,7 +147,7 @@ String PluginConfigIOS::get_plugin_main_binary(PluginConfigIOS &plugin_config, b return plugin_binary_dir.path_join(plugin_file); } -uint64_t PluginConfigIOS::get_plugin_modification_time(const PluginConfigIOS &plugin_config, const String &config_path) { +uint64_t PluginConfigAppleEmbedded::get_plugin_modification_time(const PluginConfigAppleEmbedded &plugin_config, const String &config_path) { uint64_t last_updated = FileAccess::get_modified_time(config_path); if (!plugin_config.supports_targets) { @@ -166,8 +166,8 @@ uint64_t PluginConfigIOS::get_plugin_modification_time(const PluginConfigIOS &pl return last_updated; } -PluginConfigIOS PluginConfigIOS::load_plugin_config(Ref config_file, const String &path) { - PluginConfigIOS plugin_config = {}; +PluginConfigAppleEmbedded PluginConfigAppleEmbedded::load_plugin_config(Ref config_file, const String &path) { + PluginConfigAppleEmbedded plugin_config = {}; if (config_file.is_null()) { return plugin_config; @@ -183,19 +183,19 @@ PluginConfigIOS PluginConfigIOS::load_plugin_config(Ref config_file, String config_base_dir = path.get_base_dir(); - plugin_config.name = config_file->get_value(PluginConfigIOS::CONFIG_SECTION, PluginConfigIOS::CONFIG_NAME_KEY, String()); - plugin_config.use_swift_runtime = config_file->get_value(PluginConfigIOS::CONFIG_SECTION, PluginConfigIOS::CONFIG_USE_SWIFT_KEY, false); - plugin_config.initialization_method = config_file->get_value(PluginConfigIOS::CONFIG_SECTION, PluginConfigIOS::CONFIG_INITIALIZE_KEY, String()); - plugin_config.deinitialization_method = config_file->get_value(PluginConfigIOS::CONFIG_SECTION, PluginConfigIOS::CONFIG_DEINITIALIZE_KEY, String()); + plugin_config.name = config_file->get_value(PluginConfigAppleEmbedded::CONFIG_SECTION, PluginConfigAppleEmbedded::CONFIG_NAME_KEY, String()); + plugin_config.use_swift_runtime = config_file->get_value(PluginConfigAppleEmbedded::CONFIG_SECTION, PluginConfigAppleEmbedded::CONFIG_USE_SWIFT_KEY, false); + plugin_config.initialization_method = config_file->get_value(PluginConfigAppleEmbedded::CONFIG_SECTION, PluginConfigAppleEmbedded::CONFIG_INITIALIZE_KEY, String()); + plugin_config.deinitialization_method = config_file->get_value(PluginConfigAppleEmbedded::CONFIG_SECTION, PluginConfigAppleEmbedded::CONFIG_DEINITIALIZE_KEY, String()); - String binary_path = config_file->get_value(PluginConfigIOS::CONFIG_SECTION, PluginConfigIOS::CONFIG_BINARY_KEY, String()); + String binary_path = config_file->get_value(PluginConfigAppleEmbedded::CONFIG_SECTION, PluginConfigAppleEmbedded::CONFIG_BINARY_KEY, String()); plugin_config.binary = resolve_local_dependency_path(config_base_dir, binary_path); - if (config_file->has_section(PluginConfigIOS::DEPENDENCIES_SECTION)) { - Vector linked_dependencies = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_LINKED_KEY, Vector()); - Vector embedded_dependencies = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_EMBEDDED_KEY, Vector()); - Vector system_dependencies = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_SYSTEM_KEY, Vector()); - Vector files = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_FILES_KEY, Vector()); + if (config_file->has_section(PluginConfigAppleEmbedded::DEPENDENCIES_SECTION)) { + Vector linked_dependencies = config_file->get_value(PluginConfigAppleEmbedded::DEPENDENCIES_SECTION, PluginConfigAppleEmbedded::DEPENDENCIES_LINKED_KEY, Vector()); + Vector embedded_dependencies = config_file->get_value(PluginConfigAppleEmbedded::DEPENDENCIES_SECTION, PluginConfigAppleEmbedded::DEPENDENCIES_EMBEDDED_KEY, Vector()); + Vector system_dependencies = config_file->get_value(PluginConfigAppleEmbedded::DEPENDENCIES_SECTION, PluginConfigAppleEmbedded::DEPENDENCIES_SYSTEM_KEY, Vector()); + Vector files = config_file->get_value(PluginConfigAppleEmbedded::DEPENDENCIES_SECTION, PluginConfigAppleEmbedded::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); @@ -203,77 +203,77 @@ PluginConfigIOS PluginConfigIOS::load_plugin_config(Ref config_file, plugin_config.files_to_copy = resolve_local_dependencies(config_base_dir, files); - plugin_config.capabilities = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_CAPABILITIES_KEY, Vector()); + plugin_config.capabilities = config_file->get_value(PluginConfigAppleEmbedded::DEPENDENCIES_SECTION, PluginConfigAppleEmbedded::DEPENDENCIES_CAPABILITIES_KEY, Vector()); - plugin_config.linker_flags = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_LINKER_FLAGS, Vector()); + plugin_config.linker_flags = config_file->get_value(PluginConfigAppleEmbedded::DEPENDENCIES_SECTION, PluginConfigAppleEmbedded::DEPENDENCIES_LINKER_FLAGS, Vector()); } - if (config_file->has_section(PluginConfigIOS::PLIST_SECTION)) { - Vector keys = config_file->get_section_keys(PluginConfigIOS::PLIST_SECTION); + if (config_file->has_section(PluginConfigAppleEmbedded::PLIST_SECTION)) { + Vector keys = config_file->get_section_keys(PluginConfigAppleEmbedded::PLIST_SECTION); for (const String &key : keys) { Vector key_components = key.split(":"); String key_value = ""; - PluginConfigIOS::PlistItemType key_type = PluginConfigIOS::PlistItemType::UNKNOWN; + PluginConfigAppleEmbedded::PlistItemType key_type = PluginConfigAppleEmbedded::PlistItemType::UNKNOWN; if (key_components.size() == 1) { key_value = key_components[0]; - key_type = PluginConfigIOS::PlistItemType::STRING; + key_type = PluginConfigAppleEmbedded::PlistItemType::STRING; } else if (key_components.size() == 2) { key_value = key_components[0]; if (key_components[1].to_lower() == "string") { - key_type = PluginConfigIOS::PlistItemType::STRING; + key_type = PluginConfigAppleEmbedded::PlistItemType::STRING; } else if (key_components[1].to_lower() == "integer") { - key_type = PluginConfigIOS::PlistItemType::INTEGER; + key_type = PluginConfigAppleEmbedded::PlistItemType::INTEGER; } else if (key_components[1].to_lower() == "boolean") { - key_type = PluginConfigIOS::PlistItemType::BOOLEAN; + key_type = PluginConfigAppleEmbedded::PlistItemType::BOOLEAN; } else if (key_components[1].to_lower() == "raw") { - key_type = PluginConfigIOS::PlistItemType::RAW; + key_type = PluginConfigAppleEmbedded::PlistItemType::RAW; } else if (key_components[1].to_lower() == "string_input") { - key_type = PluginConfigIOS::PlistItemType::STRING_INPUT; + key_type = PluginConfigAppleEmbedded::PlistItemType::STRING_INPUT; } } - if (key_value.is_empty() || key_type == PluginConfigIOS::PlistItemType::UNKNOWN) { + if (key_value.is_empty() || key_type == PluginConfigAppleEmbedded::PlistItemType::UNKNOWN) { continue; } String value; switch (key_type) { - case PluginConfigIOS::PlistItemType::STRING: { - String raw_value = config_file->get_value(PluginConfigIOS::PLIST_SECTION, key, String()); + case PluginConfigAppleEmbedded::PlistItemType::STRING: { + String raw_value = config_file->get_value(PluginConfigAppleEmbedded::PLIST_SECTION, key, String()); value = "" + raw_value + ""; } break; - case PluginConfigIOS::PlistItemType::INTEGER: { - int raw_value = config_file->get_value(PluginConfigIOS::PLIST_SECTION, key, 0); + case PluginConfigAppleEmbedded::PlistItemType::INTEGER: { + int raw_value = config_file->get_value(PluginConfigAppleEmbedded::PLIST_SECTION, key, 0); Dictionary value_dictionary; String value_format = "$value"; value_dictionary["value"] = raw_value; value = value_format.format(value_dictionary, "$_"); } break; - case PluginConfigIOS::PlistItemType::BOOLEAN: - if (config_file->get_value(PluginConfigIOS::PLIST_SECTION, key, false)) { + case PluginConfigAppleEmbedded::PlistItemType::BOOLEAN: + if (config_file->get_value(PluginConfigAppleEmbedded::PLIST_SECTION, key, false)) { value = ""; } else { value = ""; } break; - case PluginConfigIOS::PlistItemType::RAW: { - String raw_value = config_file->get_value(PluginConfigIOS::PLIST_SECTION, key, String()); + case PluginConfigAppleEmbedded::PlistItemType::RAW: { + String raw_value = config_file->get_value(PluginConfigAppleEmbedded::PLIST_SECTION, key, String()); value = raw_value; } break; - case PluginConfigIOS::PlistItemType::STRING_INPUT: { - String raw_value = config_file->get_value(PluginConfigIOS::PLIST_SECTION, key, String()); + case PluginConfigAppleEmbedded::PlistItemType::STRING_INPUT: { + String raw_value = config_file->get_value(PluginConfigAppleEmbedded::PLIST_SECTION, key, String()); value = raw_value; } break; default: continue; } - plugin_config.plist[key_value] = PluginConfigIOS::PlistItem{ key_type, value }; + plugin_config.plist[key_value] = PluginConfigAppleEmbedded::PlistItem{ key_type, value }; } } diff --git a/platform/ios/export/godot_plugin_config.h b/editor/export/plugin_config_apple_embedded.h similarity index 92% rename from platform/ios/export/godot_plugin_config.h rename to editor/export/plugin_config_apple_embedded.h index 13f60fc51f69..1cb28909463e 100644 --- a/platform/ios/export/godot_plugin_config.h +++ b/editor/export/plugin_config_apple_embedded.h @@ -1,5 +1,5 @@ /**************************************************************************/ -/* godot_plugin_config.h */ +/* plugin_config_apple_embedded.h */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -50,7 +50,7 @@ The `plist` section are optional. - **key**: key and value that would be added in Info.plist file. */ -struct PluginConfigIOS { +struct PluginConfigAppleEmbedded { inline static const char *PLUGIN_CONFIG_EXT = ".gdip"; inline static const char *CONFIG_SECTION = "config"; @@ -121,11 +121,11 @@ struct PluginConfigIOS { static Vector resolve_system_dependencies(Vector p_paths); - static bool validate_plugin(PluginConfigIOS &plugin_config); + static bool validate_plugin(PluginConfigAppleEmbedded &plugin_config); - static String get_plugin_main_binary(PluginConfigIOS &plugin_config, bool p_debug); + static String get_plugin_main_binary(PluginConfigAppleEmbedded &plugin_config, bool p_debug); - static uint64_t get_plugin_modification_time(const PluginConfigIOS &plugin_config, const String &config_path); + static uint64_t get_plugin_modification_time(const PluginConfigAppleEmbedded &plugin_config, const String &config_path); - static PluginConfigIOS load_plugin_config(Ref config_file, const String &path); + static PluginConfigAppleEmbedded load_plugin_config(Ref config_file, const String &path); }; diff --git a/editor/plugins/gdextension_export_plugin.h b/editor/plugins/gdextension_export_plugin.h index 0792400eda91..ff031f8ab894 100644 --- a/editor/plugins/gdextension_export_plugin.h +++ b/editor/plugins/gdextension_export_plugin.h @@ -115,9 +115,9 @@ void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p libs_added.insert(library_path); add_shared_object(library_path, tags); - if (p_features.has("ios") && (library_path.ends_with(".a") || library_path.ends_with(".xcframework"))) { + if (p_features.has("apple_embedded") && (library_path.ends_with(".a") || library_path.ends_with(".xcframework"))) { String additional_code = "extern void register_dynamic_symbol(char *name, void *address);\n" - "extern void add_ios_init_callback(void (*cb)());\n" + "extern void add_apple_embedded_platform_init_callback(void (*cb)());\n" "\n" "extern \"C\" void $ENTRY();\n" "void $ENTRY_init() {\n" @@ -125,15 +125,15 @@ void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p "}\n" "struct $ENTRY_struct {\n" " $ENTRY_struct() {\n" - " add_ios_init_callback($ENTRY_init);\n" + " add_apple_embedded_platform_init_callback($ENTRY_init);\n" " }\n" "};\n" "$ENTRY_struct $ENTRY_struct_instance;\n\n"; additional_code = additional_code.replace("$ENTRY", entry_symbol); - add_ios_cpp_code(additional_code); + add_apple_embedded_platform_cpp_code(additional_code); String linker_flags = "-Wl,-U,_" + entry_symbol; - add_ios_linker_flags(linker_flags); + add_apple_embedded_platform_linker_flags(linker_flags); } // Update found library info. diff --git a/editor/plugins/lightmap_gi_editor_plugin.cpp b/editor/plugins/lightmap_gi_editor_plugin.cpp index bd36d0f2aec9..c83814745c23 100644 --- a/editor/plugins/lightmap_gi_editor_plugin.cpp +++ b/editor/plugins/lightmap_gi_editor_plugin.cpp @@ -195,7 +195,7 @@ LightmapGIEditorPlugin::LightmapGIEditorPlugin() { #else // Disable lightmap baking if the module is disabled at compile-time. bake->set_disabled(true); -#if defined(ANDROID_ENABLED) || defined(IOS_ENABLED) +#if defined(ANDROID_ENABLED) || defined(APPLE_EMBEDDED_ENABLED) bake->set_tooltip_text(vformat(TTR("Lightmaps cannot be baked on %s."), OS::get_singleton()->get_name())); #else bake->set_tooltip_text(TTR("Lightmaps cannot be baked, as the `lightmapper_rd` module was disabled at compile-time.")); diff --git a/editor/register_editor_types.cpp b/editor/register_editor_types.cpp index 77f52162e970..0eaf0287f7dc 100644 --- a/editor/register_editor_types.cpp +++ b/editor/register_editor_types.cpp @@ -47,6 +47,7 @@ #include "editor/editor_undo_redo_manager.h" #include "editor/editor_vcs_interface.h" #include "editor/export/editor_export_platform.h" +#include "editor/export/editor_export_platform_apple_embedded.h" #include "editor/export/editor_export_platform_extension.h" #include "editor/export/editor_export_platform_pc.h" #include "editor/export/editor_export_plugin.h" @@ -163,6 +164,7 @@ void register_editor_types() { GDREGISTER_CLASS(EditorExportPlugin); GDREGISTER_ABSTRACT_CLASS(EditorExportPlatform); GDREGISTER_ABSTRACT_CLASS(EditorExportPlatformPC); + GDREGISTER_ABSTRACT_CLASS(EditorExportPlatformAppleEmbedded); GDREGISTER_CLASS(EditorExportPlatformExtension); GDREGISTER_ABSTRACT_CLASS(EditorExportPreset); diff --git a/main/main.cpp b/main/main.cpp index 103e85ad11a7..c47984641ab5 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -2191,6 +2191,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph GLOBAL_DEF_RST(PropertyInfo(Variant::STRING, "rendering/rendering_device/driver.linuxbsd", PROPERTY_HINT_ENUM, "vulkan"), "vulkan"); GLOBAL_DEF_RST(PropertyInfo(Variant::STRING, "rendering/rendering_device/driver.android", PROPERTY_HINT_ENUM, "vulkan"), "vulkan"); GLOBAL_DEF_RST(PropertyInfo(Variant::STRING, "rendering/rendering_device/driver.ios", PROPERTY_HINT_ENUM, "metal,vulkan"), "metal"); + GLOBAL_DEF_RST(PropertyInfo(Variant::STRING, "rendering/rendering_device/driver.visionos", PROPERTY_HINT_ENUM, "metal"), "metal"); GLOBAL_DEF_RST(PropertyInfo(Variant::STRING, "rendering/rendering_device/driver.macos", PROPERTY_HINT_ENUM, "metal,vulkan"), "metal"); GLOBAL_DEF_RST("rendering/rendering_device/fallback_to_vulkan", true); @@ -2672,6 +2673,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph GLOBAL_DEF_NOVAL(PropertyInfo(Variant::STRING, "display/display_server/driver.linuxbsd", PROPERTY_HINT_ENUM_SUGGESTION, "default,x11,wayland,headless"), "default"); GLOBAL_DEF_NOVAL(PropertyInfo(Variant::STRING, "display/display_server/driver.android", PROPERTY_HINT_ENUM_SUGGESTION, "default,android,headless"), "default"); GLOBAL_DEF_NOVAL(PropertyInfo(Variant::STRING, "display/display_server/driver.ios", PROPERTY_HINT_ENUM_SUGGESTION, "default,iOS,headless"), "default"); + GLOBAL_DEF_NOVAL(PropertyInfo(Variant::STRING, "display/display_server/driver.visionos", PROPERTY_HINT_ENUM_SUGGESTION, "default,visionOS,headless"), "default"); GLOBAL_DEF_NOVAL(PropertyInfo(Variant::STRING, "display/display_server/driver.macos", PROPERTY_HINT_ENUM_SUGGESTION, "default,macos,headless"), "default"); GLOBAL_DEF_RST_NOVAL("audio/driver/driver", AudioDriverManager::get_driver(0)->get_name()); @@ -3797,7 +3799,7 @@ static MainTimerSync main_timer_sync; int Main::start() { OS::get_singleton()->benchmark_begin_measure("Startup", "Main::Start"); - ERR_FAIL_COND_V(!_start_success, false); + ERR_FAIL_COND_V(!_start_success, EXIT_FAILURE); bool has_icon = false; String positional_arg; diff --git a/methods.py b/methods.py index 0505b2dd5e34..050e56be2cbd 100644 --- a/methods.py +++ b/methods.py @@ -609,17 +609,41 @@ def Run(env, function): return Action(function, "$GENCOMSTR") +def detect_darwin_toolchain_path(env): + var_name = "APPLE_TOOLCHAIN_PATH" + if not env[var_name]: + try: + xcode_path = subprocess.check_output(["xcode-select", "-p"]).strip().decode("utf-8") + if xcode_path: + env[var_name] = xcode_path + "/Toolchains/XcodeDefault.xctoolchain" + except (subprocess.CalledProcessError, OSError): + print_error("Failed to find SDK path while running 'xcode-select -p'.") + raise + + def detect_darwin_sdk_path(platform, env): sdk_name = "" + if platform == "macos": sdk_name = "macosx" var_name = "MACOS_SDK_PATH" + elif platform == "ios": sdk_name = "iphoneos" var_name = "IOS_SDK_PATH" + elif platform == "iossimulator": sdk_name = "iphonesimulator" var_name = "IOS_SDK_PATH" + + elif platform == "visionos": + sdk_name = "xros" + var_name = "VISIONOS_SDK_PATH" + + elif platform == "visionossimulator": + sdk_name = "xrsimulator" + var_name = "VISIONOS_SDK_PATH" + else: raise Exception("Invalid platform argument passed to detect_darwin_sdk_path") @@ -629,7 +653,7 @@ def detect_darwin_sdk_path(platform, env): if sdk_path: env[var_name] = sdk_path except (subprocess.CalledProcessError, OSError): - print_error("Failed to find SDK path while running xcrun --sdk {} --show-sdk-path.".format(sdk_name)) + print_error("Failed to find SDK path while running 'xcrun --sdk {} --show-sdk-path'.".format(sdk_name)) raise diff --git a/misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj b/misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj index 040f7c6c898a..1975bde18527 100644 --- a/misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj +++ b/misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj @@ -7,7 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - 054F8BE62D38852F00B81423 /* MetalFX.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 054F8BE52D38852F00B81423 /* MetalFX.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 1F1575721F582BE20003B888 /* dylibs in Resources */ = {isa = PBXBuildFile; fileRef = 1F1575711F582BE20003B888 /* dylibs */; }; DEADBEEF2F582BE20003B888 /* $binary.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = DEADBEEF1F582BE20003B888 /* $binary.xcframework */; }; $modules_buildfile @@ -43,7 +42,6 @@ 1FF4C1881F584E6300A41E41 /* $binary.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "$binary.entitlements"; sourceTree = ""; }; 1FF8DBB01FBA9DE1009DE660 /* dummy.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = dummy.cpp; sourceTree = ""; }; 9039D3BD24C093AC0020482C /* MoltenVK.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = MoltenVK; path = MoltenVK.xcframework; sourceTree = ""; }; - 054F8BE52D38852F00B81423 /* MetalFX.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MetalFX.framework; path = System/Library/Frameworks/MetalFX.framework; sourceTree = SDKROOT; }; D07CD44D1C5D589C00B7FB28 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; D0BCFE3418AEBDA2004A7AAE /* $binary.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "$binary.app"; sourceTree = BUILT_PRODUCTS_DIR; }; D0BCFE4318AEBDA2004A7AAE /* $binary-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "$binary-Info.plist"; sourceTree = ""; }; @@ -62,7 +60,6 @@ buildActionMask = 2147483647; files = ( 9039D3BE24C093AC0020482C /* MoltenVK.xcframework in Frameworks */, - 054F8BE62D38852F00B81423 /* MetalFX.framework in Frameworks */, DEADBEEF2F582BE20003B888 /* $binary.xcframework */, $modules_buildphase $additional_pbx_frameworks_build @@ -97,7 +94,6 @@ isa = PBXGroup; children = ( 9039D3BD24C093AC0020482C /* MoltenVK.xcframework */, - 054F8BE52D38852F00B81423 /* MetalFX.framework */, DEADBEEF1F582BE20003B888 /* $binary.xcframework */, $modules_buildgrp $additional_pbx_frameworks_refs diff --git a/misc/dist/visionos_xcode/PrivacyInfo.xcprivacy b/misc/dist/visionos_xcode/PrivacyInfo.xcprivacy new file mode 100644 index 000000000000..bc4a893d587a --- /dev/null +++ b/misc/dist/visionos_xcode/PrivacyInfo.xcprivacy @@ -0,0 +1,10 @@ + + + + + NSPrivacyAccessedAPITypes + $priv_api_types + $priv_tracking + $priv_collection + + diff --git a/misc/dist/visionos_xcode/data.pck b/misc/dist/visionos_xcode/data.pck new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/misc/dist/visionos_xcode/godot_visionos.xcodeproj/project.pbxproj b/misc/dist/visionos_xcode/godot_visionos.xcodeproj/project.pbxproj new file mode 100644 index 000000000000..272c4024e0af --- /dev/null +++ b/misc/dist/visionos_xcode/godot_visionos.xcodeproj/project.pbxproj @@ -0,0 +1,402 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 1F1575721F582BE20003B888 /* dylibs in Resources */ = {isa = PBXBuildFile; fileRef = 1F1575711F582BE20003B888 /* dylibs */; }; + DEADBEEF2F582BE20003B888 /* $binary.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = DEADBEEF1F582BE20003B888 /* $binary.xcframework */; }; + $modules_buildfile + $swift_runtime_buildfile + 1FF8DBB11FBA9DE1009DE660 /* dummy.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1FF8DBB01FBA9DE1009DE660 /* dummy.cpp */; }; + D07CD44E1C5D589C00B7FB28 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D07CD44D1C5D589C00B7FB28 /* Images.xcassets */; }; + D0BCFE4618AEBDA2004A7AAE /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = D0BCFE4418AEBDA2004A7AAE /* InfoPlist.strings */; }; + D0BCFE7818AEBFEB004A7AAE /* $binary.pck in Resources */ = {isa = PBXBuildFile; fileRef = D0BCFE7718AEBFEB004A7AAE /* $binary.pck */; }; + F965960D2BC2C3A800579C7E /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = F965960C2BC2C3A800579C7E /* PrivacyInfo.xcprivacy */; }; +/* 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 = ""; }; + DEADBEEF1F582BE20003B888 /* $binary.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = godot; path = "$binary.xcframework"; sourceTree = ""; }; + $modules_fileref + $swift_runtime_fileref + 1FF4C1881F584E6300A41E41 /* $binary.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "$binary.entitlements"; sourceTree = ""; }; + 1FF8DBB01FBA9DE1009DE660 /* dummy.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = dummy.cpp; sourceTree = ""; }; + D07CD44D1C5D589C00B7FB28 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + D0BCFE3418AEBDA2004A7AAE /* $binary.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "$binary.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + D0BCFE4318AEBDA2004A7AAE /* $binary-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "$binary-Info.plist"; sourceTree = ""; }; + D0BCFE4518AEBDA2004A7AAE /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; + $pbx_locale_file_reference + D0BCFE7718AEBFEB004A7AAE /* $binary.pck */ = {isa = PBXFileReference; lastKnownFileType = file; path = "$binary.pck"; sourceTree = ""; }; + F965960C2BC2C3A800579C7E /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; +/* End PBXFileReference section */ + + $additional_pbx_files + +/* Begin PBXFrameworksBuildPhase section */ + D0BCFE3118AEBDA2004A7AAE /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + DEADBEEF2F582BE20003B888 /* $binary.xcframework */, + $modules_buildphase + $additional_pbx_frameworks_build + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + D0BCFE2B18AEBDA2004A7AAE = { + isa = PBXGroup; + children = ( + 1F1575711F582BE20003B888 /* dylibs */, + D0BCFE7718AEBFEB004A7AAE /* $binary.pck */, + D0BCFE4118AEBDA2004A7AAE /* $binary */, + D0BCFE3618AEBDA2004A7AAE /* Frameworks */, + D0BCFE3518AEBDA2004A7AAE /* Products */, + F965960C2BC2C3A800579C7E /* PrivacyInfo.xcprivacy */, + $additional_pbx_resources_refs + ); + sourceTree = ""; + }; + D0BCFE3518AEBDA2004A7AAE /* Products */ = { + isa = PBXGroup; + children = ( + D0BCFE3418AEBDA2004A7AAE /* $binary.app */, + ); + name = Products; + sourceTree = ""; + }; + D0BCFE3618AEBDA2004A7AAE /* Frameworks */ = { + isa = PBXGroup; + children = ( + DEADBEEF1F582BE20003B888 /* $binary.xcframework */, + $modules_buildgrp + $additional_pbx_frameworks_refs + ); + name = Frameworks; + sourceTree = ""; + }; + D0BCFE4118AEBDA2004A7AAE /* $binary */ = { + isa = PBXGroup; + children = ( + 1FF4C1881F584E6300A41E41 /* $binary.entitlements */, + D07CD44D1C5D589C00B7FB28 /* Images.xcassets */, + D0BCFE4218AEBDA2004A7AAE /* Supporting Files */, + 1FF8DBB01FBA9DE1009DE660 /* dummy.cpp */, + $swift_runtime_binary_files + ); + path = "$binary"; + sourceTree = ""; + }; + D0BCFE4218AEBDA2004A7AAE /* Supporting Files */ = { + isa = PBXGroup; + children = ( + D0BCFE4318AEBDA2004A7AAE /* $binary-Info.plist */, + D0BCFE4418AEBDA2004A7AAE /* InfoPlist.strings */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + D0BCFE3318AEBDA2004A7AAE /* $binary */ = { + isa = PBXNativeTarget; + buildConfigurationList = D0BCFE7118AEBDA3004A7AAE /* Build configuration list for PBXNativeTarget "$binary" */; + buildPhases = ( + D0BCFE3018AEBDA2004A7AAE /* Sources */, + D0BCFE3118AEBDA2004A7AAE /* Frameworks */, + D0BCFE3218AEBDA2004A7AAE /* Resources */, + 90A13CD024AA68E500E8464F /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "$binary"; + productName = "$name"; + productReference = D0BCFE3418AEBDA2004A7AAE /* $binary.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + D0BCFE2C18AEBDA2004A7AAE /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0500; + ORGANIZATIONNAME = GodotEngine; + TargetAttributes = { + D0BCFE3318AEBDA2004A7AAE = { + DevelopmentTeam = $team_id; + $swift_runtime_migration + ProvisioningStyle = Automatic; + SystemCapabilities = { + }; + }; + }; + }; + buildConfigurationList = D0BCFE2F18AEBDA2004A7AAE /* Build configuration list for PBXProject "$binary" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = D0BCFE2B18AEBDA2004A7AAE; + productRefGroup = D0BCFE3518AEBDA2004A7AAE /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + D0BCFE3318AEBDA2004A7AAE /* $binary */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + D0BCFE3218AEBDA2004A7AAE /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D07CD44E1C5D589C00B7FB28 /* Images.xcassets in Resources */, + D0BCFE7818AEBFEB004A7AAE /* $binary.pck in Resources */, + D0BCFE4618AEBDA2004A7AAE /* InfoPlist.strings in Resources */, + F965960D2BC2C3A800579C7E /* PrivacyInfo.xcprivacy in Resources */, + $additional_pbx_resources_build + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + D0BCFE3018AEBDA2004A7AAE /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1FF8DBB11FBA9DE1009DE660 /* dummy.cpp in Sources */, + $swift_runtime_build_phase + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + D0BCFE4418AEBDA2004A7AAE /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + D0BCFE4518AEBDA2004A7AAE /* en */, + $pbx_locale_build_reference + ); + name = InfoPlist.strings; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + D0BCFE6F18AEBDA3004A7AAE /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = "$godot_archs"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "$code_sign_identity_debug"; + "CODE_SIGN_IDENTITY[sdk=xros*]" = "$code_sign_identity_debug"; + COPY_PHASE_STRIP = NO; + ENABLE_BITCODE = NO; + "FRAMEWORK_SEARCH_PATHS[arch=*]" = ( + "$(PROJECT_DIR)/**", + ); + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + 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; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + XROS_DEPLOYMENT_TARGET = $min_version; + "LD_CLASSIC_1500" = "-ld_classic"; + "LD_CLASSIC_1501" = "-ld_classic"; + "LD_CLASSIC_1510" = "-ld_classic"; + OTHER_LDFLAGS = "$(LD_CLASSIC_$(XCODE_VERSION_ACTUAL)) $linker_flags"; + SDKROOT = xros; + TARGETED_DEVICE_FAMILY = 7; + }; + name = Debug; + }; + D0BCFE7018AEBDA3004A7AAE /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = "$godot_archs"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "$code_sign_identity_release"; + "CODE_SIGN_IDENTITY[sdk=xros*]" = "$code_sign_identity_release"; + COPY_PHASE_STRIP = YES; + ENABLE_BITCODE = NO; + "FRAMEWORK_SEARCH_PATHS[arch=*]" = ( + "$(PROJECT_DIR)/**", + ); + ENABLE_NS_ASSERTIONS = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + 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; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + XROS_DEPLOYMENT_TARGET = $min_version; + "LD_CLASSIC_1500" = "-ld_classic"; + "LD_CLASSIC_1501" = "-ld_classic"; + "LD_CLASSIC_1510" = "-ld_classic"; + OTHER_LDFLAGS = "$(LD_CLASSIC_$(XCODE_VERSION_ACTUAL)) $linker_flags"; + SDKROOT = xros; + TARGETED_DEVICE_FAMILY = 7; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + D0BCFE7218AEBDA3004A7AAE /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$godot_archs"; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = "$binary/$binary.entitlements"; + CODE_SIGN_IDENTITY = "$code_sign_identity_debug"; + "CODE_SIGN_IDENTITY[sdk=xros*]" = "$code_sign_identity_debug"; + CODE_SIGN_STYLE = "$code_sign_style_debug"; + CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)"; + DEVELOPMENT_TEAM = $team_id; + INFOPLIST_FILE = "$binary/$binary-Info.plist"; + XROS_DEPLOYMENT_TARGET = $min_version; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/**", + ); + PRODUCT_BUNDLE_IDENTIFIER = $bundle_identifier; + INFOPLIST_KEY_CFBundleDisplayName = "$name"; + PRODUCT_NAME = "$binary"; + EXECUTABLE_NAME = "$binary"; + MARKETING_VERSION = $short_version; + CURRENT_PROJECT_VERSION = $version; + PROVISIONING_PROFILE = "$provisioning_profile_uuid_debug"; + PROVISIONING_PROFILE_SPECIFIER = "$provisioning_profile_specifier_debug"; + TARGETED_DEVICE_FAMILY = 7; + VALID_ARCHS = "arm64 x86_64"; + WRAPPER_EXTENSION = app; + $swift_runtime_build_settings + }; + name = Debug; + }; + D0BCFE7318AEBDA3004A7AAE /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$godot_archs"; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = "$binary/$binary.entitlements"; + CODE_SIGN_IDENTITY = "$code_sign_identity_release"; + "CODE_SIGN_IDENTITY[sdk=xros*]" = "$code_sign_identity_release"; + CODE_SIGN_STYLE = "$code_sign_style_release"; + CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)"; + DEVELOPMENT_TEAM = $team_id; + INFOPLIST_FILE = "$binary/$binary-Info.plist"; + XROS_DEPLOYMENT_TARGET = $min_version; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/**", + ); + PRODUCT_BUNDLE_IDENTIFIER = $bundle_identifier; + INFOPLIST_KEY_CFBundleDisplayName = "$name"; + PRODUCT_NAME = "$binary"; + EXECUTABLE_NAME = "$binary"; + MARKETING_VERSION = $short_version; + CURRENT_PROJECT_VERSION = $version; + PROVISIONING_PROFILE = "$provisioning_profile_uuid_release"; + PROVISIONING_PROFILE_SPECIFIER = "$provisioning_profile_specifier_release"; + TARGETED_DEVICE_FAMILY = 7; + VALID_ARCHS = "arm64"; + WRAPPER_EXTENSION = app; + $swift_runtime_build_settings + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + D0BCFE2F18AEBDA2004A7AAE /* Build configuration list for PBXProject "$binary" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D0BCFE6F18AEBDA3004A7AAE /* Debug */, + D0BCFE7018AEBDA3004A7AAE /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; + D0BCFE7118AEBDA3004A7AAE /* Build configuration list for PBXNativeTarget "$binary" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D0BCFE7218AEBDA3004A7AAE /* Debug */, + D0BCFE7318AEBDA3004A7AAE /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; +/* End XCConfigurationList section */ + }; + rootObject = D0BCFE2C18AEBDA2004A7AAE /* Project object */; +} diff --git a/misc/dist/visionos_xcode/godot_visionos.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/misc/dist/visionos_xcode/godot_visionos.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000000..c9c19829f4ae --- /dev/null +++ b/misc/dist/visionos_xcode/godot_visionos.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/misc/dist/visionos_xcode/godot_visionos.xcodeproj/xcshareddata/xcschemes/godot_visionos.xcscheme b/misc/dist/visionos_xcode/godot_visionos.xcodeproj/xcshareddata/xcschemes/godot_visionos.xcscheme new file mode 100644 index 000000000000..d61a53d5c2c2 --- /dev/null +++ b/misc/dist/visionos_xcode/godot_visionos.xcodeproj/xcshareddata/xcschemes/godot_visionos.xcscheme @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/misc/dist/visionos_xcode/godot_visionos/Images.xcassets/SplashImage.imageset/Contents.json b/misc/dist/visionos_xcode/godot_visionos/Images.xcassets/SplashImage.imageset/Contents.json new file mode 100644 index 000000000000..1e63e78edac9 --- /dev/null +++ b/misc/dist/visionos_xcode/godot_visionos/Images.xcassets/SplashImage.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "splash@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "splash@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/misc/dist/visionos_xcode/godot_visionos/Images.xcassets/SplashImage.imageset/splash@2x.png b/misc/dist/visionos_xcode/godot_visionos/Images.xcassets/SplashImage.imageset/splash@2x.png new file mode 100644 index 000000000000..3a0469319b1c Binary files /dev/null and b/misc/dist/visionos_xcode/godot_visionos/Images.xcassets/SplashImage.imageset/splash@2x.png differ diff --git a/misc/dist/visionos_xcode/godot_visionos/Images.xcassets/SplashImage.imageset/splash@3x.png b/misc/dist/visionos_xcode/godot_visionos/Images.xcassets/SplashImage.imageset/splash@3x.png new file mode 100644 index 000000000000..3a0469319b1c Binary files /dev/null and b/misc/dist/visionos_xcode/godot_visionos/Images.xcassets/SplashImage.imageset/splash@3x.png differ diff --git a/misc/dist/visionos_xcode/godot_visionos/dummy.cpp b/misc/dist/visionos_xcode/godot_visionos/dummy.cpp new file mode 100644 index 000000000000..f36e09c68a98 --- /dev/null +++ b/misc/dist/visionos_xcode/godot_visionos/dummy.cpp @@ -0,0 +1,31 @@ +/**************************************************************************/ +/* dummy.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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/visionos_xcode/godot_visionos/dummy.h b/misc/dist/visionos_xcode/godot_visionos/dummy.h new file mode 100644 index 000000000000..a29be4764564 --- /dev/null +++ b/misc/dist/visionos_xcode/godot_visionos/dummy.h @@ -0,0 +1,33 @@ +/**************************************************************************/ +/* dummy.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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. */ +/**************************************************************************/ + +#pragma once + +// #import diff --git a/misc/dist/visionos_xcode/godot_visionos/dummy.swift b/misc/dist/visionos_xcode/godot_visionos/dummy.swift new file mode 100644 index 000000000000..495b5c9a8705 --- /dev/null +++ b/misc/dist/visionos_xcode/godot_visionos/dummy.swift @@ -0,0 +1,31 @@ +/**************************************************************************/ +/* dummy.swift */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 Foundation diff --git a/misc/dist/visionos_xcode/godot_visionos/dylibs/empty b/misc/dist/visionos_xcode/godot_visionos/dylibs/empty new file mode 100644 index 000000000000..bd3e89433361 --- /dev/null +++ b/misc/dist/visionos_xcode/godot_visionos/dylibs/empty @@ -0,0 +1 @@ +Dummy file to make dylibs folder exported diff --git a/misc/dist/visionos_xcode/godot_visionos/en.lproj/InfoPlist.strings b/misc/dist/visionos_xcode/godot_visionos/en.lproj/InfoPlist.strings new file mode 100644 index 000000000000..b92732c79e00 --- /dev/null +++ b/misc/dist/visionos_xcode/godot_visionos/en.lproj/InfoPlist.strings @@ -0,0 +1 @@ +/* Localized versions of Info.plist keys */ diff --git a/misc/dist/visionos_xcode/godot_visionos/export_options.plist b/misc/dist/visionos_xcode/godot_visionos/export_options.plist new file mode 100644 index 000000000000..71073d9a0752 --- /dev/null +++ b/misc/dist/visionos_xcode/godot_visionos/export_options.plist @@ -0,0 +1,20 @@ + + + + + method + $export_method + + teamID + $team_id + + provisioningProfiles + + $bundle_identifier + $provisioning_profile_uuid + + + compileBitcode + + + diff --git a/misc/dist/visionos_xcode/godot_visionos/godot_visionos-Info.plist b/misc/dist/visionos_xcode/godot_visionos/godot_visionos-Info.plist new file mode 100644 index 000000000000..3d2ae6b52b92 --- /dev/null +++ b/misc/dist/visionos_xcode/godot_visionos/godot_visionos-Info.plist @@ -0,0 +1,63 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + $(INFOPLIST_KEY_CFBundleDisplayName) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIcons + + CFBundleIcons~ipad + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleSignature + $signature + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + ITSAppUsesNonExemptEncryption + + LSRequiresIPhoneOS + + LSSupportsOpeningDocumentsInPlace + $docs_in_place + UIFileSharingEnabled + $docs_sharing + UIRequiredDeviceCapabilities + + $required_device_capabilities + + NSCameraUsageDescription + $camera_usage_description + NSPhotoLibraryUsageDescription + $photolibrary_usage_description + NSMicrophoneUsageDescription + $microphone_usage_description + UIRequiresFullScreen + + UIStatusBarHidden + + UISupportedInterfaceOrientations + + $interface_orientations + + UISupportedInterfaceOrientations~ipad + + $ipad_interface_orientations + + $additional_plist_content + $plist_launch_screen_name + CADisableMinimumFrameDurationOnPhone + + diff --git a/misc/dist/visionos_xcode/godot_visionos/godot_visionos.entitlements b/misc/dist/visionos_xcode/godot_visionos/godot_visionos.entitlements new file mode 100644 index 000000000000..6a9e1af546e2 --- /dev/null +++ b/misc/dist/visionos_xcode/godot_visionos/godot_visionos.entitlements @@ -0,0 +1,7 @@ + + + + +$entitlements_full + + diff --git a/misc/dist/visionos_xcode/libgodot.visionos.debug.xcframework/Info.plist b/misc/dist/visionos_xcode/libgodot.visionos.debug.xcframework/Info.plist new file mode 100755 index 000000000000..46f2c86cc935 --- /dev/null +++ b/misc/dist/visionos_xcode/libgodot.visionos.debug.xcframework/Info.plist @@ -0,0 +1,39 @@ + + + + + AvailableLibraries + + + LibraryIdentifier + xros-arm64 + LibraryPath + libgodot.a + SupportedArchitectures + + arm64 + + SupportedPlatform + xros + + + LibraryIdentifier + xros-arm64-simulator + LibraryPath + libgodot.a + SupportedArchitectures + + arm64 + + SupportedPlatform + xros + SupportedPlatformVariant + simulator + + + CFBundlePackageType + XFWK + XCFrameworkFormatVersion + 1.0 + + diff --git a/misc/dist/visionos_xcode/libgodot.visionos.debug.xcframework/xros-arm64-simulator/empty b/misc/dist/visionos_xcode/libgodot.visionos.debug.xcframework/xros-arm64-simulator/empty new file mode 100644 index 000000000000..bd3e89433361 --- /dev/null +++ b/misc/dist/visionos_xcode/libgodot.visionos.debug.xcframework/xros-arm64-simulator/empty @@ -0,0 +1 @@ +Dummy file to make dylibs folder exported diff --git a/misc/dist/visionos_xcode/libgodot.visionos.debug.xcframework/xros-arm64/empty b/misc/dist/visionos_xcode/libgodot.visionos.debug.xcframework/xros-arm64/empty new file mode 100644 index 000000000000..bd3e89433361 --- /dev/null +++ b/misc/dist/visionos_xcode/libgodot.visionos.debug.xcframework/xros-arm64/empty @@ -0,0 +1 @@ +Dummy file to make dylibs folder exported diff --git a/misc/dist/visionos_xcode/libgodot.visionos.release.xcframework/Info.plist b/misc/dist/visionos_xcode/libgodot.visionos.release.xcframework/Info.plist new file mode 100755 index 000000000000..46f2c86cc935 --- /dev/null +++ b/misc/dist/visionos_xcode/libgodot.visionos.release.xcframework/Info.plist @@ -0,0 +1,39 @@ + + + + + AvailableLibraries + + + LibraryIdentifier + xros-arm64 + LibraryPath + libgodot.a + SupportedArchitectures + + arm64 + + SupportedPlatform + xros + + + LibraryIdentifier + xros-arm64-simulator + LibraryPath + libgodot.a + SupportedArchitectures + + arm64 + + SupportedPlatform + xros + SupportedPlatformVariant + simulator + + + CFBundlePackageType + XFWK + XCFrameworkFormatVersion + 1.0 + + diff --git a/misc/dist/visionos_xcode/libgodot.visionos.release.xcframework/xros-arm64-simulator/empty b/misc/dist/visionos_xcode/libgodot.visionos.release.xcframework/xros-arm64-simulator/empty new file mode 100644 index 000000000000..bd3e89433361 --- /dev/null +++ b/misc/dist/visionos_xcode/libgodot.visionos.release.xcframework/xros-arm64-simulator/empty @@ -0,0 +1 @@ +Dummy file to make dylibs folder exported diff --git a/misc/dist/visionos_xcode/libgodot.visionos.release.xcframework/xros-arm64/empty b/misc/dist/visionos_xcode/libgodot.visionos.release.xcframework/xros-arm64/empty new file mode 100644 index 000000000000..bd3e89433361 --- /dev/null +++ b/misc/dist/visionos_xcode/libgodot.visionos.release.xcframework/xros-arm64/empty @@ -0,0 +1 @@ +Dummy file to make dylibs folder exported diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs index cb202907921b..ae5a14218295 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs @@ -406,7 +406,7 @@ bool IsSharedObject(string fileName) { if (platform == OS.Platforms.iOS && path.EndsWith(".dat", StringComparison.OrdinalIgnoreCase)) { - AddIosBundleFile(path); + AddAppleEmbeddedPlatformBundleFile(path); } else { @@ -453,7 +453,7 @@ bool IsSharedObject(string fileName) throw new InvalidOperationException("Failed to generate xcframework."); } - AddIosEmbeddedFramework(xcFrameworkPath); + AddAppleEmbeddedPlatformEmbeddedFramework(xcFrameworkPath); } } diff --git a/modules/mono/editor/hostfxr_resolver.cpp b/modules/mono/editor/hostfxr_resolver.cpp index ba6e8aa27ebe..5766b64661e4 100644 --- a/modules/mono/editor/hostfxr_resolver.cpp +++ b/modules/mono/editor/hostfxr_resolver.cpp @@ -82,7 +82,7 @@ namespace { String get_hostfxr_file_name() { #if defined(WINDOWS_ENABLED) return "hostfxr.dll"; -#elif defined(MACOS_ENABLED) || defined(IOS_ENABLED) +#elif defined(MACOS_ENABLED) || defined(APPLE_EMBEDDED_ENABLED) return "libhostfxr.dylib"; #else return "libhostfxr.so"; diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp index 0417a1a24378..ee3b39cd6270 100644 --- a/modules/mono/mono_gd/gd_mono.cpp +++ b/modules/mono/mono_gd/gd_mono.cpp @@ -450,7 +450,7 @@ godot_plugins_initialize_fn try_load_native_aot_library(void *&r_aot_dll_handle) #if defined(WINDOWS_ENABLED) String native_aot_so_path = GodotSharpDirs::get_api_assemblies_dir().path_join(assembly_name + ".dll"); -#elif defined(MACOS_ENABLED) || defined(IOS_ENABLED) +#elif defined(MACOS_ENABLED) || defined(APPLE_EMBEDDED_ENABLED) String native_aot_so_path = GodotSharpDirs::get_api_assemblies_dir().path_join(assembly_name + ".dylib"); #elif defined(ANDROID_ENABLED) String native_aot_so_path = "lib" + assembly_name + ".so"; @@ -599,7 +599,7 @@ void GDMono::initialize() { godot_plugins_initialize_fn godot_plugins_initialize = nullptr; -#if !defined(IOS_ENABLED) +#if !defined(APPLE_EMBEDDED_ENABLED) // Check that the .NET assemblies directory exists before trying to use it. if (!DirAccess::exists(GodotSharpDirs::get_api_assemblies_dir())) { OS::get_singleton()->alert(vformat(RTR("Unable to find the .NET assemblies directory.\nMake sure the '%s' directory exists and contains the .NET assemblies."), GodotSharpDirs::get_api_assemblies_dir()), RTR(".NET assemblies not found")); @@ -639,7 +639,7 @@ void GDMono::initialize() { void *godot_dll_handle = nullptr; -#if defined(UNIX_ENABLED) && !defined(MACOS_ENABLED) && !defined(IOS_ENABLED) +#if defined(UNIX_ENABLED) && !defined(MACOS_ENABLED) && !defined(APPLE_EMBEDDED_ENABLED) // Managed code can access it on its own on other platforms godot_dll_handle = dlopen(nullptr, RTLD_NOW); #endif diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index ab32481381b5..6b3dcb0d495f 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -713,7 +713,7 @@ Vector EditorExportPlatformAndroid::get_plugins() { Vector plugins_filenames = list_gdap_files(plugins_dir); if (!plugins_filenames.is_empty()) { - Ref config_file = memnew(ConfigFile); + Ref config_file; for (int i = 0; i < plugins_filenames.size(); i++) { PluginConfigAndroid config = PluginConfigAndroid::load_plugin_config(config_file, plugins_dir.path_join(plugins_filenames[i])); if (config.valid_config) { diff --git a/platform/ios/README.md b/platform/ios/README.md index 0e1d6802cb96..cca6561afd7b 100644 --- a/platform/ios/README.md +++ b/platform/ios/README.md @@ -3,6 +3,10 @@ This folder contains the C++, Objective-C and Objective-C++ code for the iOS platform port. +This platform derives from the Apple embedded abstract platform ([`drivers/apple_embedded`](/drivers/apple_embedded)). + +This platform uses shared Apple code ([`drivers/apple`](/drivers/apple)). + See also [`misc/dist/ios_xcode`](/misc/dist/ios_xcode) folder for the Xcode project template used for packaging the iOS export templates. diff --git a/platform/ios/SCsub b/platform/ios/SCsub index ae21d90f9cbf..a313442e2ae3 100644 --- a/platform/ios/SCsub +++ b/platform/ios/SCsub @@ -1,28 +1,19 @@ #!/usr/bin/env python from misc.utility.scons_hints import * -import platform_ios_builders +from platform_ios_builders import generate_bundle + +from platform_methods import combine_libs_apple_embedded Import("env") ios_lib = [ - "godot_ios.mm", - "os_ios.mm", - "main.m", - "app_delegate.mm", - "view_controller.mm", - "ios.mm", - "rendering_context_driver_vulkan_ios.mm", + "device_metrics.mm", + "display_layer_ios.mm", "display_server_ios.mm", - "godot_view.mm", - "tts_ios.mm", - "display_layer.mm", - "godot_app_delegate.m", - "godot_view_renderer.mm", - "device_metrics.m", - "keyboard_input_view.mm", - "key_mapping_ios.mm", - "ios_terminal_logger.mm", + "godot_view_ios.mm", + "main_ios.mm", + "os_ios.mm", ] env_ios = env.Clone() @@ -31,12 +22,9 @@ ios_lib = env_ios.add_library("ios", ios_lib) # (iOS) Enable module support env_ios.Append(CCFLAGS=["-fmodules", "-fcxx-modules"]) - combine_command = env_ios.CommandNoCache( - "#bin/libgodot" + env_ios["LIBSUFFIX"], [ios_lib] + env_ios["LIBS"], env.Run(platform_ios_builders.combine_libs) + "#bin/libgodot" + env_ios["LIBSUFFIX"], [ios_lib] + env_ios["LIBS"], env.Run(combine_libs_apple_embedded) ) if env["generate_bundle"]: - env.AlwaysBuild( - env.CommandNoCache("generate_bundle", combine_command, env.Run(platform_ios_builders.generate_bundle)) - ) + env.AlwaysBuild(env.CommandNoCache("generate_bundle", combine_command, env.Run(generate_bundle))) diff --git a/platform/ios/api/api.cpp b/platform/ios/api/api.cpp index b8c9e7e9aabe..9de39616c0f2 100644 --- a/platform/ios/api/api.cpp +++ b/platform/ios/api/api.cpp @@ -33,11 +33,11 @@ #if defined(IOS_ENABLED) void register_ios_api() { - godot_ios_plugins_initialize(); + godot_apple_embedded_plugins_initialize(); } void unregister_ios_api() { - godot_ios_plugins_deinitialize(); + godot_apple_embedded_plugins_deinitialize(); } #else diff --git a/platform/ios/api/api.h b/platform/ios/api/api.h index c1b76750a696..393df2cafb86 100644 --- a/platform/ios/api/api.h +++ b/platform/ios/api/api.h @@ -31,8 +31,8 @@ #pragma once #if defined(IOS_ENABLED) -extern void godot_ios_plugins_initialize(); -extern void godot_ios_plugins_deinitialize(); +extern void godot_apple_embedded_plugins_initialize(); +extern void godot_apple_embedded_plugins_deinitialize(); #endif void register_ios_api(); diff --git a/platform/ios/detect.py b/platform/ios/detect.py index 16ee4385650d..f996bf7781c5 100644 --- a/platform/ios/detect.py +++ b/platform/ios/detect.py @@ -2,7 +2,7 @@ import sys from typing import TYPE_CHECKING -from methods import detect_darwin_sdk_path, print_error, print_warning +from methods import detect_darwin_sdk_path, detect_darwin_toolchain_path, print_error, print_warning from platform_methods import validate_arch if TYPE_CHECKING: @@ -25,14 +25,11 @@ def get_opts(): return [ ("vulkan_sdk_path", "Path to the Vulkan SDK", ""), - ( - "IOS_TOOLCHAIN_PATH", - "Path to iOS toolchain", - "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain", - ), + # APPLE_TOOLCHAIN_PATH Example: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain + (("APPLE_TOOLCHAIN_PATH", "IOS_TOOLCHAIN_PATH"), "Path to the Apple toolchain", ""), ("IOS_SDK_PATH", "Path to the iOS SDK", ""), - BoolVariable("ios_simulator", "Build for iOS Simulator", False), - ("ios_triple", "Triple for ios toolchain", ""), + (("apple_target_triple", "ios_triple"), "Triple for the corresponding target Apple platform toolchain", ""), + BoolVariable(("simulator", "ios_simulator"), "Build for Simulator", False), BoolVariable("generate_bundle", "Generate an APP bundle after building iOS/macOS binaries", False), ] @@ -62,6 +59,7 @@ def configure(env: "SConsEnvironment"): # Validate arch. supported_arches = ["x86_64", "arm64"] validate_arch(env["arch"], get_name(), supported_arches) + detect_darwin_toolchain_path(env) ## LTO @@ -82,9 +80,9 @@ def configure(env: "SConsEnvironment"): if "OSXCROSS_IOS" in os.environ: env["osxcross"] = True - env["ENV"]["PATH"] = env["IOS_TOOLCHAIN_PATH"] + "/Developer/usr/bin/:" + env["ENV"]["PATH"] + env["ENV"]["PATH"] = env["APPLE_TOOLCHAIN_PATH"] + "/Developer/usr/bin/:" + env["ENV"]["PATH"] - compiler_path = "$IOS_TOOLCHAIN_PATH/usr/bin/${ios_triple}" + compiler_path = "$APPLE_TOOLCHAIN_PATH/usr/bin/${apple_target_triple}" ccache_path = os.environ.get("CCACHE") if ccache_path is None: @@ -102,7 +100,7 @@ def configure(env: "SConsEnvironment"): ## Compile flags - if env["ios_simulator"]: + if env["simulator"]: detect_darwin_sdk_path("iossimulator", env) env.Append(ASFLAGS=["-mios-simulator-version-min=12.0"]) env.Append(CCFLAGS=["-mios-simulator-version-min=12.0"]) @@ -114,8 +112,8 @@ def configure(env: "SConsEnvironment"): env.Append(CCFLAGS=["-miphoneos-version-min=12.0"]) if env["arch"] == "x86_64": - if not env["ios_simulator"]: - print_error("Building for iOS with 'arch=x86_64' requires 'ios_simulator=yes'.") + if not env["simulator"]: + print_error("Building for iOS with 'arch=x86_64' requires 'simulator=yes'.") sys.exit(255) env["ENV"]["MACOSX_DEPLOYMENT_TARGET"] = "10.9" @@ -149,10 +147,10 @@ def configure(env: "SConsEnvironment"): ) env.Prepend(CPPPATH=["#platform/ios"]) - env.Append(CPPDEFINES=["IOS_ENABLED", "UNIX_ENABLED", "COREAUDIO_ENABLED"]) + env.Append(CPPDEFINES=["IOS_ENABLED", "APPLE_EMBEDDED_ENABLED", "UNIX_ENABLED", "COREAUDIO_ENABLED"]) - if env["metal"] and env["ios_simulator"]: - print_warning("iOS simulator does not support the Metal rendering driver") + if env["metal"] and env["simulator"]: + print_warning("iOS Simulator does not support the Metal rendering driver") env["metal"] = False if env["metal"]: @@ -166,8 +164,8 @@ def configure(env: "SConsEnvironment"): ) env.Prepend(CPPEXTPATH=["#thirdparty/spirv-cross"]) - if env["vulkan"] and env["ios_simulator"]: - print_warning("iOS simulator does not support the Vulkan rendering driver") + if env["vulkan"] and env["simulator"]: + print_warning("iOS Simulator does not support the Vulkan rendering driver") env["vulkan"] = False if env["vulkan"]: diff --git a/platform/ios/device_metrics.h b/platform/ios/device_metrics.h index b669e457e506..6941336fe760 100644 --- a/platform/ios/device_metrics.h +++ b/platform/ios/device_metrics.h @@ -32,7 +32,7 @@ #import -@interface GodotDeviceMetrics : NSObject +@interface GDTDeviceMetrics : NSObject @property(nonatomic, class, readonly, strong) NSDictionary *dpiList; diff --git a/platform/ios/device_metrics.m b/platform/ios/device_metrics.mm similarity index 98% rename from platform/ios/device_metrics.m rename to platform/ios/device_metrics.mm index d6f5ec8e4231..f0e92b3c2590 100644 --- a/platform/ios/device_metrics.m +++ b/platform/ios/device_metrics.mm @@ -1,5 +1,5 @@ /**************************************************************************/ -/* device_metrics.m */ +/* device_metrics.mm */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -30,7 +30,7 @@ #import "device_metrics.h" -@implementation GodotDeviceMetrics +@implementation GDTDeviceMetrics + (NSDictionary *)dpiList { return @{ diff --git a/platform/ios/display_layer.h b/platform/ios/display_layer_ios.h similarity index 84% rename from platform/ios/display_layer.h rename to platform/ios/display_layer_ios.h index eda3ad218363..14977b3204fd 100644 --- a/platform/ios/display_layer.h +++ b/platform/ios/display_layer_ios.h @@ -1,5 +1,5 @@ /**************************************************************************/ -/* display_layer.h */ +/* display_layer_ios.h */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -30,32 +30,25 @@ #pragma once +#include "drivers/apple_embedded/display_layer_apple_embedded.h" + #import #import -@protocol DisplayLayer - -- (void)startRenderDisplayLayer; -- (void)stopRenderDisplayLayer; -- (void)initializeDisplayLayer; -- (void)layoutDisplayLayer; - -@end - // An ugly workaround for iOS simulator #if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR #if defined(__IPHONE_13_0) API_AVAILABLE(ios(13.0)) -@interface GodotMetalLayer : CAMetalLayer +@interface GDTMetalLayer : CAMetalLayer #else -@interface GodotMetalLayer : CALayer +@interface GDTMetalLayer : CALayer #endif #else -@interface GodotMetalLayer : CAMetalLayer +@interface GDTMetalLayer : CAMetalLayer #endif @end API_DEPRECATED("OpenGLES is deprecated", ios(2.0, 12.0)) -@interface GodotOpenGLLayer : CAEAGLLayer +@interface GDTOpenGLLayer : CAEAGLLayer @end diff --git a/platform/ios/display_layer.mm b/platform/ios/display_layer_ios.mm similarity index 97% rename from platform/ios/display_layer.mm rename to platform/ios/display_layer_ios.mm index 27cde1c6c22d..1c7e27c1f6c0 100644 --- a/platform/ios/display_layer.mm +++ b/platform/ios/display_layer_ios.mm @@ -1,5 +1,5 @@ /**************************************************************************/ -/* display_layer.mm */ +/* display_layer_ios.mm */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,7 +28,7 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#import "display_layer.h" +#import "display_layer_ios.h" #import "display_server_ios.h" #import "os_ios.h" @@ -46,7 +46,7 @@ #import #import -@implementation GodotMetalLayer +@implementation GDTMetalLayer - (void)initializeDisplayLayer { #if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR @@ -69,7 +69,7 @@ - (void)stopRenderDisplayLayer { @end -@implementation GodotOpenGLLayer { +@implementation GDTOpenGLLayer { // The pixel dimensions of the backbuffer GLint backingWidth; GLint backingHeight; diff --git a/platform/ios/display_server_ios.h b/platform/ios/display_server_ios.h index aa4b00d0bd3f..c8538d29e291 100644 --- a/platform/ios/display_server_ios.h +++ b/platform/ios/display_server_ios.h @@ -30,209 +30,25 @@ #pragma once -#include "core/input/input.h" -#include "servers/display_server.h" +#include "drivers/apple_embedded/display_server_apple_embedded.h" -#if defined(RD_ENABLED) -#include "servers/rendering/renderer_rd/renderer_compositor_rd.h" -#include "servers/rendering/rendering_device.h" - -#if defined(VULKAN_ENABLED) -#import "rendering_context_driver_vulkan_ios.h" - -#include "drivers/vulkan/godot_vulkan.h" -#endif // VULKAN_ENABLED - -#if defined(METAL_ENABLED) -#import "drivers/metal/rendering_context_driver_metal.h" -#endif // METAL_ENABLED -#endif // RD_ENABLED - -#if defined(GLES3_ENABLED) -#include "drivers/gles3/rasterizer_gles3.h" -#endif // GLES3_ENABLED - -#import -#import - -class DisplayServerIOS : public DisplayServer { - GDSOFTCLASS(DisplayServerIOS, DisplayServer); +class DisplayServerIOS : public DisplayServerAppleEmbedded { + GDSOFTCLASS(DisplayServerIOS, DisplayServerAppleEmbedded); _THREAD_SAFE_CLASS_ -#if defined(RD_ENABLED) - RenderingContextDriver *rendering_context = nullptr; - RenderingDevice *rendering_device = nullptr; -#endif - NativeMenu *native_menu = nullptr; - - id tts = nullptr; - - DisplayServer::ScreenOrientation screen_orientation; - - ObjectID window_attached_instance_id; - - Callable window_event_callback; - Callable window_resize_callback; - Callable input_event_callback; - Callable input_text_callback; - - Callable system_theme_changed; - - int virtual_keyboard_height = 0; - - void perform_event(const Ref &p_event); - - void initialize_tts() const; - DisplayServerIOS(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error); ~DisplayServerIOS(); public: - String rendering_driver; - static DisplayServerIOS *get_singleton(); static void register_ios_driver(); static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error); - static Vector get_rendering_drivers_func(); - - // MARK: - Events - - virtual void process_events() override; - - virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; - virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; - virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; - virtual void window_set_input_text_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; - virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; - - static void _dispatch_input_events(const Ref &p_event); - void send_input_event(const Ref &p_event) const; - void send_input_text(const String &p_text) const; - void send_window_event(DisplayServer::WindowEvent p_event) const; - void _window_callback(const Callable &p_callable, const Variant &p_arg) const; - - void emit_system_theme_changed(); - - // MARK: - Input - - // MARK: Touches and Apple Pencil - void touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_double_click); - void touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y, float p_pressure, Vector2 p_tilt); - void touches_canceled(int p_idx); - - // MARK: Keyboard - - void key(Key p_key, char32_t p_char, Key p_unshifted, Key p_physical, NSInteger p_modifier, bool p_pressed, KeyLocation p_location); - bool is_keyboard_active() const; - - // MARK: Motion - - void update_gravity(const Vector3 &p_gravity); - void update_accelerometer(const Vector3 &p_accelerometer); - void update_magnetometer(const Vector3 &p_magnetometer); - void update_gyroscope(const Vector3 &p_gyroscope); - - // MARK: - - - virtual bool has_feature(Feature p_feature) const override; virtual String get_name() const override; - virtual bool tts_is_speaking() const override; - virtual bool tts_is_paused() const override; - virtual TypedArray tts_get_voices() const override; - - virtual void tts_speak(const String &p_text, const String &p_voice, int p_volume = 50, float p_pitch = 1.f, float p_rate = 1.f, int p_utterance_id = 0, bool p_interrupt = false) override; - virtual void tts_pause() override; - virtual void tts_resume() override; - virtual void tts_stop() override; - - virtual bool is_dark_mode_supported() const override; - virtual bool is_dark_mode() const override; - virtual void set_system_theme_change_callback(const Callable &p_callable) override; - - virtual Rect2i get_display_safe_area() const override; - - virtual int get_screen_count() const override; - virtual int get_primary_screen() const override; - virtual Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; - virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; - virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual float screen_get_scale(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; - - virtual Vector get_window_list() const override; - - virtual WindowID get_window_at_screen_position(const Point2i &p_position) const override; - - virtual int64_t window_get_native_handle(HandleType p_handle_type, WindowID p_window = MAIN_WINDOW_ID) const override; - - virtual void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID) override; - virtual ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const override; - - virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) override; - - virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const override; - virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override; - - virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const override; - virtual Point2i window_get_position_with_decorations(WindowID p_window = MAIN_WINDOW_ID) const override; - virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) override; - - virtual void window_set_transient(WindowID p_window, WindowID p_parent) override; - - virtual void window_set_max_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; - virtual Size2i window_get_max_size(WindowID p_window = MAIN_WINDOW_ID) const override; - - virtual void window_set_min_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; - virtual Size2i window_get_min_size(WindowID p_window = MAIN_WINDOW_ID) const override; - - virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; - virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const override; - virtual Size2i window_get_size_with_decorations(WindowID p_window = MAIN_WINDOW_ID) const override; - - virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID) override; - virtual WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const override; - - virtual bool window_is_maximize_allowed(WindowID p_window = MAIN_WINDOW_ID) const override; - - virtual void window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window = MAIN_WINDOW_ID) override; - virtual bool window_get_flag(WindowFlags p_flag, WindowID p_window = MAIN_WINDOW_ID) const override; - - virtual void window_request_attention(WindowID p_window = MAIN_WINDOW_ID) override; - virtual void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID) override; - virtual bool window_is_focused(WindowID p_window = MAIN_WINDOW_ID) const override; - - virtual float screen_get_max_scale() const override; - - virtual void screen_set_orientation(DisplayServer::ScreenOrientation p_orientation, int p_screen) override; - virtual DisplayServer::ScreenOrientation screen_get_orientation(int p_screen) const override; - - virtual bool window_can_draw(WindowID p_window = MAIN_WINDOW_ID) const override; - - virtual bool can_any_window_draw() const override; - - virtual void window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window = MAIN_WINDOW_ID) override; - virtual DisplayServer::VSyncMode window_get_vsync_mode(WindowID p_vsync_mode) const override; - - virtual bool is_touchscreen_available() const override; - - virtual void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, VirtualKeyboardType p_type, int p_max_length, int p_cursor_start, int p_cursor_end) override; - virtual void virtual_keyboard_hide() override; - - void virtual_keyboard_set_height(int height); - virtual int virtual_keyboard_get_height() const override; - virtual bool has_hardware_keyboard() const override; - - virtual void clipboard_set(const String &p_text) override; - virtual String clipboard_get() const override; - - virtual void screen_set_keep_on(bool p_enable) override; - virtual bool screen_is_kept_on() const override; - - void resize_window(CGSize size); - virtual void swap_buffers() override {} }; diff --git a/platform/ios/display_server_ios.mm b/platform/ios/display_server_ios.mm index 7c75fa0faa52..2d49bd63c474 100644 --- a/platform/ios/display_server_ios.mm +++ b/platform/ios/display_server_ios.mm @@ -30,492 +30,33 @@ #import "display_server_ios.h" -#import "app_delegate.h" #import "device_metrics.h" -#import "godot_view.h" -#import "ios.h" -#import "key_mapping_ios.h" -#import "keyboard_input_view.h" -#import "os_ios.h" -#import "tts_ios.h" -#import "view_controller.h" - -#include "core/config/project_settings.h" -#include "core/io/file_access_pack.h" +#import #import -#import - -static const float kDisplayServerIOSAcceleration = 1.f; - DisplayServerIOS *DisplayServerIOS::get_singleton() { - return (DisplayServerIOS *)DisplayServer::get_singleton(); + return (DisplayServerIOS *)DisplayServerAppleEmbedded::get_singleton(); } -DisplayServerIOS::DisplayServerIOS(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error) { - KeyMappingIOS::initialize(); - - rendering_driver = p_rendering_driver; - - // Init TTS - bool tts_enabled = GLOBAL_GET("audio/general/text_to_speech"); - if (tts_enabled) { - initialize_tts(); - } - native_menu = memnew(NativeMenu); - -#if defined(RD_ENABLED) - rendering_context = nullptr; - rendering_device = nullptr; - - CALayer *layer = nullptr; - - union { -#ifdef VULKAN_ENABLED - RenderingContextDriverVulkanIOS::WindowPlatformData vulkan; -#endif -#ifdef METAL_ENABLED - GODOT_CLANG_WARNING_PUSH_AND_IGNORE("-Wunguarded-availability") - // Eliminate "RenderingContextDriverMetal is only available on iOS 14.0 or newer". - RenderingContextDriverMetal::WindowPlatformData metal; - GODOT_CLANG_WARNING_POP -#endif - } wpd; - -#if defined(VULKAN_ENABLED) - if (rendering_driver == "vulkan") { - layer = [AppDelegate.viewController.godotView initializeRenderingForDriver:@"vulkan"]; - if (!layer) { - ERR_FAIL_MSG("Failed to create iOS Vulkan rendering layer."); - } - wpd.vulkan.layer_ptr = (CAMetalLayer *const *)&layer; - rendering_context = memnew(RenderingContextDriverVulkanIOS); - } -#endif -#ifdef METAL_ENABLED - if (rendering_driver == "metal") { - if (@available(iOS 14.0, *)) { - layer = [AppDelegate.viewController.godotView initializeRenderingForDriver:@"metal"]; - wpd.metal.layer = (CAMetalLayer *)layer; - rendering_context = memnew(RenderingContextDriverMetal); - } else { - OS::get_singleton()->alert("Metal is only supported on iOS 14.0 and later."); - r_error = ERR_UNAVAILABLE; - return; - } - } -#endif - if (rendering_context) { - if (rendering_context->initialize() != OK) { - memdelete(rendering_context); - rendering_context = nullptr; -#if defined(GLES3_ENABLED) - bool fallback_to_opengl3 = GLOBAL_GET("rendering/rendering_device/fallback_to_opengl3"); - if (fallback_to_opengl3 && rendering_driver != "opengl3") { - WARN_PRINT("Your device seem not to support MoltenVK or Metal, switching to OpenGL 3."); - rendering_driver = "opengl3"; - OS::get_singleton()->set_current_rendering_method("gl_compatibility"); - OS::get_singleton()->set_current_rendering_driver_name(rendering_driver); - } else -#endif - { - ERR_PRINT(vformat("Failed to initialize %s context", rendering_driver)); - r_error = ERR_UNAVAILABLE; - return; - } - } - } - - if (rendering_context) { - if (rendering_context->window_create(MAIN_WINDOW_ID, &wpd) != OK) { - ERR_PRINT(vformat("Failed to create %s window.", rendering_driver)); - memdelete(rendering_context); - rendering_context = nullptr; - r_error = ERR_UNAVAILABLE; - return; - } - - Size2i size = Size2i(layer.bounds.size.width, layer.bounds.size.height) * screen_get_max_scale(); - rendering_context->window_set_size(MAIN_WINDOW_ID, size.width, size.height); - rendering_context->window_set_vsync_mode(MAIN_WINDOW_ID, p_vsync_mode); - - rendering_device = memnew(RenderingDevice); - if (rendering_device->initialize(rendering_context, MAIN_WINDOW_ID) != OK) { - rendering_device = nullptr; - memdelete(rendering_context); - rendering_context = nullptr; - r_error = ERR_UNAVAILABLE; - return; - } - rendering_device->screen_create(MAIN_WINDOW_ID); - - RendererCompositorRD::make_current(); - } -#endif - -#if defined(GLES3_ENABLED) - if (rendering_driver == "opengl3") { - CALayer *layer = [AppDelegate.viewController.godotView initializeRenderingForDriver:@"opengl3"]; - - if (!layer) { - ERR_FAIL_MSG("Failed to create iOS OpenGLES rendering layer."); - } - - RasterizerGLES3::make_current(false); - } -#endif - - bool keep_screen_on = bool(GLOBAL_GET("display/window/energy_saving/keep_screen_on")); - screen_set_keep_on(keep_screen_on); - - Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events); - - r_error = OK; +DisplayServerIOS::DisplayServerIOS(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error) : + DisplayServerAppleEmbedded(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_position, p_resolution, p_screen, p_context, p_parent_window, r_error) { } DisplayServerIOS::~DisplayServerIOS() { - if (native_menu) { - memdelete(native_menu); - native_menu = nullptr; - } - -#if defined(RD_ENABLED) - if (rendering_device) { - rendering_device->screen_free(MAIN_WINDOW_ID); - memdelete(rendering_device); - rendering_device = nullptr; - } - - if (rendering_context) { - rendering_context->window_destroy(MAIN_WINDOW_ID); - memdelete(rendering_context); - rendering_context = nullptr; - } -#endif } DisplayServer *DisplayServerIOS::create_func(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error) { return memnew(DisplayServerIOS(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_position, p_resolution, p_screen, p_context, p_parent_window, r_error)); } -Vector DisplayServerIOS::get_rendering_drivers_func() { - Vector drivers; - -#if defined(VULKAN_ENABLED) - drivers.push_back("vulkan"); -#endif -#if defined(METAL_ENABLED) - if (@available(ios 14.0, *)) { - drivers.push_back("metal"); - } -#endif -#if defined(GLES3_ENABLED) - drivers.push_back("opengl3"); -#endif - - return drivers; -} - void DisplayServerIOS::register_ios_driver() { register_create_function("iOS", create_func, get_rendering_drivers_func); } -// MARK: Events - -void DisplayServerIOS::window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window) { - window_resize_callback = p_callable; -} - -void DisplayServerIOS::window_set_window_event_callback(const Callable &p_callable, WindowID p_window) { - window_event_callback = p_callable; -} -void DisplayServerIOS::window_set_input_event_callback(const Callable &p_callable, WindowID p_window) { - input_event_callback = p_callable; -} - -void DisplayServerIOS::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) { - input_text_callback = p_callable; -} - -void DisplayServerIOS::window_set_drop_files_callback(const Callable &p_callable, WindowID p_window) { - // Probably not supported for iOS -} - -void DisplayServerIOS::process_events() { - Input::get_singleton()->flush_buffered_events(); -} - -void DisplayServerIOS::_dispatch_input_events(const Ref &p_event) { - DisplayServerIOS::get_singleton()->send_input_event(p_event); -} - -void DisplayServerIOS::send_input_event(const Ref &p_event) const { - _window_callback(input_event_callback, p_event); -} - -void DisplayServerIOS::send_input_text(const String &p_text) const { - _window_callback(input_text_callback, p_text); -} - -void DisplayServerIOS::send_window_event(DisplayServer::WindowEvent p_event) const { - _window_callback(window_event_callback, int(p_event)); -} - -void DisplayServerIOS::_window_callback(const Callable &p_callable, const Variant &p_arg) const { - if (p_callable.is_valid()) { - p_callable.call(p_arg); - } -} - -// MARK: - Input - -// MARK: Touches - -void DisplayServerIOS::touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_double_click) { - Ref ev; - ev.instantiate(); - - ev->set_index(p_idx); - ev->set_pressed(p_pressed); - ev->set_position(Vector2(p_x, p_y)); - ev->set_double_tap(p_double_click); - perform_event(ev); -} - -void DisplayServerIOS::touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y, float p_pressure, Vector2 p_tilt) { - Ref ev; - ev.instantiate(); - ev->set_index(p_idx); - ev->set_pressure(p_pressure); - ev->set_tilt(p_tilt); - ev->set_position(Vector2(p_x, p_y)); - ev->set_relative(Vector2(p_x - p_prev_x, p_y - p_prev_y)); - ev->set_relative_screen_position(ev->get_relative()); - perform_event(ev); -} - -void DisplayServerIOS::perform_event(const Ref &p_event) { - Input::get_singleton()->parse_input_event(p_event); -} - -void DisplayServerIOS::touches_canceled(int p_idx) { - touch_press(p_idx, -1, -1, false, false); -} - -// MARK: Keyboard - -void DisplayServerIOS::key(Key p_key, char32_t p_char, Key p_unshifted, Key p_physical, NSInteger p_modifier, bool p_pressed, KeyLocation p_location) { - Ref ev; - ev.instantiate(); - ev->set_echo(false); - ev->set_pressed(p_pressed); - ev->set_keycode(fix_keycode(p_char, p_key)); - if (@available(iOS 13.4, *)) { - if (p_key != Key::SHIFT) { - ev->set_shift_pressed(p_modifier & UIKeyModifierShift); - } - if (p_key != Key::CTRL) { - ev->set_ctrl_pressed(p_modifier & UIKeyModifierControl); - } - if (p_key != Key::ALT) { - ev->set_alt_pressed(p_modifier & UIKeyModifierAlternate); - } - if (p_key != Key::META) { - ev->set_meta_pressed(p_modifier & UIKeyModifierCommand); - } - } - ev->set_key_label(p_unshifted); - ev->set_physical_keycode(p_physical); - ev->set_unicode(fix_unicode(p_char)); - ev->set_location(p_location); - perform_event(ev); -} - -// MARK: Motion - -void DisplayServerIOS::update_gravity(const Vector3 &p_gravity) { - Input::get_singleton()->set_gravity(p_gravity); -} - -void DisplayServerIOS::update_accelerometer(const Vector3 &p_accelerometer) { - Input::get_singleton()->set_accelerometer(p_accelerometer / kDisplayServerIOSAcceleration); -} - -void DisplayServerIOS::update_magnetometer(const Vector3 &p_magnetometer) { - Input::get_singleton()->set_magnetometer(p_magnetometer); -} - -void DisplayServerIOS::update_gyroscope(const Vector3 &p_gyroscope) { - Input::get_singleton()->set_gyroscope(p_gyroscope); -} - -// MARK: - - -bool DisplayServerIOS::has_feature(Feature p_feature) const { - switch (p_feature) { -#ifndef DISABLE_DEPRECATED - case FEATURE_GLOBAL_MENU: { - return (native_menu && native_menu->has_feature(NativeMenu::FEATURE_GLOBAL_MENU)); - } break; -#endif - // case FEATURE_CURSOR_SHAPE: - // case FEATURE_CUSTOM_CURSOR_SHAPE: - // case FEATURE_HIDPI: - // case FEATURE_ICON: - // case FEATURE_IME: - // case FEATURE_MOUSE: - // case FEATURE_MOUSE_WARP: - // case FEATURE_NATIVE_DIALOG: - // case FEATURE_NATIVE_DIALOG_INPUT: - // case FEATURE_NATIVE_DIALOG_FILE: - // case FEATURE_NATIVE_DIALOG_FILE_EXTRA: - // case FEATURE_NATIVE_DIALOG_FILE_MIME: - // case FEATURE_NATIVE_ICON: - // case FEATURE_WINDOW_TRANSPARENCY: - case FEATURE_CLIPBOARD: - case FEATURE_KEEP_SCREEN_ON: - case FEATURE_ORIENTATION: - case FEATURE_TOUCHSCREEN: - case FEATURE_VIRTUAL_KEYBOARD: - case FEATURE_TEXT_TO_SPEECH: - return true; - default: - return false; - } -} - String DisplayServerIOS::get_name() const { return "iOS"; } -void DisplayServerIOS::initialize_tts() const { - const_cast(this)->tts = [[TTS_IOS alloc] init]; -} - -bool DisplayServerIOS::tts_is_speaking() const { - if (unlikely(!tts)) { - initialize_tts(); - } - ERR_FAIL_NULL_V(tts, false); - return [tts isSpeaking]; -} - -bool DisplayServerIOS::tts_is_paused() const { - if (unlikely(!tts)) { - initialize_tts(); - } - ERR_FAIL_NULL_V(tts, false); - return [tts isPaused]; -} - -TypedArray DisplayServerIOS::tts_get_voices() const { - if (unlikely(!tts)) { - initialize_tts(); - } - ERR_FAIL_NULL_V(tts, TypedArray()); - return [tts getVoices]; -} - -void DisplayServerIOS::tts_speak(const String &p_text, const String &p_voice, int p_volume, float p_pitch, float p_rate, int p_utterance_id, bool p_interrupt) { - if (unlikely(!tts)) { - initialize_tts(); - } - ERR_FAIL_NULL(tts); - [tts speak:p_text voice:p_voice volume:p_volume pitch:p_pitch rate:p_rate utterance_id:p_utterance_id interrupt:p_interrupt]; -} - -void DisplayServerIOS::tts_pause() { - if (unlikely(!tts)) { - initialize_tts(); - } - ERR_FAIL_NULL(tts); - [tts pauseSpeaking]; -} - -void DisplayServerIOS::tts_resume() { - if (unlikely(!tts)) { - initialize_tts(); - } - ERR_FAIL_NULL(tts); - [tts resumeSpeaking]; -} - -void DisplayServerIOS::tts_stop() { - if (unlikely(!tts)) { - initialize_tts(); - } - ERR_FAIL_NULL(tts); - [tts stopSpeaking]; -} - -bool DisplayServerIOS::is_dark_mode_supported() const { - if (@available(iOS 13.0, *)) { - return true; - } else { - return false; - } -} - -bool DisplayServerIOS::is_dark_mode() const { - if (@available(iOS 13.0, *)) { - return [UITraitCollection currentTraitCollection].userInterfaceStyle == UIUserInterfaceStyleDark; - } else { - return false; - } -} - -void DisplayServerIOS::set_system_theme_change_callback(const Callable &p_callable) { - system_theme_changed = p_callable; -} - -void DisplayServerIOS::emit_system_theme_changed() { - if (system_theme_changed.is_valid()) { - Variant ret; - Callable::CallError ce; - system_theme_changed.callp(nullptr, 0, ret, ce); - if (ce.error != Callable::CallError::CALL_OK) { - ERR_PRINT(vformat("Failed to execute system theme changed callback: %s.", Variant::get_callable_error_text(system_theme_changed, nullptr, 0, ce))); - } - } -} - -Rect2i DisplayServerIOS::get_display_safe_area() const { - UIEdgeInsets insets = UIEdgeInsetsZero; - UIView *view = AppDelegate.viewController.godotView; - if ([view respondsToSelector:@selector(safeAreaInsets)]) { - insets = [view safeAreaInsets]; - } - float scale = screen_get_scale(); - Size2i insets_position = Size2i(insets.left, insets.top) * scale; - Size2i insets_size = Size2i(insets.left + insets.right, insets.top + insets.bottom) * scale; - return Rect2i(screen_get_position() + insets_position, screen_get_size() - insets_size); -} - -int DisplayServerIOS::get_screen_count() const { - return 1; -} - -int DisplayServerIOS::get_primary_screen() const { - return 0; -} - -Point2i DisplayServerIOS::screen_get_position(int p_screen) const { - return Size2i(); -} - -Size2i DisplayServerIOS::screen_get_size(int p_screen) const { - CALayer *layer = AppDelegate.viewController.godotView.renderingLayer; - - if (!layer) { - return Size2i(); - } - - return Size2i(layer.bounds.size.width, layer.bounds.size.height) * screen_get_scale(p_screen); -} - -Rect2i DisplayServerIOS::screen_get_usable_rect(int p_screen) const { - return Rect2i(screen_get_position(p_screen), screen_get_size(p_screen)); -} int DisplayServerIOS::screen_get_dpi(int p_screen) const { struct utsname systemInfo; @@ -523,7 +64,7 @@ NSString *string = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding]; - NSDictionary *iOSModelToDPI = [GodotDeviceMetrics dpiList]; + NSDictionary *iOSModelToDPI = [GDTDeviceMetrics dpiList]; for (NSArray *keyArray in iOSModelToDPI) { if ([keyArray containsObject:string]) { @@ -565,283 +106,3 @@ float DisplayServerIOS::screen_get_scale(int p_screen) const { return [UIScreen mainScreen].scale; } - -Vector DisplayServerIOS::get_window_list() const { - Vector list; - list.push_back(MAIN_WINDOW_ID); - return list; -} - -DisplayServer::WindowID DisplayServerIOS::get_window_at_screen_position(const Point2i &p_position) const { - return MAIN_WINDOW_ID; -} - -int64_t DisplayServerIOS::window_get_native_handle(HandleType p_handle_type, WindowID p_window) const { - ERR_FAIL_COND_V(p_window != MAIN_WINDOW_ID, 0); - switch (p_handle_type) { - case DISPLAY_HANDLE: { - return 0; // Not supported. - } - case WINDOW_HANDLE: { - return (int64_t)AppDelegate.viewController; - } - case WINDOW_VIEW: { - return (int64_t)AppDelegate.viewController.godotView; - } - default: { - return 0; - } - } -} - -void DisplayServerIOS::window_attach_instance_id(ObjectID p_instance, WindowID p_window) { - window_attached_instance_id = p_instance; -} - -ObjectID DisplayServerIOS::window_get_attached_instance_id(WindowID p_window) const { - return window_attached_instance_id; -} - -void DisplayServerIOS::window_set_title(const String &p_title, WindowID p_window) { - // Probably not supported for iOS -} - -int DisplayServerIOS::window_get_current_screen(WindowID p_window) const { - return SCREEN_OF_MAIN_WINDOW; -} - -void DisplayServerIOS::window_set_current_screen(int p_screen, WindowID p_window) { - // Probably not supported for iOS -} - -Point2i DisplayServerIOS::window_get_position(WindowID p_window) const { - return Point2i(); -} - -Point2i DisplayServerIOS::window_get_position_with_decorations(WindowID p_window) const { - return Point2i(); -} - -void DisplayServerIOS::window_set_position(const Point2i &p_position, WindowID p_window) { - // Probably not supported for single window iOS app -} - -void DisplayServerIOS::window_set_transient(WindowID p_window, WindowID p_parent) { - // Probably not supported for iOS -} - -void DisplayServerIOS::window_set_max_size(const Size2i p_size, WindowID p_window) { - // Probably not supported for iOS -} - -Size2i DisplayServerIOS::window_get_max_size(WindowID p_window) const { - return Size2i(); -} - -void DisplayServerIOS::window_set_min_size(const Size2i p_size, WindowID p_window) { - // Probably not supported for iOS -} - -Size2i DisplayServerIOS::window_get_min_size(WindowID p_window) const { - return Size2i(); -} - -void DisplayServerIOS::window_set_size(const Size2i p_size, WindowID p_window) { - // Probably not supported for iOS -} - -Size2i DisplayServerIOS::window_get_size(WindowID p_window) const { - CGRect screenBounds = [UIScreen mainScreen].bounds; - return Size2i(screenBounds.size.width, screenBounds.size.height) * screen_get_max_scale(); -} - -Size2i DisplayServerIOS::window_get_size_with_decorations(WindowID p_window) const { - return window_get_size(p_window); -} - -void DisplayServerIOS::window_set_mode(WindowMode p_mode, WindowID p_window) { - // Probably not supported for iOS -} - -DisplayServer::WindowMode DisplayServerIOS::window_get_mode(WindowID p_window) const { - return WindowMode::WINDOW_MODE_FULLSCREEN; -} - -bool DisplayServerIOS::window_is_maximize_allowed(WindowID p_window) const { - return false; -} - -void DisplayServerIOS::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) { - // Probably not supported for iOS -} - -bool DisplayServerIOS::window_get_flag(WindowFlags p_flag, WindowID p_window) const { - return false; -} - -void DisplayServerIOS::window_request_attention(WindowID p_window) { - // Probably not supported for iOS -} - -void DisplayServerIOS::window_move_to_foreground(WindowID p_window) { - // Probably not supported for iOS -} - -bool DisplayServerIOS::window_is_focused(WindowID p_window) const { - return true; -} - -float DisplayServerIOS::screen_get_max_scale() const { - return screen_get_scale(SCREEN_OF_MAIN_WINDOW); -} - -void DisplayServerIOS::screen_set_orientation(DisplayServer::ScreenOrientation p_orientation, int p_screen) { - screen_orientation = p_orientation; - if (@available(iOS 16.0, *)) { - [AppDelegate.viewController setNeedsUpdateOfSupportedInterfaceOrientations]; - } else { - [UIViewController attemptRotationToDeviceOrientation]; - } -} - -DisplayServer::ScreenOrientation DisplayServerIOS::screen_get_orientation(int p_screen) const { - return screen_orientation; -} - -bool DisplayServerIOS::window_can_draw(WindowID p_window) const { - return true; -} - -bool DisplayServerIOS::can_any_window_draw() const { - return true; -} - -bool DisplayServerIOS::is_touchscreen_available() const { - return true; -} - -_FORCE_INLINE_ int _convert_utf32_offset_to_utf16(const String &p_existing_text, int p_pos) { - int limit = p_pos; - for (int i = 0; i < MIN(p_existing_text.length(), p_pos); i++) { - if (p_existing_text[i] > 0xffff) { - limit++; - } - } - return limit; -} - -void DisplayServerIOS::virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, VirtualKeyboardType p_type, int p_max_length, int p_cursor_start, int p_cursor_end) { - NSString *existingString = [[NSString alloc] initWithUTF8String:p_existing_text.utf8().get_data()]; - - AppDelegate.viewController.keyboardView.keyboardType = UIKeyboardTypeDefault; - AppDelegate.viewController.keyboardView.textContentType = nil; - switch (p_type) { - case KEYBOARD_TYPE_DEFAULT: { - AppDelegate.viewController.keyboardView.keyboardType = UIKeyboardTypeDefault; - } break; - case KEYBOARD_TYPE_MULTILINE: { - AppDelegate.viewController.keyboardView.keyboardType = UIKeyboardTypeDefault; - } break; - case KEYBOARD_TYPE_NUMBER: { - AppDelegate.viewController.keyboardView.keyboardType = UIKeyboardTypeNumberPad; - } break; - case KEYBOARD_TYPE_NUMBER_DECIMAL: { - AppDelegate.viewController.keyboardView.keyboardType = UIKeyboardTypeDecimalPad; - } break; - case KEYBOARD_TYPE_PHONE: { - AppDelegate.viewController.keyboardView.keyboardType = UIKeyboardTypePhonePad; - AppDelegate.viewController.keyboardView.textContentType = UITextContentTypeTelephoneNumber; - } break; - case KEYBOARD_TYPE_EMAIL_ADDRESS: { - AppDelegate.viewController.keyboardView.keyboardType = UIKeyboardTypeEmailAddress; - AppDelegate.viewController.keyboardView.textContentType = UITextContentTypeEmailAddress; - } break; - case KEYBOARD_TYPE_PASSWORD: { - AppDelegate.viewController.keyboardView.keyboardType = UIKeyboardTypeDefault; - AppDelegate.viewController.keyboardView.textContentType = UITextContentTypePassword; - } break; - case KEYBOARD_TYPE_URL: { - AppDelegate.viewController.keyboardView.keyboardType = UIKeyboardTypeWebSearch; - AppDelegate.viewController.keyboardView.textContentType = UITextContentTypeURL; - } break; - } - - [AppDelegate.viewController.keyboardView - becomeFirstResponderWithString:existingString - cursorStart:_convert_utf32_offset_to_utf16(p_existing_text, p_cursor_start) - cursorEnd:_convert_utf32_offset_to_utf16(p_existing_text, p_cursor_end)]; -} - -bool DisplayServerIOS::is_keyboard_active() const { - return [AppDelegate.viewController.keyboardView isFirstResponder]; -} - -void DisplayServerIOS::virtual_keyboard_hide() { - [AppDelegate.viewController.keyboardView resignFirstResponder]; -} - -void DisplayServerIOS::virtual_keyboard_set_height(int height) { - virtual_keyboard_height = height * screen_get_max_scale(); -} - -int DisplayServerIOS::virtual_keyboard_get_height() const { - return virtual_keyboard_height; -} - -bool DisplayServerIOS::has_hardware_keyboard() const { - if (@available(iOS 14.0, *)) { - return [GCKeyboard coalescedKeyboard]; - } else { - return false; - } -} - -void DisplayServerIOS::clipboard_set(const String &p_text) { - [UIPasteboard generalPasteboard].string = [NSString stringWithUTF8String:p_text.utf8().get_data()]; -} - -String DisplayServerIOS::clipboard_get() const { - NSString *text = [UIPasteboard generalPasteboard].string; - - return String::utf8([text UTF8String]); -} - -void DisplayServerIOS::screen_set_keep_on(bool p_enable) { - [UIApplication sharedApplication].idleTimerDisabled = p_enable; -} - -bool DisplayServerIOS::screen_is_kept_on() const { - return [UIApplication sharedApplication].idleTimerDisabled; -} - -void DisplayServerIOS::resize_window(CGSize viewSize) { - Size2i size = Size2i(viewSize.width, viewSize.height) * screen_get_max_scale(); - -#if defined(RD_ENABLED) - if (rendering_context) { - rendering_context->window_set_size(MAIN_WINDOW_ID, size.x, size.y); - } -#endif - - Variant resize_rect = Rect2i(Point2i(), size); - _window_callback(window_resize_callback, resize_rect); -} - -void DisplayServerIOS::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window) { - _THREAD_SAFE_METHOD_ -#if defined(RD_ENABLED) - if (rendering_context) { - rendering_context->window_set_vsync_mode(p_window, p_vsync_mode); - } -#endif -} - -DisplayServer::VSyncMode DisplayServerIOS::window_get_vsync_mode(WindowID p_window) const { - _THREAD_SAFE_METHOD_ -#if defined(RD_ENABLED) - if (rendering_context) { - return rendering_context->window_get_vsync_mode(p_window); - } -#endif - return DisplayServer::VSYNC_ENABLED; -} diff --git a/platform/ios/doc_classes/EditorExportPlatformIOS.xml b/platform/ios/doc_classes/EditorExportPlatformIOS.xml index 0632e9f0d99e..96ea2a7b46a2 100644 --- a/platform/ios/doc_classes/EditorExportPlatformIOS.xml +++ b/platform/ios/doc_classes/EditorExportPlatformIOS.xml @@ -1,5 +1,5 @@ - + Exporter for iOS. @@ -24,10 +24,10 @@ Unique application identifier in a reverse-DNS format, can only contain alphanumeric characters ([code]A-Z[/code], [code]a-z[/code], and [code]0-9[/code]), hyphens ([code]-[/code]), and periods ([code].[/code]). - The "Full Name", "Common Name" or SHA-1 hash of the signing identity used for debug export. + The "Full Name", "Common Name", or SHA-1 hash of the signing identity used for debug export. - The "Full Name", "Common Name" or SHA-1 hash of the signing identity used for release export. + The "Full Name", "Common Name", or SHA-1 hash of the signing identity used for release export. If [code]true[/code], existing "project name" and "project name.xcodeproj" in the export destination directory will be unconditionally deleted during export. @@ -48,20 +48,20 @@ Minimum version of iOS required for this application to run in the [code]major.minor.patch[/code] or [code]major.minor[/code] format, can only contain numeric characters ([code]0-9[/code]) and periods ([code].[/code]). - Name of the provisioning profile. Sets XCode PROVISIONING_PROFILE_SPECIFIER for debug. [url=https://developer.apple.com/documentation/xcode/build-settings-reference#Provisioning-Profile]Used for manual provisioning[/url]. - Can be overridden with the environment variable [code]GODOT_IOS_PROFILE_SPECIFIER_DEBUG[/code]. + Name of the provisioning profile. Sets Xcode PROVISIONING_PROFILE_SPECIFIER for debug. [url=https://developer.apple.com/documentation/xcode/build-settings-reference#Provisioning-Profile]Used for manual provisioning[/url]. + Can be overridden with the environment variable [code]GODOT_APPLE_PLATFORM_PROFILE_SPECIFIER_DEBUG[/code]. - Name of the provisioning profile. Sets XCode PROVISIONING_PROFILE_SPECIFIER for release. [url=https://developer.apple.com/documentation/xcode/build-settings-reference#Provisioning-Profile]Used for manual provisioning[/url]. - Can be overridden with the environment variable [code]GODOT_IOS_PROFILE_SPECIFIER_RELEASE[/code]. + Name of the provisioning profile. Sets Xcode PROVISIONING_PROFILE_SPECIFIER for release. [url=https://developer.apple.com/documentation/xcode/build-settings-reference#Provisioning-Profile]Used for manual provisioning[/url]. + Can be overridden with the environment variable [code]GODOT_APPLE_PLATFORM_PROFILE_SPECIFIER_RELEASE[/code]. UUID of the provisioning profile. If left empty, Xcode will download or create a provisioning profile automatically. See [url=https://developer.apple.com/help/account/manage-profiles/edit-download-or-delete-profiles]Edit, download, or delete provisioning profiles[/url]. - Can be overridden with the environment variable [code]GODOT_IOS_PROVISIONING_PROFILE_UUID_DEBUG[/code]. + Can be overridden with the environment variable [code]GODOT_APPLE_PLATFORM_PROVISIONING_PROFILE_UUID_DEBUG[/code]. UUID of the provisioning profile. If left empty, Xcode will download or create a provisioning profile automatically. See [url=https://developer.apple.com/help/account/manage-profiles/edit-download-or-delete-profiles]Edit, download, or delete provisioning profiles[/url]. - Can be overridden with the environment variable [code]GODOT_IOS_PROVISIONING_PROFILE_UUID_RELEASE[/code]. + Can be overridden with the environment variable [code]GODOT_APPLE_PLATFORM_PROVISIONING_PROFILE_UUID_RELEASE[/code]. Application version visible to the user, can only contain numeric characters ([code]0-9[/code]) and periods ([code].[/code]). Falls back to [member ProjectSettings.application/config/version] if left empty. @@ -86,7 +86,7 @@ Requires the graphics performance and features of the A12 Bionic and later chips (devices supporting all Vulkan renderer features). - Enabling this option limits supported devices to: iPhone XS, iPhone XR, iPad Mini (5th gen.), iPad Air (3rd gen.), iPad (8th gen) and newer. + Enabling this option limits supported devices to: iPhone XS, iPhone XR, iPad Mini (5th gen.), iPad Air (3rd gen.), iPad (8th gen), and newer. Requires the graphics performance and features of the A17 Pro and later chips. diff --git a/platform/ios/export/export_plugin.cpp b/platform/ios/export/export_plugin.cpp index ab13f23a0a94..01c8c296f9a5 100644 --- a/platform/ios/export/export_plugin.cpp +++ b/platform/ios/export/export_plugin.cpp @@ -33,386 +33,48 @@ #include "logo_svg.gen.h" #include "run_icon_svg.gen.h" -#include "core/io/json.h" -#include "core/io/plist.h" -#include "core/string/translation.h" -#include "editor/editor_node.h" -#include "editor/editor_paths.h" -#include "editor/editor_string_names.h" -#include "editor/export/editor_export.h" -#include "editor/export/lipo.h" -#include "editor/export/macho.h" -#include "editor/import/resource_importer_texture_settings.h" -#include "editor/plugins/script_editor_plugin.h" -#include "editor/themes/editor_scale.h" - -#include "modules/modules_enabled.gen.h" // For mono. -#include "modules/svg/image_loader_svg.h" - -void EditorExportPlatformIOS::get_preset_features(const Ref &p_preset, List *r_features) const { - // Vulkan and OpenGL ES 3.0 both mandate ETC2 support. - r_features->push_back("etc2"); - r_features->push_back("astc"); - - Vector architectures = _get_preset_architectures(p_preset); - for (int i = 0; i < architectures.size(); ++i) { - r_features->push_back(architectures[i]); - } -} - -Vector EditorExportPlatformIOS::_get_supported_architectures() const { - Vector archs; - archs.push_back(ExportArchitecture("arm64", true)); - return archs; -} - -struct IconInfo { - const char *preset_key; - const char *idiom; - const char *export_name; - const char *actual_size_side; - const char *scale; - const char *unscaled_size; - const bool force_opaque; -}; - -static const IconInfo icon_infos[] = { - // Settings on iPhone, iPad Pro, iPad, iPad mini - { PNAME("icons/settings_58x58"), "universal", "Icon-58", "58", "2x", "29x29", false }, - { PNAME("icons/settings_87x87"), "universal", "Icon-87", "87", "3x", "29x29", false }, - - // Notifications on iPhone, iPad Pro, iPad, iPad mini - { PNAME("icons/notification_40x40"), "universal", "Icon-40", "40", "2x", "20x20", false }, - { PNAME("icons/notification_60x60"), "universal", "Icon-60", "60", "3x", "20x20", false }, - { PNAME("icons/notification_76x76"), "universal", "Icon-76", "76", "2x", "38x38", false }, - { PNAME("icons/notification_114x114"), "universal", "Icon-114", "114", "3x", "38x38", false }, - - // Spotlight on iPhone, iPad Pro, iPad, iPad mini - { PNAME("icons/spotlight_80x80"), "universal", "Icon-80", "80", "2x", "40x40", false }, - { PNAME("icons/spotlight_120x120"), "universal", "Icon-120", "120", "3x", "40x40", false }, - - // Home Screen on iPhone - { PNAME("icons/iphone_120x120"), "universal", "Icon-120-1", "120", "2x", "60x60", false }, - { PNAME("icons/iphone_180x180"), "universal", "Icon-180", "180", "3x", "60x60", false }, - - // Home Screen on iPad Pro - { PNAME("icons/ipad_167x167"), "universal", "Icon-167", "167", "2x", "83.5x83.5", false }, - - // Home Screen on iPad, iPad mini - { PNAME("icons/ipad_152x152"), "universal", "Icon-152", "152", "2x", "76x76", false }, - - { PNAME("icons/ios_128x128"), "universal", "Icon-128", "128", "2x", "64x64", false }, - { PNAME("icons/ios_192x192"), "universal", "Icon-192", "192", "3x", "64x64", false }, - - { PNAME("icons/ios_136x136"), "universal", "Icon-136", "136", "2x", "68x68", false }, - - // App Store - { PNAME("icons/app_store_1024x1024"), "universal", "Icon-1024", "1024", "1x", "1024x1024", true }, -}; - -struct APIAccessInfo { - String prop_name; - String type_name; - Vector prop_flag_value; - Vector prop_flag_name; - int default_value; -}; - -static const APIAccessInfo api_info[] = { - { "file_timestamp", - "NSPrivacyAccessedAPICategoryFileTimestamp", - { "DDA9.1", "C617.1", "3B52.1" }, - { "Display to user on-device:", "Inside app or group container", "Files provided to app by user" }, - 3 }, - { "system_boot_time", - "NSPrivacyAccessedAPICategorySystemBootTime", - { "35F9.1", "8FFB.1", "3D61.1" }, - { "Measure time on-device", "Calculate absolute event timestamps", "User-initiated bug report" }, - 1 }, - { "disk_space", - "NSPrivacyAccessedAPICategoryDiskSpace", - { "E174.1", "85F4.1", "7D9E.1", "B728.1" }, - { "Write or delete file on-device", "Display to user on-device", "User-initiated bug report", "Health research app" }, - 3 }, - { "active_keyboard", - "NSPrivacyAccessedAPICategoryActiveKeyboards", - { "3EC4.1", "54BD.1" }, - { "Custom keyboard app on-device", "Customize UI on-device:2" }, - 0 }, - { "user_defaults", - "NSPrivacyAccessedAPICategoryUserDefaults", - { "1C8F.1", "AC6B.1", "CA92.1" }, - { "Access info from same App Group", "Access managed app configuration", "Access info from same app" }, - 0 } -}; - -struct DataCollectionInfo { - String prop_name; - String type_name; -}; - -static const DataCollectionInfo data_collect_type_info[] = { - { "name", "NSPrivacyCollectedDataTypeName" }, - { "email_address", "NSPrivacyCollectedDataTypeEmailAddress" }, - { "phone_number", "NSPrivacyCollectedDataTypePhoneNumber" }, - { "physical_address", "NSPrivacyCollectedDataTypePhysicalAddress" }, - { "other_contact_info", "NSPrivacyCollectedDataTypeOtherUserContactInfo" }, - { "health", "NSPrivacyCollectedDataTypeHealth" }, - { "fitness", "NSPrivacyCollectedDataTypeFitness" }, - { "payment_info", "NSPrivacyCollectedDataTypePaymentInfo" }, - { "credit_info", "NSPrivacyCollectedDataTypeCreditInfo" }, - { "other_financial_info", "NSPrivacyCollectedDataTypeOtherFinancialInfo" }, - { "precise_location", "NSPrivacyCollectedDataTypePreciseLocation" }, - { "coarse_location", "NSPrivacyCollectedDataTypeCoarseLocation" }, - { "sensitive_info", "NSPrivacyCollectedDataTypeSensitiveInfo" }, - { "contacts", "NSPrivacyCollectedDataTypeContacts" }, - { "emails_or_text_messages", "NSPrivacyCollectedDataTypeEmailsOrTextMessages" }, - { "photos_or_videos", "NSPrivacyCollectedDataTypePhotosorVideos" }, - { "audio_data", "NSPrivacyCollectedDataTypeAudioData" }, - { "gameplay_content", "NSPrivacyCollectedDataTypeGameplayContent" }, - { "customer_support", "NSPrivacyCollectedDataTypeCustomerSupport" }, - { "other_user_content", "NSPrivacyCollectedDataTypeOtherUserContent" }, - { "browsing_history", "NSPrivacyCollectedDataTypeBrowsingHistory" }, - { "search_hhistory", "NSPrivacyCollectedDataTypeSearchHistory" }, - { "user_id", "NSPrivacyCollectedDataTypeUserID" }, - { "device_id", "NSPrivacyCollectedDataTypeDeviceID" }, - { "purchase_history", "NSPrivacyCollectedDataTypePurchaseHistory" }, - { "product_interaction", "NSPrivacyCollectedDataTypeProductInteraction" }, - { "advertising_data", "NSPrivacyCollectedDataTypeAdvertisingData" }, - { "other_usage_data", "NSPrivacyCollectedDataTypeOtherUsageData" }, - { "crash_data", "NSPrivacyCollectedDataTypeCrashData" }, - { "performance_data", "NSPrivacyCollectedDataTypePerformanceData" }, - { "other_diagnostic_data", "NSPrivacyCollectedDataTypeOtherDiagnosticData" }, - { "environment_scanning", "NSPrivacyCollectedDataTypeEnvironmentScanning" }, - { "hands", "NSPrivacyCollectedDataTypeHands" }, - { "head", "NSPrivacyCollectedDataTypeHead" }, - { "other_data_types", "NSPrivacyCollectedDataTypeOtherDataTypes" }, -}; - -static const DataCollectionInfo data_collect_purpose_info[] = { - { "Analytics", "NSPrivacyCollectedDataTypePurposeAnalytics" }, - { "App Functionality", "NSPrivacyCollectedDataTypePurposeAppFunctionality" }, - { "Developer Advertising", "NSPrivacyCollectedDataTypePurposeDeveloperAdvertising" }, - { "Third-party Advertising", "NSPrivacyCollectedDataTypePurposeThirdPartyAdvertising" }, - { "Product Personalization", "NSPrivacyCollectedDataTypePurposeProductPersonalization" }, - { "Other", "NSPrivacyCollectedDataTypePurposeOther" }, -}; - -static const String export_method_string[] = { - "app-store", - "development", - "ad-hoc", - "enterprise" -}; -static const String storyboard_image_scale_mode[] = { - "center", - "scaleAspectFit", - "scaleAspectFill", - "scaleToFill" -}; - -String EditorExportPlatformIOS::get_export_option_warning(const EditorExportPreset *p_preset, const StringName &p_name) const { - if (p_preset) { - if (p_name == "application/app_store_team_id") { - String team_id = p_preset->get("application/app_store_team_id"); - if (team_id.is_empty()) { - return TTR("App Store Team ID not specified.") + "\n"; - } - } else if (p_name == "application/bundle_identifier") { - String identifier = p_preset->get("application/bundle_identifier"); - String pn_err; - if (!is_package_name_valid(identifier, &pn_err)) { - return TTR("Invalid Identifier:") + " " + pn_err; - } - } else if (p_name == "privacy/file_timestamp_access_reasons") { - int access = p_preset->get("privacy/file_timestamp_access_reasons"); - if (access == 0) { - return TTR("At least one file timestamp access reason should be selected."); - } - } else if (p_name == "privacy/disk_space_access_reasons") { - int access = p_preset->get("privacy/disk_space_access_reasons"); - if (access == 0) { - return TTR("At least one disk space access reason should be selected."); - } - } else if (p_name == "privacy/system_boot_time_access_reasons") { - int access = p_preset->get("privacy/system_boot_time_access_reasons"); - if (access == 0) { - return TTR("At least one system boot time access reason should be selected."); - } - } - } - return String(); -} - -void EditorExportPlatformIOS::_notification(int p_what) { -#ifdef MACOS_ENABLED - if (p_what == NOTIFICATION_POSTINITIALIZE) { - if (EditorExport::get_singleton()) { - EditorExport::get_singleton()->connect_presets_runnable_updated(callable_mp(this, &EditorExportPlatformIOS::_update_preset_status)); - } - } -#endif +EditorExportPlatformIOS::EditorExportPlatformIOS() : + EditorExportPlatformAppleEmbedded(_ios_logo_svg, _ios_run_icon_svg) { } -bool EditorExportPlatformIOS::get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const { - // Hide unsupported .NET embedding option. - if (p_option == "dotnet/embed_build_outputs") { - return false; - } - - if (p_preset == nullptr) { - return true; - } - - bool advanced_options_enabled = p_preset->are_advanced_options_enabled(); - if (p_option.begins_with("privacy") || - (p_option.begins_with("icons/") && !p_option.begins_with("icons/icon") && !p_option.begins_with("icons/app_store")) || - p_option == "custom_template/debug" || - p_option == "custom_template/release" || - p_option == "application/additional_plist_content" || - p_option == "application/delete_old_export_files_unconditionally" || - p_option == "application/icon_interpolation" || - p_option == "application/signature") { - return advanced_options_enabled; - } - - return true; +EditorExportPlatformIOS::~EditorExportPlatformIOS() { } void EditorExportPlatformIOS::get_export_options(List *r_options) const { - 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"), "")); - - Vector architectures = _get_supported_architectures(); - for (int i = 0; i < architectures.size(); ++i) { - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, vformat("%s/%s", PNAME("architectures"), architectures[i].name)), architectures[i].is_default)); - } - - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/app_store_team_id"), "", false, true)); - - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/export_method_debug", PROPERTY_HINT_ENUM, "App Store,Development,Ad-Hoc,Enterprise"), 1)); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/code_sign_identity_debug", PROPERTY_HINT_PLACEHOLDER_TEXT, "iPhone Developer"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/code_sign_identity_release", PROPERTY_HINT_PLACEHOLDER_TEXT, "iPhone Distribution"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/provisioning_profile_uuid_debug", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/provisioning_profile_uuid_release", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/provisioning_profile_specifier_debug", PROPERTY_HINT_PLACEHOLDER_TEXT, ""), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/provisioning_profile_specifier_release", PROPERTY_HINT_PLACEHOLDER_TEXT, ""), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/export_method_release", PROPERTY_HINT_ENUM, "App Store,Development,Ad-Hoc,Enterprise"), 0)); + EditorExportPlatformAppleEmbedded::get_export_options(r_options); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/targeted_device_family", PROPERTY_HINT_ENUM, "iPhone,iPad,iPhone & iPad"), 2)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/min_ios_version"), get_minimum_deployment_target())); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/bundle_identifier", PROPERTY_HINT_PLACEHOLDER_TEXT, "com.example.game"), "", false, true)); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/signature"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/short_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "Leave empty to use project version"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/version", PROPERTY_HINT_PLACEHOLDER_TEXT, "Leave empty to use project version"), "")); - - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/min_ios_version"), "14.0")); - - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/additional_plist_content", PROPERTY_HINT_MULTILINE_TEXT), "")); - - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/icon_interpolation", PROPERTY_HINT_ENUM, "Nearest neighbor,Bilinear,Cubic,Trilinear,Lanczos"), 4)); - - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "application/export_project_only"), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "application/delete_old_export_files_unconditionally"), false)); - - Vector found_plugins = get_plugins(); - for (int i = 0; i < found_plugins.size(); i++) { - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, vformat("%s/%s", PNAME("plugins"), found_plugins[i].name)), false)); - } - - HashSet plist_keys; - - for (int i = 0; i < found_plugins.size(); i++) { - // Editable plugin plist values - PluginConfigIOS plugin = found_plugins[i]; - - for (const KeyValue &E : plugin.plist) { - switch (E.value.type) { - case PluginConfigIOS::PlistItemType::STRING_INPUT: { - String preset_name = "plugins_plist/" + E.key; - if (!plist_keys.has(preset_name)) { - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, preset_name), E.value.value)); - plist_keys.insert(preset_name); - } - } break; - default: - continue; - } - } - } - - plugins_changed.clear(); - plugins = found_plugins; - - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "entitlements/increased_memory_limit"), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "entitlements/game_center"), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "entitlements/push_notifications", PROPERTY_HINT_ENUM, "Disabled,Production,Development"), "Disabled")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "entitlements/additional", PROPERTY_HINT_MULTILINE_TEXT), "")); - - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/access_wifi"), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/performance_gaming_tier"), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/performance_a12"), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "capabilities/additional"), PackedStringArray())); - - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "user_data/accessible_from_files_app"), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "user_data/accessible_from_itunes_sharing"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "storyboard/image_scale_mode", PROPERTY_HINT_ENUM, "Same as Logo,Center,Scale to Fit,Scale to Fill,Scale"), 0)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "storyboard/custom_image@2x", PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "storyboard/custom_image@3x", PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "storyboard/use_custom_bg_color"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::COLOR, "storyboard/custom_bg_color"), Color())); +} - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/camera_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the camera"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/camera_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/microphone_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the microphone"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/microphone_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/photolibrary_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need access to the photo library"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/photolibrary_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); +bool EditorExportPlatformIOS::has_valid_export_configuration(const Ref &p_preset, String &r_error, bool &r_missing_templates, bool p_debug) const { + bool valid = EditorExportPlatformAppleEmbedded::has_valid_export_configuration(p_preset, r_error, r_missing_templates, p_debug); - for (uint64_t i = 0; i < std::size(api_info); ++i) { - String prop_name = vformat("privacy/%s_access_reasons", api_info[i].prop_name); - String hint; - for (int j = 0; j < api_info[i].prop_flag_value.size(); j++) { - if (j != 0) { - hint += ","; - } - hint += vformat("%s - %s:%d", api_info[i].prop_flag_value[j], api_info[i].prop_flag_name[j], (1 << j)); + String err; + String rendering_method = get_project_setting(p_preset, "rendering/renderer/rendering_method.mobile"); + String rendering_driver = get_project_setting(p_preset, "rendering/rendering_device/driver." + get_platform_name()); + if ((rendering_method == "forward_plus" || rendering_method == "mobile") && rendering_driver == "metal") { + float version = p_preset->get("application/min_ios_version").operator String().to_float(); + if (version < 14.0) { + err += TTR("Metal renderer require iOS 14+.") + "\n"; } - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, prop_name, PROPERTY_HINT_FLAGS, hint), api_info[i].default_value)); } - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "privacy/tracking_enabled"), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "privacy/tracking_domains"), Vector())); - - { - String hint; - for (uint64_t i = 0; i < std::size(data_collect_purpose_info); ++i) { - if (i != 0) { - hint += ","; - } - hint += vformat("%s:%d", data_collect_purpose_info[i].prop_name, (1 << i)); - } - for (uint64_t i = 0; i < std::size(data_collect_type_info); ++i) { - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, vformat("privacy/collected_data/%s/collected", data_collect_type_info[i].prop_name)), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, vformat("privacy/collected_data/%s/linked_to_user", data_collect_type_info[i].prop_name)), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, vformat("privacy/collected_data/%s/used_for_tracking", data_collect_type_info[i].prop_name)), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, vformat("privacy/collected_data/%s/collection_purposes", data_collect_type_info[i].prop_name), PROPERTY_HINT_FLAGS, hint), 0)); + if (!err.is_empty()) { + if (!r_error.is_empty()) { + r_error += err; + } else { + r_error = err; } } - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/icon_1024x1024", PROPERTY_HINT_FILE, "*.svg,*.png,*.webp,*.jpg,*.jpeg"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/icon_1024x1024_dark", PROPERTY_HINT_FILE, "*.svg,*.png,*.webp,*.jpg,*.jpeg"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/icon_1024x1024_tinted", PROPERTY_HINT_FILE, "*.svg,*.png,*.webp,*.jpg,*.jpeg"), "")); - - HashSet used_names; - for (uint64_t i = 0; i < std::size(icon_infos); ++i) { - if (!used_names.has(icon_infos[i].preset_key)) { - used_names.insert(icon_infos[i].preset_key); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, String(icon_infos[i].preset_key), PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, String(icon_infos[i].preset_key) + "_dark", PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, String(icon_infos[i].preset_key) + "_tinted", PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), "")); - } - } - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "storyboard/image_scale_mode", PROPERTY_HINT_ENUM, "Same as Logo,Center,Scale to Fit,Scale to Fill,Scale"), 0)); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "storyboard/custom_image@2x", PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "storyboard/custom_image@3x", PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "storyboard/use_custom_bg_color"), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::COLOR, "storyboard/custom_bg_color"), Color())); + return valid; } HashMap EditorExportPlatformIOS::get_custom_project_settings(const Ref &p_preset) const { @@ -436,515 +98,101 @@ HashMap EditorExportPlatformIOS::get_custom_project_settings(co return settings; } -void EditorExportPlatformIOS::_fix_config_file(const Ref &p_preset, Vector &pfile, const IOSConfigData &p_config, bool p_debug) { - String dbg_sign_id = p_preset->get("application/code_sign_identity_debug").operator String().is_empty() ? "iPhone Developer" : p_preset->get("application/code_sign_identity_debug"); - String rel_sign_id = p_preset->get("application/code_sign_identity_release").operator String().is_empty() ? "iPhone Distribution" : p_preset->get("application/code_sign_identity_release"); - bool dbg_manual = !p_preset->get_or_env("application/provisioning_profile_uuid_debug", ENV_IOS_PROFILE_UUID_DEBUG).operator String().is_empty() || (dbg_sign_id != "iPhone Developer" && dbg_sign_id != "iPhone Distribution"); - bool rel_manual = !p_preset->get_or_env("application/provisioning_profile_uuid_release", ENV_IOS_PROFILE_UUID_RELEASE).operator String().is_empty() || (rel_sign_id != "iPhone Developer" && rel_sign_id != "iPhone Distribution"); - - String provisioning_profile_specifier_dbg = p_preset->get_or_env("application/provisioning_profile_specifier_debug", ENV_IOS_PROFILE_SPECIFIER_DEBUG).operator String(); - bool valid_dbg_specifier = !provisioning_profile_specifier_dbg.is_empty(); - dbg_manual |= valid_dbg_specifier; - - String provisioning_profile_specifier_rel = p_preset->get_or_env("application/provisioning_profile_specifier_release", ENV_IOS_PROFILE_SPECIFIER_RELEASE).operator String(); - bool valid_rel_specifier = !provisioning_profile_specifier_rel.is_empty(); - rel_manual |= valid_rel_specifier; - - String str = String::utf8((const char *)pfile.ptr(), pfile.size()); - String strnew; - Vector lines = str.split("\n"); - for (int i = 0; i < lines.size(); i++) { - if (lines[i].contains("$binary")) { - strnew += lines[i].replace("$binary", p_config.binary_name) + "\n"; - } else if (lines[i].contains("$modules_buildfile")) { - strnew += lines[i].replace("$modules_buildfile", p_config.modules_buildfile) + "\n"; - } else if (lines[i].contains("$modules_fileref")) { - strnew += lines[i].replace("$modules_fileref", p_config.modules_fileref) + "\n"; - } else if (lines[i].contains("$modules_buildphase")) { - strnew += lines[i].replace("$modules_buildphase", p_config.modules_buildphase) + "\n"; - } else if (lines[i].contains("$modules_buildgrp")) { - strnew += lines[i].replace("$modules_buildgrp", p_config.modules_buildgrp) + "\n"; - } else if (lines[i].contains("$name")) { - strnew += lines[i].replace("$name", p_config.pkg_name) + "\n"; - } else if (lines[i].contains("$bundle_identifier")) { - strnew += lines[i].replace("$bundle_identifier", p_preset->get("application/bundle_identifier")) + "\n"; - } else if (lines[i].contains("$short_version")) { - strnew += lines[i].replace("$short_version", p_preset->get_version("application/short_version")) + "\n"; - } else if (lines[i].contains("$version")) { - strnew += lines[i].replace("$version", p_preset->get_version("application/version")) + "\n"; - } else if (lines[i].contains("$min_version")) { - strnew += lines[i].replace("$min_version", p_preset->get("application/min_ios_version")) + "\n"; - } else if (lines[i].contains("$signature")) { - strnew += lines[i].replace("$signature", p_preset->get("application/signature")) + "\n"; - } else if (lines[i].contains("$team_id")) { - strnew += lines[i].replace("$team_id", p_preset->get("application/app_store_team_id")) + "\n"; - } else if (lines[i].contains("$default_build_config")) { - strnew += lines[i].replace("$default_build_config", p_debug ? "Debug" : "Release") + "\n"; - } else if (lines[i].contains("$export_method")) { - int export_method = p_preset->get(p_debug ? "application/export_method_debug" : "application/export_method_release"); - strnew += lines[i].replace("$export_method", export_method_string[export_method]) + "\n"; - } else if (lines[i].contains("$provisioning_profile_specifier_debug")) { - strnew += lines[i].replace("$provisioning_profile_specifier_debug", provisioning_profile_specifier_dbg) + "\n"; - } else if (lines[i].contains("$provisioning_profile_specifier_release")) { - strnew += lines[i].replace("$provisioning_profile_specifier_release", provisioning_profile_specifier_rel) + "\n"; - } else if (lines[i].contains("$provisioning_profile_specifier")) { - String specifier = p_debug ? provisioning_profile_specifier_dbg : provisioning_profile_specifier_rel; - strnew += lines[i].replace("$provisioning_profile_specifier", specifier) + "\n"; - } else if (lines[i].contains("$provisioning_profile_uuid_release")) { - strnew += lines[i].replace("$provisioning_profile_uuid_release", p_preset->get_or_env("application/provisioning_profile_uuid_release", ENV_IOS_PROFILE_UUID_RELEASE)) + "\n"; - } else if (lines[i].contains("$provisioning_profile_uuid_debug")) { - strnew += lines[i].replace("$provisioning_profile_uuid_debug", p_preset->get_or_env("application/provisioning_profile_uuid_debug", ENV_IOS_PROFILE_UUID_DEBUG)) + "\n"; - } else if (lines[i].contains("$code_sign_style_debug")) { - if (dbg_manual) { - strnew += lines[i].replace("$code_sign_style_debug", "Manual") + "\n"; - } else { - strnew += lines[i].replace("$code_sign_style_debug", "Automatic") + "\n"; - } - } else if (lines[i].contains("$code_sign_style_release")) { - if (rel_manual) { - strnew += lines[i].replace("$code_sign_style_release", "Manual") + "\n"; - } else { - strnew += lines[i].replace("$code_sign_style_release", "Automatic") + "\n"; - } - } else if (lines[i].contains("$provisioning_profile_uuid")) { - String uuid = p_debug ? p_preset->get_or_env("application/provisioning_profile_uuid_debug", ENV_IOS_PROFILE_UUID_DEBUG) : p_preset->get_or_env("application/provisioning_profile_uuid_release", ENV_IOS_PROFILE_UUID_RELEASE); - if (uuid.is_empty()) { - Variant variant = p_debug ? provisioning_profile_specifier_dbg : provisioning_profile_specifier_rel; - bool valid = p_debug ? valid_dbg_specifier : valid_rel_specifier; - uuid = valid ? variant : ""; - } - strnew += lines[i].replace("$provisioning_profile_uuid", uuid) + "\n"; - } else if (lines[i].contains("$code_sign_identity_debug")) { - strnew += lines[i].replace("$code_sign_identity_debug", dbg_sign_id) + "\n"; - } else if (lines[i].contains("$code_sign_identity_release")) { - strnew += lines[i].replace("$code_sign_identity_release", rel_sign_id) + "\n"; - } else if (lines[i].contains("$additional_plist_content")) { - strnew += lines[i].replace("$additional_plist_content", p_config.plist_content) + "\n"; - } else if (lines[i].contains("$godot_archs")) { - strnew += lines[i].replace("$godot_archs", p_config.architectures) + "\n"; - } else if (lines[i].contains("$linker_flags")) { - strnew += lines[i].replace("$linker_flags", p_config.linker_flags) + "\n"; - } else if (lines[i].contains("$targeted_device_family")) { - String xcode_value; - switch ((int)p_preset->get("application/targeted_device_family")) { - case 0: // iPhone - xcode_value = "1"; - break; - case 1: // iPad - xcode_value = "2"; - break; - case 2: // iPhone & iPad - xcode_value = "1,2"; - break; - } - strnew += lines[i].replace("$targeted_device_family", xcode_value) + "\n"; - } else if (lines[i].contains("$cpp_code")) { - strnew += lines[i].replace("$cpp_code", p_config.cpp_code) + "\n"; - } else if (lines[i].contains("$docs_in_place")) { - strnew += lines[i].replace("$docs_in_place", ((bool)p_preset->get("user_data/accessible_from_files_app")) ? "" : "") + "\n"; - } else if (lines[i].contains("$docs_sharing")) { - strnew += lines[i].replace("$docs_sharing", ((bool)p_preset->get("user_data/accessible_from_itunes_sharing")) ? "" : "") + "\n"; - } else if (lines[i].contains("$entitlements_full")) { - String entitlements; - if ((String)p_preset->get("entitlements/push_notifications") != "Disabled") { - entitlements += "aps-environment\n" + p_preset->get("entitlements/push_notifications").operator String().to_lower() + "" + "\n"; - } - if ((bool)p_preset->get("entitlements/game_center")) { - entitlements += "com.apple.developer.game-center\n\n"; - } - if ((bool)p_preset->get("entitlements/increased_memory_limit")) { - entitlements += "com.apple.developer.kernel.increased-memory-limit\n\n"; - } - entitlements += p_preset->get("entitlements/additional").operator String() + "\n"; - - strnew += lines[i].replace("$entitlements_full", entitlements); - } else if (lines[i].contains("$required_device_capabilities")) { - String capabilities; - - // I've removed armv7 as we can run on 64bit only devices - // Note that capabilities listed here are requirements for the app to be installed. - // They don't enable anything. - Vector capabilities_list = p_config.capabilities; +Error EditorExportPlatformIOS::_export_loading_screen_file(const Ref &p_preset, const String &p_dest_dir) { + const String custom_launch_image_2x = p_preset->get("storyboard/custom_image@2x"); + const String custom_launch_image_3x = p_preset->get("storyboard/custom_image@3x"); - if ((bool)p_preset->get("capabilities/access_wifi") && !capabilities_list.has("wifi")) { - capabilities_list.push_back("wifi"); - } - if ((bool)p_preset->get("capabilities/performance_gaming_tier") && !capabilities_list.has("iphone-performance-gaming-tier")) { - capabilities_list.push_back("iphone-performance-gaming-tier"); - } - if ((bool)p_preset->get("capabilities/performance_a12") && !capabilities_list.has("iphone-ipad-minimum-performance-a12")) { - capabilities_list.push_back("iphone-ipad-minimum-performance-a12"); - } - for (int idx = 0; idx < capabilities_list.size(); idx++) { - capabilities += "" + capabilities_list[idx] + "\n"; - } - for (const String &cap : p_preset->get("capabilities/additional").operator PackedStringArray()) { - capabilities += "" + cap + "\n"; - } + if (custom_launch_image_2x.length() > 0 && custom_launch_image_3x.length() > 0) { + String image_path = p_dest_dir.path_join("splash@2x.png"); + Error err = OK; + Ref image = _load_icon_or_splash_image(custom_launch_image_2x, &err); - strnew += lines[i].replace("$required_device_capabilities", capabilities); - } else if (lines[i].contains("$interface_orientations")) { - String orientations; - const DisplayServer::ScreenOrientation screen_orientation = - DisplayServer::ScreenOrientation(int(get_project_setting(p_preset, "display/window/handheld/orientation"))); + if (err != OK || image.is_null() || image->is_empty()) { + return err; + } - switch (screen_orientation) { - case DisplayServer::SCREEN_LANDSCAPE: - orientations += "UIInterfaceOrientationLandscapeLeft\n"; - break; - case DisplayServer::SCREEN_PORTRAIT: - orientations += "UIInterfaceOrientationPortrait\n"; - break; - case DisplayServer::SCREEN_REVERSE_LANDSCAPE: - orientations += "UIInterfaceOrientationLandscapeRight\n"; - break; - case DisplayServer::SCREEN_REVERSE_PORTRAIT: - orientations += "UIInterfaceOrientationPortraitUpsideDown\n"; - break; - case DisplayServer::SCREEN_SENSOR_LANDSCAPE: - // Allow both landscape orientations depending on sensor direction. - orientations += "UIInterfaceOrientationLandscapeLeft\n"; - orientations += "UIInterfaceOrientationLandscapeRight\n"; - break; - case DisplayServer::SCREEN_SENSOR_PORTRAIT: - // Allow both portrait orientations depending on sensor direction. - orientations += "UIInterfaceOrientationPortrait\n"; - orientations += "UIInterfaceOrientationPortraitUpsideDown\n"; - break; - case DisplayServer::SCREEN_SENSOR: - // Allow all screen orientations depending on sensor direction. - orientations += "UIInterfaceOrientationLandscapeLeft\n"; - orientations += "UIInterfaceOrientationLandscapeRight\n"; - orientations += "UIInterfaceOrientationPortrait\n"; - orientations += "UIInterfaceOrientationPortraitUpsideDown\n"; - break; - } + if (image->save_png(image_path) != OK) { + return ERR_FILE_CANT_WRITE; + } - strnew += lines[i].replace("$interface_orientations", orientations); - } else if (lines[i].contains("$ipad_interface_orientations")) { - String orientations; - const DisplayServer::ScreenOrientation screen_orientation = - DisplayServer::ScreenOrientation(int(get_project_setting(p_preset, "display/window/handheld/orientation"))); + image_path = p_dest_dir.path_join("splash@3x.png"); + image = _load_icon_or_splash_image(custom_launch_image_3x, &err); - switch (screen_orientation) { - case DisplayServer::SCREEN_LANDSCAPE: - orientations += "UIInterfaceOrientationLandscapeRight\n"; - break; - case DisplayServer::SCREEN_PORTRAIT: - orientations += "UIInterfaceOrientationPortrait\n"; - break; - case DisplayServer::SCREEN_REVERSE_LANDSCAPE: - orientations += "UIInterfaceOrientationLandscapeLeft\n"; - break; - case DisplayServer::SCREEN_REVERSE_PORTRAIT: - orientations += "UIInterfaceOrientationPortraitUpsideDown\n"; - break; - case DisplayServer::SCREEN_SENSOR_LANDSCAPE: - // Allow both landscape orientations depending on sensor direction. - orientations += "UIInterfaceOrientationLandscapeLeft\n"; - orientations += "UIInterfaceOrientationLandscapeRight\n"; - break; - case DisplayServer::SCREEN_SENSOR_PORTRAIT: - // Allow both portrait orientations depending on sensor direction. - orientations += "UIInterfaceOrientationPortrait\n"; - orientations += "UIInterfaceOrientationPortraitUpsideDown\n"; - break; - case DisplayServer::SCREEN_SENSOR: - // Allow all screen orientations depending on sensor direction. - orientations += "UIInterfaceOrientationLandscapeLeft\n"; - orientations += "UIInterfaceOrientationLandscapeRight\n"; - orientations += "UIInterfaceOrientationPortrait\n"; - orientations += "UIInterfaceOrientationPortraitUpsideDown\n"; - break; - } + if (err != OK || image.is_null() || image->is_empty()) { + return err; + } - strnew += lines[i].replace("$ipad_interface_orientations", orientations); - } else if (lines[i].contains("$camera_usage_description")) { - String description = p_preset->get("privacy/camera_usage_description"); - strnew += lines[i].replace("$camera_usage_description", description) + "\n"; - } else if (lines[i].contains("$microphone_usage_description")) { - String description = p_preset->get("privacy/microphone_usage_description"); - strnew += lines[i].replace("$microphone_usage_description", description) + "\n"; - } else if (lines[i].contains("$photolibrary_usage_description")) { - String description = p_preset->get("privacy/photolibrary_usage_description"); - strnew += lines[i].replace("$photolibrary_usage_description", description) + "\n"; - } else if (lines[i].contains("$plist_launch_screen_name")) { - String value = "UILaunchStoryboardName\nLaunch Screen"; - strnew += lines[i].replace("$plist_launch_screen_name", value) + "\n"; - } else if (lines[i].contains("$pbx_launch_screen_file_reference")) { - String value = "90DD2D9D24B36E8000717FE1 = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = \"Launch Screen.storyboard\"; sourceTree = \"\"; };"; - strnew += lines[i].replace("$pbx_launch_screen_file_reference", value) + "\n"; - } else if (lines[i].contains("$pbx_launch_screen_copy_files")) { - String value = "90DD2D9D24B36E8000717FE1 /* Launch Screen.storyboard */,"; - strnew += lines[i].replace("$pbx_launch_screen_copy_files", value) + "\n"; - } else if (lines[i].contains("$pbx_launch_screen_build_phase")) { - String value = "90DD2D9E24B36E8000717FE1 /* Launch Screen.storyboard in Resources */,"; - strnew += lines[i].replace("$pbx_launch_screen_build_phase", value) + "\n"; - } else if (lines[i].contains("$pbx_launch_screen_build_reference")) { - String value = "90DD2D9E24B36E8000717FE1 /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 90DD2D9D24B36E8000717FE1 /* Launch Screen.storyboard */; };"; - strnew += lines[i].replace("$pbx_launch_screen_build_reference", value) + "\n"; -#ifndef DISABLE_DEPRECATED - } else if (lines[i].contains("$pbx_launch_image_usage_setting")) { - strnew += lines[i].replace("$pbx_launch_image_usage_setting", "") + "\n"; -#endif - } else if (lines[i].contains("$launch_screen_image_mode")) { - int image_scale_mode = p_preset->get("storyboard/image_scale_mode"); - String value; + if (image->save_png(image_path) != OK) { + return ERR_FILE_CANT_WRITE; + } + } else { + Error err = OK; + Ref splash; - switch (image_scale_mode) { - case 0: { - String logo_path = get_project_setting(p_preset, "application/boot_splash/image"); - bool is_on = get_project_setting(p_preset, "application/boot_splash/fullsize"); - // If custom logo is not specified, Godot does not scale default one, so we should do the same. - value = (is_on && logo_path.length() > 0) ? "scaleAspectFit" : "center"; - } break; - default: { - value = storyboard_image_scale_mode[image_scale_mode - 1]; - } - } + const String splash_path = get_project_setting(p_preset, "application/boot_splash/image"); - strnew += lines[i].replace("$launch_screen_image_mode", value) + "\n"; - } else if (lines[i].contains("$launch_screen_background_color")) { - bool use_custom = p_preset->get("storyboard/use_custom_bg_color"); - Color color = use_custom ? p_preset->get("storyboard/custom_bg_color") : get_project_setting(p_preset, "application/boot_splash/bg_color"); - const String value_format = "red=\"$red\" green=\"$green\" blue=\"$blue\" alpha=\"$alpha\""; + if (!splash_path.is_empty()) { + splash = _load_icon_or_splash_image(splash_path, &err); + } - Dictionary value_dictionary; - value_dictionary["red"] = color.r; - value_dictionary["green"] = color.g; - value_dictionary["blue"] = color.b; - value_dictionary["alpha"] = color.a; - String value = value_format.format(value_dictionary, "$_"); + if (err != OK || splash.is_null() || splash->is_empty()) { + splash.instantiate(boot_splash_png); + } - strnew += lines[i].replace("$launch_screen_background_color", value) + "\n"; - } else if (lines[i].contains("$pbx_locale_file_reference")) { - String locale_files; - Vector translations = get_project_setting(p_preset, "internationalization/locale/translations"); - if (translations.size() > 0) { - HashSet languages; - for (const String &E : translations) { - Ref tr = ResourceLoader::load(E); - if (tr.is_valid() && tr->get_locale() != "en") { - languages.insert(tr->get_locale()); - } - } + // Using same image for both @2x and @3x + // because Godot's own boot logo uses single image for all resolutions. + // Also not using @1x image, because devices using this image variant + // are not supported by iOS 9, which is minimal target. + const String splash_png_path_2x = p_dest_dir.path_join("splash@2x.png"); + const String splash_png_path_3x = p_dest_dir.path_join("splash@3x.png"); - int index = 0; - for (const String &lang : languages) { - locale_files += "D0BCFE4518AEBDA2004A" + itos(index).pad_zeros(4) + " /* " + lang + " */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = " + lang + "; path = " + lang + ".lproj/InfoPlist.strings; sourceTree = \"\"; };\n"; - index++; - } - } - strnew += lines[i].replace("$pbx_locale_file_reference", locale_files); - } else if (lines[i].contains("$pbx_locale_build_reference")) { - String locale_files; - Vector translations = get_project_setting(p_preset, "internationalization/locale/translations"); - if (translations.size() > 0) { - HashSet languages; - for (const String &E : translations) { - Ref tr = ResourceLoader::load(E); - if (tr.is_valid() && tr->get_locale() != "en") { - languages.insert(tr->get_locale()); - } - } + if (splash->save_png(splash_png_path_2x) != OK) { + return ERR_FILE_CANT_WRITE; + } - int index = 0; - for (const String &lang : languages) { - locale_files += "D0BCFE4518AEBDA2004A" + itos(index).pad_zeros(4) + " /* " + lang + " */,\n"; - index++; - } - } - strnew += lines[i].replace("$pbx_locale_build_reference", locale_files); - } else if (lines[i].contains("$swift_runtime_migration")) { - String value = !p_config.use_swift_runtime ? "" : "LastSwiftMigration = 1250;"; - strnew += lines[i].replace("$swift_runtime_migration", value) + "\n"; - } else if (lines[i].contains("$swift_runtime_build_settings")) { - String value = !p_config.use_swift_runtime ? "" : R"( - CLANG_ENABLE_MODULES = YES; - SWIFT_OBJC_BRIDGING_HEADER = "$binary/dummy.h"; - SWIFT_VERSION = 5.0; - )"; - value = value.replace("$binary", p_config.binary_name); - strnew += lines[i].replace("$swift_runtime_build_settings", value) + "\n"; - } else if (lines[i].contains("$swift_runtime_fileref")) { - String value = !p_config.use_swift_runtime ? "" : R"( - 90B4C2AA2680BC560039117A /* dummy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "dummy.h"; sourceTree = ""; }; - 90B4C2B52680C7E90039117A /* dummy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "dummy.swift"; sourceTree = ""; }; - )"; - strnew += lines[i].replace("$swift_runtime_fileref", value) + "\n"; - } else if (lines[i].contains("$swift_runtime_binary_files")) { - String value = !p_config.use_swift_runtime ? "" : R"( - 90B4C2AA2680BC560039117A /* dummy.h */, - 90B4C2B52680C7E90039117A /* dummy.swift */, - )"; - strnew += lines[i].replace("$swift_runtime_binary_files", value) + "\n"; - } else if (lines[i].contains("$swift_runtime_buildfile")) { - String value = !p_config.use_swift_runtime ? "" : "90B4C2B62680C7E90039117A /* dummy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90B4C2B52680C7E90039117A /* dummy.swift */; };"; - strnew += lines[i].replace("$swift_runtime_buildfile", value) + "\n"; - } else if (lines[i].contains("$swift_runtime_build_phase")) { - String value = !p_config.use_swift_runtime ? "" : "90B4C2B62680C7E90039117A /* dummy.swift */,"; - strnew += lines[i].replace("$swift_runtime_build_phase", value) + "\n"; - } else if (lines[i].contains("$priv_collection")) { - bool section_opened = false; - for (uint64_t j = 0; j < std::size(data_collect_type_info); ++j) { - bool data_collected = p_preset->get(vformat("privacy/collected_data/%s/collected", data_collect_type_info[j].prop_name)); - bool linked = p_preset->get(vformat("privacy/collected_data/%s/linked_to_user", data_collect_type_info[j].prop_name)); - bool tracking = p_preset->get(vformat("privacy/collected_data/%s/used_for_tracking", data_collect_type_info[j].prop_name)); - int purposes = p_preset->get(vformat("privacy/collected_data/%s/collection_purposes", data_collect_type_info[j].prop_name)); - if (data_collected) { - if (!section_opened) { - section_opened = true; - strnew += "\tNSPrivacyCollectedDataTypes\n"; - strnew += "\t\n"; - } - strnew += "\t\t\n"; - strnew += "\t\t\tNSPrivacyCollectedDataType\n"; - strnew += vformat("\t\t\t%s\n", data_collect_type_info[j].type_name); - strnew += "\t\t\t\tNSPrivacyCollectedDataTypeLinked\n"; - if (linked) { - strnew += "\t\t\t\t\n"; - } else { - strnew += "\t\t\t\t\n"; - } - strnew += "\t\t\t\tNSPrivacyCollectedDataTypeTracking\n"; - if (tracking) { - strnew += "\t\t\t\t\n"; - } else { - strnew += "\t\t\t\t\n"; - } - if (purposes != 0) { - strnew += "\t\t\t\tNSPrivacyCollectedDataTypePurposes\n"; - strnew += "\t\t\t\t\n"; - for (uint64_t k = 0; k < std::size(data_collect_purpose_info); ++k) { - if (purposes & (1 << k)) { - strnew += vformat("\t\t\t\t\t%s\n", data_collect_purpose_info[k].type_name); - } - } - strnew += "\t\t\t\t\n"; - } - strnew += "\t\t\t\n"; - } - } - if (section_opened) { - strnew += "\t\n"; - } - } else if (lines[i].contains("$priv_tracking")) { - bool tracking = p_preset->get("privacy/tracking_enabled"); - strnew += "\tNSPrivacyTracking\n"; - if (tracking) { - strnew += "\t\n"; - } else { - strnew += "\t\n"; - } - Vector tracking_domains = p_preset->get("privacy/tracking_domains"); - if (!tracking_domains.is_empty()) { - strnew += "\tNSPrivacyTrackingDomains\n"; - strnew += "\t\n"; - for (const String &E : tracking_domains) { - strnew += "\t\t" + E + "\n"; - } - strnew += "\t\n"; - } - } else if (lines[i].contains("$priv_api_types")) { - strnew += "\t\n"; - for (uint64_t j = 0; j < std::size(api_info); ++j) { - int api_access = p_preset->get(vformat("privacy/%s_access_reasons", api_info[j].prop_name)); - if (api_access != 0) { - strnew += "\t\t\n"; - strnew += "\t\t\tNSPrivacyAccessedAPITypeReasons\n"; - strnew += "\t\t\t\n"; - for (int k = 0; k < api_info[j].prop_flag_value.size(); k++) { - if (api_access & (1 << k)) { - strnew += vformat("\t\t\t\t%s\n", api_info[j].prop_flag_value[k]); - } - } - strnew += "\t\t\t\n"; - strnew += "\t\t\tNSPrivacyAccessedAPIType\n"; - strnew += vformat("\t\t\t%s\n", api_info[j].type_name); - strnew += "\t\t\n"; - } - } - strnew += "\t\n"; - } else { - strnew += lines[i] + "\n"; + if (splash->save_png(splash_png_path_3x) != OK) { + return ERR_FILE_CANT_WRITE; } } - // !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 macOS export. - CharString cs = strnew.utf8(); - pfile.resize(cs.size() - 1); - for (int i = 0; i < cs.size() - 1; i++) { - pfile.write[i] = cs[i]; - } + return OK; } -String EditorExportPlatformIOS::_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_ios_plist_content(); - } - return result; -} +Vector EditorExportPlatformIOS::get_icon_infos() const { + Vector icon_infos; + return { + // Settings on iPhone, iPad Pro, iPad, iPad mini + { PNAME("icons/settings_58x58"), "universal", "Icon-58", "58", "2x", "29x29", false }, + { PNAME("icons/settings_87x87"), "universal", "Icon-87", "87", "3x", "29x29", false }, -String EditorExportPlatformIOS::_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_ios_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("\"", "\\\""); -} + // Notifications on iPhone, iPad Pro, iPad, iPad mini + { PNAME("icons/notification_40x40"), "universal", "Icon-40", "40", "2x", "20x20", false }, + { PNAME("icons/notification_60x60"), "universal", "Icon-60", "60", "3x", "20x20", false }, + { PNAME("icons/notification_76x76"), "universal", "Icon-76", "76", "2x", "38x38", false }, + { PNAME("icons/notification_114x114"), "universal", "Icon-114", "114", "3x", "38x38", false }, -String EditorExportPlatformIOS::_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_ios_cpp_code(); - } - return result; -} + // Spotlight on iPhone, iPad Pro, iPad, iPad mini + { PNAME("icons/spotlight_80x80"), "universal", "Icon-80", "80", "2x", "40x40", false }, + { PNAME("icons/spotlight_120x120"), "universal", "Icon-120", "120", "3x", "40x40", false }, -void EditorExportPlatformIOS::_blend_and_rotate(Ref &p_dst, Ref &p_src, bool p_rot) { - ERR_FAIL_COND(p_dst.is_null()); - ERR_FAIL_COND(p_src.is_null()); + // Home Screen on iPhone + { PNAME("icons/iphone_120x120"), "universal", "Icon-120-1", "120", "2x", "60x60", false }, + { PNAME("icons/iphone_180x180"), "universal", "Icon-180", "180", "3x", "60x60", false }, - int sw = p_rot ? p_src->get_height() : p_src->get_width(); - int sh = p_rot ? p_src->get_width() : p_src->get_height(); + // Home Screen on iPad Pro + { PNAME("icons/ipad_167x167"), "universal", "Icon-167", "167", "2x", "83.5x83.5", false }, - int x_pos = (p_dst->get_width() - sw) / 2; - int y_pos = (p_dst->get_height() - sh) / 2; + // Home Screen on iPad, iPad mini + { PNAME("icons/ipad_152x152"), "universal", "Icon-152", "152", "2x", "76x76", false }, - int xs = (x_pos >= 0) ? 0 : -x_pos; - int ys = (y_pos >= 0) ? 0 : -y_pos; + { PNAME("icons/ios_128x128"), "universal", "Icon-128", "128", "2x", "64x64", false }, + { PNAME("icons/ios_192x192"), "universal", "Icon-192", "192", "3x", "64x64", false }, - if (sw + x_pos > p_dst->get_width()) { - sw = p_dst->get_width() - x_pos; - } - if (sh + y_pos > p_dst->get_height()) { - sh = p_dst->get_height() - y_pos; - } + { PNAME("icons/ios_136x136"), "universal", "Icon-136", "136", "2x", "68x68", false }, - for (int y = ys; y < sh; y++) { - for (int x = xs; x < sw; x++) { - Color sc = p_rot ? p_src->get_pixel(p_src->get_width() - y - 1, x) : p_src->get_pixel(x, y); - Color dc = p_dst->get_pixel(x_pos + x, y_pos + y); - dc.r = (double)(sc.a * sc.r + dc.a * (1.0 - sc.a) * dc.r); - dc.g = (double)(sc.a * sc.g + dc.a * (1.0 - sc.a) * dc.g); - dc.b = (double)(sc.a * sc.b + dc.a * (1.0 - sc.a) * dc.b); - dc.a = (double)(sc.a + dc.a * (1.0 - sc.a)); - p_dst->set_pixel(x_pos + x, y_pos + y, dc); - } - } + // App Store + { PNAME("icons/app_store_1024x1024"), "universal", "Icon-1024", "1024", "1x", "1024x1024", true }, + }; } Error EditorExportPlatformIOS::_export_icons(const Ref &p_preset, const String &p_iconset_dir) { @@ -966,8 +214,9 @@ Error EditorExportPlatformIOS::_export_icons(const Ref &p_pr ICON_MAX, }; + Vector icon_infos = get_icon_infos(); bool first_icon = true; - for (uint64_t i = 0; i < std::size(icon_infos); ++i) { + for (int i = 0; i < icon_infos.size(); ++i) { for (int color_mode = ICON_NORMAL; color_mode < ICON_MAX; color_mode++) { IconInfo info = icon_infos[i]; int side_size = String(info.actual_size_side).to_int(); @@ -1070,7 +319,7 @@ Error EditorExportPlatformIOS::_export_icons(const Ref &p_pr json_description += String("}],"); } json_description += String("\"idiom\":") + "\"" + info.idiom + "\","; - json_description += String("\"platform\":\"ios\","); + json_description += String("\"platform\":\"" + get_platform_name() + "\","); json_description += String("\"size\":") + "\"" + info.unscaled_size + "\","; if (String(info.scale) != "1x") { json_description += String("\"scale\":") + "\"" + info.scale + "\","; @@ -1101,1950 +350,3 @@ Error EditorExportPlatformIOS::_export_icons(const Ref &p_pr return OK; } - -Error EditorExportPlatformIOS::_export_loading_screen_file(const Ref &p_preset, const String &p_dest_dir) { - const String custom_launch_image_2x = p_preset->get("storyboard/custom_image@2x"); - const String custom_launch_image_3x = p_preset->get("storyboard/custom_image@3x"); - - if (custom_launch_image_2x.length() > 0 && custom_launch_image_3x.length() > 0) { - String image_path = p_dest_dir.path_join("splash@2x.png"); - Error err = OK; - Ref image = _load_icon_or_splash_image(custom_launch_image_2x, &err); - - if (err != OK || image.is_null() || image->is_empty()) { - return err; - } - - if (image->save_png(image_path) != OK) { - return ERR_FILE_CANT_WRITE; - } - - image_path = p_dest_dir.path_join("splash@3x.png"); - image = _load_icon_or_splash_image(custom_launch_image_3x, &err); - - if (err != OK || image.is_null() || image->is_empty()) { - return err; - } - - if (image->save_png(image_path) != OK) { - return ERR_FILE_CANT_WRITE; - } - } else { - Error err = OK; - Ref splash; - - const String splash_path = get_project_setting(p_preset, "application/boot_splash/image"); - - if (!splash_path.is_empty()) { - splash = _load_icon_or_splash_image(splash_path, &err); - } - - if (err != OK || splash.is_null() || splash->is_empty()) { - splash.instantiate(boot_splash_png); - } - - // Using same image for both @2x and @3x - // because Godot's own boot logo uses single image for all resolutions. - // Also not using @1x image, because devices using this image variant - // are not supported by iOS 9, which is minimal target. - const String splash_png_path_2x = p_dest_dir.path_join("splash@2x.png"); - const String splash_png_path_3x = p_dest_dir.path_join("splash@3x.png"); - - if (splash->save_png(splash_png_path_2x) != OK) { - return ERR_FILE_CANT_WRITE; - } - - if (splash->save_png(splash_png_path_3x) != OK) { - return ERR_FILE_CANT_WRITE; - } - } - - return OK; -} - -Error EditorExportPlatformIOS::_walk_dir_recursive(Ref &p_da, FileHandler p_handler, void *p_userdata) { - Vector dirs; - String current_dir = p_da->get_current_dir(); - p_da->list_dir_begin(); - String path = p_da->get_next(); - while (!path.is_empty()) { - if (p_da->current_is_dir()) { - if (path != "." && path != "..") { - dirs.push_back(path); - } - } else { - Error err = p_handler(current_dir.path_join(path), p_userdata); - if (err) { - p_da->list_dir_end(); - return err; - } - } - path = p_da->get_next(); - } - p_da->list_dir_end(); - - for (int i = 0; i < dirs.size(); ++i) { - p_da->change_dir(dirs[i]); - Error err = _walk_dir_recursive(p_da, p_handler, p_userdata); - p_da->change_dir(".."); - if (err) { - return err; - } - } - - return OK; -} - -struct CodesignData { - const Ref &preset; - bool debug = false; - - CodesignData(const Ref &p_preset, bool p_debug) : - preset(p_preset), - debug(p_debug) { - } -}; - -Error EditorExportPlatformIOS::_codesign(String p_file, void *p_userdata) { - if (p_file.ends_with(".dylib")) { - CodesignData *data = static_cast(p_userdata); - print_line(String("Signing ") + p_file); - - String sign_id; - if (data->debug) { - sign_id = data->preset->get("application/code_sign_identity_debug").operator String().is_empty() ? "iPhone Developer" : data->preset->get("application/code_sign_identity_debug"); - } else { - sign_id = data->preset->get("application/code_sign_identity_release").operator String().is_empty() ? "iPhone Distribution" : data->preset->get("application/code_sign_identity_release"); - } - - List codesign_args; - codesign_args.push_back("-f"); - codesign_args.push_back("-s"); - codesign_args.push_back(sign_id); - codesign_args.push_back(p_file); - String str; - Error err = OS::get_singleton()->execute("codesign", codesign_args, &str, nullptr, true); - print_verbose("codesign (" + p_file + "):\n" + str); - - 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 EditorExportPlatformIOS::_check_xcframework_content(const String &p_path, int &r_total_libs, int &r_static_libs, int &r_dylibs, int &r_frameworks) const { - Ref plist; - plist.instantiate(); - plist->load_file(p_path.path_join("Info.plist")); - Ref root_node = plist->get_root(); - if (root_node.is_null()) { - return; - } - Dictionary root = root_node->get_value(); - if (!root.has("AvailableLibraries")) { - return; - } - Ref libs_node = root["AvailableLibraries"]; - if (libs_node.is_null()) { - return; - } - Array libs = libs_node->get_value(); - r_total_libs = libs.size(); - for (int j = 0; j < libs.size(); j++) { - Ref lib_node = libs[j]; - if (lib_node.is_null()) { - return; - } - Dictionary lib = lib_node->get_value(); - if (lib.has("BinaryPath")) { - Ref path_node = lib["BinaryPath"]; - if (path_node.is_valid()) { - String path = path_node->get_value(); - if (path.ends_with(".a")) { - r_static_libs++; - } - if (path.ends_with(".dylib")) { - r_dylibs++; - } - if (path.ends_with(".framework")) { - r_frameworks++; - } - } - } - } -} - -Error EditorExportPlatformIOS::_convert_to_framework(const String &p_source, const String &p_destination, const String &p_id) const { - print_line("Converting to .framework", p_source, " -> ", p_destination); - - Ref da = DirAccess::create_for_path(p_source); - if (da.is_null()) { - return ERR_CANT_OPEN; - } - - Ref filesystem_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - if (filesystem_da.is_null()) { - return ERR_CANT_OPEN; - } - - if (!filesystem_da->dir_exists(p_destination)) { - Error make_dir_err = filesystem_da->make_dir_recursive(p_destination); - if (make_dir_err) { - return make_dir_err; - } - } - - String asset = p_source.ends_with("/") ? p_source.left(p_source.length() - 1) : p_source; - if (asset.ends_with(".xcframework")) { - Ref plist; - plist.instantiate(); - plist->load_file(p_source.path_join("Info.plist")); - Ref root_node = plist->get_root(); - if (root_node.is_null()) { - return ERR_CANT_OPEN; - } - Dictionary root = root_node->get_value(); - if (!root.has("AvailableLibraries")) { - return ERR_CANT_OPEN; - } - Ref libs_node = root["AvailableLibraries"]; - if (libs_node.is_null()) { - return ERR_CANT_OPEN; - } - Array libs = libs_node->get_value(); - for (int j = 0; j < libs.size(); j++) { - Ref lib_node = libs[j]; - if (lib_node.is_null()) { - return ERR_CANT_OPEN; - } - Dictionary lib = lib_node->get_value(); - if (lib.has("BinaryPath") && lib.has("LibraryPath") && lib.has("LibraryIdentifier")) { - Ref bpath_node = lib["BinaryPath"]; - Ref lpath_node = lib["LibraryPath"]; - Ref lid_node = lib["LibraryIdentifier"]; - if (bpath_node.is_valid() && lpath_node.is_valid() && lid_node.is_valid()) { - String binary_path = bpath_node->get_value(); - String library_identifier = lid_node->get_value(); - - String file_name = binary_path.get_basename().get_file(); - String framework_name = file_name + ".framework"; - - bpath_node->data_string = framework_name.utf8(); - lpath_node->data_string = framework_name.utf8(); - if (!filesystem_da->dir_exists(p_destination.path_join(library_identifier))) { - filesystem_da->make_dir_recursive(p_destination.path_join(library_identifier)); - } - _convert_to_framework(p_source.path_join(library_identifier).path_join(binary_path), p_destination.path_join(library_identifier).path_join(framework_name), p_id); - if (lib.has("DebugSymbolsPath")) { - Ref dpath_node = lib["DebugSymbolsPath"]; - if (dpath_node.is_valid()) { - String dpath = dpath_node->get_value(); - if (da->dir_exists(p_source.path_join(library_identifier).path_join(dpath))) { - da->copy_dir(p_source.path_join(library_identifier).path_join(dpath), p_destination.path_join(library_identifier).path_join("dSYMs")); - } - } - } - } - } - } - String info_plist = plist->save_text(); - - Ref f = FileAccess::open(p_destination.path_join("Info.plist"), FileAccess::WRITE); - if (f.is_valid()) { - f->store_string(info_plist); - } - } else { - String file_name = p_destination.get_basename().get_file(); - String framework_name = file_name + ".framework"; - - da->copy(p_source, p_destination.path_join(file_name)); - - // 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").path_join(framework_name).path_join(file_name)); - install_name_args.push_back(p_destination.path_join(file_name)); - - OS::get_singleton()->execute("install_name_tool", install_name_args); - } - - // Creating Info.plist - { - String lib_clean_name = file_name; - for (int i = 0; i < lib_clean_name.length(); i++) { - if (!is_ascii_alphanumeric_char(lib_clean_name[i]) && lib_clean_name[i] != '.' && lib_clean_name[i] != '-') { - lib_clean_name[i] = '-'; - } - } - String info_plist_format = "\n" - "\n" - "\n" - " \n" - " CFBundleShortVersionString\n" - " 1.0\n" - " CFBundleIdentifier\n" - " $id.framework.$cl_name\n" - " CFBundleName\n" - " $name\n" - " CFBundleExecutable\n" - " $name\n" - " DTPlatformName\n" - " iphoneos\n" - " CFBundleInfoDictionaryVersion\n" - " 6.0\n" - " CFBundleVersion\n" - " 1\n" - " CFBundlePackageType\n" - " FMWK\n" - " MinimumOSVersion\n" - " 12.0\n" - " \n" - ""; - - String info_plist = info_plist_format.replace("$id", p_id).replace("$name", file_name).replace("$cl_name", lib_clean_name); - - Ref f = FileAccess::open(p_destination.path_join("Info.plist"), FileAccess::WRITE); - if (f.is_valid()) { - f->store_string(info_plist); - } - } - } - - return OK; -} - -void EditorExportPlatformIOS::_add_assets_to_project(const String &p_out_dir, 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 IOSExportAsset &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")) { - int total_libs = 0; - int static_libs = 0; - int dylibs = 0; - int frameworks = 0; - _check_xcframework_content(p_out_dir.path_join(asset.exported_path), total_libs, static_libs, dylibs, frameworks); - if (asset.should_embed && static_libs != total_libs) { - 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 EditorExportPlatformIOS::_copy_asset(const Ref &p_preset, 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) { - String binary_name = p_out_dir.get_file().get_basename(); - - Ref da = DirAccess::create_for_path(p_asset); - if (da.is_null()) { - ERR_FAIL_V_MSG(ERR_CANT_CREATE, "Can't open directory: " + p_asset + "."); - } - bool file_exists = da->file_exists(p_asset); - bool dir_exists = da->dir_exists(p_asset); - if (!file_exists && !dir_exists) { - return ERR_FILE_NOT_FOUND; - } - - String base_dir = p_asset.get_base_dir().replace("res://", "").replace(".godot/mono/temp/bin/", ""); - String asset = p_asset.ends_with("/") ? p_asset.left(p_asset.length() - 1) : p_asset; - String destination_dir; - String destination; - String asset_path; - - Ref filesystem_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - ERR_FAIL_COND_V_MSG(filesystem_da.is_null(), ERR_CANT_CREATE, "Cannot create DirAccess for path '" + p_out_dir + "'."); - - if (p_is_framework && asset.ends_with(".dylib")) { - // For iOS we need to turn .dylib into .framework - // to be able to send application to AppStore - asset_path = String("dylibs").path_join(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.path_join(framework_name); - destination_dir = p_out_dir.path_join(asset_path); - destination = destination_dir; - - // Convert to framework and copy. - Error err = _convert_to_framework(p_asset, destination, p_preset->get("application/bundle_identifier")); - if (err) { - return err; - } - } else if (p_is_framework && asset.ends_with(".xcframework")) { - // For iOS we need to turn .dylib inside .xcframework - // into .framework to be able to send application to AppStore - - int total_libs = 0; - int static_libs = 0; - int dylibs = 0; - int frameworks = 0; - _check_xcframework_content(p_asset, total_libs, static_libs, dylibs, frameworks); - - asset_path = String("dylibs").path_join(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.path_join(file_name); - destination_dir = p_out_dir.path_join(asset_path); - destination = destination_dir; - - if (dylibs > 0) { - // Convert to framework and copy. - Error err = _convert_to_framework(p_asset, destination, p_preset->get("application/bundle_identifier")); - if (err) { - return err; - } - } else { - // Copy as is. - if (!filesystem_da->dir_exists(destination_dir)) { - Error make_dir_err = filesystem_da->make_dir_recursive(destination_dir); - if (make_dir_err) { - return make_dir_err; - } - } - Error err = dir_exists ? da->copy_dir(p_asset, destination) : da->copy(p_asset, destination); - if (err) { - return err; - } - } - } else if (p_is_framework && asset.ends_with(".framework")) { - // Framework. - asset_path = String("dylibs").path_join(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.path_join(file_name); - destination_dir = p_out_dir.path_join(asset_path); - destination = destination_dir; - - // Copy as is. - if (!filesystem_da->dir_exists(destination_dir)) { - Error make_dir_err = filesystem_da->make_dir_recursive(destination_dir); - if (make_dir_err) { - return make_dir_err; - } - } - Error err = dir_exists ? da->copy_dir(p_asset, destination) : da->copy(p_asset, destination); - if (err) { - return err; - } - } else { - // Unknown resource. - 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.path_join(asset_path); - asset_path = asset_path.path_join(file_name); - destination = p_out_dir.path_join(asset_path); - - // Copy as is. - if (!filesystem_da->dir_exists(destination_dir)) { - Error make_dir_err = filesystem_da->make_dir_recursive(destination_dir); - if (make_dir_err) { - return make_dir_err; - } - } - Error err = dir_exists ? da->copy_dir(p_asset, destination) : da->copy(p_asset, destination); - if (err) { - return err; - } - } - - if (asset_path.ends_with("/")) { - asset_path = asset_path.left(asset_path.length() - 1); - } - IOSExportAsset exported_asset = { binary_name.path_join(asset_path), p_is_framework, p_should_embed }; - r_exported_assets.push_back(exported_asset); - - return OK; -} - -Error EditorExportPlatformIOS::_export_additional_assets(const Ref &p_preset, 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) { - const String &asset = p_assets[f_idx]; - if (asset.begins_with("res://")) { - Error err = _copy_asset(p_preset, p_out_dir, asset, nullptr, p_is_framework, p_should_embed, r_exported_assets); - ERR_FAIL_COND_V(err != OK, err); - } else if (asset.is_absolute_path() && ProjectSettings::get_singleton()->localize_path(asset).begins_with("res://")) { - Error err = _copy_asset(p_preset, p_out_dir, ProjectSettings::get_singleton()->localize_path(asset), nullptr, p_is_framework, p_should_embed, r_exported_assets); - ERR_FAIL_COND_V(err != OK, err); - } else { - // either SDK-builtin or already a part of the export template - IOSExportAsset exported_asset = { asset, p_is_framework, p_should_embed }; - r_exported_assets.push_back(exported_asset); - } - } - - return OK; -} - -Error EditorExportPlatformIOS::_export_additional_assets(const Ref &p_preset, 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_ios_frameworks(); - Error err = _export_additional_assets(p_preset, p_out_dir, linked_frameworks, true, false, r_exported_assets); - ERR_FAIL_COND_V(err, err); - - Vector embedded_frameworks = export_plugins[i]->get_ios_embedded_frameworks(); - err = _export_additional_assets(p_preset, p_out_dir, embedded_frameworks, true, true, r_exported_assets); - ERR_FAIL_COND_V(err, err); - - Vector project_static_libs = export_plugins[i]->get_ios_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_preset, p_out_dir, project_static_libs, true, false, r_exported_assets); - ERR_FAIL_COND_V(err, err); - - Vector ios_bundle_files = export_plugins[i]->get_ios_bundle_files(); - err = _export_additional_assets(p_preset, p_out_dir, ios_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_preset, p_out_dir, library_paths, true, true, r_exported_assets); - ERR_FAIL_COND_V(err, err); - - return OK; -} - -Vector EditorExportPlatformIOS::_get_preset_architectures(const Ref &p_preset) const { - Vector all_archs = _get_supported_architectures(); - Vector enabled_archs; - for (int i = 0; i < all_archs.size(); ++i) { - bool is_enabled = p_preset->get("architectures/" + all_archs[i].name); - if (is_enabled) { - enabled_archs.push_back(all_archs[i].name); - } - } - return enabled_archs; -} - -Error EditorExportPlatformIOS::_export_ios_plugins(const Ref &p_preset, IOSConfigData &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; - - HashSet plugin_linker_flags; - - Error err; - - for (int i = 0; i < enabled_plugins.size(); i++) { - PluginConfigIOS plugin = enabled_plugins[i]; - - // Export plugin binary. - String plugin_main_binary = PluginConfigIOS::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(p_preset, dest_dir, plugin_main_binary, &plugin_binary_result_file, true, false, r_exported_assets); - ERR_FAIL_COND_V(err != OK, 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.has(name)) { - 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.has(name)) { - 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.has(name)) { - 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.has(capability)) { - 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 - - for (const KeyValue &E : plugin.plist) { - String key = E.key; - const PluginConfigIOS::PlistItem &item = E.value; - - String value; - - switch (item.type) { - case PluginConfigIOS::PlistItemType::STRING_INPUT: { - String preset_name = "plugins_plist/" + key; - String input_value = p_preset->get(preset_name); - value = "" + input_value + ""; - } break; - default: - value = item.value; - break; - } - - if (key.is_empty() || value.is_empty()) { - continue; - } - - String plist_key = "" + key + ""; - - plist_values[plist_key] = value; - } - - // CPP Code - String definition_comment = "// Plugin: " + plugin.name + "\n"; - String initialization_method = plugin.initialization_method + "();\n"; - String deinitialization_method = plugin.deinitialization_method + "();\n"; - - plugin_definition_cpp_code += definition_comment + - "extern void " + initialization_method + - "extern void " + deinitialization_method + "\n"; - - plugin_initialization_cpp_code += "\t" + initialization_method; - plugin_deinitialization_cpp_code += "\t" + deinitialization_method; - - if (plugin.use_swift_runtime) { - p_config_data.use_swift_runtime = true; - } - } - - // Updating `Info.plist` - { - for (const KeyValue &E : plist_values) { - String key = E.key; - String value = E.value; - - if (key.is_empty() || value.is_empty()) { - continue; - } - - p_config_data.plist_content += key + value + "\n"; - } - } - - // Export files - { - // Export linked plugin dependency - err = _export_additional_assets(p_preset, dest_dir, plugin_linked_dependencies, true, false, r_exported_assets); - ERR_FAIL_COND_V(err != OK, err); - - // Export embedded plugin dependency - err = _export_additional_assets(p_preset, dest_dir, plugin_embedded_dependencies, true, true, r_exported_assets); - ERR_FAIL_COND_V(err != OK, err); - - // Export plugin files - err = _export_additional_assets(p_preset, dest_dir, plugin_files, false, false, r_exported_assets); - ERR_FAIL_COND_V(err != OK, 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_ios_plugins_initialize();\n" - "void godot_ios_plugins_deinitialize();\n" - "// Exported Plugins\n\n" - "$definition" - "// Use Plugins\n" - "void godot_ios_plugins_initialize() {\n" - "$initialization" - "}\n\n" - "void godot_ios_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 (const String &E : plugin_linker_flags) { - const String &flag = E; - - 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 EditorExportPlatformIOS::export_project(const Ref &p_preset, bool p_debug, const String &p_path, BitField p_flags) { - return _export_project_helper(p_preset, p_debug, p_path, p_flags, false); -} - -Error EditorExportPlatformIOS::_export_project_helper(const Ref &p_preset, bool p_debug, const String &p_path, BitField p_flags, bool p_oneclick) { - ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); - - const String dest_dir = p_path.get_base_dir() + "/"; - const String binary_name = p_path.get_file().get_basename(); - const String binary_dir = dest_dir + binary_name; - - if (!DirAccess::exists(dest_dir)) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Target folder does not exist or is inaccessible: \"%s\""), dest_dir)); - return ERR_FILE_BAD_PATH; - } - - bool export_project_only = p_preset->get("application/export_project_only"); - if (p_oneclick) { - export_project_only = false; // Skip for one-click deploy. - } - - EditorProgress ep("export", export_project_only ? TTR("Exporting for iOS (Project Files Only)") : TTR("Exporting for iOS"), export_project_only ? 2 : 5, true); - - String team_id = p_preset->get("application/app_store_team_id"); - ERR_FAIL_COND_V_MSG(team_id.length() == 0, ERR_CANT_OPEN, "App Store Team ID not specified - cannot configure the project."); - - String src_pkg_name; - 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.is_empty()) { - String err; - src_pkg_name = find_export_template("ios.zip", &err); - if (src_pkg_name.is_empty()) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), TTR("Export template not found.")); - return ERR_FILE_NOT_FOUND; - } - } - - { - bool delete_old = p_preset->get("application/delete_old_export_files_unconditionally"); - Ref da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - if (da.is_valid()) { - 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(binary_dir + ".xcodeproj") == OK) { - // Check directory content before deleting. - int expected_files = 0; - int total_files = 0; - if (!delete_old) { - da->list_dir_begin(); - for (String n = da->get_next(); !n.is_empty(); n = da->get_next()) { - if (!n.begins_with(".")) { // Ignore ".", ".." and hidden files. - if (da->current_is_dir()) { - if (n == "xcshareddata" || n == "project.xcworkspace") { - expected_files++; - } - } else { - if (n == "project.pbxproj") { - expected_files++; - } - } - total_files++; - } - } - da->list_dir_end(); - } - if ((total_files == 0) || (expected_files >= Math::floor(total_files * 0.8))) { - da->erase_contents_recursive(); - } else { - add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Unexpected files found in the export destination directory \"%s.xcodeproj\", delete it manually or select another destination."), binary_dir)); - return ERR_CANT_CREATE; - } - } - da->change_dir(current_dir); - - if (da->change_dir(binary_dir) == OK) { - // Check directory content before deleting. - int expected_files = 0; - int total_files = 0; - if (!delete_old) { - da->list_dir_begin(); - for (String n = da->get_next(); !n.is_empty(); n = da->get_next()) { - if (!n.begins_with(".")) { // Ignore ".", ".." and hidden files. - if (da->current_is_dir()) { - if (n == "dylibs" || n == "Images.xcassets" || n.ends_with(".lproj") || n == "godot-publish-dotnet" || n.ends_with(".xcframework") || n.ends_with(".framework")) { - expected_files++; - } - } else { - if (n == binary_name + "-Info.plist" || n == binary_name + ".entitlements" || n == "Launch Screen.storyboard" || n == "export_options.plist" || n.begins_with("dummy.") || n.ends_with(".gdip")) { - expected_files++; - } - } - total_files++; - } - } - da->list_dir_end(); - } - if ((total_files == 0) || (expected_files >= Math::floor(total_files * 0.8))) { - da->erase_contents_recursive(); - } else { - add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Unexpected files found in the export destination directory \"%s\", delete it manually or select another destination."), binary_dir)); - return ERR_CANT_CREATE; - } - } - da->change_dir(current_dir); - - if (!da->dir_exists(binary_dir)) { - Error err = da->make_dir(binary_dir); - if (err != OK) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Failed to create the directory: \"%s\""), binary_dir)); - return err; - } - } - } - } - - if (ep.step("Making .pck", 0)) { - return ERR_SKIP; - } - String pack_path = binary_dir + ".pck"; - Vector libraries; - Error err = save_pack(p_preset, p_debug, pack_path, &libraries); - if (err) { - // Message is supplied by the subroutine method. - return err; - } - - if (ep.step("Extracting and configuring Xcode project", 1)) { - return ERR_SKIP; - } - - String library_to_use = "libgodot.ios." + String(p_debug ? "debug" : "release") + ".xcframework"; - - print_line("Static framework: " + library_to_use); - String pkg_name; - if (String(get_project_setting(p_preset, "application/config/name")) != "") { - pkg_name = String(get_project_setting(p_preset, "application/config/name")); - } else { - pkg_name = "Unnamed"; - } - - bool found_library = false; - - const String project_file = "godot_ios.xcodeproj/project.pbxproj"; - HashSet files_to_parse; - files_to_parse.insert("godot_ios/godot_ios-Info.plist"); - files_to_parse.insert(project_file); - files_to_parse.insert("godot_ios/export_options.plist"); - files_to_parse.insert("godot_ios/dummy.cpp"); - files_to_parse.insert("godot_ios.xcodeproj/project.xcworkspace/contents.xcworkspacedata"); - files_to_parse.insert("godot_ios.xcodeproj/xcshareddata/xcschemes/godot_ios.xcscheme"); - files_to_parse.insert("godot_ios/godot_ios.entitlements"); - files_to_parse.insert("godot_ios/Launch Screen.storyboard"); - files_to_parse.insert("PrivacyInfo.xcprivacy"); - - IOSConfigData config_data = { - pkg_name, - binary_name, - _get_additional_plist_content(), - String(" ").join(_get_preset_architectures(p_preset)), - _get_linker_flags(), - _get_cpp_code(), - "", - "", - "", - "", - Vector(), - false - }; - - config_data.plist_content += p_preset->get("application/additional_plist_content").operator String() + "\n"; - - Vector assets; - - Ref tmp_app_path = DirAccess::create_for_path(dest_dir); - if (tmp_app_path.is_null()) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), vformat(TTR("Could not create and open the directory: \"%s\""), dest_dir)); - return ERR_CANT_CREATE; - } - - print_line("Unzipping..."); - Ref io_fa; - zlib_filefunc_def io = zipio_create_io(&io_fa); - unzFile src_pkg_zip = unzOpen2(src_pkg_name.utf8().get_data(), &io); - if (!src_pkg_zip) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), TTR("Could not open export template (not a zip file?): \"%s\".", src_pkg_name)); - return ERR_CANT_OPEN; - } - - err = _export_ios_plugins(p_preset, config_data, binary_dir, assets, p_debug); - if (err != OK) { - // TODO: Improve error reporting by using `add_message` throughout all methods called via `_export_ios_plugins`. - // For now a generic top level message would be fine, but we're ought to use proper reporting here instead of - // just fail macros and non-descriptive error return values. - add_message(EXPORT_MESSAGE_ERROR, TTR("iOS Plugins"), vformat(TTR("Failed to export iOS plugins with code %d. Please check the output log."), err)); - return err; - } - - //export rest of the files - int ret = unzGoToFirstFile(src_pkg_zip); - Vector project_file_data; - while (ret == UNZ_OK) { -#if defined(MACOS_ENABLED) || defined(LINUXBSD_ENABLED) - bool is_execute = false; -#endif - - //get filename - unz_file_info info; - char fname[16384]; - ret = unzGetCurrentFileInfo(src_pkg_zip, &info, fname, 16384, nullptr, 0, nullptr, 0); - if (ret != UNZ_OK) { - break; - } - - String file = String::utf8(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 - - if (files_to_parse.has(file)) { - _fix_config_file(p_preset, data, config_data, p_debug); - } else if (file.begins_with("libgodot.ios")) { - if (!file.begins_with(library_to_use) || file.ends_with(String("/empty"))) { - ret = unzGoToNextFile(src_pkg_zip); - continue; //ignore! - } - found_library = true; -#if defined(MACOS_ENABLED) || defined(LINUXBSD_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_ios", binary_name); - - print_line("ADDING: " + file + " size: " + itos(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) { - unzClose(src_pkg_zip); - add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not create a directory at path \"%s\"."), dir_name)); - return ERR_CANT_CREATE; - } - } - - /* write the file */ - { - Ref f = FileAccess::open(file, FileAccess::WRITE); - if (f.is_null()) { - unzClose(src_pkg_zip); - add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not write to a file at path \"%s\"."), file)); - return ERR_CANT_CREATE; - }; - f->store_buffer(data.ptr(), data.size()); - } - -#if defined(MACOS_ENABLED) || defined(LINUXBSD_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) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Requested template library '%s' not found. It might be missing from your template archive."), library_to_use)); - return ERR_FILE_NOT_FOUND; - } - - Dictionary appnames = get_project_setting(p_preset, "application/config/name_localized"); - Dictionary camera_usage_descriptions = p_preset->get("privacy/camera_usage_description_localized"); - Dictionary microphone_usage_descriptions = p_preset->get("privacy/microphone_usage_description_localized"); - Dictionary photolibrary_usage_descriptions = p_preset->get("privacy/photolibrary_usage_description_localized"); - - Vector translations = get_project_setting(p_preset, "internationalization/locale/translations"); - if (translations.size() > 0) { - { - String fname = binary_dir + "/en.lproj"; - tmp_app_path->make_dir_recursive(fname); - Ref f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE); - f->store_line("/* Localized versions of Info.plist keys */"); - f->store_line(""); - f->store_line("CFBundleDisplayName = \"" + get_project_setting(p_preset, "application/config/name").operator String() + "\";"); - f->store_line("NSCameraUsageDescription = \"" + p_preset->get("privacy/camera_usage_description").operator String() + "\";"); - f->store_line("NSMicrophoneUsageDescription = \"" + p_preset->get("privacy/microphone_usage_description").operator String() + "\";"); - f->store_line("NSPhotoLibraryUsageDescription = \"" + p_preset->get("privacy/photolibrary_usage_description").operator String() + "\";"); - } - - HashSet languages; - for (const String &E : translations) { - Ref tr = ResourceLoader::load(E); - if (tr.is_valid() && tr->get_locale() != "en") { - languages.insert(tr->get_locale()); - } - } - - for (const String &lang : languages) { - String fname = binary_dir + "/" + lang + ".lproj"; - tmp_app_path->make_dir_recursive(fname); - Ref f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE); - f->store_line("/* Localized versions of Info.plist keys */"); - f->store_line(""); - if (appnames.has(lang)) { - f->store_line("CFBundleDisplayName = \"" + appnames[lang].operator String() + "\";"); - } - if (camera_usage_descriptions.has(lang)) { - f->store_line("NSCameraUsageDescription = \"" + camera_usage_descriptions[lang].operator String() + "\";"); - } - if (microphone_usage_descriptions.has(lang)) { - f->store_line("NSMicrophoneUsageDescription = \"" + microphone_usage_descriptions[lang].operator String() + "\";"); - } - if (photolibrary_usage_descriptions.has(lang)) { - f->store_line("NSPhotoLibraryUsageDescription = \"" + photolibrary_usage_descriptions[lang].operator String() + "\";"); - } - } - } - - // 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_ios_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) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not copy a file at path \"%s\" to \"%s\"."), static_lib_path, dest_lib_file_path)); - return lib_copy_err; - } - } - } - - String iconset_dir = binary_dir + "/Images.xcassets/AppIcon.appiconset/"; - err = OK; - if (!tmp_app_path->dir_exists(iconset_dir)) { - err = tmp_app_path->make_dir_recursive(iconset_dir); - } - if (err != OK) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not create a directory at path \"%s\"."), iconset_dir)); - return err; - } - - err = _export_icons(p_preset, iconset_dir); - if (err != OK) { - // Message is supplied by the subroutine method. - return err; - } - - { - String splash_image_path = binary_dir + "/Images.xcassets/SplashImage.imageset/"; - - Ref launch_screen_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - if (launch_screen_da.is_null()) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Could not access the filesystem.")); - return ERR_CANT_CREATE; - } - - print_line("Exporting launch screen storyboard"); - - err = _export_loading_screen_file(p_preset, splash_image_path); - if (err != OK) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Failed to create a file at path \"%s\" with code %d."), splash_image_path, err)); - } - } - - if (err != OK) { - return err; - } - - print_line("Exporting additional assets"); - _export_additional_assets(p_preset, binary_dir, libraries, assets); - _add_assets_to_project(dest_dir, p_preset, project_file_data, assets); - String project_file_name = binary_dir + ".xcodeproj/project.pbxproj"; - { - Ref f = FileAccess::open(project_file_name, FileAccess::WRITE); - if (f.is_null()) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not write to a file at path \"%s\"."), project_file_name)); - return ERR_CANT_CREATE; - }; - f->store_buffer(project_file_data.ptr(), project_file_data.size()); - } - -#ifdef MACOS_ENABLED - { - if (ep.step("Code-signing dylibs", 2)) { - return ERR_SKIP; - } - Ref dylibs_dir = DirAccess::open(binary_dir + "/dylibs"); - ERR_FAIL_COND_V(dylibs_dir.is_null(), ERR_CANT_OPEN); - CodesignData codesign_data(p_preset, p_debug); - err = _walk_dir_recursive(dylibs_dir, _codesign, &codesign_data); - if (err != OK) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Code Signing"), TTR("Code signing failed, see editor log for details.")); - return err; - } - } - - if (export_project_only) { - return OK; - } - - if (ep.step("Making .xcarchive", 3)) { - return ERR_SKIP; - } - - String archive_path = p_path.get_basename() + ".xcarchive"; - List archive_args; - archive_args.push_back("-project"); - archive_args.push_back(binary_dir + ".xcodeproj"); - archive_args.push_back("-scheme"); - archive_args.push_back(binary_name); - archive_args.push_back("-sdk"); - archive_args.push_back("iphoneos"); - archive_args.push_back("-configuration"); - archive_args.push_back(p_debug ? "Debug" : "Release"); - archive_args.push_back("-destination"); - archive_args.push_back("generic/platform=iOS"); - archive_args.push_back("archive"); - archive_args.push_back("-allowProvisioningUpdates"); - archive_args.push_back("-archivePath"); - archive_args.push_back(archive_path); - - String archive_str; - err = OS::get_singleton()->execute("xcodebuild", archive_args, &archive_str, nullptr, true); - if (err != OK) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Xcode Build"), vformat(TTR("Failed to run xcodebuild with code %d"), err)); - return err; - } - - print_line("xcodebuild (.xcarchive):\n" + archive_str); - if (!archive_str.contains("** ARCHIVE SUCCEEDED **")) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Xcode Build"), TTR("Xcode project build failed, see editor log for details.")); - return FAILED; - } - - if (!p_oneclick) { - if (ep.step("Making .ipa", 4)) { - return ERR_SKIP; - } - - List export_args; - export_args.push_back("-exportArchive"); - export_args.push_back("-archivePath"); - export_args.push_back(archive_path); - export_args.push_back("-exportOptionsPlist"); - export_args.push_back(binary_dir + "/export_options.plist"); - export_args.push_back("-allowProvisioningUpdates"); - export_args.push_back("-exportPath"); - export_args.push_back(dest_dir); - - String export_str; - err = OS::get_singleton()->execute("xcodebuild", export_args, &export_str, nullptr, true); - if (err != OK) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Xcode Build"), vformat(TTR("Failed to run xcodebuild with code %d"), err)); - return err; - } - - print_line("xcodebuild (.ipa):\n" + export_str); - if (!export_str.contains("** EXPORT SUCCEEDED **")) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Xcode Build"), TTR(".ipa export failed, see editor log for details.")); - return FAILED; - } - } -#else - add_message(EXPORT_MESSAGE_WARNING, TTR("Xcode Build"), TTR(".ipa can only be built on macOS. Leaving Xcode project without building the package.")); -#endif - - return OK; -} - -bool EditorExportPlatformIOS::has_valid_export_configuration(const Ref &p_preset, String &r_error, bool &r_missing_templates, bool p_debug) const { -#if defined(MODULE_MONO_ENABLED) && !defined(MACOS_ENABLED) - // TODO: Remove this restriction when we don't rely on macOS tools to package up the native libraries anymore. - r_error += TTR("Exporting to iOS when using C#/.NET is experimental and requires macOS.") + "\n"; - return false; -#else - - String err; - bool valid = false; - -#if defined(MODULE_MONO_ENABLED) - // iOS export is still a work in progress, keep a message as a warning. - err += TTR("Exporting to iOS when using C#/.NET is experimental.") + "\n"; -#endif - // Look for export templates (first official, and if defined custom templates). - - bool dvalid = exists_export_template("ios.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; - - const String &additional_plist_content = p_preset->get("application/additional_plist_content"); - if (!additional_plist_content.is_empty()) { - const String &plist = vformat("\n" - "" - "" - "\n" - "%s\n" - "\n" - "\n", - additional_plist_content); - - String plist_err; - Ref plist_parser; - plist_parser.instantiate(); - if (!plist_parser->load_string(plist, plist_err)) { - err += TTR("Invalid additional PList content: ") + plist_err + "\n"; - valid = false; - } - } - - String rendering_method = get_project_setting(p_preset, "rendering/renderer/rendering_method.mobile"); - String rendering_driver = get_project_setting(p_preset, "rendering/rendering_device/driver.ios"); - if ((rendering_method == "forward_plus" || rendering_method == "mobile") && rendering_driver == "metal") { - float version = p_preset->get("application/min_ios_version").operator String().to_float(); - if (version < 14.0) { - err += TTR("Metal renderer require iOS 14+.") + "\n"; - } - } - - if (!err.is_empty()) { - r_error = err; - } - - return valid; -#endif // !(MODULE_MONO_ENABLED && !MACOS_ENABLED) -} - -bool EditorExportPlatformIOS::has_valid_project_configuration(const Ref &p_preset, String &r_error) const { - String err; - bool valid = true; - - // Validate the project configuration. - - List options; - get_export_options(&options); - for (const EditorExportPlatform::ExportOption &E : options) { - if (get_export_option_visibility(p_preset.ptr(), E.option.name)) { - String warn = get_export_option_warning(p_preset.ptr(), E.option.name); - if (!warn.is_empty()) { - err += warn + "\n"; - if (E.required) { - valid = false; - } - } - } - } - - if (!ResourceImporterTextureSettings::should_import_etc2_astc()) { - valid = false; - } - - if (!err.is_empty()) { - r_error = err; - } - - return valid; -} - -int EditorExportPlatformIOS::get_options_count() const { - MutexLock lock(device_lock); - return devices.size(); -} - -String EditorExportPlatformIOS::get_options_tooltip() const { - return TTR("Select device from the list"); -} - -Ref EditorExportPlatformIOS::get_option_icon(int p_index) const { - MutexLock lock(device_lock); - - Ref icon; - if (p_index >= 0 || p_index < devices.size()) { - Ref theme = EditorNode::get_singleton()->get_editor_theme(); - if (theme.is_valid()) { - if (devices[p_index].wifi) { - icon = theme->get_icon("IOSDeviceWireless", EditorStringName(EditorIcons)); - } else { - icon = theme->get_icon("IOSDeviceWired", EditorStringName(EditorIcons)); - } - } - } - return icon; -} - -String EditorExportPlatformIOS::get_option_label(int p_index) const { - ERR_FAIL_INDEX_V(p_index, devices.size(), ""); - MutexLock lock(device_lock); - return devices[p_index].name; -} - -String EditorExportPlatformIOS::get_option_tooltip(int p_index) const { - ERR_FAIL_INDEX_V(p_index, devices.size(), ""); - MutexLock lock(device_lock); - return "UUID: " + devices[p_index].id; -} - -bool EditorExportPlatformIOS::is_package_name_valid(const String &p_package, String *r_error) const { - String pname = p_package; - - if (pname.length() == 0) { - if (r_error) { - *r_error = TTR("Identifier is missing."); - } - return false; - } - - for (int i = 0; i < pname.length(); i++) { - char32_t c = pname[i]; - if (!(is_ascii_alphanumeric_char(c) || c == '-' || c == '.')) { - if (r_error) { - *r_error = vformat(TTR("The character '%s' is not allowed in Identifier."), String::chr(c)); - } - return false; - } - } - - return true; -} - -#ifdef MACOS_ENABLED -bool EditorExportPlatformIOS::_check_xcode_install() { - static bool xcode_found = false; - if (!xcode_found) { - Vector mdfind_paths; - List mdfind_args; - mdfind_args.push_back("kMDItemCFBundleIdentifier=com.apple.dt.Xcode"); - - String output; - Error err = OS::get_singleton()->execute("mdfind", mdfind_args, &output); - if (err == OK) { - mdfind_paths = output.split("\n"); - } - for (const String &found_path : mdfind_paths) { - xcode_found = !found_path.is_empty() && DirAccess::dir_exists_absolute(found_path.strip_edges()); - if (xcode_found) { - break; - } - } - } - return xcode_found; -} - -void EditorExportPlatformIOS::_check_for_changes_poll_thread(void *ud) { - EditorExportPlatformIOS *ea = static_cast(ud); - - while (!ea->quit_request.is_set()) { - // Nothing to do if we already know the plugins have changed. - if (!ea->plugins_changed.is_set()) { - MutexLock lock(ea->plugins_lock); - - Vector loaded_plugins = get_plugins(); - - if (ea->plugins.size() != loaded_plugins.size()) { - ea->plugins_changed.set(); - } else { - for (int i = 0; i < ea->plugins.size(); i++) { - if (ea->plugins[i].name != loaded_plugins[i].name || ea->plugins[i].last_updated != loaded_plugins[i].last_updated) { - ea->plugins_changed.set(); - break; - } - } - } - } - - // Check for devices updates. - Vector ldevices; - - // Enum real devices (via ios_deploy, pre Xcode 15). - String idepl = EDITOR_GET("export/ios/ios_deploy"); - if (ea->has_runnable_preset.is_set() && !idepl.is_empty()) { - String devices; - List args; - args.push_back("-c"); - args.push_back("-timeout"); - args.push_back("1"); - args.push_back("-j"); - args.push_back("-u"); - args.push_back("-I"); - - int ec = 0; - Error err = OS::get_singleton()->execute(idepl, args, &devices, &ec, true); - if (err == OK && ec == 0) { - Ref json; - json.instantiate(); - devices = "{ \"devices\":[" + devices.replace("}{", "},{") + "]}"; - err = json->parse(devices); - if (err == OK) { - Dictionary data = json->get_data(); - Array devices = data["devices"]; - for (int i = 0; i < devices.size(); i++) { - Dictionary device_event = devices[i]; - if (device_event["Event"] == "DeviceDetected") { - Dictionary device_info = device_event["Device"]; - Device nd; - nd.id = device_info["DeviceIdentifier"]; - nd.name = device_info["DeviceName"].operator String() + " (ios_deploy, " + ((device_event["Interface"] == "WIFI") ? "network" : "wired") + ")"; - nd.wifi = device_event["Interface"] == "WIFI"; - nd.use_ios_deploy = true; - ldevices.push_back(nd); - } - } - } - } - } - - // Enum devices (via Xcode). - if (ea->has_runnable_preset.is_set() && _check_xcode_install() && (FileAccess::exists("/usr/bin/xcrun") || FileAccess::exists("/bin/xcrun"))) { - String devices; - List args; - args.push_back("devicectl"); - args.push_back("list"); - args.push_back("devices"); - args.push_back("-j"); - args.push_back("-"); - args.push_back("-q"); - int ec = 0; - Error err = OS::get_singleton()->execute("xcrun", args, &devices, &ec, true); - if (err == OK && ec == 0) { - Ref json; - json.instantiate(); - err = json->parse(devices); - if (err == OK) { - const Dictionary &data = json->get_data(); - const Dictionary &result = data["result"]; - const Array &devices = result["devices"]; - for (int i = 0; i < devices.size(); i++) { - const Dictionary &device_info = devices[i]; - const Dictionary &conn_props = device_info["connectionProperties"]; - const Dictionary &dev_props = device_info["deviceProperties"]; - if (conn_props["pairingState"] == "paired" && dev_props["developerModeStatus"] == "enabled") { - Device nd; - nd.id = device_info["identifier"]; - nd.name = dev_props["name"].operator String() + " (devicectl, " + ((conn_props["transportType"] == "localNetwork") ? "network" : "wired") + ")"; - nd.wifi = conn_props["transportType"] == "localNetwork"; - ldevices.push_back(nd); - } - } - } - } - } - - // Update device list. - { - MutexLock lock(ea->device_lock); - - bool different = false; - - if (ea->devices.size() != ldevices.size()) { - different = true; - } else { - for (int i = 0; i < ea->devices.size(); i++) { - if (ea->devices[i].id != ldevices[i].id) { - different = true; - break; - } - } - } - - if (different) { - ea->devices = ldevices; - ea->devices_changed.set(); - } - } - - uint64_t sleep = 200; - 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(1000 * sleep); - if (ea->quit_request.is_set()) { - break; - } - } - } -} - -void EditorExportPlatformIOS::_update_preset_status() { - const int preset_count = EditorExport::get_singleton()->get_export_preset_count(); - bool has_runnable = false; - - for (int i = 0; i < preset_count; i++) { - const Ref &preset = EditorExport::get_singleton()->get_export_preset(i); - if (preset->get_platform() == this && preset->is_runnable()) { - has_runnable = true; - break; - } - } - - if (has_runnable) { - has_runnable_preset.set(); - } else { - has_runnable_preset.clear(); - } - devices_changed.set(); -} -#endif - -Error EditorExportPlatformIOS::run(const Ref &p_preset, int p_device, BitField p_debug_flags) { -#ifdef MACOS_ENABLED - ERR_FAIL_INDEX_V(p_device, devices.size(), ERR_INVALID_PARAMETER); - - String can_export_error; - bool can_export_missing_templates; - if (!can_export(p_preset, can_export_error, can_export_missing_templates)) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Run"), can_export_error); - return ERR_UNCONFIGURED; - } - - MutexLock lock(device_lock); - - EditorProgress ep("run", vformat(TTR("Running on %s"), devices[p_device].name), 3); - - String id = "tmpexport." + uitos(OS::get_singleton()->get_unix_time()); - - Ref filesystem_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - ERR_FAIL_COND_V_MSG(filesystem_da.is_null(), ERR_CANT_CREATE, "Cannot create DirAccess for path '" + EditorPaths::get_singleton()->get_temp_dir() + "'."); - filesystem_da->make_dir_recursive(EditorPaths::get_singleton()->get_temp_dir().path_join(id)); - String tmp_export_path = EditorPaths::get_singleton()->get_temp_dir().path_join(id).path_join("export.ipa"); - -#define CLEANUP_AND_RETURN(m_err) \ - { \ - if (filesystem_da->change_dir(EditorPaths::get_singleton()->get_temp_dir().path_join(id)) == OK) { \ - filesystem_da->erase_contents_recursive(); \ - filesystem_da->change_dir(".."); \ - filesystem_da->remove(id); \ - } \ - return m_err; \ - } \ - ((void)0) - - Device dev = devices[p_device]; - - // Export before sending to device. - Error err = _export_project_helper(p_preset, true, tmp_export_path, p_debug_flags, true); - - if (err != OK) { - CLEANUP_AND_RETURN(err); - } - - Vector cmd_args_list; - String host = EDITOR_GET("network/debug/remote_host"); - int remote_port = (int)EDITOR_GET("network/debug/remote_port"); - - if (p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST)) { - host = "localhost"; - } - - if (p_debug_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT)) { - int port = EDITOR_GET("filesystem/file_server/port"); - String passwd = EDITOR_GET("filesystem/file_server/password"); - cmd_args_list.push_back("--remote-fs"); - cmd_args_list.push_back(host + ":" + itos(port)); - if (!passwd.is_empty()) { - cmd_args_list.push_back("--remote-fs-password"); - cmd_args_list.push_back(passwd); - } - } - - if (p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG)) { - cmd_args_list.push_back("--remote-debug"); - - cmd_args_list.push_back(get_debug_protocol() + host + ":" + String::num_int64(remote_port)); - - List breakpoints; - ScriptEditor::get_singleton()->get_breakpoints(&breakpoints); - - if (breakpoints.size()) { - cmd_args_list.push_back("--breakpoints"); - String bpoints; - for (const List::Element *E = breakpoints.front(); E; E = E->next()) { - bpoints += E->get().replace(" ", "%20"); - if (E->next()) { - bpoints += ","; - } - } - - cmd_args_list.push_back(bpoints); - } - } - - if (p_debug_flags.has_flag(DEBUG_FLAG_VIEW_COLLISIONS)) { - cmd_args_list.push_back("--debug-collisions"); - } - - if (p_debug_flags.has_flag(DEBUG_FLAG_VIEW_NAVIGATION)) { - cmd_args_list.push_back("--debug-navigation"); - } - - if (dev.use_ios_deploy) { - // Deploy and run on real device (via ios-deploy). - if (ep.step("Installing and running on device...", 4)) { - CLEANUP_AND_RETURN(ERR_SKIP); - } else { - List args; - args.push_back("-u"); - args.push_back("-I"); - args.push_back("--id"); - args.push_back(dev.id); - args.push_back("--justlaunch"); - args.push_back("--bundle"); - args.push_back(EditorPaths::get_singleton()->get_temp_dir().path_join(id).path_join("export.xcarchive/Products/Applications/export.app")); - String app_args; - for (const String &E : cmd_args_list) { - app_args += E + " "; - } - if (!app_args.is_empty()) { - args.push_back("--args"); - args.push_back(app_args); - } - - String idepl = EDITOR_GET("export/ios/ios_deploy"); - if (idepl.is_empty()) { - idepl = "ios-deploy"; - } - String log; - int ec; - err = OS::get_singleton()->execute(idepl, args, &log, &ec, true); - if (err != OK) { - add_message(EXPORT_MESSAGE_WARNING, TTR("Run"), TTR("Could not start ios-deploy executable.")); - CLEANUP_AND_RETURN(err); - } - if (ec != 0) { - print_line("ios-deploy:\n" + log); - add_message(EXPORT_MESSAGE_ERROR, TTR("Run"), TTR("Installation/running failed, see editor log for details.")); - CLEANUP_AND_RETURN(ERR_UNCONFIGURED); - } - } - } else { - // Deploy and run on real device (via Xcode). - if (ep.step("Installing to device...", 3)) { - CLEANUP_AND_RETURN(ERR_SKIP); - } else { - List args; - args.push_back("devicectl"); - args.push_back("device"); - args.push_back("install"); - args.push_back("app"); - args.push_back("-d"); - args.push_back(dev.id); - args.push_back(EditorPaths::get_singleton()->get_temp_dir().path_join(id).path_join("export.xcarchive/Products/Applications/export.app")); - - String log; - int ec; - err = OS::get_singleton()->execute("xcrun", args, &log, &ec, true); - if (err != OK) { - add_message(EXPORT_MESSAGE_WARNING, TTR("Run"), TTR("Could not start device executable.")); - CLEANUP_AND_RETURN(err); - } - if (ec != 0) { - print_line("device install:\n" + log); - add_message(EXPORT_MESSAGE_ERROR, TTR("Run"), TTR("Installation failed, see editor log for details.")); - CLEANUP_AND_RETURN(ERR_UNCONFIGURED); - } - } - - if (ep.step("Running on device...", 4)) { - CLEANUP_AND_RETURN(ERR_SKIP); - } else { - List args; - args.push_back("devicectl"); - args.push_back("device"); - args.push_back("process"); - args.push_back("launch"); - args.push_back("--terminate-existing"); - args.push_back("-d"); - args.push_back(dev.id); - args.push_back(p_preset->get("application/bundle_identifier")); - for (const String &E : cmd_args_list) { - args.push_back(E); - } - - String log; - int ec; - err = OS::get_singleton()->execute("xcrun", args, &log, &ec, true); - if (err != OK) { - add_message(EXPORT_MESSAGE_WARNING, TTR("Run"), TTR("Could not start devicectl executable.")); - CLEANUP_AND_RETURN(err); - } - if (ec != 0) { - print_line("devicectl launch:\n" + log); - add_message(EXPORT_MESSAGE_ERROR, TTR("Run"), TTR("Running failed, see editor log for details.")); - } - } - } - - CLEANUP_AND_RETURN(OK); - -#undef CLEANUP_AND_RETURN -#else - return ERR_UNCONFIGURED; -#endif -} - -EditorExportPlatformIOS::EditorExportPlatformIOS() { - if (EditorNode::get_singleton()) { - Ref img = memnew(Image); - const bool upsample = !Math::is_equal_approx(Math::round(EDSCALE), EDSCALE); - - ImageLoaderSVG::create_image_from_string(img, _ios_logo_svg, EDSCALE, upsample, false); - logo = ImageTexture::create_from_image(img); - - ImageLoaderSVG::create_image_from_string(img, _ios_run_icon_svg, EDSCALE, upsample, false); - run_icon = ImageTexture::create_from_image(img); - - plugins_changed.set(); - devices_changed.set(); -#ifdef MACOS_ENABLED - _update_preset_status(); - check_for_changes_thread.start(_check_for_changes_poll_thread, this); -#endif - } -} - -EditorExportPlatformIOS::~EditorExportPlatformIOS() { -#ifdef MACOS_ENABLED - quit_request.set(); - if (check_for_changes_thread.is_started()) { - check_for_changes_thread.wait_to_finish(); - } -#endif -} diff --git a/platform/ios/export/export_plugin.h b/platform/ios/export/export_plugin.h index 8e2e0f1ca9a2..1dd210d2607b 100644 --- a/platform/ios/export/export_plugin.h +++ b/platform/ios/export/export_plugin.h @@ -30,268 +30,35 @@ #pragma once -#include "godot_plugin_config.h" +#include "editor/export/editor_export_platform_apple_embedded.h" -#include "core/config/project_settings.h" -#include "core/io/file_access.h" -#include "core/io/image_loader.h" -#include "core/io/marshalls.h" -#include "core/io/resource_saver.h" -#include "core/io/zip_io.h" -#include "core/os/os.h" -#include "core/templates/safe_refcount.h" -#include "editor/editor_settings.h" -#include "editor/export/editor_export_platform.h" -#include "main/splash.gen.h" -#include "scene/resources/image_texture.h" +class EditorExportPlatformIOS : public EditorExportPlatformAppleEmbedded { + GDCLASS(EditorExportPlatformIOS, EditorExportPlatformAppleEmbedded); -#include + virtual String get_platform_name() const override { return "ios"; } -// Optional environment variables for defining confidential information. If any -// of these is set, they will override the values set in the credentials file. -const String ENV_IOS_PROFILE_UUID_DEBUG = "GODOT_IOS_PROVISIONING_PROFILE_UUID_DEBUG"; -const String ENV_IOS_PROFILE_UUID_RELEASE = "GODOT_IOS_PROVISIONING_PROFILE_UUID_RELEASE"; -const String ENV_IOS_PROFILE_SPECIFIER_DEBUG = "GODOT_IOS_PROFILE_SPECIFIER_DEBUG"; -const String ENV_IOS_PROFILE_SPECIFIER_RELEASE = "GODOT_IOS_PROFILE_SPECIFIER_RELEASE"; + virtual String get_sdk_name() const override { return "iphoneos"; } -class EditorExportPlatformIOS : public EditorExportPlatform { - GDCLASS(EditorExportPlatformIOS, EditorExportPlatform); + virtual String get_minimum_deployment_target() const override { return "14.0"; } - Ref logo; - Ref run_icon; + virtual Vector get_icon_infos() const override; - // Plugins - mutable SafeFlag plugins_changed; - SafeFlag devices_changed; - - struct Device { - String id; - String name; - bool wifi = false; - bool use_ios_deploy = false; - }; - - Vector devices; - Mutex device_lock; - - Mutex plugins_lock; - mutable Vector plugins; -#ifdef MACOS_ENABLED - Thread check_for_changes_thread; - SafeFlag quit_request; - SafeFlag has_runnable_preset; - - static bool _check_xcode_install(); - static void _check_for_changes_poll_thread(void *ud); - void _update_preset_status(); -#endif - - typedef Error (*FileHandler)(String p_file, void *p_userdata); - static Error _walk_dir_recursive(Ref &p_da, FileHandler p_handler, void *p_userdata); - static Error _codesign(String p_file, void *p_userdata); - void _blend_and_rotate(Ref &p_dst, Ref &p_src, bool p_rot); - - struct IOSConfigData { - String pkg_name; - String binary_name; - String plist_content; - String architectures; - String linker_flags; - String cpp_code; - String modules_buildfile; - String modules_fileref; - String modules_buildphase; - String modules_buildgrp; - Vector capabilities; - bool use_swift_runtime; - }; - struct ExportArchitecture { - String name; - bool is_default = false; - - ExportArchitecture() {} - - ExportArchitecture(String p_name, bool p_is_default) { - name = p_name; - is_default = p_is_default; - } - }; - - struct IOSExportAsset { - String exported_path; - bool is_framework = false; // framework is anything linked to the binary, otherwise it's a resource - bool should_embed = false; - }; - - String _get_additional_plist_content(); - String _get_linker_flags(); - String _get_cpp_code(); - void _fix_config_file(const Ref &p_preset, Vector &pfile, const IOSConfigData &p_config, bool p_debug); - Error _export_loading_screen_file(const Ref &p_preset, const String &p_dest_dir); - Error _export_icons(const Ref &p_preset, const String &p_iconset_dir); - - Vector _get_supported_architectures() const; - Vector _get_preset_architectures(const Ref &p_preset) const; - - void _check_xcframework_content(const String &p_path, int &r_total_libs, int &r_static_libs, int &r_dylibs, int &r_frameworks) const; - Error _convert_to_framework(const String &p_source, const String &p_destination, const String &p_id) const; - - void _add_assets_to_project(const String &p_out_dir, const Ref &p_preset, Vector &p_project_data, const Vector &p_additional_assets); - Error _export_additional_assets(const Ref &p_preset, const String &p_out_dir, const Vector &p_assets, bool p_is_framework, bool p_should_embed, Vector &r_exported_assets); - Error _copy_asset(const Ref &p_preset, 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 Ref &p_preset, const String &p_out_dir, const Vector &p_libraries, Vector &r_exported_assets); - Error _export_ios_plugins(const Ref &p_preset, IOSConfigData &p_config_data, const String &dest_dir, Vector &r_exported_assets, bool p_debug); - - Error _export_project_helper(const Ref &p_preset, bool p_debug, const String &p_path, BitField p_flags, bool p_oneclick); - - bool is_package_name_valid(const String &p_package, String *r_error = nullptr) const; - -protected: - virtual void get_preset_features(const Ref &p_preset, List *r_features) const override; virtual void get_export_options(List *r_options) const override; - virtual bool get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const override; - virtual String get_export_option_warning(const EditorExportPreset *p_preset, const StringName &p_name) const override; + virtual bool has_valid_export_configuration(const Ref &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override; - void _notification(int p_what); + virtual Error _export_loading_screen_file(const Ref &p_preset, const String &p_dest_dir) override; + virtual Error _export_icons(const Ref &p_preset, const String &p_iconset_dir) override; + virtual HashMap get_custom_project_settings(const Ref &p_preset) const override; public: virtual String get_name() const override { return "iOS"; } virtual String get_os_name() const override { return "iOS"; } - virtual Ref get_logo() const override { return logo; } - virtual Ref get_run_icon() const override { return run_icon; } - - virtual int get_options_count() const override; - virtual String get_options_tooltip() const override; - virtual Ref get_option_icon(int p_index) const override; - virtual String get_option_label(int p_index) const override; - virtual String get_option_tooltip(int p_index) const override; - virtual Error run(const Ref &p_preset, int p_device, BitField p_debug_flags) override; - - virtual bool poll_export() override { - bool dc = devices_changed.is_set(); - if (dc) { - // don't clear unless we're reporting true, to avoid race - devices_changed.clear(); - } - return dc; - } - - virtual bool should_update_export_options() override { - bool export_options_changed = plugins_changed.is_set(); - if (export_options_changed) { - // don't clear unless we're reporting true, to avoid race - plugins_changed.clear(); - } - return export_options_changed; - } - - virtual List get_binary_extensions(const Ref &p_preset) const override { - List list; - if (p_preset.is_valid()) { - bool project_only = p_preset->get("application/export_project_only"); - if (project_only) { - list.push_back("xcodeproj"); - } else { - list.push_back("ipa"); - } - } - return list; - } - - virtual HashMap get_custom_project_settings(const Ref &p_preset) const override; - - virtual Error export_project(const Ref &p_preset, bool p_debug, const String &p_path, BitField p_flags = 0) override; - - virtual bool has_valid_export_configuration(const Ref &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override; - virtual bool has_valid_project_configuration(const Ref &p_preset, String &r_error) const override; virtual void get_platform_features(List *r_features) const override { - r_features->push_back("mobile"); + EditorExportPlatformAppleEmbedded::get_platform_features(r_features); r_features->push_back("ios"); } - virtual void resolve_platform_feature_priorities(const Ref &p_preset, HashSet &p_features) override { - } - EditorExportPlatformIOS(); ~EditorExportPlatformIOS(); - - /// 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; - Ref da = DirAccess::open(p_path); - if (da.is_valid()) { - da->list_dir_begin(); - while (true) { - String file = da->get_next(); - if (file.is_empty()) { - break; - } - - if (file == "." || file == "..") { - continue; - } - - if (da->current_is_hidden()) { - continue; - } - - if (da->current_is_dir()) { - if (p_check_directories) { - Vector directory_files = list_plugin_config_files(p_path.path_join(file), false); - for (int i = 0; i < directory_files.size(); ++i) { - dir_files.push_back(file.path_join(directory_files[i])); - } - } - - continue; - } - - if (file.ends_with(PluginConfigIOS::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().path_join("ios/plugins"); - - if (DirAccess::exists(plugins_dir)) { - Vector plugins_filenames = list_plugin_config_files(plugins_dir, true); - - if (!plugins_filenames.is_empty()) { - Ref config_file = memnew(ConfigFile); - for (int i = 0; i < plugins_filenames.size(); i++) { - PluginConfigIOS config = PluginConfigIOS::load_plugin_config(config_file, plugins_dir.path_join(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++) { - PluginConfigIOS plugin = all_plugins[i]; - bool enabled = p_presets->get("plugins/" + plugin.name); - if (enabled) { - enabled_plugins.push_back(plugin); - } - } - - return enabled_plugins; - } }; diff --git a/platform/ios/godot_view_ios.h b/platform/ios/godot_view_ios.h new file mode 100644 index 000000000000..2f08d7ac2e2d --- /dev/null +++ b/platform/ios/godot_view_ios.h @@ -0,0 +1,37 @@ +/**************************************************************************/ +/* godot_view_ios.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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. */ +/**************************************************************************/ + +#pragma once + +#include "drivers/apple_embedded/godot_view_apple_embedded.h" + +@interface GDTViewIOS : GDTView + +@end diff --git a/platform/ios/godot_view_ios.mm b/platform/ios/godot_view_ios.mm new file mode 100644 index 000000000000..f8d049feef41 --- /dev/null +++ b/platform/ios/godot_view_ios.mm @@ -0,0 +1,87 @@ +/**************************************************************************/ +/* godot_view_ios.mm */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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_ios.h" + +#import "display_layer_ios.h" + +#include "core/error/error_macros.h" + +@interface GDTViewIOS () + +GODOT_CLANG_WARNING_PUSH_AND_IGNORE("-Wobjc-property-synthesis") +@property(strong, nonatomic) CALayer *renderingLayer; +GODOT_CLANG_WARNING_POP + +@end + +@implementation GDTViewIOS + +- (CALayer *)initializeRenderingForDriver:(NSString *)driverName { + if (self.renderingLayer) { + return self.renderingLayer; + } + + CALayer *layer; + + if ([driverName isEqualToString:@"vulkan"] || [driverName isEqualToString:@"metal"]) { +#if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR + if (@available(iOS 13, *)) { + layer = [GDTMetalLayer layer]; + } else { + return nil; + } +#else + layer = [GDTMetalLayer layer]; +#endif + } else if ([driverName isEqualToString:@"opengl3"]) { + GODOT_CLANG_WARNING_PUSH_AND_IGNORE("-Wdeprecated-declarations") // OpenGL is deprecated in iOS 12.0. + layer = [GDTOpenGLLayer layer]; + GODOT_CLANG_WARNING_POP + } else { + return nil; + } + + layer.frame = self.bounds; + layer.contentsScale = self.contentScaleFactor; + + [self.layer addSublayer:layer]; + self.renderingLayer = layer; + + [layer initializeDisplayLayer]; + + return self.renderingLayer; +} + +@end + +GDTView *GDTViewCreate() { + return [GDTViewIOS new]; +} diff --git a/platform/ios/ios.h b/platform/ios/ios.h index 26c118600bc7..9d836e615be8 100644 --- a/platform/ios/ios.h +++ b/platform/ios/ios.h @@ -30,30 +30,8 @@ #pragma once -#include "core/object/class_db.h" +#include "drivers/apple_embedded/apple_embedded.h" -#import - -class iOS : public Object { - GDCLASS(iOS, Object); - - static void _bind_methods(); - -private: - CHHapticEngine *haptic_engine API_AVAILABLE(ios(13)) = nullptr; - - CHHapticEngine *get_haptic_engine_instance() API_AVAILABLE(ios(13)); - void start_haptic_engine(); - void stop_haptic_engine(); - -public: - static void alert(const char *p_alert, const char *p_title); - - bool supports_haptic_engine(); - void vibrate_haptic_engine(float p_duration_seconds, float p_amplitude); - - String get_model() const; - String get_rate_url(int p_app_id) const; - - iOS(); +class iOS : public AppleEmbedded { + GDCLASS(iOS, AppleEmbedded); }; diff --git a/platform/ios/main_ios.mm b/platform/ios/main_ios.mm new file mode 100644 index 000000000000..8f7700f6ed37 --- /dev/null +++ b/platform/ios/main_ios.mm @@ -0,0 +1,89 @@ +/**************************************************************************/ +/* main_ios.mm */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 "os_ios.h" + +#import "drivers/apple_embedded/godot_app_delegate.h" +#import "drivers/apple_embedded/main_utilities.h" +#include "main/main.h" + +#import +#include + +int gargc; +char **gargv; + +static OS_IOS *os = nullptr; + +int main(int argc, char *argv[]) { +#if defined(VULKAN_ENABLED) + //MoltenVK - enable full component swizzling support + setenv("MVK_CONFIG_FULL_IMAGE_VIEW_SWIZZLE", "1", 1); +#endif + + gargc = argc; + gargv = argv; + + @autoreleasepool { + NSString *className = NSStringFromClass([GDTApplicationDelegate class]); + UIApplicationMain(argc, argv, nil, className); + } + return 0; +} + +int apple_embedded_main(int argc, char **argv) { + change_to_launch_dir(argv); + + os = new OS_IOS(); + + // We must override main when testing is enabled + TEST_MAIN_OVERRIDE + + char *fargv[64]; + argc = process_args(argc, argv, fargv); + + Error err = Main::setup(fargv[0], argc - 1, &fargv[1], false); + + if (err != OK) { + if (err == ERR_HELP) { // Returned by --help and --version, so success. + return EXIT_SUCCESS; + } + return EXIT_FAILURE; + } + + os->initialize_modules(); + + return os->get_exit_code(); +} + +void apple_embedded_finish() { + Main::cleanup(); + delete os; +} diff --git a/platform/ios/os_ios.h b/platform/ios/os_ios.h index f4283958c1fe..89eeab1fffd3 100644 --- a/platform/ios/os_ios.h +++ b/platform/ios/os_ios.h @@ -32,107 +32,16 @@ #ifdef IOS_ENABLED -#import "ios.h" - -#import "drivers/apple/joypad_apple.h" -#import "drivers/coreaudio/audio_driver_coreaudio.h" -#include "drivers/unix/os_unix.h" -#include "servers/audio_server.h" -#include "servers/rendering/renderer_compositor.h" - -#if defined(RD_ENABLED) -#include "servers/rendering/rendering_device.h" - -#if defined(VULKAN_ENABLED) -#import "rendering_context_driver_vulkan_ios.h" -#endif -#endif - -class OS_IOS : public OS_Unix { -private: - static HashMap dynamic_symbol_lookup_table; - friend void register_dynamic_symbol(char *name, void *address); - - AudioDriverCoreAudio audio_driver; - - iOS *ios = nullptr; - - JoypadApple *joypad_apple = nullptr; - - MainLoop *main_loop = nullptr; - - virtual void initialize_core() override; - virtual void initialize() override; - - virtual void initialize_joypads() override; - - virtual void set_main_loop(MainLoop *p_main_loop) override; - virtual MainLoop *get_main_loop() const override; - - virtual void delete_main_loop() override; - - virtual void finalize() override; - - bool is_focused = false; - - CGFloat _weight_to_ct(int p_weight) const; - CGFloat _stretch_to_ct(int p_stretch) const; - String _get_default_fontname(const String &p_font_name) const; - - static _FORCE_INLINE_ String get_framework_executable(const String &p_path); - - void deinitialize_modules(); +#import "drivers/apple_embedded/os_apple_embedded.h" +class OS_IOS : public OS_AppleEmbedded { public: static OS_IOS *get_singleton(); OS_IOS(); ~OS_IOS(); - void initialize_modules(); - - bool iterate(); - - void start(); - - virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override; - - virtual Vector get_system_fonts() const override; - virtual Vector get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale = String(), const String &p_script = String(), int p_weight = 400, int p_stretch = 100, bool p_italic = false) const override; - virtual String get_system_font_path(const String &p_font_name, int p_weight = 400, int p_stretch = 100, bool p_italic = false) const override; - - virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data = nullptr) override; - virtual Error close_dynamic_library(void *p_library_handle) override; - virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String &p_name, void *&p_symbol_handle, bool p_optional = false) override; - virtual String get_name() const override; - virtual String get_distribution_name() const override; - virtual String get_version() const override; - virtual String get_model_name() const override; - - virtual Error shell_open(const String &p_uri) override; - - virtual String get_user_data_dir(const String &p_user_dir) const override; - - virtual String get_cache_path() const override; - virtual String get_temp_path() const override; - - virtual String get_locale() const override; - - virtual String get_unique_id() const override; - virtual String get_processor_name() const override; - - virtual void vibrate_handheld(int p_duration_ms = 500, float p_amplitude = -1.0) override; - - virtual bool _check_internal_feature_support(const String &p_feature) override; - - void on_focus_out(); - void on_focus_in(); - - void on_enter_background(); - void on_exit_background(); - - virtual Rect2 calculate_boot_screen_rect(const Size2 &p_window_size, const Size2 &p_imgrect_size) const override; }; #endif // IOS_ENABLED diff --git a/platform/ios/os_ios.mm b/platform/ios/os_ios.mm index 161b97da64c0..6aa35c5404e2 100644 --- a/platform/ios/os_ios.mm +++ b/platform/ios/os_ios.mm @@ -30,694 +30,23 @@ #import "os_ios.h" -#ifdef IOS_ENABLED - -#import "app_delegate.h" #import "display_server_ios.h" -#import "godot_view.h" -#import "ios_terminal_logger.h" -#import "view_controller.h" - -#include "core/config/project_settings.h" -#include "core/io/dir_access.h" -#include "core/io/file_access.h" -#include "core/io/file_access_pack.h" -#include "drivers/unix/syslog_logger.h" -#include "main/main.h" - -#import -#import -#import -#import -#include - -#if defined(RD_ENABLED) -#include "servers/rendering/renderer_rd/renderer_compositor_rd.h" -#import - -#if defined(VULKAN_ENABLED) -#include "drivers/vulkan/godot_vulkan.h" -#endif // VULKAN_ENABLED -#endif - -// Initialization order between compilation units is not guaranteed, -// so we use this as a hack to ensure certain code is called before -// everything else, but after all units are initialized. -typedef void (*init_callback)(); -static init_callback *ios_init_callbacks = nullptr; -static int ios_init_callbacks_count = 0; -static int ios_init_callbacks_capacity = 0; -HashMap OS_IOS::dynamic_symbol_lookup_table; - -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) * (ios_init_callbacks_capacity + 32)); - if (new_ptr) { - ios_init_callbacks = (init_callback *)(new_ptr); - ios_init_callbacks_capacity += 32; - } else { - ERR_FAIL_MSG("Unable to allocate memory for extension callbacks."); - } - } - ios_init_callbacks[ios_init_callbacks_count++] = cb; -} - -void register_dynamic_symbol(char *name, void *address) { - OS_IOS::dynamic_symbol_lookup_table[String(name)] = address; -} - -Rect2 fit_keep_aspect_centered(const Vector2 &p_container, const Vector2 &p_rect) { - real_t available_ratio = p_container.width / p_container.height; - real_t fit_ratio = p_rect.width / p_rect.height; - Rect2 result; - if (fit_ratio < available_ratio) { - // Fit height - we'll have horizontal gaps - result.size.height = p_container.height; - result.size.width = p_container.height * fit_ratio; - result.position.y = 0; - result.position.x = (p_container.width - result.size.width) * 0.5f; - } else { - // Fit width - we'll have vertical gaps - result.size.width = p_container.width; - result.size.height = p_container.width / fit_ratio; - result.position.x = 0; - result.position.y = (p_container.height - result.size.height) * 0.5f; - } - return result; -} -Rect2 fit_keep_aspect_covered(const Vector2 &p_container, const Vector2 &p_rect) { - real_t available_ratio = p_container.width / p_container.height; - real_t fit_ratio = p_rect.width / p_rect.height; - Rect2 result; - if (fit_ratio < available_ratio) { - // Need to scale up to fit width, and crop height - result.size.width = p_container.width; - result.size.height = p_container.width / fit_ratio; - result.position.x = 0; - result.position.y = (p_container.height - result.size.height) * 0.5f; - } else { - // Need to scale up to fit height, and crop width - result.size.width = p_container.height * fit_ratio; - result.size.height = p_container.height; - result.position.x = (p_container.width - result.size.width) * 0.5f; - result.position.y = 0; - } - return result; -} +#ifdef IOS_ENABLED OS_IOS *OS_IOS::get_singleton() { - return (OS_IOS *)OS::get_singleton(); + return (OS_IOS *)OS_AppleEmbedded::get_singleton(); } -OS_IOS::OS_IOS() { - for (int i = 0; i < ios_init_callbacks_count; ++i) { - ios_init_callbacks[i](); - } - free(ios_init_callbacks); - ios_init_callbacks = nullptr; - ios_init_callbacks_count = 0; - ios_init_callbacks_capacity = 0; - - main_loop = nullptr; - - Vector loggers; - loggers.push_back(memnew(IOSTerminalLogger)); - _set_logger(memnew(CompositeLogger(loggers))); - - AudioDriverManager::add_driver(&audio_driver); - +OS_IOS::OS_IOS() : + OS_AppleEmbedded() { DisplayServerIOS::register_ios_driver(); } OS_IOS::~OS_IOS() {} -void OS_IOS::alert(const String &p_alert, const String &p_title) { - const CharString utf8_alert = p_alert.utf8(); - const CharString utf8_title = p_title.utf8(); - iOS::alert(utf8_alert.get_data(), utf8_title.get_data()); -} - -void OS_IOS::initialize_core() { - OS_Unix::initialize_core(); -} - -void OS_IOS::initialize() { - initialize_core(); -} - -void OS_IOS::initialize_joypads() { - joypad_apple = memnew(JoypadApple); -} - -void OS_IOS::initialize_modules() { - ios = memnew(iOS); - Engine::get_singleton()->add_singleton(Engine::Singleton("iOS", ios)); -} - -void OS_IOS::deinitialize_modules() { - if (joypad_apple) { - memdelete(joypad_apple); - } - - if (ios) { - memdelete(ios); - } -} - -void OS_IOS::set_main_loop(MainLoop *p_main_loop) { - main_loop = p_main_loop; -} - -MainLoop *OS_IOS::get_main_loop() const { - return main_loop; -} - -void OS_IOS::delete_main_loop() { - if (main_loop) { - main_loop->finalize(); - memdelete(main_loop); - } - - main_loop = nullptr; -} - -bool OS_IOS::iterate() { - if (!main_loop) { - return true; - } - - if (DisplayServer::get_singleton()) { - DisplayServer::get_singleton()->process_events(); - } - - joypad_apple->process_joypads(); - - return Main::iteration(); -} - -void OS_IOS::start() { - if (Main::start() == EXIT_SUCCESS) { - main_loop->initialize(); - } -} - -void OS_IOS::finalize() { - deinitialize_modules(); - - // Already gets called - //delete_main_loop(); -} - -// MARK: Dynamic Libraries - -_FORCE_INLINE_ String OS_IOS::get_framework_executable(const String &p_path) { - Ref da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - - // Read framework bundle to get executable name. - NSURL *url = [NSURL fileURLWithPath:@(p_path.utf8().get_data())]; - NSBundle *bundle = [NSBundle bundleWithURL:url]; - if (bundle) { - String exe_path = String::utf8([[bundle executablePath] UTF8String]); - if (da->file_exists(exe_path)) { - return exe_path; - } - } - - // Try default executable name (invalid framework). - if (da->dir_exists(p_path) && da->file_exists(p_path.path_join(p_path.get_file().get_basename()))) { - return p_path.path_join(p_path.get_file().get_basename()); - } - - // Not a framework, try loading as .dylib. - return p_path; -} - -Error OS_IOS::open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data) { - if (p_path.length() == 0) { - // Static xcframework. - p_library_handle = RTLD_SELF; - - if (p_data != nullptr && p_data->r_resolved_path != nullptr) { - *p_data->r_resolved_path = p_path; - } - - return OK; - } - - String path = get_framework_executable(p_path); - - if (!FileAccess::exists(path)) { - // Load .dylib or framework from within the executable path. - path = get_framework_executable(get_executable_path().get_base_dir().path_join(p_path.get_file())); - } - - if (!FileAccess::exists(path)) { - // Load .dylib converted to framework from within the executable path. - path = get_framework_executable(get_executable_path().get_base_dir().path_join(p_path.get_file().get_basename() + ".framework")); - } - - if (!FileAccess::exists(path)) { - // Load .dylib or framework from a standard iOS location. - path = get_framework_executable(get_executable_path().get_base_dir().path_join("Frameworks").path_join(p_path.get_file())); - } - - if (!FileAccess::exists(path)) { - // Load .dylib converted to framework from a standard iOS location. - path = get_framework_executable(get_executable_path().get_base_dir().path_join("Frameworks").path_join(p_path.get_file().get_basename() + ".framework")); - } - - ERR_FAIL_COND_V(!FileAccess::exists(path), ERR_FILE_NOT_FOUND); - - p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW); - ERR_FAIL_NULL_V_MSG(p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", p_path, dlerror())); - - if (p_data != nullptr && p_data->r_resolved_path != nullptr) { - *p_data->r_resolved_path = path; - } - - return OK; -} - -Error OS_IOS::close_dynamic_library(void *p_library_handle) { - if (p_library_handle == RTLD_SELF) { - return OK; - } - return OS_Unix::close_dynamic_library(p_library_handle); -} - -Error OS_IOS::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 = OS_IOS::dynamic_symbol_lookup_table.getptr(p_name); - if (ptr) { - p_symbol_handle = *ptr; - return OK; - } - } - return OS_Unix::get_dynamic_library_symbol_handle(p_library_handle, p_name, p_symbol_handle, p_optional); -} - String OS_IOS::get_name() const { return "iOS"; } -String OS_IOS::get_distribution_name() const { - return get_name(); -} - -String OS_IOS::get_version() const { - NSOperatingSystemVersion ver = [NSProcessInfo processInfo].operatingSystemVersion; - return vformat("%d.%d.%d", (int64_t)ver.majorVersion, (int64_t)ver.minorVersion, (int64_t)ver.patchVersion); -} - -String OS_IOS::get_model_name() const { - String model = ios->get_model(); - if (model != "") { - return model; - } - - return OS_Unix::get_model_name(); -} - -Error OS_IOS::shell_open(const 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; - } - - print_verbose(vformat("Opening URL %s", p_uri)); - - [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil]; - - return OK; -} - -String OS_IOS::get_user_data_dir(const String &p_user_dir) const { - static String ret; - if (ret.is_empty()) { - NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); - if (paths && [paths count] >= 1) { - ret.append_utf8([[paths firstObject] UTF8String]); - } - } - return ret; -} - -String OS_IOS::get_cache_path() const { - static String ret; - if (ret.is_empty()) { - NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); - if (paths && [paths count] >= 1) { - ret.append_utf8([[paths firstObject] UTF8String]); - } - } - return ret; -} - -String OS_IOS::get_temp_path() const { - static String ret; - if (ret.is_empty()) { - NSURL *url = [NSURL fileURLWithPath:NSTemporaryDirectory() - isDirectory:YES]; - if (url) { - ret = String::utf8([url.path UTF8String]); - ret = ret.trim_prefix("file://"); - } - } - return ret; -} - -String OS_IOS::get_locale() const { - NSString *preferredLanguage = [NSLocale preferredLanguages].firstObject; - - if (preferredLanguage) { - return String::utf8([preferredLanguage UTF8String]).replace_char('-', '_'); - } - - NSString *localeIdentifier = [[NSLocale currentLocale] localeIdentifier]; - return String::utf8([localeIdentifier UTF8String]).replace_char('-', '_'); -} - -String OS_IOS::get_unique_id() const { - NSString *uuid = [UIDevice currentDevice].identifierForVendor.UUIDString; - return String::utf8([uuid UTF8String]); -} - -String OS_IOS::get_processor_name() const { - char buffer[256]; - size_t buffer_len = 256; - if (sysctlbyname("machdep.cpu.brand_string", &buffer, &buffer_len, nullptr, 0) == 0) { - return String::utf8(buffer, buffer_len); - } - ERR_FAIL_V_MSG("", String("Couldn't get the CPU model name. Returning an empty string.")); -} - -Vector OS_IOS::get_system_fonts() const { - HashSet font_names; - CFArrayRef fonts = CTFontManagerCopyAvailableFontFamilyNames(); - if (fonts) { - for (CFIndex i = 0; i < CFArrayGetCount(fonts); i++) { - CFStringRef cf_name = (CFStringRef)CFArrayGetValueAtIndex(fonts, i); - if (cf_name && (CFStringGetLength(cf_name) > 0) && (CFStringCompare(cf_name, CFSTR("LastResort"), kCFCompareCaseInsensitive) != kCFCompareEqualTo) && (CFStringGetCharacterAtIndex(cf_name, 0) != '.')) { - NSString *ns_name = (__bridge NSString *)cf_name; - font_names.insert(String::utf8([ns_name UTF8String])); - } - } - CFRelease(fonts); - } - - Vector ret; - for (const String &E : font_names) { - ret.push_back(E); - } - return ret; -} - -String OS_IOS::_get_default_fontname(const String &p_font_name) const { - String font_name = p_font_name; - if (font_name.to_lower() == "sans-serif") { - font_name = "Helvetica"; - } else if (font_name.to_lower() == "serif") { - font_name = "Times"; - } else if (font_name.to_lower() == "monospace") { - font_name = "Courier"; - } else if (font_name.to_lower() == "fantasy") { - font_name = "Papyrus"; - } else if (font_name.to_lower() == "cursive") { - font_name = "Apple Chancery"; - }; - return font_name; -} - -CGFloat OS_IOS::_weight_to_ct(int p_weight) const { - if (p_weight < 150) { - return -0.80; - } else if (p_weight < 250) { - return -0.60; - } else if (p_weight < 350) { - return -0.40; - } else if (p_weight < 450) { - return 0.0; - } else if (p_weight < 550) { - return 0.23; - } else if (p_weight < 650) { - return 0.30; - } else if (p_weight < 750) { - return 0.40; - } else if (p_weight < 850) { - return 0.56; - } else if (p_weight < 925) { - return 0.62; - } else { - return 1.00; - } -} - -CGFloat OS_IOS::_stretch_to_ct(int p_stretch) const { - if (p_stretch < 56) { - return -0.5; - } else if (p_stretch < 69) { - return -0.37; - } else if (p_stretch < 81) { - return -0.25; - } else if (p_stretch < 93) { - return -0.13; - } else if (p_stretch < 106) { - return 0.0; - } else if (p_stretch < 137) { - return 0.13; - } else if (p_stretch < 144) { - return 0.25; - } else if (p_stretch < 162) { - return 0.37; - } else { - return 0.5; - } -} - -Vector OS_IOS::get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale, const String &p_script, int p_weight, int p_stretch, bool p_italic) const { - Vector ret; - String font_name = _get_default_fontname(p_font_name); - - CFStringRef name = CFStringCreateWithCString(kCFAllocatorDefault, font_name.utf8().get_data(), kCFStringEncodingUTF8); - CTFontSymbolicTraits traits = 0; - if (p_weight >= 700) { - traits |= kCTFontBoldTrait; - } - if (p_italic) { - traits |= kCTFontItalicTrait; - } - if (p_stretch < 100) { - traits |= kCTFontCondensedTrait; - } else if (p_stretch > 100) { - traits |= kCTFontExpandedTrait; - } - - CFNumberRef sym_traits = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &traits); - CFMutableDictionaryRef traits_dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr); - CFDictionaryAddValue(traits_dict, kCTFontSymbolicTrait, sym_traits); - - CGFloat weight = _weight_to_ct(p_weight); - CFNumberRef font_weight = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &weight); - CFDictionaryAddValue(traits_dict, kCTFontWeightTrait, font_weight); - - CGFloat stretch = _stretch_to_ct(p_stretch); - CFNumberRef font_stretch = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &stretch); - CFDictionaryAddValue(traits_dict, kCTFontWidthTrait, font_stretch); - - CFMutableDictionaryRef attributes = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr); - CFDictionaryAddValue(attributes, kCTFontFamilyNameAttribute, name); - CFDictionaryAddValue(attributes, kCTFontTraitsAttribute, traits_dict); - - CTFontDescriptorRef font = CTFontDescriptorCreateWithAttributes(attributes); - if (font) { - CTFontRef family = CTFontCreateWithFontDescriptor(font, 0, nullptr); - if (family) { - CFStringRef string = CFStringCreateWithCString(kCFAllocatorDefault, p_text.utf8().get_data(), kCFStringEncodingUTF8); - CFRange range = CFRangeMake(0, CFStringGetLength(string)); - CTFontRef fallback_family = CTFontCreateForString(family, string, range); - if (fallback_family) { - CTFontDescriptorRef fallback_font = CTFontCopyFontDescriptor(fallback_family); - if (fallback_font) { - CFURLRef url = (CFURLRef)CTFontDescriptorCopyAttribute(fallback_font, kCTFontURLAttribute); - if (url) { - NSString *font_path = [NSString stringWithString:[(__bridge NSURL *)url path]]; - ret.push_back(String::utf8([font_path UTF8String])); - CFRelease(url); - } - CFRelease(fallback_font); - } - CFRelease(fallback_family); - } - CFRelease(string); - CFRelease(family); - } - CFRelease(font); - } - - CFRelease(attributes); - CFRelease(traits_dict); - CFRelease(sym_traits); - CFRelease(font_stretch); - CFRelease(font_weight); - CFRelease(name); - - return ret; -} - -String OS_IOS::get_system_font_path(const String &p_font_name, int p_weight, int p_stretch, bool p_italic) const { - String ret; - String font_name = _get_default_fontname(p_font_name); - - CFStringRef name = CFStringCreateWithCString(kCFAllocatorDefault, font_name.utf8().get_data(), kCFStringEncodingUTF8); - - CTFontSymbolicTraits traits = 0; - if (p_weight >= 700) { - traits |= kCTFontBoldTrait; - } - if (p_italic) { - traits |= kCTFontItalicTrait; - } - if (p_stretch < 100) { - traits |= kCTFontCondensedTrait; - } else if (p_stretch > 100) { - traits |= kCTFontExpandedTrait; - } - - CFNumberRef sym_traits = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &traits); - CFMutableDictionaryRef traits_dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr); - CFDictionaryAddValue(traits_dict, kCTFontSymbolicTrait, sym_traits); - - CGFloat weight = _weight_to_ct(p_weight); - CFNumberRef font_weight = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &weight); - CFDictionaryAddValue(traits_dict, kCTFontWeightTrait, font_weight); - - CGFloat stretch = _stretch_to_ct(p_stretch); - CFNumberRef font_stretch = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &stretch); - CFDictionaryAddValue(traits_dict, kCTFontWidthTrait, font_stretch); - - CFMutableDictionaryRef attributes = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr); - CFDictionaryAddValue(attributes, kCTFontFamilyNameAttribute, name); - CFDictionaryAddValue(attributes, kCTFontTraitsAttribute, traits_dict); - - CTFontDescriptorRef font = CTFontDescriptorCreateWithAttributes(attributes); - if (font) { - CFURLRef url = (CFURLRef)CTFontDescriptorCopyAttribute(font, kCTFontURLAttribute); - if (url) { - NSString *font_path = [NSString stringWithString:[(__bridge NSURL *)url path]]; - ret = String::utf8([font_path UTF8String]); - CFRelease(url); - } - CFRelease(font); - } - - CFRelease(attributes); - CFRelease(traits_dict); - CFRelease(sym_traits); - CFRelease(font_stretch); - CFRelease(font_weight); - CFRelease(name); - - return ret; -} - -void OS_IOS::vibrate_handheld(int p_duration_ms, float p_amplitude) { - if (ios->supports_haptic_engine()) { - if (p_amplitude > 0.0) { - p_amplitude = CLAMP(p_amplitude, 0.0, 1.0); - } - - ios->vibrate_haptic_engine((float)p_duration_ms / 1000.f, p_amplitude); - } else { - // iOS <13 does not support duration for vibration - AudioServicesPlaySystemSound(kSystemSoundID_Vibrate); - } -} - -bool OS_IOS::_check_internal_feature_support(const String &p_feature) { - if (p_feature == "system_fonts") { - return true; - } - if (p_feature == "mobile") { - return true; - } - - return false; -} - -void OS_IOS::on_focus_out() { - if (is_focused) { - is_focused = false; - - if (DisplayServerIOS::get_singleton()) { - DisplayServerIOS::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_FOCUS_OUT); - } - - if (OS::get_singleton()->get_main_loop()) { - OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT); - } - - [AppDelegate.viewController.godotView stopRendering]; - - audio_driver.stop(); - } -} - -void OS_IOS::on_focus_in() { - if (!is_focused) { - is_focused = true; - - if (DisplayServerIOS::get_singleton()) { - DisplayServerIOS::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_FOCUS_IN); - } - - if (OS::get_singleton()->get_main_loop()) { - OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN); - } - - [AppDelegate.viewController.godotView startRendering]; - - audio_driver.start(); - } -} - -void OS_IOS::on_enter_background() { - // Do not check for is_focused, because on_focus_out will always be fired first by applicationWillResignActive. - - if (OS::get_singleton()->get_main_loop()) { - OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_PAUSED); - } - - on_focus_out(); -} - -void OS_IOS::on_exit_background() { - if (!is_focused) { - on_focus_in(); - - if (OS::get_singleton()->get_main_loop()) { - OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_RESUMED); - } - } -} - -Rect2 OS_IOS::calculate_boot_screen_rect(const Size2 &p_window_size, const Size2 &p_imgrect_size) const { - String scalemodestr = GLOBAL_GET("ios/launch_screen_image_mode"); - - if (scalemodestr == "scaleAspectFit") { - return fit_keep_aspect_centered(p_window_size, p_imgrect_size); - } else if (scalemodestr == "scaleAspectFill") { - return fit_keep_aspect_covered(p_window_size, p_imgrect_size); - } else if (scalemodestr == "scaleToFill") { - return Rect2(Point2(), p_window_size); - } else if (scalemodestr == "center") { - return OS_Unix::calculate_boot_screen_rect(p_window_size, p_imgrect_size); - } else { - WARN_PRINT(vformat("Boot screen scale mode mismatch between iOS and Godot: %s not supported", scalemodestr)); - return OS_Unix::calculate_boot_screen_rect(p_window_size, p_imgrect_size); - } -} - #endif // IOS_ENABLED diff --git a/platform/ios/platform_config.h b/platform/ios/platform_config.h index bc8b45603ab5..52dac8b496ee 100644 --- a/platform/ios/platform_config.h +++ b/platform/ios/platform_config.h @@ -30,15 +30,4 @@ #pragma once -#include - -#define PLATFORM_THREAD_OVERRIDE - -#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 "drivers/apple_embedded/platform_config.h" diff --git a/platform/ios/platform_ios_builders.py b/platform/ios/platform_ios_builders.py index 3e5b0df8a3cd..b2fe090969ba 100644 --- a/platform/ios/platform_ios_builders.py +++ b/platform/ios/platform_ios_builders.py @@ -1,67 +1,7 @@ """Functions used to generate source files during build time""" -import os -import shutil - -from platform_methods import detect_mvk, lipo - - -def combine_libs(target, source, env): - lib_path = target[0].srcnode().abspath - if "osxcross" in env: - libtool = "$IOS_TOOLCHAIN_PATH/usr/bin/${ios_triple}libtool" - else: - libtool = "$IOS_TOOLCHAIN_PATH/usr/bin/libtool" - env.Execute( - libtool + ' -static -o "' + lib_path + '" ' + " ".join([('"' + lib.srcnode().abspath + '"') for lib in source]) - ) +from platform_methods import generate_bundle_apple_embedded def generate_bundle(target, source, env): - bin_dir = env.Dir("#bin").abspath - - # Template bundle. - app_prefix = "godot." + env["platform"] - rel_prefix = "libgodot." + env["platform"] + "." + "template_release" - dbg_prefix = "libgodot." + env["platform"] + "." + "template_debug" - if env.dev_build: - app_prefix += ".dev" - rel_prefix += ".dev" - dbg_prefix += ".dev" - if env["precision"] == "double": - app_prefix += ".double" - rel_prefix += ".double" - dbg_prefix += ".double" - - # Lipo template libraries. - rel_target_bin = lipo(bin_dir + "/" + rel_prefix, env.extra_suffix + ".a") - dbg_target_bin = lipo(bin_dir + "/" + dbg_prefix, env.extra_suffix + ".a") - rel_target_bin_sim = lipo(bin_dir + "/" + rel_prefix, ".simulator" + env.extra_suffix + ".a") - dbg_target_bin_sim = lipo(bin_dir + "/" + dbg_prefix, ".simulator" + env.extra_suffix + ".a") - - # Assemble Xcode project bundle. - app_dir = env.Dir("#bin/ios_xcode").abspath - templ = env.Dir("#misc/dist/ios_xcode").abspath - if os.path.exists(app_dir): - shutil.rmtree(app_dir) - shutil.copytree(templ, app_dir) - if rel_target_bin != "": - shutil.copy(rel_target_bin, app_dir + "/libgodot.ios.release.xcframework/ios-arm64/libgodot.a") - if dbg_target_bin != "": - shutil.copy(dbg_target_bin, app_dir + "/libgodot.ios.debug.xcframework/ios-arm64/libgodot.a") - if rel_target_bin_sim != "": - shutil.copy( - rel_target_bin_sim, app_dir + "/libgodot.ios.release.xcframework/ios-arm64_x86_64-simulator/libgodot.a" - ) - if dbg_target_bin_sim != "": - shutil.copy( - dbg_target_bin_sim, app_dir + "/libgodot.ios.debug.xcframework/ios-arm64_x86_64-simulator/libgodot.a" - ) - mvk_path = detect_mvk(env, "ios-arm64") - if mvk_path != "": - shutil.copytree(mvk_path, app_dir + "/MoltenVK.xcframework") - - # ZIP Xcode project bundle. - zip_dir = env.Dir("#bin/" + (app_prefix + env.extra_suffix).replace(".", "_")).abspath - shutil.make_archive(zip_dir, "zip", root_dir=app_dir) - shutil.rmtree(app_dir) + generate_bundle_apple_embedded("ios", "ios-arm64", "ios-arm64_x86_64-simulator", True, target, source, env) diff --git a/platform/macos/README.md b/platform/macos/README.md index 205c59e05d62..da46aa6f8296 100644 --- a/platform/macos/README.md +++ b/platform/macos/README.md @@ -3,6 +3,8 @@ This folder contains the C++, Objective-C and Objective-C++ code for the macOS platform port. +This platform uses shared Apple code ([`drivers/apple`](/drivers/apple)). + See also [`misc/dist/macos`](/misc/dist/macos) folder for additional files used by this platform. [`misc/dist/macos_tools.app`](/misc/dist/macos_tools.app) is an `.app` bundle template used for packaging the macOS editor, while diff --git a/platform/visionos/README.md b/platform/visionos/README.md new file mode 100644 index 000000000000..6bd8f257d71c --- /dev/null +++ b/platform/visionos/README.md @@ -0,0 +1,20 @@ +# visionOS platform port + +This folder contains the C++, Objective-C and Objective-C++ code for the visionOS +platform port. + +This platform derives from the Apple Embedded abstract platform ([`drivers/apple_embedded`](drivers/apple_embedded)). + +This platform uses shared Apple code ([`drivers/apple`](drivers/apple)). + +See also [`misc/dist/ios_xcode`](/misc/dist/ios_xcode) folder for the Xcode +project template used for packaging the iOS export templates. + +## Documentation + +The compiling and exporting process is the same as on iOS, but replacing the `ios` parameter by `visionos`. + +- [Compiling for iOS](https://docs.godotengine.org/en/latest/contributing/development/compiling/compiling_for_ios.html) + - Instructions on building this platform port from source. +- [Exporting for iOS](https://docs.godotengine.org/en/latest/tutorials/export/exporting_for_ios.html) + - Instructions on using the compiled export templates to export a project. diff --git a/platform/visionos/SCsub b/platform/visionos/SCsub new file mode 100644 index 000000000000..d3da9bb04a4a --- /dev/null +++ b/platform/visionos/SCsub @@ -0,0 +1,29 @@ +#!/usr/bin/env python +from misc.utility.scons_hints import * + +from platform_visionos_builders import generate_bundle + +from platform_methods import combine_libs_apple_embedded + +Import("env") + +visionos_lib = [ + "display_layer_visionos.mm", + "display_server_visionos.mm", + "godot_view_visionos.mm", + "main_visionos.mm", + "os_visionos.mm", +] + +env_visionos = env.Clone() +visionos_lib = env_visionos.add_library("visionos", visionos_lib) + +# Enable module support +env_visionos.Append(CCFLAGS=["-fmodules", "-fcxx-modules"]) + +combine_command = env_visionos.Command( + "#bin/libgodot" + env_visionos["LIBSUFFIX"], [visionos_lib] + env_visionos["LIBS"], combine_libs_apple_embedded +) + +if env["generate_bundle"]: + env.AlwaysBuild(env.CommandNoCache("generate_bundle", combine_command, env.Run(generate_bundle))) diff --git a/platform/visionos/api/api.cpp b/platform/visionos/api/api.cpp new file mode 100644 index 000000000000..2b6b89a89f73 --- /dev/null +++ b/platform/visionos/api/api.cpp @@ -0,0 +1,48 @@ +/**************************************************************************/ +/* api.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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(VISIONOS_ENABLED) + +void register_visionos_api() { + godot_apple_embedded_plugins_initialize(); +} + +void unregister_visionos_api() { + godot_apple_embedded_plugins_deinitialize(); +} + +#else + +void register_visionos_api() {} +void unregister_visionos_api() {} + +#endif // VISIONOS_ENABLED diff --git a/platform/ios/main.m b/platform/visionos/api/api.h similarity index 80% rename from platform/ios/main.m rename to platform/visionos/api/api.h index 89a00c9ae94a..3b4230d06a82 100644 --- a/platform/ios/main.m +++ b/platform/visionos/api/api.h @@ -1,5 +1,5 @@ /**************************************************************************/ -/* main.m */ +/* api.h */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,26 +28,12 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#import "godot_app_delegate.h" +#pragma once -#import -#include - -int gargc; -char **gargv; - -int main(int argc, char *argv[]) { -#if defined(VULKAN_ENABLED) - //MoltenVK - enable full component swizzling support - setenv("MVK_CONFIG_FULL_IMAGE_VIEW_SWIZZLE", "1", 1); +#if defined(VISIONOS_ENABLED) +extern void godot_apple_embedded_plugins_initialize(); +extern void godot_apple_embedded_plugins_deinitialize(); #endif - gargc = argc; - gargv = argv; - - @autoreleasepool { - NSString *className = NSStringFromClass([GodotApplicationDelegate class]); - UIApplicationMain(argc, argv, nil, className); - } - return 0; -} +void register_visionos_api(); +void unregister_visionos_api(); diff --git a/platform/visionos/detect.py b/platform/visionos/detect.py new file mode 100644 index 000000000000..678c628a1920 --- /dev/null +++ b/platform/visionos/detect.py @@ -0,0 +1,160 @@ +import os +import sys +from typing import TYPE_CHECKING + +from methods import detect_darwin_sdk_path, detect_darwin_toolchain_path, print_warning +from platform_methods import validate_arch + +if TYPE_CHECKING: + from SCons.Script.SConscript import SConsEnvironment + + +def get_name(): + return "visionOS" + + +def can_build(): + if sys.platform == "darwin" or ("OSXCROSS_VISIONOS" in os.environ): + return True + + return False + + +def get_opts(): + from SCons.Variables import BoolVariable + + return [ + # APPLE_TOOLCHAIN_PATH Example: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain + (("APPLE_TOOLCHAIN_PATH", "IOS_TOOLCHAIN_PATH"), "Path to the Apple toolchain", ""), + ("VISIONOS_SDK_PATH", "Path to the visionOS SDK", ""), + ("apple_target_triple", "Triple for corresponding target Apple platform toolchain", ""), + BoolVariable("simulator", "Build for Simulator", False), + BoolVariable("generate_bundle", "Generate an APP bundle after building visionOS/macOS binaries", False), + ] + + +def get_doc_classes(): + return [ + "EditorExportPlatformVisionOS", + ] + + +def get_doc_path(): + return "doc_classes" + + +def get_flags(): + return { + "arch": "arm64", + "target": "template_debug", + "use_volk": False, + "metal": True, + "supported": ["metal", "mono"], + "builtin_pcre2_with_jit": False, + "vulkan": False, + "opengl3": False, + } + + +def configure(env: "SConsEnvironment"): + # Validate arch. + supported_arches = ["x86_64", "arm64"] + validate_arch(env["arch"], get_name(), supported_arches) + detect_darwin_toolchain_path(env) + + ## LTO + + if env["lto"] == "auto": # Disable by default as it makes linking in Xcode very slow. + env["lto"] = "none" + + if env["lto"] != "none": + if env["lto"] == "thin": + env.Append(CCFLAGS=["-flto=thin"]) + env.Append(LINKFLAGS=["-flto=thin"]) + else: + env.Append(CCFLAGS=["-flto"]) + env.Append(LINKFLAGS=["-flto"]) + + ## Compiler configuration + + # Save this in environment for use by other modules + if "OSXCROSS_VISIONOS" in os.environ: + env["osxcross"] = True + + env["ENV"]["PATH"] = env["APPLE_TOOLCHAIN_PATH"] + "/Developer/usr/bin/:" + env["ENV"]["PATH"] + + compiler_path = "$APPLE_TOOLCHAIN_PATH/usr/bin/${apple_target_triple}" + + ccache_path = os.environ.get("CCACHE") + if ccache_path is None: + env["CC"] = compiler_path + "clang" + env["CXX"] = compiler_path + "clang++" + env["S_compiler"] = compiler_path + "clang" + else: + # there aren't any ccache wrappers available for visionOS, + # 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 + " " + compiler_path + "clang" + env["AR"] = compiler_path + "ar" + env["RANLIB"] = compiler_path + "ranlib" + + ## Compile flags + + if env["simulator"]: + detect_darwin_sdk_path("visionossimulator", env) + env.Append(ASFLAGS=["-mtargetos=xros2.0-simulator"]) + env.Append(CCFLAGS=["-mtargetos=xros2.0-simulator"]) + env.Append(CPPDEFINES=["VISIONOS_SIMULATOR"]) + env.extra_suffix = ".simulator" + env.extra_suffix + else: + detect_darwin_sdk_path("visionos", env) + env.Append(ASFLAGS=["-mtargetos=xros2.0"]) + env.Append(CCFLAGS=["-mtargetos=xros2.0"]) + + if env["arch"] == "arm64": + env.Append( + CCFLAGS=( + "-fobjc-arc -arch arm64 -fmessage-length=0" + " -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits" + " -fpascal-strings -fblocks -fvisibility=hidden -MMD -MT dependencies" + " -isysroot $VISIONOS_SDK_PATH".split() + ) + ) + env.Append(ASFLAGS=["-arch", "arm64"]) + + # Temp fix for ABS/MAX/MIN macros in visionOS SDK blocking compilation + env.Append(CCFLAGS=["-Wno-ambiguous-macro"]) + + env.Prepend( + CPPPATH=[ + "$VISIONOS_SDK_PATH/usr/include", + "$VISIONOS_SDK_PATH/System/Library/Frameworks/AudioUnit.framework/Headers", + ] + ) + + env.Prepend(CPPPATH=["#platform/visionos"]) + env.Append(CPPDEFINES=["VISIONOS_ENABLED", "APPLE_EMBEDDED_ENABLED", "UNIX_ENABLED", "COREAUDIO_ENABLED"]) + + if env["vulkan"]: + print_warning("The visionOS platform does not support the Vulkan rendering driver") + env["vulkan"] = False + + if env["metal"] and env["simulator"]: + print_warning("visionOS Simulator does not support the Metal rendering driver") + env["metal"] = False + + if env["metal"]: + env.AppendUnique(CPPDEFINES=["METAL_ENABLED", "RD_ENABLED"]) + env.Prepend( + CPPPATH=[ + "$VISIONOS_SDK_PATH/System/Library/Frameworks/Metal.framework/Headers", + "$VISIONOS_SDK_PATH/System/Library/Frameworks/MetalFX.framework/Headers", + "$VISIONOS_SDK_PATH/System/Library/Frameworks/QuartzCore.framework/Headers", + ] + ) + env.Prepend(CPPPATH=["#thirdparty/spirv-cross"]) + + if env["opengl3"]: + print_warning("The visionOS platform does not support the OpenGL rendering driver") + env["opengl3"] = False diff --git a/platform/visionos/display_layer_visionos.h b/platform/visionos/display_layer_visionos.h new file mode 100644 index 000000000000..44ae5d7cb837 --- /dev/null +++ b/platform/visionos/display_layer_visionos.h @@ -0,0 +1,36 @@ +/**************************************************************************/ +/* display_layer_visionos.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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. */ +/**************************************************************************/ + +#pragma once + +#include "drivers/apple_embedded/display_layer_apple_embedded.h" + +@interface GDTMetalLayer : CAMetalLayer +@end diff --git a/platform/visionos/display_layer_visionos.mm b/platform/visionos/display_layer_visionos.mm new file mode 100644 index 000000000000..e8f5b8b9e237 --- /dev/null +++ b/platform/visionos/display_layer_visionos.mm @@ -0,0 +1,47 @@ +/**************************************************************************/ +/* display_layer_visionos.mm */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 "display_layer_visionos.h" + +@implementation GDTMetalLayer + +- (void)initializeDisplayLayer { +} + +- (void)layoutDisplayLayer { +} + +- (void)startRenderDisplayLayer { +} + +- (void)stopRenderDisplayLayer { +} + +@end diff --git a/platform/visionos/display_server_visionos.h b/platform/visionos/display_server_visionos.h new file mode 100644 index 000000000000..56fbe4e5cccf --- /dev/null +++ b/platform/visionos/display_server_visionos.h @@ -0,0 +1,54 @@ +/**************************************************************************/ +/* display_server_visionos.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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. */ +/**************************************************************************/ + +#pragma once + +#include "drivers/apple_embedded/display_server_apple_embedded.h" + +class DisplayServerVisionOS : public DisplayServerAppleEmbedded { + GDSOFTCLASS(DisplayServerVisionOS, DisplayServerAppleEmbedded); + + _THREAD_SAFE_CLASS_ + + DisplayServerVisionOS(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error); + ~DisplayServerVisionOS(); + +public: + static DisplayServerVisionOS *get_singleton(); + + static void register_visionos_driver(); + static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error); + + virtual String get_name() const override; + + virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + virtual float screen_get_scale(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; +}; diff --git a/platform/visionos/display_server_visionos.mm b/platform/visionos/display_server_visionos.mm new file mode 100644 index 000000000000..8c385e2694ab --- /dev/null +++ b/platform/visionos/display_server_visionos.mm @@ -0,0 +1,67 @@ +/**************************************************************************/ +/* display_server_visionos.mm */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 "display_server_visionos.h" + +DisplayServerVisionOS *DisplayServerVisionOS::get_singleton() { + return (DisplayServerVisionOS *)DisplayServerAppleEmbedded::get_singleton(); +} + +DisplayServerVisionOS::DisplayServerVisionOS(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error) : + DisplayServerAppleEmbedded(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_position, p_resolution, p_screen, p_context, p_parent_window, r_error) { +} + +DisplayServerVisionOS::~DisplayServerVisionOS() { +} + +DisplayServer *DisplayServerVisionOS::create_func(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error) { + return memnew(DisplayServerVisionOS(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_position, p_resolution, p_screen, p_context, p_parent_window, r_error)); +} + +void DisplayServerVisionOS::register_visionos_driver() { + register_create_function("visionOS", create_func, get_rendering_drivers_func); +} + +String DisplayServerVisionOS::get_name() const { + return "visionOS"; +} + +int DisplayServerVisionOS::screen_get_dpi(int p_screen) const { + // TODO(Apple): Compute this properly from SwiftUI Metric APIs + return 72; +} + +float DisplayServerVisionOS::screen_get_refresh_rate(int p_screen) const { + return 90; +} + +float DisplayServerVisionOS::screen_get_scale(int p_screen) const { + return 1; +} diff --git a/platform/visionos/doc_classes/EditorExportPlatformVisionOS.xml b/platform/visionos/doc_classes/EditorExportPlatformVisionOS.xml new file mode 100644 index 000000000000..17d6f4596d05 --- /dev/null +++ b/platform/visionos/doc_classes/EditorExportPlatformVisionOS.xml @@ -0,0 +1,588 @@ + + + + Exporter for visionOS. + + + + + $DOCS_URL/tutorials/export/exporting_for_ios.html + $DOCS_URL/tutorials/platform/ios/index.html + + + + Additional data added to the root [code]<dict>[/code] section of the [url=https://developer.apple.com/documentation/bundleresources/information_property_list]Info.plist[/url] file. The value should be an XML section with pairs of key-value elements, e.g.: + [codeblock lang=text] + <key>key_name</key> + <string>value</string> + [/codeblock] + + + Apple Team ID, unique 10-character string. To locate your Team ID check "Membership details" section in your Apple developer account dashboard, or "Organizational Unit" of your code signing certificate. See [url=https://developer.apple.com/help/account/manage-your-team/locate-your-team-id]Locate your Team ID[/url]. + + + Unique application identifier in a reverse-DNS format, can only contain alphanumeric characters ([code]A-Z[/code], [code]a-z[/code], and [code]0-9[/code]), hyphens ([code]-[/code]), and periods ([code].[/code]). + + + The "Full Name", "Common Name" or SHA-1 hash of the signing identity used for debug export. + + + The "Full Name", "Common Name" or SHA-1 hash of the signing identity used for release export. + + + If [code]true[/code], existing "project name" and "project name.xcodeproj" in the export destination directory will be unconditionally deleted during export. + + + Application distribution target (debug export). + + + Application distribution target (release export). + + + If [code]true[/code], exports iOS project files without building an XCArchive or [code].ipa[/code] file. If [code]false[/code], exports iOS project files and builds an XCArchive and [code].ipa[/code] file at the same time. When combining Godot with Fastlane or other build pipelines, you may want to set this to [code]true[/code]. + + + Interpolation method used to resize application icon. + + + + + Name of the provisioning profile. Sets XCode PROVISIONING_PROFILE_SPECIFIER for debug. [url=https://developer.apple.com/documentation/xcode/build-settings-reference#Provisioning-Profile]Used for manual provisioning[/url]. + Can be overridden with the environment variable [code]GODOT_APPLE_PLATFORM_PROFILE_SPECIFIER_DEBUG[/code]. + + + Name of the provisioning profile. Sets XCode PROVISIONING_PROFILE_SPECIFIER for release. [url=https://developer.apple.com/documentation/xcode/build-settings-reference#Provisioning-Profile]Used for manual provisioning[/url]. + Can be overridden with the environment variable [code]GODOT_APPLE_PLATFORM_PROFILE_SPECIFIER_RELEASE[/code]. + + + UUID of the provisioning profile. If left empty, Xcode will download or create a provisioning profile automatically. See [url=https://developer.apple.com/help/account/manage-profiles/edit-download-or-delete-profiles]Edit, download, or delete provisioning profiles[/url]. + Can be overridden with the environment variable [code]GODOT_APPLE_PLATFORM_PROVISIONING_PROFILE_UUID_DEBUG[/code]. + + + UUID of the provisioning profile. If left empty, Xcode will download or create a provisioning profile automatically. See [url=https://developer.apple.com/help/account/manage-profiles/edit-download-or-delete-profiles]Edit, download, or delete provisioning profiles[/url]. + Can be overridden with the environment variable [code]GODOT_APPLE_PLATFORM_PROVISIONING_PROFILE_UUID_RELEASE[/code]. + + + Application version visible to the user, can only contain numeric characters ([code]0-9[/code]) and periods ([code].[/code]). Falls back to [member ProjectSettings.application/config/version] if left empty. + + + A four-character creator code that is specific to the bundle. Optional. + + + Machine-readable application version, in the [code]major.minor.patch[/code] format, can only contain numeric characters ([code]0-9[/code]) and periods ([code].[/code]). This must be incremented on every new release pushed to the App Store. + + + If [code]true[/code], [code]arm64[/code] binaries are included into exported project. + + + If [code]true[/code], networking features related to Wi-Fi access are enabled. See [url=https://developer.apple.com/support/required-device-capabilities/]Required Device Capabilities[/url]. + + + Additional data added to the [code]UIRequiredDeviceCapabilities[/code] array of the [code]Info.plist[/code] file. + + + Requires the graphics performance and features of the A12 Bionic and later chips (devices supporting all Vulkan renderer features). + Enabling this option limits supported devices to: iPhone XS, iPhone XR, iPad Mini (5th gen.), iPad Air (3rd gen.), iPad (8th gen) and newer. + + + Requires the graphics performance and features of the A17 Pro and later chips. + Enabling this option limits supported devices to: iPhone 15 Pro and newer. + + + Path to the custom export template. If left empty, default template is used. + + + Path to the custom export template. If left empty, default template is used. + + + Additional data added to the root [code]<dict>[/code] section of the [url=https://developer.apple.com/documentation/bundleresources/entitlements].entitlements[/url] file. The value should be an XML section with pairs of key-value elements, for example: + [codeblock lang=text] + <key>key_name</key> + <string>value</string> + [/codeblock] + + + If [code]true[/code], allows access to Game Center features. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_developer_game-center]com.apple.developer.game-center[/url]. + + + If [code]true[/code], hints that the app might perform better with a higher memory limit. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_developer_kernel_increased-memory-limit]com.apple.developer.kernel.increased-memory-limit[/url]. + + + Environment for Apple Push Notification service. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/aps-environment]aps-environment[/url]. + + + Base application icon used to generate other icons. If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + + + Base application icon used to generate other icons, dark version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + + + Base application icon used to generate other icons, tinted version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + + + The reasons your app use active keyboard API. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api]Describing use of required reason API[/url]. + + + A message displayed when requesting access to the device's camera (in English). + + + A message displayed when requesting access to the device's camera (localized). + + + Indicates whether your app collects advertising data. + + + The reasons your app collects advertising data. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests]Describing data use in privacy manifests[/url]. + + + Indicates whether your app links advertising data to the user's identity. + + + Indicates whether your app uses advertising data for tracking. + + + Indicates whether your app collects audio data. + + + The reasons your app collects audio data. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests]Describing data use in privacy manifests[/url]. + + + Indicates whether your app links audio data to the user's identity. + + + Indicates whether your app uses audio data for tracking. + + + Indicates whether your app collects browsing history. + + + The reasons your app collects browsing history. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests]Describing data use in privacy manifests[/url]. + + + Indicates whether your app links browsing history to the user's identity. + + + Indicates whether your app uses browsing history for tracking. + + + Indicates whether your app collects coarse location data. + + + The reasons your app collects coarse location data. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests]Describing data use in privacy manifests[/url]. + + + Indicates whether your app links coarse location data to the user's identity. + + + Indicates whether your app uses coarse location data for tracking. + + + Indicates whether your app collects contacts. + + + The reasons your app collects contacts. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests]Describing data use in privacy manifests[/url]. + + + Indicates whether your app links contacts to the user's identity. + + + Indicates whether your app uses contacts for tracking. + + + Indicates whether your app collects crash data. + + + The reasons your app collects crash data. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests]Describing data use in privacy manifests[/url]. + + + Indicates whether your app links crash data to the user's identity. + + + Indicates whether your app uses crash data for tracking. + + + Indicates whether your app collects credit information. + + + The reasons your app collects credit information. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests]Describing data use in privacy manifests[/url]. + + + Indicates whether your app links credit information to the user's identity. + + + Indicates whether your app uses credit information for tracking. + + + Indicates whether your app collects customer support data. + + + The reasons your app collects customer support data. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests]Describing data use in privacy manifests[/url]. + + + Indicates whether your app links customer support data to the user's identity. + + + Indicates whether your app uses customer support data for tracking. + + + Indicates whether your app collects device IDs. + + + The reasons your app collects device IDs. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests]Describing data use in privacy manifests[/url]. + + + Indicates whether your app links device IDs to the user's identity. + + + Indicates whether your app uses device IDs for tracking. + + + Indicates whether your app collects email address. + + + The reasons your app collects email address. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests]Describing data use in privacy manifests[/url]. + + + Indicates whether your app links email address to the user's identity. + + + Indicates whether your app uses email address for tracking. + + + Indicates whether your app collects emails or text messages. + + + The reasons your app collects emails or text messages. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests]Describing data use in privacy manifests[/url]. + + + Indicates whether your app links emails or text messages to the user's identity. + + + Indicates whether your app uses emails or text messages for tracking. + + + Indicates whether your app collects environment scanning data. + + + The reasons your app collects environment scanning data. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests]Describing data use in privacy manifests[/url]. + + + Indicates whether your app links environment scanning data to the user's identity. + + + Indicates whether your app uses environment scanning data for tracking. + + + Indicates whether your app collects fitness and exercise data. + + + The reasons your app collects fitness and exercise data. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests]Describing data use in privacy manifests[/url]. + + + Indicates whether your app links fitness and exercise data to the user's identity. + + + Indicates whether your app uses fitness and exercise data for tracking. + + + Indicates whether your app collects gameplay content. + + + The reasons your app collects gameplay content. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests]Describing data use in privacy manifests[/url]. + + + Indicates whether your app links gameplay content to the user's identity. + + + Indicates whether your app uses gameplay content for tracking. + + + Indicates whether your app collects user's hand structure and hand movements. + + + The reasons your app collects user's hand structure and hand movements. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests]Describing data use in privacy manifests[/url]. + + + Indicates whether your app links user's hand structure and hand movements to the user's identity. + + + Indicates whether your app uses user's hand structure and hand movements for tracking. + + + Indicates whether your app collects user's head movement. + + + The reasons your app collects user's head movement. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests]Describing data use in privacy manifests[/url]. + + + Indicates whether your app links user's head movement to the user's identity. + + + Indicates whether your app uses user's head movement for tracking. + + + Indicates whether your app collects health and medical data. + + + The reasons your app collects health and medical data. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests]Describing data use in privacy manifests[/url]. + + + Indicates whether your app links health and medical data to the user's identity. + + + Indicates whether your app uses health and medical data for tracking. + + + Indicates whether your app collects user's name. + + + The reasons your app collects user's name. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests]Describing data use in privacy manifests[/url]. + + + Indicates whether your app links user's name to the user's identity. + + + Indicates whether your app uses user's name for tracking. + + + Indicates whether your app collects any other contact information. + + + The reasons your app collects any other contact information. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests]Describing data use in privacy manifests[/url]. + + + Indicates whether your app links any other contact information to the user's identity. + + + Indicates whether your app uses any other contact information for tracking. + + + Indicates whether your app collects any other data. + + + The reasons your app collects any other data. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests]Describing data use in privacy manifests[/url]. + + + Indicates whether your app links any other data to the user's identity. + + + Indicates whether your app uses any other data for tracking. + + + Indicates whether your app collects any other diagnostic data. + + + The reasons your app collects any other diagnostic data. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests]Describing data use in privacy manifests[/url]. + + + Indicates whether your app links any other diagnostic data to the user's identity. + + + Indicates whether your app uses any other diagnostic data for tracking. + + + Indicates whether your app collects any other financial information. + + + The reasons your app collects any other financial information. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests]Describing data use in privacy manifests[/url]. + + + Indicates whether your app links any other financial information to the user's identity. + + + Indicates whether your app uses any other financial information for tracking. + + + Indicates whether your app collects any other usage data. + + + The reasons your app collects any other usage data. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests]Describing data use in privacy manifests[/url]. + + + Indicates whether your app links any other usage data to the user's identity. + + + Indicates whether your app uses any other usage data for tracking. + + + Indicates whether your app collects any other user generated content. + + + The reasons your app collects any other user generated content. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests]Describing data use in privacy manifests[/url]. + + + Indicates whether your app links any other user generated content to the user's identity. + + + Indicates whether your app uses any other user generated content for tracking. + + + Indicates whether your app collects payment information. + + + The reasons your app collects payment information. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests]Describing data use in privacy manifests[/url]. + + + Indicates whether your app links payment information to the user's identity. + + + Indicates whether your app uses payment information for tracking. + + + Indicates whether your app collects performance data. + + + The reasons your app collects performance data. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests]Describing data use in privacy manifests[/url]. + + + Indicates whether your app links performance data to the user's identity. + + + Indicates whether your app uses performance data for tracking. + + + Indicates whether your app collects phone number. + + + The reasons your app collects phone number. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests]Describing data use in privacy manifests[/url]. + + + Indicates whether your app links phone number to the user's identity. + + + Indicates whether your app uses phone number for tracking. + + + Indicates whether your app collects photos or videos. + + + The reasons your app collects photos or videos. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests]Describing data use in privacy manifests[/url]. + + + Indicates whether your app links photos or videos to the user's identity. + + + Indicates whether your app uses photos or videos for tracking. + + + Indicates whether your app collects physical address. + + + The reasons your app collects physical address. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests]Describing data use in privacy manifests[/url]. + + + Indicates whether your app links physical address to the user's identity. + + + Indicates whether your app uses physical address for tracking. + + + Indicates whether your app collects precise location data. + + + The reasons your app collects precise location data. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests]Describing data use in privacy manifests[/url]. + + + Indicates whether your app links precise location data to the user's identity. + + + Indicates whether your app uses precise location data for tracking. + + + Indicates whether your app collects product interaction data. + + + The reasons your app collects product interaction data. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests]Describing data use in privacy manifests[/url]. + + + Indicates whether your app links product interaction data to the user's identity. + + + Indicates whether your app uses product interaction data for tracking. + + + Indicates whether your app collects purchase history. + + + The reasons your app collects purchase history. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests]Describing data use in privacy manifests[/url]. + + + Indicates whether your app links purchase history to the user's identity. + + + Indicates whether your app uses purchase history for tracking. + + + Indicates whether your app collects search history. + + + The reasons your app collects search history. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests]Describing data use in privacy manifests[/url]. + + + Indicates whether your app links search history to the user's identity. + + + Indicates whether your app uses search history for tracking. + + + Indicates whether your app collects sensitive user information. + + + The reasons your app collects sensitive user information. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests]Describing data use in privacy manifests[/url]. + + + Indicates whether your app links sensitive user information to the user's identity. + + + Indicates whether your app uses sensitive user information for tracking. + + + Indicates whether your app collects user IDs. + + + The reasons your app collects user IDs. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests]Describing data use in privacy manifests[/url]. + + + Indicates whether your app links user IDs to the user's identity. + + + Indicates whether your app uses user IDs for tracking. + + + The reasons your app use free disk space API. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api]Describing use of required reason API[/url]. + + + The reasons your app use file timestamp/metadata API. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api]Describing use of required reason API[/url]. + + + A message displayed when requesting access to the device's microphone (in English). + + + A message displayed when requesting access to the device's microphone (localized). + + + A message displayed when requesting access to the user's photo library (in English). + + + A message displayed when requesting access to the user's photo library (localized). + + + The reasons your app use system boot time / absolute time API. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api]Describing use of required reason API[/url]. + + + The list of internet domains your app connects to that engage in tracking. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files]Privacy manifest files[/url]. + + + Indicates whether your app uses data for tracking. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files]Privacy manifest files[/url]. + + + The reasons your app use user defaults API. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api]Describing use of required reason API[/url]. + + + If [code]true[/code], the app "Documents" folder can be accessed via "Files" app. See [url=https://developer.apple.com/documentation/bundleresources/information_property_list/lssupportsopeningdocumentsinplace]LSSupportsOpeningDocumentsInPlace[/url]. + + + If [code]true[/code], the app "Documents" folder can be accessed via iTunes file sharing. See [url=https://developer.apple.com/documentation/bundleresources/information_property_list/uifilesharingenabled]UIFileSharingEnabled[/url]. + + + diff --git a/platform/visionos/export/export.cpp b/platform/visionos/export/export.cpp new file mode 100644 index 000000000000..50acb1740c5e --- /dev/null +++ b/platform/visionos/export/export.cpp @@ -0,0 +1,46 @@ +/**************************************************************************/ +/* export.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 "export_plugin.h" + +#include "editor/export/editor_export.h" + +void register_visionos_exporter_types() { + GDREGISTER_VIRTUAL_CLASS(EditorExportPlatformVisionOS); +} + +void register_visionos_exporter() { + Ref platform; + platform.instantiate(); + + EditorExport::get_singleton()->add_export_platform(platform); +} diff --git a/platform/visionos/export/export.h b/platform/visionos/export/export.h new file mode 100644 index 000000000000..17163cc4db81 --- /dev/null +++ b/platform/visionos/export/export.h @@ -0,0 +1,34 @@ +/**************************************************************************/ +/* export.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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. */ +/**************************************************************************/ + +#pragma once + +void register_visionos_exporter_types(); +void register_visionos_exporter(); diff --git a/platform/visionos/export/export_plugin.cpp b/platform/visionos/export/export_plugin.cpp new file mode 100644 index 000000000000..8c67a0118543 --- /dev/null +++ b/platform/visionos/export/export_plugin.cpp @@ -0,0 +1,51 @@ +/**************************************************************************/ +/* export_plugin.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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_plugin.h" + +#include "logo_svg.gen.h" +#include "run_icon_svg.gen.h" + +EditorExportPlatformVisionOS::EditorExportPlatformVisionOS() : + EditorExportPlatformAppleEmbedded(_visionos_logo_svg, _visionos_run_icon_svg) { +} + +EditorExportPlatformVisionOS::~EditorExportPlatformVisionOS() { +} + +void EditorExportPlatformVisionOS::get_export_options(List *r_options) const { + EditorExportPlatformAppleEmbedded::get_export_options(r_options); + + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/min_visionos_version"), get_minimum_deployment_target())); +} + +Vector EditorExportPlatformVisionOS::get_icon_infos() const { + return Vector(); +} diff --git a/platform/visionos/export/export_plugin.h b/platform/visionos/export/export_plugin.h new file mode 100644 index 000000000000..1deb7361af26 --- /dev/null +++ b/platform/visionos/export/export_plugin.h @@ -0,0 +1,59 @@ +/**************************************************************************/ +/* export_plugin.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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. */ +/**************************************************************************/ + +#pragma once + +#include "editor/export/editor_export_platform_apple_embedded.h" + +class EditorExportPlatformVisionOS : public EditorExportPlatformAppleEmbedded { + GDCLASS(EditorExportPlatformVisionOS, EditorExportPlatformAppleEmbedded); + + virtual String get_platform_name() const override { return "visionos"; } + + virtual String get_sdk_name() const override { return "xros"; } + + virtual String get_minimum_deployment_target() const override { return "2.0"; } + + virtual Vector get_icon_infos() const override; + + virtual void get_export_options(List *r_options) const override; + +public: + virtual String get_name() const override { return "visionOS"; } + virtual String get_os_name() const override { return "visionOS"; } + + virtual void get_platform_features(List *r_features) const override { + EditorExportPlatformAppleEmbedded::get_platform_features(r_features); + r_features->push_back("visionos"); + } + + EditorExportPlatformVisionOS(); + ~EditorExportPlatformVisionOS(); +}; diff --git a/platform/visionos/export/logo.svg b/platform/visionos/export/logo.svg new file mode 100644 index 000000000000..da325f6dc783 --- /dev/null +++ b/platform/visionos/export/logo.svg @@ -0,0 +1 @@ + diff --git a/platform/visionos/export/run_icon.svg b/platform/visionos/export/run_icon.svg new file mode 100644 index 000000000000..72f55e864850 --- /dev/null +++ b/platform/visionos/export/run_icon.svg @@ -0,0 +1 @@ + diff --git a/platform/visionos/godot_view_visionos.h b/platform/visionos/godot_view_visionos.h new file mode 100644 index 000000000000..0599efbbdcaf --- /dev/null +++ b/platform/visionos/godot_view_visionos.h @@ -0,0 +1,37 @@ +/**************************************************************************/ +/* godot_view_visionos.mm */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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. */ +/**************************************************************************/ + +#pragma once + +#include "drivers/apple_embedded/godot_view_apple_embedded.h" + +@interface GDTViewVisionOS : GDTView + +@end diff --git a/platform/visionos/godot_view_visionos.mm b/platform/visionos/godot_view_visionos.mm new file mode 100644 index 000000000000..d2ed6dd827bc --- /dev/null +++ b/platform/visionos/godot_view_visionos.mm @@ -0,0 +1,80 @@ +/**************************************************************************/ +/* godot_view.mm */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 "godot_view_visionos.h" + +#include "display_layer_visionos.h" + +#include "core/error/error_macros.h" + +#import + +@interface GDTViewVisionOS () + +GODOT_CLANG_WARNING_PUSH_AND_IGNORE("-Wobjc-property-synthesis") +@property(strong, nonatomic) CALayer *renderingLayer; +GODOT_CLANG_WARNING_POP + +@end + +@implementation GDTViewVisionOS + +- (void)godot_commonInit { + [super godot_commonInit]; + + // Enable GamePad handler + GCEventInteraction *gamepadInteraction = [[GCEventInteraction alloc] init]; + gamepadInteraction.handledEventTypes = GCUIEventTypeGamepad; + [self addInteraction:gamepadInteraction]; +} + +- (CALayer *)initializeRenderingForDriver:(NSString *)driverName { + if (self.renderingLayer) { + return self.renderingLayer; + } + + CALayer *layer = [GDTMetalLayer layer]; + + layer.frame = self.bounds; + layer.contentsScale = self.contentScaleFactor; + + [self.layer addSublayer:layer]; + self.renderingLayer = layer; + + [layer initializeDisplayLayer]; + + return self.renderingLayer; +} + +@end + +GDTView *GDTViewCreate() { + return [GDTViewVisionOS new]; +} diff --git a/platform/visionos/main_visionos.mm b/platform/visionos/main_visionos.mm new file mode 100644 index 000000000000..c6bea151a7ae --- /dev/null +++ b/platform/visionos/main_visionos.mm @@ -0,0 +1,89 @@ +/**************************************************************************/ +/* main.mm */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 "os_visionos.h" + +#import "drivers/apple_embedded/godot_app_delegate.h" +#import "drivers/apple_embedded/main_utilities.h" +#include "main/main.h" + +#import +#include + +int gargc; +char **gargv; + +static OS_VisionOS *os = nullptr; + +int main(int argc, char *argv[]) { +#if defined(VULKAN_ENABLED) + //MoltenVK - enable full component swizzling support + setenv("MVK_CONFIG_FULL_IMAGE_VIEW_SWIZZLE", "1", 1); +#endif + + gargc = argc; + gargv = argv; + + @autoreleasepool { + NSString *className = NSStringFromClass([GDTApplicationDelegate class]); + UIApplicationMain(argc, argv, nil, className); + } + return 0; +} + +int apple_embedded_main(int argc, char **argv) { + change_to_launch_dir(argv); + + os = new OS_VisionOS(); + + // We must override main when testing is enabled + TEST_MAIN_OVERRIDE + + char *fargv[64]; + argc = process_args(argc, argv, fargv); + + Error err = Main::setup(fargv[0], argc - 1, &fargv[1], false); + + if (err != OK) { + if (err == ERR_HELP) { // Returned by --help and --version, so success. + return EXIT_SUCCESS; + } + return EXIT_FAILURE; + } + + os->initialize_modules(); + + return os->get_exit_code(); +} + +void apple_embedded_finish() { + Main::cleanup(); + delete os; +} diff --git a/platform/visionos/os_visionos.h b/platform/visionos/os_visionos.h new file mode 100644 index 000000000000..2ef0a8e6e985 --- /dev/null +++ b/platform/visionos/os_visionos.h @@ -0,0 +1,47 @@ +/**************************************************************************/ +/* os_visions.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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. */ +/**************************************************************************/ + +#pragma once + +#ifdef VISIONOS_ENABLED + +#import "drivers/apple_embedded/os_apple_embedded.h" + +class OS_VisionOS : public OS_AppleEmbedded { +public: + static OS_VisionOS *get_singleton(); + + OS_VisionOS(); + ~OS_VisionOS(); + + virtual String get_name() const override; +}; + +#endif // VISIONOS_ENABLED diff --git a/platform/visionos/os_visionos.mm b/platform/visionos/os_visionos.mm new file mode 100644 index 000000000000..b68b740b6ac4 --- /dev/null +++ b/platform/visionos/os_visionos.mm @@ -0,0 +1,52 @@ +/**************************************************************************/ +/* os_visionos.mm */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 "os_visionos.h" + +#import "display_server_visionos.h" + +#ifdef VISIONOS_ENABLED + +OS_VisionOS *OS_VisionOS::get_singleton() { + return (OS_VisionOS *)OS_AppleEmbedded::get_singleton(); +} + +OS_VisionOS::OS_VisionOS() : + OS_AppleEmbedded() { + DisplayServerVisionOS::register_visionos_driver(); +} + +OS_VisionOS::~OS_VisionOS() {} + +String OS_VisionOS::get_name() const { + return "visionOS"; +} + +#endif // VISIONOS_ENABLED diff --git a/platform/visionos/platform_config.h b/platform/visionos/platform_config.h new file mode 100644 index 000000000000..52dac8b496ee --- /dev/null +++ b/platform/visionos/platform_config.h @@ -0,0 +1,33 @@ +/**************************************************************************/ +/* platform_config.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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. */ +/**************************************************************************/ + +#pragma once + +#import "drivers/apple_embedded/platform_config.h" diff --git a/platform/visionos/platform_thread.h b/platform/visionos/platform_thread.h new file mode 100644 index 000000000000..42827e53fc8c --- /dev/null +++ b/platform/visionos/platform_thread.h @@ -0,0 +1,33 @@ +/**************************************************************************/ +/* platform_thread.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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. */ +/**************************************************************************/ + +#pragma once + +#include "drivers/apple/thread_apple.h" diff --git a/platform/visionos/platform_visionos_builders.py b/platform/visionos/platform_visionos_builders.py new file mode 100644 index 000000000000..a7f955a9480d --- /dev/null +++ b/platform/visionos/platform_visionos_builders.py @@ -0,0 +1,7 @@ +"""Functions used to generate source files during build time""" + +from platform_methods import generate_bundle_apple_embedded + + +def generate_bundle(target, source, env): + generate_bundle_apple_embedded("visionos", "xros-arm64", "xros-arm64-simulator", False, target, source, env) diff --git a/platform/visionos/visionos.h b/platform/visionos/visionos.h new file mode 100644 index 000000000000..beed15809be0 --- /dev/null +++ b/platform/visionos/visionos.h @@ -0,0 +1,37 @@ +/**************************************************************************/ +/* visionos.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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. */ +/**************************************************************************/ + +#pragma once + +#include "drivers/apple_embedded/apple_embedded.h" + +class visionOS : public AppleEmbedded { + GDCLASS(visionOS, AppleEmbedded); +}; diff --git a/platform_methods.py b/platform_methods.py index c8646a402267..eac68836f5c1 100644 --- a/platform_methods.py +++ b/platform_methods.py @@ -1,5 +1,6 @@ import os import platform +import shutil import subprocess import sys @@ -160,3 +161,79 @@ def detect_mvk(env, osname): return mvk_path return "" + + +def combine_libs_apple_embedded(target, source, env): + lib_path = target[0].srcnode().abspath + if "osxcross" in env: + libtool = "$APPLE_TOOLCHAIN_PATH/usr/bin/${apple_target_triple}libtool" + else: + libtool = "$APPLE_TOOLCHAIN_PATH/usr/bin/libtool" + env.Execute( + libtool + ' -static -o "' + lib_path + '" ' + " ".join([('"' + lib.srcnode().abspath + '"') for lib in source]) + ) + + +def generate_bundle_apple_embedded(platform, framework_dir, framework_dir_sim, use_mkv, target, source, env): + bin_dir = env.Dir("#bin").abspath + + # Template bundle. + app_prefix = "godot." + platform + rel_prefix = "libgodot." + platform + "." + "template_release" + dbg_prefix = "libgodot." + platform + "." + "template_debug" + if env.dev_build: + app_prefix += ".dev" + rel_prefix += ".dev" + dbg_prefix += ".dev" + if env["precision"] == "double": + app_prefix += ".double" + rel_prefix += ".double" + dbg_prefix += ".double" + + # Lipo template libraries. + # + # env.extra_suffix contains ".simulator" when building for simulator, + # but it's undesired when calling lipo() + extra_suffix = env.extra_suffix.replace(".simulator", "") + rel_target_bin = lipo(bin_dir + "/" + rel_prefix, extra_suffix + ".a") + dbg_target_bin = lipo(bin_dir + "/" + dbg_prefix, extra_suffix + ".a") + rel_target_bin_sim = lipo(bin_dir + "/" + rel_prefix, ".simulator" + extra_suffix + ".a") + dbg_target_bin_sim = lipo(bin_dir + "/" + dbg_prefix, ".simulator" + extra_suffix + ".a") + # Assemble Xcode project bundle. + app_dir = env.Dir("#bin/" + platform + "_xcode").abspath + templ = env.Dir("#misc/dist/" + platform + "_xcode").abspath + if os.path.exists(app_dir): + shutil.rmtree(app_dir) + shutil.copytree(templ, app_dir) + if rel_target_bin != "": + print(f' Copying "{platform}" release framework') + shutil.copy( + rel_target_bin, app_dir + "/libgodot." + platform + ".release.xcframework/" + framework_dir + "/libgodot.a" + ) + if dbg_target_bin != "": + print(f' Copying "{platform}" debug framework') + shutil.copy( + dbg_target_bin, app_dir + "/libgodot." + platform + ".debug.xcframework/" + framework_dir + "/libgodot.a" + ) + if rel_target_bin_sim != "": + print(f' Copying "{platform}" (simulator) release framework') + shutil.copy( + rel_target_bin_sim, + app_dir + "/libgodot." + platform + ".release.xcframework/" + framework_dir_sim + "/libgodot.a", + ) + if dbg_target_bin_sim != "": + print(f' Copying "{platform}" (simulator) debug framework') + shutil.copy( + dbg_target_bin_sim, + app_dir + "/libgodot." + platform + ".debug.xcframework/" + framework_dir_sim + "/libgodot.a", + ) + + if use_mkv: + mvk_path = detect_mvk(env, "ios-arm64") + if mvk_path != "": + shutil.copytree(mvk_path, app_dir + "/MoltenVK.xcframework") + + # ZIP Xcode project bundle. + zip_dir = env.Dir("#bin/" + (app_prefix + extra_suffix).replace(".", "_")).abspath + shutil.make_archive(zip_dir, "zip", root_dir=app_dir) + shutil.rmtree(app_dir) diff --git a/scene/3d/lightmap_gi.cpp b/scene/3d/lightmap_gi.cpp index 77d029c51aa4..0ce24f875649 100644 --- a/scene/3d/lightmap_gi.cpp +++ b/scene/3d/lightmap_gi.cpp @@ -1791,7 +1791,7 @@ PackedStringArray LightmapGI::get_configuration_warnings() const { warnings.push_back(RTR("The lightmap has no baked shadowmask textures. Please rebake with the Shadowmask Mode set to anything other than None.")); } -#elif defined(ANDROID_ENABLED) || defined(IOS_ENABLED) +#elif defined(ANDROID_ENABLED) || defined(APPLE_EMBEDDED_ENABLED) warnings.push_back(vformat(RTR("Lightmaps cannot be baked on %s. Rendering existing baked lightmaps will still work."), OS::get_singleton()->get_name())); #else warnings.push_back(RTR("Lightmaps cannot be baked, as the `lightmapper_rd` module was disabled at compile-time. Rendering existing baked lightmaps will still work.")); diff --git a/servers/rendering/renderer_rd/cluster_builder_rd.h b/servers/rendering/renderer_rd/cluster_builder_rd.h index 3f3d1a3be6eb..c2c423cc0f5b 100644 --- a/servers/rendering/renderer_rd/cluster_builder_rd.h +++ b/servers/rendering/renderer_rd/cluster_builder_rd.h @@ -184,8 +184,8 @@ class ClusterBuilderRD { }; uint32_t cluster_size = 32; -#if defined(MACOS_ENABLED) || defined(IOS_ENABLED) - // Results in visual artifacts on macOS and iOS when using MSAA and subgroups. +#if defined(MACOS_ENABLED) || defined(APPLE_EMBEDDED_ENABLED) + // Results in visual artifacts on macOS and iOS/visionOS when using MSAA and subgroups. // Using subgroups and disabling MSAA is the optimal solution for now and also works // with MoltenVK. bool use_msaa = false; diff --git a/servers/rendering/renderer_rd/effects/metal_fx.h b/servers/rendering/renderer_rd/effects/metal_fx.h index 341ecd1f4bdb..8f16dc41c525 100644 --- a/servers/rendering/renderer_rd/effects/metal_fx.h +++ b/servers/rendering/renderer_rd/effects/metal_fx.h @@ -30,6 +30,10 @@ #pragma once +#if defined(METAL_ENABLED) && !defined(VISIONOS_ENABLED) +#define METAL_MFXTEMPORAL_ENABLED +#endif + #ifdef METAL_ENABLED #include "spatial_upscaler.h" @@ -92,6 +96,8 @@ class MFXSpatialEffect : public SpatialUpscaler { ~MFXSpatialEffect(); }; +#ifdef METAL_MFXTEMPORAL_ENABLED + struct MFXTemporalContext { #ifdef __OBJC__ id scaler = nullptr; @@ -174,6 +180,8 @@ class MFXTemporalEffect { void process(MFXTemporalContext *p_ctx, Params p_params); }; +#endif + } //namespace RendererRD #endif // METAL_ENABLED diff --git a/servers/rendering/renderer_rd/effects/metal_fx.mm b/servers/rendering/renderer_rd/effects/metal_fx.mm index c3c81937229e..0f6152d5261e 100644 --- a/servers/rendering/renderer_rd/effects/metal_fx.mm +++ b/servers/rendering/renderer_rd/effects/metal_fx.mm @@ -123,6 +123,8 @@ return context; } +#ifdef METAL_MFXTEMPORAL_ENABLED + #pragma mark - Temporal Scaler MFXTemporalContext::~MFXTemporalContext() {} @@ -219,3 +221,5 @@ GODOT_CLANG_WARNING_POP } + +#endif diff --git a/servers/rendering/renderer_rd/environment/fog.cpp b/servers/rendering/renderer_rd/environment/fog.cpp index b451f67a4efb..b0ed574bac89 100644 --- a/servers/rendering/renderer_rd/environment/fog.cpp +++ b/servers/rendering/renderer_rd/environment/fog.cpp @@ -435,7 +435,7 @@ void Fog::VolumetricFog::init(const Vector3i &fog_size, RID p_sky_shader) { fog_map = RD::get_singleton()->texture_create(tf, RD::TextureView()); RD::get_singleton()->set_resource_name(fog_map, "Fog map"); -#if defined(MACOS_ENABLED) || defined(IOS_ENABLED) +#if defined(MACOS_ENABLED) || defined(APPLE_EMBEDDED_ENABLED) Vector dm; dm.resize_zeroed(fog_size.x * fog_size.y * fog_size.z * 4); @@ -574,7 +574,7 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P { RD::Uniform u; -#if defined(MACOS_ENABLED) || defined(IOS_ENABLED) +#if defined(MACOS_ENABLED) || defined(APPLE_EMBEDDED_ENABLED) u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; #else u.uniform_type = RD::UNIFORM_TYPE_IMAGE; @@ -594,7 +594,7 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P { RD::Uniform u; -#if defined(MACOS_ENABLED) || defined(IOS_ENABLED) +#if defined(MACOS_ENABLED) || defined(APPLE_EMBEDDED_ENABLED) u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; #else u.uniform_type = RD::UNIFORM_TYPE_IMAGE; @@ -606,7 +606,7 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P { RD::Uniform u; -#if defined(MACOS_ENABLED) || defined(IOS_ENABLED) +#if defined(MACOS_ENABLED) || defined(APPLE_EMBEDDED_ENABLED) u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; #else u.uniform_type = RD::UNIFORM_TYPE_IMAGE; @@ -913,7 +913,7 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P } { RD::Uniform u; -#if defined(MACOS_ENABLED) || defined(IOS_ENABLED) +#if defined(MACOS_ENABLED) || defined(APPLE_EMBEDDED_ENABLED) u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; #else u.uniform_type = RD::UNIFORM_TYPE_IMAGE; @@ -924,7 +924,7 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P } { RD::Uniform u; -#if defined(MACOS_ENABLED) || defined(IOS_ENABLED) +#if defined(MACOS_ENABLED) || defined(APPLE_EMBEDDED_ENABLED) u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; #else u.uniform_type = RD::UNIFORM_TYPE_IMAGE; @@ -936,7 +936,7 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P { RD::Uniform u; -#if defined(MACOS_ENABLED) || defined(IOS_ENABLED) +#if defined(MACOS_ENABLED) || defined(APPLE_EMBEDDED_ENABLED) u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; #else u.uniform_type = RD::UNIFORM_TYPE_IMAGE; diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp index dfb352f91c63..2d7718da59a3 100644 --- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp @@ -88,7 +88,7 @@ void RenderForwardClustered::RenderBufferDataForwardClustered::ensure_fsr2(Rende } } -#ifdef METAL_ENABLED +#ifdef METAL_MFXTEMPORAL_ENABLED bool RenderForwardClustered::RenderBufferDataForwardClustered::ensure_mfx_temporal(RendererRD::MFXTemporalEffect *p_effect) { if (mfx_temporal_context == nullptr) { RendererRD::MFXTemporalEffect::CreateParams params; @@ -127,7 +127,7 @@ void RenderForwardClustered::RenderBufferDataForwardClustered::free_data() { fsr2_context = nullptr; } -#ifdef METAL_ENABLED +#ifdef METAL_MFXTEMPORAL_ENABLED if (mfx_temporal_context) { memdelete(mfx_temporal_context); mfx_temporal_context = nullptr; @@ -1730,7 +1730,7 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co scale_type = SCALE_FSR2; break; case RS::VIEWPORT_SCALING_3D_MODE_METALFX_TEMPORAL: -#ifdef METAL_ENABLED +#ifdef METAL_MFXTEMPORAL_ENABLED scale_type = SCALE_MFX; #else scale_type = SCALE_NONE; @@ -2444,7 +2444,7 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co RD::get_singleton()->draw_command_end_label(); } else if (scale_type == SCALE_MFX) { -#ifdef METAL_ENABLED +#ifdef METAL_MFXTEMPORAL_ENABLED bool reset = rb_data->ensure_mfx_temporal(mfx_temporal_effect); RID exposure; @@ -5005,7 +5005,7 @@ RenderForwardClustered::RenderForwardClustered() { taa = memnew(RendererRD::TAA); fsr2_effect = memnew(RendererRD::FSR2Effect); ss_effects = memnew(RendererRD::SSEffects); -#ifdef METAL_ENABLED +#ifdef METAL_MFXTEMPORAL_ENABLED motion_vectors_store = memnew(RendererRD::MotionVectorsStore); mfx_temporal_effect = memnew(RendererRD::MFXTemporalEffect); #endif @@ -5027,7 +5027,7 @@ RenderForwardClustered::~RenderForwardClustered() { fsr2_effect = nullptr; } -#ifdef METAL_ENABLED +#ifdef METAL_MFXTEMPORAL_ENABLED if (mfx_temporal_effect) { memdelete(mfx_temporal_effect); mfx_temporal_effect = nullptr; diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h index 0ee21ad2252b..9f5127d04920 100644 --- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h +++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h @@ -95,7 +95,7 @@ class RenderForwardClustered : public RendererSceneRenderRD { private: RenderSceneBuffersRD *render_buffers = nullptr; RendererRD::FSR2Context *fsr2_context = nullptr; -#ifdef METAL_ENABLED +#ifdef METAL_MFXTEMPORAL_ENABLED RendererRD::MFXTemporalContext *mfx_temporal_context = nullptr; #endif @@ -141,7 +141,7 @@ class RenderForwardClustered : public RendererSceneRenderRD { void ensure_fsr2(RendererRD::FSR2Effect *p_effect); RendererRD::FSR2Context *get_fsr2_context() const { return fsr2_context; } -#ifdef METAL_ENABLED +#ifdef METAL_MFXTEMPORAL_ENABLED bool ensure_mfx_temporal(RendererRD::MFXTemporalEffect *p_effect); RendererRD::MFXTemporalContext *get_mfx_temporal_context() const { return mfx_temporal_context; } #endif @@ -719,7 +719,7 @@ class RenderForwardClustered : public RendererSceneRenderRD { RendererRD::FSR2Effect *fsr2_effect = nullptr; RendererRD::SSEffects *ss_effects = nullptr; -#ifdef METAL_ENABLED +#ifdef METAL_MFXTEMPORAL_ENABLED RendererRD::MFXTemporalEffect *mfx_temporal_effect = nullptr; #endif RendererRD::MotionVectorsStore *motion_vectors_store = nullptr; diff --git a/servers/rendering/renderer_rd/shader_rd.cpp b/servers/rendering/renderer_rd/shader_rd.cpp index ff1fefd3e150..9fa0ed596194 100644 --- a/servers/rendering/renderer_rd/shader_rd.cpp +++ b/servers/rendering/renderer_rd/shader_rd.cpp @@ -227,7 +227,7 @@ void ShaderRD::_build_variant_code(StringBuilder &builder, uint32_t p_variant, c for (const KeyValue &E : p_version->code_sections) { builder.append(String("#define ") + String(E.key) + "_CODE_USED\n"); } -#if (defined(MACOS_ENABLED) || defined(IOS_ENABLED)) +#if (defined(MACOS_ENABLED) || defined(APPLE_EMBEDDED_ENABLED)) if (RD::get_singleton()->get_device_capabilities().device_family == RDD::DEVICE_VULKAN) { builder.append("#define MOLTENVK_USED\n"); } diff --git a/servers/rendering/renderer_rd/storage_rd/utilities.cpp b/servers/rendering/renderer_rd/storage_rd/utilities.cpp index 9e0c9476500e..374eeb695ef2 100644 --- a/servers/rendering/renderer_rd/storage_rd/utilities.cpp +++ b/servers/rendering/renderer_rd/storage_rd/utilities.cpp @@ -265,7 +265,7 @@ bool Utilities::has_os_feature(const String &p_feature) const { return true; } -#if !defined(ANDROID_ENABLED) && !defined(IOS_ENABLED) +#if !defined(ANDROID_ENABLED) && !defined(APPLE_EMBEDDED_ENABLED) // Some Android devices report support for S3TC but we don't expect that and don't export the textures. // This could be fixed but so few devices support it that it doesn't seem useful (and makes bigger APKs). // For good measure we do the same hack for iOS, just in case.