summaryrefslogtreecommitdiff
path: root/platform/iphone
diff options
context:
space:
mode:
Diffstat (limited to 'platform/iphone')
-rw-r--r--platform/iphone/SCsub22
-rw-r--r--platform/iphone/app_delegate.h23
-rw-r--r--platform/iphone/app_delegate.mm667
-rw-r--r--platform/iphone/detect.py112
-rw-r--r--platform/iphone/device_metrics.h37
-rw-r--r--platform/iphone/device_metrics.m152
-rw-r--r--platform/iphone/display_layer.h (renamed from platform/iphone/in_app_store.h)59
-rw-r--r--platform/iphone/display_layer.mm183
-rw-r--r--platform/iphone/display_server_iphone.h202
-rw-r--r--platform/iphone/display_server_iphone.mm648
-rw-r--r--platform/iphone/export/export.cpp937
-rw-r--r--platform/iphone/export/export.h4
-rw-r--r--platform/iphone/game_center.mm387
-rw-r--r--platform/iphone/gl_view.h123
-rw-r--r--platform/iphone/gl_view.mm702
-rw-r--r--platform/iphone/godot_app_delegate.h41
-rw-r--r--platform/iphone/godot_app_delegate.m467
-rw-r--r--platform/iphone/godot_iphone.mm (renamed from platform/iphone/godot_iphone.cpp)61
-rw-r--r--platform/iphone/godot_view.h (renamed from platform/iphone/game_center.h)59
-rw-r--r--platform/iphone/godot_view.mm467
-rw-r--r--platform/iphone/godot_view_gesture_recognizer.h46
-rw-r--r--platform/iphone/godot_view_gesture_recognizer.mm171
-rw-r--r--platform/iphone/godot_view_renderer.h44
-rw-r--r--platform/iphone/godot_view_renderer.mm117
-rw-r--r--platform/iphone/icloud.mm359
-rw-r--r--platform/iphone/in_app_store.mm326
-rw-r--r--platform/iphone/ios.h6
-rw-r--r--platform/iphone/ios.mm43
-rw-r--r--platform/iphone/joypad_iphone.h (renamed from platform/iphone/icloud.h)44
-rw-r--r--platform/iphone/joypad_iphone.mm345
-rw-r--r--platform/iphone/keyboard_input_view.h37
-rw-r--r--platform/iphone/keyboard_input_view.mm195
-rw-r--r--platform/iphone/logo.pngbin1489 -> 1297 bytes
-rw-r--r--platform/iphone/main.m22
-rw-r--r--platform/iphone/native_video_view.h41
-rw-r--r--platform/iphone/native_video_view.m266
-rw-r--r--platform/iphone/os_iphone.cpp632
-rw-r--r--platform/iphone/os_iphone.h172
-rw-r--r--platform/iphone/os_iphone.mm330
-rw-r--r--platform/iphone/platform_config.h13
-rw-r--r--platform/iphone/plugin/godot_plugin_config.h292
-rw-r--r--platform/iphone/view_controller.h25
-rw-r--r--platform/iphone/view_controller.mm228
-rw-r--r--platform/iphone/vulkan_context_iphone.h9
-rw-r--r--platform/iphone/vulkan_context_iphone.mm24
45 files changed, 5352 insertions, 3788 deletions
diff --git a/platform/iphone/SCsub b/platform/iphone/SCsub
index a48629f720..ee7b2f4ab5 100644
--- a/platform/iphone/SCsub
+++ b/platform/iphone/SCsub
@@ -3,23 +3,31 @@
Import("env")
iphone_lib = [
- "godot_iphone.cpp",
- "os_iphone.cpp",
- "semaphore_iphone.cpp",
- "gl_view.mm",
+ "godot_iphone.mm",
+ "os_iphone.mm",
"main.m",
"app_delegate.mm",
"view_controller.mm",
- "game_center.mm",
- "in_app_store.mm",
- "icloud.mm",
"ios.mm",
"vulkan_context_iphone.mm",
+ "display_server_iphone.mm",
+ "joypad_iphone.mm",
+ "godot_view.mm",
+ "display_layer.mm",
+ "godot_app_delegate.m",
+ "godot_view_renderer.mm",
+ "godot_view_gesture_recognizer.mm",
+ "device_metrics.m",
+ "keyboard_input_view.mm",
+ "native_video_view.m",
]
env_ios = env.Clone()
ios_lib = env_ios.add_library("iphone", iphone_lib)
+# (iOS) Enable module support
+env_ios.Append(CCFLAGS=["-fmodules", "-fcxx-modules"])
+
def combine_libs(target=None, source=None, env=None):
lib_path = target[0].srcnode().abspath
diff --git a/platform/iphone/app_delegate.h b/platform/iphone/app_delegate.h
index 27552d781a..d6a2292dd2 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-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 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 */
@@ -28,29 +28,20 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#if defined(OPENGL_ENABLED)
-#import "gl_view.h"
-#endif
-#import "view_controller.h"
#import <UIKit/UIKit.h>
-#import <CoreMotion/CoreMotion.h>
+@class ViewController;
// FIXME: Add support for both GLES2 and Vulkan when GLES2 is implemented again,
// so it can't be done with compilation time branching.
//#if defined(OPENGL_ENABLED)
//@interface AppDelegate : NSObject <UIApplicationDelegate, GLViewDelegate> {
//#endif
-#if defined(VULKAN_ENABLED)
-@interface AppDelegate : NSObject <UIApplicationDelegate> {
-#endif
- //@property (strong, nonatomic) UIWindow *window;
- ViewController *view_controller;
- bool is_focus_out;
-};
+//#if defined(VULKAN_ENABLED)
+@interface AppDelegate : NSObject <UIApplicationDelegate>
+//#endif
@property(strong, nonatomic) UIWindow *window;
-
-+ (ViewController *)getViewController;
+@property(strong, class, readonly, nonatomic) ViewController *viewController;
@end
diff --git a/platform/iphone/app_delegate.mm b/platform/iphone/app_delegate.mm
index c4ef185bf1..d10ea5c68c 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-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 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,644 +29,61 @@
/*************************************************************************/
#import "app_delegate.h"
-
-#include "core/project_settings.h"
+#include "core/config/project_settings.h"
#include "drivers/coreaudio/audio_driver_coreaudio.h"
-#if defined(OPENGL_ENABLED)
-#import "gl_view.h"
-#endif
+#import "godot_view.h"
#include "main/main.h"
#include "os_iphone.h"
+#import "view_controller.h"
-#import "GameController/GameController.h"
+#import <AVFoundation/AVFoundation.h>
#import <AudioToolbox/AudioServices.h>
-#define kFilteringFactor 0.1
#define kRenderingFrequency 60
-#define kAccelerometerFrequency 100.0 // Hz
-
-Error _shell_open(String);
-void _set_keep_screen_on(bool p_enabled);
-
-Error _shell_open(String p_uri) {
- NSString *url = [[NSString alloc] initWithUTF8String:p_uri.utf8().get_data()];
-
- if (![[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:url]])
- return ERR_CANT_OPEN;
-
- printf("opening url %ls\n", p_uri.c_str());
- [[UIApplication sharedApplication] openURL:[NSURL URLWithString:url]];
- [url release];
- return OK;
-};
-
-void _set_keep_screen_on(bool p_enabled) {
- [[UIApplication sharedApplication] setIdleTimerDisabled:(BOOL)p_enabled];
-};
-
-void _vibrate() {
- AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
-};
-
-@implementation AppDelegate
-
-@synthesize window;
extern int gargc;
extern char **gargv;
-extern int iphone_main(int, int, int, char **, String);
+
+extern int iphone_main(int, char **, String);
extern void iphone_finish();
-CMMotionManager *motionManager;
-bool motionInitialised;
+@implementation AppDelegate
static ViewController *mainViewController = nil;
-+ (ViewController *)getViewController {
- return mainViewController;
-}
-
-NSMutableDictionary *ios_joysticks = nil;
-NSMutableArray *pending_ios_joysticks = nil;
-
-- (GCControllerPlayerIndex)getFreePlayerIndex {
- bool have_player_1 = false;
- bool have_player_2 = false;
- bool have_player_3 = false;
- bool have_player_4 = false;
-
- if (ios_joysticks == nil) {
- NSArray *keys = [ios_joysticks allKeys];
- for (NSNumber *key in keys) {
- GCController *controller = [ios_joysticks objectForKey:key];
- if (controller.playerIndex == GCControllerPlayerIndex1) {
- have_player_1 = true;
- } else if (controller.playerIndex == GCControllerPlayerIndex2) {
- have_player_2 = true;
- } else if (controller.playerIndex == GCControllerPlayerIndex3) {
- have_player_3 = true;
- } else if (controller.playerIndex == GCControllerPlayerIndex4) {
- have_player_4 = true;
- };
- };
- };
-
- if (!have_player_1) {
- return GCControllerPlayerIndex1;
- } else if (!have_player_2) {
- return GCControllerPlayerIndex2;
- } else if (!have_player_3) {
- return GCControllerPlayerIndex3;
- } else if (!have_player_4) {
- return GCControllerPlayerIndex4;
- } else {
- return GCControllerPlayerIndexUnset;
- };
-};
-
-void _ios_add_joystick(GCController *controller, AppDelegate *delegate) {
- // get a new id for our controller
- int joy_id = OSIPhone::get_singleton()->get_unused_joy_id();
- if (joy_id != -1) {
- // assign our player index
- if (controller.playerIndex == GCControllerPlayerIndexUnset) {
- controller.playerIndex = [delegate getFreePlayerIndex];
- };
-
- // tell Godot about our new controller
- OSIPhone::get_singleton()->joy_connection_changed(
- joy_id, true, [controller.vendorName UTF8String]);
-
- // add it to our dictionary, this will retain our controllers
- [ios_joysticks setObject:controller
- forKey:[NSNumber numberWithInt:joy_id]];
-
- // set our input handler
- [delegate setControllerInputHandler:controller];
- } else {
- printf("Couldn't retrieve new joy id\n");
- };
-}
-
-static void on_focus_out(ViewController *view_controller, bool *is_focus_out) {
- if (!*is_focus_out) {
- *is_focus_out = true;
- if (OS::get_singleton()->get_main_loop())
- OS::get_singleton()->get_main_loop()->notification(
- MainLoop::NOTIFICATION_WM_FOCUS_OUT);
-
- [view_controller.view stopAnimation];
- if (OS::get_singleton()->native_video_is_playing()) {
- OSIPhone::get_singleton()->native_video_focus_out();
- }
- AudioDriverCoreAudio *audio = dynamic_cast<AudioDriverCoreAudio *>(AudioDriverCoreAudio::get_singleton());
- if (audio)
- audio->stop();
- }
-}
-
-static void on_focus_in(ViewController *view_controller, bool *is_focus_out) {
- if (*is_focus_out) {
- *is_focus_out = false;
- if (OS::get_singleton()->get_main_loop())
- OS::get_singleton()->get_main_loop()->notification(
- MainLoop::NOTIFICATION_WM_FOCUS_IN);
-
- [view_controller.view startAnimation];
- if (OSIPhone::get_singleton()->native_video_is_playing()) {
- OSIPhone::get_singleton()->native_video_unpause();
- }
-
- AudioDriverCoreAudio *audio = dynamic_cast<AudioDriverCoreAudio *>(AudioDriverCoreAudio::get_singleton());
- if (audio)
- audio->start();
- }
++ (ViewController *)viewController {
+ return mainViewController;
}
-- (void)controllerWasConnected:(NSNotification *)notification {
- // create our dictionary if we don't have one yet
- if (ios_joysticks == nil) {
- ios_joysticks = [[NSMutableDictionary alloc] init];
- };
-
- // get our controller
- GCController *controller = (GCController *)notification.object;
- if (controller == nil) {
- printf("Couldn't retrieve new controller\n");
- } else if ([[ios_joysticks allKeysForObject:controller] count] != 0) {
- printf("Controller is already registered\n");
- } else if (frame_count > 1) {
- _ios_add_joystick(controller, self);
- } else {
- if (pending_ios_joysticks == nil)
- pending_ios_joysticks = [[NSMutableArray alloc] init];
- [pending_ios_joysticks addObject:controller];
- };
-};
-
-- (void)controllerWasDisconnected:(NSNotification *)notification {
- if (ios_joysticks != nil) {
- // find our joystick, there should be only one in our dictionary
- GCController *controller = (GCController *)notification.object;
- NSArray *keys = [ios_joysticks allKeysForObject:controller];
- for (NSNumber *key in keys) {
- // tell Godot this joystick is no longer there
- int joy_id = [key intValue];
- OSIPhone::get_singleton()->joy_connection_changed(joy_id, false, "");
-
- // and remove it from our dictionary
- [ios_joysticks removeObjectForKey:key];
- };
- };
-};
-
-- (int)getJoyIdForController:(GCController *)controller {
- if (ios_joysticks != nil) {
- // find our joystick, there should be only one in our dictionary
- NSArray *keys = [ios_joysticks allKeysForObject:controller];
- for (NSNumber *key in keys) {
- int joy_id = [key intValue];
- return joy_id;
- };
- };
-
- return -1;
-};
-
-- (void)setControllerInputHandler:(GCController *)controller {
- // Hook in the callback handler for the correct gamepad profile.
- // This is a bit of a weird design choice on Apples part.
- // You need to select the most capable gamepad profile for the
- // gamepad attached.
- if (controller.extendedGamepad != nil) {
- // The extended gamepad profile has all the input you could possibly find on
- // a gamepad but will only be active if your gamepad actually has all of
- // these...
- controller.extendedGamepad.valueChangedHandler = ^(
- GCExtendedGamepad *gamepad, GCControllerElement *element) {
- int joy_id = [self getJoyIdForController:controller];
-
- if (element == gamepad.buttonA) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_A,
- gamepad.buttonA.isPressed);
- } else if (element == gamepad.buttonB) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_B,
- gamepad.buttonB.isPressed);
- } else if (element == gamepad.buttonX) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_X,
- gamepad.buttonX.isPressed);
- } else if (element == gamepad.buttonY) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_Y,
- gamepad.buttonY.isPressed);
- } else if (element == gamepad.leftShoulder) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_LEFT_SHOULDER,
- gamepad.leftShoulder.isPressed);
- } else if (element == gamepad.rightShoulder) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_RIGHT_SHOULDER,
- gamepad.rightShoulder.isPressed);
- } else if (element == gamepad.dpad) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP,
- gamepad.dpad.up.isPressed);
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN,
- gamepad.dpad.down.isPressed);
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT,
- gamepad.dpad.left.isPressed);
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT,
- gamepad.dpad.right.isPressed);
- };
-
- InputDefault::JoyAxis jx;
- jx.min = -1;
- if (element == gamepad.leftThumbstick) {
- jx.value = gamepad.leftThumbstick.xAxis.value;
- OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_LEFT_X, jx);
- jx.value = -gamepad.leftThumbstick.yAxis.value;
- OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_LEFT_Y, jx);
- } else if (element == gamepad.rightThumbstick) {
- jx.value = gamepad.rightThumbstick.xAxis.value;
- OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_RIGHT_X, jx);
- jx.value = -gamepad.rightThumbstick.yAxis.value;
- OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_RIGHT_Y, jx);
- } else if (element == gamepad.leftTrigger) {
- jx.value = gamepad.leftTrigger.value;
- OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_TRIGGER_LEFT, jx);
- } else if (element == gamepad.rightTrigger) {
- jx.value = gamepad.rightTrigger.value;
- OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_TRIGGER_RIGHT, jx);
- };
- };
- } else if (controller.gamepad != nil) {
- // gamepad is the standard profile with 4 buttons, shoulder buttons and a
- // D-pad
- controller.gamepad.valueChangedHandler = ^(GCGamepad *gamepad,
- GCControllerElement *element) {
- int joy_id = [self getJoyIdForController:controller];
-
- if (element == gamepad.buttonA) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_A,
- gamepad.buttonA.isPressed);
- } else if (element == gamepad.buttonB) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_B,
- gamepad.buttonB.isPressed);
- } else if (element == gamepad.buttonX) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_X,
- gamepad.buttonX.isPressed);
- } else if (element == gamepad.buttonY) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_Y,
- gamepad.buttonY.isPressed);
- } else if (element == gamepad.leftShoulder) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_LEFT_SHOULDER,
- gamepad.leftShoulder.isPressed);
- } else if (element == gamepad.rightShoulder) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_RIGHT_SHOULDER,
- gamepad.rightShoulder.isPressed);
- } else if (element == gamepad.dpad) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP,
- gamepad.dpad.up.isPressed);
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN,
- gamepad.dpad.down.isPressed);
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT,
- gamepad.dpad.left.isPressed);
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT,
- gamepad.dpad.right.isPressed);
- };
- };
-#ifdef ADD_MICRO_GAMEPAD // disabling this for now, only available on iOS 9+,
- // while we are setting that as the minimum, seems our
- // build environment doesn't like it
- } else if (controller.microGamepad != nil) {
- // micro gamepads were added in OS 9 and feature just 2 buttons and a d-pad
- controller.microGamepad.valueChangedHandler =
- ^(GCMicroGamepad *gamepad, GCControllerElement *element) {
- int joy_id = [self getJoyIdForController:controller];
-
- if (element == gamepad.buttonA) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_A,
- gamepad.buttonA.isPressed);
- } else if (element == gamepad.buttonX) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_X,
- gamepad.buttonX.isPressed);
- } else if (element == gamepad.dpad) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP,
- gamepad.dpad.up.isPressed);
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN,
- gamepad.dpad.down.isPressed);
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT,
- gamepad.dpad.left.isPressed);
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT,
- gamepad.dpad.right.isPressed);
- };
- };
-#endif
- };
-
- ///@TODO need to add support for controller.motion which gives us access to
- /// the orientation of the device (if supported)
-
- ///@TODO need to add support for controllerPausedHandler which should be a
- /// toggle
-};
-
-- (void)initGameControllers {
- // get told when controllers connect, this will be called right away for
- // already connected controllers
- [[NSNotificationCenter defaultCenter]
- addObserver:self
- selector:@selector(controllerWasConnected:)
- name:GCControllerDidConnectNotification
- object:nil];
-
- // get told when controllers disconnect
- [[NSNotificationCenter defaultCenter]
- addObserver:self
- selector:@selector(controllerWasDisconnected:)
- name:GCControllerDidDisconnectNotification
- object:nil];
-};
-
-- (void)deinitGameControllers {
- [[NSNotificationCenter defaultCenter]
- removeObserver:self
- name:GCControllerDidConnectNotification
- object:nil];
- [[NSNotificationCenter defaultCenter]
- removeObserver:self
- name:GCControllerDidDisconnectNotification
- object:nil];
-
- if (ios_joysticks != nil) {
- [ios_joysticks dealloc];
- ios_joysticks = nil;
- };
-
- if (pending_ios_joysticks != nil) {
- [pending_ios_joysticks dealloc];
- pending_ios_joysticks = nil;
- };
-};
-
-OS::VideoMode _get_video_mode() {
- int backingWidth;
- int backingHeight;
-#if defined(OPENGL_ENABLED)
- glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES,
- GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
- glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES,
- GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
-#endif
-
- OS::VideoMode vm;
- vm.fullscreen = true;
- vm.width = backingWidth;
- vm.height = backingHeight;
- vm.resizable = false;
- return vm;
-};
-
-static int frame_count = 0;
-- (void)drawView:(UIView *)view;
-{
- switch (frame_count) {
- case 0: {
- OS::get_singleton()->set_video_mode(_get_video_mode());
-
- if (!OS::get_singleton()) {
- exit(0);
- };
- ++frame_count;
-
- NSString *locale_code = [[NSLocale currentLocale] localeIdentifier];
- OSIPhone::get_singleton()->set_locale(
- String::utf8([locale_code UTF8String]));
-
- NSString *uuid;
- if ([[UIDevice currentDevice]
- respondsToSelector:@selector(identifierForVendor)]) {
- uuid = [UIDevice currentDevice].identifierForVendor.UUIDString;
- } else {
- // before iOS 6, so just generate an identifier and store it
- uuid = [[NSUserDefaults standardUserDefaults]
- objectForKey:@"identiferForVendor"];
- if (!uuid) {
- CFUUIDRef cfuuid = CFUUIDCreate(NULL);
- uuid = (__bridge_transfer NSString *)CFUUIDCreateString(NULL, cfuuid);
- CFRelease(cfuuid);
- [[NSUserDefaults standardUserDefaults]
- setObject:uuid
- forKey:@"identifierForVendor"];
- }
- }
-
- OSIPhone::get_singleton()->set_unique_id(String::utf8([uuid UTF8String]));
-
- }; break;
-
- case 1: {
- Main::setup2();
- ++frame_count;
-
- if (pending_ios_joysticks != nil) {
- for (GCController *controller in pending_ios_joysticks) {
- _ios_add_joystick(controller, self);
- }
- [pending_ios_joysticks dealloc];
- pending_ios_joysticks = nil;
- }
-
- // this might be necessary before here
- NSDictionary *dict = [[NSBundle mainBundle] infoDictionary];
- for (NSString *key in dict) {
- NSObject *value = [dict objectForKey:key];
- String ukey = String::utf8([key UTF8String]);
-
- // we need a NSObject to Variant conversor
-
- if ([value isKindOfClass:[NSString class]]) {
- NSString *str = (NSString *)value;
- String uval = String::utf8([str UTF8String]);
-
- ProjectSettings::get_singleton()->set("Info.plist/" + ukey, uval);
-
- } else if ([value isKindOfClass:[NSNumber class]]) {
- NSNumber *n = (NSNumber *)value;
- double dval = [n doubleValue];
-
- ProjectSettings::get_singleton()->set("Info.plist/" + ukey, dval);
- };
- // do stuff
- }
-
- }; break;
-
- case 2: {
- Main::start();
- ++frame_count;
-
- }; break; // no fallthrough
-
- default: {
- if (OSIPhone::get_singleton()) {
- // OSIPhone::get_singleton()->update_accelerometer(accel[0], accel[1],
- // accel[2]);
- if (motionInitialised) {
- // Just using polling approach for now, we can set this up so it sends
- // data to us in intervals, might be better. See Apple reference pages
- // for more details:
- // https://developer.apple.com/reference/coremotion/cmmotionmanager?language=objc
-
- // Apple splits our accelerometer date into a gravity and user movement
- // component. We add them back together
- CMAcceleration gravity = motionManager.deviceMotion.gravity;
- CMAcceleration acceleration =
- motionManager.deviceMotion.userAcceleration;
-
- ///@TODO We don't seem to be getting data here, is my device broken or
- /// is this code incorrect?
- CMMagneticField magnetic =
- motionManager.deviceMotion.magneticField.field;
-
- ///@TODO we can access rotationRate as a CMRotationRate variable
- ///(processed date) or CMGyroData (raw data), have to see what works
- /// best
- CMRotationRate rotation = motionManager.deviceMotion.rotationRate;
-
- // Adjust for screen orientation.
- // [[UIDevice currentDevice] orientation] changes even if we've fixed
- // our orientation which is not a good thing when you're trying to get
- // your user to move the screen in all directions and want consistent
- // output
-
- ///@TODO Using [[UIApplication sharedApplication] statusBarOrientation]
- /// is a bit of a hack. Godot obviously knows the orientation so maybe
- /// we
- // can use that instead? (note that left and right seem swapped)
-
- switch ([[UIApplication sharedApplication] statusBarOrientation]) {
- case UIDeviceOrientationLandscapeLeft: {
- OSIPhone::get_singleton()->update_gravity(-gravity.y, gravity.x,
- gravity.z);
- OSIPhone::get_singleton()->update_accelerometer(
- -(acceleration.y + gravity.y), (acceleration.x + gravity.x),
- acceleration.z + gravity.z);
- OSIPhone::get_singleton()->update_magnetometer(
- -magnetic.y, magnetic.x, magnetic.z);
- OSIPhone::get_singleton()->update_gyroscope(-rotation.y, rotation.x,
- rotation.z);
- }; break;
- case UIDeviceOrientationLandscapeRight: {
- OSIPhone::get_singleton()->update_gravity(gravity.y, -gravity.x,
- gravity.z);
- OSIPhone::get_singleton()->update_accelerometer(
- (acceleration.y + gravity.y), -(acceleration.x + gravity.x),
- acceleration.z + gravity.z);
- OSIPhone::get_singleton()->update_magnetometer(
- magnetic.y, -magnetic.x, magnetic.z);
- OSIPhone::get_singleton()->update_gyroscope(rotation.y, -rotation.x,
- rotation.z);
- }; break;
- case UIDeviceOrientationPortraitUpsideDown: {
- OSIPhone::get_singleton()->update_gravity(-gravity.x, gravity.y,
- gravity.z);
- OSIPhone::get_singleton()->update_accelerometer(
- -(acceleration.x + gravity.x), (acceleration.y + gravity.y),
- acceleration.z + gravity.z);
- OSIPhone::get_singleton()->update_magnetometer(
- -magnetic.x, magnetic.y, magnetic.z);
- OSIPhone::get_singleton()->update_gyroscope(-rotation.x, rotation.y,
- rotation.z);
- }; break;
- default: { // assume portrait
- OSIPhone::get_singleton()->update_gravity(gravity.x, gravity.y,
- gravity.z);
- OSIPhone::get_singleton()->update_accelerometer(
- acceleration.x + gravity.x, acceleration.y + gravity.y,
- acceleration.z + gravity.z);
- OSIPhone::get_singleton()->update_magnetometer(magnetic.x, magnetic.y,
- magnetic.z);
- OSIPhone::get_singleton()->update_gyroscope(rotation.x, rotation.y,
- rotation.z);
- }; break;
- };
- }
-
- bool quit_request = OSIPhone::get_singleton()->iterate();
- };
-
- }; break;
- };
-};
-
-- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
- if (OS::get_singleton()->get_main_loop()) {
- OS::get_singleton()->get_main_loop()->notification(
- MainLoop::NOTIFICATION_OS_MEMORY_WARNING);
- }
-};
-
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
- CGRect rect = [[UIScreen mainScreen] bounds];
+ // TODO: might be required to make an early return, so app wouldn't crash because of timeout.
+ // TODO: logo screen is not displayed while shaders are compiling
+ // DummyViewController(Splash/LoadingViewController) -> setup -> GodotViewController
- is_focus_out = false;
-
- [application setStatusBarHidden:YES withAnimation:UIStatusBarAnimationNone];
- // disable idle timer
- // application.idleTimerDisabled = YES;
+ CGRect windowBounds = [[UIScreen mainScreen] bounds];
// Create a full-screen window
- window = [[UIWindow alloc] initWithFrame:rect];
-
- OS::VideoMode vm = _get_video_mode();
+ self.window = [[UIWindow alloc] initWithFrame:windowBounds];
- NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
- NSUserDomainMask, YES);
+ NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
- int err = iphone_main(vm.width, vm.height, gargc, gargv, String::utf8([documentsDirectory UTF8String]));
+ int err = iphone_main(gargc, gargv, String::utf8([documentsDirectory UTF8String]));
+
if (err != 0) {
// bail, things did not go very well for us, should probably output a message on screen with our error code...
exit(0);
- return FALSE;
+ return NO;
};
-#if defined(OPENGL_ENABLED)
- // WARNING: We must *always* create the GLView after we have constructed the
- // OS with iphone_main. This allows the GLView to access project settings so
- // it can properly initialize the OpenGL context
- GLView *glView = [[GLView alloc] initWithFrame:rect];
- glView.delegate = self;
-
- view_controller = [[ViewController alloc] init];
- view_controller.view = glView;
-
- _set_keep_screen_on(bool(GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true)) ? YES : NO);
- glView.useCADisplayLink =
- bool(GLOBAL_DEF("display.iOS/use_cadisplaylink", true)) ? YES : NO;
- printf("cadisaplylink: %d", glView.useCADisplayLink);
- glView.animationInterval = 1.0 / kRenderingFrequency;
- [glView startAnimation];
-#endif
-
-#if defined(VULKAN_ENABLED)
- view_controller = [[ViewController alloc] init];
-#endif
+ ViewController *viewController = [[ViewController alloc] init];
+ viewController.godotView.useCADisplayLink = bool(GLOBAL_DEF("display.iOS/use_cadisplaylink", true)) ? YES : NO;
+ viewController.godotView.renderingInterval = 1.0 / kRenderingFrequency;
- window.rootViewController = view_controller;
+ self.window.rootViewController = viewController;
// Show the window
- [window makeKeyAndVisible];
-
- // Configure and start accelerometer
- if (!motionInitialised) {
- motionManager = [[CMMotionManager alloc] init];
- if (motionManager.deviceMotionAvailable) {
- motionManager.deviceMotionUpdateInterval = 1.0 / 70.0;
- [motionManager startDeviceMotionUpdatesUsingReferenceFrame:
- CMAttitudeReferenceFrameXMagneticNorthZVertical];
- motionInitialised = YES;
- };
- };
-
- [self initGameControllers];
+ [self.window makeKeyAndVisible];
[[NSNotificationCenter defaultCenter]
addObserver:self
@@ -674,40 +91,33 @@ static int frame_count = 0;
name:AVAudioSessionInterruptionNotification
object:[AVAudioSession sharedInstance]];
- // OSIPhone::screen_width = rect.size.width - rect.origin.x;
- // OSIPhone::screen_height = rect.size.height - rect.origin.y;
-
- mainViewController = view_controller;
+ mainViewController = viewController;
// prevent to stop music in another background app
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:nil];
- return TRUE;
+ return YES;
};
- (void)onAudioInterruption:(NSNotification *)notification {
if ([notification.name isEqualToString:AVAudioSessionInterruptionNotification]) {
if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeBegan]]) {
NSLog(@"Audio interruption began");
- on_focus_out(view_controller, &is_focus_out);
+ OSIPhone::get_singleton()->on_focus_out();
} else if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeEnded]]) {
NSLog(@"Audio interruption ended");
- on_focus_in(view_controller, &is_focus_out);
+ OSIPhone::get_singleton()->on_focus_in();
}
}
};
-- (void)applicationWillTerminate:(UIApplication *)application {
- [self deinitGameControllers];
-
- if (motionInitialised) {
- ///@TODO is this the right place to clean this up?
- [motionManager stopDeviceMotionUpdates];
- [motionManager release];
- motionManager = nil;
- motionInitialised = NO;
- };
+- (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();
};
@@ -722,16 +132,15 @@ static int frame_count = 0;
// notification panel by swiping from the upper part of the screen.
- (void)applicationWillResignActive:(UIApplication *)application {
- on_focus_out(view_controller, &is_focus_out);
+ OSIPhone::get_singleton()->on_focus_out();
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
- on_focus_in(view_controller, &is_focus_out);
+ OSIPhone::get_singleton()->on_focus_in();
}
- (void)dealloc {
- [window release];
- [super dealloc];
+ self.window = nil;
}
@end
diff --git a/platform/iphone/detect.py b/platform/iphone/detect.py
index 3e6c2f0ecf..17796beb6f 100644
--- a/platform/iphone/detect.py
+++ b/platform/iphone/detect.py
@@ -12,7 +12,6 @@ def get_name():
def can_build():
-
if sys.platform == "darwin" or ("OSXCROSS_IOS" in os.environ):
return True
@@ -31,26 +30,23 @@ def get_opts():
("IPHONESDK", "Path to the iPhone SDK", ""),
BoolVariable(
"use_static_mvk",
- "Link MoltenVK statically as Level-0 driver (better portability) or use Vulkan ICD loader (enables validation layers)",
+ "Link MoltenVK statically as Level-0 driver (better portability) or use Vulkan ICD loader (enables"
+ " validation layers)",
False,
),
- BoolVariable("game_center", "Support for game center", True),
- BoolVariable("store_kit", "Support for in-app store", True),
- BoolVariable("icloud", "Support for iCloud", True),
+ BoolVariable("ios_simulator", "Build for iOS Simulator", False),
BoolVariable("ios_exceptions", "Enable exceptions", False),
("ios_triple", "Triple for ios toolchain", ""),
]
def get_flags():
-
return [
("tools", False),
]
def configure(env):
-
## Build type
if env["target"].startswith("release"):
@@ -67,7 +63,7 @@ def configure(env):
elif env["target"] == "debug":
env.Append(CCFLAGS=["-gdwarf-2", "-O0"])
- env.Append(CPPDEFINES=["_DEBUG", ("DEBUG", 1), "DEBUG_ENABLED", "DEBUG_MEMORY_ENABLED"])
+ env.Append(CPPDEFINES=["_DEBUG", ("DEBUG", 1), "DEBUG_ENABLED"])
if env["use_lto"]:
env.Append(CCFLAGS=["-flto"])
@@ -112,26 +108,46 @@ def configure(env):
## Compile flags
- if env["arch"] == "x86" or env["arch"] == "x86_64":
+ if env["ios_simulator"]:
detect_darwin_sdk_path("iphonesimulator", env)
+ env.Append(CCFLAGS=["-mios-simulator-version-min=13.0"])
+ env.Append(LINKFLAGS=["-mios-simulator-version-min=13.0"])
+ env.extra_suffix = ".simulator" + env.extra_suffix
+ else:
+ detect_darwin_sdk_path("iphone", env)
+ env.Append(CCFLAGS=["-miphoneos-version-min=11.0"])
+ env.Append(LINKFLAGS=["-miphoneos-version-min=11.0"])
+
+ if env["arch"] == "x86" or env["arch"] == "x86_64":
env["ENV"]["MACOSX_DEPLOYMENT_TARGET"] = "10.9"
arch_flag = "i386" if env["arch"] == "x86" else env["arch"]
env.Append(
CCFLAGS=(
- "-arch "
+ "-fobjc-arc -arch "
+ arch_flag
- + " -fobjc-abi-version=2 -fobjc-legacy-dispatch -fmessage-length=0 -fpascal-strings -fblocks -fasm-blocks -isysroot $IPHONESDK -mios-simulator-version-min=10.0"
+ + " -fobjc-abi-version=2 -fobjc-legacy-dispatch -fmessage-length=0 -fpascal-strings -fblocks"
+ " -fasm-blocks -isysroot $IPHONESDK"
).split()
)
elif env["arch"] == "arm":
- detect_darwin_sdk_path("iphone", env)
env.Append(
- CCFLAGS='-fno-objc-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)" -miphoneos-version-min=10.0 -MMD -MT dependencies'.split()
+ 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":
- detect_darwin_sdk_path("iphone", env)
env.Append(
- CCFLAGS="-fno-objc-arc -arch arm64 -fmessage-length=0 -fno-strict-aliasing -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits -fpascal-strings -fblocks -fvisibility=hidden -MMD -MT dependencies -miphoneos-version-min=10.0 -isysroot $IPHONESDK".split()
+ CCFLAGS=(
+ "-fobjc-arc -arch arm64 -fmessage-length=0 -fno-strict-aliasing"
+ " -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits"
+ " -fpascal-strings -fblocks -fvisibility=hidden -MMD -MT dependencies"
+ " -isysroot $IPHONESDK".split()
+ )
)
env.Append(CPPDEFINES=["NEED_LONG_INT"])
env.Append(CPPDEFINES=["LIBYUV_DISABLE_NEON"])
@@ -143,6 +159,9 @@ def configure(env):
else:
env.Append(CCFLAGS=["-fno-exceptions"])
+ # Temp fix for ABS/MAX/MIN macros in iPhone SDK blocking compilation
+ env.Append(CCFLAGS=["-Wno-ambiguous-macro"])
+
## Link flags
if env["arch"] == "x86" or env["arch"] == "x86_64":
@@ -151,7 +170,6 @@ def configure(env):
LINKFLAGS=[
"-arch",
arch_flag,
- "-mios-simulator-version-min=10.0",
"-isysroot",
"$IPHONESDK",
"-Xlinker",
@@ -162,63 +180,22 @@ def configure(env):
]
)
elif env["arch"] == "arm":
- env.Append(LINKFLAGS=["-arch", "armv7", "-Wl,-dead_strip", "-miphoneos-version-min=10.0"])
+ env.Append(LINKFLAGS=["-arch", "armv7", "-Wl,-dead_strip"])
if env["arch"] == "arm64":
- env.Append(LINKFLAGS=["-arch", "arm64", "-Wl,-dead_strip", "-miphoneos-version-min=10.0"])
+ env.Append(LINKFLAGS=["-arch", "arm64", "-Wl,-dead_strip"])
env.Append(
LINKFLAGS=[
"-isysroot",
"$IPHONESDK",
- "-framework",
- "AudioToolbox",
- "-framework",
- "AVFoundation",
- "-framework",
- "CoreAudio",
- "-framework",
- "CoreGraphics",
- "-framework",
- "CoreMedia",
- "-framework",
- "CoreVideo",
- "-framework",
- "CoreMotion",
- "-framework",
- "Foundation",
- "-framework",
- "GameController",
- "-framework",
- "MediaPlayer",
- "-framework",
- "Metal",
- "-framework",
- "QuartzCore",
- "-framework",
- "Security",
- "-framework",
- "SystemConfiguration",
- "-framework",
- "UIKit",
- "-framework",
- "ARKit",
]
)
- # Feature options
- if env["game_center"]:
- env.Append(CPPDEFINES=["GAME_CENTER_ENABLED"])
- env.Append(LINKFLAGS=["-framework", "GameKit"])
-
- if env["store_kit"]:
- env.Append(CPPDEFINES=["STOREKIT_ENABLED"])
- env.Append(LINKFLAGS=["-framework", "StoreKit"])
-
- if env["icloud"]:
- env.Append(CPPDEFINES=["ICLOUD_ENABLED"])
-
env.Prepend(
- CPPPATH=["$IPHONESDK/usr/include", "$IPHONESDK/System/Library/Frameworks/AudioUnit.framework/Headers",]
+ CPPPATH=[
+ "$IPHONESDK/usr/include",
+ "$IPHONESDK/System/Library/Frameworks/AudioUnit.framework/Headers",
+ ]
)
env["ENV"]["CODESIGN_ALLOCATE"] = "/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/codesign_allocate"
@@ -228,8 +205,7 @@ def configure(env):
env.Append(CPPDEFINES=["VULKAN_ENABLED"])
env.Append(LINKFLAGS=["-framework", "IOSurface"])
- if env["use_static_mvk"]:
- env.Append(LINKFLAGS=["-framework", "MoltenVK"])
- env["builtin_vulkan"] = False
- elif not env["builtin_vulkan"]:
- env.Append(LIBS=["vulkan"])
+
+ # Use Static Vulkan for iOS. Dynamic Framework works fine too.
+ env.Append(LINKFLAGS=["-framework", "MoltenVK"])
+ env["builtin_vulkan"] = False
diff --git a/platform/iphone/device_metrics.h b/platform/iphone/device_metrics.h
new file mode 100644
index 0000000000..bc8f761dde
--- /dev/null
+++ b/platform/iphone/device_metrics.h
@@ -0,0 +1,37 @@
+/*************************************************************************/
+/* device_metrics.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#import <Foundation/Foundation.h>
+
+@interface GodotDeviceMetrics : NSObject
+
+@property(nonatomic, class, readonly, strong) NSDictionary<NSArray *, NSNumber *> *dpiList;
+
+@end
diff --git a/platform/iphone/device_metrics.m b/platform/iphone/device_metrics.m
new file mode 100644
index 0000000000..306ca95d03
--- /dev/null
+++ b/platform/iphone/device_metrics.m
@@ -0,0 +1,152 @@
+/*************************************************************************/
+/* device_metrics.m */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#import "device_metrics.h"
+
+@implementation GodotDeviceMetrics
+
++ (NSDictionary *)dpiList {
+ return @{
+ @[
+ @"iPad1,1",
+ @"iPad2,1",
+ @"iPad2,2",
+ @"iPad2,3",
+ @"iPad2,4",
+ ] : @132,
+ @[
+ @"iPhone1,1",
+ @"iPhone1,2",
+ @"iPhone2,1",
+ @"iPad2,5",
+ @"iPad2,6",
+ @"iPad2,7",
+ @"iPod1,1",
+ @"iPod2,1",
+ @"iPod3,1",
+ ] : @163,
+ @[
+ @"iPad3,1",
+ @"iPad3,2",
+ @"iPad3,3",
+ @"iPad3,4",
+ @"iPad3,5",
+ @"iPad3,6",
+ @"iPad4,1",
+ @"iPad4,2",
+ @"iPad4,3",
+ @"iPad5,3",
+ @"iPad5,4",
+ @"iPad6,3",
+ @"iPad6,4",
+ @"iPad6,7",
+ @"iPad6,8",
+ @"iPad6,11",
+ @"iPad6,12",
+ @"iPad7,1",
+ @"iPad7,2",
+ @"iPad7,3",
+ @"iPad7,4",
+ @"iPad7,5",
+ @"iPad7,6",
+ @"iPad7,11",
+ @"iPad7,12",
+ @"iPad8,1",
+ @"iPad8,2",
+ @"iPad8,3",
+ @"iPad8,4",
+ @"iPad8,5",
+ @"iPad8,6",
+ @"iPad8,7",
+ @"iPad8,8",
+ @"iPad8,9",
+ @"iPad8,10",
+ @"iPad8,11",
+ @"iPad8,12",
+ @"iPad11,3",
+ @"iPad11,4",
+ ] : @264,
+ @[
+ @"iPhone3,1",
+ @"iPhone3,2",
+ @"iPhone3,3",
+ @"iPhone4,1",
+ @"iPhone5,1",
+ @"iPhone5,2",
+ @"iPhone5,3",
+ @"iPhone5,4",
+ @"iPhone6,1",
+ @"iPhone6,2",
+ @"iPhone7,2",
+ @"iPhone8,1",
+ @"iPhone8,4",
+ @"iPhone9,1",
+ @"iPhone9,3",
+ @"iPhone10,1",
+ @"iPhone10,4",
+ @"iPhone11,8",
+ @"iPhone12,1",
+ @"iPhone12,8",
+ @"iPad4,4",
+ @"iPad4,5",
+ @"iPad4,6",
+ @"iPad4,7",
+ @"iPad4,8",
+ @"iPad4,9",
+ @"iPad5,1",
+ @"iPad5,2",
+ @"iPad11,1",
+ @"iPad11,2",
+ @"iPod4,1",
+ @"iPod5,1",
+ @"iPod7,1",
+ @"iPod9,1",
+ ] : @326,
+ @[
+ @"iPhone7,1",
+ @"iPhone8,2",
+ @"iPhone9,2",
+ @"iPhone9,4",
+ @"iPhone10,2",
+ @"iPhone10,5",
+ ] : @401,
+ @[
+ @"iPhone10,3",
+ @"iPhone10,6",
+ @"iPhone11,2",
+ @"iPhone11,4",
+ @"iPhone11,6",
+ @"iPhone12,3",
+ @"iPhone12,5",
+ ] : @458,
+ };
+}
+
+@end
diff --git a/platform/iphone/in_app_store.h b/platform/iphone/display_layer.h
index 44e65e77ed..1b8435d959 100644
--- a/platform/iphone/in_app_store.h
+++ b/platform/iphone/display_layer.h
@@ -1,12 +1,12 @@
/*************************************************************************/
-/* in_app_store.h */
+/* display_layer.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 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 */
@@ -28,40 +28,31 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifdef STOREKIT_ENABLED
+#import <OpenGLES/EAGLDrawable.h>
+#import <QuartzCore/QuartzCore.h>
-#ifndef IN_APP_STORE_H
-#define IN_APP_STORE_H
+@protocol DisplayLayer <NSObject>
-#include "core/object.h"
+- (void)renderDisplayLayer;
+- (void)initializeDisplayLayer;
+- (void)layoutDisplayLayer;
-class InAppStore : public Object {
- GDCLASS(InAppStore, Object);
-
- static InAppStore *instance;
- static void _bind_methods();
-
- List<Variant> pending_events;
-
-public:
- Error request_product_info(Variant p_params);
- Error restore_purchases();
- Error purchase(Variant p_params);
-
- int get_pending_event_count();
- Variant pop_pending_event();
- void finish_transaction(String product_id);
- void set_auto_finish_transaction(bool b);
-
- void _post_event(Variant p_event);
- void _record_purchase(String product_id);
-
- static InAppStore *get_singleton();
-
- InAppStore();
- ~InAppStore();
-};
+@end
+// An ugly workaround for iOS simulator
+#if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR
+#if defined(__IPHONE_13_0)
+API_AVAILABLE(ios(13.0))
+@interface GodotMetalLayer : CAMetalLayer <DisplayLayer>
+#else
+@interface GodotMetalLayer : CALayer <DisplayLayer>
#endif
-
+#else
+@interface GodotMetalLayer : CAMetalLayer <DisplayLayer>
#endif
+@end
+
+API_DEPRECATED("OpenGLES is deprecated", ios(2.0, 12.0))
+@interface GodotOpenGLLayer : CAEAGLLayer <DisplayLayer>
+
+@end
diff --git a/platform/iphone/display_layer.mm b/platform/iphone/display_layer.mm
new file mode 100644
index 0000000000..b8df81b89a
--- /dev/null
+++ b/platform/iphone/display_layer.mm
@@ -0,0 +1,183 @@
+/*************************************************************************/
+/* display_layer.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#import "display_layer.h"
+#include "core/config/project_settings.h"
+#include "core/os/keyboard.h"
+#include "display_server_iphone.h"
+#include "main/main.h"
+#include "os_iphone.h"
+#include "servers/audio_server.h"
+
+#import <AudioToolbox/AudioServices.h>
+#import <GameController/GameController.h>
+#import <OpenGLES/EAGL.h>
+#import <OpenGLES/ES1/gl.h>
+#import <OpenGLES/ES1/glext.h>
+#import <QuartzCore/QuartzCore.h>
+#import <UIKit/UIKit.h>
+
+@implementation GodotMetalLayer
+
+- (void)initializeDisplayLayer {
+#if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR
+ if (@available(iOS 13, *)) {
+ // Simulator supports Metal since iOS 13
+ } else {
+ NSLog(@"iOS Simulator prior to iOS 13 does not support Metal rendering.");
+ }
+#endif
+}
+
+- (void)layoutDisplayLayer {
+}
+
+- (void)renderDisplayLayer {
+}
+
+@end
+
+@implementation GodotOpenGLLayer {
+ // The pixel dimensions of the backbuffer
+ GLint backingWidth;
+ GLint backingHeight;
+
+ EAGLContext *context;
+ GLuint viewRenderbuffer, viewFramebuffer;
+ GLuint depthRenderbuffer;
+}
+
+- (void)initializeDisplayLayer {
+ // Get our backing layer
+
+ // Configure it so that it is opaque, does not retain the contents of the backbuffer when displayed, and uses RGBA8888 color.
+ self.opaque = YES;
+ self.drawableProperties = [NSDictionary
+ dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:FALSE],
+ kEAGLDrawablePropertyRetainedBacking,
+ kEAGLColorFormatRGBA8,
+ kEAGLDrawablePropertyColorFormat,
+ nil];
+
+ // FIXME: Add Vulkan support via MoltenVK. Add fallback code back?
+
+ // Create GL ES 2 context
+ if (GLOBAL_GET("rendering/driver/driver_name") == "GLES2") {
+ context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
+ NSLog(@"Setting up an OpenGL ES 2.0 context.");
+ if (!context) {
+ NSLog(@"Failed to create OpenGL ES 2.0 context!");
+ return;
+ }
+ }
+
+ if (![EAGLContext setCurrentContext:context]) {
+ NSLog(@"Failed to set EAGLContext!");
+ return;
+ }
+ if (![self createFramebuffer]) {
+ NSLog(@"Failed to create frame buffer!");
+ return;
+ }
+}
+
+- (void)layoutDisplayLayer {
+ [EAGLContext setCurrentContext:context];
+ [self destroyFramebuffer];
+ [self createFramebuffer];
+}
+
+- (void)renderDisplayLayer {
+ [EAGLContext setCurrentContext:context];
+}
+
+- (void)dealloc {
+ if ([EAGLContext currentContext] == context) {
+ [EAGLContext setCurrentContext:nil];
+ }
+
+ if (context) {
+ context = nil;
+ }
+}
+
+- (BOOL)createFramebuffer {
+ glGenFramebuffersOES(1, &viewFramebuffer);
+ glGenRenderbuffersOES(1, &viewRenderbuffer);
+
+ glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
+ glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
+ // This call associates the storage for the current render buffer with the EAGLDrawable (our CAself)
+ // allowing us to draw into a buffer that will later be rendered to screen wherever the layer is (which corresponds with our view).
+ [context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(id<EAGLDrawable>)self];
+ glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer);
+
+ glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
+ glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
+
+ // For this sample, we also need a depth buffer, so we'll create and attach one via another renderbuffer.
+ glGenRenderbuffersOES(1, &depthRenderbuffer);
+ glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRenderbuffer);
+ glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, backingWidth, backingHeight);
+ glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer);
+
+ if (glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) {
+ NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));
+ 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;
+}
+
+// Clean up any buffers we have allocated.
+- (void)destroyFramebuffer {
+ glDeleteFramebuffersOES(1, &viewFramebuffer);
+ viewFramebuffer = 0;
+ glDeleteRenderbuffersOES(1, &viewRenderbuffer);
+ viewRenderbuffer = 0;
+
+ if (depthRenderbuffer) {
+ glDeleteRenderbuffersOES(1, &depthRenderbuffer);
+ depthRenderbuffer = 0;
+ }
+}
+
+@end
diff --git a/platform/iphone/display_server_iphone.h b/platform/iphone/display_server_iphone.h
new file mode 100644
index 0000000000..31a44723a5
--- /dev/null
+++ b/platform/iphone/display_server_iphone.h
@@ -0,0 +1,202 @@
+/*************************************************************************/
+/* display_server_iphone.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef display_server_iphone_h
+#define display_server_iphone_h
+
+#include "core/input/input.h"
+#include "servers/display_server.h"
+
+#if defined(VULKAN_ENABLED)
+#include "drivers/vulkan/rendering_device_vulkan.h"
+#include "servers/rendering/renderer_rd/renderer_compositor_rd.h"
+
+#include "vulkan_context_iphone.h"
+
+#import <QuartzCore/CAMetalLayer.h>
+#include <vulkan/vulkan_metal.h>
+#endif
+
+class DisplayServerIPhone : public DisplayServer {
+ GDCLASS(DisplayServerIPhone, DisplayServer)
+
+ _THREAD_SAFE_CLASS_
+
+#if defined(VULKAN_ENABLED)
+ VulkanContextIPhone *context_vulkan;
+ RenderingDeviceVulkan *rendering_device_vulkan;
+#endif
+
+ DisplayServer::ScreenOrientation screen_orientation;
+
+ ObjectID window_attached_instance_id;
+
+ Callable window_event_callback;
+ Callable window_resize_callback;
+ Callable input_event_callback;
+ Callable input_text_callback;
+
+ int virtual_keyboard_height = 0;
+
+ void perform_event(const Ref<InputEvent> &p_event);
+
+ DisplayServerIPhone(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error);
+ ~DisplayServerIPhone();
+
+public:
+ String rendering_driver;
+
+ static DisplayServerIPhone *get_singleton();
+
+ static void register_iphone_driver();
+ static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error);
+ static Vector<String> get_rendering_drivers_func();
+
+ // MARK: - Events
+
+ virtual void process_events() override;
+
+ virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual void window_set_input_text_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
+
+ static void _dispatch_input_events(const Ref<InputEvent> &p_event);
+ void send_input_event(const Ref<InputEvent> &p_event) const;
+ void send_input_text(const String &p_text) const;
+ void send_window_event(DisplayServer::WindowEvent p_event) const;
+ void _window_callback(const Callable &p_callable, const Variant &p_arg) const;
+
+ // MARK: - Input
+
+ // MARK: Touches
+
+ void touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick);
+ void touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y);
+ void touches_cancelled(int p_idx);
+
+ // MARK: Keyboard
+
+ void key(uint32_t p_key, bool p_pressed);
+
+ // MARK: Motion
+
+ void update_gravity(float p_x, float p_y, float p_z);
+ void update_accelerometer(float p_x, float p_y, float p_z);
+ void update_magnetometer(float p_x, float p_y, float p_z);
+ void update_gyroscope(float p_x, float p_y, float p_z);
+
+ // MARK: -
+
+ virtual bool has_feature(Feature p_feature) const override;
+ virtual String get_name() const override;
+
+ virtual void alert(const String &p_alert, const String &p_title = "ALERT!") 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 Vector<DisplayServer::WindowID> get_window_list() const override;
+
+ virtual WindowID
+ get_window_at_screen_position(const Point2i &p_position) const override;
+
+ virtual void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) override;
+
+ virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const override;
+ virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override;
+
+ virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const override;
+ virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) override;
+
+ virtual void window_set_transient(WindowID p_window, WindowID p_parent) override;
+
+ virtual void window_set_max_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual Size2i window_get_max_size(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual void window_set_min_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual Size2i window_get_min_size(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const override;
+ virtual Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual bool window_is_maximize_allowed(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual void window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual bool window_get_flag(WindowFlags p_flag, WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual void window_request_attention(WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID) override;
+
+ virtual float screen_get_max_scale() const override;
+
+ virtual void screen_set_orientation(DisplayServer::ScreenOrientation p_orientation, int p_screen) override;
+ virtual DisplayServer::ScreenOrientation screen_get_orientation(int p_screen) const override;
+
+ virtual bool window_can_draw(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual bool can_any_window_draw() const override;
+
+ virtual bool screen_is_touchscreen(int p_screen) const override;
+
+ virtual void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, bool p_multiline, int p_max_length, int p_cursor_start, int p_cursor_end) override;
+ virtual void virtual_keyboard_hide() override;
+
+ void virtual_keyboard_set_height(int height);
+ virtual int virtual_keyboard_get_height() const override;
+
+ virtual void clipboard_set(const String &p_text) override;
+ virtual String clipboard_get() const override;
+
+ virtual void screen_set_keep_on(bool p_enable) override;
+ virtual bool screen_is_kept_on() const override;
+
+ virtual Error native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track, int p_screen = SCREEN_OF_MAIN_WINDOW) override;
+ virtual bool native_video_is_playing() const override;
+ virtual void native_video_pause() override;
+ virtual void native_video_unpause() override;
+ virtual void native_video_stop() override;
+
+ void resize_window(CGSize size);
+};
+
+#endif /* display_server_iphone_h */
diff --git a/platform/iphone/display_server_iphone.mm b/platform/iphone/display_server_iphone.mm
new file mode 100644
index 0000000000..05dc78bb4d
--- /dev/null
+++ b/platform/iphone/display_server_iphone.mm
@@ -0,0 +1,648 @@
+/*************************************************************************/
+/* display_server_iphone.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "display_server_iphone.h"
+#import "app_delegate.h"
+#include "core/config/project_settings.h"
+#include "core/io/file_access_pack.h"
+#import "device_metrics.h"
+#import "godot_view.h"
+#include "ios.h"
+#import "keyboard_input_view.h"
+#import "native_video_view.h"
+#include "os_iphone.h"
+#import "view_controller.h"
+
+#import <Foundation/Foundation.h>
+#import <sys/utsname.h>
+
+static const float kDisplayServerIPhoneAcceleration = 1;
+
+DisplayServerIPhone *DisplayServerIPhone::get_singleton() {
+ return (DisplayServerIPhone *)DisplayServer::get_singleton();
+}
+
+DisplayServerIPhone::DisplayServerIPhone(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
+ rendering_driver = p_rendering_driver;
+
+#if defined(OPENGL_ENABLED)
+ // FIXME: Add support for both GLES2 and Vulkan when GLES2 is implemented
+ // again,
+
+ if (rendering_driver == "opengl_es") {
+ bool gl_initialization_error = false;
+
+ // FIXME: Add Vulkan support via MoltenVK. Add fallback code back?
+
+ if (RasterizerGLES2::is_viable() == OK) {
+ RasterizerGLES2::register_config();
+ RasterizerGLES2::make_current();
+ } else {
+ gl_initialization_error = true;
+ }
+
+ if (gl_initialization_error) {
+ OS::get_singleton()->alert("Your device does not support any of the supported OpenGL versions.", "Unable to initialize video driver");
+ // return ERR_UNAVAILABLE;
+ }
+
+ // rendering_server = memnew(RenderingServerDefault);
+ // // FIXME: Reimplement threaded rendering
+ // if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) {
+ // rendering_server = memnew(RenderingServerWrapMT(rendering_server,
+ // false));
+ // }
+ // rendering_server->init();
+ // rendering_server->cursor_set_visible(false, 0);
+
+ // reset this to what it should be, it will have been set to 0 after
+ // rendering_server->init() is called
+ // RasterizerStorageGLES2::system_fbo = gl_view_base_fb;
+ }
+#endif
+
+#if defined(VULKAN_ENABLED)
+ rendering_driver = "vulkan";
+
+ context_vulkan = nullptr;
+ rendering_device_vulkan = nullptr;
+
+ if (rendering_driver == "vulkan") {
+ context_vulkan = memnew(VulkanContextIPhone);
+ if (context_vulkan->initialize() != OK) {
+ memdelete(context_vulkan);
+ context_vulkan = nullptr;
+ ERR_FAIL_MSG("Failed to initialize Vulkan context");
+ }
+
+ CALayer *layer = [AppDelegate.viewController.godotView initializeRenderingForDriver:@"vulkan"];
+
+ if (!layer) {
+ ERR_FAIL_MSG("Failed to create iOS rendering layer.");
+ }
+
+ Size2i size = Size2i(layer.bounds.size.width, layer.bounds.size.height) * screen_get_max_scale();
+ if (context_vulkan->window_create(MAIN_WINDOW_ID, layer, size.width, size.height) != OK) {
+ memdelete(context_vulkan);
+ context_vulkan = nullptr;
+ ERR_FAIL_MSG("Failed to create Vulkan window.");
+ }
+
+ rendering_device_vulkan = memnew(RenderingDeviceVulkan);
+ rendering_device_vulkan->initialize(context_vulkan);
+
+ RendererCompositorRD::make_current();
+ }
+#endif
+
+ bool keep_screen_on = bool(GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true));
+ screen_set_keep_on(keep_screen_on);
+
+ Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events);
+
+ r_error = OK;
+}
+
+DisplayServerIPhone::~DisplayServerIPhone() {
+#if defined(VULKAN_ENABLED)
+ if (rendering_driver == "vulkan") {
+ if (rendering_device_vulkan) {
+ rendering_device_vulkan->finalize();
+ memdelete(rendering_device_vulkan);
+ rendering_device_vulkan = NULL;
+ }
+
+ if (context_vulkan) {
+ context_vulkan->window_destroy(MAIN_WINDOW_ID);
+ memdelete(context_vulkan);
+ context_vulkan = NULL;
+ }
+ }
+#endif
+}
+
+DisplayServer *DisplayServerIPhone::create_func(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
+ return memnew(DisplayServerIPhone(p_rendering_driver, p_mode, p_flags, p_resolution, r_error));
+}
+
+Vector<String> DisplayServerIPhone::get_rendering_drivers_func() {
+ Vector<String> drivers;
+
+#if defined(VULKAN_ENABLED)
+ drivers.push_back("vulkan");
+#endif
+#if defined(OPENGL_ENABLED)
+ drivers.push_back("opengl_es");
+#endif
+
+ return drivers;
+}
+
+void DisplayServerIPhone::register_iphone_driver() {
+ register_create_function("iphone", create_func, get_rendering_drivers_func);
+}
+
+// MARK: Events
+
+void DisplayServerIPhone::window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window) {
+ window_resize_callback = p_callable;
+}
+
+void DisplayServerIPhone::window_set_window_event_callback(const Callable &p_callable, WindowID p_window) {
+ window_event_callback = p_callable;
+}
+void DisplayServerIPhone::window_set_input_event_callback(const Callable &p_callable, WindowID p_window) {
+ input_event_callback = p_callable;
+}
+
+void DisplayServerIPhone::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) {
+ input_text_callback = p_callable;
+}
+
+void DisplayServerIPhone::window_set_drop_files_callback(const Callable &p_callable, WindowID p_window) {
+ // Probably not supported for iOS
+}
+
+void DisplayServerIPhone::process_events() {
+}
+
+void DisplayServerIPhone::_dispatch_input_events(const Ref<InputEvent> &p_event) {
+ DisplayServerIPhone::get_singleton()->send_input_event(p_event);
+}
+
+void DisplayServerIPhone::send_input_event(const Ref<InputEvent> &p_event) const {
+ _window_callback(input_event_callback, p_event);
+}
+
+void DisplayServerIPhone::send_input_text(const String &p_text) const {
+ _window_callback(input_text_callback, p_text);
+}
+
+void DisplayServerIPhone::send_window_event(DisplayServer::WindowEvent p_event) const {
+ _window_callback(window_event_callback, int(p_event));
+}
+
+void DisplayServerIPhone::_window_callback(const Callable &p_callable, const Variant &p_arg) const {
+ if (!p_callable.is_null()) {
+ const Variant *argp = &p_arg;
+ Variant ret;
+ Callable::CallError ce;
+ p_callable.call((const Variant **)&argp, 1, ret, ce);
+ }
+}
+
+// MARK: - Input
+
+// MARK: Touches
+
+void DisplayServerIPhone::touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick) {
+ if (!GLOBAL_DEF("debug/disable_touch", false)) {
+ Ref<InputEventScreenTouch> ev;
+ ev.instance();
+
+ ev->set_index(p_idx);
+ ev->set_pressed(p_pressed);
+ ev->set_position(Vector2(p_x, p_y));
+ perform_event(ev);
+ }
+};
+
+void DisplayServerIPhone::touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y) {
+ if (!GLOBAL_DEF("debug/disable_touch", false)) {
+ Ref<InputEventScreenDrag> ev;
+ ev.instance();
+ ev->set_index(p_idx);
+ ev->set_position(Vector2(p_x, p_y));
+ ev->set_relative(Vector2(p_x - p_prev_x, p_y - p_prev_y));
+ perform_event(ev);
+ };
+};
+
+void 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
+
+void DisplayServerIPhone::key(uint32_t p_key, bool p_pressed) {
+ Ref<InputEventKey> ev;
+ ev.instance();
+ ev->set_echo(false);
+ ev->set_pressed(p_pressed);
+ ev->set_keycode(p_key);
+ ev->set_physical_keycode(p_key);
+ ev->set_unicode(p_key);
+ perform_event(ev);
+};
+
+// MARK: Motion
+
+void DisplayServerIPhone::update_gravity(float p_x, float p_y, float p_z) {
+ Input::get_singleton()->set_gravity(Vector3(p_x, p_y, p_z));
+};
+
+void DisplayServerIPhone::update_accelerometer(float p_x, float p_y, float p_z) {
+ // Found out the Z should not be negated! Pass as is!
+ Vector3 v_accelerometer = Vector3(
+ p_x / kDisplayServerIPhoneAcceleration,
+ p_y / kDisplayServerIPhoneAcceleration,
+ p_z / kDisplayServerIPhoneAcceleration);
+
+ Input::get_singleton()->set_accelerometer(v_accelerometer);
+};
+
+void DisplayServerIPhone::update_magnetometer(float p_x, float p_y, float p_z) {
+ Input::get_singleton()->set_magnetometer(Vector3(p_x, p_y, p_z));
+};
+
+void DisplayServerIPhone::update_gyroscope(float p_x, float p_y, float p_z) {
+ Input::get_singleton()->set_gyroscope(Vector3(p_x, p_y, p_z));
+};
+
+// MARK: -
+
+bool DisplayServerIPhone::has_feature(Feature p_feature) const {
+ switch (p_feature) {
+ // case FEATURE_CONSOLE_WINDOW:
+ // case FEATURE_CURSOR_SHAPE:
+ // case FEATURE_CUSTOM_CURSOR_SHAPE:
+ // case FEATURE_GLOBAL_MENU:
+ // case FEATURE_HIDPI:
+ // case FEATURE_ICON:
+ // case FEATURE_IME:
+ // case FEATURE_MOUSE:
+ // case FEATURE_MOUSE_WARP:
+ // case FEATURE_NATIVE_DIALOG:
+ // case FEATURE_NATIVE_ICON:
+ // case FEATURE_NATIVE_VIDEO:
+ // case FEATURE_WINDOW_TRANSPARENCY:
+ case FEATURE_CLIPBOARD:
+ case FEATURE_KEEP_SCREEN_ON:
+ case FEATURE_ORIENTATION:
+ case FEATURE_TOUCHSCREEN:
+ case FEATURE_VIRTUAL_KEYBOARD:
+ return true;
+ default:
+ return false;
+ }
+}
+
+String DisplayServerIPhone::get_name() const {
+ return "iPhone";
+}
+
+void DisplayServerIPhone::alert(const String &p_alert, const String &p_title) {
+ const CharString utf8_alert = p_alert.utf8();
+ const CharString utf8_title = p_title.utf8();
+ iOS::alert(utf8_alert.get_data(), utf8_title.get_data());
+}
+
+int DisplayServerIPhone::get_screen_count() const {
+ return 1;
+}
+
+Point2i DisplayServerIPhone::screen_get_position(int p_screen) const {
+ return Size2i();
+}
+
+Size2i DisplayServerIPhone::screen_get_size(int p_screen) const {
+ CALayer *layer = AppDelegate.viewController.godotView.renderingLayer;
+
+ if (!layer) {
+ return Size2i();
+ }
+
+ return Size2i(layer.bounds.size.width, layer.bounds.size.height) * screen_get_scale(p_screen);
+}
+
+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));
+ }
+}
+
+int DisplayServerIPhone::screen_get_dpi(int p_screen) const {
+ struct utsname systemInfo;
+ uname(&systemInfo);
+
+ NSString *string = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding];
+
+ NSDictionary *iOSModelToDPI = [GodotDeviceMetrics dpiList];
+
+ for (NSArray *keyArray in iOSModelToDPI) {
+ if ([keyArray containsObject:string]) {
+ NSNumber *value = iOSModelToDPI[keyArray];
+ return [value intValue];
+ }
+ }
+
+ // If device wasn't found in dictionary
+ // make a best guess from device metrics.
+ CGFloat scale = [UIScreen mainScreen].scale;
+
+ UIUserInterfaceIdiom idiom = [UIDevice currentDevice].userInterfaceIdiom;
+
+ switch (idiom) {
+ case UIUserInterfaceIdiomPad:
+ return scale == 2 ? 264 : 132;
+ case UIUserInterfaceIdiomPhone: {
+ if (scale == 3) {
+ CGFloat nativeScale = [UIScreen mainScreen].nativeScale;
+ return nativeScale == 3 ? 458 : 401;
+ }
+
+ return 326;
+ }
+ default:
+ return 72;
+ }
+}
+
+float DisplayServerIPhone::screen_get_scale(int p_screen) const {
+ return [UIScreen mainScreen].nativeScale;
+}
+
+Vector<DisplayServer::WindowID> DisplayServerIPhone::get_window_list() const {
+ Vector<DisplayServer::WindowID> list;
+ list.push_back(MAIN_WINDOW_ID);
+ return list;
+}
+
+DisplayServer::WindowID DisplayServerIPhone::get_window_at_screen_position(const Point2i &p_position) const {
+ return MAIN_WINDOW_ID;
+}
+
+void DisplayServerIPhone::window_attach_instance_id(ObjectID p_instance, WindowID p_window) {
+ window_attached_instance_id = p_instance;
+}
+
+ObjectID DisplayServerIPhone::window_get_attached_instance_id(WindowID p_window) const {
+ return window_attached_instance_id;
+}
+
+void DisplayServerIPhone::window_set_title(const String &p_title, WindowID p_window) {
+ // Probably not supported for iOS
+}
+
+int DisplayServerIPhone::window_get_current_screen(WindowID p_window) const {
+ return SCREEN_OF_MAIN_WINDOW;
+}
+
+void DisplayServerIPhone::window_set_current_screen(int p_screen, WindowID p_window) {
+ // Probably not supported for iOS
+}
+
+Point2i DisplayServerIPhone::window_get_position(WindowID p_window) const {
+ return Point2i();
+}
+
+void DisplayServerIPhone::window_set_position(const Point2i &p_position, WindowID p_window) {
+ // Probably not supported for single window iOS app
+}
+
+void DisplayServerIPhone::window_set_transient(WindowID p_window, WindowID p_parent) {
+ // Probably not supported for iOS
+}
+
+void DisplayServerIPhone::window_set_max_size(const Size2i p_size, WindowID p_window) {
+ // Probably not supported for iOS
+}
+
+Size2i DisplayServerIPhone::window_get_max_size(WindowID p_window) const {
+ return Size2i();
+}
+
+void DisplayServerIPhone::window_set_min_size(const Size2i p_size, WindowID p_window) {
+ // Probably not supported for iOS
+}
+
+Size2i DisplayServerIPhone::window_get_min_size(WindowID p_window) const {
+ return Size2i();
+}
+
+void DisplayServerIPhone::window_set_size(const Size2i p_size, WindowID p_window) {
+ // Probably not supported for iOS
+}
+
+Size2i DisplayServerIPhone::window_get_size(WindowID p_window) const {
+ CGRect screenBounds = [UIScreen mainScreen].bounds;
+ return Size2i(screenBounds.size.width, screenBounds.size.height) * screen_get_max_scale();
+}
+
+Size2i DisplayServerIPhone::window_get_real_size(WindowID p_window) const {
+ return window_get_size(p_window);
+}
+
+void DisplayServerIPhone::window_set_mode(WindowMode p_mode, WindowID p_window) {
+ // Probably not supported for iOS
+}
+
+DisplayServer::WindowMode DisplayServerIPhone::window_get_mode(WindowID p_window) const {
+ return WindowMode::WINDOW_MODE_FULLSCREEN;
+}
+
+bool DisplayServerIPhone::window_is_maximize_allowed(WindowID p_window) const {
+ return false;
+}
+
+void DisplayServerIPhone::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) {
+ // Probably not supported for iOS
+}
+
+bool DisplayServerIPhone::window_get_flag(WindowFlags p_flag, WindowID p_window) const {
+ return false;
+}
+
+void DisplayServerIPhone::window_request_attention(WindowID p_window) {
+ // Probably not supported for iOS
+}
+
+void DisplayServerIPhone::window_move_to_foreground(WindowID p_window) {
+ // Probably not supported for iOS
+}
+
+float DisplayServerIPhone::screen_get_max_scale() const {
+ return screen_get_scale(SCREEN_OF_MAIN_WINDOW);
+};
+
+void DisplayServerIPhone::screen_set_orientation(DisplayServer::ScreenOrientation p_orientation, int p_screen) {
+ screen_orientation = p_orientation;
+}
+
+DisplayServer::ScreenOrientation DisplayServerIPhone::screen_get_orientation(int p_screen) const {
+ return screen_orientation;
+}
+
+bool DisplayServerIPhone::window_can_draw(WindowID p_window) const {
+ return true;
+}
+
+bool DisplayServerIPhone::can_any_window_draw() const {
+ return true;
+}
+
+bool DisplayServerIPhone::screen_is_touchscreen(int p_screen) const {
+ return true;
+}
+
+void DisplayServerIPhone::virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, bool p_multiline, int p_max_length, int p_cursor_start, int p_cursor_end) {
+ NSString *existingString = [[NSString alloc] initWithUTF8String:p_existing_text.utf8().get_data()];
+
+ [AppDelegate.viewController.keyboardView
+ becomeFirstResponderWithString:existingString
+ multiline:p_multiline
+ cursorStart:p_cursor_start
+ cursorEnd:p_cursor_end];
+}
+
+void DisplayServerIPhone::virtual_keyboard_hide() {
+ [AppDelegate.viewController.keyboardView resignFirstResponder];
+}
+
+void DisplayServerIPhone::virtual_keyboard_set_height(int height) {
+ virtual_keyboard_height = height * screen_get_max_scale();
+}
+
+int DisplayServerIPhone::virtual_keyboard_get_height() const {
+ return virtual_keyboard_height;
+}
+
+void DisplayServerIPhone::clipboard_set(const String &p_text) {
+ [UIPasteboard generalPasteboard].string = [NSString stringWithUTF8String:p_text.utf8()];
+}
+
+String DisplayServerIPhone::clipboard_get() const {
+ NSString *text = [UIPasteboard generalPasteboard].string;
+
+ return String::utf8([text UTF8String]);
+}
+
+void DisplayServerIPhone::screen_set_keep_on(bool p_enable) {
+ [UIApplication sharedApplication].idleTimerDisabled = p_enable;
+}
+
+bool DisplayServerIPhone::screen_is_kept_on() const {
+ return [UIApplication sharedApplication].idleTimerDisabled;
+}
+
+Error DisplayServerIPhone::native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track, int p_screen) {
+ FileAccess *f = FileAccess::open(p_path, FileAccess::READ);
+ bool exists = f && f->is_open();
+
+ String user_data_dir = OSIPhone::get_singleton()->get_user_data_dir();
+
+ if (!exists) {
+ return FAILED;
+ }
+
+ String tempFile = OSIPhone::get_singleton()->get_user_data_dir();
+
+ if (p_path.begins_with("res://")) {
+ if (PackedData::get_singleton()->has_path(p_path)) {
+ printf("Unable to play %s using the native player as it resides in a .pck file\n", p_path.utf8().get_data());
+ return ERR_INVALID_PARAMETER;
+ } else {
+ p_path = p_path.replace("res:/", ProjectSettings::get_singleton()->get_resource_path());
+ }
+ } else if (p_path.begins_with("user://")) {
+ p_path = p_path.replace("user:/", user_data_dir);
+ }
+
+ memdelete(f);
+
+ printf("Playing video: %s\n", p_path.utf8().get_data());
+
+ String file_path = ProjectSettings::get_singleton()->globalize_path(p_path);
+
+ NSString *filePath = [[NSString alloc] initWithUTF8String:file_path.utf8().get_data()];
+ NSString *audioTrack = [NSString stringWithUTF8String:p_audio_track.utf8()];
+ NSString *subtitleTrack = [NSString stringWithUTF8String:p_subtitle_track.utf8()];
+
+ if (![AppDelegate.viewController playVideoAtPath:filePath
+ volume:p_volume
+ audio:audioTrack
+ subtitle:subtitleTrack]) {
+ return OK;
+ }
+
+ return FAILED;
+}
+
+bool DisplayServerIPhone::native_video_is_playing() const {
+ return [AppDelegate.viewController.videoView isVideoPlaying];
+}
+
+void DisplayServerIPhone::native_video_pause() {
+ if (native_video_is_playing()) {
+ [AppDelegate.viewController.videoView pauseVideo];
+ }
+}
+
+void DisplayServerIPhone::native_video_unpause() {
+ [AppDelegate.viewController.videoView unpauseVideo];
+};
+
+void DisplayServerIPhone::native_video_stop() {
+ if (native_video_is_playing()) {
+ [AppDelegate.viewController.videoView stopVideo];
+ }
+}
+
+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);
+ }
+ }
+#endif
+
+ Variant resize_rect = Rect2i(Point2i(), size);
+ _window_callback(window_resize_callback, resize_rect);
+}
diff --git a/platform/iphone/export/export.cpp b/platform/iphone/export/export.cpp
index 63c3cb8c23..c585c2afbe 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-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 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,19 +30,21 @@
#include "export.h"
+#include "core/config/project_settings.h"
#include "core/io/image_loader.h"
#include "core/io/marshalls.h"
#include "core/io/resource_saver.h"
#include "core/io/zip_io.h"
#include "core/os/file_access.h"
#include "core/os/os.h"
-#include "core/project_settings.h"
+#include "core/templates/safe_refcount.h"
#include "core/version.h"
#include "editor/editor_export.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "main/splash.gen.h"
#include "platform/iphone/logo.gen.h"
+#include "platform/iphone/plugin/godot_plugin_config.h"
#include "string.h"
#include <sys/stat.h>
@@ -54,6 +56,13 @@ class EditorExportPlatformIOS : public EditorExportPlatform {
Ref<ImageTexture> logo;
+ // Plugins
+ SafeFlag plugins_changed;
+ Thread check_for_changes_thread;
+ SafeFlag quit_request;
+ Mutex plugins_lock;
+ 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 _codesign(String p_file, void *p_userdata);
@@ -70,6 +79,7 @@ class EditorExportPlatformIOS : public EditorExportPlatform {
String modules_fileref;
String modules_buildphase;
String modules_buildgrp;
+ Vector<String> capabilities;
};
struct ExportArchitecture {
String name;
@@ -85,22 +95,26 @@ class EditorExportPlatformIOS : public EditorExportPlatform {
struct IOSExportAsset {
String exported_path;
- bool is_framework; // framework is anything linked to the binary, otherwise it's a resource
+ bool is_framework = false; // framework is anything linked to the binary, otherwise it's a resource
+ bool should_embed = false;
};
String _get_additional_plist_content();
String _get_linker_flags();
String _get_cpp_code();
void _fix_config_file(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &pfile, const IOSConfigData &p_config, bool p_debug);
- Error _export_loading_screens(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir);
+ Error _export_loading_screen_images(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir);
+ Error _export_loading_screen_file(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir);
Error _export_icons(const Ref<EditorExportPreset> &p_preset, const String &p_iconset_dir);
Vector<ExportArchitecture> _get_supported_architectures();
Vector<String> _get_preset_architectures(const Ref<EditorExportPreset> &p_preset);
void _add_assets_to_project(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_project_data, const Vector<IOSExportAsset> &p_additional_assets);
- Error _export_additional_assets(const String &p_out_dir, const Vector<String> &p_assets, bool p_is_framework, Vector<IOSExportAsset> &r_exported_assets);
+ Error _export_additional_assets(const String &p_out_dir, const Vector<String> &p_assets, bool p_is_framework, bool p_should_embed, Vector<IOSExportAsset> &r_exported_assets);
+ Error _copy_asset(const String &p_out_dir, const String &p_asset, const String *p_custom_file_name, bool p_is_framework, bool p_should_embed, Vector<IOSExportAsset> &r_exported_assets);
Error _export_additional_assets(const String &p_out_dir, const Vector<SharedObject> &p_libraries, Vector<IOSExportAsset> &r_exported_assets);
+ Error _export_ios_plugins(const Ref<EditorExportPreset> &p_preset, IOSConfigData &p_config_data, const String &dest_dir, Vector<IOSExportAsset> &r_exported_assets, bool p_debug);
bool is_package_name_valid(const String &p_package, String *r_error = nullptr) const {
String pname = p_package;
@@ -113,7 +127,7 @@ class EditorExportPlatformIOS : public EditorExportPlatform {
}
for (int i = 0; i < pname.length(); i++) {
- CharType c = pname[i];
+ char32_t c = pname[i];
if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '.')) {
if (r_error) {
*r_error = vformat(TTR("The character '%s' is not allowed in Identifier."), String::chr(c));
@@ -125,42 +139,162 @@ class EditorExportPlatformIOS : public EditorExportPlatform {
return true;
}
+ static void _check_for_changes_poll_thread(void *ud) {
+ EditorExportPlatformIOS *ea = (EditorExportPlatformIOS *)ud;
+
+ while (!ea->quit_request.is_set()) {
+ // Nothing to do if we already know the plugins have changed.
+ if (!ea->plugins_changed.is_set()) {
+ MutexLock lock(ea->plugins_lock);
+
+ Vector<PluginConfigIOS> loaded_plugins = get_plugins();
+
+ if (ea->plugins.size() != loaded_plugins.size()) {
+ ea->plugins_changed.set();
+ } else {
+ for (int i = 0; i < ea->plugins.size(); i++) {
+ if (ea->plugins[i].name != loaded_plugins[i].name || ea->plugins[i].last_updated != loaded_plugins[i].last_updated) {
+ ea->plugins_changed.set();
+ break;
+ }
+ }
+ }
+ }
+
+ uint64_t wait = 3000000;
+ uint64_t time = OS::get_singleton()->get_ticks_usec();
+ while (OS::get_singleton()->get_ticks_usec() - time < wait) {
+ OS::get_singleton()->delay_usec(300000);
+
+ if (ea->quit_request.is_set()) {
+ break;
+ }
+ }
+ }
+ }
+
protected:
- virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features);
- virtual void get_export_options(List<ExportOption> *r_options);
+ virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) override;
+ virtual void get_export_options(List<ExportOption> *r_options) override;
public:
- virtual String get_name() const { return "iOS"; }
- virtual String get_os_name() const { return "iOS"; }
- virtual Ref<Texture2D> get_logo() const { return logo; }
+ virtual String get_name() const override { return "iOS"; }
+ virtual String get_os_name() const override { return "iOS"; }
+ virtual Ref<Texture2D> get_logo() const override { return logo; }
+
+ virtual bool should_update_export_options() override {
+ bool export_options_changed = plugins_changed.is_set();
+ if (export_options_changed) {
+ // don't clear unless we're reporting true, to avoid race
+ plugins_changed.clear();
+ }
+ return export_options_changed;
+ }
- virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const {
+ virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override {
List<String> list;
list.push_back("ipa");
return list;
}
- virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0);
- virtual void add_module_code(const Ref<EditorExportPreset> &p_preset, IOSConfigData &p_config_data, const String &p_name, const String &p_fid, const String &p_gid);
+ virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
- virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const;
+ virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override;
- virtual void get_platform_features(List<String> *r_features) {
+ virtual void get_platform_features(List<String> *r_features) override {
r_features->push_back("mobile");
r_features->push_back("iOS");
}
- virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) {
+ virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) override {
}
EditorExportPlatformIOS();
~EditorExportPlatformIOS();
+
+ /// 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) {
+ da->list_dir_begin();
+ while (true) {
+ String file = da->get_next();
+ if (file.is_empty()) {
+ break;
+ }
+
+ if (file == "." || file == "..") {
+ continue;
+ }
+
+ if (da->current_is_hidden()) {
+ continue;
+ }
+
+ if (da->current_is_dir()) {
+ if (p_check_directories) {
+ Vector<String> directory_files = list_plugin_config_files(p_path.plus_file(file), false);
+ for (int i = 0; i < directory_files.size(); ++i) {
+ dir_files.push_back(file.plus_file(directory_files[i]));
+ }
+ }
+
+ continue;
+ }
+
+ if (file.ends_with(PluginConfigIOS::PLUGIN_CONFIG_EXT)) {
+ dir_files.push_back(file);
+ }
+ }
+ da->list_dir_end();
+ }
+
+ return dir_files;
+ }
+
+ static Vector<PluginConfigIOS> get_plugins() {
+ Vector<PluginConfigIOS> loaded_plugins;
+
+ String plugins_dir = ProjectSettings::get_singleton()->get_resource_path().plus_file("ios/plugins");
+
+ if (DirAccess::exists(plugins_dir)) {
+ Vector<String> plugins_filenames = list_plugin_config_files(plugins_dir, true);
+
+ if (!plugins_filenames.is_empty()) {
+ Ref<ConfigFile> config_file = memnew(ConfigFile);
+ for (int i = 0; i < plugins_filenames.size(); i++) {
+ PluginConfigIOS config = load_plugin_config(config_file, plugins_dir.plus_file(plugins_filenames[i]));
+ if (config.valid_config) {
+ loaded_plugins.push_back(config);
+ } else {
+ print_error("Invalid plugin config file " + plugins_filenames[i]);
+ }
+ }
+ }
+ }
+
+ return loaded_plugins;
+ }
+
+ static Vector<PluginConfigIOS> get_enabled_plugins(const Ref<EditorExportPreset> &p_presets) {
+ Vector<PluginConfigIOS> enabled_plugins;
+ Vector<PluginConfigIOS> all_plugins = get_plugins();
+ for (int i = 0; i < all_plugins.size(); i++) {
+ PluginConfigIOS plugin = all_plugins[i];
+ bool enabled = p_presets->get("plugins/" + plugin.name);
+ if (enabled) {
+ enabled_plugins.push_back(plugin);
+ }
+ }
+
+ return enabled_plugins;
+ }
};
void EditorExportPlatformIOS::get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) {
- String driver = ProjectSettings::get_singleton()->get("rendering/quality/driver/driver_name");
- if (driver == "GLES2") {
- r_features->push_back("etc");
- } else if (driver == "Vulkan") {
+ 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");
}
@@ -181,9 +315,9 @@ Vector<EditorExportPlatformIOS::ExportArchitecture> EditorExportPlatformIOS::_ge
struct LoadingScreenInfo {
const char *preset_key;
const char *export_name;
- int width;
- int height;
- bool rotate;
+ int width = 0;
+ int height = 0;
+ bool rotate = false;
};
static const LoadingScreenInfo loading_screen_infos[] = {
@@ -205,30 +339,36 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options)
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), ""));
+ 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::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::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"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/export_method_release", PROPERTY_HINT_ENUM, "App Store,Development,Ad-Hoc,Enterprise"), 0));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/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/identifier", PROPERTY_HINT_PLACEHOLDER_TEXT, "com.example.game"), ""));
+ 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"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/arkit"), false));
- r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/camera"), false));
+ 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));
+ }
+ plugins_changed.clear();
+ plugins = found_plugins;
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/access_wifi"), false));
- r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/game_center"), true));
- r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/in_app_purchases"), false));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/push_notifications"), false));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "user_data/accessible_from_files_app"), false));
@@ -255,16 +395,18 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options)
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::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));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "storyboard/custom_image@2x", PROPERTY_HINT_FILE, "*.png"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "storyboard/custom_image@3x", PROPERTY_HINT_FILE, "*.png"), ""));
+ 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"), ""));
}
-
- 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));
- }
}
void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &pfile, const IOSConfigData &p_config, bool p_debug) {
@@ -274,6 +416,12 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_
"ad-hoc",
"enterprise"
};
+ static const String storyboard_image_scale_mode[] = {
+ "center",
+ "scaleAspectFit",
+ "scaleAspectFill",
+ "scaleToFill"
+ };
String str;
String strnew;
str.parse_utf8((const char *)pfile.ptr(), pfile.size());
@@ -293,8 +441,8 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_
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("$identifier") != -1) {
- strnew += lines[i].replace("$identifier", p_preset->get("application/identifier")) + "\n";
+ } else if (lines[i].find("$bundle_identifier") != -1) {
+ strnew += lines[i].replace("$bundle_identifier", p_preset->get("application/bundle_identifier")) + "\n";
} else if (lines[i].find("$short_version") != -1) {
strnew += lines[i].replace("$short_version", p_preset->get("application/short_version")) + "\n";
} else if (lines[i].find("$version") != -1) {
@@ -331,33 +479,23 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_
strnew += lines[i].replace("$docs_in_place", ((bool)p_preset->get("user_data/accessible_from_files_app")) ? "<true/>" : "<false/>") + "\n";
} else if (lines[i].find("$docs_sharing") != -1) {
strnew += lines[i].replace("$docs_sharing", ((bool)p_preset->get("user_data/accessible_from_itunes_sharing")) ? "<true/>" : "<false/>") + "\n";
- } else if (lines[i].find("$access_wifi") != -1) {
- bool is_on = p_preset->get("capabilities/access_wifi");
- strnew += lines[i].replace("$access_wifi", is_on ? "1" : "0") + "\n";
- } else if (lines[i].find("$game_center") != -1) {
- bool is_on = p_preset->get("capabilities/game_center");
- strnew += lines[i].replace("$game_center", is_on ? "1" : "0") + "\n";
- } else if (lines[i].find("$in_app_purchases") != -1) {
- bool is_on = p_preset->get("capabilities/in_app_purchases");
- strnew += lines[i].replace("$in_app_purchases", is_on ? "1" : "0") + "\n";
- } else if (lines[i].find("$push_notifications") != -1) {
+ } else if (lines[i].find("$entitlements_push_notifications") != -1) {
bool is_on = p_preset->get("capabilities/push_notifications");
- strnew += lines[i].replace("$push_notifications", is_on ? "1" : "0") + "\n";
+ strnew += lines[i].replace("$entitlements_push_notifications", is_on ? "<key>aps-environment</key><string>development</string>" : "") + "\n";
} else if (lines[i].find("$required_device_capabilities") != -1) {
String capabilities;
// I've removed armv7 as we can run on 64bit only devices
// Note that capabilities listed here are requirements for the app to be installed.
// They don't enable anything.
+ Vector<String> capabilities_list = p_config.capabilities;
- if ((bool)p_preset->get("capabilities/arkit")) {
- capabilities += "<string>arkit</string>\n";
- }
- if ((bool)p_preset->get("capabilities/game_center")) {
- capabilities += "<string>gamekit</string>\n";
+ if ((bool)p_preset->get("capabilities/access_wifi") && !capabilities_list.has("wifi")) {
+ capabilities_list.push_back("wifi");
}
- if ((bool)p_preset->get("capabilities/access_wifi")) {
- capabilities += "<string>wifi</string>\n";
+
+ for (int idx = 0; idx < capabilities_list.size(); idx++) {
+ capabilities += "<string>" + capabilities_list[idx] + "</string>\n";
}
strnew += lines[i].replace("$required_device_capabilities", capabilities);
@@ -387,6 +525,60 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_
} else if (lines[i].find("$photolibrary_usage_description") != -1) {
String description = p_preset->get("privacy/photolibrary_usage_description");
strnew += lines[i].replace("$photolibrary_usage_description", description) + "\n";
+ } else if (lines[i].find("$plist_launch_screen_name") != -1) {
+ bool is_on = p_preset->get("storyboard/use_launch_screen_storyboard");
+ String value = is_on ? "<key>UILaunchStoryboardName</key>\n<string>Launch Screen</string>" : "";
+ strnew += lines[i].replace("$plist_launch_screen_name", value) + "\n";
+ } else if (lines[i].find("$pbx_launch_screen_file_reference") != -1) {
+ bool is_on = p_preset->get("storyboard/use_launch_screen_storyboard");
+ String value = is_on ? "90DD2D9D24B36E8000717FE1 = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = \"Launch Screen.storyboard\"; sourceTree = \"<group>\"; };" : "";
+ strnew += lines[i].replace("$pbx_launch_screen_file_reference", value) + "\n";
+ } else if (lines[i].find("$pbx_launch_screen_copy_files") != -1) {
+ bool is_on = p_preset->get("storyboard/use_launch_screen_storyboard");
+ String value = is_on ? "90DD2D9D24B36E8000717FE1 /* Launch Screen.storyboard */," : "";
+ strnew += lines[i].replace("$pbx_launch_screen_copy_files", value) + "\n";
+ } else if (lines[i].find("$pbx_launch_screen_build_phase") != -1) {
+ bool is_on = p_preset->get("storyboard/use_launch_screen_storyboard");
+ String value = is_on ? "90DD2D9E24B36E8000717FE1 /* Launch Screen.storyboard in Resources */," : "";
+ strnew += lines[i].replace("$pbx_launch_screen_build_phase", value) + "\n";
+ } else if (lines[i].find("$pbx_launch_screen_build_reference") != -1) {
+ bool is_on = p_preset->get("storyboard/use_launch_screen_storyboard");
+ String value = is_on ? "90DD2D9E24B36E8000717FE1 /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 90DD2D9D24B36E8000717FE1 /* Launch Screen.storyboard */; };" : "";
+ strnew += lines[i].replace("$pbx_launch_screen_build_reference", value) + "\n";
+ } else if (lines[i].find("$pbx_launch_image_usage_setting") != -1) {
+ bool is_on = p_preset->get("storyboard/use_launch_screen_storyboard");
+ String value = is_on ? "" : "ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;";
+ strnew += lines[i].replace("$pbx_launch_image_usage_setting", value) + "\n";
+ } else if (lines[i].find("$launch_screen_image_mode") != -1) {
+ int image_scale_mode = p_preset->get("storyboard/image_scale_mode");
+ String value;
+
+ switch (image_scale_mode) {
+ case 0: {
+ String logo_path = ProjectSettings::get_singleton()->get("application/boot_splash/image");
+ bool is_on = ProjectSettings::get_singleton()->get("application/boot_splash/fullsize");
+ // If custom logo is not specified, Godot does not scale default one, so we should do the same.
+ value = (is_on && logo_path.length() > 0) ? "scaleAspectFit" : "center";
+ } break;
+ default: {
+ value = storyboard_image_scale_mode[image_scale_mode - 1];
+ }
+ }
+
+ strnew += lines[i].replace("$launch_screen_image_mode", value) + "\n";
+ } else if (lines[i].find("$launch_screen_background_color") != -1) {
+ bool use_custom = p_preset->get("storyboard/use_custom_bg_color");
+ Color color = use_custom ? p_preset->get("storyboard/custom_bg_color") : ProjectSettings::get_singleton()->get("application/boot_splash/bg_color");
+ const String value_format = "red=\"$red\" green=\"$green\" blue=\"$blue\" alpha=\"$alpha\"";
+
+ Dictionary value_dictionary;
+ value_dictionary["red"] = color.r;
+ value_dictionary["green"] = color.g;
+ value_dictionary["blue"] = color.b;
+ value_dictionary["alpha"] = color.a;
+ String value = value_format.format(value_dictionary, "$_");
+
+ strnew += lines[i].replace("$launch_screen_background_color", value) + "\n";
} else {
strnew += lines[i] + "\n";
}
@@ -476,7 +668,7 @@ struct IconInfo {
const char *actual_size_side;
const char *scale;
const char *unscaled_size;
- bool is_required;
+ bool is_required = false;
};
static const IconInfo icon_infos[] = {
@@ -496,7 +688,6 @@ static const IconInfo icon_infos[] = {
{ "optional_icons/spotlight_80x80", "iphone", "Icon-80.png", "80", "2x", "40x40", false },
{ "optional_icons/spotlight_80x80", "ipad", "Icon-80.png", "80", "2x", "40x40", false }
-
};
Error EditorExportPlatformIOS::_export_icons(const Ref<EditorExportPreset> &p_preset, const String &p_iconset_dir) {
@@ -588,7 +779,75 @@ Error EditorExportPlatformIOS::_export_icons(const Ref<EditorExportPreset> &p_pr
return OK;
}
-Error EditorExportPlatformIOS::_export_loading_screens(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir) {
+Error EditorExportPlatformIOS::_export_loading_screen_file(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir) {
+ const String custom_launch_image_2x = p_preset->get("storyboard/custom_image@2x");
+ const String custom_launch_image_3x = p_preset->get("storyboard/custom_image@3x");
+
+ if (custom_launch_image_2x.length() > 0 && custom_launch_image_3x.length() > 0) {
+ Ref<Image> image;
+ String image_path = p_dest_dir.plus_file("splash@2x.png");
+ image.instance();
+ Error err = image->load(custom_launch_image_2x);
+
+ if (err) {
+ image.unref();
+ return err;
+ }
+
+ if (image->save_png(image_path) != OK) {
+ return ERR_FILE_CANT_WRITE;
+ }
+
+ image.unref();
+ image_path = p_dest_dir.plus_file("splash@3x.png");
+ image.instance();
+ err = image->load(custom_launch_image_3x);
+
+ if (err) {
+ image.unref();
+ return err;
+ }
+
+ if (image->save_png(image_path) != OK) {
+ return ERR_FILE_CANT_WRITE;
+ }
+ } else {
+ Ref<Image> splash;
+
+ const String splash_path = ProjectSettings::get_singleton()->get("application/boot_splash/image");
+
+ if (!splash_path.is_empty()) {
+ splash.instance();
+ const Error err = splash->load(splash_path);
+ if (err) {
+ splash.unref();
+ }
+ }
+
+ if (splash.is_null()) {
+ splash = Ref<Image>(memnew(Image(boot_splash_png)));
+ }
+
+ // Using same image for both @2x and @3x
+ // because Godot's own boot logo uses single image for all resolutions.
+ // Also not using @1x image, because devices using this image variant
+ // are not supported by iOS 9, which is minimal target.
+ const String splash_png_path_2x = p_dest_dir.plus_file("splash@2x.png");
+ const String splash_png_path_3x = p_dest_dir.plus_file("splash@3x.png");
+
+ if (splash->save_png(splash_png_path_2x) != OK) {
+ return ERR_FILE_CANT_WRITE;
+ }
+
+ if (splash->save_png(splash_png_path_3x) != OK) {
+ return ERR_FILE_CANT_WRITE;
+ }
+ }
+
+ return OK;
+}
+
+Error EditorExportPlatformIOS::_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 + "'.");
@@ -704,7 +963,7 @@ Error EditorExportPlatformIOS::_walk_dir_recursive(DirAccess *p_da, FileHandler
struct CodesignData {
const Ref<EditorExportPreset> &preset;
- bool debug;
+ bool debug = false;
CodesignData(const Ref<EditorExportPreset> &p_preset, bool p_debug) :
preset(p_preset),
@@ -721,7 +980,7 @@ Error EditorExportPlatformIOS::_codesign(String p_file, void *p_userdata) {
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(p_file);
- return OS::get_singleton()->execute("codesign", codesign_args, true);
+ return OS::get_singleton()->execute("codesign", codesign_args);
}
return OK;
}
@@ -773,15 +1032,6 @@ struct ExportLibsData {
};
void EditorExportPlatformIOS::_add_assets_to_project(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_project_data, const Vector<IOSExportAsset> &p_additional_assets) {
- Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins();
- Vector<String> frameworks;
- for (int i = 0; i < export_plugins.size(); ++i) {
- Vector<String> plugin_frameworks = export_plugins[i]->get_ios_frameworks();
- for (int j = 0; j < plugin_frameworks.size(); ++j) {
- frameworks.push_back(plugin_frameworks[j]);
- }
- }
-
// that is just a random number, we just need Godot IDs not to clash with
// existing IDs in the project.
PbxId current_id = { 0x58938401, 0, 0 };
@@ -790,17 +1040,37 @@ void EditorExportPlatformIOS::_add_assets_to_project(const Ref<EditorExportPrese
String pbx_frameworks_refs;
String pbx_resources_build;
String pbx_resources_refs;
+ String pbx_embeded_frameworks;
const String file_info_format = String("$build_id = {isa = PBXBuildFile; fileRef = $ref_id; };\n") +
"$ref_id = {isa = PBXFileReference; lastKnownFileType = $file_type; name = \"$name\"; path = \"$file_path\"; sourceTree = \"<group>\"; };\n";
+
for (int i = 0; i < p_additional_assets.size(); ++i) {
+ String additional_asset_info_format = file_info_format;
+
String build_id = (++current_id).str();
String ref_id = (++current_id).str();
+ String framework_id = "";
+
const IOSExportAsset &asset = p_additional_assets[i];
String type;
if (asset.exported_path.ends_with(".framework")) {
+ if (asset.should_embed) {
+ additional_asset_info_format += "$framework_id = {isa = PBXBuildFile; fileRef = $ref_id; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };\n";
+ framework_id = (++current_id).str();
+ pbx_embeded_frameworks += framework_id + ",\n";
+ }
+
type = "wrapper.framework";
+ } else if (asset.exported_path.ends_with(".xcframework")) {
+ if (asset.should_embed) {
+ additional_asset_info_format += "$framework_id = {isa = PBXBuildFile; fileRef = $ref_id; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };\n";
+ framework_id = (++current_id).str();
+ pbx_embeded_frameworks += framework_id + ",\n";
+ }
+
+ type = "wrapper.xcframework";
} else if (asset.exported_path.ends_with(".dylib")) {
type = "compiled.mach-o.dylib";
} else if (asset.exported_path.ends_with(".a")) {
@@ -825,40 +1095,22 @@ void EditorExportPlatformIOS::_add_assets_to_project(const Ref<EditorExportPrese
format_dict["name"] = asset.exported_path.get_file();
format_dict["file_path"] = asset.exported_path;
format_dict["file_type"] = type;
- pbx_files += file_info_format.format(format_dict, "$_");
+ if (framework_id.length() > 0) {
+ format_dict["framework_id"] = framework_id;
+ }
+ pbx_files += additional_asset_info_format.format(format_dict, "$_");
}
// Note, frameworks like gamekit are always included in our project.pbxprof file
// even if turned off in capabilities.
- // We do need our ARKit framework
- if ((bool)p_preset->get("capabilities/arkit")) {
- String build_id = (++current_id).str();
- String ref_id = (++current_id).str();
-
- if (pbx_frameworks_build.length() > 0) {
- pbx_frameworks_build += ",\n";
- pbx_frameworks_refs += ",\n";
- }
-
- pbx_frameworks_build += build_id;
- pbx_frameworks_refs += ref_id;
-
- Dictionary format_dict;
- format_dict["build_id"] = build_id;
- format_dict["ref_id"] = ref_id;
- format_dict["name"] = "ARKit.framework";
- format_dict["file_path"] = "System/Library/Frameworks/ARKit.framework";
- format_dict["file_type"] = "wrapper.framework";
- pbx_files += file_info_format.format(format_dict, "$_");
- }
-
String str = String::utf8((const char *)p_project_data.ptr(), p_project_data.size());
str = str.replace("$additional_pbx_files", pbx_files);
str = str.replace("$additional_pbx_frameworks_build", pbx_frameworks_build);
str = str.replace("$additional_pbx_frameworks_refs", pbx_frameworks_refs);
str = str.replace("$additional_pbx_resources_build", pbx_resources_build);
str = str.replace("$additional_pbx_resources_refs", pbx_resources_refs);
+ str = str.replace("$pbx_embeded_frameworks", pbx_embeded_frameworks);
CharString cs = str.utf8();
p_project_data.resize(cs.size() - 1);
@@ -867,51 +1119,175 @@ void EditorExportPlatformIOS::_add_assets_to_project(const Ref<EditorExportPrese
}
}
-Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir, const Vector<String> &p_assets, bool p_is_framework, Vector<IOSExportAsset> &r_exported_assets) {
+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);
+ ERR_FAIL_V_MSG(ERR_CANT_CREATE, "Can't create directory: " + p_asset + ".");
+ }
+ bool file_exists = da->file_exists(p_asset);
+ bool dir_exists = da->dir_exists(p_asset);
+ if (!file_exists && !dir_exists) {
+ memdelete(da);
+ memdelete(filesystem_da);
+ return ERR_FILE_NOT_FOUND;
+ }
+
+ String base_dir = p_asset.get_base_dir().replace("res://", "");
+ String destination_dir;
+ String destination;
+ String asset_path;
+
+ bool create_framework = false;
+
+ if (p_is_framework && p_asset.ends_with(".dylib")) {
+ // For iOS we need to turn .dylib into .framework
+ // to be able to send application to AppStore
+ asset_path = String("dylibs").plus_file(base_dir);
+
+ String file_name;
+
+ if (!p_custom_file_name) {
+ file_name = p_asset.get_basename().get_file();
+ } else {
+ file_name = *p_custom_file_name;
+ }
+
+ String framework_name = file_name + ".framework";
+
+ asset_path = asset_path.plus_file(framework_name);
+ destination_dir = p_out_dir.plus_file(asset_path);
+ destination = destination_dir.plus_file(file_name);
+ create_framework = true;
+ } else if (p_is_framework && (p_asset.ends_with(".framework") || p_asset.ends_with(".xcframework"))) {
+ asset_path = String("dylibs").plus_file(base_dir);
+
+ String file_name;
+
+ if (!p_custom_file_name) {
+ file_name = p_asset.get_file();
+ } else {
+ file_name = *p_custom_file_name;
+ }
+
+ asset_path = asset_path.plus_file(file_name);
+ destination_dir = p_out_dir.plus_file(asset_path);
+ destination = destination_dir;
+ } else {
+ asset_path = base_dir;
+
+ String file_name;
+
+ if (!p_custom_file_name) {
+ file_name = p_asset.get_file();
+ } else {
+ file_name = *p_custom_file_name;
+ }
+
+ destination_dir = p_out_dir.plus_file(asset_path);
+ asset_path = asset_path.plus_file(file_name);
+ destination = p_out_dir.plus_file(asset_path);
+ }
+
+ if (!filesystem_da->dir_exists(destination_dir)) {
+ Error make_dir_err = filesystem_da->make_dir_recursive(destination_dir);
+ if (make_dir_err) {
+ memdelete(da);
+ memdelete(filesystem_da);
+ return make_dir_err;
+ }
+ }
+
+ Error err = dir_exists ? da->copy_dir(p_asset, destination) : da->copy(p_asset, destination);
+ memdelete(da);
+ if (err) {
+ memdelete(filesystem_da);
+ return err;
+ }
+ IOSExportAsset exported_asset = { binary_name.plus_file(asset_path), p_is_framework, p_should_embed };
+ r_exported_assets.push_back(exported_asset);
+
+ if (create_framework) {
+ String file_name;
+
+ if (!p_custom_file_name) {
+ file_name = p_asset.get_basename().get_file();
+ } else {
+ file_name = *p_custom_file_name;
+ }
+
+ String framework_name = file_name + ".framework";
+
+ // Performing `install_name_tool -id @rpath/{name}.framework/{name} ./{name}` on dylib
+ {
+ List<String> install_name_args;
+ install_name_args.push_back("-id");
+ install_name_args.push_back(String("@rpath").plus_file(framework_name).plus_file(file_name));
+ install_name_args.push_back(destination);
+
+ OS::get_singleton()->execute("install_name_tool", install_name_args);
+ }
+
+ // Creating Info.plist
+ {
+ String info_plist_format = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
+ "<plist version=\"1.0\">\n"
+ "<dict>\n"
+ "<key>CFBundleShortVersionString</key>\n"
+ "<string>1.0</string>\n"
+ "<key>CFBundleIdentifier</key>\n"
+ "<string>com.gdnative.framework.$name</string>\n"
+ "<key>CFBundleName</key>\n"
+ "<string>$name</string>\n"
+ "<key>CFBundleExecutable</key>\n"
+ "<string>$name</string>\n"
+ "<key>DTPlatformName</key>\n"
+ "<string>iphoneos</string>\n"
+ "<key>CFBundleInfoDictionaryVersion</key>\n"
+ "<string>6.0</string>\n"
+ "<key>CFBundleVersion</key>\n"
+ "<string>1</string>\n"
+ "<key>CFBundlePackageType</key>\n"
+ "<string>FMWK</string>\n"
+ "<key>MinimumOSVersion</key>\n"
+ "<string>10.0</string>\n"
+ "</dict>\n"
+ "</plist>";
+
+ String info_plist = info_plist_format.replace("$name", file_name);
+
+ FileAccess *f = FileAccess::open(destination_dir.plus_file("Info.plist"), FileAccess::WRITE);
+ if (f) {
+ f->store_string(info_plist);
+ f->close();
+ memdelete(f);
+ }
+ }
+ }
+
+ memdelete(filesystem_da);
+
+ return OK;
+}
+
+Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir, const Vector<String> &p_assets, bool p_is_framework, bool p_should_embed, Vector<IOSExportAsset> &r_exported_assets) {
for (int f_idx = 0; f_idx < p_assets.size(); ++f_idx) {
String asset = p_assets[f_idx];
if (!asset.begins_with("res://")) {
// either SDK-builtin or already a part of the export template
- IOSExportAsset exported_asset = { asset, p_is_framework };
+ IOSExportAsset exported_asset = { asset, p_is_framework, p_should_embed };
r_exported_assets.push_back(exported_asset);
} else {
- DirAccess *da = DirAccess::create_for_path(asset);
- if (!da) {
- memdelete(filesystem_da);
- ERR_FAIL_V_MSG(ERR_CANT_CREATE, "Can't create directory: " + asset + ".");
- }
- bool file_exists = da->file_exists(asset);
- bool dir_exists = da->dir_exists(asset);
- if (!file_exists && !dir_exists) {
- memdelete(da);
- memdelete(filesystem_da);
- return ERR_FILE_NOT_FOUND;
- }
- String additional_dir = p_is_framework && asset.ends_with(".dylib") ? "/dylibs/" : "/";
- String destination_dir = p_out_dir + additional_dir + asset.get_base_dir().replace("res://", "");
- 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;
- }
- }
-
- String destination = destination_dir.plus_file(asset.get_file());
- Error err = dir_exists ? da->copy_dir(asset, destination) : da->copy(asset, destination);
- memdelete(da);
- if (err) {
- memdelete(filesystem_da);
- return err;
- }
- IOSExportAsset exported_asset = { destination, p_is_framework };
- r_exported_assets.push_back(exported_asset);
+ Error err = _copy_asset(p_out_dir, asset, nullptr, p_is_framework, p_should_embed, r_exported_assets);
+ ERR_FAIL_COND_V(err, err);
}
}
- memdelete(filesystem_da);
return OK;
}
@@ -919,19 +1295,23 @@ Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir
Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir, const Vector<SharedObject> &p_libraries, Vector<IOSExportAsset> &r_exported_assets) {
Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins();
for (int i = 0; i < export_plugins.size(); i++) {
- Vector<String> frameworks = export_plugins[i]->get_ios_frameworks();
- Error err = _export_additional_assets(p_out_dir, frameworks, true, r_exported_assets);
+ Vector<String> linked_frameworks = export_plugins[i]->get_ios_frameworks();
+ Error err = _export_additional_assets(p_out_dir, linked_frameworks, true, false, r_exported_assets);
+ ERR_FAIL_COND_V(err, err);
+
+ Vector<String> embedded_frameworks = export_plugins[i]->get_ios_embedded_frameworks();
+ err = _export_additional_assets(p_out_dir, embedded_frameworks, true, true, r_exported_assets);
ERR_FAIL_COND_V(err, err);
Vector<String> project_static_libs = export_plugins[i]->get_ios_project_static_libs();
for (int j = 0; j < project_static_libs.size(); j++) {
project_static_libs.write[j] = project_static_libs[j].get_file(); // Only the file name as it's copied to the project
}
- err = _export_additional_assets(p_out_dir, project_static_libs, true, r_exported_assets);
+ err = _export_additional_assets(p_out_dir, project_static_libs, true, true, r_exported_assets);
ERR_FAIL_COND_V(err, err);
Vector<String> ios_bundle_files = export_plugins[i]->get_ios_bundle_files();
- err = _export_additional_assets(p_out_dir, ios_bundle_files, false, r_exported_assets);
+ err = _export_additional_assets(p_out_dir, ios_bundle_files, false, false, r_exported_assets);
ERR_FAIL_COND_V(err, err);
}
@@ -939,7 +1319,7 @@ Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir
for (int i = 0; i < p_libraries.size(); ++i) {
library_paths.push_back(p_libraries[i].path);
}
- Error err = _export_additional_assets(p_out_dir, library_paths, true, r_exported_assets);
+ Error err = _export_additional_assets(p_out_dir, library_paths, true, true, r_exported_assets);
ERR_FAIL_COND_V(err, err);
return OK;
@@ -957,20 +1337,198 @@ Vector<String> EditorExportPlatformIOS::_get_preset_architectures(const Ref<Edit
return enabled_archs;
}
-void EditorExportPlatformIOS::add_module_code(const Ref<EditorExportPreset> &p_preset, EditorExportPlatformIOS::IOSConfigData &p_config_data, const String &p_name, const String &p_fid, const String &p_gid) {
- if ((bool)p_preset->get("capabilities/" + p_name)) {
- //add module static library
- print_line("ADDING MODULE: " + p_name);
+Error EditorExportPlatformIOS::_export_ios_plugins(const Ref<EditorExportPreset> &p_preset, IOSConfigData &p_config_data, const String &dest_dir, Vector<IOSExportAsset> &r_exported_assets, bool p_debug) {
+ String plugin_definition_cpp_code;
+ String plugin_initialization_cpp_code;
+ String plugin_deinitialization_cpp_code;
- p_config_data.modules_buildfile += p_gid + " /* libgodot_" + p_name + "_module.a in Frameworks */ = {isa = PBXBuildFile; fileRef = " + p_fid + " /* libgodot_" + p_name + "_module.a */; };\n\t\t";
- p_config_data.modules_fileref += p_fid + " /* libgodot_" + p_name + "_module.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = godot_" + p_name + "_module ; path = \"libgodot_" + p_name + "_module.a\"; sourceTree = \"<group>\"; };\n\t\t";
- p_config_data.modules_buildphase += p_gid + " /* libgodot_" + p_name + "_module.a */,\n\t\t\t\t";
- p_config_data.modules_buildgrp += p_fid + " /* libgodot_" + p_name + "_module.a */,\n\t\t\t\t";
- } else {
- //add stub function for disabled module
- p_config_data.cpp_code += "void register_" + p_name + "_types() { /*stub*/ };\n";
- p_config_data.cpp_code += "void unregister_" + p_name + "_types() { /*stub*/ };\n";
+ Vector<String> plugin_linked_dependencies;
+ Vector<String> plugin_embedded_dependencies;
+ Vector<String> plugin_files;
+
+ Vector<PluginConfigIOS> enabled_plugins = get_enabled_plugins(p_preset);
+
+ Vector<String> added_linked_dependenciy_names;
+ Vector<String> added_embedded_dependenciy_names;
+ HashMap<String, String> plist_values;
+
+ Set<String> plugin_linker_flags;
+
+ Error err;
+
+ for (int i = 0; i < enabled_plugins.size(); i++) {
+ PluginConfigIOS plugin = enabled_plugins[i];
+
+ // Export plugin binary.
+ String plugin_main_binary = get_plugin_main_binary(plugin, p_debug);
+ String plugin_binary_result_file = plugin.binary.get_file();
+ // We shouldn't embed .xcframework that contains static libraries.
+ // Static libraries are not embedded anyway.
+ err = _copy_asset(dest_dir, plugin_main_binary, &plugin_binary_result_file, true, false, r_exported_assets);
+
+ ERR_FAIL_COND_V(err, err);
+
+ // Adding dependencies.
+ // Use separate container for names to check for duplicates.
+ for (int j = 0; j < plugin.linked_dependencies.size(); j++) {
+ String dependency = plugin.linked_dependencies[j];
+ String name = dependency.get_file();
+
+ if (added_linked_dependenciy_names.has(name)) {
+ continue;
+ }
+
+ added_linked_dependenciy_names.push_back(name);
+ plugin_linked_dependencies.push_back(dependency);
+ }
+
+ for (int j = 0; j < plugin.system_dependencies.size(); j++) {
+ String dependency = plugin.system_dependencies[j];
+ String name = dependency.get_file();
+
+ if (added_linked_dependenciy_names.has(name)) {
+ continue;
+ }
+
+ added_linked_dependenciy_names.push_back(name);
+ plugin_linked_dependencies.push_back(dependency);
+ }
+
+ for (int j = 0; j < plugin.embedded_dependencies.size(); j++) {
+ String dependency = plugin.embedded_dependencies[j];
+ String name = dependency.get_file();
+
+ if (added_embedded_dependenciy_names.has(name)) {
+ continue;
+ }
+
+ added_embedded_dependenciy_names.push_back(name);
+ plugin_embedded_dependencies.push_back(dependency);
+ }
+
+ plugin_files.append_array(plugin.files_to_copy);
+
+ // Capabilities
+ // Also checking for duplicates.
+ for (int j = 0; j < plugin.capabilities.size(); j++) {
+ String capability = plugin.capabilities[j];
+
+ if (p_config_data.capabilities.has(capability)) {
+ continue;
+ }
+
+ p_config_data.capabilities.push_back(capability);
+ }
+
+ // Linker flags
+ // Checking duplicates
+ for (int j = 0; j < plugin.linker_flags.size(); j++) {
+ String linker_flag = plugin.linker_flags[j];
+ plugin_linker_flags.insert(linker_flag);
+ }
+
+ // Plist
+ // Using hash map container to remove duplicates
+ const String *K = nullptr;
+
+ while ((K = plugin.plist.next(K))) {
+ String key = *K;
+ String value = plugin.plist[key];
+
+ if (key.is_empty() || value.is_empty()) {
+ continue;
+ }
+
+ plist_values[key] = value;
+ }
+
+ // CPP Code
+ String definition_comment = "// Plugin: " + plugin.name + "\n";
+ String initialization_method = plugin.initialization_method + "();\n";
+ String deinitialization_method = plugin.deinitialization_method + "();\n";
+
+ plugin_definition_cpp_code += definition_comment +
+ "extern void " + initialization_method +
+ "extern void " + deinitialization_method + "\n";
+
+ plugin_initialization_cpp_code += "\t" + initialization_method;
+ plugin_deinitialization_cpp_code += "\t" + deinitialization_method;
+ }
+
+ // Updating `Info.plist`
+ {
+ const String *K = nullptr;
+ while ((K = plist_values.next(K))) {
+ String key = *K;
+ String value = plist_values[key];
+
+ if (key.is_empty() || value.is_empty()) {
+ continue;
+ }
+
+ p_config_data.plist_content += "<key>" + key + "</key><string>" + value + "</string>\n";
+ }
+ }
+
+ // Export files
+ {
+ // Export linked plugin dependency
+ err = _export_additional_assets(dest_dir, plugin_linked_dependencies, true, false, r_exported_assets);
+ ERR_FAIL_COND_V(err, err);
+
+ // Export embedded plugin dependency
+ err = _export_additional_assets(dest_dir, plugin_embedded_dependencies, true, true, r_exported_assets);
+ ERR_FAIL_COND_V(err, err);
+
+ // Export plugin files
+ err = _export_additional_assets(dest_dir, plugin_files, false, false, r_exported_assets);
+ ERR_FAIL_COND_V(err, err);
}
+
+ // Update CPP
+ {
+ Dictionary plugin_format;
+ plugin_format["definition"] = plugin_definition_cpp_code;
+ plugin_format["initialization"] = plugin_initialization_cpp_code;
+ plugin_format["deinitialization"] = plugin_deinitialization_cpp_code;
+
+ String plugin_cpp_code = "\n// Godot Plugins\n"
+ "void godot_ios_plugins_initialize();\n"
+ "void godot_ios_plugins_deinitialize();\n"
+ "// Exported Plugins\n\n"
+ "$definition"
+ "// Use Plugins\n"
+ "void godot_ios_plugins_initialize() {\n"
+ "$initialization"
+ "}\n\n"
+ "void godot_ios_plugins_deinitialize() {\n"
+ "$deinitialization"
+ "}\n";
+
+ p_config_data.cpp_code += plugin_cpp_code.format(plugin_format, "$_");
+ }
+
+ // Update Linker Flag Values
+ {
+ String result_linker_flags = " ";
+ for (Set<String>::Element *E = plugin_linker_flags.front(); E; E = E->next()) {
+ const String &flag = E->get();
+
+ if (flag.length() == 0) {
+ continue;
+ }
+
+ if (result_linker_flags.length() > 0) {
+ result_linker_flags += ' ';
+ }
+
+ result_linker_flags += flag;
+ }
+ result_linker_flags = result_linker_flags.replace("\"", "\\\"");
+ p_config_data.linker_flags += result_linker_flags;
+ }
+
+ return OK;
}
Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
@@ -1043,9 +1601,9 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
return ERR_SKIP;
}
- String library_to_use = "libgodot.iphone." + String(p_debug ? "debug" : "release") + ".fat.a";
+ String library_to_use = "libgodot.iphone." + String(p_debug ? "debug" : "release") + ".xcframework";
- print_line("Static library: " + library_to_use);
+ 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
@@ -1066,6 +1624,8 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
files_to_parse.insert("godot_ios/dummy.cpp");
files_to_parse.insert("godot_ios.xcodeproj/project.xcworkspace/contents.xcworkspacedata");
files_to_parse.insert("godot_ios.xcodeproj/xcshareddata/xcschemes/godot_ios.xcscheme");
+ files_to_parse.insert("godot_ios/godot_ios.entitlements");
+ files_to_parse.insert("godot_ios/Launch Screen.storyboard");
IOSConfigData config_data = {
pkg_name,
@@ -1077,9 +1637,12 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
"",
"",
"",
- ""
+ "",
+ Vector<String>()
};
+ Vector<IOSExportAsset> assets;
+
DirAccess *tmp_app_path = DirAccess::create_for_path(dest_dir);
ERR_FAIL_COND_V(!tmp_app_path, ERR_CANT_CREATE);
@@ -1092,8 +1655,8 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
return ERR_CANT_OPEN;
}
- add_module_code(p_preset, config_data, "arkit", "F9B95E6E2391205500AF0000", "F9C95E812391205C00BF0000");
- add_module_code(p_preset, config_data, "camera", "F9B95E6E2391205500AF0001", "F9C95E812391205C00BF0001");
+ err = _export_ios_plugins(p_preset, config_data, dest_dir + binary_name, assets, p_debug);
+ ERR_FAIL_COND_V(err, err);
//export rest of the files
int ret = unzGoToFirstFile(src_pkg_zip);
@@ -1126,7 +1689,7 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
if (files_to_parse.has(file)) {
_fix_config_file(p_preset, data, config_data, p_debug);
} else if (file.begins_with("libgodot.iphone")) {
- if (file != library_to_use) {
+ if (!file.begins_with(library_to_use) || file.ends_with(String("/empty"))) {
ret = unzGoToNextFile(src_pkg_zip);
continue; //ignore!
}
@@ -1134,22 +1697,9 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
#if defined(OSX_ENABLED) || defined(X11_ENABLED)
is_execute = true;
#endif
- file = "godot_ios.a";
- } else if (file.begins_with("libgodot_arkit")) {
- if ((bool)p_preset->get("capabilities/arkit") && file.ends_with(String(p_debug ? "debug" : "release") + ".fat.a")) {
- file = "libgodot_arkit_module.a";
- } else {
- ret = unzGoToNextFile(src_pkg_zip);
- continue; //ignore!
- }
- } else if (file.begins_with("libgodot_camera")) {
- if ((bool)p_preset->get("capabilities/camera") && file.ends_with(String(p_debug ? "debug" : "release") + ".fat.a")) {
- file = "libgodot_camera_module.a";
- } else {
- ret = unzGoToNextFile(src_pkg_zip);
- continue; //ignore!
- }
+ file = file.replace(library_to_use, binary_name + ".xcframework");
}
+
if (file == project_file) {
project_file_data = data;
}
@@ -1241,13 +1791,48 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
return err;
}
- err = _export_loading_screens(p_preset, dest_dir + binary_name + "/Images.xcassets/LaunchImage.launchimage/");
+ 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/";
+
+ DirAccess *launch_screen_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+
+ if (!launch_screen_da) {
+ return ERR_CANT_CREATE;
+ }
+
+ 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);
+ }
+
+ 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";
+
+ 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);
+ }
+
+ err = _export_loading_screen_images(p_preset, launch_image_path);
+ }
+
+ memdelete(launch_screen_da);
+
if (err) {
return err;
}
print_line("Exporting additional assets");
- Vector<IOSExportAsset> assets;
_export_additional_assets(dest_dir + binary_name, libraries, assets);
_add_assets_to_project(p_preset, project_file_data, assets);
String project_file_name = dest_dir + binary_name + ".xcodeproj/project.pbxproj";
@@ -1289,7 +1874,7 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
archive_args.push_back("archive");
archive_args.push_back("-archivePath");
archive_args.push_back(archive_path);
- err = OS::get_singleton()->execute("xcodebuild", archive_args, true);
+ err = OS::get_singleton()->execute("xcodebuild", archive_args);
ERR_FAIL_COND_V(err, err);
if (ep.step("Making .ipa", 4)) {
@@ -1304,7 +1889,7 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
export_args.push_back("-allowProvisioningUpdates");
export_args.push_back("-exportPath");
export_args.push_back(dest_dir);
- err = OS::get_singleton()->execute("xcodebuild", export_args, true);
+ err = OS::get_singleton()->execute("xcodebuild", export_args);
ERR_FAIL_COND_V(err, err);
#else
print_line(".ipa can only be built on macOS. Leaving Xcode project without building the package.");
@@ -1346,7 +1931,7 @@ bool EditorExportPlatformIOS::can_export(const Ref<EditorExportPreset> &p_preset
valid = false;
}
- String identifier = p_preset->get("application/identifier");
+ String identifier = p_preset->get("application/bundle_identifier");
String pn_err;
if (!is_package_name_valid(identifier, &pn_err)) {
err += TTR("Invalid Identifier:") + " " + pn_err + "\n";
@@ -1365,13 +1950,13 @@ bool EditorExportPlatformIOS::can_export(const Ref<EditorExportPreset> &p_preset
}
}
- String etc_error = test_etc2();
+ String etc_error = test_etc2_or_pvrtc();
if (etc_error != String()) {
valid = false;
err += etc_error;
}
- if (!err.empty()) {
+ if (!err.is_empty()) {
r_error = err;
}
@@ -1382,9 +1967,15 @@ EditorExportPlatformIOS::EditorExportPlatformIOS() {
Ref<Image> img = memnew(Image(_iphone_logo));
logo.instance();
logo->create_from_image(img);
+
+ plugins_changed.set();
+
+ check_for_changes_thread.start(_check_for_changes_poll_thread, this);
}
EditorExportPlatformIOS::~EditorExportPlatformIOS() {
+ quit_request.set();
+ check_for_changes_thread.wait_to_finish();
}
void register_iphone_exporter() {
diff --git a/platform/iphone/export/export.h b/platform/iphone/export/export.h
index 043d21f533..4a79ca279f 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-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 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/game_center.mm b/platform/iphone/game_center.mm
deleted file mode 100644
index 8d470da1a8..0000000000
--- a/platform/iphone/game_center.mm
+++ /dev/null
@@ -1,387 +0,0 @@
-/*************************************************************************/
-/* game_center.mm */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 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. */
-/*************************************************************************/
-
-#ifdef GAME_CENTER_ENABLED
-
-#include "game_center.h"
-
-#ifdef __IPHONE_9_0
-
-#import <GameKit/GameKit.h>
-extern "C" {
-
-#else
-
-extern "C" {
-#import <GameKit/GameKit.h>
-
-#endif
-
-#import "app_delegate.h"
-};
-
-GameCenter *GameCenter::instance = NULL;
-
-void GameCenter::_bind_methods() {
- ClassDB::bind_method(D_METHOD("is_authenticated"), &GameCenter::is_authenticated);
-
- ClassDB::bind_method(D_METHOD("post_score"), &GameCenter::post_score);
- ClassDB::bind_method(D_METHOD("award_achievement"), &GameCenter::award_achievement);
- ClassDB::bind_method(D_METHOD("reset_achievements"), &GameCenter::reset_achievements);
- ClassDB::bind_method(D_METHOD("request_achievements"), &GameCenter::request_achievements);
- ClassDB::bind_method(D_METHOD("request_achievement_descriptions"), &GameCenter::request_achievement_descriptions);
- ClassDB::bind_method(D_METHOD("show_game_center"), &GameCenter::show_game_center);
- ClassDB::bind_method(D_METHOD("request_identity_verification_signature"), &GameCenter::request_identity_verification_signature);
-
- ClassDB::bind_method(D_METHOD("get_pending_event_count"), &GameCenter::get_pending_event_count);
- ClassDB::bind_method(D_METHOD("pop_pending_event"), &GameCenter::pop_pending_event);
-};
-
-void GameCenter::return_connect_error(const char *p_error_description) {
- authenticated = false;
- Dictionary ret;
- ret["type"] = "authentication";
- ret["result"] = "error";
- ret["error_code"] = 0;
- ret["error_description"] = p_error_description;
- pending_events.push_back(ret);
-}
-
-void GameCenter::connect() {
- //if this class isn't available, game center isn't implemented
- if ((NSClassFromString(@"GKLocalPlayer")) == nil) {
- return_connect_error("GameCenter not available");
- return;
- }
-
- GKLocalPlayer *player = [GKLocalPlayer localPlayer];
- if (![player respondsToSelector:@selector(authenticateHandler)]) {
- return_connect_error("GameCenter doesn't respond to 'authenticateHandler'");
- return;
- }
-
- ViewController *root_controller = (ViewController *)((AppDelegate *)[[UIApplication sharedApplication] delegate]).window.rootViewController;
- if (!root_controller) {
- return_connect_error("Window doesn't have root ViewController");
- return;
- }
-
- // This handler is called several times. First when the view needs to be shown, then again
- // after the view is cancelled or the user logs in. Or if the user's already logged in, it's
- // called just once to confirm they're authenticated. This is why no result needs to be specified
- // in the presentViewController phase. In this case, more calls to this function will follow.
- player.authenticateHandler = (^(UIViewController *controller, NSError *error) {
- if (controller) {
- [root_controller presentViewController:controller animated:YES completion:nil];
- } else {
- Dictionary ret;
- ret["type"] = "authentication";
- if (player.isAuthenticated) {
- ret["result"] = "ok";
- ret["player_id"] = [player.playerID UTF8String];
- GameCenter::get_singleton()->authenticated = true;
- } else {
- ret["result"] = "error";
- ret["error_code"] = (int64_t)error.code;
- ret["error_description"] = [error.localizedDescription UTF8String];
- GameCenter::get_singleton()->authenticated = false;
- };
-
- pending_events.push_back(ret);
- };
- });
-};
-
-bool GameCenter::is_authenticated() {
- return authenticated;
-};
-
-Error GameCenter::post_score(Variant p_score) {
- Dictionary params = p_score;
- ERR_FAIL_COND_V(!params.has("score") || !params.has("category"), ERR_INVALID_PARAMETER);
- float score = params["score"];
- String category = params["category"];
-
- NSString *cat_str = [[[NSString alloc] initWithUTF8String:category.utf8().get_data()] autorelease];
- GKScore *reporter = [[[GKScore alloc] initWithLeaderboardIdentifier:cat_str] autorelease];
- reporter.value = score;
-
- ERR_FAIL_COND_V([GKScore respondsToSelector:@selector(reportScores)], ERR_UNAVAILABLE);
-
- [GKScore reportScores:@[ reporter ]
- withCompletionHandler:^(NSError *error) {
- Dictionary ret;
- ret["type"] = "post_score";
- if (error == nil) {
- ret["result"] = "ok";
- } else {
- ret["result"] = "error";
- ret["error_code"] = (int64_t)error.code;
- ret["error_description"] = [error.localizedDescription UTF8String];
- };
-
- pending_events.push_back(ret);
- }];
-
- return OK;
-};
-
-Error GameCenter::award_achievement(Variant p_params) {
- Dictionary params = p_params;
- ERR_FAIL_COND_V(!params.has("name") || !params.has("progress"), ERR_INVALID_PARAMETER);
- String name = params["name"];
- float progress = params["progress"];
-
- NSString *name_str = [[[NSString alloc] initWithUTF8String:name.utf8().get_data()] autorelease];
- GKAchievement *achievement = [[[GKAchievement alloc] initWithIdentifier:name_str] autorelease];
- ERR_FAIL_COND_V(!achievement, FAILED);
-
- ERR_FAIL_COND_V([GKAchievement respondsToSelector:@selector(reportAchievements)], ERR_UNAVAILABLE);
-
- achievement.percentComplete = progress;
- achievement.showsCompletionBanner = NO;
- if (params.has("show_completion_banner")) {
- achievement.showsCompletionBanner = params["show_completion_banner"] ? YES : NO;
- }
-
- [GKAchievement reportAchievements:@[ achievement ]
- withCompletionHandler:^(NSError *error) {
- Dictionary ret;
- ret["type"] = "award_achievement";
- if (error == nil) {
- ret["result"] = "ok";
- } else {
- ret["result"] = "error";
- ret["error_code"] = (int64_t)error.code;
- };
-
- pending_events.push_back(ret);
- }];
-
- return OK;
-};
-
-void GameCenter::request_achievement_descriptions() {
- [GKAchievementDescription loadAchievementDescriptionsWithCompletionHandler:^(NSArray *descriptions, NSError *error) {
- Dictionary ret;
- ret["type"] = "achievement_descriptions";
- if (error == nil) {
- ret["result"] = "ok";
- PackedStringArray names;
- PackedStringArray titles;
- PackedStringArray unachieved_descriptions;
- PackedStringArray achieved_descriptions;
- PackedInt32Array maximum_points;
- Array hidden;
- Array replayable;
-
- for (int i = 0; i < [descriptions count]; i++) {
- GKAchievementDescription *description = [descriptions objectAtIndex:i];
-
- const char *str = [description.identifier UTF8String];
- names.push_back(String::utf8(str != NULL ? str : ""));
-
- str = [description.title UTF8String];
- titles.push_back(String::utf8(str != NULL ? str : ""));
-
- str = [description.unachievedDescription UTF8String];
- unachieved_descriptions.push_back(String::utf8(str != NULL ? str : ""));
-
- str = [description.achievedDescription UTF8String];
- achieved_descriptions.push_back(String::utf8(str != NULL ? str : ""));
-
- maximum_points.push_back(description.maximumPoints);
-
- hidden.push_back(description.hidden == YES);
-
- replayable.push_back(description.replayable == YES);
- }
-
- ret["names"] = names;
- ret["titles"] = titles;
- ret["unachieved_descriptions"] = unachieved_descriptions;
- ret["achieved_descriptions"] = achieved_descriptions;
- ret["maximum_points"] = maximum_points;
- ret["hidden"] = hidden;
- ret["replayable"] = replayable;
-
- } else {
- ret["result"] = "error";
- ret["error_code"] = (int64_t)error.code;
- };
-
- pending_events.push_back(ret);
- }];
-};
-
-void GameCenter::request_achievements() {
- [GKAchievement loadAchievementsWithCompletionHandler:^(NSArray *achievements, NSError *error) {
- Dictionary ret;
- ret["type"] = "achievements";
- if (error == nil) {
- ret["result"] = "ok";
- PackedStringArray names;
- PackedFloat32Array percentages;
-
- for (int i = 0; i < [achievements count]; i++) {
- GKAchievement *achievement = [achievements objectAtIndex:i];
- const char *str = [achievement.identifier UTF8String];
- names.push_back(String::utf8(str != NULL ? str : ""));
-
- percentages.push_back(achievement.percentComplete);
- }
-
- ret["names"] = names;
- ret["progress"] = percentages;
-
- } else {
- ret["result"] = "error";
- ret["error_code"] = (int64_t)error.code;
- };
-
- pending_events.push_back(ret);
- }];
-};
-
-void GameCenter::reset_achievements() {
- [GKAchievement resetAchievementsWithCompletionHandler:^(NSError *error) {
- Dictionary ret;
- ret["type"] = "reset_achievements";
- if (error == nil) {
- ret["result"] = "ok";
- } else {
- ret["result"] = "error";
- ret["error_code"] = (int64_t)error.code;
- };
-
- pending_events.push_back(ret);
- }];
-};
-
-Error GameCenter::show_game_center(Variant p_params) {
- ERR_FAIL_COND_V(!NSProtocolFromString(@"GKGameCenterControllerDelegate"), FAILED);
-
- Dictionary params = p_params;
-
- GKGameCenterViewControllerState view_state = GKGameCenterViewControllerStateDefault;
- if (params.has("view")) {
- String view_name = params["view"];
- if (view_name == "default") {
- view_state = GKGameCenterViewControllerStateDefault;
- } else if (view_name == "leaderboards") {
- view_state = GKGameCenterViewControllerStateLeaderboards;
- } else if (view_name == "achievements") {
- view_state = GKGameCenterViewControllerStateAchievements;
- } else if (view_name == "challenges") {
- view_state = GKGameCenterViewControllerStateChallenges;
- } else {
- return ERR_INVALID_PARAMETER;
- }
- }
-
- GKGameCenterViewController *controller = [[GKGameCenterViewController alloc] init];
- ERR_FAIL_COND_V(!controller, FAILED);
-
- ViewController *root_controller = (ViewController *)((AppDelegate *)[[UIApplication sharedApplication] delegate]).window.rootViewController;
- ERR_FAIL_COND_V(!root_controller, FAILED);
-
- controller.gameCenterDelegate = root_controller;
- controller.viewState = view_state;
- if (view_state == GKGameCenterViewControllerStateLeaderboards) {
- controller.leaderboardIdentifier = nil;
- if (params.has("leaderboard_name")) {
- String name = params["leaderboard_name"];
- NSString *name_str = [[[NSString alloc] initWithUTF8String:name.utf8().get_data()] autorelease];
- controller.leaderboardIdentifier = name_str;
- }
- }
-
- [root_controller presentViewController:controller animated:YES completion:nil];
-
- return OK;
-};
-
-Error GameCenter::request_identity_verification_signature() {
- ERR_FAIL_COND_V(!is_authenticated(), ERR_UNAUTHORIZED);
-
- GKLocalPlayer *player = [GKLocalPlayer localPlayer];
- [player generateIdentityVerificationSignatureWithCompletionHandler:^(NSURL *publicKeyUrl, NSData *signature, NSData *salt, uint64_t timestamp, NSError *error) {
- Dictionary ret;
- ret["type"] = "identity_verification_signature";
- if (error == nil) {
- ret["result"] = "ok";
- ret["public_key_url"] = [publicKeyUrl.absoluteString UTF8String];
- ret["signature"] = [[signature base64EncodedStringWithOptions:0] UTF8String];
- ret["salt"] = [[salt base64EncodedStringWithOptions:0] UTF8String];
- ret["timestamp"] = timestamp;
- ret["player_id"] = [player.playerID UTF8String];
- } else {
- ret["result"] = "error";
- ret["error_code"] = (int64_t)error.code;
- ret["error_description"] = [error.localizedDescription UTF8String];
- };
-
- pending_events.push_back(ret);
- }];
-
- return OK;
-};
-
-void GameCenter::game_center_closed() {
- Dictionary ret;
- ret["type"] = "show_game_center";
- ret["result"] = "ok";
- pending_events.push_back(ret);
-}
-
-int GameCenter::get_pending_event_count() {
- return pending_events.size();
-};
-
-Variant GameCenter::pop_pending_event() {
- Variant front = pending_events.front()->get();
- pending_events.pop_front();
-
- return front;
-};
-
-GameCenter *GameCenter::get_singleton() {
- return instance;
-};
-
-GameCenter::GameCenter() {
- ERR_FAIL_COND(instance != NULL);
- instance = this;
- authenticated = false;
-};
-
-GameCenter::~GameCenter() {}
-
-#endif
diff --git a/platform/iphone/gl_view.h b/platform/iphone/gl_view.h
deleted file mode 100644
index 975aa4b70a..0000000000
--- a/platform/iphone/gl_view.h
+++ /dev/null
@@ -1,123 +0,0 @@
-/*************************************************************************/
-/* gl_view.h */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#import <AVFoundation/AVFoundation.h>
-#import <MediaPlayer/MediaPlayer.h>
-#import <OpenGLES/EAGL.h>
-#import <OpenGLES/ES1/gl.h>
-#import <OpenGLES/ES1/glext.h>
-#import <UIKit/UIKit.h>
-
-@protocol GLViewDelegate;
-
-@interface GLView : UIView <UIKeyInput> {
-@private
- // The pixel dimensions of the backbuffer
- GLint backingWidth;
- GLint backingHeight;
-
- EAGLContext *context;
-
- // OpenGL names for the renderbuffer and framebuffers used to render to this view
- GLuint viewRenderbuffer, viewFramebuffer;
-
- // OpenGL name for the depth buffer that is attached to viewFramebuffer, if it exists (0 if it does not exist)
- GLuint depthRenderbuffer;
-
- BOOL useCADisplayLink;
- // CADisplayLink available on 3.1+ synchronizes the animation timer & drawing with the refresh rate of the display, only supports animation intervals of 1/60 1/30 & 1/15
- CADisplayLink *displayLink;
-
- // An animation timer that, when animation is started, will periodically call -drawView at the given rate.
- // Only used if CADisplayLink is not
- NSTimer *animationTimer;
-
- NSTimeInterval animationInterval;
-
- // Delegate to do our drawing, called by -drawView, which can be called manually or via the animation timer.
- id<GLViewDelegate> delegate;
-
- // Flag to denote that the -setupView method of a delegate has been called.
- // Resets to NO whenever the delegate changes.
- BOOL delegateSetup;
- BOOL active;
- float screen_scale;
-}
-
-@property(nonatomic, assign) id<GLViewDelegate> delegate;
-
-// AVPlayer-related properties
-@property(strong, nonatomic) AVAsset *avAsset;
-@property(strong, nonatomic) AVPlayerItem *avPlayerItem;
-@property(strong, nonatomic) AVPlayer *avPlayer;
-@property(strong, nonatomic) AVPlayerLayer *avPlayerLayer;
-
-@property(strong, nonatomic) UIWindow *backgroundWindow;
-
-@property(nonatomic) UITextAutocorrectionType autocorrectionType;
-
-- (void)startAnimation;
-- (void)stopAnimation;
-- (void)drawView;
-
-- (BOOL)canBecomeFirstResponder;
-
-- (void)open_keyboard;
-- (void)hide_keyboard;
-- (void)deleteBackward;
-- (BOOL)hasText;
-- (void)insertText:(NSString *)p_text;
-
-- (id)initGLES;
-- (BOOL)createFramebuffer;
-- (void)destroyFramebuffer;
-
-- (void)audioRouteChangeListenerCallback:(NSNotification *)notification;
-- (void)keyboardOnScreen:(NSNotification *)notification;
-- (void)keyboardHidden:(NSNotification *)notification;
-
-@property NSTimeInterval animationInterval;
-@property(nonatomic, assign) BOOL useCADisplayLink;
-
-@end
-
-@protocol GLViewDelegate <NSObject>
-
-@required
-
-// Draw with OpenGL ES
-- (void)drawView:(GLView *)view;
-
-@optional
-
-// Called whenever you need to do some initialization before rendering.
-- (void)setupView:(GLView *)view;
-
-@end
diff --git a/platform/iphone/gl_view.mm b/platform/iphone/gl_view.mm
deleted file mode 100644
index 1169ebc6b4..0000000000
--- a/platform/iphone/gl_view.mm
+++ /dev/null
@@ -1,702 +0,0 @@
-/*************************************************************************/
-/* gl_view.mm */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#import "gl_view.h"
-
-#include "core/os/keyboard.h"
-#include "core/project_settings.h"
-#include "os_iphone.h"
-#include "servers/audio_server.h"
-
-#import <OpenGLES/EAGLDrawable.h>
-#import <QuartzCore/QuartzCore.h>
-
-/*
-@interface GLView (private)
-
-- (id)initGLES;
-- (BOOL)createFramebuffer;
-- (void)destroyFramebuffer;
-@end
-*/
-
-int gl_view_base_fb;
-static String keyboard_text;
-static GLView *_instance = NULL;
-
-static bool video_found_error = false;
-static bool video_playing = false;
-static CMTime video_current_time;
-
-void _show_keyboard(String);
-void _hide_keyboard();
-bool _play_video(String, float, String, String);
-bool _is_video_playing();
-void _pause_video();
-void _focus_out_video();
-void _unpause_video();
-void _stop_video();
-CGFloat _points_to_pixels(CGFloat);
-
-void _show_keyboard(String p_existing) {
- keyboard_text = p_existing;
- printf("instance on show is %p\n", _instance);
- [_instance open_keyboard];
-};
-
-void _hide_keyboard() {
- printf("instance on hide is %p\n", _instance);
- [_instance hide_keyboard];
- keyboard_text = "";
-};
-
-Rect2 _get_ios_window_safe_area(float p_window_width, float p_window_height) {
- UIEdgeInsets insets = UIEdgeInsetsMake(0, 0, 0, 0);
- if (_instance != nil && [_instance respondsToSelector:@selector(safeAreaInsets)]) {
- insets = [_instance safeAreaInsets];
- }
- ERR_FAIL_COND_V(insets.left < 0 || insets.top < 0 || insets.right < 0 || insets.bottom < 0,
- Rect2(0, 0, p_window_width, p_window_height));
- UIEdgeInsets window_insets = UIEdgeInsetsMake(_points_to_pixels(insets.top), _points_to_pixels(insets.left), _points_to_pixels(insets.bottom), _points_to_pixels(insets.right));
- return Rect2(window_insets.left, window_insets.top, p_window_width - window_insets.right - window_insets.left, p_window_height - window_insets.bottom - window_insets.top);
-}
-
-bool _play_video(String p_path, float p_volume, String p_audio_track, String p_subtitle_track) {
- p_path = ProjectSettings::get_singleton()->globalize_path(p_path);
-
- NSString *file_path = [[[NSString alloc] initWithUTF8String:p_path.utf8().get_data()] autorelease];
-
- _instance.avAsset = [AVAsset assetWithURL:[NSURL fileURLWithPath:file_path]];
-
- _instance.avPlayerItem = [[AVPlayerItem alloc] initWithAsset:_instance.avAsset];
- [_instance.avPlayerItem addObserver:_instance forKeyPath:@"status" options:0 context:nil];
-
- _instance.avPlayer = [[AVPlayer alloc] initWithPlayerItem:_instance.avPlayerItem];
- _instance.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:_instance.avPlayer];
-
- [_instance.avPlayer addObserver:_instance forKeyPath:@"status" options:0 context:nil];
- [[NSNotificationCenter defaultCenter]
- addObserver:_instance
- selector:@selector(playerItemDidReachEnd:)
- name:AVPlayerItemDidPlayToEndTimeNotification
- object:[_instance.avPlayer currentItem]];
-
- [_instance.avPlayer addObserver:_instance forKeyPath:@"rate" options:NSKeyValueObservingOptionNew context:0];
-
- [_instance.avPlayerLayer setFrame:_instance.bounds];
- [_instance.layer addSublayer:_instance.avPlayerLayer];
- [_instance.avPlayer play];
-
- AVMediaSelectionGroup *audioGroup = [_instance.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicAudible];
-
- NSMutableArray *allAudioParams = [NSMutableArray array];
- for (id track in audioGroup.options) {
- NSString *language = [[track locale] localeIdentifier];
- NSLog(@"subtitle lang: %@", language);
-
- if ([language isEqualToString:[NSString stringWithUTF8String:p_audio_track.utf8()]]) {
- AVMutableAudioMixInputParameters *audioInputParams = [AVMutableAudioMixInputParameters audioMixInputParameters];
- [audioInputParams setVolume:p_volume atTime:kCMTimeZero];
- [audioInputParams setTrackID:[track trackID]];
- [allAudioParams addObject:audioInputParams];
-
- AVMutableAudioMix *audioMix = [AVMutableAudioMix audioMix];
- [audioMix setInputParameters:allAudioParams];
-
- [_instance.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:audioGroup];
- [_instance.avPlayer.currentItem setAudioMix:audioMix];
-
- break;
- }
- }
-
- AVMediaSelectionGroup *subtitlesGroup = [_instance.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible];
- NSArray *useableTracks = [AVMediaSelectionGroup mediaSelectionOptionsFromArray:subtitlesGroup.options withoutMediaCharacteristics:[NSArray arrayWithObject:AVMediaCharacteristicContainsOnlyForcedSubtitles]];
-
- for (id track in useableTracks) {
- NSString *language = [[track locale] localeIdentifier];
- NSLog(@"subtitle lang: %@", language);
-
- if ([language isEqualToString:[NSString stringWithUTF8String:p_subtitle_track.utf8()]]) {
- [_instance.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:subtitlesGroup];
- break;
- }
- }
-
- video_playing = true;
-
- return true;
-}
-
-bool _is_video_playing() {
- if (_instance.avPlayer.error) {
- printf("Error during playback\n");
- }
- return (_instance.avPlayer.rate > 0 && !_instance.avPlayer.error);
-}
-
-void _pause_video() {
- video_current_time = _instance.avPlayer.currentTime;
- [_instance.avPlayer pause];
- video_playing = false;
-}
-
-void _focus_out_video() {
- printf("focus out pausing video\n");
- [_instance.avPlayer pause];
-};
-
-void _unpause_video() {
- [_instance.avPlayer play];
- video_playing = true;
-};
-
-void _stop_video() {
- [_instance.avPlayer pause];
- [_instance.avPlayerLayer removeFromSuperlayer];
- _instance.avPlayer = nil;
- video_playing = false;
-}
-
-CGFloat _points_to_pixels(CGFloat points) {
- float pixelPerInch;
- if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
- pixelPerInch = 132;
- } else if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
- pixelPerInch = 163;
- } else {
- pixelPerInch = 160;
- }
- CGFloat pointsPerInch = 72.0;
- return (points / pointsPerInch * pixelPerInch);
-}
-
-@implementation GLView
-
-@synthesize animationInterval;
-
-static const int max_touches = 8;
-static UITouch *touches[max_touches];
-
-static void init_touches() {
- for (int i = 0; i < max_touches; i++) {
- touches[i] = NULL;
- };
-};
-
-static int get_touch_id(UITouch *p_touch) {
- int first = -1;
- for (int i = 0; i < max_touches; i++) {
- if (first == -1 && touches[i] == NULL) {
- first = i;
- continue;
- };
- if (touches[i] == p_touch)
- return i;
- };
-
- if (first != -1) {
- touches[first] = p_touch;
- return first;
- };
-
- return -1;
-};
-
-static int remove_touch(UITouch *p_touch) {
- int remaining = 0;
- for (int i = 0; i < max_touches; i++) {
- if (touches[i] == NULL)
- continue;
- if (touches[i] == p_touch)
- touches[i] = NULL;
- else
- ++remaining;
- };
- return remaining;
-};
-
-static void clear_touches() {
- for (int i = 0; i < max_touches; i++) {
- touches[i] = NULL;
- };
-};
-
-// Implement this to override the default layer class (which is [CALayer class]).
-// We do this so that our view will be backed by a layer that is capable of OpenGL ES rendering.
-+ (Class)layerClass {
- return [CAEAGLLayer class];
-}
-
-//The GL view is stored in the nib file. When it's unarchived it's sent -initWithCoder:
-- (id)initWithCoder:(NSCoder *)coder {
- active = FALSE;
- if ((self = [super initWithCoder:coder])) {
- self = [self initGLES];
- }
- return self;
-}
-
-- (id)initGLES {
- // Get our backing layer
- CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
-
- // Configure it so that it is opaque, does not retain the contents of the backbuffer when displayed, and uses RGBA8888 color.
- eaglLayer.opaque = YES;
- eaglLayer.drawableProperties = [NSDictionary
- dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:FALSE],
- kEAGLDrawablePropertyRetainedBacking,
- kEAGLColorFormatRGBA8,
- kEAGLDrawablePropertyColorFormat,
- nil];
-
- // FIXME: Add Vulkan support via MoltenVK. Add fallback code back?
-
- // Create GL ES 2 context
- if (GLOBAL_GET("rendering/quality/driver/driver_name") == "GLES2") {
- context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
- NSLog(@"Setting up an OpenGL ES 2.0 context.");
- if (!context) {
- NSLog(@"Failed to create OpenGL ES 2.0 context!");
- return nil;
- }
- }
-
- if (![EAGLContext setCurrentContext:context]) {
- NSLog(@"Failed to set EAGLContext!");
- return nil;
- }
- if (![self createFramebuffer]) {
- NSLog(@"Failed to create frame buffer!");
- return nil;
- }
-
- // Default the animation interval to 1/60th of a second.
- animationInterval = 1.0 / 60.0;
- return self;
-}
-
-- (id<GLViewDelegate>)delegate {
- return delegate;
-}
-
-// Update the delegate, and if it needs a -setupView: call, set our internal flag so that it will be called.
-- (void)setDelegate:(id<GLViewDelegate>)d {
- delegate = d;
- delegateSetup = ![delegate respondsToSelector:@selector(setupView:)];
-}
-
-@synthesize useCADisplayLink;
-
-// If our view is resized, we'll be asked to layout subviews.
-// This is the perfect opportunity to also update the framebuffer so that it is
-// the same size as our display area.
-
-- (void)layoutSubviews {
- [EAGLContext setCurrentContext:context];
- [self destroyFramebuffer];
- [self createFramebuffer];
- [self drawView];
-}
-
-- (BOOL)createFramebuffer {
- // Generate IDs for a framebuffer object and a color renderbuffer
- UIScreen *mainscr = [UIScreen mainScreen];
- printf("******** screen size %i, %i\n", (int)mainscr.currentMode.size.width, (int)mainscr.currentMode.size.height);
- self.contentScaleFactor = mainscr.nativeScale;
-
- glGenFramebuffersOES(1, &viewFramebuffer);
- glGenRenderbuffersOES(1, &viewRenderbuffer);
-
- glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
- glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
- // This call associates the storage for the current render buffer with the EAGLDrawable (our CAEAGLLayer)
- // allowing us to draw into a buffer that will later be rendered to screen wherever the layer is (which corresponds with our view).
- [context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(id<EAGLDrawable>)self.layer];
- glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer);
-
- glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
- glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
-
- // For this sample, we also need a depth buffer, so we'll create and attach one via another renderbuffer.
- glGenRenderbuffersOES(1, &depthRenderbuffer);
- glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRenderbuffer);
- glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, backingWidth, backingHeight);
- glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer);
-
- if (glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) {
- NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));
- 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;
-}
-
-// Clean up any buffers we have allocated.
-- (void)destroyFramebuffer {
- glDeleteFramebuffersOES(1, &viewFramebuffer);
- viewFramebuffer = 0;
- glDeleteRenderbuffersOES(1, &viewRenderbuffer);
- viewRenderbuffer = 0;
-
- if (depthRenderbuffer) {
- glDeleteRenderbuffersOES(1, &depthRenderbuffer);
- depthRenderbuffer = 0;
- }
-}
-
-- (void)startAnimation {
- if (active)
- return;
- active = TRUE;
- printf("start animation!\n");
- if (useCADisplayLink) {
- // Approximate frame rate
- // assumes device refreshes at 60 fps
- int frameInterval = (int)floor(animationInterval * 60.0f);
-
- displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawView)];
- [displayLink setFrameInterval:frameInterval];
-
- // Setup DisplayLink in main thread
- [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
- } else {
- animationTimer = [NSTimer scheduledTimerWithTimeInterval:animationInterval target:self selector:@selector(drawView) userInfo:nil repeats:YES];
- }
-
- if (video_playing) {
- _unpause_video();
- }
-}
-
-- (void)stopAnimation {
- if (!active)
- return;
- active = FALSE;
- printf("******** stop animation!\n");
-
- if (useCADisplayLink) {
- [displayLink invalidate];
- displayLink = nil;
- } else {
- [animationTimer invalidate];
- animationTimer = nil;
- }
-
- clear_touches();
-
- if (video_playing) {
- // save position
- }
-}
-
-- (void)setAnimationInterval:(NSTimeInterval)interval {
- animationInterval = interval;
- if ((useCADisplayLink && displayLink) || (!useCADisplayLink && animationTimer)) {
- [self stopAnimation];
- [self startAnimation];
- }
-}
-
-// Updates the OpenGL view when the timer fires
-- (void)drawView {
- if (!active) {
- printf("draw view not active!\n");
- return;
- };
- if (useCADisplayLink) {
- // Pause the CADisplayLink to avoid recursion
- [displayLink setPaused:YES];
-
- // Process all input events
- while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, TRUE) == kCFRunLoopRunHandledSource)
- ;
-
- // We are good to go, resume the CADisplayLink
- [displayLink setPaused:NO];
- }
-
- // Make sure that you are drawing to the current context
- [EAGLContext setCurrentContext:context];
-
- // If our drawing delegate needs to have the view setup, then call -setupView: and flag that it won't need to be called again.
- if (!delegateSetup) {
- [delegate setupView:self];
- delegateSetup = YES;
- }
-
- glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
-
- [delegate drawView:self];
-
- glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
- [context presentRenderbuffer:GL_RENDERBUFFER_OES];
-
-#ifdef DEBUG_ENABLED
- GLenum err = glGetError();
- if (err)
- NSLog(@"DrawView: %x error", err);
-#endif
-}
-
-- (void)touchesBegan:(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]]) {
- UITouch *touch = [tlist objectAtIndex:i];
- if (touch.phase != UITouchPhaseBegan)
- continue;
- int tid = get_touch_id(touch);
- ERR_FAIL_COND(tid == -1);
- CGPoint touchPoint = [touch locationInView:self];
- OSIPhone::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, true, touch.tapCount > 1);
- };
- };
-}
-
-- (void)touchesMoved:(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]]) {
- UITouch *touch = [tlist objectAtIndex:i];
- if (touch.phase != UITouchPhaseMoved)
- continue;
- int tid = get_touch_id(touch);
- ERR_FAIL_COND(tid == -1);
- CGPoint touchPoint = [touch locationInView:self];
- CGPoint prev_point = [touch previousLocationInView:self];
- OSIPhone::get_singleton()->touch_drag(tid, prev_point.x * self.contentScaleFactor, prev_point.y * self.contentScaleFactor, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor);
- };
- };
-}
-
-- (void)touchesEnded:(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]]) {
- UITouch *touch = [tlist objectAtIndex:i];
- if (touch.phase != UITouchPhaseEnded)
- continue;
- int tid = get_touch_id(touch);
- ERR_FAIL_COND(tid == -1);
- remove_touch(touch);
- CGPoint touchPoint = [touch locationInView:self];
- OSIPhone::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, false, false);
- };
- };
-}
-
-- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
- OSIPhone::get_singleton()->touches_cancelled();
- clear_touches();
-};
-
-- (BOOL)canBecomeFirstResponder {
- return YES;
-};
-
-- (void)open_keyboard {
- //keyboard_text = p_existing;
- [self becomeFirstResponder];
-};
-
-- (void)hide_keyboard {
- //keyboard_text = p_existing;
- [self resignFirstResponder];
-};
-
-- (void)keyboardOnScreen:(NSNotification *)notification {
- NSDictionary *info = notification.userInfo;
- NSValue *value = info[UIKeyboardFrameEndUserInfoKey];
-
- CGRect rawFrame = [value CGRectValue];
- CGRect keyboardFrame = [self convertRect:rawFrame fromView:nil];
-
- OSIPhone::get_singleton()->set_virtual_keyboard_height(_points_to_pixels(keyboardFrame.size.height));
-}
-
-- (void)keyboardHidden:(NSNotification *)notification {
- OSIPhone::get_singleton()->set_virtual_keyboard_height(0);
-}
-
-- (void)deleteBackward {
- if (keyboard_text.length())
- keyboard_text.erase(keyboard_text.length() - 1, 1);
- OSIPhone::get_singleton()->key(KEY_BACKSPACE, true);
-};
-
-- (BOOL)hasText {
- return keyboard_text.length() ? YES : NO;
-};
-
-- (void)insertText:(NSString *)p_text {
- String character;
- character.parse_utf8([p_text UTF8String]);
- keyboard_text = keyboard_text + character;
- OSIPhone::get_singleton()->key(character[0] == 10 ? KEY_ENTER : character[0], true);
- printf("inserting text with character %lc\n", (CharType)character[0]);
-};
-
-- (void)audioRouteChangeListenerCallback:(NSNotification *)notification {
- printf("*********** route changed!\n");
- NSDictionary *interuptionDict = notification.userInfo;
-
- NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue];
-
- switch (routeChangeReason) {
- case AVAudioSessionRouteChangeReasonNewDeviceAvailable: {
- NSLog(@"AVAudioSessionRouteChangeReasonNewDeviceAvailable");
- NSLog(@"Headphone/Line plugged in");
- }; break;
-
- case AVAudioSessionRouteChangeReasonOldDeviceUnavailable: {
- NSLog(@"AVAudioSessionRouteChangeReasonOldDeviceUnavailable");
- NSLog(@"Headphone/Line was pulled. Resuming video play....");
- if (_is_video_playing()) {
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
- [_instance.avPlayer play]; // NOTE: change this line according your current player implementation
- NSLog(@"resumed play");
- });
- };
- }; break;
-
- case AVAudioSessionRouteChangeReasonCategoryChange: {
- // called at start - also when other audio wants to play
- NSLog(@"AVAudioSessionRouteChangeReasonCategoryChange");
- }; break;
- }
-}
-
-// When created via code however, we get initWithFrame
-- (id)initWithFrame:(CGRect)frame {
- self = [super initWithFrame:frame];
- _instance = self;
- printf("after init super %p\n", self);
- if (self != nil) {
- self = [self initGLES];
- printf("after init gles %p\n", self);
- }
- init_touches();
- self.multipleTouchEnabled = YES;
- self.autocorrectionType = UITextAutocorrectionTypeNo;
-
- printf("******** adding observer for sound routing changes\n");
- [[NSNotificationCenter defaultCenter]
- addObserver:self
- selector:@selector(audioRouteChangeListenerCallback:)
- name:AVAudioSessionRouteChangeNotification
- object:nil];
-
- printf("******** adding observer for keyboard show/hide\n");
- [[NSNotificationCenter defaultCenter]
- addObserver:self
- selector:@selector(keyboardOnScreen:)
- name:UIKeyboardDidShowNotification
- object:nil];
- [[NSNotificationCenter defaultCenter]
- addObserver:self
- selector:@selector(keyboardHidden:)
- name:UIKeyboardDidHideNotification
- object:nil];
-
- //self.autoresizesSubviews = YES;
- //[self setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleWidth];
-
- return self;
-}
-
-//- (BOOL)automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers {
-// return YES;
-//}
-
-//- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation{
-// return YES;
-//}
-
-// Stop animating and release resources when they are no longer needed.
-- (void)dealloc {
- [self stopAnimation];
-
- if ([EAGLContext currentContext] == context) {
- [EAGLContext setCurrentContext:nil];
- }
-
- [context release];
- context = nil;
-
- [super dealloc];
-}
-
-- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
- if (object == _instance.avPlayerItem && [keyPath isEqualToString:@"status"]) {
- if (_instance.avPlayerItem.status == AVPlayerStatusFailed || _instance.avPlayer.status == AVPlayerStatusFailed) {
- _stop_video();
- video_found_error = true;
- }
-
- if (_instance.avPlayer.status == AVPlayerStatusReadyToPlay &&
- _instance.avPlayerItem.status == AVPlayerItemStatusReadyToPlay &&
- CMTIME_COMPARE_INLINE(video_current_time, ==, kCMTimeZero)) {
- //NSLog(@"time: %@", video_current_time);
-
- [_instance.avPlayer seekToTime:video_current_time];
- video_current_time = kCMTimeZero;
- }
- }
-
- if (object == _instance.avPlayer && [keyPath isEqualToString:@"rate"]) {
- NSLog(@"Player playback rate changed: %.5f", _instance.avPlayer.rate);
- if (_is_video_playing() && _instance.avPlayer.rate == 0.0 && !_instance.avPlayer.error) {
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
- [_instance.avPlayer play]; // NOTE: change this line according your current player implementation
- NSLog(@"resumed play");
- });
-
- NSLog(@" . . . PAUSED (or just started)");
- }
- }
-}
-
-- (void)playerItemDidReachEnd:(NSNotification *)notification {
- _stop_video();
-}
-
-@end
diff --git a/platform/iphone/godot_app_delegate.h b/platform/iphone/godot_app_delegate.h
new file mode 100644
index 0000000000..6335ada50e
--- /dev/null
+++ b/platform/iphone/godot_app_delegate.h
@@ -0,0 +1,41 @@
+/*************************************************************************/
+/* godot_app_delegate.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#import <UIKit/UIKit.h>
+
+typedef NSObject<UIApplicationDelegate> ApplicationDelegateService;
+
+@interface GodotApplicalitionDelegate : NSObject <UIApplicationDelegate>
+
+@property(class, readonly, strong) NSArray<ApplicationDelegateService *> *services;
+
++ (void)addService:(ApplicationDelegateService *)service;
+
+@end
diff --git a/platform/iphone/godot_app_delegate.m b/platform/iphone/godot_app_delegate.m
new file mode 100644
index 0000000000..3ce9bffc79
--- /dev/null
+++ b/platform/iphone/godot_app_delegate.m
@@ -0,0 +1,467 @@
+/*************************************************************************/
+/* godot_app_delegate.m */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#import "godot_app_delegate.h"
+
+#import "app_delegate.h"
+
+@interface GodotApplicalitionDelegate ()
+
+@end
+
+@implementation GodotApplicalitionDelegate
+
+static NSMutableArray<ApplicationDelegateService *> *services = nil;
+
++ (NSArray<ApplicationDelegateService *> *)services {
+ return services;
+}
+
++ (void)load {
+ services = [NSMutableArray new];
+ [services addObject:[AppDelegate new]];
+}
+
++ (void)addService:(ApplicationDelegateService *)service {
+ if (!services || !service) {
+ return;
+ }
+ [services addObject:service];
+}
+
+// UIApplicationDelegate documantation can be found here: https://developer.apple.com/documentation/uikit/uiapplicationdelegate
+
+// MARK: Window
+
+- (UIWindow *)window {
+ UIWindow *result = nil;
+
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ UIWindow *value = [service window];
+
+ if (value) {
+ result = value;
+ }
+ }
+
+ return result;
+}
+
+// MARK: Initializing
+
+- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions {
+ BOOL result = NO;
+
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ if ([service application:application willFinishLaunchingWithOptions:launchOptions]) {
+ result = YES;
+ }
+ }
+
+ return result;
+}
+
+- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions {
+ BOOL result = NO;
+
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ if ([service application:application didFinishLaunchingWithOptions:launchOptions]) {
+ result = YES;
+ }
+ }
+
+ return result;
+}
+
+/* Can be handled by Info.plist. Not yet supported by Godot.
+
+// MARK: Scene
+
+- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options {}
+
+- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet<UISceneSession *> *)sceneSessions {}
+
+*/
+
+// MARK: Life-Cycle
+
+- (void)applicationDidBecomeActive:(UIApplication *)application {
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ [service applicationDidBecomeActive:application];
+ }
+}
+
+- (void)applicationWillResignActive:(UIApplication *)application {
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ [service applicationWillResignActive:application];
+ }
+}
+
+- (void)applicationDidEnterBackground:(UIApplication *)application {
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ [service applicationDidEnterBackground:application];
+ }
+}
+
+- (void)applicationWillEnterForeground:(UIApplication *)application {
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ [service applicationWillEnterForeground:application];
+ }
+}
+
+- (void)applicationWillTerminate:(UIApplication *)application {
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ [service applicationWillTerminate:application];
+ }
+}
+
+// MARK: Environment Changes
+
+- (void)applicationProtectedDataDidBecomeAvailable:(UIApplication *)application {
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ [service applicationProtectedDataDidBecomeAvailable:application];
+ }
+}
+
+- (void)applicationProtectedDataWillBecomeUnavailable:(UIApplication *)application {
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ [service applicationProtectedDataWillBecomeUnavailable:application];
+ }
+}
+
+- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ [service applicationDidReceiveMemoryWarning:application];
+ }
+}
+
+- (void)applicationSignificantTimeChange:(UIApplication *)application {
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ [service applicationSignificantTimeChange:application];
+ }
+}
+
+// MARK: App State Restoration
+
+- (BOOL)application:(UIApplication *)application shouldSaveSecureApplicationState:(NSCoder *)coder API_AVAILABLE(ios(13.2)) {
+ BOOL result = NO;
+
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ if ([service application:application shouldSaveSecureApplicationState:coder]) {
+ result = YES;
+ }
+ }
+
+ return result;
+}
+
+- (BOOL)application:(UIApplication *)application shouldRestoreSecureApplicationState:(NSCoder *)coder API_AVAILABLE(ios(13.2)) {
+ BOOL result = NO;
+
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ if ([service application:application shouldRestoreSecureApplicationState:coder]) {
+ result = YES;
+ }
+ }
+
+ return result;
+}
+
+- (UIViewController *)application:(UIApplication *)application viewControllerWithRestorationIdentifierPath:(NSArray<NSString *> *)identifierComponents coder:(NSCoder *)coder {
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ UIViewController *controller = [service application:application viewControllerWithRestorationIdentifierPath:identifierComponents coder:coder];
+
+ if (controller) {
+ return controller;
+ }
+ }
+
+ return nil;
+}
+
+- (void)application:(UIApplication *)application willEncodeRestorableStateWithCoder:(NSCoder *)coder {
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ [service application:application willEncodeRestorableStateWithCoder:coder];
+ }
+}
+
+- (void)application:(UIApplication *)application didDecodeRestorableStateWithCoder:(NSCoder *)coder {
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ [service application:application didDecodeRestorableStateWithCoder:coder];
+ }
+}
+
+// MARK: Download Data in Background
+
+- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler {
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ [service application:application handleEventsForBackgroundURLSession:identifier completionHandler:completionHandler];
+ }
+
+ completionHandler();
+}
+
+// MARK: Remote Notification
+
+// Moved to the iOS Plugin
+
+// MARK: User Activity and Handling Quick Actions
+
+- (BOOL)application:(UIApplication *)application willContinueUserActivityWithType:(NSString *)userActivityType {
+ BOOL result = NO;
+
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ if ([service application:application willContinueUserActivityWithType:userActivityType]) {
+ result = YES;
+ }
+ }
+
+ return result;
+}
+
+- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> *restorableObjects))restorationHandler {
+ BOOL result = NO;
+
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ if ([service application:application continueUserActivity:userActivity restorationHandler:restorationHandler]) {
+ result = YES;
+ }
+ }
+
+ return result;
+}
+
+- (void)application:(UIApplication *)application didUpdateUserActivity:(NSUserActivity *)userActivity {
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ [service application:application didUpdateUserActivity:userActivity];
+ }
+}
+
+- (void)application:(UIApplication *)application didFailToContinueUserActivityWithType:(NSString *)userActivityType error:(NSError *)error {
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ [service application:application didFailToContinueUserActivityWithType:userActivityType error:error];
+ }
+}
+
+- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL succeeded))completionHandler {
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ [service application:application performActionForShortcutItem:shortcutItem completionHandler:completionHandler];
+ }
+}
+
+// MARK: WatchKit
+
+- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *replyInfo))reply {
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ [service application:application handleWatchKitExtensionRequest:userInfo reply:reply];
+ }
+}
+
+// MARK: HealthKit
+
+- (void)applicationShouldRequestHealthAuthorization:(UIApplication *)application {
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ [service applicationShouldRequestHealthAuthorization:application];
+ }
+}
+
+// MARK: Opening an URL
+
+- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ if ([service application:app openURL:url options:options]) {
+ return YES;
+ }
+ }
+
+ return NO;
+}
+
+// MARK: Disallowing Specified App Extension Types
+
+- (BOOL)application:(UIApplication *)application shouldAllowExtensionPointIdentifier:(UIApplicationExtensionPointIdentifier)extensionPointIdentifier {
+ BOOL result = NO;
+
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ if ([service application:application shouldAllowExtensionPointIdentifier:extensionPointIdentifier]) {
+ result = YES;
+ }
+ }
+
+ return result;
+}
+
+// MARK: SiriKit
+
+- (id)application:(UIApplication *)application handlerForIntent:(INIntent *)intent API_AVAILABLE(ios(14.0)) {
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ id result = [service application:application handlerForIntent:intent];
+
+ if (result) {
+ return result;
+ }
+ }
+
+ return nil;
+}
+
+// MARK: CloudKit
+
+- (void)application:(UIApplication *)application userDidAcceptCloudKitShareWithMetadata:(CKShareMetadata *)cloudKitShareMetadata {
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ [service application:application userDidAcceptCloudKitShareWithMetadata:cloudKitShareMetadata];
+ }
+}
+
+/* Handled By Info.plist file for now
+
+// MARK: Interface Geometry
+
+- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {}
+
+*/
+
+@end
diff --git a/platform/iphone/godot_iphone.cpp b/platform/iphone/godot_iphone.mm
index b9d217c9d2..62bc2e6e52 100644
--- a/platform/iphone/godot_iphone.cpp
+++ b/platform/iphone/godot_iphone.mm
@@ -1,12 +1,12 @@
/*************************************************************************/
-/* godot_iphone.cpp */
+/* godot_iphone.mm */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 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 */
@@ -28,7 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#include "core/ustring.h"
+#include "core/string/ustring.h"
#include "main/main.h"
#include "os_iphone.h"
@@ -38,19 +38,49 @@
static OSIPhone *os = nullptr;
-extern "C" {
-int add_path(int p_argc, char **p_args);
-int add_cmdline(int p_argc, char **p_args);
+int add_path(int, char **);
+int add_cmdline(int, char **);
+int iphone_main(int, char **, String);
+
+int add_path(int p_argc, char **p_args) {
+ NSString *str = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_path"];
+ if (!str) {
+ return p_argc;
+ }
+
+ p_args[p_argc++] = (char *)"--path";
+ p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding];
+ p_args[p_argc] = NULL;
+
+ return p_argc;
};
-int iphone_main(int, int, int, char **, String);
+int add_cmdline(int p_argc, char **p_args) {
+ NSArray *arr = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_cmdline"];
+ if (!arr) {
+ return p_argc;
+ }
+
+ for (NSUInteger i = 0; i < [arr count]; i++) {
+ NSString *str = [arr objectAtIndex:i];
+ if (!str) {
+ continue;
+ }
+ p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding];
+ };
+
+ p_args[p_argc] = NULL;
+
+ return p_argc;
+};
-int iphone_main(int width, int height, int argc, char **argv, String data_dir) {
+int iphone_main(int argc, char **argv, String data_dir) {
size_t len = strlen(argv[0]);
while (len--) {
- if (argv[0][len] == '/')
+ if (argv[0][len] == '/') {
break;
+ }
}
if (len >= 0) {
@@ -65,7 +95,10 @@ int iphone_main(int width, int height, int argc, char **argv, String data_dir) {
char cwd[512];
getcwd(cwd, sizeof(cwd));
printf("cwd %s\n", cwd);
- os = new OSIPhone(width, height, data_dir);
+ os = new OSIPhone(data_dir);
+
+ // We must override main when testing is enabled
+ TEST_MAIN_OVERRIDE
char *fargv[64];
for (int i = 0; i < argc; i++) {
@@ -76,10 +109,14 @@ int iphone_main(int width, int height, int argc, char **argv, String data_dir) {
argc = add_cmdline(argc, fargv);
printf("os created\n");
+
Error err = Main::setup(fargv[0], argc - 1, &fargv[1], false);
printf("setup %i\n", err);
- if (err != OK)
+ if (err != OK) {
return 255;
+ }
+
+ os->initialize_modules();
return 0;
};
diff --git a/platform/iphone/game_center.h b/platform/iphone/godot_view.h
index 0d3ef5b696..265f826173 100644
--- a/platform/iphone/game_center.h
+++ b/platform/iphone/godot_view.h
@@ -1,12 +1,12 @@
/*************************************************************************/
-/* game_center.h */
+/* godot_view.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 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 */
@@ -28,48 +28,35 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifdef GAME_CENTER_ENABLED
+#import <UIKit/UIKit.h>
-#ifndef GAME_CENTER_H
-#define GAME_CENTER_H
+class String;
-#include "core/object.h"
+@class GodotView;
+@protocol DisplayLayer;
+@protocol GodotViewRendererProtocol;
-class GameCenter : public Object {
- GDCLASS(GameCenter, Object);
+@protocol GodotViewDelegate
- static GameCenter *instance;
- static void _bind_methods();
+- (BOOL)godotViewFinishedSetup:(GodotView *)view;
- List<Variant> pending_events;
+@end
- bool authenticated;
+@interface GodotView : UIView
- void return_connect_error(const char *p_error_description);
+@property(assign, nonatomic) id<GodotViewRendererProtocol> renderer;
+@property(assign, nonatomic) id<GodotViewDelegate> delegate;
-public:
- void connect();
- bool is_authenticated();
+@property(assign, readonly, nonatomic) BOOL isActive;
- Error post_score(Variant p_score);
- Error award_achievement(Variant p_params);
- void reset_achievements();
- void request_achievements();
- void request_achievement_descriptions();
- Error show_game_center(Variant p_params);
- Error request_identity_verification_signature();
+@property(assign, nonatomic) BOOL useCADisplayLink;
+@property(strong, readonly, nonatomic) CALayer<DisplayLayer> *renderingLayer;
+@property(assign, readonly, nonatomic) BOOL canRender;
- void game_center_closed();
+@property(assign, nonatomic) NSTimeInterval renderingInterval;
- int get_pending_event_count();
- Variant pop_pending_event();
+- (CALayer<DisplayLayer> *)initializeRenderingForDriver:(NSString *)driverName;
+- (void)stopRendering;
+- (void)startRendering;
- static GameCenter *get_singleton();
-
- GameCenter();
- ~GameCenter();
-};
-
-#endif
-
-#endif
+@end
diff --git a/platform/iphone/godot_view.mm b/platform/iphone/godot_view.mm
new file mode 100644
index 0000000000..887297848e
--- /dev/null
+++ b/platform/iphone/godot_view.mm
@@ -0,0 +1,467 @@
+/*************************************************************************/
+/* godot_view.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#import "godot_view.h"
+#include "core/os/keyboard.h"
+#include "core/string/ustring.h"
+#import "display_layer.h"
+#include "display_server_iphone.h"
+#import "godot_view_gesture_recognizer.h"
+#import "godot_view_renderer.h"
+
+#import <CoreMotion/CoreMotion.h>
+
+static const int max_touches = 8;
+
+@interface GodotView () {
+ UITouch *godot_touches[max_touches];
+}
+
+@property(assign, nonatomic) BOOL isActive;
+
+// CADisplayLink available on 3.1+ synchronizes the animation timer & drawing with the refresh rate of the display, only supports animation intervals of 1/60 1/30 & 1/15
+@property(strong, nonatomic) CADisplayLink *displayLink;
+
+// An animation timer that, when animation is started, will periodically call -drawView at the given rate.
+// Only used if CADisplayLink is not
+@property(strong, nonatomic) NSTimer *animationTimer;
+
+@property(strong, nonatomic) CALayer<DisplayLayer> *renderingLayer;
+
+@property(strong, nonatomic) CMMotionManager *motionManager;
+
+@property(strong, nonatomic) GodotViewGestureRecognizer *delayGestureRecognizer;
+
+@end
+
+@implementation GodotView
+
+- (CALayer<DisplayLayer> *)initializeRenderingForDriver:(NSString *)driverName {
+ if (self.renderingLayer) {
+ return self.renderingLayer;
+ }
+
+ CALayer<DisplayLayer> *layer;
+
+ if ([driverName isEqualToString:@"vulkan"]) {
+ layer = [GodotMetalLayer layer];
+ } else if ([driverName isEqualToString:@"opengl_es"]) {
+ if (@available(iOS 13, *)) {
+ NSLog(@"OpenGL ES is deprecated on iOS 13");
+ }
+#if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR
+ return nil;
+#else
+ layer = [GodotOpenGLLayer layer];
+#endif
+ } else {
+ return nil;
+ }
+
+ layer.frame = self.bounds;
+ layer.contentsScale = self.contentScaleFactor;
+
+ [self.layer addSublayer:layer];
+ self.renderingLayer = layer;
+
+ [layer initializeDisplayLayer];
+
+ return self.renderingLayer;
+}
+
+- (instancetype)initWithCoder:(NSCoder *)coder {
+ self = [super initWithCoder:coder];
+
+ if (self) {
+ [self godot_commonInit];
+ }
+
+ return self;
+}
+
+- (instancetype)initWithFrame:(CGRect)frame {
+ self = [super initWithFrame:frame];
+
+ if (self) {
+ [self godot_commonInit];
+ }
+
+ return self;
+}
+
+- (void)dealloc {
+ [self stopRendering];
+
+ self.renderer = nil;
+ self.delegate = nil;
+
+ if (self.renderingLayer) {
+ [self.renderingLayer removeFromSuperlayer];
+ self.renderingLayer = nil;
+ }
+
+ if (self.motionManager) {
+ [self.motionManager stopDeviceMotionUpdates];
+ self.motionManager = nil;
+ }
+
+ if (self.displayLink) {
+ [self.displayLink invalidate];
+ self.displayLink = nil;
+ }
+
+ if (self.animationTimer) {
+ [self.animationTimer invalidate];
+ self.animationTimer = nil;
+ }
+
+ if (self.delayGestureRecognizer) {
+ self.delayGestureRecognizer = nil;
+ }
+}
+
+- (void)godot_commonInit {
+ self.contentScaleFactor = [UIScreen mainScreen].nativeScale;
+
+ [self initTouches];
+
+ // Configure and start accelerometer
+ if (!self.motionManager) {
+ self.motionManager = [[CMMotionManager alloc] init];
+ if (self.motionManager.deviceMotionAvailable) {
+ self.motionManager.deviceMotionUpdateInterval = 1.0 / 70.0;
+ [self.motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXMagneticNorthZVertical];
+ } else {
+ self.motionManager = nil;
+ }
+ }
+
+ // Initialize delay gesture recognizer
+ GodotViewGestureRecognizer *gestureRecognizer = [[GodotViewGestureRecognizer alloc] init];
+ self.delayGestureRecognizer = gestureRecognizer;
+ [self addGestureRecognizer:self.delayGestureRecognizer];
+}
+
+- (void)stopRendering {
+ if (!self.isActive) {
+ return;
+ }
+
+ self.isActive = NO;
+
+ printf("******** stop animation!\n");
+
+ if (self.useCADisplayLink) {
+ [self.displayLink invalidate];
+ self.displayLink = nil;
+ } else {
+ [self.animationTimer invalidate];
+ self.animationTimer = nil;
+ }
+
+ [self clearTouches];
+}
+
+- (void)startRendering {
+ if (self.isActive) {
+ return;
+ }
+
+ self.isActive = YES;
+
+ printf("start animation!\n");
+
+ if (self.useCADisplayLink) {
+ self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawView)];
+
+ // Approximate frame rate
+ // assumes device refreshes at 60 fps
+ int displayFPS = (NSInteger)(1.0 / self.renderingInterval);
+
+ self.displayLink.preferredFramesPerSecond = displayFPS;
+
+ // Setup DisplayLink in main thread
+ [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
+ } else {
+ self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:self.renderingInterval target:self selector:@selector(drawView) userInfo:nil repeats:YES];
+ }
+}
+
+- (void)drawView {
+ if (!self.isActive) {
+ printf("draw view not active!\n");
+ return;
+ }
+
+ if (self.useCADisplayLink) {
+ // Pause the CADisplayLink to avoid recursion
+ [self.displayLink setPaused:YES];
+
+ // Process all input events
+ while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, TRUE) == kCFRunLoopRunHandledSource)
+ ;
+
+ // We are good to go, resume the CADisplayLink
+ [self.displayLink setPaused:NO];
+ }
+
+ [self.renderingLayer renderDisplayLayer];
+
+ if (!self.renderer) {
+ return;
+ }
+
+ if ([self.renderer setupView:self]) {
+ return;
+ }
+
+ if (self.delegate) {
+ BOOL delegateFinishedSetup = [self.delegate godotViewFinishedSetup:self];
+
+ if (!delegateFinishedSetup) {
+ return;
+ }
+ }
+
+ [self handleMotion];
+ [self.renderer renderOnView:self];
+}
+
+- (BOOL)canRender {
+ if (self.useCADisplayLink) {
+ return self.displayLink != nil;
+ } else {
+ return self.animationTimer != nil;
+ }
+}
+
+- (void)setRenderingInterval:(NSTimeInterval)renderingInterval {
+ _renderingInterval = renderingInterval;
+
+ if (self.canRender) {
+ [self stopRendering];
+ [self startRendering];
+ }
+}
+
+- (void)layoutSubviews {
+ if (self.renderingLayer) {
+ self.renderingLayer.frame = self.bounds;
+ [self.renderingLayer layoutDisplayLayer];
+
+ if (DisplayServerIPhone::get_singleton()) {
+ DisplayServerIPhone::get_singleton()->resize_window(self.bounds.size);
+ }
+ }
+
+ [super layoutSubviews];
+}
+
+// MARK: - Input
+
+// MARK: Touches
+
+- (void)initTouches {
+ for (int i = 0; i < max_touches; i++) {
+ godot_touches[i] = NULL;
+ }
+}
+
+- (int)getTouchIDForTouch:(UITouch *)p_touch {
+ int first = -1;
+ for (int i = 0; i < max_touches; i++) {
+ if (first == -1 && godot_touches[i] == NULL) {
+ first = i;
+ continue;
+ }
+ if (godot_touches[i] == p_touch) {
+ return i;
+ }
+ }
+
+ if (first != -1) {
+ godot_touches[first] = p_touch;
+ return first;
+ }
+
+ return -1;
+}
+
+- (int)removeTouch:(UITouch *)p_touch {
+ int remaining = 0;
+ for (int i = 0; i < max_touches; i++) {
+ if (godot_touches[i] == NULL) {
+ continue;
+ }
+ if (godot_touches[i] == p_touch) {
+ godot_touches[i] = NULL;
+ } else {
+ ++remaining;
+ }
+ }
+ return remaining;
+}
+
+- (void)clearTouches {
+ for (int i = 0; i < max_touches; i++) {
+ godot_touches[i] = NULL;
+ }
+}
+
+- (void)touchesBegan:(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]]) {
+ UITouch *touch = [tlist objectAtIndex:i];
+ int tid = [self getTouchIDForTouch:touch];
+ ERR_FAIL_COND(tid == -1);
+ CGPoint touchPoint = [touch locationInView:self];
+ DisplayServerIPhone::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, true, touch.tapCount > 1);
+ }
+ }
+}
+
+- (void)touchesMoved:(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]]) {
+ UITouch *touch = [tlist objectAtIndex:i];
+ int tid = [self getTouchIDForTouch:touch];
+ ERR_FAIL_COND(tid == -1);
+ CGPoint touchPoint = [touch locationInView:self];
+ CGPoint prev_point = [touch previousLocationInView:self];
+ DisplayServerIPhone::get_singleton()->touch_drag(tid, prev_point.x * self.contentScaleFactor, prev_point.y * self.contentScaleFactor, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor);
+ }
+ }
+}
+
+- (void)touchesEnded:(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]]) {
+ UITouch *touch = [tlist objectAtIndex:i];
+ int tid = [self getTouchIDForTouch:touch];
+ ERR_FAIL_COND(tid == -1);
+ [self removeTouch:touch];
+ CGPoint touchPoint = [touch locationInView:self];
+ DisplayServerIPhone::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, false, false);
+ }
+ }
+}
+
+- (void)touchesCancelled:(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]]) {
+ UITouch *touch = [tlist objectAtIndex:i];
+ int tid = [self getTouchIDForTouch:touch];
+ ERR_FAIL_COND(tid == -1);
+ DisplayServerIPhone::get_singleton()->touches_cancelled(tid);
+ }
+ }
+ [self clearTouches];
+}
+
+// MARK: Motion
+
+- (void)handleMotion {
+ if (!self.motionManager) {
+ return;
+ }
+
+ // Just using polling approach for now, we can set this up so it sends
+ // data to us in intervals, might be better. See Apple reference pages
+ // for more details:
+ // https://developer.apple.com/reference/coremotion/cmmotionmanager?language=objc
+
+ // Apple splits our accelerometer date into a gravity and user movement
+ // component. We add them back together
+ CMAcceleration gravity = self.motionManager.deviceMotion.gravity;
+ CMAcceleration acceleration = self.motionManager.deviceMotion.userAcceleration;
+
+ ///@TODO We don't seem to be getting data here, is my device broken or
+ /// is this code incorrect?
+ CMMagneticField magnetic = self.motionManager.deviceMotion.magneticField.field;
+
+ ///@TODO we can access rotationRate as a CMRotationRate variable
+ ///(processed date) or CMGyroData (raw data), have to see what works
+ /// best
+ CMRotationRate rotation = self.motionManager.deviceMotion.rotationRate;
+
+ // Adjust for screen orientation.
+ // [[UIDevice currentDevice] orientation] changes even if we've fixed
+ // our orientation which is not a good thing when you're trying to get
+ // your user to move the screen in all directions and want consistent
+ // output
+
+ ///@TODO Using [[UIApplication sharedApplication] statusBarOrientation]
+ /// is a bit of a hack. Godot obviously knows the orientation so maybe
+ /// we
+ // can use that instead? (note that left and right seem swapped)
+
+ UIInterfaceOrientation interfaceOrientation = UIInterfaceOrientationUnknown;
+
+ if (@available(iOS 13, *)) {
+ interfaceOrientation = [UIApplication sharedApplication].delegate.window.windowScene.interfaceOrientation;
+#if !defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR
+ } else {
+ interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation];
+#endif
+ }
+
+ switch (interfaceOrientation) {
+ case UIInterfaceOrientationLandscapeLeft: {
+ DisplayServerIPhone::get_singleton()->update_gravity(-gravity.y, gravity.x, gravity.z);
+ DisplayServerIPhone::get_singleton()->update_accelerometer(-(acceleration.y + gravity.y), (acceleration.x + gravity.x), acceleration.z + gravity.z);
+ DisplayServerIPhone::get_singleton()->update_magnetometer(-magnetic.y, magnetic.x, magnetic.z);
+ DisplayServerIPhone::get_singleton()->update_gyroscope(-rotation.y, rotation.x, rotation.z);
+ } break;
+ case UIInterfaceOrientationLandscapeRight: {
+ DisplayServerIPhone::get_singleton()->update_gravity(gravity.y, -gravity.x, gravity.z);
+ DisplayServerIPhone::get_singleton()->update_accelerometer((acceleration.y + gravity.y), -(acceleration.x + gravity.x), acceleration.z + gravity.z);
+ DisplayServerIPhone::get_singleton()->update_magnetometer(magnetic.y, -magnetic.x, magnetic.z);
+ DisplayServerIPhone::get_singleton()->update_gyroscope(rotation.y, -rotation.x, rotation.z);
+ } break;
+ case UIInterfaceOrientationPortraitUpsideDown: {
+ DisplayServerIPhone::get_singleton()->update_gravity(-gravity.x, gravity.y, gravity.z);
+ DisplayServerIPhone::get_singleton()->update_accelerometer(-(acceleration.x + gravity.x), (acceleration.y + gravity.y), acceleration.z + gravity.z);
+ DisplayServerIPhone::get_singleton()->update_magnetometer(-magnetic.x, magnetic.y, magnetic.z);
+ DisplayServerIPhone::get_singleton()->update_gyroscope(-rotation.x, rotation.y, rotation.z);
+ } break;
+ default: { // assume portrait
+ DisplayServerIPhone::get_singleton()->update_gravity(gravity.x, gravity.y, gravity.z);
+ DisplayServerIPhone::get_singleton()->update_accelerometer(acceleration.x + gravity.x, acceleration.y + gravity.y, acceleration.z + gravity.z);
+ DisplayServerIPhone::get_singleton()->update_magnetometer(magnetic.x, magnetic.y, magnetic.z);
+ DisplayServerIPhone::get_singleton()->update_gyroscope(rotation.x, rotation.y, rotation.z);
+ } break;
+ }
+}
+
+@end
diff --git a/platform/iphone/godot_view_gesture_recognizer.h b/platform/iphone/godot_view_gesture_recognizer.h
new file mode 100644
index 0000000000..48b2d5ffad
--- /dev/null
+++ b/platform/iphone/godot_view_gesture_recognizer.h
@@ -0,0 +1,46 @@
+/*************************************************************************/
+/* godot_view_gesture_recognizer.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+// GLViewGestureRecognizer allows iOS gestures to work currectly by
+// emulating UIScrollView's UIScrollViewDelayedTouchesBeganGestureRecognizer.
+// It catches all gestures incoming to UIView and delays them for 150ms
+// (the same value used by UIScrollViewDelayedTouchesBeganGestureRecognizer)
+// If touch cancellation or end message is fired it fires delayed
+// begin touch immediately as well as last touch signal
+
+#import <UIKit/UIKit.h>
+
+@interface GodotViewGestureRecognizer : UIGestureRecognizer
+
+@property(nonatomic, readonly, assign) NSTimeInterval delayTimeInterval;
+
+- (instancetype)init;
+
+@end
diff --git a/platform/iphone/godot_view_gesture_recognizer.mm b/platform/iphone/godot_view_gesture_recognizer.mm
new file mode 100644
index 0000000000..cf6e5c5883
--- /dev/null
+++ b/platform/iphone/godot_view_gesture_recognizer.mm
@@ -0,0 +1,171 @@
+/*************************************************************************/
+/* godot_view_gesture_recognizer.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#import "godot_view_gesture_recognizer.h"
+
+#include "core/config/project_settings.h"
+
+// Minimum distance for touches to move to fire
+// a delay timer before scheduled time.
+// Should be the low enough to not cause issues with dragging
+// but big enough to allow click to work.
+const CGFloat kGLGestureMovementDistance = 0.5;
+
+@interface GodotViewGestureRecognizer ()
+
+@property(nonatomic, readwrite, assign) NSTimeInterval delayTimeInterval;
+
+@end
+
+@interface GodotViewGestureRecognizer ()
+
+// Timer used to delay begin touch message.
+// Should work as simple emulation of UIDelayedAction
+@property(strong, nonatomic) NSTimer *delayTimer;
+
+// Delayed touch parameters
+@property(strong, nonatomic) NSSet *delayedTouches;
+@property(strong, nonatomic) UIEvent *delayedEvent;
+
+@end
+
+@implementation GodotViewGestureRecognizer
+
+- (instancetype)init {
+ self = [super init];
+
+ self.cancelsTouchesInView = YES;
+ self.delaysTouchesBegan = YES;
+ self.delaysTouchesEnded = YES;
+
+ self.delayTimeInterval = GLOBAL_GET("input_devices/pointing/ios/touch_delay");
+
+ return self;
+}
+
+- (void)dealloc {
+ if (self.delayTimer) {
+ [self.delayTimer invalidate];
+ self.delayTimer = nil;
+ }
+
+ if (self.delayedTouches) {
+ self.delayedTouches = nil;
+ }
+
+ if (self.delayedEvent) {
+ self.delayedEvent = nil;
+ }
+}
+
+- (void)delayTouches:(NSSet *)touches andEvent:(UIEvent *)event {
+ [self.delayTimer fire];
+
+ self.delayedTouches = touches;
+ self.delayedEvent = event;
+
+ self.delayTimer = [NSTimer
+ scheduledTimerWithTimeInterval:self.delayTimeInterval
+ target:self
+ selector:@selector(fireDelayedTouches:)
+ userInfo:nil
+ repeats:NO];
+}
+
+- (void)fireDelayedTouches:(id)timer {
+ [self.delayTimer invalidate];
+ self.delayTimer = nil;
+
+ if (self.delayedTouches) {
+ [self.view touchesBegan:self.delayedTouches withEvent:self.delayedEvent];
+ }
+
+ self.delayedTouches = nil;
+ self.delayedEvent = nil;
+}
+
+- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
+ NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseBegan];
+ [self delayTouches:cleared andEvent:event];
+}
+
+- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
+ NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseMoved];
+
+ if (self.delayTimer) {
+ // 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];
+ CGFloat xDistance = from.x - to.x;
+ CGFloat yDistance = from.y - to.y;
+
+ CGFloat distance = sqrt(xDistance * xDistance + yDistance * yDistance);
+
+ // 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];
+ return;
+ }
+ }
+
+ return;
+ }
+
+ [self.view touchesMoved:cleared 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];
+}
+
+- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
+ [self.delayTimer fire];
+ [self.view touchesCancelled:touches withEvent:event];
+};
+
+- (NSSet *)copyClearedTouches:(NSSet *)touches phase:(UITouchPhase)phaseToSave {
+ NSMutableSet *cleared = [touches mutableCopy];
+
+ for (UITouch *touch in touches) {
+ if (touch.phase != phaseToSave) {
+ [cleared removeObject:touch];
+ }
+ }
+
+ return cleared;
+}
+
+@end
diff --git a/platform/iphone/godot_view_renderer.h b/platform/iphone/godot_view_renderer.h
new file mode 100644
index 0000000000..c6b0a05a4e
--- /dev/null
+++ b/platform/iphone/godot_view_renderer.h
@@ -0,0 +1,44 @@
+/*************************************************************************/
+/* godot_view_renderer.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#import <UIKit/UIKit.h>
+
+@protocol GodotViewRendererProtocol <NSObject>
+
+@property(assign, readonly, nonatomic) BOOL hasFinishedSetup;
+
+- (BOOL)setupView:(UIView *)view;
+- (void)renderOnView:(UIView *)view;
+
+@end
+
+@interface GodotViewRenderer : NSObject <GodotViewRendererProtocol>
+
+@end
diff --git a/platform/iphone/godot_view_renderer.mm b/platform/iphone/godot_view_renderer.mm
new file mode 100644
index 0000000000..07d664715a
--- /dev/null
+++ b/platform/iphone/godot_view_renderer.mm
@@ -0,0 +1,117 @@
+/*************************************************************************/
+/* godot_view_renderer.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#import "godot_view_renderer.h"
+#include "core/config/project_settings.h"
+#include "core/os/keyboard.h"
+#import "display_server_iphone.h"
+#include "main/main.h"
+#include "os_iphone.h"
+#include "servers/audio_server.h"
+
+#import <AudioToolbox/AudioServices.h>
+#import <CoreMotion/CoreMotion.h>
+#import <GameController/GameController.h>
+#import <QuartzCore/QuartzCore.h>
+#import <UIKit/UIKit.h>
+
+@interface GodotViewRenderer ()
+
+@property(assign, nonatomic) BOOL hasFinishedProjectDataSetup;
+@property(assign, nonatomic) BOOL hasStartedMain;
+@property(assign, nonatomic) BOOL hasFinishedSetup;
+
+@end
+
+@implementation GodotViewRenderer
+
+- (BOOL)setupView:(UIView *)view {
+ if (self.hasFinishedSetup) {
+ return NO;
+ }
+
+ if (!OS::get_singleton()) {
+ exit(0);
+ }
+
+ if (!self.hasFinishedProjectDataSetup) {
+ [self setupProjectData];
+ return YES;
+ }
+
+ if (!self.hasStartedMain) {
+ self.hasStartedMain = YES;
+ OSIPhone::get_singleton()->start();
+ return YES;
+ }
+
+ self.hasFinishedSetup = YES;
+
+ return NO;
+}
+
+- (void)setupProjectData {
+ self.hasFinishedProjectDataSetup = YES;
+
+ Main::setup2();
+
+ // this might be necessary before here
+ NSDictionary *dict = [[NSBundle mainBundle] infoDictionary];
+ for (NSString *key in dict) {
+ NSObject *value = [dict objectForKey:key];
+ String ukey = String::utf8([key UTF8String]);
+
+ // we need a NSObject to Variant conversor
+
+ if ([value isKindOfClass:[NSString class]]) {
+ NSString *str = (NSString *)value;
+ String uval = String::utf8([str UTF8String]);
+
+ ProjectSettings::get_singleton()->set("Info.plist/" + ukey, uval);
+
+ } else if ([value isKindOfClass:[NSNumber class]]) {
+ NSNumber *n = (NSNumber *)value;
+ double dval = [n doubleValue];
+
+ ProjectSettings::get_singleton()->set("Info.plist/" + ukey, dval);
+ };
+ // do stuff
+ }
+}
+
+- (void)renderOnView:(UIView *)view {
+ if (!OSIPhone::get_singleton()) {
+ return;
+ }
+
+ OSIPhone::get_singleton()->iterate();
+}
+
+@end
diff --git a/platform/iphone/icloud.mm b/platform/iphone/icloud.mm
deleted file mode 100644
index c768274b1f..0000000000
--- a/platform/iphone/icloud.mm
+++ /dev/null
@@ -1,359 +0,0 @@
-/*************************************************************************/
-/* icloud.mm */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 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. */
-/*************************************************************************/
-
-#ifdef ICLOUD_ENABLED
-
-#include "icloud.h"
-
-#ifndef __IPHONE_9_0
-extern "C" {
-#endif
-
-#import "app_delegate.h"
-
-#import <Foundation/Foundation.h>
-
-#ifndef __IPHONE_9_0
-};
-#endif
-
-ICloud *ICloud::instance = NULL;
-
-void ICloud::_bind_methods() {
- ClassDB::bind_method(D_METHOD("remove_key"), &ICloud::remove_key);
- ClassDB::bind_method(D_METHOD("set_key_values"), &ICloud::set_key_values);
- ClassDB::bind_method(D_METHOD("get_key_value"), &ICloud::get_key_value);
- ClassDB::bind_method(D_METHOD("synchronize_key_values"), &ICloud::synchronize_key_values);
- ClassDB::bind_method(D_METHOD("get_all_key_values"), &ICloud::get_all_key_values);
-
- ClassDB::bind_method(D_METHOD("get_pending_event_count"), &ICloud::get_pending_event_count);
- ClassDB::bind_method(D_METHOD("pop_pending_event"), &ICloud::pop_pending_event);
-};
-
-int ICloud::get_pending_event_count() {
- return pending_events.size();
-};
-
-Variant ICloud::pop_pending_event() {
- Variant front = pending_events.front()->get();
- pending_events.pop_front();
-
- return front;
-};
-
-ICloud *ICloud::get_singleton() {
- return instance;
-};
-
-//convert from apple's abstract type to godot's abstract type....
-Variant nsobject_to_variant(NSObject *object) {
- if ([object isKindOfClass:[NSString class]]) {
- const char *str = [(NSString *)object UTF8String];
- return String::utf8(str != NULL ? str : "");
- } else if ([object isKindOfClass:[NSData class]]) {
- PackedByteArray ret;
- NSData *data = (NSData *)object;
- if ([data length] > 0) {
- ret.resize([data length]);
- {
- // PackedByteArray::Write w = ret.write();
- copymem((void *)ret.ptr(), [data bytes], [data length]);
- }
- }
- return ret;
- } else if ([object isKindOfClass:[NSArray class]]) {
- Array result;
- NSArray *array = (NSArray *)object;
- for (unsigned int i = 0; i < [array count]; ++i) {
- NSObject *value = [array objectAtIndex:i];
- result.push_back(nsobject_to_variant(value));
- }
- return result;
- } else if ([object isKindOfClass:[NSDictionary class]]) {
- Dictionary result;
- NSDictionary *dic = (NSDictionary *)object;
-
- NSArray *keys = [dic allKeys];
- int count = [keys count];
- for (int i = 0; i < count; ++i) {
- NSObject *k = [keys objectAtIndex:i];
- NSObject *v = [dic objectForKey:k];
-
- result[nsobject_to_variant(k)] = nsobject_to_variant(v);
- }
- return result;
- } else if ([object isKindOfClass:[NSNumber class]]) {
- //Every type except numbers can reliably identify its type. The following is comparing to the *internal* representation, which isn't guaranteed to match the type that was used to create it, and is not advised, particularly when dealing with potential platform differences (ie, 32/64 bit)
- //To avoid errors, we'll cast as broadly as possible, and only return int or float.
- //bool, char, int, uint, longlong -> int
- //float, double -> float
- NSNumber *num = (NSNumber *)object;
- if (strcmp([num objCType], @encode(BOOL)) == 0) {
- return Variant((int)[num boolValue]);
- } else if (strcmp([num objCType], @encode(char)) == 0) {
- return Variant((int)[num charValue]);
- } else if (strcmp([num objCType], @encode(int)) == 0) {
- return Variant([num intValue]);
- } else if (strcmp([num objCType], @encode(unsigned int)) == 0) {
- return Variant((int)[num unsignedIntValue]);
- } else if (strcmp([num objCType], @encode(long long)) == 0) {
- return Variant((int)[num longValue]);
- } else if (strcmp([num objCType], @encode(float)) == 0) {
- return Variant([num floatValue]);
- } else if (strcmp([num objCType], @encode(double)) == 0) {
- return Variant((float)[num doubleValue]);
- } else {
- return Variant();
- }
- } else if ([object isKindOfClass:[NSDate class]]) {
- //this is a type that icloud supports...but how did you submit it in the first place?
- //I guess this is a type that *might* show up, if you were, say, trying to make your game
- //compatible with existing cloud data written by another engine's version of your game
- WARN_PRINT("NSDate unsupported, returning null Variant");
- return Variant();
- } else if ([object isKindOfClass:[NSNull class]] or object == nil) {
- return Variant();
- } else {
- WARN_PRINT("Trying to convert unknown NSObject type to Variant");
- return Variant();
- }
-}
-
-NSObject *variant_to_nsobject(Variant v) {
- if (v.get_type() == Variant::STRING) {
- return [[[NSString alloc] initWithUTF8String:((String)v).utf8().get_data()] autorelease];
- } else if (v.get_type() == Variant::REAL) {
- return [NSNumber numberWithDouble:(double)v];
- } else if (v.get_type() == Variant::INT) {
- return [NSNumber numberWithLongLong:(long)(int)v];
- } else if (v.get_type() == Variant::BOOL) {
- return [NSNumber numberWithBool:BOOL((bool)v)];
- } else if (v.get_type() == Variant::DICTIONARY) {
- NSMutableDictionary *result = [[[NSMutableDictionary alloc] init] autorelease];
- Dictionary dic = v;
- Array keys = dic.keys();
- for (unsigned int i = 0; i < keys.size(); ++i) {
- NSString *key = [[[NSString alloc] initWithUTF8String:((String)(keys[i])).utf8().get_data()] autorelease];
- NSObject *value = variant_to_nsobject(dic[keys[i]]);
-
- if (key == NULL || value == NULL) {
- return NULL;
- }
-
- [result setObject:value forKey:key];
- }
- return result;
- } else if (v.get_type() == Variant::ARRAY) {
- NSMutableArray *result = [[[NSMutableArray alloc] init] autorelease];
- Array arr = v;
- for (unsigned int i = 0; i < arr.size(); ++i) {
- NSObject *value = variant_to_nsobject(arr[i]);
- if (value == NULL) {
- //trying to add something unsupported to the array. cancel the whole array
- return NULL;
- }
- [result addObject:value];
- }
- return result;
- } else if (v.get_type() == Variant::PACKED_BYTE_ARRAY) {
- PackedByteArray arr = v;
- // PackedByteArray::Read r = arr.read();
- NSData *result = [NSData dataWithBytes:arr.ptr() length:arr.size()];
- return result;
- }
- WARN_PRINT(String("Could not add unsupported type to iCloud: '" + Variant::get_type_name(v.get_type()) + "'").utf8().get_data());
- return NULL;
-}
-
-Error ICloud::remove_key(Variant p_param) {
- String param = p_param;
- NSString *key = [[[NSString alloc] initWithUTF8String:param.utf8().get_data()] autorelease];
-
- NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];
-
- if (![[store dictionaryRepresentation] objectForKey:key]) {
- return ERR_INVALID_PARAMETER;
- }
-
- [store removeObjectForKey:key];
- return OK;
-}
-
-//return an array of the keys that could not be set
-Variant ICloud::set_key_values(Variant p_params) {
- Dictionary params = p_params;
- Array keys = params.keys();
-
- Array error_keys;
-
- for (unsigned int i = 0; i < keys.size(); ++i) {
- String variant_key = keys[i];
- Variant variant_value = params[variant_key];
-
- NSString *key = [[[NSString alloc] initWithUTF8String:variant_key.utf8().get_data()] autorelease];
- if (key == NULL) {
- error_keys.push_back(variant_key);
- continue;
- }
-
- NSObject *value = variant_to_nsobject(variant_value);
-
- if (value == NULL) {
- error_keys.push_back(variant_key);
- continue;
- }
-
- NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];
- [store setObject:value forKey:key];
- }
-
- return error_keys;
-}
-
-Variant ICloud::get_key_value(Variant p_param) {
- String param = p_param;
-
- NSString *key = [[[NSString alloc] initWithUTF8String:param.utf8().get_data()] autorelease];
- NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];
-
- if (![[store dictionaryRepresentation] objectForKey:key]) {
- return Variant();
- }
-
- Variant result = nsobject_to_variant([[store dictionaryRepresentation] objectForKey:key]);
-
- return result;
-}
-
-Variant ICloud::get_all_key_values() {
- Dictionary result;
-
- NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];
- NSDictionary *store_dictionary = [store dictionaryRepresentation];
-
- NSArray *keys = [store_dictionary allKeys];
- int count = [keys count];
- for (int i = 0; i < count; ++i) {
- NSString *k = [keys objectAtIndex:i];
- NSObject *v = [store_dictionary objectForKey:k];
-
- const char *str = [k UTF8String];
- if (str != NULL) {
- result[String::utf8(str)] = nsobject_to_variant(v);
- }
- }
-
- return result;
-}
-
-Error ICloud::synchronize_key_values() {
- NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];
- BOOL result = [store synchronize];
- if (result == YES) {
- return OK;
- } else {
- return FAILED;
- }
-}
-
-/*
-Error ICloud::initial_sync() {
- //you sometimes have to write something to the store to get it to download new data. go apple!
- NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];
- if ([store boolForKey:@"isb"])
- {
- [store setBool:NO forKey:@"isb"];
- }
- else
- {
- [store setBool:YES forKey:@"isb"];
- }
- return synchronize();
-}
-
-*/
-ICloud::ICloud() {
- ERR_FAIL_COND(instance != NULL);
- instance = this;
- //connected = false;
-
- [[NSNotificationCenter defaultCenter]
- addObserverForName:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
- object:[NSUbiquitousKeyValueStore defaultStore]
- queue:nil
- usingBlock:^(NSNotification *notification) {
- NSDictionary *userInfo = [notification userInfo];
- NSInteger change = [[userInfo objectForKey:NSUbiquitousKeyValueStoreChangeReasonKey] integerValue];
-
- Dictionary ret;
- ret["type"] = "key_value_changed";
-
- //PackedStringArray result_keys;
- //Array result_values;
- Dictionary keyValues;
- String reason = "";
-
- if (change == NSUbiquitousKeyValueStoreServerChange) {
- reason = "server";
- } else if (change == NSUbiquitousKeyValueStoreInitialSyncChange) {
- reason = "initial_sync";
- } else if (change == NSUbiquitousKeyValueStoreQuotaViolationChange) {
- reason = "quota_violation";
- } else if (change == NSUbiquitousKeyValueStoreAccountChange) {
- reason = "account";
- }
-
- ret["reason"] = reason;
-
- NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];
-
- NSArray *keys = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangedKeysKey];
- for (NSString *key in keys) {
- const char *str = [key UTF8String];
- if (str == NULL) {
- continue;
- }
-
- NSObject *object = [store objectForKey:key];
-
- //figure out what kind of object it is
- Variant value = nsobject_to_variant(object);
-
- keyValues[String::utf8(str)] = value;
- }
-
- ret["changed_values"] = keyValues;
- pending_events.push_back(ret);
- }];
-}
-
-ICloud::~ICloud() {}
-
-#endif
diff --git a/platform/iphone/in_app_store.mm b/platform/iphone/in_app_store.mm
deleted file mode 100644
index a2efd6691b..0000000000
--- a/platform/iphone/in_app_store.mm
+++ /dev/null
@@ -1,326 +0,0 @@
-/*************************************************************************/
-/* in_app_store.mm */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 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. */
-/*************************************************************************/
-
-#ifdef STOREKIT_ENABLED
-
-#include "in_app_store.h"
-
-extern "C" {
-#import <Foundation/Foundation.h>
-#import <StoreKit/StoreKit.h>
-};
-
-bool auto_finish_transactions = true;
-NSMutableDictionary *pending_transactions = [NSMutableDictionary dictionary];
-
-@interface SKProduct (LocalizedPrice)
-@property(nonatomic, readonly) NSString *localizedPrice;
-@end
-
-//----------------------------------//
-// SKProduct extension
-//----------------------------------//
-@implementation SKProduct (LocalizedPrice)
-- (NSString *)localizedPrice {
- NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
- [numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
- [numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
- [numberFormatter setLocale:self.priceLocale];
- NSString *formattedString = [numberFormatter stringFromNumber:self.price];
- [numberFormatter release];
- return formattedString;
-}
-
-@end
-
-InAppStore *InAppStore::instance = NULL;
-
-void InAppStore::_bind_methods() {
- ClassDB::bind_method(D_METHOD("request_product_info"), &InAppStore::request_product_info);
- ClassDB::bind_method(D_METHOD("restore_purchases"), &InAppStore::restore_purchases);
- ClassDB::bind_method(D_METHOD("purchase"), &InAppStore::purchase);
-
- ClassDB::bind_method(D_METHOD("get_pending_event_count"), &InAppStore::get_pending_event_count);
- ClassDB::bind_method(D_METHOD("pop_pending_event"), &InAppStore::pop_pending_event);
- ClassDB::bind_method(D_METHOD("finish_transaction"), &InAppStore::finish_transaction);
- ClassDB::bind_method(D_METHOD("set_auto_finish_transaction"), &InAppStore::set_auto_finish_transaction);
-};
-
-@interface ProductsDelegate : NSObject <SKProductsRequestDelegate> {
-};
-
-@end
-
-@implementation ProductsDelegate
-
-- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
- NSArray *products = response.products;
- Dictionary ret;
- ret["type"] = "product_info";
- ret["result"] = "ok";
- PackedStringArray titles;
- PackedStringArray descriptions;
- PackedFloat32Array prices;
- PackedStringArray ids;
- PackedStringArray localized_prices;
- PackedStringArray currency_codes;
-
- for (NSUInteger i = 0; i < [products count]; i++) {
- SKProduct *product = [products objectAtIndex:i];
-
- const char *str = [product.localizedTitle UTF8String];
- titles.push_back(String::utf8(str != NULL ? str : ""));
-
- str = [product.localizedDescription UTF8String];
- descriptions.push_back(String::utf8(str != NULL ? str : ""));
- prices.push_back([product.price doubleValue]);
- ids.push_back(String::utf8([product.productIdentifier UTF8String]));
- localized_prices.push_back(String::utf8([product.localizedPrice UTF8String]));
- currency_codes.push_back(String::utf8([[[product priceLocale] objectForKey:NSLocaleCurrencyCode] UTF8String]));
- };
- ret["titles"] = titles;
- ret["descriptions"] = descriptions;
- ret["prices"] = prices;
- ret["ids"] = ids;
- ret["localized_prices"] = localized_prices;
- ret["currency_codes"] = currency_codes;
-
- PackedStringArray invalid_ids;
-
- for (NSString *ipid in response.invalidProductIdentifiers) {
- invalid_ids.push_back(String::utf8([ipid UTF8String]));
- };
- ret["invalid_ids"] = invalid_ids;
-
- InAppStore::get_singleton()->_post_event(ret);
-
- [request release];
-};
-
-@end
-
-Error InAppStore::request_product_info(Variant p_params) {
- Dictionary params = p_params;
- ERR_FAIL_COND_V(!params.has("product_ids"), ERR_INVALID_PARAMETER);
-
- PackedStringArray pids = params["product_ids"];
- printf("************ request product info! %i\n", pids.size());
-
- NSMutableArray *array = [[[NSMutableArray alloc] initWithCapacity:pids.size()] autorelease];
- for (int i = 0; i < pids.size(); i++) {
- printf("******** adding %ls to product list\n", pids[i].c_str());
- NSString *pid = [[[NSString alloc] initWithUTF8String:pids[i].utf8().get_data()] autorelease];
- [array addObject:pid];
- };
-
- NSSet *products = [[[NSSet alloc] initWithArray:array] autorelease];
- SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:products];
-
- ProductsDelegate *delegate = [[ProductsDelegate alloc] init];
-
- request.delegate = delegate;
- [request start];
-
- return OK;
-};
-
-Error InAppStore::restore_purchases() {
- printf("restoring purchases!\n");
- [[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
-
- return OK;
-};
-
-@interface TransObserver : NSObject <SKPaymentTransactionObserver> {
-};
-@end
-
-@implementation TransObserver
-
-- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
- printf("transactions updated!\n");
- for (SKPaymentTransaction *transaction in transactions) {
- switch (transaction.transactionState) {
- case SKPaymentTransactionStatePurchased: {
- printf("status purchased!\n");
- String pid = String::utf8([transaction.payment.productIdentifier UTF8String]);
- String transactionId = String::utf8([transaction.transactionIdentifier UTF8String]);
- InAppStore::get_singleton()->_record_purchase(pid);
- Dictionary ret;
- ret["type"] = "purchase";
- ret["result"] = "ok";
- ret["product_id"] = pid;
- ret["transaction_id"] = transactionId;
-
- NSData *receipt = nil;
- int sdk_version = 6;
-
- if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0) {
- NSURL *receiptFileURL = nil;
- NSBundle *bundle = [NSBundle mainBundle];
- if ([bundle respondsToSelector:@selector(appStoreReceiptURL)]) {
- // Get the transaction receipt file path location in the app bundle.
- receiptFileURL = [bundle appStoreReceiptURL];
-
- // Read in the contents of the transaction file.
- receipt = [NSData dataWithContentsOfURL:receiptFileURL];
- sdk_version = 7;
-
- } else {
- // Fall back to deprecated transaction receipt,
- // which is still available in iOS 7.
-
- // Use SKPaymentTransaction's transactionReceipt.
- receipt = transaction.transactionReceipt;
- }
-
- } else {
- receipt = transaction.transactionReceipt;
- }
-
- NSString *receipt_to_send = nil;
- if (receipt != nil) {
- receipt_to_send = [receipt description];
- }
- Dictionary receipt_ret;
- receipt_ret["receipt"] = String::utf8(receipt_to_send != nil ? [receipt_to_send UTF8String] : "");
- receipt_ret["sdk"] = sdk_version;
- ret["receipt"] = receipt_ret;
-
- InAppStore::get_singleton()->_post_event(ret);
-
- if (auto_finish_transactions) {
- [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
- } else {
- [pending_transactions setObject:transaction forKey:transaction.payment.productIdentifier];
- }
-
- }; break;
- case SKPaymentTransactionStateFailed: {
- printf("status transaction failed!\n");
- String pid = String::utf8([transaction.payment.productIdentifier UTF8String]);
- Dictionary ret;
- ret["type"] = "purchase";
- ret["result"] = "error";
- ret["product_id"] = pid;
- ret["error"] = String::utf8([transaction.error.localizedDescription UTF8String]);
- InAppStore::get_singleton()->_post_event(ret);
- [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
- } break;
- case SKPaymentTransactionStateRestored: {
- printf("status transaction restored!\n");
- String pid = String::utf8([transaction.originalTransaction.payment.productIdentifier UTF8String]);
- InAppStore::get_singleton()->_record_purchase(pid);
- Dictionary ret;
- ret["type"] = "restore";
- ret["result"] = "ok";
- ret["product_id"] = pid;
- InAppStore::get_singleton()->_post_event(ret);
- [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
- } break;
- default: {
- printf("status default %i!\n", (int)transaction.transactionState);
- }; break;
- };
- };
-};
-
-@end
-
-Error InAppStore::purchase(Variant p_params) {
- ERR_FAIL_COND_V(![SKPaymentQueue canMakePayments], ERR_UNAVAILABLE);
- if (![SKPaymentQueue canMakePayments])
- return ERR_UNAVAILABLE;
-
- printf("purchasing!\n");
- Dictionary params = p_params;
- ERR_FAIL_COND_V(!params.has("product_id"), ERR_INVALID_PARAMETER);
-
- NSString *pid = [[[NSString alloc] initWithUTF8String:String(params["product_id"]).utf8().get_data()] autorelease];
- SKPayment *payment = [SKPayment paymentWithProductIdentifier:pid];
- SKPaymentQueue *defq = [SKPaymentQueue defaultQueue];
- [defq addPayment:payment];
- printf("purchase sent!\n");
-
- return OK;
-};
-
-int InAppStore::get_pending_event_count() {
- return pending_events.size();
-};
-
-Variant InAppStore::pop_pending_event() {
- Variant front = pending_events.front()->get();
- pending_events.pop_front();
-
- return front;
-};
-
-void InAppStore::_post_event(Variant p_event) {
- pending_events.push_back(p_event);
-};
-
-void InAppStore::_record_purchase(String product_id) {
- String skey = "purchased/" + product_id;
- NSString *key = [[[NSString alloc] initWithUTF8String:skey.utf8().get_data()] autorelease];
- [[NSUserDefaults standardUserDefaults] setBool:YES forKey:key];
- [[NSUserDefaults standardUserDefaults] synchronize];
-};
-
-InAppStore *InAppStore::get_singleton() {
- return instance;
-};
-
-InAppStore::InAppStore() {
- ERR_FAIL_COND(instance != NULL);
- instance = this;
- auto_finish_transactions = false;
-
- TransObserver *observer = [[TransObserver alloc] init];
- [[SKPaymentQueue defaultQueue] addTransactionObserver:observer];
- //pending_transactions = [NSMutableDictionary dictionary];
-};
-
-void InAppStore::finish_transaction(String product_id) {
- NSString *prod_id = [NSString stringWithCString:product_id.utf8().get_data() encoding:NSUTF8StringEncoding];
-
- if ([pending_transactions objectForKey:prod_id]) {
- [[SKPaymentQueue defaultQueue] finishTransaction:[pending_transactions objectForKey:prod_id]];
- [pending_transactions removeObjectForKey:prod_id];
- }
-};
-
-void InAppStore::set_auto_finish_transaction(bool b) {
- auto_finish_transactions = b;
-}
-
-InAppStore::~InAppStore() {}
-
-#endif
diff --git a/platform/iphone/ios.h b/platform/iphone/ios.h
index 2b29e6f268..4bf64d4bc1 100644
--- a/platform/iphone/ios.h
+++ b/platform/iphone/ios.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 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,7 +31,7 @@
#ifndef IOS_H
#define IOS_H
-#include "core/object.h"
+#include "core/object/class_db.h"
class iOS : public Object {
GDCLASS(iOS, Object);
diff --git a/platform/iphone/ios.mm b/platform/iphone/ios.mm
index 5923f558a5..cef03534c4 100644
--- a/platform/iphone/ios.mm
+++ b/platform/iphone/ios.mm
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 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,17 +29,28 @@
/*************************************************************************/
#include "ios.h"
-#include <sys/sysctl.h>
-
+#import "app_delegate.h"
+#import "view_controller.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);
};
void iOS::alert(const char *p_alert, const char *p_title) {
- UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:[NSString stringWithUTF8String:p_title] message:[NSString stringWithUTF8String:p_alert] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil, nil] autorelease];
- [alert show];
+ NSString *title = [NSString stringWithUTF8String:p_title];
+ NSString *message = [NSString stringWithUTF8String:p_alert];
+
+ UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
+ UIAlertAction *button = [UIAlertAction actionWithTitle:@"OK"
+ style:UIAlertActionStyleCancel
+ handler:^(id){
+ }];
+
+ [alert addAction:button];
+
+ [AppDelegate.viewController presentViewController:alert animated:YES completion:nil];
}
String iOS::get_model() const {
@@ -58,25 +69,11 @@ String iOS::get_model() const {
}
String iOS::get_rate_url(int p_app_id) const {
- String templ = "itms-apps://ax.itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?type=Purple+Software&id=APP_ID";
- String templ_iOS7 = "itms-apps://itunes.apple.com/app/idAPP_ID";
- String templ_iOS8 = "itms-apps://itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?id=APP_ID&onlyLatestVersion=true&pageNumber=0&sortOrdering=1&type=Purple+Software";
-
- //ios7 before
- String ret = templ;
-
- if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0 && [[[UIDevice currentDevice] systemVersion] floatValue] < 7.1) {
- // iOS 7 needs a different templateReviewURL @see https://github.com/arashpayan/appirater/issues/131
- ret = templ_iOS7;
- } else if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0) {
- // iOS 8 needs a different templateReviewURL also @see https://github.com/arashpayan/appirater/issues/182
- ret = templ_iOS8;
- }
+ String app_url_path = "itms-apps://itunes.apple.com/app/idAPP_ID";
- // ios7 for everything?
- ret = templ_iOS7.replace("APP_ID", String::num(p_app_id));
+ String ret = app_url_path.replace("APP_ID", String::num(p_app_id));
- printf("returning rate url %ls\n", ret.c_str());
+ printf("returning rate url %s\n", ret.utf8().get_data());
return ret;
};
diff --git a/platform/iphone/icloud.h b/platform/iphone/joypad_iphone.h
index b11e22fec6..329cc8207f 100644
--- a/platform/iphone/icloud.h
+++ b/platform/iphone/joypad_iphone.h
@@ -1,12 +1,12 @@
/*************************************************************************/
-/* icloud.h */
+/* joypad_iphone.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 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 */
@@ -28,37 +28,23 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifdef ICLOUD_ENABLED
+#import <GameController/GameController.h>
-#ifndef ICLOUD_H
-#define ICLOUD_H
+@interface JoypadIPhoneObserver : NSObject
-#include "core/object.h"
+- (void)startObserving;
+- (void)startProcessing;
+- (void)finishObserving;
-class ICloud : public Object {
- GDCLASS(ICloud, Object);
+@end
- static ICloud *instance;
- static void _bind_methods();
-
- List<Variant> pending_events;
+class JoypadIPhone {
+private:
+ JoypadIPhoneObserver *observer;
public:
- Error remove_key(Variant p_param);
- Variant set_key_values(Variant p_param);
- Variant get_key_value(Variant p_param);
- Error synchronize_key_values();
- Variant get_all_key_values();
-
- int get_pending_event_count();
- Variant pop_pending_event();
-
- static ICloud *get_singleton();
+ JoypadIPhone();
+ ~JoypadIPhone();
- ICloud();
- ~ICloud();
+ void start_processing();
};
-
-#endif
-
-#endif
diff --git a/platform/iphone/joypad_iphone.mm b/platform/iphone/joypad_iphone.mm
new file mode 100644
index 0000000000..a0f0eee5d3
--- /dev/null
+++ b/platform/iphone/joypad_iphone.mm
@@ -0,0 +1,345 @@
+/*************************************************************************/
+/* joypad_iphone.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#import "joypad_iphone.h"
+#include "core/config/project_settings.h"
+#include "drivers/coreaudio/audio_driver_coreaudio.h"
+#include "main/main.h"
+
+#import "godot_view.h"
+
+#include "os_iphone.h"
+
+JoypadIPhone::JoypadIPhone() {
+ observer = [[JoypadIPhoneObserver alloc] init];
+ [observer startObserving];
+}
+
+JoypadIPhone::~JoypadIPhone() {
+ if (observer) {
+ [observer finishObserving];
+ observer = nil;
+ }
+}
+
+void JoypadIPhone::start_processing() {
+ if (observer) {
+ [observer startProcessing];
+ }
+}
+
+@interface JoypadIPhoneObserver ()
+
+@property(assign, nonatomic) BOOL isObserving;
+@property(assign, nonatomic) BOOL isProcessing;
+@property(strong, nonatomic) NSMutableDictionary *connectedJoypads;
+@property(strong, nonatomic) NSMutableArray *joypadsQueue;
+
+@end
+
+@implementation JoypadIPhoneObserver
+
+- (instancetype)init {
+ self = [super init];
+
+ if (self) {
+ [self godot_commonInit];
+ }
+
+ return self;
+}
+
+- (void)godot_commonInit {
+ self.isObserving = NO;
+ self.isProcessing = NO;
+}
+
+- (void)startProcessing {
+ self.isProcessing = YES;
+
+ for (GCController *controller in self.joypadsQueue) {
+ [self addiOSJoypad:controller];
+ }
+
+ [self.joypadsQueue removeAllObjects];
+}
+
+- (void)startObserving {
+ if (self.isObserving) {
+ return;
+ }
+
+ self.isObserving = YES;
+
+ self.connectedJoypads = [NSMutableDictionary dictionary];
+ self.joypadsQueue = [NSMutableArray array];
+
+ // get told when controllers connect, this will be called right away for
+ // already connected controllers
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(controllerWasConnected:)
+ name:GCControllerDidConnectNotification
+ object:nil];
+
+ // get told when controllers disconnect
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(controllerWasDisconnected:)
+ name:GCControllerDidDisconnectNotification
+ object:nil];
+}
+
+- (void)finishObserving {
+ if (self.isObserving) {
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ }
+
+ self.isObserving = NO;
+ self.isProcessing = NO;
+
+ self.connectedJoypads = nil;
+ self.joypadsQueue = nil;
+}
+
+- (void)dealloc {
+ [self finishObserving];
+}
+
+- (int)getJoyIdForController:(GCController *)controller {
+ NSArray *keys = [self.connectedJoypads allKeysForObject:controller];
+
+ 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
+ int joy_id = Input::get_singleton()->get_unused_joy_id();
+
+ if (joy_id == -1) {
+ printf("Couldn't retrieve new joy id\n");
+ return;
+ }
+
+ // 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]);
+
+ // add it to our dictionary, this will retain our controllers
+ [self.connectedJoypads setObject:controller forKey:[NSNumber numberWithInt:joy_id]];
+
+ // set our input handler
+ [self setControllerInputHandler:controller];
+}
+
+- (void)controllerWasConnected:(NSNotification *)notification {
+ // get our controller
+ GCController *controller = (GCController *)notification.object;
+
+ if (!controller) {
+ printf("Couldn't retrieve new controller\n");
+ return;
+ }
+
+ if ([[self.connectedJoypads allKeysForObject:controller] count] > 0) {
+ printf("Controller is already registered\n");
+ } else if (!self.isProcessing) {
+ [self.joypadsQueue addObject:controller];
+ } else {
+ [self addiOSJoypad:controller];
+ }
+}
+
+- (void)controllerWasDisconnected:(NSNotification *)notification {
+ // find our joystick, there should be only one in our dictionary
+ GCController *controller = (GCController *)notification.object;
+
+ if (!controller) {
+ return;
+ }
+
+ NSArray *keys = [self.connectedJoypads allKeysForObject:controller];
+ for (NSNumber *key in keys) {
+ // tell Godot this joystick is no longer there
+ int joy_id = [key intValue];
+ Input::get_singleton()->joy_connection_changed(joy_id, false, "");
+
+ // and remove it from our dictionary
+ [self.connectedJoypads removeObjectForKey:key];
+ };
+};
+
+- (GCControllerPlayerIndex)getFreePlayerIndex {
+ bool have_player_1 = false;
+ bool have_player_2 = false;
+ bool have_player_3 = false;
+ bool have_player_4 = false;
+
+ if (self.connectedJoypads == nil) {
+ NSArray *keys = [self.connectedJoypads allKeys];
+ for (NSNumber *key in keys) {
+ GCController *controller = [self.connectedJoypads objectForKey:key];
+ if (controller.playerIndex == GCControllerPlayerIndex1) {
+ have_player_1 = true;
+ } else if (controller.playerIndex == GCControllerPlayerIndex2) {
+ have_player_2 = true;
+ } else if (controller.playerIndex == GCControllerPlayerIndex3) {
+ have_player_3 = true;
+ } else if (controller.playerIndex == GCControllerPlayerIndex4) {
+ have_player_4 = true;
+ };
+ };
+ };
+
+ if (!have_player_1) {
+ return GCControllerPlayerIndex1;
+ } else if (!have_player_2) {
+ return GCControllerPlayerIndex2;
+ } else if (!have_player_3) {
+ return GCControllerPlayerIndex3;
+ } else if (!have_player_4) {
+ return GCControllerPlayerIndex4;
+ } else {
+ return GCControllerPlayerIndexUnset;
+ };
+}
+
+- (void)setControllerInputHandler:(GCController *)controller {
+ // Hook in the callback handler for the correct gamepad profile.
+ // This is a bit of a weird design choice on Apples part.
+ // You need to select the most capable gamepad profile for the
+ // gamepad attached.
+ if (controller.extendedGamepad != nil) {
+ // The extended gamepad profile has all the input you could possibly find on
+ // a gamepad but will only be active if your gamepad actually has all of
+ // these...
+ _weakify(self);
+ _weakify(controller);
+
+ controller.extendedGamepad.valueChangedHandler = ^(GCExtendedGamepad *gamepad, GCControllerElement *element) {
+ _strongify(self);
+ _strongify(controller);
+
+ int joy_id = [self getJoyIdForController:controller];
+
+ if (element == gamepad.buttonA) {
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_A,
+ gamepad.buttonA.isPressed);
+ } else if (element == gamepad.buttonB) {
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_B,
+ gamepad.buttonB.isPressed);
+ } else if (element == gamepad.buttonX) {
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_X,
+ gamepad.buttonX.isPressed);
+ } else if (element == gamepad.buttonY) {
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_Y,
+ gamepad.buttonY.isPressed);
+ } else if (element == gamepad.leftShoulder) {
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_LEFT_SHOULDER,
+ gamepad.leftShoulder.isPressed);
+ } else if (element == gamepad.rightShoulder) {
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_RIGHT_SHOULDER,
+ gamepad.rightShoulder.isPressed);
+ } else if (element == gamepad.dpad) {
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP,
+ gamepad.dpad.up.isPressed);
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN,
+ gamepad.dpad.down.isPressed);
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT,
+ gamepad.dpad.left.isPressed);
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT,
+ gamepad.dpad.right.isPressed);
+ };
+
+ Input::JoyAxis jx;
+ jx.min = -1;
+ if (element == gamepad.leftThumbstick) {
+ jx.value = gamepad.leftThumbstick.xAxis.value;
+ Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_LEFT_X, jx);
+ jx.value = -gamepad.leftThumbstick.yAxis.value;
+ Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_LEFT_Y, jx);
+ } else if (element == gamepad.rightThumbstick) {
+ jx.value = gamepad.rightThumbstick.xAxis.value;
+ Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_RIGHT_X, jx);
+ jx.value = -gamepad.rightThumbstick.yAxis.value;
+ Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_RIGHT_Y, jx);
+ } else if (element == gamepad.leftTrigger) {
+ jx.value = gamepad.leftTrigger.value;
+ Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_TRIGGER_LEFT, jx);
+ } else if (element == gamepad.rightTrigger) {
+ jx.value = gamepad.rightTrigger.value;
+ Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_TRIGGER_RIGHT, jx);
+ };
+ };
+ } else if (controller.microGamepad != nil) {
+ // micro gamepads were added in OS 9 and feature just 2 buttons and a d-pad
+ _weakify(self);
+ _weakify(controller);
+
+ controller.microGamepad.valueChangedHandler = ^(GCMicroGamepad *gamepad, GCControllerElement *element) {
+ _strongify(self);
+ _strongify(controller);
+
+ int joy_id = [self getJoyIdForController:controller];
+
+ if (element == gamepad.buttonA) {
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_A,
+ gamepad.buttonA.isPressed);
+ } else if (element == gamepad.buttonX) {
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_X,
+ gamepad.buttonX.isPressed);
+ } else if (element == gamepad.dpad) {
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP,
+ gamepad.dpad.up.isPressed);
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN,
+ gamepad.dpad.down.isPressed);
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT, gamepad.dpad.left.isPressed);
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT, gamepad.dpad.right.isPressed);
+ };
+ };
+ }
+
+ ///@TODO need to add support for controller.motion which gives us access to
+ /// the orientation of the device (if supported)
+
+ ///@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
new file mode 100644
index 0000000000..cfbfc7a120
--- /dev/null
+++ b/platform/iphone/keyboard_input_view.h
@@ -0,0 +1,37 @@
+/*************************************************************************/
+/* keyboard_input_view.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#import <UIKit/UIKit.h>
+
+@interface GodotKeyboardInputView : UITextView
+
+- (BOOL)becomeFirstResponderWithString:(NSString *)existingString multiline:(BOOL)flag cursorStart:(NSInteger)start cursorEnd:(NSInteger)end;
+
+@end
diff --git a/platform/iphone/keyboard_input_view.mm b/platform/iphone/keyboard_input_view.mm
new file mode 100644
index 0000000000..1408f78e90
--- /dev/null
+++ b/platform/iphone/keyboard_input_view.mm
@@ -0,0 +1,195 @@
+/*************************************************************************/
+/* keyboard_input_view.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#import "keyboard_input_view.h"
+
+#include "core/os/keyboard.h"
+#include "display_server_iphone.h"
+#include "os_iphone.h"
+
+@interface GodotKeyboardInputView () <UITextViewDelegate>
+
+@property(nonatomic, copy) NSString *previousText;
+@property(nonatomic, assign) NSRange previousSelectedRange;
+
+@end
+
+@implementation GodotKeyboardInputView
+
+- (instancetype)initWithCoder:(NSCoder *)coder {
+ self = [super initWithCoder:coder];
+
+ if (self) {
+ [self godot_commonInit];
+ }
+
+ return self;
+}
+
+- (instancetype)initWithFrame:(CGRect)frame textContainer:(NSTextContainer *)textContainer {
+ self = [super initWithFrame:frame textContainer:textContainer];
+
+ if (self) {
+ [self godot_commonInit];
+ }
+
+ return self;
+}
+
+- (void)godot_commonInit {
+ self.hidden = YES;
+ self.delegate = self;
+
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(observeTextChange:)
+ name:UITextViewTextDidChangeNotification
+ object:self];
+}
+
+- (void)dealloc {
+ self.delegate = nil;
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+}
+
+// MARK: Keyboard
+
+- (BOOL)canBecomeFirstResponder {
+ return YES;
+}
+
+- (BOOL)becomeFirstResponderWithString:(NSString *)existingString multiline:(BOOL)flag cursorStart:(NSInteger)start cursorEnd:(NSInteger)end {
+ self.text = existingString;
+ self.previousText = existingString;
+
+ NSRange textRange;
+
+ // Either a simple cursor or a selection.
+ if (end > 0) {
+ textRange = NSMakeRange(start, end - start);
+ } else {
+ textRange = NSMakeRange(start, 0);
+ }
+
+ self.selectedRange = textRange;
+ self.previousSelectedRange = textRange;
+
+ return [self becomeFirstResponder];
+}
+
+- (BOOL)resignFirstResponder {
+ self.text = nil;
+ self.previousText = nil;
+ return [super resignFirstResponder];
+}
+
+// MARK: OS Messages
+
+- (void)deleteText:(NSInteger)charactersToDelete {
+ for (int i = 0; i < charactersToDelete; i++) {
+ DisplayServerIPhone::get_singleton()->key(KEY_BACKSPACE, true);
+ DisplayServerIPhone::get_singleton()->key(KEY_BACKSPACE, false);
+ }
+}
+
+- (void)enterText:(NSString *)substring {
+ String characters;
+ characters.parse_utf8([substring UTF8String]);
+
+ for (int i = 0; i < characters.size(); i++) {
+ int character = characters[i];
+
+ switch (character) {
+ case 10:
+ character = KEY_ENTER;
+ break;
+ case 8198:
+ character = KEY_SPACE;
+ break;
+ default:
+ break;
+ }
+
+ DisplayServerIPhone::get_singleton()->key(character, true);
+ DisplayServerIPhone::get_singleton()->key(character, false);
+ }
+}
+
+// MARK: Observer
+
+- (void)observeTextChange:(NSNotification *)notification {
+ if (notification.object != self) {
+ return;
+ }
+
+ if (self.previousSelectedRange.length == 0) {
+ // We are deleting all text before cursor if no range was selected.
+ // This way any inserted or changed text will be updated.
+ NSString *substringToDelete = [self.previousText substringToIndex:self.previousSelectedRange.location];
+ [self deleteText:substringToDelete.length];
+ } else {
+ // If text was previously selected
+ // we are sending only one `backspace`.
+ // It will remove all text from text input.
+ [self deleteText:1];
+ }
+
+ NSString *substringToEnter;
+
+ if (self.selectedRange.length == 0) {
+ // If previous cursor had a selection
+ // we have to calculate an inserted text.
+ if (self.previousSelectedRange.length != 0) {
+ NSInteger rangeEnd = self.selectedRange.location + self.selectedRange.length;
+ NSInteger rangeStart = MIN(self.previousSelectedRange.location, self.selectedRange.location);
+ NSInteger rangeLength = MAX(0, rangeEnd - rangeStart);
+
+ NSRange calculatedRange;
+
+ if (rangeLength >= 0) {
+ calculatedRange = NSMakeRange(rangeStart, rangeLength);
+ } else {
+ calculatedRange = NSMakeRange(rangeStart, 0);
+ }
+
+ substringToEnter = [self.text substringWithRange:calculatedRange];
+ } else {
+ substringToEnter = [self.text substringToIndex:self.selectedRange.location];
+ }
+ } else {
+ substringToEnter = [self.text substringWithRange:self.selectedRange];
+ }
+
+ [self enterText:substringToEnter];
+
+ self.previousText = self.text;
+ self.previousSelectedRange = self.selectedRange;
+}
+
+@end
diff --git a/platform/iphone/logo.png b/platform/iphone/logo.png
index 405b6f93ca..966d8aa70a 100644
--- a/platform/iphone/logo.png
+++ b/platform/iphone/logo.png
Binary files differ
diff --git a/platform/iphone/main.m b/platform/iphone/main.m
index 164db2a74b..fddd5bd99f 100644
--- a/platform/iphone/main.m
+++ b/platform/iphone/main.m
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 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 */
@@ -28,24 +28,30 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#import "app_delegate.h"
+#import "godot_app_delegate.h"
#import <UIKit/UIKit.h>
#include <stdio.h>
+#include <vulkan/vulkan.h>
int gargc;
char **gargv;
int main(int argc, char *argv[]) {
+#if defined(VULKAN_ENABLED)
+ //MoltenVK - enable full component swizzling support
+ setenv("MVK_CONFIG_FULL_IMAGE_VIEW_SWIZZLE", "1", 1);
+#endif
+
printf("*********** main.m\n");
gargc = argc;
gargv = argv;
- NSAutoreleasePool *pool = [NSAutoreleasePool new];
- AppDelegate *app = [AppDelegate alloc];
printf("running app main\n");
- UIApplicationMain(argc, argv, nil, @"AppDelegate");
- printf("main done, pool release\n");
- [pool release];
+ @autoreleasepool {
+ NSString *className = NSStringFromClass([GodotApplicalitionDelegate class]);
+ UIApplicationMain(argc, argv, nil, className);
+ }
+ printf("main done\n");
return 0;
}
diff --git a/platform/iphone/native_video_view.h b/platform/iphone/native_video_view.h
new file mode 100644
index 0000000000..2df5e27fa4
--- /dev/null
+++ b/platform/iphone/native_video_view.h
@@ -0,0 +1,41 @@
+/*************************************************************************/
+/* native_video_view.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#import <UIKit/UIKit.h>
+
+@interface GodotNativeVideoView : UIView
+
+- (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack;
+- (BOOL)isVideoPlaying;
+- (void)pauseVideo;
+- (void)unpauseVideo;
+- (void)stopVideo;
+
+@end
diff --git a/platform/iphone/native_video_view.m b/platform/iphone/native_video_view.m
new file mode 100644
index 0000000000..f126749600
--- /dev/null
+++ b/platform/iphone/native_video_view.m
@@ -0,0 +1,266 @@
+/*************************************************************************/
+/* native_video_view.m */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#import "native_video_view.h"
+#import <AVFoundation/AVFoundation.h>
+
+@interface GodotNativeVideoView ()
+
+@property(strong, nonatomic) AVAsset *avAsset;
+@property(strong, nonatomic) AVPlayerItem *avPlayerItem;
+@property(strong, nonatomic) AVPlayer *avPlayer;
+@property(strong, nonatomic) AVPlayerLayer *avPlayerLayer;
+@property(assign, nonatomic) CMTime videoCurrentTime;
+@property(assign, nonatomic) BOOL isVideoCurrentlyPlaying;
+
+@end
+
+@implementation GodotNativeVideoView
+
+- (instancetype)initWithFrame:(CGRect)frame {
+ self = [super initWithFrame:frame];
+
+ if (self) {
+ [self godot_commonInit];
+ }
+
+ return self;
+}
+
+- (instancetype)initWithCoder:(NSCoder *)coder {
+ self = [super initWithCoder:coder];
+
+ if (self) {
+ [self godot_commonInit];
+ }
+
+ return self;
+}
+
+- (void)godot_commonInit {
+ self.isVideoCurrentlyPlaying = NO;
+ self.videoCurrentTime = kCMTimeZero;
+
+ [self observeVideoAudio];
+}
+
+- (void)layoutSubviews {
+ [super layoutSubviews];
+
+ self.avPlayerLayer.frame = self.bounds;
+}
+
+- (void)observeVideoAudio {
+ printf("******** adding observer for sound routing changes\n");
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(audioRouteChangeListenerCallback:)
+ name:AVAudioSessionRouteChangeNotification
+ object:nil];
+}
+
+- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
+ if (object == self.avPlayerItem && [keyPath isEqualToString:@"status"]) {
+ [self handleVideoOrPlayerStatus];
+ }
+
+ if (object == self.avPlayer && [keyPath isEqualToString:@"rate"]) {
+ [self handleVideoPlayRate];
+ }
+}
+
+// MARK: Video Audio
+
+- (void)audioRouteChangeListenerCallback:(NSNotification *)notification {
+ printf("*********** route changed!\n");
+ NSDictionary *interuptionDict = notification.userInfo;
+
+ NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue];
+
+ switch (routeChangeReason) {
+ case AVAudioSessionRouteChangeReasonNewDeviceAvailable: {
+ NSLog(@"AVAudioSessionRouteChangeReasonNewDeviceAvailable");
+ NSLog(@"Headphone/Line plugged in");
+ } break;
+ case AVAudioSessionRouteChangeReasonOldDeviceUnavailable: {
+ NSLog(@"AVAudioSessionRouteChangeReasonOldDeviceUnavailable");
+ NSLog(@"Headphone/Line was pulled. Resuming video play....");
+ if ([self isVideoPlaying]) {
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
+ [self.avPlayer play]; // NOTE: change this line according your current player implementation
+ NSLog(@"resumed play");
+ });
+ }
+ } break;
+ case AVAudioSessionRouteChangeReasonCategoryChange: {
+ // called at start - also when other audio wants to play
+ NSLog(@"AVAudioSessionRouteChangeReasonCategoryChange");
+ } break;
+ }
+}
+
+// MARK: Native Video Player
+
+- (void)handleVideoOrPlayerStatus {
+ if (self.avPlayerItem.status == AVPlayerItemStatusFailed || self.avPlayer.status == AVPlayerStatusFailed) {
+ [self stopVideo];
+ }
+
+ if (self.avPlayer.status == AVPlayerStatusReadyToPlay && self.avPlayerItem.status == AVPlayerItemStatusReadyToPlay && CMTimeCompare(self.videoCurrentTime, kCMTimeZero) == 0) {
+ // NSLog(@"time: %@", self.video_current_time);
+ [self.avPlayer seekToTime:self.videoCurrentTime];
+ self.videoCurrentTime = kCMTimeZero;
+ }
+}
+
+- (void)handleVideoPlayRate {
+ NSLog(@"Player playback rate changed: %.5f", self.avPlayer.rate);
+ if ([self isVideoPlaying] && self.avPlayer.rate == 0.0 && !self.avPlayer.error) {
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
+ [self.avPlayer play]; // NOTE: change this line according your current player implementation
+ NSLog(@"resumed play");
+ });
+
+ NSLog(@" . . . PAUSED (or just started)");
+ }
+}
+
+- (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack {
+ self.avAsset = [AVAsset assetWithURL:[NSURL fileURLWithPath:filePath]];
+
+ self.avPlayerItem = [AVPlayerItem playerItemWithAsset:self.avAsset];
+ [self.avPlayerItem addObserver:self forKeyPath:@"status" options:0 context:nil];
+
+ self.avPlayer = [AVPlayer playerWithPlayerItem:self.avPlayerItem];
+ self.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:self.avPlayer];
+
+ [self.avPlayer addObserver:self forKeyPath:@"status" options:0 context:nil];
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(playerItemDidReachEnd:)
+ name:AVPlayerItemDidPlayToEndTimeNotification
+ object:[self.avPlayer currentItem]];
+
+ [self.avPlayer addObserver:self forKeyPath:@"rate" options:NSKeyValueObservingOptionNew context:0];
+
+ [self.avPlayerLayer setFrame:self.bounds];
+ [self.layer addSublayer:self.avPlayerLayer];
+ [self.avPlayer play];
+
+ AVMediaSelectionGroup *audioGroup = [self.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicAudible];
+
+ NSMutableArray *allAudioParams = [NSMutableArray array];
+ for (id track in audioGroup.options) {
+ NSString *language = [[track locale] localeIdentifier];
+ NSLog(@"subtitle lang: %@", language);
+
+ if ([language isEqualToString:audioTrack]) {
+ AVMutableAudioMixInputParameters *audioInputParams = [AVMutableAudioMixInputParameters audioMixInputParameters];
+ [audioInputParams setVolume:videoVolume atTime:kCMTimeZero];
+ [audioInputParams setTrackID:[track trackID]];
+ [allAudioParams addObject:audioInputParams];
+
+ AVMutableAudioMix *audioMix = [AVMutableAudioMix audioMix];
+ [audioMix setInputParameters:allAudioParams];
+
+ [self.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:audioGroup];
+ [self.avPlayer.currentItem setAudioMix:audioMix];
+
+ break;
+ }
+ }
+
+ AVMediaSelectionGroup *subtitlesGroup = [self.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible];
+ NSArray *useableTracks = [AVMediaSelectionGroup mediaSelectionOptionsFromArray:subtitlesGroup.options withoutMediaCharacteristics:[NSArray arrayWithObject:AVMediaCharacteristicContainsOnlyForcedSubtitles]];
+
+ for (id track in useableTracks) {
+ NSString *language = [[track locale] localeIdentifier];
+ NSLog(@"subtitle lang: %@", language);
+
+ if ([language isEqualToString:subtitleTrack]) {
+ [self.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:subtitlesGroup];
+ break;
+ }
+ }
+
+ self.isVideoCurrentlyPlaying = YES;
+
+ return true;
+}
+
+- (BOOL)isVideoPlaying {
+ if (self.avPlayer.error) {
+ printf("Error during playback\n");
+ }
+ return (self.avPlayer.rate > 0 && !self.avPlayer.error);
+}
+
+- (void)pauseVideo {
+ self.videoCurrentTime = self.avPlayer.currentTime;
+ [self.avPlayer pause];
+ self.isVideoCurrentlyPlaying = NO;
+}
+
+- (void)unpauseVideo {
+ [self.avPlayer play];
+ self.isVideoCurrentlyPlaying = YES;
+}
+
+- (void)playerItemDidReachEnd:(NSNotification *)notification {
+ [self stopVideo];
+}
+
+- (void)finishPlayingVideo {
+ [self.avPlayer pause];
+ [self.avPlayerLayer removeFromSuperlayer];
+ self.avPlayerLayer = nil;
+
+ if (self.avPlayerItem) {
+ [self.avPlayerItem removeObserver:self forKeyPath:@"status"];
+ self.avPlayerItem = nil;
+ }
+
+ if (self.avPlayer) {
+ [self.avPlayer removeObserver:self forKeyPath:@"status"];
+ self.avPlayer = nil;
+ }
+
+ self.avAsset = nil;
+
+ self.isVideoCurrentlyPlaying = NO;
+}
+
+- (void)stopVideo {
+ [self finishPlayingVideo];
+
+ [self removeFromSuperview];
+}
+
+@end
diff --git a/platform/iphone/os_iphone.cpp b/platform/iphone/os_iphone.cpp
deleted file mode 100644
index 41dd623e69..0000000000
--- a/platform/iphone/os_iphone.cpp
+++ /dev/null
@@ -1,632 +0,0 @@
-/*************************************************************************/
-/* os_iphone.cpp */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 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. */
-/*************************************************************************/
-
-#ifdef IPHONE_ENABLED
-
-#include "os_iphone.h"
-
-#if defined(OPENGL_ENABLED)
-#include "drivers/gles2/rasterizer_gles2.h"
-#endif
-
-#if defined(VULKAN_ENABLED)
-#include "servers/rendering/rasterizer_rd/rasterizer_rd.h"
-// #import <QuartzCore/CAMetalLayer.h>
-#include <vulkan/vulkan_metal.h>
-#endif
-
-#include "servers/rendering/rendering_server_raster.h"
-#include "servers/rendering/rendering_server_wrap_mt.h"
-
-#include "main/main.h"
-
-#include "core/io/file_access_pack.h"
-#include "core/os/dir_access.h"
-#include "core/os/file_access.h"
-#include "core/project_settings.h"
-#include "drivers/unix/syslog_logger.h"
-
-#include "semaphore_iphone.h"
-
-#include <dlfcn.h>
-
-int OSIPhone::get_video_driver_count() const {
- return 2;
-};
-
-const char *OSIPhone::get_video_driver_name(int p_driver) const {
- switch (p_driver) {
- case VIDEO_DRIVER_GLES2:
- return "GLES2";
- }
- ERR_FAIL_V_MSG(nullptr, "Invalid video driver index: " + itos(p_driver) + ".");
-};
-
-OSIPhone *OSIPhone::get_singleton() {
- return (OSIPhone *)OS::get_singleton();
-};
-
-extern int gl_view_base_fb; // from gl_view.mm
-
-void OSIPhone::set_data_dir(String p_dir) {
- DirAccess *da = DirAccess::open(p_dir);
-
- data_dir = da->get_current_dir();
- printf("setting data dir to %ls from %ls\n", data_dir.c_str(), p_dir.c_str());
- memdelete(da);
-};
-
-void OSIPhone::set_unique_id(String p_id) {
- unique_id = p_id;
-};
-
-String OSIPhone::get_unique_id() const {
- return unique_id;
-};
-
-void OSIPhone::initialize_core() {
- OS_Unix::initialize_core();
-
- set_data_dir(data_dir);
-};
-
-int OSIPhone::get_current_video_driver() const {
- return video_driver_index;
-}
-
-Error OSIPhone::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) {
- video_driver_index = p_video_driver;
-
-#if defined(OPENGL_ENABLED)
- bool gl_initialization_error = false;
-
- // FIXME: Add Vulkan support via MoltenVK. Add fallback code back?
-
- if (RasterizerGLES2::is_viable() == OK) {
- RasterizerGLES2::register_config();
- RasterizerGLES2::make_current();
- } else {
- gl_initialization_error = true;
- }
-
- if (gl_initialization_error) {
- OS::get_singleton()->alert("Your device does not support any of the supported OpenGL versions.",
- "Unable to initialize video driver");
- return ERR_UNAVAILABLE;
- }
-#endif
-
-#if defined(VULKAN_ENABLED)
- RasterizerRD::make_current();
-#endif
-
- rendering_server = memnew(RenderingServerRaster);
- // FIXME: Reimplement threaded rendering
- if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) {
- rendering_server = memnew(RenderingServerWrapMT(rendering_server, false));
- }
- rendering_server->init();
- //rendering_server->cursor_set_visible(false, 0);
-
-#if defined(OPENGL_ENABLED)
- // reset this to what it should be, it will have been set to 0 after rendering_server->init() is called
- RasterizerStorageGLES2::system_fbo = gl_view_base_fb;
-#endif
-
- AudioDriverManager::initialize(p_audio_driver);
-
- input = memnew(InputDefault);
-
-#ifdef GAME_CENTER_ENABLED
- game_center = memnew(GameCenter);
- Engine::get_singleton()->add_singleton(Engine::Singleton("GameCenter", game_center));
- game_center->connect();
-#endif
-
-#ifdef STOREKIT_ENABLED
- store_kit = memnew(InAppStore);
- Engine::get_singleton()->add_singleton(Engine::Singleton("InAppStore", store_kit));
-#endif
-
-#ifdef ICLOUD_ENABLED
- icloud = memnew(ICloud);
- Engine::get_singleton()->add_singleton(Engine::Singleton("ICloud", icloud));
- //icloud->connect();
-#endif
- ios = memnew(iOS);
- Engine::get_singleton()->add_singleton(Engine::Singleton("iOS", ios));
-
- return OK;
-};
-
-MainLoop *OSIPhone::get_main_loop() const {
- return main_loop;
-};
-
-void OSIPhone::set_main_loop(MainLoop *p_main_loop) {
- main_loop = p_main_loop;
-
- if (main_loop) {
- input->set_main_loop(p_main_loop);
- main_loop->init();
- }
-};
-
-bool OSIPhone::iterate() {
- if (!main_loop)
- return true;
-
- if (main_loop) {
- for (int i = 0; i < event_count; i++) {
- input->parse_input_event(event_queue[i]);
- };
- };
- event_count = 0;
-
- return Main::iteration();
-};
-
-void OSIPhone::key(uint32_t p_key, bool p_pressed) {
- Ref<InputEventKey> ev;
- ev.instance();
- ev->set_echo(false);
- ev->set_pressed(p_pressed);
- ev->set_keycode(p_key);
- ev->set_physical_keycode(p_key);
- ev->set_unicode(p_key);
- queue_event(ev);
-};
-
-void OSIPhone::touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick) {
- if (!GLOBAL_DEF("debug/disable_touch", false)) {
- Ref<InputEventScreenTouch> ev;
- ev.instance();
-
- ev->set_index(p_idx);
- ev->set_pressed(p_pressed);
- ev->set_position(Vector2(p_x, p_y));
- queue_event(ev);
- };
-
- touch_list.pressed[p_idx] = p_pressed;
-};
-
-void OSIPhone::touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y) {
- if (!GLOBAL_DEF("debug/disable_touch", false)) {
- Ref<InputEventScreenDrag> ev;
- ev.instance();
- ev->set_index(p_idx);
- ev->set_position(Vector2(p_x, p_y));
- ev->set_relative(Vector2(p_x - p_prev_x, p_y - p_prev_y));
- queue_event(ev);
- };
-};
-
-void OSIPhone::queue_event(const Ref<InputEvent> &p_event) {
- ERR_FAIL_INDEX(event_count, MAX_EVENTS);
-
- event_queue[event_count++] = p_event;
-};
-
-void OSIPhone::touches_cancelled() {
- for (int i = 0; i < MAX_MOUSE_COUNT; i++) {
- if (touch_list.pressed[i]) {
- // send a mouse_up outside the screen
- touch_press(i, -1, -1, false, false);
- };
- };
-};
-
-static const float ACCEL_RANGE = 1;
-
-void OSIPhone::update_gravity(float p_x, float p_y, float p_z) {
- input->set_gravity(Vector3(p_x, p_y, p_z));
-};
-
-void OSIPhone::update_accelerometer(float p_x, float p_y, float p_z) {
- // Found out the Z should not be negated! Pass as is!
- input->set_accelerometer(Vector3(p_x / (float)ACCEL_RANGE, p_y / (float)ACCEL_RANGE, p_z / (float)ACCEL_RANGE));
-
- /*
- if (p_x != last_accel.x) {
- //printf("updating accel x %f\n", p_x);
- InputEvent ev;
- ev.type = InputEvent::JOYPAD_MOTION;
- ev.device = 0;
- ev.joy_motion.axis = JOY_ANALOG_0;
- ev.joy_motion.axis_value = (p_x / (float)ACCEL_RANGE);
- last_accel.x = p_x;
- queue_event(ev);
- };
- if (p_y != last_accel.y) {
- //printf("updating accel y %f\n", p_y);
- InputEvent ev;
- ev.type = InputEvent::JOYPAD_MOTION;
- ev.device = 0;
- ev.joy_motion.axis = JOY_ANALOG_1;
- ev.joy_motion.axis_value = (p_y / (float)ACCEL_RANGE);
- last_accel.y = p_y;
- queue_event(ev);
- };
- if (p_z != last_accel.z) {
- //printf("updating accel z %f\n", p_z);
- InputEvent ev;
- ev.type = InputEvent::JOYPAD_MOTION;
- ev.device = 0;
- ev.joy_motion.axis = JOY_ANALOG_2;
- ev.joy_motion.axis_value = ( (1.0 - p_z) / (float)ACCEL_RANGE);
- last_accel.z = p_z;
- queue_event(ev);
- };
- */
-};
-
-void OSIPhone::update_magnetometer(float p_x, float p_y, float p_z) {
- input->set_magnetometer(Vector3(p_x, p_y, p_z));
-};
-
-void OSIPhone::update_gyroscope(float p_x, float p_y, float p_z) {
- input->set_gyroscope(Vector3(p_x, p_y, p_z));
-};
-
-int OSIPhone::get_unused_joy_id() {
- return input->get_unused_joy_id();
-};
-
-void OSIPhone::joy_connection_changed(int p_idx, bool p_connected, String p_name) {
- input->joy_connection_changed(p_idx, p_connected, p_name);
-};
-
-void OSIPhone::joy_button(int p_device, int p_button, bool p_pressed) {
- input->joy_button(p_device, p_button, p_pressed);
-};
-
-void OSIPhone::joy_axis(int p_device, int p_axis, const InputDefault::JoyAxis &p_value) {
- input->joy_axis(p_device, p_axis, p_value);
-};
-
-void OSIPhone::delete_main_loop() {
- if (main_loop) {
- main_loop->finish();
- memdelete(main_loop);
- };
-
- main_loop = nullptr;
-};
-
-void OSIPhone::finalize() {
- delete_main_loop();
-
- memdelete(input);
- memdelete(ios);
-
-#ifdef GAME_CENTER_ENABLED
- memdelete(game_center);
-#endif
-
-#ifdef STOREKIT_ENABLED
- memdelete(store_kit);
-#endif
-
-#ifdef ICLOUD_ENABLED
- memdelete(icloud);
-#endif
-
- rendering_server->finish();
- memdelete(rendering_server);
- // memdelete(rasterizer);
-
- // Free unhandled events before close
- for (int i = 0; i < MAX_EVENTS; i++) {
- event_queue[i].unref();
- };
- event_count = 0;
-};
-
-void OSIPhone::set_mouse_show(bool p_show) {}
-void OSIPhone::set_mouse_grab(bool p_grab) {}
-
-bool OSIPhone::is_mouse_grab_enabled() const {
- return true;
-};
-
-Point2 OSIPhone::get_mouse_position() const {
- return Point2();
-};
-
-int OSIPhone::get_mouse_button_state() const {
- return 0;
-};
-
-void OSIPhone::set_window_title(const String &p_title) {}
-
-void OSIPhone::alert(const String &p_alert, const String &p_title) {
- const CharString utf8_alert = p_alert.utf8();
- const CharString utf8_title = p_title.utf8();
- iOS::alert(utf8_alert.get_data(), utf8_title.get_data());
-}
-
-Error OSIPhone::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) {
- if (p_path.length() == 0) {
- p_library_handle = RTLD_SELF;
- return OK;
- }
- return OS_Unix::open_dynamic_library(p_path, p_library_handle, p_also_set_library_path);
-}
-
-Error OSIPhone::close_dynamic_library(void *p_library_handle) {
- if (p_library_handle == RTLD_SELF) {
- return OK;
- }
- return OS_Unix::close_dynamic_library(p_library_handle);
-}
-
-HashMap<String, void *> OSIPhone::dynamic_symbol_lookup_table;
-void register_dynamic_symbol(char *name, void *address) {
- OSIPhone::dynamic_symbol_lookup_table[String(name)] = address;
-}
-
-Error OSIPhone::get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional) {
- if (p_library_handle == RTLD_SELF) {
- void **ptr = OSIPhone::dynamic_symbol_lookup_table.getptr(p_name);
- if (ptr) {
- p_symbol_handle = *ptr;
- return OK;
- }
- }
- return OS_Unix::get_dynamic_library_symbol_handle(p_library_handle, p_name, p_symbol_handle, p_optional);
-}
-
-void OSIPhone::set_video_mode(const VideoMode &p_video_mode, int p_screen) {
- video_mode = p_video_mode;
-};
-
-OS::VideoMode OSIPhone::get_video_mode(int p_screen) const {
- return video_mode;
-};
-
-void OSIPhone::get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen) const {
- p_list->push_back(video_mode);
-};
-
-bool OSIPhone::can_draw() const {
- if (native_video_is_playing())
- return false;
- return true;
-};
-
-int OSIPhone::set_base_framebuffer(int p_fb) {
-#if defined(OPENGL_ENABLED)
- // gl_view_base_fb has not been updated yet
- RasterizerStorageGLES2::system_fbo = p_fb;
-#endif
-
- return 0;
-};
-
-bool OSIPhone::has_virtual_keyboard() const {
- return true;
-};
-
-extern void _show_keyboard(String p_existing);
-extern void _hide_keyboard();
-extern Error _shell_open(String p_uri);
-extern void _set_keep_screen_on(bool p_enabled);
-extern void _vibrate();
-
-void OSIPhone::show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
- _show_keyboard(p_existing_text);
-};
-
-void OSIPhone::hide_virtual_keyboard() {
- _hide_keyboard();
-};
-
-void OSIPhone::set_virtual_keyboard_height(int p_height) {
- virtual_keyboard_height = p_height;
-}
-
-int OSIPhone::get_virtual_keyboard_height() const {
- return virtual_keyboard_height;
-}
-
-Error OSIPhone::shell_open(String p_uri) {
- return _shell_open(p_uri);
-};
-
-void OSIPhone::set_keep_screen_on(bool p_enabled) {
- OS::set_keep_screen_on(p_enabled);
- _set_keep_screen_on(p_enabled);
-};
-
-String OSIPhone::get_user_data_dir() const {
- return data_dir;
-};
-
-String OSIPhone::get_name() const {
- return "iOS";
-};
-
-String OSIPhone::get_model_name() const {
- String model = ios->get_model();
- if (model != "")
- return model;
-
- return OS_Unix::get_model_name();
-}
-
-Size2 OSIPhone::get_window_size() const {
- return Vector2(video_mode.width, video_mode.height);
-}
-
-extern Rect2 _get_ios_window_safe_area(float p_window_width, float p_window_height);
-
-Rect2 OSIPhone::get_window_safe_area() const {
- return _get_ios_window_safe_area(video_mode.width, video_mode.height);
-}
-
-bool OSIPhone::has_touchscreen_ui_hint() const {
- return true;
-}
-
-void OSIPhone::set_locale(String p_locale) {
- locale_code = p_locale;
-}
-
-String OSIPhone::get_locale() const {
- return locale_code;
-}
-
-extern bool _play_video(String p_path, float p_volume, String p_audio_track, String p_subtitle_track);
-extern bool _is_video_playing();
-extern void _pause_video();
-extern void _unpause_video();
-extern void _stop_video();
-extern void _focus_out_video();
-
-Error OSIPhone::native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track) {
- FileAccess *f = FileAccess::open(p_path, FileAccess::READ);
- bool exists = f && f->is_open();
-
- String tempFile = get_user_data_dir();
- if (!exists)
- return FAILED;
-
- if (p_path.begins_with("res://")) {
- if (PackedData::get_singleton()->has_path(p_path)) {
- print("Unable to play %S using the native player as it resides in a .pck file\n", p_path.c_str());
- return ERR_INVALID_PARAMETER;
- } else {
- p_path = p_path.replace("res:/", ProjectSettings::get_singleton()->get_resource_path());
- }
- } else if (p_path.begins_with("user://"))
- p_path = p_path.replace("user:/", get_user_data_dir());
-
- memdelete(f);
-
- print("Playing video: %S\n", p_path.c_str());
- if (_play_video(p_path, p_volume, p_audio_track, p_subtitle_track))
- return OK;
- return FAILED;
-}
-
-bool OSIPhone::native_video_is_playing() const {
- return _is_video_playing();
-}
-
-void OSIPhone::native_video_pause() {
- if (native_video_is_playing())
- _pause_video();
-}
-
-void OSIPhone::native_video_unpause() {
- _unpause_video();
-};
-
-void OSIPhone::native_video_focus_out() {
- _focus_out_video();
-};
-
-void OSIPhone::native_video_stop() {
- if (native_video_is_playing())
- _stop_video();
-}
-
-void OSIPhone::vibrate_handheld(int p_duration_ms) {
- // iOS does not support duration for vibration
- _vibrate();
-}
-
-bool OSIPhone::_check_internal_feature_support(const String &p_feature) {
- return p_feature == "mobile";
-}
-
-// Initialization order between compilation units is not guaranteed,
-// so we use this as a hack to ensure certain code is called before
-// everything else, but after all units are initialized.
-typedef void (*init_callback)();
-static init_callback *ios_init_callbacks = nullptr;
-static int ios_init_callbacks_count = 0;
-static int ios_init_callbacks_capacity = 0;
-
-void add_ios_init_callback(init_callback cb) {
- if (ios_init_callbacks_count == ios_init_callbacks_capacity) {
- void *new_ptr = realloc(ios_init_callbacks, sizeof(cb) * 32);
- if (new_ptr) {
- ios_init_callbacks = (init_callback *)(new_ptr);
- ios_init_callbacks_capacity += 32;
- }
- }
- if (ios_init_callbacks_capacity > ios_init_callbacks_count) {
- ios_init_callbacks[ios_init_callbacks_count] = cb;
- ++ios_init_callbacks_count;
- }
-}
-
-OSIPhone::OSIPhone(int width, int height, String p_data_dir) {
- for (int i = 0; i < ios_init_callbacks_count; ++i) {
- ios_init_callbacks[i]();
- }
- free(ios_init_callbacks);
- ios_init_callbacks = nullptr;
- ios_init_callbacks_count = 0;
- ios_init_callbacks_capacity = 0;
-
- main_loop = nullptr;
- rendering_server = nullptr;
-
- VideoMode vm;
- vm.fullscreen = true;
- vm.width = width;
- vm.height = height;
- vm.resizable = false;
- set_video_mode(vm);
- event_count = 0;
- virtual_keyboard_height = 0;
-
- // can't call set_data_dir from here, since it requires DirAccess
- // which is initialized in initialize_core
- data_dir = p_data_dir;
-
- Vector<Logger *> loggers;
- loggers.push_back(memnew(SyslogLogger));
-#ifdef DEBUG_ENABLED
- // it seems iOS app's stdout/stderr is only obtainable if you launch it from Xcode
- loggers.push_back(memnew(StdLogger));
-#endif
- _set_logger(memnew(CompositeLogger(loggers)));
-
- AudioDriverManager::add_driver(&audio_driver);
-};
-
-OSIPhone::~OSIPhone() {
-}
-
-#endif
diff --git a/platform/iphone/os_iphone.h b/platform/iphone/os_iphone.h
index 955eb15d57..f4ff909adf 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-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 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 */
@@ -33,180 +33,92 @@
#ifndef OS_IPHONE_H
#define OS_IPHONE_H
-#include "core/input/input.h"
#include "drivers/coreaudio/audio_driver_coreaudio.h"
#include "drivers/unix/os_unix.h"
-#include "game_center.h"
-#include "icloud.h"
-#include "in_app_store.h"
#include "ios.h"
+#include "joypad_iphone.h"
#include "servers/audio_server.h"
-#include "servers/rendering/rasterizer.h"
-#include "servers/rendering_server.h"
+#include "servers/rendering/renderer_compositor.h"
#if defined(VULKAN_ENABLED)
#include "drivers/vulkan/rendering_device_vulkan.h"
#include "platform/iphone/vulkan_context_iphone.h"
#endif
+extern void godot_ios_plugins_initialize();
+extern void godot_ios_plugins_deinitialize();
+
class OSIPhone : public OS_Unix {
private:
- enum {
- MAX_MOUSE_COUNT = 8,
- MAX_EVENTS = 64,
- };
-
static HashMap<String, void *> dynamic_symbol_lookup_table;
friend void register_dynamic_symbol(char *name, void *address);
- RenderingServer *rendering_server;
-
AudioDriverCoreAudio audio_driver;
-#ifdef GAME_CENTER_ENABLED
- GameCenter *game_center;
-#endif
-#ifdef STOREKIT_ENABLED
- InAppStore *store_kit;
-#endif
-#ifdef ICLOUD_ENABLED
- ICloud *icloud;
-#endif
iOS *ios;
- MainLoop *main_loop;
-
-#if defined(VULKAN_ENABLED)
- VulkanContextIPhone *context_vulkan;
- RenderingDeviceVulkan *rendering_device_vulkan;
-#endif
- VideoMode video_mode;
-
- virtual int get_video_driver_count() const;
- virtual const char *get_video_driver_name(int p_driver) const;
-
- virtual int get_current_video_driver() const;
-
- virtual void initialize_core();
- virtual Error initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver);
-
- virtual void set_main_loop(MainLoop *p_main_loop);
- virtual MainLoop *get_main_loop() const;
-
- virtual void delete_main_loop();
+ JoypadIPhone *joypad_iphone;
- virtual void finalize();
+ MainLoop *main_loop;
- struct MouseList {
- bool pressed[MAX_MOUSE_COUNT];
- MouseList() {
- for (int i = 0; i < MAX_MOUSE_COUNT; i++)
- pressed[i] = false;
- };
- };
+ virtual void initialize_core() override;
+ virtual void initialize() override;
- MouseList touch_list;
+ virtual void initialize_joypads() override {
+ }
- Vector3 last_accel;
+ virtual void set_main_loop(MainLoop *p_main_loop) override;
+ virtual MainLoop *get_main_loop() const override;
- Ref<InputEvent> event_queue[MAX_EVENTS];
- int event_count;
- void queue_event(const Ref<InputEvent> &p_event);
+ virtual void delete_main_loop() override;
- String data_dir;
- String unique_id;
- String locale_code;
+ virtual void finalize() override;
- InputDefault *input;
+ String user_data_dir;
- int virtual_keyboard_height;
+ bool is_focused = false;
- int video_driver_index;
+ void deinitialize_modules();
public:
- bool iterate();
-
- uint8_t get_orientations() const;
-
- void touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick);
- void touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y);
- void touches_cancelled();
- void key(uint32_t p_key, bool p_pressed);
- void set_virtual_keyboard_height(int p_height);
-
- int set_base_framebuffer(int p_fb);
-
- void update_gravity(float p_x, float p_y, float p_z);
- void update_accelerometer(float p_x, float p_y, float p_z);
- void update_magnetometer(float p_x, float p_y, float p_z);
- void update_gyroscope(float p_x, float p_y, float p_z);
-
- int get_unused_joy_id();
- void joy_connection_changed(int p_idx, bool p_connected, String p_name);
- void joy_button(int p_device, int p_button, bool p_pressed);
- void joy_axis(int p_device, int p_axis, const InputDefault::JoyAxis &p_value);
-
static OSIPhone *get_singleton();
- virtual void set_mouse_show(bool p_show);
- virtual void set_mouse_grab(bool p_grab);
- virtual bool is_mouse_grab_enabled() const;
- virtual Point2 get_mouse_position() const;
- virtual int get_mouse_button_state() const;
- virtual void set_window_title(const String &p_title);
-
- virtual void alert(const String &p_alert, const String &p_title = "ALERT!");
-
- virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false);
- virtual Error close_dynamic_library(void *p_library_handle);
- virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional = false);
-
- virtual void set_video_mode(const VideoMode &p_video_mode, int p_screen = 0);
- virtual VideoMode get_video_mode(int p_screen = 0) const;
- virtual void get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen = 0) const;
+ OSIPhone(String p_data_dir);
+ ~OSIPhone();
- virtual void set_keep_screen_on(bool p_enabled);
+ void initialize_modules();
- virtual bool can_draw() const;
+ bool iterate();
- virtual bool has_virtual_keyboard() const;
- virtual void show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), int p_max_input_length = -1, int p_cursor_start = -1, int p_cursor_end = -1);
- virtual void hide_virtual_keyboard();
- virtual int get_virtual_keyboard_height() const;
+ void start();
- virtual Size2 get_window_size() const;
- virtual Rect2 get_window_safe_area() const;
+ virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false) override;
+ virtual Error close_dynamic_library(void *p_library_handle) override;
+ virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional = false) override;
- virtual bool has_touchscreen_ui_hint() const;
+ virtual void alert(const String &p_alert,
+ const String &p_title = "ALERT!") override;
- void set_data_dir(String p_dir);
+ virtual String get_name() const override;
+ virtual String get_model_name() const override;
- virtual String get_name() const;
- virtual String get_model_name() const;
+ virtual Error shell_open(String p_uri) override;
- Error shell_open(String p_uri);
+ void set_user_data_dir(String p_dir);
+ virtual String get_user_data_dir() const override;
- String get_user_data_dir() const;
+ virtual String get_locale() const override;
- void set_locale(String p_locale);
- String get_locale() const;
+ virtual String get_unique_id() const override;
- void set_unique_id(String p_id);
- String get_unique_id() const;
+ virtual void vibrate_handheld(int p_duration_ms = 500) override;
- virtual Error native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track);
- virtual bool native_video_is_playing() const;
- virtual void native_video_pause();
- virtual void native_video_unpause();
- virtual void native_video_focus_out();
- virtual void native_video_stop();
- virtual void vibrate_handheld(int p_duration_ms = 500);
+ virtual bool _check_internal_feature_support(const String &p_feature) override;
- virtual bool _check_internal_feature_support(const String &p_feature);
- OSIPhone(int width, int height, String p_data_dir);
- ~OSIPhone();
+ void on_focus_out();
+ void on_focus_in();
};
#endif // OS_IPHONE_H
-#endif
+#endif // IPHONE_ENABLED
diff --git a/platform/iphone/os_iphone.mm b/platform/iphone/os_iphone.mm
new file mode 100644
index 0000000000..51c4da2960
--- /dev/null
+++ b/platform/iphone/os_iphone.mm
@@ -0,0 +1,330 @@
+/*************************************************************************/
+/* os_iphone.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 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. */
+/*************************************************************************/
+
+#ifdef IPHONE_ENABLED
+
+#include "os_iphone.h"
+#import "app_delegate.h"
+#include "core/config/project_settings.h"
+#include "core/io/file_access_pack.h"
+#include "core/os/dir_access.h"
+#include "core/os/file_access.h"
+#include "display_server_iphone.h"
+#include "drivers/unix/syslog_logger.h"
+#import "godot_view.h"
+#include "main/main.h"
+#import "view_controller.h"
+
+#import <AudioToolbox/AudioServices.h>
+#import <UIKit/UIKit.h>
+#import <dlfcn.h>
+
+#if defined(VULKAN_ENABLED)
+#include "servers/rendering/renderer_rd/renderer_compositor_rd.h"
+#import <QuartzCore/CAMetalLayer.h>
+#include <vulkan/vulkan_metal.h>
+#endif
+
+// Initialization order between compilation units is not guaranteed,
+// so we use this as a hack to ensure certain code is called before
+// everything else, but after all units are initialized.
+typedef void (*init_callback)();
+static init_callback *ios_init_callbacks = nullptr;
+static int ios_init_callbacks_count = 0;
+static int ios_init_callbacks_capacity = 0;
+HashMap<String, void *> OSIPhone::dynamic_symbol_lookup_table;
+
+void add_ios_init_callback(init_callback cb) {
+ if (ios_init_callbacks_count == ios_init_callbacks_capacity) {
+ void *new_ptr = realloc(ios_init_callbacks, sizeof(cb) * 32);
+ if (new_ptr) {
+ ios_init_callbacks = (init_callback *)(new_ptr);
+ ios_init_callbacks_capacity += 32;
+ }
+ }
+ if (ios_init_callbacks_capacity > ios_init_callbacks_count) {
+ ios_init_callbacks[ios_init_callbacks_count] = cb;
+ ++ios_init_callbacks_count;
+ }
+}
+
+void register_dynamic_symbol(char *name, void *address) {
+ OSIPhone::dynamic_symbol_lookup_table[String(name)] = address;
+}
+
+OSIPhone *OSIPhone::get_singleton() {
+ return (OSIPhone *)OS::get_singleton();
+}
+
+OSIPhone::OSIPhone(String p_data_dir) {
+ for (int i = 0; i < ios_init_callbacks_count; ++i) {
+ ios_init_callbacks[i]();
+ }
+ free(ios_init_callbacks);
+ ios_init_callbacks = nullptr;
+ ios_init_callbacks_count = 0;
+ ios_init_callbacks_capacity = 0;
+
+ main_loop = nullptr;
+
+ // can't call set_data_dir from here, since it requires DirAccess
+ // which is initialized in initialize_core
+ user_data_dir = p_data_dir;
+
+ Vector<Logger *> loggers;
+ loggers.push_back(memnew(SyslogLogger));
+#ifdef DEBUG_ENABLED
+ // it seems iOS app's stdout/stderr is only obtainable if you launch it from
+ // Xcode
+ loggers.push_back(memnew(StdLogger));
+#endif
+ _set_logger(memnew(CompositeLogger(loggers)));
+
+ AudioDriverManager::add_driver(&audio_driver);
+
+ DisplayServerIPhone::register_iphone_driver();
+}
+
+OSIPhone::~OSIPhone() {}
+
+void OSIPhone::initialize_core() {
+ OS_Unix::initialize_core();
+
+ set_user_data_dir(user_data_dir);
+}
+
+void OSIPhone::initialize() {
+ initialize_core();
+}
+
+void OSIPhone::initialize_modules() {
+ ios = memnew(iOS);
+ Engine::get_singleton()->add_singleton(Engine::Singleton("iOS", ios));
+
+ joypad_iphone = memnew(JoypadIPhone);
+}
+
+void OSIPhone::deinitialize_modules() {
+ if (joypad_iphone) {
+ memdelete(joypad_iphone);
+ }
+
+ if (ios) {
+ memdelete(ios);
+ }
+
+ godot_ios_plugins_deinitialize();
+}
+
+void OSIPhone::set_main_loop(MainLoop *p_main_loop) {
+ main_loop = p_main_loop;
+
+ if (main_loop) {
+ main_loop->initialize();
+ }
+}
+
+MainLoop *OSIPhone::get_main_loop() const {
+ return main_loop;
+}
+
+void OSIPhone::delete_main_loop() {
+ if (main_loop) {
+ main_loop->finalize();
+ memdelete(main_loop);
+ };
+
+ main_loop = nullptr;
+}
+
+bool OSIPhone::iterate() {
+ if (!main_loop) {
+ return true;
+ }
+
+ if (DisplayServer::get_singleton()) {
+ DisplayServer::get_singleton()->process_events();
+ }
+
+ return Main::iteration();
+}
+
+void OSIPhone::start() {
+ godot_ios_plugins_initialize();
+
+ Main::start();
+
+ if (joypad_iphone) {
+ joypad_iphone->start_processing();
+ }
+}
+
+void OSIPhone::finalize() {
+ deinitialize_modules();
+
+ // Already gets called
+ // 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) {
+ if (p_path.length() == 0) {
+ p_library_handle = RTLD_SELF;
+ return OK;
+ }
+ return OS_Unix::open_dynamic_library(p_path, p_library_handle, p_also_set_library_path);
+}
+
+Error OSIPhone::close_dynamic_library(void *p_library_handle) {
+ if (p_library_handle == RTLD_SELF) {
+ return OK;
+ }
+ return OS_Unix::close_dynamic_library(p_library_handle);
+}
+
+Error OSIPhone::get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional) {
+ if (p_library_handle == RTLD_SELF) {
+ void **ptr = OSIPhone::dynamic_symbol_lookup_table.getptr(p_name);
+ if (ptr) {
+ p_symbol_handle = *ptr;
+ return OK;
+ }
+ }
+ return OS_Unix::get_dynamic_library_symbol_handle(p_library_handle, p_name, p_symbol_handle, p_optional);
+}
+
+void OSIPhone::alert(const String &p_alert, const String &p_title) {
+ const CharString utf8_alert = p_alert.utf8();
+ const CharString utf8_title = p_title.utf8();
+ iOS::alert(utf8_alert.get_data(), utf8_title.get_data());
+}
+
+String OSIPhone::get_name() const {
+ return "iOS";
+};
+
+String OSIPhone::get_model_name() const {
+ String model = ios->get_model();
+ if (model != "")
+ return model;
+
+ return OS_Unix::get_model_name();
+}
+
+Error OSIPhone::shell_open(String p_uri) {
+ NSString *urlPath = [[NSString alloc] initWithUTF8String:p_uri.utf8().get_data()];
+ NSURL *url = [NSURL URLWithString:urlPath];
+
+ if (![[UIApplication sharedApplication] canOpenURL:url]) {
+ return ERR_CANT_OPEN;
+ }
+
+ printf("opening url %s\n", p_uri.utf8().get_data());
+
+ [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
+
+ return OK;
+};
+
+void OSIPhone::set_user_data_dir(String p_dir) {
+ 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 {
+ return user_data_dir;
+}
+
+String OSIPhone::get_locale() const {
+ NSString *preferedLanguage = [NSLocale preferredLanguages].firstObject;
+
+ if (preferedLanguage) {
+ return String::utf8([preferedLanguage UTF8String]).replace("-", "_");
+ }
+
+ NSString *localeIdentifier = [[NSLocale currentLocale] localeIdentifier];
+ return String::utf8([localeIdentifier UTF8String]).replace("-", "_");
+}
+
+String OSIPhone::get_unique_id() const {
+ NSString *uuid = [UIDevice currentDevice].identifierForVendor.UUIDString;
+ return String::utf8([uuid UTF8String]);
+}
+
+void OSIPhone::vibrate_handheld(int p_duration_ms) {
+ // iOS does not support duration for vibration
+ AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
+}
+
+bool OSIPhone::_check_internal_feature_support(const String &p_feature) {
+ return p_feature == "mobile";
+}
+
+void OSIPhone::on_focus_out() {
+ if (is_focused) {
+ is_focused = false;
+
+ if (DisplayServerIPhone::get_singleton()) {
+ DisplayServerIPhone::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_FOCUS_OUT);
+ }
+
+ [AppDelegate.viewController.godotView stopRendering];
+
+ if (DisplayServerIPhone::get_singleton() && DisplayServerIPhone::get_singleton()->native_video_is_playing()) {
+ DisplayServerIPhone::get_singleton()->native_video_pause();
+ }
+
+ audio_driver.stop();
+ }
+}
+
+void OSIPhone::on_focus_in() {
+ if (!is_focused) {
+ is_focused = true;
+
+ if (DisplayServerIPhone::get_singleton()) {
+ DisplayServerIPhone::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_FOCUS_IN);
+ }
+
+ [AppDelegate.viewController.godotView startRendering];
+
+ if (DisplayServerIPhone::get_singleton() && DisplayServerIPhone::get_singleton()->native_video_is_playing()) {
+ DisplayServerIPhone::get_singleton()->native_video_unpause();
+ }
+
+ audio_driver.start();
+ }
+}
+
+#endif // IPHONE_ENABLED
diff --git a/platform/iphone/platform_config.h b/platform/iphone/platform_config.h
index bc190ba956..88ad4a3f67 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-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 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,8 +30,13 @@
#include <alloca.h>
-#define GLES2_INCLUDE_H <ES2/gl.h>
-
#define PLATFORM_REFCOUNT
#define PTHREAD_RENAME_SELF
+
+#define _weakify(var) __weak typeof(var) GDWeak_##var = var;
+#define _strongify(var) \
+ _Pragma("clang diagnostic push") \
+ _Pragma("clang diagnostic ignored \"-Wshadow\"") \
+ __strong typeof(var) var = GDWeak_##var; \
+ _Pragma("clang diagnostic pop")
diff --git a/platform/iphone/plugin/godot_plugin_config.h b/platform/iphone/plugin/godot_plugin_config.h
new file mode 100644
index 0000000000..f4e30c8349
--- /dev/null
+++ b/platform/iphone/plugin/godot_plugin_config.h
@@ -0,0 +1,292 @@
+/*************************************************************************/
+/* godot_plugin_config.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef GODOT_PLUGIN_CONFIG_H
+#define GODOT_PLUGIN_CONFIG_H
+
+#include "core/error/error_list.h"
+#include "core/io/config_file.h"
+#include "core/string/ustring.h"
+
+/*
+ The `config` section and fields are required and defined as follow:
+- **name**: name of the plugin
+- **binary**: path to static `.a` library
+
+The `dependencies` and fields are optional.
+- **linked**: dependencies that should only be linked.
+- **embedded**: dependencies that should be linked and embedded into application.
+- **system**: system dependencies that should be linked.
+- **capabilities**: capabilities that would be used for `UIRequiredDeviceCapabilities` options in Info.plist file.
+- **files**: files that would be copied into application
+
+The `plist` section are optional.
+- **key**: key and value that would be added in Info.plist file.
+ */
+
+struct PluginConfigIOS {
+ inline static const char *PLUGIN_CONFIG_EXT = ".gdip";
+
+ inline static const char *CONFIG_SECTION = "config";
+ inline static const char *CONFIG_NAME_KEY = "name";
+ inline static const char *CONFIG_BINARY_KEY = "binary";
+ inline static const char *CONFIG_INITIALIZE_KEY = "initialization";
+ inline static const char *CONFIG_DEINITIALIZE_KEY = "deinitialization";
+
+ inline static const char *DEPENDENCIES_SECTION = "dependencies";
+ inline static const char *DEPENDENCIES_LINKED_KEY = "linked";
+ inline static const char *DEPENDENCIES_EMBEDDED_KEY = "embedded";
+ inline static const char *DEPENDENCIES_SYSTEM_KEY = "system";
+ inline static const char *DEPENDENCIES_CAPABILITIES_KEY = "capabilities";
+ inline static const char *DEPENDENCIES_FILES_KEY = "files";
+ inline static const char *DEPENDENCIES_LINKER_FLAGS = "linker_flags";
+
+ inline static const char *PLIST_SECTION = "plist";
+
+ // Set to true when the config file is properly loaded.
+ bool valid_config = false;
+ bool supports_targets = false;
+ // Unix timestamp of last change to this plugin.
+ uint64_t last_updated = 0;
+
+ // Required config section
+ String name;
+ String binary;
+ String initialization_method;
+ String deinitialization_method;
+
+ // Optional dependencies section
+ Vector<String> linked_dependencies;
+ Vector<String> embedded_dependencies;
+ Vector<String> system_dependencies;
+
+ Vector<String> files_to_copy;
+ Vector<String> capabilities;
+
+ Vector<String> linker_flags;
+
+ // Optional plist section
+ // Supports only string types for now
+ HashMap<String, String> plist;
+};
+
+static inline String resolve_local_dependency_path(String plugin_config_dir, String dependency_path) {
+ String absolute_path;
+
+ if (dependency_path.is_empty()) {
+ return absolute_path;
+ }
+
+ if (dependency_path.is_abs_path()) {
+ return dependency_path;
+ }
+
+ String res_path = ProjectSettings::get_singleton()->globalize_path("res://");
+ absolute_path = plugin_config_dir.plus_file(dependency_path);
+
+ return absolute_path.replace(res_path, "res://");
+}
+
+static inline String resolve_system_dependency_path(String dependency_path) {
+ String absolute_path;
+
+ if (dependency_path.is_empty()) {
+ return absolute_path;
+ }
+
+ if (dependency_path.is_abs_path()) {
+ return dependency_path;
+ }
+
+ String system_path = "/System/Library/Frameworks";
+
+ return system_path.plus_file(dependency_path);
+}
+
+static inline Vector<String> resolve_local_dependencies(String plugin_config_dir, Vector<String> p_paths) {
+ Vector<String> paths;
+
+ for (int i = 0; i < p_paths.size(); i++) {
+ String path = resolve_local_dependency_path(plugin_config_dir, p_paths[i]);
+
+ if (path.is_empty()) {
+ continue;
+ }
+
+ paths.push_back(path);
+ }
+
+ return paths;
+}
+
+static inline Vector<String> resolve_system_dependencies(Vector<String> p_paths) {
+ Vector<String> paths;
+
+ for (int i = 0; i < p_paths.size(); i++) {
+ String path = resolve_system_dependency_path(p_paths[i]);
+
+ if (path.is_empty()) {
+ continue;
+ }
+
+ paths.push_back(path);
+ }
+
+ return paths;
+}
+
+static inline bool validate_plugin(PluginConfigIOS &plugin_config) {
+ bool valid_name = !plugin_config.name.is_empty();
+ bool valid_binary_name = !plugin_config.binary.is_empty();
+ bool valid_initialize = !plugin_config.initialization_method.is_empty();
+ bool valid_deinitialize = !plugin_config.deinitialization_method.is_empty();
+
+ bool fields_value = valid_name && valid_binary_name && valid_initialize && valid_deinitialize;
+
+ if (!fields_value) {
+ return false;
+ }
+
+ String plugin_extension = plugin_config.binary.get_extension().to_lower();
+
+ if ((plugin_extension == "a" && FileAccess::exists(plugin_config.binary)) ||
+ (plugin_extension == "xcframework" && DirAccess::exists(plugin_config.binary))) {
+ plugin_config.valid_config = true;
+ plugin_config.supports_targets = false;
+ } else {
+ String file_path = plugin_config.binary.get_base_dir();
+ String file_name = plugin_config.binary.get_basename().get_file();
+ String file_extension = plugin_config.binary.get_extension();
+ String release_file_name = file_path.plus_file(file_name + ".release." + file_extension);
+ String debug_file_name = file_path.plus_file(file_name + ".debug." + file_extension);
+
+ if ((plugin_extension == "a" && FileAccess::exists(release_file_name) && FileAccess::exists(debug_file_name)) ||
+ (plugin_extension == "xcframework" && DirAccess::exists(release_file_name) && DirAccess::exists(debug_file_name))) {
+ plugin_config.valid_config = true;
+ plugin_config.supports_targets = true;
+ }
+ }
+
+ return plugin_config.valid_config;
+}
+
+static inline String get_plugin_main_binary(PluginConfigIOS &plugin_config, bool p_debug) {
+ if (!plugin_config.supports_targets) {
+ return plugin_config.binary;
+ }
+
+ String plugin_binary_dir = plugin_config.binary.get_base_dir();
+ String plugin_name_prefix = plugin_config.binary.get_basename().get_file();
+ String plugin_extension = plugin_config.binary.get_extension();
+ String plugin_file = plugin_name_prefix + "." + (p_debug ? "debug" : "release") + "." + plugin_extension;
+
+ return plugin_binary_dir.plus_file(plugin_file);
+}
+
+static inline uint64_t get_plugin_modification_time(const PluginConfigIOS &plugin_config, const String &config_path) {
+ uint64_t last_updated = FileAccess::get_modified_time(config_path);
+
+ if (!plugin_config.supports_targets) {
+ last_updated = MAX(last_updated, FileAccess::get_modified_time(plugin_config.binary));
+ } else {
+ String file_path = plugin_config.binary.get_base_dir();
+ String file_name = plugin_config.binary.get_basename().get_file();
+ String release_file_name = file_path.plus_file(file_name + ".release.a");
+ String debug_file_name = file_path.plus_file(file_name + ".debug.a");
+
+ last_updated = MAX(last_updated, FileAccess::get_modified_time(release_file_name));
+ last_updated = MAX(last_updated, FileAccess::get_modified_time(debug_file_name));
+ }
+
+ return last_updated;
+}
+
+static inline PluginConfigIOS load_plugin_config(Ref<ConfigFile> config_file, const String &path) {
+ PluginConfigIOS plugin_config = {};
+
+ if (!config_file.is_valid()) {
+ return plugin_config;
+ }
+
+ Error err = config_file->load(path);
+
+ if (err != OK) {
+ return plugin_config;
+ }
+
+ String config_base_dir = path.get_base_dir();
+
+ plugin_config.name = config_file->get_value(PluginConfigIOS::CONFIG_SECTION, PluginConfigIOS::CONFIG_NAME_KEY, String());
+ plugin_config.initialization_method = config_file->get_value(PluginConfigIOS::CONFIG_SECTION, PluginConfigIOS::CONFIG_INITIALIZE_KEY, String());
+ plugin_config.deinitialization_method = config_file->get_value(PluginConfigIOS::CONFIG_SECTION, PluginConfigIOS::CONFIG_DEINITIALIZE_KEY, String());
+
+ String binary_path = config_file->get_value(PluginConfigIOS::CONFIG_SECTION, PluginConfigIOS::CONFIG_BINARY_KEY, String());
+ plugin_config.binary = resolve_local_dependency_path(config_base_dir, binary_path);
+
+ if (config_file->has_section(PluginConfigIOS::DEPENDENCIES_SECTION)) {
+ Vector<String> linked_dependencies = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_LINKED_KEY, Vector<String>());
+ Vector<String> embedded_dependencies = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_EMBEDDED_KEY, Vector<String>());
+ Vector<String> system_dependencies = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_SYSTEM_KEY, Vector<String>());
+ Vector<String> files = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_FILES_KEY, Vector<String>());
+
+ plugin_config.linked_dependencies = resolve_local_dependencies(config_base_dir, linked_dependencies);
+ plugin_config.embedded_dependencies = resolve_local_dependencies(config_base_dir, embedded_dependencies);
+ plugin_config.system_dependencies = resolve_system_dependencies(system_dependencies);
+
+ plugin_config.files_to_copy = resolve_local_dependencies(config_base_dir, files);
+
+ plugin_config.capabilities = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_CAPABILITIES_KEY, Vector<String>());
+
+ plugin_config.linker_flags = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_LINKER_FLAGS, Vector<String>());
+ }
+
+ if (config_file->has_section(PluginConfigIOS::PLIST_SECTION)) {
+ List<String> keys;
+ config_file->get_section_keys(PluginConfigIOS::PLIST_SECTION, &keys);
+
+ for (int i = 0; i < keys.size(); i++) {
+ String value = config_file->get_value(PluginConfigIOS::PLIST_SECTION, keys[i], String());
+
+ if (value.is_empty()) {
+ continue;
+ }
+
+ plugin_config.plist[keys[i]] = value;
+ }
+ }
+
+ if (validate_plugin(plugin_config)) {
+ plugin_config.last_updated = get_plugin_modification_time(plugin_config, path);
+ }
+
+ return plugin_config;
+}
+
+#endif // GODOT_PLUGIN_CONFIG_H
diff --git a/platform/iphone/view_controller.h b/platform/iphone/view_controller.h
index f6bbe11d97..52fb6fbbf2 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-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 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 */
@@ -28,23 +28,20 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#import <GameKit/GameKit.h>
#import <UIKit/UIKit.h>
-@interface ViewController : UIViewController <GKGameCenterControllerDelegate> {
-};
+@class GodotView;
+@class GodotNativeVideoView;
+@class GodotKeyboardInputView;
-- (BOOL)shouldAutorotateToInterfaceOrientation:
- (UIInterfaceOrientation)p_orientation;
+@interface ViewController : UIViewController
-- (void)didReceiveMemoryWarning;
+@property(nonatomic, readonly, strong) GodotView *godotView;
+@property(nonatomic, readonly, strong) GodotNativeVideoView *videoView;
+@property(nonatomic, readonly, strong) GodotKeyboardInputView *keyboardView;
-- (void)viewDidLoad;
+// MARK: Native Video Player
-- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures;
-
-- (BOOL)prefersStatusBarHidden;
-
-- (BOOL)prefersHomeIndicatorAutoHidden;
+- (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack;
@end
diff --git a/platform/iphone/view_controller.mm b/platform/iphone/view_controller.mm
index 279bcc1226..6cef244567 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-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 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,96 +29,184 @@
/*************************************************************************/
#import "view_controller.h"
-
+#include "core/config/project_settings.h"
+#include "display_server_iphone.h"
+#import "godot_view.h"
+#import "godot_view_renderer.h"
+#import "keyboard_input_view.h"
+#import "native_video_view.h"
#include "os_iphone.h"
-#include "core/project_settings.h"
+#import <AVFoundation/AVFoundation.h>
+#import <GameController/GameController.h>
-extern "C" {
+@interface ViewController () <GodotViewDelegate>
-int add_path(int, char **);
-int add_cmdline(int, char **);
+@property(strong, nonatomic) GodotViewRenderer *renderer;
+@property(strong, nonatomic) GodotNativeVideoView *videoView;
+@property(strong, nonatomic) GodotKeyboardInputView *keyboardView;
-int add_path(int p_argc, char **p_args) {
- NSString *str = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_path"];
- if (!str)
- return p_argc;
+@property(strong, nonatomic) UIView *godotLoadingOverlay;
- p_args[p_argc++] = "--path";
- [str retain]; // memory leak lol (maybe make it static here and delete it in ViewController destructor? @todo
- p_args[p_argc++] = (char *)[str cString];
- p_args[p_argc] = NULL;
+@end
- return p_argc;
-};
+@implementation ViewController
-int add_cmdline(int p_argc, char **p_args) {
- NSArray *arr = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_cmdline"];
- if (!arr)
- return p_argc;
+- (GodotView *)godotView {
+ return (GodotView *)self.view;
+}
- for (int i = 0; i < [arr count]; i++) {
- NSString *str = [arr objectAtIndex:i];
- if (!str)
- continue;
- [str retain]; // @todo delete these at some point
- p_args[p_argc++] = (char *)[str cString];
- };
+- (void)loadView {
+ GodotView *view = [[GodotView alloc] init];
+ GodotViewRenderer *renderer = [[GodotViewRenderer alloc] init];
- p_args[p_argc] = NULL;
+ self.renderer = renderer;
+ self.view = view;
- return p_argc;
-};
-}; // extern "C"
+ view.renderer = self.renderer;
+ view.delegate = self;
+}
-@interface ViewController ()
+- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
+ self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
-@end
+ if (self) {
+ [self godot_commonInit];
+ }
-@implementation ViewController
+ return self;
+}
+
+- (instancetype)initWithCoder:(NSCoder *)coder {
+ self = [super initWithCoder:coder];
+
+ if (self) {
+ [self godot_commonInit];
+ }
+
+ return self;
+}
+
+- (void)godot_commonInit {
+ // Initialize view controller values.
+}
- (void)didReceiveMemoryWarning {
+ [super didReceiveMemoryWarning];
printf("*********** did receive memory warning!\n");
-};
+}
- (void)viewDidLoad {
[super viewDidLoad];
+ [self observeKeyboard];
+ [self displayLoadingOverlay];
+
if (@available(iOS 11.0, *)) {
[self setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
}
}
+- (void)observeKeyboard {
+ printf("******** setting up keyboard input view\n");
+ self.keyboardView = [GodotKeyboardInputView new];
+ [self.view addSubview:self.keyboardView];
+
+ printf("******** adding observer for keyboard show/hide\n");
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(keyboardOnScreen:)
+ name:UIKeyboardDidShowNotification
+ object:nil];
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(keyboardHidden:)
+ name:UIKeyboardDidHideNotification
+ object:nil];
+}
+
+- (void)displayLoadingOverlay {
+ NSBundle *bundle = [NSBundle mainBundle];
+ NSString *storyboardName = @"Launch Screen";
+
+ if ([bundle pathForResource:storyboardName ofType:@"storyboardc"] == nil) {
+ return;
+ }
+
+ UIStoryboard *launchStoryboard = [UIStoryboard storyboardWithName:storyboardName bundle:bundle];
+
+ UIViewController *controller = [launchStoryboard instantiateInitialViewController];
+ self.godotLoadingOverlay = controller.view;
+ self.godotLoadingOverlay.frame = self.view.bounds;
+ self.godotLoadingOverlay.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
+
+ [self.view addSubview:self.godotLoadingOverlay];
+}
+
+- (BOOL)godotViewFinishedSetup:(GodotView *)view {
+ [self.godotLoadingOverlay removeFromSuperview];
+ self.godotLoadingOverlay = nil;
+
+ return YES;
+}
+
+- (void)dealloc {
+ [self.videoView stopVideo];
+
+ self.videoView = nil;
+
+ self.keyboardView = nil;
+
+ self.renderer = nil;
+
+ if (self.godotLoadingOverlay) {
+ [self.godotLoadingOverlay removeFromSuperview];
+ self.godotLoadingOverlay = nil;
+ }
+
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+}
+
+// MARK: Orientation
+
- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures {
return UIRectEdgeAll;
}
- (BOOL)shouldAutorotate {
- switch (OS::get_singleton()->get_screen_orientation()) {
- case OS::SCREEN_SENSOR:
- case OS::SCREEN_SENSOR_LANDSCAPE:
- case OS::SCREEN_SENSOR_PORTRAIT:
+ if (!DisplayServerIPhone::get_singleton()) {
+ return NO;
+ }
+
+ switch (DisplayServerIPhone::get_singleton()->screen_get_orientation(DisplayServer::SCREEN_OF_MAIN_WINDOW)) {
+ case DisplayServer::SCREEN_SENSOR:
+ case DisplayServer::SCREEN_SENSOR_LANDSCAPE:
+ case DisplayServer::SCREEN_SENSOR_PORTRAIT:
return YES;
default:
return NO;
}
-};
+}
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
- switch (OS::get_singleton()->get_screen_orientation()) {
- case OS::SCREEN_PORTRAIT:
+ if (!DisplayServerIPhone::get_singleton()) {
+ return UIInterfaceOrientationMaskAll;
+ }
+
+ switch (DisplayServerIPhone::get_singleton()->screen_get_orientation(DisplayServer::SCREEN_OF_MAIN_WINDOW)) {
+ case DisplayServer::SCREEN_PORTRAIT:
return UIInterfaceOrientationMaskPortrait;
- case OS::SCREEN_REVERSE_LANDSCAPE:
+ case DisplayServer::SCREEN_REVERSE_LANDSCAPE:
return UIInterfaceOrientationMaskLandscapeRight;
- case OS::SCREEN_REVERSE_PORTRAIT:
+ case DisplayServer::SCREEN_REVERSE_PORTRAIT:
return UIInterfaceOrientationMaskPortraitUpsideDown;
- case OS::SCREEN_SENSOR_LANDSCAPE:
+ case DisplayServer::SCREEN_SENSOR_LANDSCAPE:
return UIInterfaceOrientationMaskLandscape;
- case OS::SCREEN_SENSOR_PORTRAIT:
+ case DisplayServer::SCREEN_SENSOR_PORTRAIT:
return UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown;
- case OS::SCREEN_SENSOR:
+ case DisplayServer::SCREEN_SENSOR:
return UIInterfaceOrientationMaskAll;
- case OS::SCREEN_LANDSCAPE:
+ case DisplayServer::SCREEN_LANDSCAPE:
return UIInterfaceOrientationMaskLandscapeLeft;
}
};
@@ -135,12 +223,42 @@ int add_cmdline(int p_argc, char **p_args) {
}
}
-#ifdef GAME_CENTER_ENABLED
-- (void)gameCenterViewControllerDidFinish:(GKGameCenterViewController *)gameCenterViewController {
- //[gameCenterViewController dismissViewControllerAnimated:YES completion:^{GameCenter::get_singleton()->game_center_closed();}];//version for signaling when overlay is completely gone
- GameCenter::get_singleton()->game_center_closed();
- [gameCenterViewController dismissViewControllerAnimated:YES completion:nil];
+// MARK: Keyboard
+
+- (void)keyboardOnScreen:(NSNotification *)notification {
+ NSDictionary *info = notification.userInfo;
+ NSValue *value = info[UIKeyboardFrameEndUserInfoKey];
+
+ CGRect rawFrame = [value CGRectValue];
+ CGRect keyboardFrame = [self.view convertRect:rawFrame fromView:nil];
+
+ if (DisplayServerIPhone::get_singleton()) {
+ DisplayServerIPhone::get_singleton()->virtual_keyboard_set_height(keyboardFrame.size.height);
+ }
+}
+
+- (void)keyboardHidden:(NSNotification *)notification {
+ if (DisplayServerIPhone::get_singleton()) {
+ DisplayServerIPhone::get_singleton()->virtual_keyboard_set_height(0);
+ }
+}
+
+// MARK: Native Video Player
+
+- (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack {
+ // If we are showing some video already, reuse existing view for new video.
+ if (self.videoView) {
+ return [self.videoView playVideoAtPath:filePath volume:videoVolume audio:audioTrack subtitle:subtitleTrack];
+ } else {
+ // Create autoresizing view for video playback.
+ GodotNativeVideoView *videoView = [[GodotNativeVideoView alloc] initWithFrame:self.view.bounds];
+ videoView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
+ [self.view addSubview:videoView];
+
+ self.videoView = videoView;
+
+ return [self.videoView playVideoAtPath:filePath volume:videoVolume audio:audioTrack subtitle:subtitleTrack];
+ }
}
-#endif
@end
diff --git a/platform/iphone/vulkan_context_iphone.h b/platform/iphone/vulkan_context_iphone.h
index cadd701636..88764e270e 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-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 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,13 +32,14 @@
#define VULKAN_CONTEXT_IPHONE_H
#include "drivers/vulkan/vulkan_context.h"
-// #import <UIKit/UIKit.h>
+
+#import <UIKit/UIKit.h>
class VulkanContextIPhone : public VulkanContext {
virtual const char *_get_platform_surface_extension() const;
public:
- int window_create(void *p_window, int p_width, int p_height);
+ Error window_create(DisplayServer::WindowID p_window_id, CALayer *p_metal_layer, int p_width, int p_height);
VulkanContextIPhone();
~VulkanContextIPhone();
diff --git a/platform/iphone/vulkan_context_iphone.mm b/platform/iphone/vulkan_context_iphone.mm
index 44c940dc3a..b980ae99f0 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-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 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 */
@@ -35,21 +35,21 @@ const char *VulkanContextIPhone::_get_platform_surface_extension() const {
return VK_MVK_IOS_SURFACE_EXTENSION_NAME;
}
-int VulkanContextIPhone::window_create(void *p_window, int p_width, int p_height) {
+Error VulkanContextIPhone::window_create(DisplayServer::WindowID p_window_id, CALayer *p_metal_layer, int p_width, int p_height) {
VkIOSSurfaceCreateInfoMVK createInfo;
- createInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK;
+ createInfo.sType = VK_STRUCTURE_TYPE_IOS_SURFACE_CREATE_INFO_MVK;
createInfo.pNext = NULL;
createInfo.flags = 0;
- createInfo.pView = p_window;
+ createInfo.pView = (__bridge const void *)p_metal_layer;
VkSurfaceKHR surface;
- VkResult err = vkCreateIOSSurfaceMVK(_get_instance(), &createInfo, NULL, &surface);
- ERR_FAIL_COND_V(err, -1);
- return _window_create(surface, p_width, p_height);
-}
+ VkResult err =
+ vkCreateIOSSurfaceMVK(_get_instance(), &createInfo, NULL, &surface);
+ ERR_FAIL_COND_V(err, ERR_CANT_CREATE);
-VulkanContextIPhone::VulkanContextIPhone() {
+ return _window_create(p_window_id, surface, p_width, p_height);
}
-VulkanContextIPhone::~VulkanContextIPhone() {
-}
+VulkanContextIPhone::VulkanContextIPhone() {}
+
+VulkanContextIPhone::~VulkanContextIPhone() {}