From d6c1d5712ec1644f78a570c919cad22b0a57c59d Mon Sep 17 00:00:00 2001 From: Sergey Minakov Date: Tue, 8 Feb 2022 09:52:36 +0300 Subject: [PATCH 1/6] [UIKit] Implemented shared code for UIKit based platforms --- drivers/gles3/rasterizer_gles3.cpp | 2 +- platform/iphone/SCsub | 14 +- platform/iphone/app_delegate.h | 4 +- platform/iphone/app_delegate.mm | 8 +- platform/iphone/detect.py | 2 +- platform/iphone/display_server_iphone.h | 116 +---- platform/iphone/display_server_iphone.mm | 388 +-------------- platform/iphone/godot_app_delegate.h | 10 +- platform/iphone/godot_app_delegate.m | 424 +--------------- platform/iphone/godot_view.h | 31 +- platform/iphone/godot_view.mm | 175 +------ ...w_controller.h => godot_view_controller.h} | 6 +- ...controller.mm => godot_view_controller.mm} | 41 +- platform/iphone/godot_view_renderer.h | 12 +- platform/iphone/godot_view_renderer.mm | 59 +-- platform/iphone/ios.mm | 2 +- platform/iphone/os_iphone.h | 49 +- platform/iphone/os_iphone.mm | 151 +----- platform/uikit/uikit_app_delegate.h | 41 ++ platform/uikit/uikit_app_delegate.m | 459 ++++++++++++++++++ .../uikit_display_layer.h} | 16 +- .../uikit_display_layer.mm} | 30 +- platform/uikit/uikit_display_server.h | 181 +++++++ platform/uikit/uikit_display_server.mm | 446 +++++++++++++++++ .../joypad_iphone.h => uikit/uikit_joypad.h} | 16 +- .../uikit_joypad.mm} | 90 +++- platform/uikit/uikit_os.h | 99 ++++ platform/uikit/uikit_os.mm | 214 ++++++++ platform/uikit/uikit_platform_config.h | 44 ++ platform/uikit/uikit_view.h | 64 +++ platform/uikit/uikit_view.mm | 255 ++++++++++ platform/uikit/uikit_view_controller.h | 41 ++ platform/uikit/uikit_view_controller.mm | 126 +++++ platform/uikit/uikit_view_renderer.h | 46 ++ platform/uikit/uikit_view_renderer.mm | 112 +++++ .../uikit_vulkan_context.h} | 15 +- .../uikit_vulkan_context.mm} | 12 +- 37 files changed, 2318 insertions(+), 1483 deletions(-) rename platform/iphone/{view_controller.h => godot_view_controller.h} (93%) rename platform/iphone/{view_controller.mm => godot_view_controller.mm} (84%) create mode 100644 platform/uikit/uikit_app_delegate.h create mode 100644 platform/uikit/uikit_app_delegate.m rename platform/{iphone/display_layer.h => uikit/uikit_display_layer.h} (85%) rename platform/{iphone/display_layer.mm => uikit/uikit_display_layer.mm} (91%) create mode 100644 platform/uikit/uikit_display_server.h create mode 100644 platform/uikit/uikit_display_server.mm rename platform/{iphone/joypad_iphone.h => uikit/uikit_joypad.h} (90%) rename platform/{iphone/joypad_iphone.mm => uikit/uikit_joypad.mm} (83%) create mode 100644 platform/uikit/uikit_os.h create mode 100644 platform/uikit/uikit_os.mm create mode 100644 platform/uikit/uikit_platform_config.h create mode 100644 platform/uikit/uikit_view.h create mode 100644 platform/uikit/uikit_view.mm create mode 100644 platform/uikit/uikit_view_controller.h create mode 100644 platform/uikit/uikit_view_controller.mm create mode 100644 platform/uikit/uikit_view_renderer.h create mode 100644 platform/uikit/uikit_view_renderer.mm rename platform/{iphone/vulkan_context_iphone.h => uikit/uikit_vulkan_context.h} (90%) rename platform/{iphone/vulkan_context_iphone.mm => uikit/uikit_vulkan_context.mm} (86%) diff --git a/drivers/gles3/rasterizer_gles3.cpp b/drivers/gles3/rasterizer_gles3.cpp index faadb2a4edfb..4cc3e69e21c9 100644 --- a/drivers/gles3/rasterizer_gles3.cpp +++ b/drivers/gles3/rasterizer_gles3.cpp @@ -67,7 +67,7 @@ #endif #endif -#if !defined(IPHONE_ENABLED) && !defined(JAVASCRIPT_ENABLED) +#if !defined(UIKIT_ENABLED) && !defined(JAVASCRIPT_ENABLED) // We include EGL below to get debug callback on GLES2 platforms, // but EGL is not available on iOS. #define CAN_DEBUG diff --git a/platform/iphone/SCsub b/platform/iphone/SCsub index 58b574a72ff8..c2a1ef75702c 100644 --- a/platform/iphone/SCsub +++ b/platform/iphone/SCsub @@ -3,18 +3,24 @@ Import("env") iphone_lib = [ + "#platform/uikit/uikit_app_delegate.m", + "#platform/uikit/uikit_display_layer.mm", + "#platform/uikit/uikit_display_server.mm", + "#platform/uikit/uikit_joypad.mm", + "#platform/uikit/uikit_os.mm", + "#platform/uikit/uikit_view_controller.mm", + "#platform/uikit/uikit_view_renderer.mm", + "#platform/uikit/uikit_view.mm", + "#platform/uikit/uikit_vulkan_context.mm", "godot_iphone.mm", "os_iphone.mm", "main.m", "app_delegate.mm", - "view_controller.mm", "ios.mm", - "vulkan_context_iphone.mm", "display_server_iphone.mm", - "joypad_iphone.mm", "godot_view.mm", - "display_layer.mm", "godot_app_delegate.m", + "godot_view_controller.mm", "godot_view_renderer.mm", "godot_view_gesture_recognizer.mm", "device_metrics.m", diff --git a/platform/iphone/app_delegate.h b/platform/iphone/app_delegate.h index 0ec1dc071b68..343f75633b31 100644 --- a/platform/iphone/app_delegate.h +++ b/platform/iphone/app_delegate.h @@ -30,7 +30,7 @@ #import -@class ViewController; +@class GodotViewController; // FIXME: Add support for both OpenGL and Vulkan when OpenGL is implemented again, // so it can't be done with compilation time branching. @@ -42,6 +42,6 @@ //#endif @property(strong, nonatomic) UIWindow *window; -@property(strong, class, readonly, nonatomic) ViewController *viewController; +@property(strong, class, readonly, nonatomic) GodotViewController *viewController; @end diff --git a/platform/iphone/app_delegate.mm b/platform/iphone/app_delegate.mm index c5c9b5a5f98f..4bfce09f125a 100644 --- a/platform/iphone/app_delegate.mm +++ b/platform/iphone/app_delegate.mm @@ -33,9 +33,9 @@ #include "core/config/project_settings.h" #include "drivers/coreaudio/audio_driver_coreaudio.h" #import "godot_view.h" +#import "godot_view_controller.h" #include "main/main.h" #include "os_iphone.h" -#import "view_controller.h" #import #import @@ -50,9 +50,9 @@ @implementation AppDelegate -static ViewController *mainViewController = nil; +static GodotViewController *mainViewController = nil; -+ (ViewController *)viewController { ++ (GodotViewController *)viewController { return mainViewController; } @@ -79,7 +79,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( return NO; } - ViewController *viewController = [[ViewController alloc] init]; + GodotViewController *viewController = [[GodotViewController alloc] init]; viewController.godotView.useCADisplayLink = bool(GLOBAL_DEF("display.iOS/use_cadisplaylink", true)) ? YES : NO; viewController.godotView.renderingInterval = 1.0 / kRenderingFrequency; diff --git a/platform/iphone/detect.py b/platform/iphone/detect.py index f442235e7c48..c2479c47018b 100644 --- a/platform/iphone/detect.py +++ b/platform/iphone/detect.py @@ -139,7 +139,7 @@ def configure(env): ) env.Prepend(CPPPATH=["#platform/iphone"]) - env.Append(CPPDEFINES=["IPHONE_ENABLED", "UNIX_ENABLED", "COREAUDIO_ENABLED"]) + env.Append(CPPDEFINES=["UIKIT_ENABLED", "IPHONE_ENABLED", "UNIX_ENABLED", "COREAUDIO_ENABLED"]) if env["vulkan"]: env.Append(CPPDEFINES=["VULKAN_ENABLED"]) diff --git a/platform/iphone/display_server_iphone.h b/platform/iphone/display_server_iphone.h index 7441550f6762..3962c5aafebd 100644 --- a/platform/iphone/display_server_iphone.h +++ b/platform/iphone/display_server_iphone.h @@ -31,6 +31,8 @@ #ifndef display_server_iphone_h #define display_server_iphone_h +#include "platform/uikit/uikit_display_server.h" + #include "core/input/input.h" #include "servers/display_server.h" @@ -38,8 +40,6 @@ #include "drivers/vulkan/rendering_device_vulkan.h" #include "servers/rendering/renderer_rd/renderer_compositor_rd.h" -#include "vulkan_context_iphone.h" - #import #ifdef USE_VOLK #include @@ -48,142 +48,41 @@ #endif #endif -class DisplayServerIPhone : public DisplayServer { +CALayer *initialize_uikit_rendering_layer(const String &p_driver); + +class DisplayServerIPhone : public DisplayServerUIKit { GDCLASS(DisplayServerIPhone, DisplayServer) _THREAD_SAFE_CLASS_ -#if defined(VULKAN_ENABLED) - VulkanContextIPhone *context_vulkan = nullptr; - RenderingDeviceVulkan *rendering_device_vulkan = nullptr; -#endif - DisplayServer::ScreenOrientation screen_orientation; - ObjectID window_attached_instance_id; - - Callable window_event_callback; - Callable window_resize_callback; - Callable input_event_callback; - Callable input_text_callback; - int virtual_keyboard_height = 0; - void perform_event(const Ref &p_event); - DisplayServerIPhone(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); ~DisplayServerIPhone(); public: - String rendering_driver; - static DisplayServerIPhone *get_singleton(); static void register_iphone_driver(); static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error); static Vector get_rendering_drivers_func(); - // MARK: - Events - - virtual void process_events() override; - - virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; - virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; - virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; - virtual void window_set_input_text_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; - virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; - - static void _dispatch_input_events(const Ref &p_event); - void send_input_event(const Ref &p_event) const; - void send_input_text(const String &p_text) const; - void send_window_event(DisplayServer::WindowEvent p_event) const; - void _window_callback(const Callable &p_callable, const Variant &p_arg) const; - - // 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 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 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; @@ -194,11 +93,6 @@ class DisplayServerIPhone : public DisplayServer { 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_iphone_h */ diff --git a/platform/iphone/display_server_iphone.mm b/platform/iphone/display_server_iphone.mm index a0f8daf5a021..9f03429e0ce4 100644 --- a/platform/iphone/display_server_iphone.mm +++ b/platform/iphone/display_server_iphone.mm @@ -35,117 +35,28 @@ #include "core/io/file_access_pack.h" #import "device_metrics.h" #import "godot_view.h" +#import "godot_view_controller.h" #include "ios.h" #import "keyboard_input_view.h" #include "os_iphone.h" -#import "view_controller.h" #import #import -static const float kDisplayServerIPhoneAcceleration = 1; +CALayer *initialize_uikit_rendering_layer(const String &p_driver) { + NSString *driverName = [NSString stringWithUTF8String:p_driver.utf8().get_data()]; + return [AppDelegate.viewController.godotView initializeRenderingForDriver:driverName]; +} DisplayServerIPhone *DisplayServerIPhone::get_singleton() { return (DisplayServerIPhone *)DisplayServer::get_singleton(); } -DisplayServerIPhone::DisplayServerIPhone(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { - rendering_driver = p_rendering_driver; - -#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(VulkanContextIPhone); - if (context_vulkan->initialize() != OK) { - memdelete(context_vulkan); - context_vulkan = nullptr; - ERR_FAIL_MSG("Failed to initialize Vulkan context"); - } - - CALayer *layer = [AppDelegate.viewController.godotView initializeRenderingForDriver:@"vulkan"]; - - if (!layer) { - ERR_FAIL_MSG("Failed to create iOS rendering layer."); - } - - Size2i size = Size2i(layer.bounds.size.width, layer.bounds.size.height) * screen_get_max_scale(); - if (context_vulkan->window_create(MAIN_WINDOW_ID, 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; +DisplayServerIPhone::DisplayServerIPhone(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) : + DisplayServerUIKit(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_resolution, r_error) { } DisplayServerIPhone::~DisplayServerIPhone() { -#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 *DisplayServerIPhone::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) { @@ -169,127 +80,6 @@ register_create_function("iphone", create_func, get_rendering_drivers_func); } -// MARK: Events - -void DisplayServerIPhone::window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window) { - window_resize_callback = p_callable; -} - -void DisplayServerIPhone::window_set_window_event_callback(const Callable &p_callable, WindowID p_window) { - window_event_callback = p_callable; -} -void DisplayServerIPhone::window_set_input_event_callback(const Callable &p_callable, WindowID p_window) { - input_event_callback = p_callable; -} - -void DisplayServerIPhone::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) { - input_text_callback = p_callable; -} - -void DisplayServerIPhone::window_set_drop_files_callback(const Callable &p_callable, WindowID p_window) { - // Probably not supported for iOS -} - -void DisplayServerIPhone::process_events() { -} - -void DisplayServerIPhone::_dispatch_input_events(const Ref &p_event) { - DisplayServerIPhone::get_singleton()->send_input_event(p_event); -} - -void DisplayServerIPhone::send_input_event(const Ref &p_event) const { - _window_callback(input_event_callback, p_event); -} - -void DisplayServerIPhone::send_input_text(const String &p_text) const { - _window_callback(input_text_callback, p_text); -} - -void DisplayServerIPhone::send_window_event(DisplayServer::WindowEvent p_event) const { - _window_callback(window_event_callback, int(p_event)); -} - -void DisplayServerIPhone::_window_callback(const Callable &p_callable, const Variant &p_arg) const { - if (!p_callable.is_null()) { - const Variant *argp = &p_arg; - Variant ret; - Callable::CallError ce; - p_callable.call((const Variant **)&argp, 1, ret, ce); - } -} - -// MARK: - Input - -// MARK: Touches - -void DisplayServerIPhone::touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_double_click) { - if (!GLOBAL_DEF("debug/disable_touch", false)) { - Ref 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 DisplayServerIPhone::touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y) { - if (!GLOBAL_DEF("debug/disable_touch", false)) { - Ref 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 DisplayServerIPhone::perform_event(const Ref &p_event) { - Input::get_singleton()->parse_input_event(p_event); -} - -void DisplayServerIPhone::touches_cancelled(int p_idx) { - touch_press(p_idx, -1, -1, false, false); -} - -// MARK: Keyboard - -void DisplayServerIPhone::key(Key p_key, bool p_pressed) { - Ref 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 DisplayServerIPhone::update_gravity(float p_x, float p_y, float p_z) { - Input::get_singleton()->set_gravity(Vector3(p_x, p_y, p_z)); -} - -void DisplayServerIPhone::update_accelerometer(float p_x, float p_y, float p_z) { - // Found out the Z should not be negated! Pass as is! - Vector3 v_accelerometer = Vector3( - p_x / kDisplayServerIPhoneAcceleration, - p_y / kDisplayServerIPhoneAcceleration, - p_z / kDisplayServerIPhoneAcceleration); - - Input::get_singleton()->set_accelerometer(v_accelerometer); -} - -void DisplayServerIPhone::update_magnetometer(float p_x, float p_y, float p_z) { - Input::get_singleton()->set_magnetometer(Vector3(p_x, p_y, p_z)); -} - -void DisplayServerIPhone::update_gyroscope(float p_x, float p_y, float p_z) { - Input::get_singleton()->set_gyroscope(Vector3(p_x, p_y, p_z)); -} - // MARK: - bool DisplayServerIPhone::has_feature(Feature p_feature) const { @@ -320,14 +110,6 @@ return "iPhone"; } -int DisplayServerIPhone::get_screen_count() const { - return 1; -} - -Point2i DisplayServerIPhone::screen_get_position(int p_screen) const { - return Size2i(); -} - Size2i DisplayServerIPhone::screen_get_size(int p_screen) const { CALayer *layer = AppDelegate.viewController.godotView.renderingLayer; @@ -394,24 +176,6 @@ } } -float DisplayServerIPhone::screen_get_refresh_rate(int p_screen) const { - return [UIScreen mainScreen].maximumFramesPerSecond; -} - -float DisplayServerIPhone::screen_get_scale(int p_screen) const { - return [UIScreen mainScreen].nativeScale; -} - -Vector DisplayServerIPhone::get_window_list() const { - Vector list; - list.push_back(MAIN_WINDOW_ID); - return list; -} - -DisplayServer::WindowID DisplayServerIPhone::get_window_at_screen_position(const Point2i &p_position) const { - return MAIN_WINDOW_ID; -} - int64_t DisplayServerIPhone::window_get_native_handle(HandleType p_handle_type, WindowID p_window) const { ERR_FAIL_COND_V(p_window != MAIN_WINDOW_ID, 0); switch (p_handle_type) { @@ -430,99 +194,6 @@ } } -void DisplayServerIPhone::window_attach_instance_id(ObjectID p_instance, WindowID p_window) { - window_attached_instance_id = p_instance; -} - -ObjectID DisplayServerIPhone::window_get_attached_instance_id(WindowID p_window) const { - return window_attached_instance_id; -} - -void DisplayServerIPhone::window_set_title(const String &p_title, WindowID p_window) { - // Probably not supported for iOS -} - -int DisplayServerIPhone::window_get_current_screen(WindowID p_window) const { - return SCREEN_OF_MAIN_WINDOW; -} - -void DisplayServerIPhone::window_set_current_screen(int p_screen, WindowID p_window) { - // Probably not supported for iOS -} - -Point2i DisplayServerIPhone::window_get_position(WindowID p_window) const { - return Point2i(); -} - -void DisplayServerIPhone::window_set_position(const Point2i &p_position, WindowID p_window) { - // Probably not supported for single window iOS app -} - -void DisplayServerIPhone::window_set_transient(WindowID p_window, WindowID p_parent) { - // Probably not supported for iOS -} - -void DisplayServerIPhone::window_set_max_size(const Size2i p_size, WindowID p_window) { - // Probably not supported for iOS -} - -Size2i DisplayServerIPhone::window_get_max_size(WindowID p_window) const { - return Size2i(); -} - -void DisplayServerIPhone::window_set_min_size(const Size2i p_size, WindowID p_window) { - // Probably not supported for iOS -} - -Size2i DisplayServerIPhone::window_get_min_size(WindowID p_window) const { - return Size2i(); -} - -void DisplayServerIPhone::window_set_size(const Size2i p_size, WindowID p_window) { - // Probably not supported for iOS -} - -Size2i DisplayServerIPhone::window_get_size(WindowID p_window) const { - CGRect screenBounds = [UIScreen mainScreen].bounds; - return Size2i(screenBounds.size.width, screenBounds.size.height) * screen_get_max_scale(); -} - -Size2i DisplayServerIPhone::window_get_real_size(WindowID p_window) const { - return window_get_size(p_window); -} - -void DisplayServerIPhone::window_set_mode(WindowMode p_mode, WindowID p_window) { - // Probably not supported for iOS -} - -DisplayServer::WindowMode DisplayServerIPhone::window_get_mode(WindowID p_window) const { - return WindowMode::WINDOW_MODE_FULLSCREEN; -} - -bool DisplayServerIPhone::window_is_maximize_allowed(WindowID p_window) const { - return false; -} - -void DisplayServerIPhone::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) { - // Probably not supported for iOS -} - -bool DisplayServerIPhone::window_get_flag(WindowFlags p_flag, WindowID p_window) const { - return false; -} - -void DisplayServerIPhone::window_request_attention(WindowID p_window) { - // Probably not supported for iOS -} - -void DisplayServerIPhone::window_move_to_foreground(WindowID p_window) { - // Probably not supported for iOS -} - -float DisplayServerIPhone::screen_get_max_scale() const { - return screen_get_scale(SCREEN_OF_MAIN_WINDOW); -} - void DisplayServerIPhone::screen_set_orientation(DisplayServer::ScreenOrientation p_orientation, int p_screen) { screen_orientation = p_orientation; } @@ -531,14 +202,6 @@ return screen_orientation; } -bool DisplayServerIPhone::window_can_draw(WindowID p_window) const { - return true; -} - -bool DisplayServerIPhone::can_any_window_draw() const { - return true; -} - bool DisplayServerIPhone::screen_is_touchscreen(int p_screen) const { return true; } @@ -574,40 +237,3 @@ return String::utf8([text UTF8String]); } - -void DisplayServerIPhone::screen_set_keep_on(bool p_enable) { - [UIApplication sharedApplication].idleTimerDisabled = p_enable; -} - -bool DisplayServerIPhone::screen_is_kept_on() const { - return [UIApplication sharedApplication].idleTimerDisabled; -} - -void DisplayServerIPhone::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 DisplayServerIPhone::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 DisplayServerIPhone::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 -} diff --git a/platform/iphone/godot_app_delegate.h b/platform/iphone/godot_app_delegate.h index 703a906bda96..f474b5f50f57 100644 --- a/platform/iphone/godot_app_delegate.h +++ b/platform/iphone/godot_app_delegate.h @@ -28,14 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#import +#import "platform/uikit/uikit_app_delegate.h" -typedef NSObject ApplicationDelegateService; - -@interface GodotApplicalitionDelegate : NSObject - -@property(class, readonly, strong) NSArray *services; - -+ (void)addService:(ApplicationDelegateService *)service; +@interface GodotApplicalitionDelegate : UIKitApplicalitionDelegate @end diff --git a/platform/iphone/godot_app_delegate.m b/platform/iphone/godot_app_delegate.m index 84347f9a3073..b2c4859981a2 100644 --- a/platform/iphone/godot_app_delegate.m +++ b/platform/iphone/godot_app_delegate.m @@ -38,430 +38,8 @@ @interface GodotApplicalitionDelegate () @implementation GodotApplicalitionDelegate -static NSMutableArray *services = nil; - -+ (NSArray *)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 *)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 *)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 *)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 *)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> *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]; - } + [self addService:[AppDelegate new]]; } -// MARK: Opening an URL - -- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options { - for (ApplicationDelegateService *service in services) { - if (![service respondsToSelector:_cmd]) { - continue; - } - - if ([service application:app openURL:url options:options]) { - return YES; - } - } - - return NO; -} - -// MARK: Disallowing Specified App Extension Types - -- (BOOL)application:(UIApplication *)application shouldAllowExtensionPointIdentifier:(UIApplicationExtensionPointIdentifier)extensionPointIdentifier { - BOOL result = NO; - - for (ApplicationDelegateService *service in services) { - if (![service respondsToSelector:_cmd]) { - continue; - } - - if ([service application:application shouldAllowExtensionPointIdentifier:extensionPointIdentifier]) { - result = YES; - } - } - - return result; -} - -// MARK: SiriKit - -- (id)application:(UIApplication *)application handlerForIntent:(INIntent *)intent API_AVAILABLE(ios(14.0)) { - for (ApplicationDelegateService *service in services) { - if (![service respondsToSelector:_cmd]) { - continue; - } - - id result = [service application:application handlerForIntent:intent]; - - if (result) { - return result; - } - } - - return nil; -} - -// MARK: CloudKit - -- (void)application:(UIApplication *)application userDidAcceptCloudKitShareWithMetadata:(CKShareMetadata *)cloudKitShareMetadata { - for (ApplicationDelegateService *service in services) { - if (![service respondsToSelector:_cmd]) { - continue; - } - - [service application:application userDidAcceptCloudKitShareWithMetadata:cloudKitShareMetadata]; - } -} - -/* Handled By Info.plist file for now - -// MARK: Interface Geometry - -- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {} - -*/ - @end diff --git a/platform/iphone/godot_view.h b/platform/iphone/godot_view.h index fcb97fa63a80..497ebd8c9be6 100644 --- a/platform/iphone/godot_view.h +++ b/platform/iphone/godot_view.h @@ -28,36 +28,9 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#import +#import "platform/uikit/uikit_view.h" -class String; - -@class GodotView; -@protocol DisplayLayer; -@protocol GodotViewRendererProtocol; - -@protocol GodotViewDelegate - -- (BOOL)godotViewFinishedSetup:(GodotView *)view; - -@end - -@interface GodotView : UIView - -@property(assign, nonatomic) id renderer; -@property(assign, nonatomic) id delegate; - -@property(assign, readonly, nonatomic) BOOL isActive; - -@property(assign, nonatomic) BOOL useCADisplayLink; -@property(strong, readonly, nonatomic) CALayer *renderingLayer; -@property(assign, readonly, nonatomic) BOOL canRender; - -@property(assign, nonatomic) NSTimeInterval renderingInterval; - -- (CALayer *)initializeRenderingForDriver:(NSString *)driverName; -- (void)stopRendering; -- (void)startRendering; +@interface GodotView : UIKitView - (void)godotTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event; - (void)godotTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event; diff --git a/platform/iphone/godot_view.mm b/platform/iphone/godot_view.mm index e48dd2e5079b..30effec97b90 100644 --- a/platform/iphone/godot_view.mm +++ b/platform/iphone/godot_view.mm @@ -32,7 +32,6 @@ #include "core/os/keyboard.h" #include "core/string/ustring.h" -#import "display_layer.h" #include "display_server_iphone.h" #import "godot_view_gesture_recognizer.h" #import "godot_view_renderer.h" @@ -46,17 +45,6 @@ @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 *renderingLayer; - @property(strong, nonatomic) CMMotionManager *motionManager; @property(strong, nonatomic) GodotViewGestureRecognizer *delayGestureRecognizer; @@ -65,39 +53,6 @@ @interface GodotView () { @implementation GodotView -- (CALayer *)initializeRenderingForDriver:(NSString *)driverName { - if (self.renderingLayer) { - return self.renderingLayer; - } - - CALayer *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]; @@ -119,39 +74,17 @@ - (instancetype)initWithFrame:(CGRect)frame { } - (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; @@ -174,119 +107,15 @@ - (void)godot_commonInit { } - (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; - } + [super stopRendering]; [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; - } - } + [super drawView]; [self handleMotion]; - [self.renderer renderOnView:self]; -} - -- (BOOL)canRender { - if (self.useCADisplayLink) { - return self.displayLink != nil; - } else { - return self.animationTimer != nil; - } -} - -- (void)setRenderingInterval:(NSTimeInterval)renderingInterval { - _renderingInterval = renderingInterval; - - if (self.canRender) { - [self stopRendering]; - [self startRendering]; - } -} - -- (void)layoutSubviews { - if (self.renderingLayer) { - self.renderingLayer.frame = self.bounds; - [self.renderingLayer layoutDisplayLayer]; - - if (DisplayServerIPhone::get_singleton()) { - DisplayServerIPhone::get_singleton()->resize_window(self.bounds.size); - } - } - - [super layoutSubviews]; } // MARK: - Input diff --git a/platform/iphone/view_controller.h b/platform/iphone/godot_view_controller.h similarity index 93% rename from platform/iphone/view_controller.h rename to platform/iphone/godot_view_controller.h index c8b37a4d111b..3786cf63698e 100644 --- a/platform/iphone/view_controller.h +++ b/platform/iphone/godot_view_controller.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* view_controller.h */ +/* godot_view_controller.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,13 +28,13 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ +#import "platform/uikit/uikit_view_controller.h" #import @class GodotView; -@class GodotNativeVideoView; @class GodotKeyboardInputView; -@interface ViewController : UIViewController +@interface GodotViewController : UIKitViewController @property(nonatomic, readonly, strong) GodotView *godotView; @property(nonatomic, readonly, strong) GodotKeyboardInputView *keyboardView; diff --git a/platform/iphone/view_controller.mm b/platform/iphone/godot_view_controller.mm similarity index 84% rename from platform/iphone/view_controller.mm rename to platform/iphone/godot_view_controller.mm index 4f4ef4f04630..373b4c5e7910 100644 --- a/platform/iphone/view_controller.mm +++ b/platform/iphone/godot_view_controller.mm @@ -1,5 +1,5 @@ /*************************************************************************/ -/* view_controller.mm */ +/* godot_view_controller.mm */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,7 +28,7 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#import "view_controller.h" +#import "godot_view_controller.h" #include "core/config/project_settings.h" #include "display_server_iphone.h" #import "godot_view.h" @@ -39,16 +39,14 @@ #import #import -@interface ViewController () +@interface GodotViewController () @property(strong, nonatomic) GodotViewRenderer *renderer; @property(strong, nonatomic) GodotKeyboardInputView *keyboardView; -@property(strong, nonatomic) UIView *godotLoadingOverlay; - @end -@implementation ViewController +@implementation GodotViewController - (GodotView *)godotView { return (GodotView *)self.view; @@ -98,7 +96,6 @@ - (void)viewDidLoad { [super viewDidLoad]; [self observeKeyboard]; - [self displayLoadingOverlay]; if (@available(iOS 11.0, *)) { [self setNeedsUpdateOfScreenEdgesDeferringSystemGestures]; @@ -123,41 +120,11 @@ - (void)observeKeyboard { 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]; } diff --git a/platform/iphone/godot_view_renderer.h b/platform/iphone/godot_view_renderer.h index b3ee23ae4f90..30cb80b54bc3 100644 --- a/platform/iphone/godot_view_renderer.h +++ b/platform/iphone/godot_view_renderer.h @@ -28,17 +28,9 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ +#import "platform/uikit/uikit_view_renderer.h" #import -@protocol GodotViewRendererProtocol - -@property(assign, readonly, nonatomic) BOOL hasFinishedSetup; - -- (BOOL)setupView:(UIView *)view; -- (void)renderOnView:(UIView *)view; - -@end - -@interface GodotViewRenderer : NSObject +@interface GodotViewRenderer : UIKitViewRenderer @end diff --git a/platform/iphone/godot_view_renderer.mm b/platform/iphone/godot_view_renderer.mm index 32477ae41cd0..c1e0fd282012 100644 --- a/platform/iphone/godot_view_renderer.mm +++ b/platform/iphone/godot_view_renderer.mm @@ -45,66 +45,13 @@ @interface GodotViewRenderer () -@property(assign, nonatomic) BOOL hasFinishedProjectDataSetup; -@property(assign, nonatomic) BOOL hasStartedMain; -@property(assign, nonatomic) BOOL hasFinishedSetup; - @end @implementation GodotViewRenderer -- (BOOL)setupView:(UIView *)view { - if (self.hasFinishedSetup) { - return NO; - } - - if (!OS::get_singleton()) { - exit(0); - } - - if (!self.hasFinishedProjectDataSetup) { - [self setupProjectData]; - return YES; - } - - if (!self.hasStartedMain) { - self.hasStartedMain = YES; - OSIPhone::get_singleton()->start(); - return YES; - } - - self.hasFinishedSetup = YES; - - return NO; -} - -- (void)setupProjectData { - self.hasFinishedProjectDataSetup = YES; - - Main::setup2(); - - // this might be necessary before here - NSDictionary *dict = [[NSBundle mainBundle] infoDictionary]; - for (NSString *key in dict) { - NSObject *value = [dict objectForKey:key]; - String ukey = String::utf8([key UTF8String]); - - // we need a NSObject to Variant conversor - - if ([value isKindOfClass:[NSString class]]) { - NSString *str = (NSString *)value; - String uval = String::utf8([str UTF8String]); - - ProjectSettings::get_singleton()->set("Info.plist/" + ukey, uval); - - } else if ([value isKindOfClass:[NSNumber class]]) { - NSNumber *n = (NSNumber *)value; - double dval = [n doubleValue]; - - ProjectSettings::get_singleton()->set("Info.plist/" + ukey, dval); - } - // do stuff - } +- (BOOL)startUIKitPlatform { + OSIPhone::get_singleton()->start(); + return YES; } - (void)renderOnView:(UIView *)view { diff --git a/platform/iphone/ios.mm b/platform/iphone/ios.mm index ad1ea70c1052..6a131db9e5fb 100644 --- a/platform/iphone/ios.mm +++ b/platform/iphone/ios.mm @@ -31,7 +31,7 @@ #include "ios.h" #import "app_delegate.h" -#import "view_controller.h" +#import "godot_view_controller.h" #import #include diff --git a/platform/iphone/os_iphone.h b/platform/iphone/os_iphone.h index 3281ff0cdb71..737f6f1f6ec7 100644 --- a/platform/iphone/os_iphone.h +++ b/platform/iphone/os_iphone.h @@ -28,24 +28,23 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifdef IPHONE_ENABLED - #ifndef OS_IPHONE_H #define OS_IPHONE_H +#include "platform/uikit/uikit_os.h" + #include "drivers/coreaudio/audio_driver_coreaudio.h" #include "drivers/unix/os_unix.h" #include "ios.h" -#include "joypad_iphone.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/iphone/vulkan_context_iphone.h" +#include "platform/uikit/uikit_vulkan_context.h" #endif -class OSIPhone : public OS_Unix { +class OSIPhone : public OS_UIKit { private: static HashMap dynamic_symbol_lookup_table; friend void register_dynamic_symbol(char *name, void *address); @@ -54,61 +53,25 @@ class OSIPhone : public OS_Unix { iOS *ios; - JoypadIPhone *joypad_iphone; - - MainLoop *main_loop; - - 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 OSIPhone *get_singleton(); OSIPhone(String p_data_dir, String p_cache_dir); ~OSIPhone(); - 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) 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; @@ -117,8 +80,8 @@ class OSIPhone : public OS_Unix { void on_focus_out(); void on_focus_in(); + + void initialize_modules(); }; #endif // OS_IPHONE_H - -#endif // IPHONE_ENABLED diff --git a/platform/iphone/os_iphone.mm b/platform/iphone/os_iphone.mm index 1d990b562549..b39718059ee0 100644 --- a/platform/iphone/os_iphone.mm +++ b/platform/iphone/os_iphone.mm @@ -28,8 +28,6 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifdef IPHONE_ENABLED - #include "os_iphone.h" #import "app_delegate.h" @@ -40,8 +38,8 @@ #include "display_server_iphone.h" #include "drivers/unix/syslog_logger.h" #import "godot_view.h" +#import "godot_view_controller.h" #include "main/main.h" -#import "view_controller.h" #import #import @@ -89,7 +87,8 @@ void register_dynamic_symbol(char *name, void *address) { return (OSIPhone *)OS::get_singleton(); } -OSIPhone::OSIPhone(String p_data_dir, String p_cache_dir) { +OSIPhone::OSIPhone(String p_data_dir, String p_cache_dir) : + OS_UIKit(p_data_dir, p_cache_dir) { for (int i = 0; i < ios_init_callbacks_count; ++i) { ios_init_callbacks[i](); } @@ -98,24 +97,6 @@ void register_dynamic_symbol(char *name, void *address) { 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 loggers; - loggers.push_back(memnew(SyslogLogger)); -#ifdef DEBUG_ENABLED - // it seems iOS app's stdout/stderr is only obtainable if you launch it from - // Xcode - loggers.push_back(memnew(StdLogger)); -#endif - _set_logger(memnew(CompositeLogger(loggers))); - - AudioDriverManager::add_driver(&audio_driver); - DisplayServerIPhone::register_iphone_driver(); } @@ -127,98 +108,25 @@ void register_dynamic_symbol(char *name, void *address) { iOS::alert(utf8_alert.get_data(), utf8_title.get_data()); } -void OSIPhone::initialize_core() { - OS_Unix::initialize_core(); - - set_user_data_dir(user_data_dir); -} - void OSIPhone::initialize() { - initialize_core(); + OS_UIKit::initialize(); } void OSIPhone::initialize_modules() { ios = memnew(iOS); Engine::get_singleton()->add_singleton(Engine::Singleton("iOS", ios)); - - joypad_iphone = memnew(JoypadIPhone); } -void OSIPhone::deinitialize_modules() { - if (joypad_iphone) { - memdelete(joypad_iphone); - } - +void OSIPhone::finalize() { if (ios) { memdelete(ios); } -} - -void OSIPhone::set_main_loop(MainLoop *p_main_loop) { - main_loop = p_main_loop; - - if (main_loop) { - main_loop->initialize(); - } -} - -MainLoop *OSIPhone::get_main_loop() const { - return main_loop; -} - -void OSIPhone::delete_main_loop() { - if (main_loop) { - main_loop->finalize(); - memdelete(main_loop); - } - - main_loop = nullptr; -} - -bool OSIPhone::iterate() { - if (!main_loop) { - return true; - } - - if (DisplayServer::get_singleton()) { - DisplayServer::get_singleton()->process_events(); - } - - return Main::iteration(); -} - -void OSIPhone::start() { - Main::start(); - - if (joypad_iphone) { - joypad_iphone->start_processing(); - } -} - -void OSIPhone::finalize() { - deinitialize_modules(); - // Already gets called - //delete_main_loop(); + OS_UIKit::finalize(); } // MARK: Dynamic Libraries -Error OSIPhone::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) { - if (p_path.length() == 0) { - p_library_handle = RTLD_SELF; - return OK; - } - return OS_Unix::open_dynamic_library(p_path, p_library_handle, p_also_set_library_path); -} - -Error OSIPhone::close_dynamic_library(void *p_library_handle) { - if (p_library_handle == RTLD_SELF) { - return OK; - } - return OS_Unix::close_dynamic_library(p_library_handle); -} - Error OSIPhone::get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional) { if (p_library_handle == RTLD_SELF) { void **ptr = OSIPhone::dynamic_symbol_lookup_table.getptr(p_name); @@ -243,51 +151,6 @@ void register_dynamic_symbol(char *name, void *address) { return OS_Unix::get_model_name(); } -Error OSIPhone::shell_open(String p_uri) { - NSString *urlPath = [[NSString alloc] initWithUTF8String:p_uri.utf8().get_data()]; - NSURL *url = [NSURL URLWithString:urlPath]; - - if (![[UIApplication sharedApplication] canOpenURL:url]) { - return ERR_CANT_OPEN; - } - - printf("opening url %s\n", p_uri.utf8().get_data()); - - [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil]; - - return OK; -} - -void OSIPhone::set_user_data_dir(String p_dir) { - DirAccessRef 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 OSIPhone::get_user_data_dir() const { - return user_data_dir; -} - -String OSIPhone::get_cache_path() const { - return cache_dir; -} - -String OSIPhone::get_locale() const { - NSString *preferedLanguage = [NSLocale preferredLanguages].firstObject; - - if (preferedLanguage) { - return String::utf8([preferedLanguage UTF8String]).replace("-", "_"); - } - - NSString *localeIdentifier = [[NSLocale currentLocale] localeIdentifier]; - return String::utf8([localeIdentifier UTF8String]).replace("-", "_"); -} - -String OSIPhone::get_unique_id() const { - NSString *uuid = [UIDevice currentDevice].identifierForVendor.UUIDString; - return String::utf8([uuid UTF8String]); -} - String OSIPhone::get_processor_name() const { char buffer[256]; size_t buffer_len = 256; @@ -333,5 +196,3 @@ void register_dynamic_symbol(char *name, void *address) { audio_driver.start(); } } - -#endif // IPHONE_ENABLED diff --git a/platform/uikit/uikit_app_delegate.h b/platform/uikit/uikit_app_delegate.h new file mode 100644 index 000000000000..198faabfb12b --- /dev/null +++ b/platform/uikit/uikit_app_delegate.h @@ -0,0 +1,41 @@ +/*************************************************************************/ +/* uikit_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 + +typedef NSObject ApplicationDelegateService; + +@interface UIKitApplicalitionDelegate : NSObject + +@property(class, readonly, strong) NSArray *services; + ++ (void)addService:(ApplicationDelegateService *)service; + +@end diff --git a/platform/uikit/uikit_app_delegate.m b/platform/uikit/uikit_app_delegate.m new file mode 100644 index 000000000000..49fc8a01f59b --- /dev/null +++ b/platform/uikit/uikit_app_delegate.m @@ -0,0 +1,459 @@ +/*************************************************************************/ +/* uikit_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 "uikit_app_delegate.h" + +@interface UIKitApplicalitionDelegate () + +@end + +@implementation UIKitApplicalitionDelegate + +static NSMutableArray *services = nil; + ++ (NSArray *)services { + return services; +} + ++ (void)load { + services = [NSMutableArray 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 [[self class] services]) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + UIWindow *value = [service window]; + + if (value) { + result = value; + } + } + + return result; +} + +// MARK: Initializing + +- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions API_AVAILABLE(ios(6.0)) { + BOOL result = NO; + + for (ApplicationDelegateService *service in [[self class] services]) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + if ([service application:application willFinishLaunchingWithOptions:launchOptions]) { + result = YES; + } + } + + return result; +} + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions API_AVAILABLE(ios(3.0)) { + BOOL result = NO; + + for (ApplicationDelegateService *service in [[self class] 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 *)sceneSessions {} +*/ + +// MARK: Life-Cycle + +- (void)applicationDidBecomeActive:(UIApplication *)application { + for (ApplicationDelegateService *service in [[self class] services]) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service applicationDidBecomeActive:application]; + } +} + +- (void)applicationWillResignActive:(UIApplication *)application { + for (ApplicationDelegateService *service in [[self class] services]) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service applicationWillResignActive:application]; + } +} + +- (void)applicationDidEnterBackground:(UIApplication *)application API_AVAILABLE(ios(4.0)) { + for (ApplicationDelegateService *service in [[self class] services]) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service applicationDidEnterBackground:application]; + } +} + +- (void)applicationWillEnterForeground:(UIApplication *)application API_AVAILABLE(ios(4.0)) { + for (ApplicationDelegateService *service in [[self class] services]) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service applicationWillEnterForeground:application]; + } +} + +- (void)applicationWillTerminate:(UIApplication *)application { + for (ApplicationDelegateService *service in [[self class] services]) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service applicationWillTerminate:application]; + } +} + +// MARK: Environment Changes + +- (void)applicationProtectedDataDidBecomeAvailable:(UIApplication *)application API_AVAILABLE(ios(4.0)) { + for (ApplicationDelegateService *service in [[self class] services]) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service applicationProtectedDataDidBecomeAvailable:application]; + } +} + +- (void)applicationProtectedDataWillBecomeUnavailable:(UIApplication *)application API_AVAILABLE(ios(4.0)) { + for (ApplicationDelegateService *service in [[self class] services]) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service applicationProtectedDataWillBecomeUnavailable:application]; + } +} + +- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application { + for (ApplicationDelegateService *service in [[self class] services]) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service applicationDidReceiveMemoryWarning:application]; + } +} + +- (void)applicationSignificantTimeChange:(UIApplication *)application { + for (ApplicationDelegateService *service in [[self class] 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 [[self class] 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 [[self class] services]) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + if ([service application:application shouldRestoreSecureApplicationState:coder]) { + result = YES; + } + } + + return result; +} + +- (UIViewController *)application:(UIApplication *)application viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder API_AVAILABLE(ios(6.0)) { + for (ApplicationDelegateService *service in [[self class] 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 API_AVAILABLE(ios(6.0)) { + for (ApplicationDelegateService *service in [[self class] services]) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service application:application willEncodeRestorableStateWithCoder:coder]; + } +} + +- (void)application:(UIApplication *)application didDecodeRestorableStateWithCoder:(NSCoder *)coder API_AVAILABLE(ios(6.0)) { + for (ApplicationDelegateService *service in [[self class] 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 API_AVAILABLE(ios(7.0)) { + for (ApplicationDelegateService *service in [[self class] 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 API_AVAILABLE(ios(8.0)) { + BOOL result = NO; + + for (ApplicationDelegateService *service in [[self class] 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> *restorableObjects))restorationHandler API_AVAILABLE(ios(8.0)) { + BOOL result = NO; + + for (ApplicationDelegateService *service in [[self class] 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 API_AVAILABLE(ios(8.0)) { + for (ApplicationDelegateService *service in [[self class] services]) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service application:application didUpdateUserActivity:userActivity]; + } +} + +- (void)application:(UIApplication *)application didFailToContinueUserActivityWithType:(NSString *)userActivityType error:(NSError *)error API_AVAILABLE(ios(8.0)) { + for (ApplicationDelegateService *service in [[self class] 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 API_AVAILABLE(ios(9.0))API_UNAVAILABLE(tvos) { + for (ApplicationDelegateService *service in [[self class] 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 API_AVAILABLE(ios(8.2)) { + for (ApplicationDelegateService *service in [[self class] services]) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service application:application handleWatchKitExtensionRequest:userInfo reply:reply]; + } +} + +// MARK: HealthKit + +- (void)applicationShouldRequestHealthAuthorization:(UIApplication *)application API_AVAILABLE(ios(9.0)) { + for (ApplicationDelegateService *service in [[self class] services]) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service applicationShouldRequestHealthAuthorization:application]; + } +} + +// MARK: Opening an URL + +- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options API_AVAILABLE(ios(9.0)) { + BOOL result = NO; + + for (ApplicationDelegateService *service in [[self class] services]) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + if ([service application:app openURL:url options:options]) { + result = YES; + } + } + + return result; +} + +// MARK: Disallowing Specified App Extension Types + +- (BOOL)application:(UIApplication *)application shouldAllowExtensionPointIdentifier:(UIApplicationExtensionPointIdentifier)extensionPointIdentifier API_AVAILABLE(ios(8.0)) { + BOOL result = NO; + + for (ApplicationDelegateService *service in [[self class] 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 [[self class] 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 API_AVAILABLE(ios(10.0)) { + for (ApplicationDelegateService *service in [[self class] services]) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service application:application userDidAcceptCloudKitShareWithMetadata:cloudKitShareMetadata]; + } +} + +/* Handled By Info.plist file for now +// MARK: Interface Geometry +- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {} +*/ + +@end diff --git a/platform/iphone/display_layer.h b/platform/uikit/uikit_display_layer.h similarity index 85% rename from platform/iphone/display_layer.h rename to platform/uikit/uikit_display_layer.h index a17c75dba185..3c4477238bc7 100644 --- a/platform/iphone/display_layer.h +++ b/platform/uikit/uikit_display_layer.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* display_layer.h */ +/* uikit_display_layer.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -31,9 +31,10 @@ #import #import -@protocol DisplayLayer +@protocol UIKitDisplayLayer -- (void)renderDisplayLayer; +- (void)startRenderDisplayLayer; +- (void)stopRenderDisplayLayer; - (void)initializeDisplayLayer; - (void)layoutDisplayLayer; @@ -43,16 +44,15 @@ #if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR #if defined(__IPHONE_13_0) API_AVAILABLE(ios(13.0)) -@interface GodotMetalLayer : CAMetalLayer +@interface UIKitMetalLayer : CAMetalLayer #else -@interface GodotMetalLayer : CALayer +@interface UIKitMetalLayer : CALayer #endif #else -@interface GodotMetalLayer : CAMetalLayer +@interface UIKitMetalLayer : CAMetalLayer #endif @end -API_DEPRECATED("OpenGLES is deprecated", ios(2.0, 12.0)) -@interface GodotOpenGLLayer : CAEAGLLayer +@interface UIKitOpenGLLayer : CAEAGLLayer @end diff --git a/platform/iphone/display_layer.mm b/platform/uikit/uikit_display_layer.mm similarity index 91% rename from platform/iphone/display_layer.mm rename to platform/uikit/uikit_display_layer.mm index 92e81448acf5..e9bb4c38d2b3 100644 --- a/platform/iphone/display_layer.mm +++ b/platform/uikit/uikit_display_layer.mm @@ -1,5 +1,5 @@ /*************************************************************************/ -/* display_layer.mm */ +/* uikit_display_layer.mm */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,14 +28,13 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#import "display_layer.h" +#import "uikit_display_layer.h" #include "core/config/project_settings.h" #include "core/os/keyboard.h" -#include "display_server_iphone.h" #include "main/main.h" -#include "os_iphone.h" #include "servers/audio_server.h" +#include "uikit_os.h" #import #import @@ -45,7 +44,7 @@ #import #import -@implementation GodotMetalLayer +@implementation UIKitMetalLayer - (void)initializeDisplayLayer { #if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR @@ -60,12 +59,15 @@ - (void)initializeDisplayLayer { - (void)layoutDisplayLayer { } -- (void)renderDisplayLayer { +- (void)startRenderDisplayLayer { +} + +- (void)stopRenderDisplayLayer { } @end -@implementation GodotOpenGLLayer { +@implementation UIKitOpenGLLayer { // The pixel dimensions of the backbuffer GLint backingWidth; GLint backingHeight; @@ -115,10 +117,22 @@ - (void)layoutDisplayLayer { [self createFramebuffer]; } -- (void)renderDisplayLayer { +- (void)startRenderDisplayLayer { [EAGLContext setCurrentContext:context]; } +- (void)stopRenderDisplayLayer { + glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer); + [context presentRenderbuffer:GL_RENDERBUFFER_OES]; + +#ifdef DEBUG_ENABLED + GLenum err = glGetError(); + if (err) { + NSLog(@"DrawView: %x error", err); + } +#endif +} + - (void)dealloc { if ([EAGLContext currentContext] == context) { [EAGLContext setCurrentContext:nil]; diff --git a/platform/uikit/uikit_display_server.h b/platform/uikit/uikit_display_server.h new file mode 100644 index 000000000000..989484b20d71 --- /dev/null +++ b/platform/uikit/uikit_display_server.h @@ -0,0 +1,181 @@ +/*************************************************************************/ +/* uikit_display_server.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_uikit_h +#define display_server_uikit_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 "uikit_vulkan_context.h" + +#import +#import +#ifdef USE_VOLK +#include +#else +#include +#endif +#endif + +extern CALayer *initialize_uikit_rendering_layer(const String &p_driver); + +class DisplayServerUIKit : public DisplayServer { + GDCLASS(DisplayServerUIKit, DisplayServer) + + _THREAD_SAFE_CLASS_ + +#if defined(VULKAN_ENABLED) + VulkanContextUIKit *context_vulkan = nullptr; + RenderingDeviceVulkan *rendering_device_vulkan = nullptr; +#endif + + ObjectID window_attached_instance_id; + + Callable window_event_callback; + Callable window_resize_callback; + Callable input_event_callback; + Callable input_text_callback; + +protected: + void perform_event(const Ref &p_event); + + DisplayServerUIKit(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); + ~DisplayServerUIKit(); + +public: + String rendering_driver; + + static DisplayServerUIKit *get_singleton(); + + // MARK: - Events + + virtual void process_events() override; + + virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; + virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; + virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; + virtual void window_set_input_text_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; + virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; + + static void _dispatch_input_events(const Ref &p_event); + void send_input_event(const Ref &p_event) const; + void send_input_text(const String &p_text) const; + void send_window_event(DisplayServer::WindowEvent p_event) const; + void _window_callback(const Callable &p_callable, const Variant &p_arg) const; + + // 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_unicode(char32_t p_unicode, bool p_pressed); + 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 String get_name() const override; + + virtual int get_screen_count() const override; + virtual Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + virtual float screen_get_scale(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + + virtual Vector get_window_list() const override; + + virtual WindowID + get_window_at_screen_position(const Point2i &p_position) const override; + + virtual 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 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 void screen_set_keep_on(bool p_enable) override; + virtual bool screen_is_kept_on() const override; + + virtual void resize_window(CGSize size); +}; + +#endif /* display_server_uikit_h */ diff --git a/platform/uikit/uikit_display_server.mm b/platform/uikit/uikit_display_server.mm new file mode 100644 index 000000000000..328fab604fd0 --- /dev/null +++ b/platform/uikit/uikit_display_server.mm @@ -0,0 +1,446 @@ +/*************************************************************************/ +/* uikit_display_server.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 "uikit_display_server.h" + +#import "app_delegate.h" +#include "core/config/project_settings.h" +#include "core/io/file_access_pack.h" + +#import +#import + +static const float kDisplayServerUIKitAcceleration = 1; + +DisplayServerUIKit *DisplayServerUIKit::get_singleton() { + return (DisplayServerUIKit *)DisplayServer::get_singleton(); +} + +DisplayServerUIKit::DisplayServerUIKit(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; + +#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(VulkanContextUIKit); + if (context_vulkan->initialize() != OK) { + memdelete(context_vulkan); + context_vulkan = nullptr; + ERR_FAIL_MSG("Failed to initialize Vulkan context"); + } + + CALayer *layer = initialize_uikit_rendering_layer("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; +} + +DisplayServerUIKit::~DisplayServerUIKit() { +#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 +} + +// MARK: Events + +void DisplayServerUIKit::window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window) { + window_resize_callback = p_callable; +} + +void DisplayServerUIKit::window_set_window_event_callback(const Callable &p_callable, WindowID p_window) { + window_event_callback = p_callable; +} +void DisplayServerUIKit::window_set_input_event_callback(const Callable &p_callable, WindowID p_window) { + input_event_callback = p_callable; +} + +void DisplayServerUIKit::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) { + input_text_callback = p_callable; +} + +void DisplayServerUIKit::window_set_drop_files_callback(const Callable &p_callable, WindowID p_window) { + // Probably not supported for iOS +} + +void DisplayServerUIKit::process_events() { +} + +void DisplayServerUIKit::_dispatch_input_events(const Ref &p_event) { + DisplayServerUIKit::get_singleton()->send_input_event(p_event); +} + +void DisplayServerUIKit::send_input_event(const Ref &p_event) const { + _window_callback(input_event_callback, p_event); +} + +void DisplayServerUIKit::send_input_text(const String &p_text) const { + _window_callback(input_text_callback, p_text); +} + +void DisplayServerUIKit::send_window_event(DisplayServer::WindowEvent p_event) const { + _window_callback(window_event_callback, int(p_event)); +} + +void DisplayServerUIKit::_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 DisplayServerUIKit::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 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 DisplayServerUIKit::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 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 DisplayServerUIKit::perform_event(const Ref &p_event) { + Input::get_singleton()->parse_input_event(p_event); +}; + +void DisplayServerUIKit::touches_cancelled(int p_idx) { + touch_press(p_idx, -1, -1, false, false); +}; + +// MARK: Keyboard + +void DisplayServerUIKit::key_unicode(char32_t p_unicode, bool p_pressed) { + Ref ev; + ev.instantiate(); + ev->set_echo(false); + ev->set_pressed(p_pressed); + ev->set_keycode(Key::NONE); + ev->set_physical_keycode(Key::NONE); + ev->set_unicode(p_unicode); + + perform_event(ev); +} + +void DisplayServerUIKit::key(Key p_key, bool p_pressed) { + Ref ev; + ev.instantiate(); + ev->set_echo(false); + ev->set_pressed(p_pressed); + ev->set_keycode(p_key); + ev->set_physical_keycode(p_key); + + perform_event(ev); +}; + +// MARK: Motion + +void DisplayServerUIKit::update_gravity(float p_x, float p_y, float p_z) { + Input::get_singleton()->set_gravity(Vector3(p_x, p_y, p_z)); +}; + +void DisplayServerUIKit::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 / kDisplayServerUIKitAcceleration, + p_y / kDisplayServerUIKitAcceleration, + p_z / kDisplayServerUIKitAcceleration); + + Input::get_singleton()->set_accelerometer(v_accelerometer); +}; + +void DisplayServerUIKit::update_magnetometer(float p_x, float p_y, float p_z) { + Input::get_singleton()->set_magnetometer(Vector3(p_x, p_y, p_z)); +}; + +void DisplayServerUIKit::update_gyroscope(float p_x, float p_y, float p_z) { + Input::get_singleton()->set_gyroscope(Vector3(p_x, p_y, p_z)); +}; + +// MARK: - + +String DisplayServerUIKit::get_name() const { + return "UIKit"; +} + +int DisplayServerUIKit::get_screen_count() const { + return 1; +} + +Point2i DisplayServerUIKit::screen_get_position(int p_screen) const { + return Size2i(); +} + +float DisplayServerUIKit::screen_get_refresh_rate(int p_screen) const { + return [UIScreen mainScreen].maximumFramesPerSecond; +} + +float DisplayServerUIKit::screen_get_scale(int p_screen) const { + return [UIScreen mainScreen].nativeScale; +} + +Vector DisplayServerUIKit::get_window_list() const { + Vector list; + list.push_back(MAIN_WINDOW_ID); + return list; +} + +DisplayServer::WindowID DisplayServerUIKit::get_window_at_screen_position(const Point2i &p_position) const { + return MAIN_WINDOW_ID; +} + +void DisplayServerUIKit::window_attach_instance_id(ObjectID p_instance, WindowID p_window) { + window_attached_instance_id = p_instance; +} + +ObjectID DisplayServerUIKit::window_get_attached_instance_id(WindowID p_window) const { + return window_attached_instance_id; +} + +void DisplayServerUIKit::window_set_title(const String &p_title, WindowID p_window) { + // Probably not supported for iOS/tvOS +} + +int DisplayServerUIKit::window_get_current_screen(WindowID p_window) const { + return SCREEN_OF_MAIN_WINDOW; +} + +void DisplayServerUIKit::window_set_current_screen(int p_screen, WindowID p_window) { + // Probably not supported for iOS/tvOS +} + +Point2i DisplayServerUIKit::window_get_position(WindowID p_window) const { + return Point2i(); +} + +void DisplayServerUIKit::window_set_position(const Point2i &p_position, WindowID p_window) { + // Probably not supported for single window iOS/tvOS app +} + +void DisplayServerUIKit::window_set_transient(WindowID p_window, WindowID p_parent) { + // Probably not supported for iOS/tvOS +} + +void DisplayServerUIKit::window_set_max_size(const Size2i p_size, WindowID p_window) { + // Probably not supported for iOS/tvOS +} + +Size2i DisplayServerUIKit::window_get_max_size(WindowID p_window) const { + return Size2i(); +} + +void DisplayServerUIKit::window_set_min_size(const Size2i p_size, WindowID p_window) { + // Probably not supported for iOS/tvOS +} + +Size2i DisplayServerUIKit::window_get_min_size(WindowID p_window) const { + return Size2i(); +} + +void DisplayServerUIKit::window_set_size(const Size2i p_size, WindowID p_window) { + // Probably not supported for iOS/tvOS +} + +Size2i DisplayServerUIKit::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 DisplayServerUIKit::window_get_real_size(WindowID p_window) const { + return window_get_size(p_window); +} + +void DisplayServerUIKit::window_set_mode(WindowMode p_mode, WindowID p_window) { + // Probably not supported for iOS/tvOS +} + +DisplayServer::WindowMode DisplayServerUIKit::window_get_mode(WindowID p_window) const { + return WindowMode::WINDOW_MODE_FULLSCREEN; +} + +bool DisplayServerUIKit::window_is_maximize_allowed(WindowID p_window) const { + return false; +} + +void DisplayServerUIKit::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) { + // Probably not supported for iOS/tvOS +} + +bool DisplayServerUIKit::window_get_flag(WindowFlags p_flag, WindowID p_window) const { + return false; +} + +void DisplayServerUIKit::window_request_attention(WindowID p_window) { + // Probably not supported for iOS/tvOS +} + +void DisplayServerUIKit::window_move_to_foreground(WindowID p_window) { + // Probably not supported for iOS/tvOS +} + +float DisplayServerUIKit::screen_get_max_scale() const { + return screen_get_scale(SCREEN_OF_MAIN_WINDOW); +} + +bool DisplayServerUIKit::window_can_draw(WindowID p_window) const { + return true; +} + +bool DisplayServerUIKit::can_any_window_draw() const { + return true; +} + +void DisplayServerUIKit::screen_set_keep_on(bool p_enable) { + [UIApplication sharedApplication].idleTimerDisabled = p_enable; +} + +bool DisplayServerUIKit::screen_is_kept_on() const { + return [UIApplication sharedApplication].idleTimerDisabled; +} + +void DisplayServerUIKit::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 DisplayServerUIKit::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 DisplayServerUIKit::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 +} diff --git a/platform/iphone/joypad_iphone.h b/platform/uikit/uikit_joypad.h similarity index 90% rename from platform/iphone/joypad_iphone.h rename to platform/uikit/uikit_joypad.h index 37e272a2c901..745a617078d0 100644 --- a/platform/iphone/joypad_iphone.h +++ b/platform/uikit/uikit_joypad.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* joypad_iphone.h */ +/* uikit_joypad.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -30,7 +30,9 @@ #import -@interface JoypadIPhoneObserver : NSObject +class String; + +@interface UIKitJoypadObserver : NSObject - (void)startObserving; - (void)startProcessing; @@ -38,13 +40,15 @@ @end -class JoypadIPhone { +class UIKitJoypad { private: - JoypadIPhoneObserver *observer; + UIKitJoypadObserver *observer; public: - JoypadIPhone(); - ~JoypadIPhone(); + UIKitJoypad(); + ~UIKitJoypad(); void start_processing(); + + int joy_id_for_name(const String &p_name); }; diff --git a/platform/iphone/joypad_iphone.mm b/platform/uikit/uikit_joypad.mm similarity index 83% rename from platform/iphone/joypad_iphone.mm rename to platform/uikit/uikit_joypad.mm index 9c2feeaaca8f..2f3b8b868d8b 100644 --- a/platform/iphone/joypad_iphone.mm +++ b/platform/uikit/uikit_joypad.mm @@ -1,5 +1,5 @@ /*************************************************************************/ -/* joypad_iphone.mm */ +/* uikit_joypad.mm */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,35 +28,50 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#import "joypad_iphone.h" +#import "uikit_joypad.h" #include "core/config/project_settings.h" #include "drivers/coreaudio/audio_driver_coreaudio.h" #include "main/main.h" +#include "uikit_os.h" -#import "godot_view.h" +@interface UIKitJoypadObserver (JoypadSearch) -#include "os_iphone.h" +- (int)getJoyIdForControllerName:(NSString *)controllerName; -JoypadIPhone::JoypadIPhone() { - observer = [[JoypadIPhoneObserver alloc] init]; +@end + +UIKitJoypad::UIKitJoypad() { + observer = [[UIKitJoypadObserver alloc] init]; [observer startObserving]; } -JoypadIPhone::~JoypadIPhone() { +UIKitJoypad::~UIKitJoypad() { if (observer) { [observer finishObserving]; observer = nil; } } -void JoypadIPhone::start_processing() { +void UIKitJoypad::start_processing() { if (observer) { [observer startProcessing]; } } -@interface JoypadIPhoneObserver () +int UIKitJoypad::joy_id_for_name(const String &p_name) { + if (!observer) { + return -1; + } + + @autoreleasepool { + NSString *controllerName = [[NSString alloc] initWithUTF8String:p_name.utf8().get_data()]; + + return [observer getJoyIdForControllerName:controllerName]; + } +} + +@interface UIKitJoypadObserver () @property(assign, nonatomic) BOOL isObserving; @property(assign, nonatomic) BOOL isProcessing; @@ -65,19 +80,19 @@ @interface JoypadIPhoneObserver () @end -@implementation JoypadIPhoneObserver +@implementation UIKitJoypadObserver - (instancetype)init { self = [super init]; if (self) { - [self godot_commonInit]; + [self uikit_commonInit]; } return self; } -- (void)godot_commonInit { +- (void)uikit_commonInit { self.isObserving = NO; self.isProcessing = NO; } @@ -86,7 +101,7 @@ - (void)startProcessing { self.isProcessing = YES; for (GCController *controller in self.joypadsQueue) { - [self addiOSJoypad:controller]; + [self addUIKitJoypad:controller]; } [self.joypadsQueue removeAllObjects]; @@ -145,7 +160,22 @@ - (int)getJoyIdForController:(GCController *)controller { return -1; } -- (void)addiOSJoypad:(GCController *)controller { +- (int)getJoyIdForControllerName:(NSString *)controllerName { + NSArray *keys = [self.connectedJoypads allKeys]; + + for (NSNumber *key in keys) { + int joy_id = [key intValue]; + GCController *controller = self.connectedJoypads[key]; + + if ([controller.vendorName containsString:controllerName]) { + return joy_id; + } + } + + return -1; +} + +- (void)addUIKitJoypad:(GCController *)controller { // get a new id for our controller int joy_id = Input::get_singleton()->get_unused_joy_id(); @@ -183,7 +213,7 @@ - (void)controllerWasConnected:(NSNotification *)notification { } else if (!self.isProcessing) { [self.joypadsQueue addObject:controller]; } else { - [self addiOSJoypad:controller]; + [self addUIKitJoypad:controller]; } } @@ -305,16 +335,32 @@ - (void)setControllerInputHandler:(GCController *)controller { float value = gamepad.rightTrigger.value; Input::get_singleton()->joy_axis(joy_id, JoyAxis::TRIGGER_RIGHT, value); } + + if (@available(iOS 13.0, tvOS 13.0, *)) { + if (element == gamepad.buttonMenu) { + Input::get_singleton()->joy_button(joy_id, JoyButton::START, + gamepad.buttonMenu.isPressed); + } else if (element == gamepad.buttonOptions) { + Input::get_singleton()->joy_button(joy_id, JoyButton::BACK, + gamepad.buttonOptions.isPressed); + } + } }; } 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) { + controller.microGamepad.valueChangedHandler = ^(GCMicroGamepad *, GCControllerElement *element) { _strongify(self); _strongify(controller); + // Callback gamepad sometimes has different address then + // the one used by `controller.microGamepad` instance + // which results in gamepad loosing some button events. + + GCMicroGamepad *gamepad = controller.microGamepad; + int joy_id = [self getJoyIdForController:controller]; if (element == gamepad.buttonA) { @@ -324,10 +370,22 @@ - (void)setControllerInputHandler:(GCController *)controller { Input::get_singleton()->joy_button(joy_id, JoyButton::X, gamepad.buttonX.isPressed); } else if (element == gamepad.dpad) { + float value = gamepad.dpad.xAxis.value; + Input::get_singleton()->joy_axis(joy_id, JoyAxis::LEFT_X, value); + + 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); + + value = -gamepad.dpad.yAxis.value; + Input::get_singleton()->joy_axis(joy_id, JoyAxis::LEFT_Y, value); + 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); } diff --git a/platform/uikit/uikit_os.h b/platform/uikit/uikit_os.h new file mode 100644 index 000000000000..2a128cfc5f05 --- /dev/null +++ b/platform/uikit/uikit_os.h @@ -0,0 +1,99 @@ +/*************************************************************************/ +/* uikit_os.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 OS_UIKIT_H +#define OS_UIKIT_H + +#include "drivers/coreaudio/audio_driver_coreaudio.h" +#include "drivers/unix/os_unix.h" +#include "servers/audio_server.h" +#include "servers/rendering/renderer_compositor.h" +#include "uikit_joypad.h" + +#if defined(VULKAN_ENABLED) +#include "drivers/vulkan/rendering_device_vulkan.h" +#include "platform/uikit/uikit_vulkan_context.h" +#endif + +class OS_UIKit : public OS_Unix { +private: + AudioDriverCoreAudio audio_driver; + + UIKitJoypad *uikit_joypad; + + MainLoop *main_loop; + + String user_data_dir; + String cache_dir; + +protected: + 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; + +public: + static OS_UIKit *get_singleton(); + + OS_UIKit(String p_data_dir, String p_cache_dir); + ~OS_UIKit(); + + virtual bool iterate(); + + virtual void start(); + + virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false) override; + virtual Error close_dynamic_library(void *p_library_handle) override; + + virtual String get_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; + + int joy_id_for_name(const String &p_name); +}; + +#endif // OS_UIKIT_H diff --git a/platform/uikit/uikit_os.mm b/platform/uikit/uikit_os.mm new file mode 100644 index 000000000000..7b941b23dd83 --- /dev/null +++ b/platform/uikit/uikit_os.mm @@ -0,0 +1,214 @@ +/*************************************************************************/ +/* uikit_os.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 "uikit_os.h" + +#include "core/config/project_settings.h" +#include "core/io/dir_access.h" +#include "core/io/file_access.h" +#include "core/io/file_access_pack.h" +#include "drivers/unix/syslog_logger.h" +#include "main/main.h" +#include "uikit_display_server.h" + +#import +#import + +#if defined(VULKAN_ENABLED) +#include "servers/rendering/renderer_rd/renderer_compositor_rd.h" +#import +#ifdef USE_VOLK +#include +#else +#include +#endif +#endif + +OS_UIKit *OS_UIKit::get_singleton() { + return (OS_UIKit *)OS::get_singleton(); +} + +OS_UIKit::OS_UIKit(String p_data_dir, String p_cache_dir) { + 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 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); +} + +OS_UIKit::~OS_UIKit() {} + +void OS_UIKit::initialize_core() { + OS_Unix::initialize_core(); + + set_user_data_dir(user_data_dir); + + uikit_joypad = memnew(UIKitJoypad); +} + +void OS_UIKit::initialize() { + initialize_core(); +} + +void OS_UIKit::set_main_loop(MainLoop *p_main_loop) { + main_loop = p_main_loop; + + if (main_loop) { + main_loop->initialize(); + } +} + +MainLoop *OS_UIKit::get_main_loop() const { + return main_loop; +} + +void OS_UIKit::delete_main_loop() { + if (main_loop) { + main_loop->finalize(); + memdelete(main_loop); + }; + + main_loop = nullptr; +} + +bool OS_UIKit::iterate() { + if (!main_loop) { + return true; + } + + if (DisplayServerUIKit::get_singleton()) { + DisplayServerUIKit::get_singleton()->process_events(); + } + + return Main::iteration(); +} + +void OS_UIKit::start() { + Main::start(); + + if (uikit_joypad) { + uikit_joypad->start_processing(); + } +} + +void OS_UIKit::finalize() { + if (uikit_joypad) { + memdelete(uikit_joypad); + } + + // Already gets called + // delete_main_loop(); +} + +// MARK: Dynamic Libraries + +Error OS_UIKit::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) { + if (p_path.length() == 0) { + p_library_handle = RTLD_SELF; + return OK; + } + return OS_Unix::open_dynamic_library(p_path, p_library_handle, p_also_set_library_path); +} + +Error OS_UIKit::close_dynamic_library(void *p_library_handle) { + if (p_library_handle == RTLD_SELF) { + return OK; + } + return OS_Unix::close_dynamic_library(p_library_handle); +} + +String OS_UIKit::get_name() const { + return "UIKit"; +}; + +Error OS_UIKit::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_UIKit::set_user_data_dir(String p_dir) { + DirAccessRef 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_UIKit::get_user_data_dir() const { + return user_data_dir; +} + +String OS_UIKit::get_cache_path() const { + return cache_dir; +} + +String OS_UIKit::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_UIKit::get_unique_id() const { + NSString *uuid = [UIDevice currentDevice].identifierForVendor.UUIDString; + return String::utf8([uuid UTF8String]); +} + +int OS_UIKit::joy_id_for_name(const String &p_name) { + if (!uikit_joypad) { + return -1; + } + + return uikit_joypad->joy_id_for_name(p_name); +} diff --git a/platform/uikit/uikit_platform_config.h b/platform/uikit/uikit_platform_config.h new file mode 100644 index 000000000000..c94418c04486 --- /dev/null +++ b/platform/uikit/uikit_platform_config.h @@ -0,0 +1,44 @@ +/*************************************************************************/ +/* uikit_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 + +#define OPENGL_INCLUDE_H + +#define PLATFORM_REFCOUNT + +#define PTHREAD_RENAME_SELF + +#define _weakify(var) __weak typeof(var) GDWeak_##var = var; +#define _strongify(var) \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wshadow\"") \ + __strong typeof(var) var = GDWeak_##var; \ + _Pragma("clang diagnostic pop") diff --git a/platform/uikit/uikit_view.h b/platform/uikit/uikit_view.h new file mode 100644 index 000000000000..f1aa6aa28bd1 --- /dev/null +++ b/platform/uikit/uikit_view.h @@ -0,0 +1,64 @@ +/*************************************************************************/ +/* uikit_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 + +class String; + +@class UIKitView; +@protocol UIKitDisplayLayer; +@protocol UIKitViewRendererProtocol; + +@protocol UIKitViewDelegate + +- (BOOL)uikitViewFinishedSetup:(UIKitView *)view; + +@end + +@interface UIKitView : UIView + +@property(assign, nonatomic) id renderer; +@property(assign, nonatomic) id delegate; + +@property(assign, readonly, nonatomic) BOOL isActive; + +@property(strong, readonly, nonatomic) CALayer *renderingLayer; +@property(assign, readonly, nonatomic) BOOL canRender; + +@property(assign, nonatomic) NSTimeInterval renderingInterval; + +- (CALayer *)initializeRenderingForDriver:(NSString *)driverName; +- (void)stopRendering; +- (void)startRendering; +- (void)drawView; + +@property(nonatomic, assign) BOOL useCADisplayLink; + +@end diff --git a/platform/uikit/uikit_view.mm b/platform/uikit/uikit_view.mm new file mode 100644 index 000000000000..b8663836e942 --- /dev/null +++ b/platform/uikit/uikit_view.mm @@ -0,0 +1,255 @@ +/*************************************************************************/ +/* uikit_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 "uikit_view.h" + +#include "core/config/project_settings.h" +#include "servers/audio_server.h" + +#import + +#import "uikit_display_layer.h" +#import "uikit_view_renderer.h" + +@interface UIKitView () + +@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 *renderingLayer; + +@end + +@implementation UIKitView + +// Implement this to override the default layer class (which is [CALayer class]). +// We do this so that our view will be backed by a layer that is capable of OpenGL ES rendering. +- (CALayer *)initializeRenderingForDriver:(NSString *)driverName { + if (self.renderingLayer) { + return self.renderingLayer; + } + + CALayer *layer; + + if ([driverName isEqualToString:@"vulkan"]) { + layer = [UIKitMetalLayer 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 = [UIKitOpenGLLayer 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 uikit_commonInit]; + } + + return self; +} + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + + if (self) { + [self uikit_commonInit]; + } + + return self; +} + +// Stop animating and release resources when they are no longer needed. +- (void)dealloc { + [self stopRendering]; + + self.renderer = nil; + self.delegate = nil; + + if (self.renderingLayer) { + [self.renderingLayer removeFromSuperlayer]; + self.renderingLayer = nil; + } + + if (self.displayLink) { + [self.displayLink invalidate]; + self.displayLink = nil; + } + + if (self.animationTimer) { + [self.animationTimer invalidate]; + self.animationTimer = nil; + } +} + +- (void)uikit_commonInit { + self.contentScaleFactor = [UIScreen mainScreen].nativeScale; +} + +- (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)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; + } +} + +// Updates the OpenGL view when the timer fires +- (void)drawView { + if (!self.isActive) { + printf("draw view not active!\n"); + return; + } + + if (self.useCADisplayLink) { + // Pause the CADisplayLink to avoid recursion + [self.displayLink setPaused:YES]; + + // Process all input events + while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, TRUE) == kCFRunLoopRunHandledSource) + ; + + // We are good to go, resume the CADisplayLink + [self.displayLink setPaused:NO]; + } + + [self.renderingLayer startRenderDisplayLayer]; + + if (!self.renderer) { + return; + } + + if ([self.renderer setupView:self]) { + return; + } + + if (self.delegate) { + BOOL delegateFinishedSetup = [self.delegate uikitViewFinishedSetup:self]; + + if (!delegateFinishedSetup) { + return; + } + } + + [self.renderer renderOnView:self]; + + [self.renderingLayer stopRenderDisplayLayer]; +} + +- (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]; + } +} + +// If our view is resized, we'll be asked to layout subviews. +// This is the perfect opportunity to also update the framebuffer so that it is +// the same size as our display area. + +- (void)layoutSubviews { + if (self.renderingLayer) { + self.renderingLayer.frame = self.bounds; + [self.renderingLayer layoutDisplayLayer]; + } + + [super layoutSubviews]; +} + +@end diff --git a/platform/uikit/uikit_view_controller.h b/platform/uikit/uikit_view_controller.h new file mode 100644 index 000000000000..3cf7f997687e --- /dev/null +++ b/platform/uikit/uikit_view_controller.h @@ -0,0 +1,41 @@ +/*************************************************************************/ +/* uikit_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 + +#import "uikit_view.h" + +@class UIKitView; + +@interface UIKitViewController : UIViewController + +@property(nonatomic, readonly, strong) UIKitView *uikitView; + +@end diff --git a/platform/uikit/uikit_view_controller.mm b/platform/uikit/uikit_view_controller.mm new file mode 100644 index 000000000000..5ac82e1b3930 --- /dev/null +++ b/platform/uikit/uikit_view_controller.mm @@ -0,0 +1,126 @@ +/*************************************************************************/ +/* uikit_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 "uikit_view_controller.h" + +#include "core/config/project_settings.h" +#import "uikit_view.h" +#import "uikit_view_renderer.h" + +@interface UIKitViewController () + +@property(strong, nonatomic) UIView *uikitLoadingOverlay; + +@end + +@implementation UIKitViewController + +- (UIKitView *)uikitView { + return (UIKitView *)self.view; +} + +- (void)loadView { + UIKitView *view = [[UIKitView alloc] init]; + + self.view = view; + + view.delegate = self; +} + +- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { + self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; + + if (self) { + [self uikit_commonInit]; + } + + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + self = [super initWithCoder:coder]; + + if (self) { + [self uikit_commonInit]; + } + + return self; +} + +- (void)uikit_commonInit { + // Initialize view controller values. +} + +- (void)didReceiveMemoryWarning { + [super didReceiveMemoryWarning]; + printf("*********** did receive memory warning!\n"); +}; + +- (void)viewDidLoad { + [super viewDidLoad]; + + [self displayLoadingOverlay]; +} + +- (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.uikitLoadingOverlay = controller.view; + self.uikitLoadingOverlay.frame = self.view.bounds; + self.uikitLoadingOverlay.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; + + [self.view addSubview:self.uikitLoadingOverlay]; +} + +- (BOOL)uikitViewFinishedSetup:(UIKitView *)view { + [self.uikitLoadingOverlay removeFromSuperview]; + self.uikitLoadingOverlay = nil; + + return YES; +} + +- (void)dealloc { + if (self.uikitLoadingOverlay) { + [self.uikitLoadingOverlay removeFromSuperview]; + self.uikitLoadingOverlay = nil; + } + + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +@end diff --git a/platform/uikit/uikit_view_renderer.h b/platform/uikit/uikit_view_renderer.h new file mode 100644 index 000000000000..3c53ed4b6cea --- /dev/null +++ b/platform/uikit/uikit_view_renderer.h @@ -0,0 +1,46 @@ +/*************************************************************************/ +/* uikit_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 + +@protocol UIKitViewRendererProtocol + +@property(assign, readonly, nonatomic) BOOL hasFinishedSetup; + +- (BOOL)setupView:(UIView *)view; +- (void)renderOnView:(UIView *)view; + +@end + +@interface UIKitViewRenderer : NSObject + +- (BOOL)startUIKitPlatform; + +@end diff --git a/platform/uikit/uikit_view_renderer.mm b/platform/uikit/uikit_view_renderer.mm new file mode 100644 index 000000000000..149df76d809b --- /dev/null +++ b/platform/uikit/uikit_view_renderer.mm @@ -0,0 +1,112 @@ +/*************************************************************************/ +/* uikit_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 "uikit_view_renderer.h" + +#include "core/config/project_settings.h" +#include "core/os/keyboard.h" +#include "main/main.h" +#include "servers/audio_server.h" + +#import +#import +#import + +@interface UIKitViewRenderer () + +@property(assign, nonatomic) BOOL hasFinishedProjectDataSetup; +@property(assign, nonatomic) BOOL hasStartedMain; +@property(assign, nonatomic) BOOL hasFinishedSetup; + +@end + +@implementation UIKitViewRenderer + +- (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 = [self startUIKitPlatform]; + 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 + } +} + +- (BOOL)startUIKitPlatform { + return NO; +} + +- (void)renderOnView:(UIView *)view { +} + +@end diff --git a/platform/iphone/vulkan_context_iphone.h b/platform/uikit/uikit_vulkan_context.h similarity index 90% rename from platform/iphone/vulkan_context_iphone.h rename to platform/uikit/uikit_vulkan_context.h index 757652575595..e0cddc442b0e 100644 --- a/platform/iphone/vulkan_context_iphone.h +++ b/platform/uikit/uikit_vulkan_context.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* vulkan_context_iphone.h */ +/* uikit_vulkan_context.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,21 +28,22 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef VULKAN_CONTEXT_IPHONE_H -#define VULKAN_CONTEXT_IPHONE_H +#ifndef VULKAN_CONTEXT_UIKIT_H +#define VULKAN_CONTEXT_UIKIT_H #include "drivers/vulkan/vulkan_context.h" #import -class VulkanContextIPhone : public VulkanContext { +class VulkanContextUIKit : public VulkanContext { +protected: 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); - VulkanContextIPhone(); - ~VulkanContextIPhone(); + VulkanContextUIKit(); + ~VulkanContextUIKit(); }; -#endif // VULKAN_CONTEXT_IPHONE_H +#endif // VULKAN_CONTEXT_UIKIT_H diff --git a/platform/iphone/vulkan_context_iphone.mm b/platform/uikit/uikit_vulkan_context.mm similarity index 86% rename from platform/iphone/vulkan_context_iphone.mm rename to platform/uikit/uikit_vulkan_context.mm index 17cb0f60098c..7a52fef9ad62 100644 --- a/platform/iphone/vulkan_context_iphone.mm +++ b/platform/uikit/uikit_vulkan_context.mm @@ -1,5 +1,5 @@ /*************************************************************************/ -/* vulkan_context_iphone.mm */ +/* uikit_vulkan_context.mm */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,18 +28,18 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "vulkan_context_iphone.h" +#include "uikit_vulkan_context.h" #ifdef USE_VOLK #include #else #include #endif -const char *VulkanContextIPhone::_get_platform_surface_extension() const { +const char *VulkanContextUIKit::_get_platform_surface_extension() const { return VK_MVK_IOS_SURFACE_EXTENSION_NAME; } -Error VulkanContextIPhone::window_create(DisplayServer::WindowID p_window_id, DisplayServer::VSyncMode p_vsync_mode, CALayer *p_metal_layer, int p_width, int p_height) { +Error VulkanContextUIKit::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; @@ -54,6 +54,6 @@ return _window_create(p_window_id, p_vsync_mode, surface, p_width, p_height); } -VulkanContextIPhone::VulkanContextIPhone() {} +VulkanContextUIKit::VulkanContextUIKit() {} -VulkanContextIPhone::~VulkanContextIPhone() {} +VulkanContextUIKit::~VulkanContextUIKit() {} From e4496ff69ae2c44ff4c374a2c3249b2efa57f1be Mon Sep 17 00:00:00 2001 From: Sergey Minakov Date: Tue, 8 Feb 2022 09:59:26 +0300 Subject: [PATCH 2/6] [tvOS] tvOS platform support --- core/input/input_builders.py | 1 + doc/classes/EditorExportPlugin.xml | 51 + doc/classes/ProjectSettings.xml | 3 + drivers/gles3/rasterizer_gles3.cpp | 2 +- drivers/unix/os_unix.cpp | 4 + drivers/vulkan/SCsub | 2 +- editor/editor_export.cpp | 66 + editor/editor_export.h | 31 + main/main.cpp | 1 + methods.py | 6 + platform/tvos/SCsub | 47 + platform/tvos/api/api.cpp | 48 + platform/tvos/api/api.h | 42 + platform/tvos/app_delegate.h | 47 + platform/tvos/app_delegate.mm | 135 ++ platform/tvos/detect.py | 182 +++ platform/tvos/display_server_tvos.h | 83 ++ platform/tvos/display_server_tvos.mm | 176 +++ platform/tvos/export/export.cpp | 1264 +++++++++++++++++ platform/tvos/export/export.h | 36 + platform/tvos/export/godot_plugin_config.h | 290 ++++ platform/tvos/godot_app_delegate.h | 35 + platform/tvos/godot_app_delegate.m | 45 + platform/tvos/godot_tvos.mm | 124 ++ platform/tvos/godot_view.h | 35 + platform/tvos/godot_view.mm | 135 ++ platform/tvos/godot_view_controller.h | 42 + platform/tvos/godot_view_controller.mm | 116 ++ platform/tvos/godot_view_gesture_recognizer.h | 47 + .../tvos/godot_view_gesture_recognizer.mm | 124 ++ platform/tvos/godot_view_renderer.h | 36 + platform/tvos/godot_view_renderer.mm | 60 + platform/tvos/keyboard_input_view.h | 37 + platform/tvos/keyboard_input_view.mm | 217 +++ platform/tvos/logo.png | Bin 0 -> 727 bytes platform/tvos/main.m | 56 + platform/tvos/os_tvos.h | 88 ++ platform/tvos/os_tvos.mm | 189 +++ platform/tvos/platform_config.h | 44 + platform/tvos/tvos.h | 53 + platform/tvos/tvos.mm | 103 ++ .../renderer_rd/cluster_builder_rd.cpp | 10 + servers/rendering/renderer_rd/effects_rd.cpp | 2 +- .../renderer_rd/renderer_scene_render_rd.cpp | 14 +- servers/rendering/renderer_rd/shader_rd.cpp | 2 +- 45 files changed, 4120 insertions(+), 11 deletions(-) create mode 100644 platform/tvos/SCsub create mode 100644 platform/tvos/api/api.cpp create mode 100644 platform/tvos/api/api.h create mode 100644 platform/tvos/app_delegate.h create mode 100644 platform/tvos/app_delegate.mm create mode 100644 platform/tvos/detect.py create mode 100644 platform/tvos/display_server_tvos.h create mode 100644 platform/tvos/display_server_tvos.mm create mode 100644 platform/tvos/export/export.cpp create mode 100644 platform/tvos/export/export.h create mode 100644 platform/tvos/export/godot_plugin_config.h create mode 100644 platform/tvos/godot_app_delegate.h create mode 100644 platform/tvos/godot_app_delegate.m create mode 100644 platform/tvos/godot_tvos.mm create mode 100644 platform/tvos/godot_view.h create mode 100644 platform/tvos/godot_view.mm create mode 100644 platform/tvos/godot_view_controller.h create mode 100644 platform/tvos/godot_view_controller.mm create mode 100644 platform/tvos/godot_view_gesture_recognizer.h create mode 100644 platform/tvos/godot_view_gesture_recognizer.mm create mode 100644 platform/tvos/godot_view_renderer.h create mode 100644 platform/tvos/godot_view_renderer.mm create mode 100644 platform/tvos/keyboard_input_view.h create mode 100644 platform/tvos/keyboard_input_view.mm create mode 100644 platform/tvos/logo.png create mode 100644 platform/tvos/main.m create mode 100644 platform/tvos/os_tvos.h create mode 100644 platform/tvos/os_tvos.mm create mode 100644 platform/tvos/platform_config.h create mode 100644 platform/tvos/tvos.h create mode 100644 platform/tvos/tvos.mm diff --git a/core/input/input_builders.py b/core/input/input_builders.py index 748ec0613307..d2b07783c14b 100644 --- a/core/input/input_builders.py +++ b/core/input/input_builders.py @@ -50,6 +50,7 @@ def make_default_controller_mappings(target, source, env): "Mac OS X": "#ifdef OSX_ENABLED", "Android": "#if defined(__ANDROID__)", "iOS": "#ifdef IPHONE_ENABLED", + "tvOS": "#ifdef TVOS_ENABLED", "Javascript": "#ifdef JAVASCRIPT_ENABLED", "UWP": "#ifdef UWP_ENABLED", } diff --git a/doc/classes/EditorExportPlugin.xml b/doc/classes/EditorExportPlugin.xml index 8aa2db2cf80c..0b26ca839e28 100644 --- a/doc/classes/EditorExportPlugin.xml +++ b/doc/classes/EditorExportPlugin.xml @@ -115,6 +115,57 @@ In case of a directory code-sign will error if you place non code object in directory. + + + + + Adds an tvOS bundle file from the given [code]path[/code] to the exported project. + + + + + + + Adds a C++ code to the tvOS export. The final code is created from the code appended by each active export plugin. + + + + + + + Adds a dynamic library (*.dylib, *.framework) to Linking Phase in tvOS's Xcode project and embeds it into resulting binary. + [b]Note:[/b] For static libraries (*.a) works in same way as [code]add_ios_framework[/code]. + This method should not be used for System libraries as they are already present on the device. + + + + + + + Adds a static library (*.a) or dynamic library (*.dylib, *.framework) to Linking Phase in tvOS's Xcode project. + + + + + + + Adds linker flags for the tvOS export. + + + + + + + Adds content for tvOS Property List files. + + + + + + + Adds a static lib from the given [code]path[/code] to the tvOS project. + + diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index a8b4129061ad..6424349ea6ba 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -774,6 +774,9 @@ Default delay for touch events. This only affects iOS devices. + + Default delay for end press events. This only affects tvOS devices. + The locale to fall back to if a translation isn't available in a given language. If left empty, [code]en[/code] (English) will be used. diff --git a/drivers/gles3/rasterizer_gles3.cpp b/drivers/gles3/rasterizer_gles3.cpp index 4cc3e69e21c9..0b9ff1aa86dc 100644 --- a/drivers/gles3/rasterizer_gles3.cpp +++ b/drivers/gles3/rasterizer_gles3.cpp @@ -69,7 +69,7 @@ #if !defined(UIKIT_ENABLED) && !defined(JAVASCRIPT_ENABLED) // We include EGL below to get debug callback on GLES2 platforms, -// but EGL is not available on iOS. +// but EGL is not available on iOS/tvOS. #define CAN_DEBUG #endif diff --git a/drivers/unix/os_unix.cpp b/drivers/unix/os_unix.cpp index 3b5e1bf91d53..6c1e54807102 100644 --- a/drivers/unix/os_unix.cpp +++ b/drivers/unix/os_unix.cpp @@ -294,6 +294,8 @@ Error OS_Unix::execute(const String &p_path, const List &p_arguments, St // Don't compile this code at all to avoid undefined references. // Actual virtual call goes to OS_JavaScript. ERR_FAIL_V(ERR_BUG); +#elif defined(TVOS_ENABLED) + ERR_FAIL_V(ERR_CANT_FORK); #else if (r_pipe) { String command = "\"" + p_path + "\""; @@ -363,6 +365,8 @@ Error OS_Unix::create_process(const String &p_path, const List &p_argume // Don't compile this code at all to avoid undefined references. // Actual virtual call goes to OS_JavaScript. ERR_FAIL_V(ERR_BUG); +#elif defined(TVOS_ENABLED) + ERR_FAIL_V(ERR_CANT_FORK); #else pid_t pid = fork(); ERR_FAIL_COND_V(pid < 0, ERR_CANT_FORK); diff --git a/drivers/vulkan/SCsub b/drivers/vulkan/SCsub index b6ceb1cdeafd..7ad7a755ee6d 100644 --- a/drivers/vulkan/SCsub +++ b/drivers/vulkan/SCsub @@ -15,7 +15,7 @@ if env["use_volk"]: if env["platform"] == "android": env.AppendUnique(CPPDEFINES=["VK_USE_PLATFORM_ANDROID_KHR"]) -elif env["platform"] == "iphone": +elif env["platform"] == "iphone" or env["platform"] == "tvos": env.AppendUnique(CPPDEFINES=["VK_USE_PLATFORM_IOS_MVK"]) elif env["platform"] == "linuxbsd": env.AppendUnique(CPPDEFINES=["VK_USE_PLATFORM_XLIB_KHR"]) diff --git a/editor/editor_export.cpp b/editor/editor_export.cpp index afb5bd9d4d4d..c659c26602ec 100644 --- a/editor/editor_export.cpp +++ b/editor/editor_export.cpp @@ -637,6 +637,65 @@ Vector EditorExportPlugin::get_ios_project_static_libs() const { return ios_project_static_libs; } +void EditorExportPlugin::add_tvos_framework(const String &p_path) { + tvos_frameworks.push_back(p_path); +} + +void EditorExportPlugin::add_tvos_embedded_framework(const String &p_path) { + tvos_embedded_frameworks.push_back(p_path); +} + +Vector EditorExportPlugin::get_tvos_frameworks() const { + return tvos_frameworks; +} + +Vector EditorExportPlugin::get_tvos_embedded_frameworks() const { + return tvos_embedded_frameworks; +} + +void EditorExportPlugin::add_tvos_plist_content(const String &p_plist_content) { + tvos_plist_content += p_plist_content + "\n"; +} + +String EditorExportPlugin::get_tvos_plist_content() const { + return tvos_plist_content; +} + +void EditorExportPlugin::add_tvos_linker_flags(const String &p_flags) { + if (tvos_linker_flags.length() > 0) { + tvos_linker_flags += ' '; + } + tvos_linker_flags += p_flags; +} + +String EditorExportPlugin::get_tvos_linker_flags() const { + return tvos_linker_flags; +} + +void EditorExportPlugin::add_tvos_bundle_file(const String &p_path) { + tvos_bundle_files.push_back(p_path); +} + +Vector EditorExportPlugin::get_tvos_bundle_files() const { + return tvos_bundle_files; +} + +void EditorExportPlugin::add_tvos_cpp_code(const String &p_code) { + tvos_cpp_code += p_code; +} + +String EditorExportPlugin::get_tvos_cpp_code() const { + return tvos_cpp_code; +} + +void EditorExportPlugin::add_tvos_project_static_lib(const String &p_path) { + tvos_project_static_libs.push_back(p_path); +} + +Vector EditorExportPlugin::get_tvos_project_static_libs() const { + return tvos_project_static_libs; +} + void EditorExportPlugin::_export_file_script(const String &p_path, const String &p_type, const Vector &p_features) { GDVIRTUAL_CALL(_export_file, p_path, p_type, p_features); } @@ -670,6 +729,13 @@ void EditorExportPlugin::_bind_methods() { ClassDB::bind_method(D_METHOD("add_ios_bundle_file", "path"), &EditorExportPlugin::add_ios_bundle_file); ClassDB::bind_method(D_METHOD("add_ios_cpp_code", "code"), &EditorExportPlugin::add_ios_cpp_code); ClassDB::bind_method(D_METHOD("add_osx_plugin_file", "path"), &EditorExportPlugin::add_osx_plugin_file); + ClassDB::bind_method(D_METHOD("add_tvos_project_static_lib", "path"), &EditorExportPlugin::add_tvos_project_static_lib); + ClassDB::bind_method(D_METHOD("add_tvos_framework", "path"), &EditorExportPlugin::add_tvos_framework); + ClassDB::bind_method(D_METHOD("add_tvos_embedded_framework", "path"), &EditorExportPlugin::add_tvos_embedded_framework); + ClassDB::bind_method(D_METHOD("add_tvos_plist_content", "plist_content"), &EditorExportPlugin::add_tvos_plist_content); + ClassDB::bind_method(D_METHOD("add_tvos_linker_flags", "flags"), &EditorExportPlugin::add_tvos_linker_flags); + ClassDB::bind_method(D_METHOD("add_tvos_bundle_file", "path"), &EditorExportPlugin::add_tvos_bundle_file); + ClassDB::bind_method(D_METHOD("add_tvos_cpp_code", "code"), &EditorExportPlugin::add_tvos_cpp_code); ClassDB::bind_method(D_METHOD("skip"), &EditorExportPlugin::skip); GDVIRTUAL_BIND(_export_file, "path", "type", "features"); diff --git a/editor/editor_export.h b/editor/editor_export.h index 108abab29bc8..f2a74ec44a76 100644 --- a/editor/editor_export.h +++ b/editor/editor_export.h @@ -311,6 +311,14 @@ class EditorExportPlugin : public RefCounted { Vector osx_plugin_files; + Vector tvos_frameworks; + Vector tvos_embedded_frameworks; + Vector tvos_project_static_libs; + String tvos_plist_content; + String tvos_linker_flags; + Vector tvos_bundle_files; + String tvos_cpp_code; + _FORCE_INLINE_ void _clear() { shared_objects.clear(); extra_files.clear(); @@ -325,6 +333,13 @@ class EditorExportPlugin : public RefCounted { ios_linker_flags = ""; ios_cpp_code = ""; osx_plugin_files.clear(); + + tvos_frameworks.clear(); + tvos_embedded_frameworks.clear(); + tvos_bundle_files.clear(); + tvos_plist_content = ""; + tvos_linker_flags = ""; + tvos_cpp_code = ""; } void _export_file_script(const String &p_path, const String &p_type, const Vector &p_features); @@ -347,6 +362,14 @@ class EditorExportPlugin : public RefCounted { void add_ios_cpp_code(const String &p_code); void add_osx_plugin_file(const String &p_path); + void add_tvos_framework(const String &p_path); + void add_tvos_embedded_framework(const String &p_path); + void add_tvos_project_static_lib(const String &p_path); + void add_tvos_plist_content(const String &p_plist_content); + void add_tvos_linker_flags(const String &p_flags); + void add_tvos_bundle_file(const String &p_path); + void add_tvos_cpp_code(const String &p_code); + void skip(); virtual void _export_file(const String &p_path, const String &p_type, const Set &p_features); @@ -368,6 +391,14 @@ class EditorExportPlugin : public RefCounted { String get_ios_cpp_code() const; const Vector &get_osx_plugin_files() const; + Vector get_tvos_frameworks() const; + Vector get_tvos_embedded_frameworks() const; + Vector get_tvos_project_static_libs() const; + String get_tvos_plist_content() const; + String get_tvos_linker_flags() const; + Vector get_tvos_bundle_files() const; + String get_tvos_cpp_code() const; + EditorExportPlugin(); }; diff --git a/main/main.cpp b/main/main.cpp index f41fa136bac4..51756e050727 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -1487,6 +1487,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph GLOBAL_DEF("display/window/ios/hide_home_indicator", true); GLOBAL_DEF("input_devices/pointing/ios/touch_delay", 0.150); + GLOBAL_DEF("input_devices/pointing/tvos/press_end_delay", 0.150); Engine::get_singleton()->set_frame_delay(frame_delay); diff --git a/methods.py b/methods.py index fe84641e9d06..13b978d0510d 100644 --- a/methods.py +++ b/methods.py @@ -853,6 +853,12 @@ def detect_darwin_sdk_path(platform, env): elif platform == "iphonesimulator": sdk_name = "iphonesimulator" var_name = "IPHONESDK" + elif platform == "tvos": + sdk_name = "appletvos" + var_name = "TVOSSDK" + elif platform == "tvossimulator": + sdk_name = "appletvsimulator" + var_name = "TVOSSDK" else: raise Exception("Invalid platform argument passed to detect_darwin_sdk_path") diff --git a/platform/tvos/SCsub b/platform/tvos/SCsub new file mode 100644 index 000000000000..fe106af1413f --- /dev/null +++ b/platform/tvos/SCsub @@ -0,0 +1,47 @@ +#!/usr/bin/env python + +Import("env") + +tvos_lib = [ + "#platform/uikit/uikit_app_delegate.m", + "#platform/uikit/uikit_display_layer.mm", + "#platform/uikit/uikit_display_server.mm", + "#platform/uikit/uikit_joypad.mm", + "#platform/uikit/uikit_os.mm", + "#platform/uikit/uikit_view_controller.mm", + "#platform/uikit/uikit_view_renderer.mm", + "#platform/uikit/uikit_view.mm", + "#platform/uikit/uikit_vulkan_context.mm", + "godot_tvos.mm", + "os_tvos.mm", + "main.m", + "app_delegate.mm", + "tvos.mm", + "display_server_tvos.mm", + "godot_view.mm", + "godot_app_delegate.m", + "godot_view_controller.mm", + "godot_view_renderer.mm", + "godot_view_gesture_recognizer.mm", + "keyboard_input_view.mm", +] + +env_tvos = env.Clone() +tvos_lib = env_tvos.add_library("tvos", tvos_lib) + +# (tvOS) Enable module support +env_tvos.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 = "$TVOSPATH/usr/bin/${tvos_triple}libtool" + else: + libtool = "$TVOSPATH/usr/bin/libtool" + env.Execute( + libtool + ' -static -o "' + lib_path + '" ' + " ".join([('"' + lib.srcnode().abspath + '"') for lib in source]) + ) + + +combine_command = env_tvos.Command("#bin/libgodot" + env_tvos["LIBSUFFIX"], [tvos_lib] + env_tvos["LIBS"], combine_libs) diff --git a/platform/tvos/api/api.cpp b/platform/tvos/api/api.cpp new file mode 100644 index 000000000000..1a709c725c05 --- /dev/null +++ b/platform/tvos/api/api.cpp @@ -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(TVOS_ENABLED) + +void register_tvos_api() { + godot_tvos_plugins_initialize(); +} + +void unregister_tvos_api() { + godot_tvos_plugins_deinitialize(); +} + +#else + +void register_tvos_api() {} +void unregister_tvos_api() {} + +#endif diff --git a/platform/tvos/api/api.h b/platform/tvos/api/api.h new file mode 100644 index 000000000000..90a685015eed --- /dev/null +++ b/platform/tvos/api/api.h @@ -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 TVOS_API_H +#define TVOS_API_H + +#if defined(TVOS_ENABLED) +extern void godot_tvos_plugins_initialize(); +extern void godot_tvos_plugins_deinitialize(); +#endif + +void register_tvos_api(); +void unregister_tvos_api(); + +#endif // TVOS_API_H diff --git a/platform/tvos/app_delegate.h b/platform/tvos/app_delegate.h new file mode 100644 index 000000000000..343f75633b31 --- /dev/null +++ b/platform/tvos/app_delegate.h @@ -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 + +@class GodotViewController; + +// FIXME: Add support for both OpenGL and Vulkan when OpenGL is implemented again, +// so it can't be done with compilation time branching. +//#if defined(GLES3_ENABLED) +//@interface AppDelegate : NSObject { +//#endif +//#if defined(VULKAN_ENABLED) +@interface AppDelegate : NSObject +//#endif + +@property(strong, nonatomic) UIWindow *window; +@property(strong, class, readonly, nonatomic) GodotViewController *viewController; + +@end diff --git a/platform/tvos/app_delegate.mm b/platform/tvos/app_delegate.mm new file mode 100644 index 000000000000..9779dcb06c52 --- /dev/null +++ b/platform/tvos/app_delegate.mm @@ -0,0 +1,135 @@ +/*************************************************************************/ +/* 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" +#import "godot_view_controller.h" +#include "main/main.h" +#include "os_tvos.h" + +#import + +#define kRenderingFrequency 60 + +extern int gargc; +extern char **gargv; + +extern int appletv_main(int, char **, String, String); +extern void appletv_finish(); + +@implementation AppDelegate + +static GodotViewController *mainViewController = nil; + ++ (GodotViewController *)viewController { + return mainViewController; +} + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + // Create a full-screen window + CGRect windowBounds = [[UIScreen mainScreen] bounds]; + self.window = [[UIWindow alloc] initWithFrame:windowBounds]; + + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, + NSUserDomainMask, YES); + NSString *documentsDirectory = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"GodotDocuments"]; + + if (![[NSFileManager defaultManager] fileExistsAtPath:documentsDirectory]) { + [[NSFileManager defaultManager] createDirectoryAtPath:documentsDirectory withIntermediateDirectories:YES attributes:nil error:nil]; + } + + paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); + NSString *cacheDirectory = [paths objectAtIndex:0]; + + int err = appletv_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; + } + + // WARNING: We must *always* create the GodotView after we have constructed the + // OS with appletv_main. This allows the GodotView to access project settings so + // it can properly initialize the OpenGL context + + GodotViewController *viewController = [[GodotViewController 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]; + + mainViewController = viewController; + + // prevent to stop music in another background app + [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:nil]; + + return YES; +} + +- (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 { + appletv_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 { + OSAppleTV::get_singleton()->on_focus_out(); +} + +- (void)applicationDidBecomeActive:(UIApplication *)application { + OSAppleTV::get_singleton()->on_focus_in(); +} + +- (void)dealloc { + self.window = nil; +} + +@end diff --git a/platform/tvos/detect.py b/platform/tvos/detect.py new file mode 100644 index 000000000000..a93444cf04ab --- /dev/null +++ b/platform/tvos/detect.py @@ -0,0 +1,182 @@ +import os +import sys +from methods import detect_darwin_sdk_path + + +def is_active(): + return True + + +def get_name(): + return "tvOS" + + +def can_build(): + if sys.platform == "darwin" or ("OSXCROSS_TVOS" in os.environ): + return True + + return False + + +def get_opts(): + from SCons.Variables import BoolVariable + + return [ + ( + "TVOSPATH", + "Path to tvOS toolchain", + "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain", + ), + ("TVOSSDK", "Path to the tvOS SDK", ""), + BoolVariable("simulator", "Build for simulator", False), + ("tvos_triple", "Triple for tvOS 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) + env.Append(CCFLAGS=["-O2", "-ftree-vectorize", "-fomit-frame-pointer"]) + env.Append(LINKFLAGS=["-O2"]) + else: # optimize for size + env.Append(CCFLAGS=["-Os", "-ftree-vectorize"]) + env.Append(LINKFLAGS=["-Os"]) + + if env["target"] == "release_debug": + env.Append(CPPDEFINES=["DEBUG_ENABLED"]) + + elif env["target"] == "debug": + env.Append(CCFLAGS=["-gdwarf-2", "-O0"]) + env.Append(CPPDEFINES=["_DEBUG", ("DEBUG", 1), "DEBUG_ENABLED"]) + + if env["use_lto"]: + env.Append(CCFLAGS=["-flto=thin"]) + env.Append(LINKFLAGS=["-flto=thin"]) + + ## Architecture + if env["arch"] == "x86": # i386 + env["bits"] = "32" + elif env["arch"] == "x86_64": + env["bits"] = "64" + else: # armv64 + env["arch"] = "arm64" + env["bits"] = "64" + + ## Compiler configuration + + # Save this in environment for use by other modules + if "OSXCROSS_IOS" in os.environ: + env["osxcross"] = True + + env["ENV"]["PATH"] = env["TVOSSDK"] + "/Developer/usr/bin/:" + env["ENV"]["PATH"] + + compiler_path = "$TVOSPATH/usr/bin/${tvos_triple}" + s_compiler_path = "$TVOSPATH/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["simulator"]: + detect_darwin_sdk_path("tvossimulator", env) + env.Append(CCFLAGS=("-stdlib=libc++ -isysroot $TVOSSDK -mappletvsimulator-version-min=10.0").split()) + env.Append(LINKFLAGS=[" -mappletvsimulator-version-min=10.0"]) + else: + detect_darwin_sdk_path("tvos", env) + env.Append(CCFLAGS=("-stdlib=libc++ -isysroot $TVOSSDK -mappletvos-version-min=10.0").split()) + env.Append(LINKFLAGS=["-mappletvos-version-min=10.0"]) + + if env["arch"] == "x86" or env["arch"] == "x86_64": + env["ENV"]["MACOSX_DEPLOYMENT_TARGET"] = "10.9" + arch_flag = "i386" if env["arch"] == "x86" else env["arch"] + env.Append( + CCFLAGS=( + "-arch " + + arch_flag + + " -fobjc-arc -fobjc-abi-version=2 -fobjc-legacy-dispatch -fmessage-length=0 -fpascal-strings -fblocks -fasm-blocks" + ).split() + ) + 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".split() + ) + env.Append(CPPDEFINES=["NEED_LONG_INT"]) + env.Append(CPPDEFINES=["LIBYUV_DISABLE_NEON"]) + + # Temp fix to silence 'aligned deallocation function' diagnostics + env.Append(CCFLAGS=["-faligned-allocation"]) + + # Temp fix for ABS/MAX/MIN macros in tvOS/iOS SDK blocking compilation + env.Append(CCFLAGS=["-Wno-ambiguous-macro"]) + + # tvOS requires Bitcode. + env.Append(CCFLAGS=["-fembed-bitcode"]) + env.Append(LINKFLAGS=["-bitcode_bundle"]) + + ## Link flags + + if env["arch"] == "x86" or env["arch"] == "x86_64": + arch_flag = "i386" if env["arch"] == "x86" else env["arch"] + env.Append( + LINKFLAGS=[ + "-arch", + arch_flag, + "-isysroot", + "$TVOSSDK", + "-Xlinker", + "-objc_abi_version", + "-Xlinker", + "2", + "-F$TVOSSDK", + ] + ) + + if env["arch"] == "arm64": + env.Append(LINKFLAGS=["-arch", "arm64", "-Wl,-dead_strip"]) + + env.Append( + LINKFLAGS=[ + "-isysroot", + "$TVOSSDK", + ] + ) + + env.Prepend( + CPPPATH=[ + "$TVOSSDK/usr/include", + "$TVOSSDK/System/Library/Frameworks/OpenGLES.framework/Headers", + "$TVOSSDK/System/Library/Frameworks/AudioUnit.framework/Headers", + ] + ) + + env.Prepend(CPPPATH=["#platform/tvos"]) + env.Append(CPPDEFINES=["UIKIT_ENABLED", "TVOS_ENABLED", "UNIX_ENABLED", "COREAUDIO_ENABLED"]) + + env.Append(CPPDEFINES=["VULKAN_ENABLED"]) + env.Append(LINKFLAGS=["-framework", "IOSurface"]) + + # Use Static Vulkan for tvOS. Dynamic Framework works fine too. + env.Append(LINKFLAGS=["-framework", "MoltenVK"]) + env["builtin_vulkan"] = False diff --git a/platform/tvos/display_server_tvos.h b/platform/tvos/display_server_tvos.h new file mode 100644 index 000000000000..1c0c4762c6a9 --- /dev/null +++ b/platform/tvos/display_server_tvos.h @@ -0,0 +1,83 @@ +/*************************************************************************/ +/* display_server_tvos.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_tvos_h +#define display_server_tvos_h + +#include "platform/uikit/uikit_display_server.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" + +#import +#ifdef USE_VOLK +#include +#else +#include +#endif +#endif + +CALayer *initialize_uikit_rendering_layer(const String &p_driver); + +class DisplayServerAppleTV : public DisplayServerUIKit { + GDCLASS(DisplayServerAppleTV, DisplayServer) + + _THREAD_SAFE_CLASS_ + + DisplayServerAppleTV(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); + ~DisplayServerAppleTV(); + +public: + static DisplayServerAppleTV *get_singleton(); + + static void register_tvos_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 get_rendering_drivers_func(); + + // MARK: - + + virtual bool has_feature(Feature p_feature) const override; + virtual String get_name() 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 int64_t window_get_native_handle(HandleType p_handle_type, WindowID p_window = MAIN_WINDOW_ID) 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; +}; + +#endif /* display_server_tvos_h */ diff --git a/platform/tvos/display_server_tvos.mm b/platform/tvos/display_server_tvos.mm new file mode 100644 index 000000000000..5b7cb2d270ee --- /dev/null +++ b/platform/tvos/display_server_tvos.mm @@ -0,0 +1,176 @@ +/*************************************************************************/ +/* display_server_tvos.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_tvos.h" +#import "app_delegate.h" +#include "core/config/project_settings.h" +#include "core/io/file_access_pack.h" +#import "godot_view.h" +#import "godot_view_controller.h" +#import "keyboard_input_view.h" +#include "os_tvos.h" +#include "tvos.h" + +#import +#import + +CALayer *initialize_uikit_rendering_layer(const String &p_driver) { + NSString *driverName = [NSString stringWithUTF8String:p_driver.utf8().get_data()]; + return [AppDelegate.viewController.godotView initializeRenderingForDriver:driverName]; +} + +DisplayServerAppleTV *DisplayServerAppleTV::get_singleton() { + return (DisplayServerAppleTV *)DisplayServer::get_singleton(); +} + +DisplayServerAppleTV::DisplayServerAppleTV(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) : + DisplayServerUIKit(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_resolution, r_error) { +} + +DisplayServerAppleTV::~DisplayServerAppleTV() { +} + +DisplayServer *DisplayServerAppleTV::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(DisplayServerAppleTV(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_resolution, r_error)); +} + +Vector DisplayServerAppleTV::get_rendering_drivers_func() { + Vector drivers; + +#if defined(VULKAN_ENABLED) + drivers.push_back("vulkan"); +#endif +#if defined(GLES3_ENABLED) + drivers.push_back("opengl_es"); +#endif + + return drivers; +} + +void DisplayServerAppleTV::register_tvos_driver() { + register_create_function("tvos", create_func, get_rendering_drivers_func); +} + +// MARK: - + +bool DisplayServerAppleTV::has_feature(Feature p_feature) const { + switch (p_feature) { + // case FEATURE_CONSOLE_WINDOW: + // case FEATURE_CURSOR_SHAPE: + // case FEATURE_CUSTOM_CURSOR_SHAPE: + // case FEATURE_GLOBAL_MENU: + // case FEATURE_HIDPI: + // case FEATURE_ICON: + // case FEATURE_IME: + // case FEATURE_MOUSE: + // case FEATURE_MOUSE_WARP: + // case FEATURE_NATIVE_DIALOG: + // case FEATURE_NATIVE_ICON: + // case FEATURE_NATIVE_VIDEO: + // case FEATURE_WINDOW_TRANSPARENCY: + // case FEATURE_CLIPBOARD: + case FEATURE_KEEP_SCREEN_ON: + // case FEATURE_ORIENTATION: + case FEATURE_TOUCHSCREEN: + case FEATURE_VIRTUAL_KEYBOARD: + return true; + default: + return false; + } +} + +String DisplayServerAppleTV::get_name() const { + return "tvOS"; +} + +Size2i DisplayServerAppleTV::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 DisplayServerAppleTV::screen_get_usable_rect(int p_screen) const { + if (@available(tvOS 11, *)) { + UIEdgeInsets insets = UIEdgeInsetsZero; + UIView *view = AppDelegate.viewController.godotView; + + if ([view respondsToSelector:@selector(safeAreaInsets)]) { + insets = [view safeAreaInsets]; + } + + float scale = screen_get_scale(p_screen); + Size2i insets_position = Size2i(insets.left, insets.top) * scale; + Size2i insets_size = Size2i(insets.left + insets.right, insets.top + insets.bottom) * scale; + + return Rect2i(screen_get_position(p_screen) + insets_position, screen_get_size(p_screen) - insets_size); + } else { + return Rect2i(screen_get_position(p_screen), screen_get_size(p_screen)); + } +} + +int DisplayServerAppleTV::screen_get_dpi(int p_screen) const { + return 96; +} + +int64_t DisplayServerAppleTV::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 DisplayServerAppleTV::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 DisplayServerAppleTV::virtual_keyboard_hide() { + [AppDelegate.viewController.keyboardView resignFirstResponder]; +} diff --git a/platform/tvos/export/export.cpp b/platform/tvos/export/export.cpp new file mode 100644 index 000000000000..7b6c4ecd9c5b --- /dev/null +++ b/platform/tvos/export/export.cpp @@ -0,0 +1,1264 @@ +/*************************************************************************/ +/* 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 "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_node.h" +#include "editor/editor_settings.h" +#include "main/splash.gen.h" +#include "platform/tvos/logo.gen.h" +#include "string.h" + +#include "godot_plugin_config.h" + +#include + +class EditorExportPlatformTVOS : public EditorExportPlatform { + GDCLASS(EditorExportPlatformTVOS, EditorExportPlatform); + + int version_code; + + Ref logo; + + // Plugins + SafeFlag plugins_changed; + Thread check_for_changes_thread; + SafeFlag quit_request; + Mutex plugins_lock; + Vector plugins; + + typedef Error (*FileHandler)(String p_file, void *p_userdata); + static Error _walk_dir_recursive(DirAccess *p_da, FileHandler p_handler, void *p_userdata); + + struct TVOSConfigData { + String pkg_name; + String binary_name; + String plist_content; + String linker_flags; + String cpp_code; + String modules_buildfile; + String modules_fileref; + String modules_buildphase; + String modules_buildgrp; + Vector capabilities; + }; + struct ExportArchitecture { + String name; + bool is_default; + + ExportArchitecture() : + name(""), + is_default(false) { + } + + ExportArchitecture(String p_name, bool p_is_default) { + name = p_name; + is_default = p_is_default; + } + }; + + struct TVOSExportAsset { + String exported_path; + bool is_framework; // framework is anything linked to the binary, otherwise it's a resource + bool should_embed; + }; + + String _get_additional_plist_content(); + String _get_linker_flags(); + String _get_cpp_code(); + void _fix_config_file(const Ref &p_preset, Vector &pfile, const TVOSConfigData &p_config, bool p_debug); + + void _add_assets_to_project(const Ref &p_preset, Vector &p_project_data, const Vector &p_additional_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 &r_exported_assets); + Error _export_additional_assets(const String &p_out_dir, const Vector &p_assets, bool p_is_framework, bool p_should_embed, Vector &r_exported_assets); + Error _export_additional_assets(const String &p_out_dir, const Vector &p_libraries, Vector &r_exported_assets); + Error _export_tvos_plugins(const Ref &p_preset, TVOSConfigData &p_config_data, const String &dest_dir, Vector &r_exported_assets, bool p_debug); + + bool is_package_name_valid(const String &p_package, String *r_error = NULL) 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 (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '.')) { + if (r_error) { + *r_error = vformat(TTR("The character '%s' is not allowed in Identifier."), String::chr(c)); + } + return false; + } + } + + return true; + } + + static void _check_for_changes_poll_thread(void *ud) { + EditorExportPlatformTVOS *ea = (EditorExportPlatformTVOS *)ud; + + while (!ea->quit_request.is_set()) { + // Nothing to do if we already know the plugins have changed. + if (!ea->plugins_changed.is_set()) { + MutexLock lock(ea->plugins_lock); + + Vector loaded_plugins = get_plugins(); + + if (ea->plugins.size() != loaded_plugins.size()) { + ea->plugins_changed.set(); + } else { + for (int i = 0; i < ea->plugins.size(); i++) { + if (ea->plugins[i].name != loaded_plugins[i].name || ea->plugins[i].last_updated != loaded_plugins[i].last_updated) { + ea->plugins_changed.set(); + break; + } + } + } + } + + 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 &p_preset, List *r_features) override; + virtual void get_export_options(List *r_options) override; + +public: + virtual String get_name() const override { return "tvOS"; } + virtual String get_os_name() const override { return "tvOS"; } + virtual Ref 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 get_binary_extensions(const Ref &p_preset) const override { + List list; + list.push_back("ipa"); + return list; + } + + virtual Error export_project(const Ref &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override; + + virtual bool can_export(const Ref &p_preset, String &r_error, bool &r_missing_templates) const override; + + virtual void get_platform_features(List *r_features) override { + r_features->push_back("mobile"); + r_features->push_back("tvOS"); + } + + virtual void resolve_platform_feature_priorities(const Ref &p_preset, Set &p_features) override { + } + + EditorExportPlatformTVOS(); + ~EditorExportPlatformTVOS(); + + /// List the gdip files in the directory specified by the p_path parameter. + static Vector list_plugin_config_files(const String &p_path, bool p_check_directories) { + Vector dir_files; + DirAccessRef da = DirAccess::open(p_path); + if (da) { + da->list_dir_begin(); + while (true) { + String file = da->get_next(); + if (file.is_empty()) { + break; + } + + if (file == "." || file == "..") { + continue; + } + + if (da->current_is_hidden()) { + continue; + } + + if (da->current_is_dir()) { + if (p_check_directories) { + Vector 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(PluginConfigTVOS::PLUGIN_CONFIG_EXT)) { + dir_files.push_back(file); + } + } + da->list_dir_end(); + } + + return dir_files; + } + + static Vector get_plugins() { + Vector loaded_plugins; + + String plugins_dir = ProjectSettings::get_singleton()->get_resource_path().plus_file("tvos/plugins"); + + if (DirAccess::exists(plugins_dir)) { + Vector plugins_filenames = list_plugin_config_files(plugins_dir, true); + + if (!plugins_filenames.is_empty()) { + Ref config_file = memnew(ConfigFile); + for (int i = 0; i < plugins_filenames.size(); i++) { + PluginConfigTVOS config = load_plugin_config(config_file, plugins_dir.plus_file(plugins_filenames[i])); + if (config.valid_config) { + loaded_plugins.push_back(config); + } else { + print_error("Invalid plugin config file " + plugins_filenames[i]); + } + } + } + } + + return loaded_plugins; + } + + static Vector get_enabled_plugins(const Ref &p_presets) { + Vector enabled_plugins; + Vector all_plugins = get_plugins(); + for (int i = 0; i < all_plugins.size(); i++) { + PluginConfigTVOS plugin = all_plugins[i]; + bool enabled = p_presets->get("plugins/" + plugin.name); + if (enabled) { + enabled_plugins.push_back(plugin); + } + } + + return enabled_plugins; + } +}; + +void EditorExportPlatformTVOS::get_preset_features(const Ref &p_preset, List *r_features) { + String driver = ProjectSettings::get_singleton()->get("rendering/quality/driver/driver_name"); + r_features->push_back("pvrtc"); + if (driver == "GLES3") { + r_features->push_back("etc2"); + } + + r_features->push_back("arm64"); +} + +void EditorExportPlatformTVOS::get_export_options(List *r_options) { + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), "")); + + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/app_store_team_id"), "")); + + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/bundle_identifier", PROPERTY_HINT_PLACEHOLDER_TEXT, "com.example.game"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/short_version"), "1.0")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/build_version"), "1")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/copyright"), "")); + + Vector found_plugins = get_plugins(); + for (int i = 0; i < found_plugins.size(); i++) { + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "plugins/" + found_plugins[i].name), false)); + } + + plugins_changed.clear(); + plugins = found_plugins; +} + +void EditorExportPlatformTVOS::_fix_config_file(const Ref &p_preset, Vector &pfile, const TVOSConfigData &p_config, bool p_debug) { + String str; + String strnew; + str.parse_utf8((const char *)pfile.ptr(), pfile.size()); + Vector lines = str.split("\n"); + for (int i = 0; i < lines.size(); i++) { + if (lines[i].find("$binary") != -1) { + strnew += lines[i].replace("$binary", p_config.binary_name) + "\n"; + } else if (lines[i].find("$modules_buildfile") != -1) { + strnew += lines[i].replace("$modules_buildfile", p_config.modules_buildfile) + "\n"; + } else if (lines[i].find("$modules_fileref") != -1) { + strnew += lines[i].replace("$modules_fileref", p_config.modules_fileref) + "\n"; + } else if (lines[i].find("$modules_buildphase") != -1) { + strnew += lines[i].replace("$modules_buildphase", p_config.modules_buildphase) + "\n"; + } else if (lines[i].find("$modules_buildgrp") != -1) { + strnew += lines[i].replace("$modules_buildgrp", p_config.modules_buildgrp) + "\n"; + } else if (lines[i].find("$name") != -1) { + strnew += lines[i].replace("$name", p_config.pkg_name) + "\n"; + } else if (lines[i].find("$info") != -1) { + strnew += lines[i].replace("$info", p_preset->get("application/info")) + "\n"; + } else if (lines[i].find("$bundle_identifier") != -1) { + strnew += lines[i].replace("$bundle_identifier", p_preset->get("application/bundle_identifier")) + "\n"; + } else if (lines[i].find("$short_version") != -1) { + strnew += lines[i].replace("$short_version", p_preset->get("application/short_version")) + "\n"; + } else if (lines[i].find("$build_version") != -1) { + strnew += lines[i].replace("$build_version", p_preset->get("application/build_version")) + "\n"; + } else if (lines[i].find("$copyright") != -1) { + strnew += lines[i].replace("$copyright", p_preset->get("application/copyright")) + "\n"; + } else if (lines[i].find("$team_id") != -1) { + strnew += lines[i].replace("$team_id", p_preset->get("application/app_store_team_id")) + "\n"; + } else if (lines[i].find("$additional_plist_content") != -1) { + strnew += lines[i].replace("$additional_plist_content", p_config.plist_content) + "\n"; + } else if (lines[i].find("$linker_flags") != -1) { + strnew += lines[i].replace("$linker_flags", p_config.linker_flags) + "\n"; + } else if (lines[i].find("$cpp_code") != -1) { + strnew += lines[i].replace("$cpp_code", p_config.cpp_code) + "\n"; + } else { + strnew += lines[i] + "\n"; + } + } + + // !BAS! I'm assuming the 9 in the original code was a typo. I've added -1 or else it seems to also be adding our terminating zero... + // should apply the same fix in our OSX export. + CharString cs = strnew.utf8(); + pfile.resize(cs.size() - 1); + for (int i = 0; i < cs.size() - 1; i++) { + pfile.write[i] = cs[i]; + } +} + +String EditorExportPlatformTVOS::_get_additional_plist_content() { + Vector> export_plugins = EditorExport::get_singleton()->get_export_plugins(); + String result; + for (int i = 0; i < export_plugins.size(); ++i) { + result += export_plugins[i]->get_tvos_plist_content(); + } + return result; +} + +String EditorExportPlatformTVOS::_get_linker_flags() { + Vector> export_plugins = EditorExport::get_singleton()->get_export_plugins(); + String result; + for (int i = 0; i < export_plugins.size(); ++i) { + String flags = export_plugins[i]->get_tvos_linker_flags(); + if (flags.length() == 0) + continue; + if (result.length() > 0) { + result += ' '; + } + result += flags; + } + // the flags will be enclosed in quotes, so need to escape them + return result.replace("\"", "\\\""); +} + +String EditorExportPlatformTVOS::_get_cpp_code() { + Vector> export_plugins = EditorExport::get_singleton()->get_export_plugins(); + String result; + for (int i = 0; i < export_plugins.size(); ++i) { + result += export_plugins[i]->get_tvos_cpp_code(); + } + return result; +} + +Error EditorExportPlatformTVOS::_walk_dir_recursive(DirAccess *p_da, FileHandler p_handler, void *p_userdata) { + Vector dirs; + String current_dir = p_da->get_current_dir(); + p_da->list_dir_begin(); + + String path = p_da->get_next(); + while (!path.is_empty()) { + if (p_da->current_is_dir()) { + if (path != "." && path != "..") { + dirs.push_back(path); + } + } else { + Error err = p_handler(current_dir.plus_file(path), p_userdata); + if (err) { + p_da->list_dir_end(); + return err; + } + } + + path = p_da->get_next(); + } + p_da->list_dir_end(); + + for (int i = 0; i < dirs.size(); ++i) { + String dir = dirs[i]; + p_da->change_dir(dir); + Error err = _walk_dir_recursive(p_da, p_handler, p_userdata); + p_da->change_dir(".."); + if (err) { + return err; + } + } + + return OK; +} + +struct PbxId { +private: + static char _hex_char(uint8_t four_bits) { + if (four_bits < 10) { + return ('0' + four_bits); + } + return 'A' + (four_bits - 10); + } + + static String _hex_pad(uint32_t num) { + Vector ret; + ret.resize(sizeof(num) * 2); + for (uint64_t i = 0; i < sizeof(num) * 2; ++i) { + uint8_t four_bits = (num >> (sizeof(num) * 8 - (i + 1) * 4)) & 0xF; + ret.write[i] = _hex_char(four_bits); + } + return String::utf8(ret.ptr(), ret.size()); + } + +public: + uint32_t high_bits; + uint32_t mid_bits; + uint32_t low_bits; + + String str() const { + return _hex_pad(high_bits) + _hex_pad(mid_bits) + _hex_pad(low_bits); + } + + PbxId &operator++() { + low_bits++; + if (!low_bits) { + mid_bits++; + if (!mid_bits) { + high_bits++; + } + } + + return *this; + } +}; + +struct ExportLibsData { + Vector lib_paths; + String dest_dir; +}; + +void EditorExportPlatformTVOS::_add_assets_to_project(const Ref &p_preset, Vector &p_project_data, const Vector &p_additional_assets) { + // that is just a random number, we just need Godot IDs not to clash with + // existing IDs in the project. + PbxId current_id = { 0x58938401, 0, 0 }; + String pbx_files; + String pbx_frameworks_build; + String pbx_frameworks_refs; + String pbx_resources_build; + String pbx_resources_refs; + String pbx_embeded_frameworks; + + const String file_info_format = String("$build_id = {isa = PBXBuildFile; fileRef = $ref_id; };\n") + + "$ref_id = {isa = PBXFileReference; lastKnownFileType = $file_type; name = \"$name\"; path = \"$file_path\"; sourceTree = \"\"; };\n"; + + for (int i = 0; i < p_additional_assets.size(); ++i) { + String additional_asset_info_format = file_info_format; + + String build_id = (++current_id).str(); + String ref_id = (++current_id).str(); + String framework_id = ""; + + const TVOSExportAsset &asset = p_additional_assets[i]; + + String type; + if (asset.exported_path.ends_with(".framework")) { + if (asset.should_embed) { + additional_asset_info_format += "$framework_id = {isa = PBXBuildFile; fileRef = $ref_id; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };\n"; + framework_id = (++current_id).str(); + pbx_embeded_frameworks += framework_id + ",\n"; + } + + type = "wrapper.framework"; + } else if (asset.exported_path.ends_with(".xcframework")) { + if (asset.should_embed) { + additional_asset_info_format += "$framework_id = {isa = PBXBuildFile; fileRef = $ref_id; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };\n"; + framework_id = (++current_id).str(); + pbx_embeded_frameworks += framework_id + ",\n"; + } + + type = "wrapper.xcframework"; + } else if (asset.exported_path.ends_with(".dylib")) { + type = "compiled.mach-o.dylib"; + } else if (asset.exported_path.ends_with(".a")) { + type = "archive.ar"; + } else { + type = "file"; + } + + String &pbx_build = asset.is_framework ? pbx_frameworks_build : pbx_resources_build; + String &pbx_refs = asset.is_framework ? pbx_frameworks_refs : pbx_resources_refs; + + if (pbx_build.length() > 0) { + pbx_build += ",\n"; + pbx_refs += ",\n"; + } + pbx_build += build_id; + pbx_refs += ref_id; + + Dictionary format_dict; + format_dict["build_id"] = build_id; + format_dict["ref_id"] = ref_id; + format_dict["name"] = asset.exported_path.get_file(); + format_dict["file_path"] = asset.exported_path; + format_dict["file_type"] = type; + if (framework_id.length() > 0) { + format_dict["framework_id"] = framework_id; + } + pbx_files += additional_asset_info_format.format(format_dict, "$_"); + } + + // Note, frameworks like gamekit are always included in our project.pbxprof file + // even if turned off in capabilities. + + String str = String::utf8((const char *)p_project_data.ptr(), p_project_data.size()); + str = str.replace("$additional_pbx_files", pbx_files); + str = str.replace("$additional_pbx_frameworks_build", pbx_frameworks_build); + str = str.replace("$additional_pbx_frameworks_refs", pbx_frameworks_refs); + str = str.replace("$additional_pbx_resources_build", pbx_resources_build); + str = str.replace("$additional_pbx_resources_refs", pbx_resources_refs); + str = str.replace("$pbx_embeded_frameworks", pbx_embeded_frameworks); + + CharString cs = str.utf8(); + p_project_data.resize(cs.size() - 1); + for (int i = 0; i < cs.size() - 1; i++) { + p_project_data.write[i] = cs[i]; + } +} + +Error EditorExportPlatformTVOS::_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 &r_exported_assets) { + DirAccess *filesystem_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + ERR_FAIL_COND_V_MSG(!filesystem_da, ERR_CANT_CREATE, "Cannot create DirAccess for path '" + p_out_dir + "'."); + + String binary_name = p_out_dir.get_file().get_basename(); + + DirAccess *da = DirAccess::create_for_path(p_asset); + if (!da) { + memdelete(filesystem_da); + ERR_FAIL_V_MSG(ERR_CANT_CREATE, "Can't create directory: " + p_asset + "."); + } + bool file_exists = da->file_exists(p_asset); + bool dir_exists = da->dir_exists(p_asset); + if (!file_exists && !dir_exists) { + memdelete(da); + memdelete(filesystem_da); + return ERR_FILE_NOT_FOUND; + } + + String base_dir = p_asset.get_base_dir().replace("res://", ""); + String destination_dir; + String destination; + String asset_path; + + bool create_framework = false; + + if (p_is_framework && p_asset.ends_with(".dylib")) { + // For tvOS we need to turn .dylib into .framework + // to be able to send application to AppStore + asset_path = String("dylibs").plus_file(base_dir); + + String file_name; + + if (!p_custom_file_name) { + file_name = p_asset.get_basename().get_file(); + } else { + file_name = *p_custom_file_name; + } + + String framework_name = file_name + ".framework"; + + asset_path = asset_path.plus_file(framework_name); + destination_dir = p_out_dir.plus_file(asset_path); + destination = destination_dir.plus_file(file_name); + create_framework = true; + } else if (p_is_framework && (p_asset.ends_with(".framework") || p_asset.ends_with(".xcframework"))) { + asset_path = String("dylibs").plus_file(base_dir); + + String file_name; + + if (!p_custom_file_name) { + file_name = p_asset.get_file(); + } else { + file_name = *p_custom_file_name; + } + + asset_path = asset_path.plus_file(file_name); + destination_dir = p_out_dir.plus_file(asset_path); + destination = destination_dir; + } else { + asset_path = base_dir; + + String file_name; + + if (!p_custom_file_name) { + file_name = p_asset.get_file(); + } else { + file_name = *p_custom_file_name; + } + + destination_dir = p_out_dir.plus_file(asset_path); + asset_path = asset_path.plus_file(file_name); + destination = p_out_dir.plus_file(asset_path); + } + + if (!filesystem_da->dir_exists(destination_dir)) { + Error make_dir_err = filesystem_da->make_dir_recursive(destination_dir); + if (make_dir_err) { + memdelete(da); + memdelete(filesystem_da); + return make_dir_err; + } + } + + Error err = dir_exists ? da->copy_dir(p_asset, destination) : da->copy(p_asset, destination); + memdelete(da); + if (err) { + memdelete(filesystem_da); + return err; + } + TVOSExportAsset exported_asset = { binary_name.plus_file(asset_path), p_is_framework, p_should_embed }; + r_exported_assets.push_back(exported_asset); + + if (create_framework) { + String file_name; + + if (!p_custom_file_name) { + file_name = p_asset.get_basename().get_file(); + } else { + file_name = *p_custom_file_name; + } + + String framework_name = file_name + ".framework"; + + // Performing `install_name_tool -id @rpath/{name}.framework/{name} ./{name}` on dylib + { + List install_name_args; + install_name_args.push_back("-id"); + install_name_args.push_back(String("@rpath").plus_file(framework_name).plus_file(file_name)); + install_name_args.push_back(destination); + + OS::get_singleton()->execute("install_name_tool", install_name_args); + } + + // Creating Info.plist + { + String info_plist_format = "\n" + "\n" + "\n" + "\n" + "CFBundleShortVersionString\n" + "1.0\n" + "CFBundleIdentifier\n" + "com.gdnative.framework.$name\n" + "CFBundleName\n" + "$name\n" + "CFBundleExecutable\n" + "$name\n" + "DTPlatformName\n" + "appletvos\n" + "CFBundleInfoDictionaryVersion\n" + "6.0\n" + "CFBundleVersion\n" + "1\n" + "CFBundlePackageType\n" + "FMWK\n" + "MinimumOSVersion\n" + "10.0\n" + "\n" + ""; + + String info_plist = info_plist_format.replace("$name", file_name); + + FileAccess *f = FileAccess::open(destination_dir.plus_file("Info.plist"), FileAccess::WRITE); + if (f) { + f->store_string(info_plist); + f->close(); + memdelete(f); + } + } + } + + memdelete(filesystem_da); + + return OK; +} + +Error EditorExportPlatformTVOS::_export_additional_assets(const String &p_out_dir, const Vector &p_assets, bool p_is_framework, bool p_should_embed, Vector &r_exported_assets) { + for (int f_idx = 0; f_idx < p_assets.size(); ++f_idx) { + String asset = p_assets[f_idx]; + if (!asset.begins_with("res://")) { + // either SDK-builtin or already a part of the export template + TVOSExportAsset exported_asset = { asset, p_is_framework, p_should_embed }; + r_exported_assets.push_back(exported_asset); + } else { + Error err = _copy_asset(p_out_dir, asset, nullptr, p_is_framework, p_should_embed, r_exported_assets); + ERR_FAIL_COND_V(err, err); + } + } + + return OK; +} + +Error EditorExportPlatformTVOS::_export_additional_assets(const String &p_out_dir, const Vector &p_libraries, Vector &r_exported_assets) { + Vector> export_plugins = EditorExport::get_singleton()->get_export_plugins(); + for (int i = 0; i < export_plugins.size(); i++) { + Vector linked_frameworks = export_plugins[i]->get_tvos_frameworks(); + Error err = _export_additional_assets(p_out_dir, linked_frameworks, true, false, r_exported_assets); + ERR_FAIL_COND_V(err, err); + + Vector embedded_frameworks = export_plugins[i]->get_tvos_embedded_frameworks(); + err = _export_additional_assets(p_out_dir, embedded_frameworks, true, true, r_exported_assets); + ERR_FAIL_COND_V(err, err); + + Vector project_static_libs = export_plugins[i]->get_tvos_project_static_libs(); + for (int j = 0; j < project_static_libs.size(); j++) + project_static_libs.write[j] = project_static_libs[j].get_file(); // Only the file name as it's copied to the project + err = _export_additional_assets(p_out_dir, project_static_libs, true, true, r_exported_assets); + ERR_FAIL_COND_V(err, err); + + Vector tvos_bundle_files = export_plugins[i]->get_tvos_bundle_files(); + err = _export_additional_assets(p_out_dir, tvos_bundle_files, false, false, r_exported_assets); + ERR_FAIL_COND_V(err, err); + } + + Vector library_paths; + for (int i = 0; i < p_libraries.size(); ++i) { + library_paths.push_back(p_libraries[i].path); + } + Error err = _export_additional_assets(p_out_dir, library_paths, true, true, r_exported_assets); + ERR_FAIL_COND_V(err, err); + + return OK; +} + +Error EditorExportPlatformTVOS::_export_tvos_plugins(const Ref &p_preset, TVOSConfigData &p_config_data, const String &dest_dir, Vector &r_exported_assets, bool p_debug) { + String plugin_definition_cpp_code; + String plugin_initialization_cpp_code; + String plugin_deinitialization_cpp_code; + + Vector plugin_linked_dependencies; + Vector plugin_embedded_dependencies; + Vector plugin_files; + + Vector enabled_plugins = get_enabled_plugins(p_preset); + + Vector added_linked_dependenciy_names; + Vector added_embedded_dependenciy_names; + HashMap plist_values; + + Error err; + + for (int i = 0; i < enabled_plugins.size(); i++) { + PluginConfigTVOS plugin = enabled_plugins[i]; + + // Export plugin binary. + String plugin_main_binary = get_plugin_main_binary(plugin, p_debug); + String plugin_binary_result_file = plugin.binary.get_file(); + // We shouldn't embed .xcframework that contains static libraries. + // Static libraries are not embedded anyway. + err = _copy_asset(dest_dir, plugin_main_binary, &plugin_binary_result_file, true, false, r_exported_assets); + + ERR_FAIL_COND_V(err, err); + + // Adding dependencies. + // Use separate container for names to check for duplicates. + for (int j = 0; j < plugin.linked_dependencies.size(); j++) { + String dependency = plugin.linked_dependencies[j]; + String name = dependency.get_file(); + + if (added_linked_dependenciy_names.find(name) != -1) { + continue; + } + + added_linked_dependenciy_names.push_back(name); + plugin_linked_dependencies.push_back(dependency); + } + + for (int j = 0; j < plugin.system_dependencies.size(); j++) { + String dependency = plugin.system_dependencies[j]; + String name = dependency.get_file(); + + if (added_linked_dependenciy_names.find(name) != -1) { + continue; + } + + added_linked_dependenciy_names.push_back(name); + plugin_linked_dependencies.push_back(dependency); + } + + for (int j = 0; j < plugin.embedded_dependencies.size(); j++) { + String dependency = plugin.embedded_dependencies[j]; + String name = dependency.get_file(); + + if (added_embedded_dependenciy_names.find(name) != -1) { + continue; + } + + added_embedded_dependenciy_names.push_back(name); + plugin_embedded_dependencies.push_back(dependency); + } + + plugin_files.append_array(plugin.files_to_copy); + + // Capabilities + // Also checking for duplicates. + for (int j = 0; j < plugin.capabilities.size(); j++) { + String capability = plugin.capabilities[j]; + + if (p_config_data.capabilities.find(capability) != -1) { + continue; + } + + p_config_data.capabilities.push_back(capability); + } + + // Plist + // Using hash map container to remove duplicates + const String *K = nullptr; + + while ((K = plugin.plist.next(K))) { + String key = *K; + String value = plugin.plist[key]; + + if (key.is_empty() || value.is_empty()) { + continue; + } + + plist_values[key] = value; + } + + // CPP Code + String definition_comment = "// Plugin: " + plugin.name + "\n"; + String initialization_method = plugin.initialization_method + "();\n"; + String deinitialization_method = plugin.deinitialization_method + "();\n"; + + plugin_definition_cpp_code += definition_comment + + "extern void " + initialization_method + + "extern void " + deinitialization_method + "\n"; + + plugin_initialization_cpp_code += "\t" + initialization_method; + plugin_deinitialization_cpp_code += "\t" + deinitialization_method; + } + + // Updating `Info.plist` + { + const String *K = nullptr; + while ((K = plist_values.next(K))) { + String key = *K; + String value = plist_values[key]; + + if (key.is_empty() || value.is_empty()) { + continue; + } + + p_config_data.plist_content += "" + key + "" + value + "\n"; + } + } + + // Export files + { + // Export linked plugin dependency + err = _export_additional_assets(dest_dir, plugin_linked_dependencies, true, false, r_exported_assets); + ERR_FAIL_COND_V(err, err); + + // Export embedded plugin dependency + err = _export_additional_assets(dest_dir, plugin_embedded_dependencies, true, true, r_exported_assets); + ERR_FAIL_COND_V(err, err); + + // Export plugin files + err = _export_additional_assets(dest_dir, plugin_files, false, false, r_exported_assets); + ERR_FAIL_COND_V(err, err); + } + + // Update CPP + { + Dictionary plugin_format; + plugin_format["definition"] = plugin_definition_cpp_code; + plugin_format["initialization"] = plugin_initialization_cpp_code; + plugin_format["deinitialization"] = plugin_deinitialization_cpp_code; + + String plugin_cpp_code = "\n// Godot Plugins\n" + "void godot_tvos_plugins_initialize();\n" + "void godot_tvos_plugins_deinitialize();\n" + "// Exported Plugins\n\n" + "$definition" + "// Use Plugins\n" + "void godot_tvos_plugins_initialize() {\n" + "$initialization" + "}\n\n" + "void godot_tvos_plugins_deinitialize() {\n" + "$deinitialization" + "}\n"; + + p_config_data.cpp_code += plugin_cpp_code.format(plugin_format, "$_"); + } + return OK; +} + +Error EditorExportPlatformTVOS::export_project(const Ref &p_preset, bool p_debug, const String &p_path, int p_flags) { + ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); + + String src_pkg_name; + String dest_dir = p_path.get_base_dir() + "/"; + String binary_name = p_path.get_file().get_basename(); + + EditorProgress ep("export", "Exporting for tvOS", 5, true); + + String team_id = p_preset->get("application/app_store_team_id"); + + if (p_debug) + src_pkg_name = p_preset->get("custom_template/debug"); + else + src_pkg_name = p_preset->get("custom_template/release"); + + if (src_pkg_name == "") { + String err; + src_pkg_name = find_export_template("tvos.zip", &err); + if (src_pkg_name == "") { + EditorNode::add_io_error(err); + return ERR_FILE_NOT_FOUND; + } + } + + if (!DirAccess::exists(dest_dir)) { + return ERR_FILE_BAD_PATH; + } + + DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + if (da) { + String current_dir = da->get_current_dir(); + + // remove leftovers from last export so they don't interfere + // in case some files are no longer needed + if (da->change_dir(dest_dir + binary_name + ".xcodeproj") == OK) { + da->erase_contents_recursive(); + } + if (da->change_dir(dest_dir + binary_name) == OK) { + da->erase_contents_recursive(); + } + + da->change_dir(current_dir); + + if (!da->dir_exists(dest_dir + binary_name)) { + Error err = da->make_dir(dest_dir + binary_name); + if (err) { + memdelete(da); + return err; + } + } + memdelete(da); + } + + if (ep.step("Making .pck", 0)) { + return ERR_SKIP; + } + String pack_path = dest_dir + binary_name + ".pck"; + Vector libraries; + Error err = save_pack(p_preset, pack_path, &libraries); + if (err) + return err; + + if (ep.step("Extracting and configuring Xcode project", 1)) { + return ERR_SKIP; + } + + String library_to_use = "libgodot.tvos." + String(p_debug ? "debug" : "release") + ".xcframework"; + + print_line("Static library: " + library_to_use); + String pkg_name; + if (p_preset->get("application/name") != "") + pkg_name = p_preset->get("application/name"); // app_name + else if (String(ProjectSettings::get_singleton()->get("application/config/name")) != "") + pkg_name = String(ProjectSettings::get_singleton()->get("application/config/name")); + else + pkg_name = "Unnamed"; + + bool found_library = false; + int total_size = 0; + + const String project_file = "godot_tvos.xcodeproj/project.pbxproj"; + Set files_to_parse; + files_to_parse.insert("godot_tvos/Info.plist"); + files_to_parse.insert(project_file); + files_to_parse.insert("godot_tvos/dummy.cpp"); + files_to_parse.insert("godot_tvos.xcodeproj/project.xcworkspace/contents.xcworkspacedata"); + files_to_parse.insert("godot_tvos.xcodeproj/xcshareddata/xcschemes/godot_tvos.xcscheme"); + files_to_parse.insert("godot_tvos/Launch Screen.storyboard"); + + TVOSConfigData config_data = { + pkg_name, + binary_name, + _get_additional_plist_content(), + _get_linker_flags(), + _get_cpp_code(), + "", + "", + "", + "", + Vector() + }; + + Vector assets; + + DirAccess *tmp_app_path = DirAccess::create_for_path(dest_dir); + ERR_FAIL_COND_V(!tmp_app_path, ERR_CANT_CREATE); + + print_line("Unzipping..."); + FileAccess *src_f = NULL; + zlib_filefunc_def io = zipio_create_io_from_file(&src_f); + unzFile src_pkg_zip = unzOpen2(src_pkg_name.utf8().get_data(), &io); + if (!src_pkg_zip) { + EditorNode::add_io_error("Could not open export template (not a zip file?):\n" + src_pkg_name); + return ERR_CANT_OPEN; + } + + err = _export_tvos_plugins(p_preset, config_data, dest_dir + binary_name, assets, p_debug); + ERR_FAIL_COND_V(err, err); + + //export rest of the files + int ret = unzGoToFirstFile(src_pkg_zip); + Vector project_file_data; + while (ret == UNZ_OK) { +#if defined(OSX_ENABLED) || defined(X11_ENABLED) + bool is_execute = false; +#endif + + //get filename + unz_file_info info; + char fname[16384]; + ret = unzGetCurrentFileInfo(src_pkg_zip, &info, fname, 16384, NULL, 0, NULL, 0); + + String file = fname; + + print_line("READ: " + file); + Vector data; + data.resize(info.uncompressed_size); + + //read + unzOpenCurrentFile(src_pkg_zip); + unzReadCurrentFile(src_pkg_zip, data.ptrw(), data.size()); + unzCloseCurrentFile(src_pkg_zip); + + //write + + file = file.replace_first("tvos/", ""); + + if (files_to_parse.has(file)) { + _fix_config_file(p_preset, data, config_data, p_debug); + } else if (file.begins_with("libgodot.tvos")) { + if (!file.begins_with(library_to_use) || file.ends_with(String("/empty"))) { + ret = unzGoToNextFile(src_pkg_zip); + continue; //ignore! + } + found_library = true; +#if defined(OSX_ENABLED) || defined(X11_ENABLED) + is_execute = true; +#endif + file = file.replace(library_to_use, binary_name + ".xcframework"); + } + + if (file == project_file) { + project_file_data = data; + } + + ///@TODO need to parse logo files + + if (data.size() > 0) { + file = file.replace("godot_tvos", binary_name); + + print_line("ADDING: " + file + " size: " + itos(data.size())); + total_size += data.size(); + + /* write it into our folder structure */ + file = dest_dir + file; + + /* make sure this folder exists */ + String dir_name = file.get_base_dir(); + if (!tmp_app_path->dir_exists(dir_name)) { + print_line("Creating " + dir_name); + Error dir_err = tmp_app_path->make_dir_recursive(dir_name); + if (dir_err) { + ERR_PRINT("Can't create '" + dir_name + "'."); + unzClose(src_pkg_zip); + memdelete(tmp_app_path); + return ERR_CANT_CREATE; + } + } + + /* write the file */ + FileAccess *f = FileAccess::open(file, FileAccess::WRITE); + if (!f) { + ERR_PRINT("Can't write '" + file + "'."); + unzClose(src_pkg_zip); + memdelete(tmp_app_path); + return ERR_CANT_CREATE; + }; + f->store_buffer(data.ptr(), data.size()); + f->close(); + memdelete(f); + +#if defined(OSX_ENABLED) || defined(X11_ENABLED) + if (is_execute) { + // we need execute rights on this file + chmod(file.utf8().get_data(), 0755); + } +#endif + } + + ret = unzGoToNextFile(src_pkg_zip); + } + + /* we're done with our source zip */ + unzClose(src_pkg_zip); + + if (!found_library) { + ERR_PRINT("Requested template library '" + library_to_use + "' not found. It might be missing from your template archive."); + memdelete(tmp_app_path); + return ERR_FILE_NOT_FOUND; + } + + // Copy project static libs to the project + Vector> export_plugins = EditorExport::get_singleton()->get_export_plugins(); + for (int i = 0; i < export_plugins.size(); i++) { + Vector project_static_libs = export_plugins[i]->get_tvos_project_static_libs(); + for (int j = 0; j < project_static_libs.size(); j++) { + const String &static_lib_path = project_static_libs[j]; + String dest_lib_file_path = dest_dir + static_lib_path.get_file(); + Error lib_copy_err = tmp_app_path->copy(static_lib_path, dest_lib_file_path); + if (lib_copy_err != OK) { + ERR_PRINT("Can't copy '" + static_lib_path + "'."); + memdelete(tmp_app_path); + return lib_copy_err; + } + } + } + + print_line("Exporting additional assets"); + _export_additional_assets(dest_dir + binary_name, libraries, assets); + _add_assets_to_project(p_preset, project_file_data, assets); + String project_file_name = dest_dir + binary_name + ".xcodeproj/project.pbxproj"; + FileAccess *f = FileAccess::open(project_file_name, FileAccess::WRITE); + if (!f) { + ERR_PRINT("Can't write '" + project_file_name + "'."); + return ERR_CANT_CREATE; + }; + f->store_buffer(project_file_data.ptr(), project_file_data.size()); + f->close(); + memdelete(f); + + return OK; +} + +bool EditorExportPlatformTVOS::can_export(const Ref &p_preset, String &r_error, bool &r_missing_templates) const { + String err; + bool valid = false; + + // Look for export templates (first official, and if defined custom templates). + + bool dvalid = exists_export_template("tvos.zip", &err); + bool rvalid = dvalid; // Both in the same ZIP. + + if (p_preset->get("custom_template/debug") != "") { + dvalid = FileAccess::exists(p_preset->get("custom_template/debug")); + if (!dvalid) { + err += TTR("Custom debug template not found.") + "\n"; + } + } + if (p_preset->get("custom_template/release") != "") { + rvalid = FileAccess::exists(p_preset->get("custom_template/release")); + if (!rvalid) { + err += TTR("Custom release template not found.") + "\n"; + } + } + + valid = dvalid || rvalid; + r_missing_templates = !valid; + + // Validate the rest of the configuration. + + String identifier = p_preset->get("application/bundle_identifier"); + String pn_err; + if (!is_package_name_valid(identifier, &pn_err)) { + err += TTR("Invalid Identifier:") + " " + pn_err + "\n"; + valid = false; + } + + String etc_error = test_etc2(); + if (etc_error != String()) { + valid = false; + err += etc_error; + } + + if (!err.is_empty()) + r_error = err; + + return valid; +} + +EditorExportPlatformTVOS::EditorExportPlatformTVOS() { + Ref img = memnew(Image(_tvos_logo)); + logo.instantiate(); + logo->create_from_image(img); + + plugins_changed.set(); + + check_for_changes_thread.start(_check_for_changes_poll_thread, this); +} + +EditorExportPlatformTVOS::~EditorExportPlatformTVOS() { + quit_request.set(); + check_for_changes_thread.wait_to_finish(); +} + +void register_tvos_exporter() { + Ref platform; + platform.instantiate(); + + EditorExport::get_singleton()->add_export_platform(platform); +} diff --git a/platform/tvos/export/export.h b/platform/tvos/export/export.h new file mode 100644 index 000000000000..e645e40b44ca --- /dev/null +++ b/platform/tvos/export/export.h @@ -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 TVOS_EXPORT_H +#define TVOS_EXPORT_H + +void register_tvos_exporter(); + +#endif // TVOS_EXPORT_H diff --git a/platform/tvos/export/godot_plugin_config.h b/platform/tvos/export/godot_plugin_config.h new file mode 100644 index 000000000000..fa64bebe8fad --- /dev/null +++ b/platform/tvos/export/godot_plugin_config.h @@ -0,0 +1,290 @@ +/*************************************************************************/ +/* 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 GODOT_PLUGIN_CONFIG_H +#define GODOT_PLUGIN_CONFIG_H + +#include "core/error/error_list.h" +#include "core/io/config_file.h" +#include "core/string/ustring.h" + +/* + The `config` section and fields are required and defined as follow: +- **name**: name of the plugin +- **binary**: path to static `.a` library +The `dependencies` and fields are optional. +- **linked**: dependencies that should only be linked. +- **embedded**: dependencies that should be linked and embedded into application. +- **system**: system dependencies that should be linked. +- **capabilities**: capabilities that would be used for `UIRequiredDeviceCapabilities` options in Info.plist file. +- **files**: files that would be copied into application +The `plist` section are optional. +- **key**: key and value that would be added in Info.plist file. + */ + +struct PluginConfigTVOS { + inline static const char *PLUGIN_CONFIG_EXT = ".gdatvp"; + + inline static const char *CONFIG_SECTION = "config"; + inline static const char *CONFIG_NAME_KEY = "name"; + inline static const char *CONFIG_BINARY_KEY = "binary"; + inline static const char *CONFIG_INITIALIZE_KEY = "initialization"; + inline static const char *CONFIG_DEINITIALIZE_KEY = "deinitialization"; + + inline static const char *DEPENDENCIES_SECTION = "dependencies"; + inline static const char *DEPENDENCIES_LINKED_KEY = "linked"; + inline static const char *DEPENDENCIES_EMBEDDED_KEY = "embedded"; + inline static const char *DEPENDENCIES_SYSTEM_KEY = "system"; + inline static const char *DEPENDENCIES_CAPABILITIES_KEY = "capabilities"; + inline static const char *DEPENDENCIES_FILES_KEY = "files"; + inline static const char *DEPENDENCIES_LINKER_FLAGS = "linker_flags"; + + inline static const char *PLIST_SECTION = "plist"; + + // Set to true when the config file is properly loaded. + bool valid_config = false; + bool supports_targets = false; + // Unix timestamp of last change to this plugin. + uint64_t last_updated = 0; + + // Required config section + String name; + String binary; + String initialization_method; + String deinitialization_method; + + // Optional dependencies section + Vector linked_dependencies; + Vector embedded_dependencies; + Vector system_dependencies; + + Vector files_to_copy; + Vector capabilities; + + Vector linker_flags; + + // Optional plist section + // Supports only string types for now + HashMap plist; +}; + +static inline String resolve_local_dependency_path(String plugin_config_dir, String dependency_path) { + String absolute_path; + + if (dependency_path.is_empty()) { + return absolute_path; + } + + if (dependency_path.is_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://"); +} + +static inline String resolve_system_dependency_path(String dependency_path) { + String absolute_path; + + if (dependency_path.is_empty()) { + return absolute_path; + } + + if (dependency_path.is_absolute_path()) { + return dependency_path; + } + + String system_path = "/System/Library/Frameworks"; + + return system_path.plus_file(dependency_path); +} + +static inline Vector resolve_local_dependencies(String plugin_config_dir, Vector p_paths) { + Vector paths; + + for (int i = 0; i < p_paths.size(); i++) { + String path = resolve_local_dependency_path(plugin_config_dir, p_paths[i]); + + if (path.is_empty()) { + continue; + } + + paths.push_back(path); + } + + return paths; +} + +static inline Vector resolve_system_dependencies(Vector p_paths) { + Vector paths; + + for (int i = 0; i < p_paths.size(); i++) { + String path = resolve_system_dependency_path(p_paths[i]); + + if (path.is_empty()) { + continue; + } + + paths.push_back(path); + } + + return paths; +} + +static inline bool validate_plugin(PluginConfigTVOS &plugin_config) { + bool valid_name = !plugin_config.name.is_empty(); + bool valid_binary_name = !plugin_config.binary.is_empty(); + bool valid_initialize = !plugin_config.initialization_method.is_empty(); + bool valid_deinitialize = !plugin_config.deinitialization_method.is_empty(); + + bool fields_value = valid_name && valid_binary_name && valid_initialize && valid_deinitialize; + + if (!fields_value) { + return false; + } + + String plugin_extension = plugin_config.binary.get_extension().to_lower(); + + if ((plugin_extension == "a" && FileAccess::exists(plugin_config.binary)) || + (plugin_extension == "xcframework" && DirAccess::exists(plugin_config.binary))) { + plugin_config.valid_config = true; + plugin_config.supports_targets = false; + } else { + String file_path = plugin_config.binary.get_base_dir(); + String file_name = plugin_config.binary.get_basename().get_file(); + String file_extension = plugin_config.binary.get_extension(); + String release_file_name = file_path.plus_file(file_name + ".release." + file_extension); + String debug_file_name = file_path.plus_file(file_name + ".debug." + file_extension); + + if ((plugin_extension == "a" && FileAccess::exists(release_file_name) && FileAccess::exists(debug_file_name)) || + (plugin_extension == "xcframework" && DirAccess::exists(release_file_name) && DirAccess::exists(debug_file_name))) { + plugin_config.valid_config = true; + plugin_config.supports_targets = true; + } + } + + return plugin_config.valid_config; +} + +static inline String get_plugin_main_binary(PluginConfigTVOS &plugin_config, bool p_debug) { + if (!plugin_config.supports_targets) { + return plugin_config.binary; + } + + String plugin_binary_dir = plugin_config.binary.get_base_dir(); + String plugin_name_prefix = plugin_config.binary.get_basename().get_file(); + String plugin_extension = plugin_config.binary.get_extension(); + String plugin_file = plugin_name_prefix + "." + (p_debug ? "debug" : "release") + "." + plugin_extension; + + return plugin_binary_dir.plus_file(plugin_file); +} + +static inline uint64_t get_plugin_modification_time(const PluginConfigTVOS &plugin_config, const String &config_path) { + uint64_t last_updated = FileAccess::get_modified_time(config_path); + + if (!plugin_config.supports_targets) { + last_updated = MAX(last_updated, FileAccess::get_modified_time(plugin_config.binary)); + } else { + String file_path = plugin_config.binary.get_base_dir(); + String file_name = plugin_config.binary.get_basename().get_file(); + String release_file_name = file_path.plus_file(file_name + ".release.a"); + String debug_file_name = file_path.plus_file(file_name + ".debug.a"); + + last_updated = MAX(last_updated, FileAccess::get_modified_time(release_file_name)); + last_updated = MAX(last_updated, FileAccess::get_modified_time(debug_file_name)); + } + + return last_updated; +} + +static inline PluginConfigTVOS load_plugin_config(Ref config_file, const String &path) { + PluginConfigTVOS plugin_config = {}; + + if (!config_file.is_valid()) { + return plugin_config; + } + + Error err = config_file->load(path); + + if (err != OK) { + return plugin_config; + } + + String config_base_dir = path.get_base_dir(); + + plugin_config.name = config_file->get_value(PluginConfigTVOS::CONFIG_SECTION, PluginConfigTVOS::CONFIG_NAME_KEY, String()); + plugin_config.initialization_method = config_file->get_value(PluginConfigTVOS::CONFIG_SECTION, PluginConfigTVOS::CONFIG_INITIALIZE_KEY, String()); + plugin_config.deinitialization_method = config_file->get_value(PluginConfigTVOS::CONFIG_SECTION, PluginConfigTVOS::CONFIG_DEINITIALIZE_KEY, String()); + + String binary_path = config_file->get_value(PluginConfigTVOS::CONFIG_SECTION, PluginConfigTVOS::CONFIG_BINARY_KEY, String()); + plugin_config.binary = resolve_local_dependency_path(config_base_dir, binary_path); + + if (config_file->has_section(PluginConfigTVOS::DEPENDENCIES_SECTION)) { + Vector linked_dependencies = config_file->get_value(PluginConfigTVOS::DEPENDENCIES_SECTION, PluginConfigTVOS::DEPENDENCIES_LINKED_KEY, Vector()); + Vector embedded_dependencies = config_file->get_value(PluginConfigTVOS::DEPENDENCIES_SECTION, PluginConfigTVOS::DEPENDENCIES_EMBEDDED_KEY, Vector()); + Vector system_dependencies = config_file->get_value(PluginConfigTVOS::DEPENDENCIES_SECTION, PluginConfigTVOS::DEPENDENCIES_SYSTEM_KEY, Vector()); + Vector files = config_file->get_value(PluginConfigTVOS::DEPENDENCIES_SECTION, PluginConfigTVOS::DEPENDENCIES_FILES_KEY, Vector()); + + plugin_config.linked_dependencies = resolve_local_dependencies(config_base_dir, linked_dependencies); + plugin_config.embedded_dependencies = resolve_local_dependencies(config_base_dir, embedded_dependencies); + 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(PluginConfigTVOS::DEPENDENCIES_SECTION, PluginConfigTVOS::DEPENDENCIES_CAPABILITIES_KEY, Vector()); + + plugin_config.linker_flags = config_file->get_value(PluginConfigTVOS::DEPENDENCIES_SECTION, PluginConfigTVOS::DEPENDENCIES_LINKER_FLAGS, Vector()); + } + + if (config_file->has_section(PluginConfigTVOS::PLIST_SECTION)) { + List keys; + config_file->get_section_keys(PluginConfigTVOS::PLIST_SECTION, &keys); + + for (int i = 0; i < keys.size(); i++) { + String value = config_file->get_value(PluginConfigTVOS::PLIST_SECTION, keys[i], String()); + + if (value.is_empty()) { + continue; + } + + plugin_config.plist[keys[i]] = value; + } + } + + if (validate_plugin(plugin_config)) { + plugin_config.last_updated = get_plugin_modification_time(plugin_config, path); + } + + return plugin_config; +} + +#endif // GODOT_PLUGIN_CONFIG_H diff --git a/platform/tvos/godot_app_delegate.h b/platform/tvos/godot_app_delegate.h new file mode 100644 index 000000000000..f474b5f50f57 --- /dev/null +++ b/platform/tvos/godot_app_delegate.h @@ -0,0 +1,35 @@ +/*************************************************************************/ +/* 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 "platform/uikit/uikit_app_delegate.h" + +@interface GodotApplicalitionDelegate : UIKitApplicalitionDelegate + +@end diff --git a/platform/tvos/godot_app_delegate.m b/platform/tvos/godot_app_delegate.m new file mode 100644 index 000000000000..b2c4859981a2 --- /dev/null +++ b/platform/tvos/godot_app_delegate.m @@ -0,0 +1,45 @@ +/*************************************************************************/ +/* 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 + ++ (void)load { + [self addService:[AppDelegate new]]; +} + +@end diff --git a/platform/tvos/godot_tvos.mm b/platform/tvos/godot_tvos.mm new file mode 100644 index 000000000000..24b887725bcd --- /dev/null +++ b/platform/tvos/godot_tvos.mm @@ -0,0 +1,124 @@ +/*************************************************************************/ +/* godot_tvos.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_tvos.h" + +#include +#include +#include + +static OSAppleTV *os = NULL; + +int add_path(int p_argc, char **p_args) { + NSString *str = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_path"]; + if (!str) { + return p_argc; + } + + p_args[p_argc++] = (char *)"--path"; + p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding]; + p_args[p_argc] = NULL; + + return p_argc; +} + +int add_cmdline(int p_argc, char **p_args) { + NSArray *arr = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_cmdline"]; + if (!arr) { + return p_argc; + } + + for (NSUInteger i = 0; i < [arr count]; i++) { + NSString *str = [arr objectAtIndex:i]; + if (!str) { + continue; + } + p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding]; + } + + p_args[p_argc] = NULL; + + return p_argc; +} + +int appletv_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_appletv %s\n", argv[0]); + char cwd[512]; + getcwd(cwd, sizeof(cwd)); + printf("cwd %s\n", cwd); + os = new OSAppleTV(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 != OK) { + return 255; + } + + os->initialize_modules(); + + return 0; +} + +void appletv_finish() { + printf("appletv_finish\n"); + Main::cleanup(); + delete os; +} diff --git a/platform/tvos/godot_view.h b/platform/tvos/godot_view.h new file mode 100644 index 000000000000..1062241d3e61 --- /dev/null +++ b/platform/tvos/godot_view.h @@ -0,0 +1,35 @@ +/*************************************************************************/ +/* 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 "platform/uikit/uikit_view.h" + +@interface GodotView : UIKitView + +@end diff --git a/platform/tvos/godot_view.mm b/platform/tvos/godot_view.mm new file mode 100644 index 000000000000..129a2e14067e --- /dev/null +++ b/platform/tvos/godot_view.mm @@ -0,0 +1,135 @@ +/*************************************************************************/ +/* 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" +#include "display_server_tvos.h" +#import "godot_view_gesture_recognizer.h" +#import "godot_view_renderer.h" +#import "os_tvos.h" + +@interface GodotView () + +@property(strong, nonatomic) GodotViewGestureRecognizer *delayGestureRecognizer; + +@end + +@implementation GodotView + +- (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 { + if (self.delayGestureRecognizer) { + self.delayGestureRecognizer = nil; + } +} + +- (void)godot_commonInit { + // Initialize delay gesture recognizer + GodotViewGestureRecognizer *gestureRecognizer = [[GodotViewGestureRecognizer alloc] init]; + self.delayGestureRecognizer = gestureRecognizer; + [self addGestureRecognizer:self.delayGestureRecognizer]; +} + +// MARK: - Input + +// MARK: Menu Button + +- (void)pressesBegan:(NSSet *)presses withEvent:(UIPressesEvent *)event { + if (!self.delayGestureRecognizer.overridesRemoteButtons) { + return [super pressesEnded:presses withEvent:event]; + } + + NSArray *tlist = [event.allPresses allObjects]; + + for (UIPress *press in tlist) { + if ([presses containsObject:press] && press.type == UIPressTypeMenu) { + int joy_id = OSAppleTV::get_singleton()->joy_id_for_name("Remote"); + Input::get_singleton()->joy_button(joy_id, JoyButton::START, true); + } else { + [super pressesBegan:presses withEvent:event]; + } + } +} + +- (void)pressesEnded:(NSSet *)presses withEvent:(UIPressesEvent *)event { + if (!self.delayGestureRecognizer.overridesRemoteButtons) { + return [super pressesEnded:presses withEvent:event]; + } + + NSArray *tlist = [presses allObjects]; + + for (UIPress *press in tlist) { + if ([presses containsObject:press] && press.type == UIPressTypeMenu) { + int joy_id = OSAppleTV::get_singleton()->joy_id_for_name("Remote"); + Input::get_singleton()->joy_button(joy_id, JoyButton::START, false); + } else { + [super pressesEnded:presses withEvent:event]; + } + } +} + +- (void)pressesCancelled:(NSSet *)presses withEvent:(UIPressesEvent *)event { + if (!self.delayGestureRecognizer.overridesRemoteButtons) { + return [super pressesEnded:presses withEvent:event]; + } + + NSArray *tlist = [event.allPresses allObjects]; + + for (UIPress *press in tlist) { + if ([presses containsObject:press] && press.type == UIPressTypeMenu) { + int joy_id = OSAppleTV::get_singleton()->joy_id_for_name("Remote"); + Input::get_singleton()->joy_button(joy_id, JoyButton::START, false); + } else { + [super pressesCancelled:presses withEvent:event]; + } + } +} + +@end diff --git a/platform/tvos/godot_view_controller.h b/platform/tvos/godot_view_controller.h new file mode 100644 index 000000000000..3786cf63698e --- /dev/null +++ b/platform/tvos/godot_view_controller.h @@ -0,0 +1,42 @@ +/*************************************************************************/ +/* godot_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 "platform/uikit/uikit_view_controller.h" +#import + +@class GodotView; +@class GodotKeyboardInputView; + +@interface GodotViewController : UIKitViewController + +@property(nonatomic, readonly, strong) GodotView *godotView; +@property(nonatomic, readonly, strong) GodotKeyboardInputView *keyboardView; + +@end diff --git a/platform/tvos/godot_view_controller.mm b/platform/tvos/godot_view_controller.mm new file mode 100644 index 000000000000..4020dac723b6 --- /dev/null +++ b/platform/tvos/godot_view_controller.mm @@ -0,0 +1,116 @@ +/*************************************************************************/ +/* godot_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 "godot_view_controller.h" +#include "core/config/project_settings.h" +#include "display_server_tvos.h" +#import "godot_view.h" +#import "godot_view_renderer.h" +#import "keyboard_input_view.h" +#include "os_tvos.h" + +#import +#import + +@interface GodotViewController () + +@property(strong, nonatomic) GodotViewRenderer *renderer; +@property(strong, nonatomic) GodotKeyboardInputView *keyboardView; + +@end + +@implementation GodotViewController + +- (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]; +} + +- (void)observeKeyboard { + printf("******** setting up keyboard input view\n"); + self.keyboardView = [GodotKeyboardInputView new]; + [self.view addSubview:self.keyboardView]; +} + +- (void)dealloc { + [self.keyboardView removeFromSuperview]; + self.keyboardView = nil; + + self.renderer = nil; + + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +@end diff --git a/platform/tvos/godot_view_gesture_recognizer.h b/platform/tvos/godot_view_gesture_recognizer.h new file mode 100644 index 000000000000..da9e87d3ebbf --- /dev/null +++ b/platform/tvos/godot_view_gesture_recognizer.h @@ -0,0 +1,47 @@ +/*************************************************************************/ +/* 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 + +@interface GodotViewGestureRecognizer : UIGestureRecognizer + +@property(nonatomic, readonly, assign) NSTimeInterval delayTimeInterval; +@property(nonatomic, readonly, assign) BOOL overridesRemoteButtons; + +- (instancetype)init; + +@end diff --git a/platform/tvos/godot_view_gesture_recognizer.mm b/platform/tvos/godot_view_gesture_recognizer.mm new file mode 100644 index 000000000000..d87770c73edb --- /dev/null +++ b/platform/tvos/godot_view_gesture_recognizer.mm @@ -0,0 +1,124 @@ +/*************************************************************************/ +/* 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" + +#include "core/config/project_settings.h" + +#import "os_tvos.h" + +@interface GodotViewGestureRecognizer () + +// Timer used to delay end press message. +@property(nonatomic, readwrite, strong) NSTimer *delayTimer; + +// Delayed touch parameters +@property(nonatomic, readwrite, copy) NSSet *delayedPresses; +@property(nonatomic, readwrite, strong) UIPressesEvent *delayedEvent; + +@property(nonatomic, readwrite, assign) NSTimeInterval delayTimeInterval; + +@end + +@implementation GodotViewGestureRecognizer + +- (instancetype)init { + self = [super init]; + + self.delayTimeInterval = GLOBAL_GET("input_devices/pointing/tvos/press_end_delay"); + self.allowedPressTypes = @[ @(UIPressTypeMenu) ]; + + return self; +} + +- (void)delayPresses:(NSSet *)presses andEvent:(UIPressesEvent *)event { + [self.delayTimer fire]; + + self.delayedPresses = presses; + self.delayedEvent = event; + + self.delayTimer = [NSTimer scheduledTimerWithTimeInterval:self.delayTimeInterval target:self selector:@selector(fireDelayedPress:) userInfo:nil repeats:NO]; +} + +- (void)fireDelayedPress:(id)timer { + [self.delayTimer invalidate]; + self.delayTimer = nil; + + if (self.delayedPresses) { + [self.view pressesEnded:self.delayedPresses withEvent:self.delayedEvent]; + } + + self.delayedPresses = nil; + self.delayedEvent = nil; +} + +- (BOOL)overridesRemoteButtons { + return OSAppleTV::get_singleton()->get_overrides_menu_button(); +} + +- (BOOL)shouldReceiveEvent:(UIEvent *)event { + return self.overridesRemoteButtons; +} + +- (void)pressesBegan:(NSSet *)presses withEvent:(UIPressesEvent *)event { + [self.delayTimer fire]; + [self.view pressesBegan:presses withEvent:event]; +} + +- (void)pressesEnded:(NSSet *)presses withEvent:(UIPressesEvent *)event { + NSSet *cleared = [self copyClearedPresses:presses type:UIPressTypeMenu phase:UIPressPhaseEnded]; + [self delayPresses:cleared andEvent:event]; +} + +- (void)pressesCancelled:(NSSet *)presses withEvent:(UIPressesEvent *)event { + [self cancelDelayTimer]; + [self.view pressesCancelled:presses withEvent:event]; +}; + +- (void)cancelDelayTimer { + [self.delayTimer invalidate]; + self.delayTimer = nil; + self.delayedPresses = nil; + self.delayedEvent = nil; +} + +- (NSSet *)copyClearedPresses:(NSSet *)presses type:(UIPressType)phaseToSave phase:(UIPressPhase)phase { + NSMutableSet *cleared = [NSMutableSet new]; + + for (UIPress *press in presses) { + if (press.type == phaseToSave && press.phase == phase) { + [cleared addObject:press]; + } + } + + return cleared; +} + +@end diff --git a/platform/tvos/godot_view_renderer.h b/platform/tvos/godot_view_renderer.h new file mode 100644 index 000000000000..30cb80b54bc3 --- /dev/null +++ b/platform/tvos/godot_view_renderer.h @@ -0,0 +1,36 @@ +/*************************************************************************/ +/* 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 "platform/uikit/uikit_view_renderer.h" +#import + +@interface GodotViewRenderer : UIKitViewRenderer + +@end diff --git a/platform/tvos/godot_view_renderer.mm b/platform/tvos/godot_view_renderer.mm new file mode 100644 index 000000000000..b80f9e8f84c5 --- /dev/null +++ b/platform/tvos/godot_view_renderer.mm @@ -0,0 +1,60 @@ +/*************************************************************************/ +/* 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_tvos.h" +#include "main/main.h" +#include "os_tvos.h" +#include "servers/audio_server.h" + +#import + +@interface GodotViewRenderer () + +@end + +@implementation GodotViewRenderer + +- (BOOL)startUIKitPlatform { + OSAppleTV::get_singleton()->start(); + return YES; +} + +- (void)renderOnView:(UIView *)view { + if (!OSAppleTV::get_singleton()) { + return; + } + + OSAppleTV::get_singleton()->iterate(); +} + +@end diff --git a/platform/tvos/keyboard_input_view.h b/platform/tvos/keyboard_input_view.h new file mode 100644 index 000000000000..b9cae4de90c2 --- /dev/null +++ b/platform/tvos/keyboard_input_view.h @@ -0,0 +1,37 @@ +/*************************************************************************/ +/* keyboard_input_view.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-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 + +@interface GodotKeyboardInputView : UITextField + +- (BOOL)becomeFirstResponderWithString:(NSString *)existingString multiline:(BOOL)flag cursorStart:(NSInteger)start cursorEnd:(NSInteger)end; + +@end diff --git a/platform/tvos/keyboard_input_view.mm b/platform/tvos/keyboard_input_view.mm new file mode 100644 index 000000000000..2289bf782082 --- /dev/null +++ b/platform/tvos/keyboard_input_view.mm @@ -0,0 +1,217 @@ +/*************************************************************************/ +/* 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_tvos.h" +#include "os_tvos.h" + +@interface GodotKeyboardInputView () + +@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 { + self = [super initWithFrame:frame]; + + if (self) { + [self godot_commonInit]; + } + + return self; +} + +- (void)godot_commonInit { + self.hidden = YES; + self.delegate = self; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(observeTextChange:) + name:UITextFieldTextDidChangeNotification + object:self]; +} + +- (void)setSelectedRange:(NSRange)range { + UITextPosition *beginning = self.beginningOfDocument; + UITextPosition *start = [self positionFromPosition:beginning offset:range.location]; + UITextPosition *end = [self positionFromPosition:start offset:range.length]; + UITextRange *textRange = [self textRangeFromPosition:start toPosition:end]; + + self.selectedTextRange = textRange; +} + +- (NSRange)selectedRange { + UITextPosition *beginning = self.beginningOfDocument; + + UITextRange *selectedRange = self.selectedTextRange; + UITextPosition *selectionStart = selectedRange.start; + UITextPosition *selectionEnd = selectedRange.end; + + const NSInteger location = [self offsetFromPosition:beginning toPosition:selectionStart]; + const NSInteger length = [self offsetFromPosition:selectionStart toPosition:selectionEnd]; + + return NSMakeRange(location, length); +} + +- (void)dealloc { + self.delegate = nil; + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +// MARK: Keyboard + +- (BOOL)canBecomeFirstResponder { + return YES; +} + +- (BOOL)becomeFirstResponderWithString:(NSString *)existingString multiline:(BOOL)flag cursorStart:(NSInteger)start cursorEnd:(NSInteger)end { + self.text = existingString; + self.previousText = existingString; + + NSRange textRange; + + // Either a simple cursor or a selection. + if (end > 0) { + textRange = NSMakeRange(start, end - start); + } else { + textRange = NSMakeRange(start, 0); + } + + self.selectedRange = textRange; + self.previousSelectedRange = textRange; + + return [self becomeFirstResponder]; +} + +- (BOOL)resignFirstResponder { + self.text = nil; + self.previousText = nil; + return [super resignFirstResponder]; +} + +// MARK: OS Messages + +- (void)deleteText:(NSInteger)charactersToDelete { + for (int i = 0; i < charactersToDelete; i++) { + DisplayServerAppleTV::get_singleton()->key(Key::BACKSPACE, true); + DisplayServerAppleTV::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; + } + + DisplayServerAppleTV::get_singleton()->key((Key)character, true); + DisplayServerAppleTV::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 diff --git a/platform/tvos/logo.png b/platform/tvos/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..012e75dbe7f4d43d74280439af79395b9c462f87 GIT binary patch literal 727 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzmUKs7M+SzC{oH>NS%G|u)CkWs zUtb0-Ae)1Mu|1Q41;}CqVksbIU|?Fn1Q$_Qz>HvnB>VjwP6Cy9dAc};L~yF@aJ9O%-?5PV! zzfOFz`1QW$JJ0r39nVQ~`~K|8?`M_siqE~DbKmm(N2YX3It>2hfg z5d1n3(RTRZ_p4c7U3Wz2@OUl#l(qf#LuL6xbNrUOO>7WmvTGICma+NfhBN+x2dehA zbu{=NWk~#gn^B;6X%OezGHJKPf_}>IKxoAGcn-`5?pJy83w5%{O_X)=p#fQC(1KG?T^rW5V{^{3Y|nomR=p+ir2+ zbT{wj&a-LGd*j&6ofd|zjtqNRBzeBL#UX2})MC4-UWU>@C-)ye%=GlI!tKR89!rDL z?qrxGY>bG|?F +#include + +int gargc; +char **gargv; + +int main(int argc, char *argv[]) { +#if defined(VULKAN_ENABLED) + //MoltenVK - enable full component swizzling support + setenv("MVK_CONFIG_FULL_IMAGE_VIEW_SWIZZLE", "1", 1); +#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; +} diff --git a/platform/tvos/os_tvos.h b/platform/tvos/os_tvos.h new file mode 100644 index 000000000000..bc0c30ace76a --- /dev/null +++ b/platform/tvos/os_tvos.h @@ -0,0 +1,88 @@ +/*************************************************************************/ +/* os_tvos.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 OS_TVOS_H +#define OS_TVOS_H + +#include "platform/uikit/uikit_os.h" + +#include "drivers/coreaudio/audio_driver_coreaudio.h" +#include "drivers/unix/os_unix.h" +#include "servers/audio_server.h" +#include "servers/rendering/renderer_compositor.h" +#include "tvos.h" + +#if defined(VULKAN_ENABLED) +#include "drivers/vulkan/rendering_device_vulkan.h" +#include "platform/uikit/uikit_vulkan_context.h" +#endif + +class OSAppleTV : public OS_UIKit { +private: + static HashMap dynamic_symbol_lookup_table; + friend void register_dynamic_symbol(char *name, void *address); + + AudioDriverCoreAudio audio_driver; + + tvOS *tvos; + + virtual void initialize() override; + + virtual void finalize() override; + + bool is_focused = false; + + bool overrides_menu_button = true; + +public: + static OSAppleTV *get_singleton(); + + OSAppleTV(String p_data_dir, String p_cache_dir); + ~OSAppleTV(); + + virtual void alert(const String &p_alert, const String &p_title = "ALERT!") 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 bool _check_internal_feature_support(const String &p_feature) override; + + void on_focus_out(); + void on_focus_in(); + + bool get_overrides_menu_button() const; + void set_overrides_menu_button(bool p_flag); + + void initialize_modules(); +}; + +#endif // OS_TVOS_H diff --git a/platform/tvos/os_tvos.mm b/platform/tvos/os_tvos.mm new file mode 100644 index 000000000000..068718673742 --- /dev/null +++ b/platform/tvos/os_tvos.mm @@ -0,0 +1,189 @@ +/*************************************************************************/ +/* os_tvos.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 "os_tvos.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_tvos.h" +#include "drivers/unix/syslog_logger.h" +#import "godot_view.h" +#import "godot_view_controller.h" +#include "main/main.h" + +#import +#import +#import + +#if defined(VULKAN_ENABLED) +#include "servers/rendering/renderer_rd/renderer_compositor_rd.h" +#import +#ifdef USE_VOLK +#include +#else +#include +#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 *tvos_init_callbacks = NULL; +static int tvos_init_callbacks_count = 0; +static int tvos_init_callbacks_capacity = 0; +HashMap OSAppleTV::dynamic_symbol_lookup_table; + +void add_tvos_init_callback(init_callback cb) { + if (tvos_init_callbacks_count == tvos_init_callbacks_capacity) { + void *new_ptr = realloc(tvos_init_callbacks, sizeof(cb) * 32); + if (new_ptr) { + tvos_init_callbacks = (init_callback *)(new_ptr); + tvos_init_callbacks_capacity += 32; + } + } + if (tvos_init_callbacks_capacity > tvos_init_callbacks_count) { + tvos_init_callbacks[tvos_init_callbacks_count] = cb; + ++tvos_init_callbacks_count; + } +} + +void register_dynamic_symbol(char *name, void *address) { + OSAppleTV::dynamic_symbol_lookup_table[String(name)] = address; +} + +OSAppleTV *OSAppleTV::get_singleton() { + return (OSAppleTV *)OS::get_singleton(); +} + +OSAppleTV::OSAppleTV(String p_data_dir, String p_cache_dir) : + OS_UIKit(p_data_dir, p_cache_dir) { + for (int i = 0; i < tvos_init_callbacks_count; ++i) { + tvos_init_callbacks[i](); + } + free(tvos_init_callbacks); + tvos_init_callbacks = nullptr; + tvos_init_callbacks_count = 0; + tvos_init_callbacks_capacity = 0; + + DisplayServerAppleTV::register_tvos_driver(); +} + +OSAppleTV::~OSAppleTV() {} + +void OSAppleTV::alert(const String &p_alert, const String &p_title) { + const CharString utf8_alert = p_alert.utf8(); + const CharString utf8_title = p_title.utf8(); + tvOS::alert(utf8_alert.get_data(), utf8_title.get_data()); +} + +void OSAppleTV::initialize() { + OS_UIKit::initialize(); +} + +void OSAppleTV::initialize_modules() { + tvos = memnew(tvOS); + Engine::get_singleton()->add_singleton(Engine::Singleton("tvOS", tvos)); +} + +void OSAppleTV::finalize() { + if (tvos) { + memdelete(tvos); + } + + OS_UIKit::finalize(); +} + +// MARK: Dynamic Libraries + +Error OSAppleTV::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 = OSAppleTV::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 OSAppleTV::get_name() const { + return "tvOS"; +}; + +String OSAppleTV::get_model_name() const { + String model = tvos->get_model(); + if (model != "") + return model; + + return OS_Unix::get_model_name(); +} + +bool OSAppleTV::_check_internal_feature_support(const String &p_feature) { + return p_feature == "mobile"; +} + +void OSAppleTV::on_focus_out() { + if (is_focused) { + is_focused = false; + + if (DisplayServerAppleTV::get_singleton()) { + DisplayServerAppleTV::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_FOCUS_OUT); + } + + [AppDelegate.viewController.godotView stopRendering]; + + audio_driver.stop(); + } +} + +void OSAppleTV::on_focus_in() { + if (!is_focused) { + is_focused = true; + + if (DisplayServerAppleTV::get_singleton()) { + DisplayServerAppleTV::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_FOCUS_IN); + } + + [AppDelegate.viewController.godotView startRendering]; + + audio_driver.start(); + } +} + +bool OSAppleTV::get_overrides_menu_button() const { + return overrides_menu_button; +} + +void OSAppleTV::set_overrides_menu_button(bool p_flag) { + overrides_menu_button = p_flag; +} diff --git a/platform/tvos/platform_config.h b/platform/tvos/platform_config.h new file mode 100644 index 000000000000..fed77d893253 --- /dev/null +++ b/platform/tvos/platform_config.h @@ -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 + +#define OPENGL_INCLUDE_H + +#define PLATFORM_REFCOUNT + +#define PTHREAD_RENAME_SELF + +#define _weakify(var) __weak typeof(var) GDWeak_##var = var; +#define _strongify(var) \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wshadow\"") \ + __strong typeof(var) var = GDWeak_##var; \ + _Pragma("clang diagnostic pop") diff --git a/platform/tvos/tvos.h b/platform/tvos/tvos.h new file mode 100644 index 000000000000..740b8a4ddf70 --- /dev/null +++ b/platform/tvos/tvos.h @@ -0,0 +1,53 @@ +/*************************************************************************/ +/* tvos.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 TVOS_H +#define TVOS_H + +#include "core/object/class_db.h" + +class tvOS : public Object { + GDCLASS(tvOS, Object); + + static void _bind_methods(); + +public: + static void alert(const char *p_alert, const char *p_title); + + String get_model() const; + String get_rate_url(int p_app_id) const; + + bool get_overrides_menu_button() const; + void set_overrides_menu_button(bool p_flag); + + tvOS(); +}; + +#endif diff --git a/platform/tvos/tvos.mm b/platform/tvos/tvos.mm new file mode 100644 index 000000000000..a4ac436389b2 --- /dev/null +++ b/platform/tvos/tvos.mm @@ -0,0 +1,103 @@ +/*************************************************************************/ +/* tvos.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 "tvos.h" + +#import "app_delegate.h" +#import "godot_view_controller.h" +#include "os_tvos.h" + +#import +#include + +void tvOS::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_rate_url", "app_id"), &tvOS::get_rate_url); + + ClassDB::bind_method(D_METHOD("get_overrides_menu_button"), &tvOS::get_overrides_menu_button); + ClassDB::bind_method(D_METHOD("set_overrides_menu_button", "p_flag"), &tvOS::set_overrides_menu_button); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "overrides_menu_button"), "set_overrides_menu_button", "get_overrides_menu_button"); +}; + +void tvOS::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 tvOS::get_model() const { + size_t size; + sysctlbyname("hw.machine", NULL, &size, NULL, 0); + char *model = (char *)malloc(size); + if (model == NULL) { + return ""; + } + sysctlbyname("hw.machine", model, &size, NULL, 0); + NSString *platform = [NSString stringWithCString:model encoding:NSUTF8StringEncoding]; + free(model); + const char *str = [platform UTF8String]; + return String(str != NULL ? str : ""); +} + +String tvOS::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; +}; + +bool tvOS::get_overrides_menu_button() const { + if (!OSAppleTV::get_singleton()) { + return false; + } + + return OSAppleTV::get_singleton()->get_overrides_menu_button(); +} + +void tvOS::set_overrides_menu_button(bool p_flag) { + if (!OSAppleTV::get_singleton()) { + return; + } + + OSAppleTV::get_singleton()->set_overrides_menu_button(p_flag); +} + +tvOS::tvOS(){}; diff --git a/servers/rendering/renderer_rd/cluster_builder_rd.cpp b/servers/rendering/renderer_rd/cluster_builder_rd.cpp index 24108b3a598e..218b059ab4bd 100644 --- a/servers/rendering/renderer_rd/cluster_builder_rd.cpp +++ b/servers/rendering/renderer_rd/cluster_builder_rd.cpp @@ -47,6 +47,15 @@ ClusterBuilderSharedDataRD::ClusterBuilderSharedDataRD() { } { + // FIXME: this block of code causes crash on tvOS + // with error message: No valid pixelFormats set. + // Passing non-empty p_framebuffer_format to render_pipeline_create() + // fixes the crash. + // Resulting RenderingDevice::FramebufferFormatID should be created with + // either color attachment or depth attachement or both. + // Using empty attachments list also results in crash. +#ifdef TVOS_ENABLED +#else Vector versions; versions.push_back(""); cluster_render.cluster_render_shader.initialize(versions); @@ -56,6 +65,7 @@ ClusterBuilderSharedDataRD::ClusterBuilderSharedDataRD() { RD::PipelineMultisampleState ms; ms.sample_count = RD::TEXTURE_SAMPLES_4; cluster_render.shader_pipelines[ClusterRender::PIPELINE_MSAA] = RD::get_singleton()->render_pipeline_create(cluster_render.shader, RD::get_singleton()->framebuffer_format_create_empty(), vertex_format, RD::RENDER_PRIMITIVE_TRIANGLES, RD::PipelineRasterizationState(), ms, RD::PipelineDepthStencilState(), RD::PipelineColorBlendState(), 0); +#endif } { Vector versions; diff --git a/servers/rendering/renderer_rd/effects_rd.cpp b/servers/rendering/renderer_rd/effects_rd.cpp index a5a9dae0b90c..f2976ff4d01a 100644 --- a/servers/rendering/renderer_rd/effects_rd.cpp +++ b/servers/rendering/renderer_rd/effects_rd.cpp @@ -2290,7 +2290,7 @@ EffectsRD::EffectsRD(bool p_prefer_raster_effects) { { Vector FSR_upscale_modes; -#if defined(OSX_ENABLED) || defined(IPHONE_ENABLED) +#if defined(OSX_ENABLED) || defined(IPHONE_ENABLED) || defined(TVOS_ENABLED) // MoltenVK does not support some of the operations used by the normal mode of FSR. Fallback works just fine though. FSR_upscale_modes.push_back("\n#define MODE_FSR_UPSCALE_FALLBACK\n"); #else diff --git a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp index 4a6dbc137c48..2501d1e04c2e 100644 --- a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp @@ -4121,7 +4121,7 @@ void RendererSceneRenderRD::_update_volumetric_fog(RID p_render_buffers, RID p_e rb->volumetric_fog->fog_map = RD::get_singleton()->texture_create(tf, RD::TextureView()); RD::get_singleton()->set_resource_name(rb->volumetric_fog->fog_map, "Fog map"); -#if defined(OSX_ENABLED) || defined(IPHONE_ENABLED) +#if defined(OSX_ENABLED) || defined(IPHONE_ENABLED) || defined(TVOS_ENABLED) Vector dm; dm.resize(target_width * target_height * volumetric_fog_depth * 4); dm.fill(0); @@ -4210,7 +4210,7 @@ void RendererSceneRenderRD::_update_volumetric_fog(RID p_render_buffers, RID p_e { RD::Uniform u; -#if defined(OSX_ENABLED) || defined(IPHONE_ENABLED) +#if defined(OSX_ENABLED) || defined(IPHONE_ENABLED) || defined(TVOS_ENABLED) u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; #else u.uniform_type = RD::UNIFORM_TYPE_IMAGE; @@ -4230,7 +4230,7 @@ void RendererSceneRenderRD::_update_volumetric_fog(RID p_render_buffers, RID p_e { RD::Uniform u; -#if defined(OSX_ENABLED) || defined(IPHONE_ENABLED) +#if defined(OSX_ENABLED) || defined(IPHONE_ENABLED) || defined(TVOS_ENABLED) u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; #else u.uniform_type = RD::UNIFORM_TYPE_IMAGE; @@ -4242,7 +4242,7 @@ void RendererSceneRenderRD::_update_volumetric_fog(RID p_render_buffers, RID p_e { RD::Uniform u; -#if defined(OSX_ENABLED) || defined(IPHONE_ENABLED) +#if defined(OSX_ENABLED) || defined(IPHONE_ENABLED) || defined(TVOS_ENABLED) u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; #else u.uniform_type = RD::UNIFORM_TYPE_IMAGE; @@ -4513,7 +4513,7 @@ void RendererSceneRenderRD::_update_volumetric_fog(RID p_render_buffers, RID p_e } { RD::Uniform u; -#if defined(OSX_ENABLED) || defined(IPHONE_ENABLED) +#if defined(OSX_ENABLED) || defined(IPHONE_ENABLED) || defined(TVOS_ENABLED) u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; #else u.uniform_type = RD::UNIFORM_TYPE_IMAGE; @@ -4524,7 +4524,7 @@ void RendererSceneRenderRD::_update_volumetric_fog(RID p_render_buffers, RID p_e } { RD::Uniform u; -#if defined(OSX_ENABLED) || defined(IPHONE_ENABLED) +#if defined(OSX_ENABLED) || defined(IPHONE_ENABLED) || defined(TVOS_ENABLED) u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; #else u.uniform_type = RD::UNIFORM_TYPE_IMAGE; @@ -4536,7 +4536,7 @@ void RendererSceneRenderRD::_update_volumetric_fog(RID p_render_buffers, RID p_e { RD::Uniform u; -#if defined(OSX_ENABLED) || defined(IPHONE_ENABLED) +#if defined(OSX_ENABLED) || defined(IPHONE_ENABLED) || defined(TVOS_ENABLED) u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; #else u.uniform_type = RD::UNIFORM_TYPE_IMAGE; diff --git a/servers/rendering/renderer_rd/shader_rd.cpp b/servers/rendering/renderer_rd/shader_rd.cpp index 73766d14d8ed..0bdf11cf14eb 100644 --- a/servers/rendering/renderer_rd/shader_rd.cpp +++ b/servers/rendering/renderer_rd/shader_rd.cpp @@ -177,7 +177,7 @@ void ShaderRD::_build_variant_code(StringBuilder &builder, uint32_t p_variant, c for (const KeyValue &E : p_version->code_sections) { builder.append(String("#define ") + String(E.key) + "_CODE_USED\n"); } -#if defined(OSX_ENABLED) || defined(IPHONE_ENABLED) +#if defined(OSX_ENABLED) || defined(IPHONE_ENABLED) || defined(TVOS_ENABLED) builder.append("#define MOLTENVK_USED\n"); #endif } break; From e6fb840264426bbee3550f729f9815dfff43a785 Mon Sep 17 00:00:00 2001 From: Sergey Minakov Date: Tue, 8 Feb 2022 09:59:58 +0300 Subject: [PATCH 3/6] [tvOS] tvOS export xcode project --- misc/dist/tvos_xcode/data.pck | 0 .../godot_tvos.xcodeproj/project.pbxproj | 374 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcschemes/godot_tvos.xcscheme | 93 +++++ .../AccentColor.colorset/Contents.json | 11 + .../Content.imageset/Contents.json | 11 + .../Back.imagestacklayer/Contents.json | 6 + .../Contents.json | 17 + .../Content.imageset/Contents.json | 11 + .../Front.imagestacklayer/Contents.json | 6 + .../Content.imageset/Contents.json | 11 + .../Middle.imagestacklayer/Contents.json | 6 + .../Content.imageset/Contents.json | 16 + .../Back.imagestacklayer/Contents.json | 6 + .../App Icon.imagestack/Contents.json | 17 + .../Content.imageset/Contents.json | 16 + .../Front.imagestacklayer/Contents.json | 6 + .../Content.imageset/Contents.json | 16 + .../Middle.imagestacklayer/Contents.json | 6 + .../Contents.json | 32 ++ .../Contents.json | 24 ++ .../Top Shelf Image.imageset/Contents.json | 24 ++ .../godot_tvos/Assets.xcassets/Contents.json | 6 + .../Base.lproj/Launch Screen.storyboard | 24 ++ misc/dist/tvos_xcode/godot_tvos/Info.plist | 34 ++ misc/dist/tvos_xcode/godot_tvos/dummy.cpp | 31 ++ misc/dist/tvos_xcode/godot_tvos/dylibs/empty | 1 + .../Info.plist | 40 ++ .../tvos-arm64/empty | 1 + .../tvos-arm64_x86_64-simulator/empty | 1 + .../Info.plist | 40 ++ .../tvos-arm64/empty | 1 + .../tvos-arm64_x86_64-simulator/empty | 1 + 33 files changed, 896 insertions(+) create mode 100644 misc/dist/tvos_xcode/data.pck create mode 100644 misc/dist/tvos_xcode/godot_tvos.xcodeproj/project.pbxproj create mode 100644 misc/dist/tvos_xcode/godot_tvos.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 misc/dist/tvos_xcode/godot_tvos.xcodeproj/xcshareddata/xcschemes/godot_tvos.xcscheme create mode 100644 misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json create mode 100644 misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json create mode 100644 misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json create mode 100644 misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json create mode 100644 misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json create mode 100644 misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json create mode 100644 misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json create mode 100644 misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json create mode 100644 misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json create mode 100644 misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json create mode 100644 misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json create mode 100644 misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json create mode 100644 misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json create mode 100644 misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json create mode 100644 misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json create mode 100644 misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json create mode 100644 misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json create mode 100644 misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/Contents.json create mode 100644 misc/dist/tvos_xcode/godot_tvos/Base.lproj/Launch Screen.storyboard create mode 100644 misc/dist/tvos_xcode/godot_tvos/Info.plist create mode 100644 misc/dist/tvos_xcode/godot_tvos/dummy.cpp create mode 100644 misc/dist/tvos_xcode/godot_tvos/dylibs/empty create mode 100644 misc/dist/tvos_xcode/libgodot.tvos.debug.xcframework/Info.plist create mode 100644 misc/dist/tvos_xcode/libgodot.tvos.debug.xcframework/tvos-arm64/empty create mode 100644 misc/dist/tvos_xcode/libgodot.tvos.debug.xcframework/tvos-arm64_x86_64-simulator/empty create mode 100644 misc/dist/tvos_xcode/libgodot.tvos.release.xcframework/Info.plist create mode 100644 misc/dist/tvos_xcode/libgodot.tvos.release.xcframework/tvos-arm64/empty create mode 100644 misc/dist/tvos_xcode/libgodot.tvos.release.xcframework/tvos-arm64_x86_64-simulator/empty diff --git a/misc/dist/tvos_xcode/data.pck b/misc/dist/tvos_xcode/data.pck new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/misc/dist/tvos_xcode/godot_tvos.xcodeproj/project.pbxproj b/misc/dist/tvos_xcode/godot_tvos.xcodeproj/project.pbxproj new file mode 100644 index 000000000000..da6c721c37c0 --- /dev/null +++ b/misc/dist/tvos_xcode/godot_tvos.xcodeproj/project.pbxproj @@ -0,0 +1,374 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 52; + objects = { + +/* Begin PBXBuildFile section */ + 1F1575721F582BE20003B888 /* dylibs in Resources */ = {isa = PBXBuildFile; fileRef = 1F1575711F582BE20003B888 /* dylibs */; }; + 9088C79E25C98AF800FCAE9A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9088C79D25C98AF800FCAE9A /* Assets.xcassets */; }; + 9088C7A125C98AF800FCAE9A /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9088C79F25C98AF800FCAE9A /* Launch Screen.storyboard */; }; + 9088C7A425C98AF800FCAE9A /* dummy.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9088C7A325C98AF800FCAE9A /* dummy.cpp */; }; + DEADBEEF2F582BE20003B888 /* $binary.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = DEADBEEF1F582BE20003B888 /* $binary.xcframework */; }; + 90C4BA1125C9A60500CD5FD1 /* $binary.pck in Resources */ = {isa = PBXBuildFile; fileRef = 90C4BA1025C9A5FC00CD5FD1 /* $binary.pck */; }; +/* End PBXBuildFile section */ + + +/* Begin PBXCopyFilesBuildPhase section */ + 90A13CD024AA68E500E8464F /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + $pbx_embeded_frameworks + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1F1575711F582BE20003B888 /* dylibs */ = {isa = PBXFileReference; lastKnownFileType = folder; name = dylibs; path = "$binary/dylibs"; sourceTree = ""; }; + 9088C79125C98AF700FCAE9A /* $binary.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "$binary.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 9088C79D25C98AF800FCAE9A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 9088C7A025C98AF800FCAE9A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = "Base.lproj/Launch Screen.storyboard"; sourceTree = ""; }; + 9088C7A225C98AF800FCAE9A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 9088C7A325C98AF800FCAE9A /* dummy.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = dummy.cpp; sourceTree = ""; }; + DEADBEEF1F582BE20003B888 /* $binary.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = godot; path = "$binary.xcframework"; sourceTree = ""; }; + 90C4BA1025C9A5FC00CD5FD1 /* $binary.pck */ = {isa = PBXFileReference; lastKnownFileType = file; path = "$binary.pck"; sourceTree = SOURCE_ROOT; }; + $additional_pbx_files +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 9088C78E25C98AF700FCAE9A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + DEADBEEF2F582BE20003B888 /* $binary.xcframework */, + $additional_pbx_frameworks_build + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9088C78825C98AF700FCAE9A = { + isa = PBXGroup; + children = ( + 1F1575711F582BE20003B888 /* dylibs */, + 90C4BA1025C9A5FC00CD5FD1 /* $binary.pck */, + 9088C79325C98AF700FCAE9A /* $binary */, + D0BCFE3618AEBDA2004A7AAE /* Frameworks */, + 9088C79225C98AF700FCAE9A /* Products */, + $additional_pbx_resources_refs + ); + sourceTree = ""; + }; + 9088C79225C98AF700FCAE9A /* Products */ = { + isa = PBXGroup; + children = ( + 9088C79125C98AF700FCAE9A /* $binary.app */, + ); + name = Products; + sourceTree = ""; + }; + D0BCFE3618AEBDA2004A7AAE /* Frameworks */ = { + isa = PBXGroup; + children = ( + DEADBEEF1F582BE20003B888 /* $binary.xcframework */, + $additional_pbx_frameworks_refs + ); + name = Frameworks; + sourceTree = ""; + }; + 9088C79325C98AF700FCAE9A /* $binary */ = { + isa = PBXGroup; + children = ( + 9088C79D25C98AF800FCAE9A /* Assets.xcassets */, + 9088C79F25C98AF800FCAE9A /* Launch Screen.storyboard */, + 90C4BA1025C9A5FC00CD5FD1 /* $binary.pck */, + 9088C7A225C98AF800FCAE9A /* Info.plist */, + 9088C7A325C98AF800FCAE9A /* dummy.cpp */, + ); + path = "$binary"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 9088C79025C98AF700FCAE9A /* $binary */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9088C7A725C98AF800FCAE9A /* Build configuration list for PBXNativeTarget "$binary" */; + buildPhases = ( + 9088C78D25C98AF700FCAE9A /* Sources */, + 9088C78E25C98AF700FCAE9A /* Frameworks */, + 9088C78F25C98AF700FCAE9A /* Resources */, + 90A13CD024AA68E500E8464F /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "$binary"; + productName = "$name"; + productReference = 9088C79125C98AF700FCAE9A /* $binary.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 9088C78925C98AF700FCAE9A /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1240; + TargetAttributes = { + 9088C79025C98AF700FCAE9A = { + CreatedOnToolsVersion = 12.4; + }; + }; + }; + buildConfigurationList = 9088C78C25C98AF700FCAE9A /* Build configuration list for PBXProject "$binary" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 9088C78825C98AF700FCAE9A; + productRefGroup = 9088C79225C98AF700FCAE9A /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 9088C79025C98AF700FCAE9A /* $binary */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 9088C78F25C98AF700FCAE9A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9088C7A125C98AF800FCAE9A /* Launch Screen.storyboard in Resources */, + 9088C79E25C98AF800FCAE9A /* Assets.xcassets in Resources */, + 90C4BA1125C9A60500CD5FD1 /* $binary.pck in Resources */, + $additional_pbx_resources_build + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 9088C78D25C98AF700FCAE9A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9088C7A425C98AF800FCAE9A /* dummy.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 9088C79F25C98AF800FCAE9A /* Launch Screen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 9088C7A025C98AF800FCAE9A /* Base */, + ); + name = "Launch Screen.storyboard"; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 9088C7A525C98AF800FCAE9A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + OTHER_LDFLAGS = "$linker_flags"; + SDKROOT = appletvos; + TVOS_DEPLOYMENT_TARGET = 6; + }; + name = Debug; + }; + 9088C7A625C98AF800FCAE9A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + OTHER_LDFLAGS = "$linker_flags"; + SDKROOT = appletvos; + TVOS_DEPLOYMENT_TARGET = 6; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 9088C7A825C98AF800FCAE9A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = "$team_id"; + FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/**"; + INFOPLIST_FILE = "$binary/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/**", + ); + PRODUCT_BUNDLE_IDENTIFIER = "$bundle_identifier"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 10.0; + }; + name = Debug; + }; + 9088C7A925C98AF800FCAE9A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = "$team_id"; + FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/**"; + INFOPLIST_FILE = "$binary/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/**", + ); + PRODUCT_BUNDLE_IDENTIFIER = "$bundle_identifier"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 10.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 9088C78C25C98AF700FCAE9A /* Build configuration list for PBXProject "$binary" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9088C7A525C98AF800FCAE9A /* Debug */, + 9088C7A625C98AF800FCAE9A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 9088C7A725C98AF800FCAE9A /* Build configuration list for PBXNativeTarget "$binary" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9088C7A825C98AF800FCAE9A /* Debug */, + 9088C7A925C98AF800FCAE9A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 9088C78925C98AF700FCAE9A /* Project object */; +} diff --git a/misc/dist/tvos_xcode/godot_tvos.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/misc/dist/tvos_xcode/godot_tvos.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000000..c9c19829f4ae --- /dev/null +++ b/misc/dist/tvos_xcode/godot_tvos.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/misc/dist/tvos_xcode/godot_tvos.xcodeproj/xcshareddata/xcschemes/godot_tvos.xcscheme b/misc/dist/tvos_xcode/godot_tvos.xcodeproj/xcshareddata/xcschemes/godot_tvos.xcscheme new file mode 100644 index 000000000000..b6beeb012fea --- /dev/null +++ b/misc/dist/tvos_xcode/godot_tvos.xcodeproj/xcshareddata/xcschemes/godot_tvos.xcscheme @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/AccentColor.colorset/Contents.json b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 000000000000..eb8789700816 --- /dev/null +++ b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json new file mode 100644 index 000000000000..2e003356c750 --- /dev/null +++ b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json @@ -0,0 +1,11 @@ +{ + "images" : [ + { + "idiom" : "tv" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json new file mode 100644 index 000000000000..73c00596a7fc --- /dev/null +++ b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json new file mode 100644 index 000000000000..de59d885ae8d --- /dev/null +++ b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json @@ -0,0 +1,17 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "layers" : [ + { + "filename" : "Front.imagestacklayer" + }, + { + "filename" : "Middle.imagestacklayer" + }, + { + "filename" : "Back.imagestacklayer" + } + ] +} diff --git a/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json new file mode 100644 index 000000000000..2e003356c750 --- /dev/null +++ b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json @@ -0,0 +1,11 @@ +{ + "images" : [ + { + "idiom" : "tv" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json new file mode 100644 index 000000000000..73c00596a7fc --- /dev/null +++ b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json new file mode 100644 index 000000000000..2e003356c750 --- /dev/null +++ b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json @@ -0,0 +1,11 @@ +{ + "images" : [ + { + "idiom" : "tv" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json new file mode 100644 index 000000000000..73c00596a7fc --- /dev/null +++ b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json new file mode 100644 index 000000000000..795cce17243c --- /dev/null +++ b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "idiom" : "tv", + "scale" : "1x" + }, + { + "idiom" : "tv", + "scale" : "2x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json new file mode 100644 index 000000000000..73c00596a7fc --- /dev/null +++ b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json new file mode 100644 index 000000000000..de59d885ae8d --- /dev/null +++ b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json @@ -0,0 +1,17 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "layers" : [ + { + "filename" : "Front.imagestacklayer" + }, + { + "filename" : "Middle.imagestacklayer" + }, + { + "filename" : "Back.imagestacklayer" + } + ] +} diff --git a/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json new file mode 100644 index 000000000000..795cce17243c --- /dev/null +++ b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "idiom" : "tv", + "scale" : "1x" + }, + { + "idiom" : "tv", + "scale" : "2x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json new file mode 100644 index 000000000000..73c00596a7fc --- /dev/null +++ b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json new file mode 100644 index 000000000000..795cce17243c --- /dev/null +++ b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "idiom" : "tv", + "scale" : "1x" + }, + { + "idiom" : "tv", + "scale" : "2x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json new file mode 100644 index 000000000000..73c00596a7fc --- /dev/null +++ b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json new file mode 100644 index 000000000000..f47ba43daac4 --- /dev/null +++ b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json @@ -0,0 +1,32 @@ +{ + "assets" : [ + { + "filename" : "App Icon - App Store.imagestack", + "idiom" : "tv", + "role" : "primary-app-icon", + "size" : "1280x768" + }, + { + "filename" : "App Icon.imagestack", + "idiom" : "tv", + "role" : "primary-app-icon", + "size" : "400x240" + }, + { + "filename" : "Top Shelf Image Wide.imageset", + "idiom" : "tv", + "role" : "top-shelf-image-wide", + "size" : "2320x720" + }, + { + "filename" : "Top Shelf Image.imageset", + "idiom" : "tv", + "role" : "top-shelf-image", + "size" : "1920x720" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json new file mode 100644 index 000000000000..b65f0cddcfcf --- /dev/null +++ b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json @@ -0,0 +1,24 @@ +{ + "images" : [ + { + "idiom" : "tv", + "scale" : "1x" + }, + { + "idiom" : "tv", + "scale" : "2x" + }, + { + "idiom" : "tv-marketing", + "scale" : "1x" + }, + { + "idiom" : "tv-marketing", + "scale" : "2x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json new file mode 100644 index 000000000000..b65f0cddcfcf --- /dev/null +++ b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json @@ -0,0 +1,24 @@ +{ + "images" : [ + { + "idiom" : "tv", + "scale" : "1x" + }, + { + "idiom" : "tv", + "scale" : "2x" + }, + { + "idiom" : "tv-marketing", + "scale" : "1x" + }, + { + "idiom" : "tv-marketing", + "scale" : "2x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/Contents.json b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/Contents.json new file mode 100644 index 000000000000..73c00596a7fc --- /dev/null +++ b/misc/dist/tvos_xcode/godot_tvos/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/misc/dist/tvos_xcode/godot_tvos/Base.lproj/Launch Screen.storyboard b/misc/dist/tvos_xcode/godot_tvos/Base.lproj/Launch Screen.storyboard new file mode 100644 index 000000000000..660ba53de4f7 --- /dev/null +++ b/misc/dist/tvos_xcode/godot_tvos/Base.lproj/Launch Screen.storyboard @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/misc/dist/tvos_xcode/godot_tvos/Info.plist b/misc/dist/tvos_xcode/godot_tvos/Info.plist new file mode 100644 index 000000000000..daa9782324e2 --- /dev/null +++ b/misc/dist/tvos_xcode/godot_tvos/Info.plist @@ -0,0 +1,34 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + ITSAppUsesNonExemptEncryption + + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + $short_version + CFBundleVersion + $build_version + LSRequiresIPhoneOS + + UILaunchStoryboardName + Launch Screen + UIRequiredDeviceCapabilities + + arm64 + + UIUserInterfaceStyle + Automatic + + diff --git a/misc/dist/tvos_xcode/godot_tvos/dummy.cpp b/misc/dist/tvos_xcode/godot_tvos/dummy.cpp new file mode 100644 index 000000000000..de5b02dc9949 --- /dev/null +++ b/misc/dist/tvos_xcode/godot_tvos/dummy.cpp @@ -0,0 +1,31 @@ +/*************************************************************************/ +/* dummy.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. */ +/*************************************************************************/ + +$cpp_code diff --git a/misc/dist/tvos_xcode/godot_tvos/dylibs/empty b/misc/dist/tvos_xcode/godot_tvos/dylibs/empty new file mode 100644 index 000000000000..bd3e89433361 --- /dev/null +++ b/misc/dist/tvos_xcode/godot_tvos/dylibs/empty @@ -0,0 +1 @@ +Dummy file to make dylibs folder exported diff --git a/misc/dist/tvos_xcode/libgodot.tvos.debug.xcframework/Info.plist b/misc/dist/tvos_xcode/libgodot.tvos.debug.xcframework/Info.plist new file mode 100644 index 000000000000..ea990d4c4388 --- /dev/null +++ b/misc/dist/tvos_xcode/libgodot.tvos.debug.xcframework/Info.plist @@ -0,0 +1,40 @@ + + + + + AvailableLibraries + + + LibraryIdentifier + tvos-arm64 + LibraryPath + libgodot.a + SupportedArchitectures + + arm64 + + SupportedPlatform + tvos + + + LibraryIdentifier + tvos-arm64_x86_64-simulator + LibraryPath + libgodot.a + SupportedArchitectures + + arm64 + x86_64 + + SupportedPlatform + tvos + SupportedPlatformVariant + simulator + + + CFBundlePackageType + XFWK + XCFrameworkFormatVersion + 1.0 + + diff --git a/misc/dist/tvos_xcode/libgodot.tvos.debug.xcframework/tvos-arm64/empty b/misc/dist/tvos_xcode/libgodot.tvos.debug.xcframework/tvos-arm64/empty new file mode 100644 index 000000000000..bd3e89433361 --- /dev/null +++ b/misc/dist/tvos_xcode/libgodot.tvos.debug.xcframework/tvos-arm64/empty @@ -0,0 +1 @@ +Dummy file to make dylibs folder exported diff --git a/misc/dist/tvos_xcode/libgodot.tvos.debug.xcframework/tvos-arm64_x86_64-simulator/empty b/misc/dist/tvos_xcode/libgodot.tvos.debug.xcframework/tvos-arm64_x86_64-simulator/empty new file mode 100644 index 000000000000..bd3e89433361 --- /dev/null +++ b/misc/dist/tvos_xcode/libgodot.tvos.debug.xcframework/tvos-arm64_x86_64-simulator/empty @@ -0,0 +1 @@ +Dummy file to make dylibs folder exported diff --git a/misc/dist/tvos_xcode/libgodot.tvos.release.xcframework/Info.plist b/misc/dist/tvos_xcode/libgodot.tvos.release.xcframework/Info.plist new file mode 100644 index 000000000000..ea990d4c4388 --- /dev/null +++ b/misc/dist/tvos_xcode/libgodot.tvos.release.xcframework/Info.plist @@ -0,0 +1,40 @@ + + + + + AvailableLibraries + + + LibraryIdentifier + tvos-arm64 + LibraryPath + libgodot.a + SupportedArchitectures + + arm64 + + SupportedPlatform + tvos + + + LibraryIdentifier + tvos-arm64_x86_64-simulator + LibraryPath + libgodot.a + SupportedArchitectures + + arm64 + x86_64 + + SupportedPlatform + tvos + SupportedPlatformVariant + simulator + + + CFBundlePackageType + XFWK + XCFrameworkFormatVersion + 1.0 + + diff --git a/misc/dist/tvos_xcode/libgodot.tvos.release.xcframework/tvos-arm64/empty b/misc/dist/tvos_xcode/libgodot.tvos.release.xcframework/tvos-arm64/empty new file mode 100644 index 000000000000..bd3e89433361 --- /dev/null +++ b/misc/dist/tvos_xcode/libgodot.tvos.release.xcframework/tvos-arm64/empty @@ -0,0 +1 @@ +Dummy file to make dylibs folder exported diff --git a/misc/dist/tvos_xcode/libgodot.tvos.release.xcframework/tvos-arm64_x86_64-simulator/empty b/misc/dist/tvos_xcode/libgodot.tvos.release.xcframework/tvos-arm64_x86_64-simulator/empty new file mode 100644 index 000000000000..bd3e89433361 --- /dev/null +++ b/misc/dist/tvos_xcode/libgodot.tvos.release.xcframework/tvos-arm64_x86_64-simulator/empty @@ -0,0 +1 @@ +Dummy file to make dylibs folder exported From 22b6f1ac8fbabf0e3cc5fa7ad1a47dd55e6ec309 Mon Sep 17 00:00:00 2001 From: Sergey Minakov Date: Tue, 8 Feb 2022 10:00:30 +0300 Subject: [PATCH 4/6] [tvOS] GitHub workflow --- .github/workflows/tvos_builds.yml | 38 +++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 .github/workflows/tvos_builds.yml diff --git a/.github/workflows/tvos_builds.yml b/.github/workflows/tvos_builds.yml new file mode 100644 index 000000000000..65a23f8e9fb7 --- /dev/null +++ b/.github/workflows/tvos_builds.yml @@ -0,0 +1,38 @@ +name: 📺 tvOS Builds +on: [push, pull_request] + +# Global Settings +env: + GODOT_BASE_BRANCH: master + SCONSFLAGS: verbose=yes warnings=extra werror=yes debug_symbols=no module_text_server_fb_enabled=yes + +concurrency: + group: ci-${{github.actor}}-${{github.head_ref || github.run_number}}-${{github.ref}}-tvos + cancel-in-progress: true + +jobs: + tvos-template: + runs-on: "macos-latest" + name: Template (target=release, tools=no) + + steps: + - uses: actions/checkout@v2 + + - name: Setup Godot build cache + uses: ./.github/actions/godot-cache + continue-on-error: true + + - name: Setup python and scons + uses: ./.github/actions/godot-deps + + - name: Compilation (armv7) + uses: ./.github/actions/godot-build + with: + sconsflags: ${{ env.SCONSFLAGS }} + platform: tvos + target: release + tools: false + tests: false + + - name: Upload artifact + uses: ./.github/actions/upload-artifact From c18868349f1c17854209df75f2381feb751bee6c Mon Sep 17 00:00:00 2001 From: Sergey Minakov Date: Wed, 9 Feb 2022 16:35:03 +0300 Subject: [PATCH 5/6] [tvOS] extend tvOS plugins to support linker flags and plist types --- platform/tvos/export/export.cpp | 75 ++++++++++++++++++- platform/tvos/export/godot_plugin_config.h | 83 ++++++++++++++++++++-- 2 files changed, 150 insertions(+), 8 deletions(-) diff --git a/platform/tvos/export/export.cpp b/platform/tvos/export/export.cpp index 7b6c4ecd9c5b..957330864e55 100644 --- a/platform/tvos/export/export.cpp +++ b/platform/tvos/export/export.cpp @@ -314,6 +314,30 @@ void EditorExportPlatformTVOS::get_export_options(List *r_options) r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "plugins/" + found_plugins[i].name), false)); } + Set plist_keys; + + for (int i = 0; i < found_plugins.size(); i++) { + // Editable plugin plist values + PluginConfigTVOS plugin = found_plugins[i]; + const String *K = nullptr; + + while ((K = plugin.plist.next(K))) { + String key = *K; + PluginConfigTVOS::PlistItem item = plugin.plist[key]; + switch (item.type) { + case PluginConfigTVOS::PlistItemType::STRING_INPUT: { + String preset_name = "plugins_plist/" + key; + if (!plist_keys.has(preset_name)) { + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, preset_name), item.value)); + plist_keys.insert(preset_name); + } + } break; + default: + continue; + } + } + } + plugins_changed.clear(); plugins = found_plugins; } @@ -792,6 +816,8 @@ Error EditorExportPlatformTVOS::_export_tvos_plugins(const Ref added_embedded_dependenciy_names; HashMap plist_values; + Set plugin_linker_flags; + Error err; for (int i = 0; i < enabled_plugins.size(); i++) { @@ -858,19 +884,41 @@ Error EditorExportPlatformTVOS::_export_tvos_plugins(const Refget(preset_name); + value = "" + input_value + ""; + } break; + default: + value = item.value; + break; + } if (key.is_empty() || value.is_empty()) { continue; } - plist_values[key] = value; + String plist_key = "" + key + ""; + + plist_values[plist_key] = value; } // CPP Code @@ -897,7 +945,7 @@ Error EditorExportPlatformTVOS::_export_tvos_plugins(const Ref" + value + "\n"; + p_config_data.plist_content += key + value + "\n"; } } @@ -938,6 +986,27 @@ Error EditorExportPlatformTVOS::_export_tvos_plugins(const Ref::Element *E = plugin_linker_flags.front(); E; E = E->next()) { + const String &flag = E->get(); + + if (flag.length() == 0) { + continue; + } + + if (result_linker_flags.length() > 0) { + result_linker_flags += ' '; + } + + result_linker_flags += flag; + } + result_linker_flags = result_linker_flags.replace("\"", "\\\""); + p_config_data.linker_flags += result_linker_flags; + } + return OK; } diff --git a/platform/tvos/export/godot_plugin_config.h b/platform/tvos/export/godot_plugin_config.h index fa64bebe8fad..7d74c42474f4 100644 --- a/platform/tvos/export/godot_plugin_config.h +++ b/platform/tvos/export/godot_plugin_config.h @@ -68,6 +68,20 @@ struct PluginConfigTVOS { 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; @@ -91,8 +105,10 @@ struct PluginConfigTVOS { Vector linker_flags; // Optional plist section - // Supports only string types for now - HashMap plist; + // String value is default value. + // Currently supports `string`, `boolean`, `integer`, `raw`, `string_input` types + // : = + HashMap plist; }; static inline String resolve_local_dependency_path(String plugin_config_dir, String dependency_path) { @@ -233,6 +249,8 @@ static inline PluginConfigTVOS load_plugin_config(Ref config_file, c return plugin_config; } + config_file->clear(); + Error err = config_file->load(path); if (err != OK) { @@ -270,13 +288,68 @@ static inline PluginConfigTVOS load_plugin_config(Ref config_file, c config_file->get_section_keys(PluginConfigTVOS::PLIST_SECTION, &keys); for (int i = 0; i < keys.size(); i++) { - String value = config_file->get_value(PluginConfigTVOS::PLIST_SECTION, keys[i], String()); + Vector key_components = keys[i].split(":"); + + String key_value = ""; + PluginConfigTVOS::PlistItemType key_type = PluginConfigTVOS::PlistItemType::UNKNOWN; + + if (key_components.size() == 1) { + key_value = key_components[0]; + key_type = PluginConfigTVOS::PlistItemType::STRING; + } else if (key_components.size() == 2) { + key_value = key_components[0]; + + if (key_components[1].to_lower() == "string") { + key_type = PluginConfigTVOS::PlistItemType::STRING; + } else if (key_components[1].to_lower() == "integer") { + key_type = PluginConfigTVOS::PlistItemType::INTEGER; + } else if (key_components[1].to_lower() == "boolean") { + key_type = PluginConfigTVOS::PlistItemType::BOOLEAN; + } else if (key_components[1].to_lower() == "raw") { + key_type = PluginConfigTVOS::PlistItemType::RAW; + } else if (key_components[1].to_lower() == "string_input") { + key_type = PluginConfigTVOS::PlistItemType::STRING_INPUT; + } + } - if (value.is_empty()) { + if (key_value.is_empty() || key_type == PluginConfigTVOS::PlistItemType::UNKNOWN) { continue; } - plugin_config.plist[keys[i]] = value; + String value; + + switch (key_type) { + case PluginConfigTVOS::PlistItemType::STRING: { + String raw_value = config_file->get_value(PluginConfigTVOS::PLIST_SECTION, keys[i], String()); + value = "" + raw_value + ""; + } break; + case PluginConfigTVOS::PlistItemType::INTEGER: { + int raw_value = config_file->get_value(PluginConfigTVOS::PLIST_SECTION, keys[i], 0); + Dictionary value_dictionary; + String value_format = "$value"; + value_dictionary["value"] = raw_value; + value = value_format.format(value_dictionary, "$_"); + } break; + case PluginConfigTVOS::PlistItemType::BOOLEAN: + if (config_file->get_value(PluginConfigTVOS::PLIST_SECTION, keys[i], false)) { + value = ""; + } else { + value = ""; + } + break; + case PluginConfigTVOS::PlistItemType::RAW: { + String raw_value = config_file->get_value(PluginConfigTVOS::PLIST_SECTION, keys[i], String()); + value = raw_value; + } break; + case PluginConfigTVOS::PlistItemType::STRING_INPUT: { + String raw_value = config_file->get_value(PluginConfigTVOS::PLIST_SECTION, keys[i], String()); + value = raw_value; + } break; + default: + continue; + } + + plugin_config.plist[key_value] = PluginConfigTVOS::PlistItem{ key_type, value }; } } From 2d199ba4603124ea9c5795ed1a598462b442f240 Mon Sep 17 00:00:00 2001 From: Sergey Minakov Date: Tue, 22 Mar 2022 21:51:48 +0300 Subject: [PATCH 6/6] [tvOS] GDExtensionPlugin support --- editor/plugins/gdextension_export_plugin.h | 19 +++++++++++++------ platform/tvos/export/export.cpp | 2 +- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/editor/plugins/gdextension_export_plugin.h b/editor/plugins/gdextension_export_plugin.h index 8ed72b1c422b..d3eb1632d7ad 100644 --- a/editor/plugins/gdextension_export_plugin.h +++ b/editor/plugins/gdextension_export_plugin.h @@ -81,9 +81,9 @@ void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p } add_shared_object(library_path, tags); - if (p_features.has("iOS") && (library_path.ends_with(".a") || library_path.ends_with(".xcframework"))) { + if ((p_features.has("iOS") || p_features.has("tvOS")) && (library_path.ends_with(".a") || library_path.ends_with(".xcframework"))) { String additional_code = "extern void register_dynamic_symbol(char *name, void *address);\n" - "extern void add_ios_init_callback(void (*cb)());\n" + "extern void add_$PLATFORM_init_callback(void (*cb)());\n" "\n" "extern \"C\" void $ENTRY();\n" "void $ENTRY_init() {\n" @@ -91,15 +91,22 @@ void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p "}\n" "struct $ENTRY_struct {\n" " $ENTRY_struct() {\n" - " add_ios_init_callback($ENTRY_init);\n" + " add_$PLATFORM_init_callback($ENTRY_init);\n" " }\n" "};\n" "$ENTRY_struct $ENTRY_struct_instance;\n\n"; additional_code = additional_code.replace("$ENTRY", entry_symbol); - add_ios_cpp_code(additional_code); - String linker_flags = "-Wl,-U,_" + entry_symbol; - add_ios_linker_flags(linker_flags); + + if (p_features.has("iOS")) { + additional_code = additional_code.replace("$PLATFORM", "ios"); + add_ios_cpp_code(additional_code); + add_ios_linker_flags(linker_flags); + } else if (p_features.has("tvOS")) { + additional_code = additional_code.replace("$PLATFORM", "tvos"); + add_tvos_cpp_code(additional_code); + add_tvos_linker_flags(linker_flags); + } } break; } diff --git a/platform/tvos/export/export.cpp b/platform/tvos/export/export.cpp index 957330864e55..0ff9e203611b 100644 --- a/platform/tvos/export/export.cpp +++ b/platform/tvos/export/export.cpp @@ -1069,7 +1069,7 @@ Error EditorExportPlatformTVOS::export_project(const Ref &p_ } String pack_path = dest_dir + binary_name + ".pck"; Vector libraries; - Error err = save_pack(p_preset, pack_path, &libraries); + Error err = save_pack(p_preset, p_debug, pack_path, &libraries); if (err) return err;