Rename OSX to macOS and iPhoneOS to iOS.

This commit is contained in:
bruvzg 2022-07-20 09:28:22 +03:00
parent 292c952e3b
commit 8823eae328
No known key found for this signature in database
GPG key ID: 7960FCF39844EC38
245 changed files with 1151 additions and 1149 deletions

43
platform/ios/SCsub Normal file
View file

@ -0,0 +1,43 @@
#!/usr/bin/env python
Import("env")
ios_lib = [
"godot_ios.mm",
"os_ios.mm",
"main.m",
"app_delegate.mm",
"view_controller.mm",
"ios.mm",
"vulkan_context_ios.mm",
"display_server_ios.mm",
"joypad_ios.mm",
"godot_view.mm",
"tts_ios.mm",
"display_layer.mm",
"godot_app_delegate.m",
"godot_view_renderer.mm",
"godot_view_gesture_recognizer.mm",
"device_metrics.m",
"keyboard_input_view.mm",
]
env_ios = env.Clone()
ios_lib = env_ios.add_library("ios", ios_lib)
# (iOS) Enable module support
env_ios.Append(CCFLAGS=["-fmodules", "-fcxx-modules"])
def combine_libs(target=None, source=None, env=None):
lib_path = target[0].srcnode().abspath
if "osxcross" in env:
libtool = "$IOS_TOOLCHAIN_PATH/usr/bin/${ios_triple}libtool"
else:
libtool = "$IOS_TOOLCHAIN_PATH/usr/bin/libtool"
env.Execute(
libtool + ' -static -o "' + lib_path + '" ' + " ".join([('"' + lib.srcnode().abspath + '"') for lib in source])
)
combine_command = env_ios.Command("#bin/libgodot" + env_ios["LIBSUFFIX"], [ios_lib] + env_ios["LIBS"], combine_libs)

48
platform/ios/api/api.cpp Normal file
View file

@ -0,0 +1,48 @@
/*************************************************************************/
/* api.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "api.h"
#if defined(IOS_ENABLED)
void register_ios_api() {
godot_ios_plugins_initialize();
}
void unregister_ios_api() {
godot_ios_plugins_deinitialize();
}
#else
void register_ios_api() {}
void unregister_ios_api() {}
#endif

42
platform/ios/api/api.h Normal file
View file

@ -0,0 +1,42 @@
/*************************************************************************/
/* api.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#ifndef IOS_API_H
#define IOS_API_H
#if defined(IOS_ENABLED)
extern void godot_ios_plugins_initialize();
extern void godot_ios_plugins_deinitialize();
#endif
void register_ios_api();
void unregister_ios_api();
#endif // IOS_API_H

View file

@ -0,0 +1,47 @@
/*************************************************************************/
/* app_delegate.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#import <UIKit/UIKit.h>
@class ViewController;
// FIXME: Add support for both OpenGL and Vulkan when OpenGL is implemented again,
// so it can't be done with compilation time branching.
//#if defined(GLES3_ENABLED)
//@interface AppDelegate : NSObject <UIApplicationDelegate, GLViewDelegate> {
//#endif
//#if defined(VULKAN_ENABLED)
@interface AppDelegate : NSObject <UIApplicationDelegate>
//#endif
@property(strong, nonatomic) UIWindow *window;
@property(strong, class, readonly, nonatomic) ViewController *viewController;
@end

View file

@ -0,0 +1,149 @@
/*************************************************************************/
/* app_delegate.mm */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#import "app_delegate.h"
#include "core/config/project_settings.h"
#include "drivers/coreaudio/audio_driver_coreaudio.h"
#import "godot_view.h"
#include "main/main.h"
#include "os_ios.h"
#import "view_controller.h"
#import <AVFoundation/AVFoundation.h>
#import <AudioToolbox/AudioServices.h>
#define kRenderingFrequency 60
extern int gargc;
extern char **gargv;
extern int ios_main(int, char **, String, String);
extern void ios_finish();
@implementation AppDelegate
static ViewController *mainViewController = nil;
+ (ViewController *)viewController {
return mainViewController;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// TODO: might be required to make an early return, so app wouldn't crash because of timeout.
// TODO: logo screen is not displayed while shaders are compiling
// DummyViewController(Splash/LoadingViewController) -> setup -> GodotViewController
CGRect windowBounds = [[UIScreen mainScreen] bounds];
// Create a full-screen window
self.window = [[UIWindow alloc] initWithFrame:windowBounds];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *cacheDirectory = [paths objectAtIndex:0];
int err = ios_main(gargc, gargv, String::utf8([documentsDirectory UTF8String]), String::utf8([cacheDirectory UTF8String]));
if (err != 0) {
// bail, things did not go very well for us, should probably output a message on screen with our error code...
exit(0);
return NO;
}
ViewController *viewController = [[ViewController alloc] init];
viewController.godotView.useCADisplayLink = bool(GLOBAL_DEF("display.iOS/use_cadisplaylink", true)) ? YES : NO;
viewController.godotView.renderingInterval = 1.0 / kRenderingFrequency;
self.window.rootViewController = viewController;
// Show the window
[self.window makeKeyAndVisible];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(onAudioInterruption:)
name:AVAudioSessionInterruptionNotification
object:[AVAudioSession sharedInstance]];
mainViewController = viewController;
// prevent to stop music in another background app
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:nil];
return YES;
}
- (void)onAudioInterruption:(NSNotification *)notification {
if ([notification.name isEqualToString:AVAudioSessionInterruptionNotification]) {
if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeBegan]]) {
NSLog(@"Audio interruption began");
OS_IOS::get_singleton()->on_focus_out();
} else if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeEnded]]) {
NSLog(@"Audio interruption ended");
OS_IOS::get_singleton()->on_focus_in();
}
}
}
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
if (OS::get_singleton()->get_main_loop()) {
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_MEMORY_WARNING);
}
}
- (void)applicationWillTerminate:(UIApplication *)application {
ios_finish();
}
// When application goes to background (e.g. user switches to another app or presses Home),
// then applicationWillResignActive -> applicationDidEnterBackground are called.
// When user opens the inactive app again,
// applicationWillEnterForeground -> applicationDidBecomeActive are called.
// There are cases when applicationWillResignActive -> applicationDidBecomeActive
// sequence is called without the app going to background. For example, that happens
// if you open the app list without switching to another app or open/close the
// notification panel by swiping from the upper part of the screen.
- (void)applicationWillResignActive:(UIApplication *)application {
OS_IOS::get_singleton()->on_focus_out();
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
OS_IOS::get_singleton()->on_focus_in();
}
- (void)dealloc {
self.window = nil;
}
@end

152
platform/ios/detect.py Normal file
View file

@ -0,0 +1,152 @@
import os
import sys
from methods import detect_darwin_sdk_path
def is_active():
return True
def get_name():
return "iOS"
def can_build():
if sys.platform == "darwin" or ("OSXCROSS_IOS" in os.environ):
return True
return False
def get_opts():
from SCons.Variables import BoolVariable
return [
(
"IOS_TOOLCHAIN_PATH",
"Path to iOS toolchain",
"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain",
),
("IOS_SDK_PATH", "Path to the iOS SDK", ""),
BoolVariable("ios_simulator", "Build for iOS Simulator", False),
BoolVariable("ios_exceptions", "Enable exceptions", False),
("ios_triple", "Triple for ios toolchain", ""),
]
def get_flags():
return [
("tools", False),
("use_volk", False),
]
def configure(env):
## Build type
if env["target"].startswith("release"):
env.Append(CPPDEFINES=["NDEBUG", ("NS_BLOCK_ASSERTIONS", 1)])
if env["optimize"] == "speed": # optimize for speed (default)
# `-O2` is more friendly to debuggers than `-O3`, leading to better crash backtraces
# when using `target=release_debug`.
opt = "-O3" if env["target"] == "release" else "-O2"
env.Append(CCFLAGS=[opt, "-ftree-vectorize", "-fomit-frame-pointer"])
env.Append(LINKFLAGS=[opt])
elif env["optimize"] == "size": # optimize for size
env.Append(CCFLAGS=["-Os", "-ftree-vectorize"])
env.Append(LINKFLAGS=["-Os"])
elif env["target"] == "debug":
env.Append(CCFLAGS=["-gdwarf-2", "-O0"])
env.Append(CPPDEFINES=["_DEBUG", ("DEBUG", 1)])
if env["use_lto"]:
env.Append(CCFLAGS=["-flto"])
env.Append(LINKFLAGS=["-flto"])
## Architecture
env["bits"] = "64"
if env["arch"] != "x86_64":
env["arch"] = "arm64"
## Compiler configuration
# Save this in environment for use by other modules
if "OSXCROSS_IOS" in os.environ:
env["osxcross"] = True
env["ENV"]["PATH"] = env["IOS_TOOLCHAIN_PATH"] + "/Developer/usr/bin/:" + env["ENV"]["PATH"]
compiler_path = "$IOS_TOOLCHAIN_PATH/usr/bin/${ios_triple}"
s_compiler_path = "$IOS_TOOLCHAIN_PATH/Developer/usr/bin/"
ccache_path = os.environ.get("CCACHE")
if ccache_path is None:
env["CC"] = compiler_path + "clang"
env["CXX"] = compiler_path + "clang++"
env["S_compiler"] = s_compiler_path + "gcc"
else:
# there aren't any ccache wrappers available for iOS,
# to enable caching we need to prepend the path to the ccache binary
env["CC"] = ccache_path + " " + compiler_path + "clang"
env["CXX"] = ccache_path + " " + compiler_path + "clang++"
env["S_compiler"] = ccache_path + " " + s_compiler_path + "gcc"
env["AR"] = compiler_path + "ar"
env["RANLIB"] = compiler_path + "ranlib"
## Compile flags
if env["ios_simulator"]:
detect_darwin_sdk_path("iossimulator", env)
env.Append(ASFLAGS=["-mios-simulator-version-min=13.0"])
env.Append(CCFLAGS=["-mios-simulator-version-min=13.0"])
env.extra_suffix = ".simulator" + env.extra_suffix
else:
detect_darwin_sdk_path("ios", env)
env.Append(ASFLAGS=["-miphoneos-version-min=11.0"])
env.Append(CCFLAGS=["-miphoneos-version-min=11.0"])
if env["arch"] == "x86_64":
env["ENV"]["MACOSX_DEPLOYMENT_TARGET"] = "10.9"
env.Append(
CCFLAGS=(
"-fobjc-arc -arch x86_64"
" -fobjc-abi-version=2 -fobjc-legacy-dispatch -fmessage-length=0 -fpascal-strings -fblocks"
" -fasm-blocks -isysroot $IOS_SDK_PATH"
).split()
)
env.Append(ASFLAGS=["-arch", "x86_64"])
elif env["arch"] == "arm64":
env.Append(
CCFLAGS=(
"-fobjc-arc -arch arm64 -fmessage-length=0 -fno-strict-aliasing"
" -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits"
" -fpascal-strings -fblocks -fvisibility=hidden -MMD -MT dependencies"
" -isysroot $IOS_SDK_PATH".split()
)
)
env.Append(ASFLAGS=["-arch", "arm64"])
env.Append(CPPDEFINES=["NEED_LONG_INT"])
# Disable exceptions on non-tools (template) builds
if not env["tools"]:
if env["ios_exceptions"]:
env.Append(CCFLAGS=["-fexceptions"])
else:
env.Append(CCFLAGS=["-fno-exceptions"])
# Temp fix for ABS/MAX/MIN macros in iOS SDK blocking compilation
env.Append(CCFLAGS=["-Wno-ambiguous-macro"])
env.Prepend(
CPPPATH=[
"$IOS_SDK_PATH/usr/include",
"$IOS_SDK_PATH/System/Library/Frameworks/AudioUnit.framework/Headers",
]
)
env.Prepend(CPPPATH=["#platform/ios"])
env.Append(CPPDEFINES=["IOS_ENABLED", "UNIX_ENABLED", "COREAUDIO_ENABLED"])
if env["vulkan"]:
env.Append(CPPDEFINES=["VULKAN_ENABLED"])

View file

@ -0,0 +1,37 @@
/*************************************************************************/
/* device_metrics.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#import <Foundation/Foundation.h>
@interface GodotDeviceMetrics : NSObject
@property(nonatomic, class, readonly, strong) NSDictionary<NSArray *, NSNumber *> *dpiList;
@end

View file

@ -0,0 +1,152 @@
/*************************************************************************/
/* device_metrics.m */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#import "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

View file

@ -0,0 +1,58 @@
/*************************************************************************/
/* display_layer.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#import <OpenGLES/EAGLDrawable.h>
#import <QuartzCore/QuartzCore.h>
@protocol DisplayLayer <NSObject>
- (void)renderDisplayLayer;
- (void)initializeDisplayLayer;
- (void)layoutDisplayLayer;
@end
// An ugly workaround for iOS simulator
#if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR
#if defined(__IPHONE_13_0)
API_AVAILABLE(ios(13.0))
@interface GodotMetalLayer : CAMetalLayer <DisplayLayer>
#else
@interface GodotMetalLayer : CALayer <DisplayLayer>
#endif
#else
@interface GodotMetalLayer : CAMetalLayer <DisplayLayer>
#endif
@end
API_DEPRECATED("OpenGLES is deprecated", ios(2.0, 12.0))
@interface GodotOpenGLLayer : CAEAGLLayer <DisplayLayer>
@end

View file

@ -0,0 +1,173 @@
/*************************************************************************/
/* display_layer.mm */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#import "display_layer.h"
#include "core/config/project_settings.h"
#include "core/os/keyboard.h"
#include "display_server_ios.h"
#include "main/main.h"
#include "os_ios.h"
#include "servers/audio_server.h"
#import <AudioToolbox/AudioServices.h>
#import <GameController/GameController.h>
#import <OpenGLES/EAGL.h>
#import <OpenGLES/ES1/gl.h>
#import <OpenGLES/ES1/glext.h>
#import <QuartzCore/QuartzCore.h>
#import <UIKit/UIKit.h>
@implementation GodotMetalLayer
- (void)initializeDisplayLayer {
#if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR
if (@available(iOS 13, *)) {
// Simulator supports Metal since iOS 13
} else {
NSLog(@"iOS Simulator prior to iOS 13 does not support Metal rendering.");
}
#endif
}
- (void)layoutDisplayLayer {
}
- (void)renderDisplayLayer {
}
@end
@implementation GodotOpenGLLayer {
// The pixel dimensions of the backbuffer
GLint backingWidth;
GLint backingHeight;
EAGLContext *context;
GLuint viewRenderbuffer, viewFramebuffer;
GLuint depthRenderbuffer;
}
- (void)initializeDisplayLayer {
// Get our backing layer
// Configure it so that it is opaque, does not retain the contents of the backbuffer when displayed, and uses RGBA8888 color.
self.opaque = YES;
self.drawableProperties = [NSDictionary
dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:FALSE],
kEAGLDrawablePropertyRetainedBacking,
kEAGLColorFormatRGBA8,
kEAGLDrawablePropertyColorFormat,
nil];
// FIXME: Add Vulkan support via MoltenVK. Add fallback code back?
// Create GL ES 2 context
if (GLOBAL_GET("rendering/driver/driver_name") == "opengl3") {
context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
NSLog(@"Setting up an OpenGL ES 2.0 context.");
if (!context) {
NSLog(@"Failed to create OpenGL ES 2.0 context!");
return;
}
}
if (![EAGLContext setCurrentContext:context]) {
NSLog(@"Failed to set EAGLContext!");
return;
}
if (![self createFramebuffer]) {
NSLog(@"Failed to create frame buffer!");
return;
}
}
- (void)layoutDisplayLayer {
[EAGLContext setCurrentContext:context];
[self destroyFramebuffer];
[self createFramebuffer];
}
- (void)renderDisplayLayer {
[EAGLContext setCurrentContext:context];
}
- (void)dealloc {
if ([EAGLContext currentContext] == context) {
[EAGLContext setCurrentContext:nil];
}
if (context) {
context = nil;
}
}
- (BOOL)createFramebuffer {
glGenFramebuffersOES(1, &viewFramebuffer);
glGenRenderbuffersOES(1, &viewRenderbuffer);
glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
// This call associates the storage for the current render buffer with the EAGLDrawable (our CAself)
// allowing us to draw into a buffer that will later be rendered to screen wherever the layer is (which corresponds with our view).
[context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(id<EAGLDrawable>)self];
glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer);
glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
// For this sample, we also need a depth buffer, so we'll create and attach one via another renderbuffer.
glGenRenderbuffersOES(1, &depthRenderbuffer);
glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRenderbuffer);
glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, backingWidth, backingHeight);
glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer);
if (glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) {
NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));
return NO;
}
return YES;
}
// Clean up any buffers we have allocated.
- (void)destroyFramebuffer {
glDeleteFramebuffersOES(1, &viewFramebuffer);
viewFramebuffer = 0;
glDeleteRenderbuffersOES(1, &viewRenderbuffer);
viewRenderbuffer = 0;
if (depthRenderbuffer) {
glDeleteRenderbuffersOES(1, &depthRenderbuffer);
depthRenderbuffer = 0;
}
}
@end

