diff options
Diffstat (limited to 'platform/iphone')
43 files changed, 980 insertions, 496 deletions
diff --git a/platform/iphone/SCsub b/platform/iphone/SCsub index 58b574a72f..5e10bf5646 100644 --- a/platform/iphone/SCsub +++ b/platform/iphone/SCsub @@ -13,6 +13,7 @@ iphone_lib = [ "display_server_iphone.mm", "joypad_iphone.mm", "godot_view.mm", + "tts_ios.mm", "display_layer.mm", "godot_app_delegate.m", "godot_view_renderer.mm", diff --git a/platform/iphone/api/api.cpp b/platform/iphone/api/api.cpp index a23791fe1c..f2e6fd7a7a 100644 --- a/platform/iphone/api/api.cpp +++ b/platform/iphone/api/api.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/iphone/api/api.h b/platform/iphone/api/api.h index c6570da7ec..ece91a9f1a 100644 --- a/platform/iphone/api/api.h +++ b/platform/iphone/api/api.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/iphone/app_delegate.h b/platform/iphone/app_delegate.h index 76c28b2272..0ec1dc071b 100644 --- a/platform/iphone/app_delegate.h +++ b/platform/iphone/app_delegate.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/iphone/app_delegate.mm b/platform/iphone/app_delegate.mm index c6f91665c5..c5c9b5a5f9 100644 --- a/platform/iphone/app_delegate.mm +++ b/platform/iphone/app_delegate.mm @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -29,6 +29,7 @@ /*************************************************************************/ #import "app_delegate.h" + #include "core/config/project_settings.h" #include "drivers/coreaudio/audio_driver_coreaudio.h" #import "godot_view.h" @@ -76,7 +77,7 @@ static ViewController *mainViewController = nil; // bail, things did not go very well for us, should probably output a message on screen with our error code... exit(0); return NO; - }; + } ViewController *viewController = [[ViewController alloc] init]; viewController.godotView.useCADisplayLink = bool(GLOBAL_DEF("display.iOS/use_cadisplaylink", true)) ? YES : NO; @@ -99,7 +100,7 @@ static ViewController *mainViewController = nil; [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:nil]; return YES; -}; +} - (void)onAudioInterruption:(NSNotification *)notification { if ([notification.name isEqualToString:AVAudioSessionInterruptionNotification]) { @@ -111,17 +112,17 @@ static ViewController *mainViewController = nil; OSIPhone::get_singleton()->on_focus_in(); } } -}; +} - (void)applicationDidReceiveMemoryWarning:(UIApplication *)application { if (OS::get_singleton()->get_main_loop()) { OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_MEMORY_WARNING); } -}; +} - (void)applicationWillTerminate:(UIApplication *)application { iphone_finish(); -}; +} // When application goes to background (e.g. user switches to another app or presses Home), // then applicationWillResignActive -> applicationDidEnterBackground are called. diff --git a/platform/iphone/detect.py b/platform/iphone/detect.py index 0d28aa2f06..392a0151be 100644 --- a/platform/iphone/detect.py +++ b/platform/iphone/detect.py @@ -47,8 +47,11 @@ def configure(env): if env["target"].startswith("release"): env.Append(CPPDEFINES=["NDEBUG", ("NS_BLOCK_ASSERTIONS", 1)]) if env["optimize"] == "speed": # optimize for speed (default) - env.Append(CCFLAGS=["-O2", "-ftree-vectorize", "-fomit-frame-pointer"]) - env.Append(LINKFLAGS=["-O2"]) + # `-O2` is more friendly to debuggers than `-O3`, leading to better crash backtraces + # when using `target=release_debug`. + opt = "-O3" if env["target"] == "release" else "-O2" + env.Append(CCFLAGS=[opt, "-ftree-vectorize", "-fomit-frame-pointer"]) + env.Append(LINKFLAGS=[opt]) elif env["optimize"] == "size": # optimize for size env.Append(CCFLAGS=["-Os", "-ftree-vectorize"]) env.Append(LINKFLAGS=["-Os"]) @@ -62,16 +65,9 @@ def configure(env): env.Append(LINKFLAGS=["-flto"]) ## Architecture - if env["arch"] == "x86": # i386 - env["bits"] = "32" - elif env["arch"] == "x86_64": - env["bits"] = "64" - elif env["arch"] == "arm" or env["arch"] == "arm32" or env["arch"] == "armv7" or env["bits"] == "32": # arm - env["arch"] = "arm" - env["bits"] = "32" - else: # armv64 + env["bits"] = "64" + if env["arch"] != "x86_64": env["arch"] = "arm64" - env["bits"] = "64" ## Compiler configuration @@ -108,28 +104,15 @@ def configure(env): detect_darwin_sdk_path("iphone", env) env.Append(CCFLAGS=["-miphoneos-version-min=11.0"]) - if env["arch"] == "x86" or env["arch"] == "x86_64": + if env["arch"] == "x86_64": env["ENV"]["MACOSX_DEPLOYMENT_TARGET"] = "10.9" - arch_flag = "i386" if env["arch"] == "x86" else env["arch"] env.Append( CCFLAGS=( - "-fobjc-arc -arch " - + arch_flag - + " -fobjc-abi-version=2 -fobjc-legacy-dispatch -fmessage-length=0 -fpascal-strings -fblocks" + "-fobjc-arc -arch x86_64" + " -fobjc-abi-version=2 -fobjc-legacy-dispatch -fmessage-length=0 -fpascal-strings -fblocks" " -fasm-blocks -isysroot $IPHONESDK" ).split() ) - elif env["arch"] == "arm": - env.Append( - CCFLAGS=( - "-fobjc-arc -arch armv7 -fmessage-length=0 -fno-strict-aliasing" - " -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits" - " -fpascal-strings -fblocks -isysroot $IPHONESDK -fvisibility=hidden -mthumb" - ' "-DIBOutlet=__attribute__((iboutlet))"' - ' "-DIBOutletCollection(ClassName)=__attribute__((iboutletcollection(ClassName)))"' - ' "-DIBAction=void)__attribute__((ibaction)" -MMD -MT dependencies'.split() - ) - ) elif env["arch"] == "arm64": env.Append( CCFLAGS=( @@ -140,7 +123,6 @@ def configure(env): ) ) env.Append(CPPDEFINES=["NEED_LONG_INT"]) - env.Append(CPPDEFINES=["LIBYUV_DISABLE_NEON"]) # Disable exceptions on non-tools (template) builds if not env["tools"]: diff --git a/platform/iphone/device_metrics.h b/platform/iphone/device_metrics.h index bc8f761dde..b9fb9b2fd9 100644 --- a/platform/iphone/device_metrics.h +++ b/platform/iphone/device_metrics.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/iphone/device_metrics.m b/platform/iphone/device_metrics.m index 306ca95d03..ec4dd8130d 100644 --- a/platform/iphone/device_metrics.m +++ b/platform/iphone/device_metrics.m @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/iphone/display_layer.h b/platform/iphone/display_layer.h index 1b8435d959..a17c75dba1 100644 --- a/platform/iphone/display_layer.h +++ b/platform/iphone/display_layer.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/iphone/display_layer.mm b/platform/iphone/display_layer.mm index afe612e1a5..92e81448ac 100644 --- a/platform/iphone/display_layer.mm +++ b/platform/iphone/display_layer.mm @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -29,6 +29,7 @@ /*************************************************************************/ #import "display_layer.h" + #include "core/config/project_settings.h" #include "core/os/keyboard.h" #include "display_server_iphone.h" @@ -153,17 +154,6 @@ return NO; } - // if (OS::get_singleton()) { - // OS::VideoMode vm; - // vm.fullscreen = true; - // vm.width = backingWidth; - // vm.height = backingHeight; - // vm.resizable = false; - // OS::get_singleton()->set_video_mode(vm); - // OSIPhone::get_singleton()->set_base_framebuffer(viewFramebuffer); - // }; - // gl_view_base_fb = viewFramebuffer; - return YES; } diff --git a/platform/iphone/display_server_iphone.h b/platform/iphone/display_server_iphone.h index 5cfcc1765c..7af222e3f8 100644 --- a/platform/iphone/display_server_iphone.h +++ b/platform/iphone/display_server_iphone.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -54,10 +54,12 @@ class DisplayServerIPhone : public DisplayServer { _THREAD_SAFE_CLASS_ #if defined(VULKAN_ENABLED) - VulkanContextIPhone *context_vulkan; - RenderingDeviceVulkan *rendering_device_vulkan; + VulkanContextIPhone *context_vulkan = nullptr; + RenderingDeviceVulkan *rendering_device_vulkan = nullptr; #endif + id tts = nullptr; + DisplayServer::ScreenOrientation screen_orientation; ObjectID window_attached_instance_id; @@ -123,18 +125,32 @@ public: 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 Array 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 Rect2i get_display_safe_area() const override; + virtual int get_screen_count() const override; virtual Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual float screen_get_scale(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual Vector<DisplayServer::WindowID> 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; diff --git a/platform/iphone/display_server_iphone.mm b/platform/iphone/display_server_iphone.mm index b746c60d4e..573ee9b7a8 100644 --- a/platform/iphone/display_server_iphone.mm +++ b/platform/iphone/display_server_iphone.mm @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -29,6 +29,7 @@ /*************************************************************************/ #include "display_server_iphone.h" + #import "app_delegate.h" #include "core/config/project_settings.h" #include "core/io/file_access_pack.h" @@ -37,6 +38,7 @@ #include "ios.h" #import "keyboard_input_view.h" #include "os_iphone.h" +#include "tts_ios.h" #import "view_controller.h" #import <Foundation/Foundation.h> @@ -51,9 +53,14 @@ DisplayServerIPhone *DisplayServerIPhone::get_singleton() { DisplayServerIPhone::DisplayServerIPhone(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { rendering_driver = p_rendering_driver; + // Init TTS + tts = [[TTS_IOS alloc] init]; + #if defined(GLES3_ENABLED) // FIXME: Add support for both OpenGL and Vulkan when OpenGL is implemented // again, + // Note that we should be checking "opengl3" as the driver, might never enable this seeing OpenGL is deprecated on iOS + // We are hardcoding the rendering_driver to "vulkan" down below if (rendering_driver == "opengl_es") { bool gl_initialization_error = false; @@ -131,18 +138,16 @@ DisplayServerIPhone::DisplayServerIPhone(const String &p_rendering_driver, Windo DisplayServerIPhone::~DisplayServerIPhone() { #if defined(VULKAN_ENABLED) - if (rendering_driver == "vulkan") { - if (rendering_device_vulkan) { - rendering_device_vulkan->finalize(); - memdelete(rendering_device_vulkan); - rendering_device_vulkan = nullptr; - } + if (rendering_device_vulkan) { + rendering_device_vulkan->finalize(); + memdelete(rendering_device_vulkan); + rendering_device_vulkan = nullptr; + } - if (context_vulkan) { - context_vulkan->window_destroy(MAIN_WINDOW_ID); - memdelete(context_vulkan); - context_vulkan = nullptr; - } + if (context_vulkan) { + context_vulkan->window_destroy(MAIN_WINDOW_ID); + memdelete(context_vulkan); + context_vulkan = nullptr; } #endif } @@ -231,7 +236,7 @@ void DisplayServerIPhone::touch_press(int p_idx, int p_x, int p_y, bool p_presse ev->set_position(Vector2(p_x, p_y)); perform_event(ev); } -}; +} void DisplayServerIPhone::touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y) { if (!GLOBAL_DEF("debug/disable_touch", false)) { @@ -241,16 +246,16 @@ void DisplayServerIPhone::touch_drag(int p_idx, int p_prev_x, int p_prev_y, int ev->set_position(Vector2(p_x, p_y)); ev->set_relative(Vector2(p_x - p_prev_x, p_y - p_prev_y)); perform_event(ev); - }; -}; + } +} void DisplayServerIPhone::perform_event(const Ref<InputEvent> &p_event) { Input::get_singleton()->parse_input_event(p_event); -}; +} void DisplayServerIPhone::touches_cancelled(int p_idx) { touch_press(p_idx, -1, -1, false, false); -}; +} // MARK: Keyboard @@ -263,13 +268,13 @@ void DisplayServerIPhone::key(Key p_key, bool p_pressed) { ev->set_physical_keycode(p_key); ev->set_unicode((char32_t)p_key); perform_event(ev); -}; +} // MARK: Motion void DisplayServerIPhone::update_gravity(float p_x, float p_y, float p_z) { Input::get_singleton()->set_gravity(Vector3(p_x, p_y, p_z)); -}; +} void DisplayServerIPhone::update_accelerometer(float p_x, float p_y, float p_z) { // Found out the Z should not be negated! Pass as is! @@ -279,21 +284,20 @@ void DisplayServerIPhone::update_accelerometer(float p_x, float p_y, float p_z) p_z / kDisplayServerIPhoneAcceleration); Input::get_singleton()->set_accelerometer(v_accelerometer); -}; +} void DisplayServerIPhone::update_magnetometer(float p_x, float p_y, float p_z) { Input::get_singleton()->set_magnetometer(Vector3(p_x, p_y, p_z)); -}; +} void DisplayServerIPhone::update_gyroscope(float p_x, float p_y, float p_z) { Input::get_singleton()->set_gyroscope(Vector3(p_x, p_y, p_z)); -}; +} // MARK: - bool DisplayServerIPhone::has_feature(Feature p_feature) const { switch (p_feature) { - // case FEATURE_CONSOLE_WINDOW: // case FEATURE_CURSOR_SHAPE: // case FEATURE_CUSTOM_CURSOR_SHAPE: // case FEATURE_GLOBAL_MENU: @@ -310,6 +314,7 @@ bool DisplayServerIPhone::has_feature(Feature p_feature) const { case FEATURE_ORIENTATION: case FEATURE_TOUCHSCREEN: case FEATURE_VIRTUAL_KEYBOARD: + case FEATURE_TEXT_TO_SPEECH: return true; default: return false; @@ -320,6 +325,57 @@ String DisplayServerIPhone::get_name() const { return "iPhone"; } +bool DisplayServerIPhone::tts_is_speaking() const { + ERR_FAIL_COND_V(!tts, false); + return [tts isSpeaking]; +} + +bool DisplayServerIPhone::tts_is_paused() const { + ERR_FAIL_COND_V(!tts, false); + return [tts isPaused]; +} + +Array DisplayServerIPhone::tts_get_voices() const { + ERR_FAIL_COND_V(!tts, Array()); + return [tts getVoices]; +} + +void DisplayServerIPhone::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) { + ERR_FAIL_COND(!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 DisplayServerIPhone::tts_pause() { + ERR_FAIL_COND(!tts); + [tts pauseSpeaking]; +} + +void DisplayServerIPhone::tts_resume() { + ERR_FAIL_COND(!tts); + [tts resumeSpeaking]; +} + +void DisplayServerIPhone::tts_stop() { + ERR_FAIL_COND(!tts); + [tts stopSpeaking]; +} + +Rect2i DisplayServerIPhone::get_display_safe_area() const { + if (@available(iOS 11, *)) { + 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); + } else { + return Rect2i(screen_get_position(), screen_get_size()); + } +} + int DisplayServerIPhone::get_screen_count() const { return 1; } @@ -339,22 +395,7 @@ Size2i DisplayServerIPhone::screen_get_size(int p_screen) const { } Rect2i DisplayServerIPhone::screen_get_usable_rect(int p_screen) const { - if (@available(iOS 11, *)) { - UIEdgeInsets insets = UIEdgeInsetsZero; - UIView *view = AppDelegate.viewController.godotView; - - if ([view respondsToSelector:@selector(safeAreaInsets)]) { - insets = [view safeAreaInsets]; - } - - float scale = screen_get_scale(p_screen); - Size2i insets_position = Size2i(insets.left, insets.top) * scale; - Size2i insets_size = Size2i(insets.left + insets.right, insets.top + insets.bottom) * scale; - - return Rect2i(screen_get_position(p_screen) + insets_position, screen_get_size(p_screen) - insets_size); - } else { - return Rect2i(screen_get_position(p_screen), screen_get_size(p_screen)); - } + return Rect2i(screen_get_position(p_screen), screen_get_size(p_screen)); } int DisplayServerIPhone::screen_get_dpi(int p_screen) const { @@ -394,6 +435,10 @@ int DisplayServerIPhone::screen_get_dpi(int p_screen) const { } } +float DisplayServerIPhone::screen_get_refresh_rate(int p_screen) const { + return [UIScreen mainScreen].maximumFramesPerSecond; +} + float DisplayServerIPhone::screen_get_scale(int p_screen) const { return [UIScreen mainScreen].nativeScale; } @@ -408,6 +453,24 @@ DisplayServer::WindowID DisplayServerIPhone::get_window_at_screen_position(const return MAIN_WINDOW_ID; } +int64_t DisplayServerIPhone::window_get_native_handle(HandleType p_handle_type, WindowID p_window) const { + ERR_FAIL_COND_V(p_window != MAIN_WINDOW_ID, 0); + switch (p_handle_type) { + 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 DisplayServerIPhone::window_attach_instance_id(ObjectID p_instance, WindowID p_window) { window_attached_instance_id = p_instance; } @@ -499,7 +562,7 @@ void DisplayServerIPhone::window_move_to_foreground(WindowID p_window) { float DisplayServerIPhone::screen_get_max_scale() const { return screen_get_scale(SCREEN_OF_MAIN_WINDOW); -}; +} void DisplayServerIPhone::screen_set_orientation(DisplayServer::ScreenOrientation p_orientation, int p_screen) { screen_orientation = p_orientation; @@ -565,10 +628,8 @@ void DisplayServerIPhone::resize_window(CGSize viewSize) { Size2i size = Size2i(viewSize.width, viewSize.height) * screen_get_max_scale(); #if defined(VULKAN_ENABLED) - if (rendering_driver == "vulkan") { - if (context_vulkan) { - context_vulkan->window_resize(MAIN_WINDOW_ID, size.x, size.y); - } + if (context_vulkan) { + context_vulkan->window_resize(MAIN_WINDOW_ID, size.x, size.y); } #endif diff --git a/platform/iphone/export/export.cpp b/platform/iphone/export/export.cpp index 208626ae36..3b02e661c1 100644 --- a/platform/iphone/export/export.cpp +++ b/platform/iphone/export/export.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/iphone/export/export.h b/platform/iphone/export/export.h index 4a79ca279f..adb3c23957 100644 --- a/platform/iphone/export/export.h +++ b/platform/iphone/export/export.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/iphone/export/export_plugin.cpp b/platform/iphone/export/export_plugin.cpp index 247b456b26..1f5e378098 100644 --- a/platform/iphone/export/export_plugin.cpp +++ b/platform/iphone/export/export_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -30,13 +30,12 @@ #include "export_plugin.h" +#include "editor/editor_node.h" + void EditorExportPlatformIOS::get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) { String driver = ProjectSettings::get_singleton()->get("rendering/driver/driver_name"); - r_features->push_back("pvrtc"); - if (driver == "vulkan") { - // FIXME: Review if this is correct. - r_features->push_back("etc2"); - } + // Vulkan and OpenGL ES 3.0 both mandate ETC2 support. + r_features->push_back("etc2"); Vector<String> architectures = _get_preset_architectures(p_preset); for (int i = 0; i < architectures.size(); ++i) { @@ -46,7 +45,6 @@ void EditorExportPlatformIOS::get_preset_features(const Ref<EditorExportPreset> Vector<EditorExportPlatformIOS::ExportArchitecture> EditorExportPlatformIOS::_get_supported_architectures() { Vector<ExportArchitecture> archs; - archs.push_back(ExportArchitecture("armv7", false)); // Disabled by default, not included in official templates. archs.push_back(ExportArchitecture("arm64", true)); return archs; } @@ -60,18 +58,18 @@ struct LoadingScreenInfo { }; static const LoadingScreenInfo loading_screen_infos[] = { - { "landscape_launch_screens/iphone_2436x1125", "Default-Landscape-X.png", 2436, 1125, false }, - { "landscape_launch_screens/iphone_2208x1242", "Default-Landscape-736h@3x.png", 2208, 1242, false }, - { "landscape_launch_screens/ipad_1024x768", "Default-Landscape.png", 1024, 768, false }, - { "landscape_launch_screens/ipad_2048x1536", "Default-Landscape@2x.png", 2048, 1536, false }, - - { "portrait_launch_screens/iphone_640x960", "Default-480h@2x.png", 640, 960, true }, - { "portrait_launch_screens/iphone_640x1136", "Default-568h@2x.png", 640, 1136, true }, - { "portrait_launch_screens/iphone_750x1334", "Default-667h@2x.png", 750, 1334, true }, - { "portrait_launch_screens/iphone_1125x2436", "Default-Portrait-X.png", 1125, 2436, true }, - { "portrait_launch_screens/ipad_768x1024", "Default-Portrait.png", 768, 1024, true }, - { "portrait_launch_screens/ipad_1536x2048", "Default-Portrait@2x.png", 1536, 2048, true }, - { "portrait_launch_screens/iphone_1242x2208", "Default-Portrait-736h@3x.png", 1242, 2208, true } + { PNAME("landscape_launch_screens/iphone_2436x1125"), "Default-Landscape-X.png", 2436, 1125, false }, + { PNAME("landscape_launch_screens/iphone_2208x1242"), "Default-Landscape-736h@3x.png", 2208, 1242, false }, + { PNAME("landscape_launch_screens/ipad_1024x768"), "Default-Landscape.png", 1024, 768, false }, + { PNAME("landscape_launch_screens/ipad_2048x1536"), "Default-Landscape@2x.png", 2048, 1536, false }, + + { PNAME("portrait_launch_screens/iphone_640x960"), "Default-480h@2x.png", 640, 960, true }, + { PNAME("portrait_launch_screens/iphone_640x1136"), "Default-568h@2x.png", 640, 1136, true }, + { PNAME("portrait_launch_screens/iphone_750x1334"), "Default-667h@2x.png", 750, 1334, true }, + { PNAME("portrait_launch_screens/iphone_1125x2436"), "Default-Portrait-X.png", 1125, 2436, true }, + { PNAME("portrait_launch_screens/ipad_768x1024"), "Default-Portrait.png", 768, 1024, true }, + { PNAME("portrait_launch_screens/ipad_1536x2048"), "Default-Portrait@2x.png", 1536, 2048, true }, + { PNAME("portrait_launch_screens/iphone_1242x2208"), "Default-Portrait-736h@3x.png", 1242, 2208, true } }; void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options) { @@ -80,13 +78,13 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options) Vector<ExportArchitecture> architectures = _get_supported_architectures(); for (int i = 0; i < architectures.size(); ++i) { - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "architectures/" + architectures[i].name), architectures[i].is_default)); + 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"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/provisioning_profile_uuid_debug"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/code_sign_identity_debug", PROPERTY_HINT_PLACEHOLDER_TEXT, "iPhone Developer"), "iPhone Developer")); + 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::INT, "application/export_method_debug", PROPERTY_HINT_ENUM, "App Store,Development,Ad-Hoc,Enterprise"), 1)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/provisioning_profile_uuid_release"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/code_sign_identity_release", PROPERTY_HINT_PLACEHOLDER_TEXT, "iPhone Distribution"), "")); @@ -94,34 +92,28 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *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/name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/info"), "Made with Godot Engine")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/bundle_identifier", PROPERTY_HINT_PLACEHOLDER_TEXT, "com.example.game"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/signature"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/short_version"), "1.0")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/version"), "1.0")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/copyright"), "")); Vector<PluginConfigIOS> found_plugins = get_plugins(); for (int i = 0; i < found_plugins.size(); i++) { - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "plugins/" + found_plugins[i].name), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, vformat("%s/%s", PNAME("plugins"), found_plugins[i].name)), false)); } - Set<String> plist_keys; + HashSet<String> plist_keys; for (int i = 0; i < found_plugins.size(); i++) { // Editable plugin plist values PluginConfigIOS plugin = found_plugins[i]; - const String *K = nullptr; - while ((K = plugin.plist.next(K))) { - String key = *K; - PluginConfigIOS::PlistItem item = plugin.plist[key]; - switch (item.type) { + for (const KeyValue<String, PluginConfigIOS::PlistItem> &E : plugin.plist) { + switch (E.value.type) { case PluginConfigIOS::PlistItemType::STRING_INPUT: { - String preset_name = "plugins_plist/" + key; + String preset_name = "plugins_plist/" + E.key; if (!plist_keys.has(preset_name)) { - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, preset_name), item.value)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, preset_name), E.value.value)); plist_keys.insert(preset_name); } } break; @@ -141,20 +133,23 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options) 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())); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "icons/generate_missing"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/iphone_120x120", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPhone/iPod Touch with Retina display + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/iphone_180x180", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPhone with Retina HD display - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "required_icons/iphone_120x120", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPhone/iPod Touch with retina display - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "required_icons/ipad_76x76", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPad - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "required_icons/app_store_1024x1024", PROPERTY_HINT_FILE, "*.png"), "")); // App Store + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/ipad_76x76", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPad + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/ipad_152x152", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPad with Retina display + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/ipad_167x167", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPad Pro - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "optional_icons/iphone_180x180", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPhone with retina HD display - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "optional_icons/ipad_152x152", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPad with retina display - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "optional_icons/ipad_167x167", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPad Pro - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "optional_icons/spotlight_40x40", PROPERTY_HINT_FILE, "*.png"), "")); // Spotlight - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "optional_icons/spotlight_80x80", PROPERTY_HINT_FILE, "*.png"), "")); // Spotlight on devices with retina display + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/app_store_1024x1024", PROPERTY_HINT_FILE, "*.png"), "")); // App Store + + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/spotlight_40x40", PROPERTY_HINT_FILE, "*.png"), "")); // Spotlight + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/spotlight_80x80", PROPERTY_HINT_FILE, "*.png"), "")); // Spotlight on devices with Retina display r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "storyboard/use_launch_screen_storyboard"), 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)); @@ -163,8 +158,6 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options) 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::BOOL, "launch_screens/generate_missing"), false)); - for (uint64_t i = 0; i < sizeof(loading_screen_infos) / sizeof(loading_screen_infos[0]); ++i) { r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, loading_screen_infos[i].preset_key, PROPERTY_HINT_FILE, "*.png"), "")); } @@ -183,6 +176,10 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_ "scaleAspectFill", "scaleToFill" }; + 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("application/provisioning_profile_uuid_debug").operator String().is_empty() || (dbg_sign_id != "iPhone Developer"); + bool rel_manual = !p_preset->get("application/provisioning_profile_uuid_release").operator String().is_empty() || (rel_sign_id != "iPhone Distribution"); String str; String strnew; str.parse_utf8((const char *)pfile.ptr(), pfile.size()); @@ -200,8 +197,6 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_ strnew += lines[i].replace("$modules_buildgrp", p_config.modules_buildgrp) + "\n"; } else if (lines[i].find("$name") != -1) { strnew += lines[i].replace("$name", p_config.pkg_name) + "\n"; - } else if (lines[i].find("$info") != -1) { - strnew += lines[i].replace("$info", p_preset->get("application/info")) + "\n"; } else if (lines[i].find("$bundle_identifier") != -1) { strnew += lines[i].replace("$bundle_identifier", p_preset->get("application/bundle_identifier")) + "\n"; } else if (lines[i].find("$short_version") != -1) { @@ -210,8 +205,6 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_ strnew += lines[i].replace("$version", p_preset->get("application/version")) + "\n"; } else if (lines[i].find("$signature") != -1) { strnew += lines[i].replace("$signature", p_preset->get("application/signature")) + "\n"; - } else if (lines[i].find("$copyright") != -1) { - strnew += lines[i].replace("$copyright", p_preset->get("application/copyright")) + "\n"; } else if (lines[i].find("$team_id") != -1) { strnew += lines[i].replace("$team_id", p_preset->get("application/app_store_team_id")) + "\n"; } else if (lines[i].find("$default_build_config") != -1) { @@ -223,13 +216,25 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_ strnew += lines[i].replace("$provisioning_profile_uuid_release", p_preset->get("application/provisioning_profile_uuid_release")) + "\n"; } else if (lines[i].find("$provisioning_profile_uuid_debug") != -1) { strnew += lines[i].replace("$provisioning_profile_uuid_debug", p_preset->get("application/provisioning_profile_uuid_debug")) + "\n"; + } else if (lines[i].find("$code_sign_style_debug") != -1) { + 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].find("$code_sign_style_release") != -1) { + 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].find("$provisioning_profile_uuid") != -1) { String uuid = p_debug ? p_preset->get("application/provisioning_profile_uuid_debug") : p_preset->get("application/provisioning_profile_uuid_release"); strnew += lines[i].replace("$provisioning_profile_uuid", uuid) + "\n"; } else if (lines[i].find("$code_sign_identity_debug") != -1) { - strnew += lines[i].replace("$code_sign_identity_debug", p_preset->get("application/code_sign_identity_debug")) + "\n"; + strnew += lines[i].replace("$code_sign_identity_debug", dbg_sign_id) + "\n"; } else if (lines[i].find("$code_sign_identity_release") != -1) { - strnew += lines[i].replace("$code_sign_identity_release", p_preset->get("application/code_sign_identity_release")) + "\n"; + strnew += lines[i].replace("$code_sign_identity_release", rel_sign_id) + "\n"; } else if (lines[i].find("$additional_plist_content") != -1) { strnew += lines[i].replace("$additional_plist_content", p_config.plist_content) + "\n"; } else if (lines[i].find("$godot_archs") != -1) { @@ -377,6 +382,36 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_ String value = value_format.format(value_dictionary, "$_"); strnew += lines[i].replace("$launch_screen_background_color", value) + "\n"; + } else if (lines[i].find("$pbx_locale_file_reference") != -1) { + String locale_files; + Vector<String> translations = ProjectSettings::get_singleton()->get("internationalization/locale/translations"); + if (translations.size() > 0) { + int index = 0; + for (const String &E : translations) { + Ref<Translation> tr = ResourceLoader::load(E); + if (tr.is_valid()) { + String lang = tr->get_locale(); + locale_files += "D0BCFE4518AEBDA2004A" + itos(index).pad_zeros(4) + " /* " + lang + " */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = " + lang + "; path = " + lang + ".lproj/InfoPlist.strings; sourceTree = \"<group>\"; };"; + } + index++; + } + } + strnew += lines[i].replace("$pbx_locale_file_reference", locale_files); + } else if (lines[i].find("$pbx_locale_build_reference") != -1) { + String locale_files; + Vector<String> translations = ProjectSettings::get_singleton()->get("internationalization/locale/translations"); + if (translations.size() > 0) { + int index = 0; + for (const String &E : translations) { + Ref<Translation> tr = ResourceLoader::load(E); + if (tr.is_valid()) { + String lang = tr->get_locale(); + locale_files += "D0BCFE4518AEBDA2004A" + itos(index).pad_zeros(4) + " /* " + lang + " */,"; + } + index++; + } + } + strnew += lines[i].replace("$pbx_locale_build_reference", locale_files); } else { strnew += lines[i] + "\n"; } @@ -466,69 +501,57 @@ struct IconInfo { const char *actual_size_side; const char *scale; const char *unscaled_size; - bool is_required = false; }; static const IconInfo icon_infos[] = { - { "required_icons/iphone_120x120", "iphone", "Icon-120.png", "120", "2x", "60x60", true }, - { "required_icons/iphone_120x120", "iphone", "Icon-120.png", "120", "3x", "40x40", true }, - - { "required_icons/ipad_76x76", "ipad", "Icon-76.png", "76", "1x", "76x76", true }, - { "required_icons/app_store_1024x1024", "ios-marketing", "Icon-1024.png", "1024", "1x", "1024x1024", true }, - - { "optional_icons/iphone_180x180", "iphone", "Icon-180.png", "180", "3x", "60x60", false }, - - { "optional_icons/ipad_152x152", "ipad", "Icon-152.png", "152", "2x", "76x76", false }, - - { "optional_icons/ipad_167x167", "ipad", "Icon-167.png", "167", "2x", "83.5x83.5", false }, - - { "optional_icons/spotlight_40x40", "ipad", "Icon-40.png", "40", "1x", "40x40", false }, - - { "optional_icons/spotlight_80x80", "iphone", "Icon-80.png", "80", "2x", "40x40", false }, - { "optional_icons/spotlight_80x80", "ipad", "Icon-80.png", "80", "2x", "40x40", false } + // Home screen on iPhone + { "icons/iphone_120x120", "iphone", "Icon-120.png", "120", "2x", "60x60" }, + { "icons/iphone_120x120", "iphone", "Icon-120.png", "120", "3x", "40x40" }, + { "icons/iphone_180x180", "iphone", "Icon-180.png", "180", "3x", "60x60" }, + + // Home screen on iPad + { "icons/ipad_76x76", "ipad", "Icon-76.png", "76", "1x", "76x76" }, + { "icons/ipad_152x152", "ipad", "Icon-152.png", "152", "2x", "76x76" }, + { "icons/ipad_167x167", "ipad", "Icon-167.png", "167", "2x", "83.5x83.5" }, + + // App Store + { "icons/app_store_1024x1024", "ios-marketing", "Icon-1024.png", "1024", "1x", "1024x1024" }, + + // Spotlight + { "icons/spotlight_40x40", "ipad", "Icon-40.png", "40", "1x", "40x40" }, + { "icons/spotlight_80x80", "iphone", "Icon-80.png", "80", "2x", "40x40" }, + { "icons/spotlight_80x80", "ipad", "Icon-80.png", "80", "2x", "40x40" } }; Error EditorExportPlatformIOS::_export_icons(const Ref<EditorExportPreset> &p_preset, const String &p_iconset_dir) { String json_description = "{\"images\":["; String sizes; - DirAccess *da = DirAccess::open(p_iconset_dir); - ERR_FAIL_COND_V_MSG(!da, ERR_CANT_OPEN, "Cannot open directory '" + p_iconset_dir + "'."); + Ref<DirAccess> da = DirAccess::open(p_iconset_dir); + ERR_FAIL_COND_V_MSG(da.is_null(), ERR_CANT_OPEN, "Cannot open directory '" + p_iconset_dir + "'."); for (uint64_t i = 0; i < (sizeof(icon_infos) / sizeof(icon_infos[0])); ++i) { IconInfo info = icon_infos[i]; int side_size = String(info.actual_size_side).to_int(); String icon_path = p_preset->get(info.preset_key); if (icon_path.length() == 0) { - if ((bool)p_preset->get("icons/generate_missing")) { - // Resize main app icon - icon_path = ProjectSettings::get_singleton()->get("application/config/icon"); - Ref<Image> img = memnew(Image); - Error err = ImageLoader::load_image(icon_path, img); - if (err != OK) { - ERR_PRINT("Invalid icon (" + String(info.preset_key) + "): '" + icon_path + "'."); - return ERR_UNCONFIGURED; - } - img->resize(side_size, side_size); - err = img->save_png(p_iconset_dir + info.export_name); - if (err) { - String err_str = String("Failed to export icon(" + String(info.preset_key) + "): '" + icon_path + "'."); - ERR_PRINT(err_str.utf8().get_data()); - return err; - } - } else { - if (info.is_required) { - String err_str = String("Required icon (") + info.preset_key + ") is not specified in the preset."; - ERR_PRINT(err_str); - return ERR_UNCONFIGURED; - } else { - String err_str = String("Icon (") + info.preset_key + ") is not specified in the preset."; - WARN_PRINT(err_str); - } - continue; + // Resize main app icon + icon_path = ProjectSettings::get_singleton()->get("application/config/icon"); + Ref<Image> img = memnew(Image); + Error err = ImageLoader::load_image(icon_path, img); + if (err != OK) { + ERR_PRINT("Invalid icon (" + String(info.preset_key) + "): '" + icon_path + "'."); + return ERR_UNCONFIGURED; + } + img->resize(side_size, side_size); + err = img->save_png(p_iconset_dir + info.export_name); + if (err) { + String err_str = String("Failed to export icon(" + String(info.preset_key) + "): '" + icon_path + "'."); + ERR_PRINT(err_str.utf8().get_data()); + return err; } } else { - // Load custom icon + // Load custom icon and resize if required Ref<Image> img = memnew(Image); Error err = ImageLoader::load_image(icon_path, img); if (err != OK) { @@ -536,13 +559,14 @@ Error EditorExportPlatformIOS::_export_icons(const Ref<EditorExportPreset> &p_pr return ERR_UNCONFIGURED; } if (img->get_width() != side_size || img->get_height() != side_size) { - ERR_PRINT("Invalid icon size (" + String(info.preset_key) + "): '" + icon_path + "'."); - return ERR_UNCONFIGURED; + WARN_PRINT("Icon (" + String(info.preset_key) + "): '" + icon_path + "' has incorrect size (" + String::num_int64(img->get_width()) + "x" + String::num_int64(img->get_height()) + ") and was automatically resized to " + String::num_int64(side_size) + "x" + String::num_int64(side_size) + "."); + img->resize(side_size, side_size); + err = img->save_png(p_iconset_dir + info.export_name); + } else { + err = da->copy(icon_path, p_iconset_dir + info.export_name); } - err = da->copy(icon_path, p_iconset_dir + info.export_name); if (err) { - memdelete(da); String err_str = String("Failed to export icon(" + String(info.preset_key) + "): '" + icon_path + "'."); ERR_PRINT(err_str.utf8().get_data()); return err; @@ -560,19 +584,16 @@ Error EditorExportPlatformIOS::_export_icons(const Ref<EditorExportPreset> &p_pr json_description += String("}"); } json_description += "]}"; - memdelete(da); - FileAccess *json_file = FileAccess::open(p_iconset_dir + "Contents.json", FileAccess::WRITE); - ERR_FAIL_COND_V(!json_file, ERR_CANT_CREATE); + Ref<FileAccess> json_file = FileAccess::open(p_iconset_dir + "Contents.json", FileAccess::WRITE); + ERR_FAIL_COND_V(json_file.is_null(), ERR_CANT_CREATE); CharString json_utf8 = json_description.utf8(); json_file->store_buffer((const uint8_t *)json_utf8.get_data(), json_utf8.length()); - memdelete(json_file); - FileAccess *sizes_file = FileAccess::open(p_iconset_dir + "sizes", FileAccess::WRITE); - ERR_FAIL_COND_V(!sizes_file, ERR_CANT_CREATE); + Ref<FileAccess> sizes_file = FileAccess::open(p_iconset_dir + "sizes", FileAccess::WRITE); + ERR_FAIL_COND_V(sizes_file.is_null(), ERR_CANT_CREATE); CharString sizes_utf8 = sizes.utf8(); sizes_file->store_buffer((const uint8_t *)sizes_utf8.get_data(), sizes_utf8.length()); - memdelete(sizes_file); return OK; } @@ -646,14 +667,19 @@ Error EditorExportPlatformIOS::_export_loading_screen_file(const Ref<EditorExpor } Error EditorExportPlatformIOS::_export_loading_screen_images(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir) { - DirAccess *da = DirAccess::open(p_dest_dir); - ERR_FAIL_COND_V_MSG(!da, ERR_CANT_OPEN, "Cannot open directory '" + p_dest_dir + "'."); + Ref<DirAccess> da = DirAccess::open(p_dest_dir); + ERR_FAIL_COND_V_MSG(da.is_null(), ERR_CANT_OPEN, "Cannot open directory '" + p_dest_dir + "'."); for (uint64_t i = 0; i < sizeof(loading_screen_infos) / sizeof(loading_screen_infos[0]); ++i) { LoadingScreenInfo info = loading_screen_infos[i]; String loading_screen_file = p_preset->get(info.preset_key); + + Color boot_bg_color = ProjectSettings::get_singleton()->get("application/boot_splash/bg_color"); + String boot_logo_path = ProjectSettings::get_singleton()->get("application/boot_splash/image"); + bool boot_logo_scale = ProjectSettings::get_singleton()->get("application/boot_splash/fullsize"); + if (loading_screen_file.size() > 0) { - // Load custom loading screens + // Load custom loading screens, and resize if required. Ref<Image> img = memnew(Image); Error err = ImageLoader::load_image(loading_screen_file, img); if (err != OK) { @@ -661,22 +687,30 @@ Error EditorExportPlatformIOS::_export_loading_screen_images(const Ref<EditorExp return ERR_UNCONFIGURED; } if (img->get_width() != info.width || img->get_height() != info.height) { - ERR_PRINT("Invalid loading screen size (" + String(info.preset_key) + "): '" + loading_screen_file + "'."); - return ERR_UNCONFIGURED; + WARN_PRINT("Loading screen (" + String(info.preset_key) + "): '" + loading_screen_file + "' has incorrect size (" + String::num_int64(img->get_width()) + "x" + String::num_int64(img->get_height()) + ") and was automatically resized to " + String::num_int64(info.width) + "x" + String::num_int64(info.height) + "."); + float aspect_ratio = (float)img->get_width() / (float)img->get_height(); + if (boot_logo_scale) { + if (info.height * aspect_ratio <= info.width) { + img->resize(info.height * aspect_ratio, info.height); + } else { + img->resize(info.width, info.width / aspect_ratio); + } + } + Ref<Image> new_img = memnew(Image); + new_img->create(info.width, info.height, false, Image::FORMAT_RGBA8); + new_img->fill(boot_bg_color); + _blend_and_rotate(new_img, img, false); + err = new_img->save_png(p_dest_dir + info.export_name); + } else { + err = da->copy(loading_screen_file, p_dest_dir + info.export_name); } - err = da->copy(loading_screen_file, p_dest_dir + info.export_name); if (err) { - memdelete(da); String err_str = String("Failed to export loading screen (") + info.preset_key + ") from path '" + loading_screen_file + "'."; ERR_PRINT(err_str.utf8().get_data()); return err; } - } else if ((bool)p_preset->get("launch_screens/generate_missing")) { + } else { // Generate loading screen from the splash screen - Color boot_bg_color = ProjectSettings::get_singleton()->get("application/boot_splash/bg_color"); - String boot_logo_path = ProjectSettings::get_singleton()->get("application/boot_splash/image"); - bool boot_logo_scale = ProjectSettings::get_singleton()->get("application/boot_splash/fullsize"); - Ref<Image> img = memnew(Image); img->create(info.width, info.height, false, Image::FORMAT_RGBA8); img->fill(boot_bg_color); @@ -716,17 +750,13 @@ Error EditorExportPlatformIOS::_export_loading_screen_images(const Ref<EditorExp String err_str = String("Failed to export loading screen (") + info.preset_key + ") from splash screen."; WARN_PRINT(err_str.utf8().get_data()); } - } else { - String err_str = String("No loading screen (") + info.preset_key + ") specified."; - WARN_PRINT(err_str.utf8().get_data()); } } - memdelete(da); return OK; } -Error EditorExportPlatformIOS::_walk_dir_recursive(DirAccess *p_da, FileHandler p_handler, void *p_userdata) { +Error EditorExportPlatformIOS::_walk_dir_recursive(Ref<DirAccess> &p_da, FileHandler p_handler, void *p_userdata) { Vector<String> dirs; String current_dir = p_da->get_current_dir(); p_da->list_dir_begin(); @@ -772,12 +802,20 @@ struct CodesignData { Error EditorExportPlatformIOS::_codesign(String p_file, void *p_userdata) { if (p_file.ends_with(".dylib")) { - CodesignData *data = (CodesignData *)p_userdata; + CodesignData *data = static_cast<CodesignData *>(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<String> codesign_args; codesign_args.push_back("-f"); codesign_args.push_back("-s"); - codesign_args.push_back(data->preset->get(data->debug ? "application/code_sign_identity_debug" : "application/code_sign_identity_release")); + codesign_args.push_back(sign_id); codesign_args.push_back(p_file); return OS::get_singleton()->execute("codesign", codesign_args); } @@ -919,21 +957,15 @@ void EditorExportPlatformIOS::_add_assets_to_project(const Ref<EditorExportPrese } Error EditorExportPlatformIOS::_copy_asset(const String &p_out_dir, const String &p_asset, const String *p_custom_file_name, bool p_is_framework, bool p_should_embed, Vector<IOSExportAsset> &r_exported_assets) { - DirAccess *filesystem_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - ERR_FAIL_COND_V_MSG(!filesystem_da, ERR_CANT_CREATE, "Cannot create DirAccess for path '" + p_out_dir + "'."); - String binary_name = p_out_dir.get_file().get_basename(); - DirAccess *da = DirAccess::create_for_path(p_asset); - if (!da) { - memdelete(filesystem_da); + Ref<DirAccess> da = DirAccess::create_for_path(p_asset); + if (da.is_null()) { ERR_FAIL_V_MSG(ERR_CANT_CREATE, "Can't create directory: " + p_asset + "."); } bool file_exists = da->file_exists(p_asset); bool dir_exists = da->dir_exists(p_asset); if (!file_exists && !dir_exists) { - memdelete(da); - memdelete(filesystem_da); return ERR_FILE_NOT_FOUND; } @@ -993,19 +1025,18 @@ Error EditorExportPlatformIOS::_copy_asset(const String &p_out_dir, const String destination = p_out_dir.plus_file(asset_path); } + Ref<DirAccess> 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 (!filesystem_da->dir_exists(destination_dir)) { Error make_dir_err = filesystem_da->make_dir_recursive(destination_dir); if (make_dir_err) { - memdelete(da); - memdelete(filesystem_da); return make_dir_err; } } Error err = dir_exists ? da->copy_dir(p_asset, destination) : da->copy(p_asset, destination); - memdelete(da); if (err) { - memdelete(filesystem_da); return err; } IOSExportAsset exported_asset = { binary_name.plus_file(asset_path), p_is_framework, p_should_embed }; @@ -1061,17 +1092,13 @@ Error EditorExportPlatformIOS::_copy_asset(const String &p_out_dir, const String String info_plist = info_plist_format.replace("$name", file_name); - FileAccess *f = FileAccess::open(destination_dir.plus_file("Info.plist"), FileAccess::WRITE); - if (f) { + Ref<FileAccess> f = FileAccess::open(destination_dir.plus_file("Info.plist"), FileAccess::WRITE); + if (f.is_valid()) { f->store_string(info_plist); - f->close(); - memdelete(f); } } } - memdelete(filesystem_da); - return OK; } @@ -1151,7 +1178,7 @@ Error EditorExportPlatformIOS::_export_ios_plugins(const Ref<EditorExportPreset> Vector<String> added_embedded_dependenciy_names; HashMap<String, String> plist_values; - Set<String> plugin_linker_flags; + HashSet<String> plugin_linker_flags; Error err; @@ -1228,11 +1255,10 @@ Error EditorExportPlatformIOS::_export_ios_plugins(const Ref<EditorExportPreset> // Plist // Using hash map container to remove duplicates - const String *K = nullptr; - while ((K = plugin.plist.next(K))) { - String key = *K; - PluginConfigIOS::PlistItem item = plugin.plist[key]; + for (const KeyValue<String, PluginConfigIOS::PlistItem> &E : plugin.plist) { + String key = E.key; + const PluginConfigIOS::PlistItem &item = E.value; String value; @@ -1271,10 +1297,9 @@ Error EditorExportPlatformIOS::_export_ios_plugins(const Ref<EditorExportPreset> // Updating `Info.plist` { - const String *K = nullptr; - while ((K = plist_values.next(K))) { - String key = *K; - String value = plist_values[key]; + for (const KeyValue<String, String> &E : plist_values) { + String key = E.key; + String value = E.value; if (key.is_empty() || value.is_empty()) { continue; @@ -1325,8 +1350,8 @@ Error EditorExportPlatformIOS::_export_ios_plugins(const Ref<EditorExportPreset> // Update Linker Flag Values { String result_linker_flags = " "; - for (Set<String>::Element *E = plugin_linker_flags.front(); E; E = E->next()) { - const String &flag = E->get(); + for (const String &E : plugin_linker_flags) { + const String &flag = E; if (flag.length() == 0) { continue; @@ -1376,29 +1401,29 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p return ERR_FILE_BAD_PATH; } - DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - if (da) { - String current_dir = da->get_current_dir(); - - // remove leftovers from last export so they don't interfere - // in case some files are no longer needed - if (da->change_dir(dest_dir + binary_name + ".xcodeproj") == OK) { - da->erase_contents_recursive(); - } - if (da->change_dir(dest_dir + binary_name) == OK) { - da->erase_contents_recursive(); - } + { + Ref<DirAccess> 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(dest_dir + binary_name + ".xcodeproj") == OK) { + da->erase_contents_recursive(); + } + if (da->change_dir(dest_dir + binary_name) == OK) { + da->erase_contents_recursive(); + } - da->change_dir(current_dir); + da->change_dir(current_dir); - if (!da->dir_exists(dest_dir + binary_name)) { - Error err = da->make_dir(dest_dir + binary_name); - if (err) { - memdelete(da); - return err; + if (!da->dir_exists(dest_dir + binary_name)) { + Error err = da->make_dir(dest_dir + binary_name); + if (err) { + return err; + } } } - memdelete(da); } if (ep.step("Making .pck", 0)) { @@ -1406,7 +1431,7 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p } String pack_path = dest_dir + binary_name + ".pck"; Vector<SharedObject> libraries; - Error err = save_pack(p_preset, pack_path, &libraries); + Error err = save_pack(p_preset, p_debug, pack_path, &libraries); if (err) { return err; } @@ -1419,9 +1444,7 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p print_line("Static framework: " + library_to_use); String pkg_name; - if (p_preset->get("application/name") != "") { - pkg_name = p_preset->get("application/name"); // app_name - } else if (String(ProjectSettings::get_singleton()->get("application/config/name")) != "") { + if (String(ProjectSettings::get_singleton()->get("application/config/name")) != "") { pkg_name = String(ProjectSettings::get_singleton()->get("application/config/name")); } else { pkg_name = "Unnamed"; @@ -1430,7 +1453,7 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p bool found_library = false; const String project_file = "godot_ios.xcodeproj/project.pbxproj"; - Set<String> files_to_parse; + HashSet<String> 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"); @@ -1456,12 +1479,12 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p Vector<IOSExportAsset> assets; - DirAccess *tmp_app_path = DirAccess::create_for_path(dest_dir); - ERR_FAIL_COND_V(!tmp_app_path, ERR_CANT_CREATE); + Ref<DirAccess> tmp_app_path = DirAccess::create_for_path(dest_dir); + ERR_FAIL_COND_V(tmp_app_path.is_null(), ERR_CANT_CREATE); print_line("Unzipping..."); - FileAccess *src_f = nullptr; - zlib_filefunc_def io = zipio_create_io_from_file(&src_f); + Ref<FileAccess> 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) { EditorNode::add_io_error("Could not open export template (not a zip file?):\n" + src_pkg_name); @@ -1483,8 +1506,11 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p 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 = fname; + String file = String::utf8(fname); print_line("READ: " + file); Vector<uint8_t> data; @@ -1535,22 +1561,20 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p if (dir_err) { ERR_PRINT("Can't create '" + dir_name + "'."); unzClose(src_pkg_zip); - memdelete(tmp_app_path); return ERR_CANT_CREATE; } } /* write the file */ - FileAccess *f = FileAccess::open(file, FileAccess::WRITE); - if (!f) { - ERR_PRINT("Can't write '" + file + "'."); - unzClose(src_pkg_zip); - memdelete(tmp_app_path); - return ERR_CANT_CREATE; - }; - f->store_buffer(data.ptr(), data.size()); - f->close(); - memdelete(f); + { + Ref<FileAccess> f = FileAccess::open(file, FileAccess::WRITE); + if (f.is_null()) { + ERR_PRINT("Can't write '" + file + "'."); + unzClose(src_pkg_zip); + return ERR_CANT_CREATE; + }; + f->store_buffer(data.ptr(), data.size()); + } #if defined(OSX_ENABLED) || defined(X11_ENABLED) if (is_execute) { @@ -1568,10 +1592,53 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p if (!found_library) { ERR_PRINT("Requested template library '" + library_to_use + "' not found. It might be missing from your template archive."); - memdelete(tmp_app_path); return ERR_FILE_NOT_FOUND; } + Dictionary appnames = ProjectSettings::get_singleton()->get("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<String> translations = ProjectSettings::get_singleton()->get("internationalization/locale/translations"); + if (translations.size() > 0) { + { + String fname = dest_dir + binary_name + "/en.lproj"; + tmp_app_path->make_dir_recursive(fname); + Ref<FileAccess> f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE); + f->store_line("/* Localized versions of Info.plist keys */"); + f->store_line(""); + f->store_line("CFBundleDisplayName = \"" + ProjectSettings::get_singleton()->get("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() + "\";"); + } + + for (const String &E : translations) { + Ref<Translation> tr = ResourceLoader::load(E); + if (tr.is_valid()) { + String lang = tr->get_locale(); + String fname = dest_dir + binary_name + "/" + lang + ".lproj"; + tmp_app_path->make_dir_recursive(fname); + Ref<FileAccess> 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<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins(); for (int i = 0; i < export_plugins.size(); i++) { @@ -1582,7 +1649,6 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p Error lib_copy_err = tmp_app_path->copy(static_lib_path, dest_lib_file_path); if (lib_copy_err != OK) { ERR_PRINT("Can't copy '" + static_lib_path + "'."); - memdelete(tmp_app_path); return lib_copy_err; } } @@ -1593,7 +1659,6 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p if (!tmp_app_path->dir_exists(iconset_dir)) { err = tmp_app_path->make_dir_recursive(iconset_dir); } - memdelete(tmp_app_path); if (err) { return err; } @@ -1603,43 +1668,42 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p return err; } - bool use_storyboard = p_preset->get("storyboard/use_launch_screen_storyboard"); + { + bool use_storyboard = p_preset->get("storyboard/use_launch_screen_storyboard"); - String launch_image_path = dest_dir + binary_name + "/Images.xcassets/LaunchImage.launchimage/"; - String splash_image_path = dest_dir + binary_name + "/Images.xcassets/SplashImage.imageset/"; + String launch_image_path = dest_dir + binary_name + "/Images.xcassets/LaunchImage.launchimage/"; + String splash_image_path = dest_dir + binary_name + "/Images.xcassets/SplashImage.imageset/"; - DirAccess *launch_screen_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + Ref<DirAccess> launch_screen_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + if (launch_screen_da.is_null()) { + return ERR_CANT_CREATE; + } - if (!launch_screen_da) { - return ERR_CANT_CREATE; - } + if (use_storyboard) { + print_line("Using Launch Storyboard"); - if (use_storyboard) { - print_line("Using Launch Storyboard"); + if (launch_screen_da->change_dir(launch_image_path) == OK) { + launch_screen_da->erase_contents_recursive(); + launch_screen_da->remove(launch_image_path); + } - if (launch_screen_da->change_dir(launch_image_path) == OK) { - launch_screen_da->erase_contents_recursive(); - launch_screen_da->remove(launch_image_path); - } + err = _export_loading_screen_file(p_preset, splash_image_path); + } else { + print_line("Using Launch Images"); - err = _export_loading_screen_file(p_preset, splash_image_path); - } else { - print_line("Using Launch Images"); + const String launch_screen_path = dest_dir + binary_name + "/Launch Screen.storyboard"; - const String launch_screen_path = dest_dir + binary_name + "/Launch Screen.storyboard"; + launch_screen_da->remove(launch_screen_path); - launch_screen_da->remove(launch_screen_path); + if (launch_screen_da->change_dir(splash_image_path) == OK) { + launch_screen_da->erase_contents_recursive(); + launch_screen_da->remove(splash_image_path); + } - if (launch_screen_da->change_dir(splash_image_path) == OK) { - launch_screen_da->erase_contents_recursive(); - launch_screen_da->remove(splash_image_path); + err = _export_loading_screen_images(p_preset, launch_image_path); } - - err = _export_loading_screen_images(p_preset, launch_image_path); } - memdelete(launch_screen_da); - if (err) { return err; } @@ -1648,25 +1712,26 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p _export_additional_assets(dest_dir + binary_name, libraries, assets); _add_assets_to_project(p_preset, project_file_data, assets); String project_file_name = dest_dir + binary_name + ".xcodeproj/project.pbxproj"; - FileAccess *f = FileAccess::open(project_file_name, FileAccess::WRITE); - if (!f) { - ERR_PRINT("Can't write '" + project_file_name + "'."); - return ERR_CANT_CREATE; - }; - f->store_buffer(project_file_data.ptr(), project_file_data.size()); - f->close(); - memdelete(f); + { + Ref<FileAccess> f = FileAccess::open(project_file_name, FileAccess::WRITE); + if (f.is_null()) { + ERR_PRINT("Can't write '" + project_file_name + "'."); + return ERR_CANT_CREATE; + }; + f->store_buffer(project_file_data.ptr(), project_file_data.size()); + } #ifdef OSX_ENABLED - if (ep.step("Code-signing dylibs", 2)) { - return ERR_SKIP; + { + if (ep.step("Code-signing dylibs", 2)) { + return ERR_SKIP; + } + Ref<DirAccess> dylibs_dir = DirAccess::open(dest_dir + binary_name + "/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); + ERR_FAIL_COND_V(err, err); } - DirAccess *dylibs_dir = DirAccess::open(dest_dir + binary_name + "/dylibs"); - ERR_FAIL_COND_V(!dylibs_dir, ERR_CANT_OPEN); - CodesignData codesign_data(p_preset, p_debug); - err = _walk_dir_recursive(dylibs_dir, _codesign, &codesign_data); - memdelete(dylibs_dir); - ERR_FAIL_COND_V(err, err); if (ep.step("Making .xcarchive", 3)) { return ERR_SKIP; @@ -1684,6 +1749,7 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p 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; @@ -1754,19 +1820,7 @@ bool EditorExportPlatformIOS::can_export(const Ref<EditorExportPreset> &p_preset valid = false; } - for (uint64_t i = 0; i < (sizeof(icon_infos) / sizeof(icon_infos[0])); ++i) { - IconInfo info = icon_infos[i]; - String icon_path = p_preset->get(info.preset_key); - if (icon_path.length() == 0) { - if (info.is_required) { - err += TTR("Required icon is not specified in the preset.") + "\n"; - valid = false; - } - break; - } - } - - String etc_error = test_etc2_or_pvrtc(); + const String etc_error = test_etc2(); if (!etc_error.is_empty()) { valid = false; err += etc_error; diff --git a/platform/iphone/export/export_plugin.h b/platform/iphone/export/export_plugin.h index 359f855d86..1db7418e3f 100644 --- a/platform/iphone/export/export_plugin.h +++ b/platform/iphone/export/export_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -41,7 +41,6 @@ #include "core/templates/safe_refcount.h" #include "core/version.h" #include "editor/editor_export.h" -#include "editor/editor_node.h" #include "editor/editor_settings.h" #include "main/splash.gen.h" #include "platform/iphone/logo.gen.h" @@ -54,8 +53,6 @@ class EditorExportPlatformIOS : public EditorExportPlatform { GDCLASS(EditorExportPlatformIOS, EditorExportPlatform); - int version_code; - Ref<ImageTexture> logo; // Plugins @@ -66,7 +63,7 @@ class EditorExportPlatformIOS : public EditorExportPlatform { Vector<PluginConfigIOS> plugins; typedef Error (*FileHandler)(String p_file, void *p_userdata); - static Error _walk_dir_recursive(DirAccess *p_da, FileHandler p_handler, void *p_userdata); + static Error _walk_dir_recursive(Ref<DirAccess> &p_da, FileHandler p_handler, void *p_userdata); static Error _codesign(String p_file, void *p_userdata); void _blend_and_rotate(Ref<Image> &p_dst, Ref<Image> &p_src, bool p_rot); @@ -130,7 +127,7 @@ class EditorExportPlatformIOS : public EditorExportPlatform { for (int i = 0; i < pname.length(); i++) { char32_t c = pname[i]; - if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '.')) { + if (!(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)); } @@ -142,7 +139,7 @@ class EditorExportPlatformIOS : public EditorExportPlatform { } static void _check_for_changes_poll_thread(void *ud) { - EditorExportPlatformIOS *ea = (EditorExportPlatformIOS *)ud; + EditorExportPlatformIOS *ea = static_cast<EditorExportPlatformIOS *>(ud); while (!ea->quit_request.is_set()) { // Nothing to do if we already know the plugins have changed. @@ -207,7 +204,7 @@ public: r_features->push_back("ios"); } - virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) override { + virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, HashSet<String> &p_features) override { } EditorExportPlatformIOS(); @@ -216,8 +213,8 @@ public: /// List the gdip files in the directory specified by the p_path parameter. static Vector<String> list_plugin_config_files(const String &p_path, bool p_check_directories) { Vector<String> dir_files; - DirAccessRef da = DirAccess::open(p_path); - if (da) { + Ref<DirAccess> da = DirAccess::open(p_path); + if (da.is_valid()) { da->list_dir_begin(); while (true) { String file = da->get_next(); diff --git a/platform/iphone/export/godot_plugin_config.cpp b/platform/iphone/export/godot_plugin_config.cpp index 9d0324f41a..9118b95337 100644 --- a/platform/iphone/export/godot_plugin_config.cpp +++ b/platform/iphone/export/godot_plugin_config.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/iphone/export/godot_plugin_config.h b/platform/iphone/export/godot_plugin_config.h index 1add281627..9ef8aa8c6d 100644 --- a/platform/iphone/export/godot_plugin_config.h +++ b/platform/iphone/export/godot_plugin_config.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/iphone/godot_app_delegate.h b/platform/iphone/godot_app_delegate.h index 6335ada50e..703a906bda 100644 --- a/platform/iphone/godot_app_delegate.h +++ b/platform/iphone/godot_app_delegate.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/iphone/godot_app_delegate.m b/platform/iphone/godot_app_delegate.m index 6c433c5c3c..84347f9a30 100644 --- a/platform/iphone/godot_app_delegate.m +++ b/platform/iphone/godot_app_delegate.m @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/iphone/godot_iphone.mm b/platform/iphone/godot_iphone.mm index 6f6f9d0708..49474ef554 100644 --- a/platform/iphone/godot_iphone.mm +++ b/platform/iphone/godot_iphone.mm @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -53,7 +53,7 @@ int add_path(int p_argc, char **p_args) { p_args[p_argc] = nullptr; return p_argc; -}; +} int add_cmdline(int p_argc, char **p_args) { NSArray *arr = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_cmdline"]; @@ -67,12 +67,12 @@ int add_cmdline(int p_argc, char **p_args) { continue; } p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding]; - }; + } p_args[p_argc] = nullptr; return p_argc; -}; +} int iphone_main(int argc, char **argv, String data_dir, String cache_dir) { size_t len = strlen(argv[0]); @@ -103,7 +103,7 @@ int iphone_main(int argc, char **argv, String data_dir, String cache_dir) { 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); @@ -119,10 +119,10 @@ int iphone_main(int argc, char **argv, String data_dir, String cache_dir) { os->initialize_modules(); return 0; -}; +} void iphone_finish() { printf("iphone_finish\n"); Main::cleanup(); delete os; -}; +} diff --git a/platform/iphone/godot_view.h b/platform/iphone/godot_view.h index 265f826173..fcb97fa63a 100644 --- a/platform/iphone/godot_view.h +++ b/platform/iphone/godot_view.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -59,4 +59,9 @@ class String; - (void)stopRendering; - (void)startRendering; +- (void)godotTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event; +- (void)godotTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event; +- (void)godotTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event; +- (void)godotTouchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event; + @end diff --git a/platform/iphone/godot_view.mm b/platform/iphone/godot_view.mm index 00a88d79c5..e48dd2e507 100644 --- a/platform/iphone/godot_view.mm +++ b/platform/iphone/godot_view.mm @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -29,6 +29,7 @@ /*************************************************************************/ #import "godot_view.h" + #include "core/os/keyboard.h" #include "core/string/ustring.h" #import "display_layer.h" @@ -153,6 +154,8 @@ static const float earth_gravity = 9.80665; [self initTouches]; + self.multipleTouchEnabled = YES; + // Configure and start accelerometer if (!self.motionManager) { self.motionManager = [[CMMotionManager alloc] init]; @@ -226,8 +229,9 @@ static const float earth_gravity = 9.80665; [self.displayLink setPaused:YES]; // Process all input events - while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, TRUE) == kCFRunLoopRunHandledSource) - ; + while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, TRUE) == kCFRunLoopRunHandledSource) { + // Continue. + } // We are good to go, resume the CADisplayLink [self.displayLink setPaused:NO]; @@ -336,7 +340,7 @@ static const float earth_gravity = 9.80665; } } -- (void)touchesBegan:(NSSet *)touchesSet withEvent:(UIEvent *)event { +- (void)godotTouchesBegan:(NSSet *)touchesSet withEvent:(UIEvent *)event { NSArray *tlist = [event.allTouches allObjects]; for (unsigned int i = 0; i < [tlist count]; i++) { if ([touchesSet containsObject:[tlist objectAtIndex:i]]) { @@ -349,7 +353,7 @@ static const float earth_gravity = 9.80665; } } -- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { +- (void)godotTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { NSArray *tlist = [event.allTouches allObjects]; for (unsigned int i = 0; i < [tlist count]; i++) { if ([touches containsObject:[tlist objectAtIndex:i]]) { @@ -363,7 +367,7 @@ static const float earth_gravity = 9.80665; } } -- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { +- (void)godotTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { NSArray *tlist = [event.allTouches allObjects]; for (unsigned int i = 0; i < [tlist count]; i++) { if ([touches containsObject:[tlist objectAtIndex:i]]) { @@ -377,7 +381,7 @@ static const float earth_gravity = 9.80665; } } -- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { +- (void)godotTouchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { NSArray *tlist = [event.allTouches allObjects]; for (unsigned int i = 0; i < [tlist count]; i++) { if ([touches containsObject:[tlist objectAtIndex:i]]) { diff --git a/platform/iphone/godot_view_gesture_recognizer.h b/platform/iphone/godot_view_gesture_recognizer.h index 61438ef22f..9fd8a6b222 100644 --- a/platform/iphone/godot_view_gesture_recognizer.h +++ b/platform/iphone/godot_view_gesture_recognizer.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/iphone/godot_view_gesture_recognizer.mm b/platform/iphone/godot_view_gesture_recognizer.mm index cf6e5c5883..49a92add5e 100644 --- a/platform/iphone/godot_view_gesture_recognizer.mm +++ b/platform/iphone/godot_view_gesture_recognizer.mm @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -30,6 +30,8 @@ #import "godot_view_gesture_recognizer.h" +#import "godot_view.h" + #include "core/config/project_settings.h" // Minimum distance for touches to move to fire @@ -58,12 +60,17 @@ const CGFloat kGLGestureMovementDistance = 0.5; @implementation GodotViewGestureRecognizer +- (GodotView *)godotView { + return (GodotView *)self.view; +} + - (instancetype)init { self = [super init]; self.cancelsTouchesInView = YES; self.delaysTouchesBegan = YES; self.delaysTouchesEnded = YES; + self.requiresExclusiveTouchType = NO; self.delayTimeInterval = GLOBAL_GET("input_devices/pointing/ios/touch_delay"); @@ -104,7 +111,7 @@ const CGFloat kGLGestureMovementDistance = 0.5; self.delayTimer = nil; if (self.delayedTouches) { - [self.view touchesBegan:self.delayedTouches withEvent:self.delayedEvent]; + [self.godotView godotTouchesBegan:self.delayedTouches withEvent:self.delayedEvent]; } self.delayedTouches = nil; @@ -114,6 +121,8 @@ const CGFloat kGLGestureMovementDistance = 0.5; - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseBegan]; [self delayTouches:cleared andEvent:event]; + + [super touchesBegan:touches withEvent:event]; } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { @@ -123,8 +132,8 @@ const CGFloat kGLGestureMovementDistance = 0.5; // We should check if movement was significant enough to fire an event // for dragging to work correctly. for (UITouch *touch in cleared) { - CGPoint from = [touch locationInView:self.view]; - CGPoint to = [touch previousLocationInView:self.view]; + CGPoint from = [touch locationInView:self.godotView]; + CGPoint to = [touch previousLocationInView:self.godotView]; CGFloat xDistance = from.x - to.x; CGFloat yDistance = from.y - to.y; @@ -133,7 +142,7 @@ const CGFloat kGLGestureMovementDistance = 0.5; // Early exit, since one of touches has moved enough to fire a drag event. if (distance > kGLGestureMovementDistance) { [self.delayTimer fire]; - [self.view touchesMoved:cleared withEvent:event]; + [self.godotView godotTouchesMoved:cleared withEvent:event]; return; } } @@ -141,26 +150,32 @@ const CGFloat kGLGestureMovementDistance = 0.5; return; } - [self.view touchesMoved:cleared withEvent:event]; + [self.godotView godotTouchesMoved:cleared withEvent:event]; + + [super touchesMoved:touches withEvent:event]; } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { [self.delayTimer fire]; NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseEnded]; - [self.view touchesEnded:cleared withEvent:event]; + [self.godotView godotTouchesEnded:cleared withEvent:event]; + + [super touchesEnded:touches withEvent:event]; } - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { [self.delayTimer fire]; - [self.view touchesCancelled:touches withEvent:event]; -}; + [self.godotView godotTouchesCancelled:touches withEvent:event]; + + [super touchesCancelled:touches withEvent:event]; +} - (NSSet *)copyClearedTouches:(NSSet *)touches phase:(UITouchPhase)phaseToSave { NSMutableSet *cleared = [touches mutableCopy]; for (UITouch *touch in touches) { - if (touch.phase != phaseToSave) { + if (touch.view != self.view || touch.phase != phaseToSave) { [cleared removeObject:touch]; } } diff --git a/platform/iphone/godot_view_renderer.h b/platform/iphone/godot_view_renderer.h index c6b0a05a4e..b3ee23ae4f 100644 --- a/platform/iphone/godot_view_renderer.h +++ b/platform/iphone/godot_view_renderer.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/iphone/godot_view_renderer.mm b/platform/iphone/godot_view_renderer.mm index 07d664715a..32477ae41c 100644 --- a/platform/iphone/godot_view_renderer.mm +++ b/platform/iphone/godot_view_renderer.mm @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -29,6 +29,7 @@ /*************************************************************************/ #import "godot_view_renderer.h" + #include "core/config/project_settings.h" #include "core/os/keyboard.h" #import "display_server_iphone.h" @@ -101,7 +102,7 @@ double dval = [n doubleValue]; ProjectSettings::get_singleton()->set("Info.plist/" + ukey, dval); - }; + } // do stuff } } diff --git a/platform/iphone/ios.h b/platform/iphone/ios.h index 4bf64d4bc1..0607d7b395 100644 --- a/platform/iphone/ios.h +++ b/platform/iphone/ios.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -32,15 +32,26 @@ #define IOS_H #include "core/object/class_db.h" +#import <CoreHaptics/CoreHaptics.h> 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); + String get_model() const; String get_rate_url(int p_app_id) const; diff --git a/platform/iphone/ios.mm b/platform/iphone/ios.mm index 3430a9cba7..79baae028a 100644 --- a/platform/iphone/ios.mm +++ b/platform/iphone/ios.mm @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -29,15 +29,115 @@ /*************************************************************************/ #include "ios.h" + #import "app_delegate.h" #import "view_controller.h" + +#import <CoreHaptics/CoreHaptics.h> #import <UIKit/UIKit.h> #include <sys/sysctl.h> 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); }; +bool iOS::supports_haptic_engine() { + if (@available(iOS 13, *)) { + id<CHHapticDeviceCapability> capabilities = [CHHapticEngine capabilitiesForHardware]; + return capabilities.supportsHaptics; + } + + return false; +} + +CHHapticEngine *iOS::get_haptic_engine_instance() API_AVAILABLE(ios(13)) { + if (haptic_engine == nullptr) { + NSError *error = nullptr; + haptic_engine = [[CHHapticEngine alloc] initAndReturnError:&error]; + + if (!error) { + [haptic_engine setAutoShutdownEnabled:true]; + } else { + haptic_engine = nullptr; + NSLog(@"Could not initialize haptic engine: %@", error); + } + } + + return haptic_engine; +} + +void iOS::vibrate_haptic_engine(float p_duration_seconds) 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 *haptic_engine = get_haptic_engine_instance(); + if (haptic_engine) { + NSDictionary *hapticDict = @{ + CHHapticPatternKeyPattern : @[ + @{CHHapticPatternKeyEvent : @{ + CHHapticPatternKeyEventType : CHHapticEventTypeHapticContinuous, + CHHapticPatternKeyTime : @(CHHapticTimeImmediate), + CHHapticPatternKeyEventDuration : @(p_duration_seconds) + }, + }, + ], + }; + + NSError *error; + CHHapticPattern *pattern = [[CHHapticPattern alloc] initWithDictionary:hapticDict error:&error]; + + [[haptic_engine createPlayerWithPattern:pattern error:&error] startAtTime:0 error:&error]; + + NSLog(@"Could not vibrate using haptic engine: %@", error); + } + + return; + } + } + + NSLog(@"Haptic engine is not supported in this version of iOS"); +} + +void iOS::start_haptic_engine() { + if (@available(iOS 13, *)) { + if (supports_haptic_engine()) { + CHHapticEngine *haptic_engine = get_haptic_engine_instance(); + if (haptic_engine) { + [haptic_engine startWithCompletionHandler:^(NSError *returnedError) { + if (returnedError) { + NSLog(@"Could not start haptic engine: %@", returnedError); + } + }]; + } + + return; + } + } + + NSLog(@"Haptic engine is not supported in this version of iOS"); +} + +void iOS::stop_haptic_engine() { + if (@available(iOS 13, *)) { + if (supports_haptic_engine()) { + CHHapticEngine *haptic_engine = get_haptic_engine_instance(); + if (haptic_engine) { + [haptic_engine stopWithCompletionHandler:^(NSError *returnedError) { + if (returnedError) { + NSLog(@"Could not stop haptic engine: %@", returnedError); + } + }]; + } + + return; + } + } + + NSLog(@"Haptic engine is not supported in this version of iOS"); +} + void iOS::alert(const char *p_alert, const char *p_title) { NSString *title = [NSString stringWithUTF8String:p_title]; NSString *message = [NSString stringWithUTF8String:p_alert]; @@ -65,7 +165,7 @@ String iOS::get_model() const { NSString *platform = [NSString stringWithCString:model encoding:NSUTF8StringEncoding]; free(model); const char *str = [platform UTF8String]; - return String(str != nullptr ? str : ""); + return String::utf8(str != nullptr ? str : ""); } String iOS::get_rate_url(int p_app_id) const { @@ -75,6 +175,6 @@ String iOS::get_rate_url(int p_app_id) const { printf("returning rate url %s\n", ret.utf8().get_data()); return ret; -}; +} iOS::iOS() {} diff --git a/platform/iphone/joypad_iphone.h b/platform/iphone/joypad_iphone.h index 329cc8207f..37e272a2c9 100644 --- a/platform/iphone/joypad_iphone.h +++ b/platform/iphone/joypad_iphone.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/iphone/joypad_iphone.mm b/platform/iphone/joypad_iphone.mm index 1bf5462d91..9c2feeaaca 100644 --- a/platform/iphone/joypad_iphone.mm +++ b/platform/iphone/joypad_iphone.mm @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -29,6 +29,7 @@ /*************************************************************************/ #import "joypad_iphone.h" + #include "core/config/project_settings.h" #include "drivers/coreaudio/audio_driver_coreaudio.h" #include "main/main.h" @@ -139,10 +140,10 @@ void JoypadIPhone::start_processing() { for (NSNumber *key in keys) { int joy_id = [key intValue]; return joy_id; - }; + } return -1; -}; +} - (void)addiOSJoypad:(GCController *)controller { // get a new id for our controller @@ -156,10 +157,10 @@ void JoypadIPhone::start_processing() { // assign our player index if (controller.playerIndex == GCControllerPlayerIndexUnset) { controller.playerIndex = [self getFreePlayerIndex]; - }; + } // tell Godot about our new controller - Input::get_singleton()->joy_connection_changed(joy_id, true, [controller.vendorName UTF8String]); + Input::get_singleton()->joy_connection_changed(joy_id, true, String::utf8([controller.vendorName UTF8String])); // add it to our dictionary, this will retain our controllers [self.connectedJoypads setObject:controller forKey:[NSNumber numberWithInt:joy_id]]; @@ -202,8 +203,8 @@ void JoypadIPhone::start_processing() { // and remove it from our dictionary [self.connectedJoypads removeObjectForKey:key]; - }; -}; + } +} - (GCControllerPlayerIndex)getFreePlayerIndex { bool have_player_1 = false; @@ -223,9 +224,9 @@ void JoypadIPhone::start_processing() { have_player_3 = true; } else if (controller.playerIndex == GCControllerPlayerIndex4) { have_player_4 = true; - }; - }; - }; + } + } + } if (!have_player_1) { return GCControllerPlayerIndex1; @@ -237,7 +238,7 @@ void JoypadIPhone::start_processing() { return GCControllerPlayerIndex4; } else { return GCControllerPlayerIndexUnset; - }; + } } - (void)setControllerInputHandler:(GCController *)controller { @@ -285,27 +286,25 @@ void JoypadIPhone::start_processing() { gamepad.dpad.left.isPressed); Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_RIGHT, gamepad.dpad.right.isPressed); - }; + } - Input::JoyAxisValue jx; - jx.min = -1; if (element == gamepad.leftThumbstick) { - jx.value = gamepad.leftThumbstick.xAxis.value; - Input::get_singleton()->joy_axis(joy_id, JoyAxis::LEFT_X, jx); - jx.value = -gamepad.leftThumbstick.yAxis.value; - Input::get_singleton()->joy_axis(joy_id, JoyAxis::LEFT_Y, jx); + float value = gamepad.leftThumbstick.xAxis.value; + Input::get_singleton()->joy_axis(joy_id, JoyAxis::LEFT_X, value); + value = -gamepad.leftThumbstick.yAxis.value; + Input::get_singleton()->joy_axis(joy_id, JoyAxis::LEFT_Y, value); } else if (element == gamepad.rightThumbstick) { - jx.value = gamepad.rightThumbstick.xAxis.value; - Input::get_singleton()->joy_axis(joy_id, JoyAxis::RIGHT_X, jx); - jx.value = -gamepad.rightThumbstick.yAxis.value; - Input::get_singleton()->joy_axis(joy_id, JoyAxis::RIGHT_Y, jx); + float value = gamepad.rightThumbstick.xAxis.value; + Input::get_singleton()->joy_axis(joy_id, JoyAxis::RIGHT_X, value); + value = -gamepad.rightThumbstick.yAxis.value; + Input::get_singleton()->joy_axis(joy_id, JoyAxis::RIGHT_Y, value); } else if (element == gamepad.leftTrigger) { - jx.value = gamepad.leftTrigger.value; - Input::get_singleton()->joy_axis(joy_id, JoyAxis::TRIGGER_LEFT, jx); + float value = gamepad.leftTrigger.value; + Input::get_singleton()->joy_axis(joy_id, JoyAxis::TRIGGER_LEFT, value); } else if (element == gamepad.rightTrigger) { - jx.value = gamepad.rightTrigger.value; - Input::get_singleton()->joy_axis(joy_id, JoyAxis::TRIGGER_RIGHT, jx); - }; + float value = gamepad.rightTrigger.value; + Input::get_singleton()->joy_axis(joy_id, JoyAxis::TRIGGER_RIGHT, value); + } }; } else if (controller.microGamepad != nil) { // micro gamepads were added in OS 9 and feature just 2 buttons and a d-pad @@ -331,7 +330,7 @@ void JoypadIPhone::start_processing() { gamepad.dpad.down.isPressed); Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_LEFT, gamepad.dpad.left.isPressed); Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_RIGHT, gamepad.dpad.right.isPressed); - }; + } }; } @@ -340,6 +339,6 @@ void JoypadIPhone::start_processing() { ///@TODO need to add support for controllerPausedHandler which should be a /// toggle -}; +} @end diff --git a/platform/iphone/keyboard_input_view.h b/platform/iphone/keyboard_input_view.h index cfbfc7a120..33fa5d571a 100644 --- a/platform/iphone/keyboard_input_view.h +++ b/platform/iphone/keyboard_input_view.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/iphone/keyboard_input_view.mm b/platform/iphone/keyboard_input_view.mm index b11d04181e..f2a7b22483 100644 --- a/platform/iphone/keyboard_input_view.mm +++ b/platform/iphone/keyboard_input_view.mm @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/iphone/main.m b/platform/iphone/main.m index d2c41d4d84..acfa7ab731 100644 --- a/platform/iphone/main.m +++ b/platform/iphone/main.m @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/iphone/os_iphone.h b/platform/iphone/os_iphone.h index 7a81d8f593..d03403bbb4 100644 --- a/platform/iphone/os_iphone.h +++ b/platform/iphone/os_iphone.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -52,11 +52,11 @@ private: AudioDriverCoreAudio audio_driver; - iOS *ios; + iOS *ios = nullptr; - JoypadIPhone *joypad_iphone; + JoypadIPhone *joypad_iphone = nullptr; - MainLoop *main_loop; + MainLoop *main_loop = nullptr; virtual void initialize_core() override; virtual void initialize() override; @@ -92,7 +92,7 @@ public: virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override; - virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false) override; + virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = 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; @@ -109,6 +109,7 @@ public: virtual String get_locale() const override; virtual String get_unique_id() const override; + virtual String get_processor_name() const override; virtual void vibrate_handheld(int p_duration_ms = 500) override; diff --git a/platform/iphone/os_iphone.mm b/platform/iphone/os_iphone.mm index fc07d321b7..95b06b728e 100644 --- a/platform/iphone/os_iphone.mm +++ b/platform/iphone/os_iphone.mm @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -31,6 +31,7 @@ #ifdef IPHONE_ENABLED #include "os_iphone.h" + #import "app_delegate.h" #include "core/config/project_settings.h" #include "core/io/dir_access.h" @@ -45,6 +46,7 @@ #import <AudioToolbox/AudioServices.h> #import <UIKit/UIKit.h> #import <dlfcn.h> +#include <sys/sysctl.h> #if defined(VULKAN_ENABLED) #include "servers/rendering/renderer_rd/renderer_compositor_rd.h" @@ -168,7 +170,7 @@ void OSIPhone::delete_main_loop() { if (main_loop) { main_loop->finalize(); memdelete(main_loop); - }; + } main_loop = nullptr; } @@ -197,17 +199,22 @@ void OSIPhone::finalize() { deinitialize_modules(); // Already gets called - // delete_main_loop(); + //delete_main_loop(); } // MARK: Dynamic Libraries -Error OSIPhone::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) { +Error OSIPhone::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path, String *r_resolved_path) { if (p_path.length() == 0) { p_library_handle = RTLD_SELF; + + if (r_resolved_path != nullptr) { + *r_resolved_path = p_path; + } + return OK; } - return OS_Unix::open_dynamic_library(p_path, p_library_handle, p_also_set_library_path); + return OS_Unix::open_dynamic_library(p_path, p_library_handle, p_also_set_library_path, r_resolved_path); } Error OSIPhone::close_dynamic_library(void *p_library_handle) { @@ -230,12 +237,13 @@ Error OSIPhone::get_dynamic_library_symbol_handle(void *p_library_handle, const String OSIPhone::get_name() const { return "iOS"; -}; +} String OSIPhone::get_model_name() const { String model = ios->get_model(); - if (model != "") + if (model != "") { return model; + } return OS_Unix::get_model_name(); } @@ -253,14 +261,12 @@ Error OSIPhone::shell_open(String p_uri) { [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil]; return OK; -}; +} void OSIPhone::set_user_data_dir(String p_dir) { - DirAccess *da = DirAccess::open(p_dir); - + Ref<DirAccess> da = DirAccess::open(p_dir); user_data_dir = da->get_current_dir(); printf("setting data dir to %s from %s\n", user_data_dir.utf8().get_data(), p_dir.utf8().get_data()); - memdelete(da); } String OSIPhone::get_user_data_dir() const { @@ -287,9 +293,22 @@ String OSIPhone::get_unique_id() const { return String::utf8([uuid UTF8String]); } +String OSIPhone::get_processor_name() const { + char buffer[256]; + size_t buffer_len = 256; + if (sysctlbyname("machdep.cpu.brand_string", &buffer, &buffer_len, NULL, 0) == 0) { + return String::utf8(buffer, buffer_len); + } + ERR_FAIL_V_MSG("", String("Couldn't get the CPU model name. Returning an empty string.")); +} + void OSIPhone::vibrate_handheld(int p_duration_ms) { - // iOS does not support duration for vibration - AudioServicesPlaySystemSound(kSystemSoundID_Vibrate); + if (ios->supports_haptic_engine()) { + ios->vibrate_haptic_engine((float)p_duration_ms / 1000.f); + } else { + // iOS <13 does not support duration for vibration + AudioServicesPlaySystemSound(kSystemSoundID_Vibrate); + } } bool OSIPhone::_check_internal_feature_support(const String &p_feature) { diff --git a/platform/iphone/platform_config.h b/platform/iphone/platform_config.h index 68ef4e31a7..fed77d8932 100644 --- a/platform/iphone/platform_config.h +++ b/platform/iphone/platform_config.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/iphone/tts_ios.h b/platform/iphone/tts_ios.h new file mode 100644 index 0000000000..064316b0b2 --- /dev/null +++ b/platform/iphone/tts_ios.h @@ -0,0 +1,63 @@ +/*************************************************************************/ +/* tts_ios.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef TTS_IOS_H +#define TTS_IOS_H + +#if __has_include(<AVFAudio/AVSpeechSynthesis.h>) +#import <AVFAudio/AVSpeechSynthesis.h> +#else +#import <AVFoundation/AVFoundation.h> +#endif + +#include "core/string/ustring.h" +#include "core/templates/list.h" +#include "core/templates/rb_map.h" +#include "core/variant/array.h" +#include "servers/display_server.h" + +@interface TTS_IOS : NSObject <AVSpeechSynthesizerDelegate> { + bool speaking; + HashMap<id, int> ids; + + AVSpeechSynthesizer *av_synth; + List<DisplayServer::TTSUtterance> queue; +} + +- (void)pauseSpeaking; +- (void)resumeSpeaking; +- (void)stopSpeaking; +- (bool)isSpeaking; +- (bool)isPaused; +- (void)speak:(const String &)text voice:(const String &)voice volume:(int)volume pitch:(float)pitch rate:(float)rate utterance_id:(int)utterance_id interrupt:(bool)interrupt; +- (Array)getVoices; +@end + +#endif // TTS_IOS_H diff --git a/platform/iphone/tts_ios.mm b/platform/iphone/tts_ios.mm new file mode 100644 index 0000000000..a079d02add --- /dev/null +++ b/platform/iphone/tts_ios.mm @@ -0,0 +1,164 @@ +/*************************************************************************/ +/* tts_ios.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "tts_ios.h" + +@implementation TTS_IOS + +- (id)init { + self = [super init]; + self->speaking = false; + self->av_synth = [[AVSpeechSynthesizer alloc] init]; + [self->av_synth setDelegate:self]; + print_verbose("Text-to-Speech: AVSpeechSynthesizer initialized."); + return self; +} + +- (void)speechSynthesizer:(AVSpeechSynthesizer *)av_synth willSpeakRangeOfSpeechString:(NSRange)characterRange utterance:(AVSpeechUtterance *)utterance { + NSString *string = [utterance speechString]; + + // Convert from UTF-16 to UTF-32 position. + int pos = 0; + for (NSUInteger i = 0; i < MIN(characterRange.location, string.length); i++) { + unichar c = [string characterAtIndex:i]; + if ((c & 0xfffffc00) == 0xd800) { + i++; + } + pos++; + } + + DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_BOUNDARY, ids[utterance], pos); +} + +- (void)speechSynthesizer:(AVSpeechSynthesizer *)av_synth didCancelSpeechUtterance:(AVSpeechUtterance *)utterance { + DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, ids[utterance]); + ids.erase(utterance); + speaking = false; + [self update]; +} + +- (void)speechSynthesizer:(AVSpeechSynthesizer *)av_synth didFinishSpeechUtterance:(AVSpeechUtterance *)utterance { + DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_ENDED, ids[utterance]); + ids.erase(utterance); + speaking = false; + [self update]; +} + +- (void)update { + if (!speaking && queue.size() > 0) { + DisplayServer::TTSUtterance &message = queue.front()->get(); + + AVSpeechUtterance *new_utterance = [[AVSpeechUtterance alloc] initWithString:[NSString stringWithUTF8String:message.text.utf8().get_data()]]; + [new_utterance setVoice:[AVSpeechSynthesisVoice voiceWithIdentifier:[NSString stringWithUTF8String:message.voice.utf8().get_data()]]]; + if (message.rate > 1.f) { + [new_utterance setRate:Math::range_lerp(message.rate, 1.f, 10.f, AVSpeechUtteranceDefaultSpeechRate, AVSpeechUtteranceMaximumSpeechRate)]; + } else if (message.rate < 1.f) { + [new_utterance setRate:Math::range_lerp(message.rate, 0.1f, 1.f, AVSpeechUtteranceMinimumSpeechRate, AVSpeechUtteranceDefaultSpeechRate)]; + } + [new_utterance setPitchMultiplier:message.pitch]; + [new_utterance setVolume:(Math::range_lerp(message.volume, 0.f, 100.f, 0.f, 1.f))]; + + ids[new_utterance] = message.id; + [av_synth speakUtterance:new_utterance]; + + queue.pop_front(); + + DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_STARTED, message.id); + speaking = true; + } +} + +- (void)pauseSpeaking { + [av_synth pauseSpeakingAtBoundary:AVSpeechBoundaryImmediate]; +} + +- (void)resumeSpeaking { + [av_synth continueSpeaking]; +} + +- (void)stopSpeaking { + for (DisplayServer::TTSUtterance &message : queue) { + DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, message.id); + } + queue.clear(); + [av_synth stopSpeakingAtBoundary:AVSpeechBoundaryImmediate]; + speaking = false; +} + +- (bool)isSpeaking { + return speaking || (queue.size() > 0); +} + +- (bool)isPaused { + return [av_synth isPaused]; +} + +- (void)speak:(const String &)text voice:(const String &)voice volume:(int)volume pitch:(float)pitch rate:(float)rate utterance_id:(int)utterance_id interrupt:(bool)interrupt { + if (interrupt) { + [self stopSpeaking]; + } + + if (text.is_empty()) { + DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, utterance_id); + return; + } + + DisplayServer::TTSUtterance message; + message.text = text; + message.voice = voice; + message.volume = CLAMP(volume, 0, 100); + message.pitch = CLAMP(pitch, 0.f, 2.f); + message.rate = CLAMP(rate, 0.1f, 10.f); + message.id = utterance_id; + queue.push_back(message); + + if ([self isPaused]) { + [self resumeSpeaking]; + } else { + [self update]; + } +} + +- (Array)getVoices { + Array list; + for (AVSpeechSynthesisVoice *voice in [AVSpeechSynthesisVoice speechVoices]) { + NSString *voiceIdentifierString = [voice identifier]; + NSString *voiceLocaleIdentifier = [voice language]; + NSString *voiceName = [voice name]; + Dictionary voice_d; + voice_d["name"] = String::utf8([voiceName UTF8String]); + voice_d["id"] = String::utf8([voiceIdentifierString UTF8String]); + voice_d["language"] = String::utf8([voiceLocaleIdentifier UTF8String]); + list.push_back(voice_d); + } + return list; +} + +@end diff --git a/platform/iphone/view_controller.h b/platform/iphone/view_controller.h index 400145b8b7..c8b37a4d11 100644 --- a/platform/iphone/view_controller.h +++ b/platform/iphone/view_controller.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/iphone/view_controller.mm b/platform/iphone/view_controller.mm index 2723ac4706..4f4ef4f046 100644 --- a/platform/iphone/view_controller.mm +++ b/platform/iphone/view_controller.mm @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -203,7 +203,7 @@ case DisplayServer::SCREEN_LANDSCAPE: return UIInterfaceOrientationMaskLandscapeLeft; } -}; +} - (BOOL)prefersStatusBarHidden { return YES; diff --git a/platform/iphone/vulkan_context_iphone.h b/platform/iphone/vulkan_context_iphone.h index ec6aaf46e8..7576525755 100644 --- a/platform/iphone/vulkan_context_iphone.h +++ b/platform/iphone/vulkan_context_iphone.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/iphone/vulkan_context_iphone.mm b/platform/iphone/vulkan_context_iphone.mm index 547cee9570..17cb0f6009 100644 --- a/platform/iphone/vulkan_context_iphone.mm +++ b/platform/iphone/vulkan_context_iphone.mm @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ |