summaryrefslogtreecommitdiffstats
path: root/platform/macos
diff options
context:
space:
mode:
Diffstat (limited to 'platform/macos')
-rw-r--r--platform/macos/SCsub99
-rw-r--r--platform/macos/detect.py82
-rw-r--r--platform/macos/display_server_macos.h27
-rw-r--r--platform/macos/display_server_macos.mm214
-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.mm67
-rw-r--r--platform/macos/godot_content_view.h2
-rw-r--r--platform/macos/godot_content_view.mm49
-rw-r--r--platform/macos/godot_open_save_delegate.mm74
-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/os_macos.h4
-rw-r--r--platform/macos/os_macos.mm5
-rw-r--r--platform/macos/rendering_context_driver_vulkan_macos.h4
-rw-r--r--platform/macos/rendering_context_driver_vulkan_macos.mm12
17 files changed, 801 insertions, 177 deletions
diff --git a/platform/macos/SCsub b/platform/macos/SCsub
index 5a93c3a09f..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,6 +105,7 @@ 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",
@@ -33,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/detect.py b/platform/macos/detect.py
index 0d1e40fb3d..cfbe9a8ee7 100644
--- a/platform/macos/detect.py
+++ b/platform/macos/detect.py
@@ -1,12 +1,12 @@
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
if TYPE_CHECKING:
- from SCons import Environment
+ from SCons.Script.SConscript import SConsEnvironment
def get_name():
@@ -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,48 +59,7 @@ 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_min = ver_parse("1.3.231.0")
- 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 and ver_comp >= ver_min:
- # 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"):
+def configure(env: "SConsEnvironment"):
# Validate arch.
supported_arches = ["x86_64", "arm64"]
if env["arch"] not in supported_arches:
@@ -274,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 e298b54970..7373a40237 100644
--- a/platform/macos/display_server_macos.h
+++ b/platform/macos/display_server_macos.h
@@ -201,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;
@@ -243,6 +256,8 @@ public:
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);
@@ -274,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;
@@ -343,6 +362,8 @@ 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;
@@ -486,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 7bcb6df18d..ce4c7b2e05 100644
--- a/platform/macos/display_server_macos.mm
+++ b/platform/macos/display_server_macos.mm
@@ -35,6 +35,7 @@
#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"
@@ -203,7 +204,7 @@ DisplayServerMacOS::WindowID DisplayServerMacOS::_create_window(WindowMode p_mod
} 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 = rendering_context->window_create(window_id_counter, &wpd);
@@ -698,10 +699,12 @@ DisplayServerMacOS::WindowData &DisplayServerMacOS::get_window(WindowID p_window
}
void DisplayServerMacOS::send_event(NSEvent *p_event) {
- // Special case handling of command-period, which is traditionally a special
- // shortcut in macOS and doesn't arrive at our regular keyDown handler.
+ // Special case handling of shortcuts that don't arrive at the regular keyDown handler
if ([p_event type] == NSEventTypeKeyDown) {
- if ((([p_event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask) == NSEventModifierFlagCommand) && [p_event keyCode] == 0x2f) {
+ NSEventModifierFlags flags = [p_event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask;
+
+ // Command-period
+ if ((flags == NSEventModifierFlagCommand) && [p_event keyCode] == 0x2f) {
Ref<InputEventKey> k;
k.instantiate();
@@ -714,6 +717,24 @@ void DisplayServerMacOS::send_event(NSEvent *p_event) {
k->set_echo([p_event isARepeat]);
Input::get_singleton()->parse_input_event(k);
+ return;
+ }
+
+ // Ctrl+Tab and Ctrl+Shift+Tab
+ if (((flags == NSEventModifierFlagControl) || (flags == (NSEventModifierFlagControl | NSEventModifierFlagShift))) && [p_event keyCode] == 0x30) {
+ Ref<InputEventKey> k;
+ k.instantiate();
+
+ get_key_modifier_state([p_event modifierFlags], k);
+ k->set_window_id(DisplayServerMacOS::INVALID_WINDOW_ID);
+ k->set_pressed(true);
+ k->set_keycode(Key::TAB);
+ k->set_physical_keycode(Key::TAB);
+ k->set_key_label(Key::TAB);
+ k->set_echo([p_event isARepeat]);
+
+ Input::get_singleton()->parse_input_event(k);
+ return;
}
}
}
@@ -838,6 +859,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: {
}
@@ -849,6 +872,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]];
@@ -2035,7 +2071,17 @@ 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]];
@@ -2048,6 +2094,41 @@ Color DisplayServerMacOS::get_accent_color() const {
}
}
+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]];
+ return Color(components[0], components[1], components[2], components[3]);
+ } else {
+ return Color(0, 0, 0, 0);
+ }
+ } else {
+ return Color(0, 0, 0, 0);
+ }
+}
+
+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_
@@ -4301,6 +4382,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) {
@@ -4705,6 +4904,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;
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 c1de8ade58..2e76d4aa97 100644
--- a/platform/macos/godot_application_delegate.mm
+++ b/platform/macos/godot_application_delegate.mm
@@ -39,6 +39,59 @@
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.
@@ -63,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")];
@@ -71,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 {
@@ -83,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 f6f054c1e6..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];
}
}
@@ -458,7 +462,7 @@
}
- (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 {
@@ -466,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;
}
@@ -487,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)) {
diff --git a/platform/macos/godot_open_save_delegate.mm b/platform/macos/godot_open_save_delegate.mm
index 306015d644..6b55b70629 100644
--- a/platform/macos/godot_open_save_delegate.mm
+++ b/platform/macos/godot_open_save_delegate.mm
@@ -103,7 +103,7 @@
}
NSMutableArray *new_allowed_types = [[NSMutableArray alloc] init];
- bool allow_other = false;
+ bool has_type_popup = false;
{
NSTextField *label = [NSTextField labelWithString:[NSString stringWithUTF8String:RTR("Format").utf8().get_data()]];
if (@available(macOS 10.14, *)) {
@@ -113,13 +113,38 @@
label.font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]];
}
- NSPopUpButton *popup = [[NSPopUpButton alloc] initWithFrame:NSZeroRect pullsDown:NO];
- if (p_filters.is_empty()) {
- [popup addItemWithTitle:@"All Files"];
- }
+ 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:);
- for (int i = 0; i < p_filters.size(); i++) {
- Vector<String> tokens = p_filters[i].split(";");
+ [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(",");
@@ -127,25 +152,17 @@
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()) {
+ 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()];
[new_allowed_types addObject:type_filters];
- [popup addItemWithTitle:name_str];
}
}
}
[self setFileTypes:new_allowed_types];
- popup.target = self;
- popup.action = @selector(popupFileAction:);
-
- [view addRowWithViews:[NSArray arrayWithObjects:label, popup, nil]];
}
[base_view addSubview:view];
@@ -154,12 +171,21 @@
[constraints addObject:[base_view.centerXAnchor constraintEqualToAnchor:view.centerXAnchor constant:10]];
[NSLayoutConstraint activateConstraints:constraints];
- [p_panel setAllowsOtherFileTypes:allow_other];
- if (option_count > 0 || [new_allowed_types count] > 0) {
+ if (option_count > 0 || has_type_popup) {
[p_panel setAccessoryView:base_view];
}
if ([new_allowed_types count] > 0) {
- [p_panel setAllowedFileTypes:[new_allowed_types objectAtIndex: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];
}
}
@@ -220,10 +246,18 @@
if (btn) {
NSUInteger index = [btn indexOfSelectedItem];
if (allowed_types && index < [allowed_types count]) {
- [dialog setAllowedFileTypes:[allowed_types objectAtIndex:index]];
+ 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;
}
}
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/os_macos.h b/platform/macos/os_macos.h
index ae94b6296d..9b20e51f47 100644
--- a/platform/macos/os_macos.h
+++ b/platform/macos/os_macos.h
@@ -85,7 +85,7 @@ public:
virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override;
- virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = nullptr) override;
+ virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = nullptr) override;
virtual MainLoop *get_main_loop() const override;
@@ -98,7 +98,7 @@ public:
virtual String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const override;
- virtual Error shell_open(String p_uri) override;
+ virtual Error shell_open(const String &p_uri) override;
virtual Error shell_show_in_file_manager(String p_path, bool p_open_folder) override;
virtual String get_locale() const override;
diff --git a/platform/macos/os_macos.mm b/platform/macos/os_macos.mm
index 000215ac46..934767db74 100644
--- a/platform/macos/os_macos.mm
+++ b/platform/macos/os_macos.mm
@@ -217,7 +217,7 @@ _FORCE_INLINE_ String OS_MacOS::get_framework_executable(const String &p_path) {
return p_path;
}
-Error OS_MacOS::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path, String *r_resolved_path) {
+Error OS_MacOS::open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path, String *r_resolved_path) {
String path = get_framework_executable(p_path);
if (!FileAccess::exists(path)) {
@@ -353,7 +353,7 @@ Error OS_MacOS::shell_show_in_file_manager(String p_path, bool p_open_folder) {
return OK;
}
-Error OS_MacOS::shell_open(String p_uri) {
+Error OS_MacOS::shell_open(const String &p_uri) {
NSString *string = [NSString stringWithUTF8String:p_uri.utf8().get_data()];
NSURL *uri = [[NSURL alloc] initWithString:string];
if (!uri || !uri.scheme || [uri.scheme isEqual:@"file"]) {
@@ -830,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/rendering_context_driver_vulkan_macos.h b/platform/macos/rendering_context_driver_vulkan_macos.h
index bbc67581db..32f8891a2e 100644
--- a/platform/macos/rendering_context_driver_vulkan_macos.h
+++ b/platform/macos/rendering_context_driver_vulkan_macos.h
@@ -35,7 +35,7 @@
#include "drivers/vulkan/rendering_context_driver_vulkan.h"
-#import <AppKit/AppKit.h>
+#import <QuartzCore/CAMetalLayer.h>
class RenderingContextDriverVulkanMacOS : public RenderingContextDriverVulkan {
private:
@@ -46,7 +46,7 @@ protected:
public:
struct WindowPlatformData {
- const id *view_ptr;
+ CAMetalLayer *const *layer_ptr;
};
RenderingContextDriverVulkanMacOS();
diff --git a/platform/macos/rendering_context_driver_vulkan_macos.mm b/platform/macos/rendering_context_driver_vulkan_macos.mm
index e0f8bf9e67..afefe5a6f7 100644
--- a/platform/macos/rendering_context_driver_vulkan_macos.mm
+++ b/platform/macos/rendering_context_driver_vulkan_macos.mm
@@ -35,22 +35,22 @@
#ifdef USE_VOLK
#include <volk.h>
#else
-#include <vulkan/vulkan.h>
+#include <vulkan/vulkan_metal.h>
#endif
const char *RenderingContextDriverVulkanMacOS::_get_platform_surface_extension() const {
- return VK_MVK_MACOS_SURFACE_EXTENSION_NAME;
+ return VK_EXT_METAL_SURFACE_EXTENSION_NAME;
}
RenderingContextDriver::SurfaceID RenderingContextDriverVulkanMacOS::surface_create(const void *p_platform_data) {
const WindowPlatformData *wpd = (const WindowPlatformData *)(p_platform_data);
- VkMacOSSurfaceCreateInfoMVK create_info = {};
- create_info.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK;
- create_info.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 vk_surface = VK_NULL_HANDLE;
- VkResult err = vkCreateMacOSSurfaceMVK(instance_get(), &create_info, nullptr, &vk_surface);
+ VkResult err = vkCreateMetalSurfaceEXT(instance_get(), &create_info, nullptr, &vk_surface);
ERR_FAIL_COND_V(err != VK_SUCCESS, SurfaceID());
Surface *surface = memnew(Surface);