summaryrefslogtreecommitdiffstats
path: root/platform/macos
diff options
context:
space:
mode:
Diffstat (limited to 'platform/macos')
-rw-r--r--platform/macos/SCsub102
-rw-r--r--platform/macos/crash_handler_macos.mm3
-rw-r--r--platform/macos/detect.py77
-rw-r--r--platform/macos/display_server_macos.h35
-rw-r--r--platform/macos/display_server_macos.mm535
-rw-r--r--platform/macos/export/export_plugin.cpp184
-rw-r--r--platform/macos/godot_application_delegate.h2
-rw-r--r--platform/macos/godot_application_delegate.mm71
-rw-r--r--platform/macos/godot_content_view.h2
-rw-r--r--platform/macos/godot_content_view.mm80
-rw-r--r--platform/macos/godot_main_macos.mm14
-rw-r--r--platform/macos/godot_open_save_delegate.h66
-rw-r--r--platform/macos/godot_open_save_delegate.mm284
-rw-r--r--platform/macos/godot_status_item.h51
-rw-r--r--platform/macos/godot_status_item.mm101
-rw-r--r--platform/macos/godot_window_delegate.mm1
-rw-r--r--platform/macos/key_mapping_macos.h1
-rw-r--r--platform/macos/key_mapping_macos.mm24
-rw-r--r--platform/macos/os_macos.mm30
-rw-r--r--platform/macos/rendering_context_driver_vulkan_macos.h (renamed from platform/macos/vulkan_context_macos.h)25
-rw-r--r--platform/macos/rendering_context_driver_vulkan_macos.mm (renamed from platform/macos/vulkan_context_macos.mm)37
21 files changed, 1343 insertions, 382 deletions
diff --git a/platform/macos/SCsub b/platform/macos/SCsub
index 30202e5de7..08783ee14a 100644
--- a/platform/macos/SCsub
+++ b/platform/macos/SCsub
@@ -2,8 +2,99 @@
Import("env")
-from platform_methods import run_in_subprocess
+import os, json
+from platform_methods import run_in_subprocess, architectures, lipo, get_build_version
import platform_macos_builders
+import subprocess
+import shutil
+
+
+def generate_bundle(target, source, env):
+ bin_dir = Dir("#bin").abspath
+
+ if env.editor_build:
+ # Editor bundle.
+ prefix = "godot." + env["platform"] + "." + env["target"]
+ if env.dev_build:
+ prefix += ".dev"
+ if env["precision"] == "double":
+ prefix += ".double"
+
+ # Lipo editor executable.
+ target_bin = lipo(bin_dir + "/" + prefix, env.extra_suffix)
+
+ # Assemble .app bundle and update version info.
+ app_dir = Dir("#bin/" + (prefix + env.extra_suffix).replace(".", "_") + ".app").abspath
+ templ = Dir("#misc/dist/macos_tools.app").abspath
+ if os.path.exists(app_dir):
+ shutil.rmtree(app_dir)
+ shutil.copytree(templ, app_dir, ignore=shutil.ignore_patterns("Contents/Info.plist"))
+ if not os.path.isdir(app_dir + "/Contents/MacOS"):
+ os.mkdir(app_dir + "/Contents/MacOS")
+ if target_bin != "":
+ shutil.copy(target_bin, app_dir + "/Contents/MacOS/Godot")
+ version = get_build_version(False)
+ short_version = get_build_version(True)
+ with open(Dir("#misc/dist/macos").abspath + "/editor_info_plist.template", "rt") as fin:
+ with open(app_dir + "/Contents/Info.plist", "wt") as fout:
+ for line in fin:
+ line = line.replace("$version", version)
+ line = line.replace("$short_version", short_version)
+ fout.write(line)
+
+ # Sign .app bundle.
+ if env["bundle_sign_identity"] != "":
+ sign_command = [
+ "codesign",
+ "-s",
+ env["bundle_sign_identity"],
+ "--deep",
+ "--force",
+ "--options=runtime",
+ "--entitlements",
+ ]
+ if env.dev_build:
+ sign_command += [Dir("#misc/dist/macos").abspath + "/editor_debug.entitlements"]
+ else:
+ sign_command += [Dir("#misc/dist/macos").abspath + "/editor.entitlements"]
+ sign_command += [app_dir]
+ subprocess.run(sign_command)
+ else:
+ # Template bundle.
+ app_prefix = "godot." + env["platform"]
+ rel_prefix = "godot." + env["platform"] + "." + "template_release"
+ dbg_prefix = "godot." + env["platform"] + "." + "template_debug"
+ if env.dev_build:
+ app_prefix += ".dev"
+ rel_prefix += ".dev"
+ dbg_prefix += ".dev"
+ if env["precision"] == "double":
+ app_prefix += ".double"
+ rel_prefix += ".double"
+ dbg_prefix += ".double"
+
+ # Lipo template executables.
+ rel_target_bin = lipo(bin_dir + "/" + rel_prefix, env.extra_suffix)
+ dbg_target_bin = lipo(bin_dir + "/" + dbg_prefix, env.extra_suffix)
+
+ # Assemble .app bundle.
+ app_dir = Dir("#bin/macos_template.app").abspath
+ templ = Dir("#misc/dist/macos_template.app").abspath
+ if os.path.exists(app_dir):
+ shutil.rmtree(app_dir)
+ shutil.copytree(templ, app_dir)
+ if not os.path.isdir(app_dir + "/Contents/MacOS"):
+ os.mkdir(app_dir + "/Contents/MacOS")
+ if rel_target_bin != "":
+ shutil.copy(rel_target_bin, app_dir + "/Contents/MacOS/godot_macos_release.universal")
+ if dbg_target_bin != "":
+ shutil.copy(dbg_target_bin, app_dir + "/Contents/MacOS/godot_macos_debug.universal")
+
+ # ZIP .app bundle.
+ zip_dir = Dir("#bin/" + (app_prefix + env.extra_suffix).replace(".", "_")).abspath
+ shutil.make_archive(zip_dir, "zip", root_dir=bin_dir, base_dir="macos_template.app")
+ shutil.rmtree(app_dir)
+
files = [
"os_macos.mm",
@@ -14,16 +105,18 @@ files = [
"display_server_macos.mm",
"godot_button_view.mm",
"godot_content_view.mm",
+ "godot_status_item.mm",
"godot_window_delegate.mm",
"godot_window.mm",
"key_mapping_macos.mm",
"godot_main_macos.mm",
"godot_menu_delegate.mm",
"godot_menu_item.mm",
+ "godot_open_save_delegate.mm",
"dir_access_macos.mm",
"tts_macos.mm",
"joypad_macos.cpp",
- "vulkan_context_macos.mm",
+ "rendering_context_driver_vulkan_macos.mm",
"gl_manager_macos_angle.mm",
"gl_manager_macos_legacy.mm",
]
@@ -32,3 +125,8 @@ prog = env.add_program("#bin/godot", files)
if env["debug_symbols"] and env["separate_debug_symbols"]:
env.AddPostAction(prog, run_in_subprocess(platform_macos_builders.make_debug_macos))
+
+if env["generate_bundle"]:
+ generate_bundle_command = env.Command("generate_bundle", [], generate_bundle)
+ command = env.AlwaysBuild(generate_bundle_command)
+ env.Depends(command, [prog])
diff --git a/platform/macos/crash_handler_macos.mm b/platform/macos/crash_handler_macos.mm
index 7c0cab0210..c370422bfa 100644
--- a/platform/macos/crash_handler_macos.mm
+++ b/platform/macos/crash_handler_macos.mm
@@ -75,6 +75,7 @@ static void handle_crash(int sig) {
signal(SIGSEGV, SIG_DFL);
signal(SIGFPE, SIG_DFL);
signal(SIGILL, SIG_DFL);
+ signal(SIGTRAP, SIG_DFL);
if (OS::get_singleton() == nullptr) {
abort();
@@ -193,6 +194,7 @@ void CrashHandler::disable() {
signal(SIGSEGV, SIG_DFL);
signal(SIGFPE, SIG_DFL);
signal(SIGILL, SIG_DFL);
+ signal(SIGTRAP, SIG_DFL);
#endif
disabled = true;
@@ -203,5 +205,6 @@ void CrashHandler::initialize() {
signal(SIGSEGV, handle_crash);
signal(SIGFPE, handle_crash);
signal(SIGILL, handle_crash);
+ signal(SIGTRAP, handle_crash);
#endif
}
diff --git a/platform/macos/detect.py b/platform/macos/detect.py
index 4a8e9cd956..54eeb833fa 100644
--- a/platform/macos/detect.py
+++ b/platform/macos/detect.py
@@ -1,7 +1,7 @@
import os
import sys
from methods import detect_darwin_sdk_path, get_compiler_version, is_vanilla_clang
-from platform_methods import detect_arch
+from platform_methods import detect_arch, detect_mvk
from typing import TYPE_CHECKING
@@ -33,6 +33,12 @@ def get_opts():
BoolVariable("use_tsan", "Use LLVM/GCC compiler thread sanitizer (TSAN)", False),
BoolVariable("use_coverage", "Use instrumentation codes in the binary (e.g. for code coverage)", False),
("angle_libs", "Path to the ANGLE static libraries", ""),
+ (
+ "bundle_sign_identity",
+ "The 'Full Name', 'Common Name' or SHA-1 hash of the signing identity used to sign editor .app bundle.",
+ "-",
+ ),
+ BoolVariable("generate_bundle", "Generate an APP bundle after building iOS/macOS binaries", False),
]
@@ -53,46 +59,6 @@ def get_flags():
]
-def get_mvk_sdk_path():
- def int_or_zero(i):
- try:
- return int(i)
- except:
- return 0
-
- def ver_parse(a):
- return [int_or_zero(i) for i in a.split(".")]
-
- dirname = os.path.expanduser("~/VulkanSDK")
- if not os.path.exists(dirname):
- return ""
-
- ver_num = ver_parse("0.0.0.0")
- files = os.listdir(dirname)
- lib_name_out = dirname
- for file in files:
- if os.path.isdir(os.path.join(dirname, file)):
- ver_comp = ver_parse(file)
- if ver_comp > ver_num:
- # Try new SDK location.
- lib_name = os.path.join(
- os.path.join(dirname, file), "macOS/lib/MoltenVK.xcframework/macos-arm64_x86_64/"
- )
- if os.path.isfile(os.path.join(lib_name, "libMoltenVK.a")):
- ver_num = ver_comp
- lib_name_out = lib_name
- else:
- # Try old SDK location.
- lib_name = os.path.join(
- os.path.join(dirname, file), "MoltenVK/MoltenVK.xcframework/macos-arm64_x86_64/"
- )
- if os.path.isfile(os.path.join(lib_name, "libMoltenVK.a")):
- ver_num = ver_comp
- lib_name_out = lib_name
-
- return lib_name_out
-
-
def configure(env: "Environment"):
# Validate arch.
supported_arches = ["x86_64", "arm64"]
@@ -273,32 +239,11 @@ def configure(env: "Environment"):
env.Append(LINKFLAGS=["-framework", "Metal", "-framework", "IOSurface"])
if not env["use_volk"]:
env.Append(LINKFLAGS=["-lMoltenVK"])
- mvk_found = False
-
- mvk_list = [get_mvk_sdk_path(), "/opt/homebrew/lib", "/usr/local/homebrew/lib", "/opt/local/lib"]
- if env["vulkan_sdk_path"] != "":
- mvk_list.insert(0, os.path.expanduser(env["vulkan_sdk_path"]))
- mvk_list.insert(
- 0,
- os.path.join(
- os.path.expanduser(env["vulkan_sdk_path"]), "macOS/lib/MoltenVK.xcframework/macos-arm64_x86_64/"
- ),
- )
- mvk_list.insert(
- 0,
- os.path.join(
- os.path.expanduser(env["vulkan_sdk_path"]), "MoltenVK/MoltenVK.xcframework/macos-arm64_x86_64/"
- ),
- )
-
- for mvk_path in mvk_list:
- if mvk_path and os.path.isfile(os.path.join(mvk_path, "libMoltenVK.a")):
- mvk_found = True
- print("MoltenVK found at: " + mvk_path)
- env.Append(LINKFLAGS=["-L" + mvk_path])
- break
+ mvk_path = detect_mvk(env, "macos-arm64_x86_64")
- if not mvk_found:
+ if mvk_path != "":
+ env.Append(LINKFLAGS=["-L" + os.path.join(mvk_path, "macos-arm64_x86_64")])
+ else:
print(
"MoltenVK SDK installation directory not found, use 'vulkan_sdk_path' SCons parameter to specify SDK path."
)
diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h
index cc343a6d64..7373a40237 100644
--- a/platform/macos/display_server_macos.h
+++ b/platform/macos/display_server_macos.h
@@ -43,7 +43,7 @@
#include "servers/rendering/rendering_device.h"
#if defined(VULKAN_ENABLED)
-#include "vulkan_context_macos.h"
+#include "rendering_context_driver_vulkan_macos.h"
#endif // VULKAN_ENABLED
#endif // RD_ENABLED
@@ -75,6 +75,7 @@ public:
Key physical_keycode = Key::NONE;
Key key_label = Key::NONE;
uint32_t unicode = 0;
+ KeyLocation location = KeyLocation::UNSPECIFIED;
};
struct WindowData {
@@ -136,7 +137,7 @@ private:
GLManagerANGLE_MacOS *gl_manager_angle = nullptr;
#endif
#if defined(RD_ENABLED)
- ApiContextRD *context_rd = nullptr;
+ RenderingContextDriver *rendering_context = nullptr;
RenderingDevice *rendering_device = nullptr;
#endif
String rendering_driver;
@@ -200,14 +201,27 @@ private:
HashMap<WindowID, WindowData> windows;
+ struct IndicatorData {
+ id view;
+ id item;
+ };
+
+ IndicatorID indicator_id_counter = 0;
+ HashMap<IndicatorID, IndicatorData> indicators;
+
IOPMAssertionID screen_keep_on_assertion = kIOPMNullAssertionID;
+ Callable help_search_callback;
+ Callable help_action_callback;
+
struct MenuCall {
Variant tag;
Callable callback;
};
List<MenuCall> deferred_menu_calls;
+ Callable system_theme_changed;
+
const NSMenu *_get_menu_root(const String &p_menu_root) const;
NSMenu *_get_menu_root(const String &p_menu_root);
bool _is_menu_opened(NSMenu *p_menu) const;
@@ -234,12 +248,16 @@ private:
int _get_system_menu_count(const NSMenu *p_menu) const;
NSMenuItem *_menu_add_item(const String &p_menu_root, const String &p_label, Key p_accel, int p_index, int *r_out);
+ Error _file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback, bool p_options_in_cb);
+
public:
NSMenu *get_dock_menu() const;
void menu_callback(id p_sender);
void menu_open(NSMenu *p_menu);
void menu_close(NSMenu *p_menu);
+ void emit_system_theme_changed();
+
bool has_window(WindowID p_window) const;
WindowData &get_window(WindowID p_window);
@@ -271,6 +289,10 @@ public:
virtual bool has_feature(Feature p_feature) const override;
virtual String get_name() const override;
+ virtual void help_set_search_callbacks(const Callable &p_search_callback = Callable(), const Callable &p_action_callback = Callable()) override;
+ Callable _help_get_search_callback() const;
+ Callable _help_get_action_callback() const;
+
virtual void global_menu_set_popup_callbacks(const String &p_menu_root, const Callable &p_open_callback = Callable(), const Callable &p_close_callback = Callable()) override;
virtual int global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index = -1) override;
@@ -340,11 +362,14 @@ public:
virtual bool is_dark_mode_supported() const override;
virtual bool is_dark_mode() const override;
virtual Color get_accent_color() const override;
+ virtual Color get_base_color() const override;
+ virtual void set_system_theme_change_callback(const Callable &p_callable) 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;
virtual Error file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) override;
+ virtual Error file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback) override;
virtual void mouse_set_mode(MouseMode p_mode) override;
virtual MouseMode mouse_get_mode() const override;
@@ -482,6 +507,12 @@ public:
virtual void set_native_icon(const String &p_filename) override;
virtual void set_icon(const Ref<Image> &p_icon) override;
+ virtual IndicatorID create_status_indicator(const Ref<Image> &p_icon, const String &p_tooltip, const Callable &p_callback) override;
+ virtual void status_indicator_set_icon(IndicatorID p_id, const Ref<Image> &p_icon) override;
+ virtual void status_indicator_set_tooltip(IndicatorID p_id, const String &p_tooltip) override;
+ virtual void status_indicator_set_callback(IndicatorID p_id, const Callable &p_callback) override;
+ virtual void delete_status_indicator(IndicatorID p_id) override;
+
static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Error &r_error);
static Vector<String> get_rendering_drivers_func();
diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm
index 378688f78a..344dc1a8f7 100644
--- a/platform/macos/display_server_macos.mm
+++ b/platform/macos/display_server_macos.mm
@@ -34,6 +34,8 @@
#include "godot_content_view.h"
#include "godot_menu_delegate.h"
#include "godot_menu_item.h"
+#include "godot_open_save_delegate.h"
+#include "godot_status_item.h"
#include "godot_window.h"
#include "godot_window_delegate.h"
#include "key_mapping_macos.h"
@@ -159,6 +161,7 @@ DisplayServerMacOS::WindowID DisplayServerMacOS::_create_window(WindowMode p_mod
defer:NO];
ERR_FAIL_NULL_V_MSG(wd.window_object, INVALID_WINDOW_ID, "Can't create a window");
[wd.window_object setWindowID:window_id_counter];
+ [wd.window_object setReleasedWhenClosed:NO];
wd.window_view = [[GodotContentView alloc] init];
ERR_FAIL_NULL_V_MSG(wd.window_view, INVALID_WINDOW_ID, "Can't create a window view");
@@ -193,19 +196,22 @@ DisplayServerMacOS::WindowID DisplayServerMacOS::_create_window(WindowMode p_mod
}
#if defined(RD_ENABLED)
- if (context_rd) {
+ if (rendering_context) {
union {
#ifdef VULKAN_ENABLED
- VulkanContextMacOS::WindowPlatformData vulkan;
+ RenderingContextDriverVulkanMacOS::WindowPlatformData vulkan;
#endif
} wpd;
#ifdef VULKAN_ENABLED
if (rendering_driver == "vulkan") {
- wpd.vulkan.view_ptr = &wd.window_view;
+ wpd.vulkan.layer_ptr = (CAMetalLayer *const *)&layer;
}
#endif
- Error err = context_rd->window_create(window_id_counter, p_vsync_mode, p_rect.size.width, p_rect.size.height, &wpd);
- ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, vformat("Can't create a %s context", context_rd->get_api_name()));
+ Error err = rendering_context->window_create(window_id_counter, &wpd);
+ ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, vformat("Can't create a %s context", rendering_driver));
+
+ rendering_context->window_set_size(window_id_counter, p_rect.size.width, p_rect.size.height);
+ rendering_context->window_set_vsync_mode(window_id_counter, p_vsync_mode);
}
#endif
#if defined(GLES3_ENABLED)
@@ -255,11 +261,6 @@ DisplayServerMacOS::WindowID DisplayServerMacOS::_create_window(WindowMode p_mod
gl_manager_angle->window_resize(id, wd.size.width, wd.size.height);
}
#endif
-#if defined(RD_ENABLED)
- if (context_rd) {
- context_rd->window_resize(id, wd.size.width, wd.size.height);
- }
-#endif
return id;
}
@@ -477,6 +478,7 @@ void DisplayServerMacOS::_process_key_events() {
k->set_physical_keycode(ke.physical_keycode);
k->set_key_label(ke.key_label);
k->set_unicode(ke.unicode);
+ k->set_location(ke.location);
_push_input(k);
} else {
@@ -505,6 +507,7 @@ void DisplayServerMacOS::_process_key_events() {
k->set_keycode(ke.keycode);
k->set_physical_keycode(ke.physical_keycode);
k->set_key_label(ke.key_label);
+ k->set_location(ke.location);
if (i + 1 < key_event_pos && key_event_buffer[i + 1].keycode == Key::NONE) {
k->set_unicode(key_event_buffer[i + 1].unicode);
@@ -787,8 +790,12 @@ void DisplayServerMacOS::window_destroy(WindowID p_window) {
}
#endif
#ifdef RD_ENABLED
- if (context_rd) {
- context_rd->window_destroy(p_window);
+ if (rendering_device) {
+ rendering_device->screen_free(p_window);
+ }
+
+ if (rendering_context) {
+ rendering_context->window_destroy(p_window);
}
#endif
windows.erase(p_window);
@@ -796,6 +803,11 @@ void DisplayServerMacOS::window_destroy(WindowID p_window) {
}
void DisplayServerMacOS::window_resize(WindowID p_window, int p_width, int p_height) {
+#if defined(RD_ENABLED)
+ if (rendering_context) {
+ rendering_context->window_set_size(p_window, p_width, p_height);
+ }
+#endif
#if defined(GLES3_ENABLED)
if (gl_manager_legacy) {
gl_manager_legacy->window_resize(p_window, p_width, p_height);
@@ -804,11 +816,6 @@ void DisplayServerMacOS::window_resize(WindowID p_window, int p_width, int p_hei
gl_manager_angle->window_resize(p_window, p_width, p_height);
}
#endif
-#if defined(VULKAN_ENABLED)
- if (context_rd) {
- context_rd->window_resize(p_window, p_width, p_height);
- }
-#endif
}
bool DisplayServerMacOS::has_feature(Feature p_feature) const {
@@ -832,6 +839,8 @@ bool DisplayServerMacOS::has_feature(Feature p_feature) const {
case FEATURE_TEXT_TO_SPEECH:
case FEATURE_EXTEND_TO_TITLE:
case FEATURE_SCREEN_CAPTURE:
+ case FEATURE_STATUS_INDICATOR:
+ case FEATURE_NATIVE_HELP:
return true;
default: {
}
@@ -843,6 +852,19 @@ String DisplayServerMacOS::get_name() const {
return "macOS";
}
+void DisplayServerMacOS::help_set_search_callbacks(const Callable &p_search_callback, const Callable &p_action_callback) {
+ help_search_callback = p_search_callback;
+ help_action_callback = p_action_callback;
+}
+
+Callable DisplayServerMacOS::_help_get_search_callback() const {
+ return help_search_callback;
+}
+
+Callable DisplayServerMacOS::_help_get_action_callback() const {
+ return help_action_callback;
+}
+
bool DisplayServerMacOS::_is_menu_opened(NSMenu *p_menu) const {
if (submenu_inv.has(p_menu)) {
const MenuData &md = submenu[submenu_inv[p_menu]];
@@ -1672,6 +1694,10 @@ void DisplayServerMacOS::global_menu_set_item_text(const String &p_menu_root, in
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
[menu_item setTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]];
+ NSMenu *sub_menu = [menu_item submenu];
+ if (sub_menu) {
+ [sub_menu setTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]];
+ }
}
}
}
@@ -2025,7 +2051,42 @@ bool DisplayServerMacOS::is_dark_mode() const {
Color DisplayServerMacOS::get_accent_color() const {
if (@available(macOS 10.14, *)) {
- NSColor *color = [[NSColor controlAccentColor] colorUsingColorSpace:[NSColorSpace genericRGBColorSpace]];
+ __block NSColor *color = nullptr;
+ if (@available(macOS 11.0, *)) {
+ [NSApp.effectiveAppearance performAsCurrentDrawingAppearance:^{
+ color = [[NSColor controlAccentColor] colorUsingColorSpace:[NSColorSpace genericRGBColorSpace]];
+ }];
+ } else {
+ NSAppearance *saved_appearance = [NSAppearance currentAppearance];
+ [NSAppearance setCurrentAppearance:[NSApp effectiveAppearance]];
+ color = [[NSColor controlAccentColor] colorUsingColorSpace:[NSColorSpace genericRGBColorSpace]];
+ [NSAppearance setCurrentAppearance:saved_appearance];
+ }
+ if (color) {
+ CGFloat components[4];
+ [color getRed:&components[0] green:&components[1] blue:&components[2] alpha:&components[3]];
+ return Color(components[0], components[1], components[2], components[3]);
+ } else {
+ return Color(0, 0, 0, 0);
+ }
+ } else {
+ return Color(0, 0, 0, 0);
+ }
+}
+
+Color DisplayServerMacOS::get_base_color() const {
+ if (@available(macOS 10.14, *)) {
+ __block NSColor *color = nullptr;
+ if (@available(macOS 11.0, *)) {
+ [NSApp.effectiveAppearance performAsCurrentDrawingAppearance:^{
+ color = [[NSColor controlColor] colorUsingColorSpace:[NSColorSpace genericRGBColorSpace]];
+ }];
+ } else {
+ NSAppearance *saved_appearance = [NSAppearance currentAppearance];
+ [NSAppearance setCurrentAppearance:[NSApp effectiveAppearance]];
+ color = [[NSColor controlColor] colorUsingColorSpace:[NSColorSpace genericRGBColorSpace]];
+ [NSAppearance setCurrentAppearance:saved_appearance];
+ }
if (color) {
CGFloat components[4];
[color getRed:&components[0] green:&components[1] blue:&components[2] alpha:&components[3]];
@@ -2038,6 +2099,16 @@ Color DisplayServerMacOS::get_accent_color() const {
}
}
+void DisplayServerMacOS::set_system_theme_change_callback(const Callable &p_callable) {
+ system_theme_changed = p_callable;
+}
+
+void DisplayServerMacOS::emit_system_theme_changed() {
+ if (system_theme_changed.is_valid()) {
+ system_theme_changed.call();
+ }
+}
+
Error DisplayServerMacOS::dialog_show(String p_title, String p_description, Vector<String> p_buttons, const Callable &p_callback) {
_THREAD_SAFE_METHOD_
@@ -2079,139 +2150,37 @@ Error DisplayServerMacOS::dialog_show(String p_title, String p_description, Vect
return OK;
}
-@interface FileDialogDropdown : NSObject {
- NSSavePanel *dialog;
- NSMutableArray *allowed_types;
- int cur_index;
-}
-
-- (instancetype)initWithDialog:(NSSavePanel *)p_dialog fileTypes:(NSMutableArray *)p_allowed_types;
-- (void)popupAction:(id)sender;
-- (int)getIndex;
-
-@end
-
-@implementation FileDialogDropdown
-
-- (int)getIndex {
- return cur_index;
+Error DisplayServerMacOS::file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) {
+ return _file_dialog_with_options_show(p_title, p_current_directory, String(), p_filename, p_show_hidden, p_mode, p_filters, TypedArray<Dictionary>(), p_callback, false);
}
-- (instancetype)initWithDialog:(NSSavePanel *)p_dialog fileTypes:(NSMutableArray *)p_allowed_types {
- if ((self = [super init])) {
- dialog = p_dialog;
- allowed_types = p_allowed_types;
- cur_index = 0;
- }
- return self;
+Error DisplayServerMacOS::file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback) {
+ return _file_dialog_with_options_show(p_title, p_current_directory, p_root, p_filename, p_show_hidden, p_mode, p_filters, p_options, p_callback, true);
}
-- (void)popupAction:(id)sender {
- NSUInteger index = [sender indexOfSelectedItem];
- if (index < [allowed_types count]) {
- [dialog setAllowedFileTypes:[allowed_types objectAtIndex:index]];
- cur_index = index;
- } else {
- [dialog setAllowedFileTypes:@[]];
- cur_index = -1;
- }
-}
-
-@end
-
-FileDialogDropdown *_make_accessory_view(NSSavePanel *p_panel, const Vector<String> &p_filters) {
- NSView *group = [[NSView alloc] initWithFrame:NSZeroRect];
- group.translatesAutoresizingMaskIntoConstraints = NO;
-
- NSTextField *label = [NSTextField labelWithString:[NSString stringWithUTF8String:RTR("Format").utf8().get_data()]];
- label.translatesAutoresizingMaskIntoConstraints = NO;
- if (@available(macOS 10.14, *)) {
- label.textColor = NSColor.secondaryLabelColor;
- }
- if (@available(macOS 11.10, *)) {
- label.font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]];
- }
- [group addSubview:label];
-
- NSPopUpButton *popup = [[NSPopUpButton alloc] initWithFrame:NSZeroRect pullsDown:NO];
- popup.translatesAutoresizingMaskIntoConstraints = NO;
-
- NSMutableArray *allowed_types = [[NSMutableArray alloc] init];
- bool allow_other = false;
- for (int i = 0; i < p_filters.size(); i++) {
- Vector<String> tokens = p_filters[i].split(";");
- if (tokens.size() >= 1) {
- String flt = tokens[0].strip_edges();
- int filter_slice_count = flt.get_slice_count(",");
-
- NSMutableArray *type_filters = [[NSMutableArray alloc] init];
- for (int j = 0; j < filter_slice_count; j++) {
- String str = (flt.get_slice(",", j).strip_edges());
- if (str.strip_edges() == "*.*" || str.strip_edges() == "*") {
- allow_other = true;
- } else if (!str.is_empty()) {
- [type_filters addObject:[NSString stringWithUTF8String:str.replace("*.", "").strip_edges().utf8().get_data()]];
- }
- }
-
- if ([type_filters count] > 0) {
- NSString *name_str = [NSString stringWithUTF8String:((tokens.size() == 1) ? tokens[0] : vformat("%s (%s)", tokens[1], tokens[0])).strip_edges().utf8().get_data()];
- [allowed_types addObject:type_filters];
- [popup addItemWithTitle:name_str];
- }
- }
- }
- FileDialogDropdown *handler = [[FileDialogDropdown alloc] initWithDialog:p_panel fileTypes:allowed_types];
- popup.target = handler;
- popup.action = @selector(popupAction:);
-
- [group addSubview:popup];
-
- NSView *view = [[NSView alloc] initWithFrame:NSZeroRect];
- view.translatesAutoresizingMaskIntoConstraints = NO;
- [view addSubview:group];
-
- NSMutableArray *constraints = [NSMutableArray array];
- [constraints addObject:[popup.topAnchor constraintEqualToAnchor:group.topAnchor constant:10]];
- [constraints addObject:[label.leadingAnchor constraintEqualToAnchor:group.leadingAnchor constant:10]];
- [constraints addObject:[popup.leadingAnchor constraintEqualToAnchor:label.trailingAnchor constant:10]];
- [constraints addObject:[popup.firstBaselineAnchor constraintEqualToAnchor:label.firstBaselineAnchor]];
- [constraints addObject:[group.trailingAnchor constraintEqualToAnchor:popup.trailingAnchor constant:10]];
- [constraints addObject:[group.bottomAnchor constraintEqualToAnchor:popup.bottomAnchor constant:10]];
- [constraints addObject:[group.topAnchor constraintEqualToAnchor:view.topAnchor]];
- [constraints addObject:[group.centerXAnchor constraintEqualToAnchor:view.centerXAnchor]];
- [constraints addObject:[view.bottomAnchor constraintEqualToAnchor:group.bottomAnchor]];
- [NSLayoutConstraint activateConstraints:constraints];
-
- [p_panel setAllowsOtherFileTypes:allow_other];
- if ([allowed_types count] > 0) {
- [p_panel setAccessoryView:view];
- [p_panel setAllowedFileTypes:[allowed_types objectAtIndex:0]];
- }
-
- return handler;
-}
-
-Error DisplayServerMacOS::file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) {
+Error DisplayServerMacOS::_file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback, bool p_options_in_cb) {
_THREAD_SAFE_METHOD_
ERR_FAIL_INDEX_V(int(p_mode), FILE_DIALOG_MODE_SAVE_MAX, FAILED);
NSString *url = [NSString stringWithUTF8String:p_current_directory.utf8().get_data()];
- FileDialogDropdown *handler = nullptr;
-
WindowID prev_focus = last_focused_window;
+ GodotOpenSaveDelegate *panel_delegate = [[GodotOpenSaveDelegate alloc] init];
+ if (p_root.length() > 0) {
+ [panel_delegate setRootPath:p_root];
+ }
Callable callback = p_callback; // Make a copy for async completion handler.
if (p_mode == FILE_DIALOG_MODE_SAVE_FILE) {
NSSavePanel *panel = [NSSavePanel savePanel];
[panel setDirectoryURL:[NSURL fileURLWithPath:url]];
- handler = _make_accessory_view(panel, p_filters);
+ [panel_delegate makeAccessoryView:panel filters:p_filters options:p_options];
[panel setExtensionHidden:YES];
[panel setCanSelectHiddenExtension:YES];
[panel setCanCreateDirectories:YES];
[panel setShowsHiddenFiles:p_show_hidden];
+ [panel setDelegate:panel_delegate];
if (p_filename != "") {
NSString *fileurl = [NSString stringWithUTF8String:p_filename.utf8().get_data()];
[panel setNameFieldStringValue:fileurl];
@@ -2248,30 +2217,60 @@ Error DisplayServerMacOS::file_dialog_show(const String &p_title, const String &
url.parse_utf8([[[panel URL] path] UTF8String]);
files.push_back(url);
if (!callback.is_null()) {
- Variant v_result = true;
- Variant v_files = files;
- Variant v_index = [handler getIndex];
- Variant ret;
- Callable::CallError ce;
- const Variant *args[3] = { &v_result, &v_files, &v_index };
-
- callback.callp(args, 3, ret, ce);
- if (ce.error != Callable::CallError::CALL_OK) {
- ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 3, ce)));
+ if (p_options_in_cb) {
+ Variant v_result = true;
+ Variant v_files = files;
+ Variant v_index = [panel_delegate getIndex];
+ Variant v_opt = [panel_delegate getSelection];
+ Variant ret;
+ Callable::CallError ce;
+ const Variant *args[4] = { &v_result, &v_files, &v_index, &v_opt };
+
+ callback.callp(args, 4, ret, ce);
+ if (ce.error != Callable::CallError::CALL_OK) {
+ ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 4, ce)));
+ }
+ } else {
+ Variant v_result = true;
+ Variant v_files = files;
+ Variant v_index = [panel_delegate getIndex];
+ Variant ret;
+ Callable::CallError ce;
+ const Variant *args[3] = { &v_result, &v_files, &v_index };
+
+ callback.callp(args, 3, ret, ce);
+ if (ce.error != Callable::CallError::CALL_OK) {
+ ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 3, ce)));
+ }
}
}
} else {
if (!callback.is_null()) {
- Variant v_result = false;
- Variant v_files = Vector<String>();
- Variant v_index = [handler getIndex];
- Variant ret;
- Callable::CallError ce;
- const Variant *args[3] = { &v_result, &v_files, &v_index };
-
- callback.callp(args, 3, ret, ce);
- if (ce.error != Callable::CallError::CALL_OK) {
- ERR_PRINT(vformat("Failed to execute file dialogs callback: %s.", Variant::get_callable_error_text(callback, args, 3, ce)));
+ if (p_options_in_cb) {
+ Variant v_result = false;
+ Variant v_files = Vector<String>();
+ Variant v_index = [panel_delegate getIndex];
+ Variant v_opt = [panel_delegate getSelection];
+ Variant ret;
+ Callable::CallError ce;
+ const Variant *args[4] = { &v_result, &v_files, &v_index, &v_opt };
+
+ callback.callp(args, 4, ret, ce);
+ if (ce.error != Callable::CallError::CALL_OK) {
+ ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 4, ce)));
+ }
+ } else {
+ Variant v_result = false;
+ Variant v_files = Vector<String>();
+ Variant v_index = [panel_delegate getIndex];
+ Variant ret;
+ Callable::CallError ce;
+ const Variant *args[3] = { &v_result, &v_files, &v_index };
+
+ callback.callp(args, 3, ret, ce);
+ if (ce.error != Callable::CallError::CALL_OK) {
+ ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 3, ce)));
+ }
}
}
}
@@ -2283,13 +2282,14 @@ Error DisplayServerMacOS::file_dialog_show(const String &p_title, const String &
NSOpenPanel *panel = [NSOpenPanel openPanel];
[panel setDirectoryURL:[NSURL fileURLWithPath:url]];
- handler = _make_accessory_view(panel, p_filters);
+ [panel_delegate makeAccessoryView:panel filters:p_filters options:p_options];
[panel setExtensionHidden:YES];
[panel setCanSelectHiddenExtension:YES];
[panel setCanCreateDirectories:YES];
[panel setCanChooseFiles:(p_mode != FILE_DIALOG_MODE_OPEN_DIR)];
[panel setCanChooseDirectories:(p_mode == FILE_DIALOG_MODE_OPEN_DIR || p_mode == FILE_DIALOG_MODE_OPEN_ANY)];
[panel setShowsHiddenFiles:p_show_hidden];
+ [panel setDelegate:panel_delegate];
if (p_filename != "") {
NSString *fileurl = [NSString stringWithUTF8String:p_filename.utf8().get_data()];
[panel setNameFieldStringValue:fileurl];
@@ -2333,30 +2333,60 @@ Error DisplayServerMacOS::file_dialog_show(const String &p_title, const String &
files.push_back(url);
}
if (!callback.is_null()) {
- Variant v_result = true;
- Variant v_files = files;
- Variant v_index = [handler getIndex];
- Variant ret;
- Callable::CallError ce;
- const Variant *args[3] = { &v_result, &v_files, &v_index };
-
- callback.callp(args, 3, ret, ce);
- if (ce.error != Callable::CallError::CALL_OK) {
- ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 3, ce)));
+ if (p_options_in_cb) {
+ Variant v_result = true;
+ Variant v_files = files;
+ Variant v_index = [panel_delegate getIndex];
+ Variant v_opt = [panel_delegate getSelection];
+ Variant ret;
+ Callable::CallError ce;
+ const Variant *args[4] = { &v_result, &v_files, &v_index, &v_opt };
+
+ callback.callp(args, 4, ret, ce);
+ if (ce.error != Callable::CallError::CALL_OK) {
+ ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 4, ce)));
+ }
+ } else {
+ Variant v_result = true;
+ Variant v_files = files;
+ Variant v_index = [panel_delegate getIndex];
+ Variant ret;
+ Callable::CallError ce;
+ const Variant *args[3] = { &v_result, &v_files, &v_index };
+
+ callback.callp(args, 3, ret, ce);
+ if (ce.error != Callable::CallError::CALL_OK) {
+ ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 3, ce)));
+ }
}
}
} else {
if (!callback.is_null()) {
- Variant v_result = false;
- Variant v_files = Vector<String>();
- Variant v_index = [handler getIndex];
- Variant ret;
- Callable::CallError ce;
- const Variant *args[3] = { &v_result, &v_files, &v_index };
-
- callback.callp(args, 3, ret, ce);
- if (ce.error != Callable::CallError::CALL_OK) {
- ERR_PRINT(vformat("Failed to execute file dialogs callback: %s.", Variant::get_callable_error_text(callback, args, 3, ce)));
+ if (p_options_in_cb) {
+ Variant v_result = false;
+ Variant v_files = Vector<String>();
+ Variant v_index = [panel_delegate getIndex];
+ Variant v_opt = [panel_delegate getSelection];
+ Variant ret;
+ Callable::CallError ce;
+ const Variant *args[4] = { &v_result, &v_files, &v_index, &v_opt };
+
+ callback.callp(args, 4, ret, ce);
+ if (ce.error != Callable::CallError::CALL_OK) {
+ ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 4, ce)));
+ }
+ } else {
+ Variant v_result = false;
+ Variant v_files = Vector<String>();
+ Variant v_index = [panel_delegate getIndex];
+ Variant ret;
+ Callable::CallError ce;
+ const Variant *args[3] = { &v_result, &v_files, &v_index };
+
+ callback.callp(args, 3, ret, ce);
+ if (ce.error != Callable::CallError::CALL_OK) {
+ ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 3, ce)));
+ }
}
}
}
@@ -2883,7 +2913,11 @@ DisplayServer::WindowID DisplayServerMacOS::create_sub_window(WindowMode p_mode,
window_set_flag(WindowFlags(i), true, id);
}
}
-
+#ifdef RD_ENABLED
+ if (rendering_device) {
+ rendering_device->screen_create(id);
+ }
+#endif
return id;
}
@@ -3844,8 +3878,8 @@ void DisplayServerMacOS::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_
}
#endif
#if defined(VULKAN_ENABLED)
- if (context_rd) {
- context_rd->set_vsync_mode(p_window, p_vsync_mode);
+ if (rendering_context) {
+ rendering_context->window_set_vsync_mode(p_window, p_vsync_mode);
}
#endif
}
@@ -3861,8 +3895,8 @@ DisplayServer::VSyncMode DisplayServerMacOS::window_get_vsync_mode(WindowID p_wi
}
#endif
#if defined(VULKAN_ENABLED)
- if (context_rd) {
- return context_rd->get_vsync_mode(p_window);
+ if (rendering_context) {
+ return rendering_context->window_get_vsync_mode(p_window);
}
#endif
return DisplayServer::VSYNC_ENABLED;
@@ -4323,6 +4357,124 @@ void DisplayServerMacOS::set_icon(const Ref<Image> &p_icon) {
}
}
+DisplayServer::IndicatorID DisplayServerMacOS::create_status_indicator(const Ref<Image> &p_icon, const String &p_tooltip, const Callable &p_callback) {
+ NSImage *nsimg = nullptr;
+ if (p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0) {
+ Ref<Image> img = p_icon->duplicate();
+ img->convert(Image::FORMAT_RGBA8);
+
+ NSBitmapImageRep *imgrep = [[NSBitmapImageRep alloc]
+ initWithBitmapDataPlanes:nullptr
+ pixelsWide:img->get_width()
+ pixelsHigh:img->get_height()
+ bitsPerSample:8
+ samplesPerPixel:4
+ hasAlpha:YES
+ isPlanar:NO
+ colorSpaceName:NSDeviceRGBColorSpace
+ bytesPerRow:img->get_width() * 4
+ bitsPerPixel:32];
+ if (imgrep) {
+ uint8_t *pixels = [imgrep bitmapData];
+
+ int len = img->get_width() * img->get_height();
+ const uint8_t *r = img->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;
+ }
+
+ nsimg = [[NSImage alloc] initWithSize:NSMakeSize(img->get_width(), img->get_height())];
+ if (nsimg) {
+ [nsimg addRepresentation:imgrep];
+ }
+ }
+ }
+
+ IndicatorData idat;
+
+ idat.item = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength];
+ idat.view = [[GodotStatusItemView alloc] init];
+
+ [idat.view setToolTip:[NSString stringWithUTF8String:p_tooltip.utf8().get_data()]];
+ [idat.view setImage:nsimg];
+ [idat.view setCallback:p_callback];
+ [idat.item setView:idat.view];
+
+ IndicatorID iid = indicator_id_counter++;
+ indicators[iid] = idat;
+
+ return iid;
+}
+
+void DisplayServerMacOS::status_indicator_set_icon(IndicatorID p_id, const Ref<Image> &p_icon) {
+ ERR_FAIL_COND(!indicators.has(p_id));
+
+ NSImage *nsimg = nullptr;
+ if (p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0) {
+ Ref<Image> img = p_icon->duplicate();
+ img->convert(Image::FORMAT_RGBA8);
+
+ NSBitmapImageRep *imgrep = [[NSBitmapImageRep alloc]
+ initWithBitmapDataPlanes:nullptr
+ pixelsWide:img->get_width()
+ pixelsHigh:img->get_height()
+ bitsPerSample:8
+ samplesPerPixel:4
+ hasAlpha:YES
+ isPlanar:NO
+ colorSpaceName:NSDeviceRGBColorSpace
+ bytesPerRow:img->get_width() * 4
+ bitsPerPixel:32];
+ if (imgrep) {
+ uint8_t *pixels = [imgrep bitmapData];
+
+ int len = img->get_width() * img->get_height();
+ const uint8_t *r = img->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;
+ }
+
+ nsimg = [[NSImage alloc] initWithSize:NSMakeSize(img->get_width(), img->get_height())];
+ if (nsimg) {
+ [nsimg addRepresentation:imgrep];
+ }
+ }
+ }
+
+ [indicators[p_id].view setImage:nsimg];
+}
+
+void DisplayServerMacOS::status_indicator_set_tooltip(IndicatorID p_id, const String &p_tooltip) {
+ ERR_FAIL_COND(!indicators.has(p_id));
+
+ [indicators[p_id].view setToolTip:[NSString stringWithUTF8String:p_tooltip.utf8().get_data()]];
+}
+
+void DisplayServerMacOS::status_indicator_set_callback(IndicatorID p_id, const Callable &p_callback) {
+ ERR_FAIL_COND(!indicators.has(p_id));
+
+ [indicators[p_id].view setCallback:p_callback];
+}
+
+void DisplayServerMacOS::delete_status_indicator(IndicatorID p_id) {
+ ERR_FAIL_COND(!indicators.has(p_id));
+
+ [[NSStatusBar systemStatusBar] removeStatusItem:indicators[p_id].item];
+ indicators.erase(p_id);
+}
+
DisplayServer *DisplayServerMacOS::create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Error &r_error) {
DisplayServer *ds = memnew(DisplayServerMacOS(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_position, p_resolution, p_screen, r_error));
if (r_error != OK) {
@@ -4665,16 +4817,16 @@ DisplayServerMacOS::DisplayServerMacOS(const String &p_rendering_driver, WindowM
#if defined(RD_ENABLED)
#if defined(VULKAN_ENABLED)
if (rendering_driver == "vulkan") {
- context_rd = memnew(VulkanContextMacOS);
+ rendering_context = memnew(RenderingContextDriverVulkanMacOS);
}
#endif
- if (context_rd) {
- if (context_rd->initialize() != OK) {
- memdelete(context_rd);
- context_rd = nullptr;
+ if (rendering_context) {
+ if (rendering_context->initialize() != OK) {
+ memdelete(rendering_context);
+ rendering_context = nullptr;
r_error = ERR_CANT_CREATE;
- ERR_FAIL_MSG("Could not initialize Vulkan");
+ ERR_FAIL_MSG("Could not initialize " + rendering_driver);
}
}
#endif
@@ -4709,9 +4861,10 @@ DisplayServerMacOS::DisplayServerMacOS(const String &p_rendering_driver, WindowM
}
#endif
#if defined(RD_ENABLED)
- if (context_rd) {
+ if (rendering_context) {
rendering_device = memnew(RenderingDevice);
- rendering_device->initialize(context_rd);
+ rendering_device->initialize(rendering_context, MAIN_WINDOW_ID);
+ rendering_device->screen_create(MAIN_WINDOW_ID);
RendererCompositorRD::make_current();
}
@@ -4726,6 +4879,11 @@ DisplayServerMacOS::~DisplayServerMacOS() {
screen_keep_on_assertion = kIOPMNullAssertionID;
}
+ // Destroy all status indicators.
+ for (HashMap<IndicatorID, IndicatorData>::Iterator E = indicators.begin(); E;) {
+ [[NSStatusBar systemStatusBar] removeStatusItem:E->value.item];
+ }
+
// Destroy all windows.
for (HashMap<WindowID, WindowData>::Iterator E = windows.begin(); E;) {
HashMap<WindowID, WindowData>::Iterator F = E;
@@ -4747,14 +4905,13 @@ DisplayServerMacOS::~DisplayServerMacOS() {
#endif
#if defined(RD_ENABLED)
if (rendering_device) {
- rendering_device->finalize();
memdelete(rendering_device);
rendering_device = nullptr;
}
- if (context_rd) {
- memdelete(context_rd);
- context_rd = nullptr;
+ if (rendering_context) {
+ memdelete(rendering_context);
+ rendering_context = nullptr;
}
#endif
diff --git a/platform/macos/export/export_plugin.cpp b/platform/macos/export/export_plugin.cpp
index 7ed78db6f8..9cc57e4066 100644
--- a/platform/macos/export/export_plugin.cpp
+++ b/platform/macos/export/export_plugin.cpp
@@ -354,6 +354,9 @@ List<String> EditorExportPlatformMacOS::get_binary_extensions(const Ref<EditorEx
list.push_back("dmg");
#endif
list.push_back("zip");
+#ifndef WINDOWS_ENABLED
+ list.push_back("app");
+#endif
} else if (dist_type == 2) {
#ifdef MACOS_ENABLED
list.push_back("pkg");
@@ -497,65 +500,49 @@ void _rgba8_to_packbits_encode(int p_ch, int p_size, Vector<uint8_t> &p_source,
int src_len = p_size * p_size;
Vector<uint8_t> result;
- result.resize(src_len * 1.25); //temp vector for rle encoded data, make it 25% larger for worst case scenario
- int res_size = 0;
-
- uint8_t buf[128];
- int buf_size = 0;
int i = 0;
+ const uint8_t *src = p_source.ptr();
while (i < src_len) {
- uint8_t cur = p_source.ptr()[i * 4 + p_ch];
-
- if (i < src_len - 2) {
- if ((p_source.ptr()[(i + 1) * 4 + p_ch] == cur) && (p_source.ptr()[(i + 2) * 4 + p_ch] == cur)) {
- if (buf_size > 0) {
- result.write[res_size++] = (uint8_t)(buf_size - 1);
- memcpy(&result.write[res_size], &buf, buf_size);
- res_size += buf_size;
- buf_size = 0;
- }
-
- uint8_t lim = i + 130 >= src_len ? src_len - i - 1 : 130;
- bool hit_lim = true;
-
- for (int j = 3; j <= lim; j++) {
- if (p_source.ptr()[(i + j) * 4 + p_ch] != cur) {
- hit_lim = false;
- i = i + j - 1;
- result.write[res_size++] = (uint8_t)(j - 3 + 0x80);
- result.write[res_size++] = cur;
- break;
- }
- }
- if (hit_lim) {
- result.write[res_size++] = (uint8_t)(lim - 3 + 0x80);
- result.write[res_size++] = cur;
- i = i + lim;
- }
- } else {
- buf[buf_size++] = cur;
- if (buf_size == 128) {
- result.write[res_size++] = (uint8_t)(buf_size - 1);
- memcpy(&result.write[res_size], &buf, buf_size);
- res_size += buf_size;
- buf_size = 0;
- }
+ Vector<uint8_t> seq;
+
+ uint8_t count = 0;
+ while (count <= 0x7f && i < src_len) {
+ if (i + 2 < src_len && src[i * 4 + p_ch] == src[(i + 1) * 4 + p_ch] && src[i] == src[(i + 2) * 4 + p_ch]) {
+ break;
}
- } else {
- buf[buf_size++] = cur;
- result.write[res_size++] = (uint8_t)(buf_size - 1);
- memcpy(&result.write[res_size], &buf, buf_size);
- res_size += buf_size;
- buf_size = 0;
+ seq.push_back(src[i * 4 + p_ch]);
+ i++;
+ count++;
+ }
+ if (!seq.is_empty()) {
+ result.push_back(count - 1);
+ result.append_array(seq);
+ }
+ if (i >= src_len) {
+ break;
}
- i++;
+ uint8_t rep = src[i * 4 + p_ch];
+ count = 0;
+ while (count <= 0x7f && i < src_len && src[i * 4 + p_ch] == rep) {
+ i++;
+ count++;
+ }
+ if (count >= 3) {
+ result.push_back(0x80 + count - 3);
+ result.push_back(rep);
+ } else {
+ result.push_back(count - 1);
+ for (int j = 0; j < count; j++) {
+ result.push_back(rep);
+ }
+ }
}
int ofs = p_dest.size();
- p_dest.resize(p_dest.size() + res_size);
- memcpy(&p_dest.write[ofs], result.ptr(), res_size);
+ p_dest.resize(p_dest.size() + result.size());
+ memcpy(&p_dest.write[ofs], result.ptr(), result.size());
}
void EditorExportPlatformMacOS::_make_icon(const Ref<EditorExportPreset> &p_preset, const Ref<Image> &p_icon, Vector<uint8_t> &p_data) {
@@ -618,6 +605,9 @@ void EditorExportPlatformMacOS::_make_icon(const Ref<EditorExportPreset> &p_pres
_rgba8_to_packbits_encode(1, icon_infos[i].size, src_data, data); // Encode G.
_rgba8_to_packbits_encode(2, icon_infos[i].size, src_data, data); // Encode B.
+ // Note: workaround for macOS icon decoder bug corrupting last RLE encoded value.
+ data.push_back(0x00);
+
int len = data.size() - ofs;
len = BSWAP32(len);
memcpy(&data.write[ofs], icon_infos[i].name, 4);
@@ -1116,10 +1106,73 @@ Error EditorExportPlatformMacOS::_copy_and_sign_files(Ref<DirAccess> &dir_access
add_message(EXPORT_MESSAGE_INFO, TTR("Export"), vformat(TTR("Relative symlinks are not supported, exported \"%s\" might be broken!"), p_src_path.get_file()));
#endif
print_verbose("export framework: " + p_src_path + " -> " + p_in_app_path);
+
+ bool plist_misssing = false;
+ Ref<PList> plist;
+ plist.instantiate();
+ plist->load_file(p_src_path.path_join("Resources").path_join("Info.plist"));
+
+ Ref<PListNode> root_node = plist->get_root();
+ if (root_node.is_null()) {
+ plist_misssing = true;
+ } else {
+ Dictionary root = root_node->get_value();
+ if (!root.has("CFBundleExecutable") || !root.has("CFBundleIdentifier") || !root.has("CFBundlePackageType") || !root.has("CFBundleInfoDictionaryVersion") || !root.has("CFBundleName") || !root.has("CFBundleSupportedPlatforms")) {
+ plist_misssing = true;
+ }
+ }
+
err = dir_access->make_dir_recursive(p_in_app_path);
if (err == OK) {
err = dir_access->copy_dir(p_src_path, p_in_app_path, -1, true);
}
+ if (err == OK && plist_misssing) {
+ add_message(EXPORT_MESSAGE_WARNING, TTR("Export"), vformat(TTR("\"%s\": Info.plist missing or invalid, new Info.plist generated."), p_src_path.get_file()));
+ // Generate Info.plist
+ String lib_name = p_src_path.get_basename().get_file();
+ String lib_id = p_preset->get("application/bundle_identifier");
+ String lib_clean_name = lib_name;
+ for (int i = 0; i < lib_clean_name.length(); i++) {
+ if (!is_ascii_alphanumeric_char(lib_clean_name[i]) && lib_clean_name[i] != '.' && lib_clean_name[i] != '-') {
+ lib_clean_name[i] = '-';
+ }
+ }
+
+ String info_plist_format = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
+ "<plist version=\"1.0\">\n"
+ " <dict>\n"
+ " <key>CFBundleExecutable</key>\n"
+ " <string>$name</string>\n"
+ " <key>CFBundleIdentifier</key>\n"
+ " <string>$id.framework.$cl_name</string>\n"
+ " <key>CFBundleInfoDictionaryVersion</key>\n"
+ " <string>6.0</string>\n"
+ " <key>CFBundleName</key>\n"
+ " <string>$name</string>\n"
+ " <key>CFBundlePackageType</key>\n"
+ " <string>FMWK</string>\n"
+ " <key>CFBundleShortVersionString</key>\n"
+ " <string>1.0.0</string>\n"
+ " <key>CFBundleSupportedPlatforms</key>\n"
+ " <array>\n"
+ " <string>MacOSX</string>\n"
+ " </array>\n"
+ " <key>CFBundleVersion</key>\n"
+ " <string>1.0.0</string>\n"
+ " <key>LSMinimumSystemVersion</key>\n"
+ " <string>10.12</string>\n"
+ " </dict>\n"
+ "</plist>";
+
+ String info_plist = info_plist_format.replace("$id", lib_id).replace("$name", lib_name).replace("$cl_name", lib_clean_name);
+
+ err = dir_access->make_dir_recursive(p_in_app_path.path_join("Resources"));
+ Ref<FileAccess> f = FileAccess::open(p_in_app_path.path_join("Resources").path_join("Info.plist"), FileAccess::WRITE);
+ if (f.is_valid()) {
+ f->store_string(info_plist);
+ }
+ }
} else {
print_verbose("export dylib: " + p_src_path + " -> " + p_in_app_path);
err = dir_access->copy(p_src_path, p_in_app_path);
@@ -1954,6 +2007,8 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p
err = _code_sign(p_preset, tmp_app_path_name, ent_path);
}
+ String noto_path = p_path;
+ bool noto_enabled = (p_preset->get("notarization/notarization").operator int() > 0);
if (export_format == "dmg") {
// Create a DMG.
if (err == OK) {
@@ -1995,17 +2050,36 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p
zipClose(zip, nullptr);
}
+ } else if (export_format == "app" && noto_enabled) {
+ // Create temporary ZIP.
+ if (err == OK) {
+ noto_path = EditorPaths::get_singleton()->get_cache_dir().path_join(pkg_name + ".zip");
+
+ if (ep.step(TTR("Making ZIP"), 3)) {
+ return ERR_SKIP;
+ }
+ if (FileAccess::exists(noto_path)) {
+ OS::get_singleton()->move_to_trash(noto_path);
+ }
+
+ Ref<FileAccess> io_fa_dst;
+ zlib_filefunc_def io_dst = zipio_create_io(&io_fa_dst);
+ zipFile zip = zipOpen2(noto_path.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io_dst);
+
+ zip_folder_recursive(zip, tmp_base_path_name, tmp_app_dir_name, pkg_name);
+
+ zipClose(zip, nullptr);
+ }
}
- bool noto_enabled = (p_preset->get("notarization/notarization").operator int() > 0);
if (err == OK && noto_enabled) {
- if (export_format == "app" || export_format == "pkg") {
+ if (export_format == "pkg") {
add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), TTR("Notarization requires the app to be archived first, select the DMG or ZIP export format instead."));
} else {
if (ep.step(TTR("Sending archive for notarization"), 4)) {
return ERR_SKIP;
}
- err = _notarize(p_preset, p_path);
+ err = _notarize(p_preset, noto_path);
}
}
@@ -2024,6 +2098,10 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p
tmp_app_dir->change_dir("..");
tmp_app_dir->remove(pkg_name);
}
+ } else if (noto_path != p_path) {
+ if (FileAccess::exists(noto_path)) {
+ DirAccess::remove_file_or_error(noto_path);
+ }
}
}
diff --git a/platform/macos/godot_application_delegate.h b/platform/macos/godot_application_delegate.h
index 2426fb0b1c..45bd85c45c 100644
--- a/platform/macos/godot_application_delegate.h
+++ b/platform/macos/godot_application_delegate.h
@@ -36,7 +36,7 @@
#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>
-@interface GodotApplicationDelegate : NSObject
+@interface GodotApplicationDelegate : NSObject <NSUserInterfaceItemSearching, NSApplicationDelegate>
- (void)forceUnbundledWindowActivationHackStep1;
- (void)forceUnbundledWindowActivationHackStep2;
- (void)forceUnbundledWindowActivationHackStep3;
diff --git a/platform/macos/godot_application_delegate.mm b/platform/macos/godot_application_delegate.mm
index a1925195b8..2e76d4aa97 100644
--- a/platform/macos/godot_application_delegate.mm
+++ b/platform/macos/godot_application_delegate.mm
@@ -35,6 +35,63 @@
@implementation GodotApplicationDelegate
+- (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app {
+ return YES;
+}
+
+- (NSArray<NSString *> *)localizedTitlesForItem:(id)item {
+ NSArray *item_name = @[ item[1] ];
+ return item_name;
+}
+
+- (void)searchForItemsWithSearchString:(NSString *)searchString resultLimit:(NSInteger)resultLimit matchedItemHandler:(void (^)(NSArray *items))handleMatchedItems {
+ NSMutableArray *found_items = [[NSMutableArray alloc] init];
+
+ DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
+ if (ds && ds->_help_get_search_callback().is_valid()) {
+ Callable cb = ds->_help_get_search_callback();
+
+ Variant ret;
+ Variant search_string = String::utf8([searchString UTF8String]);
+ Variant result_limit = (uint64_t)resultLimit;
+ Callable::CallError ce;
+ const Variant *args[2] = { &search_string, &result_limit };
+
+ cb.callp(args, 2, ret, ce);
+ if (ce.error != Callable::CallError::CALL_OK) {
+ ERR_PRINT(vformat(RTR("Failed to execute help search callback: %s."), Variant::get_callable_error_text(cb, args, 2, ce)));
+ }
+ Dictionary results = ret;
+ for (const Variant *E = results.next(); E; E = results.next(E)) {
+ const String &key = *E;
+ const String &value = results[*E];
+ if (key.length() > 0 && value.length() > 0) {
+ NSArray *item = @[ [NSString stringWithUTF8String:key.utf8().get_data()], [NSString stringWithUTF8String:value.utf8().get_data()] ];
+ [found_items addObject:item];
+ }
+ }
+ }
+
+ handleMatchedItems(found_items);
+}
+
+- (void)performActionForItem:(id)item {
+ DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
+ if (ds && ds->_help_get_action_callback().is_valid()) {
+ Callable cb = ds->_help_get_action_callback();
+
+ Variant ret;
+ Variant item_string = String::utf8([item[0] UTF8String]);
+ Callable::CallError ce;
+ const Variant *args[1] = { &item_string };
+
+ cb.callp(args, 1, ret, ce);
+ if (ce.error != Callable::CallError::CALL_OK) {
+ ERR_PRINT(vformat(RTR("Failed to execute help action callback: %s."), Variant::get_callable_error_text(cb, args, 1, ce)));
+ }
+ }
+}
+
- (void)forceUnbundledWindowActivationHackStep1 {
// Step 1: Switch focus to macOS SystemUIServer process.
// Required to perform step 2, TransformProcessType will fail if app is already the in focus.
@@ -59,6 +116,13 @@
[[NSRunningApplication currentApplication] activateWithOptions:NSApplicationActivateIgnoringOtherApps];
}
+- (void)system_theme_changed:(NSNotification *)notification {
+ DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
+ if (ds) {
+ ds->emit_system_theme_changed();
+ }
+}
+
- (void)applicationDidFinishLaunching:(NSNotification *)notice {
NSString *nsappname = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"];
NSString *nsbundleid_env = [NSString stringWithUTF8String:getenv("__CFBundleIdentifier")];
@@ -67,6 +131,8 @@
// If the executable is started from terminal or is not bundled, macOS WindowServer won't register and activate app window correctly (menu and title bar are grayed out and input ignored).
[self performSelector:@selector(forceUnbundledWindowActivationHackStep1) withObject:nil afterDelay:0.02];
}
+ [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(system_theme_changed:) name:@"AppleInterfaceThemeChangedNotification" object:nil];
+ [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(system_theme_changed:) name:@"AppleColorPreferencesChangedNotification" object:nil];
}
- (id)init {
@@ -79,6 +145,11 @@
return self;
}
+- (void)dealloc {
+ [[NSDistributedNotificationCenter defaultCenter] removeObserver:self name:@"AppleInterfaceThemeChangedNotification" object:nil];
+ [[NSDistributedNotificationCenter defaultCenter] removeObserver:self name:@"AppleColorPreferencesChangedNotification" object:nil];
+}
+
- (void)handleAppleEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent {
OS_MacOS *os = (OS_MacOS *)OS::get_singleton();
if (!event || !os) {
diff --git a/platform/macos/godot_content_view.h b/platform/macos/godot_content_view.h
index c6060c96c6..dc8d11be54 100644
--- a/platform/macos/godot_content_view.h
+++ b/platform/macos/godot_content_view.h
@@ -72,7 +72,7 @@
- (void)processScrollEvent:(NSEvent *)event button:(MouseButton)button factor:(double)factor;
- (void)processPanEvent:(NSEvent *)event dx:(double)dx dy:(double)dy;
-- (void)processMouseEvent:(NSEvent *)event index:(MouseButton)index pressed:(bool)pressed;
+- (void)processMouseEvent:(NSEvent *)event index:(MouseButton)index pressed:(bool)pressed outofstream:(bool)outofstream;
- (void)setWindowID:(DisplayServer::WindowID)wid;
- (void)updateLayerDelegate;
- (void)cancelComposition;
diff --git a/platform/macos/godot_content_view.mm b/platform/macos/godot_content_view.mm
index 139411249c..93bba84783 100644
--- a/platform/macos/godot_content_view.mm
+++ b/platform/macos/godot_content_view.mm
@@ -356,7 +356,7 @@
ds->cursor_update_shape();
}
-- (void)processMouseEvent:(NSEvent *)event index:(MouseButton)index pressed:(bool)pressed {
+- (void)processMouseEvent:(NSEvent *)event index:(MouseButton)index pressed:(bool)pressed outofstream:(bool)outofstream {
DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
if (!ds || !ds->has_window(window_id)) {
return;
@@ -377,14 +377,18 @@
Ref<InputEventMouseButton> mb;
mb.instantiate();
mb->set_window_id(window_id);
- ds->update_mouse_pos(wd, [event locationInWindow]);
+ if (outofstream) {
+ ds->update_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream]);
+ } else {
+ ds->update_mouse_pos(wd, [event locationInWindow]);
+ }
ds->get_key_modifier_state([event modifierFlags], mb);
mb->set_button_index(index);
mb->set_pressed(pressed);
mb->set_position(wd.mouse_pos);
mb->set_global_position(wd.mouse_pos);
mb->set_button_mask(last_button_state);
- if (index == MouseButton::LEFT && pressed) {
+ if (!outofstream && index == MouseButton::LEFT && pressed) {
mb->set_double_click([event clickCount] == 2);
}
@@ -394,10 +398,10 @@
- (void)mouseDown:(NSEvent *)event {
if (([event modifierFlags] & NSEventModifierFlagControl)) {
mouse_down_control = true;
- [self processMouseEvent:event index:MouseButton::RIGHT pressed:true];
+ [self processMouseEvent:event index:MouseButton::RIGHT pressed:true outofstream:false];
} else {
mouse_down_control = false;
- [self processMouseEvent:event index:MouseButton::LEFT pressed:true];
+ [self processMouseEvent:event index:MouseButton::LEFT pressed:true outofstream:false];
}
}
@@ -407,9 +411,9 @@
- (void)mouseUp:(NSEvent *)event {
if (mouse_down_control) {
- [self processMouseEvent:event index:MouseButton::RIGHT pressed:false];
+ [self processMouseEvent:event index:MouseButton::RIGHT pressed:false outofstream:false];
} else {
- [self processMouseEvent:event index:MouseButton::LEFT pressed:false];
+ [self processMouseEvent:event index:MouseButton::LEFT pressed:false outofstream:false];
}
}
@@ -448,15 +452,17 @@
}
mm->set_global_position(wd.mouse_pos);
mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity());
+ mm->set_screen_velocity(mm->get_velocity());
const Vector2i relativeMotion = Vector2i(delta.x, delta.y) * ds->screen_get_max_scale();
mm->set_relative(relativeMotion);
+ mm->set_relative_screen_position(relativeMotion);
ds->get_key_modifier_state([event modifierFlags], mm);
Input::get_singleton()->parse_input_event(mm);
}
- (void)rightMouseDown:(NSEvent *)event {
- [self processMouseEvent:event index:MouseButton::RIGHT pressed:true];
+ [self processMouseEvent:event index:MouseButton::RIGHT pressed:true outofstream:false];
}
- (void)rightMouseDragged:(NSEvent *)event {
@@ -464,16 +470,16 @@
}
- (void)rightMouseUp:(NSEvent *)event {
- [self processMouseEvent:event index:MouseButton::RIGHT pressed:false];
+ [self processMouseEvent:event index:MouseButton::RIGHT pressed:false outofstream:false];
}
- (void)otherMouseDown:(NSEvent *)event {
if ((int)[event buttonNumber] == 2) {
- [self processMouseEvent:event index:MouseButton::MIDDLE pressed:true];
+ [self processMouseEvent:event index:MouseButton::MIDDLE pressed:true outofstream:false];
} else if ((int)[event buttonNumber] == 3) {
- [self processMouseEvent:event index:MouseButton::MB_XBUTTON1 pressed:true];
+ [self processMouseEvent:event index:MouseButton::MB_XBUTTON1 pressed:true outofstream:false];
} else if ((int)[event buttonNumber] == 4) {
- [self processMouseEvent:event index:MouseButton::MB_XBUTTON2 pressed:true];
+ [self processMouseEvent:event index:MouseButton::MB_XBUTTON2 pressed:true outofstream:false];
} else {
return;
}
@@ -485,16 +491,31 @@
- (void)otherMouseUp:(NSEvent *)event {
if ((int)[event buttonNumber] == 2) {
- [self processMouseEvent:event index:MouseButton::MIDDLE pressed:false];
+ [self processMouseEvent:event index:MouseButton::MIDDLE pressed:false outofstream:false];
} else if ((int)[event buttonNumber] == 3) {
- [self processMouseEvent:event index:MouseButton::MB_XBUTTON1 pressed:false];
+ [self processMouseEvent:event index:MouseButton::MB_XBUTTON1 pressed:false outofstream:false];
} else if ((int)[event buttonNumber] == 4) {
- [self processMouseEvent:event index:MouseButton::MB_XBUTTON2 pressed:false];
+ [self processMouseEvent:event index:MouseButton::MB_XBUTTON2 pressed:false outofstream:false];
} else {
return;
}
}
+- (void)swipeWithEvent:(NSEvent *)event {
+ // Swipe gesture on Trackpad/Magic Mouse, or physical back/forward mouse buttons.
+ if ([event phase] == NSEventPhaseEnded || [event phase] == NSEventPhaseChanged) {
+ if (Math::is_equal_approx([event deltaX], 1.0)) {
+ // Swipe left (back).
+ [self processMouseEvent:event index:MouseButton::MB_XBUTTON1 pressed:true outofstream:true];
+ [self processMouseEvent:event index:MouseButton::MB_XBUTTON1 pressed:false outofstream:true];
+ } else if (Math::is_equal_approx([event deltaX], -1.0)) {
+ // Swipe right (forward).
+ [self processMouseEvent:event index:MouseButton::MB_XBUTTON2 pressed:true outofstream:true];
+ [self processMouseEvent:event index:MouseButton::MB_XBUTTON2 pressed:false outofstream:true];
+ }
+ }
+}
+
- (void)mouseExited:(NSEvent *)event {
DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
if (!ds || !ds->has_window(window_id)) {
@@ -576,21 +597,23 @@
String u32text;
u32text.parse_utf16(text.ptr(), text.length());
+ DisplayServerMacOS::KeyEvent ke;
+ ke.window_id = window_id;
+ ke.macos_state = [event modifierFlags];
+ ke.pressed = true;
+ ke.echo = [event isARepeat];
+ ke.keycode = KeyMappingMacOS::remap_key([event keyCode], [event modifierFlags], false);
+ ke.physical_keycode = KeyMappingMacOS::translate_key([event keyCode]);
+ ke.key_label = KeyMappingMacOS::remap_key([event keyCode], [event modifierFlags], true);
+ ke.raw = true;
+
+ if (u32text.is_empty()) {
+ ke.unicode = 0;
+ ds->push_to_key_event_buffer(ke);
+ }
for (int i = 0; i < u32text.length(); i++) {
const char32_t codepoint = u32text[i];
-
- DisplayServerMacOS::KeyEvent ke;
-
- ke.window_id = window_id;
- ke.macos_state = [event modifierFlags];
- ke.pressed = true;
- ke.echo = [event isARepeat];
- ke.keycode = KeyMappingMacOS::remap_key([event keyCode], [event modifierFlags], false);
- ke.physical_keycode = KeyMappingMacOS::translate_key([event keyCode]);
- ke.key_label = KeyMappingMacOS::remap_key([event keyCode], [event modifierFlags], true);
ke.unicode = fix_unicode(codepoint);
- ke.raw = true;
-
ds->push_to_key_event_buffer(ke);
}
} else {
@@ -604,6 +627,7 @@
ke.physical_keycode = KeyMappingMacOS::translate_key([event keyCode]);
ke.key_label = KeyMappingMacOS::remap_key([event keyCode], [event modifierFlags], true);
ke.unicode = 0;
+ ke.location = KeyMappingMacOS::translate_location([event keyCode]);
ke.raw = false;
ds->push_to_key_event_buffer(ke);
@@ -669,6 +693,7 @@
ke.physical_keycode = KeyMappingMacOS::translate_key(key);
ke.key_label = KeyMappingMacOS::remap_key(key, mod, true);
ke.unicode = 0;
+ ke.location = KeyMappingMacOS::translate_location(key);
ds->push_to_key_event_buffer(ke);
}
@@ -696,6 +721,7 @@
ke.physical_keycode = KeyMappingMacOS::translate_key([event keyCode]);
ke.key_label = KeyMappingMacOS::remap_key([event keyCode], [event modifierFlags], true);
ke.unicode = 0;
+ ke.location = KeyMappingMacOS::translate_location([event keyCode]);
ke.raw = true;
ds->push_to_key_event_buffer(ke);
diff --git a/platform/macos/godot_main_macos.mm b/platform/macos/godot_main_macos.mm
index 58263471b0..3959fb686c 100644
--- a/platform/macos/godot_main_macos.mm
+++ b/platform/macos/godot_main_macos.mm
@@ -65,7 +65,9 @@ int main(int argc, char **argv) {
// We must override main when testing is enabled.
TEST_MAIN_OVERRIDE
- err = Main::setup(argv[0], argc - first_arg, &argv[first_arg]);
+ @autoreleasepool {
+ err = Main::setup(argv[0], argc - first_arg, &argv[first_arg]);
+ }
if (err == ERR_HELP) { // Returned by --help and --version, so success.
return 0;
@@ -73,11 +75,17 @@ int main(int argc, char **argv) {
return 255;
}
- if (Main::start()) {
+ bool ok;
+ @autoreleasepool {
+ ok = Main::start();
+ }
+ if (ok) {
os.run(); // It is actually the OS that decides how to run.
}
- Main::cleanup();
+ @autoreleasepool {
+ Main::cleanup();
+ }
return os.get_exit_code();
}
diff --git a/platform/macos/godot_open_save_delegate.h b/platform/macos/godot_open_save_delegate.h
new file mode 100644
index 0000000000..8857ef1fa9
--- /dev/null
+++ b/platform/macos/godot_open_save_delegate.h
@@ -0,0 +1,66 @@
+/**************************************************************************/
+/* godot_open_save_delegate.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef GODOT_OPEN_SAVE_DELEGATE_H
+#define GODOT_OPEN_SAVE_DELEGATE_H
+
+#import <AppKit/AppKit.h>
+#import <Foundation/Foundation.h>
+
+#include "core/templates/hash_map.h"
+#include "core/variant/typed_array.h"
+#include "core/variant/variant.h"
+
+@interface GodotOpenSaveDelegate : NSObject <NSOpenSavePanelDelegate> {
+ NSSavePanel *dialog;
+ NSMutableArray *allowed_types;
+
+ HashMap<int, String> ctr_ids;
+ Dictionary options;
+ int cur_index;
+ int ctr_id;
+
+ String root;
+}
+
+- (void)makeAccessoryView:(NSSavePanel *)p_panel filters:(const Vector<String> &)p_filters options:(const TypedArray<Dictionary> &)p_options;
+- (void)setFileTypes:(NSMutableArray *)p_allowed_types;
+- (void)popupOptionAction:(id)p_sender;
+- (void)popupCheckAction:(id)p_sender;
+- (void)popupFileAction:(id)p_sender;
+- (int)getIndex;
+- (Dictionary)getSelection;
+- (int)setDefaultInt:(const String &)p_name value:(int)p_value;
+- (int)setDefaultBool:(const String &)p_name value:(bool)p_value;
+- (void)setRootPath:(const String &)p_root_path;
+
+@end
+
+#endif // GODOT_OPEN_SAVE_DELEGATE_H
diff --git a/platform/macos/godot_open_save_delegate.mm b/platform/macos/godot_open_save_delegate.mm
new file mode 100644
index 0000000000..6b55b70629
--- /dev/null
+++ b/platform/macos/godot_open_save_delegate.mm
@@ -0,0 +1,284 @@
+/**************************************************************************/
+/* godot_open_save_delegate.mm */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "godot_open_save_delegate.h"
+
+@implementation GodotOpenSaveDelegate
+
+- (instancetype)init {
+ self = [super init];
+ if ((self = [super init])) {
+ dialog = nullptr;
+ cur_index = 0;
+ ctr_id = 1;
+ allowed_types = nullptr;
+ root = String();
+ }
+ return self;
+}
+
+- (void)makeAccessoryView:(NSSavePanel *)p_panel filters:(const Vector<String> &)p_filters options:(const TypedArray<Dictionary> &)p_options {
+ dialog = p_panel;
+
+ NSMutableArray *constraints = [NSMutableArray array];
+
+ NSView *base_view = [[NSView alloc] initWithFrame:NSZeroRect];
+ base_view.translatesAutoresizingMaskIntoConstraints = NO;
+
+ NSGridView *view = [NSGridView gridViewWithNumberOfColumns:2 rows:0];
+ view.translatesAutoresizingMaskIntoConstraints = NO;
+ view.columnSpacing = 10;
+ view.rowSpacing = 10;
+ view.rowAlignment = NSGridRowAlignmentLastBaseline;
+
+ int option_count = 0;
+
+ for (int i = 0; i < p_options.size(); i++) {
+ const Dictionary &item = p_options[i];
+ if (!item.has("name") || !item.has("values") || !item.has("default")) {
+ continue;
+ }
+ const String &name = item["name"];
+ const Vector<String> &values = item["values"];
+ int default_idx = item["default"];
+
+ NSTextField *label = [NSTextField labelWithString:[NSString stringWithUTF8String:name.utf8().get_data()]];
+ if (@available(macOS 10.14, *)) {
+ label.textColor = NSColor.secondaryLabelColor;
+ }
+ if (@available(macOS 11.10, *)) {
+ label.font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]];
+ }
+
+ NSView *popup = nullptr;
+ if (values.is_empty()) {
+ NSButton *popup_check = [NSButton checkboxWithTitle:@"" target:self action:@selector(popupCheckAction:)];
+ int tag = [self setDefaultBool:name value:(bool)default_idx];
+ popup_check.state = (default_idx) ? NSControlStateValueOn : NSControlStateValueOff;
+ popup_check.tag = tag;
+ popup = popup_check;
+ } else {
+ NSPopUpButton *popup_list = [[NSPopUpButton alloc] initWithFrame:NSZeroRect pullsDown:NO];
+ for (int i = 0; i < values.size(); i++) {
+ [popup_list addItemWithTitle:[NSString stringWithUTF8String:values[i].utf8().get_data()]];
+ }
+ int tag = [self setDefaultInt:name value:default_idx];
+ [popup_list selectItemAtIndex:default_idx];
+ popup_list.tag = tag;
+ popup_list.target = self;
+ popup_list.action = @selector(popupOptionAction:);
+ popup = popup_list;
+ }
+
+ [view addRowWithViews:[NSArray arrayWithObjects:label, popup, nil]];
+
+ option_count++;
+ }
+
+ NSMutableArray *new_allowed_types = [[NSMutableArray alloc] init];
+ bool has_type_popup = false;
+ {
+ NSTextField *label = [NSTextField labelWithString:[NSString stringWithUTF8String:RTR("Format").utf8().get_data()]];
+ if (@available(macOS 10.14, *)) {
+ label.textColor = NSColor.secondaryLabelColor;
+ }
+ if (@available(macOS 11.10, *)) {
+ label.font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]];
+ }
+
+ if (p_filters.size() > 1) {
+ NSPopUpButton *popup = [[NSPopUpButton alloc] initWithFrame:NSZeroRect pullsDown:NO];
+ for (int i = 0; i < p_filters.size(); i++) {
+ Vector<String> tokens = p_filters[i].split(";");
+ if (tokens.size() >= 1) {
+ String flt = tokens[0].strip_edges();
+ int filter_slice_count = flt.get_slice_count(",");
+
+ NSMutableArray *type_filters = [[NSMutableArray alloc] init];
+ for (int j = 0; j < filter_slice_count; j++) {
+ String str = (flt.get_slice(",", j).strip_edges());
+ if (!str.is_empty()) {
+ [type_filters addObject:[NSString stringWithUTF8String:str.replace("*.", "").strip_edges().utf8().get_data()]];
+ }
+ }
+
+ if ([type_filters count] > 0) {
+ NSString *name_str = [NSString stringWithUTF8String:((tokens.size() == 1) ? tokens[0] : vformat("%s (%s)", tokens[1].strip_edges(), tokens[0].strip_edges())).utf8().get_data()];
+ [new_allowed_types addObject:type_filters];
+ [popup addItemWithTitle:name_str];
+ }
+ }
+ }
+ if (popup.numberOfItems > 1) {
+ has_type_popup = true;
+ popup.target = self;
+ popup.action = @selector(popupFileAction:);
+
+ [view addRowWithViews:[NSArray arrayWithObjects:label, popup, nil]];
+ }
+ } else if (p_filters.size() == 1) {
+ Vector<String> tokens = p_filters[0].split(";");
+ if (tokens.size() >= 1) {
+ String flt = tokens[0].strip_edges();
+ int filter_slice_count = flt.get_slice_count(",");
+
+ NSMutableArray *type_filters = [[NSMutableArray alloc] init];
+ for (int j = 0; j < filter_slice_count; j++) {
+ String str = (flt.get_slice(",", j).strip_edges());
+ if (!str.is_empty()) {
+ [type_filters addObject:[NSString stringWithUTF8String:str.replace("*.", "").strip_edges().utf8().get_data()]];
+ }
+ }
+
+ if ([type_filters count] > 0) {
+ [new_allowed_types addObject:type_filters];
+ }
+ }
+ }
+ [self setFileTypes:new_allowed_types];
+ }
+
+ [base_view addSubview:view];
+ [constraints addObject:[view.topAnchor constraintEqualToAnchor:base_view.topAnchor constant:10]];
+ [constraints addObject:[base_view.bottomAnchor constraintEqualToAnchor:view.bottomAnchor constant:10]];
+ [constraints addObject:[base_view.centerXAnchor constraintEqualToAnchor:view.centerXAnchor constant:10]];
+ [NSLayoutConstraint activateConstraints:constraints];
+
+ if (option_count > 0 || has_type_popup) {
+ [p_panel setAccessoryView:base_view];
+ }
+ if ([new_allowed_types count] > 0) {
+ NSMutableArray *type_filters = [new_allowed_types objectAtIndex:0];
+ if (type_filters && [type_filters count] == 1 && [[type_filters objectAtIndex:0] isEqualToString:@"*"]) {
+ [p_panel setAllowedFileTypes:@[]];
+ [p_panel setAllowsOtherFileTypes:true];
+ } else {
+ [p_panel setAllowsOtherFileTypes:false];
+ [p_panel setAllowedFileTypes:type_filters];
+ }
+ } else {
+ [p_panel setAllowedFileTypes:@[]];
+ [p_panel setAllowsOtherFileTypes:true];
+ }
+}
+
+- (int)getIndex {
+ return cur_index;
+}
+
+- (Dictionary)getSelection {
+ return options;
+}
+
+- (int)setDefaultInt:(const String &)p_name value:(int)p_value {
+ int cid = ctr_id++;
+ options[p_name] = p_value;
+ ctr_ids[cid] = p_name;
+
+ return cid;
+}
+
+- (int)setDefaultBool:(const String &)p_name value:(bool)p_value {
+ int cid = ctr_id++;
+ options[p_name] = p_value;
+ ctr_ids[cid] = p_name;
+
+ return cid;
+}
+
+- (void)setFileTypes:(NSMutableArray *)p_allowed_types {
+ allowed_types = p_allowed_types;
+}
+
+- (instancetype)initWithDialog:(NSSavePanel *)p_dialog {
+ if ((self = [super init])) {
+ dialog = p_dialog;
+ cur_index = 0;
+ ctr_id = 1;
+ allowed_types = nullptr;
+ }
+ return self;
+}
+
+- (void)popupCheckAction:(id)p_sender {
+ NSButton *btn = p_sender;
+ if (btn && ctr_ids.has(btn.tag)) {
+ options[ctr_ids[btn.tag]] = ([btn state] == NSControlStateValueOn);
+ }
+}
+
+- (void)popupOptionAction:(id)p_sender {
+ NSPopUpButton *btn = p_sender;
+ if (btn && ctr_ids.has(btn.tag)) {
+ options[ctr_ids[btn.tag]] = (int)[btn indexOfSelectedItem];
+ }
+}
+
+- (void)popupFileAction:(id)p_sender {
+ NSPopUpButton *btn = p_sender;
+ if (btn) {
+ NSUInteger index = [btn indexOfSelectedItem];
+ if (allowed_types && index < [allowed_types count]) {
+ NSMutableArray *type_filters = [allowed_types objectAtIndex:index];
+ if (type_filters && [type_filters count] == 1 && [[type_filters objectAtIndex:0] isEqualToString:@"*"]) {
+ [dialog setAllowedFileTypes:@[]];
+ [dialog setAllowsOtherFileTypes:true];
+ } else {
+ [dialog setAllowsOtherFileTypes:false];
+ [dialog setAllowedFileTypes:type_filters];
+ }
+ cur_index = index;
+ } else {
+ [dialog setAllowedFileTypes:@[]];
+ [dialog setAllowsOtherFileTypes:true];
+ cur_index = -1;
+ }
+ }
+}
+
+- (void)setRootPath:(const String &)p_root_path {
+ root = p_root_path;
+}
+
+- (BOOL)panel:(id)sender validateURL:(NSURL *)url error:(NSError *_Nullable *)outError {
+ if (root.is_empty()) {
+ return YES;
+ }
+
+ NSString *ns_path = url.URLByStandardizingPath.URLByResolvingSymlinksInPath.path;
+ String path = String::utf8([ns_path UTF8String]).simplify_path();
+ if (!path.begins_with(root.simplify_path())) {
+ return NO;
+ }
+
+ return YES;
+}
+
+@end
diff --git a/platform/macos/godot_status_item.h b/platform/macos/godot_status_item.h
new file mode 100644
index 0000000000..1827baa9bd
--- /dev/null
+++ b/platform/macos/godot_status_item.h
@@ -0,0 +1,51 @@
+/**************************************************************************/
+/* godot_status_item.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef GODOT_STATUS_ITEM_H
+#define GODOT_STATUS_ITEM_H
+
+#include "core/input/input_enums.h"
+#include "core/variant/callable.h"
+
+#import <AppKit/AppKit.h>
+#import <Foundation/Foundation.h>
+
+@interface GodotStatusItemView : NSView {
+ NSImage *image;
+ Callable cb;
+}
+
+- (void)processMouseEvent:(NSEvent *)event index:(MouseButton)index;
+- (void)setImage:(NSImage *)image;
+- (void)setCallback:(const Callable &)callback;
+
+@end
+
+#endif // GODOT_STATUS_ITEM_H
diff --git a/platform/macos/godot_status_item.mm b/platform/macos/godot_status_item.mm
new file mode 100644
index 0000000000..71ed0a0f71
--- /dev/null
+++ b/platform/macos/godot_status_item.mm
@@ -0,0 +1,101 @@
+/**************************************************************************/
+/* godot_status_item.mm */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "godot_status_item.h"
+
+#include "display_server_macos.h"
+
+@implementation GodotStatusItemView
+
+- (id)init {
+ self = [super init];
+ image = nullptr;
+ return self;
+}
+
+- (void)setImage:(NSImage *)newImage {
+ image = newImage;
+ [self setNeedsDisplayInRect:self.frame];
+}
+
+- (void)setCallback:(const Callable &)callback {
+ cb = callback;
+}
+
+- (void)drawRect:(NSRect)rect {
+ if (image) {
+ [image drawInRect:rect];
+ }
+}
+
+- (void)processMouseEvent:(NSEvent *)event index:(MouseButton)index {
+ DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
+ if (!ds) {
+ return;
+ }
+
+ if (cb.is_valid()) {
+ Variant v_button = index;
+ Variant v_pos = ds->mouse_get_position();
+ Variant *v_args[2] = { &v_button, &v_pos };
+ Variant ret;
+ Callable::CallError ce;
+ cb.callp((const Variant **)&v_args, 2, ret, ce);
+ }
+}
+
+- (void)mouseDown:(NSEvent *)event {
+ [super mouseDown:event];
+ if (([event modifierFlags] & NSEventModifierFlagControl)) {
+ [self processMouseEvent:event index:MouseButton::RIGHT];
+ } else {
+ [self processMouseEvent:event index:MouseButton::LEFT];
+ }
+}
+
+- (void)rightMouseDown:(NSEvent *)event {
+ [super rightMouseDown:event];
+
+ [self processMouseEvent:event index:MouseButton::RIGHT];
+}
+
+- (void)otherMouseDown:(NSEvent *)event {
+ [super otherMouseDown:event];
+
+ if ((int)[event buttonNumber] == 2) {
+ [self processMouseEvent:event index:MouseButton::MIDDLE];
+ } else if ((int)[event buttonNumber] == 3) {
+ [self processMouseEvent:event index:MouseButton::MB_XBUTTON1];
+ } else if ((int)[event buttonNumber] == 4) {
+ [self processMouseEvent:event index:MouseButton::MB_XBUTTON2];
+ }
+}
+
+@end
diff --git a/platform/macos/godot_window_delegate.mm b/platform/macos/godot_window_delegate.mm
index 93396b0e01..2d83b46007 100644
--- a/platform/macos/godot_window_delegate.mm
+++ b/platform/macos/godot_window_delegate.mm
@@ -362,6 +362,7 @@
}
DisplayServerMacOS::WindowData &wd = ds->get_window(window_id);
+ wd.is_visible = ([wd.window_object occlusionState] & NSWindowOcclusionStateVisible) && [wd.window_object isVisible];
if ([wd.window_object isKeyWindow]) {
wd.focused = true;
ds->set_last_focused_window(window_id);
diff --git a/platform/macos/key_mapping_macos.h b/platform/macos/key_mapping_macos.h
index 1bda4eb406..f5b0ff8d02 100644
--- a/platform/macos/key_mapping_macos.h
+++ b/platform/macos/key_mapping_macos.h
@@ -45,6 +45,7 @@ public:
static Key translate_key(unsigned int p_key);
static unsigned int unmap_key(Key p_key);
static Key remap_key(unsigned int p_key, unsigned int p_state, bool p_unicode);
+ static KeyLocation translate_location(unsigned int p_key);
// Mapping for menu shortcuts.
static String keycode_get_native_string(Key p_keycode);
diff --git a/platform/macos/key_mapping_macos.mm b/platform/macos/key_mapping_macos.mm
index db3fa4e02d..b5e72048e7 100644
--- a/platform/macos/key_mapping_macos.mm
+++ b/platform/macos/key_mapping_macos.mm
@@ -46,6 +46,7 @@ HashSet<unsigned int> numpad_keys;
HashMap<unsigned int, Key, HashMapHasherKeys> keysym_map;
HashMap<Key, unsigned int, HashMapHasherKeys> keysym_map_inv;
HashMap<Key, char32_t, HashMapHasherKeys> keycode_map;
+HashMap<unsigned int, KeyLocation, HashMapHasherKeys> location_map;
void KeyMappingMacOS::initialize() {
numpad_keys.insert(0x41); //kVK_ANSI_KeypadDecimal
@@ -321,6 +322,20 @@ void KeyMappingMacOS::initialize() {
keycode_map[Key::BAR] = '|';
keycode_map[Key::BRACERIGHT] = '}';
keycode_map[Key::ASCIITILDE] = '~';
+
+ // Keysym -> physical location.
+ // Ctrl.
+ location_map[0x3b] = KeyLocation::LEFT;
+ location_map[0x3e] = KeyLocation::RIGHT;
+ // Shift.
+ location_map[0x38] = KeyLocation::LEFT;
+ location_map[0x3c] = KeyLocation::RIGHT;
+ // Alt/Option.
+ location_map[0x3a] = KeyLocation::LEFT;
+ location_map[0x3d] = KeyLocation::RIGHT;
+ // Meta/Command (yes, right < left).
+ location_map[0x36] = KeyLocation::RIGHT;
+ location_map[0x37] = KeyLocation::LEFT;
}
bool KeyMappingMacOS::is_numpad_key(unsigned int p_key) {
@@ -396,6 +411,15 @@ Key KeyMappingMacOS::remap_key(unsigned int p_key, unsigned int p_state, bool p_
}
}
+// Translates a macOS keycode to a Godot key location.
+KeyLocation KeyMappingMacOS::translate_location(unsigned int p_key) {
+ const KeyLocation *location = location_map.getptr(p_key);
+ if (location) {
+ return *location;
+ }
+ return KeyLocation::UNSPECIFIED;
+}
+
String KeyMappingMacOS::keycode_get_native_string(Key p_keycode) {
const char32_t *key = keycode_map.getptr(p_keycode);
if (key) {
diff --git a/platform/macos/os_macos.mm b/platform/macos/os_macos.mm
index b8496d72fb..80c9f1b422 100644
--- a/platform/macos/os_macos.mm
+++ b/platform/macos/os_macos.mm
@@ -356,8 +356,11 @@ Error OS_MacOS::shell_show_in_file_manager(String p_path, bool p_open_folder) {
Error OS_MacOS::shell_open(String p_uri) {
NSString *string = [NSString stringWithUTF8String:p_uri.utf8().get_data()];
NSURL *uri = [[NSURL alloc] initWithString:string];
- // Escape special characters in filenames
if (!uri || !uri.scheme || [uri.scheme isEqual:@"file"]) {
+ // No scheme set, assume "file://" and escape special characters.
+ if (!p_uri.begins_with("file://")) {
+ string = [NSString stringWithUTF8String:("file://" + p_uri).utf8().get_data()];
+ }
uri = [[NSURL alloc] initWithString:[string stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]]];
}
[[NSWorkspace sharedWorkspace] openURL:uri];
@@ -755,21 +758,25 @@ void OS_MacOS::run() {
return;
}
- main_loop->initialize();
+ @autoreleasepool {
+ main_loop->initialize();
+ }
bool quit = false;
while (!quit) {
- @try {
- if (DisplayServer::get_singleton()) {
- DisplayServer::get_singleton()->process_events(); // Get rid of pending events.
- }
- joypad_macos->process_joypads();
+ @autoreleasepool {
+ @try {
+ if (DisplayServer::get_singleton()) {
+ DisplayServer::get_singleton()->process_events(); // Get rid of pending events.
+ }
+ joypad_macos->process_joypads();
- if (Main::iteration()) {
- quit = true;
+ if (Main::iteration()) {
+ quit = true;
+ }
+ } @catch (NSException *exception) {
+ ERR_PRINT("NSException: " + String::utf8([exception reason].UTF8String));
}
- } @catch (NSException *exception) {
- ERR_PRINT("NSException: " + String::utf8([exception reason].UTF8String));
}
}
@@ -823,6 +830,7 @@ OS_MacOS::OS_MacOS() {
id delegate = [[GodotApplicationDelegate alloc] init];
ERR_FAIL_NULL(delegate);
[NSApp setDelegate:delegate];
+ [NSApp registerUserInterfaceItemSearchHandler:delegate];
pre_wait_observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, &pre_wait_observer_cb, nullptr);
CFRunLoopAddObserver(CFRunLoopGetCurrent(), pre_wait_observer, kCFRunLoopCommonModes);
diff --git a/platform/macos/vulkan_context_macos.h b/platform/macos/rendering_context_driver_vulkan_macos.h
index 6205877120..32f8891a2e 100644
--- a/platform/macos/vulkan_context_macos.h
+++ b/platform/macos/rendering_context_driver_vulkan_macos.h
@@ -1,5 +1,5 @@
/**************************************************************************/
-/* vulkan_context_macos.h */
+/* rendering_context_driver_vulkan_macos.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,28 +28,31 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
-#ifndef VULKAN_CONTEXT_MACOS_H
-#define VULKAN_CONTEXT_MACOS_H
+#ifndef RENDERING_CONTEXT_DRIVER_VULKAN_MACOS_H
+#define RENDERING_CONTEXT_DRIVER_VULKAN_MACOS_H
#ifdef VULKAN_ENABLED
-#include "drivers/vulkan/vulkan_context.h"
+#include "drivers/vulkan/rendering_context_driver_vulkan.h"
-#import <AppKit/AppKit.h>
+#import <QuartzCore/CAMetalLayer.h>
-class VulkanContextMacOS : public VulkanContext {
+class RenderingContextDriverVulkanMacOS : public RenderingContextDriverVulkan {
+private:
virtual const char *_get_platform_surface_extension() const override final;
+protected:
+ SurfaceID surface_create(const void *p_platform_data) override final;
+
public:
struct WindowPlatformData {
- const id *view_ptr;
+ CAMetalLayer *const *layer_ptr;
};
- virtual Error window_create(DisplayServer::WindowID p_window_id, DisplayServer::VSyncMode p_vsync_mode, int p_width, int p_height, const void *p_platform_data) override final;
- VulkanContextMacOS();
- ~VulkanContextMacOS();
+ RenderingContextDriverVulkanMacOS();
+ ~RenderingContextDriverVulkanMacOS();
};
#endif // VULKAN_ENABLED
-#endif // VULKAN_CONTEXT_MACOS_H
+#endif // RENDERING_CONTEXT_DRIVER_VULKAN_MACOS_H
diff --git a/platform/macos/vulkan_context_macos.mm b/platform/macos/rendering_context_driver_vulkan_macos.mm
index 18c3bda39b..afefe5a6f7 100644
--- a/platform/macos/vulkan_context_macos.mm
+++ b/platform/macos/rendering_context_driver_vulkan_macos.mm
@@ -1,5 +1,5 @@
/**************************************************************************/
-/* vulkan_context_macos.mm */
+/* rendering_context_driver_vulkan_macos.mm */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,37 +28,42 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
-#include "vulkan_context_macos.h"
+#include "rendering_context_driver_vulkan_macos.h"
#ifdef VULKAN_ENABLED
#ifdef USE_VOLK
#include <volk.h>
#else
-#include <vulkan/vulkan.h>
+#include <vulkan/vulkan_metal.h>
#endif
-const char *VulkanContextMacOS::_get_platform_surface_extension() const {
- return VK_MVK_MACOS_SURFACE_EXTENSION_NAME;
+const char *RenderingContextDriverVulkanMacOS::_get_platform_surface_extension() const {
+ return VK_EXT_METAL_SURFACE_EXTENSION_NAME;
}
-Error VulkanContextMacOS::window_create(DisplayServer::WindowID p_window_id, DisplayServer::VSyncMode p_vsync_mode, int p_width, int p_height, const void *p_platform_data) {
- const WindowPlatformData *wpd = (const WindowPlatformData *)p_platform_data;
+RenderingContextDriver::SurfaceID RenderingContextDriverVulkanMacOS::surface_create(const void *p_platform_data) {
+ const WindowPlatformData *wpd = (const WindowPlatformData *)(p_platform_data);
- VkMacOSSurfaceCreateInfoMVK createInfo = {};
- createInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK;
- createInfo.pView = (__bridge const void *)(*wpd->view_ptr);
+ VkMetalSurfaceCreateInfoEXT create_info = {};
+ create_info.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT;
+ create_info.pLayer = *wpd->layer_ptr;
- VkSurfaceKHR surface = VK_NULL_HANDLE;
- VkResult err = vkCreateMacOSSurfaceMVK(get_instance(), &createInfo, nullptr, &surface);
- ERR_FAIL_COND_V(err, ERR_CANT_CREATE);
- return _window_create(p_window_id, p_vsync_mode, surface, p_width, p_height);
+ VkSurfaceKHR vk_surface = VK_NULL_HANDLE;
+ VkResult err = vkCreateMetalSurfaceEXT(instance_get(), &create_info, nullptr, &vk_surface);
+ ERR_FAIL_COND_V(err != VK_SUCCESS, SurfaceID());
+
+ Surface *surface = memnew(Surface);
+ surface->vk_surface = vk_surface;
+ return SurfaceID(surface);
}
-VulkanContextMacOS::VulkanContextMacOS() {
+RenderingContextDriverVulkanMacOS::RenderingContextDriverVulkanMacOS() {
+ // Does nothing.
}
-VulkanContextMacOS::~VulkanContextMacOS() {
+RenderingContextDriverVulkanMacOS::~RenderingContextDriverVulkanMacOS() {
+ // Does nothing.
}
#endif // VULKAN_ENABLED