diff options
Diffstat (limited to 'platform/osx')
29 files changed, 776 insertions, 316 deletions
diff --git a/platform/osx/SCsub b/platform/osx/SCsub index d72a75af04..3a4c95613d 100644 --- a/platform/osx/SCsub +++ b/platform/osx/SCsub @@ -18,6 +18,7 @@ files = [ "key_mapping_osx.mm", "godot_main_osx.mm", "dir_access_osx.mm", + "tts_osx.mm", "joypad_osx.cpp", "vulkan_context_osx.mm", "gl_manager_osx_legacy.mm", diff --git a/platform/osx/crash_handler_osx.mm b/platform/osx/crash_handler_osx.mm index 06ed91907c..a798ba3b46 100644 --- a/platform/osx/crash_handler_osx.mm +++ b/platform/osx/crash_handler_osx.mm @@ -32,6 +32,7 @@ #include "core/config/project_settings.h" #include "core/os/os.h" +#include "core/string/print_string.h" #include "core/version.h" #include "main/main.h" @@ -85,21 +86,22 @@ static void handle_crash(int sig) { msg = proj_settings->get("debug/settings/crash_handler/message"); } - // Dump the backtrace to stderr with a message to the user - fprintf(stderr, "\n================================================================\n"); - fprintf(stderr, "%s: Program crashed with signal %d\n", __FUNCTION__, sig); - + // Tell MainLoop about the crash. This can be handled by users too in Node. if (OS::get_singleton()->get_main_loop()) { OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_CRASH); } + // Dump the backtrace to stderr with a message to the user + print_error("\n================================================================"); + print_error(vformat("%s: Program crashed with signal %d", __FUNCTION__, sig)); + // Print the engine version just before, so that people are reminded to include the version in backtrace reports. if (String(VERSION_HASH).is_empty()) { - fprintf(stderr, "Engine version: %s\n", VERSION_FULL_NAME); + print_error(vformat("Engine version: %s", VERSION_FULL_NAME)); } else { - fprintf(stderr, "Engine version: %s (%s)\n", VERSION_FULL_NAME, VERSION_HASH); + print_error(vformat("Engine version: %s (%s)", VERSION_FULL_NAME, VERSION_HASH)); } - fprintf(stderr, "Dumping the backtrace. %s\n", msg.utf8().get_data()); + print_error(vformat("Dumping the backtrace. %s", msg)); char **strings = backtrace_symbols(bt_buffer, size); if (strings) { void *load_addr = (void *)load_address(); @@ -157,13 +159,13 @@ static void handle_crash(int sig) { } } - fprintf(stderr, "[%zu] %s\n", i, output.utf8().get_data()); + print_error(vformat("[%d] %s", (int64_t)i, output)); } free(strings); } - fprintf(stderr, "-- END OF BACKTRACE --\n"); - fprintf(stderr, "================================================================\n"); + print_error("-- END OF BACKTRACE --"); + print_error("================================================================"); // Abort to pass the error to the OS abort(); diff --git a/platform/osx/detect.py b/platform/osx/detect.py index 0ff93bedb4..8d848d2094 100644 --- a/platform/osx/detect.py +++ b/platform/osx/detect.py @@ -127,6 +127,7 @@ def configure(env): if env["use_ubsan"] or env["use_asan"] or env["use_tsan"]: env.extra_suffix += ".san" + env.Append(CCFLAGS=["-DSANITIZERS_ENABLED"]) if env["use_ubsan"]: env.Append( diff --git a/platform/osx/dir_access_osx.mm b/platform/osx/dir_access_osx.mm index d26f35e847..6bafb9470d 100644 --- a/platform/osx/dir_access_osx.mm +++ b/platform/osx/dir_access_osx.mm @@ -34,8 +34,8 @@ #include <errno.h> -#include <AppKit/NSWorkspace.h> -#include <Foundation/Foundation.h> +#import <AppKit/NSWorkspace.h> +#import <Foundation/Foundation.h> String DirAccessOSX::fix_unicode_name(const char *p_name) const { String fname; diff --git a/platform/osx/display_server_osx.h b/platform/osx/display_server_osx.h index d9a0d2ce17..9575cb29a2 100644 --- a/platform/osx/display_server_osx.h +++ b/platform/osx/display_server_osx.h @@ -45,10 +45,11 @@ #include "platform/osx/vulkan_context_osx.h" #endif // VULKAN_ENABLED -#include <AppKit/AppKit.h> -#include <AppKit/NSCursor.h> -#include <ApplicationServices/ApplicationServices.h> -#include <CoreVideo/CoreVideo.h> +#import <AppKit/AppKit.h> +#import <AppKit/NSCursor.h> +#import <ApplicationServices/ApplicationServices.h> +#import <CoreVideo/CoreVideo.h> +#import <Foundation/Foundation.h> #undef BitMap #undef CursorShape @@ -96,7 +97,7 @@ public: WindowID transient_parent = INVALID_WINDOW_ID; bool exclusive = false; - Set<WindowID> transient_children; + HashSet<WindowID> transient_children; bool layered_window = false; bool fullscreen = false; @@ -124,7 +125,7 @@ private: NSMenu *apple_menu = nullptr; NSMenu *dock_menu = nullptr; - Map<String, NSMenu *> submenu; + HashMap<String, NSMenu *> submenu; struct WarpEvent { NSTimeInterval timestamp; @@ -137,6 +138,8 @@ private: Vector<KeyEvent> key_event_buffer; int key_event_pos = 0; + id tts = nullptr; + Point2i im_selection; String im_text; @@ -164,9 +167,9 @@ private: CursorShape cursor_shape = CURSOR_ARROW; NSCursor *cursors[CURSOR_MAX]; - Map<CursorShape, Vector<Variant>> cursors_cache; + HashMap<CursorShape, Vector<Variant>> cursors_cache; - Map<WindowID, WindowData> windows; + HashMap<WindowID, WindowData> windows; const NSMenu *_get_menu_root(const String &p_menu_root) const; NSMenu *_get_menu_root(const String &p_menu_root); @@ -205,7 +208,7 @@ public: void push_to_key_event_buffer(const KeyEvent &p_event); void update_im_text(const Point2i &p_selection, const String &p_text); void set_last_focused_window(WindowID p_window); - void mouse_process_popups(bool p_close = false); + bool mouse_process_popups(bool p_close = false); void popup_open(WindowID p_window); void popup_close(WindowID p_window); void set_is_resizing(bool p_is_resizing); @@ -264,6 +267,15 @@ public: virtual void global_menu_remove_item(const String &p_menu_root, int p_idx) override; virtual void global_menu_clear(const String &p_menu_root) override; + virtual bool tts_is_speaking() const override; + virtual bool tts_is_paused() const override; + virtual Array tts_get_voices() const override; + + virtual void tts_speak(const String &p_text, const String &p_voice, int p_volume = 50, float p_pitch = 1.f, float p_rate = 1.f, int p_utterance_id = 0, bool p_interrupt = false) override; + virtual void tts_pause() override; + virtual void tts_resume() override; + virtual void tts_stop() override; + virtual Error dialog_show(String p_title, String p_description, Vector<String> p_buttons, const Callable &p_callback) override; virtual Error dialog_input_text(String p_title, String p_description, String p_partial, const Callable &p_callback) override; @@ -271,7 +283,7 @@ public: virtual MouseMode mouse_get_mode() const override; bool update_mouse_wrap(WindowData &p_wd, NSPoint &r_delta, NSPoint &r_mpos, NSTimeInterval p_timestamp); - virtual void mouse_warp_to_position(const Point2i &p_to) override; + virtual void warp_mouse(const Point2i &p_position) override; virtual Point2i mouse_get_position() const override; void mouse_set_button_state(MouseButton p_state); virtual MouseButton mouse_get_button_state() const override; @@ -361,7 +373,7 @@ public: void cursor_update_shape(); virtual void cursor_set_shape(CursorShape p_shape) override; virtual CursorShape cursor_get_shape() const override; - virtual void cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape = CURSOR_ARROW, const Vector2 &p_hotspot = Vector2()) override; + virtual void cursor_set_custom_image(const Ref<Resource> &p_cursor, CursorShape p_shape = CURSOR_ARROW, const Vector2 &p_hotspot = Vector2()) override; virtual bool get_swap_cancel_ok() override; diff --git a/platform/osx/display_server_osx.mm b/platform/osx/display_server_osx.mm index 9e32cab5ed..b6a5813bd0 100644 --- a/platform/osx/display_server_osx.mm +++ b/platform/osx/display_server_osx.mm @@ -37,18 +37,20 @@ #include "key_mapping_osx.h" #include "os_osx.h" +#include "tts_osx.h" + #include "core/io/marshalls.h" #include "core/math/geometry_2d.h" #include "core/os/keyboard.h" #include "main/main.h" #include "scene/resources/texture.h" -#include <Carbon/Carbon.h> -#include <Cocoa/Cocoa.h> -#include <IOKit/IOCFPlugIn.h> -#include <IOKit/IOKitLib.h> -#include <IOKit/hid/IOHIDKeys.h> -#include <IOKit/hid/IOHIDLib.h> +#import <Carbon/Carbon.h> +#import <Cocoa/Cocoa.h> +#import <IOKit/IOCFPlugIn.h> +#import <IOKit/IOKitLib.h> +#import <IOKit/hid/IOHIDKeys.h> +#import <IOKit/hid/IOHIDLib.h> #if defined(GLES3_ENABLED) #include "drivers/gles3/rasterizer_gles3.h" @@ -144,7 +146,7 @@ DisplayServerOSX::WindowID DisplayServerOSX::_create_window(WindowMode p_mode, V [wd.window_object setTabbingMode:NSWindowTabbingModeDisallowed]; } - CALayer *layer = [wd.window_view layer]; + CALayer *layer = [(NSView *)wd.window_view layer]; if (layer) { layer.contentsScale = scale; } @@ -172,7 +174,7 @@ DisplayServerOSX::WindowID DisplayServerOSX::_create_window(WindowMode p_mode, V wd.size.width = contentRect.size.width * scale; wd.size.height = contentRect.size.height * scale; - CALayer *layer = [wd.window_view layer]; + CALayer *layer = [(NSView *)wd.window_view layer]; if (layer) { layer.contentsScale = scale; } @@ -207,16 +209,16 @@ void DisplayServerOSX::_update_window_style(WindowData p_wd) { if (borderless_full) { // If the window covers up the screen set the level to above the main menu and hide on deactivate. - [p_wd.window_object setLevel:NSMainMenuWindowLevel + 1]; - [p_wd.window_object setHidesOnDeactivate:YES]; + [(NSWindow *)p_wd.window_object setLevel:NSMainMenuWindowLevel + 1]; + [(NSWindow *)p_wd.window_object setHidesOnDeactivate:YES]; } else { // Reset these when our window is not a borderless window that covers up the screen. if (p_wd.on_top && !p_wd.fullscreen) { - [p_wd.window_object setLevel:NSFloatingWindowLevel]; + [(NSWindow *)p_wd.window_object setLevel:NSFloatingWindowLevel]; } else { - [p_wd.window_object setLevel:NSNormalWindowLevel]; + [(NSWindow *)p_wd.window_object setLevel:NSNormalWindowLevel]; } - [p_wd.window_object setHidesOnDeactivate:NO]; + [(NSWindow *)p_wd.window_object setHidesOnDeactivate:NO]; } } @@ -232,7 +234,7 @@ void DisplayServerOSX::_set_window_per_pixel_transparency_enabled(bool p_enabled [wd.window_object setBackgroundColor:[NSColor clearColor]]; [wd.window_object setOpaque:NO]; [wd.window_object setHasShadow:NO]; - CALayer *layer = [wd.window_view layer]; + CALayer *layer = [(NSView *)wd.window_view layer]; if (layer) { [layer setBackgroundColor:[NSColor clearColor].CGColor]; [layer setOpaque:NO]; @@ -247,7 +249,7 @@ void DisplayServerOSX::_set_window_per_pixel_transparency_enabled(bool p_enabled [wd.window_object setBackgroundColor:[NSColor colorWithCalibratedWhite:1 alpha:1]]; [wd.window_object setOpaque:YES]; [wd.window_object setHasShadow:YES]; - CALayer *layer = [wd.window_view layer]; + CALayer *layer = [(NSView *)wd.window_view layer]; if (layer) { [layer setBackgroundColor:[NSColor colorWithCalibratedWhite:1 alpha:1].CGColor]; [layer setOpaque:YES]; @@ -326,7 +328,7 @@ void DisplayServerOSX::_dispatch_input_event(const Ref<InputEvent> &p_event) { Callable::CallError ce; { - List<WindowID>::Element *E = popup_list.front(); + List<WindowID>::Element *E = popup_list.back(); if (E && Object::cast_to<InputEventKey>(*p_event)) { // Redirect keyboard input to active popup. if (windows.has(E->get())) { @@ -567,9 +569,6 @@ DisplayServerOSX::WindowData &DisplayServerOSX::get_window(WindowID p_window) { } void DisplayServerOSX::send_event(NSEvent *p_event) { - if ([p_event type] == NSEventTypeLeftMouseDown || [p_event type] == NSEventTypeRightMouseDown || [p_event type] == NSEventTypeOtherMouseDown) { - mouse_process_popups(); - } // Special case handling of command-period, which is traditionally a special // shortcut in macOS and doesn't arrive at our regular keyDown handler. if ([p_event type] == NSEventTypeKeyDown) { @@ -702,6 +701,7 @@ bool DisplayServerOSX::has_feature(Feature p_feature) const { case FEATURE_NATIVE_ICON: //case FEATURE_KEEP_SCREEN_ON: case FEATURE_SWAP_BUFFERS: + case FEATURE_TEXT_TO_SPEECH: return true; default: { } @@ -1068,9 +1068,9 @@ String DisplayServerOSX::global_menu_get_item_submenu(const String &p_menu_root, if (menu_item) { const NSMenu *sub_menu = [menu_item submenu]; if (sub_menu) { - for (Map<String, NSMenu *>::Element *E = submenu.front(); E; E = E->next()) { - if (E->get() == sub_menu) { - return E->key(); + for (const KeyValue<String, NSMenu *> &E : submenu) { + if (E.value == sub_menu) { + return E.key; } } } @@ -1458,6 +1458,41 @@ void DisplayServerOSX::global_menu_clear(const String &p_menu_root) { } } +bool DisplayServerOSX::tts_is_speaking() const { + ERR_FAIL_COND_V(!tts, false); + return [tts isSpeaking]; +} + +bool DisplayServerOSX::tts_is_paused() const { + ERR_FAIL_COND_V(!tts, false); + return [tts isPaused]; +} + +Array DisplayServerOSX::tts_get_voices() const { + ERR_FAIL_COND_V(!tts, Array()); + return [tts getVoices]; +} + +void DisplayServerOSX::tts_speak(const String &p_text, const String &p_voice, int p_volume, float p_pitch, float p_rate, int p_utterance_id, bool p_interrupt) { + ERR_FAIL_COND(!tts); + [tts speak:p_text voice:p_voice volume:p_volume pitch:p_pitch rate:p_rate utterance_id:p_utterance_id interrupt:p_interrupt]; +} + +void DisplayServerOSX::tts_pause() { + ERR_FAIL_COND(!tts); + [tts pauseSpeaking]; +} + +void DisplayServerOSX::tts_resume() { + ERR_FAIL_COND(!tts); + [tts resumeSpeaking]; +} + +void DisplayServerOSX::tts_stop() { + ERR_FAIL_COND(!tts); + [tts stopSpeaking]; +} + Error DisplayServerOSX::dialog_show(String p_title, String p_description, Vector<String> p_buttons, const Callable &p_callback) { _THREAD_SAFE_METHOD_ @@ -1646,7 +1681,7 @@ bool DisplayServerOSX::update_mouse_wrap(WindowData &p_wd, NSPoint &r_delta, NSP return false; } -void DisplayServerOSX::mouse_warp_to_position(const Point2i &p_to) { +void DisplayServerOSX::warp_mouse(const Point2i &p_position) { _THREAD_SAFE_METHOD_ if (mouse_mode != MOUSE_MODE_CAPTURED) { @@ -1656,7 +1691,7 @@ void DisplayServerOSX::mouse_warp_to_position(const Point2i &p_to) { // Local point in window coords. const NSRect contentRect = [wd.window_view frame]; const float scale = screen_get_max_scale(); - NSRect pointInWindowRect = NSMakeRect(p_to.x / scale, contentRect.size.height - (p_to.y / scale - 1), 0, 0); + NSRect pointInWindowRect = NSMakeRect(p_position.x / scale, contentRect.size.height - (p_position.y / scale - 1), 0, 0); NSPoint pointOnScreen = [[wd.window_view window] convertRectToScreen:pointInWindowRect].origin; // Point in scren coords. @@ -1682,7 +1717,11 @@ Point2i DisplayServerOSX::mouse_get_position() const { for (NSScreen *screen in [NSScreen screens]) { NSRect frame = [screen frame]; if (NSMouseInRect(mouse_pos, frame, NO)) { - return Vector2i((int)mouse_pos.x, (int)-mouse_pos.y) * scale + _get_screens_origin(); + Vector2i pos = Vector2i((int)mouse_pos.x, (int)mouse_pos.y); + pos *= scale; + pos -= _get_screens_origin(); + pos.y *= -1; + return pos; } } return Vector2i(); @@ -1859,8 +1898,8 @@ Vector<DisplayServer::WindowID> DisplayServerOSX::get_window_list() const { _THREAD_SAFE_METHOD_ Vector<int> ret; - for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { - ret.push_back(E->key()); + for (const KeyValue<WindowID, WindowData> &E : windows) { + ret.push_back(E.key); } return ret; } @@ -2214,7 +2253,7 @@ void DisplayServerOSX::window_set_mode(WindowMode p_mode, WindowID p_window) { } break; case WINDOW_MODE_EXCLUSIVE_FULLSCREEN: case WINDOW_MODE_FULLSCREEN: { - [wd.window_object setLevel:NSNormalWindowLevel]; + [(NSWindow *)wd.window_object setLevel:NSNormalWindowLevel]; _set_window_per_pixel_transparency_enabled(true, p_window); if (wd.resize_disabled) { // Restore resize disabled. [wd.window_object setStyleMask:[wd.window_object styleMask] & ~NSWindowStyleMaskResizable]; @@ -2338,9 +2377,9 @@ void DisplayServerOSX::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo return; } if (p_enabled) { - [wd.window_object setLevel:NSFloatingWindowLevel]; + [(NSWindow *)wd.window_object setLevel:NSFloatingWindowLevel]; } else { - [wd.window_object setLevel:NSNormalWindowLevel]; + [(NSWindow *)wd.window_object setLevel:NSNormalWindowLevel]; } } break; case WINDOW_FLAG_TRANSPARENT: { @@ -2381,7 +2420,7 @@ bool DisplayServerOSX::window_get_flag(WindowFlags p_flag, WindowID p_window) co if (wd.fullscreen) { return wd.on_top; } else { - return [wd.window_object level] == NSFloatingWindowLevel; + return [(NSWindow *)wd.window_object level] == NSFloatingWindowLevel; } } break; case WINDOW_FLAG_TRANSPARENT: { @@ -2426,8 +2465,8 @@ bool DisplayServerOSX::window_can_draw(WindowID p_window) const { bool DisplayServerOSX::can_any_window_draw() const { _THREAD_SAFE_METHOD_ - for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { - if (window_get_mode(E->key()) != WINDOW_MODE_MINIMIZED) { + for (const KeyValue<WindowID, WindowData> &E : windows) { + if (window_get_mode(E.key) != WINDOW_MODE_MINIMIZED) { return true; } } @@ -2463,9 +2502,9 @@ DisplayServer::WindowID DisplayServerOSX::get_window_at_screen_position(const Po position /= screen_get_max_scale(); NSInteger wnum = [NSWindow windowNumberAtPoint:NSMakePoint(position.x, position.y) belowWindowWithWindowNumber:0 /*topmost*/]; - for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { - if ([E->get().window_object windowNumber] == wnum) { - return E->key(); + for (const KeyValue<WindowID, WindowData> &E : windows) { + if ([E.value.window_object windowNumber] == wnum) { + return E.key; } } return INVALID_WINDOW_ID; @@ -2632,14 +2671,14 @@ DisplayServerOSX::CursorShape DisplayServerOSX::cursor_get_shape() const { return cursor_shape; } -void DisplayServerOSX::cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) { +void DisplayServerOSX::cursor_set_custom_image(const Ref<Resource> &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) { _THREAD_SAFE_METHOD_ if (p_cursor.is_valid()) { - Map<CursorShape, Vector<Variant>>::Element *cursor_c = cursors_cache.find(p_shape); + HashMap<CursorShape, Vector<Variant>>::Iterator cursor_c = cursors_cache.find(p_shape); if (cursor_c) { - if (cursor_c->get()[0] == p_cursor && cursor_c->get()[1] == p_hotspot) { + if (cursor_c->value[0] == p_cursor && cursor_c->value[1] == p_hotspot) { cursor_set_shape(p_shape); return; } @@ -2844,8 +2883,8 @@ void DisplayServerOSX::process_events() { Input::get_singleton()->flush_buffered_events(); } - for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { - WindowData &wd = E->get(); + for (KeyValue<WindowID, WindowData> &E : windows) { + WindowData &wd = E.value; if (wd.mpath.size() > 0) { update_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream]); if (Geometry2D::is_point_in_polygon(wd.mouse_pos, wd.mpath)) { @@ -2888,14 +2927,13 @@ void DisplayServerOSX::swap_buffers() { void DisplayServerOSX::set_native_icon(const String &p_filename) { _THREAD_SAFE_METHOD_ - FileAccess *f = FileAccess::open(p_filename, FileAccess::READ); - ERR_FAIL_COND(!f); + Ref<FileAccess> f = FileAccess::open(p_filename, FileAccess::READ); + ERR_FAIL_COND(f.is_null()); Vector<uint8_t> data; uint64_t len = f->get_length(); data.resize(len); f->get_buffer((uint8_t *)&data.write[0], len); - memdelete(f); NSData *icon_data = [[NSData alloc] initWithBytes:&data.write[0] length:len]; ERR_FAIL_COND_MSG(!icon_data, "Error reading icon data."); @@ -2996,21 +3034,25 @@ Rect2i DisplayServerOSX::window_get_popup_safe_rect(WindowID p_window) const { } void DisplayServerOSX::popup_open(WindowID p_window) { + _THREAD_SAFE_METHOD_ + WindowData &wd = windows[p_window]; if (wd.is_popup) { bool was_empty = popup_list.is_empty(); - // Close all popups, up to current popup parent, or every popup if new window is not transient. + // Find current popup parent, or root popup if new window is not transient. + List<WindowID>::Element *C = nullptr; List<WindowID>::Element *E = popup_list.back(); while (E) { if (wd.transient_parent != E->get() || wd.transient_parent == INVALID_WINDOW_ID) { - send_window_event(windows[E->get()], DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST); - List<WindowID>::Element *F = E->prev(); - popup_list.erase(E); - E = F; + C = E; + E = E->prev(); } else { break; } } + if (C) { + send_window_event(windows[C->get()], DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST); + } if (was_empty && popup_list.is_empty()) { // Inform OS that popup was opened, to close other native popups. @@ -3022,12 +3064,16 @@ void DisplayServerOSX::popup_open(WindowID p_window) { } void DisplayServerOSX::popup_close(WindowID p_window) { + _THREAD_SAFE_METHOD_ + bool was_empty = popup_list.is_empty(); List<WindowID>::Element *E = popup_list.find(p_window); while (E) { - send_window_event(windows[E->get()], DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST); List<WindowID>::Element *F = E->next(); + WindowID win_id = E->get(); popup_list.erase(E); + + send_window_event(windows[win_id], DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST); E = F; } if (!was_empty && popup_list.is_empty()) { @@ -3036,18 +3082,17 @@ void DisplayServerOSX::popup_close(WindowID p_window) { } } -void DisplayServerOSX::mouse_process_popups(bool p_close) { +bool DisplayServerOSX::mouse_process_popups(bool p_close) { _THREAD_SAFE_METHOD_ bool was_empty = popup_list.is_empty(); + bool closed = false; if (p_close) { // Close all popups. List<WindowID>::Element *E = popup_list.front(); - while (E) { + if (E) { send_window_event(windows[E->get()], DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST); - List<WindowID>::Element *F = E->next(); - popup_list.erase(E); - E = F; + closed = true; } if (!was_empty) { // Inform OS that all popups are closed. @@ -3056,11 +3101,13 @@ void DisplayServerOSX::mouse_process_popups(bool p_close) { } else { uint64_t delta = OS::get_singleton()->get_ticks_msec() - time_since_popup; if (delta < 250) { - return; + return false; } Point2i pos = mouse_get_position(); + List<WindowID>::Element *C = nullptr; List<WindowID>::Element *E = popup_list.back(); + // Find top popup to close. while (E) { // Popup window area. Rect2i win_rect = Rect2i(window_get_position(E->get()), window_get_size(E->get())); @@ -3071,17 +3118,20 @@ void DisplayServerOSX::mouse_process_popups(bool p_close) { } else if (safe_rect != Rect2i() && safe_rect.has_point(pos)) { break; } else { - send_window_event(windows[E->get()], DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST); - List<WindowID>::Element *F = E->prev(); - popup_list.erase(E); - E = F; + C = E; + E = E->prev(); } } + if (C) { + send_window_event(windows[C->get()], DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST); + closed = true; + } if (!was_empty && popup_list.is_empty()) { // Inform OS that all popups are closed. [[NSDistributedNotificationCenter defaultCenter] postNotificationName:@"com.apple.HIToolbox.endMenuTrackingNotification" object:@"org.godotengine.godot.popup_window"]; } } + return closed; } DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { @@ -3110,6 +3160,9 @@ DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode // Register to be notified on displays arrangement changes. CGDisplayRegisterReconfigurationCallback(_displays_arrangement_changed, nullptr); + // Init TTS + tts = [[TTS_OSX alloc] init]; + NSMenuItem *menu_item; NSString *title; @@ -3214,11 +3267,11 @@ DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode DisplayServerOSX::~DisplayServerOSX() { // Destroy all windows. - for (Map<WindowID, WindowData>::Element *E = windows.front(); E;) { - Map<WindowID, WindowData>::Element *F = E; - E = E->next(); - [F->get().window_object setContentView:nil]; - [F->get().window_object close]; + for (HashMap<WindowID, WindowData>::Iterator E = windows.begin(); E;) { + HashMap<WindowID, WindowData>::Iterator F = E; + ++E; + [F->value.window_object setContentView:nil]; + [F->value.window_object close]; } // Destroy drivers. diff --git a/platform/osx/export/codesign.cpp b/platform/osx/export/codesign.cpp index b609a21c44..fd044c00cc 100644 --- a/platform/osx/export/codesign.cpp +++ b/platform/osx/export/codesign.cpp @@ -49,8 +49,8 @@ /*************************************************************************/ String CodeSignCodeResources::hash_sha1_base64(const String &p_path) { - FileAccessRef fa = FileAccess::open(p_path, FileAccess::READ); - ERR_FAIL_COND_V_MSG(!fa, String(), vformat("CodeSign/CodeResources: Can't open file: \"%s\".", p_path)); + Ref<FileAccess> fa = FileAccess::open(p_path, FileAccess::READ); + ERR_FAIL_COND_V_MSG(fa.is_null(), String(), vformat("CodeSign/CodeResources: Can't open file: \"%s\".", p_path)); CryptoCore::SHA1Context ctx; ctx.start(); @@ -68,14 +68,13 @@ String CodeSignCodeResources::hash_sha1_base64(const String &p_path) { unsigned char hash[0x14]; ctx.finish(hash); - fa->close(); return CryptoCore::b64_encode_str(hash, 0x14); } String CodeSignCodeResources::hash_sha256_base64(const String &p_path) { - FileAccessRef fa = FileAccess::open(p_path, FileAccess::READ); - ERR_FAIL_COND_V_MSG(!fa, String(), vformat("CodeSign/CodeResources: Can't open file: \"%s\".", p_path)); + Ref<FileAccess> fa = FileAccess::open(p_path, FileAccess::READ); + ERR_FAIL_COND_V_MSG(fa.is_null(), String(), vformat("CodeSign/CodeResources: Can't open file: \"%s\".", p_path)); CryptoCore::SHA256Context ctx; ctx.start(); @@ -93,7 +92,6 @@ String CodeSignCodeResources::hash_sha256_base64(const String &p_path) { unsigned char hash[0x20]; ctx.finish(hash); - fa->close(); return CryptoCore::b64_encode_str(hash, 0x20); } @@ -211,16 +209,14 @@ bool CodeSignCodeResources::add_nested_file(const String &p_root, const String & } \ } - DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - ERR_FAIL_COND_V(!da, false); + Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + ERR_FAIL_COND_V(da.is_null(), false); Vector<String> files_to_add; if (LipO::is_lipo(p_exepath)) { String tmp_path_name = EditorPaths::get_singleton()->get_cache_dir().plus_file("_lipo"); Error err = da->make_dir_recursive(tmp_path_name); - if (err != OK) { - ERR_FAIL_V_MSG(false, vformat("CodeSign/CodeResources: Failed to create \"%s\" subfolder.", tmp_path_name)); - } + ERR_FAIL_COND_V_MSG(err != OK, false, vformat("CodeSign/CodeResources: Failed to create \"%s\" subfolder.", tmp_path_name)); LipO lip; if (lip.open_file(p_exepath)) { for (int i = 0; i < lip.get_arch_count(); i++) { @@ -287,8 +283,8 @@ bool CodeSignCodeResources::add_nested_file(const String &p_root, const String & } bool CodeSignCodeResources::add_folder_recursive(const String &p_root, const String &p_path, const String &p_main_exe_path) { - DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - ERR_FAIL_COND_V(!da, false); + Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + ERR_FAIL_COND_V(da.is_null(), false); Error err = da->change_dir(p_root.plus_file(p_path)); ERR_FAIL_COND_V(err != OK, false); @@ -431,12 +427,11 @@ bool CodeSignCodeResources::save_to_file(const String &p_path) { String text = pl.save_text(); ERR_FAIL_COND_V_MSG(text.is_empty(), false, "CodeSign/CodeResources: Generating resources PList failed."); - FileAccessRef fa = FileAccess::open(p_path, FileAccess::WRITE); - ERR_FAIL_COND_V_MSG(!fa, false, vformat("CodeSign/CodeResources: Can't open file: \"%s\".", p_path)); + Ref<FileAccess> fa = FileAccess::open(p_path, FileAccess::WRITE); + ERR_FAIL_COND_V_MSG(fa.is_null(), false, vformat("CodeSign/CodeResources: Can't open file: \"%s\".", p_path)); CharString cs = text.utf8(); fa->store_buffer((const uint8_t *)cs.ptr(), cs.length()); - fa->close(); return true; } @@ -809,8 +804,8 @@ int CodeSignRequirements::get_size() const { return blob.size(); } -void CodeSignRequirements::write_to_file(FileAccess *p_file) const { - ERR_FAIL_COND_MSG(!p_file, "CodeSign/Requirements: Invalid file handle."); +void CodeSignRequirements::write_to_file(Ref<FileAccess> p_file) const { + ERR_FAIL_COND_MSG(p_file.is_null(), "CodeSign/Requirements: Invalid file handle."); p_file->store_buffer(blob.ptr(), blob.size()); } @@ -863,8 +858,8 @@ int CodeSignEntitlementsText::get_size() const { return blob.size(); } -void CodeSignEntitlementsText::write_to_file(FileAccess *p_file) const { - ERR_FAIL_COND_MSG(!p_file, "CodeSign/EntitlementsText: Invalid file handle."); +void CodeSignEntitlementsText::write_to_file(Ref<FileAccess> p_file) const { + ERR_FAIL_COND_MSG(p_file.is_null(), "CodeSign/EntitlementsText: Invalid file handle."); p_file->store_buffer(blob.ptr(), blob.size()); } @@ -918,8 +913,8 @@ int CodeSignEntitlementsBinary::get_size() const { return blob.size(); } -void CodeSignEntitlementsBinary::write_to_file(FileAccess *p_file) const { - ERR_FAIL_COND_MSG(!p_file, "CodeSign/EntitlementsBinary: Invalid file handle."); +void CodeSignEntitlementsBinary::write_to_file(Ref<FileAccess> p_file) const { + ERR_FAIL_COND_MSG(p_file.is_null(), "CodeSign/EntitlementsBinary: Invalid file handle."); p_file->store_buffer(blob.ptr(), blob.size()); } @@ -947,7 +942,7 @@ CodeSignCodeDirectory::CodeSignCodeDirectory(uint8_t p_hash_size, uint8_t p_hash } blob.resize(cd_size); memset(blob.ptrw() + 8, 0x00, cd_size - 8); - CodeDirectoryHeader *cd = (CodeDirectoryHeader *)(blob.ptrw() + 8); + CodeDirectoryHeader *cd = reinterpret_cast<CodeDirectoryHeader *>(blob.ptrw() + 8); bool is_64_cl = (p_code_limit >= std::numeric_limits<uint32_t>::max()); @@ -1040,8 +1035,8 @@ int CodeSignCodeDirectory::get_size() const { return blob.size(); } -void CodeSignCodeDirectory::write_to_file(FileAccess *p_file) const { - ERR_FAIL_COND_MSG(!p_file, "CodeSign/CodeDirectory: Invalid file handle."); +void CodeSignCodeDirectory::write_to_file(Ref<FileAccess> p_file) const { + ERR_FAIL_COND_MSG(p_file.is_null(), "CodeSign/CodeDirectory: Invalid file handle."); p_file->store_buffer(blob.ptr(), blob.size()); } @@ -1086,8 +1081,8 @@ int CodeSignSignature::get_size() const { return blob.size(); } -void CodeSignSignature::write_to_file(FileAccess *p_file) const { - ERR_FAIL_COND_MSG(!p_file, "CodeSign/Signature: Invalid file handle."); +void CodeSignSignature::write_to_file(Ref<FileAccess> p_file) const { + ERR_FAIL_COND_MSG(p_file.is_null(), "CodeSign/Signature: Invalid file handle."); p_file->store_buffer(blob.ptr(), blob.size()); } @@ -1115,8 +1110,8 @@ int CodeSignSuperBlob::get_size() const { return size; } -void CodeSignSuperBlob::write_to_file(FileAccess *p_file) const { - ERR_FAIL_COND_MSG(!p_file, "CodeSign/SuperBlob: Invalid file handle."); +void CodeSignSuperBlob::write_to_file(Ref<FileAccess> p_file) const { + ERR_FAIL_COND_MSG(p_file.is_null(), "CodeSign/SuperBlob: Invalid file handle."); uint32_t size = get_size(); uint32_t data_offset = 12 + blobs.size() * 8; @@ -1147,8 +1142,8 @@ void CodeSignSuperBlob::write_to_file(FileAccess *p_file) const { PackedByteArray CodeSign::file_hash_sha1(const String &p_path) { PackedByteArray file_hash; - FileAccessRef f = FileAccess::open(p_path, FileAccess::READ); - ERR_FAIL_COND_V_MSG(!f, PackedByteArray(), vformat("CodeSign: Can't open file: \"%s\".", p_path)); + Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ); + ERR_FAIL_COND_V_MSG(f.is_null(), PackedByteArray(), vformat("CodeSign: Can't open file: \"%s\".", p_path)); CryptoCore::SHA1Context ctx; ctx.start(); @@ -1171,8 +1166,8 @@ PackedByteArray CodeSign::file_hash_sha1(const String &p_path) { PackedByteArray CodeSign::file_hash_sha256(const String &p_path) { PackedByteArray file_hash; - FileAccessRef f = FileAccess::open(p_path, FileAccess::READ); - ERR_FAIL_COND_V_MSG(!f, PackedByteArray(), vformat("CodeSign: Can't open file: \"%s\".", p_path)); + Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ); + ERR_FAIL_COND_V_MSG(f.is_null(), PackedByteArray(), vformat("CodeSign: Can't open file: \"%s\".", p_path)); CryptoCore::SHA256Context ctx; ctx.start(); @@ -1208,8 +1203,8 @@ Error CodeSign::_codesign_file(bool p_use_hardened_runtime, bool p_force, const String id; String main_exe = p_exe_path; - DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - if (!da) { + Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + if (da.is_null()) { r_error_msg = TTR("Can't get filesystem access."); ERR_FAIL_V_MSG(ERR_CANT_CREATE, "CodeSign: Can't get filesystem access."); } @@ -1522,8 +1517,8 @@ Error CodeSign::_codesign_file(bool p_use_hardened_runtime, bool p_force, const } Error CodeSign::codesign(bool p_use_hardened_runtime, bool p_force, const String &p_path, const String &p_ent_path, String &r_error_msg) { - DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - if (!da) { + Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + if (da.is_null()) { r_error_msg = TTR("Can't get filesystem access."); ERR_FAIL_V_MSG(ERR_CANT_CREATE, "CodeSign: Can't get filesystem access."); } diff --git a/platform/osx/export/codesign.h b/platform/osx/export/codesign.h index e5e9be5c28..3a08c0ea86 100644 --- a/platform/osx/export/codesign.h +++ b/platform/osx/export/codesign.h @@ -132,7 +132,7 @@ public: virtual int get_size() const = 0; virtual uint32_t get_index_type() const = 0; - virtual void write_to_file(FileAccess *p_file) const = 0; + virtual void write_to_file(Ref<FileAccess> p_file) const = 0; }; /*************************************************************************/ @@ -168,7 +168,7 @@ public: virtual int get_size() const override; virtual uint32_t get_index_type() const override { return 0x00000002; }; - virtual void write_to_file(FileAccess *p_file) const override; + virtual void write_to_file(Ref<FileAccess> p_file) const override; }; /*************************************************************************/ @@ -190,7 +190,7 @@ public: virtual int get_size() const override; virtual uint32_t get_index_type() const override { return 0x00000005; }; - virtual void write_to_file(FileAccess *p_file) const override; + virtual void write_to_file(Ref<FileAccess> p_file) const override; }; /*************************************************************************/ @@ -212,7 +212,7 @@ public: virtual int get_size() const override; virtual uint32_t get_index_type() const override { return 0x00000007; }; - virtual void write_to_file(FileAccess *p_file) const override; + virtual void write_to_file(Ref<FileAccess> p_file) const override; }; /*************************************************************************/ @@ -314,7 +314,7 @@ public: virtual int get_size() const override; virtual uint32_t get_index_type() const override { return 0x00000000; }; - virtual void write_to_file(FileAccess *p_file) const override; + virtual void write_to_file(Ref<FileAccess> p_file) const override; }; /*************************************************************************/ @@ -333,7 +333,7 @@ public: virtual int get_size() const override; virtual uint32_t get_index_type() const override { return 0x00010000; }; - virtual void write_to_file(FileAccess *p_file) const override; + virtual void write_to_file(Ref<FileAccess> p_file) const override; }; /*************************************************************************/ @@ -347,7 +347,7 @@ public: bool add_blob(const Ref<CodeSignBlob> &p_blob); int get_size() const; - void write_to_file(FileAccess *p_file) const; + void write_to_file(Ref<FileAccess> p_file) const; }; /*************************************************************************/ diff --git a/platform/osx/export/export_plugin.cpp b/platform/osx/export/export_plugin.cpp index 4f06342fac..7010709123 100644 --- a/platform/osx/export/export_plugin.cpp +++ b/platform/osx/export/export_plugin.cpp @@ -51,7 +51,7 @@ void EditorExportPlatformOSX::get_preset_features(const Ref<EditorExportPreset> r_features->push_back("64"); } -bool EditorExportPlatformOSX::get_export_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const { +bool EditorExportPlatformOSX::get_export_option_visibility(const String &p_option, const HashMap<StringName, Variant> &p_options) const { // These options are not supported by built-in codesign, used on non macOS host. if (!OS::get_singleton()->has_feature("macos")) { if (p_option == "codesign/identity" || p_option == "codesign/timestamp" || p_option == "codesign/hardened_runtime" || p_option == "codesign/custom_options" || p_option.begins_with("notarization/")) { @@ -256,22 +256,24 @@ void EditorExportPlatformOSX::_make_icon(const Ref<Image> &p_icon, Vector<uint8_ String path = EditorPaths::get_singleton()->get_cache_dir().plus_file("icon.png"); ResourceSaver::save(path, it); - FileAccess *f = FileAccess::open(path, FileAccess::READ); - if (!f) { - // Clean up generated file. - DirAccess::remove_file_or_error(path); - ERR_FAIL(); - } + { + Ref<FileAccess> f = FileAccess::open(path, FileAccess::READ); + if (f.is_null()) { + // Clean up generated file. + DirAccess::remove_file_or_error(path); + add_message(EXPORT_MESSAGE_ERROR, TTR("Icon Creation"), vformat(TTR("Could not open icon file \"%s\"."), path)); + return; + } - int ofs = data.size(); - uint64_t len = f->get_length(); - data.resize(data.size() + len + 8); - f->get_buffer(&data.write[ofs + 8], len); - memdelete(f); - len += 8; - len = BSWAP32(len); - memcpy(&data.write[ofs], icon_infos[i].name, 4); - encode_uint32(len, &data.write[ofs + 4]); + int ofs = data.size(); + uint64_t len = f->get_length(); + data.resize(data.size() + len + 8); + f->get_buffer(&data.write[ofs + 8], len); + len += 8; + len = BSWAP32(len); + memcpy(&data.write[ofs], icon_infos[i].name, 4); + encode_uint32(len, &data.write[ofs + 4]); + } // Clean up generated file. DirAccess::remove_file_or_error(path); @@ -440,18 +442,25 @@ Error EditorExportPlatformOSX::_notarize(const Ref<EditorExportPreset> &p_preset String str; Error err = OS::get_singleton()->execute("xcrun", args, &str, nullptr, true); - ERR_FAIL_COND_V(err != OK, err); + if (err != OK || (str.find("not found") != -1) || (str.find("not recognized") != -1)) { + add_message(EXPORT_MESSAGE_WARNING, TTR("Notarization"), TTR("Could not start xcrun executable.")); + return err; + } print_verbose("altool (" + p_path + "):\n" + str); - if (str.find("RequestUUID") == -1) { - EditorNode::add_io_error("altool: " + str); + int rq_offset = str.find("RequestUUID"); + if (rq_offset == -1) { + add_message(EXPORT_MESSAGE_WARNING, TTR("Notarization"), TTR("Notarization failed.")); return FAILED; } else { - print_line(TTR("Note: The notarization process generally takes less than an hour. When the process is completed, you'll receive an email.")); - print_line(" " + TTR("You can check progress manually by opening a Terminal and running the following command:")); - print_line(" \"xcrun altool --notarization-history 0 -u <your email> -p <app-specific pwd>\""); - print_line(" " + TTR("Run the following command to staple the notarization ticket to the exported application (optional):")); - print_line(" \"xcrun stapler staple <app path>\""); + int next_nl = str.find("\n", rq_offset); + String request_uuid = (next_nl == -1) ? str.substr(rq_offset + 14, -1) : str.substr(rq_offset + 14, next_nl - rq_offset - 14); + add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), vformat(TTR("Notarization request UUID: \"%s\""), request_uuid)); + add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), TTR("The notarization process generally takes less than an hour. When the process is completed, you'll receive an email.")); + add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t" + TTR("You can check progress manually by opening a Terminal and running the following command:")); + add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t\t\"xcrun altool --notarization-history 0 -u <your email> -p <app-specific pwd>\""); + add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t" + TTR("Run the following command to staple the notarization ticket to the exported application (optional):")); + add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t\t\"xcrun stapler staple <app path>\""); } #endif @@ -469,21 +478,21 @@ Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_prese #ifdef OSX_ENABLED if (p_preset->get("codesign/timestamp") && p_warn) { - WARN_PRINT("Timestamping is not compatible with ad-hoc signature, and was disabled!"); + add_message(EXPORT_MESSAGE_INFO, TTR("Code Signing"), TTR("Timestamping is not compatible with ad-hoc signature, and was disabled!")); } if (p_preset->get("codesign/hardened_runtime") && p_warn) { - WARN_PRINT("Hardened Runtime is not compatible with ad-hoc signature, and was disabled!"); + add_message(EXPORT_MESSAGE_INFO, TTR("Code Signing"), TTR("Hardened Runtime is not compatible with ad-hoc signature, and was disabled!")); } #endif String error_msg; Error err = CodeSign::codesign(false, p_preset->get("codesign/replace_existing_signature"), p_path, p_ent_path, error_msg); if (err != OK) { - EditorNode::add_io_error("Built-in CodeSign: " + error_msg); + add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("Built-in CodeSign failed with error \"%s\"."), error_msg)); return FAILED; } #else - ERR_FAIL_V_MSG(FAILED, "Built-in CodeSign require regex module"); + add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Built-in CodeSign require regex module.")); #endif return OK; } else { @@ -492,7 +501,7 @@ Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_prese if (p_preset->get("codesign/timestamp")) { if (ad_hoc) { if (p_warn) { - WARN_PRINT("Timestamping is not compatible with ad-hoc signature, and was disabled!"); + add_message(EXPORT_MESSAGE_INFO, TTR("Code Signing"), TTR("Timestamping is not compatible with ad-hoc signature, and was disabled!")); } } else { args.push_back("--timestamp"); @@ -501,7 +510,7 @@ Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_prese if (p_preset->get("codesign/hardened_runtime")) { if (ad_hoc) { if (p_warn) { - WARN_PRINT("Hardened Runtime is not compatible with ad-hoc signature, and was disabled!"); + add_message(EXPORT_MESSAGE_INFO, TTR("Code Signing"), TTR("Hardened Runtime is not compatible with ad-hoc signature, and was disabled!")); } } else { args.push_back("--options"); @@ -539,15 +548,18 @@ Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_prese String str; Error err = OS::get_singleton()->execute("codesign", args, &str, nullptr, true); - ERR_FAIL_COND_V(err != OK, err); + if (err != OK || (str.find("not found") != -1) || (str.find("not recognized") != -1)) { + add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Could not start codesign executable, make sure Xcode command line tools are installed.")); + return err; + } print_verbose("codesign (" + p_path + "):\n" + str); if (str.find("no identity found") != -1) { - EditorNode::add_io_error("CodeSign: " + TTR("No identity found.")); + add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("No identity found.")); return FAILED; } if ((str.find("unrecognized blob type") != -1) || (str.find("cannot read entitlement data") != -1)) { - EditorNode::add_io_error("CodeSign: " + TTR("Invalid entitlements file.")); + add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Invalid entitlements file.")); return FAILED; } return OK; @@ -565,7 +577,7 @@ Error EditorExportPlatformOSX::_code_sign_directory(const Ref<EditorExportPreset } Error dir_access_error; - DirAccessRef dir_access{ DirAccess::open(p_path, &dir_access_error) }; + Ref<DirAccess> dir_access{ DirAccess::open(p_path, &dir_access_error) }; if (dir_access_error != OK) { return dir_access_error; @@ -592,7 +604,7 @@ Error EditorExportPlatformOSX::_code_sign_directory(const Ref<EditorExportPreset return code_sign_error; } } else if (p_should_error_on_non_code) { - ERR_PRINT(vformat("Cannot sign file %s.", current_file)); + add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("Cannot sign file %s."), current_file)); return Error::FAILED; } @@ -603,14 +615,14 @@ Error EditorExportPlatformOSX::_code_sign_directory(const Ref<EditorExportPreset return OK; } -Error EditorExportPlatformOSX::_copy_and_sign_files(DirAccessRef &dir_access, const String &p_src_path, +Error EditorExportPlatformOSX::_copy_and_sign_files(Ref<DirAccess> &dir_access, const String &p_src_path, const String &p_in_app_path, bool p_sign_enabled, const Ref<EditorExportPreset> &p_preset, const String &p_ent_path, bool p_should_error_on_non_code_sign) { Error err{ OK }; if (dir_access->dir_exists(p_src_path)) { #ifndef UNIX_ENABLED - WARN_PRINT("Relative symlinks are not supported, exported " + p_src_path.get_file() + " might be broken!"); + add_message(EXPORT_MESSAGE_INFO, TTR("Export"), vformat(TTR("Relative symlinks are not supported, exported \"%s\" might be broken!"), p_src_path.get_file())); #endif print_verbose("export framework: " + p_src_path + " -> " + p_in_app_path); err = dir_access->make_dir_recursive(p_in_app_path); @@ -633,7 +645,7 @@ Error EditorExportPlatformOSX::_copy_and_sign_files(DirAccessRef &dir_access, co } Error EditorExportPlatformOSX::_export_osx_plugins_for(Ref<EditorExportPlugin> p_editor_export_plugin, - const String &p_app_path_name, DirAccessRef &dir_access, + const String &p_app_path_name, Ref<DirAccess> &dir_access, bool p_sign_enabled, const Ref<EditorExportPreset> &p_preset, const String &p_ent_path) { Error error{ OK }; @@ -667,14 +679,17 @@ Error EditorExportPlatformOSX::_create_dmg(const String &p_dmg_path, const Strin String str; Error err = OS::get_singleton()->execute("hdiutil", args, &str, nullptr, true); - ERR_FAIL_COND_V(err != OK, err); + if (err != OK) { + add_message(EXPORT_MESSAGE_ERROR, TTR("DMG Creation"), TTR("Could not start hdiutil executable.")); + return err; + } print_verbose("hdiutil returned: " + str); if (str.find("create failed") != -1) { if (str.find("File exists") != -1) { - EditorNode::add_io_error("hdiutil: " + TTR("DMG creation failed, file already exists.")); + add_message(EXPORT_MESSAGE_ERROR, TTR("DMG Creation"), TTR("`hdiutil create` failed - file exists.")); } else { - EditorNode::add_io_error("hdiutil: " + TTR("DMG create failed.")); + add_message(EXPORT_MESSAGE_ERROR, TTR("DMG Creation"), TTR("`hdiutil create` failed.")); } return FAILED; } @@ -683,8 +698,11 @@ Error EditorExportPlatformOSX::_create_dmg(const String &p_dmg_path, const Strin } Error EditorExportPlatformOSX::_export_debug_script(const Ref<EditorExportPreset> &p_preset, const String &p_app_name, const String &p_pkg_name, const String &p_path) { - FileAccessRef f = FileAccess::open(p_path, FileAccess::WRITE); - ERR_FAIL_COND_V(!f, ERR_CANT_CREATE); + Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::WRITE); + if (f.is_null()) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Debug Script Export"), vformat(TTR("Could not open file \"%s\"."), p_path)); + return ERR_CANT_CREATE; + } f->store_line("#!/bin/sh"); f->store_line("echo -ne '\\033c\\033]0;" + p_app_name + "\\a'"); @@ -712,17 +730,18 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p String err; src_pkg_name = find_export_template("osx.zip", &err); if (src_pkg_name.is_empty()) { - EditorNode::add_io_error(err); + add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), TTR("Export template not found.")); return ERR_FILE_NOT_FOUND; } } if (!DirAccess::exists(p_path.get_base_dir())) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), TTR("The given export path doesn't exist.")); return ERR_FILE_BAD_PATH; } - FileAccess *src_f = nullptr; - zlib_filefunc_def io = zipio_create_io_from_file(&src_f); + Ref<FileAccess> io_fa; + zlib_filefunc_def io = zipio_create_io(&io_fa); if (ep.step(TTR("Creating app bundle"), 0)) { return ERR_SKIP; @@ -730,7 +749,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p unzFile src_pkg_zip = unzOpen2(src_pkg_name.utf8().get_data(), &io); if (!src_pkg_zip) { - EditorNode::add_io_error(TTR("Could not find template app to export:") + "\n" + src_pkg_name); + add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), vformat(TTR("Could not find template app to export: \"%s\"."), src_pkg_name)); return ERR_FILE_NOT_FOUND; } @@ -755,7 +774,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p } else if (p_path.ends_with("app")) { export_format = "app"; } else { - EditorNode::add_io_error("Invalid export format"); + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Invalid export format.")); return ERR_CANT_CREATE; } @@ -778,15 +797,18 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p Error err = OK; - DirAccessRef tmp_app_dir = DirAccess::create_for_path(tmp_base_path_name); - if (!tmp_app_dir) { + Ref<DirAccess> tmp_app_dir = DirAccess::create_for_path(tmp_base_path_name); + if (tmp_app_dir.is_null()) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not create directory: \"%s\"."), tmp_base_path_name)); err = ERR_CANT_CREATE; } DirAccess::remove_file_or_error(scr_path); if (DirAccess::exists(tmp_app_path_name)) { + String old_dir = tmp_app_dir->get_current_dir(); if (tmp_app_dir->change_dir(tmp_app_path_name) == OK) { tmp_app_dir->erase_contents_recursive(); + tmp_app_dir->change_dir(old_dir); } } @@ -796,21 +818,33 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p if (err == OK) { print_verbose("Creating " + tmp_app_path_name + "/Contents/MacOS"); err = tmp_app_dir->make_dir_recursive(tmp_app_path_name + "/Contents/MacOS"); + if (err != OK) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not create directory \"%s\"."), tmp_app_path_name + "/Contents/MacOS")); + } } if (err == OK) { print_verbose("Creating " + tmp_app_path_name + "/Contents/Frameworks"); err = tmp_app_dir->make_dir_recursive(tmp_app_path_name + "/Contents/Frameworks"); + if (err != OK) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not create directory \"%s\"."), tmp_app_path_name + "/Contents/Frameworks")); + } } if ((err == OK) && helpers.size() > 0) { print_line("Creating " + tmp_app_path_name + "/Contents/Helpers"); err = tmp_app_dir->make_dir_recursive(tmp_app_path_name + "/Contents/Helpers"); + if (err != OK) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not create directory \"%s\"."), tmp_app_path_name + "/Contents/Helpers")); + } } if (err == OK) { print_verbose("Creating " + tmp_app_path_name + "/Contents/Resources"); err = tmp_app_dir->make_dir_recursive(tmp_app_path_name + "/Contents/Resources"); + if (err != OK) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not create directory \"%s\"."), tmp_app_path_name + "/Contents/Resources")); + } } Dictionary appnames = ProjectSettings::get_singleton()->get("application/config/name_localized"); @@ -832,7 +866,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p { String fname = tmp_app_path_name + "/Contents/Resources/en.lproj"; tmp_app_dir->make_dir_recursive(fname); - FileAccessRef f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE); + Ref<FileAccess> f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE); f->store_line("/* Localized versions of Info.plist keys */"); f->store_line(""); f->store_line("CFBundleDisplayName = \"" + ProjectSettings::get_singleton()->get("application/config/name").operator String() + "\";"); @@ -878,7 +912,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p String lang = tr->get_locale(); String fname = tmp_app_path_name + "/Contents/Resources/" + lang + ".lproj"; tmp_app_dir->make_dir_recursive(fname); - FileAccessRef f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE); + Ref<FileAccess> f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE); f->store_line("/* Localized versions of Info.plist keys */"); f->store_line(""); if (appnames.has(lang)) { @@ -935,6 +969,9 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p unz_file_info info; char fname[16384]; ret = unzGetCurrentFileInfo(src_pkg_zip, &info, fname, 16384, nullptr, 0, nullptr, 0); + if (ret != UNZ_OK) { + break; + } String file = String::utf8(fname); @@ -951,16 +988,22 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p if (((info.external_fa >> 16L) & 0120000) == 0120000) { #ifndef UNIX_ENABLED - WARN_PRINT(vformat("Relative symlinks are not supported on this OS, the exported project might be broken!")); + add_message(EXPORT_MESSAGE_INFO, TTR("Export"), TTR("Relative symlinks are not supported on this OS, the exported project might be broken!")); #endif // Handle symlinks in the archive. file = tmp_app_path_name.plus_file(file); if (err == OK) { err = tmp_app_dir->make_dir_recursive(file.get_base_dir()); + if (err != OK) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not create directory \"%s\"."), file.get_base_dir())); + } } if (err == OK) { String lnk_data = String::utf8((const char *)data.ptr(), data.size()); err = tmp_app_dir->create_link(lnk_data, file); + if (err != OK) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not created symlink \"%s\" -> \"%s\"."), lnk_data, file)); + } print_verbose(vformat("ADDING SYMLINK %s => %s\n", file, lnk_data)); } @@ -993,12 +1036,10 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p if (!iconpath.is_empty()) { if (iconpath.get_extension() == "icns") { - FileAccess *icon = FileAccess::open(iconpath, FileAccess::READ); - if (icon) { + Ref<FileAccess> icon = FileAccess::open(iconpath, FileAccess::READ); + if (icon.is_valid()) { data.resize(icon->get_length()); icon->get_buffer(&data.write[0], icon->get_length()); - icon->close(); - memdelete(icon); } } else { Ref<Image> icon; @@ -1037,18 +1078,20 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p file = tmp_app_path_name.plus_file(file); if (err == OK) { err = tmp_app_dir->make_dir_recursive(file.get_base_dir()); + if (err != OK) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not create directory \"%s\"."), file.get_base_dir())); + } } if (err == OK) { - FileAccess *f = FileAccess::open(file, FileAccess::WRITE); - if (f) { + Ref<FileAccess> f = FileAccess::open(file, FileAccess::WRITE); + if (f.is_valid()) { f->store_buffer(data.ptr(), data.size()); - f->close(); if (is_execute) { // chmod with 0755 if the file is executable. FileAccess::set_unix_permissions(file, 0755); } - memdelete(f); } else { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not open \"%s\"."), file)); err = ERR_CANT_CREATE; } } @@ -1061,7 +1104,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p unzClose(src_pkg_zip); if (!found_binary) { - ERR_PRINT(vformat("Requested template binary '%s' not found. It might be missing from your template archive.", binary_to_use)); + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Requested template binary \"%s\" not found. It might be missing from your template archive."), binary_to_use)); err = ERR_FILE_NOT_FOUND; } @@ -1071,6 +1114,9 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p if ((con_scr == 1 && p_debug) || (con_scr == 2)) { err = _export_debug_script(p_preset, pkg_name, tmp_app_path_name.get_file() + "/Contents/MacOS/" + pkg_name, scr_path); FileAccess::set_unix_permissions(scr_path, 0755); + if (err != OK) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Could not create console script.")); + } } } @@ -1091,8 +1137,8 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p if (sign_enabled && (ent_path.is_empty())) { ent_path = EditorPaths::get_singleton()->get_cache_dir().plus_file(pkg_name + ".entitlements"); - FileAccess *ent_f = FileAccess::open(ent_path, FileAccess::WRITE); - if (ent_f) { + Ref<FileAccess> ent_f = FileAccess::open(ent_path, FileAccess::WRITE); + if (ent_f.is_valid()) { ent_f->store_line("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); ent_f->store_line("<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">"); ent_f->store_line("<plist version=\"1.0\">"); @@ -1213,16 +1259,14 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p ent_f->store_line("</dict>"); ent_f->store_line("</plist>"); - - ent_f->close(); - memdelete(ent_f); } else { + add_message(EXPORT_MESSAGE_ERROR, TTR("Code Signing"), TTR("Could not create entitlements file.")); err = ERR_CANT_CREATE; } if ((err == OK) && helpers.size() > 0) { ent_f = FileAccess::open(hlp_ent_path, FileAccess::WRITE); - if (ent_f) { + if (ent_f.is_valid()) { ent_f->store_line("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); ent_f->store_line("<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">"); ent_f->store_line("<plist version=\"1.0\">"); @@ -1233,17 +1277,15 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p ent_f->store_line("<true/>"); ent_f->store_line("</dict>"); ent_f->store_line("</plist>"); - - ent_f->close(); - memdelete(ent_f); } else { + add_message(EXPORT_MESSAGE_ERROR, TTR("Code Signing"), TTR("Could not create helper entitlements file.")); err = ERR_CANT_CREATE; } } } if ((err == OK) && helpers.size() > 0) { - DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); for (int i = 0; i < helpers.size(); i++) { String hlp_path = helpers[i]; err = da->copy(hlp_path, tmp_app_path_name + "/Contents/Helpers/" + hlp_path.get_file()); @@ -1264,13 +1306,13 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p ad_hoc = (sign_identity == "" || sign_identity == "-"); bool lib_validation = p_preset->get("codesign/entitlements/disable_library_validation"); if ((!dylibs_found.is_empty() || !shared_objects.is_empty()) && sign_enabled && ad_hoc && !lib_validation) { - ERR_PRINT("Ad-hoc signed applications require the 'Disable Library Validation' entitlement to load dynamic libraries."); + add_message(EXPORT_MESSAGE_ERROR, TTR("Code Signing"), TTR("Ad-hoc signed applications require the 'Disable Library Validation' entitlement to load dynamic libraries.")); err = ERR_CANT_CREATE; } } if (err == OK) { - DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); for (int i = 0; i < shared_objects.size(); i++) { String src_path = ProjectSettings::get_singleton()->globalize_path(shared_objects[i].path); if (shared_objects[i].target.is_empty()) { @@ -1334,8 +1376,8 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p OS::get_singleton()->move_to_trash(p_path); } - FileAccess *dst_f = nullptr; - zlib_filefunc_def io_dst = zipio_create_io_from_file(&dst_f); + Ref<FileAccess> io_fa_dst; + zlib_filefunc_def io_dst = zipio_create_io(&io_fa_dst); zipFile zip = zipOpen2(p_path.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io_dst); _zip_folder_recursive(zip, tmp_base_path_name, "", pkg_name); @@ -1348,7 +1390,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p bool noto_enabled = p_preset->get("notarization/enable"); if (err == OK && noto_enabled) { if (export_format == "app") { - WARN_PRINT("Notarization requires the app to be archived first, select the DMG or ZIP export format instead."); + add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), TTR("Notarization requires the app to be archived first, select the DMG or ZIP export format instead.")); } else { if (ep.step(TTR("Sending archive for notarization"), 4)) { return ERR_SKIP; @@ -1380,7 +1422,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p void EditorExportPlatformOSX::_zip_folder_recursive(zipFile &p_zip, const String &p_root_path, const String &p_folder, const String &p_pkg_name) { String dir = p_folder.is_empty() ? p_root_path : p_root_path.plus_file(p_folder); - DirAccessRef da = DirAccess::open(dir); + Ref<DirAccess> da = DirAccess::open(dir); da->list_dir_begin(); String f = da->get_next(); while (!f.is_empty()) { @@ -1471,9 +1513,10 @@ void EditorExportPlatformOSX::_zip_folder_recursive(zipFile &p_zip, const String 0x0314, // "version made by", 0x03 - Unix, 0x14 - ZIP specification version 2.0, required to store Unix file permissions 0); - FileAccessRef fa = FileAccess::open(dir.plus_file(f), FileAccess::READ); - if (!fa) { - ERR_FAIL_MSG(vformat("Can't open file to read from path \"%s\".", dir.plus_file(f))); + Ref<FileAccess> fa = FileAccess::open(dir.plus_file(f), FileAccess::READ); + if (fa.is_null()) { + add_message(EXPORT_MESSAGE_ERROR, TTR("ZIP Creation"), vformat(TTR("Could not open file to read from path \"%s\"."), dir.plus_file(f))); + return; } const int bufsize = 16384; uint8_t buf[bufsize]; diff --git a/platform/osx/export/export_plugin.h b/platform/osx/export/export_plugin.h index b3edfb7f90..ec97d4139f 100644 --- a/platform/osx/export/export_plugin.h +++ b/platform/osx/export/export_plugin.h @@ -58,11 +58,11 @@ class EditorExportPlatformOSX : public EditorExportPlatform { Error _notarize(const Ref<EditorExportPreset> &p_preset, const String &p_path); Error _code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path, const String &p_ent_path, bool p_warn = true); Error _code_sign_directory(const Ref<EditorExportPreset> &p_preset, const String &p_path, const String &p_ent_path, bool p_should_error_on_non_code = true); - Error _copy_and_sign_files(DirAccessRef &dir_access, const String &p_src_path, const String &p_in_app_path, + Error _copy_and_sign_files(Ref<DirAccess> &dir_access, const String &p_src_path, const String &p_in_app_path, bool p_sign_enabled, const Ref<EditorExportPreset> &p_preset, const String &p_ent_path, bool p_should_error_on_non_code_sign); Error _export_osx_plugins_for(Ref<EditorExportPlugin> p_editor_export_plugin, const String &p_app_path_name, - DirAccessRef &dir_access, bool p_sign_enabled, const Ref<EditorExportPreset> &p_preset, + Ref<DirAccess> &dir_access, bool p_sign_enabled, const Ref<EditorExportPreset> &p_preset, const String &p_ent_path); Error _create_dmg(const String &p_dmg_path, const String &p_pkg_name, const String &p_app_path_name); void _zip_folder_recursive(zipFile &p_zip, const String &p_root_path, const String &p_folder, const String &p_pkg_name); @@ -101,7 +101,7 @@ class EditorExportPlatformOSX : public EditorExportPlatform { protected: virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) override; virtual void get_export_options(List<ExportOption> *r_options) override; - virtual bool get_export_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const override; + virtual bool get_export_option_visibility(const String &p_option, const HashMap<StringName, Variant> &p_options) const override; public: virtual String get_name() const override { return "macOS"; } @@ -127,7 +127,7 @@ public: r_features->push_back("macos"); } - virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) override { + virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, HashSet<String> &p_features) override { } EditorExportPlatformOSX(); diff --git a/platform/osx/export/lipo.cpp b/platform/osx/export/lipo.cpp index 66d2ecdbcf..82baf18c52 100644 --- a/platform/osx/export/lipo.cpp +++ b/platform/osx/export/lipo.cpp @@ -35,8 +35,8 @@ #ifdef MODULE_REGEX_ENABLED bool LipO::is_lipo(const String &p_path) { - FileAccessRef fb = FileAccess::open(p_path, FileAccess::READ); - ERR_FAIL_COND_V_MSG(!fb, false, vformat("LipO: Can't open file: \"%s\".", p_path)); + Ref<FileAccess> fb = FileAccess::open(p_path, FileAccess::READ); + ERR_FAIL_COND_V_MSG(fb.is_null(), false, vformat("LipO: Can't open file: \"%s\".", p_path)); uint32_t magic = fb->get_32(); return (magic == 0xbebafeca || magic == 0xcafebabe || magic == 0xbfbafeca || magic == 0xcafebabf); } @@ -45,7 +45,7 @@ bool LipO::create_file(const String &p_output_path, const PackedStringArray &p_f close(); fa = FileAccess::open(p_output_path, FileAccess::WRITE); - ERR_FAIL_COND_V_MSG(!fa, false, vformat("LipO: Can't open file: \"%s\".", p_output_path)); + ERR_FAIL_COND_V_MSG(fa.is_null(), false, vformat("LipO: Can't open file: \"%s\".", p_output_path)); uint64_t max_size = 0; for (int i = 0; i < p_files.size(); i++) { @@ -64,8 +64,8 @@ bool LipO::create_file(const String &p_output_path, const PackedStringArray &p_f archs.push_back(arch); - FileAccessRef fb = FileAccess::open(p_files[i], FileAccess::READ); - if (!fb) { + Ref<FileAccess> fb = FileAccess::open(p_files[i], FileAccess::READ); + if (fb.is_null()) { close(); ERR_FAIL_V_MSG(false, vformat("LipO: Can't open file: \"%s.\"", p_files[i])); } @@ -101,8 +101,8 @@ bool LipO::create_file(const String &p_output_path, const PackedStringArray &p_f // Write files and padding. for (int i = 0; i < archs.size(); i++) { - FileAccessRef fb = FileAccess::open(p_files[i], FileAccess::READ); - if (!fb) { + Ref<FileAccess> fb = FileAccess::open(p_files[i], FileAccess::READ); + if (fb.is_null()) { close(); ERR_FAIL_V_MSG(false, vformat("LipO: Can't open file: \"%s.\"", p_files[i])); } @@ -123,7 +123,6 @@ bool LipO::create_file(const String &p_output_path, const PackedStringArray &p_f if (br > 0) { fa->store_buffer(step, br); } - fb->close(); } return true; } @@ -132,7 +131,7 @@ bool LipO::open_file(const String &p_path) { close(); fa = FileAccess::open(p_path, FileAccess::READ); - ERR_FAIL_COND_V_MSG(!fa, false, vformat("LipO: Can't open file: \"%s\".", p_path)); + ERR_FAIL_COND_V_MSG(fa.is_null(), false, vformat("LipO: Can't open file: \"%s\".", p_path)); uint32_t magic = fa->get_32(); if (magic == 0xbebafeca) { @@ -197,16 +196,16 @@ bool LipO::open_file(const String &p_path) { } int LipO::get_arch_count() const { - ERR_FAIL_COND_V_MSG(!fa, 0, "LipO: File not opened."); + ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "LipO: File not opened."); return archs.size(); } bool LipO::extract_arch(int p_index, const String &p_path) { - ERR_FAIL_COND_V_MSG(!fa, false, "LipO: File not opened."); + ERR_FAIL_COND_V_MSG(fa.is_null(), false, "LipO: File not opened."); ERR_FAIL_INDEX_V(p_index, archs.size(), false); - FileAccessRef fb = FileAccess::open(p_path, FileAccess::WRITE); - ERR_FAIL_COND_V_MSG(!fb, false, vformat("LipO: Can't open file: \"%s\".", p_path)); + Ref<FileAccess> fb = FileAccess::open(p_path, FileAccess::WRITE); + ERR_FAIL_COND_V_MSG(fb.is_null(), false, vformat("LipO: Can't open file: \"%s\".", p_path)); fa->seek(archs[p_index].offset); @@ -223,16 +222,10 @@ bool LipO::extract_arch(int p_index, const String &p_path) { if (br > 0) { fb->store_buffer(step, br); } - fb->close(); return true; } void LipO::close() { - if (fa) { - fa->close(); - memdelete(fa); - fa = nullptr; - } archs.clear(); } diff --git a/platform/osx/export/lipo.h b/platform/osx/export/lipo.h index 68bbe42dd6..0e419be17e 100644 --- a/platform/osx/export/lipo.h +++ b/platform/osx/export/lipo.h @@ -50,7 +50,7 @@ class LipO : public RefCounted { uint32_t align; }; - FileAccess *fa = nullptr; + Ref<FileAccess> fa; Vector<FatArch> archs; static inline size_t PAD(size_t s, size_t a) { diff --git a/platform/osx/export/macho.cpp b/platform/osx/export/macho.cpp index 08f2a855b0..e6e67eff06 100644 --- a/platform/osx/export/macho.cpp +++ b/platform/osx/export/macho.cpp @@ -49,7 +49,7 @@ uint32_t MachO::seg_align(uint64_t p_vmaddr, uint32_t p_min, uint32_t p_max) { } bool MachO::alloc_signature(uint64_t p_size) { - ERR_FAIL_COND_V_MSG(!fa, false, "MachO: File not opened."); + ERR_FAIL_COND_V_MSG(fa.is_null(), false, "MachO: File not opened."); if (signature_offset != 0) { // Nothing to do, already have signature load command. return true; @@ -103,15 +103,15 @@ bool MachO::alloc_signature(uint64_t p_size) { } bool MachO::is_macho(const String &p_path) { - FileAccessRef fb = FileAccess::open(p_path, FileAccess::READ); - ERR_FAIL_COND_V_MSG(!fb, false, vformat("MachO: Can't open file: \"%s\".", p_path)); + Ref<FileAccess> fb = FileAccess::open(p_path, FileAccess::READ); + ERR_FAIL_COND_V_MSG(fb.is_null(), false, vformat("MachO: Can't open file: \"%s\".", p_path)); uint32_t magic = fb->get_32(); return (magic == 0xcefaedfe || magic == 0xfeedface || magic == 0xcffaedfe || magic == 0xfeedfacf); } bool MachO::open_file(const String &p_path) { fa = FileAccess::open(p_path, FileAccess::READ_WRITE); - ERR_FAIL_COND_V_MSG(!fa, false, vformat("MachO: Can't open file: \"%s\".", p_path)); + ERR_FAIL_COND_V_MSG(fa.is_null(), false, vformat("MachO: Can't open file: \"%s\".", p_path)); uint32_t magic = fa->get_32(); MachHeader mach_header; @@ -232,37 +232,37 @@ bool MachO::open_file(const String &p_path) { } uint64_t MachO::get_exe_base() { - ERR_FAIL_COND_V_MSG(!fa, 0, "MachO: File not opened."); + ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "MachO: File not opened."); return exe_base; } uint64_t MachO::get_exe_limit() { - ERR_FAIL_COND_V_MSG(!fa, 0, "MachO: File not opened."); + ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "MachO: File not opened."); return exe_limit; } int32_t MachO::get_align() { - ERR_FAIL_COND_V_MSG(!fa, 0, "MachO: File not opened."); + ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "MachO: File not opened."); return align; } uint32_t MachO::get_cputype() { - ERR_FAIL_COND_V_MSG(!fa, 0, "MachO: File not opened."); + ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "MachO: File not opened."); return cputype; } uint32_t MachO::get_cpusubtype() { - ERR_FAIL_COND_V_MSG(!fa, 0, "MachO: File not opened."); + ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "MachO: File not opened."); return cpusubtype; } uint64_t MachO::get_size() { - ERR_FAIL_COND_V_MSG(!fa, 0, "MachO: File not opened."); + ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "MachO: File not opened."); return fa->get_length(); } uint64_t MachO::get_signature_offset() { - ERR_FAIL_COND_V_MSG(!fa, 0, "MachO: File not opened."); + ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "MachO: File not opened."); ERR_FAIL_COND_V_MSG(signature_offset == 0, 0, "MachO: No signature load command."); fa->seek(signature_offset + 8); @@ -274,7 +274,7 @@ uint64_t MachO::get_signature_offset() { } uint64_t MachO::get_code_limit() { - ERR_FAIL_COND_V_MSG(!fa, 0, "MachO: File not opened."); + ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "MachO: File not opened."); if (signature_offset == 0) { return fa->get_length() + PAD(fa->get_length(), 16); @@ -284,7 +284,7 @@ uint64_t MachO::get_code_limit() { } uint64_t MachO::get_signature_size() { - ERR_FAIL_COND_V_MSG(!fa, 0, "MachO: File not opened."); + ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "MachO: File not opened."); ERR_FAIL_COND_V_MSG(signature_offset == 0, 0, "MachO: No signature load command."); fa->seek(signature_offset + 12); @@ -296,7 +296,7 @@ uint64_t MachO::get_signature_size() { } bool MachO::is_signed() { - ERR_FAIL_COND_V_MSG(!fa, false, "MachO: File not opened."); + ERR_FAIL_COND_V_MSG(fa.is_null(), false, "MachO: File not opened."); if (signature_offset == 0) { return false; } @@ -325,7 +325,7 @@ bool MachO::is_signed() { } PackedByteArray MachO::get_cdhash_sha1() { - ERR_FAIL_COND_V_MSG(!fa, PackedByteArray(), "MachO: File not opened."); + ERR_FAIL_COND_V_MSG(fa.is_null(), PackedByteArray(), "MachO: File not opened."); if (signature_offset == 0) { return PackedByteArray(); } @@ -372,7 +372,7 @@ PackedByteArray MachO::get_cdhash_sha1() { } PackedByteArray MachO::get_cdhash_sha256() { - ERR_FAIL_COND_V_MSG(!fa, PackedByteArray(), "MachO: File not opened."); + ERR_FAIL_COND_V_MSG(fa.is_null(), PackedByteArray(), "MachO: File not opened."); if (signature_offset == 0) { return PackedByteArray(); } @@ -419,7 +419,7 @@ PackedByteArray MachO::get_cdhash_sha256() { } PackedByteArray MachO::get_requirements() { - ERR_FAIL_COND_V_MSG(!fa, PackedByteArray(), "MachO: File not opened."); + ERR_FAIL_COND_V_MSG(fa.is_null(), PackedByteArray(), "MachO: File not opened."); if (signature_offset == 0) { return PackedByteArray(); } @@ -451,16 +451,16 @@ PackedByteArray MachO::get_requirements() { return PackedByteArray(); } -const FileAccess *MachO::get_file() const { +const Ref<FileAccess> MachO::get_file() const { return fa; } -FileAccess *MachO::get_file() { +Ref<FileAccess> MachO::get_file() { return fa; } bool MachO::set_signature_size(uint64_t p_size) { - ERR_FAIL_COND_V_MSG(!fa, false, "MachO: File not opened."); + ERR_FAIL_COND_V_MSG(fa.is_null(), false, "MachO: File not opened."); // Ensure signature load command exists. ERR_FAIL_COND_V_MSG(link_edit_offset == 0, false, "MachO: No __LINKEDIT segment found."); @@ -545,12 +545,4 @@ bool MachO::set_signature_size(uint64_t p_size) { return true; } -MachO::~MachO() { - if (fa) { - fa->close(); - memdelete(fa); - fa = nullptr; - } -} - #endif // MODULE_REGEX_ENABLED diff --git a/platform/osx/export/macho.h b/platform/osx/export/macho.h index e09906898b..6cfc3c44f5 100644 --- a/platform/osx/export/macho.h +++ b/platform/osx/export/macho.h @@ -161,7 +161,7 @@ class MachO : public RefCounted { uint32_t reserved3; }; - FileAccess *fa = nullptr; + Ref<FileAccess> fa; bool swap = false; uint64_t lc_limit = 0; @@ -203,13 +203,11 @@ public: PackedByteArray get_requirements(); - const FileAccess *get_file() const; - FileAccess *get_file(); + const Ref<FileAccess> get_file() const; + Ref<FileAccess> get_file(); uint64_t get_signature_size(); bool set_signature_size(uint64_t p_size); - - ~MachO(); }; #endif // MODULE_REGEX_ENABLED diff --git a/platform/osx/export/plist.cpp b/platform/osx/export/plist.cpp index 553b864180..36de9dd34b 100644 --- a/platform/osx/export/plist.cpp +++ b/platform/osx/export/plist.cpp @@ -140,10 +140,11 @@ size_t PListNode::get_asn1_size(uint8_t p_len_octets) const { } break; case PList::PLNodeType::PL_NODE_TYPE_DICT: { size_t size = 0; - for (const Map<String, Ref<PListNode>>::Element *it = data_dict.front(); it; it = it->next()) { + + for (const KeyValue<String, Ref<PListNode>> &E : data_dict) { size += 1 + _asn1_size_len(p_len_octets); // Sequence. - size += 1 + _asn1_size_len(p_len_octets) + it->key().utf8().length(); //Key. - size += 1 + _asn1_size_len(p_len_octets) + it->value()->get_asn1_size(p_len_octets); // Value. + size += 1 + _asn1_size_len(p_len_octets) + E.key.utf8().length(); //Key. + size += 1 + _asn1_size_len(p_len_octets) + E.value->get_asn1_size(p_len_octets); // Value. } return size; } break; @@ -225,13 +226,13 @@ bool PListNode::store_asn1(PackedByteArray &p_stream, uint8_t p_len_octets) cons case PList::PLNodeType::PL_NODE_TYPE_DICT: { p_stream.push_back(0x31); // Set. store_asn1_size(p_stream, p_len_octets); - for (const Map<String, Ref<PListNode>>::Element *it = data_dict.front(); it; it = it->next()) { - CharString cs = it->key().utf8(); + for (const KeyValue<String, Ref<PListNode>> &E : data_dict) { + CharString cs = E.key.utf8(); uint32_t size = cs.length(); // Sequence. p_stream.push_back(0x30); - uint32_t seq_size = 2 * (1 + _asn1_size_len(p_len_octets)) + size + it->value()->get_asn1_size(p_len_octets); + uint32_t seq_size = 2 * (1 + _asn1_size_len(p_len_octets)) + size + E.value->get_asn1_size(p_len_octets); if (p_len_octets > 1) { p_stream.push_back(0x80 + p_len_octets); } @@ -252,7 +253,7 @@ bool PListNode::store_asn1(PackedByteArray &p_stream, uint8_t p_len_octets) cons p_stream.push_back(cs[i]); } // Value. - valid = valid && it->value()->store_asn1(p_stream, p_len_octets); + valid = valid && E.value->store_asn1(p_stream, p_len_octets); } } break; } @@ -317,12 +318,12 @@ void PListNode::store_text(String &p_stream, uint8_t p_indent) const { case PList::PLNodeType::PL_NODE_TYPE_DICT: { p_stream += String("\t").repeat(p_indent); p_stream += "<dict>\n"; - for (const Map<String, Ref<PListNode>>::Element *it = data_dict.front(); it; it = it->next()) { + for (const KeyValue<String, Ref<PListNode>> &E : data_dict) { p_stream += String("\t").repeat(p_indent + 1); p_stream += "<key>"; - p_stream += it->key(); + p_stream += E.key; p_stream += "</key>\n"; - it->value()->store_text(p_stream, p_indent + 1); + E.value->store_text(p_stream, p_indent + 1); } p_stream += String("\t").repeat(p_indent); p_stream += "</dict>\n"; @@ -343,8 +344,8 @@ PList::PList(const String &p_string) { bool PList::load_file(const String &p_filename) { root = Ref<PListNode>(); - FileAccessRef fb = FileAccess::open(p_filename, FileAccess::READ); - if (!fb) { + Ref<FileAccess> fb = FileAccess::open(p_filename, FileAccess::READ); + if (fb.is_null()) { return false; } @@ -398,7 +399,6 @@ bool PList::load_string(const String &p_string) { } if (token == "/plist") { - in_plist = false; done_plist = true; break; } diff --git a/platform/osx/export/plist.h b/platform/osx/export/plist.h index fb4aaaa935..ba9eaec196 100644 --- a/platform/osx/export/plist.h +++ b/platform/osx/export/plist.h @@ -83,7 +83,7 @@ public: CharString data_string; Vector<Ref<PListNode>> data_array; - Map<String, Ref<PListNode>> data_dict; + HashMap<String, Ref<PListNode>> data_dict; union { int32_t data_int; bool data_bool; diff --git a/platform/osx/gl_manager_osx_legacy.h b/platform/osx/gl_manager_osx_legacy.h index b5a1b9dd98..2d4913a7a6 100644 --- a/platform/osx/gl_manager_osx_legacy.h +++ b/platform/osx/gl_manager_osx_legacy.h @@ -38,9 +38,9 @@ #include "core/templates/local_vector.h" #include "servers/display_server.h" -#include <AppKit/AppKit.h> -#include <ApplicationServices/ApplicationServices.h> -#include <CoreVideo/CoreVideo.h> +#import <AppKit/AppKit.h> +#import <ApplicationServices/ApplicationServices.h> +#import <CoreVideo/CoreVideo.h> class GLManager_OSX { public: @@ -57,7 +57,7 @@ private: NSOpenGLContext *context = nullptr; }; - Map<DisplayServer::WindowID, GLWindow> windows; + RBMap<DisplayServer::WindowID, GLWindow> windows; NSOpenGLContext *shared_context = nullptr; DisplayServer::WindowID current_window = DisplayServer::INVALID_WINDOW_ID; diff --git a/platform/osx/gl_manager_osx_legacy.mm b/platform/osx/gl_manager_osx_legacy.mm index fbe64e32a3..c769d7f5c5 100644 --- a/platform/osx/gl_manager_osx_legacy.mm +++ b/platform/osx/gl_manager_osx_legacy.mm @@ -167,8 +167,8 @@ void GLManager_OSX::make_current() { } void GLManager_OSX::swap_buffers() { - for (Map<DisplayServer::WindowID, GLWindow>::Element *E = windows.front(); E; E = E->next()) { - [E->get().context flushBuffer]; + for (const KeyValue<DisplayServer::WindowID, GLWindow> &E : windows) { + [E.value.context flushBuffer]; } } diff --git a/platform/osx/godot_application.mm b/platform/osx/godot_application.mm index 00a58700e8..13313a025a 100644 --- a/platform/osx/godot_application.mm +++ b/platform/osx/godot_application.mm @@ -37,6 +37,11 @@ - (void)sendEvent:(NSEvent *)event { DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); if (ds) { + if ([event type] == NSEventTypeLeftMouseDown || [event type] == NSEventTypeRightMouseDown || [event type] == NSEventTypeOtherMouseDown) { + if (ds->mouse_process_popups()) { + return; + } + } ds->send_event(event); } diff --git a/platform/osx/godot_main_osx.mm b/platform/osx/godot_main_osx.mm index f3db363151..053a7f4a1d 100644 --- a/platform/osx/godot_main_osx.mm +++ b/platform/osx/godot_main_osx.mm @@ -35,12 +35,22 @@ #include <string.h> #include <unistd.h> +#if defined(SANITIZERS_ENABLED) +#include <sys/resource.h> +#endif + 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 +#if defined(SANITIZERS_ENABLED) + // Note: Set stack size to be at least 30 MB (vs 8 MB default) to avoid overflow, address sanitizer can increase stack usage up to 3 times. + struct rlimit stack_lim = { 0x1E00000, 0x1E00000 }; + setrlimit(RLIMIT_STACK, &stack_lim); +#endif + int first_arg = 1; const char *dbg_arg = "-NSDocumentRevisionsDebugMode"; printf("arguments\n"); diff --git a/platform/osx/godot_window_delegate.mm b/platform/osx/godot_window_delegate.mm index 9f49a6a4e9..521127f01b 100644 --- a/platform/osx/godot_window_delegate.mm +++ b/platform/osx/godot_window_delegate.mm @@ -58,7 +58,7 @@ DisplayServerOSX::WindowData &wd = ds->get_window(window_id); while (wd.transient_children.size()) { - ds->window_set_transient(wd.transient_children.front()->get(), DisplayServerOSX::INVALID_WINDOW_ID); + ds->window_set_transient(*wd.transient_children.begin(), DisplayServerOSX::INVALID_WINDOW_ID); } if (wd.transient_parent != DisplayServerOSX::INVALID_WINDOW_ID) { diff --git a/platform/osx/joypad_osx.cpp b/platform/osx/joypad_osx.cpp index 7d31ede61d..be9567e17c 100644 --- a/platform/osx/joypad_osx.cpp +++ b/platform/osx/joypad_osx.cpp @@ -195,7 +195,7 @@ void joypad::add_hid_element(IOHIDElementRef p_element) { } static void hid_element_added(const void *p_value, void *p_parameter) { - joypad *joy = (joypad *)p_parameter; + joypad *joy = static_cast<joypad *>(p_parameter); joy->add_hid_element((IOHIDElementRef)p_value); } @@ -540,10 +540,10 @@ static CFDictionaryRef create_match_dictionary(const UInt32 page, const UInt32 u CFDictionaryRef retval = nullptr; CFNumberRef pageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page); CFNumberRef usageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage); - const void *keys[2] = { (void *)CFSTR(kIOHIDDeviceUsagePageKey), (void *)CFSTR(kIOHIDDeviceUsageKey) }; - const void *vals[2] = { (void *)pageNumRef, (void *)usageNumRef }; if (pageNumRef && usageNumRef) { + const void *keys[2] = { (void *)CFSTR(kIOHIDDeviceUsagePageKey), (void *)CFSTR(kIOHIDDeviceUsageKey) }; + const void *vals[2] = { (void *)pageNumRef, (void *)usageNumRef }; retval = CFDictionaryCreate(kCFAllocatorDefault, keys, vals, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); } diff --git a/platform/osx/joypad_osx.h b/platform/osx/joypad_osx.h index 4ca7fb1698..3f89048ce6 100644 --- a/platform/osx/joypad_osx.h +++ b/platform/osx/joypad_osx.h @@ -32,13 +32,13 @@ #define JOYPADOSX_H #ifdef MACOS_10_0_4 -#include <IOKit/hidsystem/IOHIDUsageTables.h> +#import <IOKit/hidsystem/IOHIDUsageTables.h> #else -#include <Kernel/IOKit/hidsystem/IOHIDUsageTables.h> +#import <Kernel/IOKit/hidsystem/IOHIDUsageTables.h> #endif -#include <ForceFeedback/ForceFeedback.h> -#include <ForceFeedback/ForceFeedbackConstants.h> -#include <IOKit/hid/IOHIDLib.h> +#import <ForceFeedback/ForceFeedback.h> +#import <ForceFeedback/ForceFeedbackConstants.h> +#import <IOKit/hid/IOHIDLib.h> #include "core/input/input.h" @@ -94,7 +94,7 @@ class JoypadOSX { }; private: - Input *input; + Input *input = nullptr; IOHIDManagerRef hid_manager; Vector<joypad> device_list; diff --git a/platform/osx/key_mapping_osx.mm b/platform/osx/key_mapping_osx.mm index fde9206824..bfec45de58 100644 --- a/platform/osx/key_mapping_osx.mm +++ b/platform/osx/key_mapping_osx.mm @@ -30,8 +30,8 @@ #include "key_mapping_osx.h" -#include <Carbon/Carbon.h> -#include <Cocoa/Cocoa.h> +#import <Carbon/Carbon.h> +#import <Cocoa/Cocoa.h> bool KeyMappingOSX::is_numpad_key(unsigned int key) { static const unsigned int table[] = { diff --git a/platform/osx/os_osx.h b/platform/osx/os_osx.h index 53c5c8bd90..e4ec411c96 100644 --- a/platform/osx/os_osx.h +++ b/platform/osx/os_osx.h @@ -80,7 +80,7 @@ public: virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override; - virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false) override; + virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = nullptr) override; virtual MainLoop *get_main_loop() const override; diff --git a/platform/osx/os_osx.mm b/platform/osx/os_osx.mm index 7e0cf9f9cc..a8fa56e34b 100644 --- a/platform/osx/os_osx.mm +++ b/platform/osx/os_osx.mm @@ -47,7 +47,7 @@ _FORCE_INLINE_ String OS_OSX::get_framework_executable(const String &p_path) { // Append framework executable name, or return as is if p_path is not a framework. - DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); if (da->dir_exists(p_path) && da->file_exists(p_path.plus_file(p_path.get_file().get_basename()))) { return p_path.plus_file(p_path.get_file().get_basename()); } else { @@ -150,7 +150,7 @@ void OS_OSX::alert(const String &p_alert, const String &p_title) { } } -Error OS_OSX::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) { +Error OS_OSX::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path, String *r_resolved_path) { String path = get_framework_executable(p_path); if (!FileAccess::exists(path)) { @@ -165,6 +165,11 @@ Error OS_OSX::open_dynamic_library(const String p_path, void *&p_library_handle, p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW); ERR_FAIL_COND_V_MSG(!p_library_handle, ERR_CANT_OPEN, "Can't open dynamic library: " + p_path + ", error: " + dlerror() + "."); + + if (r_resolved_path != nullptr) { + *r_resolved_path = path; + } + return OK; } @@ -313,21 +318,22 @@ String OS_OSX::get_executable_path() const { } Error OS_OSX::create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id, bool p_open_console) { - if (@available(macOS 10.15, *)) { - // Use NSWorkspace if path is an .app bundle. - NSURL *url = [NSURL fileURLWithPath:@(p_path.utf8().get_data())]; - NSBundle *bundle = [NSBundle bundleWithURL:url]; - if (bundle) { - NSMutableArray *arguments = [[NSMutableArray alloc] init]; - for (const List<String>::Element *E = p_arguments.front(); E; E = E->next()) { - [arguments addObject:[NSString stringWithUTF8String:E->get().utf8().get_data()]]; - } + // Use NSWorkspace if path is an .app bundle. + NSURL *url = [NSURL fileURLWithPath:@(p_path.utf8().get_data())]; + NSBundle *bundle = [NSBundle bundleWithURL:url]; + if (bundle) { + NSMutableArray *arguments = [[NSMutableArray alloc] init]; + for (const String &arg : p_arguments) { + [arguments addObject:[NSString stringWithUTF8String:arg.utf8().get_data()]]; + } + if (@available(macOS 10.15, *)) { NSWorkspaceOpenConfiguration *configuration = [[NSWorkspaceOpenConfiguration alloc] init]; [configuration setArguments:arguments]; [configuration setCreatesNewApplicationInstance:YES]; __block dispatch_semaphore_t lock = dispatch_semaphore_create(0); __block Error err = ERR_TIMEOUT; __block pid_t pid = 0; + [[NSWorkspace sharedWorkspace] openApplicationAtURL:url configuration:configuration completionHandler:^(NSRunningApplication *app, NSError *error) { @@ -350,7 +356,19 @@ Error OS_OSX::create_process(const String &p_path, const List<String> &p_argumen return err; } else { - return OS_Unix::create_process(p_path, p_arguments, r_child_id, p_open_console); + Error err = ERR_TIMEOUT; + NSError *error = nullptr; + NSRunningApplication *app = [[NSWorkspace sharedWorkspace] launchApplicationAtURL:url options:NSWorkspaceLaunchNewInstance configuration:[NSDictionary dictionaryWithObject:arguments forKey:NSWorkspaceLaunchConfigurationArguments] error:&error]; + if (error) { + err = ERR_CANT_FORK; + NSLog(@"Failed to execute: %@", error.localizedDescription); + } else { + if (r_child_id) { + *r_child_id = (ProcessID)[app processIdentifier]; + } + err = OK; + } + return err; } } else { return OS_Unix::create_process(p_path, p_arguments, r_child_id, p_open_console); diff --git a/platform/osx/tts_osx.h b/platform/osx/tts_osx.h new file mode 100644 index 0000000000..449418e48f --- /dev/null +++ b/platform/osx/tts_osx.h @@ -0,0 +1,71 @@ +/*************************************************************************/ +/* tts_osx.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef TTS_OSX_H +#define TTS_OSX_H + +#include "core/string/ustring.h" +#include "core/templates/list.h" +#include "core/templates/rb_map.h" +#include "core/variant/array.h" +#include "servers/display_server.h" + +#import <AppKit/AppKit.h> + +#if __has_include(<AVFAudio/AVSpeechSynthesis.h>) +#import <AVFAudio/AVSpeechSynthesis.h> +#else +#import <AVFoundation/AVFoundation.h> +#endif + +@interface TTS_OSX : NSObject <AVSpeechSynthesizerDelegate> { + // AVSpeechSynthesizer + bool speaking; + HashMap<id, int> ids; + + // NSSpeechSynthesizer + bool paused; + bool have_utterance; + int last_utterance; + + id synth; // NSSpeechSynthesizer or AVSpeechSynthesizer + List<DisplayServer::TTSUtterance> queue; +} + +- (void)pauseSpeaking; +- (void)resumeSpeaking; +- (void)stopSpeaking; +- (bool)isSpeaking; +- (bool)isPaused; +- (void)speak:(const String &)text voice:(const String &)voice volume:(int)volume pitch:(float)pitch rate:(float)rate utterance_id:(int)utterance_id interrupt:(bool)interrupt; +- (Array)getVoices; +@end + +#endif // TTS_OSX_H diff --git a/platform/osx/tts_osx.mm b/platform/osx/tts_osx.mm new file mode 100644 index 0000000000..e6a5236cd9 --- /dev/null +++ b/platform/osx/tts_osx.mm @@ -0,0 +1,266 @@ +/*************************************************************************/ +/* tts_osx.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "tts_osx.h" + +@implementation TTS_OSX + +- (id)init { + self = [super init]; + self->speaking = false; + self->have_utterance = false; + self->last_utterance = -1; + self->paused = false; + if (@available(macOS 10.14, *)) { + self->synth = [[AVSpeechSynthesizer alloc] init]; + [self->synth setDelegate:self]; + print_verbose("Text-to-Speech: AVSpeechSynthesizer initialized."); + } else { + self->synth = [[NSSpeechSynthesizer alloc] init]; + [self->synth setDelegate:self]; + print_verbose("Text-to-Speech: NSSpeechSynthesizer initialized."); + } + return self; +} + +// AVSpeechSynthesizer callback (macOS 10.14+) + +- (void)speechSynthesizer:(AVSpeechSynthesizer *)av_synth willSpeakRangeOfSpeechString:(NSRange)characterRange utterance:(AVSpeechUtterance *)utterance API_AVAILABLE(macosx(10.14)) { + NSString *string = [utterance speechString]; + + // Convert from UTF-16 to UTF-32 position. + int pos = 0; + for (NSUInteger i = 0; i < MIN(characterRange.location, string.length); i++) { + unichar c = [string characterAtIndex:i]; + if ((c & 0xfffffc00) == 0xd800) { + i++; + } + pos++; + } + + DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_BOUNDARY, ids[utterance], pos); +} + +// AVSpeechSynthesizer callback (macOS 10.14+) + +- (void)speechSynthesizer:(AVSpeechSynthesizer *)av_synth didCancelSpeechUtterance:(AVSpeechUtterance *)utterance API_AVAILABLE(macosx(10.14)) { + DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, ids[utterance]); + ids.erase(utterance); + speaking = false; + [self update]; +} + +// AVSpeechSynthesizer callback (macOS 10.14+) + +- (void)speechSynthesizer:(AVSpeechSynthesizer *)av_synth didFinishSpeechUtterance:(AVSpeechUtterance *)utterance API_AVAILABLE(macosx(10.14)) { + DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_ENDED, ids[utterance]); + ids.erase(utterance); + speaking = false; + [self update]; +} + +// NSSpeechSynthesizer callback (macOS 10.4+) + +- (void)speechSynthesizer:(NSSpeechSynthesizer *)ns_synth willSpeakWord:(NSRange)characterRange ofString:(NSString *)string { + if (!paused && have_utterance) { + // Convert from UTF-16 to UTF-32 position. + int pos = 0; + for (NSUInteger i = 0; i < MIN(characterRange.location, string.length); i++) { + unichar c = [string characterAtIndex:i]; + if ((c & 0xfffffc00) == 0xd800) { + i++; + } + pos++; + } + + DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_BOUNDARY, last_utterance, pos); + } +} + +- (void)speechSynthesizer:(NSSpeechSynthesizer *)ns_synth didFinishSpeaking:(BOOL)success { + if (!paused && have_utterance) { + if (success) { + DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_ENDED, last_utterance); + } else { + DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, last_utterance); + } + have_utterance = false; + } + speaking = false; + [self update]; +} + +- (void)update { + if (!speaking && queue.size() > 0) { + DisplayServer::TTSUtterance &message = queue.front()->get(); + + if (@available(macOS 10.14, *)) { + AVSpeechSynthesizer *av_synth = synth; + AVSpeechUtterance *new_utterance = [[AVSpeechUtterance alloc] initWithString:[NSString stringWithUTF8String:message.text.utf8().get_data()]]; + [new_utterance setVoice:[AVSpeechSynthesisVoice voiceWithIdentifier:[NSString stringWithUTF8String:message.voice.utf8().get_data()]]]; + if (message.rate > 1.f) { + [new_utterance setRate:Math::range_lerp(message.rate, 1.f, 10.f, AVSpeechUtteranceDefaultSpeechRate, AVSpeechUtteranceMaximumSpeechRate)]; + } else if (message.rate < 1.f) { + [new_utterance setRate:Math::range_lerp(message.rate, 0.1f, 1.f, AVSpeechUtteranceMinimumSpeechRate, AVSpeechUtteranceDefaultSpeechRate)]; + } + [new_utterance setPitchMultiplier:message.pitch]; + [new_utterance setVolume:(Math::range_lerp(message.volume, 0.f, 100.f, 0.f, 1.f))]; + + ids[new_utterance] = message.id; + [av_synth speakUtterance:new_utterance]; + } else { + NSSpeechSynthesizer *ns_synth = synth; + [ns_synth setObject:nil forProperty:NSSpeechResetProperty error:nil]; + [ns_synth setVoice:[NSString stringWithUTF8String:message.voice.utf8().get_data()]]; + int base_pitch = [[ns_synth objectForProperty:NSSpeechPitchBaseProperty error:nil] intValue]; + [ns_synth setObject:[NSNumber numberWithInt:(base_pitch * (message.pitch / 2.f + 0.5f))] forProperty:NSSpeechPitchBaseProperty error:nullptr]; + [ns_synth setVolume:(Math::range_lerp(message.volume, 0.f, 100.f, 0.f, 1.f))]; + [ns_synth setRate:(message.rate * 200)]; + + last_utterance = message.id; + have_utterance = true; + [ns_synth startSpeakingString:[NSString stringWithUTF8String:message.text.utf8().get_data()]]; + } + queue.pop_front(); + + DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_STARTED, message.id); + speaking = true; + } +} + +- (void)pauseSpeaking { + if (@available(macOS 10.14, *)) { + AVSpeechSynthesizer *av_synth = synth; + [av_synth pauseSpeakingAtBoundary:AVSpeechBoundaryImmediate]; + } else { + NSSpeechSynthesizer *ns_synth = synth; + [ns_synth pauseSpeakingAtBoundary:NSSpeechImmediateBoundary]; + } + paused = true; +} + +- (void)resumeSpeaking { + if (@available(macOS 10.14, *)) { + AVSpeechSynthesizer *av_synth = synth; + [av_synth continueSpeaking]; + } else { + NSSpeechSynthesizer *ns_synth = synth; + [ns_synth continueSpeaking]; + } + paused = false; +} + +- (void)stopSpeaking { + for (DisplayServer::TTSUtterance &message : queue) { + DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, message.id); + } + queue.clear(); + if (@available(macOS 10.14, *)) { + AVSpeechSynthesizer *av_synth = synth; + [av_synth stopSpeakingAtBoundary:AVSpeechBoundaryImmediate]; + } else { + NSSpeechSynthesizer *ns_synth = synth; + if (have_utterance) { + DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, last_utterance); + } + [ns_synth stopSpeaking]; + } + have_utterance = false; + speaking = false; + paused = false; +} + +- (bool)isSpeaking { + return speaking || (queue.size() > 0); +} + +- (bool)isPaused { + if (@available(macOS 10.14, *)) { + AVSpeechSynthesizer *av_synth = synth; + return [av_synth isPaused]; + } else { + return paused; + } +} + +- (void)speak:(const String &)text voice:(const String &)voice volume:(int)volume pitch:(float)pitch rate:(float)rate utterance_id:(int)utterance_id interrupt:(bool)interrupt { + if (interrupt) { + [self stopSpeaking]; + } + + if (text.is_empty()) { + DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, utterance_id); + return; + } + + DisplayServer::TTSUtterance message; + message.text = text; + message.voice = voice; + message.volume = CLAMP(volume, 0, 100); + message.pitch = CLAMP(pitch, 0.f, 2.f); + message.rate = CLAMP(rate, 0.1f, 10.f); + message.id = utterance_id; + queue.push_back(message); + + if ([self isPaused]) { + [self resumeSpeaking]; + } else { + [self update]; + } +} + +- (Array)getVoices { + Array list; + if (@available(macOS 10.14, *)) { + for (AVSpeechSynthesisVoice *voice in [AVSpeechSynthesisVoice speechVoices]) { + NSString *voiceIdentifierString = [voice identifier]; + NSString *voiceLocaleIdentifier = [voice language]; + NSString *voiceName = [voice name]; + Dictionary voice_d; + voice_d["name"] = String::utf8([voiceName UTF8String]); + voice_d["id"] = String::utf8([voiceIdentifierString UTF8String]); + voice_d["language"] = String::utf8([voiceLocaleIdentifier UTF8String]); + list.push_back(voice_d); + } + } else { + for (NSString *voiceIdentifierString in [NSSpeechSynthesizer availableVoices]) { + NSString *voiceLocaleIdentifier = [[NSSpeechSynthesizer attributesForVoice:voiceIdentifierString] objectForKey:NSVoiceLocaleIdentifier]; + NSString *voiceName = [[NSSpeechSynthesizer attributesForVoice:voiceIdentifierString] objectForKey:NSVoiceName]; + Dictionary voice_d; + voice_d["name"] = String([voiceName UTF8String]); + voice_d["id"] = String([voiceIdentifierString UTF8String]); + voice_d["language"] = String([voiceLocaleIdentifier UTF8String]); + list.push_back(voice_d); + } + } + return list; +} + +@end diff --git a/platform/osx/vulkan_context_osx.h b/platform/osx/vulkan_context_osx.h index b78b4eb141..ade0f4a4c9 100644 --- a/platform/osx/vulkan_context_osx.h +++ b/platform/osx/vulkan_context_osx.h @@ -32,7 +32,7 @@ #define VULKAN_DEVICE_OSX_H #include "drivers/vulkan/vulkan_context.h" -#include <AppKit/AppKit.h> +#import <AppKit/AppKit.h> class VulkanContextOSX : public VulkanContext { virtual const char *_get_platform_surface_extension() const; |