View file

@ -0,0 +1,217 @@
/*************************************************************************/
/* display_server_ios.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#ifndef display_server_ios_h
#define display_server_ios_h
#include "core/input/input.h"
#include "servers/display_server.h"
#if defined(VULKAN_ENABLED)
#include "drivers/vulkan/rendering_device_vulkan.h"
#include "servers/rendering/renderer_rd/renderer_compositor_rd.h"
#include "vulkan_context_ios.h"
#import <QuartzCore/CAMetalLayer.h>
#ifdef USE_VOLK
#include <volk.h>
#else
#include <vulkan/vulkan.h>
#endif
#endif
class DisplayServerIOS : public DisplayServer {
GDCLASS(DisplayServerIOS, DisplayServer)
_THREAD_SAFE_CLASS_
#if defined(VULKAN_ENABLED)
VulkanContextIOS *context_vulkan = nullptr;
RenderingDeviceVulkan *rendering_device_vulkan = nullptr;
#endif
id tts = nullptr;
DisplayServer::ScreenOrientation screen_orientation;
ObjectID window_attached_instance_id;
Callable window_event_callback;
Callable window_resize_callback;
Callable input_event_callback;
Callable input_text_callback;
int virtual_keyboard_height = 0;
void perform_event(const Ref<InputEvent> &p_event);
DisplayServerIOS(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error);
~DisplayServerIOS();
public:
String rendering_driver;
static DisplayServerIOS *get_singleton();
static void register_ios_driver();
static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error);
static Vector<String> get_rendering_drivers_func();
// MARK: - Events
virtual void process_events() override;
virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_set_input_text_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
static void _dispatch_input_events(const Ref<InputEvent> &p_event);
void send_input_event(const Ref<InputEvent> &p_event) const;
void send_input_text(const String &p_text) const;
void send_window_event(DisplayServer::WindowEvent p_event) const;
void _window_callback(const Callable &p_callable, const Variant &p_arg) const;
// MARK: - Input
// MARK: Touches
void touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_double_click);
void touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y);
void touches_cancelled(int p_idx);
// MARK: Keyboard
void key(Key p_key, bool p_pressed);
// MARK: Motion
void update_gravity(float p_x, float p_y, float p_z);
void update_accelerometer(float p_x, float p_y, float p_z);
void update_magnetometer(float p_x, float p_y, float p_z);
void update_gyroscope(float p_x, float p_y, float p_z);
// MARK: -
virtual bool has_feature(Feature p_feature) const override;
virtual String get_name() const override;
virtual bool tts_is_speaking() const override;
virtual bool tts_is_paused() const override;
virtual Array tts_get_voices() const override;
virtual void tts_speak(const String &p_text, const String &p_voice, int p_volume = 50, float p_pitch = 1.f, float p_rate = 1.f, int p_utterance_id = 0, bool p_interrupt = false) override;
virtual void tts_pause() override;
virtual void tts_resume() override;
virtual void tts_stop() override;
virtual Rect2i get_display_safe_area() const override;
virtual int get_screen_count() const override;
virtual Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual float screen_get_scale(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual Vector<DisplayServer::WindowID> get_window_list() const override;
virtual WindowID
get_window_at_screen_position(const Point2i &p_position) const override;
virtual int64_t window_get_native_handle(HandleType p_handle_type, WindowID p_window = MAIN_WINDOW_ID) const override;
virtual void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID) override;
virtual ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const override;
virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) override;
virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const override;
virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override;
virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const override;
virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_set_transient(WindowID p_window, WindowID p_parent) override;
virtual void window_set_max_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
virtual Size2i window_get_max_size(WindowID p_window = MAIN_WINDOW_ID) const override;
virtual void window_set_min_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
virtual Size2i window_get_min_size(WindowID p_window = MAIN_WINDOW_ID) const override;
virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const override;
virtual Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const override;
virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID) override;
virtual WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const override;
virtual bool window_is_maximize_allowed(WindowID p_window = MAIN_WINDOW_ID) const override;
virtual void window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window = MAIN_WINDOW_ID) override;
virtual bool window_get_flag(WindowFlags p_flag, WindowID p_window = MAIN_WINDOW_ID) const override;
virtual void window_request_attention(WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID) override;
virtual float screen_get_max_scale() const override;
virtual void screen_set_orientation(DisplayServer::ScreenOrientation p_orientation, int p_screen) override;
virtual DisplayServer::ScreenOrientation screen_get_orientation(int p_screen) const override;
virtual bool window_can_draw(WindowID p_window = MAIN_WINDOW_ID) const override;
virtual bool can_any_window_draw() const override;
virtual void window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window = MAIN_WINDOW_ID) override;
virtual DisplayServer::VSyncMode window_get_vsync_mode(WindowID p_vsync_mode) const override;
virtual bool screen_is_touchscreen(int p_screen) const override;
virtual void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, bool p_multiline, int p_max_length, int p_cursor_start, int p_cursor_end) override;
virtual void virtual_keyboard_hide() override;
void virtual_keyboard_set_height(int height);
virtual int virtual_keyboard_get_height() const override;
virtual void clipboard_set(const String &p_text) override;
virtual String clipboard_get() const override;
virtual void screen_set_keep_on(bool p_enable) override;
virtual bool screen_is_kept_on() const override;
void resize_window(CGSize size);
};
#endif /* DISPLAY_SERVER_IOS_H */

View file

@ -0,0 +1,655 @@
/*************************************************************************/
/* display_server_ios.mm */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "display_server_ios.h"
#import "app_delegate.h"
#include "core/config/project_settings.h"
#include "core/io/file_access_pack.h"
#import "device_metrics.h"
#import "godot_view.h"
#include "ios.h"
#import "keyboard_input_view.h"
#include "os_ios.h"
#include "tts_ios.h"
#import "view_controller.h"
#import <Foundation/Foundation.h>
#import <sys/utsname.h>
static const float kDisplayServerIOSAcceleration = 1.f;
DisplayServerIOS *DisplayServerIOS::get_singleton() {
return (DisplayServerIOS *)DisplayServer::get_singleton();
}
DisplayServerIOS::DisplayServerIOS(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
rendering_driver = p_rendering_driver;
// Init TTS
tts = [[TTS_IOS alloc] init];
#if defined(GLES3_ENABLED)
// FIXME: Add support for both OpenGL and Vulkan when OpenGL is implemented
// again,
// Note that we should be checking "opengl3" as the driver, might never enable this seeing OpenGL is deprecated on iOS
// We are hardcoding the rendering_driver to "vulkan" down below
if (rendering_driver == "opengl_es") {
bool gl_initialization_error = false;
// FIXME: Add Vulkan support via MoltenVK. Add fallback code back?
if (RasterizerGLES3::is_viable() == OK) {
RasterizerGLES3::register_config();
RasterizerGLES3::make_current();
} else {
gl_initialization_error = true;
}
if (gl_initialization_error) {
OS::get_singleton()->alert("Your device does not support any of the supported OpenGL versions.", "Unable to initialize video driver");
// return ERR_UNAVAILABLE;
}
// rendering_server = memnew(RenderingServerDefault);
// // FIXME: Reimplement threaded rendering
// if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) {
// rendering_server = memnew(RenderingServerWrapMT(rendering_server,
// false));
// }
// rendering_server->init();
// rendering_server->cursor_set_visible(false, 0);
// reset this to what it should be, it will have been set to 0 after
// rendering_server->init() is called
// RasterizerStorageGLES3system_fbo = gl_view_base_fb;
}
#endif
#if defined(VULKAN_ENABLED)
rendering_driver = "vulkan";
context_vulkan = nullptr;
rendering_device_vulkan = nullptr;
if (rendering_driver == "vulkan") {
context_vulkan = memnew(VulkanContextIOS);
if (context_vulkan->initialize() != OK) {
memdelete(context_vulkan);
context_vulkan = nullptr;
ERR_FAIL_MSG("Failed to initialize Vulkan context");
}
CALayer *layer = [AppDelegate.viewController.godotView initializeRenderingForDriver:@"vulkan"];
if (!layer) {
ERR_FAIL_MSG("Failed to create iOS rendering layer.");
}
Size2i size = Size2i(layer.bounds.size.width, layer.bounds.size.height) * screen_get_max_scale();
if (context_vulkan->window_create(MAIN_WINDOW_ID, p_vsync_mode, layer, size.width, size.height) != OK) {
memdelete(context_vulkan);
context_vulkan = nullptr;
ERR_FAIL_MSG("Failed to create Vulkan window.");
}
rendering_device_vulkan = memnew(RenderingDeviceVulkan);
rendering_device_vulkan->initialize(context_vulkan);
RendererCompositorRD::make_current();
}
#endif
bool keep_screen_on = bool(GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true));
screen_set_keep_on(keep_screen_on);
Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events);
r_error = OK;
}
DisplayServerIOS::~DisplayServerIOS() {
#if defined(VULKAN_ENABLED)
if (rendering_device_vulkan) {
rendering_device_vulkan->finalize();
memdelete(rendering_device_vulkan);
rendering_device_vulkan = nullptr;
}
if (context_vulkan) {
context_vulkan->window_destroy(MAIN_WINDOW_ID);
memdelete(context_vulkan);
context_vulkan = nullptr;
}
#endif
}
DisplayServer *DisplayServerIOS::create_func(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
return memnew(DisplayServerIOS(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_resolution, r_error));
}
Vector<String> DisplayServerIOS::get_rendering_drivers_func() {
Vector<String> drivers;
#if defined(VULKAN_ENABLED)
drivers.push_back("vulkan");
#endif
#if defined(GLES3_ENABLED)
drivers.push_back("opengl_es");
#endif
return drivers;
}
void DisplayServerIOS::register_ios_driver() {
register_create_function("iOS", create_func, get_rendering_drivers_func);
}
// MARK: Events
void DisplayServerIOS::window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window) {
window_resize_callback = p_callable;
}
void DisplayServerIOS::window_set_window_event_callback(const Callable &p_callable, WindowID p_window) {
window_event_callback = p_callable;
}
void DisplayServerIOS::window_set_input_event_callback(const Callable &p_callable, WindowID p_window) {
input_event_callback = p_callable;
}
void DisplayServerIOS::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) {
input_text_callback = p_callable;
}
void DisplayServerIOS::window_set_drop_files_callback(const Callable &p_callable, WindowID p_window) {
// Probably not supported for iOS
}
void DisplayServerIOS::process_events() {
Input::get_singleton()->flush_buffered_events();
}
void DisplayServerIOS::_dispatch_input_events(const Ref<InputEvent> &p_event) {
DisplayServerIOS::get_singleton()->send_input_event(p_event);
}
void DisplayServerIOS::send_input_event(const Ref<InputEvent> &p_event) const {
_window_callback(input_event_callback, p_event);
}
void DisplayServerIOS::send_input_text(const String &p_text) const {
_window_callback(input_text_callback, p_text);
}
void DisplayServerIOS::send_window_event(DisplayServer::WindowEvent p_event) const {
_window_callback(window_event_callback, int(p_event));
}
void DisplayServerIOS::_window_callback(const Callable &p_callable, const Variant &p_arg) const {
if (!p_callable.is_null()) {
const Variant *argp = &p_arg;
Variant ret;
Callable::CallError ce;
p_callable.call((const Variant **)&argp, 1, ret, ce);
}
}
// MARK: - Input
// MARK: Touches
void DisplayServerIOS::touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_double_click) {
if (!GLOBAL_DEF("debug/disable_touch", false)) {
Ref<InputEventScreenTouch> ev;
ev.instantiate();
ev->set_index(p_idx);
ev->set_pressed(p_pressed);
ev->set_position(Vector2(p_x, p_y));
perform_event(ev);
}
}
void DisplayServerIOS::touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y) {
if (!GLOBAL_DEF("debug/disable_touch", false)) {
Ref<InputEventScreenDrag> ev;
ev.instantiate();
ev->set_index(p_idx);
ev->set_position(Vector2(p_x, p_y));
ev->set_relative(Vector2(p_x - p_prev_x, p_y - p_prev_y));
perform_event(ev);
}
}
void DisplayServerIOS::perform_event(const Ref<InputEvent> &p_event) {
Input::get_singleton()->parse_input_event(p_event);
}
void DisplayServerIOS::touches_cancelled(int p_idx) {
touch_press(p_idx, -1, -1, false, false);
}
// MARK: Keyboard
void DisplayServerIOS::key(Key p_key, bool p_pressed) {
Ref<InputEventKey> ev;
ev.instantiate();
ev->set_echo(false);
ev->set_pressed(p_pressed);
ev->set_keycode(p_key);
ev->set_physical_keycode(p_key);
ev->set_unicode((char32_t)p_key);
perform_event(ev);
}
// MARK: Motion
void DisplayServerIOS::update_gravity(float p_x, float p_y, float p_z) {
Input::get_singleton()->set_gravity(Vector3(p_x, p_y, p_z));
}
void DisplayServerIOS::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 / kDisplayServerIOSAcceleration,
p_y / kDisplayServerIOSAcceleration,
p_z / kDisplayServerIOSAcceleration);
Input::get_singleton()->set_accelerometer(v_accelerometer);
}
void DisplayServerIOS::update_magnetometer(float p_x, float p_y, float p_z) {
Input::get_singleton()->set_magnetometer(Vector3(p_x, p_y, p_z));
}
void DisplayServerIOS::update_gyroscope(float p_x, float p_y, float p_z) {
Input::get_singleton()->set_gyroscope(Vector3(p_x, p_y, p_z));
}
// MARK: -
bool DisplayServerIOS::has_feature(Feature p_feature) const {
switch (p_feature) {
// case FEATURE_CURSOR_SHAPE:
// case FEATURE_CUSTOM_CURSOR_SHAPE:
// case FEATURE_GLOBAL_MENU:
// case FEATURE_HIDPI:
// case FEATURE_ICON:
// case FEATURE_IME:
// case FEATURE_MOUSE:
// case FEATURE_MOUSE_WARP:
// case FEATURE_NATIVE_DIALOG:
// case FEATURE_NATIVE_ICON:
// case FEATURE_WINDOW_TRANSPARENCY:
case FEATURE_CLIPBOARD:
case FEATURE_KEEP_SCREEN_ON:
case FEATURE_ORIENTATION:
case FEATURE_TOUCHSCREEN:
case FEATURE_VIRTUAL_KEYBOARD:
case FEATURE_TEXT_TO_SPEECH:
return true;
default:
return false;
}
}
String DisplayServerIOS::get_name() const {
return "iOS";
}
bool DisplayServerIOS::tts_is_speaking() const {
ERR_FAIL_COND_V(!tts, false);
return [tts isSpeaking];
}
bool DisplayServerIOS::tts_is_paused() const {
ERR_FAIL_COND_V(!tts, false);
return [tts isPaused];
}
Array DisplayServerIOS::tts_get_voices() const {
ERR_FAIL_COND_V(!tts, Array());
return [tts getVoices];
}
void DisplayServerIOS::tts_speak(const String &p_text, const String &p_voice, int p_volume, float p_pitch, float p_rate, int p_utterance_id, bool p_interrupt) {
ERR_FAIL_COND(!tts);
[tts speak:p_text voice:p_voice volume:p_volume pitch:p_pitch rate:p_rate utterance_id:p_utterance_id interrupt:p_interrupt];
}
void DisplayServerIOS::tts_pause() {
ERR_FAIL_COND(!tts);
[tts pauseSpeaking];
}
void DisplayServerIOS::tts_resume() {
ERR_FAIL_COND(!tts);
[tts resumeSpeaking];
}
void DisplayServerIOS::tts_stop() {
ERR_FAIL_COND(!tts);
[tts stopSpeaking];
}
Rect2i DisplayServerIOS::get_display_safe_area() const {
if (@available(iOS 11, *)) {
UIEdgeInsets insets = UIEdgeInsetsZero;
UIView *view = AppDelegate.viewController.godotView;
if ([view respondsToSelector:@selector(safeAreaInsets)]) {
insets = [view safeAreaInsets];
}
float scale = screen_get_scale();
Size2i insets_position = Size2i(insets.left, insets.top) * scale;
Size2i insets_size = Size2i(insets.left + insets.right, insets.top + insets.bottom) * scale;
return Rect2i(screen_get_position() + insets_position, screen_get_size() - insets_size);
} else {
return Rect2i(screen_get_position(), screen_get_size());
}
}
int DisplayServerIOS::get_screen_count() const {
return 1;
}
Point2i DisplayServerIOS::screen_get_position(int p_screen) const {
return Size2i();
}
Size2i DisplayServerIOS::screen_get_size(int p_screen) const {
CALayer *layer = AppDelegate.viewController.godotView.renderingLayer;
if (!layer) {
return Size2i();
}
return Size2i(layer.bounds.size.width, layer.bounds.size.height) * screen_get_scale(p_screen);
}
Rect2i DisplayServerIOS::screen_get_usable_rect(int p_screen) const {
return Rect2i(screen_get_position(p_screen), screen_get_size(p_screen));
}
int DisplayServerIOS::screen_get_dpi(int p_screen) const {
struct utsname systemInfo;
uname(&systemInfo);
NSString *string = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding];
NSDictionary *iOSModelToDPI = [GodotDeviceMetrics dpiList];
for (NSArray *keyArray in iOSModelToDPI) {
if ([keyArray containsObject:string]) {
NSNumber *value = iOSModelToDPI[keyArray];
return [value intValue];
}
}
// If device wasn't found in dictionary
// make a best guess from device metrics.
CGFloat scale = [UIScreen mainScreen].scale;
UIUserInterfaceIdiom idiom = [UIDevice currentDevice].userInterfaceIdiom;
switch (idiom) {
case UIUserInterfaceIdiomPad:
return scale == 2 ? 264 : 132;
case UIUserInterfaceIdiomPhone: {
if (scale == 3) {
CGFloat nativeScale = [UIScreen mainScreen].nativeScale;
return nativeScale == 3 ? 458 : 401;
}
return 326;
}
default:
return 72;
}
}
float DisplayServerIOS::screen_get_refresh_rate(int p_screen) const {
return [UIScreen mainScreen].maximumFramesPerSecond;
}
float DisplayServerIOS::screen_get_scale(int p_screen) const {
return [UIScreen mainScreen].nativeScale;
}
Vector<DisplayServer::WindowID> DisplayServerIOS::get_window_list() const {
Vector<DisplayServer::WindowID> list;
list.push_back(MAIN_WINDOW_ID);
return list;
}
DisplayServer::WindowID DisplayServerIOS::get_window_at_screen_position(const Point2i &p_position) const {
return MAIN_WINDOW_ID;
}
int64_t DisplayServerIOS::window_get_native_handle(HandleType p_handle_type, WindowID p_window) const {
ERR_FAIL_COND_V(p_window != MAIN_WINDOW_ID, 0);
switch (p_handle_type) {
case DISPLAY_HANDLE: {
return 0; // Not supported.
}
case WINDOW_HANDLE: {
return (int64_t)AppDelegate.viewController;
}
case WINDOW_VIEW: {
return (int64_t)AppDelegate.viewController.godotView;
}
default: {
return 0;
}
}
}
void DisplayServerIOS::window_attach_instance_id(ObjectID p_instance, WindowID p_window) {
window_attached_instance_id = p_instance;
}
ObjectID DisplayServerIOS::window_get_attached_instance_id(WindowID p_window) const {
return window_attached_instance_id;
}
void DisplayServerIOS::window_set_title(const String &p_title, WindowID p_window) {
// Probably not supported for iOS
}
int DisplayServerIOS::window_get_current_screen(WindowID p_window) const {
return SCREEN_OF_MAIN_WINDOW;
}
void DisplayServerIOS::window_set_current_screen(int p_screen, WindowID p_window) {
// Probably not supported for iOS
}
Point2i DisplayServerIOS::window_get_position(WindowID p_window) const {
return Point2i();
}
void DisplayServerIOS::window_set_position(const Point2i &p_position, WindowID p_window) {
// Probably not supported for single window iOS app
}
void DisplayServerIOS::window_set_transient(WindowID p_window, WindowID p_parent) {
// Probably not supported for iOS
}
void DisplayServerIOS::window_set_max_size(const Size2i p_size, WindowID p_window) {
// Probably not supported for iOS
}
Size2i DisplayServerIOS::window_get_max_size(WindowID p_window) const {
return Size2i();
}
void DisplayServerIOS::window_set_min_size(const Size2i p_size, WindowID p_window) {
// Probably not supported for iOS
}
Size2i DisplayServerIOS::window_get_min_size(WindowID p_window) const {
return Size2i();
}
void DisplayServerIOS::window_set_size(const Size2i p_size, WindowID p_window) {
// Probably not supported for iOS
}
Size2i DisplayServerIOS::window_get_size(WindowID p_window) const {
CGRect screenBounds = [UIScreen mainScreen].bounds;
return Size2i(screenBounds.size.width, screenBounds.size.height) * screen_get_max_scale();
}
Size2i DisplayServerIOS::window_get_real_size(WindowID p_window) const {
return window_get_size(p_window);
}
void DisplayServerIOS::window_set_mode(WindowMode p_mode, WindowID p_window) {
// Probably not supported for iOS
}
DisplayServer::WindowMode DisplayServerIOS::window_get_mode(WindowID p_window) const {
return WindowMode::WINDOW_MODE_FULLSCREEN;
}
bool DisplayServerIOS::window_is_maximize_allowed(WindowID p_window) const {
return false;
}
void DisplayServerIOS::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) {
// Probably not supported for iOS
}
bool DisplayServerIOS::window_get_flag(WindowFlags p_flag, WindowID p_window) const {
return false;
}
void DisplayServerIOS::window_request_attention(WindowID p_window) {
// Probably not supported for iOS
}
void DisplayServerIOS::window_move_to_foreground(WindowID p_window) {
// Probably not supported for iOS
}
float DisplayServerIOS::screen_get_max_scale() const {
return screen_get_scale(SCREEN_OF_MAIN_WINDOW);
}
void DisplayServerIOS::screen_set_orientation(DisplayServer::ScreenOrientation p_orientation, int p_screen) {
screen_orientation = p_orientation;
}
DisplayServer::ScreenOrientation DisplayServerIOS::screen_get_orientation(int p_screen) const {
return screen_orientation;
}
bool DisplayServerIOS::window_can_draw(WindowID p_window) const {
return true;
}
bool DisplayServerIOS::can_any_window_draw() const {
return true;
}
bool DisplayServerIOS::screen_is_touchscreen(int p_screen) const {
return true;
}
void DisplayServerIOS::virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, bool p_multiline, int p_max_length, int p_cursor_start, int p_cursor_end) {
NSString *existingString = [[NSString alloc] initWithUTF8String:p_existing_text.utf8().get_data()];
[AppDelegate.viewController.keyboardView
becomeFirstResponderWithString:existingString
multiline:p_multiline
cursorStart:p_cursor_start
cursorEnd:p_cursor_end];
}
void DisplayServerIOS::virtual_keyboard_hide() {
[AppDelegate.viewController.keyboardView resignFirstResponder];
}
void DisplayServerIOS::virtual_keyboard_set_height(int height) {
virtual_keyboard_height = height * screen_get_max_scale();
}
int DisplayServerIOS::virtual_keyboard_get_height() const {
return virtual_keyboard_height;
}
void DisplayServerIOS::clipboard_set(const String &p_text) {
[UIPasteboard generalPasteboard].string = [NSString stringWithUTF8String:p_text.utf8()];
}
String DisplayServerIOS::clipboard_get() const {
NSString *text = [UIPasteboard generalPasteboard].string;
return String::utf8([text UTF8String]);
}
void DisplayServerIOS::screen_set_keep_on(bool p_enable) {
[UIApplication sharedApplication].idleTimerDisabled = p_enable;
}
bool DisplayServerIOS::screen_is_kept_on() const {
return [UIApplication sharedApplication].idleTimerDisabled;
}
void DisplayServerIOS::resize_window(CGSize viewSize) {
Size2i size = Size2i(viewSize.width, viewSize.height) * screen_get_max_scale();
#if defined(VULKAN_ENABLED)
if (context_vulkan) {
context_vulkan->window_resize(MAIN_WINDOW_ID, size.x, size.y);
}
#endif
Variant resize_rect = Rect2i(Point2i(), size);
_window_callback(window_resize_callback, resize_rect);
}
void DisplayServerIOS::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window) {
_THREAD_SAFE_METHOD_
#if defined(VULKAN_ENABLED)
context_vulkan->set_vsync_mode(p_window, p_vsync_mode);
#endif
}
DisplayServer::VSyncMode DisplayServerIOS::window_get_vsync_mode(WindowID p_window) const {
_THREAD_SAFE_METHOD_
#if defined(VULKAN_ENABLED)
return context_vulkan->get_vsync_mode(p_window);
#else
return DisplayServer::VSYNC_ENABLED;
#endif
}

