summaryrefslogtreecommitdiff
path: root/platform/osx
diff options
context:
space:
mode:
Diffstat (limited to 'platform/osx')
-rw-r--r--platform/osx/SCsub1
-rw-r--r--platform/osx/crash_handler_osx.mm22
-rw-r--r--platform/osx/detect.py1
-rw-r--r--platform/osx/dir_access_osx.mm4
-rw-r--r--platform/osx/display_server_osx.h71
-rw-r--r--platform/osx/display_server_osx.mm707
-rw-r--r--platform/osx/export/codesign.cpp69
-rw-r--r--platform/osx/export/codesign.h14
-rw-r--r--platform/osx/export/export_plugin.cpp139
-rw-r--r--platform/osx/export/export_plugin.h9
-rw-r--r--platform/osx/export/lipo.cpp31
-rw-r--r--platform/osx/export/lipo.h2
-rw-r--r--platform/osx/export/macho.cpp48
-rw-r--r--platform/osx/export/macho.h8
-rw-r--r--platform/osx/export/plist.cpp26
-rw-r--r--platform/osx/export/plist.h2
-rw-r--r--platform/osx/gl_manager_osx_legacy.h8
-rw-r--r--platform/osx/gl_manager_osx_legacy.mm4
-rw-r--r--platform/osx/godot_main_osx.mm10
-rw-r--r--platform/osx/godot_menu_item.h11
-rw-r--r--platform/osx/joypad_osx.cpp6
-rw-r--r--platform/osx/joypad_osx.h12
-rw-r--r--platform/osx/key_mapping_osx.mm4
-rw-r--r--platform/osx/os_osx.h2
-rw-r--r--platform/osx/os_osx.mm42
-rw-r--r--platform/osx/tts_osx.h71
-rw-r--r--platform/osx/tts_osx.mm266
-rw-r--r--platform/osx/vulkan_context_osx.h2
28 files changed, 1278 insertions, 314 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 cc9ac162ea..76df8b400a 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;
+ RBSet<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);
@@ -186,6 +189,7 @@ private:
void _process_key_events();
void _update_keyboard_layouts();
static void _keyboard_layout_changed(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef user_info);
+ NSImage *_convert_to_nsimg(Ref<Image> &p_image) const;
static NSCursor *_cursor_from_selector(SEL p_selector, SEL p_fallback = nil);
@@ -217,30 +221,61 @@ public:
virtual bool has_feature(Feature p_feature) const override;
virtual String get_name() const override;
- virtual void global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag = Variant()) override;
- virtual void global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag = Variant()) override;
- virtual void global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu) override;
- virtual void global_menu_add_separator(const String &p_menu_root) override;
+ virtual void global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+ virtual void global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+ virtual void global_menu_add_icon_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+ virtual void global_menu_add_icon_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+ virtual void global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+ virtual void global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+ virtual void global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+ virtual void global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index = -1) override;
+ virtual void global_menu_add_separator(const String &p_menu_root, int p_index = -1) override;
+
+ virtual int global_menu_get_item_index_from_text(const String &p_menu_root, const String &p_text) const override;
+ virtual int global_menu_get_item_index_from_tag(const String &p_menu_root, const Variant &p_tag) const override;
virtual bool global_menu_is_item_checked(const String &p_menu_root, int p_idx) const override;
virtual bool global_menu_is_item_checkable(const String &p_menu_root, int p_idx) const override;
- virtual Callable global_menu_get_item_callback(const String &p_menu_root, int p_idx) override;
- virtual Variant global_menu_get_item_tag(const String &p_menu_root, int p_idx) override;
- virtual String global_menu_get_item_text(const String &p_menu_root, int p_idx) override;
- virtual String global_menu_get_item_submenu(const String &p_menu_root, int p_idx) override;
+ virtual bool global_menu_is_item_radio_checkable(const String &p_menu_root, int p_idx) const override;
+ virtual Callable global_menu_get_item_callback(const String &p_menu_root, int p_idx) const override;
+ virtual Variant global_menu_get_item_tag(const String &p_menu_root, int p_idx) const override;
+ virtual String global_menu_get_item_text(const String &p_menu_root, int p_idx) const override;
+ virtual String global_menu_get_item_submenu(const String &p_menu_root, int p_idx) const override;
+ virtual Key global_menu_get_item_accelerator(const String &p_menu_root, int p_idx) const override;
+ virtual bool global_menu_is_item_disabled(const String &p_menu_root, int p_idx) const override;
+ virtual String global_menu_get_item_tooltip(const String &p_menu_root, int p_idx) const override;
+ virtual int global_menu_get_item_state(const String &p_menu_root, int p_idx) const override;
+ virtual int global_menu_get_item_max_states(const String &p_menu_root, int p_idx) const override;
+ virtual Ref<Texture2D> global_menu_get_item_icon(const String &p_menu_root, int p_idx) const override;
virtual void global_menu_set_item_checked(const String &p_menu_root, int p_idx, bool p_checked) override;
virtual void global_menu_set_item_checkable(const String &p_menu_root, int p_idx, bool p_checkable) override;
+ virtual void global_menu_set_item_radio_checkable(const String &p_menu_root, int p_idx, bool p_checkable) override;
virtual void global_menu_set_item_callback(const String &p_menu_root, int p_idx, const Callable &p_callback) override;
virtual void global_menu_set_item_tag(const String &p_menu_root, int p_idx, const Variant &p_tag) override;
virtual void global_menu_set_item_text(const String &p_menu_root, int p_idx, const String &p_text) override;
virtual void global_menu_set_item_submenu(const String &p_menu_root, int p_idx, const String &p_submenu) override;
+ virtual void global_menu_set_item_accelerator(const String &p_menu_root, int p_idx, Key p_keycode) override;
+ virtual void global_menu_set_item_disabled(const String &p_menu_root, int p_idx, bool p_disabled) override;
+ virtual void global_menu_set_item_tooltip(const String &p_menu_root, int p_idx, const String &p_tooltip) override;
+ virtual void global_menu_set_item_state(const String &p_menu_root, int p_idx, int p_state) override;
+ virtual void global_menu_set_item_max_states(const String &p_menu_root, int p_idx, int p_max_states) override;
+ virtual void global_menu_set_item_icon(const String &p_menu_root, int p_idx, const Ref<Texture2D> &p_icon) override;
virtual int global_menu_get_item_count(const String &p_menu_root) const override;
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;
@@ -248,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;
@@ -338,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 89ca6e50ec..536751432b 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"
@@ -91,6 +93,7 @@ NSMenu *DisplayServerOSX::_get_menu_root(const String &p_menu_root) {
// Submenu.
if (!submenu.has(p_menu_root)) {
NSMenu *n_menu = [[NSMenu alloc] initWithTitle:[NSString stringWithUTF8String:p_menu_root.utf8().get_data()]];
+ [n_menu setAutoenablesItems:NO];
submenu[p_menu_root] = n_menu;
}
menu = submenu[p_menu_root];
@@ -143,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;
}
@@ -171,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;
}
@@ -206,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];
}
}
@@ -231,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];
@@ -246,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];
@@ -325,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())) {
@@ -472,6 +475,40 @@ void DisplayServerOSX::_keyboard_layout_changed(CFNotificationCenterRef center,
}
}
+NSImage *DisplayServerOSX::_convert_to_nsimg(Ref<Image> &p_image) const {
+ p_image->convert(Image::FORMAT_RGBA8);
+ NSBitmapImageRep *imgrep = [[NSBitmapImageRep alloc]
+ initWithBitmapDataPlanes:NULL
+ pixelsWide:p_image->get_width()
+ pixelsHigh:p_image->get_height()
+ bitsPerSample:8
+ samplesPerPixel:4
+ hasAlpha:YES
+ isPlanar:NO
+ colorSpaceName:NSDeviceRGBColorSpace
+ bytesPerRow:int(p_image->get_width()) * 4
+ bitsPerPixel:32];
+ ERR_FAIL_COND_V(imgrep == nil, nil);
+ uint8_t *pixels = [imgrep bitmapData];
+
+ int len = p_image->get_width() * p_image->get_height();
+ const uint8_t *r = p_image->get_data().ptr();
+
+ /* Premultiply the alpha channel */
+ for (int i = 0; i < len; i++) {
+ uint8_t alpha = r[i * 4 + 3];
+ pixels[i * 4 + 0] = (uint8_t)(((uint16_t)r[i * 4 + 0] * alpha) / 255);
+ pixels[i * 4 + 1] = (uint8_t)(((uint16_t)r[i * 4 + 1] * alpha) / 255);
+ pixels[i * 4 + 2] = (uint8_t)(((uint16_t)r[i * 4 + 2] * alpha) / 255);
+ pixels[i * 4 + 3] = alpha;
+ }
+
+ NSImage *nsimg = [[NSImage alloc] initWithSize:NSMakeSize(p_image->get_width(), p_image->get_height())];
+ ERR_FAIL_COND_V(nsimg == nil, nil);
+ [nsimg addRepresentation:imgrep];
+ return nsimg;
+}
+
NSCursor *DisplayServerOSX::_cursor_from_selector(SEL p_selector, SEL p_fallback) {
if ([NSCursor respondsToSelector:p_selector]) {
id object = [NSCursor performSelector:p_selector];
@@ -498,7 +535,14 @@ void DisplayServerOSX::menu_callback(id p_sender) {
GodotMenuItem *value = [p_sender representedObject];
if (value) {
- if (value->checkable) {
+ if (value->max_states > 0) {
+ value->state++;
+ if (value->state >= value->max_states) {
+ value->state = 0;
+ }
+ }
+
+ if (value->checkable_type == CHECKABLE_TYPE_CHECK_BOX) {
if ([p_sender state] == NSControlStateValueOff) {
[p_sender setState:NSControlStateValueOn];
} else {
@@ -660,6 +704,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: {
}
@@ -671,35 +716,195 @@ String DisplayServerOSX::get_name() const {
return "OSX";
}
-void DisplayServerOSX::global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag) {
+void DisplayServerOSX::global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
+ _THREAD_SAFE_METHOD_
+
+ NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ String keycode = KeyMappingOSX::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK);
+ NSMenuItem *menu_item;
+ if (p_index != -1) {
+ menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index];
+ } else {
+ menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]];
+ }
+ GodotMenuItem *obj = [[GodotMenuItem alloc] init];
+ obj->callback = p_callback;
+ obj->meta = p_tag;
+ obj->checkable_type = CHECKABLE_TYPE_NONE;
+ obj->max_states = 0;
+ obj->state = 0;
+ [menu_item setKeyEquivalentModifierMask:KeyMappingOSX::keycode_get_native_mask(p_accel)];
+ [menu_item setRepresentedObject:obj];
+ }
+}
+
+void DisplayServerOSX::global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
+ _THREAD_SAFE_METHOD_
+
+ NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ String keycode = KeyMappingOSX::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK);
+ NSMenuItem *menu_item;
+ if (p_index != -1) {
+ menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index];
+ } else {
+ menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]];
+ }
+ GodotMenuItem *obj = [[GodotMenuItem alloc] init];
+ obj->callback = p_callback;
+ obj->meta = p_tag;
+ obj->checkable_type = CHECKABLE_TYPE_CHECK_BOX;
+ obj->max_states = 0;
+ obj->state = 0;
+ [menu_item setKeyEquivalentModifierMask:KeyMappingOSX::keycode_get_native_mask(p_accel)];
+ [menu_item setRepresentedObject:obj];
+ }
+}
+
+void DisplayServerOSX::global_menu_add_icon_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
+ _THREAD_SAFE_METHOD_
+
+ NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ String keycode = KeyMappingOSX::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK);
+ NSMenuItem *menu_item;
+ if (p_index != -1) {
+ menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index];
+ } else {
+ menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]];
+ }
+ GodotMenuItem *obj = [[GodotMenuItem alloc] init];
+ obj->callback = p_callback;
+ obj->meta = p_tag;
+ obj->checkable_type = CHECKABLE_TYPE_NONE;
+ obj->max_states = 0;
+ obj->state = 0;
+ if (p_icon.is_valid()) {
+ obj->img = p_icon->get_image();
+ obj->img = obj->img->duplicate();
+ if (obj->img->is_compressed()) {
+ obj->img->decompress();
+ }
+ obj->img->resize(16, 16, Image::INTERPOLATE_LANCZOS);
+ [menu_item setImage:_convert_to_nsimg(obj->img)];
+ }
+ [menu_item setKeyEquivalentModifierMask:KeyMappingOSX::keycode_get_native_mask(p_accel)];
+ [menu_item setRepresentedObject:obj];
+ }
+}
+
+void DisplayServerOSX::global_menu_add_icon_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
+ _THREAD_SAFE_METHOD_
+
+ NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ String keycode = KeyMappingOSX::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK);
+ NSMenuItem *menu_item;
+ if (p_index != -1) {
+ menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index];
+ } else {
+ menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]];
+ }
+ GodotMenuItem *obj = [[GodotMenuItem alloc] init];
+ obj->callback = p_callback;
+ obj->meta = p_tag;
+ obj->checkable_type = CHECKABLE_TYPE_CHECK_BOX;
+ obj->max_states = 0;
+ obj->state = 0;
+ if (p_icon.is_valid()) {
+ obj->img = p_icon->get_image();
+ obj->img = obj->img->duplicate();
+ if (obj->img->is_compressed()) {
+ obj->img->decompress();
+ }
+ obj->img->resize(16, 16, Image::INTERPOLATE_LANCZOS);
+ [menu_item setImage:_convert_to_nsimg(obj->img)];
+ }
+ [menu_item setKeyEquivalentModifierMask:KeyMappingOSX::keycode_get_native_mask(p_accel)];
+ [menu_item setRepresentedObject:obj];
+ }
+}
+
+void DisplayServerOSX::global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
+ _THREAD_SAFE_METHOD_
+
+ NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ String keycode = KeyMappingOSX::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK);
+ NSMenuItem *menu_item;
+ if (p_index != -1) {
+ menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index];
+ } else {
+ menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]];
+ }
+ GodotMenuItem *obj = [[GodotMenuItem alloc] init];
+ obj->callback = p_callback;
+ obj->meta = p_tag;
+ obj->checkable_type = CHECKABLE_TYPE_RADIO_BUTTON;
+ obj->max_states = 0;
+ obj->state = 0;
+ [menu_item setKeyEquivalentModifierMask:KeyMappingOSX::keycode_get_native_mask(p_accel)];
+ [menu_item setRepresentedObject:obj];
+ }
+}
+
+void DisplayServerOSX::global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
_THREAD_SAFE_METHOD_
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
- NSMenuItem *menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:@""];
+ String keycode = KeyMappingOSX::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK);
+ NSMenuItem *menu_item;
+ if (p_index != -1) {
+ menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index];
+ } else {
+ menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]];
+ }
GodotMenuItem *obj = [[GodotMenuItem alloc] init];
obj->callback = p_callback;
obj->meta = p_tag;
- obj->checkable = false;
+ obj->checkable_type = CHECKABLE_TYPE_RADIO_BUTTON;
+ obj->max_states = 0;
+ obj->state = 0;
+ if (p_icon.is_valid()) {
+ obj->img = p_icon->get_image();
+ obj->img = obj->img->duplicate();
+ if (obj->img->is_compressed()) {
+ obj->img->decompress();
+ }
+ obj->img->resize(16, 16, Image::INTERPOLATE_LANCZOS);
+ [menu_item setImage:_convert_to_nsimg(obj->img)];
+ }
+ [menu_item setKeyEquivalentModifierMask:KeyMappingOSX::keycode_get_native_mask(p_accel)];
[menu_item setRepresentedObject:obj];
}
}
-void DisplayServerOSX::global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag) {
+void DisplayServerOSX::global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
_THREAD_SAFE_METHOD_
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
- NSMenuItem *menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:@""];
+ String keycode = KeyMappingOSX::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK);
+ NSMenuItem *menu_item;
+ if (p_index != -1) {
+ menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index];
+ } else {
+ menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]];
+ }
GodotMenuItem *obj = [[GodotMenuItem alloc] init];
obj->callback = p_callback;
obj->meta = p_tag;
- obj->checkable = true;
+ obj->checkable_type = CHECKABLE_TYPE_NONE;
+ obj->max_states = p_max_states;
+ obj->state = p_default_state;
+ [menu_item setKeyEquivalentModifierMask:KeyMappingOSX::keycode_get_native_mask(p_accel)];
[menu_item setRepresentedObject:obj];
}
}
-void DisplayServerOSX::global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu) {
+void DisplayServerOSX::global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index) {
_THREAD_SAFE_METHOD_
NSMenu *menu = _get_menu_root(p_menu_root);
@@ -713,20 +918,60 @@ void DisplayServerOSX::global_menu_add_submenu_item(const String &p_menu_root, c
ERR_PRINT("Can't set submenu to menu that is already a submenu of some other menu!");
return;
}
- NSMenuItem *menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:nil keyEquivalent:@""];
+ NSMenuItem *menu_item;
+ if (p_index != -1) {
+ menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:nil keyEquivalent:@"" atIndex:p_index];
+ } else {
+ menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:nil keyEquivalent:@""];
+ }
+ [sub_menu setTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()]];
[menu setSubmenu:sub_menu forItem:menu_item];
}
}
-void DisplayServerOSX::global_menu_add_separator(const String &p_menu_root) {
+void DisplayServerOSX::global_menu_add_separator(const String &p_menu_root, int p_index) {
_THREAD_SAFE_METHOD_
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
- [menu addItem:[NSMenuItem separatorItem]];
+ if (p_index != -1) {
+ [menu insertItem:[NSMenuItem separatorItem] atIndex:p_index];
+ } else {
+ [menu addItem:[NSMenuItem separatorItem]];
+ }
}
}
+int DisplayServerOSX::global_menu_get_item_index_from_text(const String &p_menu_root, const String &p_text) const {
+ _THREAD_SAFE_METHOD_
+
+ const NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ return [menu indexOfItemWithTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]];
+ }
+
+ return -1;
+}
+
+int DisplayServerOSX::global_menu_get_item_index_from_tag(const String &p_menu_root, const Variant &p_tag) const {
+ _THREAD_SAFE_METHOD_
+
+ const NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ for (NSInteger i = 0; i < [menu numberOfItems]; i++) {
+ const NSMenuItem *menu_item = [menu itemAtIndex:i];
+ if (menu_item) {
+ const GodotMenuItem *obj = [menu_item representedObject];
+ if (obj && obj->meta == p_tag) {
+ return i;
+ }
+ }
+ }
+ }
+
+ return -1;
+}
+
bool DisplayServerOSX::global_menu_is_item_checked(const String &p_menu_root, int p_idx) const {
_THREAD_SAFE_METHOD_
@@ -749,14 +994,30 @@ bool DisplayServerOSX::global_menu_is_item_checkable(const String &p_menu_root,
if (menu_item) {
GodotMenuItem *obj = [menu_item representedObject];
if (obj) {
- return obj->checkable;
+ return obj->checkable_type == CHECKABLE_TYPE_CHECK_BOX;
}
}
}
return false;
}
-Callable DisplayServerOSX::global_menu_get_item_callback(const String &p_menu_root, int p_idx) {
+bool DisplayServerOSX::global_menu_is_item_radio_checkable(const String &p_menu_root, int p_idx) const {
+ _THREAD_SAFE_METHOD_
+
+ const NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+ if (menu_item) {
+ GodotMenuItem *obj = [menu_item representedObject];
+ if (obj) {
+ return obj->checkable_type == CHECKABLE_TYPE_RADIO_BUTTON;
+ }
+ }
+ }
+ return false;
+}
+
+Callable DisplayServerOSX::global_menu_get_item_callback(const String &p_menu_root, int p_idx) const {
_THREAD_SAFE_METHOD_
const NSMenu *menu = _get_menu_root(p_menu_root);
@@ -772,7 +1033,7 @@ Callable DisplayServerOSX::global_menu_get_item_callback(const String &p_menu_ro
return Callable();
}
-Variant DisplayServerOSX::global_menu_get_item_tag(const String &p_menu_root, int p_idx) {
+Variant DisplayServerOSX::global_menu_get_item_tag(const String &p_menu_root, int p_idx) const {
_THREAD_SAFE_METHOD_
const NSMenu *menu = _get_menu_root(p_menu_root);
@@ -788,22 +1049,20 @@ Variant DisplayServerOSX::global_menu_get_item_tag(const String &p_menu_root, in
return Variant();
}
-String DisplayServerOSX::global_menu_get_item_text(const String &p_menu_root, int p_idx) {
+String DisplayServerOSX::global_menu_get_item_text(const String &p_menu_root, int p_idx) const {
_THREAD_SAFE_METHOD_
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
- String ret;
- ret.parse_utf8([[menu_item title] UTF8String]);
- return ret;
+ return String::utf8([[menu_item title] UTF8String]);
}
}
return String();
}
-String DisplayServerOSX::global_menu_get_item_submenu(const String &p_menu_root, int p_idx) {
+String DisplayServerOSX::global_menu_get_item_submenu(const String &p_menu_root, int p_idx) const {
_THREAD_SAFE_METHOD_
const NSMenu *menu = _get_menu_root(p_menu_root);
@@ -812,9 +1071,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;
}
}
}
@@ -823,6 +1082,116 @@ String DisplayServerOSX::global_menu_get_item_submenu(const String &p_menu_root,
return String();
}
+Key DisplayServerOSX::global_menu_get_item_accelerator(const String &p_menu_root, int p_idx) const {
+ _THREAD_SAFE_METHOD_
+
+ const NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+ if (menu_item) {
+ String ret = String::utf8([[menu_item keyEquivalent] UTF8String]);
+ Key keycode = find_keycode(ret);
+ NSUInteger mask = [menu_item keyEquivalentModifierMask];
+ if (mask & NSEventModifierFlagControl) {
+ keycode |= KeyModifierMask::CTRL;
+ }
+ if (mask & NSEventModifierFlagOption) {
+ keycode |= KeyModifierMask::ALT;
+ }
+ if (mask & NSEventModifierFlagShift) {
+ keycode |= KeyModifierMask::SHIFT;
+ }
+ if (mask & NSEventModifierFlagCommand) {
+ keycode |= KeyModifierMask::META;
+ }
+ if (mask & NSEventModifierFlagNumericPad) {
+ keycode |= KeyModifierMask::KPAD;
+ }
+ return keycode;
+ }
+ }
+ return Key::NONE;
+}
+
+bool DisplayServerOSX::global_menu_is_item_disabled(const String &p_menu_root, int p_idx) const {
+ _THREAD_SAFE_METHOD_
+
+ const NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+ if (menu_item) {
+ return ![menu_item isEnabled];
+ }
+ }
+ return false;
+}
+
+String DisplayServerOSX::global_menu_get_item_tooltip(const String &p_menu_root, int p_idx) const {
+ _THREAD_SAFE_METHOD_
+
+ const NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+ if (menu_item) {
+ return String::utf8([[menu_item toolTip] UTF8String]);
+ }
+ }
+ return String();
+}
+
+int DisplayServerOSX::global_menu_get_item_state(const String &p_menu_root, int p_idx) const {
+ _THREAD_SAFE_METHOD_
+
+ const NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+ if (menu_item) {
+ GodotMenuItem *obj = [menu_item representedObject];
+ if (obj) {
+ return obj->state;
+ }
+ }
+ }
+ return 0;
+}
+
+int DisplayServerOSX::global_menu_get_item_max_states(const String &p_menu_root, int p_idx) const {
+ _THREAD_SAFE_METHOD_
+
+ const NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+ if (menu_item) {
+ GodotMenuItem *obj = [menu_item representedObject];
+ if (obj) {
+ return obj->max_states;
+ }
+ }
+ }
+ return 0;
+}
+
+Ref<Texture2D> DisplayServerOSX::global_menu_get_item_icon(const String &p_menu_root, int p_idx) const {
+ _THREAD_SAFE_METHOD_
+
+ const NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+ if (menu_item) {
+ GodotMenuItem *obj = [menu_item representedObject];
+ if (obj) {
+ if (obj->img.is_valid()) {
+ Ref<ImageTexture> txt;
+ txt.instantiate();
+ txt->create_from_image(obj->img);
+ return txt;
+ }
+ }
+ }
+ }
+ return Ref<Texture2D>();
+}
+
void DisplayServerOSX::global_menu_set_item_checked(const String &p_menu_root, int p_idx, bool p_checked) {
_THREAD_SAFE_METHOD_
@@ -853,7 +1222,23 @@ void DisplayServerOSX::global_menu_set_item_checkable(const String &p_menu_root,
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
GodotMenuItem *obj = [menu_item representedObject];
- obj->checkable = p_checkable;
+ obj->checkable_type = (p_checkable) ? CHECKABLE_TYPE_CHECK_BOX : CHECKABLE_TYPE_NONE;
+ }
+ }
+}
+
+void DisplayServerOSX::global_menu_set_item_radio_checkable(const String &p_menu_root, int p_idx, bool p_checkable) {
+ _THREAD_SAFE_METHOD_
+
+ NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
+ return;
+ }
+ NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+ if (menu_item) {
+ GodotMenuItem *obj = [menu_item representedObject];
+ obj->checkable_type = (p_checkable) ? CHECKABLE_TYPE_RADIO_BUTTON : CHECKABLE_TYPE_NONE;
}
}
}
@@ -929,6 +1314,116 @@ void DisplayServerOSX::global_menu_set_item_submenu(const String &p_menu_root, i
}
}
+void DisplayServerOSX::global_menu_set_item_accelerator(const String &p_menu_root, int p_idx, Key p_keycode) {
+ _THREAD_SAFE_METHOD_
+
+ NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
+ return;
+ }
+ NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+ if (menu_item) {
+ [menu_item setKeyEquivalentModifierMask:KeyMappingOSX::keycode_get_native_mask(p_keycode)];
+ String keycode = KeyMappingOSX::keycode_get_native_string(p_keycode & KeyModifierMask::CODE_MASK);
+ [menu_item setKeyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]];
+ }
+ }
+}
+
+void DisplayServerOSX::global_menu_set_item_disabled(const String &p_menu_root, int p_idx, bool p_disabled) {
+ _THREAD_SAFE_METHOD_
+
+ NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
+ return;
+ }
+ NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+ if (menu_item) {
+ [menu_item setEnabled:(!p_disabled)];
+ }
+ }
+}
+
+void DisplayServerOSX::global_menu_set_item_tooltip(const String &p_menu_root, int p_idx, const String &p_tooltip) {
+ _THREAD_SAFE_METHOD_
+
+ NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
+ return;
+ }
+ NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+ if (menu_item) {
+ [menu_item setToolTip:[NSString stringWithUTF8String:p_tooltip.utf8().get_data()]];
+ }
+ }
+}
+
+void DisplayServerOSX::global_menu_set_item_state(const String &p_menu_root, int p_idx, int p_state) {
+ _THREAD_SAFE_METHOD_
+
+ NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
+ return;
+ }
+ NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+ if (menu_item) {
+ GodotMenuItem *obj = [menu_item representedObject];
+ if (obj) {
+ obj->state = p_state;
+ }
+ }
+ }
+}
+
+void DisplayServerOSX::global_menu_set_item_max_states(const String &p_menu_root, int p_idx, int p_max_states) {
+ _THREAD_SAFE_METHOD_
+
+ NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
+ return;
+ }
+ NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+ if (menu_item) {
+ GodotMenuItem *obj = [menu_item representedObject];
+ if (obj) {
+ obj->max_states = p_max_states;
+ }
+ }
+ }
+}
+
+void DisplayServerOSX::global_menu_set_item_icon(const String &p_menu_root, int p_idx, const Ref<Texture2D> &p_icon) {
+ _THREAD_SAFE_METHOD_
+
+ NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
+ return;
+ }
+ NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+ if (menu_item) {
+ GodotMenuItem *obj = [menu_item representedObject];
+ if (p_icon.is_valid()) {
+ obj->img = p_icon->get_image();
+ obj->img = obj->img->duplicate();
+ if (obj->img->is_compressed()) {
+ obj->img->decompress();
+ }
+ obj->img->resize(16, 16, Image::INTERPOLATE_LANCZOS);
+ [menu_item setImage:_convert_to_nsimg(obj->img)];
+ } else {
+ obj->img = Ref<Image>();
+ [menu_item setImage:nil];
+ }
+ }
+ }
+}
+
int DisplayServerOSX::global_menu_get_item_count(const String &p_menu_root) const {
_THREAD_SAFE_METHOD_
@@ -966,6 +1461,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_
@@ -1154,7 +1684,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) {
@@ -1164,7 +1694,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.
@@ -1190,7 +1720,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();
@@ -1367,8 +1901,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;
}
@@ -1722,7 +2256,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];
@@ -1846,9 +2380,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: {
@@ -1889,7 +2423,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: {
@@ -1934,8 +2468,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;
}
}
@@ -1971,9 +2505,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;
@@ -2140,14 +2674,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;
}
@@ -2352,8 +2886,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)) {
@@ -2396,14 +2930,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.");
@@ -2504,21 +3037,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.
@@ -2530,12 +3067,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()) {
@@ -2551,11 +3092,8 @@ void DisplayServerOSX::mouse_process_popups(bool p_close) {
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;
}
if (!was_empty) {
// Inform OS that all popups are closed.
@@ -2568,7 +3106,9 @@ void DisplayServerOSX::mouse_process_popups(bool p_close) {
}
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()));
@@ -2579,12 +3119,13 @@ 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);
+ }
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"];
@@ -2618,6 +3159,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;
@@ -2628,11 +3172,13 @@ DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode
// Setup Dock menu.
dock_menu = [[NSMenu alloc] initWithTitle:@"_dock"];
+ [dock_menu setAutoenablesItems:NO];
// Setup Apple menu.
apple_menu = [[NSMenu alloc] initWithTitle:@""];
title = [NSString stringWithFormat:NSLocalizedString(@"About %@", nil), nsappname];
[apple_menu addItemWithTitle:title action:@selector(showAbout:) keyEquivalent:@""];
+ [apple_menu setAutoenablesItems:NO];
[apple_menu addItem:[NSMenuItem separatorItem]];
@@ -2660,6 +3206,7 @@ DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode
NSMenu *main_menu = [NSApp mainMenu];
menu_item = [main_menu addItemWithTitle:@"" action:nil keyEquivalent:@""];
[main_menu setSubmenu:apple_menu forItem:menu_item];
+ [main_menu setAutoenablesItems:NO];
//!!!!!!!!!!!!!!!!!!!!!!!!!!
//TODO - do Vulkan and OpenGL support checks, driver selection and fallback
@@ -2719,11 +3266,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 890e66ffd5..9309b9f89b 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/")) {
@@ -72,6 +72,7 @@ void EditorExportPlatformOSX::get_export_options(List<ExportOption> *r_options)
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "debug/export_console_script", PROPERTY_HINT_ENUM, "No,Debug Only,Debug and Release"), 1));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/icon", PROPERTY_HINT_FILE, "*.png,*.icns"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/bundle_identifier", PROPERTY_HINT_PLACEHOLDER_TEXT, "com.example.game"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/signature"), ""));
@@ -255,22 +256,23 @@ 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);
+ ERR_FAIL();
+ }
- 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);
@@ -564,7 +566,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;
@@ -602,7 +604,7 @@ 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) {
@@ -632,7 +634,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 };
@@ -681,6 +683,19 @@ Error EditorExportPlatformOSX::_create_dmg(const String &p_dmg_path, const Strin
return OK;
}
+Error EditorExportPlatformOSX::_export_debug_script(const Ref<EditorExportPreset> &p_preset, const String &p_app_name, const String &p_pkg_name, const String &p_path) {
+ Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::WRITE);
+ ERR_FAIL_COND_V(f.is_null(), ERR_CANT_CREATE);
+
+ f->store_line("#!/bin/sh");
+ f->store_line("echo -ne '\\033c\\033]0;" + p_app_name + "\\a'");
+ f->store_line("function realpath() { python -c \"import os,sys; print(os.path.realpath(sys.argv[1]))\" \"$0\"; }");
+ f->store_line("base_path=\"$(dirname \"$(realpath \"$0\")\")\"");
+ f->store_line("\"$base_path/" + p_pkg_name + "\" \"$@\"");
+
+ return OK;
+}
+
Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
@@ -707,8 +722,8 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
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;
@@ -747,22 +762,30 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
// Create our application bundle.
String tmp_app_dir_name = pkg_name + ".app";
+ String tmp_base_path_name;
String tmp_app_path_name;
+ String scr_path;
if (export_format == "app") {
+ tmp_base_path_name = p_path.get_base_dir();
tmp_app_path_name = p_path;
+ scr_path = p_path.get_basename() + ".command";
} else {
- tmp_app_path_name = EditorPaths::get_singleton()->get_cache_dir().plus_file(tmp_app_dir_name);
+ tmp_base_path_name = EditorPaths::get_singleton()->get_cache_dir().plus_file(pkg_name);
+ tmp_app_path_name = tmp_base_path_name.plus_file(tmp_app_dir_name);
+ scr_path = tmp_base_path_name.plus_file(pkg_name + ".command");
}
+
print_verbose("Exporting to " + tmp_app_path_name);
Error err = OK;
- DirAccessRef tmp_app_dir = DirAccess::create_for_path(tmp_app_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()) {
err = ERR_CANT_CREATE;
}
- if (DirAccess::exists(tmp_app_dir_name)) {
+ DirAccess::remove_file_or_error(scr_path);
+ if (DirAccess::exists(tmp_app_path_name)) {
if (tmp_app_dir->change_dir(tmp_app_path_name) == OK) {
tmp_app_dir->erase_contents_recursive();
}
@@ -810,7 +833,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() + "\";");
@@ -856,7 +879,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)) {
@@ -913,6 +936,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);
@@ -971,12 +997,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;
@@ -1017,15 +1041,13 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
err = tmp_app_dir->make_dir_recursive(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 {
err = ERR_CANT_CREATE;
}
@@ -1043,6 +1065,15 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
err = ERR_FILE_NOT_FOUND;
}
+ // Save console script.
+ if (err == OK) {
+ int con_scr = p_preset->get("debug/export_console_script");
+ 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) {
if (ep.step(TTR("Making PKG"), 1)) {
return ERR_SKIP;
@@ -1060,8 +1091,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\">");
@@ -1182,16 +1213,13 @@ 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 {
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\">");
@@ -1202,9 +1230,6 @@ 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 {
err = ERR_CANT_CREATE;
}
@@ -1212,7 +1237,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
}
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());
@@ -1239,7 +1264,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
}
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()) {
@@ -1284,7 +1309,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
if (ep.step(TTR("Making DMG"), 3)) {
return ERR_SKIP;
}
- err = _create_dmg(p_path, pkg_name, tmp_app_path_name);
+ err = _create_dmg(p_path, pkg_name, tmp_base_path_name);
}
// Sign DMG.
if (err == OK && sign_enabled && !ad_hoc) {
@@ -1303,11 +1328,11 @@ 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, EditorPaths::get_singleton()->get_cache_dir(), pkg_name + ".app", pkg_name);
+ _zip_folder_recursive(zip, tmp_base_path_name, "", pkg_name);
zipClose(zip, nullptr);
}
@@ -1335,10 +1360,10 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
tmp_app_dir->remove(ent_path);
}
if (export_format != "app") {
- if (tmp_app_dir->change_dir(tmp_app_path_name) == OK) {
+ if (tmp_app_dir->change_dir(tmp_base_path_name) == OK) {
tmp_app_dir->erase_contents_recursive();
tmp_app_dir->change_dir("..");
- tmp_app_dir->remove(tmp_app_dir_name);
+ tmp_app_dir->remove(pkg_name);
}
}
}
@@ -1347,9 +1372,9 @@ 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_root_path.plus_file(p_folder);
+ 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()) {
@@ -1401,7 +1426,7 @@ void EditorExportPlatformOSX::_zip_folder_recursive(zipFile &p_zip, const String
} else if (da->current_is_dir()) {
_zip_folder_recursive(p_zip, p_root_path, p_folder.plus_file(f), p_pkg_name);
} else {
- bool is_executable = (p_folder.ends_with("MacOS") && (f == p_pkg_name)) || p_folder.ends_with("Helpers");
+ bool is_executable = (p_folder.ends_with("MacOS") && (f == p_pkg_name)) || p_folder.ends_with("Helpers") || f.ends_with(".command");
OS::Time time = OS::get_singleton()->get_time();
OS::Date date = OS::get_singleton()->get_date();
@@ -1440,8 +1465,8 @@ 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) {
+ Ref<FileAccess> fa = FileAccess::open(dir.plus_file(f), FileAccess::READ);
+ if (fa.is_null()) {
ERR_FAIL_MSG(vformat("Can't open file to read from path \"%s\".", dir.plus_file(f)));
}
const int bufsize = 16384;
diff --git a/platform/osx/export/export_plugin.h b/platform/osx/export/export_plugin.h
index 20cdd33f86..c90c5c29b2 100644
--- a/platform/osx/export/export_plugin.h
+++ b/platform/osx/export/export_plugin.h
@@ -58,14 +58,15 @@ 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);
+ Error _export_debug_script(const Ref<EditorExportPreset> &p_preset, const String &p_app_name, const String &p_pkg_name, const String &p_path);
bool use_codesign() const { return true; }
#ifdef OSX_ENABLED
@@ -100,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"; }
@@ -126,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, RBSet<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_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_menu_item.h b/platform/osx/godot_menu_item.h
index 50c4709c18..2c12897f10 100644
--- a/platform/osx/godot_menu_item.h
+++ b/platform/osx/godot_menu_item.h
@@ -36,12 +36,21 @@
#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>
+enum GlobalMenuCheckType {
+ CHECKABLE_TYPE_NONE,
+ CHECKABLE_TYPE_CHECK_BOX,
+ CHECKABLE_TYPE_RADIO_BUTTON,
+};
+
@interface GodotMenuItem : NSObject {
@public
Callable callback;
Variant meta;
int id;
- bool checkable;
+ GlobalMenuCheckType checkable_type;
+ int max_states;
+ int state;
+ Ref<Image> img;
}
@end
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;