diff options
Diffstat (limited to 'platform/macos')
-rw-r--r-- | platform/macos/SCsub | 99 | ||||
-rw-r--r-- | platform/macos/detect.py | 82 | ||||
-rw-r--r-- | platform/macos/display_server_macos.h | 27 | ||||
-rw-r--r-- | platform/macos/display_server_macos.mm | 214 | ||||
-rw-r--r-- | platform/macos/export/export_plugin.cpp | 184 | ||||
-rw-r--r-- | platform/macos/godot_application_delegate.h | 2 | ||||
-rw-r--r-- | platform/macos/godot_application_delegate.mm | 67 | ||||
-rw-r--r-- | platform/macos/godot_content_view.h | 2 | ||||
-rw-r--r-- | platform/macos/godot_content_view.mm | 49 | ||||
-rw-r--r-- | platform/macos/godot_open_save_delegate.mm | 74 | ||||
-rw-r--r-- | platform/macos/godot_status_item.h | 51 | ||||
-rw-r--r-- | platform/macos/godot_status_item.mm | 101 | ||||
-rw-r--r-- | platform/macos/godot_window_delegate.mm | 1 | ||||
-rw-r--r-- | platform/macos/os_macos.h | 4 | ||||
-rw-r--r-- | platform/macos/os_macos.mm | 5 | ||||
-rw-r--r-- | platform/macos/rendering_context_driver_vulkan_macos.h | 4 | ||||
-rw-r--r-- | platform/macos/rendering_context_driver_vulkan_macos.mm | 12 |
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); |