View file

@ -0,0 +1,40 @@
/*************************************************************************/
/* export.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "export.h"
#include "export_plugin.h"
void register_ios_exporter() {
Ref<EditorExportPlatformIOS> platform;
platform.instantiate();
EditorExport::get_singleton()->add_export_platform(platform);
}

View file

@ -0,0 +1,36 @@
/*************************************************************************/
/* export.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#ifndef IOS_EXPORT_H
#define IOS_EXPORT_H
void register_ios_exporter();
#endif // IOS_EXPORT_H

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,293 @@
/*************************************************************************/
/* export_plugin.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#ifndef IOS_EXPORT_PLUGIN_H
#define IOS_EXPORT_PLUGIN_H
#include "core/config/project_settings.h"
#include "core/io/file_access.h"
#include "core/io/image_loader.h"
#include "core/io/marshalls.h"
#include "core/io/resource_saver.h"
#include "core/io/zip_io.h"
#include "core/os/os.h"
#include "core/templates/safe_refcount.h"
#include "core/version.h"
#include "editor/editor_export.h"
#include "editor/editor_settings.h"
#include "main/splash.gen.h"
#include "platform/ios/logo.gen.h"
#include "string.h"
#include "godot_plugin_config.h"
#include <sys/stat.h>
class EditorExportPlatformIOS : public EditorExportPlatform {
GDCLASS(EditorExportPlatformIOS, EditorExportPlatform);
Ref<ImageTexture> logo;
// Plugins
SafeFlag plugins_changed;
Thread check_for_changes_thread;
SafeFlag quit_request;
Mutex plugins_lock;
Vector<PluginConfigIOS> plugins;
typedef Error (*FileHandler)(String p_file, void *p_userdata);
static Error _walk_dir_recursive(Ref<DirAccess> &p_da, FileHandler p_handler, void *p_userdata);
static Error _codesign(String p_file, void *p_userdata);
void _blend_and_rotate(Ref<Image> &p_dst, Ref<Image> &p_src, bool p_rot);
struct IOSConfigData {
String pkg_name;
String binary_name;
String plist_content;
String architectures;
String linker_flags;
String cpp_code;
String modules_buildfile;
String modules_fileref;
String modules_buildphase;
String modules_buildgrp;
Vector<String> capabilities;
};
struct ExportArchitecture {
String name;
bool is_default = false;
ExportArchitecture() {}
ExportArchitecture(String p_name, bool p_is_default) {
name = p_name;
is_default = p_is_default;
}
};
struct IOSExportAsset {
String exported_path;
bool is_framework = false; // framework is anything linked to the binary, otherwise it's a resource
bool should_embed = false;
};
String _get_additional_plist_content();
String _get_linker_flags();
String _get_cpp_code();
void _fix_config_file(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &pfile, const IOSConfigData &p_config, bool p_debug);
Error _export_loading_screen_images(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir);
Error _export_loading_screen_file(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir);
Error _export_icons(const Ref<EditorExportPreset> &p_preset, const String &p_iconset_dir);
Vector<ExportArchitecture> _get_supported_architectures();
Vector<String> _get_preset_architectures(const Ref<EditorExportPreset> &p_preset);
void _add_assets_to_project(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_project_data, const Vector<IOSExportAsset> &p_additional_assets);
Error _export_additional_assets(const String &p_out_dir, const Vector<String> &p_assets, bool p_is_framework, 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;
if (pname.length() == 0) {
if (r_error) {
*r_error = TTR("Identifier is missing.");
}
return false;
}
for (int i = 0; i < pname.length(); i++) {
char32_t c = pname[i];
if (!(is_ascii_alphanumeric_char(c) || c == '-' || c == '.')) {
if (r_error) {
*r_error = vformat(TTR("The character '%s' is not allowed in Identifier."), String::chr(c));
}
return false;
}
}
return true;
}
static void _check_for_changes_poll_thread(void *ud) {
EditorExportPlatformIOS *ea = static_cast<EditorExportPlatformIOS *>(ud);
while (!ea->quit_request.is_set()) {
// Nothing to do if we already know the plugins have changed.
if (!ea->plugins_changed.is_set()) {
MutexLock lock(ea->plugins_lock);
Vector<PluginConfigIOS> loaded_plugins = get_plugins();
if (ea->plugins.size() != loaded_plugins.size()) {
ea->plugins_changed.set();
} else {
for (int i = 0; i < ea->plugins.size(); i++) {
if (ea->plugins[i].name != loaded_plugins[i].name || ea->plugins[i].last_updated != loaded_plugins[i].last_updated) {
ea->plugins_changed.set();
break;
}
}
}
}
uint64_t wait = 3000000;
uint64_t time = OS::get_singleton()->get_ticks_usec();
while (OS::get_singleton()->get_ticks_usec() - time < wait) {
OS::get_singleton()->delay_usec(300000);
if (ea->quit_request.is_set()) {
break;
}
}
}
}
protected:
virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) override;
virtual void get_export_options(List<ExportOption> *r_options) override;
public:
virtual String get_name() const override { return "iOS"; }
virtual String get_os_name() const override { return "iOS"; }
virtual Ref<Texture2D> get_logo() const override { return logo; }
virtual bool should_update_export_options() override {
bool export_options_changed = plugins_changed.is_set();
if (export_options_changed) {
// don't clear unless we're reporting true, to avoid race
plugins_changed.clear();
}
return export_options_changed;
}
virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const 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 bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override;
virtual void get_platform_features(List<String> *r_features) override {
r_features->push_back("mobile");
r_features->push_back("ios");
}
virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, HashSet<String> &p_features) override {
}
EditorExportPlatformIOS();
~EditorExportPlatformIOS();
/// List the gdip files in the directory specified by the p_path parameter.
static Vector<String> list_plugin_config_files(const String &p_path, bool p_check_directories) {
Vector<String> dir_files;
Ref<DirAccess> da = DirAccess::open(p_path);
if (da.is_valid()) {
da->list_dir_begin();
while (true) {
String file = da->get_next();
if (file.is_empty()) {
break;
}
if (file == "." || file == "..") {
continue;
}
if (da->current_is_hidden()) {
continue;
}
if (da->current_is_dir()) {
if (p_check_directories) {
Vector<String> directory_files = list_plugin_config_files(p_path.plus_file(file), false);
for (int i = 0; i < directory_files.size(); ++i) {
dir_files.push_back(file.plus_file(directory_files[i]));
}
}
continue;
}
if (file.ends_with(PluginConfigIOS::PLUGIN_CONFIG_EXT)) {
dir_files.push_back(file);
}
}
da->list_dir_end();
}
return dir_files;
}
static Vector<PluginConfigIOS> get_plugins() {
Vector<PluginConfigIOS> loaded_plugins;
String plugins_dir = ProjectSettings::get_singleton()->get_resource_path().plus_file("ios/plugins");
if (DirAccess::exists(plugins_dir)) {
Vector<String> plugins_filenames = list_plugin_config_files(plugins_dir, true);
if (!plugins_filenames.is_empty()) {
Ref<ConfigFile> config_file = memnew(ConfigFile);
for (int i = 0; i < plugins_filenames.size(); i++) {
PluginConfigIOS config = PluginConfigIOS::load_plugin_config(config_file, plugins_dir.plus_file(plugins_filenames[i]));
if (config.valid_config) {
loaded_plugins.push_back(config);
} else {
print_error("Invalid plugin config file " + plugins_filenames[i]);
}
}
}
}
return loaded_plugins;
}
static Vector<PluginConfigIOS> get_enabled_plugins(const Ref<EditorExportPreset> &p_presets) {
Vector<PluginConfigIOS> enabled_plugins;
Vector<PluginConfigIOS> all_plugins = get_plugins();
for (int i = 0; i < all_plugins.size(); i++) {
PluginConfigIOS plugin = all_plugins[i];
bool enabled = p_presets->get("plugins/" + plugin.name);
if (enabled) {
enabled_plugins.push_back(plugin);
}
}
return enabled_plugins;
}
};
#endif

View file

@ -0,0 +1,285 @@
/*************************************************************************/
/* godot_plugin_config.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "godot_plugin_config.h"
#include "core/config/project_settings.h"
#include "core/io/dir_access.h"
#include "core/io/file_access.h"
String PluginConfigIOS::resolve_local_dependency_path(String plugin_config_dir, String dependency_path) {
String absolute_path;
if (dependency_path.is_empty()) {
return absolute_path;
}
if (dependency_path.is_absolute_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://");
}
String PluginConfigIOS::resolve_system_dependency_path(String dependency_path) {
String absolute_path;
if (dependency_path.is_empty()) {
return absolute_path;
}
if (dependency_path.is_absolute_path()) {
return dependency_path;
}
String system_path = "/System/Library/Frameworks";
return system_path.plus_file(dependency_path);
}
Vector<String> PluginConfigIOS::resolve_local_dependencies(String plugin_config_dir, Vector<String> p_paths) {
Vector<String> paths;
for (int i = 0; i < p_paths.size(); i++) {
String path = resolve_local_dependency_path(plugin_config_dir, p_paths[i]);
if (path.is_empty()) {
continue;
}
paths.push_back(path);
}
return paths;
}
Vector<String> PluginConfigIOS::resolve_system_dependencies(Vector<String> p_paths) {
Vector<String> paths;
for (int i = 0; i < p_paths.size(); i++) {
String path = resolve_system_dependency_path(p_paths[i]);
if (path.is_empty()) {
continue;
}
paths.push_back(path);
}
return paths;
}
bool PluginConfigIOS::validate_plugin(PluginConfigIOS &plugin_config) {
bool valid_name = !plugin_config.name.is_empty();
bool valid_binary_name = !plugin_config.binary.is_empty();
bool valid_initialize = !plugin_config.initialization_method.is_empty();
bool valid_deinitialize = !plugin_config.deinitialization_method.is_empty();
bool fields_value = valid_name && valid_binary_name && valid_initialize && valid_deinitialize;
if (!fields_value) {
return false;
}
String plugin_extension = plugin_config.binary.get_extension().to_lower();
if ((plugin_extension == "a" && FileAccess::exists(plugin_config.binary)) ||
(plugin_extension == "xcframework" && DirAccess::exists(plugin_config.binary))) {
plugin_config.valid_config = true;
plugin_config.supports_targets = false;
} else {
String file_path = plugin_config.binary.get_base_dir();
String file_name = plugin_config.binary.get_basename().get_file();
String file_extension = plugin_config.binary.get_extension();
String release_file_name = file_path.plus_file(file_name + ".release." + file_extension);
String debug_file_name = file_path.plus_file(file_name + ".debug." + file_extension);
if ((plugin_extension == "a" && FileAccess::exists(release_file_name) && FileAccess::exists(debug_file_name)) ||
(plugin_extension == "xcframework" && DirAccess::exists(release_file_name) && DirAccess::exists(debug_file_name))) {
plugin_config.valid_config = true;
plugin_config.supports_targets = true;
}
}
return plugin_config.valid_config;
}
String PluginConfigIOS::get_plugin_main_binary(PluginConfigIOS &plugin_config, bool p_debug) {
if (!plugin_config.supports_targets) {
return plugin_config.binary;
}
String plugin_binary_dir = plugin_config.binary.get_base_dir();
String plugin_name_prefix = plugin_config.binary.get_basename().get_file();
String plugin_extension = plugin_config.binary.get_extension();
String plugin_file = plugin_name_prefix + "." + (p_debug ? "debug" : "release") + "." + plugin_extension;
return plugin_binary_dir.plus_file(plugin_file);
}
uint64_t PluginConfigIOS::get_plugin_modification_time(const PluginConfigIOS &plugin_config, const String &config_path) {
uint64_t last_updated = FileAccess::get_modified_time(config_path);
if (!plugin_config.supports_targets) {
last_updated = MAX(last_updated, FileAccess::get_modified_time(plugin_config.binary));
} else {
String file_path = plugin_config.binary.get_base_dir();
String file_name = plugin_config.binary.get_basename().get_file();
String plugin_extension = plugin_config.binary.get_extension();
String release_file_name = file_path.plus_file(file_name + ".release." + plugin_extension);
String debug_file_name = file_path.plus_file(file_name + ".debug." + plugin_extension);
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;
}
PluginConfigIOS PluginConfigIOS::load_plugin_config(Ref<ConfigFile> config_file, const String &path) {
PluginConfigIOS plugin_config = {};
if (!config_file.is_valid()) {
return plugin_config;
}
config_file->clear();
Error err = config_file->load(path);
if (err != OK) {
return plugin_config;
}
String config_base_dir = path.get_base_dir();
plugin_config.name = config_file->get_value(PluginConfigIOS::CONFIG_SECTION, PluginConfigIOS::CONFIG_NAME_KEY, String());
plugin_config.initialization_method = config_file->get_value(PluginConfigIOS::CONFIG_SECTION, PluginConfigIOS::CONFIG_INITIALIZE_KEY, String());
plugin_config.deinitialization_method = config_file->get_value(PluginConfigIOS::CONFIG_SECTION, PluginConfigIOS::CONFIG_DEINITIALIZE_KEY, String());
String binary_path = config_file->get_value(PluginConfigIOS::CONFIG_SECTION, PluginConfigIOS::CONFIG_BINARY_KEY, String());
plugin_config.binary = resolve_local_dependency_path(config_base_dir, binary_path);
if (config_file->has_section(PluginConfigIOS::DEPENDENCIES_SECTION)) {
Vector<String> linked_dependencies = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_LINKED_KEY, Vector<String>());
Vector<String> embedded_dependencies = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_EMBEDDED_KEY, Vector<String>());
Vector<String> system_dependencies = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_SYSTEM_KEY, Vector<String>());
Vector<String> files = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_FILES_KEY, Vector<String>());
plugin_config.linked_dependencies = resolve_local_dependencies(config_base_dir, linked_dependencies);
plugin_config.embedded_dependencies = resolve_local_dependencies(config_base_dir, embedded_dependencies);
plugin_config.system_dependencies = resolve_system_dependencies(system_dependencies);
plugin_config.files_to_copy = resolve_local_dependencies(config_base_dir, files);
plugin_config.capabilities = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_CAPABILITIES_KEY, Vector<String>());
plugin_config.linker_flags = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_LINKER_FLAGS, Vector<String>());
}
if (config_file->has_section(PluginConfigIOS::PLIST_SECTION)) {
List<String> keys;
config_file->get_section_keys(PluginConfigIOS::PLIST_SECTION, &keys);
for (int i = 0; i < keys.size(); i++) {
Vector<String> key_components = keys[i].split(":");
String key_value = "";
PluginConfigIOS::PlistItemType key_type = PluginConfigIOS::PlistItemType::UNKNOWN;
if (key_components.size() == 1) {
key_value = key_components[0];
key_type = PluginConfigIOS::PlistItemType::STRING;
} else if (key_components.size() == 2) {
key_value = key_components[0];
if (key_components[1].to_lower() == "string") {
key_type = PluginConfigIOS::PlistItemType::STRING;
} else if (key_components[1].to_lower() == "integer") {
key_type = PluginConfigIOS::PlistItemType::INTEGER;
} else if (key_components[1].to_lower() == "boolean") {
key_type = PluginConfigIOS::PlistItemType::BOOLEAN;
} else if (key_components[1].to_lower() == "raw") {
key_type = PluginConfigIOS::PlistItemType::RAW;
} else if (key_components[1].to_lower() == "string_input") {
key_type = PluginConfigIOS::PlistItemType::STRING_INPUT;
}
}
if (key_value.is_empty() || key_type == PluginConfigIOS::PlistItemType::UNKNOWN) {
continue;
}
String value;
switch (key_type) {
case PluginConfigIOS::PlistItemType::STRING: {
String raw_value = config_file->get_value(PluginConfigIOS::PLIST_SECTION, keys[i], String());
value = "<string>" + raw_value + "</string>";
} break;
case PluginConfigIOS::PlistItemType::INTEGER: {
int raw_value = config_file->get_value(PluginConfigIOS::PLIST_SECTION, keys[i], 0);
Dictionary value_dictionary;
String value_format = "<integer>$value</integer>";
value_dictionary["value"] = raw_value;
value = value_format.format(value_dictionary, "$_");
} break;
case PluginConfigIOS::PlistItemType::BOOLEAN:
if (config_file->get_value(PluginConfigIOS::PLIST_SECTION, keys[i], false)) {
value = "<true/>";
} else {
value = "<false/>";
}
break;
case PluginConfigIOS::PlistItemType::RAW: {
String raw_value = config_file->get_value(PluginConfigIOS::PLIST_SECTION, keys[i], String());
value = raw_value;
} break;
case PluginConfigIOS::PlistItemType::STRING_INPUT: {
String raw_value = config_file->get_value(PluginConfigIOS::PLIST_SECTION, keys[i], String());
value = raw_value;
} break;
default:
continue;
}
plugin_config.plist[key_value] = PluginConfigIOS::PlistItem{ key_type, value };
}
}
if (validate_plugin(plugin_config)) {
plugin_config.last_updated = get_plugin_modification_time(plugin_config, path);
}
return plugin_config;
}

View file

@ -0,0 +1,132 @@
/*************************************************************************/
/* godot_plugin_config.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#ifndef IOS_GODOT_PLUGIN_CONFIG_H
#define IOS_GODOT_PLUGIN_CONFIG_H
#include "core/error/error_list.h"
#include "core/io/config_file.h"
#include "core/string/ustring.h"
/*
The `config` section and fields are required and defined as follow:
- **name**: name of the plugin
- **binary**: path to static `.a` library
The `dependencies` and fields are optional.
- **linked**: dependencies that should only be linked.
- **embedded**: dependencies that should be linked and embedded into application.
- **system**: system dependencies that should be linked.
- **capabilities**: capabilities that would be used for `UIRequiredDeviceCapabilities` options in Info.plist file.
- **files**: files that would be copied into application
The `plist` section are optional.
- **key**: key and value that would be added in Info.plist file.
*/
struct PluginConfigIOS {
inline static const char *PLUGIN_CONFIG_EXT = ".gdip";
inline static const char *CONFIG_SECTION = "config";
inline static const char *CONFIG_NAME_KEY = "name";
inline static const char *CONFIG_BINARY_KEY = "binary";
inline static const char *CONFIG_INITIALIZE_KEY = "initialization";
inline static const char *CONFIG_DEINITIALIZE_KEY = "deinitialization";
inline static const char *DEPENDENCIES_SECTION = "dependencies";
inline static const char *DEPENDENCIES_LINKED_KEY = "linked";
inline static const char *DEPENDENCIES_EMBEDDED_KEY = "embedded";
inline static const char *DEPENDENCIES_SYSTEM_KEY = "system";
inline static const char *DEPENDENCIES_CAPABILITIES_KEY = "capabilities";
inline static const char *DEPENDENCIES_FILES_KEY = "files";
inline static const char *DEPENDENCIES_LINKER_FLAGS = "linker_flags";
inline static const char *PLIST_SECTION = "plist";
enum PlistItemType {
UNKNOWN,
STRING,
INTEGER,
BOOLEAN,
RAW,
STRING_INPUT,
};
struct PlistItem {
PlistItemType type;
String value;
};
// Set to true when the config file is properly loaded.
bool valid_config = false;
bool supports_targets = false;
// Unix timestamp of last change to this plugin.
uint64_t last_updated = 0;
// Required config section
String name;
String binary;
String initialization_method;
String deinitialization_method;
// Optional dependencies section
Vector<String> linked_dependencies;
Vector<String> embedded_dependencies;
Vector<String> system_dependencies;
Vector<String> files_to_copy;
Vector<String> capabilities;
Vector<String> linker_flags;
// Optional plist section
// String value is default value.
// Currently supports `string`, `boolean`, `integer`, `raw`, `string_input` types
// <name>:<type> = <value>
HashMap<String, PlistItem> plist;
static String resolve_local_dependency_path(String plugin_config_dir, String dependency_path);
static String resolve_system_dependency_path(String dependency_path);
static Vector<String> resolve_local_dependencies(String plugin_config_dir, Vector<String> p_paths);
static Vector<String> resolve_system_dependencies(Vector<String> p_paths);
static bool validate_plugin(PluginConfigIOS &plugin_config);
static String get_plugin_main_binary(PluginConfigIOS &plugin_config, bool p_debug);
static uint64_t get_plugin_modification_time(const PluginConfigIOS &plugin_config, const String &config_path);
static PluginConfigIOS load_plugin_config(Ref<ConfigFile> config_file, const String &path);
};
#endif // GODOT_PLUGIN_CONFIG_H

