summaryrefslogtreecommitdiff
path: root/platform/iphone
diff options
context:
space:
mode:
Diffstat (limited to 'platform/iphone')
-rw-r--r--platform/iphone/SCsub8
-rw-r--r--platform/iphone/app_delegate.mm6
-rw-r--r--platform/iphone/detect.py26
-rw-r--r--platform/iphone/device_metrics.h (renamed from platform/iphone/icloud.h)37
-rw-r--r--platform/iphone/device_metrics.m152
-rw-r--r--platform/iphone/display_layer.mm5
-rw-r--r--platform/iphone/display_server_iphone.mm202
-rw-r--r--platform/iphone/export/export.cpp713
-rw-r--r--platform/iphone/game_center.mm398
-rw-r--r--platform/iphone/godot_iphone.mm6
-rw-r--r--platform/iphone/godot_view.h4
-rw-r--r--platform/iphone/godot_view.mm55
-rw-r--r--platform/iphone/godot_view_gesture_recognizer.h4
-rw-r--r--platform/iphone/godot_view_gesture_recognizer.mm (renamed from platform/iphone/godot_view_gesture_recognizer.m)22
-rw-r--r--platform/iphone/godot_view_renderer.mm35
-rw-r--r--platform/iphone/icloud.mm357
-rw-r--r--platform/iphone/in_app_store.mm344
-rw-r--r--platform/iphone/ios.h2
-rw-r--r--platform/iphone/ios.mm21
-rw-r--r--platform/iphone/joypad_iphone.mm103
-rw-r--r--platform/iphone/keyboard_input_view.h (renamed from platform/iphone/in_app_store.h)40
-rw-r--r--platform/iphone/keyboard_input_view.mm195
-rw-r--r--platform/iphone/logo.pngbin1489 -> 1297 bytes
-rw-r--r--platform/iphone/native_video_view.h (renamed from platform/iphone/game_center.h)52
-rw-r--r--platform/iphone/native_video_view.m260
-rw-r--r--platform/iphone/os_iphone.h19
-rw-r--r--platform/iphone/os_iphone.mm69
-rw-r--r--platform/iphone/platform_config.h9
-rw-r--r--platform/iphone/plugin/godot_plugin_config.h265
-rw-r--r--platform/iphone/view_controller.h13
-rw-r--r--platform/iphone/view_controller.mm229
-rw-r--r--platform/iphone/vulkan_context_iphone.mm6
32 files changed, 1570 insertions, 2087 deletions
diff --git a/platform/iphone/SCsub b/platform/iphone/SCsub
index 49c77468ed..d289e3a16f 100644
--- a/platform/iphone/SCsub
+++ b/platform/iphone/SCsub
@@ -8,9 +8,6 @@ iphone_lib = [
"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",
@@ -18,7 +15,10 @@ iphone_lib = [
"godot_view.mm",
"display_layer.mm",
"godot_view_renderer.mm",
- "godot_view_gesture_recognizer.m",
+ "godot_view_gesture_recognizer.mm",
+ "device_metrics.m",
+ "keyboard_input_view.mm",
+ "native_video_view.m",
]
env_ios = env.Clone()
diff --git a/platform/iphone/app_delegate.mm b/platform/iphone/app_delegate.mm
index 7edbcc4667..c1942e77dd 100644
--- a/platform/iphone/app_delegate.mm
+++ b/platform/iphone/app_delegate.mm
@@ -29,13 +29,14 @@
/*************************************************************************/
#import "app_delegate.h"
-#include "core/project_settings.h"
+#include "core/config/project_settings.h"
#include "drivers/coreaudio/audio_driver_coreaudio.h"
#import "godot_view.h"
#include "main/main.h"
#include "os_iphone.h"
#import "view_controller.h"
+#import <AVFoundation/AVFoundation.h>
#import <AudioToolbox/AudioServices.h>
#define kRenderingFrequency 60
@@ -62,7 +63,7 @@ static ViewController *mainViewController = nil;
CGRect windowBounds = [[UIScreen mainScreen] bounds];
// Create a full-screen window
- self.window = [[[UIWindow alloc] initWithFrame:windowBounds] autorelease];
+ self.window = [[UIWindow alloc] initWithFrame:windowBounds];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
@@ -140,7 +141,6 @@ static ViewController *mainViewController = nil;
- (void)dealloc {
self.window = nil;
- [super dealloc];
}
@end
diff --git a/platform/iphone/detect.py b/platform/iphone/detect.py
index f4ef40a0ba..0456458326 100644
--- a/platform/iphone/detect.py
+++ b/platform/iphone/detect.py
@@ -35,9 +35,6 @@ def get_opts():
" 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_exceptions", "Enable exceptions", False),
("ios_triple", "Triple for ios toolchain", ""),
]
@@ -119,7 +116,7 @@ def configure(env):
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=13.0"
@@ -129,7 +126,7 @@ def configure(env):
detect_darwin_sdk_path("iphone", env)
env.Append(
CCFLAGS=(
- "-fno-objc-arc -arch armv7 -fmessage-length=0 -fno-strict-aliasing"
+ "-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))"'
@@ -141,7 +138,7 @@ def configure(env):
detect_darwin_sdk_path("iphone", env)
env.Append(
CCFLAGS=(
- "-fno-objc-arc -arch arm64 -fmessage-length=0 -fno-strict-aliasing"
+ "-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 -miphoneos-version-min=11.0"
" -isysroot $IPHONESDK".split()
@@ -222,20 +219,11 @@ def configure(env):
]
)
- # 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"
diff --git a/platform/iphone/icloud.h b/platform/iphone/device_metrics.h
index 381edfa718..6d0ff49077 100644
--- a/platform/iphone/icloud.h
+++ b/platform/iphone/device_metrics.h
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* icloud.h */
+/* device_metrics.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,37 +28,10 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifdef ICLOUD_ENABLED
+#import <Foundation/Foundation.h>
-#ifndef ICLOUD_H
-#define ICLOUD_H
+@interface GodotDeviceMetrics : NSObject
-#include "core/object.h"
+@property(nonatomic, class, readonly, strong) NSDictionary<NSArray *, NSNumber *> *dpiList;
-class ICloud : public Object {
- GDCLASS(ICloud, Object);
-
- static ICloud *instance;
- static void _bind_methods();
-
- List<Variant> pending_events;
-
-public:
- Error remove_key(String p_param);
- Array set_key_values(Dictionary p_params);
- Variant get_key_value(String p_param);
- Error synchronize_key_values();
- Variant get_all_key_values();
-
- int get_pending_event_count();
- Variant pop_pending_event();
-
- static ICloud *get_singleton();
-
- ICloud();
- ~ICloud();
-};
-
-#endif
-
-#endif
+@end
diff --git a/platform/iphone/device_metrics.m b/platform/iphone/device_metrics.m
new file mode 100644
index 0000000000..747872bc49
--- /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-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 "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/display_layer.mm b/platform/iphone/display_layer.mm
index 5ec94fb471..7420a5ebe6 100644
--- a/platform/iphone/display_layer.mm
+++ b/platform/iphone/display_layer.mm
@@ -29,8 +29,8 @@
/*************************************************************************/
#import "display_layer.h"
+#include "core/config/project_settings.h"
#include "core/os/keyboard.h"
-#include "core/project_settings.h"
#include "display_server_iphone.h"
#include "main/main.h"
#include "os_iphone.h"
@@ -124,11 +124,8 @@
}
if (context) {
- [context release];
context = nil;
}
-
- [super dealloc];
}
- (BOOL)createFramebuffer {
diff --git a/platform/iphone/display_server_iphone.mm b/platform/iphone/display_server_iphone.mm
index 1721da3db6..d47d131719 100644
--- a/platform/iphone/display_server_iphone.mm
+++ b/platform/iphone/display_server_iphone.mm
@@ -30,10 +30,13 @@
#include "display_server_iphone.h"
#import "app_delegate.h"
+#include "core/config/project_settings.h"
#include "core/io/file_access_pack.h"
-#include "core/project_settings.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"
@@ -41,120 +44,6 @@
#import <sys/utsname.h>
static const float kDisplayServerIPhoneAcceleration = 1;
-static NSDictionary *iOSModelToDPI = @{
- @[
- @"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,
-};
DisplayServerIPhone *DisplayServerIPhone::get_singleton() {
return (DisplayServerIPhone *)DisplayServer::get_singleton();
@@ -383,8 +272,7 @@ 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) {
+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,
@@ -392,39 +280,6 @@ void DisplayServerIPhone::update_accelerometer(float p_x, float p_y,
p_z / kDisplayServerIPhoneAcceleration);
Input::get_singleton()->set_accelerometer(v_accelerometer);
-
- /*
- 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 DisplayServerIPhone::update_magnetometer(float p_x, float p_y, float p_z) {
@@ -516,6 +371,8 @@ int DisplayServerIPhone::screen_get_dpi(int p_screen) const {
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];
@@ -523,7 +380,26 @@ int DisplayServerIPhone::screen_get_dpi(int p_screen) const {
}
}
- return 163;
+ // 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 {
@@ -654,11 +530,17 @@ bool DisplayServerIPhone::screen_is_touchscreen(int p_screen) const {
}
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) {
- [AppDelegate.viewController.godotView becomeFirstResponderWithString:p_existing_text];
+ 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.godotView resignFirstResponder];
+ [AppDelegate.viewController.keyboardView resignFirstResponder];
}
void DisplayServerIPhone::virtual_keyboard_set_height(int height) {
@@ -701,7 +583,7 @@ Error DisplayServerIPhone::native_video_play(String p_path, float p_volume, Stri
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.c_str());
+ 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());
@@ -712,11 +594,11 @@ Error DisplayServerIPhone::native_video_play(String p_path, float p_volume, Stri
memdelete(f);
- printf("Playing video: %S\n", p_path.c_str());
+ 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()] autorelease];
+ 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()];
@@ -731,22 +613,22 @@ Error DisplayServerIPhone::native_video_play(String p_path, float p_volume, Stri
}
bool DisplayServerIPhone::native_video_is_playing() const {
- return [AppDelegate.viewController isVideoPlaying];
+ return [AppDelegate.viewController.videoView isVideoPlaying];
}
void DisplayServerIPhone::native_video_pause() {
if (native_video_is_playing()) {
- [AppDelegate.viewController pauseVideo];
+ [AppDelegate.viewController.videoView pauseVideo];
}
}
void DisplayServerIPhone::native_video_unpause() {
- [AppDelegate.viewController unpauseVideo];
+ [AppDelegate.viewController.videoView unpauseVideo];
};
void DisplayServerIPhone::native_video_stop() {
if (native_video_is_playing()) {
- [AppDelegate.viewController stopVideo];
+ [AppDelegate.viewController.videoView stopVideo];
}
}
diff --git a/platform/iphone/export/export.cpp b/platform/iphone/export/export.cpp
index 4a751488cb..cbef136247 100644
--- a/platform/iphone/export/export.cpp
+++ b/platform/iphone/export/export.cpp
@@ -30,19 +30,20 @@
#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/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 +55,13 @@ class EditorExportPlatformIOS : public EditorExportPlatform {
Ref<ImageTexture> logo;
+ // Plugins
+ volatile bool plugins_changed;
+ Thread *check_for_changes_thread;
+ volatile bool quit_request;
+ Mutex plugins_lock;
+ Vector<PluginConfig> 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 +78,7 @@ class EditorExportPlatformIOS : public EditorExportPlatform {
String modules_fileref;
String modules_buildphase;
String modules_buildgrp;
+ Vector<String> capabilities;
};
struct ExportArchitecture {
String name;
@@ -86,6 +95,7 @@ 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 should_embed;
};
String _get_additional_plist_content();
@@ -100,8 +110,10 @@ class EditorExportPlatformIOS : public EditorExportPlatform {
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;
@@ -114,7 +126,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));
@@ -126,6 +138,40 @@ class EditorExportPlatformIOS : public EditorExportPlatform {
return true;
}
+ static void _check_for_changes_poll_thread(void *ud) {
+ EditorExportPlatformIOS *ea = (EditorExportPlatformIOS *)ud;
+
+ while (!ea->quit_request) {
+ // Nothing to do if we already know the plugins have changed.
+ if (!ea->plugins_changed) {
+ MutexLock lock(ea->plugins_lock);
+
+ Vector<PluginConfig> loaded_plugins = get_plugins();
+
+ if (ea->plugins.size() != loaded_plugins.size()) {
+ ea->plugins_changed = true;
+ } 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 = true;
+ 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) {
+ break;
+ }
+ }
+ }
+ }
+
protected:
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;
@@ -135,13 +181,21 @@ public:
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;
+ if (export_options_changed) {
+ // don't clear unless we're reporting true, to avoid race
+ plugins_changed = false;
+ }
+ return export_options_changed;
+ }
+
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) override;
- 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 bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override;
@@ -155,13 +209,91 @@ public:
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.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(PLUGIN_CONFIG_EXT)) {
+ dir_files.push_back(file);
+ }
+ }
+ da->list_dir_end();
+ }
+
+ return dir_files;
+ }
+
+ static Vector<PluginConfig> get_plugins() {
+ Vector<PluginConfig> 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.empty()) {
+ Ref<ConfigFile> config_file = memnew(ConfigFile);
+ for (int i = 0; i < plugins_filenames.size(); i++) {
+ PluginConfig 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<PluginConfig> get_enabled_plugins(const Ref<EditorExportPreset> &p_presets) {
+ Vector<PluginConfig> enabled_plugins;
+ Vector<PluginConfig> all_plugins = get_plugins();
+ for (int i = 0; i < all_plugins.size(); i++) {
+ PluginConfig 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") {
+ r_features->push_back("pvrtc");
+ if (driver == "Vulkan") {
// FIXME: Review if this is correct.
r_features->push_back("etc2");
}
@@ -224,12 +356,16 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options)
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<PluginConfig> 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 = false;
+ 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));
@@ -345,18 +481,6 @@ 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) {
- bool is_on = p_preset->get("capabilities/push_notifications");
- strnew += lines[i].replace("$push_notifications", is_on ? "1" : "0") + "\n";
} else if (lines[i].find("$entitlements_push_notifications") != -1) {
bool is_on = p_preset->get("capabilities/push_notifications");
strnew += lines[i].replace("$entitlements_push_notifications", is_on ? "<key>aps-environment</key><string>development</string>" : "") + "\n";
@@ -366,15 +490,14 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_
// 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/access_wifi") && !capabilities_list.has("wifi")) {
+ capabilities_list.push_back("wifi");
}
- if ((bool)p_preset->get("capabilities/game_center")) {
- capabilities += "<string>gamekit</string>\n";
- }
- 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);
@@ -912,15 +1035,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 };
@@ -945,15 +1059,19 @@ void EditorExportPlatformIOS::_add_assets_to_project(const Ref<EditorExportPrese
String type;
if (asset.exported_path.ends_with(".framework")) {
- 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";
+ 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")) {
- 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";
+ 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")) {
@@ -989,28 +1107,6 @@ void EditorExportPlatformIOS::_add_assets_to_project(const Ref<EditorExportPrese
// 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);
@@ -1026,158 +1122,199 @@ 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();
- ERR_FAIL_COND_V_MSG(!filesystem_da, ERR_CANT_CREATE, "Cannot create DirAccess for path '" + p_out_dir + "'.");
- 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 };
- r_exported_assets.push_back(exported_asset);
+ 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 {
- 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;
- }
+ file_name = *p_custom_file_name;
+ }
- String base_dir = asset.get_base_dir().replace("res://", "");
- String destination_dir;
- String destination;
- String asset_path;
+ String framework_name = file_name + ".framework";
- bool create_framework = false;
+ 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);
- if (p_is_framework && asset.ends_with(".dylib")) {
- // For iOS we need to turn .dylib into .framework
- // to be able to send application to AppStore
- asset_path = String("dylibs").plus_file(base_dir);
+ String file_name;
- String file_name = asset.get_basename().get_file();
- String framework_name = file_name + ".framework";
+ if (!p_custom_file_name) {
+ file_name = p_asset.get_file();
+ } else {
+ file_name = *p_custom_file_name;
+ }
- 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 && (asset.ends_with(".framework") || asset.ends_with(".xcframework"))) {
- asset_path = String("dylibs").plus_file(base_dir);
+ 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 = asset.get_file();
- 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;
- String file_name = asset.get_file();
- 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 (!p_custom_file_name) {
+ file_name = p_asset.get_file();
+ } else {
+ file_name = *p_custom_file_name;
+ }
- 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;
- }
- }
+ 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);
+ }
- Error err = dir_exists ? da->copy_dir(asset, destination) : da->copy(asset, destination);
+ if (!filesystem_da->dir_exists(destination_dir)) {
+ Error make_dir_err = filesystem_da->make_dir_recursive(destination_dir);
+ if (make_dir_err) {
memdelete(da);
- if (err) {
- memdelete(filesystem_da);
- return err;
- }
- IOSExportAsset exported_asset = { binary_name.plus_file(asset_path), p_is_framework };
- r_exported_assets.push_back(exported_asset);
+ memdelete(filesystem_da);
+ return make_dir_err;
+ }
+ }
- if (create_framework) {
- String file_name = asset.get_basename().get_file();
- String framework_name = file_name + ".framework";
+ 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);
- // 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);
+ if (create_framework) {
+ String file_name;
- OS::get_singleton()->execute("install_name_tool", install_name_args, true);
- }
+ if (!p_custom_file_name) {
+ file_name = p_asset.get_basename().get_file();
+ } else {
+ file_name = *p_custom_file_name;
+ }
- // 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);
- }
- }
+ 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, true);
+ }
+
+ // 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, p_should_embed };
+ r_exported_assets.push_back(exported_asset);
+ } else {
+ Error err = _copy_asset(p_out_dir, asset, nullptr, p_is_framework, p_should_embed, r_exported_assets);
+ ERR_FAIL_COND_V(err, err);
+ }
+ }
+
+ return OK;
+}
+
Error 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);
}
@@ -1185,7 +1322,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;
@@ -1203,20 +1340,173 @@ 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<PluginConfig> enabled_plugins = get_enabled_plugins(p_preset);
+
+ Vector<String> added_linked_dependenciy_names;
+ Vector<String> added_embedded_dependenciy_names;
+ HashMap<String, String> plist_values;
+
+ Error err;
+
+ for (int i = 0; i < enabled_plugins.size(); i++) {
+ PluginConfig plugin = enabled_plugins[i];
+
+ // Export plugin binary.
+ if (!plugin.supports_targets) {
+ err = _copy_asset(dest_dir, plugin.binary, nullptr, true, true, r_exported_assets);
+ } else {
+ String plugin_binary_dir = plugin.binary.get_base_dir();
+ String plugin_name_prefix = plugin.binary.get_basename().get_file();
+ String plugin_file = plugin_name_prefix + "." + (p_debug ? "debug" : "release") + ".a";
+ String result_file_name = plugin.binary.get_file();
+
+ err = _copy_asset(dest_dir, plugin_binary_dir.plus_file(plugin_file), &result_file_name, true, true, 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);
+ }
+
+ // 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.empty() || value.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.empty() || value.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, "$_");
}
+ return OK;
}
Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
@@ -1325,9 +1615,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);
@@ -1340,8 +1633,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);
@@ -1383,21 +1676,8 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
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!
- }
}
+
if (file == project_file) {
project_file_data = data;
}
@@ -1531,7 +1811,6 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
}
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";
@@ -1649,7 +1928,7 @@ 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;
@@ -1666,9 +1945,17 @@ EditorExportPlatformIOS::EditorExportPlatformIOS() {
Ref<Image> img = memnew(Image(_iphone_logo));
logo.instance();
logo->create_from_image(img);
+
+ plugins_changed = true;
+ quit_request = false;
+
+ check_for_changes_thread = Thread::create(_check_for_changes_poll_thread, this);
}
EditorExportPlatformIOS::~EditorExportPlatformIOS() {
+ quit_request = true;
+ Thread::wait_to_finish(check_for_changes_thread);
+ memdelete(check_for_changes_thread);
}
void register_iphone_exporter() {
diff --git a/platform/iphone/game_center.mm b/platform/iphone/game_center.mm
deleted file mode 100644
index 4481775c32..0000000000
--- a/platform/iphone/game_center.mm
+++ /dev/null
@@ -1,398 +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"
-};
-
-#import "view_controller.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", "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";
- if (@available(iOS 13, *)) {
- ret["player_id"] = [player.teamPlayerID UTF8String];
-#if !defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR
- } else {
- ret["player_id"] = [player.playerID UTF8String];
-#endif
- }
-
- 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(Dictionary p_score) {
- ERR_FAIL_COND_V(!p_score.has("score") || !p_score.has("category"), ERR_INVALID_PARAMETER);
- float score = p_score["score"];
- String category = p_score["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(Dictionary p_params) {
- ERR_FAIL_COND_V(!p_params.has("name") || !p_params.has("progress"), ERR_INVALID_PARAMETER);
- String name = p_params["name"];
- float progress = p_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 (p_params.has("show_completion_banner")) {
- achievement.showsCompletionBanner = p_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 (NSUInteger 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 (NSUInteger 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(Dictionary p_params) {
- ERR_FAIL_COND_V(!NSProtocolFromString(@"GKGameCenterControllerDelegate"), FAILED);
-
- GKGameCenterViewControllerState view_state = GKGameCenterViewControllerStateDefault;
- if (p_params.has("view")) {
- String view_name = p_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] autorelease];
- 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 (p_params.has("leaderboard_name")) {
- String name = p_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;
- if (@available(iOS 13, *)) {
- ret["player_id"] = [player.teamPlayerID UTF8String];
-#if !defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR
- } else {
- ret["player_id"] = [player.playerID UTF8String];
-#endif
- }
- } 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/godot_iphone.mm b/platform/iphone/godot_iphone.mm
index 090b772947..a4119bcafd 100644
--- a/platform/iphone/godot_iphone.mm
+++ b/platform/iphone/godot_iphone.mm
@@ -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"
@@ -49,10 +49,8 @@ int add_path(int p_argc, char **p_args) {
}
p_args[p_argc++] = (char *)"--path";
- [str retain]; // memory leak lol (maybe make it static here and delete it in ViewController destructor? @todo
p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding];
p_args[p_argc] = NULL;
- [str release];
return p_argc;
};
@@ -68,9 +66,7 @@ int add_cmdline(int p_argc, char **p_args) {
if (!str) {
continue;
}
- [str retain]; // @todo delete these at some point
p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding];
- [str release];
};
p_args[p_argc] = NULL;
diff --git a/platform/iphone/godot_view.h b/platform/iphone/godot_view.h
index 62fa2f5a32..a8f4cb38d9 100644
--- a/platform/iphone/godot_view.h
+++ b/platform/iphone/godot_view.h
@@ -35,7 +35,7 @@ class String;
@protocol DisplayLayer;
@protocol GodotViewRendererProtocol;
-@interface GodotView : UIView <UIKeyInput>
+@interface GodotView : UIView
@property(assign, nonatomic) id<GodotViewRendererProtocol> renderer;
@@ -51,6 +51,4 @@ class String;
- (void)stopRendering;
- (void)startRendering;
-- (BOOL)becomeFirstResponderWithString:(String)p_existing;
-
@end
diff --git a/platform/iphone/godot_view.mm b/platform/iphone/godot_view.mm
index c0a31549c4..0c50842cfa 100644
--- a/platform/iphone/godot_view.mm
+++ b/platform/iphone/godot_view.mm
@@ -30,7 +30,7 @@
#import "godot_view.h"
#include "core/os/keyboard.h"
-#include "core/ustring.h"
+#include "core/string/ustring.h"
#import "display_layer.h"
#include "display_server_iphone.h"
#import "godot_view_gesture_recognizer.h"
@@ -42,7 +42,6 @@ static const int max_touches = 8;
@interface GodotView () {
UITouch *godot_touches[max_touches];
- String keyboard_text;
}
@property(assign, nonatomic) BOOL isActive;
@@ -145,8 +144,6 @@ static const int max_touches = 8;
if (self.delayGestureRecognizer) {
self.delayGestureRecognizer = nil;
}
-
- [super dealloc];
}
- (void)godot_commonInit {
@@ -156,7 +153,7 @@ static const int max_touches = 8;
// Configure and start accelerometer
if (!self.motionManager) {
- self.motionManager = [[[CMMotionManager alloc] init] autorelease];
+ self.motionManager = [[CMMotionManager alloc] init];
if (self.motionManager.deviceMotionAvailable) {
self.motionManager.deviceMotionUpdateInterval = 1.0 / 70.0;
[self.motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXMagneticNorthZVertical];
@@ -169,7 +166,6 @@ static const int max_touches = 8;
GodotViewGestureRecognizer *gestureRecognizer = [[GodotViewGestureRecognizer alloc] init];
self.delayGestureRecognizer = gestureRecognizer;
[self addGestureRecognizer:self.delayGestureRecognizer];
- [gestureRecognizer release];
}
- (void)stopRendering {
@@ -204,14 +200,11 @@ static const int max_touches = 8;
if (self.useCADisplayLink) {
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawView)];
- // if (@available(iOS 10, *)) {
- self.displayLink.preferredFramesPerSecond = (NSInteger)(1.0 / self.renderingInterval);
- // } else {
- // // Approximate frame rate
- // // assumes device refreshes at 60 fps
- // int frameInterval = (int)floor(self.renderingInterval * 60.0f);
- // [self.displayLink setFrameInterval:frameInterval];
- // }
+ // 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];
@@ -284,40 +277,6 @@ static const int max_touches = 8;
// MARK: - Input
-// MARK: Keyboard
-
-- (BOOL)canBecomeFirstResponder {
- return YES;
-}
-
-- (BOOL)becomeFirstResponderWithString:(String)p_existing {
- keyboard_text = p_existing;
- return [self becomeFirstResponder];
-}
-
-- (BOOL)resignFirstResponder {
- keyboard_text = String();
- return [super resignFirstResponder];
-}
-
-- (void)deleteBackward {
- if (keyboard_text.length()) {
- keyboard_text.erase(keyboard_text.length() - 1, 1);
- }
- DisplayServerIPhone::get_singleton()->key(KEY_BACKSPACE, true);
-}
-
-- (BOOL)hasText {
- return keyboard_text.length() > 0;
-}
-
-- (void)insertText:(NSString *)p_text {
- String character;
- character.parse_utf8([p_text UTF8String]);
- keyboard_text = keyboard_text + character;
- DisplayServerIPhone::get_singleton()->key(character[0] == 10 ? KEY_ENTER : character[0], true);
-}
-
// MARK: Touches
- (void)initTouches {
diff --git a/platform/iphone/godot_view_gesture_recognizer.h b/platform/iphone/godot_view_gesture_recognizer.h
index ca3bd808d1..1431a9fb89 100644
--- a/platform/iphone/godot_view_gesture_recognizer.h
+++ b/platform/iphone/godot_view_gesture_recognizer.h
@@ -32,13 +32,15 @@
// emulating UIScrollView's UIScrollViewDelayedTouchesBeganGestureRecognizer.
// It catches all gestures incoming to UIView and delays them for 150ms
// (the same value used by UIScrollViewDelayedTouchesBeganGestureRecognizer)
-// If touch cancelation or end message is fired it fires delayed
+// 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.m b/platform/iphone/godot_view_gesture_recognizer.mm
index 377ccd52a5..91cc07fcf6 100644
--- a/platform/iphone/godot_view_gesture_recognizer.m
+++ b/platform/iphone/godot_view_gesture_recognizer.mm
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* godot_view_gesture_recognizer.m */
+/* godot_view_gesture_recognizer.mm */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -30,8 +30,7 @@
#import "godot_view_gesture_recognizer.h"
-// Using same delay interval that is used for `UIScrollView`
-const NSTimeInterval kGLGestureDelayInterval = 0.150;
+#include "core/config/project_settings.h"
// Minimum distance for touches to move to fire
// a delay timer before scheduled time.
@@ -41,6 +40,12 @@ 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;
@@ -60,6 +65,8 @@ const CGFloat kGLGestureMovementDistance = 0.5;
self.delaysTouchesBegan = YES;
self.delaysTouchesEnded = YES;
+ self.delayTimeInterval = GLOBAL_GET("input_devices/pointing/ios/touch_delay");
+
return self;
}
@@ -76,8 +83,6 @@ const CGFloat kGLGestureMovementDistance = 0.5;
if (self.delayedEvent) {
self.delayedEvent = nil;
}
-
- [super dealloc];
}
- (void)delayTouches:(NSSet *)touches andEvent:(UIEvent *)event {
@@ -87,7 +92,7 @@ const CGFloat kGLGestureMovementDistance = 0.5;
self.delayedEvent = event;
self.delayTimer = [NSTimer
- scheduledTimerWithTimeInterval:kGLGestureDelayInterval
+ scheduledTimerWithTimeInterval:self.delayTimeInterval
target:self
selector:@selector(fireDelayedTouches:)
userInfo:nil
@@ -109,7 +114,6 @@ const CGFloat kGLGestureMovementDistance = 0.5;
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseBegan];
[self delayTouches:cleared andEvent:event];
- [cleared release];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
@@ -130,17 +134,14 @@ const CGFloat kGLGestureMovementDistance = 0.5;
if (distance > kGLGestureMovementDistance) {
[self.delayTimer fire];
[self.view touchesMoved:cleared withEvent:event];
- [cleared release];
return;
}
}
- [cleared release];
return;
}
[self.view touchesMoved:cleared withEvent:event];
- [cleared release];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
@@ -148,7 +149,6 @@ const CGFloat kGLGestureMovementDistance = 0.5;
NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseEnded];
[self.view touchesEnded:cleared withEvent:event];
- [cleared release];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
diff --git a/platform/iphone/godot_view_renderer.mm b/platform/iphone/godot_view_renderer.mm
index 1fc822b457..f2ff417e6f 100644
--- a/platform/iphone/godot_view_renderer.mm
+++ b/platform/iphone/godot_view_renderer.mm
@@ -29,8 +29,8 @@
/*************************************************************************/
#import "godot_view_renderer.h"
+#include "core/config/project_settings.h"
#include "core/os/keyboard.h"
-#include "core/project_settings.h"
#import "display_server_iphone.h"
#include "main/main.h"
#include "os_iphone.h"
@@ -44,7 +44,6 @@
@interface GodotViewRenderer ()
-@property(assign, nonatomic) BOOL hasFinishedLocaleSetup;
@property(assign, nonatomic) BOOL hasFinishedProjectDataSetup;
@property(assign, nonatomic) BOOL hasStartedMain;
@property(assign, nonatomic) BOOL hasFinishedSetup;
@@ -58,9 +57,8 @@
return NO;
}
- if (!self.hasFinishedLocaleSetup) {
- [self setupLocaleAndUUID];
- return YES;
+ if (!OS::get_singleton()) {
+ exit(0);
}
if (!self.hasFinishedProjectDataSetup) {
@@ -79,33 +77,6 @@
return NO;
}
-- (void)setupLocaleAndUUID {
- self.hasFinishedLocaleSetup = YES;
-
- if (!OS::get_singleton()) {
- exit(0);
- }
-
- 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 = [(NSString *)CFUUIDCreateString(NULL, cfuuid) autorelease];
- CFRelease(cfuuid);
- [[NSUserDefaults standardUserDefaults] setObject:uuid forKey:@"identifierForVendor"];
- }
- }
-
- OSIPhone::get_singleton()->set_unique_id(String::utf8([uuid UTF8String]));
-}
-
- (void)setupProjectData {
self.hasFinishedProjectDataSetup = YES;
diff --git a/platform/iphone/icloud.mm b/platform/iphone/icloud.mm
deleted file mode 100644
index d3086e6cea..0000000000
--- a/platform/iphone/icloud.mm
+++ /dev/null
@@ -1,357 +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 (NSUInteger 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::FLOAT) {
- 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 (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 (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(String p_param) {
- NSString *key = [[[NSString alloc] initWithUTF8String:p_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
-Array ICloud::set_key_values(Dictionary p_params) {
- Array keys = p_params.keys();
-
- Array error_keys;
-
- for (int i = 0; i < keys.size(); ++i) {
- String variant_key = keys[i];
- Variant variant_value = p_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(String p_param) {
- NSString *key = [[[NSString alloc] initWithUTF8String:p_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 dfec5d7634..0000000000
--- a/platform/iphone/in_app_store.mm
+++ /dev/null
@@ -1,344 +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];
-static NSArray *latestProducts;
-
-@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;
- latestProducts = 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(Dictionary p_params) {
- ERR_FAIL_COND_V(!p_params.has("product_ids"), ERR_INVALID_PARAMETER);
-
- PackedStringArray pids = p_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 = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]];
- }
-
- } else {
- receipt = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]];
- }
-
- NSString *receipt_to_send = nil;
- if (receipt != nil) {
- receipt_to_send = [receipt base64EncodedStringWithOptions:0];
- }
- 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(Dictionary p_params) {
- ERR_FAIL_COND_V(![SKPaymentQueue canMakePayments], ERR_UNAVAILABLE);
- if (![SKPaymentQueue canMakePayments])
- return ERR_UNAVAILABLE;
-
- printf("purchasing!\n");
- ERR_FAIL_COND_V(!p_params.has("product_id"), ERR_INVALID_PARAMETER);
-
- NSString *pid = [[[NSString alloc] initWithUTF8String:String(p_params["product_id"]).utf8().get_data()] autorelease];
-
- SKProduct *product = nil;
-
- if (latestProducts) {
- for (SKProduct *storedProduct in latestProducts) {
- if ([storedProduct.productIdentifier isEqualToString:pid]) {
- product = storedProduct;
- break;
- }
- }
- }
-
- if (!product) {
- return ERR_INVALID_PARAMETER;
- }
-
- SKPayment *payment = [SKPayment paymentWithProduct:product];
- 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..5415c36cd9 100644
--- a/platform/iphone/ios.h
+++ b/platform/iphone/ios.h
@@ -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 ad26d0ada3..d4e099063f 100644
--- a/platform/iphone/ios.mm
+++ b/platform/iphone/ios.mm
@@ -30,6 +30,7 @@
#include "ios.h"
#import "app_delegate.h"
+#import "view_controller.h"
#import <UIKit/UIKit.h>
#include <sys/sysctl.h>
@@ -68,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";
+ String app_url_path = "itms-apps://itunes.apple.com/app/idAPP_ID";
- //ios7 before
- String ret = templ;
+ String ret = app_url_path.replace("APP_ID", String::num(p_app_id));
- 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;
- }
-
- // ios7 for everything?
- ret = templ_iOS7.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/joypad_iphone.mm b/platform/iphone/joypad_iphone.mm
index 6088f1c25c..22d7dd4bd2 100644
--- a/platform/iphone/joypad_iphone.mm
+++ b/platform/iphone/joypad_iphone.mm
@@ -29,7 +29,7 @@
/*************************************************************************/
#import "joypad_iphone.h"
-#include "core/project_settings.h"
+#include "core/config/project_settings.h"
#include "drivers/coreaudio/audio_driver_coreaudio.h"
#include "main/main.h"
@@ -131,8 +131,6 @@ void JoypadIPhone::start_processing() {
- (void)dealloc {
[self finishObserving];
-
- [super dealloc];
}
- (int)getJoyIdForController:(GCController *)controller {
@@ -251,8 +249,13 @@ void JoypadIPhone::start_processing() {
// 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) {
+ _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) {
@@ -304,71 +307,33 @@ void JoypadIPhone::start_processing() {
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);
+ };
+ };
}
- // 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) {
- // 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);
- // };
- // };
- //#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) {
- // 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);
- // };
- // };
- //#endif
- // };
///@TODO need to add support for controller.motion which gives us access to
/// the orientation of the device (if supported)
diff --git a/platform/iphone/in_app_store.h b/platform/iphone/keyboard_input_view.h
index beb58af2c7..5382a2e9a9 100644
--- a/platform/iphone/in_app_store.h
+++ b/platform/iphone/keyboard_input_view.h
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* in_app_store.h */
+/* keyboard_input_view.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,40 +28,10 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifdef STOREKIT_ENABLED
+#import <UIKit/UIKit.h>
-#ifndef IN_APP_STORE_H
-#define IN_APP_STORE_H
+@interface GodotKeyboardInputView : UITextView
-#include "core/object.h"
+- (BOOL)becomeFirstResponderWithString:(NSString *)existingString multiline:(BOOL)flag cursorStart:(NSInteger)start cursorEnd:(NSInteger)end;
-class InAppStore : public Object {
- GDCLASS(InAppStore, Object);
-
- static InAppStore *instance;
- static void _bind_methods();
-
- List<Variant> pending_events;
-
-public:
- Error request_product_info(Dictionary p_params);
- Error restore_purchases();
- Error purchase(Dictionary 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();
-};
-
-#endif
-
-#endif
+@end
diff --git a/platform/iphone/keyboard_input_view.mm b/platform/iphone/keyboard_input_view.mm
new file mode 100644
index 0000000000..1a37403de7
--- /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-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 "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/game_center.h b/platform/iphone/native_video_view.h
index 1e9a68fe48..d8687b3538 100644
--- a/platform/iphone/game_center.h
+++ b/platform/iphone/native_video_view.h
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* game_center.h */
+/* native_video_view.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,48 +28,14 @@
/* 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
+@interface GodotNativeVideoView : UIView
-#include "core/object.h"
+- (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack;
+- (BOOL)isVideoPlaying;
+- (void)pauseVideo;
+- (void)unpauseVideo;
+- (void)stopVideo;
-class GameCenter : public Object {
- GDCLASS(GameCenter, Object);
-
- static GameCenter *instance;
- static void _bind_methods();
-
- List<Variant> pending_events;
-
- bool authenticated;
-
- void return_connect_error(const char *p_error_description);
-
-public:
- void connect();
- bool is_authenticated();
-
- Error post_score(Dictionary p_score);
- Error award_achievement(Dictionary p_params);
- void reset_achievements();
- void request_achievements();
- void request_achievement_descriptions();
- Error show_game_center(Dictionary p_params);
- Error request_identity_verification_signature();
-
- void game_center_closed();
-
- int get_pending_event_count();
- Variant pop_pending_event();
-
- static GameCenter *get_singleton();
-
- GameCenter();
- ~GameCenter();
-};
-
-#endif
-
-#endif
+@end
diff --git a/platform/iphone/native_video_view.m b/platform/iphone/native_video_view.m
new file mode 100644
index 0000000000..a4e9f209f0
--- /dev/null
+++ b/platform/iphone/native_video_view.m
@@ -0,0 +1,260 @@
+/*************************************************************************/
+/* native_video_view.m */
+/*************************************************************************/
+/* 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 "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)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.h b/platform/iphone/os_iphone.h
index f3bde46717..04a0a478d5 100644
--- a/platform/iphone/os_iphone.h
+++ b/platform/iphone/os_iphone.h
@@ -35,9 +35,6 @@
#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"
@@ -48,6 +45,9 @@
#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:
static HashMap<String, void *> dynamic_symbol_lookup_table;
@@ -55,15 +55,6 @@ private:
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;
JoypadIPhone *joypad_iphone;
@@ -84,8 +75,6 @@ private:
virtual void finalize() override;
String user_data_dir;
- String unique_id;
- String locale_code;
bool is_focused = false;
@@ -118,10 +107,8 @@ public:
void set_user_data_dir(String p_dir);
virtual String get_user_data_dir() const override;
- void set_locale(String p_locale);
virtual String get_locale() const override;
- void set_unique_id(String p_id);
virtual String get_unique_id() const override;
virtual void vibrate_handheld(int p_duration_ms = 500) override;
diff --git a/platform/iphone/os_iphone.mm b/platform/iphone/os_iphone.mm
index f0bbbd39ca..b87e6f37a0 100644
--- a/platform/iphone/os_iphone.mm
+++ b/platform/iphone/os_iphone.mm
@@ -32,10 +32,10 @@
#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 "core/project_settings.h"
#include "display_server_iphone.h"
#include "drivers/unix/syslog_logger.h"
#import "godot_view.h"
@@ -46,10 +46,6 @@
#import <UIKit/UIKit.h>
#import <dlfcn.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>
@@ -129,22 +125,6 @@ void OSIPhone::initialize() {
}
void OSIPhone::initialize_modules() {
-#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));
-#endif
-
ios = memnew(iOS);
Engine::get_singleton()->add_singleton(Engine::Singleton("iOS", ios));
@@ -160,26 +140,12 @@ void OSIPhone::deinitialize_modules() {
memdelete(ios);
}
-#ifdef GAME_CENTER_ENABLED
- if (game_center) {
- memdelete(game_center);
- }
-#endif
-
-#ifdef STOREKIT_ENABLED
- if (store_kit) {
- memdelete(store_kit);
- }
-#endif
-
-#ifdef ICLOUD_ENABLED
- if (icloud) {
- memdelete(icloud);
- }
-#endif
+ godot_ios_plugins_deinitialize();
}
void OSIPhone::set_main_loop(MainLoop *p_main_loop) {
+ godot_ios_plugins_initialize();
+
main_loop = p_main_loop;
if (main_loop) {
@@ -276,19 +242,14 @@ String OSIPhone::get_model_name() const {
Error OSIPhone::shell_open(String p_uri) {
NSString *urlPath = [[NSString alloc] initWithUTF8String:p_uri.utf8().get_data()];
NSURL *url = [NSURL URLWithString:urlPath];
- [urlPath release];
if (![[UIApplication sharedApplication] canOpenURL:url]) {
return ERR_CANT_OPEN;
}
- printf("opening url %ls\n", p_uri.c_str());
+ printf("opening url %s\n", p_uri.utf8().get_data());
- // if (@available(iOS 10, *)) {
[[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
- // } else {
- // [[UIApplication sharedApplication] openURL:url];
- // }
return OK;
};
@@ -297,7 +258,7 @@ 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 %ls from %ls\n", user_data_dir.c_str(), p_dir.c_str());
+ printf("setting data dir to %s from %s\n", user_data_dir.utf8().get_data(), p_dir.utf8().get_data());
memdelete(da);
}
@@ -305,20 +266,20 @@ String OSIPhone::get_user_data_dir() const {
return user_data_dir;
}
-void OSIPhone::set_locale(String p_locale) {
- locale_code = p_locale;
-}
-
String OSIPhone::get_locale() const {
- return locale_code;
-}
+ NSString *preferedLanguage = [NSLocale preferredLanguages].firstObject;
+
+ if (preferedLanguage) {
+ return String::utf8([preferedLanguage UTF8String]).replace("-", "_");
+ }
-void OSIPhone::set_unique_id(String p_id) {
- unique_id = p_id;
+ NSString *localeIdentifier = [[NSLocale currentLocale] localeIdentifier];
+ return String::utf8([localeIdentifier UTF8String]).replace("-", "_");
}
String OSIPhone::get_unique_id() const {
- return unique_id;
+ NSString *uuid = [UIDevice currentDevice].identifierForVendor.UUIDString;
+ return String::utf8([uuid UTF8String]);
}
void OSIPhone::vibrate_handheld(int p_duration_ms) {
diff --git a/platform/iphone/platform_config.h b/platform/iphone/platform_config.h
index bc190ba956..ec39ad0ba4 100644
--- a/platform/iphone/platform_config.h
+++ b/platform/iphone/platform_config.h
@@ -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..5323f94989
--- /dev/null
+++ b/platform/iphone/plugin/godot_plugin_config.h
@@ -0,0 +1,265 @@
+/*************************************************************************/
+/* godot_plugin_config.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. */
+/*************************************************************************/
+
+#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"
+
+static const char *PLUGIN_CONFIG_EXT = ".gdip";
+
+static const char *CONFIG_SECTION = "config";
+static const char *CONFIG_NAME_KEY = "name";
+static const char *CONFIG_BINARY_KEY = "binary";
+static const char *CONFIG_INITIALIZE_KEY = "initialization";
+static const char *CONFIG_DEINITIALIZE_KEY = "deinitialization";
+
+static const char *DEPENDENCIES_SECTION = "dependencies";
+static const char *DEPENDENCIES_LINKED_KEY = "linked";
+static const char *DEPENDENCIES_EMBEDDED_KEY = "embedded";
+static const char *DEPENDENCIES_SYSTEM_KEY = "system";
+static const char *DEPENDENCIES_CAPABILITIES_KEY = "capabilities";
+static const char *DEPENDENCIES_FILES_KEY = "files";
+
+static const char *PLIST_SECTION = "plist";
+
+/*
+ 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 PluginConfig {
+ // 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;
+
+ // 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.empty()) {
+ return absolute_path;
+ }
+
+ if (dependency_path.is_abs_path()) {
+ return dependency_path;
+ }
+
+ String res_path = ProjectSettings::get_singleton()->globalize_path("res://");
+ absolute_path = plugin_config_dir.plus_file(dependency_path);
+
+ return absolute_path.replace(res_path, "res://");
+}
+
+static inline String resolve_system_dependency_path(String dependency_path) {
+ String absolute_path;
+
+ if (dependency_path.empty()) {
+ return absolute_path;
+ }
+
+ if (dependency_path.is_abs_path()) {
+ return dependency_path;
+ }
+
+ String system_path = "/System/Library/Frameworks";
+
+ return system_path.plus_file(dependency_path);
+}
+
+static inline Vector<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.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.empty()) {
+ continue;
+ }
+
+ paths.push_back(path);
+ }
+
+ return paths;
+}
+
+static inline bool validate_plugin(PluginConfig &plugin_config) {
+ bool valid_name = !plugin_config.name.empty();
+ bool valid_binary_name = !plugin_config.binary.empty();
+ bool valid_initialize = !plugin_config.initialization_method.empty();
+ bool valid_deinitialize = !plugin_config.deinitialization_method.empty();
+
+ bool fields_value = valid_name && valid_binary_name && valid_initialize && valid_deinitialize;
+
+ if (fields_value && FileAccess::exists(plugin_config.binary)) {
+ plugin_config.valid_config = true;
+ plugin_config.supports_targets = false;
+ } else if (fields_value) {
+ 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");
+
+ if (FileAccess::exists(release_file_name) && FileAccess::exists(debug_file_name)) {
+ plugin_config.valid_config = true;
+ plugin_config.supports_targets = true;
+ }
+ }
+
+ return plugin_config.valid_config;
+}
+
+static inline uint64_t get_plugin_modification_time(const PluginConfig &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 PluginConfig load_plugin_config(Ref<ConfigFile> config_file, const String &path) {
+ PluginConfig 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(CONFIG_SECTION, CONFIG_NAME_KEY, String());
+ plugin_config.initialization_method = config_file->get_value(CONFIG_SECTION, CONFIG_INITIALIZE_KEY, String());
+ plugin_config.deinitialization_method = config_file->get_value(CONFIG_SECTION, CONFIG_DEINITIALIZE_KEY, String());
+
+ String binary_path = config_file->get_value(CONFIG_SECTION, CONFIG_BINARY_KEY, String());
+ plugin_config.binary = resolve_local_dependency_path(config_base_dir, binary_path);
+
+ if (config_file->has_section(DEPENDENCIES_SECTION)) {
+ Vector<String> linked_dependencies = config_file->get_value(DEPENDENCIES_SECTION, DEPENDENCIES_LINKED_KEY, Vector<String>());
+ Vector<String> embedded_dependencies = config_file->get_value(DEPENDENCIES_SECTION, DEPENDENCIES_EMBEDDED_KEY, Vector<String>());
+ Vector<String> system_dependencies = config_file->get_value(DEPENDENCIES_SECTION, DEPENDENCIES_SYSTEM_KEY, Vector<String>());
+ Vector<String> files = config_file->get_value(DEPENDENCIES_SECTION, 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(DEPENDENCIES_SECTION, DEPENDENCIES_CAPABILITIES_KEY, Vector<String>());
+ }
+
+ if (config_file->has_section(PLIST_SECTION)) {
+ List<String> keys;
+ config_file->get_section_keys(PLIST_SECTION, &keys);
+
+ for (int i = 0; i < keys.size(); i++) {
+ String value = config_file->get_value(PLIST_SECTION, keys[i], String());
+
+ if (value.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 dffdc01d4a..ff76359842 100644
--- a/platform/iphone/view_controller.h
+++ b/platform/iphone/view_controller.h
@@ -28,21 +28,20 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#import <GameKit/GameKit.h>
#import <UIKit/UIKit.h>
@class GodotView;
+@class GodotNativeVideoView;
+@class GodotKeyboardInputView;
-@interface ViewController : UIViewController <GKGameCenterControllerDelegate>
+@interface ViewController : UIViewController
-- (GodotView *)godotView;
+@property(nonatomic, readonly, strong) GodotView *godotView;
+@property(nonatomic, readonly, strong) GodotNativeVideoView *videoView;
+@property(nonatomic, readonly, strong) GodotKeyboardInputView *keyboardView;
// MARK: Native Video Player
- (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/view_controller.mm b/platform/iphone/view_controller.mm
index 31597f7856..d3969e6b02 100644
--- a/platform/iphone/view_controller.mm
+++ b/platform/iphone/view_controller.mm
@@ -29,27 +29,22 @@
/*************************************************************************/
#import "view_controller.h"
-#include "core/project_settings.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"
+#import <AVFoundation/AVFoundation.h>
#import <GameController/GameController.h>
@interface ViewController ()
@property(strong, nonatomic) GodotViewRenderer *renderer;
-
-// TODO: separate view to handle video
-// AVPlayer-related properties
-@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;
-@property(assign, nonatomic) BOOL videoHasFoundError;
+@property(strong, nonatomic) GodotNativeVideoView *videoView;
+@property(strong, nonatomic) GodotKeyboardInputView *keyboardView;
@end
@@ -67,9 +62,6 @@
self.view = view;
view.renderer = self.renderer;
-
- [renderer release];
- [view release];
}
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
@@ -93,9 +85,7 @@
}
- (void)godot_commonInit {
- self.isVideoCurrentlyPlaying = NO;
- self.videoCurrentTime = kCMTimeZero;
- self.videoHasFoundError = false;
+ // Initialize view controller values.
}
- (void)didReceiveMemoryWarning {
@@ -107,7 +97,6 @@
[super viewDidLoad];
[self observeKeyboard];
- [self observeAudio];
if (@available(iOS 11.0, *)) {
[self setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
@@ -115,6 +104,10 @@
}
- (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
@@ -128,33 +121,16 @@
object:nil];
}
-- (void)observeAudio {
- 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];
- }
+- (void)dealloc {
+ [self.videoView stopVideo];
- if (object == self.avPlayer && [keyPath isEqualToString:@"rate"]) {
- [self handleVideoPlayRate];
- }
-}
+ self.videoView = nil;
-- (void)dealloc {
- [self stopVideo];
+ self.keyboardView = nil;
self.renderer = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self];
-
- [super dealloc];
}
// MARK: Orientation
@@ -233,176 +209,19 @@
}
}
-// MARK: 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];
- self.videoHasFoundError = true;
- }
-
- 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.view.bounds];
- [self.view.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)stopVideo {
- [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;
+ // 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];
+ return [self.videoView playVideoAtPath:filePath volume:videoVolume audio:audioTrack subtitle:subtitleTrack];
}
-
- self.avAsset = nil;
-
- self.isVideoCurrentlyPlaying = NO;
-}
-
-// MARK: Delegates
-
-#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];
}
-#endif
@end
diff --git a/platform/iphone/vulkan_context_iphone.mm b/platform/iphone/vulkan_context_iphone.mm
index cb4dbe7f85..d62e826957 100644
--- a/platform/iphone/vulkan_context_iphone.mm
+++ b/platform/iphone/vulkan_context_iphone.mm
@@ -35,14 +35,12 @@ const char *VulkanContextIPhone::_get_platform_surface_extension() const {
return VK_MVK_IOS_SURFACE_EXTENSION_NAME;
}
-Error VulkanContextIPhone::window_create(DisplayServer::WindowID p_window_id,
- CALayer *p_metal_layer, 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_IOS_SURFACE_CREATE_INFO_MVK;
createInfo.pNext = NULL;
createInfo.flags = 0;
- createInfo.pView = p_metal_layer;
+ createInfo.pView = (__bridge const void *)p_metal_layer;
VkSurfaceKHR surface;
VkResult err =