View file

@ -0,0 +1,41 @@
/*************************************************************************/
/* godot_app_delegate.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#import <UIKit/UIKit.h>
typedef NSObject<UIApplicationDelegate> ApplicationDelegateService;
@interface GodotApplicalitionDelegate : NSObject <UIApplicationDelegate>
@property(class, readonly, strong) NSArray<ApplicationDelegateService *> *services;
+ (void)addService:(ApplicationDelegateService *)service;
@end

View file

@ -0,0 +1,467 @@
/*************************************************************************/
/* godot_app_delegate.m */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#import "godot_app_delegate.h"
#import "app_delegate.h"
@interface GodotApplicalitionDelegate ()
@end
@implementation GodotApplicalitionDelegate
static NSMutableArray<ApplicationDelegateService *> *services = nil;
+ (NSArray<ApplicationDelegateService *> *)services {
return services;
}
+ (void)load {
services = [NSMutableArray new];
[services addObject:[AppDelegate new]];
}
+ (void)addService:(ApplicationDelegateService *)service {
if (!services || !service) {
return;
}
[services addObject:service];
}
// UIApplicationDelegate documentation can be found here: https://developer.apple.com/documentation/uikit/uiapplicationdelegate
// MARK: Window
- (UIWindow *)window {
UIWindow *result = nil;
for (ApplicationDelegateService *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
UIWindow *value = [service window];
if (value) {
result = value;
}
}
return result;
}
// MARK: Initializing
- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions {
BOOL result = NO;
for (ApplicationDelegateService *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
if ([service application:application willFinishLaunchingWithOptions:launchOptions]) {
result = YES;
}
}
return result;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions {
BOOL result = NO;
for (ApplicationDelegateService *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
if ([service application:application didFinishLaunchingWithOptions:launchOptions]) {
result = YES;
}
}
return result;
}
/* Can be handled by Info.plist. Not yet supported by Godot.
// MARK: Scene
- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options {}
- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet<UISceneSession *> *)sceneSessions {}
*/
// MARK: Life-Cycle
- (void)applicationDidBecomeActive:(UIApplication *)application {
for (ApplicationDelegateService *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service applicationDidBecomeActive:application];
}
}
- (void)applicationWillResignActive:(UIApplication *)application {
for (ApplicationDelegateService *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service applicationWillResignActive:application];
}
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
for (ApplicationDelegateService *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service applicationDidEnterBackground:application];
}
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
for (ApplicationDelegateService *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service applicationWillEnterForeground:application];
}
}
- (void)applicationWillTerminate:(UIApplication *)application {
for (ApplicationDelegateService *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service applicationWillTerminate:application];
}
}
// MARK: Environment Changes
- (void)applicationProtectedDataDidBecomeAvailable:(UIApplication *)application {
for (ApplicationDelegateService *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service applicationProtectedDataDidBecomeAvailable:application];
}
}
- (void)applicationProtectedDataWillBecomeUnavailable:(UIApplication *)application {
for (ApplicationDelegateService *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service applicationProtectedDataWillBecomeUnavailable:application];
}
}
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
for (ApplicationDelegateService *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service applicationDidReceiveMemoryWarning:application];
}
}
- (void)applicationSignificantTimeChange:(UIApplication *)application {
for (ApplicationDelegateService *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service applicationSignificantTimeChange:application];
}
}
// MARK: App State Restoration
- (BOOL)application:(UIApplication *)application shouldSaveSecureApplicationState:(NSCoder *)coder API_AVAILABLE(ios(13.2)) {
BOOL result = NO;
for (ApplicationDelegateService *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
if ([service application:application shouldSaveSecureApplicationState:coder]) {
result = YES;
}
}
return result;
}
- (BOOL)application:(UIApplication *)application shouldRestoreSecureApplicationState:(NSCoder *)coder API_AVAILABLE(ios(13.2)) {
BOOL result = NO;
for (ApplicationDelegateService *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
if ([service application:application shouldRestoreSecureApplicationState:coder]) {
result = YES;
}
}
return result;
}
- (UIViewController *)application:(UIApplication *)application viewControllerWithRestorationIdentifierPath:(NSArray<NSString *> *)identifierComponents coder:(NSCoder *)coder {
for (ApplicationDelegateService *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
UIViewController *controller = [service application:application viewControllerWithRestorationIdentifierPath:identifierComponents coder:coder];
if (controller) {
return controller;
}
}
return nil;
}
- (void)application:(UIApplication *)application willEncodeRestorableStateWithCoder:(NSCoder *)coder {
for (ApplicationDelegateService *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service application:application willEncodeRestorableStateWithCoder:coder];
}
}
- (void)application:(UIApplication *)application didDecodeRestorableStateWithCoder:(NSCoder *)coder {
for (ApplicationDelegateService *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service application:application didDecodeRestorableStateWithCoder:coder];
}
}
// MARK: Download Data in Background
- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler {
for (ApplicationDelegateService *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service application:application handleEventsForBackgroundURLSession:identifier completionHandler:completionHandler];
}
completionHandler();
}
// MARK: Remote Notification
// Moved to the iOS Plugin
// MARK: User Activity and Handling Quick Actions
- (BOOL)application:(UIApplication *)application willContinueUserActivityWithType:(NSString *)userActivityType {
BOOL result = NO;
for (ApplicationDelegateService *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
if ([service application:application willContinueUserActivityWithType:userActivityType]) {
result = YES;
}
}
return result;
}
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> *restorableObjects))restorationHandler {
BOOL result = NO;
for (ApplicationDelegateService *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
if ([service application:application continueUserActivity:userActivity restorationHandler:restorationHandler]) {
result = YES;
}
}
return result;
}
- (void)application:(UIApplication *)application didUpdateUserActivity:(NSUserActivity *)userActivity {
for (ApplicationDelegateService *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service application:application didUpdateUserActivity:userActivity];
}
}
- (void)application:(UIApplication *)application didFailToContinueUserActivityWithType:(NSString *)userActivityType error:(NSError *)error {
for (ApplicationDelegateService *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service application:application didFailToContinueUserActivityWithType:userActivityType error:error];
}
}
- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL succeeded))completionHandler {
for (ApplicationDelegateService *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service application:application performActionForShortcutItem:shortcutItem completionHandler:completionHandler];
}
}
// MARK: WatchKit
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *replyInfo))reply {
for (ApplicationDelegateService *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service application:application handleWatchKitExtensionRequest:userInfo reply:reply];
}
}
// MARK: HealthKit
- (void)applicationShouldRequestHealthAuthorization:(UIApplication *)application {
for (ApplicationDelegateService *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service applicationShouldRequestHealthAuthorization:application];
}
}
// MARK: Opening an URL
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
for (ApplicationDelegateService *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
if ([service application:app openURL:url options:options]) {
return YES;
}
}
return NO;
}
// MARK: Disallowing Specified App Extension Types
- (BOOL)application:(UIApplication *)application shouldAllowExtensionPointIdentifier:(UIApplicationExtensionPointIdentifier)extensionPointIdentifier {
BOOL result = NO;
for (ApplicationDelegateService *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
if ([service application:application shouldAllowExtensionPointIdentifier:extensionPointIdentifier]) {
result = YES;
}
}
return result;
}
// MARK: SiriKit
- (id)application:(UIApplication *)application handlerForIntent:(INIntent *)intent API_AVAILABLE(ios(14.0)) {
for (ApplicationDelegateService *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
id result = [service application:application handlerForIntent:intent];
if (result) {
return result;
}
}
return nil;
}
// MARK: CloudKit
- (void)application:(UIApplication *)application userDidAcceptCloudKitShareWithMetadata:(CKShareMetadata *)cloudKitShareMetadata {
for (ApplicationDelegateService *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service application:application userDidAcceptCloudKitShareWithMetadata:cloudKitShareMetadata];
}
}
/* Handled By Info.plist file for now
// MARK: Interface Geometry
- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {}
*/
@end

131
platform/ios/godot_ios.mm Normal file
View file

@ -0,0 +1,131 @@
/*************************************************************************/
/* godot_ios.mm */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "core/string/ustring.h"
#include "main/main.h"
#include "os_ios.h"
#include <stdio.h>
#include <string.h>
#include <unistd.h>
static OS_IOS *os = nullptr;
int add_path(int, char **);
int add_cmdline(int, char **);
int ios_main(int, char **, String);
int add_path(int p_argc, char **p_args) {
NSString *str = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_path"];
if (!str) {
return p_argc;
}
p_args[p_argc++] = (char *)"--path";
p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding];
p_args[p_argc] = nullptr;
return p_argc;
}
int add_cmdline(int p_argc, char **p_args) {
NSArray *arr = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_cmdline"];
if (!arr) {
return p_argc;
}
for (NSUInteger i = 0; i < [arr count]; i++) {
NSString *str = [arr objectAtIndex:i];
if (!str) {
continue;
}
p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding];
}
p_args[p_argc] = nullptr;
return p_argc;
}
int ios_main(int argc, char **argv, String data_dir, String cache_dir) {
size_t len = strlen(argv[0]);
while (len--) {
if (argv[0][len] == '/') {
break;
}
}
if (len >= 0) {
char path[512];
memcpy(path, argv[0], len > sizeof(path) ? sizeof(path) : len);
path[len] = 0;
printf("Path: %s\n", path);
chdir(path);
}
printf("godot_ios %s\n", argv[0]);
char cwd[512];
getcwd(cwd, sizeof(cwd));
printf("cwd %s\n", cwd);
os = new OS_IOS(data_dir, cache_dir);
// We must override main when testing is enabled
TEST_MAIN_OVERRIDE
char *fargv[64];
for (int i = 0; i < argc; i++) {
fargv[i] = argv[i];
}
fargv[argc] = nullptr;
argc = add_path(argc, fargv);
argc = add_cmdline(argc, fargv);
printf("os created\n");
Error err = Main::setup(fargv[0], argc - 1, &fargv[1], false);
printf("setup %i\n", err);
if (err == ERR_HELP) { // Returned by --help and --version, so success.
return 0;
} else if (err != OK) {
return 255;
}
os->initialize_modules();
return 0;
}
void ios_finish() {
printf("ios_finish\n");
Main::cleanup();
delete os;
}

67
platform/ios/godot_view.h Normal file
View file

@ -0,0 +1,67 @@
/*************************************************************************/
/* godot_view.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#import <UIKit/UIKit.h>
class String;
@class GodotView;
@protocol DisplayLayer;
@protocol GodotViewRendererProtocol;
@protocol GodotViewDelegate
- (BOOL)godotViewFinishedSetup:(GodotView *)view;
@end
@interface GodotView : UIView
@property(assign, nonatomic) id<GodotViewRendererProtocol> renderer;
@property(assign, nonatomic) id<GodotViewDelegate> delegate;
@property(assign, readonly, nonatomic) BOOL isActive;
@property(assign, nonatomic) BOOL useCADisplayLink;
@property(strong, readonly, nonatomic) CALayer<DisplayLayer> *renderingLayer;
@property(assign, readonly, nonatomic) BOOL canRender;
@property(assign, nonatomic) NSTimeInterval renderingInterval;
- (CALayer<DisplayLayer> *)initializeRenderingForDriver:(NSString *)driverName;
- (void)stopRendering;
- (void)startRendering;
- (void)godotTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)godotTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)godotTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)godotTouchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
@end

481
platform/ios/godot_view.mm Normal file
View file

@ -0,0 +1,481 @@
/*************************************************************************/
/* godot_view.mm */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#import "godot_view.h"
#include "core/os/keyboard.h"
#include "core/string/ustring.h"
#import "display_layer.h"
#include "display_server_ios.h"
#import "godot_view_gesture_recognizer.h"
#import "godot_view_renderer.h"
#import <CoreMotion/CoreMotion.h>
static const int max_touches = 8;
static const float earth_gravity = 9.80665;
@interface GodotView () {
UITouch *godot_touches[max_touches];
}
@property(assign, nonatomic) BOOL isActive;
// CADisplayLink available on 3.1+ synchronizes the animation timer & drawing with the refresh rate of the display, only supports animation intervals of 1/60 1/30 & 1/15
@property(strong, nonatomic) CADisplayLink *displayLink;
// An animation timer that, when animation is started, will periodically call -drawView at the given rate.
// Only used if CADisplayLink is not
@property(strong, nonatomic) NSTimer *animationTimer;
@property(strong, nonatomic) CALayer<DisplayLayer> *renderingLayer;
@property(strong, nonatomic) CMMotionManager *motionManager;
@property(strong, nonatomic) GodotViewGestureRecognizer *delayGestureRecognizer;
@end
@implementation GodotView
- (CALayer<DisplayLayer> *)initializeRenderingForDriver:(NSString *)driverName {
if (self.renderingLayer) {
return self.renderingLayer;
}
CALayer<DisplayLayer> *layer;
if ([driverName isEqualToString:@"vulkan"]) {
layer = [GodotMetalLayer layer];
} else if ([driverName isEqualToString:@"opengl_es"]) {
if (@available(iOS 13, *)) {
NSLog(@"OpenGL ES is deprecated on iOS 13");
}
#if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR
return nil;
#else
layer = [GodotOpenGLLayer layer];
#endif
} else {
return nil;
}
layer.frame = self.bounds;
layer.contentsScale = self.contentScaleFactor;
[self.layer addSublayer:layer];
self.renderingLayer = layer;
[layer initializeDisplayLayer];
return self.renderingLayer;
}
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
[self godot_commonInit];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self godot_commonInit];
}
return self;
}
- (void)dealloc {
[self stopRendering];
self.renderer = nil;
self.delegate = nil;
if (self.renderingLayer) {
[self.renderingLayer removeFromSuperlayer];
self.renderingLayer = nil;
}
if (self.motionManager) {
[self.motionManager stopDeviceMotionUpdates];
self.motionManager = nil;
}
if (self.displayLink) {
[self.displayLink invalidate];
self.displayLink = nil;
}
if (self.animationTimer) {
[self.animationTimer invalidate];
self.animationTimer = nil;
}
if (self.delayGestureRecognizer) {
self.delayGestureRecognizer = nil;
}
}
- (void)godot_commonInit {
self.contentScaleFactor = [UIScreen mainScreen].nativeScale;
[self initTouches];
self.multipleTouchEnabled = YES;
// Configure and start accelerometer
if (!self.motionManager) {
self.motionManager = [[CMMotionManager alloc] init];
if (self.motionManager.deviceMotionAvailable) {
self.motionManager.deviceMotionUpdateInterval = 1.0 / 70.0;
[self.motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXMagneticNorthZVertical];
} else {
self.motionManager = nil;
}
}
// Initialize delay gesture recognizer
GodotViewGestureRecognizer *gestureRecognizer = [[GodotViewGestureRecognizer alloc] init];
self.delayGestureRecognizer = gestureRecognizer;
[self addGestureRecognizer:self.delayGestureRecognizer];
}
- (void)stopRendering {
if (!self.isActive) {
return;
}
self.isActive = NO;
printf("******** stop animation!\n");
if (self.useCADisplayLink) {
[self.displayLink invalidate];
self.displayLink = nil;
} else {
[self.animationTimer invalidate];
self.animationTimer = nil;
}
[self clearTouches];
}
- (void)startRendering {
if (self.isActive) {
return;
}
self.isActive = YES;
printf("start animation!\n");
if (self.useCADisplayLink) {
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawView)];
// Approximate frame rate
// assumes device refreshes at 60 fps
int displayFPS = (NSInteger)(1.0 / self.renderingInterval);
self.displayLink.preferredFramesPerSecond = displayFPS;
// Setup DisplayLink in main thread
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
} else {
self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:self.renderingInterval target:self selector:@selector(drawView) userInfo:nil repeats:YES];
}
}
- (void)drawView {
if (!self.isActive) {
printf("draw view not active!\n");
return;
}
if (self.useCADisplayLink) {
// Pause the CADisplayLink to avoid recursion
[self.displayLink setPaused:YES];
// Process all input events
while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, TRUE) == kCFRunLoopRunHandledSource) {
// Continue.
}
// We are good to go, resume the CADisplayLink
[self.displayLink setPaused:NO];
}
[self.renderingLayer renderDisplayLayer];
if (!self.renderer) {
return;
}
if ([self.renderer setupView:self]) {
return;
}
if (self.delegate) {
BOOL delegateFinishedSetup = [self.delegate godotViewFinishedSetup:self];
if (!delegateFinishedSetup) {
return;
}
}
[self handleMotion];
[self.renderer renderOnView:self];
}
- (BOOL)canRender {
if (self.useCADisplayLink) {
return self.displayLink != nil;
} else {
return self.animationTimer != nil;
}
}
- (void)setRenderingInterval:(NSTimeInterval)renderingInterval {
_renderingInterval = renderingInterval;
if (self.canRender) {
[self stopRendering];
[self startRendering];
}
}
- (void)layoutSubviews {
if (self.renderingLayer) {
self.renderingLayer.frame = self.bounds;
[self.renderingLayer layoutDisplayLayer];
if (DisplayServerIOS::get_singleton()) {
DisplayServerIOS::get_singleton()->resize_window(self.bounds.size);
}
}
[super layoutSubviews];
}
// MARK: - Input
// MARK: Touches
- (void)initTouches {
for (int i = 0; i < max_touches; i++) {
godot_touches[i] = nullptr;
}
}
- (int)getTouchIDForTouch:(UITouch *)p_touch {
int first = -1;
for (int i = 0; i < max_touches; i++) {
if (first == -1 && godot_touches[i] == nullptr) {
first = i;
continue;
}
if (godot_touches[i] == p_touch) {
return i;
}
}
if (first != -1) {
godot_touches[first] = p_touch;
return first;
}
return -1;
}
- (int)removeTouch:(UITouch *)p_touch {
int remaining = 0;
for (int i = 0; i < max_touches; i++) {
if (godot_touches[i] == nullptr) {
continue;
}
if (godot_touches[i] == p_touch) {
godot_touches[i] = nullptr;
} else {
++remaining;
}
}
return remaining;
}
- (void)clearTouches {
for (int i = 0; i < max_touches; i++) {
godot_touches[i] = nullptr;
}
}
- (void)godotTouchesBegan:(NSSet *)touchesSet withEvent:(UIEvent *)event {
NSArray *tlist = [event.allTouches allObjects];
for (unsigned int i = 0; i < [tlist count]; i++) {
if ([touchesSet containsObject:[tlist objectAtIndex:i]]) {
UITouch *touch = [tlist objectAtIndex:i];
int tid = [self getTouchIDForTouch:touch];
ERR_FAIL_COND(tid == -1);
CGPoint touchPoint = [touch locationInView:self];
DisplayServerIOS::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, true, touch.tapCount > 1);
}
}
}
- (void)godotTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
NSArray *tlist = [event.allTouches allObjects];
for (unsigned int i = 0; i < [tlist count]; i++) {
if ([touches containsObject:[tlist objectAtIndex:i]]) {
UITouch *touch = [tlist objectAtIndex:i];
int tid = [self getTouchIDForTouch:touch];
ERR_FAIL_COND(tid == -1);
CGPoint touchPoint = [touch locationInView:self];
CGPoint prev_point = [touch previousLocationInView:self];
DisplayServerIOS::get_singleton()->touch_drag(tid, prev_point.x * self.contentScaleFactor, prev_point.y * self.contentScaleFactor, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor);
}
}
}
- (void)godotTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
NSArray *tlist = [event.allTouches allObjects];
for (unsigned int i = 0; i < [tlist count]; i++) {
if ([touches containsObject:[tlist objectAtIndex:i]]) {
UITouch *touch = [tlist objectAtIndex:i];
int tid = [self getTouchIDForTouch:touch];
ERR_FAIL_COND(tid == -1);
[self removeTouch:touch];
CGPoint touchPoint = [touch locationInView:self];
DisplayServerIOS::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, false, false);
}
}
}
- (void)godotTouchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
NSArray *tlist = [event.allTouches allObjects];
for (unsigned int i = 0; i < [tlist count]; i++) {
if ([touches containsObject:[tlist objectAtIndex:i]]) {
UITouch *touch = [tlist objectAtIndex:i];
int tid = [self getTouchIDForTouch:touch];
ERR_FAIL_COND(tid == -1);
DisplayServerIOS::get_singleton()->touches_cancelled(tid);
}
}
[self clearTouches];
}
// MARK: Motion
- (void)handleMotion {
if (!self.motionManager) {
return;
}
// Just using polling approach for now, we can set this up so it sends
// data to us in intervals, might be better. See Apple reference pages
// for more details:
// https://developer.apple.com/reference/coremotion/cmmotionmanager?language=objc
// Apple splits our accelerometer date into a gravity and user movement
// component. We add them back together.
CMAcceleration gravity = self.motionManager.deviceMotion.gravity;
CMAcceleration acceleration = self.motionManager.deviceMotion.userAcceleration;
// To be consistent with Android we convert the unit of measurement from g (Earth's gravity)
// to m/s^2.
gravity.x *= earth_gravity;
gravity.y *= earth_gravity;
gravity.z *= earth_gravity;
acceleration.x *= earth_gravity;
acceleration.y *= earth_gravity;
acceleration.z *= earth_gravity;
///@TODO We don't seem to be getting data here, is my device broken or
/// is this code incorrect?
CMMagneticField magnetic = self.motionManager.deviceMotion.magneticField.field;
///@TODO we can access rotationRate as a CMRotationRate variable
///(processed date) or CMGyroData (raw data), have to see what works
/// best
CMRotationRate rotation = self.motionManager.deviceMotion.rotationRate;
// Adjust for screen orientation.
// [[UIDevice currentDevice] orientation] changes even if we've fixed
// our orientation which is not a good thing when you're trying to get
// your user to move the screen in all directions and want consistent
// output
///@TODO Using [[UIApplication sharedApplication] statusBarOrientation]
/// is a bit of a hack. Godot obviously knows the orientation so maybe
/// we
// can use that instead? (note that left and right seem swapped)
UIInterfaceOrientation interfaceOrientation = UIInterfaceOrientationUnknown;
if (@available(iOS 13, *)) {
interfaceOrientation = [UIApplication sharedApplication].delegate.window.windowScene.interfaceOrientation;
#if !defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR
} else {
interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation];
#endif
}
switch (interfaceOrientation) {
case UIInterfaceOrientationLandscapeLeft: {
DisplayServerIOS::get_singleton()->update_gravity(-gravity.y, gravity.x, gravity.z);
DisplayServerIOS::get_singleton()->update_accelerometer(-(acceleration.y + gravity.y), (acceleration.x + gravity.x), acceleration.z + gravity.z);
DisplayServerIOS::get_singleton()->update_magnetometer(-magnetic.y, magnetic.x, magnetic.z);
DisplayServerIOS::get_singleton()->update_gyroscope(-rotation.y, rotation.x, rotation.z);
} break;
case UIInterfaceOrientationLandscapeRight: {
DisplayServerIOS::get_singleton()->update_gravity(gravity.y, -gravity.x, gravity.z);
DisplayServerIOS::get_singleton()->update_accelerometer((acceleration.y + gravity.y), -(acceleration.x + gravity.x), acceleration.z + gravity.z);
DisplayServerIOS::get_singleton()->update_magnetometer(magnetic.y, -magnetic.x, magnetic.z);
DisplayServerIOS::get_singleton()->update_gyroscope(rotation.y, -rotation.x, rotation.z);
} break;
case UIInterfaceOrientationPortraitUpsideDown: {
DisplayServerIOS::get_singleton()->update_gravity(-gravity.x, gravity.y, gravity.z);
DisplayServerIOS::get_singleton()->update_accelerometer(-(acceleration.x + gravity.x), (acceleration.y + gravity.y), acceleration.z + gravity.z);
DisplayServerIOS::get_singleton()->update_magnetometer(-magnetic.x, magnetic.y, magnetic.z);
DisplayServerIOS::get_singleton()->update_gyroscope(-rotation.x, rotation.y, rotation.z);
} break;
default: { // assume portrait
DisplayServerIOS::get_singleton()->update_gravity(gravity.x, gravity.y, gravity.z);
DisplayServerIOS::get_singleton()->update_accelerometer(acceleration.x + gravity.x, acceleration.y + gravity.y, acceleration.z + gravity.z);
DisplayServerIOS::get_singleton()->update_magnetometer(magnetic.x, magnetic.y, magnetic.z);
DisplayServerIOS::get_singleton()->update_gyroscope(rotation.x, rotation.y, rotation.z);
} break;
}
}
@end

View file

@ -0,0 +1,46 @@
/*************************************************************************/
/* godot_view_gesture_recognizer.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
// GLViewGestureRecognizer allows iOS gestures to work correctly by
// emulating UIScrollView's UIScrollViewDelayedTouchesBeganGestureRecognizer.
// It catches all gestures incoming to UIView and delays them for 150ms
// (the same value used by UIScrollViewDelayedTouchesBeganGestureRecognizer)
// If touch cancellation or end message is fired it fires delayed
// begin touch immediately as well as last touch signal
#import <UIKit/UIKit.h>
@interface GodotViewGestureRecognizer : UIGestureRecognizer
@property(nonatomic, readonly, assign) NSTimeInterval delayTimeInterval;
- (instancetype)init;
@end

View file

@ -0,0 +1,186 @@
/*************************************************************************/
/* godot_view_gesture_recognizer.mm */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#import "godot_view_gesture_recognizer.h"
#import "godot_view.h"
#include "core/config/project_settings.h"
// Minimum distance for touches to move to fire
// a delay timer before scheduled time.
// Should be the low enough to not cause issues with dragging
// but big enough to allow click to work.
const CGFloat kGLGestureMovementDistance = 0.5;
@interface GodotViewGestureRecognizer ()
@property(nonatomic, readwrite, assign) NSTimeInterval delayTimeInterval;
@end
@interface GodotViewGestureRecognizer ()
// Timer used to delay begin touch message.
// Should work as simple emulation of UIDelayedAction
@property(strong, nonatomic) NSTimer *delayTimer;
// Delayed touch parameters
@property(strong, nonatomic) NSSet *delayedTouches;
@property(strong, nonatomic) UIEvent *delayedEvent;
@end
@implementation GodotViewGestureRecognizer
- (GodotView *)godotView {
return (GodotView *)self.view;
}
- (instancetype)init {
self = [super init];
self.cancelsTouchesInView = YES;
self.delaysTouchesBegan = YES;
self.delaysTouchesEnded = YES;
self.requiresExclusiveTouchType = NO;
self.delayTimeInterval = GLOBAL_GET("input_devices/pointing/ios/touch_delay");
return self;
}
- (void)dealloc {
if (self.delayTimer) {
[self.delayTimer invalidate];
self.delayTimer = nil;
}
if (self.delayedTouches) {
self.delayedTouches = nil;
}
if (self.delayedEvent) {
self.delayedEvent = nil;
}
}
- (void)delayTouches:(NSSet *)touches andEvent:(UIEvent *)event {
[self.delayTimer fire];
self.delayedTouches = touches;
self.delayedEvent = event;
self.delayTimer = [NSTimer
scheduledTimerWithTimeInterval:self.delayTimeInterval
target:self
selector:@selector(fireDelayedTouches:)
userInfo:nil
repeats:NO];
}
- (void)fireDelayedTouches:(id)timer {
[self.delayTimer invalidate];
self.delayTimer = nil;
if (self.delayedTouches) {
[self.godotView godotTouchesBegan:self.delayedTouches withEvent:self.delayedEvent];
}
self.delayedTouches = nil;
self.delayedEvent = nil;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseBegan];
[self delayTouches:cleared andEvent:event];
[super touchesBegan:touches withEvent:event];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseMoved];
if (self.delayTimer) {
// We should check if movement was significant enough to fire an event
// for dragging to work correctly.
for (UITouch *touch in cleared) {
CGPoint from = [touch locationInView:self.godotView];
CGPoint to = [touch previousLocationInView:self.godotView];
CGFloat xDistance = from.x - to.x;
CGFloat yDistance = from.y - to.y;
CGFloat distance = sqrt(xDistance * xDistance + yDistance * yDistance);
// Early exit, since one of touches has moved enough to fire a drag event.
if (distance > kGLGestureMovementDistance) {
[self.delayTimer fire];
[self.godotView godotTouchesMoved:cleared withEvent:event];
return;
}
}
return;
}
[self.godotView godotTouchesMoved:cleared withEvent:event];
[super touchesMoved:touches withEvent:event];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[self.delayTimer fire];
NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseEnded];
[self.godotView godotTouchesEnded:cleared withEvent:event];
[super touchesEnded:touches withEvent:event];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[self.delayTimer fire];
[self.godotView godotTouchesCancelled:touches withEvent:event];
[super touchesCancelled:touches withEvent:event];
}
- (NSSet *)copyClearedTouches:(NSSet *)touches phase:(UITouchPhase)phaseToSave {
NSMutableSet *cleared = [touches mutableCopy];
for (UITouch *touch in touches) {
if (touch.view != self.view || touch.phase != phaseToSave) {
[cleared removeObject:touch];
}
}
return cleared;
}
@end

View file

@ -0,0 +1,44 @@
/*************************************************************************/
/* godot_view_renderer.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#import <UIKit/UIKit.h>
@protocol GodotViewRendererProtocol <NSObject>
@property(assign, readonly, nonatomic) BOOL hasFinishedSetup;
- (BOOL)setupView:(UIView *)view;
- (void)renderOnView:(UIView *)view;
@end
@interface GodotViewRenderer : NSObject <GodotViewRendererProtocol>
@end

View file

@ -0,0 +1,118 @@
/*************************************************************************/
/* godot_view_renderer.mm */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#import "godot_view_renderer.h"
#include "core/config/project_settings.h"
#include "core/os/keyboard.h"
#import "display_server_ios.h"
#include "main/main.h"
#include "os_ios.h"
#include "servers/audio_server.h"
#import <AudioToolbox/AudioServices.h>
#import <CoreMotion/CoreMotion.h>
#import <GameController/GameController.h>
#import <QuartzCore/QuartzCore.h>
#import <UIKit/UIKit.h>
@interface GodotViewRenderer ()
@property(assign, nonatomic) BOOL hasFinishedProjectDataSetup;
@property(assign, nonatomic) BOOL hasStartedMain;
@property(assign, nonatomic) BOOL hasFinishedSetup;
@end
@implementation GodotViewRenderer
- (BOOL)setupView:(UIView *)view {
if (self.hasFinishedSetup) {
return NO;
}
if (!OS::get_singleton()) {
exit(0);
}
if (!self.hasFinishedProjectDataSetup) {
[self setupProjectData];
return YES;
}
if (!self.hasStartedMain) {
self.hasStartedMain = YES;
OS_IOS::get_singleton()->start();
return YES;
}
self.hasFinishedSetup = YES;
return NO;
}
- (void)setupProjectData {
self.hasFinishedProjectDataSetup = YES;
Main::setup2();
// this might be necessary before here
NSDictionary *dict = [[NSBundle mainBundle] infoDictionary];
for (NSString *key in dict) {
NSObject *value = [dict objectForKey:key];
String ukey = String::utf8([key UTF8String]);
// we need a NSObject to Variant conversor
if ([value isKindOfClass:[NSString class]]) {
NSString *str = (NSString *)value;
String uval = String::utf8([str UTF8String]);
ProjectSettings::get_singleton()->set("Info.plist/" + ukey, uval);
} else if ([value isKindOfClass:[NSNumber class]]) {
NSNumber *n = (NSNumber *)value;
double dval = [n doubleValue];
ProjectSettings::get_singleton()->set("Info.plist/" + ukey, dval);
}
// do stuff
}
}
- (void)renderOnView:(UIView *)view {
if (!OS_IOS::get_singleton()) {
return;
}
OS_IOS::get_singleton()->iterate();
}
@end

61
platform/ios/ios.h Normal file
View file

@ -0,0 +1,61 @@
/*************************************************************************/
/* ios.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#ifndef IOS_H
#define IOS_H
#include "core/object/class_db.h"
#import <CoreHaptics/CoreHaptics.h>
class iOS : public Object {
GDCLASS(iOS, Object);
static void _bind_methods();
private:
CHHapticEngine *haptic_engine API_AVAILABLE(ios(13)) = nullptr;
CHHapticEngine *get_haptic_engine_instance() API_AVAILABLE(ios(13));
void start_haptic_engine();
void stop_haptic_engine();
public:
static void alert(const char *p_alert, const char *p_title);
bool supports_haptic_engine();
void vibrate_haptic_engine(float p_duration_seconds);
String get_model() const;
String get_rate_url(int p_app_id) const;
iOS();
};
#endif

180
platform/ios/ios.mm Normal file
View file

@ -0,0 +1,180 @@
/*************************************************************************/
/* ios.mm */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "ios.h"
#import "app_delegate.h"
#import "view_controller.h"
#import <CoreHaptics/CoreHaptics.h>
#import <UIKit/UIKit.h>
#include <sys/sysctl.h>
void iOS::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_rate_url", "app_id"), &iOS::get_rate_url);
ClassDB::bind_method(D_METHOD("supports_haptic_engine"), &iOS::supports_haptic_engine);
ClassDB::bind_method(D_METHOD("start_haptic_engine"), &iOS::start_haptic_engine);
ClassDB::bind_method(D_METHOD("stop_haptic_engine"), &iOS::stop_haptic_engine);
};
bool iOS::supports_haptic_engine() {
if (@available(iOS 13, *)) {
id<CHHapticDeviceCapability> capabilities = [CHHapticEngine capabilitiesForHardware];
return capabilities.supportsHaptics;
}
return false;
}
CHHapticEngine *iOS::get_haptic_engine_instance() API_AVAILABLE(ios(13)) {
if (haptic_engine == nullptr) {
NSError *error = nullptr;
haptic_engine = [[CHHapticEngine alloc] initAndReturnError:&error];
if (!error) {
[haptic_engine setAutoShutdownEnabled:true];
} else {
haptic_engine = nullptr;
NSLog(@"Could not initialize haptic engine: %@", error);
}
}
return haptic_engine;
}
void iOS::vibrate_haptic_engine(float p_duration_seconds) API_AVAILABLE(ios(13)) {
if (@available(iOS 13, *)) { // We need the @available check every time to make the compiler happy...
if (supports_haptic_engine()) {
CHHapticEngine *haptic_engine = get_haptic_engine_instance();
if (haptic_engine) {
NSDictionary *hapticDict = @{
CHHapticPatternKeyPattern : @[
@{CHHapticPatternKeyEvent : @{
CHHapticPatternKeyEventType : CHHapticEventTypeHapticContinuous,
CHHapticPatternKeyTime : @(CHHapticTimeImmediate),
CHHapticPatternKeyEventDuration : @(p_duration_seconds)
},
},
],
};
NSError *error;
CHHapticPattern *pattern = [[CHHapticPattern alloc] initWithDictionary:hapticDict error:&error];
[[haptic_engine createPlayerWithPattern:pattern error:&error] startAtTime:0 error:&error];
NSLog(@"Could not vibrate using haptic engine: %@", error);
}
return;
}
}
NSLog(@"Haptic engine is not supported in this version of iOS");
}
void iOS::start_haptic_engine() {
if (@available(iOS 13, *)) {
if (supports_haptic_engine()) {
CHHapticEngine *haptic_engine = get_haptic_engine_instance();
if (haptic_engine) {
[haptic_engine startWithCompletionHandler:^(NSError *returnedError) {
if (returnedError) {
NSLog(@"Could not start haptic engine: %@", returnedError);
}
}];
}
return;
}
}
NSLog(@"Haptic engine is not supported in this version of iOS");
}
void iOS::stop_haptic_engine() {
if (@available(iOS 13, *)) {
if (supports_haptic_engine()) {
CHHapticEngine *haptic_engine = get_haptic_engine_instance();
if (haptic_engine) {
[haptic_engine stopWithCompletionHandler:^(NSError *returnedError) {
if (returnedError) {
NSLog(@"Could not stop haptic engine: %@", returnedError);
}
}];
}
return;
}
}
NSLog(@"Haptic engine is not supported in this version of iOS");
}
void iOS::alert(const char *p_alert, const char *p_title) {
NSString *title = [NSString stringWithUTF8String:p_title];
NSString *message = [NSString stringWithUTF8String:p_alert];
UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *button = [UIAlertAction actionWithTitle:@"OK"
style:UIAlertActionStyleCancel
handler:^(id){
}];
[alert addAction:button];
[AppDelegate.viewController presentViewController:alert animated:YES completion:nil];
}
String iOS::get_model() const {
// [[UIDevice currentDevice] model] only returns "iPad" or "iPhone".
size_t size;
sysctlbyname("hw.machine", nullptr, &size, nullptr, 0);
char *model = (char *)malloc(size);
if (model == nullptr) {
return "";
}
sysctlbyname("hw.machine", model, &size, nullptr, 0);
NSString *platform = [NSString stringWithCString:model encoding:NSUTF8StringEncoding];
free(model);
const char *str = [platform UTF8String];
return String::utf8(str != nullptr ? str : "");
}
String iOS::get_rate_url(int p_app_id) const {
String app_url_path = "itms-apps://itunes.apple.com/app/idAPP_ID";
String ret = app_url_path.replace("APP_ID", String::num(p_app_id));
printf("returning rate url %s\n", ret.utf8().get_data());
return ret;
}
iOS::iOS() {}

50
platform/ios/joypad_ios.h Normal file
View file

@ -0,0 +1,50 @@
/*************************************************************************/
/* joypad_ios.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#import <GameController/GameController.h>
@interface JoypadIOSObserver : NSObject
- (void)startObserving;
- (void)startProcessing;
- (void)finishObserving;
@end
class JoypadIOS {
private:
JoypadIOSObserver *observer;
public:
JoypadIOS();
~JoypadIOS();
void start_processing();
};

344
platform/ios/joypad_ios.mm Normal file
View file

@ -0,0 +1,344 @@
/*************************************************************************/
/* joypad_ios.mm */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#import "joypad_ios.h"
#include "core/config/project_settings.h"
#include "drivers/coreaudio/audio_driver_coreaudio.h"
#include "main/main.h"
#import "godot_view.h"
#include "os_ios.h"
JoypadIOS::JoypadIOS() {
observer = [[JoypadIOSObserver alloc] init];
[observer startObserving];
}
JoypadIOS::~JoypadIOS() {
if (observer) {
[observer finishObserving];
observer = nil;
}
}
void JoypadIOS::start_processing() {
if (observer) {
[observer startProcessing];
}
}
@interface JoypadIOSObserver ()
@property(assign, nonatomic) BOOL isObserving;
@property(assign, nonatomic) BOOL isProcessing;
@property(strong, nonatomic) NSMutableDictionary *connectedJoypads;
@property(strong, nonatomic) NSMutableArray *joypadsQueue;
@end
@implementation JoypadIOSObserver
- (instancetype)init {
self = [super init];
if (self) {
[self godot_commonInit];
}
return self;
}
- (void)godot_commonInit {
self.isObserving = NO;
self.isProcessing = NO;
}
- (void)startProcessing {
self.isProcessing = YES;
for (GCController *controller in self.joypadsQueue) {
[self addiOSJoypad:controller];
}
[self.joypadsQueue removeAllObjects];
}
- (void)startObserving {
if (self.isObserving) {
return;
}
self.isObserving = YES;
self.connectedJoypads = [NSMutableDictionary dictionary];
self.joypadsQueue = [NSMutableArray array];
// get told when controllers connect, this will be called right away for
// already connected controllers
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(controllerWasConnected:)
name:GCControllerDidConnectNotification
object:nil];
// get told when controllers disconnect
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(controllerWasDisconnected:)
name:GCControllerDidDisconnectNotification
object:nil];
}
- (void)finishObserving {
if (self.isObserving) {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
self.isObserving = NO;
self.isProcessing = NO;
self.connectedJoypads = nil;
self.joypadsQueue = nil;
}
- (void)dealloc {
[self finishObserving];
}
- (int)getJoyIdForController:(GCController *)controller {
NSArray *keys = [self.connectedJoypads allKeysForObject:controller];
for (NSNumber *key in keys) {
int joy_id = [key intValue];
return joy_id;
}
return -1;
}
- (void)addiOSJoypad:(GCController *)controller {
// get a new id for our controller
int joy_id = Input::get_singleton()->get_unused_joy_id();
if (joy_id == -1) {
printf("Couldn't retrieve new joy id\n");
return;
}
// assign our player index
if (controller.playerIndex == GCControllerPlayerIndexUnset) {
controller.playerIndex = [self getFreePlayerIndex];
}
// tell Godot about our new controller
Input::get_singleton()->joy_connection_changed(joy_id, true, String::utf8([controller.vendorName UTF8String]));
// add it to our dictionary, this will retain our controllers
[self.connectedJoypads setObject:controller forKey:[NSNumber numberWithInt:joy_id]];
// set our input handler
[self setControllerInputHandler:controller];
}
- (void)controllerWasConnected:(NSNotification *)notification {
// get our controller
GCController *controller = (GCController *)notification.object;
if (!controller) {
printf("Couldn't retrieve new controller\n");
return;
}
if ([[self.connectedJoypads allKeysForObject:controller] count] > 0) {
printf("Controller is already registered\n");
} else if (!self.isProcessing) {
[self.joypadsQueue addObject:controller];
} else {
[self addiOSJoypad:controller];
}
}
- (void)controllerWasDisconnected:(NSNotification *)notification {
// find our joystick, there should be only one in our dictionary
GCController *controller = (GCController *)notification.object;
if (!controller) {
return;
}
NSArray *keys = [self.connectedJoypads allKeysForObject:controller];
for (NSNumber *key in keys) {
// tell Godot this joystick is no longer there
int joy_id = [key intValue];
Input::get_singleton()->joy_connection_changed(joy_id, false, "");
// and remove it from our dictionary
[self.connectedJoypads removeObjectForKey:key];
}
}
- (GCControllerPlayerIndex)getFreePlayerIndex {
bool have_player_1 = false;
bool have_player_2 = false;
bool have_player_3 = false;
bool have_player_4 = false;
if (self.connectedJoypads == nil) {
NSArray *keys = [self.connectedJoypads allKeys];
for (NSNumber *key in keys) {
GCController *controller = [self.connectedJoypads objectForKey:key];
if (controller.playerIndex == GCControllerPlayerIndex1) {
have_player_1 = true;
} else if (controller.playerIndex == GCControllerPlayerIndex2) {
have_player_2 = true;
} else if (controller.playerIndex == GCControllerPlayerIndex3) {
have_player_3 = true;
} else if (controller.playerIndex == GCControllerPlayerIndex4) {
have_player_4 = true;
}
}
}
if (!have_player_1) {
return GCControllerPlayerIndex1;
} else if (!have_player_2) {
return GCControllerPlayerIndex2;
} else if (!have_player_3) {
return GCControllerPlayerIndex3;
} else if (!have_player_4) {
return GCControllerPlayerIndex4;
} else {
return GCControllerPlayerIndexUnset;
}
}
- (void)setControllerInputHandler:(GCController *)controller {
// Hook in the callback handler for the correct gamepad profile.
// This is a bit of a weird design choice on Apples part.
// You need to select the most capable gamepad profile for the
// gamepad attached.
if (controller.extendedGamepad != nil) {
// The extended gamepad profile has all the input you could possibly find on
// a gamepad but will only be active if your gamepad actually has all of
// these...
_weakify(self);
_weakify(controller);
controller.extendedGamepad.valueChangedHandler = ^(GCExtendedGamepad *gamepad, GCControllerElement *element) {
_strongify(self);
_strongify(controller);
int joy_id = [self getJoyIdForController:controller];
if (element == gamepad.buttonA) {
Input::get_singleton()->joy_button(joy_id, JoyButton::A,
gamepad.buttonA.isPressed);
} else if (element == gamepad.buttonB) {
Input::get_singleton()->joy_button(joy_id, JoyButton::B,
gamepad.buttonB.isPressed);
} else if (element == gamepad.buttonX) {
Input::get_singleton()->joy_button(joy_id, JoyButton::X,
gamepad.buttonX.isPressed);
} else if (element == gamepad.buttonY) {
Input::get_singleton()->joy_button(joy_id, JoyButton::Y,
gamepad.buttonY.isPressed);
} else if (element == gamepad.leftShoulder) {
Input::get_singleton()->joy_button(joy_id, JoyButton::LEFT_SHOULDER,
gamepad.leftShoulder.isPressed);
} else if (element == gamepad.rightShoulder) {
Input::get_singleton()->joy_button(joy_id, JoyButton::RIGHT_SHOULDER,
gamepad.rightShoulder.isPressed);
} else if (element == gamepad.dpad) {
Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_UP,
gamepad.dpad.up.isPressed);
Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_DOWN,
gamepad.dpad.down.isPressed);
Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_LEFT,
gamepad.dpad.left.isPressed);
Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_RIGHT,
gamepad.dpad.right.isPressed);
}
if (element == gamepad.leftThumbstick) {
float value = gamepad.leftThumbstick.xAxis.value;
Input::get_singleton()->joy_axis(joy_id, JoyAxis::LEFT_X, value);
value = -gamepad.leftThumbstick.yAxis.value;
Input::get_singleton()->joy_axis(joy_id, JoyAxis::LEFT_Y, value);
} else if (element == gamepad.rightThumbstick) {
float value = gamepad.rightThumbstick.xAxis.value;
Input::get_singleton()->joy_axis(joy_id, JoyAxis::RIGHT_X, value);
value = -gamepad.rightThumbstick.yAxis.value;
Input::get_singleton()->joy_axis(joy_id, JoyAxis::RIGHT_Y, value);
} else if (element == gamepad.leftTrigger) {
float value = gamepad.leftTrigger.value;
Input::get_singleton()->joy_axis(joy_id, JoyAxis::TRIGGER_LEFT, value);
} else if (element == gamepad.rightTrigger) {
float value = gamepad.rightTrigger.value;
Input::get_singleton()->joy_axis(joy_id, JoyAxis::TRIGGER_RIGHT, value);
}
};
} else if (controller.microGamepad != nil) {
// micro gamepads were added in OS 9 and feature just 2 buttons and a d-pad
_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, JoyButton::A,
gamepad.buttonA.isPressed);
} else if (element == gamepad.buttonX) {
Input::get_singleton()->joy_button(joy_id, JoyButton::X,
gamepad.buttonX.isPressed);
} else if (element == gamepad.dpad) {
Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_UP,
gamepad.dpad.up.isPressed);
Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_DOWN,
gamepad.dpad.down.isPressed);
Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_LEFT, gamepad.dpad.left.isPressed);
Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_RIGHT, gamepad.dpad.right.isPressed);
}
};
}
///@TODO need to add support for controller.motion which gives us access to
/// the orientation of the device (if supported)
///@TODO need to add support for controllerPausedHandler which should be a
/// toggle
}
@end

View file

@ -0,0 +1,37 @@
/*************************************************************************/
/* keyboard_input_view.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#import <UIKit/UIKit.h>
@interface GodotKeyboardInputView : UITextView
- (BOOL)becomeFirstResponderWithString:(NSString *)existingString multiline:(BOOL)flag cursorStart:(NSInteger)start cursorEnd:(NSInteger)end;
@end

View file

@ -0,0 +1,197 @@
/*************************************************************************/
/* keyboard_input_view.mm */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#import "keyboard_input_view.h"
#include "core/os/keyboard.h"
#include "display_server_ios.h"
#include "os_ios.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;
NSInteger safeStartIndex = MAX(start, 0);
NSRange textRange;
// Either a simple cursor or a selection.
if (end > 0) {
textRange = NSMakeRange(safeStartIndex, end - start);
} else {
textRange = NSMakeRange(safeStartIndex, 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++) {
DisplayServerIOS::get_singleton()->key(Key::BACKSPACE, true);
DisplayServerIOS::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 = (int)Key::ENTER;
break;
case 8198:
character = (int)Key::SPACE;
break;
default:
break;
}
DisplayServerIOS::get_singleton()->key((Key)character, true);
DisplayServerIOS::get_singleton()->key((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

BIN
platform/ios/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

56
platform/ios/main.m Normal file
View file

@ -0,0 +1,56 @@
/*************************************************************************/
/* main.m */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#import "godot_app_delegate.h"
#import <UIKit/UIKit.h>
#include <stdio.h>
int gargc;
char **gargv;
int main(int argc, char *argv[]) {
#if defined(VULKAN_ENABLED)
//MoltenVK - enable full component swizzling support
setenv("MVK_CONFIG_FULL_IMAGE_VIEW_SWIZZLE", "1", 1);
#endif
printf("*********** main.m\n");
gargc = argc;
gargv = argv;
printf("running app main\n");
@autoreleasepool {
NSString *className = NSStringFromClass([GodotApplicalitionDelegate class]);
UIApplicationMain(argc, argv, nil, className);
}
printf("main done\n");
return 0;
}

124
platform/ios/os_ios.h Normal file
View file

@ -0,0 +1,124 @@
/*************************************************************************/
/* os_ios.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#ifdef IOS_ENABLED
#ifndef OS_IOS_H
#define OS_IOS_H
#include "drivers/coreaudio/audio_driver_coreaudio.h"
#include "drivers/unix/os_unix.h"
#include "ios.h"
#include "joypad_ios.h"
#include "servers/audio_server.h"
#include "servers/rendering/renderer_compositor.h"
#if defined(VULKAN_ENABLED)
#include "drivers/vulkan/rendering_device_vulkan.h"
#include "platform/ios/vulkan_context_ios.h"
#endif
class OS_IOS : public OS_Unix {
private:
static HashMap<String, void *> dynamic_symbol_lookup_table;
friend void register_dynamic_symbol(char *name, void *address);
AudioDriverCoreAudio audio_driver;
iOS *ios = nullptr;
JoypadIOS *joypad_ios = nullptr;
MainLoop *main_loop = nullptr;
virtual void initialize_core() override;
virtual void initialize() override;
virtual void initialize_joypads() override {
}
virtual void set_main_loop(MainLoop *p_main_loop) override;
virtual MainLoop *get_main_loop() const override;
virtual void delete_main_loop() override;
virtual void finalize() override;
String user_data_dir;
String cache_dir;
bool is_focused = false;
void deinitialize_modules();
public:
static OS_IOS *get_singleton();
OS_IOS(String p_data_dir, String p_cache_dir);
~OS_IOS();
void initialize_modules();
bool iterate();
void start();
virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override;
virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = nullptr) override;
virtual Error close_dynamic_library(void *p_library_handle) override;
virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional = false) override;
virtual String get_name() const override;
virtual String get_model_name() const override;
virtual Error shell_open(String p_uri) override;
void set_user_data_dir(String p_dir);
virtual String get_user_data_dir() const override;
virtual String get_cache_path() const override;
virtual String get_locale() const override;
virtual String get_unique_id() const override;
virtual String get_processor_name() const override;
virtual void vibrate_handheld(int p_duration_ms = 500) override;
virtual bool _check_internal_feature_support(const String &p_feature) override;
void on_focus_out();
void on_focus_in();
};
#endif // OS_IOS_H
#endif // IOS_ENABLED

346
platform/ios/os_ios.mm Normal file
View file

@ -0,0 +1,346 @@
/*************************************************************************/
/* os_ios.mm */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#ifdef IOS_ENABLED
#include "os_ios.h"
#import "app_delegate.h"
#include "core/config/project_settings.h"
#include "core/io/dir_access.h"
#include "core/io/file_access.h"
#include "core/io/file_access_pack.h"
#include "display_server_ios.h"
#include "drivers/unix/syslog_logger.h"
#import "godot_view.h"
#include "main/main.h"
#import "view_controller.h"
#import <AudioToolbox/AudioServices.h>
#import <UIKit/UIKit.h>
#import <dlfcn.h>
#include <sys/sysctl.h>
#if defined(VULKAN_ENABLED)
#include "servers/rendering/renderer_rd/renderer_compositor_rd.h"
#import <QuartzCore/CAMetalLayer.h>
#ifdef USE_VOLK
#include <volk.h>
#else
#include <vulkan/vulkan.h>
#endif
#endif
// Initialization order between compilation units is not guaranteed,
// so we use this as a hack to ensure certain code is called before
// everything else, but after all units are initialized.
typedef void (*init_callback)();
static init_callback *ios_init_callbacks = nullptr;
static int ios_init_callbacks_count = 0;
static int ios_init_callbacks_capacity = 0;
HashMap<String, void *> OS_IOS::dynamic_symbol_lookup_table;
void add_ios_init_callback(init_callback cb) {
if (ios_init_callbacks_count == ios_init_callbacks_capacity) {
void *new_ptr = realloc(ios_init_callbacks, sizeof(cb) * 32);
if (new_ptr) {
ios_init_callbacks = (init_callback *)(new_ptr);
ios_init_callbacks_capacity += 32;
}
}
if (ios_init_callbacks_capacity > ios_init_callbacks_count) {
ios_init_callbacks[ios_init_callbacks_count] = cb;
++ios_init_callbacks_count;
}
}
void register_dynamic_symbol(char *name, void *address) {
OS_IOS::dynamic_symbol_lookup_table[String(name)] = address;
}
OS_IOS *OS_IOS::get_singleton() {
return (OS_IOS *)OS::get_singleton();
}
OS_IOS::OS_IOS(String p_data_dir, String p_cache_dir) {
for (int i = 0; i < ios_init_callbacks_count; ++i) {
ios_init_callbacks[i]();
}
free(ios_init_callbacks);
ios_init_callbacks = nullptr;
ios_init_callbacks_count = 0;
ios_init_callbacks_capacity = 0;
main_loop = nullptr;
// can't call set_data_dir from here, since it requires DirAccess
// which is initialized in initialize_core
user_data_dir = p_data_dir;
cache_dir = p_cache_dir;
Vector<Logger *> loggers;
loggers.push_back(memnew(SyslogLogger));
#ifdef DEBUG_ENABLED
// it seems iOS app's stdout/stderr is only obtainable if you launch it from
// Xcode
loggers.push_back(memnew(StdLogger));
#endif
_set_logger(memnew(CompositeLogger(loggers)));
AudioDriverManager::add_driver(&audio_driver);
DisplayServerIOS::register_ios_driver();
}
OS_IOS::~OS_IOS() {}
void OS_IOS::alert(const String &p_alert, const String &p_title) {
const CharString utf8_alert = p_alert.utf8();
const CharString utf8_title = p_title.utf8();
iOS::alert(utf8_alert.get_data(), utf8_title.get_data());
}
void OS_IOS::initialize_core() {
OS_Unix::initialize_core();
set_user_data_dir(user_data_dir);
}
void OS_IOS::initialize() {
initialize_core();
}
void OS_IOS::initialize_modules() {
ios = memnew(iOS);
Engine::get_singleton()->add_singleton(Engine::Singleton("iOS", ios));
joypad_ios = memnew(JoypadIOS);
}
void OS_IOS::deinitialize_modules() {
if (joypad_ios) {
memdelete(joypad_ios);
}
if (ios) {
memdelete(ios);
}
}
void OS_IOS::set_main_loop(MainLoop *p_main_loop) {
main_loop = p_main_loop;
if (main_loop) {
main_loop->initialize();
}
}
MainLoop *OS_IOS::get_main_loop() const {
return main_loop;
}
void OS_IOS::delete_main_loop() {
if (main_loop) {
main_loop->finalize();
memdelete(main_loop);
}
main_loop = nullptr;
}
bool OS_IOS::iterate() {
if (!main_loop) {
return true;
}
if (DisplayServer::get_singleton()) {
DisplayServer::get_singleton()->process_events();
}
return Main::iteration();
}
void OS_IOS::start() {
Main::start();
if (joypad_ios) {
joypad_ios->start_processing();
}
}
void OS_IOS::finalize() {
deinitialize_modules();
// Already gets called
//delete_main_loop();
}
// MARK: Dynamic Libraries
Error OS_IOS::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path, String *r_resolved_path) {
if (p_path.length() == 0) {
p_library_handle = RTLD_SELF;
if (r_resolved_path != nullptr) {
*r_resolved_path = p_path;
}
return OK;
}
return OS_Unix::open_dynamic_library(p_path, p_library_handle, p_also_set_library_path, r_resolved_path);
}
Error OS_IOS::close_dynamic_library(void *p_library_handle) {
if (p_library_handle == RTLD_SELF) {
return OK;
}
return OS_Unix::close_dynamic_library(p_library_handle);
}
Error OS_IOS::get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional) {
if (p_library_handle == RTLD_SELF) {
void **ptr = OS_IOS::dynamic_symbol_lookup_table.getptr(p_name);
if (ptr) {
p_symbol_handle = *ptr;
return OK;
}
}
return OS_Unix::get_dynamic_library_symbol_handle(p_library_handle, p_name, p_symbol_handle, p_optional);
}
String OS_IOS::get_name() const {
return "iOS";
}
String OS_IOS::get_model_name() const {
String model = ios->get_model();
if (model != "") {
return model;
}
return OS_Unix::get_model_name();
}
Error OS_IOS::shell_open(String p_uri) {
NSString *urlPath = [[NSString alloc] initWithUTF8String:p_uri.utf8().get_data()];
NSURL *url = [NSURL URLWithString:urlPath];
if (![[UIApplication sharedApplication] canOpenURL:url]) {
return ERR_CANT_OPEN;
}
printf("opening url %s\n", p_uri.utf8().get_data());
[[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
return OK;
}
void OS_IOS::set_user_data_dir(String p_dir) {
Ref<DirAccess> da = DirAccess::open(p_dir);
user_data_dir = da->get_current_dir();
printf("setting data dir to %s from %s\n", user_data_dir.utf8().get_data(), p_dir.utf8().get_data());
}
String OS_IOS::get_user_data_dir() const {
return user_data_dir;
}
String OS_IOS::get_cache_path() const {
return cache_dir;
}
String OS_IOS::get_locale() const {
NSString *preferedLanguage = [NSLocale preferredLanguages].firstObject;
if (preferedLanguage) {
return String::utf8([preferedLanguage UTF8String]).replace("-", "_");
}
NSString *localeIdentifier = [[NSLocale currentLocale] localeIdentifier];
return String::utf8([localeIdentifier UTF8String]).replace("-", "_");
}
String OS_IOS::get_unique_id() const {
NSString *uuid = [UIDevice currentDevice].identifierForVendor.UUIDString;
return String::utf8([uuid UTF8String]);
}
String OS_IOS::get_processor_name() const {
char buffer[256];
size_t buffer_len = 256;
if (sysctlbyname("machdep.cpu.brand_string", &buffer, &buffer_len, NULL, 0) == 0) {
return String::utf8(buffer, buffer_len);
}
ERR_FAIL_V_MSG("", String("Couldn't get the CPU model name. Returning an empty string."));
}
void OS_IOS::vibrate_handheld(int p_duration_ms) {
if (ios->supports_haptic_engine()) {
ios->vibrate_haptic_engine((float)p_duration_ms / 1000.f);
} else {
// iOS <13 does not support duration for vibration
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
}
}
bool OS_IOS::_check_internal_feature_support(const String &p_feature) {
return p_feature == "mobile";
}
void OS_IOS::on_focus_out() {
if (is_focused) {
is_focused = false;
if (DisplayServerIOS::get_singleton()) {
DisplayServerIOS::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_FOCUS_OUT);
}
[AppDelegate.viewController.godotView stopRendering];
audio_driver.stop();
}
}
void OS_IOS::on_focus_in() {
if (!is_focused) {
is_focused = true;
if (DisplayServerIOS::get_singleton()) {
DisplayServerIOS::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_FOCUS_IN);
}
[AppDelegate.viewController.godotView startRendering];
audio_driver.start();
}
}
#endif // IOS_ENABLED

View file

@ -0,0 +1,44 @@
/*************************************************************************/
/* platform_config.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include <alloca.h>
#define OPENGL_INCLUDE_H <ES3/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")

63
platform/ios/tts_ios.h Normal file
View file

@ -0,0 +1,63 @@
/*************************************************************************/
/* tts_ios.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#ifndef TTS_IOS_H
#define TTS_IOS_H
#if __has_include(<AVFAudio/AVSpeechSynthesis.h>)
#import <AVFAudio/AVSpeechSynthesis.h>
#else
#import <AVFoundation/AVFoundation.h>
#endif
#include "core/string/ustring.h"
#include "core/templates/list.h"
#include "core/templates/rb_map.h"
#include "core/variant/array.h"
#include "servers/display_server.h"
@interface TTS_IOS : NSObject <AVSpeechSynthesizerDelegate> {
bool speaking;
HashMap<id, int> ids;
AVSpeechSynthesizer *av_synth;
List<DisplayServer::TTSUtterance> queue;
}
- (void)pauseSpeaking;
- (void)resumeSpeaking;
- (void)stopSpeaking;
- (bool)isSpeaking;
- (bool)isPaused;
- (void)speak:(const String &)text voice:(const String &)voice volume:(int)volume pitch:(float)pitch rate:(float)rate utterance_id:(int)utterance_id interrupt:(bool)interrupt;
- (Array)getVoices;
@end
#endif // TTS_IOS_H

164
platform/ios/tts_ios.mm Normal file
View file

@ -0,0 +1,164 @@
/*************************************************************************/
/* tts_ios.mm */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "tts_ios.h"
@implementation TTS_IOS
- (id)init {
self = [super init];
self->speaking = false;
self->av_synth = [[AVSpeechSynthesizer alloc] init];
[self->av_synth setDelegate:self];
print_verbose("Text-to-Speech: AVSpeechSynthesizer initialized.");
return self;
}
- (void)speechSynthesizer:(AVSpeechSynthesizer *)av_synth willSpeakRangeOfSpeechString:(NSRange)characterRange utterance:(AVSpeechUtterance *)utterance {
NSString *string = [utterance speechString];
// Convert from UTF-16 to UTF-32 position.
int pos = 0;
for (NSUInteger i = 0; i < MIN(characterRange.location, string.length); i++) {
unichar c = [string characterAtIndex:i];
if ((c & 0xfffffc00) == 0xd800) {
i++;
}
pos++;
}
DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_BOUNDARY, ids[utterance], pos);
}
- (void)speechSynthesizer:(AVSpeechSynthesizer *)av_synth didCancelSpeechUtterance:(AVSpeechUtterance *)utterance {
DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, ids[utterance]);
ids.erase(utterance);
speaking = false;
[self update];
}
- (void)speechSynthesizer:(AVSpeechSynthesizer *)av_synth didFinishSpeechUtterance:(AVSpeechUtterance *)utterance {
DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_ENDED, ids[utterance]);
ids.erase(utterance);
speaking = false;
[self update];
}
- (void)update {
if (!speaking && queue.size() > 0) {
DisplayServer::TTSUtterance &message = queue.front()->get();
AVSpeechUtterance *new_utterance = [[AVSpeechUtterance alloc] initWithString:[NSString stringWithUTF8String:message.text.utf8().get_data()]];
[new_utterance setVoice:[AVSpeechSynthesisVoice voiceWithIdentifier:[NSString stringWithUTF8String:message.voice.utf8().get_data()]]];
if (message.rate > 1.f) {
[new_utterance setRate:Math::range_lerp(message.rate, 1.f, 10.f, AVSpeechUtteranceDefaultSpeechRate, AVSpeechUtteranceMaximumSpeechRate)];
} else if (message.rate < 1.f) {
[new_utterance setRate:Math::range_lerp(message.rate, 0.1f, 1.f, AVSpeechUtteranceMinimumSpeechRate, AVSpeechUtteranceDefaultSpeechRate)];
}
[new_utterance setPitchMultiplier:message.pitch];
[new_utterance setVolume:(Math::range_lerp(message.volume, 0.f, 100.f, 0.f, 1.f))];
ids[new_utterance] = message.id;
[av_synth speakUtterance:new_utterance];
queue.pop_front();
DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_STARTED, message.id);
speaking = true;
}
}
- (void)pauseSpeaking {
[av_synth pauseSpeakingAtBoundary:AVSpeechBoundaryImmediate];
}
- (void)resumeSpeaking {
[av_synth continueSpeaking];
}
- (void)stopSpeaking {
for (DisplayServer::TTSUtterance &message : queue) {
DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, message.id);
}
queue.clear();
[av_synth stopSpeakingAtBoundary:AVSpeechBoundaryImmediate];
speaking = false;
}
- (bool)isSpeaking {
return speaking || (queue.size() > 0);
}
- (bool)isPaused {
return [av_synth isPaused];
}
- (void)speak:(const String &)text voice:(const String &)voice volume:(int)volume pitch:(float)pitch rate:(float)rate utterance_id:(int)utterance_id interrupt:(bool)interrupt {
if (interrupt) {
[self stopSpeaking];
}
if (text.is_empty()) {
DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, utterance_id);
return;
}
DisplayServer::TTSUtterance message;
message.text = text;
message.voice = voice;
message.volume = CLAMP(volume, 0, 100);
message.pitch = CLAMP(pitch, 0.f, 2.f);
message.rate = CLAMP(rate, 0.1f, 10.f);
message.id = utterance_id;
queue.push_back(message);
if ([self isPaused]) {
[self resumeSpeaking];
} else {
[self update];
}
}
- (Array)getVoices {
Array list;
for (AVSpeechSynthesisVoice *voice in [AVSpeechSynthesisVoice speechVoices]) {
NSString *voiceIdentifierString = [voice identifier];
NSString *voiceLocaleIdentifier = [voice language];
NSString *voiceName = [voice name];
Dictionary voice_d;
voice_d["name"] = String::utf8([voiceName UTF8String]);
voice_d["id"] = String::utf8([voiceIdentifierString UTF8String]);
voice_d["language"] = String::utf8([voiceLocaleIdentifier UTF8String]);
list.push_back(voice_d);
}
return list;
}
@end

View file

@ -0,0 +1,42 @@
/*************************************************************************/
/* view_controller.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#import <UIKit/UIKit.h>
@class GodotView;
@class GodotNativeVideoView;
@class GodotKeyboardInputView;
@interface ViewController : UIViewController
@property(nonatomic, readonly, strong) GodotView *godotView;
@property(nonatomic, readonly, strong) GodotKeyboardInputView *keyboardView;
@end

View file

@ -0,0 +1,240 @@
/*************************************************************************/
/* view_controller.mm */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#import "view_controller.h"
#include "core/config/project_settings.h"
#include "display_server_ios.h"
#import "godot_view.h"
#import "godot_view_renderer.h"
#import "keyboard_input_view.h"
#include "os_ios.h"
#import <AVFoundation/AVFoundation.h>
#import <GameController/GameController.h>
@interface ViewController () <GodotViewDelegate>
@property(strong, nonatomic) GodotViewRenderer *renderer;
@property(strong, nonatomic) GodotKeyboardInputView *keyboardView;
@property(strong, nonatomic) UIView *godotLoadingOverlay;
@end
@implementation ViewController
- (GodotView *)godotView {
return (GodotView *)self.view;
}
- (void)loadView {
GodotView *view = [[GodotView alloc] init];
GodotViewRenderer *renderer = [[GodotViewRenderer alloc] init];
self.renderer = renderer;
self.view = view;
view.renderer = self.renderer;
view.delegate = self;
}
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
[self godot_commonInit];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
[self godot_commonInit];
}
return self;
}
- (void)godot_commonInit {
// Initialize view controller values.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
printf("*********** did receive memory warning!\n");
}
- (void)viewDidLoad {
[super viewDidLoad];
[self observeKeyboard];
[self displayLoadingOverlay];
if (@available(iOS 11.0, *)) {
[self setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
}
}
- (void)observeKeyboard {
printf("******** setting up keyboard input view\n");
self.keyboardView = [GodotKeyboardInputView new];
[self.view addSubview:self.keyboardView];
printf("******** adding observer for keyboard show/hide\n");
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(keyboardOnScreen:)
name:UIKeyboardDidShowNotification
object:nil];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(keyboardHidden:)
name:UIKeyboardDidHideNotification
object:nil];
}
- (void)displayLoadingOverlay {
NSBundle *bundle = [NSBundle mainBundle];
NSString *storyboardName = @"Launch Screen";
if ([bundle pathForResource:storyboardName ofType:@"storyboardc"] == nil) {
return;
}
UIStoryboard *launchStoryboard = [UIStoryboard storyboardWithName:storyboardName bundle:bundle];
UIViewController *controller = [launchStoryboard instantiateInitialViewController];
self.godotLoadingOverlay = controller.view;
self.godotLoadingOverlay.frame = self.view.bounds;
self.godotLoadingOverlay.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
[self.view addSubview:self.godotLoadingOverlay];
}
- (BOOL)godotViewFinishedSetup:(GodotView *)view {
[self.godotLoadingOverlay removeFromSuperview];
self.godotLoadingOverlay = nil;
return YES;
}
- (void)dealloc {
self.keyboardView = nil;
self.renderer = nil;
if (self.godotLoadingOverlay) {
[self.godotLoadingOverlay removeFromSuperview];
self.godotLoadingOverlay = nil;
}
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
// MARK: Orientation
- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures {
return UIRectEdgeAll;
}
- (BOOL)shouldAutorotate {
if (!DisplayServerIOS::get_singleton()) {
return NO;
}
switch (DisplayServerIOS::get_singleton()->screen_get_orientation(DisplayServer::SCREEN_OF_MAIN_WINDOW)) {
case DisplayServer::SCREEN_SENSOR:
case DisplayServer::SCREEN_SENSOR_LANDSCAPE:
case DisplayServer::SCREEN_SENSOR_PORTRAIT:
return YES;
default:
return NO;
}
}
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
if (!DisplayServerIOS::get_singleton()) {
return UIInterfaceOrientationMaskAll;
}
switch (DisplayServerIOS::get_singleton()->screen_get_orientation(DisplayServer::SCREEN_OF_MAIN_WINDOW)) {
case DisplayServer::SCREEN_PORTRAIT:
return UIInterfaceOrientationMaskPortrait;
case DisplayServer::SCREEN_REVERSE_LANDSCAPE:
return UIInterfaceOrientationMaskLandscapeRight;
case DisplayServer::SCREEN_REVERSE_PORTRAIT:
return UIInterfaceOrientationMaskPortraitUpsideDown;
case DisplayServer::SCREEN_SENSOR_LANDSCAPE:
return UIInterfaceOrientationMaskLandscape;
case DisplayServer::SCREEN_SENSOR_PORTRAIT:
return UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown;
case DisplayServer::SCREEN_SENSOR:
return UIInterfaceOrientationMaskAll;
case DisplayServer::SCREEN_LANDSCAPE:
return UIInterfaceOrientationMaskLandscapeLeft;
}
}
- (BOOL)prefersStatusBarHidden {
return YES;
}
- (BOOL)prefersHomeIndicatorAutoHidden {
if (GLOBAL_GET("display/window/ios/hide_home_indicator")) {
return YES;
} else {
return NO;
}
}
// MARK: Keyboard
- (void)keyboardOnScreen:(NSNotification *)notification {
NSDictionary *info = notification.userInfo;
NSValue *value = info[UIKeyboardFrameEndUserInfoKey];
CGRect rawFrame = [value CGRectValue];
CGRect keyboardFrame = [self.view convertRect:rawFrame fromView:nil];
if (DisplayServerIOS::get_singleton()) {
DisplayServerIOS::get_singleton()->virtual_keyboard_set_height(keyboardFrame.size.height);
}
}
- (void)keyboardHidden:(NSNotification *)notification {
if (DisplayServerIOS::get_singleton()) {
DisplayServerIOS::get_singleton()->virtual_keyboard_set_height(0);
}
}
@end

View file

@ -0,0 +1,48 @@
/*************************************************************************/
/* vulkan_context_ios.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#ifndef VULKAN_CONTEXT_IOS_H
#define VULKAN_CONTEXT_IOS_H
#include "drivers/vulkan/vulkan_context.h"
#import <UIKit/UIKit.h>
class VulkanContextIOS : public VulkanContext {
virtual const char *_get_platform_surface_extension() const;
public:
Error window_create(DisplayServer::WindowID p_window_id, DisplayServer::VSyncMode p_vsync_mode, CALayer *p_metal_layer, int p_width, int p_height);
VulkanContextIOS();
~VulkanContextIOS();
};
#endif // VULKAN_CONTEXT_IOS_H

View file

@ -0,0 +1,59 @@
/*************************************************************************/
/* vulkan_context_ios.mm */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "vulkan_context_ios.h"
#ifdef USE_VOLK
#include <volk.h>
#else
#include <vulkan/vulkan.h>
#endif
const char *VulkanContextIOS::_get_platform_surface_extension() const {
return VK_MVK_IOS_SURFACE_EXTENSION_NAME;
}
Error VulkanContextIOS::window_create(DisplayServer::WindowID p_window_id, DisplayServer::VSyncMode p_vsync_mode, CALayer *p_metal_layer, int p_width, int p_height) {
VkIOSSurfaceCreateInfoMVK createInfo;
createInfo.sType = VK_STRUCTURE_TYPE_IOS_SURFACE_CREATE_INFO_MVK;
createInfo.pNext = nullptr;
createInfo.flags = 0;
createInfo.pView = (__bridge const void *)p_metal_layer;
VkSurfaceKHR surface;
VkResult err =
vkCreateIOSSurfaceMVK(get_instance(), &createInfo, nullptr, &surface);
ERR_FAIL_COND_V(err, ERR_CANT_CREATE);
return _window_create(p_window_id, p_vsync_mode, surface, p_width, p_height);
}
VulkanContextIOS::VulkanContextIOS() {}
VulkanContextIOS::~VulkanContextIOS() {}