diff options
Diffstat (limited to 'platform/osx')
22 files changed, 5039 insertions, 3811 deletions
diff --git a/platform/osx/SCsub b/platform/osx/SCsub index e15b4339a7..ad62db358b 100644 --- a/platform/osx/SCsub +++ b/platform/osx/SCsub @@ -1,22 +1,22 @@ #!/usr/bin/env python -Import('env') +Import("env") from platform_methods import run_in_subprocess import platform_osx_builders files = [ - 'crash_handler_osx.mm', - 'os_osx.mm', - 'godot_main_osx.mm', - 'semaphore_osx.cpp', - 'dir_access_osx.mm', - 'joypad_osx.cpp', - 'power_osx.cpp', + "crash_handler_osx.mm", + "os_osx.mm", + "display_server_osx.mm", + "godot_main_osx.mm", + "dir_access_osx.mm", + "joypad_osx.cpp", + "vulkan_context_osx.mm", + "context_gl_osx.mm", ] -prog = env.add_program('#bin/godot', files) +prog = env.add_program("#bin/godot", files) if (env["debug_symbols"] == "full" or env["debug_symbols"] == "yes") and env["separate_debug_symbols"]: env.AddPostAction(prog, run_in_subprocess(platform_osx_builders.make_debug_osx)) - diff --git a/platform/osx/power_osx.h b/platform/osx/context_gl_osx.h index 6f9b213439..cce00fb35f 100644 --- a/platform/osx/power_osx.h +++ b/platform/osx/context_gl_osx.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* power_osx.h */ +/* context_gl_osx.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,32 +28,47 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef POWER_OSX_H -#define POWER_OSX_H +#ifndef CONTEXT_GL_OSX_H +#define CONTEXT_GL_OSX_H -#include "core/os/file_access.h" +#if defined(OPENGL_ENABLED) || defined(GLES_ENABLED) + +#include "core/error_list.h" #include "core/os/os.h" -#include "dir_access_osx.h" -#include <CoreFoundation/CoreFoundation.h> +#include <AppKit/AppKit.h> +#include <ApplicationServices/ApplicationServices.h> +#include <CoreVideo/CoreVideo.h> -class PowerOSX { +class ContextGL_OSX { + bool opengl_3_context; + bool use_vsync; -private: - int nsecs_left; - int percent_left; - OS::PowerState power_state; - void checkps(CFDictionaryRef dict, bool *have_ac, bool *have_battery, bool *charging); - bool GetPowerInfo_MacOSX(/*PowerState * state, int *seconds, int *percent*/); - bool UpdatePowerInfo(); + void *framework; + id window_view; + NSOpenGLPixelFormat *pixelFormat; + NSOpenGLContext *context; public: - PowerOSX(); - virtual ~PowerOSX(); + void release_current(); + + void make_current(); + void update(); + + void set_opacity(GLint p_opacity); + + int get_window_width(); + int get_window_height(); + void swap_buffers(); + + Error initialize(); + + void set_use_vsync(bool p_use); + bool is_using_vsync() const; - OS::PowerState get_power_state(); - int get_power_seconds_left(); - int get_power_percent_left(); + ContextGL_OSX(id p_view, bool p_opengl_3_context); + ~ContextGL_OSX(); }; -#endif // POWER_OSX_H +#endif +#endif diff --git a/platform/osx/context_gl_osx.mm b/platform/osx/context_gl_osx.mm new file mode 100644 index 0000000000..2319e9eb1f --- /dev/null +++ b/platform/osx/context_gl_osx.mm @@ -0,0 +1,161 @@ +/*************************************************************************/ +/* context_gl_osx.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "context_gl_osx.h" + +#if defined(OPENGL_ENABLED) || defined(GLES_ENABLED) + +void ContextGL_OSX::release_current() { + [NSOpenGLContext clearCurrentContext]; +} + +void ContextGL_OSX::make_current() { + [context makeCurrentContext]; +} + +void ContextGL_OSX::update() { + [context update]; +} + +void ContextGL_OSX::set_opacity(GLint p_opacity) { + [context setValues:&p_opacity forParameter:NSOpenGLCPSurfaceOpacity]; +} + +int ContextGL_OSX::get_window_width() { + return OS::get_singleton()->get_video_mode().width; +} + +int ContextGL_OSX::get_window_height() { + return OS::get_singleton()->get_video_mode().height; +} + +void ContextGL_OSX::swap_buffers() { + [context flushBuffer]; +} + +void ContextGL_OSX::set_use_vsync(bool p_use) { + CGLContextObj ctx = CGLGetCurrentContext(); + if (ctx) { + GLint swapInterval = p_use ? 1 : 0; + CGLSetParameter(ctx, kCGLCPSwapInterval, &swapInterval); + use_vsync = p_use; + } +} + +bool ContextGL_OSX::is_using_vsync() const { + return use_vsync; +} + +Error ContextGL_OSX::initialize() { + framework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl")); + ERR_FAIL_COND_V(!framework, ERR_CANT_CREATE); + + unsigned int attributeCount = 0; + + // OS X needs non-zero color size, so set reasonable values + int colorBits = 32; + + // Fail if a robustness strategy was requested + +#define ADD_ATTR(x) \ + { attributes[attributeCount++] = x; } +#define ADD_ATTR2(x, y) \ + { \ + ADD_ATTR(x); \ + ADD_ATTR(y); \ + } + + // Arbitrary array size here + NSOpenGLPixelFormatAttribute attributes[40]; + + ADD_ATTR(NSOpenGLPFADoubleBuffer); + ADD_ATTR(NSOpenGLPFAClosestPolicy); + + if (!opengl_3_context) { + ADD_ATTR2(NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersionLegacy); + } else { + //we now need OpenGL 3 or better, maybe even change this to 3_3Core ? + ADD_ATTR2(NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core); + } + + ADD_ATTR2(NSOpenGLPFAColorSize, colorBits); + + /* + if (fbconfig->alphaBits > 0) + ADD_ATTR2(NSOpenGLPFAAlphaSize, fbconfig->alphaBits); +*/ + + ADD_ATTR2(NSOpenGLPFADepthSize, 24); + + ADD_ATTR2(NSOpenGLPFAStencilSize, 8); + + /* + if (fbconfig->stereo) + ADD_ATTR(NSOpenGLPFAStereo); +*/ + + /* + if (fbconfig->samples > 0) { + ADD_ATTR2(NSOpenGLPFASampleBuffers, 1); + ADD_ATTR2(NSOpenGLPFASamples, fbconfig->samples); + } +*/ + + // NOTE: All NSOpenGLPixelFormats on the relevant cards support sRGB + // framebuffer, so there's no need (and no way) to request it + + ADD_ATTR(0); + +#undef ADD_ATTR +#undef ADD_ATTR2 + + pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes]; + ERR_FAIL_COND_V(pixelFormat == nil, ERR_CANT_CREATE); + + context = [[NSOpenGLContext alloc] initWithFormat:pixelFormat shareContext:nil]; + + ERR_FAIL_COND_V(context == nil, ERR_CANT_CREATE); + + [context setView:window_view]; + + [context makeCurrentContext]; + + return OK; +} + +ContextGL_OSX::ContextGL_OSX(id p_view, bool p_opengl_3_context) { + opengl_3_context = p_opengl_3_context; + window_view = p_view; + use_vsync = false; +} + +ContextGL_OSX::~ContextGL_OSX() {} + +#endif diff --git a/platform/osx/crash_handler_osx.h b/platform/osx/crash_handler_osx.h index abd9812596..9970f6045a 100644 --- a/platform/osx/crash_handler_osx.h +++ b/platform/osx/crash_handler_osx.h @@ -32,7 +32,6 @@ #define CRASH_HANDLER_OSX_H class CrashHandler { - bool disabled; public: diff --git a/platform/osx/crash_handler_osx.mm b/platform/osx/crash_handler_osx.mm index 5da0118686..9fb2f63935 100644 --- a/platform/osx/crash_handler_osx.mm +++ b/platform/osx/crash_handler_osx.mm @@ -90,7 +90,7 @@ static void handle_crash(int sig) { if (OS::get_singleton()->get_main_loop()) OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_CRASH); - fprintf(stderr, "Dumping the backtrace. %ls\n", msg.c_str()); + fprintf(stderr, "Dumping the backtrace. %s\n", msg.utf8().get_data()); char **strings = backtrace_symbols(bt_buffer, size); if (strings) { void *load_addr = (void *)load_address(); @@ -142,7 +142,7 @@ static void handle_crash(int sig) { } } - fprintf(stderr, "[%zu] %ls\n", i, output.c_str()); + fprintf(stderr, "[%zu] %s\n", i, output.utf8().get_data()); } free(strings); diff --git a/platform/osx/detect.py b/platform/osx/detect.py index fe839199e8..6fc1dc65af 100644 --- a/platform/osx/detect.py +++ b/platform/osx/detect.py @@ -13,7 +13,7 @@ def get_name(): def can_build(): - if (sys.platform == "darwin" or ("OSXCROSS_ROOT" in os.environ)): + if sys.platform == "darwin" or ("OSXCROSS_ROOT" in os.environ): return True return False @@ -23,51 +23,59 @@ def get_opts(): from SCons.Variables import BoolVariable, EnumVariable return [ - ('osxcross_sdk', 'OSXCross SDK version', 'darwin14'), - ('MACOS_SDK_PATH', 'Path to the macOS SDK', ''), - EnumVariable('debug_symbols', 'Add debugging symbols to release builds', 'yes', ('yes', 'no', 'full')), - BoolVariable('separate_debug_symbols', 'Create a separate file containing debugging symbols', False), - BoolVariable('use_ubsan', 'Use LLVM/GCC compiler undefined behavior sanitizer (UBSAN)', False), - BoolVariable('use_asan', 'Use LLVM/GCC compiler address sanitizer (ASAN))', False), - BoolVariable('use_tsan', 'Use LLVM/GCC compiler thread sanitizer (TSAN))', False), + ("osxcross_sdk", "OSXCross SDK version", "darwin16"), + ("MACOS_SDK_PATH", "Path to the macOS SDK", ""), + BoolVariable( + "use_static_mvk", + "Link MoltenVK statically as Level-0 driver (better portability) or use Vulkan ICD loader (enables" + " validation layers)", + False, + ), + EnumVariable("debug_symbols", "Add debugging symbols to release builds", "yes", ("yes", "no", "full")), + BoolVariable("separate_debug_symbols", "Create a separate file containing debugging symbols", False), + BoolVariable("use_ubsan", "Use LLVM/GCC compiler undefined behavior sanitizer (UBSAN)", False), + BoolVariable("use_asan", "Use LLVM/GCC compiler address sanitizer (ASAN))", False), + BoolVariable("use_tsan", "Use LLVM/GCC compiler thread sanitizer (TSAN))", False), ] def get_flags(): - return [ - ] + return [] def configure(env): - ## Build type - - if (env["target"] == "release"): - if (env["optimize"] == "speed"): #optimize for speed (default) - env.Prepend(CCFLAGS=['-O3', '-fomit-frame-pointer', '-ftree-vectorize', '-msse2']) - else: #optimize for size - env.Prepend(CCFLAGS=['-Os','-ftree-vectorize', '-msse2']) - - if (env["debug_symbols"] == "yes"): - env.Prepend(CCFLAGS=['-g1']) - if (env["debug_symbols"] == "full"): - env.Prepend(CCFLAGS=['-g2']) - - elif (env["target"] == "release_debug"): - if (env["optimize"] == "speed"): #optimize for speed (default) - env.Prepend(CCFLAGS=['-O2']) - else: #optimize for size - env.Prepend(CCFLAGS=['-Os']) - env.Prepend(CPPDEFINES=['DEBUG_ENABLED']) - if (env["debug_symbols"] == "yes"): - env.Prepend(CCFLAGS=['-g1']) - if (env["debug_symbols"] == "full"): - env.Prepend(CCFLAGS=['-g2']) - - elif (env["target"] == "debug"): - env.Prepend(CCFLAGS=['-g3']) - env.Prepend(CPPDEFINES=['DEBUG_ENABLED', 'DEBUG_MEMORY_ENABLED']) + ## Build type + + if env["target"] == "release": + if env["optimize"] == "speed": # optimize for speed (default) + env.Prepend(CCFLAGS=["-O3", "-fomit-frame-pointer", "-ftree-vectorize"]) + else: # optimize for size + env.Prepend(CCFLAGS=["-Os", "-ftree-vectorize"]) + if env["arch"] != "arm64": + env.Prepend(CCFLAGS=["-msse2"]) + + if env["debug_symbols"] == "yes": + env.Prepend(CCFLAGS=["-g1"]) + if env["debug_symbols"] == "full": + env.Prepend(CCFLAGS=["-g2"]) + + elif env["target"] == "release_debug": + if env["optimize"] == "speed": # optimize for speed (default) + env.Prepend(CCFLAGS=["-O2"]) + else: # optimize for size + env.Prepend(CCFLAGS=["-Os"]) + env.Prepend(CPPDEFINES=["DEBUG_ENABLED"]) + if env["debug_symbols"] == "yes": + env.Prepend(CCFLAGS=["-g1"]) + if env["debug_symbols"] == "full": + env.Prepend(CCFLAGS=["-g2"]) + + elif env["target"] == "debug": + env.Prepend(CCFLAGS=["-g3"]) + env.Prepend(CPPDEFINES=["DEBUG_ENABLED"]) + env.Prepend(LINKFLAGS=["-Xlinker", "-no_deduplicate"]) ## Architecture @@ -81,76 +89,117 @@ def configure(env): if "OSXCROSS_ROOT" in os.environ: env["osxcross"] = True - if not "osxcross" in env: # regular native build - env.Append(CCFLAGS=['-arch', 'x86_64']) - env.Append(LINKFLAGS=['-arch', 'x86_64']) - if (env["macports_clang"] != 'no'): + if env["arch"] == "arm64": + print("Building for macOS 10.15+, platform arm64.") + env.Append(CCFLAGS=["-arch", "arm64", "-mmacosx-version-min=10.15", "-target", "arm64-apple-macos10.15"]) + env.Append(LINKFLAGS=["-arch", "arm64", "-mmacosx-version-min=10.15", "-target", "arm64-apple-macos10.15"]) + else: + print("Building for macOS 10.12+, platform x86-64.") + env.Append(CCFLAGS=["-arch", "x86_64", "-mmacosx-version-min=10.12"]) + env.Append(LINKFLAGS=["-arch", "x86_64", "-mmacosx-version-min=10.12"]) + + if not "osxcross" in env: # regular native build + if env["macports_clang"] != "no": mpprefix = os.environ.get("MACPORTS_PREFIX", "/opt/local") mpclangver = env["macports_clang"] env["CC"] = mpprefix + "/libexec/llvm-" + mpclangver + "/bin/clang" env["LINK"] = mpprefix + "/libexec/llvm-" + mpclangver + "/bin/clang++" env["CXX"] = mpprefix + "/libexec/llvm-" + mpclangver + "/bin/clang++" - env['AR'] = mpprefix + "/libexec/llvm-" + mpclangver + "/bin/llvm-ar" - env['RANLIB'] = mpprefix + "/libexec/llvm-" + mpclangver + "/bin/llvm-ranlib" - env['AS'] = mpprefix + "/libexec/llvm-" + mpclangver + "/bin/llvm-as" - env.Append(CPPDEFINES=['__MACPORTS__']) #hack to fix libvpx MM256_BROADCASTSI128_SI256 define + env["AR"] = mpprefix + "/libexec/llvm-" + mpclangver + "/bin/llvm-ar" + env["RANLIB"] = mpprefix + "/libexec/llvm-" + mpclangver + "/bin/llvm-ranlib" + env["AS"] = mpprefix + "/libexec/llvm-" + mpclangver + "/bin/llvm-as" + env.Append(CPPDEFINES=["__MACPORTS__"]) # hack to fix libvpx MM256_BROADCASTSI128_SI256 define else: - env['CC'] = 'clang' - env['CXX'] = 'clang++' + env["CC"] = "clang" + env["CXX"] = "clang++" - detect_darwin_sdk_path('osx', env) - env.Append(CCFLAGS=['-isysroot', '$MACOS_SDK_PATH']) - env.Append(LINKFLAGS=['-isysroot', '$MACOS_SDK_PATH']) + detect_darwin_sdk_path("osx", env) + env.Append(CCFLAGS=["-isysroot", "$MACOS_SDK_PATH"]) + env.Append(LINKFLAGS=["-isysroot", "$MACOS_SDK_PATH"]) - else: # osxcross build + else: # osxcross build root = os.environ.get("OSXCROSS_ROOT", 0) - basecmd = root + "/target/bin/x86_64-apple-" + env["osxcross_sdk"] + "-" + if env["arch"] == "arm64": + basecmd = root + "/target/bin/arm64-apple-" + env["osxcross_sdk"] + "-" + else: + basecmd = root + "/target/bin/x86_64-apple-" + env["osxcross_sdk"] + "-" ccache_path = os.environ.get("CCACHE") if ccache_path is None: - env['CC'] = basecmd + "cc" - env['CXX'] = basecmd + "c++" + env["CC"] = basecmd + "cc" + env["CXX"] = basecmd + "c++" else: # there aren't any ccache wrappers available for OS X cross-compile, # to enable caching we need to prepend the path to the ccache binary - env['CC'] = ccache_path + ' ' + basecmd + "cc" - env['CXX'] = ccache_path + ' ' + basecmd + "c++" - env['AR'] = basecmd + "ar" - env['RANLIB'] = basecmd + "ranlib" - env['AS'] = basecmd + "as" - env.Append(CPPDEFINES=['__MACPORTS__']) #hack to fix libvpx MM256_BROADCASTSI128_SI256 define - - if (env["CXX"] == "clang++"): - env.Append(CPPDEFINES=['TYPED_METHOD_BIND']) + env["CC"] = ccache_path + " " + basecmd + "cc" + env["CXX"] = ccache_path + " " + basecmd + "c++" + env["AR"] = basecmd + "ar" + env["RANLIB"] = basecmd + "ranlib" + env["AS"] = basecmd + "as" + env.Append(CPPDEFINES=["__MACPORTS__"]) # hack to fix libvpx MM256_BROADCASTSI128_SI256 define + + if env["CXX"] == "clang++": + env.Append(CPPDEFINES=["TYPED_METHOD_BIND"]) env["CC"] = "clang" env["LINK"] = "clang++" - if env['use_ubsan'] or env['use_asan'] or env['use_tsan']: + if env["use_ubsan"] or env["use_asan"] or env["use_tsan"]: env.extra_suffix += "s" - if env['use_ubsan']: - env.Append(CCFLAGS=['-fsanitize=undefined']) - env.Append(LINKFLAGS=['-fsanitize=undefined']) + if env["use_ubsan"]: + env.Append(CCFLAGS=["-fsanitize=undefined"]) + env.Append(LINKFLAGS=["-fsanitize=undefined"]) - if env['use_asan']: - env.Append(CCFLAGS=['-fsanitize=address']) - env.Append(LINKFLAGS=['-fsanitize=address']) + if env["use_asan"]: + env.Append(CCFLAGS=["-fsanitize=address"]) + env.Append(LINKFLAGS=["-fsanitize=address"]) - if env['use_tsan']: - env.Append(CCFLAGS=['-fsanitize=thread']) - env.Append(LINKFLAGS=['-fsanitize=thread']) + if env["use_tsan"]: + env.Append(CCFLAGS=["-fsanitize=thread"]) + env.Append(LINKFLAGS=["-fsanitize=thread"]) ## Dependencies - if env['builtin_libtheora']: - env["x86_libtheora_opt_gcc"] = True + if env["builtin_libtheora"]: + if env["arch"] != "arm64": + env["x86_libtheora_opt_gcc"] = True ## Flags - env.Prepend(CPPPATH=['#platform/osx']) - env.Append(CPPDEFINES=['OSX_ENABLED', 'UNIX_ENABLED', 'GLES_ENABLED', 'APPLE_STYLE_KEYS', 'COREAUDIO_ENABLED', 'COREMIDI_ENABLED']) - env.Append(LINKFLAGS=['-framework', 'Cocoa', '-framework', 'Carbon', '-framework', 'OpenGL', '-framework', 'AGL', '-framework', 'AudioUnit', '-framework', 'CoreAudio', '-framework', 'CoreMIDI', '-lz', '-framework', 'IOKit', '-framework', 'ForceFeedback', '-framework', 'AVFoundation', '-framework', 'CoreMedia', '-framework', 'CoreVideo']) - env.Append(LIBS=['pthread']) - - env.Append(CCFLAGS=['-mmacosx-version-min=10.9']) - env.Append(LINKFLAGS=['-mmacosx-version-min=10.9']) + env.Prepend(CPPPATH=["#platform/osx"]) + env.Append(CPPDEFINES=["OSX_ENABLED", "UNIX_ENABLED", "APPLE_STYLE_KEYS", "COREAUDIO_ENABLED", "COREMIDI_ENABLED"]) + env.Append( + LINKFLAGS=[ + "-framework", + "Cocoa", + "-framework", + "Carbon", + "-framework", + "AudioUnit", + "-framework", + "CoreAudio", + "-framework", + "CoreMIDI", + "-framework", + "IOKit", + "-framework", + "ForceFeedback", + "-framework", + "CoreVideo", + "-framework", + "AVFoundation", + "-framework", + "CoreMedia", + ] + ) + env.Append(LIBS=["pthread", "z"]) + + env.Append(CPPDEFINES=["VULKAN_ENABLED"]) + env.Append(LINKFLAGS=["-framework", "Metal", "-framework", "QuartzCore", "-framework", "IOSurface"]) + if env["use_static_mvk"]: + env.Append(LINKFLAGS=["-framework", "MoltenVK"]) + env["builtin_vulkan"] = False + elif not env["builtin_vulkan"]: + env.Append(LIBS=["vulkan"]) + + # env.Append(CPPDEFINES=['GLES_ENABLED', 'OPENGL_ENABLED']) diff --git a/platform/osx/dir_access_osx.mm b/platform/osx/dir_access_osx.mm index 66ea380903..7791ba5407 100644 --- a/platform/osx/dir_access_osx.mm +++ b/platform/osx/dir_access_osx.mm @@ -38,7 +38,6 @@ #include <Foundation/Foundation.h> String DirAccessOSX::fix_unicode_name(const char *p_name) const { - String fname; NSString *nsstr = [[NSString stringWithUTF8String:p_name] precomposedStringWithCanonicalMapping]; diff --git a/platform/osx/display_server_osx.h b/platform/osx/display_server_osx.h new file mode 100644 index 0000000000..073d35008b --- /dev/null +++ b/platform/osx/display_server_osx.h @@ -0,0 +1,325 @@ +/*************************************************************************/ +/* display_server_osx.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef DISPLAY_SERVER_OSX_H +#define DISPLAY_SERVER_OSX_H + +#define BitMap _QDBitMap // Suppress deprecated QuickDraw definition. + +#include "core/input/input.h" +#include "servers/display_server.h" + +#if defined(OPENGL_ENABLED) +#include "context_gl_osx.h" +//TODO - reimplement OpenGLES +#endif + +#if defined(VULKAN_ENABLED) +#include "drivers/vulkan/rendering_device_vulkan.h" +#include "platform/osx/vulkan_context_osx.h" +#endif + +#include <AppKit/AppKit.h> +#include <AppKit/NSCursor.h> +#include <ApplicationServices/ApplicationServices.h> +#include <CoreVideo/CoreVideo.h> + +#undef BitMap +#undef CursorShape + +class DisplayServerOSX : public DisplayServer { + GDCLASS(DisplayServerOSX, DisplayServer) + + _THREAD_SAFE_CLASS_ + +public: +#if defined(OPENGL_ENABLED) + ContextGL_OSX *context_gles2; +#endif +#if defined(VULKAN_ENABLED) + VulkanContextOSX *context_vulkan; + RenderingDeviceVulkan *rendering_device_vulkan; +#endif + + const NSMenu *_get_menu_root(const String &p_menu_root) const; + NSMenu *_get_menu_root(const String &p_menu_root); + + NSMenu *apple_menu = nullptr; + NSMenu *dock_menu = nullptr; + Map<String, NSMenu *> submenu; + + struct KeyEvent { + WindowID window_id; + unsigned int osx_state; + bool pressed; + bool echo; + bool raw; + uint32_t keycode; + uint32_t physical_keycode; + uint32_t unicode; + }; + + struct WarpEvent { + NSTimeInterval timestamp; + NSPoint delta; + }; + + List<WarpEvent> warp_events; + NSTimeInterval last_warp = 0; + + Vector<KeyEvent> key_event_buffer; + int key_event_pos; + + struct WindowData { + id window_delegate; + id window_object; + id window_view; + + Vector<Vector2> mpath; + +#if defined(OPENGL_ENABLED) + ContextGL_OSX *context_gles2 = nullptr; +#endif + Point2i mouse_pos; + + Size2i min_size; + Size2i max_size; + Size2i size; + + bool mouse_down_control = false; + + bool im_active = false; + Size2i im_position; + + Callable rect_changed_callback; + Callable event_callback; + Callable input_event_callback; + Callable input_text_callback; + Callable drop_files_callback; + + ObjectID instance_id; + + WindowID transient_parent = INVALID_WINDOW_ID; + Set<WindowID> transient_children; + + bool layered_window = false; + bool fullscreen = false; + bool on_top = false; + bool borderless = false; + bool resize_disabled = false; + bool no_focus = false; + }; + + Point2i im_selection; + String im_text; + + Map<WindowID, WindowData> windows; + + WindowID window_id_counter = MAIN_WINDOW_ID; + + WindowID _create_window(WindowMode p_mode, const Rect2i &p_rect); + void _update_window(WindowData p_wd); + void _send_window_event(const WindowData &wd, WindowEvent p_event); + static void _dispatch_input_events(const Ref<InputEvent> &p_event); + void _dispatch_input_event(const Ref<InputEvent> &p_event); + WindowID _find_window_id(id p_window); + + void _set_window_per_pixel_transparency_enabled(bool p_enabled, WindowID p_window); + + Point2i _get_screens_origin() const; + Point2i _get_native_screen_position(int p_screen) const; + + void _push_input(const Ref<InputEvent> &p_event); + void _process_key_events(); + void _release_pressed_events(); + + String rendering_driver; + + id delegate; + id autoreleasePool; + CGEventSourceRef eventSource; + + CursorShape cursor_shape; + NSCursor *cursors[CURSOR_MAX]; + Map<CursorShape, Vector<Variant>> cursors_cache; + + MouseMode mouse_mode; + Point2i last_mouse_pos; + uint32_t last_button_state; + + bool window_focused; + bool drop_events; + bool in_dispatch_input_event = false; + +public: + virtual bool has_feature(Feature p_feature) const override; + virtual String get_name() const override; + + virtual void global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag = Variant()) override; + virtual void global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag = Variant()) override; + virtual void global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu) override; + virtual void global_menu_add_separator(const String &p_menu_root) override; + + virtual bool global_menu_is_item_checked(const String &p_menu_root, int p_idx) const override; + virtual bool global_menu_is_item_checkable(const String &p_menu_root, int p_idx) const override; + virtual Callable global_menu_get_item_callback(const String &p_menu_root, int p_idx) override; + virtual Variant global_menu_get_item_tag(const String &p_menu_root, int p_idx) override; + virtual String global_menu_get_item_text(const String &p_menu_root, int p_idx) override; + virtual String global_menu_get_item_submenu(const String &p_menu_root, int p_idx) override; + + virtual void global_menu_set_item_checked(const String &p_menu_root, int p_idx, bool p_checked) override; + virtual void global_menu_set_item_checkable(const String &p_menu_root, int p_idx, bool p_checkable) override; + virtual void global_menu_set_item_callback(const String &p_menu_root, int p_idx, const Callable &p_callback) override; + virtual void global_menu_set_item_tag(const String &p_menu_root, int p_idx, const Variant &p_tag) override; + virtual void global_menu_set_item_text(const String &p_menu_root, int p_idx, const String &p_text) override; + virtual void global_menu_set_item_submenu(const String &p_menu_root, int p_idx, const String &p_submenu) override; + + virtual int global_menu_get_item_count(const String &p_menu_root) const override; + + virtual void global_menu_remove_item(const String &p_menu_root, int p_idx) override; + virtual void global_menu_clear(const String &p_menu_root) override; + + virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override; + virtual Error dialog_show(String p_title, String p_description, Vector<String> p_buttons, const Callable &p_callback) override; + virtual Error dialog_input_text(String p_title, String p_description, String p_partial, const Callable &p_callback) override; + + virtual void mouse_set_mode(MouseMode p_mode) override; + virtual MouseMode mouse_get_mode() const override; + + virtual void mouse_warp_to_position(const Point2i &p_to) override; + virtual Point2i mouse_get_position() const override; + virtual Point2i mouse_get_absolute_position() const override; + virtual int mouse_get_button_state() const override; + + virtual void clipboard_set(const String &p_text) override; + virtual String clipboard_get() const override; + + virtual int get_screen_count() const override; + virtual Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + virtual float screen_get_scale(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + virtual float screen_get_max_scale() const override; + virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + + virtual Vector<int> get_window_list() const override; + + virtual WindowID create_sub_window(WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect = Rect2i()) override; + virtual void show_window(WindowID p_id) override; + virtual void delete_sub_window(WindowID p_id) override; + + virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; + virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; + virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; + virtual void window_set_input_text_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; + virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; + + virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) override; + virtual void window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window = MAIN_WINDOW_ID) override; + + virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const override; + virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override; + + virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const override; + virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) override; + + virtual void window_set_transient(WindowID p_window, WindowID p_parent) override; + + virtual void window_set_max_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; + virtual Size2i window_get_max_size(WindowID p_window = MAIN_WINDOW_ID) const override; + + virtual void window_set_min_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; + virtual Size2i window_get_min_size(WindowID p_window = MAIN_WINDOW_ID) const override; + + virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; + virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const override; + virtual Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const override; + + virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID) override; + virtual WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const override; + + virtual bool window_is_maximize_allowed(WindowID p_window = MAIN_WINDOW_ID) const override; + + virtual void window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window = MAIN_WINDOW_ID) override; + virtual bool window_get_flag(WindowFlags p_flag, WindowID p_window = MAIN_WINDOW_ID) const override; + + virtual void window_request_attention(WindowID p_window = MAIN_WINDOW_ID) override; + virtual void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID) override; + + virtual bool window_can_draw(WindowID p_window = MAIN_WINDOW_ID) const override; + + virtual bool can_any_window_draw() const override; + + virtual void window_set_ime_active(const bool p_active, WindowID p_window = MAIN_WINDOW_ID) override; + virtual void window_set_ime_position(const Point2i &p_pos, WindowID p_window = MAIN_WINDOW_ID) override; + + virtual WindowID get_window_at_screen_position(const Point2i &p_position) const override; + + virtual void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID) override; + virtual ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const override; + + virtual Point2i ime_get_selection() const override; + virtual String ime_get_text() const override; + + virtual void cursor_set_shape(CursorShape p_shape) override; + virtual CursorShape cursor_get_shape() const override; + virtual void cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape = CURSOR_ARROW, const Vector2 &p_hotspot = Vector2()) override; + + virtual bool get_swap_cancel_ok() override; + + virtual int keyboard_get_layout_count() const override; + virtual int keyboard_get_current_layout() const override; + virtual void keyboard_set_current_layout(int p_index) override; + virtual String keyboard_get_layout_language(int p_index) const override; + virtual String keyboard_get_layout_name(int p_index) const override; + + virtual void process_events() override; + virtual void force_process_and_drop_events() override; + + virtual void release_rendering_thread() override; + virtual void make_rendering_thread() override; + virtual void swap_buffers() override; + + virtual void set_native_icon(const String &p_filename) override; + virtual void set_icon(const Ref<Image> &p_icon) override; + + virtual void console_set_visible(bool p_enabled) override; + virtual bool is_console_visible() const override; + + static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error); + static Vector<String> get_rendering_drivers_func(); + + static void register_osx_driver(); + + DisplayServerOSX(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error); + ~DisplayServerOSX(); +}; + +#endif // DISPLAY_SERVER_OSX_H diff --git a/platform/osx/display_server_osx.mm b/platform/osx/display_server_osx.mm new file mode 100644 index 0000000000..e33af520c9 --- /dev/null +++ b/platform/osx/display_server_osx.mm @@ -0,0 +1,3875 @@ +/*************************************************************************/ +/* display_server_osx.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "display_server_osx.h" + +#include "os_osx.h" + +#include "core/io/marshalls.h" +#include "core/math/geometry_2d.h" +#include "core/os/keyboard.h" +#include "main/main.h" +#include "scene/resources/texture.h" + +#include <Carbon/Carbon.h> +#include <Cocoa/Cocoa.h> +#include <IOKit/IOCFPlugIn.h> +#include <IOKit/IOKitLib.h> +#include <IOKit/hid/IOHIDKeys.h> +#include <IOKit/hid/IOHIDLib.h> + +#if defined(OPENGL_ENABLED) +//TODO - reimplement OpenGLES + +#import <AppKit/NSOpenGLView.h> +#endif + +#if defined(VULKAN_ENABLED) +#include "servers/rendering/rasterizer_rd/rasterizer_rd.h" + +#include <QuartzCore/CAMetalLayer.h> +#endif + +#ifndef NSAppKitVersionNumber10_14 +#define NSAppKitVersionNumber10_14 1671 +#endif + +#define DS_OSX ((DisplayServerOSX *)(DisplayServerOSX::get_singleton())) + +static void _get_key_modifier_state(unsigned int p_osx_state, Ref<InputEventWithModifiers> r_state) { + r_state->set_shift((p_osx_state & NSEventModifierFlagShift)); + r_state->set_control((p_osx_state & NSEventModifierFlagControl)); + r_state->set_alt((p_osx_state & NSEventModifierFlagOption)); + r_state->set_metakey((p_osx_state & NSEventModifierFlagCommand)); +} + +static Vector2i _get_mouse_pos(DisplayServerOSX::WindowData &p_wd, NSPoint p_locationInWindow) { + const NSRect contentRect = [p_wd.window_view frame]; + const float scale = DS_OSX->screen_get_max_scale(); + p_wd.mouse_pos.x = p_locationInWindow.x * scale; + p_wd.mouse_pos.y = (contentRect.size.height - p_locationInWindow.y) * scale; + DS_OSX->last_mouse_pos = p_wd.mouse_pos; + Input::get_singleton()->set_mouse_position(p_wd.mouse_pos); + return p_wd.mouse_pos; +} + +static void _push_to_key_event_buffer(const DisplayServerOSX::KeyEvent &p_event) { + Vector<DisplayServerOSX::KeyEvent> &buffer = DS_OSX->key_event_buffer; + if (DS_OSX->key_event_pos >= buffer.size()) { + buffer.resize(1 + DS_OSX->key_event_pos); + } + buffer.write[DS_OSX->key_event_pos++] = p_event; +} + +static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) { + if ([NSCursor respondsToSelector:selector]) { + id object = [NSCursor performSelector:selector]; + if ([object isKindOfClass:[NSCursor class]]) { + return object; + } + } + if (fallback) { + // Fallback should be a reasonable default, no need to check. + return [NSCursor performSelector:fallback]; + } + return [NSCursor arrowCursor]; +} + +/*************************************************************************/ +/* GodotApplication */ +/*************************************************************************/ + +@interface GodotApplication : NSApplication +@end + +@implementation GodotApplication + +- (void)sendEvent:(NSEvent *)event { + // special case handling of command-period, which is traditionally a special + // shortcut in macOS and doesn't arrive at our regular keyDown handler. + if ([event type] == NSEventTypeKeyDown) { + if (([event modifierFlags] & NSEventModifierFlagCommand) && [event keyCode] == 0x2f) { + Ref<InputEventKey> k; + k.instance(); + + _get_key_modifier_state([event modifierFlags], k); + k->set_window_id(DisplayServerOSX::INVALID_WINDOW_ID); + k->set_pressed(true); + k->set_keycode(KEY_PERIOD); + k->set_physical_keycode(KEY_PERIOD); + k->set_echo([event isARepeat]); + + Input::get_singleton()->accumulate_input_event(k); + } + } + + // From http://cocoadev.com/index.pl?GameKeyboardHandlingAlmost + // This works around an AppKit bug, where key up events while holding + // down the command key don't get sent to the key window. + if ([event type] == NSEventTypeKeyUp && ([event modifierFlags] & NSEventModifierFlagCommand)) { + [[self keyWindow] sendEvent:event]; + } else { + [super sendEvent:event]; + } +} + +@end + +/*************************************************************************/ +/* GlobalMenuItem */ +/*************************************************************************/ + +@interface GlobalMenuItem : NSObject { +@public + Callable callback; + Variant meta; + bool checkable; +} + +@end + +@implementation GlobalMenuItem +@end + +/*************************************************************************/ +/* GodotApplicationDelegate */ +/*************************************************************************/ + +@interface GodotApplicationDelegate : NSObject +- (void)forceUnbundledWindowActivationHackStep1; +- (void)forceUnbundledWindowActivationHackStep2; +- (void)forceUnbundledWindowActivationHackStep3; +@end + +@implementation GodotApplicationDelegate + +- (void)forceUnbundledWindowActivationHackStep1 { + // Step1: Switch focus to macOS Dock. + // Required to perform step 2, TransformProcessType will fail if app is already the in focus. + for (NSRunningApplication *app in [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.dock"]) { + [app activateWithOptions:NSApplicationActivateIgnoringOtherApps]; + break; + } + [self performSelector:@selector(forceUnbundledWindowActivationHackStep2) withObject:nil afterDelay:0.02]; +} + +- (void)forceUnbundledWindowActivationHackStep2 { + // Step 2: Register app as foreground process. + ProcessSerialNumber psn = { 0, kCurrentProcess }; + (void)TransformProcessType(&psn, kProcessTransformToForegroundApplication); + [self performSelector:@selector(forceUnbundledWindowActivationHackStep3) withObject:nil afterDelay:0.02]; +} + +- (void)forceUnbundledWindowActivationHackStep3 { + // Step 3: Switch focus back to app window. + [[NSRunningApplication currentApplication] activateWithOptions:NSApplicationActivateIgnoringOtherApps]; +} + +- (void)applicationDidFinishLaunching:(NSNotification *)notice { + NSString *nsappname = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"]; + if (nsappname == nil) { + // If executable is not a 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]; + } +} + +- (void)applicationDidResignActive:(NSNotification *)notification { + if (OS_OSX::get_singleton()->get_main_loop()) { + OS_OSX::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT); + } +} + +- (void)applicationDidBecomeActive:(NSNotification *)notification { + if (OS_OSX::get_singleton()->get_main_loop()) { + OS_OSX::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN); + } +} + +- (void)globalMenuCallback:(id)sender { + if (![sender representedObject]) { + return; + } + + GlobalMenuItem *value = [sender representedObject]; + + if (value) { + if (value->checkable) { + if ([sender state] == NSControlStateValueOff) { + [sender setState:NSControlStateValueOn]; + } else { + [sender setState:NSControlStateValueOff]; + } + } + + if (value->callback != Callable()) { + Variant tag = value->meta; + Variant *tagp = &tag; + Variant ret; + Callable::CallError ce; + value->callback.call((const Variant **)&tagp, 1, ret, ce); + } + } +} + +- (NSMenu *)applicationDockMenu:(NSApplication *)sender { + return DS_OSX->dock_menu; +} + +- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename { + // Note: may be called called before main loop init! + char *utfs = strdup([filename UTF8String]); + ((OS_OSX *)(OS_OSX::get_singleton()))->open_with_filename.parse_utf8(utfs); + free(utfs); + +#ifdef TOOLS_ENABLED + // Open new instance + if (OS_OSX::get_singleton()->get_main_loop()) { + List<String> args; + args.push_back(((OS_OSX *)(OS_OSX::get_singleton()))->open_with_filename); + String exec = OS::get_singleton()->get_executable_path(); + + OS::ProcessID pid = 0; + OS::get_singleton()->execute(exec, args, false, &pid); + } +#endif + return YES; +} + +- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender { + DS_OSX->_send_window_event(DS_OSX->windows[DisplayServerOSX::MAIN_WINDOW_ID], DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST); + return NSTerminateCancel; +} + +- (void)showAbout:(id)sender { + if (OS_OSX::get_singleton()->get_main_loop()) { + OS_OSX::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_ABOUT); + } +} + +@end + +/*************************************************************************/ +/* GodotWindowDelegate */ +/*************************************************************************/ + +@interface GodotWindowDelegate : NSObject { + DisplayServerOSX::WindowID window_id; +} + +- (void)windowWillClose:(NSNotification *)notification; +- (void)setWindowID:(DisplayServerOSX::WindowID)wid; + +@end + +@implementation GodotWindowDelegate + +- (void)setWindowID:(DisplayServerOSX::WindowID)wid { + window_id = wid; +} + +- (BOOL)windowShouldClose:(id)sender { + if (!DS_OSX || !DS_OSX->windows.has(window_id)) { + return YES; + } + DS_OSX->_send_window_event(DS_OSX->windows[window_id], DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST); + return NO; +} + +- (void)windowWillClose:(NSNotification *)notification { + if (!DS_OSX || !DS_OSX->windows.has(window_id)) { + return; + } + DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; + + while (wd.transient_children.size()) { + DS_OSX->window_set_transient(wd.transient_children.front()->get(), DisplayServerOSX::INVALID_WINDOW_ID); + } + + if (wd.transient_parent != DisplayServerOSX::INVALID_WINDOW_ID) { + DisplayServerOSX::WindowData &pwd = DS_OSX->windows[wd.transient_parent]; + [pwd.window_object makeKeyAndOrderFront:nil]; // Move focus back to parent. + DS_OSX->window_set_transient(window_id, DisplayServerOSX::INVALID_WINDOW_ID); + } else if ((window_id != DisplayServerOSX::MAIN_WINDOW_ID) && (DS_OSX->windows.size() == 1)) { + DisplayServerOSX::WindowData &pwd = DS_OSX->windows[DisplayServerOSX::MAIN_WINDOW_ID]; + [pwd.window_object makeKeyAndOrderFront:nil]; // Move focus back to main window if there is no parent or other windows left. + } + +#if defined(OPENGL_ENABLED) + if (DS_OSX->rendering_driver == "opengl_es") { + //TODO - reimplement OpenGLES + } +#endif +#ifdef VULKAN_ENABLED + if (DS_OSX->rendering_driver == "vulkan") { + DS_OSX->context_vulkan->window_destroy(window_id); + } +#endif + + DS_OSX->windows.erase(window_id); +} + +- (void)windowDidEnterFullScreen:(NSNotification *)notification { + if (!DS_OSX || !DS_OSX->windows.has(window_id)) { + return; + } + DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; + + wd.fullscreen = true; + + [wd.window_object setContentMinSize:NSMakeSize(0, 0)]; + [wd.window_object setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)]; +} + +- (void)windowDidExitFullScreen:(NSNotification *)notification { + if (!DS_OSX || !DS_OSX->windows.has(window_id)) { + return; + } + DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; + + wd.fullscreen = false; + + const float scale = DS_OSX->screen_get_max_scale(); + if (wd.min_size != Size2i()) { + Size2i size = wd.min_size / scale; + [wd.window_object setContentMinSize:NSMakeSize(size.x, size.y)]; + } + if (wd.max_size != Size2i()) { + Size2i size = wd.max_size / scale; + [wd.window_object setContentMaxSize:NSMakeSize(size.x, size.y)]; + } + + if (wd.resize_disabled) { + [wd.window_object setStyleMask:[wd.window_object styleMask] & ~NSWindowStyleMaskResizable]; + } +} + +- (void)windowDidChangeBackingProperties:(NSNotification *)notification { + if (!DisplayServerOSX::get_singleton()) { + return; + } + + ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); + DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; + + CGFloat newBackingScaleFactor = [wd.window_object backingScaleFactor]; + CGFloat oldBackingScaleFactor = [[[notification userInfo] objectForKey:@"NSBackingPropertyOldScaleFactorKey"] doubleValue]; + + if (newBackingScaleFactor != oldBackingScaleFactor) { + //Set new display scale and window size + const float scale = DS_OSX->screen_get_max_scale(); + const NSRect contentRect = [wd.window_view frame]; + + wd.size.width = contentRect.size.width * scale; + wd.size.height = contentRect.size.height * scale; + + DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_DPI_CHANGE); + + CALayer *layer = [wd.window_view layer]; + if (layer) { + layer.contentsScale = scale; + } + + //Force window resize event + [self windowDidResize:notification]; + } +} + +- (void)windowDidResize:(NSNotification *)notification { + if (!DS_OSX || !DS_OSX->windows.has(window_id)) { + return; + } + DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; + + const NSRect contentRect = [wd.window_view frame]; + + const float scale = DS_OSX->screen_get_max_scale(); + wd.size.width = contentRect.size.width * scale; + wd.size.height = contentRect.size.height * scale; + + CALayer *layer = [wd.window_view layer]; + if (layer) { + layer.contentsScale = scale; + } + +#if defined(OPENGL_ENABLED) + if (DS_OSX->rendering_driver == "opengl_es") { + //TODO - reimplement OpenGLES + } +#endif +#if defined(VULKAN_ENABLED) + if (DS_OSX->rendering_driver == "vulkan") { + DS_OSX->context_vulkan->window_resize(window_id, wd.size.width, wd.size.height); + } +#endif + + if (!wd.rect_changed_callback.is_null()) { + Variant size = Rect2i(DS_OSX->window_get_position(window_id), DS_OSX->window_get_size(window_id)); + Variant *sizep = &size; + Variant ret; + Callable::CallError ce; + wd.rect_changed_callback.call((const Variant **)&sizep, 1, ret, ce); + } + + if (OS_OSX::get_singleton()->get_main_loop()) { + Main::force_redraw(); + //Event retrieval blocks until resize is over. Call Main::iteration() directly. + if (!Main::is_iterating()) { //avoid cyclic loop + Main::iteration(); + } + } +} + +- (void)windowDidMove:(NSNotification *)notification { + if (!DS_OSX || !DS_OSX->windows.has(window_id)) { + return; + } + DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; + + DS_OSX->_release_pressed_events(); + + if (!wd.rect_changed_callback.is_null()) { + Variant size = Rect2i(DS_OSX->window_get_position(window_id), DS_OSX->window_get_size(window_id)); + Variant *sizep = &size; + Variant ret; + Callable::CallError ce; + wd.rect_changed_callback.call((const Variant **)&sizep, 1, ret, ce); + } +} + +- (void)windowDidBecomeKey:(NSNotification *)notification { + if (!DS_OSX || !DS_OSX->windows.has(window_id)) { + return; + } + DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; + + _get_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream]); + Input::get_singleton()->set_mouse_position(wd.mouse_pos); + + DS_OSX->window_focused = true; + DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_FOCUS_IN); +} + +- (void)windowDidResignKey:(NSNotification *)notification { + if (!DS_OSX || !DS_OSX->windows.has(window_id)) { + return; + } + DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; + + DS_OSX->window_focused = false; + + DS_OSX->_release_pressed_events(); + DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_FOCUS_OUT); +} + +- (void)windowDidMiniaturize:(NSNotification *)notification { + if (!DS_OSX || !DS_OSX->windows.has(window_id)) { + return; + } + DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; + + DS_OSX->window_focused = false; + + DS_OSX->_release_pressed_events(); + DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_FOCUS_OUT); +} + +- (void)windowDidDeminiaturize:(NSNotification *)notification { + if (!DS_OSX || !DS_OSX->windows.has(window_id)) { + return; + } + DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; + + DS_OSX->window_focused = true; + DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_FOCUS_IN); +} + +@end + +/*************************************************************************/ +/* GodotContentView */ +/*************************************************************************/ + +@interface GodotContentView : NSView <NSTextInputClient> { + DisplayServerOSX::WindowID window_id; + NSTrackingArea *trackingArea; + NSMutableAttributedString *markedText; + bool imeInputEventInProgress; +} + +- (void)cancelComposition; +- (CALayer *)makeBackingLayer; +- (BOOL)wantsUpdateLayer; +- (void)updateLayer; +- (void)setWindowID:(DisplayServerOSX::WindowID)wid; + +@end + +@implementation GodotContentView + +- (void)setWindowID:(DisplayServerOSX::WindowID)wid { + window_id = wid; +} + ++ (void)initialize { + if (self == [GodotContentView class]) { + // nothing left to do here at the moment.. + } +} + +- (CALayer *)makeBackingLayer { +#if defined(OPENGL_ENABLED) + if (DS_OSX->rendering_driver == "opengl_es") { + CALayer *layer = [[NSOpenGLLayer class] layer]; + return layer; + } +#endif +#if defined(VULKAN_ENABLED) + if (DS_OSX->rendering_driver == "vulkan") { + CALayer *layer = [[CAMetalLayer class] layer]; + return layer; + } +#endif + return [super makeBackingLayer]; +} + +- (void)updateLayer { +#if defined(OPENGL_ENABLED) + if (DS_OSX->rendering_driver == "opengl_es") { + [super updateLayer]; + //TODO - reimplement OpenGLES + } +#endif +#if defined(VULKAN_ENABLED) + if (DS_OSX->rendering_driver == "vulkan") { + [super updateLayer]; + } +#endif +} + +- (BOOL)wantsUpdateLayer { + return YES; +} + +- (id)init { + self = [super init]; + trackingArea = nil; + imeInputEventInProgress = false; + [self updateTrackingAreas]; +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101400 + [self registerForDraggedTypes:[NSArray arrayWithObject:NSPasteboardTypeFileURL]]; +#else + [self registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]]; +#endif + markedText = [[NSMutableAttributedString alloc] init]; + return self; +} + +- (void)dealloc { + [trackingArea release]; + [markedText release]; + [super dealloc]; +} + +static const NSRange kEmptyRange = { NSNotFound, 0 }; + +- (BOOL)hasMarkedText { + return (markedText.length > 0); +} + +- (NSRange)markedRange { + return NSMakeRange(0, markedText.length); +} + +- (NSRange)selectedRange { + return kEmptyRange; +} + +- (void)setMarkedText:(id)aString selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange { + if ([aString isKindOfClass:[NSAttributedString class]]) { + [markedText initWithAttributedString:aString]; + } else { + [markedText initWithString:aString]; + } + if (markedText.length == 0) { + [self unmarkText]; + return; + } + + ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); + DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; + + if (wd.im_active) { + imeInputEventInProgress = true; + DS_OSX->im_text.parse_utf8([[markedText mutableString] UTF8String]); + DS_OSX->im_selection = Point2i(selectedRange.location, selectedRange.length); + + OS_OSX::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE); + } +} + +- (void)doCommandBySelector:(SEL)aSelector { + if ([self respondsToSelector:aSelector]) { + [self performSelector:aSelector]; + } +} + +- (void)unmarkText { + imeInputEventInProgress = false; + [[markedText mutableString] setString:@""]; + + ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); + DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; + + if (wd.im_active) { + DS_OSX->im_text = String(); + DS_OSX->im_selection = Point2i(); + + OS_OSX::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE); + } +} + +- (NSArray *)validAttributesForMarkedText { + return [NSArray array]; +} + +- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange { + return nil; +} + +- (NSUInteger)characterIndexForPoint:(NSPoint)aPoint { + return 0; +} + +- (NSRect)firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange { + ERR_FAIL_COND_V(!DS_OSX->windows.has(window_id), NSMakeRect(0, 0, 0, 0)); + DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; + + const NSRect contentRect = [wd.window_view frame]; + const float scale = DS_OSX->screen_get_max_scale(); + NSRect pointInWindowRect = NSMakeRect(wd.im_position.x / scale, contentRect.size.height - (wd.im_position.y / scale) - 1, 0, 0); + NSPoint pointOnScreen = [wd.window_object convertRectToScreen:pointInWindowRect].origin; + + return NSMakeRect(pointOnScreen.x, pointOnScreen.y, 0, 0); +} + +- (void)cancelComposition { + [self unmarkText]; + NSTextInputContext *currentInputContext = [NSTextInputContext currentInputContext]; + [currentInputContext discardMarkedText]; +} + +- (void)insertText:(id)aString { + [self insertText:aString replacementRange:NSMakeRange(0, 0)]; +} + +- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange { + NSEvent *event = [NSApp currentEvent]; + + NSString *characters; + if ([aString isKindOfClass:[NSAttributedString class]]) { + characters = [aString string]; + } else { + characters = (NSString *)aString; + } + + NSUInteger i, length = [characters length]; + + NSCharacterSet *ctrlChars = [NSCharacterSet controlCharacterSet]; + NSCharacterSet *wsnlChars = [NSCharacterSet whitespaceAndNewlineCharacterSet]; + if ([characters rangeOfCharacterFromSet:ctrlChars].length && [characters rangeOfCharacterFromSet:wsnlChars].length == 0) { + NSTextInputContext *currentInputContext = [NSTextInputContext currentInputContext]; + [currentInputContext discardMarkedText]; + [self cancelComposition]; + return; + } + + for (i = 0; i < length; i++) { + const unichar codepoint = [characters characterAtIndex:i]; + if ((codepoint & 0xFF00) == 0xF700) { + continue; + } + + DisplayServerOSX::KeyEvent ke; + + ke.window_id = window_id; + ke.osx_state = [event modifierFlags]; + ke.pressed = true; + ke.echo = false; + ke.raw = false; // IME input event + ke.keycode = 0; + ke.physical_keycode = 0; + ke.unicode = codepoint; + + _push_to_key_event_buffer(ke); + } + [self cancelComposition]; +} + +- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender { + return NSDragOperationCopy; +} + +- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender { + return NSDragOperationCopy; +} + +- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender { + ERR_FAIL_COND_V(!DS_OSX->windows.has(window_id), NO); + DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; + + NSPasteboard *pboard = [sender draggingPasteboard]; +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101400 + NSArray<NSURL *> *filenames = [pboard propertyListForType:NSPasteboardTypeFileURL]; +#else + NSArray *filenames = [pboard propertyListForType:NSFilenamesPboardType]; +#endif + + Vector<String> files; + for (NSUInteger i = 0; i < filenames.count; i++) { +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101400 + NSString *ns = [[filenames objectAtIndex:i] path]; +#else + NSString *ns = [filenames objectAtIndex:i]; +#endif + char *utfs = strdup([ns UTF8String]); + String ret; + ret.parse_utf8(utfs); + free(utfs); + files.push_back(ret); + } + + if (!wd.drop_files_callback.is_null()) { + Variant v = files; + Variant *vp = &v; + Variant ret; + Callable::CallError ce; + wd.drop_files_callback.call((const Variant **)&vp, 1, ret, ce); + } + + return NO; +} + +- (BOOL)isOpaque { + return YES; +} + +- (BOOL)canBecomeKeyView { + if (DS_OSX->windows.has(window_id)) { + DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; + if (wd.no_focus) { + return NO; + } + } + return YES; +} + +- (BOOL)acceptsFirstResponder { + return YES; +} + +- (void)cursorUpdate:(NSEvent *)event { + DisplayServer::CursorShape p_shape = DS_OSX->cursor_shape; + DS_OSX->cursor_shape = DisplayServer::CURSOR_MAX; + DS_OSX->cursor_set_shape(p_shape); +} + +static void _mouseDownEvent(DisplayServer::WindowID window_id, NSEvent *event, int index, int mask, bool pressed) { + ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); + DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; + + if (pressed) { + DS_OSX->last_button_state |= mask; + } else { + DS_OSX->last_button_state &= ~mask; + } + + Ref<InputEventMouseButton> mb; + mb.instance(); + mb->set_window_id(window_id); + const Vector2 pos = _get_mouse_pos(wd, [event locationInWindow]); + _get_key_modifier_state([event modifierFlags], mb); + mb->set_button_index(index); + mb->set_pressed(pressed); + mb->set_position(pos); + mb->set_global_position(pos); + mb->set_button_mask(DS_OSX->last_button_state); + if (index == BUTTON_LEFT && pressed) { + mb->set_doubleclick([event clickCount] == 2); + } + + Input::get_singleton()->accumulate_input_event(mb); +} + +- (void)mouseDown:(NSEvent *)event { + ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); + DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; + + if (([event modifierFlags] & NSEventModifierFlagControl)) { + wd.mouse_down_control = true; + _mouseDownEvent(window_id, event, BUTTON_RIGHT, BUTTON_MASK_RIGHT, true); + } else { + wd.mouse_down_control = false; + _mouseDownEvent(window_id, event, BUTTON_LEFT, BUTTON_MASK_LEFT, true); + } +} + +- (void)mouseDragged:(NSEvent *)event { + [self mouseMoved:event]; +} + +- (void)mouseUp:(NSEvent *)event { + ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); + DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; + + if (wd.mouse_down_control) { + _mouseDownEvent(window_id, event, BUTTON_RIGHT, BUTTON_MASK_RIGHT, false); + } else { + _mouseDownEvent(window_id, event, BUTTON_LEFT, BUTTON_MASK_LEFT, false); + } +} + +- (void)mouseMoved:(NSEvent *)event { + ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); + DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; + + NSPoint delta = NSMakePoint([event deltaX], [event deltaY]); + NSPoint mpos = [event locationInWindow]; + + if (DS_OSX->mouse_mode == DisplayServer::MOUSE_MODE_CONFINED) { + // Discard late events + if (([event timestamp]) < DS_OSX->last_warp) { + return; + } + + // Warp affects next event delta, subtract previous warp deltas + List<DisplayServerOSX::WarpEvent>::Element *F = DS_OSX->warp_events.front(); + while (F) { + if (F->get().timestamp < [event timestamp]) { + List<DisplayServerOSX::WarpEvent>::Element *E = F; + delta.x -= E->get().delta.x; + delta.y -= E->get().delta.y; + F = F->next(); + DS_OSX->warp_events.erase(E); + } else { + F = F->next(); + } + } + + // Confine mouse position to the window, and update delta + NSRect frame = [wd.window_object frame]; + NSPoint conf_pos = mpos; + conf_pos.x = CLAMP(conf_pos.x + delta.x, 0.f, frame.size.width); + conf_pos.y = CLAMP(conf_pos.y - delta.y, 0.f, frame.size.height); + delta.x = conf_pos.x - mpos.x; + delta.y = mpos.y - conf_pos.y; + mpos = conf_pos; + + // Move mouse cursor + NSRect pointInWindowRect = NSMakeRect(conf_pos.x, conf_pos.y, 0, 0); + conf_pos = [[wd.window_view window] convertRectToScreen:pointInWindowRect].origin; + conf_pos.y = CGDisplayBounds(CGMainDisplayID()).size.height - conf_pos.y; + CGWarpMouseCursorPosition(conf_pos); + + // Save warp data + DS_OSX->last_warp = [[NSProcessInfo processInfo] systemUptime]; + DisplayServerOSX::WarpEvent ev; + ev.timestamp = DS_OSX->last_warp; + ev.delta = delta; + DS_OSX->warp_events.push_back(ev); + } + + Ref<InputEventMouseMotion> mm; + mm.instance(); + + mm->set_window_id(window_id); + mm->set_button_mask(DS_OSX->last_button_state); + const Vector2i pos = _get_mouse_pos(wd, mpos); + mm->set_position(pos); + mm->set_pressure([event pressure]); + if ([event subtype] == NSEventSubtypeTabletPoint) { + const NSPoint p = [event tilt]; + mm->set_tilt(Vector2(p.x, p.y)); + } + mm->set_global_position(pos); + mm->set_speed(Input::get_singleton()->get_last_mouse_speed()); + const Vector2i relativeMotion = Vector2i(delta.x, delta.y) * DS_OSX->screen_get_max_scale(); + mm->set_relative(relativeMotion); + _get_key_modifier_state([event modifierFlags], mm); + + Input::get_singleton()->set_mouse_position(wd.mouse_pos); + Input::get_singleton()->accumulate_input_event(mm); +} + +- (void)rightMouseDown:(NSEvent *)event { + _mouseDownEvent(window_id, event, BUTTON_RIGHT, BUTTON_MASK_RIGHT, true); +} + +- (void)rightMouseDragged:(NSEvent *)event { + [self mouseMoved:event]; +} + +- (void)rightMouseUp:(NSEvent *)event { + _mouseDownEvent(window_id, event, BUTTON_RIGHT, BUTTON_MASK_RIGHT, false); +} + +- (void)otherMouseDown:(NSEvent *)event { + if ((int)[event buttonNumber] == 2) { + _mouseDownEvent(window_id, event, BUTTON_MIDDLE, BUTTON_MASK_MIDDLE, true); + } else if ((int)[event buttonNumber] == 3) { + _mouseDownEvent(window_id, event, BUTTON_XBUTTON1, BUTTON_MASK_XBUTTON1, true); + } else if ((int)[event buttonNumber] == 4) { + _mouseDownEvent(window_id, event, BUTTON_XBUTTON2, BUTTON_MASK_XBUTTON2, true); + } else { + return; + } +} + +- (void)otherMouseDragged:(NSEvent *)event { + [self mouseMoved:event]; +} + +- (void)otherMouseUp:(NSEvent *)event { + if ((int)[event buttonNumber] == 2) { + _mouseDownEvent(window_id, event, BUTTON_MIDDLE, BUTTON_MASK_MIDDLE, false); + } else if ((int)[event buttonNumber] == 3) { + _mouseDownEvent(window_id, event, BUTTON_XBUTTON1, BUTTON_MASK_XBUTTON1, false); + } else if ((int)[event buttonNumber] == 4) { + _mouseDownEvent(window_id, event, BUTTON_XBUTTON2, BUTTON_MASK_XBUTTON2, false); + } else { + return; + } +} + +- (void)mouseExited:(NSEvent *)event { + ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); + DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; + + if (DS_OSX->mouse_mode != DisplayServer::MOUSE_MODE_CAPTURED) { + DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_MOUSE_EXIT); + } +} + +- (void)mouseEntered:(NSEvent *)event { + ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); + DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; + + if (DS_OSX->mouse_mode != DisplayServer::MOUSE_MODE_CAPTURED) { + DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_MOUSE_ENTER); + } + + DisplayServer::CursorShape p_shape = DS_OSX->cursor_shape; + DS_OSX->cursor_shape = DisplayServer::CURSOR_MAX; + DS_OSX->cursor_set_shape(p_shape); +} + +- (void)magnifyWithEvent:(NSEvent *)event { + ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); + DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; + + Ref<InputEventMagnifyGesture> ev; + ev.instance(); + ev->set_window_id(window_id); + _get_key_modifier_state([event modifierFlags], ev); + ev->set_position(_get_mouse_pos(wd, [event locationInWindow])); + ev->set_factor([event magnification] + 1.0); + + Input::get_singleton()->accumulate_input_event(ev); +} + +- (void)viewDidChangeBackingProperties { + // nothing left to do here +} + +- (void)updateTrackingAreas { + if (trackingArea != nil) { + [self removeTrackingArea:trackingArea]; + [trackingArea release]; + } + + NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | NSTrackingActiveInKeyWindow | NSTrackingCursorUpdate | NSTrackingInVisibleRect; + trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] options:options owner:self userInfo:nil]; + + [self addTrackingArea:trackingArea]; + [super updateTrackingAreas]; +} + +static bool isNumpadKey(unsigned int key) { + static const unsigned int table[] = { + 0x41, /* kVK_ANSI_KeypadDecimal */ + 0x43, /* kVK_ANSI_KeypadMultiply */ + 0x45, /* kVK_ANSI_KeypadPlus */ + 0x47, /* kVK_ANSI_KeypadClear */ + 0x4b, /* kVK_ANSI_KeypadDivide */ + 0x4c, /* kVK_ANSI_KeypadEnter */ + 0x4e, /* kVK_ANSI_KeypadMinus */ + 0x51, /* kVK_ANSI_KeypadEquals */ + 0x52, /* kVK_ANSI_Keypad0 */ + 0x53, /* kVK_ANSI_Keypad1 */ + 0x54, /* kVK_ANSI_Keypad2 */ + 0x55, /* kVK_ANSI_Keypad3 */ + 0x56, /* kVK_ANSI_Keypad4 */ + 0x57, /* kVK_ANSI_Keypad5 */ + 0x58, /* kVK_ANSI_Keypad6 */ + 0x59, /* kVK_ANSI_Keypad7 */ + 0x5b, /* kVK_ANSI_Keypad8 */ + 0x5c, /* kVK_ANSI_Keypad9 */ + 0x5f, /* kVK_JIS_KeypadComma */ + 0x00 + }; + for (int i = 0; table[i] != 0; i++) { + if (key == table[i]) { + return true; + } + } + return false; +} + +// Translates a OS X keycode to a Godot keycode +// +static int translateKey(unsigned int key) { + // Keyboard symbol translation table + static const unsigned int table[128] = { + /* 00 */ KEY_A, + /* 01 */ KEY_S, + /* 02 */ KEY_D, + /* 03 */ KEY_F, + /* 04 */ KEY_H, + /* 05 */ KEY_G, + /* 06 */ KEY_Z, + /* 07 */ KEY_X, + /* 08 */ KEY_C, + /* 09 */ KEY_V, + /* 0a */ KEY_SECTION, /* ISO Section */ + /* 0b */ KEY_B, + /* 0c */ KEY_Q, + /* 0d */ KEY_W, + /* 0e */ KEY_E, + /* 0f */ KEY_R, + /* 10 */ KEY_Y, + /* 11 */ KEY_T, + /* 12 */ KEY_1, + /* 13 */ KEY_2, + /* 14 */ KEY_3, + /* 15 */ KEY_4, + /* 16 */ KEY_6, + /* 17 */ KEY_5, + /* 18 */ KEY_EQUAL, + /* 19 */ KEY_9, + /* 1a */ KEY_7, + /* 1b */ KEY_MINUS, + /* 1c */ KEY_8, + /* 1d */ KEY_0, + /* 1e */ KEY_BRACERIGHT, + /* 1f */ KEY_O, + /* 20 */ KEY_U, + /* 21 */ KEY_BRACELEFT, + /* 22 */ KEY_I, + /* 23 */ KEY_P, + /* 24 */ KEY_ENTER, + /* 25 */ KEY_L, + /* 26 */ KEY_J, + /* 27 */ KEY_APOSTROPHE, + /* 28 */ KEY_K, + /* 29 */ KEY_SEMICOLON, + /* 2a */ KEY_BACKSLASH, + /* 2b */ KEY_COMMA, + /* 2c */ KEY_SLASH, + /* 2d */ KEY_N, + /* 2e */ KEY_M, + /* 2f */ KEY_PERIOD, + /* 30 */ KEY_TAB, + /* 31 */ KEY_SPACE, + /* 32 */ KEY_QUOTELEFT, + /* 33 */ KEY_BACKSPACE, + /* 34 */ KEY_UNKNOWN, + /* 35 */ KEY_ESCAPE, + /* 36 */ KEY_META, + /* 37 */ KEY_META, + /* 38 */ KEY_SHIFT, + /* 39 */ KEY_CAPSLOCK, + /* 3a */ KEY_ALT, + /* 3b */ KEY_CONTROL, + /* 3c */ KEY_SHIFT, + /* 3d */ KEY_ALT, + /* 3e */ KEY_CONTROL, + /* 3f */ KEY_UNKNOWN, /* Function */ + /* 40 */ KEY_UNKNOWN, /* F17 */ + /* 41 */ KEY_KP_PERIOD, + /* 42 */ KEY_UNKNOWN, + /* 43 */ KEY_KP_MULTIPLY, + /* 44 */ KEY_UNKNOWN, + /* 45 */ KEY_KP_ADD, + /* 46 */ KEY_UNKNOWN, + /* 47 */ KEY_NUMLOCK, /* Really KeypadClear... */ + /* 48 */ KEY_VOLUMEUP, /* VolumeUp */ + /* 49 */ KEY_VOLUMEDOWN, /* VolumeDown */ + /* 4a */ KEY_VOLUMEMUTE, /* Mute */ + /* 4b */ KEY_KP_DIVIDE, + /* 4c */ KEY_KP_ENTER, + /* 4d */ KEY_UNKNOWN, + /* 4e */ KEY_KP_SUBTRACT, + /* 4f */ KEY_UNKNOWN, /* F18 */ + /* 50 */ KEY_UNKNOWN, /* F19 */ + /* 51 */ KEY_EQUAL, /* KeypadEqual */ + /* 52 */ KEY_KP_0, + /* 53 */ KEY_KP_1, + /* 54 */ KEY_KP_2, + /* 55 */ KEY_KP_3, + /* 56 */ KEY_KP_4, + /* 57 */ KEY_KP_5, + /* 58 */ KEY_KP_6, + /* 59 */ KEY_KP_7, + /* 5a */ KEY_UNKNOWN, /* F20 */ + /* 5b */ KEY_KP_8, + /* 5c */ KEY_KP_9, + /* 5d */ KEY_YEN, /* JIS Yen */ + /* 5e */ KEY_UNDERSCORE, /* JIS Underscore */ + /* 5f */ KEY_COMMA, /* JIS KeypadComma */ + /* 60 */ KEY_F5, + /* 61 */ KEY_F6, + /* 62 */ KEY_F7, + /* 63 */ KEY_F3, + /* 64 */ KEY_F8, + /* 65 */ KEY_F9, + /* 66 */ KEY_UNKNOWN, /* JIS Eisu */ + /* 67 */ KEY_F11, + /* 68 */ KEY_UNKNOWN, /* JIS Kana */ + /* 69 */ KEY_F13, + /* 6a */ KEY_F16, + /* 6b */ KEY_F14, + /* 6c */ KEY_UNKNOWN, + /* 6d */ KEY_F10, + /* 6e */ KEY_MENU, + /* 6f */ KEY_F12, + /* 70 */ KEY_UNKNOWN, + /* 71 */ KEY_F15, + /* 72 */ KEY_INSERT, /* Really Help... */ + /* 73 */ KEY_HOME, + /* 74 */ KEY_PAGEUP, + /* 75 */ KEY_DELETE, + /* 76 */ KEY_F4, + /* 77 */ KEY_END, + /* 78 */ KEY_F2, + /* 79 */ KEY_PAGEDOWN, + /* 7a */ KEY_F1, + /* 7b */ KEY_LEFT, + /* 7c */ KEY_RIGHT, + /* 7d */ KEY_DOWN, + /* 7e */ KEY_UP, + /* 7f */ KEY_UNKNOWN, + }; + + if (key >= 128) { + return KEY_UNKNOWN; + } + + return table[key]; +} + +struct _KeyCodeMap { + UniChar kchar; + int kcode; +}; + +static const _KeyCodeMap _keycodes[55] = { + { '`', KEY_QUOTELEFT }, + { '~', KEY_ASCIITILDE }, + { '0', KEY_0 }, + { '1', KEY_1 }, + { '2', KEY_2 }, + { '3', KEY_3 }, + { '4', KEY_4 }, + { '5', KEY_5 }, + { '6', KEY_6 }, + { '7', KEY_7 }, + { '8', KEY_8 }, + { '9', KEY_9 }, + { '-', KEY_MINUS }, + { '_', KEY_UNDERSCORE }, + { '=', KEY_EQUAL }, + { '+', KEY_PLUS }, + { 'q', KEY_Q }, + { 'w', KEY_W }, + { 'e', KEY_E }, + { 'r', KEY_R }, + { 't', KEY_T }, + { 'y', KEY_Y }, + { 'u', KEY_U }, + { 'i', KEY_I }, + { 'o', KEY_O }, + { 'p', KEY_P }, + { '[', KEY_BRACELEFT }, + { ']', KEY_BRACERIGHT }, + { '{', KEY_BRACELEFT }, + { '}', KEY_BRACERIGHT }, + { 'a', KEY_A }, + { 's', KEY_S }, + { 'd', KEY_D }, + { 'f', KEY_F }, + { 'g', KEY_G }, + { 'h', KEY_H }, + { 'j', KEY_J }, + { 'k', KEY_K }, + { 'l', KEY_L }, + { ';', KEY_SEMICOLON }, + { ':', KEY_COLON }, + { '\'', KEY_APOSTROPHE }, + { '\"', KEY_QUOTEDBL }, + { '\\', KEY_BACKSLASH }, + { '#', KEY_NUMBERSIGN }, + { 'z', KEY_Z }, + { 'x', KEY_X }, + { 'c', KEY_C }, + { 'v', KEY_V }, + { 'b', KEY_B }, + { 'n', KEY_N }, + { 'm', KEY_M }, + { ',', KEY_COMMA }, + { '.', KEY_PERIOD }, + { '/', KEY_SLASH } +}; + +static int remapKey(unsigned int key, unsigned int state) { + if (isNumpadKey(key)) { + return translateKey(key); + } + + TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource(); + if (!currentKeyboard) { + return translateKey(key); + } + + CFDataRef layoutData = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData); + if (!layoutData) { + return translateKey(key); + } + + const UCKeyboardLayout *keyboardLayout = (const UCKeyboardLayout *)CFDataGetBytePtr(layoutData); + + UInt32 keysDown = 0; + UniChar chars[4]; + UniCharCount realLength; + + OSStatus err = UCKeyTranslate(keyboardLayout, + key, + kUCKeyActionDisplay, + (state >> 8) & 0xFF, + LMGetKbdType(), + kUCKeyTranslateNoDeadKeysBit, + &keysDown, + sizeof(chars) / sizeof(chars[0]), + &realLength, + chars); + + if (err != noErr) { + return translateKey(key); + } + + for (unsigned int i = 0; i < 55; i++) { + if (_keycodes[i].kchar == chars[0]) { + return _keycodes[i].kcode; + } + } + return translateKey(key); +} + +- (void)keyDown:(NSEvent *)event { + ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); + DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; + + // Ignore all input if IME input is in progress + if (!imeInputEventInProgress) { + NSString *characters = [event characters]; + NSUInteger length = [characters length]; + + if (!wd.im_active && length > 0 && keycode_has_unicode(remapKey([event keyCode], [event modifierFlags]))) { + // Fallback unicode character handler used if IME is not active + for (NSUInteger i = 0; i < length; i++) { + DisplayServerOSX::KeyEvent ke; + + ke.window_id = window_id; + ke.osx_state = [event modifierFlags]; + ke.pressed = true; + ke.echo = [event isARepeat]; + ke.keycode = remapKey([event keyCode], [event modifierFlags]); + ke.physical_keycode = translateKey([event keyCode]); + ke.raw = true; + ke.unicode = [characters characterAtIndex:i]; + + _push_to_key_event_buffer(ke); + } + } else { + DisplayServerOSX::KeyEvent ke; + + ke.window_id = window_id; + ke.osx_state = [event modifierFlags]; + ke.pressed = true; + ke.echo = [event isARepeat]; + ke.keycode = remapKey([event keyCode], [event modifierFlags]); + ke.physical_keycode = translateKey([event keyCode]); + ke.raw = false; + ke.unicode = 0; + + _push_to_key_event_buffer(ke); + } + } + + // Pass events to IME handler + if (wd.im_active) { + [self interpretKeyEvents:[NSArray arrayWithObject:event]]; + } +} + +- (void)flagsChanged:(NSEvent *)event { + // Ignore all input if IME input is in progress + if (!imeInputEventInProgress) { + DisplayServerOSX::KeyEvent ke; + + ke.window_id = window_id; + ke.echo = false; + ke.raw = true; + + int key = [event keyCode]; + int mod = [event modifierFlags]; + + if (key == 0x36 || key == 0x37) { + if (mod & NSEventModifierFlagCommand) { + mod &= ~NSEventModifierFlagCommand; + ke.pressed = true; + } else { + ke.pressed = false; + } + } else if (key == 0x38 || key == 0x3c) { + if (mod & NSEventModifierFlagShift) { + mod &= ~NSEventModifierFlagShift; + ke.pressed = true; + } else { + ke.pressed = false; + } + } else if (key == 0x3a || key == 0x3d) { + if (mod & NSEventModifierFlagOption) { + mod &= ~NSEventModifierFlagOption; + ke.pressed = true; + } else { + ke.pressed = false; + } + } else if (key == 0x3b || key == 0x3e) { + if (mod & NSEventModifierFlagControl) { + mod &= ~NSEventModifierFlagControl; + ke.pressed = true; + } else { + ke.pressed = false; + } + } else { + return; + } + + ke.osx_state = mod; + ke.keycode = remapKey(key, mod); + ke.physical_keycode = translateKey(key); + ke.unicode = 0; + + _push_to_key_event_buffer(ke); + } +} + +- (void)keyUp:(NSEvent *)event { + ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); + DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; + + // Ignore all input if IME input is in progress + if (!imeInputEventInProgress) { + NSString *characters = [event characters]; + NSUInteger length = [characters length]; + + // Fallback unicode character handler used if IME is not active + if (!wd.im_active && length > 0 && keycode_has_unicode(remapKey([event keyCode], [event modifierFlags]))) { + for (NSUInteger i = 0; i < length; i++) { + DisplayServerOSX::KeyEvent ke; + + ke.window_id = window_id; + ke.osx_state = [event modifierFlags]; + ke.pressed = false; + ke.echo = [event isARepeat]; + ke.keycode = remapKey([event keyCode], [event modifierFlags]); + ke.physical_keycode = translateKey([event keyCode]); + ke.raw = true; + ke.unicode = [characters characterAtIndex:i]; + + _push_to_key_event_buffer(ke); + } + } else { + DisplayServerOSX::KeyEvent ke; + + ke.window_id = window_id; + ke.osx_state = [event modifierFlags]; + ke.pressed = false; + ke.echo = [event isARepeat]; + ke.keycode = remapKey([event keyCode], [event modifierFlags]); + ke.physical_keycode = translateKey([event keyCode]); + ke.raw = true; + ke.unicode = 0; + + _push_to_key_event_buffer(ke); + } + } +} + +inline void sendScrollEvent(DisplayServer::WindowID window_id, int button, double factor, int modifierFlags) { + ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); + DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; + + unsigned int mask = 1 << (button - 1); + + Ref<InputEventMouseButton> sc; + sc.instance(); + + sc->set_window_id(window_id); + _get_key_modifier_state(modifierFlags, sc); + sc->set_button_index(button); + sc->set_factor(factor); + sc->set_pressed(true); + sc->set_position(wd.mouse_pos); + sc->set_global_position(wd.mouse_pos); + DS_OSX->last_button_state |= mask; + sc->set_button_mask(DS_OSX->last_button_state); + + Input::get_singleton()->accumulate_input_event(sc); + + sc.instance(); + sc->set_window_id(window_id); + sc->set_button_index(button); + sc->set_factor(factor); + sc->set_pressed(false); + sc->set_position(wd.mouse_pos); + sc->set_global_position(wd.mouse_pos); + DS_OSX->last_button_state &= ~mask; + sc->set_button_mask(DS_OSX->last_button_state); + + Input::get_singleton()->accumulate_input_event(sc); +} + +inline void sendPanEvent(DisplayServer::WindowID window_id, double dx, double dy, int modifierFlags) { + ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); + DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; + + Ref<InputEventPanGesture> pg; + pg.instance(); + + pg->set_window_id(window_id); + _get_key_modifier_state(modifierFlags, pg); + pg->set_position(wd.mouse_pos); + pg->set_delta(Vector2(-dx, -dy)); + + Input::get_singleton()->accumulate_input_event(pg); +} + +- (void)scrollWheel:(NSEvent *)event { + ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); + DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; + + double deltaX, deltaY; + + _get_mouse_pos(wd, [event locationInWindow]); + + deltaX = [event scrollingDeltaX]; + deltaY = [event scrollingDeltaY]; + + if ([event hasPreciseScrollingDeltas]) { + deltaX *= 0.03; + deltaY *= 0.03; + } + + if ([event phase] != NSEventPhaseNone || [event momentumPhase] != NSEventPhaseNone) { + sendPanEvent(window_id, deltaX, deltaY, [event modifierFlags]); + } else { + if (fabs(deltaX)) { + sendScrollEvent(window_id, 0 > deltaX ? BUTTON_WHEEL_RIGHT : BUTTON_WHEEL_LEFT, fabs(deltaX * 0.3), [event modifierFlags]); + } + if (fabs(deltaY)) { + sendScrollEvent(window_id, 0 < deltaY ? BUTTON_WHEEL_UP : BUTTON_WHEEL_DOWN, fabs(deltaY * 0.3), [event modifierFlags]); + } + } +} + +@end + +/*************************************************************************/ +/* GodotWindow */ +/*************************************************************************/ + +@interface GodotWindow : NSWindow { +} + +@end + +@implementation GodotWindow + +- (BOOL)canBecomeKeyWindow { + // Required for NSBorderlessWindowMask windows + for (Map<DisplayServer::WindowID, DisplayServerOSX::WindowData>::Element *E = DS_OSX->windows.front(); E; E = E->next()) { + if (E->get().window_object == self) { + if (E->get().no_focus) { + return NO; + } + } + } + return YES; +} + +- (BOOL)canBecomeMainWindow { + // Required for NSBorderlessWindowMask windows + for (Map<DisplayServer::WindowID, DisplayServerOSX::WindowData>::Element *E = DS_OSX->windows.front(); E; E = E->next()) { + if (E->get().window_object == self) { + if (E->get().no_focus) { + return NO; + } + } + } + return YES; +} + +@end + +/*************************************************************************/ +/* DisplayServerOSX */ +/*************************************************************************/ + +bool DisplayServerOSX::has_feature(Feature p_feature) const { + switch (p_feature) { + case FEATURE_GLOBAL_MENU: + case FEATURE_SUBWINDOWS: + //case FEATURE_TOUCHSCREEN: + case FEATURE_MOUSE: + case FEATURE_MOUSE_WARP: + case FEATURE_CLIPBOARD: + case FEATURE_CURSOR_SHAPE: + case FEATURE_CUSTOM_CURSOR_SHAPE: + case FEATURE_NATIVE_DIALOG: + //case FEATURE_CONSOLE_WINDOW: + case FEATURE_IME: + case FEATURE_WINDOW_TRANSPARENCY: + case FEATURE_HIDPI: + case FEATURE_ICON: + case FEATURE_NATIVE_ICON: + //case FEATURE_KEEP_SCREEN_ON: + case FEATURE_SWAP_BUFFERS: + return true; + default: { + } + } + return false; +} + +String DisplayServerOSX::get_name() const { + return "OSX"; +} + +const NSMenu *DisplayServerOSX::_get_menu_root(const String &p_menu_root) const { + const NSMenu *menu = NULL; + if (p_menu_root == "") { + // Main menu.x + menu = [NSApp mainMenu]; + } else if (p_menu_root.to_lower() == "_dock") { + // macOS dock menu. + menu = dock_menu; + } else { + // Submenu. + if (submenu.has(p_menu_root)) { + menu = submenu[p_menu_root]; + } + } + if (menu == apple_menu) { + // Do not allow to change Apple menu. + return NULL; + } + return menu; +} + +NSMenu *DisplayServerOSX::_get_menu_root(const String &p_menu_root) { + NSMenu *menu = NULL; + if (p_menu_root == "") { + // Main menu. + menu = [NSApp mainMenu]; + } else if (p_menu_root.to_lower() == "_dock") { + // macOS dock menu. + menu = dock_menu; + } else { + // Submenu. + if (!submenu.has(p_menu_root)) { + NSMenu *n_menu = [[NSMenu alloc] initWithTitle:[NSString stringWithUTF8String:p_menu_root.utf8().get_data()]]; + submenu[p_menu_root] = n_menu; + } + menu = submenu[p_menu_root]; + } + if (menu == apple_menu) { + // Do not allow to change Apple menu. + return NULL; + } + return menu; +} + +void DisplayServerOSX::global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag) { + _THREAD_SAFE_METHOD_ + + NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + NSMenuItem *menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:@""]; + GlobalMenuItem *obj = [[[GlobalMenuItem alloc] init] autorelease]; + obj->callback = p_callback; + obj->meta = p_tag; + obj->checkable = false; + [menu_item setRepresentedObject:obj]; + } +} + +void DisplayServerOSX::global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag) { + _THREAD_SAFE_METHOD_ + + NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + NSMenuItem *menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:@""]; + GlobalMenuItem *obj = [[[GlobalMenuItem alloc] init] autorelease]; + obj->callback = p_callback; + obj->meta = p_tag; + obj->checkable = true; + [menu_item setRepresentedObject:obj]; + } +} + +void DisplayServerOSX::global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu) { + _THREAD_SAFE_METHOD_ + + NSMenu *menu = _get_menu_root(p_menu_root); + NSMenu *sub_menu = _get_menu_root(p_submenu); + if (menu && sub_menu) { + if (sub_menu == menu) { + ERR_PRINT("Can't set submenu to self!"); + return; + } + if ([sub_menu supermenu]) { + ERR_PRINT("Can't set submenu to menu that is already a submenu of some other menu!"); + return; + } + NSMenuItem *menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:nil keyEquivalent:@""]; + [menu setSubmenu:sub_menu forItem:menu_item]; + } +} + +void DisplayServerOSX::global_menu_add_separator(const String &p_menu_root) { + _THREAD_SAFE_METHOD_ + + NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + [menu addItem:[NSMenuItem separatorItem]]; + } +} + +bool DisplayServerOSX::global_menu_is_item_checked(const String &p_menu_root, int p_idx) const { + _THREAD_SAFE_METHOD_ + + const NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + return ([menu_item state] == NSControlStateValueOn); + } + } + return false; +} + +bool DisplayServerOSX::global_menu_is_item_checkable(const String &p_menu_root, int p_idx) const { + _THREAD_SAFE_METHOD_ + + const NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + GlobalMenuItem *obj = [menu_item representedObject]; + if (obj) { + return obj->checkable; + } + } + } + return false; +} + +Callable DisplayServerOSX::global_menu_get_item_callback(const String &p_menu_root, int p_idx) { + _THREAD_SAFE_METHOD_ + + const NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + GlobalMenuItem *obj = [menu_item representedObject]; + if (obj) { + return obj->callback; + } + } + } + return Callable(); +} + +Variant DisplayServerOSX::global_menu_get_item_tag(const String &p_menu_root, int p_idx) { + _THREAD_SAFE_METHOD_ + + const NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + GlobalMenuItem *obj = [menu_item representedObject]; + if (obj) { + return obj->meta; + } + } + } + return Variant(); +} + +String DisplayServerOSX::global_menu_get_item_text(const String &p_menu_root, int p_idx) { + _THREAD_SAFE_METHOD_ + + const NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + char *utfs = strdup([[menu_item title] UTF8String]); + String ret; + ret.parse_utf8(utfs); + free(utfs); + return ret; + } + } + return String(); +} + +String DisplayServerOSX::global_menu_get_item_submenu(const String &p_menu_root, int p_idx) { + _THREAD_SAFE_METHOD_ + + const NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + const NSMenu *sub_menu = [menu_item submenu]; + if (sub_menu) { + for (Map<String, NSMenu *>::Element *E = submenu.front(); E; E = E->next()) { + if (E->get() == sub_menu) + return E->key(); + } + } + } + } + return String(); +} + +void DisplayServerOSX::global_menu_set_item_checked(const String &p_menu_root, int p_idx, bool p_checked) { + _THREAD_SAFE_METHOD_ + + NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu. + return; + } + NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + if (p_checked) { + [menu_item setState:NSControlStateValueOn]; + } else { + [menu_item setState:NSControlStateValueOff]; + } + } + } +} + +void DisplayServerOSX::global_menu_set_item_checkable(const String &p_menu_root, int p_idx, bool p_checkable) { + _THREAD_SAFE_METHOD_ + + NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu. + return; + } + NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + GlobalMenuItem *obj = [menu_item representedObject]; + obj->checkable = p_checkable; + } + } +} + +void DisplayServerOSX::global_menu_set_item_callback(const String &p_menu_root, int p_idx, const Callable &p_callback) { + _THREAD_SAFE_METHOD_ + + NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu. + return; + } + NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + GlobalMenuItem *obj = [menu_item representedObject]; + obj->callback = p_callback; + } + } +} + +void DisplayServerOSX::global_menu_set_item_tag(const String &p_menu_root, int p_idx, const Variant &p_tag) { + _THREAD_SAFE_METHOD_ + + NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu. + return; + } + NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + GlobalMenuItem *obj = [menu_item representedObject]; + obj->meta = p_tag; + } + } +} + +void DisplayServerOSX::global_menu_set_item_text(const String &p_menu_root, int p_idx, const String &p_text) { + _THREAD_SAFE_METHOD_ + + NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu. + return; + } + NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + [menu_item setTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]]; + } + } +} + +void DisplayServerOSX::global_menu_set_item_submenu(const String &p_menu_root, int p_idx, const String &p_submenu) { + _THREAD_SAFE_METHOD_ + + NSMenu *menu = _get_menu_root(p_menu_root); + NSMenu *sub_menu = _get_menu_root(p_submenu); + if (menu && sub_menu) { + if (sub_menu == menu) { + ERR_PRINT("Can't set submenu to self!"); + return; + } + if ([sub_menu supermenu]) { + ERR_PRINT("Can't set submenu to menu that is already a submenu of some other menu!"); + return; + } + if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu. + return; + } + NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + [menu setSubmenu:sub_menu forItem:menu_item]; + } + } +} + +int DisplayServerOSX::global_menu_get_item_count(const String &p_menu_root) const { + _THREAD_SAFE_METHOD_ + + const NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + return [menu numberOfItems]; + } else { + return 0; + } +} + +void DisplayServerOSX::global_menu_remove_item(const String &p_menu_root, int p_idx) { + _THREAD_SAFE_METHOD_ + + NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not delete Apple menu. + return; + } + [menu removeItemAtIndex:p_idx]; + } +} + +void DisplayServerOSX::global_menu_clear(const String &p_menu_root) { + _THREAD_SAFE_METHOD_ + + NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + [menu removeAllItems]; + // Restore Apple menu. + if (menu == [NSApp mainMenu]) { + NSMenuItem *menu_item = [menu addItemWithTitle:@"" action:nil keyEquivalent:@""]; + [menu setSubmenu:apple_menu forItem:menu_item]; + } + } +} + +void DisplayServerOSX::alert(const String &p_alert, const String &p_title) { + _THREAD_SAFE_METHOD_ + + NSAlert *window = [[NSAlert alloc] init]; + NSString *ns_title = [NSString stringWithUTF8String:p_title.utf8().get_data()]; + NSString *ns_alert = [NSString stringWithUTF8String:p_alert.utf8().get_data()]; + + [window addButtonWithTitle:@"OK"]; + [window setMessageText:ns_title]; + [window setInformativeText:ns_alert]; + [window setAlertStyle:NSAlertStyleWarning]; + + id key_window = [[NSApplication sharedApplication] keyWindow]; + [window runModal]; + [window release]; + if (key_window) { + [key_window makeKeyAndOrderFront:nil]; + } +} + +Error DisplayServerOSX::dialog_show(String p_title, String p_description, Vector<String> p_buttons, const Callable &p_callback) { + _THREAD_SAFE_METHOD_ + + NSAlert *window = [[NSAlert alloc] init]; + NSString *ns_title = [NSString stringWithUTF8String:p_title.utf8().get_data()]; + NSString *ns_description = [NSString stringWithUTF8String:p_description.utf8().get_data()]; + + for (int i = 0; i < p_buttons.size(); i++) { + NSString *ns_button = [NSString stringWithUTF8String:p_buttons[i].utf8().get_data()]; + [window addButtonWithTitle:ns_button]; + } + [window setMessageText:ns_title]; + [window setInformativeText:ns_description]; + [window setAlertStyle:NSAlertStyleInformational]; + + int button_pressed; + NSInteger ret = [window runModal]; + if (ret == NSAlertFirstButtonReturn) { + button_pressed = 0; + } else if (ret == NSAlertSecondButtonReturn) { + button_pressed = 1; + } else if (ret == NSAlertThirdButtonReturn) { + button_pressed = 2; + } else { + button_pressed = 2 + (ret - NSAlertThirdButtonReturn); + } + + if (!p_callback.is_null()) { + Variant button = button_pressed; + Variant *buttonp = &button; + Variant ret; + Callable::CallError ce; + p_callback.call((const Variant **)&buttonp, 1, ret, ce); + } + + [window release]; + return OK; +} + +Error DisplayServerOSX::dialog_input_text(String p_title, String p_description, String p_partial, const Callable &p_callback) { + _THREAD_SAFE_METHOD_ + + NSAlert *window = [[NSAlert alloc] init]; + NSString *ns_title = [NSString stringWithUTF8String:p_title.utf8().get_data()]; + NSString *ns_description = [NSString stringWithUTF8String:p_description.utf8().get_data()]; + NSTextField *input = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 250, 30)]; + + [window addButtonWithTitle:@"OK"]; + [window setMessageText:ns_title]; + [window setInformativeText:ns_description]; + [window setAlertStyle:NSAlertStyleInformational]; + + [input setStringValue:[NSString stringWithUTF8String:p_partial.utf8().get_data()]]; + [window setAccessoryView:input]; + + [window runModal]; + + char *utfs = strdup([[input stringValue] UTF8String]); + String ret; + ret.parse_utf8(utfs); + free(utfs); + + if (!p_callback.is_null()) { + Variant text = ret; + Variant *textp = &text; + Variant ret; + Callable::CallError ce; + p_callback.call((const Variant **)&textp, 1, ret, ce); + } + + [window release]; + return OK; +} + +void DisplayServerOSX::mouse_set_mode(MouseMode p_mode) { + _THREAD_SAFE_METHOD_ + + if (p_mode == mouse_mode) { + return; + } + + if (p_mode == MOUSE_MODE_CAPTURED) { + // Apple Docs state that the display parameter is not used. + // "This parameter is not used. By default, you may pass kCGDirectMainDisplay." + // https://developer.apple.com/library/mac/documentation/graphicsimaging/reference/Quartz_Services_Ref/Reference/reference.html + if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) { + CGDisplayHideCursor(kCGDirectMainDisplay); + } + CGAssociateMouseAndMouseCursorPosition(false); + } else if (p_mode == MOUSE_MODE_HIDDEN) { + if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) { + CGDisplayHideCursor(kCGDirectMainDisplay); + } + CGAssociateMouseAndMouseCursorPosition(true); + } else if (p_mode == MOUSE_MODE_CONFINED) { + CGDisplayShowCursor(kCGDirectMainDisplay); + CGAssociateMouseAndMouseCursorPosition(false); + } else { + CGDisplayShowCursor(kCGDirectMainDisplay); + CGAssociateMouseAndMouseCursorPosition(true); + } + + warp_events.clear(); + mouse_mode = p_mode; +} + +DisplayServer::MouseMode DisplayServerOSX::mouse_get_mode() const { + return mouse_mode; +} + +void DisplayServerOSX::mouse_warp_to_position(const Point2i &p_to) { + _THREAD_SAFE_METHOD_ + + if (mouse_mode == MOUSE_MODE_CAPTURED) { + last_mouse_pos = p_to; + } else { + WindowData &wd = windows[MAIN_WINDOW_ID]; + + //local point in window coords + const NSRect contentRect = [wd.window_view frame]; + const float scale = screen_get_max_scale(); + NSRect pointInWindowRect = NSMakeRect(p_to.x / scale, contentRect.size.height - (p_to.y / scale - 1), 0, 0); + NSPoint pointOnScreen = [[wd.window_view window] convertRectToScreen:pointInWindowRect].origin; + + //point in scren coords + CGPoint lMouseWarpPos = { pointOnScreen.x, CGDisplayBounds(CGMainDisplayID()).size.height - pointOnScreen.y }; + + //do the warping + CGEventSourceRef lEventRef = CGEventSourceCreate(kCGEventSourceStateCombinedSessionState); + CGEventSourceSetLocalEventsSuppressionInterval(lEventRef, 0.0); + CGAssociateMouseAndMouseCursorPosition(false); + CGWarpMouseCursorPosition(lMouseWarpPos); + if (mouse_mode != MOUSE_MODE_CONFINED) { + CGAssociateMouseAndMouseCursorPosition(true); + } + } +} + +Point2i DisplayServerOSX::mouse_get_position() const { + return last_mouse_pos; +} + +Point2i DisplayServerOSX::mouse_get_absolute_position() const { + _THREAD_SAFE_METHOD_ + + const NSPoint mouse_pos = [NSEvent mouseLocation]; + const float scale = screen_get_max_scale(); + + for (NSScreen *screen in [NSScreen screens]) { + NSRect frame = [screen frame]; + if (NSMouseInRect(mouse_pos, frame, NO)) { + return Vector2i((int)mouse_pos.x, (int)-mouse_pos.y) * scale + _get_screens_origin(); + } + } + return Vector2i(); +} + +int DisplayServerOSX::mouse_get_button_state() const { + return last_button_state; +} + +void DisplayServerOSX::clipboard_set(const String &p_text) { + _THREAD_SAFE_METHOD_ + + NSString *copiedString = [NSString stringWithUTF8String:p_text.utf8().get_data()]; + NSArray *copiedStringArray = [NSArray arrayWithObject:copiedString]; + + NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; + [pasteboard clearContents]; + [pasteboard writeObjects:copiedStringArray]; +} + +String DisplayServerOSX::clipboard_get() const { + _THREAD_SAFE_METHOD_ + + NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; + NSArray *classArray = [NSArray arrayWithObject:[NSString class]]; + NSDictionary *options = [NSDictionary dictionary]; + + BOOL ok = [pasteboard canReadObjectForClasses:classArray options:options]; + + if (!ok) { + return ""; + } + + NSArray *objectsToPaste = [pasteboard readObjectsForClasses:classArray options:options]; + NSString *string = [objectsToPaste objectAtIndex:0]; + + char *utfs = strdup([string UTF8String]); + String ret; + ret.parse_utf8(utfs); + free(utfs); + + return ret; +} + +int DisplayServerOSX::get_screen_count() const { + _THREAD_SAFE_METHOD_ + + NSArray *screenArray = [NSScreen screens]; + return [screenArray count]; +} + +// Returns the native top-left screen coordinate of the smallest rectangle +// that encompasses all screens. Needed in get_screen_position(), +// window_get_position, and window_set_position() +// to convert between OS X native screen coordinates and the ones expected by Godot + +static bool displays_arrangement_dirty = true; +static bool displays_scale_dirty = true; +static void displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplayChangeSummaryFlags flags, void *user_info) { + displays_arrangement_dirty = true; + displays_scale_dirty = true; +} + +Point2i DisplayServerOSX::_get_screens_origin() const { + static Point2i origin; + + if (displays_arrangement_dirty) { + origin = Point2i(); + + for (int i = 0; i < get_screen_count(); i++) { + Point2i position = _get_native_screen_position(i); + if (position.x < origin.x) { + origin.x = position.x; + } + if (position.y > origin.y) { + origin.y = position.y; + } + } + displays_arrangement_dirty = false; + } + + return origin; +} + +Point2i DisplayServerOSX::_get_native_screen_position(int p_screen) const { + NSArray *screenArray = [NSScreen screens]; + if ((NSUInteger)p_screen < [screenArray count]) { + NSRect nsrect = [[screenArray objectAtIndex:p_screen] frame]; + // Return the top-left corner of the screen, for OS X the y starts at the bottom + return Point2i(nsrect.origin.x, nsrect.origin.y + nsrect.size.height) * screen_get_max_scale(); + } + + return Point2i(); +} + +Point2i DisplayServerOSX::screen_get_position(int p_screen) const { + _THREAD_SAFE_METHOD_ + + if (p_screen == SCREEN_OF_MAIN_WINDOW) { + p_screen = window_get_current_screen(); + } + + Point2i position = _get_native_screen_position(p_screen) - _get_screens_origin(); + // OS X native y-coordinate relative to _get_screens_origin() is negative, + // Godot expects a positive value + position.y *= -1; + return position; +} + +Size2i DisplayServerOSX::screen_get_size(int p_screen) const { + _THREAD_SAFE_METHOD_ + + if (p_screen == SCREEN_OF_MAIN_WINDOW) { + p_screen = window_get_current_screen(); + } + + NSArray *screenArray = [NSScreen screens]; + if ((NSUInteger)p_screen < [screenArray count]) { + // Note: Use frame to get the whole screen size + NSRect nsrect = [[screenArray objectAtIndex:p_screen] frame]; + return Size2i(nsrect.size.width, nsrect.size.height) * screen_get_max_scale(); + } + + return Size2i(); +} + +int DisplayServerOSX::screen_get_dpi(int p_screen) const { + _THREAD_SAFE_METHOD_ + + if (p_screen == SCREEN_OF_MAIN_WINDOW) { + p_screen = window_get_current_screen(); + } + + NSArray *screenArray = [NSScreen screens]; + if ((NSUInteger)p_screen < [screenArray count]) { + NSDictionary *description = [[screenArray objectAtIndex:p_screen] deviceDescription]; + NSSize displayDPI = [[description objectForKey:NSDeviceResolution] sizeValue]; + return (displayDPI.width + displayDPI.height) / 2; + } + + return 96; +} + +float DisplayServerOSX::screen_get_scale(int p_screen) const { + _THREAD_SAFE_METHOD_ + + if (p_screen == SCREEN_OF_MAIN_WINDOW) { + p_screen = window_get_current_screen(); + } + if (OS::get_singleton()->is_hidpi_allowed()) { + NSArray *screenArray = [NSScreen screens]; + if ((NSUInteger)p_screen < [screenArray count]) { + if ([[screenArray objectAtIndex:p_screen] respondsToSelector:@selector(backingScaleFactor)]) { + return fmax(1.0, [[screenArray objectAtIndex:p_screen] backingScaleFactor]); + } + } + } + + return 1.f; +} + +float DisplayServerOSX::screen_get_max_scale() const { + _THREAD_SAFE_METHOD_ + + static float scale = 1.f; + if (displays_scale_dirty) { + int screen_count = get_screen_count(); + for (int i = 0; i < screen_count; i++) { + scale = fmax(scale, screen_get_scale(i)); + } + displays_scale_dirty = false; + } + return scale; +} + +Rect2i DisplayServerOSX::screen_get_usable_rect(int p_screen) const { + _THREAD_SAFE_METHOD_ + + if (p_screen == SCREEN_OF_MAIN_WINDOW) { + p_screen = window_get_current_screen(); + } + + NSArray *screenArray = [NSScreen screens]; + if ((NSUInteger)p_screen < [screenArray count]) { + const float scale = screen_get_max_scale(); + NSRect nsrect = [[screenArray objectAtIndex:p_screen] visibleFrame]; + + Point2i position = Point2i(nsrect.origin.x, nsrect.origin.y + nsrect.size.height) * scale - _get_screens_origin(); + position.y *= -1; + Size2i size = Size2i(nsrect.size.width, nsrect.size.height) * scale; + + return Rect2i(position, size); + } + + return Rect2i(); +} + +Vector<DisplayServer::WindowID> DisplayServerOSX::get_window_list() const { + _THREAD_SAFE_METHOD_ + + Vector<int> ret; + for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { + ret.push_back(E->key()); + } + return ret; +} + +DisplayServer::WindowID DisplayServerOSX::create_sub_window(WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect) { + _THREAD_SAFE_METHOD_ + + WindowID id = _create_window(p_mode, p_rect); + for (int i = 0; i < WINDOW_FLAG_MAX; i++) { + if (p_flags & (1 << i)) { + window_set_flag(WindowFlags(i), true, id); + } + } + + return id; +} + +void DisplayServerOSX::show_window(WindowID p_id) { + WindowData &wd = windows[p_id]; + + if (wd.no_focus) { + [wd.window_object orderFront:nil]; + } else { + [wd.window_object makeKeyAndOrderFront:nil]; + } +} + +void DisplayServerOSX::_send_window_event(const WindowData &wd, WindowEvent p_event) { + _THREAD_SAFE_METHOD_ + + if (!wd.event_callback.is_null()) { + Variant event = int(p_event); + Variant *eventp = &event; + Variant ret; + Callable::CallError ce; + wd.event_callback.call((const Variant **)&eventp, 1, ret, ce); + } +} + +DisplayServerOSX::WindowID DisplayServerOSX::_find_window_id(id p_window) { + for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { + if (E->get().window_object == p_window) { + return E->key(); + } + } + return INVALID_WINDOW_ID; +} + +void DisplayServerOSX::_update_window(WindowData p_wd) { + bool borderless_full = false; + + if (p_wd.borderless) { + NSRect frameRect = [p_wd.window_object frame]; + NSRect screenRect = [[p_wd.window_object screen] frame]; + + // Check if our window covers up the screen + if (frameRect.origin.x <= screenRect.origin.x && frameRect.origin.y <= frameRect.origin.y && + frameRect.size.width >= screenRect.size.width && frameRect.size.height >= screenRect.size.height) { + borderless_full = true; + } + } + + if (borderless_full) { + // If the window covers up the screen set the level to above the main menu and hide on deactivate + [p_wd.window_object setLevel:NSMainMenuWindowLevel + 1]; + [p_wd.window_object setHidesOnDeactivate:YES]; + } else { + // Reset these when our window is not a borderless window that covers up the screen + if (p_wd.on_top) { + [p_wd.window_object setLevel:NSFloatingWindowLevel]; + } else { + [p_wd.window_object setLevel:NSNormalWindowLevel]; + } + [p_wd.window_object setHidesOnDeactivate:NO]; + } +} + +void DisplayServerOSX::delete_sub_window(WindowID p_id) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_id)); + ERR_FAIL_COND_MSG(p_id == MAIN_WINDOW_ID, "Main window can't be deleted"); + + WindowData &wd = windows[p_id]; + + [wd.window_object setContentView:nil]; + [wd.window_object close]; +} + +void DisplayServerOSX::window_set_title(const String &p_title, WindowID p_window) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; + + [wd.window_object setTitle:[NSString stringWithUTF8String:p_title.utf8().get_data()]]; +} + +void DisplayServerOSX::window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; + + wd.mpath = p_region; +} + +void DisplayServerOSX::window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; + wd.rect_changed_callback = p_callable; +} + +void DisplayServerOSX::window_set_window_event_callback(const Callable &p_callable, WindowID p_window) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; + wd.event_callback = p_callable; +} + +void DisplayServerOSX::window_set_input_event_callback(const Callable &p_callable, WindowID p_window) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; + wd.input_event_callback = p_callable; +} + +void DisplayServerOSX::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) { + _THREAD_SAFE_METHOD_ + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; + wd.input_text_callback = p_callable; +} + +void DisplayServerOSX::window_set_drop_files_callback(const Callable &p_callable, WindowID p_window) { + _THREAD_SAFE_METHOD_ + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; + wd.drop_files_callback = p_callable; +} + +int DisplayServerOSX::window_get_current_screen(WindowID p_window) const { + _THREAD_SAFE_METHOD_ + ERR_FAIL_COND_V(!windows.has(p_window), -1); + const WindowData &wd = windows[p_window]; + + const NSUInteger index = [[NSScreen screens] indexOfObject:[wd.window_object screen]]; + return (index == NSNotFound) ? 0 : index; +} + +void DisplayServerOSX::window_set_current_screen(int p_screen, WindowID p_window) { + _THREAD_SAFE_METHOD_ + Point2i wpos = window_get_position(p_window) - screen_get_position(window_get_current_screen(p_window)); + window_set_position(wpos + screen_get_position(p_screen), p_window); +} + +void DisplayServerOSX::window_set_transient(WindowID p_window, WindowID p_parent) { + _THREAD_SAFE_METHOD_ + ERR_FAIL_COND(p_window == p_parent); + + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd_window = windows[p_window]; + + ERR_FAIL_COND(wd_window.transient_parent == p_parent); + + ERR_FAIL_COND_MSG(wd_window.on_top, "Windows with the 'on top' can't become transient."); + if (p_parent == INVALID_WINDOW_ID) { + //remove transient + ERR_FAIL_COND(wd_window.transient_parent == INVALID_WINDOW_ID); + ERR_FAIL_COND(!windows.has(wd_window.transient_parent)); + + WindowData &wd_parent = windows[wd_window.transient_parent]; + + wd_window.transient_parent = INVALID_WINDOW_ID; + wd_parent.transient_children.erase(p_window); + + [wd_parent.window_object removeChildWindow:wd_window.window_object]; + } else { + ERR_FAIL_COND(!windows.has(p_parent)); + ERR_FAIL_COND_MSG(wd_window.transient_parent != INVALID_WINDOW_ID, "Window already has a transient parent"); + WindowData &wd_parent = windows[p_parent]; + + wd_window.transient_parent = p_parent; + wd_parent.transient_children.insert(p_window); + + [wd_parent.window_object addChildWindow:wd_window.window_object ordered:NSWindowAbove]; + } +} + +Point2i DisplayServerOSX::window_get_position(WindowID p_window) const { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND_V(!windows.has(p_window), Point2i()); + const WindowData &wd = windows[p_window]; + + NSRect nsrect = [wd.window_object frame]; + Point2i pos; + + // Return the position of the top-left corner, for OS X the y starts at the bottom + const float scale = screen_get_max_scale(); + pos.x = nsrect.origin.x; + pos.y = (nsrect.origin.y + nsrect.size.height); + pos *= scale; + pos -= _get_screens_origin(); + // OS X native y-coordinate relative to _get_screens_origin() is negative, + // Godot expects a positive value + pos.y *= -1; + return pos; +} + +void DisplayServerOSX::window_set_position(const Point2i &p_position, WindowID p_window) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; + + Point2i position = p_position; + // OS X native y-coordinate relative to _get_screens_origin() is negative, + // Godot passes a positive value + position.y *= -1; + position += _get_screens_origin(); + position /= screen_get_max_scale(); + + [wd.window_object setFrameTopLeftPoint:NSMakePoint(position.x, position.y)]; + + _update_window(wd); + _get_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream]); +} + +void DisplayServerOSX::window_set_max_size(const Size2i p_size, WindowID p_window) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; + + if ((p_size != Size2i()) && ((p_size.x < wd.min_size.x) || (p_size.y < wd.min_size.y))) { + ERR_PRINT("Maximum window size can't be smaller than minimum window size!"); + return; + } + wd.max_size = p_size; + + if ((wd.max_size != Size2i()) && !wd.fullscreen) { + Size2i size = wd.max_size / screen_get_max_scale(); + [wd.window_object setContentMaxSize:NSMakeSize(size.x, size.y)]; + } else { + [wd.window_object setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)]; + } +} + +Size2i DisplayServerOSX::window_get_max_size(WindowID p_window) const { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND_V(!windows.has(p_window), Size2i()); + const WindowData &wd = windows[p_window]; + return wd.max_size; +} + +void DisplayServerOSX::window_set_min_size(const Size2i p_size, WindowID p_window) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; + + if ((p_size != Size2i()) && (wd.max_size != Size2i()) && ((p_size.x > wd.max_size.x) || (p_size.y > wd.max_size.y))) { + ERR_PRINT("Minimum window size can't be larger than maximum window size!"); + return; + } + wd.min_size = p_size; + + if ((wd.min_size != Size2i()) && !wd.fullscreen) { + Size2i size = wd.min_size / screen_get_max_scale(); + [wd.window_object setContentMinSize:NSMakeSize(size.x, size.y)]; + } else { + [wd.window_object setContentMinSize:NSMakeSize(0, 0)]; + } +} + +Size2i DisplayServerOSX::window_get_min_size(WindowID p_window) const { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND_V(!windows.has(p_window), Size2i()); + const WindowData &wd = windows[p_window]; + + return wd.min_size; +} + +void DisplayServerOSX::window_set_size(const Size2i p_size, WindowID p_window) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; + + Size2i size = p_size / screen_get_max_scale(); + + NSPoint top_left; + NSRect old_frame = [wd.window_object frame]; + top_left.x = old_frame.origin.x; + top_left.y = NSMaxY(old_frame); + + NSRect new_frame = NSMakeRect(0, 0, size.x, size.y); + new_frame = [wd.window_object frameRectForContentRect:new_frame]; + + new_frame.origin.x = top_left.x; + new_frame.origin.y = top_left.y - new_frame.size.height; + + [wd.window_object setFrame:new_frame display:YES]; + + _update_window(wd); +} + +Size2i DisplayServerOSX::window_get_size(WindowID p_window) const { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND_V(!windows.has(p_window), Size2i()); + const WindowData &wd = windows[p_window]; + return wd.size; +} + +Size2i DisplayServerOSX::window_get_real_size(WindowID p_window) const { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND_V(!windows.has(p_window), Size2i()); + const WindowData &wd = windows[p_window]; + NSRect frame = [wd.window_object frame]; + return Size2i(frame.size.width, frame.size.height) * screen_get_max_scale(); +} + +bool DisplayServerOSX::window_is_maximize_allowed(WindowID p_window) const { + return true; +} + +void DisplayServerOSX::_set_window_per_pixel_transparency_enabled(bool p_enabled, WindowID p_window) { + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; + + if (!OS_OSX::get_singleton()->is_layered_allowed()) { + return; + } + if (wd.layered_window != p_enabled) { + if (p_enabled) { + [wd.window_object setBackgroundColor:[NSColor clearColor]]; + [wd.window_object setOpaque:NO]; + [wd.window_object setHasShadow:NO]; + CALayer *layer = [wd.window_view layer]; + if (layer) { + [layer setOpaque:NO]; + } +#if defined(VULKAN_ENABLED) + if (rendering_driver == "vulkan") { + //TODO - implement transparency for Vulkan + } +#endif +#if defined(OPENGL_ENABLED) + if (rendering_driver == "opengl_es") { + //TODO - reimplement OpenGLES + } +#endif + wd.layered_window = true; + } else { + [wd.window_object setBackgroundColor:[NSColor colorWithCalibratedWhite:1 alpha:1]]; + [wd.window_object setOpaque:YES]; + [wd.window_object setHasShadow:YES]; + CALayer *layer = [wd.window_view layer]; + if (layer) { + [layer setOpaque:YES]; + } +#if defined(VULKAN_ENABLED) + if (rendering_driver == "vulkan") { + //TODO - implement transparency for Vulkan + } +#endif +#if defined(OPENGL_ENABLED) + if (rendering_driver == "opengl_es") { + //TODO - reimplement OpenGLES + } +#endif + wd.layered_window = false; + } +#if defined(OPENGL_ENABLED) + if (rendering_driver == "opengl_es") { + //TODO - reimplement OpenGLES + } +#endif +#if defined(VULKAN_ENABLED) + if (rendering_driver == "vulkan") { + //TODO - implement transparency for Vulkan + } +#endif + NSRect frameRect = [wd.window_object frame]; + [wd.window_object setFrame:NSMakeRect(frameRect.origin.x, frameRect.origin.y, frameRect.size.width + 1, frameRect.size.height) display:YES]; + [wd.window_object setFrame:frameRect display:YES]; + } +} + +void DisplayServerOSX::window_set_mode(WindowMode p_mode, WindowID p_window) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; + + WindowMode old_mode = window_get_mode(p_window); + if (old_mode == p_mode) { + return; // do nothing + } + + switch (old_mode) { + case WINDOW_MODE_WINDOWED: { + //do nothing + } break; + case WINDOW_MODE_MINIMIZED: { + [wd.window_object deminiaturize:nil]; + } break; + case WINDOW_MODE_FULLSCREEN: { + if (wd.layered_window) { + _set_window_per_pixel_transparency_enabled(true, p_window); + } + if (wd.resize_disabled) { //restore resize disabled + [wd.window_object setStyleMask:[wd.window_object styleMask] & ~NSWindowStyleMaskResizable]; + } + if (wd.min_size != Size2i()) { + Size2i size = wd.min_size / screen_get_max_scale(); + [wd.window_object setContentMinSize:NSMakeSize(size.x, size.y)]; + } + if (wd.max_size != Size2i()) { + Size2i size = wd.max_size / screen_get_max_scale(); + [wd.window_object setContentMaxSize:NSMakeSize(size.x, size.y)]; + } + [wd.window_object toggleFullScreen:nil]; + wd.fullscreen = false; + } break; + case WINDOW_MODE_MAXIMIZED: { + if ([wd.window_object isZoomed]) { + [wd.window_object zoom:nil]; + } + } break; + } + + switch (p_mode) { + case WINDOW_MODE_WINDOWED: { + //do nothing + } break; + case WINDOW_MODE_MINIMIZED: { + [wd.window_object performMiniaturize:nil]; + } break; + case WINDOW_MODE_FULLSCREEN: { + if (wd.layered_window) + _set_window_per_pixel_transparency_enabled(false, p_window); + if (wd.resize_disabled) //fullscreen window should be resizable to work + [wd.window_object setStyleMask:[wd.window_object styleMask] | NSWindowStyleMaskResizable]; + [wd.window_object setContentMinSize:NSMakeSize(0, 0)]; + [wd.window_object setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)]; + [wd.window_object toggleFullScreen:nil]; + wd.fullscreen = true; + } break; + case WINDOW_MODE_MAXIMIZED: { + if (![wd.window_object isZoomed]) { + [wd.window_object zoom:nil]; + } + } break; + } +} + +DisplayServer::WindowMode DisplayServerOSX::window_get_mode(WindowID p_window) const { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND_V(!windows.has(p_window), WINDOW_MODE_WINDOWED); + const WindowData &wd = windows[p_window]; + + if (wd.fullscreen) { //if fullscreen, it's not in another mode + return WINDOW_MODE_FULLSCREEN; + } + if ([wd.window_object isZoomed] && !wd.resize_disabled) { + return WINDOW_MODE_MAXIMIZED; + } + if ([wd.window_object respondsToSelector:@selector(isMiniaturized)]) { + if ([wd.window_object isMiniaturized]) { + return WINDOW_MODE_MINIMIZED; + } + } + + // all other discarded, return windowed. + return WINDOW_MODE_WINDOWED; +} + +void DisplayServerOSX::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; + + switch (p_flag) { + case WINDOW_FLAG_RESIZE_DISABLED: { + wd.resize_disabled = p_enabled; + if (wd.fullscreen) { //fullscreen window should be resizable, style will be applied on exiting fs + return; + } + if (p_enabled) { + [wd.window_object setStyleMask:[wd.window_object styleMask] & ~NSWindowStyleMaskResizable]; + } else { + [wd.window_object setStyleMask:[wd.window_object styleMask] | NSWindowStyleMaskResizable]; + } + } break; + case WINDOW_FLAG_BORDERLESS: { + // OrderOut prevents a lose focus bug with the window + if ([wd.window_object isVisible]) { + [wd.window_object orderOut:nil]; + } + wd.borderless = p_enabled; + if (p_enabled) { + [wd.window_object setStyleMask:NSWindowStyleMaskBorderless]; + } else { + if (wd.layered_window) + _set_window_per_pixel_transparency_enabled(false, p_window); + [wd.window_object setStyleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | (wd.resize_disabled ? 0 : NSWindowStyleMaskResizable)]; + // Force update of the window styles + NSRect frameRect = [wd.window_object frame]; + [wd.window_object setFrame:NSMakeRect(frameRect.origin.x, frameRect.origin.y, frameRect.size.width + 1, frameRect.size.height) display:NO]; + [wd.window_object setFrame:frameRect display:NO]; + } + _update_window(wd); + if ([wd.window_object isVisible]) { + if (wd.no_focus) { + [wd.window_object orderFront:nil]; + } else { + [wd.window_object makeKeyAndOrderFront:nil]; + } + } + } break; + case WINDOW_FLAG_ALWAYS_ON_TOP: { + wd.on_top = p_enabled; + if (p_enabled) { + [wd.window_object setLevel:NSFloatingWindowLevel]; + } else { + [wd.window_object setLevel:NSNormalWindowLevel]; + } + } break; + case WINDOW_FLAG_TRANSPARENT: { + wd.layered_window = p_enabled; + if (p_enabled) { + [wd.window_object setStyleMask:NSWindowStyleMaskBorderless]; // force borderless + } else if (!wd.borderless) { + [wd.window_object setStyleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | (wd.resize_disabled ? 0 : NSWindowStyleMaskResizable)]; + } + _set_window_per_pixel_transparency_enabled(p_enabled, p_window); + } break; + case WINDOW_FLAG_NO_FOCUS: { + wd.no_focus = p_enabled; + } break; + default: { + } + } +} + +bool DisplayServerOSX::window_get_flag(WindowFlags p_flag, WindowID p_window) const { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND_V(!windows.has(p_window), false); + const WindowData &wd = windows[p_window]; + + switch (p_flag) { + case WINDOW_FLAG_RESIZE_DISABLED: { + return wd.resize_disabled; + } break; + case WINDOW_FLAG_BORDERLESS: { + return [wd.window_object styleMask] == NSWindowStyleMaskBorderless; + } break; + case WINDOW_FLAG_ALWAYS_ON_TOP: { + return [wd.window_object level] == NSFloatingWindowLevel; + } break; + case WINDOW_FLAG_TRANSPARENT: { + return wd.layered_window; + } break; + case WINDOW_FLAG_NO_FOCUS: { + return wd.no_focus; + } break; + default: { + } + } + + return false; +} + +void DisplayServerOSX::window_request_attention(WindowID p_window) { + // It's app global, ignore window id. + [NSApp requestUserAttention:NSCriticalRequest]; +} + +void DisplayServerOSX::window_move_to_foreground(WindowID p_window) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + const WindowData &wd = windows[p_window]; + + [[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; + if (wd.no_focus) { + [wd.window_object orderFront:nil]; + } else { + [wd.window_object makeKeyAndOrderFront:nil]; + } +} + +bool DisplayServerOSX::window_can_draw(WindowID p_window) const { + return window_get_mode(p_window) != WINDOW_MODE_MINIMIZED; +} + +bool DisplayServerOSX::can_any_window_draw() const { + _THREAD_SAFE_METHOD_ + + for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { + if (window_get_mode(E->key()) != WINDOW_MODE_MINIMIZED) { + return true; + } + } + return false; +} + +void DisplayServerOSX::window_set_ime_active(const bool p_active, WindowID p_window) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; + + wd.im_active = p_active; + + if (!p_active) { + [wd.window_view cancelComposition]; + } +} + +void DisplayServerOSX::window_set_ime_position(const Point2i &p_pos, WindowID p_window) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; + + wd.im_position = p_pos; +} + +bool DisplayServerOSX::get_swap_cancel_ok() { + return false; +} + +void DisplayServerOSX::cursor_set_shape(CursorShape p_shape) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_INDEX(p_shape, CURSOR_MAX); + + if (cursor_shape == p_shape) { + return; + } + + if (mouse_mode != MOUSE_MODE_VISIBLE && mouse_mode != MOUSE_MODE_CONFINED) { + cursor_shape = p_shape; + return; + } + + if (cursors[p_shape] != NULL) { + [cursors[p_shape] set]; + } else { + switch (p_shape) { + case CURSOR_ARROW: + [[NSCursor arrowCursor] set]; + break; + case CURSOR_IBEAM: + [[NSCursor IBeamCursor] set]; + break; + case CURSOR_POINTING_HAND: + [[NSCursor pointingHandCursor] set]; + break; + case CURSOR_CROSS: + [[NSCursor crosshairCursor] set]; + break; + case CURSOR_WAIT: + [[NSCursor arrowCursor] set]; + break; + case CURSOR_BUSY: + [[NSCursor arrowCursor] set]; + break; + case CURSOR_DRAG: + [[NSCursor closedHandCursor] set]; + break; + case CURSOR_CAN_DROP: + [[NSCursor openHandCursor] set]; + break; + case CURSOR_FORBIDDEN: + [[NSCursor operationNotAllowedCursor] set]; + break; + case CURSOR_VSIZE: + [_cursorFromSelector(@selector(_windowResizeNorthSouthCursor), @selector(resizeUpDownCursor)) set]; + break; + case CURSOR_HSIZE: + [_cursorFromSelector(@selector(_windowResizeEastWestCursor), @selector(resizeLeftRightCursor)) set]; + break; + case CURSOR_BDIAGSIZE: + [_cursorFromSelector(@selector(_windowResizeNorthEastSouthWestCursor)) set]; + break; + case CURSOR_FDIAGSIZE: + [_cursorFromSelector(@selector(_windowResizeNorthWestSouthEastCursor)) set]; + break; + case CURSOR_MOVE: + [[NSCursor arrowCursor] set]; + break; + case CURSOR_VSPLIT: + [[NSCursor resizeUpDownCursor] set]; + break; + case CURSOR_HSPLIT: + [[NSCursor resizeLeftRightCursor] set]; + break; + case CURSOR_HELP: + [_cursorFromSelector(@selector(_helpCursor)) set]; + break; + default: { + } + } + } + + cursor_shape = p_shape; +} + +DisplayServerOSX::CursorShape DisplayServerOSX::cursor_get_shape() const { + return cursor_shape; +} + +void DisplayServerOSX::cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) { + _THREAD_SAFE_METHOD_ + + if (p_cursor.is_valid()) { + Map<CursorShape, Vector<Variant>>::Element *cursor_c = cursors_cache.find(p_shape); + + if (cursor_c) { + if (cursor_c->get()[0] == p_cursor && cursor_c->get()[1] == p_hotspot) { + cursor_set_shape(p_shape); + return; + } + cursors_cache.erase(p_shape); + } + + Ref<Texture2D> texture = p_cursor; + Ref<AtlasTexture> atlas_texture = p_cursor; + Ref<Image> image; + Size2 texture_size; + Rect2 atlas_rect; + + if (texture.is_valid()) { + image = texture->get_data(); + } + + if (!image.is_valid() && atlas_texture.is_valid()) { + texture = atlas_texture->get_atlas(); + + atlas_rect.size.width = texture->get_width(); + atlas_rect.size.height = texture->get_height(); + atlas_rect.position.x = atlas_texture->get_region().position.x; + atlas_rect.position.y = atlas_texture->get_region().position.y; + + texture_size.width = atlas_texture->get_region().size.x; + texture_size.height = atlas_texture->get_region().size.y; + } else if (image.is_valid()) { + texture_size.width = texture->get_width(); + texture_size.height = texture->get_height(); + } + + ERR_FAIL_COND(!texture.is_valid()); + ERR_FAIL_COND(p_hotspot.x < 0 || p_hotspot.y < 0); + ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256); + ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height); + + image = texture->get_data(); + + ERR_FAIL_COND(!image.is_valid()); + + NSBitmapImageRep *imgrep = [[NSBitmapImageRep alloc] + initWithBitmapDataPlanes:NULL + pixelsWide:int(texture_size.width) + pixelsHigh:int(texture_size.height) + bitsPerSample:8 + samplesPerPixel:4 + hasAlpha:YES + isPlanar:NO + colorSpaceName:NSDeviceRGBColorSpace + bytesPerRow:int(texture_size.width) * 4 + bitsPerPixel:32]; + + ERR_FAIL_COND(imgrep == nil); + uint8_t *pixels = [imgrep bitmapData]; + + int len = int(texture_size.width * texture_size.height); + + for (int i = 0; i < len; i++) { + int row_index = floor(i / texture_size.width) + atlas_rect.position.y; + int column_index = (i % int(texture_size.width)) + atlas_rect.position.x; + + if (atlas_texture.is_valid()) { + column_index = MIN(column_index, atlas_rect.size.width - 1); + row_index = MIN(row_index, atlas_rect.size.height - 1); + } + + uint32_t color = image->get_pixel(column_index, row_index).to_argb32(); + + uint8_t alpha = (color >> 24) & 0xFF; + pixels[i * 4 + 0] = ((color >> 16) & 0xFF) * alpha / 255; + pixels[i * 4 + 1] = ((color >> 8) & 0xFF) * alpha / 255; + pixels[i * 4 + 2] = ((color)&0xFF) * alpha / 255; + pixels[i * 4 + 3] = alpha; + } + + NSImage *nsimage = [[NSImage alloc] initWithSize:NSMakeSize(texture_size.width, texture_size.height)]; + [nsimage addRepresentation:imgrep]; + + NSCursor *cursor = [[NSCursor alloc] initWithImage:nsimage hotSpot:NSMakePoint(p_hotspot.x, p_hotspot.y)]; + + [cursors[p_shape] release]; + cursors[p_shape] = cursor; + + Vector<Variant> params; + params.push_back(p_cursor); + params.push_back(p_hotspot); + cursors_cache.insert(p_shape, params); + + if (p_shape == cursor_shape) { + if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) { + [cursor set]; + } + } + + [imgrep release]; + [nsimage release]; + } else { + // Reset to default system cursor + if (cursors[p_shape] != NULL) { + [cursors[p_shape] release]; + cursors[p_shape] = NULL; + } + + CursorShape c = cursor_shape; + cursor_shape = CURSOR_MAX; + cursor_set_shape(c); + + cursors_cache.erase(p_shape); + } +} + +struct LayoutInfo { + String name; + String code; +}; + +static Vector<LayoutInfo> kbd_layouts; +static int current_layout = 0; +static bool keyboard_layout_dirty = true; +static void keyboard_layout_changed(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef user_info) { + kbd_layouts.clear(); + current_layout = 0; + keyboard_layout_dirty = true; +} + +void _update_keyboard_layouts() { + @autoreleasepool { + TISInputSourceRef cur_source = TISCopyCurrentKeyboardInputSource(); + NSString *cur_name = (NSString *)TISGetInputSourceProperty(cur_source, kTISPropertyLocalizedName); + CFRelease(cur_source); + + // Enum IME layouts + NSDictionary *filter_ime = @{ (NSString *)kTISPropertyInputSourceType : (NSString *)kTISTypeKeyboardInputMode }; + NSArray *list_ime = (NSArray *)TISCreateInputSourceList((CFDictionaryRef)filter_ime, false); + for (NSUInteger i = 0; i < [list_ime count]; i++) { + LayoutInfo ly; + NSString *name = (NSString *)TISGetInputSourceProperty((TISInputSourceRef)[list_ime objectAtIndex:i], kTISPropertyLocalizedName); + ly.name.parse_utf8([name UTF8String]); + + NSArray *langs = (NSArray *)TISGetInputSourceProperty((TISInputSourceRef)[list_ime objectAtIndex:i], kTISPropertyInputSourceLanguages); + ly.code.parse_utf8([(NSString *)[langs objectAtIndex:0] UTF8String]); + kbd_layouts.push_back(ly); + + if ([name isEqualToString:cur_name]) { + current_layout = kbd_layouts.size() - 1; + } + } + [list_ime release]; + + // Enum plain keyboard layouts + NSDictionary *filter_kbd = @{ (NSString *)kTISPropertyInputSourceType : (NSString *)kTISTypeKeyboardLayout }; + NSArray *list_kbd = (NSArray *)TISCreateInputSourceList((CFDictionaryRef)filter_kbd, false); + for (NSUInteger i = 0; i < [list_kbd count]; i++) { + LayoutInfo ly; + NSString *name = (NSString *)TISGetInputSourceProperty((TISInputSourceRef)[list_kbd objectAtIndex:i], kTISPropertyLocalizedName); + ly.name.parse_utf8([name UTF8String]); + + NSArray *langs = (NSArray *)TISGetInputSourceProperty((TISInputSourceRef)[list_kbd objectAtIndex:i], kTISPropertyInputSourceLanguages); + ly.code.parse_utf8([(NSString *)[langs objectAtIndex:0] UTF8String]); + kbd_layouts.push_back(ly); + + if ([name isEqualToString:cur_name]) { + current_layout = kbd_layouts.size() - 1; + } + } + [list_kbd release]; + } + + keyboard_layout_dirty = false; +} + +int DisplayServerOSX::keyboard_get_layout_count() const { + if (keyboard_layout_dirty) { + _update_keyboard_layouts(); + } + return kbd_layouts.size(); +} + +void DisplayServerOSX::keyboard_set_current_layout(int p_index) { + if (keyboard_layout_dirty) { + _update_keyboard_layouts(); + } + + ERR_FAIL_INDEX(p_index, kbd_layouts.size()); + + NSString *cur_name = [NSString stringWithUTF8String:kbd_layouts[p_index].name.utf8().get_data()]; + + NSDictionary *filter_kbd = @{ (NSString *)kTISPropertyInputSourceType : (NSString *)kTISTypeKeyboardLayout }; + NSArray *list_kbd = (NSArray *)TISCreateInputSourceList((CFDictionaryRef)filter_kbd, false); + for (NSUInteger i = 0; i < [list_kbd count]; i++) { + NSString *name = (NSString *)TISGetInputSourceProperty((TISInputSourceRef)[list_kbd objectAtIndex:i], kTISPropertyLocalizedName); + if ([name isEqualToString:cur_name]) { + TISSelectInputSource((TISInputSourceRef)[list_kbd objectAtIndex:i]); + break; + } + } + [list_kbd release]; + + NSDictionary *filter_ime = @{ (NSString *)kTISPropertyInputSourceType : (NSString *)kTISTypeKeyboardInputMode }; + NSArray *list_ime = (NSArray *)TISCreateInputSourceList((CFDictionaryRef)filter_ime, false); + for (NSUInteger i = 0; i < [list_ime count]; i++) { + NSString *name = (NSString *)TISGetInputSourceProperty((TISInputSourceRef)[list_ime objectAtIndex:i], kTISPropertyLocalizedName); + if ([name isEqualToString:cur_name]) { + TISSelectInputSource((TISInputSourceRef)[list_ime objectAtIndex:i]); + break; + } + } + [list_ime release]; +} + +int DisplayServerOSX::keyboard_get_current_layout() const { + if (keyboard_layout_dirty) { + _update_keyboard_layouts(); + } + + return current_layout; +} + +String DisplayServerOSX::keyboard_get_layout_language(int p_index) const { + if (keyboard_layout_dirty) { + _update_keyboard_layouts(); + } + + ERR_FAIL_INDEX_V(p_index, kbd_layouts.size(), ""); + return kbd_layouts[p_index].code; +} + +String DisplayServerOSX::keyboard_get_layout_name(int p_index) const { + if (keyboard_layout_dirty) { + _update_keyboard_layouts(); + } + + ERR_FAIL_INDEX_V(p_index, kbd_layouts.size(), ""); + return kbd_layouts[p_index].name; +} + +void DisplayServerOSX::_push_input(const Ref<InputEvent> &p_event) { + Ref<InputEvent> ev = p_event; + Input::get_singleton()->accumulate_input_event(ev); +} + +void DisplayServerOSX::_release_pressed_events() { + _THREAD_SAFE_METHOD_ + if (Input::get_singleton()) { + Input::get_singleton()->release_pressed_events(); + } +} + +void DisplayServerOSX::_process_key_events() { + Ref<InputEventKey> k; + for (int i = 0; i < key_event_pos; i++) { + const KeyEvent &ke = key_event_buffer[i]; + if (ke.raw) { + // Non IME input - no composite characters, pass events as is + k.instance(); + + k->set_window_id(ke.window_id); + _get_key_modifier_state(ke.osx_state, k); + k->set_pressed(ke.pressed); + k->set_echo(ke.echo); + k->set_keycode(ke.keycode); + k->set_physical_keycode(ke.physical_keycode); + k->set_unicode(ke.unicode); + + _push_input(k); + } else { + // IME input + if ((i == 0 && ke.keycode == 0) || (i > 0 && key_event_buffer[i - 1].keycode == 0)) { + k.instance(); + + k->set_window_id(ke.window_id); + _get_key_modifier_state(ke.osx_state, k); + k->set_pressed(ke.pressed); + k->set_echo(ke.echo); + k->set_keycode(0); + k->set_physical_keycode(0); + k->set_unicode(ke.unicode); + + _push_input(k); + } + if (ke.keycode != 0) { + k.instance(); + + k->set_window_id(ke.window_id); + _get_key_modifier_state(ke.osx_state, k); + k->set_pressed(ke.pressed); + k->set_echo(ke.echo); + k->set_keycode(ke.keycode); + k->set_physical_keycode(ke.physical_keycode); + + if (i + 1 < key_event_pos && key_event_buffer[i + 1].keycode == 0) { + k->set_unicode(key_event_buffer[i + 1].unicode); + } + + _push_input(k); + } + } + } + + key_event_pos = 0; +} + +void DisplayServerOSX::process_events() { + _THREAD_SAFE_METHOD_ + + while (true) { + NSEvent *event = [NSApp + nextEventMatchingMask:NSEventMaskAny + untilDate:[NSDate distantPast] + inMode:NSDefaultRunLoopMode + dequeue:YES]; + + if (event == nil) { + break; + } + + [NSApp sendEvent:event]; + } + + if (!drop_events) { + _process_key_events(); + Input::get_singleton()->flush_accumulated_events(); + } + + for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { + WindowData &wd = E->get(); + if (wd.mpath.size() > 0) { + const Vector2 mpos = _get_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream]); + if (Geometry2D::is_point_in_polygon(mpos, wd.mpath)) { + if ([wd.window_object ignoresMouseEvents]) { + [wd.window_object setIgnoresMouseEvents:NO]; + } + } else { + if (![wd.window_object ignoresMouseEvents]) { + [wd.window_object setIgnoresMouseEvents:YES]; + } + } + } else { + if ([wd.window_object ignoresMouseEvents]) { + [wd.window_object setIgnoresMouseEvents:NO]; + } + } + } + + [autoreleasePool drain]; + autoreleasePool = [[NSAutoreleasePool alloc] init]; +} + +void DisplayServerOSX::force_process_and_drop_events() { + _THREAD_SAFE_METHOD_ + + drop_events = true; + process_events(); + drop_events = false; +} + +void DisplayServerOSX::set_native_icon(const String &p_filename) { + _THREAD_SAFE_METHOD_ + + FileAccess *f = FileAccess::open(p_filename, FileAccess::READ); + ERR_FAIL_COND(!f); + + Vector<uint8_t> data; + uint32_t len = f->get_len(); + data.resize(len); + f->get_buffer((uint8_t *)&data.write[0], len); + memdelete(f); + + NSData *icon_data = [[[NSData alloc] initWithBytes:&data.write[0] length:len] autorelease]; + ERR_FAIL_COND_MSG(!icon_data, "Error reading icon data."); + + NSImage *icon = [[[NSImage alloc] initWithData:icon_data] autorelease]; + ERR_FAIL_COND_MSG(!icon, "Error loading icon."); + + [NSApp setApplicationIconImage:icon]; +} + +void DisplayServerOSX::set_icon(const Ref<Image> &p_icon) { + _THREAD_SAFE_METHOD_ + + Ref<Image> img = p_icon; + img = img->duplicate(); + img->convert(Image::FORMAT_RGBA8); + NSBitmapImageRep *imgrep = [[NSBitmapImageRep alloc] + initWithBitmapDataPlanes:NULL + 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]; + ERR_FAIL_COND(imgrep == nil); + 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; + } + + NSImage *nsimg = [[NSImage alloc] initWithSize:NSMakeSize(img->get_width(), img->get_height())]; + ERR_FAIL_COND(nsimg == nil); + + [nsimg addRepresentation:imgrep]; + [NSApp setApplicationIconImage:nsimg]; + + [imgrep release]; + [nsimg release]; +} + +Vector<String> DisplayServerOSX::get_rendering_drivers_func() { + Vector<String> drivers; + +#if defined(VULKAN_ENABLED) + drivers.push_back("vulkan"); +#endif +#if defined(OPENGL_ENABLED) + drivers.push_back("opengl_es"); +#endif + + return drivers; +} + +Point2i DisplayServerOSX::ime_get_selection() const { + return im_selection; +} + +String DisplayServerOSX::ime_get_text() const { + return im_text; +} + +DisplayServer::WindowID DisplayServerOSX::get_window_at_screen_position(const Point2i &p_position) const { + Point2i position = p_position; + position.y *= -1; + position += _get_screens_origin(); + position /= screen_get_max_scale(); + + NSInteger wnum = [NSWindow windowNumberAtPoint:NSMakePoint(position.x, position.y) belowWindowWithWindowNumber:0 /*topmost*/]; + for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { + if ([E->get().window_object windowNumber] == wnum) { + return E->key(); + } + } + return INVALID_WINDOW_ID; +} + +void DisplayServerOSX::window_attach_instance_id(ObjectID p_instance, WindowID p_window) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + windows[p_window].instance_id = p_instance; +} + +ObjectID DisplayServerOSX::window_get_attached_instance_id(WindowID p_window) const { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND_V(!windows.has(p_window), ObjectID()); + return windows[p_window].instance_id; +} + +DisplayServer *DisplayServerOSX::create_func(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { + DisplayServer *ds = memnew(DisplayServerOSX(p_rendering_driver, p_mode, p_flags, p_resolution, r_error)); + if (r_error != OK) { + ds->alert("Your video card driver does not support any of the supported Metal versions.", "Unable to initialize Video driver"); + } + return ds; +} + +DisplayServerOSX::WindowID DisplayServerOSX::_create_window(WindowMode p_mode, const Rect2i &p_rect) { + WindowID id; + const float scale = screen_get_max_scale(); + { + WindowData wd; + + wd.window_delegate = [[GodotWindowDelegate alloc] init]; + ERR_FAIL_COND_V_MSG(wd.window_delegate == nil, INVALID_WINDOW_ID, "Can't create a window delegate"); + [wd.window_delegate setWindowID:window_id_counter]; + + Point2i position = p_rect.position; + // OS X native y-coordinate relative to _get_screens_origin() is negative, + // Godot passes a positive value + position.y *= -1; + position += _get_screens_origin(); + + // initWithContentRect uses bottom-left corner of the window’s frame as origin. + wd.window_object = [[GodotWindow alloc] + initWithContentRect:NSMakeRect(position.x / scale, (position.y - p_rect.size.height) / scale, p_rect.size.width / scale, p_rect.size.height / scale) + styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable + backing:NSBackingStoreBuffered + defer:NO]; + ERR_FAIL_COND_V_MSG(wd.window_object == nil, INVALID_WINDOW_ID, "Can't create a window"); + + wd.window_view = [[GodotContentView alloc] init]; + ERR_FAIL_COND_V_MSG(wd.window_view == nil, INVALID_WINDOW_ID, "Can't create a window view"); + [wd.window_view setWindowID:window_id_counter]; + [wd.window_view setWantsLayer:TRUE]; + + [wd.window_object setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary]; + [wd.window_object setContentView:wd.window_view]; + [wd.window_object setDelegate:wd.window_delegate]; + [wd.window_object setAcceptsMouseMovedEvents:YES]; + [wd.window_object setRestorable:NO]; + + if ([wd.window_object respondsToSelector:@selector(setTabbingMode:)]) { + [wd.window_object setTabbingMode:NSWindowTabbingModeDisallowed]; + } + + CALayer *layer = [wd.window_view layer]; + if (layer) { + layer.contentsScale = scale; + } + +#if defined(VULKAN_ENABLED) + if (rendering_driver == "vulkan") { + if (context_vulkan) { + Error err = context_vulkan->window_create(window_id_counter, wd.window_view, p_rect.size.width, p_rect.size.height); + ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't create a Vulkan context"); + } + } +#endif +#if defined(OPENGL_ENABLED) + if (rendering_driver == "opengl_es") { + //TODO - reimplement OpenGLES + } +#endif + id = window_id_counter++; + windows[id] = wd; + } + + WindowData &wd = windows[id]; + window_set_mode(p_mode, id); + + const NSRect contentRect = [wd.window_view frame]; + wd.size.width = contentRect.size.width * scale; + wd.size.height = contentRect.size.height * scale; + + CALayer *layer = [wd.window_view layer]; + if (layer) { + layer.contentsScale = scale; + } + +#if defined(OPENGL_ENABLED) + if (rendering_driver == "opengl_es") { + //TODO - reimplement OpenGLES + } +#endif +#if defined(VULKAN_ENABLED) + if (rendering_driver == "vulkan") { + context_vulkan->window_resize(id, wd.size.width, wd.size.height); + } +#endif + + return id; +} + +void DisplayServerOSX::_dispatch_input_events(const Ref<InputEvent> &p_event) { + ((DisplayServerOSX *)(get_singleton()))->_dispatch_input_event(p_event); +} + +void DisplayServerOSX::_dispatch_input_event(const Ref<InputEvent> &p_event) { + _THREAD_SAFE_METHOD_ + if (!in_dispatch_input_event) { + in_dispatch_input_event = true; + + Variant ev = p_event; + Variant *evp = &ev; + Variant ret; + Callable::CallError ce; + + Ref<InputEventFromWindow> event_from_window = p_event; + if (event_from_window.is_valid() && event_from_window->get_window_id() != INVALID_WINDOW_ID) { + //send to a window + if (windows.has(event_from_window->get_window_id())) { + Callable callable = windows[event_from_window->get_window_id()].input_event_callback; + if (callable.is_null()) { + return; + } + callable.call((const Variant **)&evp, 1, ret, ce); + } + } else { + //send to all windows + for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { + Callable callable = E->get().input_event_callback; + if (callable.is_null()) { + continue; + } + callable.call((const Variant **)&evp, 1, ret, ce); + } + } + + in_dispatch_input_event = false; + } +} + +void DisplayServerOSX::release_rendering_thread() { + //TODO - reimplement OpenGLES +} + +void DisplayServerOSX::make_rendering_thread() { + //TODO - reimplement OpenGLES +} + +void DisplayServerOSX::swap_buffers() { + //TODO - reimplement OpenGLES +} + +void DisplayServerOSX::console_set_visible(bool p_enabled) { + //TODO - open terminal and redirect +} + +bool DisplayServerOSX::is_console_visible() const { + return isatty(STDIN_FILENO); +} + +DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { + Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events); + + r_error = OK; + drop_events = false; + + memset(cursors, 0, sizeof(cursors)); + cursor_shape = CURSOR_ARROW; + + key_event_pos = 0; + mouse_mode = MOUSE_MODE_VISIBLE; + last_button_state = 0; + + autoreleasePool = [[NSAutoreleasePool alloc] init]; + + eventSource = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); + ERR_FAIL_COND(!eventSource); + + CGEventSourceSetLocalEventsSuppressionInterval(eventSource, 0.0); + + // Implicitly create shared NSApplication instance + [GodotApplication sharedApplication]; + + // In case we are unbundled, make us a proper UI application + [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; + + keyboard_layout_dirty = true; + displays_arrangement_dirty = true; + displays_scale_dirty = true; + + // Register to be notified on keyboard layout changes + CFNotificationCenterAddObserver(CFNotificationCenterGetDistributedCenter(), + NULL, keyboard_layout_changed, + kTISNotifySelectedKeyboardInputSourceChanged, NULL, + CFNotificationSuspensionBehaviorDeliverImmediately); + + // Register to be notified on displays arrangement changes + CGDisplayRegisterReconfigurationCallback(displays_arrangement_changed, NULL); + + // Menu bar setup must go between sharedApplication above and + // finishLaunching below, in order to properly emulate the behavior + // of NSApplicationMain + NSMenuItem *menu_item; + NSString *title; + + NSString *nsappname = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"]; + if (nsappname == nil) { + nsappname = [[NSProcessInfo processInfo] processName]; + } + + // Setup Dock menu + dock_menu = [[NSMenu alloc] initWithTitle:@"_dock"]; + + // Setup Apple menu + apple_menu = [[[NSMenu alloc] initWithTitle:@""] autorelease]; + title = [NSString stringWithFormat:NSLocalizedString(@"About %@", nil), nsappname]; + [apple_menu addItemWithTitle:title action:@selector(showAbout:) keyEquivalent:@""]; + + [apple_menu addItem:[NSMenuItem separatorItem]]; + + NSMenu *services = [[NSMenu alloc] initWithTitle:@""]; + menu_item = [apple_menu addItemWithTitle:NSLocalizedString(@"Services", nil) action:nil keyEquivalent:@""]; + [apple_menu setSubmenu:services forItem:menu_item]; + [NSApp setServicesMenu:services]; + [services release]; + + [apple_menu addItem:[NSMenuItem separatorItem]]; + + title = [NSString stringWithFormat:NSLocalizedString(@"Hide %@", nil), nsappname]; + [apple_menu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h"]; + + menu_item = [apple_menu addItemWithTitle:NSLocalizedString(@"Hide Others", nil) action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; + [menu_item setKeyEquivalentModifierMask:(NSEventModifierFlagOption | NSEventModifierFlagCommand)]; + + [apple_menu addItemWithTitle:NSLocalizedString(@"Show all", nil) action:@selector(unhideAllApplications:) keyEquivalent:@""]; + + [apple_menu addItem:[NSMenuItem separatorItem]]; + + title = [NSString stringWithFormat:NSLocalizedString(@"Quit %@", nil), nsappname]; + [apple_menu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"]; + + // Setup menu bar + NSMenu *main_menu = [[[NSMenu alloc] initWithTitle:@""] autorelease]; + menu_item = [main_menu addItemWithTitle:@"" action:nil keyEquivalent:@""]; + [main_menu setSubmenu:apple_menu forItem:menu_item]; + [NSApp setMainMenu:main_menu]; + + [NSApp finishLaunching]; + + delegate = [[GodotApplicationDelegate alloc] init]; + ERR_FAIL_COND(!delegate); + [NSApp setDelegate:delegate]; + + //process application:openFile: event + while (true) { + NSEvent *event = [NSApp + nextEventMatchingMask:NSEventMaskAny + untilDate:[NSDate distantPast] + inMode:NSDefaultRunLoopMode + dequeue:YES]; + + if (event == nil) { + break; + } + + [NSApp sendEvent:event]; + } + + //!!!!!!!!!!!!!!!!!!!!!!!!!! + //TODO - do Vulkan and GLES2 support checks, driver selection and fallback + rendering_driver = p_rendering_driver; + +#ifndef _MSC_VER +#warning Forcing vulkan rendering driver because OpenGL not implemented yet +#endif + rendering_driver = "vulkan"; + +#if defined(OPENGL_ENABLED) + if (rendering_driver == "opengl_es") { + //TODO - reimplement OpenGLES + } +#endif +#if defined(VULKAN_ENABLED) + if (rendering_driver == "vulkan") { + context_vulkan = memnew(VulkanContextOSX); + if (context_vulkan->initialize() != OK) { + memdelete(context_vulkan); + context_vulkan = NULL; + r_error = ERR_CANT_CREATE; + ERR_FAIL_MSG("Could not initialize Vulkan"); + } + } +#endif + + Point2i window_position( + screen_get_position(0).x + (screen_get_size(0).width - p_resolution.width) / 2, + screen_get_position(0).y + (screen_get_size(0).height - p_resolution.height) / 2); + WindowID main_window = _create_window(p_mode, Rect2i(window_position, p_resolution)); + ERR_FAIL_COND(main_window == INVALID_WINDOW_ID); + for (int i = 0; i < WINDOW_FLAG_MAX; i++) { + if (p_flags & (1 << i)) { + window_set_flag(WindowFlags(i), true, main_window); + } + } + show_window(MAIN_WINDOW_ID); + +#if defined(OPENGL_ENABLED) + if (rendering_driver == "opengl_es") { + //TODO - reimplement OpenGLES + } +#endif +#if defined(VULKAN_ENABLED) + if (rendering_driver == "vulkan") { + rendering_device_vulkan = memnew(RenderingDeviceVulkan); + rendering_device_vulkan->initialize(context_vulkan); + + RasterizerRD::make_current(); + } +#endif + + [NSApp activateIgnoringOtherApps:YES]; +} + +DisplayServerOSX::~DisplayServerOSX() { + if (dock_menu) { + [dock_menu release]; + } + + for (Map<String, NSMenu *>::Element *E = submenu.front(); E; E = E->next()) { + [E->get() release]; + } + + //destroy all windows + for (Map<WindowID, WindowData>::Element *E = windows.front(); E;) { + Map<WindowID, WindowData>::Element *F = E; + E = E->next(); + [F->get().window_object setContentView:nil]; + [F->get().window_object close]; + } + + //destroy drivers +#if defined(OPENGL_ENABLED) + if (rendering_driver == "opengl_es") { + //TODO - reimplement OpenGLES + } +#endif +#if defined(VULKAN_ENABLED) + if (rendering_driver == "vulkan") { + if (rendering_device_vulkan) { + rendering_device_vulkan->finalize(); + memdelete(rendering_device_vulkan); + } + + if (context_vulkan) { + memdelete(context_vulkan); + } + } +#endif + + CFNotificationCenterRemoveObserver(CFNotificationCenterGetDistributedCenter(), NULL, kTISNotifySelectedKeyboardInputSourceChanged, NULL); + CGDisplayRemoveReconfigurationCallback(displays_arrangement_changed, NULL); + + cursors_cache.clear(); +} + +void DisplayServerOSX::register_osx_driver() { + register_create_function("osx", create_func, get_rendering_drivers_func); +} diff --git a/platform/osx/export/export.cpp b/platform/osx/export/export.cpp index cf38664022..9f2160dd9e 100644 --- a/platform/osx/export/export.cpp +++ b/platform/osx/export/export.cpp @@ -46,7 +46,6 @@ #include <sys/stat.h> class EditorExportPlatformOSX : public EditorExportPlatform { - GDCLASS(EditorExportPlatformOSX, EditorExportPlatform); int version_code; @@ -56,8 +55,10 @@ class EditorExportPlatformOSX : public EditorExportPlatform { void _fix_plist(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &plist, const String &p_binary); void _make_icon(const Ref<Image> &p_icon, Vector<uint8_t> &p_data); + Error _notarize(const Ref<EditorExportPreset> &p_preset, const String &p_path); Error _code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path); Error _create_dmg(const String &p_dmg_path, const String &p_pkg_name, const String &p_app_path_name); + void _zip_folder_recursive(zipFile &p_zip, const String &p_root_path, const String &p_folder, const String &p_pkg_name); #ifdef OSX_ENABLED bool use_codesign() const { return true; } @@ -66,17 +67,39 @@ class EditorExportPlatformOSX : public EditorExportPlatform { bool use_codesign() const { return false; } bool use_dmg() const { return false; } #endif + bool is_package_name_valid(const String &p_package, String *r_error = nullptr) const { + String pname = p_package; + + if (pname.length() == 0) { + if (r_error) { + *r_error = TTR("Identifier is missing."); + } + return false; + } + + for (int i = 0; i < pname.length(); i++) { + char32_t c = pname[i]; + if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '.')) { + if (r_error) { + *r_error = vformat(TTR("The character '%s' is not allowed in Identifier."), String::chr(c)); + } + return false; + } + } + + return true; + } protected: - virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features); - virtual void get_export_options(List<ExportOption> *r_options); + virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) override; + virtual void get_export_options(List<ExportOption> *r_options) override; public: - virtual String get_name() const { return "Mac OSX"; } - virtual String get_os_name() const { return "OSX"; } - virtual Ref<Texture> get_logo() const { return logo; } + virtual String get_name() const override { return "macOS"; } + virtual String get_os_name() const override { return "macOS"; } + virtual Ref<Texture2D> get_logo() const override { return logo; } - virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const { + virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override { List<String> list; if (use_dmg()) { list.push_back("dmg"); @@ -84,18 +107,17 @@ public: list.push_back("zip"); return list; } - virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0); - - virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const; + virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override; - virtual void get_platform_features(List<String> *r_features) { + virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override; + virtual void get_platform_features(List<String> *r_features) override { r_features->push_back("pc"); r_features->push_back("s3tc"); r_features->push_back("OSX"); } - virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) { + virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) override { } EditorExportPlatformOSX(); @@ -117,14 +139,13 @@ void EditorExportPlatformOSX::get_preset_features(const Ref<EditorExportPreset> } void EditorExportPlatformOSX::get_export_options(List<ExportOption> *r_options) { - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/info"), "Made with Godot Engine")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/icon", PROPERTY_HINT_FILE, "*.png,*.icns"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/identifier", PROPERTY_HINT_PLACEHOLDER_TEXT, "com.example.game"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/bundle_identifier", PROPERTY_HINT_PLACEHOLDER_TEXT, "com.example.game"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/signature"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/short_version"), "1.0")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/version"), "1.0")); @@ -139,7 +160,12 @@ void EditorExportPlatformOSX::get_export_options(List<ExportOption> *r_options) r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/timestamp"), true)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/hardened_runtime"), true)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/entitlements", PROPERTY_HINT_GLOBAL_FILE, "*.plist"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::POOL_STRING_ARRAY, "codesign/custom_options"), PoolStringArray())); + r_options->push_back(ExportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "codesign/custom_options"), PackedStringArray())); + + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "notarization/enable"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_id_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Apple ID email"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_id_password", PROPERTY_HINT_PLACEHOLDER_TEXT, "Enable two-factor authentication and provide app-specific password"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_team_id", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide team ID if your Apple ID belongs to multiple teams"), "")); #endif r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/s3tc"), true)); @@ -147,8 +173,7 @@ void EditorExportPlatformOSX::get_export_options(List<ExportOption> *r_options) r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/etc2"), false)); } -void _rgba8_to_packbits_encode(int p_ch, int p_size, PoolVector<uint8_t> &p_source, Vector<uint8_t> &p_dest) { - +void _rgba8_to_packbits_encode(int p_ch, int p_size, Vector<uint8_t> &p_source, Vector<uint8_t> &p_dest) { int src_len = p_size * p_size; Vector<uint8_t> result; @@ -160,11 +185,10 @@ void _rgba8_to_packbits_encode(int p_ch, int p_size, PoolVector<uint8_t> &p_sour int i = 0; while (i < src_len) { - uint8_t cur = p_source.read()[i * 4 + p_ch]; + uint8_t cur = p_source.ptr()[i * 4 + p_ch]; if (i < src_len - 2) { - - if ((p_source.read()[(i + 1) * 4 + p_ch] == cur) && (p_source.read()[(i + 2) * 4 + p_ch] == cur)) { + 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); copymem(&result.write[res_size], &buf, buf_size); @@ -176,7 +200,7 @@ void _rgba8_to_packbits_encode(int p_ch, int p_size, PoolVector<uint8_t> &p_sour bool hit_lim = true; for (int j = 3; j <= lim; j++) { - if (p_source.read()[(i + j) * 4 + p_ch] != cur) { + 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); @@ -215,7 +239,6 @@ void _rgba8_to_packbits_encode(int p_ch, int p_size, PoolVector<uint8_t> &p_sour } void EditorExportPlatformOSX::_make_icon(const Ref<Image> &p_icon, Vector<uint8_t> &p_data) { - Ref<ImageTexture> it = memnew(ImageTexture); Vector<uint8_t> data; @@ -278,7 +301,7 @@ void EditorExportPlatformOSX::_make_icon(const Ref<Image> &p_icon, Vector<uint8_ DirAccess::remove_file_or_error(path); } else { - PoolVector<uint8_t> src_data = copy->get_data(); + Vector<uint8_t> src_data = copy->get_data(); //encode 24bit RGB RLE icon { @@ -302,7 +325,7 @@ void EditorExportPlatformOSX::_make_icon(const Ref<Image> &p_icon, Vector<uint8_ data.resize(data.size() + len + 8); for (int j = 0; j < len; j++) { - data.write[ofs + 8 + j] = src_data.read()[j * 4 + 3]; + data.write[ofs + 8 + j] = src_data.ptr()[j * 4 + 3]; } len += 8; len = BSWAP32(len); @@ -320,7 +343,6 @@ void EditorExportPlatformOSX::_make_icon(const Ref<Image> &p_icon, Vector<uint8_ } void EditorExportPlatformOSX::_fix_plist(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &plist, const String &p_binary) { - String str; String strnew; str.parse_utf8((const char *)plist.ptr(), plist.size()); @@ -332,8 +354,8 @@ void EditorExportPlatformOSX::_fix_plist(const Ref<EditorExportPreset> &p_preset strnew += lines[i].replace("$name", p_binary) + "\n"; } else if (lines[i].find("$info") != -1) { strnew += lines[i].replace("$info", p_preset->get("application/info")) + "\n"; - } else if (lines[i].find("$identifier") != -1) { - strnew += lines[i].replace("$identifier", p_preset->get("application/identifier")) + "\n"; + } else if (lines[i].find("$bundle_identifier") != -1) { + strnew += lines[i].replace("$bundle_identifier", p_preset->get("application/bundle_identifier")) + "\n"; } else if (lines[i].find("$short_version") != -1) { strnew += lines[i].replace("$short_version", p_preset->get("application/short_version")) + "\n"; } else if (lines[i].find("$version") != -1) { @@ -369,7 +391,54 @@ void EditorExportPlatformOSX::_fix_plist(const Ref<EditorExportPreset> &p_preset - and then wrap it up in a DMG **/ +Error EditorExportPlatformOSX::_notarize(const Ref<EditorExportPreset> &p_preset, const String &p_path) { +#ifdef OSX_ENABLED + List<String> args; + + args.push_back("altool"); + args.push_back("--notarize-app"); + + args.push_back("--primary-bundle-id"); + args.push_back(p_preset->get("application/bundle_identifier")); + + args.push_back("--username"); + args.push_back(p_preset->get("notarization/apple_id_name")); + + args.push_back("--password"); + args.push_back(p_preset->get("notarization/apple_id_password")); + + args.push_back("--type"); + args.push_back("osx"); + + if (p_preset->get("notarization/apple_team_id")) { + args.push_back("--asc-provider"); + args.push_back(p_preset->get("notarization/apple_team_id")); + } + + args.push_back("--file"); + args.push_back(p_path); + + String str; + Error err = OS::get_singleton()->execute("xcrun", args, true, nullptr, &str, nullptr, true); + ERR_FAIL_COND_V(err != OK, err); + + print_line("altool (" + p_path + "):\n" + str); + if (str.find("RequestUUID") == -1) { + EditorNode::add_io_error("altool: " + str); + return FAILED; + } else { + print_line("Note: The notarization process generally takes less than an hour. When the process is completed, you'll receive an email."); + print_line(" You can check progress manually by opening a Terminal and running the following command:"); + print_line(" \"xcrun altool --notarization-history 0 -u <your email> -p <app-specific pwd>\""); + } + +#endif + + return OK; +} + Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path) { +#ifdef OSX_ENABLED List<String> args; if (p_preset->get("codesign/timestamp")) { @@ -380,13 +449,12 @@ Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_prese args.push_back("runtime"); } - if (p_preset->get("codesign/entitlements") != "") { - /* this should point to our entitlements.plist file that sandboxes our application, I don't know if this should also be placed in our app bundle */ + if ((p_preset->get("codesign/entitlements") != "") && (p_path.get_extension() != "dmg")) { args.push_back("--entitlements"); args.push_back(p_preset->get("codesign/entitlements")); } - PoolStringArray user_args = p_preset->get("codesign/custom_options"); + PackedStringArray user_args = p_preset->get("codesign/custom_options"); for (int i = 0; i < user_args.size(); i++) { String user_arg = user_args[i].strip_edges(); if (!user_arg.empty()) { @@ -402,10 +470,10 @@ Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_prese args.push_back(p_path); String str; - Error err = OS::get_singleton()->execute("codesign", args, true, NULL, &str, NULL, true); + Error err = OS::get_singleton()->execute("codesign", args, true, nullptr, &str, nullptr, true); ERR_FAIL_COND_V(err != OK, err); - print_line("codesign (" + p_path + "): " + str); + print_line("codesign (" + p_path + "):\n" + str); if (str.find("no identity found") != -1) { EditorNode::add_io_error("codesign: no identity found"); return FAILED; @@ -414,6 +482,7 @@ Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_prese EditorNode::add_io_error("codesign: invalid entitlements file"); return FAILED; } +#endif return OK; } @@ -435,7 +504,7 @@ Error EditorExportPlatformOSX::_create_dmg(const String &p_dmg_path, const Strin args.push_back(p_app_path_name); String str; - Error err = OS::get_singleton()->execute("hdiutil", args, true, NULL, &str, NULL, true); + Error err = OS::get_singleton()->execute("hdiutil", args, true, nullptr, &str, nullptr, true); ERR_FAIL_COND_V(err != OK, err); print_line("hdiutil returned: " + str); @@ -458,10 +527,11 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p EditorProgress ep("export", "Exporting for OSX", 3, true); - if (p_debug) + if (p_debug) { src_pkg_name = p_preset->get("custom_template/debug"); - else + } else { src_pkg_name = p_preset->get("custom_template/release"); + } if (src_pkg_name == "") { String err; @@ -476,7 +546,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p return ERR_FILE_BAD_PATH; } - FileAccess *src_f = NULL; + FileAccess *src_f = nullptr; zlib_filefunc_def io = zipio_create_io_from_file(&src_f); if (ep.step("Creating app", 0)) { @@ -485,7 +555,6 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p unzFile src_pkg_zip = unzOpen2(src_pkg_name.utf8().get_data(), &io); if (!src_pkg_zip) { - EditorNode::add_io_error("Could not find template app to export:\n" + src_pkg_name); return ERR_FILE_NOT_FOUND; } @@ -495,80 +564,69 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p String binary_to_use = "godot_osx_" + String(p_debug ? "debug" : "release") + ".64"; String pkg_name; - if (p_preset->get("application/name") != "") + if (p_preset->get("application/name") != "") { pkg_name = p_preset->get("application/name"); // app_name - else if (String(ProjectSettings::get_singleton()->get("application/config/name")) != "") + } else if (String(ProjectSettings::get_singleton()->get("application/config/name")) != "") { pkg_name = String(ProjectSettings::get_singleton()->get("application/config/name")); - else + } else { pkg_name = "Unnamed"; + } String pkg_name_safe = OS::get_singleton()->get_safe_dir_name(pkg_name); Error err = OK; String tmp_app_path_name = ""; - zlib_filefunc_def io2 = io; - FileAccess *dst_f = NULL; - io2.opaque = &dst_f; - zipFile dst_pkg_zip = NULL; - DirAccess *tmp_app_path = NULL; + DirAccess *tmp_app_path = nullptr; String export_format = use_dmg() && p_path.ends_with("dmg") ? "dmg" : "zip"; - if (export_format == "dmg") { - // We're on OSX so we can export to DMG, but first we create our application bundle - tmp_app_path_name = EditorSettings::get_singleton()->get_cache_dir().plus_file(pkg_name + ".app"); - print_line("Exporting to " + tmp_app_path_name); - tmp_app_path = DirAccess::create_for_path(tmp_app_path_name); - if (!tmp_app_path) { - err = ERR_CANT_CREATE; - } - // Create our folder structure or rely on unzip? - if (err == OK) { - print_line("Creating " + tmp_app_path_name + "/Contents/MacOS"); - err = tmp_app_path->make_dir_recursive(tmp_app_path_name + "/Contents/MacOS"); - } + // Create our application bundle. + tmp_app_path_name = EditorSettings::get_singleton()->get_cache_dir().plus_file(pkg_name + ".app"); + print_line("Exporting to " + tmp_app_path_name); + tmp_app_path = DirAccess::create_for_path(tmp_app_path_name); + if (!tmp_app_path) { + err = ERR_CANT_CREATE; + } - if (err == OK) { - print_line("Creating " + tmp_app_path_name + "/Contents/Frameworks"); - err = tmp_app_path->make_dir_recursive(tmp_app_path_name + "/Contents/Frameworks"); - } + // Create our folder structure. + if (err == OK) { + print_line("Creating " + tmp_app_path_name + "/Contents/MacOS"); + err = tmp_app_path->make_dir_recursive(tmp_app_path_name + "/Contents/MacOS"); + } - if (err == OK) { - print_line("Creating " + tmp_app_path_name + "/Contents/Resources"); - err = tmp_app_path->make_dir_recursive(tmp_app_path_name + "/Contents/Resources"); - } - } else { - // Open our destination zip file - dst_pkg_zip = zipOpen2(p_path.utf8().get_data(), APPEND_STATUS_CREATE, NULL, &io2); - if (!dst_pkg_zip) { - err = ERR_CANT_CREATE; - } + if (err == OK) { + print_line("Creating " + tmp_app_path_name + "/Contents/Frameworks"); + err = tmp_app_path->make_dir_recursive(tmp_app_path_name + "/Contents/Frameworks"); } - // Now process our template + if (err == OK) { + print_line("Creating " + tmp_app_path_name + "/Contents/Resources"); + err = tmp_app_path->make_dir_recursive(tmp_app_path_name + "/Contents/Resources"); + } + + // Now process our template. bool found_binary = false; int total_size = 0; while (ret == UNZ_OK && err == OK) { bool is_execute = false; - //get filename + // Get filename. unz_file_info info; char fname[16384]; - ret = unzGetCurrentFileInfo(src_pkg_zip, &info, fname, 16384, NULL, 0, NULL, 0); + ret = unzGetCurrentFileInfo(src_pkg_zip, &info, fname, 16384, nullptr, 0, nullptr, 0); String file = fname; Vector<uint8_t> data; data.resize(info.uncompressed_size); - //read + // Read. unzOpenCurrentFile(src_pkg_zip); unzReadCurrentFile(src_pkg_zip, data.ptrw(), data.size()); unzCloseCurrentFile(src_pkg_zip); - //write - + // Write. file = file.replace_first("osx_template.app/", ""); if (file == "Contents/Info.plist") { @@ -578,7 +636,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p if (file.begins_with("Contents/MacOS/godot_")) { if (file != "Contents/MacOS/" + binary_to_use) { ret = unzGoToNextFile(src_pkg_zip); - continue; //ignore! + continue; // skip } found_binary = true; is_execute = true; @@ -586,12 +644,13 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p } if (file == "Contents/Resources/icon.icns") { - //see if there is an icon + // See if there is an icon. String iconpath; - if (p_preset->get("application/icon") != "") + if (p_preset->get("application/icon") != "") { iconpath = p_preset->get("application/icon"); - else + } else { iconpath = ProjectSettings::get_singleton()->get("application/config/icon"); + } if (iconpath != "") { if (iconpath.get_extension() == "icns") { @@ -614,18 +673,17 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p } if (data.size() > 0) { - if (file.find("/data.mono.osx.64.release_debug/") != -1) { if (!p_debug) { ret = unzGoToNextFile(src_pkg_zip); - continue; //skip + continue; // skip } file = file.replace("/data.mono.osx.64.release_debug/", "/data_" + pkg_name_safe + "/"); } if (file.find("/data.mono.osx.64.release/") != -1) { if (p_debug) { ret = unzGoToNextFile(src_pkg_zip); - continue; //skip + continue; // skip } file = file.replace("/data.mono.osx.64.release/", "/data_" + pkg_name_safe + "/"); } @@ -633,66 +691,35 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p print_line("ADDING: " + file + " size: " + itos(data.size())); total_size += data.size(); - if (export_format == "dmg") { - // write it into our application bundle - file = tmp_app_path_name.plus_file(file); - if (err == OK) { - err = tmp_app_path->make_dir_recursive(file.get_base_dir()); - } - if (err == OK) { - // write the file, need to add chmod - FileAccess *f = FileAccess::open(file, FileAccess::WRITE); - if (f) { - f->store_buffer(data.ptr(), data.size()); - f->close(); - if (is_execute) { - // Chmod with 0755 if the file is executable - FileAccess::set_unix_permissions(file, 0755); - } - memdelete(f); - } else { - err = ERR_CANT_CREATE; + // Write it into our application bundle. + file = tmp_app_path_name.plus_file(file); + if (err == OK) { + err = tmp_app_path->make_dir_recursive(file.get_base_dir()); + } + if (err == OK) { + FileAccess *f = FileAccess::open(file, FileAccess::WRITE); + if (f) { + f->store_buffer(data.ptr(), data.size()); + f->close(); + if (is_execute) { + // chmod with 0755 if the file is executable. + FileAccess::set_unix_permissions(file, 0755); } + memdelete(f); + } else { + err = ERR_CANT_CREATE; } - } else { - // add it to our zip file - file = pkg_name + ".app/" + file; - - zip_fileinfo fi; - fi.tmz_date.tm_hour = info.tmu_date.tm_hour; - fi.tmz_date.tm_min = info.tmu_date.tm_min; - fi.tmz_date.tm_sec = info.tmu_date.tm_sec; - fi.tmz_date.tm_mon = info.tmu_date.tm_mon; - fi.tmz_date.tm_mday = info.tmu_date.tm_mday; - fi.tmz_date.tm_year = info.tmu_date.tm_year; - fi.dosDate = info.dosDate; - fi.internal_fa = info.internal_fa; - fi.external_fa = info.external_fa; - - zipOpenNewFileInZip(dst_pkg_zip, - file.utf8().get_data(), - &fi, - NULL, - 0, - NULL, - 0, - NULL, - Z_DEFLATED, - Z_DEFAULT_COMPRESSION); - - zipWriteInFileInZip(dst_pkg_zip, data.ptr(), data.size()); - zipCloseFileInZip(dst_pkg_zip); } } ret = unzGoToNextFile(src_pkg_zip); } - // we're done with our source zip + // We're done with our source zip. unzClose(src_pkg_zip); if (!found_binary) { - ERR_PRINTS("Requested template binary '" + binary_to_use + "' not found. It might be missing from your template archive."); + ERR_PRINT("Requested template binary '" + binary_to_use + "' not found. It might be missing from your template archive."); err = ERR_FILE_NOT_FOUND; } @@ -701,124 +728,143 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p return ERR_SKIP; } - if (export_format == "dmg") { - String pack_path = tmp_app_path_name + "/Contents/Resources/" + pkg_name + ".pck"; - Vector<SharedObject> shared_objects; - err = save_pack(p_preset, pack_path, &shared_objects); + String pack_path = tmp_app_path_name + "/Contents/Resources/" + pkg_name + ".pck"; + Vector<SharedObject> shared_objects; + err = save_pack(p_preset, pack_path, &shared_objects); - // see if we can code sign our new package - bool sign_enabled = p_preset->get("codesign/enable"); + // See if we can code sign our new package. + bool sign_enabled = p_preset->get("codesign/enable"); - if (err == OK) { - DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - for (int i = 0; i < shared_objects.size(); i++) { - err = da->copy(shared_objects[i].path, tmp_app_path_name + "/Contents/Frameworks/" + shared_objects[i].path.get_file()); - if (err == OK && sign_enabled) { - err = _code_sign(p_preset, tmp_app_path_name + "/Contents/Frameworks/" + shared_objects[i].path.get_file()); - } + if (err == OK) { + DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + for (int i = 0; i < shared_objects.size(); i++) { + err = da->copy(shared_objects[i].path, tmp_app_path_name + "/Contents/Frameworks/" + shared_objects[i].path.get_file()); + if (err == OK && sign_enabled) { + err = _code_sign(p_preset, tmp_app_path_name + "/Contents/Frameworks/" + shared_objects[i].path.get_file()); } - memdelete(da); } + memdelete(da); + } - if (err == OK && sign_enabled) { - if (ep.step("Code signing bundle", 2)) { - return ERR_SKIP; - } - - // the order in which we code sign is important, this is a bit of a shame or we could do this in our loop that extracts the files from our ZIP - - // start with our application - err = _code_sign(p_preset, tmp_app_path_name + "/Contents/MacOS/" + pkg_name); - - ///@TODO we should check the contents of /Contents/Frameworks for frameworks to sign + if (err == OK && sign_enabled) { + if (ep.step("Code signing bundle", 2)) { + return ERR_SKIP; } + err = _code_sign(p_preset, tmp_app_path_name + "/Contents/MacOS/" + pkg_name); + } - // and finally create a DMG + if (export_format == "dmg") { + // Create a DMG. if (err == OK) { if (ep.step("Making DMG", 3)) { return ERR_SKIP; } err = _create_dmg(p_path, pkg_name, tmp_app_path_name); } - - // Clean up temporary .app dir - OS::get_singleton()->move_to_trash(tmp_app_path_name); - - } else { // pck - - String pack_path = EditorSettings::get_singleton()->get_cache_dir().plus_file(pkg_name + ".pck"); - - Vector<SharedObject> shared_objects; - err = save_pack(p_preset, pack_path, &shared_objects); - - if (err == OK) { - zipOpenNewFileInZip(dst_pkg_zip, - (pkg_name + ".app/Contents/Resources/" + pkg_name + ".pck").utf8().get_data(), - NULL, - NULL, - 0, - NULL, - 0, - NULL, - Z_DEFLATED, - Z_DEFAULT_COMPRESSION); - - FileAccess *pf = FileAccess::open(pack_path, FileAccess::READ); - if (pf) { - const int BSIZE = 16384; - uint8_t buf[BSIZE]; - - while (true) { - - int r = pf->get_buffer(buf, BSIZE); - if (r <= 0) - break; - zipWriteInFileInZip(dst_pkg_zip, buf, r); - } - - zipCloseFileInZip(dst_pkg_zip); - memdelete(pf); - } else { - err = ERR_CANT_OPEN; + // Sign DMG. + if (err == OK && sign_enabled) { + if (ep.step("Code signing DMG", 3)) { + return ERR_SKIP; } + err = _code_sign(p_preset, p_path); } - + } else { + // Create ZIP. if (err == OK) { - //add shared objects - for (int i = 0; i < shared_objects.size(); i++) { - Vector<uint8_t> file = FileAccess::get_file_as_array(shared_objects[i].path); - ERR_CONTINUE(file.empty()); - - zipOpenNewFileInZip(dst_pkg_zip, - (pkg_name + ".app/Contents/Frameworks/").plus_file(shared_objects[i].path.get_file()).utf8().get_data(), - NULL, - NULL, - 0, - NULL, - 0, - NULL, - Z_DEFLATED, - Z_DEFAULT_COMPRESSION); - - zipWriteInFileInZip(dst_pkg_zip, file.ptr(), file.size()); - zipCloseFileInZip(dst_pkg_zip); + if (ep.step("Making ZIP", 3)) { + return ERR_SKIP; + } + if (FileAccess::exists(p_path)) { + OS::get_singleton()->move_to_trash(p_path); } + + FileAccess *dst_f = nullptr; + zlib_filefunc_def io_dst = zipio_create_io_from_file(&dst_f); + zipFile zip = zipOpen2(p_path.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io_dst); + + _zip_folder_recursive(zip, EditorSettings::get_singleton()->get_cache_dir(), pkg_name + ".app", pkg_name); + + zipClose(zip, nullptr); } + } - // Clean up generated file. - DirAccess::remove_file_or_error(pack_path); + bool noto_enabled = p_preset->get("notarization/enable"); + if (err == OK && noto_enabled) { + if (ep.step("Sending archive for notarization", 4)) { + return ERR_SKIP; + } + err = _notarize(p_preset, p_path); } - } - if (dst_pkg_zip) { - zipClose(dst_pkg_zip, NULL); + // Clean up temporary .app dir. + OS::get_singleton()->move_to_trash(tmp_app_path_name); } return err; } -bool EditorExportPlatformOSX::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const { +void EditorExportPlatformOSX::_zip_folder_recursive(zipFile &p_zip, const String &p_root_path, const String &p_folder, const String &p_pkg_name) { + String dir = p_root_path.plus_file(p_folder); + + DirAccess *da = DirAccess::open(dir); + da->list_dir_begin(); + String f; + while ((f = da->get_next()) != "") { + if (f == "." || f == "..") { + continue; + } + if (da->current_is_dir()) { + _zip_folder_recursive(p_zip, p_root_path, p_folder.plus_file(f), p_pkg_name); + } else { + bool is_executable = (p_folder.ends_with("MacOS") && (f == p_pkg_name)); + + OS::Time time = OS::get_singleton()->get_time(); + OS::Date date = OS::get_singleton()->get_date(); + + zip_fileinfo zipfi; + zipfi.tmz_date.tm_hour = time.hour; + zipfi.tmz_date.tm_mday = date.day; + zipfi.tmz_date.tm_min = time.min; + zipfi.tmz_date.tm_mon = date.month - 1; // Note: "tm" month range - 0..11, Godot month range - 1..12, http://www.cplusplus.com/reference/ctime/tm/ + zipfi.tmz_date.tm_sec = time.sec; + zipfi.tmz_date.tm_year = date.year; + zipfi.dosDate = 0; + // 0100000: regular file type + // 0000755: permissions rwxr-xr-x + // 0000644: permissions rw-r--r-- + uint32_t _mode = (is_executable ? 0100755 : 0100644); + zipfi.external_fa = (_mode << 16L) | !(_mode & 0200); + zipfi.internal_fa = 0; + + zipOpenNewFileInZip4(p_zip, + p_folder.plus_file(f).utf8().get_data(), + &zipfi, + nullptr, + 0, + nullptr, + 0, + nullptr, + Z_DEFLATED, + Z_DEFAULT_COMPRESSION, + 0, + -MAX_WBITS, + DEF_MEM_LEVEL, + Z_DEFAULT_STRATEGY, + nullptr, + 0, + 0x0314, // "version made by", 0x03 - Unix, 0x14 - ZIP specification version 2.0, required to store Unix file permissions + 0); + + Vector<uint8_t> array = FileAccess::get_file_as_array(dir.plus_file(f)); + zipWriteInFileInZip(p_zip, array.ptr(), array.size()); + zipCloseFileInZip(p_zip); + } + } + da->list_dir_end(); + memdelete(da); +} +bool EditorExportPlatformOSX::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const { String err; bool valid = false; @@ -843,13 +889,48 @@ bool EditorExportPlatformOSX::can_export(const Ref<EditorExportPreset> &p_preset valid = dvalid || rvalid; r_missing_templates = !valid; - if (!err.empty()) + String identifier = p_preset->get("application/bundle_identifier"); + String pn_err; + if (!is_package_name_valid(identifier, &pn_err)) { + err += TTR("Invalid bundle identifier:") + " " + pn_err + "\n"; + valid = false; + } + + bool sign_enabled = p_preset->get("codesign/enable"); + if (sign_enabled) { + if (p_preset->get("codesign/identity") == "") { + err += TTR("Codesign: identity not specified.") + "\n"; + valid = false; + } + } + bool noto_enabled = p_preset->get("notarization/enable"); + if (noto_enabled) { + if (!sign_enabled) { + err += TTR("Notarization: code signing required.") + "\n"; + valid = false; + } + bool hr_enabled = p_preset->get("codesign/hardened_runtime"); + if (!hr_enabled) { + err += TTR("Notarization: hardened runtime required.") + "\n"; + valid = false; + } + if (p_preset->get("notarization/apple_id_name") == "") { + err += TTR("Notarization: Apple ID name not specified.") + "\n"; + valid = false; + } + if (p_preset->get("notarization/apple_id_password") == "") { + err += TTR("Notarization: Apple ID password not specified.") + "\n"; + valid = false; + } + } + + if (!err.empty()) { r_error = err; + } return valid; } EditorExportPlatformOSX::EditorExportPlatformOSX() { - Ref<Image> img = memnew(Image(_osx_logo)); logo.instance(); logo->create_from_image(img); @@ -859,7 +940,6 @@ EditorExportPlatformOSX::~EditorExportPlatformOSX() { } void register_osx_exporter() { - Ref<EditorExportPlatformOSX> platform; platform.instance(); diff --git a/platform/osx/export/export.h b/platform/osx/export/export.h index 7b8832cb01..4ddcec09fb 100644 --- a/platform/osx/export/export.h +++ b/platform/osx/export/export.h @@ -28,4 +28,9 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ +#ifndef OSX_EXPORT_H +#define OSX_EXPORT_H + void register_osx_exporter(); + +#endif // OSX_EXPORT_H diff --git a/platform/osx/godot_main_osx.mm b/platform/osx/godot_main_osx.mm index e6f8cbecf1..4e73d5441c 100644 --- a/platform/osx/godot_main_osx.mm +++ b/platform/osx/godot_main_osx.mm @@ -36,6 +36,11 @@ #include <unistd.h> int main(int argc, char **argv) { +#if defined(VULKAN_ENABLED) + // MoltenVK - enable full component swizzling support + setenv("MVK_CONFIG_FULL_IMAGE_VIEW_SWIZZLE", "1", 1); +#endif + int first_arg = 1; const char *dbg_arg = "-NSDocumentRevisionsDebugMode"; printf("arguments\n"); @@ -55,6 +60,9 @@ int main(int argc, char **argv) { OS_OSX os; Error err; + // We must override main when testing is enabled + TEST_MAIN_OVERRIDE + if (os.open_with_filename != "") { char *argv_c = (char *)malloc(os.open_with_filename.utf8().size()); memcpy(argv_c, os.open_with_filename.utf8().get_data(), os.open_with_filename.utf8().size()); diff --git a/platform/osx/joypad_osx.cpp b/platform/osx/joypad_osx.cpp index 13ece678f3..cfc371710b 100644 --- a/platform/osx/joypad_osx.cpp +++ b/platform/osx/joypad_osx.cpp @@ -34,13 +34,13 @@ #define GODOT_JOY_LOOP_RUN_MODE CFSTR("GodotJoypad") -static JoypadOSX *self = NULL; +static JoypadOSX *self = nullptr; joypad::joypad() { - device_ref = NULL; - ff_device = NULL; - ff_axes = NULL; - ff_directions = NULL; + device_ref = nullptr; + ff_device = nullptr; + ff_axes = nullptr; + ff_directions = nullptr; ffservice = 0; ff_timestamp = 0; id = 0; @@ -53,7 +53,7 @@ joypad::joypad() { ff_effect.dwTriggerButton = FFEB_NOTRIGGER; ff_effect.dwStartDelay = 0; ff_effect.dwTriggerRepeatInterval = 0; - ff_effect.lpEnvelope = NULL; + ff_effect.lpEnvelope = nullptr; ff_effect.cbTypeSpecificParams = sizeof(FFCONSTANTFORCE); ff_effect.lpvTypeSpecificParams = &ff_constant_force; ff_effect.dwSize = sizeof(ff_effect); @@ -98,6 +98,7 @@ int joypad::get_hid_element_state(rec_element *p_element) const { } return value; } + void joypad::add_hid_element(IOHIDElementRef p_element) { const CFTypeID elementTypeID = p_element ? CFGetTypeID(p_element) : 0; @@ -105,7 +106,7 @@ void joypad::add_hid_element(IOHIDElementRef p_element) { const IOHIDElementCookie cookie = IOHIDElementGetCookie(p_element); const uint32_t usagePage = IOHIDElementGetUsagePage(p_element); const uint32_t usage = IOHIDElementGetUsage(p_element); - Vector<rec_element> *list = NULL; + Vector<rec_element> *list = nullptr; switch (IOHIDElementGetType(p_element)) { case kIOHIDElementTypeInput_Misc: @@ -208,9 +209,8 @@ void joypad::add_hid_elements(CFArrayRef p_array) { CFArrayApplyFunction(p_array, range, hid_element_added, this); } -static void joypad_removed_callback(void *ctx, IOReturn result, void *sender) { - int id = (intptr_t)ctx; - self->_device_removed(id); +static void joypad_removed_callback(void *ctx, IOReturn res, void *sender, IOHIDDeviceRef ioHIDDeviceObject) { + self->_device_removed(res, ioHIDDeviceObject); } static void joypad_added_callback(void *ctx, IOReturn res, void *sender, IOHIDDeviceRef ioHIDDeviceObject) { @@ -241,7 +241,6 @@ static bool is_joypad(IOHIDDeviceRef p_device_ref) { } void JoypadOSX::_device_added(IOReturn p_res, IOHIDDeviceRef p_device) { - if (p_res != kIOReturnSuccess || have_device(p_device)) { return; } @@ -250,7 +249,7 @@ void JoypadOSX::_device_added(IOReturn p_res, IOHIDDeviceRef p_device) { if (is_joypad(p_device)) { configure_joypad(p_device, &new_joypad); #if MAC_OS_X_VERSION_MIN_REQUIRED < 1060 - if (IOHIDDeviceGetService != NULL) { + if (IOHIDDeviceGetService != nullptr) { #endif const io_service_t ioservice = IOHIDDeviceGetService(p_device); if ((ioservice) && (FFIsForceFeedback(ioservice) == FF_OK) && new_joypad.config_force_feedback(ioservice)) { @@ -261,22 +260,19 @@ void JoypadOSX::_device_added(IOReturn p_res, IOHIDDeviceRef p_device) { #endif device_list.push_back(new_joypad); } - IOHIDDeviceRegisterRemovalCallback(p_device, joypad_removed_callback, (void *)(intptr_t)new_joypad.id); IOHIDDeviceScheduleWithRunLoop(p_device, CFRunLoopGetCurrent(), GODOT_JOY_LOOP_RUN_MODE); } -void JoypadOSX::_device_removed(int p_id) { - - int device = get_joy_index(p_id); +void JoypadOSX::_device_removed(IOReturn p_res, IOHIDDeviceRef p_device) { + int device = get_joy_ref(p_device); ERR_FAIL_COND(device == -1); - input->joy_connection_changed(p_id, false, ""); + input->joy_connection_changed(device_list[device].id, false, ""); device_list.write[device].free(); device_list.remove(device); } static String _hex_str(uint8_t p_byte) { - static const char *dict = "0123456789abcdef"; char ret[3]; ret[2] = 0; @@ -288,7 +284,6 @@ static String _hex_str(uint8_t p_byte) { } bool JoypadOSX::configure_joypad(IOHIDDeviceRef p_device_ref, joypad *p_joy) { - p_joy->device_ref = p_device_ref; /* get device name */ String name; @@ -316,9 +311,16 @@ bool JoypadOSX::configure_joypad(IOHIDDeviceRef p_device_ref, joypad *p_joy) { if (refCF) { CFNumberGetValue((CFNumberRef)refCF, kCFNumberSInt32Type, &product_id); } + + int version = 0; + refCF = IOHIDDeviceGetProperty(p_device_ref, CFSTR(kIOHIDVersionNumberKey)); + if (refCF) { + CFNumberGetValue((CFNumberRef)refCF, kCFNumberSInt32Type, &version); + } + if (vendor && product_id) { char uid[128]; - sprintf(uid, "%04x%08x%04x%08x", OSSwapHostToBigInt32(vendor), 0, OSSwapHostToBigInt32(product_id), 0); + sprintf(uid, "%08x%08x%08x%08x", OSSwapHostToBigInt32(3), OSSwapHostToBigInt32(vendor), OSSwapHostToBigInt32(product_id), OSSwapHostToBigInt32(version)); input->joy_connection_changed(id, true, name, uid); } else { //bluetooth device @@ -332,7 +334,7 @@ bool JoypadOSX::configure_joypad(IOHIDDeviceRef p_device_ref, joypad *p_joy) { input->joy_connection_changed(id, true, name, guid); } - CFArrayRef array = IOHIDDeviceCopyMatchingElements(p_device_ref, NULL, kIOHIDOptionsTypeNone); + CFArrayRef array = IOHIDDeviceCopyMatchingElements(p_device_ref, nullptr, kIOHIDOptionsTypeNone); if (array) { p_joy->add_hid_elements(array); CFRelease(array); @@ -348,7 +350,6 @@ bool JoypadOSX::configure_joypad(IOHIDDeviceRef p_device_ref, joypad *p_joy) { } \ } bool joypad::config_force_feedback(io_service_t p_service) { - HRESULT ret = FFCreateDevice(p_service, &ff_device); ERR_FAIL_COND_V(ret != FF_OK, false); @@ -370,13 +371,13 @@ bool joypad::config_force_feedback(io_service_t p_service) { #define TEST_FF(ff) (features.supportedEffects & (ff)) bool joypad::check_ff_features() { - FFCAPABILITIES features; HRESULT ret = FFDeviceGetForceFeedbackCapabilities(ff_device, &features); if (ret == FF_OK && (features.supportedEffects & FFCAP_ET_CONSTANTFORCE)) { uint32_t val; ret = FFDeviceGetForceFeedbackProperty(ff_device, FFPROP_FFGAIN, &val, sizeof(val)); - if (ret != FF_OK) return false; + if (ret != FF_OK) + return false; int num_axes = features.numFfAxes; ff_axes = (DWORD *)memalloc(sizeof(DWORD) * num_axes); ff_directions = (LONG *)memalloc(sizeof(LONG) * num_axes); @@ -397,38 +398,38 @@ bool joypad::check_ff_features() { static int process_hat_value(int p_min, int p_max, int p_value) { int range = (p_max - p_min + 1); int value = p_value - p_min; - int hat_value = InputDefault::HAT_MASK_CENTER; + int hat_value = Input::HAT_MASK_CENTER; if (range == 4) { value *= 2; } switch (value) { case 0: - hat_value = InputDefault::HAT_MASK_UP; + hat_value = Input::HAT_MASK_UP; break; case 1: - hat_value = InputDefault::HAT_MASK_UP | InputDefault::HAT_MASK_RIGHT; + hat_value = Input::HAT_MASK_UP | Input::HAT_MASK_RIGHT; break; case 2: - hat_value = InputDefault::HAT_MASK_RIGHT; + hat_value = Input::HAT_MASK_RIGHT; break; case 3: - hat_value = InputDefault::HAT_MASK_DOWN | InputDefault::HAT_MASK_RIGHT; + hat_value = Input::HAT_MASK_DOWN | Input::HAT_MASK_RIGHT; break; case 4: - hat_value = InputDefault::HAT_MASK_DOWN; + hat_value = Input::HAT_MASK_DOWN; break; case 5: - hat_value = InputDefault::HAT_MASK_DOWN | InputDefault::HAT_MASK_LEFT; + hat_value = Input::HAT_MASK_DOWN | Input::HAT_MASK_LEFT; break; case 6: - hat_value = InputDefault::HAT_MASK_LEFT; + hat_value = Input::HAT_MASK_LEFT; break; case 7: - hat_value = InputDefault::HAT_MASK_UP | InputDefault::HAT_MASK_LEFT; + hat_value = Input::HAT_MASK_UP | Input::HAT_MASK_LEFT; break; default: - hat_value = InputDefault::HAT_MASK_CENTER; + hat_value = Input::HAT_MASK_CENTER; break; } return hat_value; @@ -440,8 +441,8 @@ void JoypadOSX::poll_joypads() const { } } -static const InputDefault::JoyAxis axis_correct(int p_value, int p_min, int p_max) { - InputDefault::JoyAxis jx; +static const Input::JoyAxis axis_correct(int p_value, int p_min, int p_max) { + Input::JoyAxis jx; if (p_min < 0) { jx.min = -1; if (p_value < 0) { @@ -511,7 +512,16 @@ void JoypadOSX::joypad_vibration_stop(int p_id, uint64_t p_timestamp) { int JoypadOSX::get_joy_index(int p_id) const { for (int i = 0; i < device_list.size(); i++) { - if (device_list[i].id == p_id) return i; + if (device_list[i].id == p_id) + return i; + } + return -1; +} + +int JoypadOSX::get_joy_ref(IOHIDDeviceRef p_device) const { + for (int i = 0; i < device_list.size(); i++) { + if (device_list[i].device_ref == p_device) + return i; } return -1; } @@ -526,7 +536,7 @@ bool JoypadOSX::have_device(IOHIDDeviceRef p_device) const { } static CFDictionaryRef create_match_dictionary(const UInt32 page, const UInt32 usage, int *okay) { - CFDictionaryRef retval = NULL; + CFDictionaryRef retval = nullptr; CFNumberRef pageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page); CFNumberRef usageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage); const void *keys[2] = { (void *)CFSTR(kIOHIDDeviceUsagePageKey), (void *)CFSTR(kIOHIDDeviceUsageKey) }; @@ -551,13 +561,13 @@ static CFDictionaryRef create_match_dictionary(const UInt32 page, const UInt32 u } void JoypadOSX::config_hid_manager(CFArrayRef p_matching_array) const { - CFRunLoopRef runloop = CFRunLoopGetCurrent(); IOReturn ret = IOHIDManagerOpen(hid_manager, kIOHIDOptionsTypeNone); ERR_FAIL_COND(ret != kIOReturnSuccess); IOHIDManagerSetDeviceMatchingMultiple(hid_manager, p_matching_array); - IOHIDManagerRegisterDeviceMatchingCallback(hid_manager, joypad_added_callback, NULL); + IOHIDManagerRegisterDeviceMatchingCallback(hid_manager, joypad_added_callback, nullptr); + IOHIDManagerRegisterDeviceRemovalCallback(hid_manager, joypad_removed_callback, nullptr); IOHIDManagerScheduleWithRunLoop(hid_manager, runloop, GODOT_JOY_LOOP_RUN_MODE); while (CFRunLoopRunInMode(GODOT_JOY_LOOP_RUN_MODE, 0, TRUE) == kCFRunLoopRunHandledSource) { @@ -565,9 +575,9 @@ void JoypadOSX::config_hid_manager(CFArrayRef p_matching_array) const { } } -JoypadOSX::JoypadOSX() { +JoypadOSX::JoypadOSX(Input *in) { self = this; - input = (InputDefault *)Input::get_singleton(); + input = in; int okay = 1; const void *vals[] = { @@ -576,7 +586,7 @@ JoypadOSX::JoypadOSX() { (void *)create_match_dictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController, &okay), }; const size_t n_elements = sizeof(vals) / sizeof(vals[0]); - CFArrayRef array = okay ? CFArrayCreate(kCFAllocatorDefault, vals, n_elements, &kCFTypeArrayCallBacks) : NULL; + CFArrayRef array = okay ? CFArrayCreate(kCFAllocatorDefault, vals, n_elements, &kCFTypeArrayCallBacks) : nullptr; for (size_t i = 0; i < n_elements; i++) { if (vals[i]) { @@ -586,7 +596,7 @@ JoypadOSX::JoypadOSX() { if (array) { hid_manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); - if (hid_manager != NULL) { + if (hid_manager != nullptr) { config_hid_manager(array); } CFRelease(array); @@ -594,7 +604,6 @@ JoypadOSX::JoypadOSX() { } JoypadOSX::~JoypadOSX() { - for (int i = 0; i < device_list.size(); i++) { device_list.write[i].free(); } @@ -602,5 +611,5 @@ JoypadOSX::~JoypadOSX() { IOHIDManagerUnscheduleFromRunLoop(hid_manager, CFRunLoopGetCurrent(), GODOT_JOY_LOOP_RUN_MODE); IOHIDManagerClose(hid_manager, kIOHIDOptionsTypeNone); CFRelease(hid_manager); - hid_manager = NULL; + hid_manager = nullptr; } diff --git a/platform/osx/joypad_osx.h b/platform/osx/joypad_osx.h index 388251016b..dc238e68e4 100644 --- a/platform/osx/joypad_osx.h +++ b/platform/osx/joypad_osx.h @@ -40,7 +40,7 @@ #include <ForceFeedback/ForceFeedbackConstants.h> #include <IOKit/hid/IOHIDLib.h> -#include "main/input_default.h" +#include "core/input/input.h" struct rec_element { IOHIDElementRef ref; @@ -88,13 +88,12 @@ struct joypad { }; class JoypadOSX { - enum { JOYPADS_MAX = 16, }; private: - InputDefault *input; + Input *input; IOHIDManagerRef hid_manager; Vector<joypad> device_list; @@ -103,6 +102,7 @@ private: bool configure_joypad(IOHIDDeviceRef p_device_ref, joypad *p_joy); int get_joy_index(int p_id) const; + int get_joy_ref(IOHIDDeviceRef p_device) const; void poll_joypads() const; void setup_joypad_objects(); @@ -115,9 +115,9 @@ public: void process_joypads(); void _device_added(IOReturn p_res, IOHIDDeviceRef p_device); - void _device_removed(int p_id); + void _device_removed(IOReturn p_res, IOHIDDeviceRef p_device); - JoypadOSX(); + JoypadOSX(Input *in); ~JoypadOSX(); }; diff --git a/platform/osx/logo.png b/platform/osx/logo.png Binary files differindex 834bbf3ba6..b5a660b165 100644 --- a/platform/osx/logo.png +++ b/platform/osx/logo.png diff --git a/platform/osx/os_osx.h b/platform/osx/os_osx.h index 190dbcf662..5a9e43450f 100644 --- a/platform/osx/os_osx.h +++ b/platform/osx/os_osx.h @@ -31,50 +31,20 @@ #ifndef OS_OSX_H #define OS_OSX_H -#define BitMap _QDBitMap // Suppress deprecated QuickDraw definition. - -#include "core/os/input.h" +#include "core/input/input.h" #include "crash_handler_osx.h" #include "drivers/coreaudio/audio_driver_coreaudio.h" #include "drivers/coremidi/midi_driver_coremidi.h" #include "drivers/unix/os_unix.h" #include "joypad_osx.h" -#include "main/input_default.h" -#include "power_osx.h" #include "servers/audio_server.h" -#include "servers/visual/rasterizer.h" -#include "servers/visual/visual_server_wrap_mt.h" -#include "servers/visual_server.h" - -#include <AppKit/AppKit.h> -#include <AppKit/NSCursor.h> -#include <ApplicationServices/ApplicationServices.h> -#include <CoreVideo/CoreVideo.h> - -#undef BitMap -#undef CursorShape class OS_OSX : public OS_Unix { -public: - struct KeyEvent { - unsigned int osx_state; - bool pressed; - bool echo; - bool raw; - uint32_t scancode; - uint32_t unicode; - }; - - Vector<KeyEvent> key_event_buffer; - int key_event_pos; + virtual void delete_main_loop(); bool force_quit; - // rasterizer seems to no longer be given to visual server, its using GLES3 directly? - //Rasterizer *rasterizer; - VisualServer *visual_server; - List<String> args; - MainLoop *main_loop; + JoypadOSX *joypad_osx = nullptr; #ifdef COREAUDIO_ENABLED AudioDriverCoreAudio audio_driver; @@ -83,139 +53,27 @@ public: MIDIDriverCoreMidi midi_driver; #endif - InputDefault *input; - JoypadOSX *joypad_osx; - - /* objc */ - - CGEventSourceRef eventSource; - - void process_events(); - void process_key_events(); - - void *framework; - // pthread_key_t current; - bool mouse_grab; - Point2 mouse_pos; - - id delegate; - id window_delegate; - id window_object; - id window_view; - id autoreleasePool; - id cursor; - NSOpenGLPixelFormat *pixelFormat; - NSOpenGLContext *context; - - bool layered_window; - - CursorShape cursor_shape; - NSCursor *cursors[CURSOR_MAX]; - Map<CursorShape, Vector<Variant> > cursors_cache; - MouseMode mouse_mode; - - String title; - bool minimized; - bool maximized; - bool zoomed; - bool resizable; - bool window_focused; - - Size2 window_size; - Rect2 restore_rect; - - String open_with_filename; - - Point2 im_position; - bool im_active; - String im_text; - Point2 im_selection; - - Size2 min_size; - Size2 max_size; - - PowerOSX *power_manager; - CrashHandler crash_handler; - float _mouse_scale(float p_scale) { - if (_display_scale() > 1.0) - return p_scale; - else - return 1.0; - } - - float _display_scale() const; - float _display_scale(id screen) const; - - void _update_window(); - - int video_driver_index; - virtual int get_current_video_driver() const; - - struct GlobalMenuItem { - String label; - Variant signal; - Variant meta; - - GlobalMenuItem() { - //NOP - } - - GlobalMenuItem(const String &p_label, const Variant &p_signal, const Variant &p_meta) { - label = p_label; - signal = p_signal; - meta = p_meta; - } - }; - - Map<String, Vector<GlobalMenuItem> > global_menus; + MainLoop *main_loop; - void _update_global_menu(); +public: + String open_with_filename; protected: virtual void initialize_core(); - virtual Error initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver); + virtual void initialize(); virtual void finalize(); + virtual void initialize_joypads(); + virtual void set_main_loop(MainLoop *p_main_loop); - virtual void delete_main_loop(); public: - static OS_OSX *singleton; - - void global_menu_add_item(const String &p_menu, const String &p_label, const Variant &p_signal, const Variant &p_meta); - void global_menu_add_separator(const String &p_menu); - void global_menu_remove_item(const String &p_menu, int p_idx); - void global_menu_clear(const String &p_menu); - - void wm_minimized(bool p_minimized); - virtual String get_name() const; - virtual void alert(const String &p_alert, const String &p_title = "ALERT!"); - virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false); - virtual void set_cursor_shape(CursorShape p_shape); - virtual CursorShape get_cursor_shape() const; - virtual void set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot); - - virtual void set_mouse_show(bool p_show); - virtual void set_mouse_grab(bool p_grab); - virtual bool is_mouse_grab_enabled() const; - virtual void warp_mouse_position(const Point2 &p_to); - virtual Point2 get_mouse_position() const; - virtual int get_mouse_button_state() const; - void update_real_mouse_position(); - virtual void set_window_title(const String &p_title); - - virtual Size2 get_window_size() const; - virtual Size2 get_real_window_size() const; - - virtual void set_native_icon(const String &p_filename); - virtual void set_icon(const Ref<Image> &p_icon); - virtual MainLoop *get_main_loop() const; virtual String get_config_path() const; @@ -226,99 +84,24 @@ public: virtual String get_system_dir(SystemDir p_dir) const; - virtual bool can_draw() const; - - virtual void set_clipboard(const String &p_text); - virtual String get_clipboard() const; - - virtual void release_rendering_thread(); - virtual void make_rendering_thread(); - virtual void swap_buffers(); - Error shell_open(String p_uri); - void push_input(const Ref<InputEvent> &p_event); String get_locale() const; - virtual void set_video_mode(const VideoMode &p_video_mode, int p_screen = 0); - virtual VideoMode get_video_mode(int p_screen = 0) const; - virtual void get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen = 0) const; - virtual String get_executable_path() const; - virtual LatinKeyboardVariant get_latin_keyboard_variant() const; - - virtual void move_window_to_foreground(); - - virtual int get_screen_count() const; - virtual int get_current_screen() const; - virtual void set_current_screen(int p_screen); - virtual Point2 get_screen_position(int p_screen = -1) const; - virtual Size2 get_screen_size(int p_screen = -1) const; - virtual int get_screen_dpi(int p_screen = -1) const; - - virtual Point2 get_window_position() const; - virtual void set_window_position(const Point2 &p_position); - virtual Size2 get_max_window_size() const; - virtual Size2 get_min_window_size() const; - virtual void set_min_window_size(const Size2 p_size); - virtual void set_max_window_size(const Size2 p_size); - virtual void set_window_size(const Size2 p_size); - virtual void set_window_fullscreen(bool p_enabled); - virtual bool is_window_fullscreen() const; - virtual void set_window_resizable(bool p_enabled); - virtual bool is_window_resizable() const; - virtual void set_window_minimized(bool p_enabled); - virtual bool is_window_minimized() const; - virtual void set_window_maximized(bool p_enabled); - virtual bool is_window_maximized() const; - virtual void set_window_always_on_top(bool p_enabled); - virtual bool is_window_always_on_top() const; - virtual bool is_window_focused() const; - virtual void request_attention(); - virtual String get_joy_guid(int p_device) const; - - virtual void set_borderless_window(bool p_borderless); - virtual bool get_borderless_window(); - - virtual bool get_window_per_pixel_transparency_enabled() const; - virtual void set_window_per_pixel_transparency_enabled(bool p_enabled); - - virtual void set_ime_active(const bool p_active); - virtual void set_ime_position(const Point2 &p_pos); - virtual Point2 get_ime_selection() const; - virtual String get_ime_text() const; - - virtual String get_unique_id() const; - - virtual OS::PowerState get_power_state(); - virtual int get_power_seconds_left(); - virtual int get_power_percent_left(); + virtual String get_unique_id() const; //++ virtual bool _check_internal_feature_support(const String &p_feature); - virtual void _set_use_vsync(bool p_enable); - //virtual bool is_vsync_enabled() const; - void run(); - void set_mouse_mode(MouseMode p_mode); - MouseMode get_mouse_mode() const; - void disable_crash_handler(); bool is_disable_crash_handler() const; virtual Error move_to_trash(const String &p_path); - void force_process_input(); - OS_OSX(); - -private: - Point2 get_native_screen_position(int p_screen) const; - Point2 get_native_window_position() const; - void set_native_window_position(const Point2 &p_position); - Point2 get_screens_origin() const; }; #endif diff --git a/platform/osx/os_osx.mm b/platform/osx/os_osx.mm index 53fe11b3bb..399a29cbe0 100644 --- a/platform/osx/os_osx.mm +++ b/platform/osx/os_osx.mm @@ -30,1355 +30,73 @@ #include "os_osx.h" -#include "core/os/keyboard.h" -#include "core/print_string.h" #include "core/version_generated.gen.h" + #include "dir_access_osx.h" -#include "drivers/gles2/rasterizer_gles2.h" -#include "drivers/gles3/rasterizer_gles3.h" +#include "display_server_osx.h" #include "main/main.h" -#include "semaphore_osx.h" -#include "servers/visual/visual_server_raster.h" - -#include <mach-o/dyld.h> - -#include <Carbon/Carbon.h> -#import <Cocoa/Cocoa.h> -#include <IOKit/IOCFPlugIn.h> -#include <IOKit/IOKitLib.h> -#include <IOKit/hid/IOHIDKeys.h> -#include <IOKit/hid/IOHIDLib.h> -#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101200 -#include <os/log.h> -#endif #include <dlfcn.h> -#include <fcntl.h> #include <libproc.h> -#include <stdio.h> -#include <stdlib.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <unistd.h> - -#if MAC_OS_X_VERSION_MAX_ALLOWED < 101200 -#define NSEventMaskAny NSAnyEventMask -#define NSEventTypeKeyDown NSKeyDown -#define NSEventTypeKeyUp NSKeyUp -#define NSEventModifierFlagShift NSShiftKeyMask -#define NSEventModifierFlagCommand NSCommandKeyMask -#define NSEventModifierFlagControl NSControlKeyMask -#define NSEventModifierFlagOption NSAlternateKeyMask -#define NSWindowStyleMaskTitled NSTitledWindowMask -#define NSWindowStyleMaskResizable NSResizableWindowMask -#define NSWindowStyleMaskMiniaturizable NSMiniaturizableWindowMask -#define NSWindowStyleMaskClosable NSClosableWindowMask -#define NSWindowStyleMaskBorderless NSBorderlessWindowMask -#endif - -#ifndef NSAppKitVersionNumber10_12 -#define NSAppKitVersionNumber10_12 1504 -#endif -#ifndef NSAppKitVersionNumber10_14 -#define NSAppKitVersionNumber10_14 1671 -#endif - -static void get_key_modifier_state(unsigned int p_osx_state, Ref<InputEventWithModifiers> state) { - - state->set_shift((p_osx_state & NSEventModifierFlagShift)); - state->set_control((p_osx_state & NSEventModifierFlagControl)); - state->set_alt((p_osx_state & NSEventModifierFlagOption)); - state->set_metakey((p_osx_state & NSEventModifierFlagCommand)); -} - -static void push_to_key_event_buffer(const OS_OSX::KeyEvent &p_event) { - - Vector<OS_OSX::KeyEvent> &buffer = OS_OSX::singleton->key_event_buffer; - if (OS_OSX::singleton->key_event_pos >= buffer.size()) { - buffer.resize(1 + OS_OSX::singleton->key_event_pos); - } - buffer.write[OS_OSX::singleton->key_event_pos++] = p_event; -} - -static int mouse_x = 0; -static int mouse_y = 0; -static int button_mask = 0; -static bool mouse_down_control = false; - -static Vector2 get_mouse_pos(NSPoint locationInWindow, CGFloat backingScaleFactor) { - - const NSRect contentRect = [OS_OSX::singleton->window_view frame]; - const NSPoint p = locationInWindow; - const float s = OS_OSX::singleton->_mouse_scale(backingScaleFactor); - mouse_x = p.x * s; - mouse_y = (contentRect.size.height - p.y) * s; - return Vector2(mouse_x, mouse_y); -} - -static NSCursor *cursorFromSelector(SEL selector, SEL fallback = nil) { - if ([NSCursor respondsToSelector:selector]) { - id object = [NSCursor performSelector:selector]; - if ([object isKindOfClass:[NSCursor class]]) { - return object; - } - } - if (fallback) { - // Fallback should be a reasonable default, no need to check. - return [NSCursor performSelector:fallback]; - } - return [NSCursor arrowCursor]; -} - -@interface GodotApplication : NSApplication -@end - -@implementation GodotApplication - -- (void)sendEvent:(NSEvent *)event { - - // special case handling of command-period, which is traditionally a special - // shortcut in macOS and doesn't arrive at our regular keyDown handler. - if ([event type] == NSEventTypeKeyDown) { - if (([event modifierFlags] & NSEventModifierFlagCommand) && [event keyCode] == 0x2f) { - - Ref<InputEventKey> k; - k.instance(); - - get_key_modifier_state([event modifierFlags], k); - k->set_pressed(true); - k->set_scancode(KEY_PERIOD); - k->set_echo([event isARepeat]); - - OS_OSX::singleton->push_input(k); - } - } - - // From http://cocoadev.com/index.pl?GameKeyboardHandlingAlmost - // This works around an AppKit bug, where key up events while holding - // down the command key don't get sent to the key window. - if ([event type] == NSEventTypeKeyUp && ([event modifierFlags] & NSEventModifierFlagCommand)) - [[self keyWindow] sendEvent:event]; - else - [super sendEvent:event]; -} - -@end - -@interface GodotApplicationDelegate : NSObject -- (void)forceUnbundledWindowActivationHackStep1; -- (void)forceUnbundledWindowActivationHackStep2; -- (void)forceUnbundledWindowActivationHackStep3; -@end - -@implementation GodotApplicationDelegate - -- (void)forceUnbundledWindowActivationHackStep1 { - // Step1: Switch focus to macOS Dock. - // Required to perform step 2, TransformProcessType will fail if app is already the in focus. - for (NSRunningApplication *app in [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.dock"]) { - [app activateWithOptions:NSApplicationActivateIgnoringOtherApps]; - break; - } - [self performSelector:@selector(forceUnbundledWindowActivationHackStep2) withObject:nil afterDelay:0.02]; -} - -- (void)forceUnbundledWindowActivationHackStep2 { - // Step 2: Register app as foreground process. - ProcessSerialNumber psn = { 0, kCurrentProcess }; - (void)TransformProcessType(&psn, kProcessTransformToForegroundApplication); - - [self performSelector:@selector(forceUnbundledWindowActivationHackStep3) withObject:nil afterDelay:0.02]; -} - -- (void)forceUnbundledWindowActivationHackStep3 { - // Step 3: Switch focus back to app window. - [[NSRunningApplication currentApplication] activateWithOptions:NSApplicationActivateIgnoringOtherApps]; -} - -- (void)applicationDidFinishLaunching:(NSNotification *)notice { - NSString *nsappname = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"]; - if (nsappname == nil) { - // If executable is not a 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]; - } -} - -- (void)globalMenuCallback:(id)sender { - - if (![sender representedObject]) - return; - - OS_OSX::GlobalMenuItem *item = (OS_OSX::GlobalMenuItem *)[[sender representedObject] pointerValue]; - - if (!item) - return; - - OS_OSX::singleton->main_loop->global_menu_action(item->signal, item->meta); -} - -- (NSMenu *)applicationDockMenu:(NSApplication *)sender { - - NSMenu *menu = [[[NSMenu alloc] initWithTitle:@""] autorelease]; - - Vector<OS_OSX::GlobalMenuItem> &E = OS_OSX::singleton->global_menus["_dock"]; - for (int i = 0; i < E.size(); i++) { - if (E[i].label == String()) { - [menu addItem:[NSMenuItem separatorItem]]; - } else { - NSMenuItem *menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:E[i].label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:@""]; - [menu_item setRepresentedObject:[NSValue valueWithPointer:&(E[i])]]; - } - } - - return menu; -} - -- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename { - // Note: may be called called before main loop init! - char *utfs = strdup([filename UTF8String]); - OS_OSX::singleton->open_with_filename.parse_utf8(utfs); - free(utfs); - -#ifdef TOOLS_ENABLED - // Open new instance - if (OS_OSX::singleton->get_main_loop()) { - List<String> args; - args.push_back(OS_OSX::singleton->open_with_filename); - String exec = OS::get_singleton()->get_executable_path(); - - OS::ProcessID pid = 0; - OS::get_singleton()->execute(exec, args, false, &pid); - } -#endif - return YES; -} - -- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender { - if (OS_OSX::singleton->get_main_loop()) - OS_OSX::singleton->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_QUIT_REQUEST); - - return NSTerminateCancel; -} - -- (void)applicationDidHide:(NSNotification *)notification { - /* - _Godotwindow* window; - for (window = _Godot.windowListHead; window; window = window->next) - _GodotInputWindowVisibility(window, GL_FALSE); -*/ -} - -- (void)applicationDidUnhide:(NSNotification *)notification { - /* - _Godotwindow* window; - - for (window = _Godot.windowListHead; window; window = window->next) { - if ([window_object isVisible]) - _GodotInputWindowVisibility(window, GL_TRUE); - } -*/ -} - -- (void)applicationDidChangeScreenParameters:(NSNotification *)notification { - //_GodotInputMonitorChange(); -} - -- (void)showAbout:(id)sender { - if (OS_OSX::singleton->get_main_loop()) - OS_OSX::singleton->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_ABOUT); -} - -@end - -@interface GodotWindowDelegate : NSObject { - //_Godotwindow* window; -} - -@end - -@implementation GodotWindowDelegate - -- (BOOL)windowShouldClose:(id)sender { - //_GodotInputWindowCloseRequest(window); - if (OS_OSX::singleton->get_main_loop()) - OS_OSX::singleton->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_QUIT_REQUEST); - - return NO; -} - -- (void)windowDidEnterFullScreen:(NSNotification *)notification { - OS_OSX::singleton->zoomed = true; - - [OS_OSX::singleton->window_object setContentMinSize:NSMakeSize(0, 0)]; - [OS_OSX::singleton->window_object setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)]; -} - -- (void)windowDidExitFullScreen:(NSNotification *)notification { - OS_OSX::singleton->zoomed = false; - - if (OS_OSX::singleton->min_size != Size2()) { - Size2 size = OS_OSX::singleton->min_size / OS_OSX::singleton->_display_scale(); - [OS_OSX::singleton->window_object setContentMinSize:NSMakeSize(size.x, size.y)]; - } - if (OS_OSX::singleton->max_size != Size2()) { - Size2 size = OS_OSX::singleton->max_size / OS_OSX::singleton->_display_scale(); - [OS_OSX::singleton->window_object setContentMaxSize:NSMakeSize(size.x, size.y)]; - } - - if (!OS_OSX::singleton->resizable) - [OS_OSX::singleton->window_object setStyleMask:[OS_OSX::singleton->window_object styleMask] & ~NSWindowStyleMaskResizable]; -} - -- (void)windowDidChangeBackingProperties:(NSNotification *)notification { - if (!OS_OSX::singleton) - return; - - NSWindow *window = (NSWindow *)[notification object]; - CGFloat newBackingScaleFactor = [window backingScaleFactor]; - CGFloat oldBackingScaleFactor = [[[notification userInfo] objectForKey:@"NSBackingPropertyOldScaleFactorKey"] doubleValue]; - if (OS_OSX::singleton->is_hidpi_allowed()) { - [OS_OSX::singleton->window_view setWantsBestResolutionOpenGLSurface:YES]; - } else { - [OS_OSX::singleton->window_view setWantsBestResolutionOpenGLSurface:NO]; - } - - if (newBackingScaleFactor != oldBackingScaleFactor) { - //Set new display scale and window size - float newDisplayScale = OS_OSX::singleton->is_hidpi_allowed() ? newBackingScaleFactor : 1.0; - - const NSRect contentRect = [OS_OSX::singleton->window_view frame]; - const NSRect fbRect = contentRect; - - OS_OSX::singleton->window_size.width = fbRect.size.width * newDisplayScale; - OS_OSX::singleton->window_size.height = fbRect.size.height * newDisplayScale; - - //Update context - if (OS_OSX::singleton->main_loop) { - //Force window resize event - [self windowDidResize:notification]; - } - } -} - -- (void)windowDidResize:(NSNotification *)notification { - [OS_OSX::singleton->context update]; - - const NSRect contentRect = [OS_OSX::singleton->window_view frame]; - const NSRect fbRect = contentRect; - - float displayScale = OS_OSX::singleton->_display_scale(); - OS_OSX::singleton->window_size.width = fbRect.size.width * displayScale; - OS_OSX::singleton->window_size.height = fbRect.size.height * displayScale; - - if (OS_OSX::singleton->main_loop) { - Main::force_redraw(); - //Event retrieval blocks until resize is over. Call Main::iteration() directly. - if (!Main::is_iterating()) { //avoid cyclic loop - Main::iteration(); - } - } - - /* - _GodotInputFramebufferSize(window, fbRect.size.width, fbRect.size.height); - _GodotInputWindowSize(window, contentRect.size.width, contentRect.size.height); - _GodotInputWindowDamage(window); - - if (window->cursorMode == Godot_CURSOR_DISABLED) - centerCursor(window); -*/ -} - -- (void)windowDidMove:(NSNotification *)notification { - - if (OS_OSX::singleton->get_main_loop()) { - OS_OSX::singleton->input->release_pressed_events(); - } - - /* - [window->nsgl.context update]; - - int x, y; - _GodotPlatformGetWindowPos(window, &x, &y); - _GodotInputWindowPos(window, x, y); - - if (window->cursorMode == Godot_CURSOR_DISABLED) - centerCursor(window); -*/ -} - -- (void)windowDidBecomeKey:(NSNotification *)notification { - if (OS_OSX::singleton->get_main_loop()) { - get_mouse_pos( - [OS_OSX::singleton->window_object mouseLocationOutsideOfEventStream], - [OS_OSX::singleton->window_view backingScaleFactor]); - OS_OSX::singleton->input->set_mouse_position(Point2(mouse_x, mouse_y)); - - OS_OSX::singleton->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_FOCUS_IN); - } - - OS_OSX::singleton->window_focused = true; -} - -- (void)windowDidResignKey:(NSNotification *)notification { - if (OS_OSX::singleton->get_main_loop()) - OS_OSX::singleton->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_FOCUS_OUT); - - OS_OSX::singleton->window_focused = false; -} - -- (void)windowDidMiniaturize:(NSNotification *)notification { - OS_OSX::singleton->wm_minimized(true); - if (OS_OSX::singleton->get_main_loop()) - OS_OSX::singleton->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_FOCUS_OUT); - - OS_OSX::singleton->window_focused = false; -}; - -- (void)windowDidDeminiaturize:(NSNotification *)notification { - OS_OSX::singleton->wm_minimized(false); - if (OS_OSX::singleton->get_main_loop()) - OS_OSX::singleton->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_FOCUS_IN); - - OS_OSX::singleton->window_focused = true; -}; - -@end - -@interface GodotContentView : NSView <NSTextInputClient> { - NSTrackingArea *trackingArea; - NSMutableAttributedString *markedText; - bool imeInputEventInProgress; -} -- (void)cancelComposition; -- (BOOL)wantsUpdateLayer; -- (void)updateLayer; -@end - -@implementation GodotContentView - -+ (void)initialize { - if (self == [GodotContentView class]) { - // nothing left to do here at the moment.. - } -} - -- (BOOL)wantsUpdateLayer { - return YES; -} - -- (void)updateLayer { - [OS_OSX::singleton->context update]; -} - -- (id)init { - self = [super init]; - trackingArea = nil; - imeInputEventInProgress = false; - [self updateTrackingAreas]; - [self registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]]; - markedText = [[NSMutableAttributedString alloc] init]; - return self; -} - -- (void)dealloc { - [trackingArea release]; - [markedText release]; - [super dealloc]; -} - -static const NSRange kEmptyRange = { NSNotFound, 0 }; - -- (BOOL)hasMarkedText { - return (markedText.length > 0); -} - -- (NSRange)markedRange { - return NSMakeRange(0, markedText.length); -} - -- (NSRange)selectedRange { - return kEmptyRange; -} - -- (void)setMarkedText:(id)aString selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange { - if ([aString isKindOfClass:[NSAttributedString class]]) { - [markedText initWithAttributedString:aString]; - } else { - [markedText initWithString:aString]; - } - if (markedText.length == 0) { - [self unmarkText]; - return; - } - if (OS_OSX::singleton->im_active) { - imeInputEventInProgress = true; - OS_OSX::singleton->im_text.parse_utf8([[markedText mutableString] UTF8String]); - OS_OSX::singleton->im_selection = Point2(selectedRange.location, selectedRange.length); - - if (OS_OSX::singleton->get_main_loop()) - OS_OSX::singleton->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE); - } -} - -- (void)doCommandBySelector:(SEL)aSelector { - if ([self respondsToSelector:aSelector]) - [self performSelector:aSelector]; -} - -- (void)unmarkText { - imeInputEventInProgress = false; - [[markedText mutableString] setString:@""]; - if (OS_OSX::singleton->im_active) { - OS_OSX::singleton->im_text = String(); - OS_OSX::singleton->im_selection = Point2(); - - if (OS_OSX::singleton->get_main_loop()) - OS_OSX::singleton->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE); - } -} - -- (NSArray *)validAttributesForMarkedText { - return [NSArray array]; -} - -- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange { - return nil; -} - -- (NSUInteger)characterIndexForPoint:(NSPoint)aPoint { - return 0; -} - -- (NSRect)firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange { - const NSRect contentRect = [OS_OSX::singleton->window_view frame]; - float displayScale = OS_OSX::singleton->_display_scale(); - NSRect pointInWindowRect = NSMakeRect(OS_OSX::singleton->im_position.x / displayScale, contentRect.size.height - (OS_OSX::singleton->im_position.y / displayScale) - 1, 0, 0); - NSPoint pointOnScreen = [[OS_OSX::singleton->window_view window] convertRectToScreen:pointInWindowRect].origin; - - return NSMakeRect(pointOnScreen.x, pointOnScreen.y, 0, 0); -} - -- (void)cancelComposition { - [self unmarkText]; - NSTextInputContext *currentInputContext = [NSTextInputContext currentInputContext]; - [currentInputContext discardMarkedText]; -} - -- (void)insertText:(id)aString { - [self insertText:aString replacementRange:NSMakeRange(0, 0)]; -} - -- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange { - NSEvent *event = [NSApp currentEvent]; - - NSString *characters; - if ([aString isKindOfClass:[NSAttributedString class]]) { - characters = [aString string]; - } else { - characters = (NSString *)aString; - } - - NSUInteger i, length = [characters length]; - - NSCharacterSet *ctrlChars = [NSCharacterSet controlCharacterSet]; - NSCharacterSet *wsnlChars = [NSCharacterSet whitespaceAndNewlineCharacterSet]; - if ([characters rangeOfCharacterFromSet:ctrlChars].length && [characters rangeOfCharacterFromSet:wsnlChars].length == 0) { - NSTextInputContext *currentInputContext = [NSTextInputContext currentInputContext]; - [currentInputContext discardMarkedText]; - [self cancelComposition]; - return; - } - - for (i = 0; i < length; i++) { - const unichar codepoint = [characters characterAtIndex:i]; - if ((codepoint & 0xFF00) == 0xF700) - continue; - - OS_OSX::KeyEvent ke; - - ke.osx_state = [event modifierFlags]; - ke.pressed = true; - ke.echo = false; - ke.raw = false; // IME input event - ke.scancode = 0; - ke.unicode = codepoint; - - push_to_key_event_buffer(ke); - } - [self cancelComposition]; -} - -- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender { - return NSDragOperationCopy; -} - -- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender { - return NSDragOperationCopy; -} - -- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender { - - NSPasteboard *pboard = [sender draggingPasteboard]; - NSArray *filenames = [pboard propertyListForType:NSFilenamesPboardType]; - - Vector<String> files; - for (NSUInteger i = 0; i < filenames.count; i++) { - NSString *ns = [filenames objectAtIndex:i]; - char *utfs = strdup([ns UTF8String]); - String ret; - ret.parse_utf8(utfs); - free(utfs); - files.push_back(ret); - } - - if (files.size()) { - OS_OSX::singleton->main_loop->drop_files(files, 0); - OS_OSX::singleton->move_window_to_foreground(); - } - - return NO; -} - -- (BOOL)isOpaque { - return YES; -} - -- (BOOL)canBecomeKeyView { - return YES; -} - -- (BOOL)acceptsFirstResponder { - return YES; -} - -- (void)cursorUpdate:(NSEvent *)event { - OS::CursorShape p_shape = OS_OSX::singleton->cursor_shape; - OS_OSX::singleton->cursor_shape = OS::CURSOR_MAX; - OS_OSX::singleton->set_cursor_shape(p_shape); -} - -static void _mouseDownEvent(NSEvent *event, int index, int mask, bool pressed) { - if (pressed) { - button_mask |= mask; - } else { - button_mask &= ~mask; - } - - Ref<InputEventMouseButton> mb; - mb.instance(); - const CGFloat backingScaleFactor = [[event window] backingScaleFactor]; - const Vector2 pos = get_mouse_pos([event locationInWindow], backingScaleFactor); - get_key_modifier_state([event modifierFlags], mb); - mb->set_button_index(index); - mb->set_pressed(pressed); - mb->set_position(pos); - mb->set_global_position(pos); - mb->set_button_mask(button_mask); - if (index == BUTTON_LEFT && pressed) { - mb->set_doubleclick([event clickCount] == 2); - } - OS_OSX::singleton->push_input(mb); -} - -- (void)mouseDown:(NSEvent *)event { - if (([event modifierFlags] & NSEventModifierFlagControl)) { - mouse_down_control = true; - _mouseDownEvent(event, BUTTON_RIGHT, BUTTON_MASK_RIGHT, true); - } else { - mouse_down_control = false; - _mouseDownEvent(event, BUTTON_LEFT, BUTTON_MASK_LEFT, true); - } -} - -- (void)mouseDragged:(NSEvent *)event { - [self mouseMoved:event]; -} - -- (void)mouseUp:(NSEvent *)event { - if (mouse_down_control) { - _mouseDownEvent(event, BUTTON_RIGHT, BUTTON_MASK_RIGHT, false); - } else { - _mouseDownEvent(event, BUTTON_LEFT, BUTTON_MASK_LEFT, false); - } -} - -- (void)mouseMoved:(NSEvent *)event { - - Ref<InputEventMouseMotion> mm; - mm.instance(); - - mm->set_button_mask(button_mask); - const CGFloat backingScaleFactor = [[event window] backingScaleFactor]; - const Vector2 pos = get_mouse_pos([event locationInWindow], backingScaleFactor); - mm->set_position(pos); - mm->set_pressure([event pressure]); - if ([event subtype] == NSTabletPointEventSubtype) { - const NSPoint p = [event tilt]; - mm->set_tilt(Vector2(p.x, p.y)); - } - mm->set_global_position(pos); - mm->set_speed(OS_OSX::singleton->input->get_last_mouse_speed()); - Vector2 relativeMotion = Vector2(); - relativeMotion.x = [event deltaX] * OS_OSX::singleton -> _mouse_scale(backingScaleFactor); - relativeMotion.y = [event deltaY] * OS_OSX::singleton -> _mouse_scale(backingScaleFactor); - mm->set_relative(relativeMotion); - get_key_modifier_state([event modifierFlags], mm); - - OS_OSX::singleton->input->set_mouse_position(Point2(mouse_x, mouse_y)); - OS_OSX::singleton->push_input(mm); -} - -- (void)rightMouseDown:(NSEvent *)event { - _mouseDownEvent(event, BUTTON_RIGHT, BUTTON_MASK_RIGHT, true); -} - -- (void)rightMouseDragged:(NSEvent *)event { - [self mouseMoved:event]; -} - -- (void)rightMouseUp:(NSEvent *)event { - _mouseDownEvent(event, BUTTON_RIGHT, BUTTON_MASK_RIGHT, false); -} - -- (void)otherMouseDown:(NSEvent *)event { - - if ((int)[event buttonNumber] == 2) { - _mouseDownEvent(event, BUTTON_MIDDLE, BUTTON_MASK_MIDDLE, true); - - } else if ((int)[event buttonNumber] == 3) { - _mouseDownEvent(event, BUTTON_XBUTTON1, BUTTON_MASK_XBUTTON1, true); - - } else if ((int)[event buttonNumber] == 4) { - _mouseDownEvent(event, BUTTON_XBUTTON2, BUTTON_MASK_XBUTTON2, true); - - } else { - return; - } -} - -- (void)otherMouseDragged:(NSEvent *)event { - [self mouseMoved:event]; -} - -- (void)otherMouseUp:(NSEvent *)event { - - if ((int)[event buttonNumber] == 2) { - _mouseDownEvent(event, BUTTON_MIDDLE, BUTTON_MASK_MIDDLE, false); - - } else if ((int)[event buttonNumber] == 3) { - _mouseDownEvent(event, BUTTON_XBUTTON1, BUTTON_MASK_XBUTTON1, false); - - } else if ((int)[event buttonNumber] == 4) { - _mouseDownEvent(event, BUTTON_XBUTTON2, BUTTON_MASK_XBUTTON2, false); - - } else { - return; - } -} - -- (void)mouseExited:(NSEvent *)event { - if (!OS_OSX::singleton) - return; - - if (OS_OSX::singleton->main_loop && OS_OSX::singleton->mouse_mode != OS::MOUSE_MODE_CAPTURED) - OS_OSX::singleton->main_loop->notification(MainLoop::NOTIFICATION_WM_MOUSE_EXIT); -} - -- (void)mouseEntered:(NSEvent *)event { - if (!OS_OSX::singleton) - return; - if (OS_OSX::singleton->main_loop && OS_OSX::singleton->mouse_mode != OS::MOUSE_MODE_CAPTURED) - OS_OSX::singleton->main_loop->notification(MainLoop::NOTIFICATION_WM_MOUSE_ENTER); - - OS::CursorShape p_shape = OS_OSX::singleton->cursor_shape; - OS_OSX::singleton->cursor_shape = OS::CURSOR_MAX; - OS_OSX::singleton->set_cursor_shape(p_shape); -} - -- (void)magnifyWithEvent:(NSEvent *)event { - Ref<InputEventMagnifyGesture> ev; - ev.instance(); - get_key_modifier_state([event modifierFlags], ev); - ev->set_position(get_mouse_pos([event locationInWindow], [[event window] backingScaleFactor])); - ev->set_factor([event magnification] + 1.0); - OS_OSX::singleton->push_input(ev); -} - -- (void)viewDidChangeBackingProperties { - // nothing left to do here -} - -- (void)updateTrackingAreas { - if (trackingArea != nil) { - [self removeTrackingArea:trackingArea]; - [trackingArea release]; - } - - NSTrackingAreaOptions options = - NSTrackingMouseEnteredAndExited | - NSTrackingActiveInKeyWindow | - NSTrackingCursorUpdate | - NSTrackingInVisibleRect; - - trackingArea = [[NSTrackingArea alloc] - initWithRect:[self bounds] - options:options - owner:self - userInfo:nil]; - - [self addTrackingArea:trackingArea]; - [super updateTrackingAreas]; -} - -static bool isNumpadKey(unsigned int key) { - - static const unsigned int table[] = { - 0x41, /* kVK_ANSI_KeypadDecimal */ - 0x43, /* kVK_ANSI_KeypadMultiply */ - 0x45, /* kVK_ANSI_KeypadPlus */ - 0x47, /* kVK_ANSI_KeypadClear */ - 0x4b, /* kVK_ANSI_KeypadDivide */ - 0x4c, /* kVK_ANSI_KeypadEnter */ - 0x4e, /* kVK_ANSI_KeypadMinus */ - 0x51, /* kVK_ANSI_KeypadEquals */ - 0x52, /* kVK_ANSI_Keypad0 */ - 0x53, /* kVK_ANSI_Keypad1 */ - 0x54, /* kVK_ANSI_Keypad2 */ - 0x55, /* kVK_ANSI_Keypad3 */ - 0x56, /* kVK_ANSI_Keypad4 */ - 0x57, /* kVK_ANSI_Keypad5 */ - 0x58, /* kVK_ANSI_Keypad6 */ - 0x59, /* kVK_ANSI_Keypad7 */ - 0x5b, /* kVK_ANSI_Keypad8 */ - 0x5c, /* kVK_ANSI_Keypad9 */ - 0x5f, /* kVK_JIS_KeypadComma */ - 0x00 - }; - for (int i = 0; table[i] != 0; i++) { - if (key == table[i]) - return true; - } - return false; -} - -// Translates a OS X keycode to a Godot keycode -// -static int translateKey(unsigned int key) { - - // Keyboard symbol translation table - static const unsigned int table[128] = { - /* 00 */ KEY_A, - /* 01 */ KEY_S, - /* 02 */ KEY_D, - /* 03 */ KEY_F, - /* 04 */ KEY_H, - /* 05 */ KEY_G, - /* 06 */ KEY_Z, - /* 07 */ KEY_X, - /* 08 */ KEY_C, - /* 09 */ KEY_V, - /* 0a */ KEY_SECTION, /* ISO Section */ - /* 0b */ KEY_B, - /* 0c */ KEY_Q, - /* 0d */ KEY_W, - /* 0e */ KEY_E, - /* 0f */ KEY_R, - /* 10 */ KEY_Y, - /* 11 */ KEY_T, - /* 12 */ KEY_1, - /* 13 */ KEY_2, - /* 14 */ KEY_3, - /* 15 */ KEY_4, - /* 16 */ KEY_6, - /* 17 */ KEY_5, - /* 18 */ KEY_EQUAL, - /* 19 */ KEY_9, - /* 1a */ KEY_7, - /* 1b */ KEY_MINUS, - /* 1c */ KEY_8, - /* 1d */ KEY_0, - /* 1e */ KEY_BRACERIGHT, - /* 1f */ KEY_O, - /* 20 */ KEY_U, - /* 21 */ KEY_BRACELEFT, - /* 22 */ KEY_I, - /* 23 */ KEY_P, - /* 24 */ KEY_ENTER, - /* 25 */ KEY_L, - /* 26 */ KEY_J, - /* 27 */ KEY_APOSTROPHE, - /* 28 */ KEY_K, - /* 29 */ KEY_SEMICOLON, - /* 2a */ KEY_BACKSLASH, - /* 2b */ KEY_COMMA, - /* 2c */ KEY_SLASH, - /* 2d */ KEY_N, - /* 2e */ KEY_M, - /* 2f */ KEY_PERIOD, - /* 30 */ KEY_TAB, - /* 31 */ KEY_SPACE, - /* 32 */ KEY_QUOTELEFT, - /* 33 */ KEY_BACKSPACE, - /* 34 */ KEY_UNKNOWN, - /* 35 */ KEY_ESCAPE, - /* 36 */ KEY_META, - /* 37 */ KEY_META, - /* 38 */ KEY_SHIFT, - /* 39 */ KEY_CAPSLOCK, - /* 3a */ KEY_ALT, - /* 3b */ KEY_CONTROL, - /* 3c */ KEY_SHIFT, - /* 3d */ KEY_ALT, - /* 3e */ KEY_CONTROL, - /* 3f */ KEY_UNKNOWN, /* Function */ - /* 40 */ KEY_UNKNOWN, /* F17 */ - /* 41 */ KEY_KP_PERIOD, - /* 42 */ KEY_UNKNOWN, - /* 43 */ KEY_KP_MULTIPLY, - /* 44 */ KEY_UNKNOWN, - /* 45 */ KEY_KP_ADD, - /* 46 */ KEY_UNKNOWN, - /* 47 */ KEY_NUMLOCK, /* Really KeypadClear... */ - /* 48 */ KEY_VOLUMEUP, /* VolumeUp */ - /* 49 */ KEY_VOLUMEDOWN, /* VolumeDown */ - /* 4a */ KEY_VOLUMEMUTE, /* Mute */ - /* 4b */ KEY_KP_DIVIDE, - /* 4c */ KEY_KP_ENTER, - /* 4d */ KEY_UNKNOWN, - /* 4e */ KEY_KP_SUBTRACT, - /* 4f */ KEY_UNKNOWN, /* F18 */ - /* 50 */ KEY_UNKNOWN, /* F19 */ - /* 51 */ KEY_EQUAL, /* KeypadEqual */ - /* 52 */ KEY_KP_0, - /* 53 */ KEY_KP_1, - /* 54 */ KEY_KP_2, - /* 55 */ KEY_KP_3, - /* 56 */ KEY_KP_4, - /* 57 */ KEY_KP_5, - /* 58 */ KEY_KP_6, - /* 59 */ KEY_KP_7, - /* 5a */ KEY_UNKNOWN, /* F20 */ - /* 5b */ KEY_KP_8, - /* 5c */ KEY_KP_9, - /* 5d */ KEY_YEN, /* JIS Yen */ - /* 5e */ KEY_UNDERSCORE, /* JIS Underscore */ - /* 5f */ KEY_COMMA, /* JIS KeypadComma */ - /* 60 */ KEY_F5, - /* 61 */ KEY_F6, - /* 62 */ KEY_F7, - /* 63 */ KEY_F3, - /* 64 */ KEY_F8, - /* 65 */ KEY_F9, - /* 66 */ KEY_UNKNOWN, /* JIS Eisu */ - /* 67 */ KEY_F11, - /* 68 */ KEY_UNKNOWN, /* JIS Kana */ - /* 69 */ KEY_F13, - /* 6a */ KEY_F16, - /* 6b */ KEY_F14, - /* 6c */ KEY_UNKNOWN, - /* 6d */ KEY_F10, - /* 6e */ KEY_MENU, - /* 6f */ KEY_F12, - /* 70 */ KEY_UNKNOWN, - /* 71 */ KEY_F15, - /* 72 */ KEY_INSERT, /* Really Help... */ - /* 73 */ KEY_HOME, - /* 74 */ KEY_PAGEUP, - /* 75 */ KEY_DELETE, - /* 76 */ KEY_F4, - /* 77 */ KEY_END, - /* 78 */ KEY_F2, - /* 79 */ KEY_PAGEDOWN, - /* 7a */ KEY_F1, - /* 7b */ KEY_LEFT, - /* 7c */ KEY_RIGHT, - /* 7d */ KEY_DOWN, - /* 7e */ KEY_UP, - /* 7f */ KEY_UNKNOWN, - }; - - if (key >= 128) - return KEY_UNKNOWN; - - return table[key]; -} - -struct _KeyCodeMap { - UniChar kchar; - int kcode; -}; - -static const _KeyCodeMap _keycodes[55] = { - { '`', KEY_QUOTELEFT }, - { '~', KEY_ASCIITILDE }, - { '0', KEY_0 }, - { '1', KEY_1 }, - { '2', KEY_2 }, - { '3', KEY_3 }, - { '4', KEY_4 }, - { '5', KEY_5 }, - { '6', KEY_6 }, - { '7', KEY_7 }, - { '8', KEY_8 }, - { '9', KEY_9 }, - { '-', KEY_MINUS }, - { '_', KEY_UNDERSCORE }, - { '=', KEY_EQUAL }, - { '+', KEY_PLUS }, - { 'q', KEY_Q }, - { 'w', KEY_W }, - { 'e', KEY_E }, - { 'r', KEY_R }, - { 't', KEY_T }, - { 'y', KEY_Y }, - { 'u', KEY_U }, - { 'i', KEY_I }, - { 'o', KEY_O }, - { 'p', KEY_P }, - { '[', KEY_BRACELEFT }, - { ']', KEY_BRACERIGHT }, - { '{', KEY_BRACELEFT }, - { '}', KEY_BRACERIGHT }, - { 'a', KEY_A }, - { 's', KEY_S }, - { 'd', KEY_D }, - { 'f', KEY_F }, - { 'g', KEY_G }, - { 'h', KEY_H }, - { 'j', KEY_J }, - { 'k', KEY_K }, - { 'l', KEY_L }, - { ';', KEY_SEMICOLON }, - { ':', KEY_COLON }, - { '\'', KEY_APOSTROPHE }, - { '\"', KEY_QUOTEDBL }, - { '\\', KEY_BACKSLASH }, - { '#', KEY_NUMBERSIGN }, - { 'z', KEY_Z }, - { 'x', KEY_X }, - { 'c', KEY_C }, - { 'v', KEY_V }, - { 'b', KEY_B }, - { 'n', KEY_N }, - { 'm', KEY_M }, - { ',', KEY_COMMA }, - { '.', KEY_PERIOD }, - { '/', KEY_SLASH } -}; - -static int remapKey(unsigned int key, unsigned int state) { - - if (isNumpadKey(key)) - return translateKey(key); - - TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource(); - if (!currentKeyboard) - return translateKey(key); - - CFDataRef layoutData = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData); - if (!layoutData) - return translateKey(key); - - const UCKeyboardLayout *keyboardLayout = (const UCKeyboardLayout *)CFDataGetBytePtr(layoutData); - - UInt32 keysDown = 0; - UniChar chars[4]; - UniCharCount realLength; - - OSStatus err = UCKeyTranslate(keyboardLayout, - key, - kUCKeyActionDisplay, - (state >> 8) & 0xFF, - LMGetKbdType(), - kUCKeyTranslateNoDeadKeysBit, - &keysDown, - sizeof(chars) / sizeof(chars[0]), - &realLength, - chars); - - if (err != noErr) { - return translateKey(key); - } - - for (unsigned int i = 0; i < 55; i++) { - if (_keycodes[i].kchar == chars[0]) { - return _keycodes[i].kcode; - } - } - return translateKey(key); -} - -- (void)keyDown:(NSEvent *)event { - - // Ignore all input if IME input is in progress - if (!imeInputEventInProgress) { - NSString *characters = [event characters]; - NSUInteger length = [characters length]; - - if (!OS_OSX::singleton->im_active && length > 0 && keycode_has_unicode(remapKey([event keyCode], [event modifierFlags]))) { - // Fallback unicode character handler used if IME is not active - for (NSUInteger i = 0; i < length; i++) { - OS_OSX::KeyEvent ke; - - ke.osx_state = [event modifierFlags]; - ke.pressed = true; - ke.echo = [event isARepeat]; - ke.scancode = remapKey([event keyCode], [event modifierFlags]); - ke.raw = true; - ke.unicode = [characters characterAtIndex:i]; - - push_to_key_event_buffer(ke); - } - } else { - OS_OSX::KeyEvent ke; - - ke.osx_state = [event modifierFlags]; - ke.pressed = true; - ke.echo = [event isARepeat]; - ke.scancode = remapKey([event keyCode], [event modifierFlags]); - ke.raw = false; - ke.unicode = 0; - - push_to_key_event_buffer(ke); - } - } - - // Pass events to IME handler - if (OS_OSX::singleton->im_active) - [self interpretKeyEvents:[NSArray arrayWithObject:event]]; -} - -- (void)flagsChanged:(NSEvent *)event { - - // Ignore all input if IME input is in progress - if (!imeInputEventInProgress) { - OS_OSX::KeyEvent ke; - - ke.echo = false; - ke.raw = true; +#include <mach-o/dyld.h> +#include <os/log.h> - int key = [event keyCode]; - int mod = [event modifierFlags]; +/*************************************************************************/ +/* OSXTerminalLogger */ +/*************************************************************************/ - if (key == 0x36 || key == 0x37) { - if (mod & NSEventModifierFlagCommand) { - mod &= ~NSEventModifierFlagCommand; - ke.pressed = true; - } else { - ke.pressed = false; - } - } else if (key == 0x38 || key == 0x3c) { - if (mod & NSEventModifierFlagShift) { - mod &= ~NSEventModifierFlagShift; - ke.pressed = true; - } else { - ke.pressed = false; - } - } else if (key == 0x3a || key == 0x3d) { - if (mod & NSEventModifierFlagOption) { - mod &= ~NSEventModifierFlagOption; - ke.pressed = true; - } else { - ke.pressed = false; - } - } else if (key == 0x3b || key == 0x3e) { - if (mod & NSEventModifierFlagControl) { - mod &= ~NSEventModifierFlagControl; - ke.pressed = true; - } else { - ke.pressed = false; - } - } else { +class OSXTerminalLogger : public StdLogger { +public: + virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, ErrorType p_type = ERR_ERROR) { + if (!should_log(true)) { return; } - ke.osx_state = mod; - ke.scancode = remapKey(key, mod); - ke.unicode = 0; - - push_to_key_event_buffer(ke); - } -} - -- (void)keyUp:(NSEvent *)event { - - // Ignore all input if IME input is in progress - if (!imeInputEventInProgress) { - NSString *characters = [event characters]; - NSUInteger length = [characters length]; - - // Fallback unicode character handler used if IME is not active - if (!OS_OSX::singleton->im_active && length > 0 && keycode_has_unicode(remapKey([event keyCode], [event modifierFlags]))) { - for (NSUInteger i = 0; i < length; i++) { - OS_OSX::KeyEvent ke; - - ke.osx_state = [event modifierFlags]; - ke.pressed = false; - ke.echo = [event isARepeat]; - ke.scancode = remapKey([event keyCode], [event modifierFlags]); - ke.raw = true; - ke.unicode = [characters characterAtIndex:i]; - - push_to_key_event_buffer(ke); - } - } else { - OS_OSX::KeyEvent ke; - - ke.osx_state = [event modifierFlags]; - ke.pressed = false; - ke.echo = [event isARepeat]; - ke.scancode = remapKey([event keyCode], [event modifierFlags]); - ke.raw = true; - ke.unicode = 0; - - push_to_key_event_buffer(ke); - } - } -} - -inline void sendScrollEvent(int button, double factor, int modifierFlags) { - - unsigned int mask = 1 << (button - 1); - Vector2 mouse_pos = Vector2(mouse_x, mouse_y); - - Ref<InputEventMouseButton> sc; - sc.instance(); - - get_key_modifier_state(modifierFlags, sc); - sc->set_button_index(button); - sc->set_factor(factor); - sc->set_pressed(true); - sc->set_position(mouse_pos); - sc->set_global_position(mouse_pos); - button_mask |= mask; - sc->set_button_mask(button_mask); - OS_OSX::singleton->push_input(sc); - - sc.instance(); - sc->set_button_index(button); - sc->set_factor(factor); - sc->set_pressed(false); - sc->set_position(mouse_pos); - sc->set_global_position(mouse_pos); - button_mask &= ~mask; - sc->set_button_mask(button_mask); - OS_OSX::singleton->push_input(sc); -} - -inline void sendPanEvent(double dx, double dy, int modifierFlags) { - - Ref<InputEventPanGesture> pg; - pg.instance(); - - get_key_modifier_state(modifierFlags, pg); - Vector2 mouse_pos = Vector2(mouse_x, mouse_y); - pg->set_position(mouse_pos); - pg->set_delta(Vector2(-dx, -dy)); - OS_OSX::singleton->push_input(pg); -} - -- (void)scrollWheel:(NSEvent *)event { - double deltaX, deltaY; - - get_mouse_pos([event locationInWindow], [[event window] backingScaleFactor]); - - deltaX = [event scrollingDeltaX]; - deltaY = [event scrollingDeltaY]; - - if ([event hasPreciseScrollingDeltas]) { - deltaX *= 0.03; - deltaY *= 0.03; - } - - if ([event phase] != NSEventPhaseNone || [event momentumPhase] != NSEventPhaseNone) { - sendPanEvent(deltaX, deltaY, [event modifierFlags]); - } else { - if (fabs(deltaX)) { - sendScrollEvent(0 > deltaX ? BUTTON_WHEEL_RIGHT : BUTTON_WHEEL_LEFT, fabs(deltaX * 0.3), [event modifierFlags]); - } - if (fabs(deltaY)) { - sendScrollEvent(0 < deltaY ? BUTTON_WHEEL_UP : BUTTON_WHEEL_DOWN, fabs(deltaY * 0.3), [event modifierFlags]); - } - } -} - -@end - -@interface GodotWindow : NSWindow { -} -@end - -@implementation GodotWindow - -- (BOOL)canBecomeKeyWindow { - // Required for NSBorderlessWindowMask windows - return YES; -} - -@end - -void OS_OSX::_update_global_menu() { - - NSMenu *main_menu = [NSApp mainMenu]; + const char *err_details; + if (p_rationale && p_rationale[0]) + err_details = p_rationale; + else + err_details = p_code; - for (int i = 1; i < [main_menu numberOfItems]; i++) { - [main_menu removeItemAtIndex:i]; - } - for (Map<String, Vector<GlobalMenuItem> >::Element *E = global_menus.front(); E; E = E->next()) { - if (E->key() != "_dock") { - NSMenu *menu = [[[NSMenu alloc] initWithTitle:[NSString stringWithUTF8String:E->key().utf8().get_data()]] autorelease]; - for (int i = 0; i < E->get().size(); i++) { - if (E->get()[i].label == String()) { - [menu addItem:[NSMenuItem separatorItem]]; - } else { - NSMenuItem *menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:E->get()[i].label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:@""]; - [menu_item setRepresentedObject:[NSValue valueWithPointer:&(E->get()[i])]]; - } - } - NSMenuItem *menu_item = [main_menu addItemWithTitle:[NSString stringWithUTF8String:E->key().utf8().get_data()] action:nil keyEquivalent:@""]; - [main_menu setSubmenu:menu forItem:menu_item]; + switch (p_type) { + case ERR_WARNING: + os_log_info(OS_LOG_DEFAULT, + "WARNING: %{public}s\nat: %{public}s (%{public}s:%i)", + err_details, p_function, p_file, p_line); + logf_error("\E[1;33mWARNING:\E[0;93m %s\n", err_details); + logf_error("\E[0;90m at: %s (%s:%i)\E[0m\n", p_function, p_file, p_line); + break; + case ERR_SCRIPT: + os_log_error(OS_LOG_DEFAULT, + "SCRIPT ERROR: %{public}s\nat: %{public}s (%{public}s:%i)", + err_details, p_function, p_file, p_line); + logf_error("\E[1;35mSCRIPT ERROR:\E[0;95m %s\n", err_details); + logf_error("\E[0;90m at: %s (%s:%i)\E[0m\n", p_function, p_file, p_line); + break; + case ERR_SHADER: + os_log_error(OS_LOG_DEFAULT, + "SHADER ERROR: %{public}s\nat: %{public}s (%{public}s:%i)", + err_details, p_function, p_file, p_line); + logf_error("\E[1;36mSHADER ERROR:\E[0;96m %s\n", err_details); + logf_error("\E[0;90m at: %s (%s:%i)\E[0m\n", p_function, p_file, p_line); + break; + case ERR_ERROR: + default: + os_log_error(OS_LOG_DEFAULT, + "ERROR: %{public}s\nat: %{public}s (%{public}s:%i)", + err_details, p_function, p_file, p_line); + logf_error("\E[1;31mERROR:\E[0;91m %s\n", err_details); + logf_error("\E[0;90m at: %s (%s:%i)\E[0m\n", p_function, p_file, p_line); + break; } } -} - -void OS_OSX::global_menu_add_item(const String &p_menu, const String &p_label, const Variant &p_signal, const Variant &p_meta) { - - global_menus[p_menu].push_back(GlobalMenuItem(p_label, p_signal, p_meta)); - _update_global_menu(); -} - -void OS_OSX::global_menu_add_separator(const String &p_menu) { - - global_menus[p_menu].push_back(GlobalMenuItem()); - _update_global_menu(); -} - -void OS_OSX::global_menu_remove_item(const String &p_menu, int p_idx) { - - ERR_FAIL_INDEX(p_idx, global_menus[p_menu].size()); - - global_menus[p_menu].remove(p_idx); - _update_global_menu(); -} - -void OS_OSX::global_menu_clear(const String &p_menu) { - - global_menus[p_menu].clear(); - _update_global_menu(); -} - -Point2 OS_OSX::get_ime_selection() const { - - return im_selection; -} - -String OS_OSX::get_ime_text() const { +}; - return im_text; -} +/*************************************************************************/ +/* OS_OSX */ +/*************************************************************************/ String OS_OSX::get_unique_id() const { - static String serial_number; if (serial_number.empty()) { @@ -1401,300 +119,42 @@ String OS_OSX::get_unique_id() const { return serial_number; } -void OS_OSX::set_ime_active(const bool p_active) { - - im_active = p_active; - if (!im_active) - [window_view cancelComposition]; -} - -void OS_OSX::set_ime_position(const Point2 &p_pos) { - - im_position = p_pos; -} - void OS_OSX::initialize_core() { - - crash_handler.initialize(); - OS_Unix::initialize_core(); DirAccess::make_default<DirAccessOSX>(DirAccess::ACCESS_RESOURCES); DirAccess::make_default<DirAccessOSX>(DirAccess::ACCESS_USERDATA); DirAccess::make_default<DirAccessOSX>(DirAccess::ACCESS_FILESYSTEM); - - SemaphoreOSX::make_default(); -} - -static bool keyboard_layout_dirty = true; -static void keyboard_layout_changed(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef user_info) { - keyboard_layout_dirty = true; } -static bool displays_arrangement_dirty = true; -static void displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplayChangeSummaryFlags flags, void *user_info) { - displays_arrangement_dirty = true; +void OS_OSX::initialize_joypads() { + joypad_osx = memnew(JoypadOSX(Input::get_singleton())); } -int OS_OSX::get_current_video_driver() const { - return video_driver_index; -} - -Error OS_OSX::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) { - - /*** OSX INITIALIZATION ***/ - /*** OSX INITIALIZATION ***/ - /*** OSX INITIALIZATION ***/ - - keyboard_layout_dirty = true; - displays_arrangement_dirty = true; - - // Register to be notified on keyboard layout changes - CFNotificationCenterAddObserver(CFNotificationCenterGetDistributedCenter(), - NULL, keyboard_layout_changed, - kTISNotifySelectedKeyboardInputSourceChanged, NULL, - CFNotificationSuspensionBehaviorDeliverImmediately); - - // Register to be notified on displays arrangement changes - CGDisplayRegisterReconfigurationCallback(displays_arrangement_changed, NULL); - - window_delegate = [[GodotWindowDelegate alloc] init]; - - // Don't use accumulation buffer support; it's not accelerated - // Aux buffers probably aren't accelerated either - - unsigned int styleMask; - - if (p_desired.borderless_window) { - styleMask = NSWindowStyleMaskBorderless; - } else { - resizable = p_desired.resizable; - styleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | (p_desired.resizable ? NSWindowStyleMaskResizable : 0); - } - - window_object = [[GodotWindow alloc] - initWithContentRect:NSMakeRect(0, 0, p_desired.width, p_desired.height) - styleMask:styleMask - backing:NSBackingStoreBuffered - defer:NO]; - - ERR_FAIL_COND_V(window_object == nil, ERR_UNAVAILABLE); - - window_view = [[GodotContentView alloc] init]; - if (NSAppKitVersionNumber >= NSAppKitVersionNumber10_14) { - [window_view setWantsLayer:TRUE]; - } - - float displayScale = 1.0; - if (is_hidpi_allowed()) { - // note that mainScreen is not screen #0 but the one with the keyboard focus. - NSScreen *screen = [NSScreen mainScreen]; - if ([screen respondsToSelector:@selector(backingScaleFactor)]) { - displayScale = fmax(displayScale, [screen backingScaleFactor]); - } - } - - window_size.width = p_desired.width * displayScale; - window_size.height = p_desired.height * displayScale; - - if (displayScale > 1.0) { - [window_view setWantsBestResolutionOpenGLSurface:YES]; - //if (current_videomode.resizable) - [window_object setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary]; - } else { - [window_view setWantsBestResolutionOpenGLSurface:NO]; - } - - //[window_object setTitle:[NSString stringWithUTF8String:"GodotEnginies"]]; - [window_object setContentView:window_view]; - [window_object setDelegate:window_delegate]; - [window_object setAcceptsMouseMovedEvents:YES]; - [(NSWindow *)window_object center]; - - [window_object setRestorable:NO]; - - unsigned int attributeCount = 0; - - // OS X needs non-zero color size, so set reasonable values - int colorBits = 32; - - // Fail if a robustness strategy was requested - -#define ADD_ATTR(x) \ - { attributes[attributeCount++] = x; } -#define ADD_ATTR2(x, y) \ - { \ - ADD_ATTR(x); \ - ADD_ATTR(y); \ - } - - // Arbitrary array size here - NSOpenGLPixelFormatAttribute attributes[40]; - - ADD_ATTR(NSOpenGLPFADoubleBuffer); - ADD_ATTR(NSOpenGLPFAClosestPolicy); - - if (p_video_driver == VIDEO_DRIVER_GLES2) { - ADD_ATTR2(NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersionLegacy); - } else { - //we now need OpenGL 3 or better, maybe even change this to 3_3Core ? - ADD_ATTR2(NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core); - } - - ADD_ATTR2(NSOpenGLPFAColorSize, colorBits); - - /* - if (fbconfig->alphaBits > 0) - ADD_ATTR2(NSOpenGLPFAAlphaSize, fbconfig->alphaBits); -*/ - - ADD_ATTR2(NSOpenGLPFADepthSize, 24); - - ADD_ATTR2(NSOpenGLPFAStencilSize, 8); - - /* - if (fbconfig->stereo) - ADD_ATTR(NSOpenGLPFAStereo); -*/ - - /* - if (fbconfig->samples > 0) { - ADD_ATTR2(NSOpenGLPFASampleBuffers, 1); - ADD_ATTR2(NSOpenGLPFASamples, fbconfig->samples); - } -*/ - - // NOTE: All NSOpenGLPixelFormats on the relevant cards support sRGB - // framebuffer, so there's no need (and no way) to request it - - ADD_ATTR(0); - -#undef ADD_ATTR -#undef ADD_ATTR2 - - pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes]; - ERR_FAIL_COND_V(pixelFormat == nil, ERR_UNAVAILABLE); - - context = [[NSOpenGLContext alloc] initWithFormat:pixelFormat shareContext:nil]; - - ERR_FAIL_COND_V(context == nil, ERR_UNAVAILABLE); - - [context setView:window_view]; - - [context makeCurrentContext]; - - set_use_vsync(p_desired.use_vsync); - - [NSApp activateIgnoringOtherApps:YES]; - - _update_window(); - - [window_object makeKeyAndOrderFront:nil]; - - if (p_desired.fullscreen) - zoomed = true; - - /*** END OSX INITIALIZATION ***/ - - bool gles3 = true; - if (p_video_driver == VIDEO_DRIVER_GLES2) { - gles3 = false; - } - - bool editor = Engine::get_singleton()->is_editor_hint(); - bool gl_initialization_error = false; - - while (true) { - if (gles3) { - if (RasterizerGLES3::is_viable() == OK) { - RasterizerGLES3::register_config(); - RasterizerGLES3::make_current(); - break; - } else { - if (GLOBAL_GET("rendering/quality/driver/fallback_to_gles2") || editor) { - p_video_driver = VIDEO_DRIVER_GLES2; - gles3 = false; - continue; - } else { - gl_initialization_error = true; - break; - } - } - } else { - if (RasterizerGLES2::is_viable() == OK) { - RasterizerGLES2::register_config(); - RasterizerGLES2::make_current(); - break; - } else { - gl_initialization_error = true; - break; - } - } - } - - if (gl_initialization_error) { - OS::get_singleton()->alert("Your video card driver does not support any of the supported OpenGL versions.\n" - "Please update your drivers or if you have a very old or integrated GPU upgrade it.", - "Unable to initialize Video driver"); - return ERR_UNAVAILABLE; - } - - video_driver_index = p_video_driver; - - visual_server = memnew(VisualServerRaster); - if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) { - visual_server = memnew(VisualServerWrapMT(visual_server, get_render_thread_mode() == RENDER_SEPARATE_THREAD)); - } - - visual_server->init(); - AudioDriverManager::initialize(p_audio_driver); - - input = memnew(InputDefault); - joypad_osx = memnew(JoypadOSX); - - power_manager = memnew(PowerOSX); - - _ensure_user_data_dir(); - - restore_rect = Rect2(get_window_position(), get_window_size()); - - if (p_desired.layered) { - set_window_per_pixel_transparency_enabled(true); - } - - update_real_mouse_position(); +void OS_OSX::initialize() { + crash_handler.initialize(); - return OK; + initialize_core(); + //ensure_user_data_dir(); } void OS_OSX::finalize() { - #ifdef COREMIDI_ENABLED midi_driver.close(); #endif - CFNotificationCenterRemoveObserver(CFNotificationCenterGetDistributedCenter(), NULL, kTISNotifySelectedKeyboardInputSourceChanged, NULL); - CGDisplayRemoveReconfigurationCallback(displays_arrangement_changed, NULL); - delete_main_loop(); - memdelete(joypad_osx); - memdelete(input); - - cursors_cache.clear(); - visual_server->finish(); - memdelete(visual_server); - //memdelete(rasterizer); + if (joypad_osx) { + memdelete(joypad_osx); + } } void OS_OSX::set_main_loop(MainLoop *p_main_loop) { - main_loop = p_main_loop; - input->set_main_loop(p_main_loop); } void OS_OSX::delete_main_loop() { - if (!main_loop) return; memdelete(main_loop); @@ -1702,92 +162,10 @@ void OS_OSX::delete_main_loop() { } String OS_OSX::get_name() const { - - return "OSX"; -} - -#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101200 -class OSXTerminalLogger : public StdLogger { -public: - virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, ErrorType p_type = ERR_ERROR) { - if (!should_log(true)) { - return; - } - - const char *err_details; - if (p_rationale && p_rationale[0]) - err_details = p_rationale; - else - err_details = p_code; - - switch (p_type) { - case ERR_WARNING: - if (NSAppKitVersionNumber >= NSAppKitVersionNumber10_12) { - os_log_info(OS_LOG_DEFAULT, - "WARNING: %{public}s: %{public}s\nAt: %{public}s:%i.", - p_function, err_details, p_file, p_line); - } - logf_error("\E[1;33mWARNING: %s: \E[0m\E[1m%s\n", p_function, - err_details); - logf_error("\E[0;33m At: %s:%i.\E[0m\n", p_file, p_line); - break; - case ERR_SCRIPT: - if (NSAppKitVersionNumber >= NSAppKitVersionNumber10_12) { - os_log_error(OS_LOG_DEFAULT, - "SCRIPT ERROR: %{public}s: %{public}s\nAt: %{public}s:%i.", - p_function, err_details, p_file, p_line); - } - logf_error("\E[1;35mSCRIPT ERROR: %s: \E[0m\E[1m%s\n", p_function, - err_details); - logf_error("\E[0;35m At: %s:%i.\E[0m\n", p_file, p_line); - break; - case ERR_SHADER: - if (NSAppKitVersionNumber >= NSAppKitVersionNumber10_12) { - os_log_error(OS_LOG_DEFAULT, - "SHADER ERROR: %{public}s: %{public}s\nAt: %{public}s:%i.", - p_function, err_details, p_file, p_line); - } - logf_error("\E[1;36mSHADER ERROR: %s: \E[0m\E[1m%s\n", p_function, - err_details); - logf_error("\E[0;36m At: %s:%i.\E[0m\n", p_file, p_line); - break; - case ERR_ERROR: - default: - if (NSAppKitVersionNumber >= NSAppKitVersionNumber10_12) { - os_log_error(OS_LOG_DEFAULT, - "ERROR: %{public}s: %{public}s\nAt: %{public}s:%i.", - p_function, err_details, p_file, p_line); - } - logf_error("\E[1;31mERROR: %s: \E[0m\E[1m%s\n", p_function, err_details); - logf_error("\E[0;31m At: %s:%i.\E[0m\n", p_file, p_line); - break; - } - } -}; - -#else - -typedef UnixTerminalLogger OSXTerminalLogger; -#endif - -void OS_OSX::alert(const String &p_alert, const String &p_title) { - // Set OS X-compliant variables - NSAlert *window = [[NSAlert alloc] init]; - NSString *ns_title = [NSString stringWithUTF8String:p_title.utf8().get_data()]; - NSString *ns_alert = [NSString stringWithUTF8String:p_alert.utf8().get_data()]; - - [window addButtonWithTitle:@"OK"]; - [window setMessageText:ns_title]; - [window setInformativeText:ns_alert]; - [window setAlertStyle:NSWarningAlertStyle]; - - // Display it, then release - [window runModal]; - [window release]; + return "macOS"; } Error OS_OSX::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) { - String path = p_path; if (!FileAccess::exists(path)) { @@ -1805,301 +183,11 @@ Error OS_OSX::open_dynamic_library(const String p_path, void *&p_library_handle, return OK; } -void OS_OSX::set_cursor_shape(CursorShape p_shape) { - - if (cursor_shape == p_shape) - return; - - if (mouse_mode != MOUSE_MODE_VISIBLE && mouse_mode != MOUSE_MODE_CONFINED) { - cursor_shape = p_shape; - return; - } - - if (cursors[p_shape] != NULL) { - [cursors[p_shape] set]; - } else { - switch (p_shape) { - case CURSOR_ARROW: [[NSCursor arrowCursor] set]; break; - case CURSOR_IBEAM: [[NSCursor IBeamCursor] set]; break; - case CURSOR_POINTING_HAND: [[NSCursor pointingHandCursor] set]; break; - case CURSOR_CROSS: [[NSCursor crosshairCursor] set]; break; - case CURSOR_WAIT: [[NSCursor arrowCursor] set]; break; - case CURSOR_BUSY: [[NSCursor arrowCursor] set]; break; - case CURSOR_DRAG: [[NSCursor closedHandCursor] set]; break; - case CURSOR_CAN_DROP: [[NSCursor openHandCursor] set]; break; - case CURSOR_FORBIDDEN: [[NSCursor operationNotAllowedCursor] set]; break; - case CURSOR_VSIZE: [cursorFromSelector(@selector(_windowResizeNorthSouthCursor), @selector(resizeUpDownCursor)) set]; break; - case CURSOR_HSIZE: [cursorFromSelector(@selector(_windowResizeEastWestCursor), @selector(resizeLeftRightCursor)) set]; break; - case CURSOR_BDIAGSIZE: [cursorFromSelector(@selector(_windowResizeNorthEastSouthWestCursor)) set]; break; - case CURSOR_FDIAGSIZE: [cursorFromSelector(@selector(_windowResizeNorthWestSouthEastCursor)) set]; break; - case CURSOR_MOVE: [[NSCursor arrowCursor] set]; break; - case CURSOR_VSPLIT: [[NSCursor resizeUpDownCursor] set]; break; - case CURSOR_HSPLIT: [[NSCursor resizeLeftRightCursor] set]; break; - case CURSOR_HELP: [cursorFromSelector(@selector(_helpCursor)) set]; break; - default: { - }; - } - } - - cursor_shape = p_shape; -} - -OS::CursorShape OS_OSX::get_cursor_shape() const { - - return cursor_shape; -} - -void OS_OSX::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) { - - if (p_cursor.is_valid()) { - - Map<CursorShape, Vector<Variant> >::Element *cursor_c = cursors_cache.find(p_shape); - - if (cursor_c) { - if (cursor_c->get()[0] == p_cursor && cursor_c->get()[1] == p_hotspot) { - set_cursor_shape(p_shape); - return; - } - - cursors_cache.erase(p_shape); - } - - Ref<Texture> texture = p_cursor; - Ref<AtlasTexture> atlas_texture = p_cursor; - Ref<Image> image; - Size2 texture_size; - Rect2 atlas_rect; - - if (texture.is_valid()) { - image = texture->get_data(); - } - - if (!image.is_valid() && atlas_texture.is_valid()) { - texture = atlas_texture->get_atlas(); - - atlas_rect.size.width = texture->get_width(); - atlas_rect.size.height = texture->get_height(); - atlas_rect.position.x = atlas_texture->get_region().position.x; - atlas_rect.position.y = atlas_texture->get_region().position.y; - - texture_size.width = atlas_texture->get_region().size.x; - texture_size.height = atlas_texture->get_region().size.y; - } else if (image.is_valid()) { - texture_size.width = texture->get_width(); - texture_size.height = texture->get_height(); - } - - ERR_FAIL_COND(!texture.is_valid()); - ERR_FAIL_COND(p_hotspot.x < 0 || p_hotspot.y < 0); - ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256); - ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height); - - image = texture->get_data(); - - ERR_FAIL_COND(!image.is_valid()); - - NSBitmapImageRep *imgrep = [[NSBitmapImageRep alloc] - initWithBitmapDataPlanes:NULL - pixelsWide:int(texture_size.width) - pixelsHigh:int(texture_size.height) - bitsPerSample:8 - samplesPerPixel:4 - hasAlpha:YES - isPlanar:NO - colorSpaceName:NSDeviceRGBColorSpace - bytesPerRow:int(texture_size.width) * 4 - bitsPerPixel:32]; - - ERR_FAIL_COND(imgrep == nil); - uint8_t *pixels = [imgrep bitmapData]; - - int len = int(texture_size.width * texture_size.height); - PoolVector<uint8_t> data = image->get_data(); - PoolVector<uint8_t>::Read r = data.read(); - - image->lock(); - - /* Premultiply the alpha channel */ - for (int i = 0; i < len; i++) { - int row_index = floor(i / texture_size.width) + atlas_rect.position.y; - int column_index = (i % int(texture_size.width)) + atlas_rect.position.x; - - if (atlas_texture.is_valid()) { - column_index = MIN(column_index, atlas_rect.size.width - 1); - row_index = MIN(row_index, atlas_rect.size.height - 1); - } - - uint32_t color = image->get_pixel(column_index, row_index).to_argb32(); - - uint8_t alpha = (color >> 24) & 0xFF; - pixels[i * 4 + 0] = ((color >> 16) & 0xFF) * alpha / 255; - pixels[i * 4 + 1] = ((color >> 8) & 0xFF) * alpha / 255; - pixels[i * 4 + 2] = ((color)&0xFF) * alpha / 255; - pixels[i * 4 + 3] = alpha; - } - - image->unlock(); - - NSImage *nsimage = [[NSImage alloc] initWithSize:NSMakeSize(texture_size.width, texture_size.height)]; - [nsimage addRepresentation:imgrep]; - - NSCursor *cursor = [[NSCursor alloc] initWithImage:nsimage hotSpot:NSMakePoint(p_hotspot.x, p_hotspot.y)]; - - [cursors[p_shape] release]; - cursors[p_shape] = cursor; - - Vector<Variant> params; - params.push_back(p_cursor); - params.push_back(p_hotspot); - cursors_cache.insert(p_shape, params); - - if (p_shape == cursor_shape) { - if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) { - [cursor set]; - } - } - - [imgrep release]; - [nsimage release]; - } else { - // Reset to default system cursor - if (cursors[p_shape] != NULL) { - [cursors[p_shape] release]; - cursors[p_shape] = NULL; - } - - CursorShape c = cursor_shape; - cursor_shape = CURSOR_MAX; - set_cursor_shape(c); - - cursors_cache.erase(p_shape); - } -} - -void OS_OSX::set_mouse_show(bool p_show) { -} - -void OS_OSX::set_mouse_grab(bool p_grab) { -} - -bool OS_OSX::is_mouse_grab_enabled() const { - - return mouse_grab; -} - -void OS_OSX::warp_mouse_position(const Point2 &p_to) { - - //copied from windows impl with osx native calls - if (mouse_mode == MOUSE_MODE_CAPTURED) { - mouse_x = p_to.x; - mouse_y = p_to.y; - } else { //set OS position - - //local point in window coords - const NSRect contentRect = [window_view frame]; - float displayScale = _display_scale(); - NSRect pointInWindowRect = NSMakeRect(p_to.x / displayScale, contentRect.size.height - (p_to.y / displayScale) - 1, 0, 0); - NSPoint pointOnScreen = [[window_view window] convertRectToScreen:pointInWindowRect].origin; - - //point in scren coords - CGPoint lMouseWarpPos = { pointOnScreen.x, CGDisplayBounds(CGMainDisplayID()).size.height - pointOnScreen.y }; - - //do the warping - CGEventSourceRef lEventRef = CGEventSourceCreate(kCGEventSourceStateCombinedSessionState); - CGEventSourceSetLocalEventsSuppressionInterval(lEventRef, 0.0); - CGAssociateMouseAndMouseCursorPosition(false); - CGWarpMouseCursorPosition(lMouseWarpPos); - CGAssociateMouseAndMouseCursorPosition(true); - } -} - -void OS_OSX::update_real_mouse_position() { - - get_mouse_pos([window_object mouseLocationOutsideOfEventStream], [window_view backingScaleFactor]); - input->set_mouse_position(Point2(mouse_x, mouse_y)); -} - -Point2 OS_OSX::get_mouse_position() const { - - return Vector2(mouse_x, mouse_y); -} - -int OS_OSX::get_mouse_button_state() const { - return button_mask; -} - -void OS_OSX::set_window_title(const String &p_title) { - title = p_title; - - [window_object setTitle:[NSString stringWithUTF8String:p_title.utf8().get_data()]]; -} - -void OS_OSX::set_native_icon(const String &p_filename) { - - FileAccess *f = FileAccess::open(p_filename, FileAccess::READ); - ERR_FAIL_COND(!f); - - Vector<uint8_t> data; - uint32_t len = f->get_len(); - data.resize(len); - f->get_buffer((uint8_t *)&data.write[0], len); - memdelete(f); - - NSData *icon_data = [[[NSData alloc] initWithBytes:&data.write[0] length:len] autorelease]; - ERR_FAIL_COND_MSG(!icon_data, "Error reading icon data."); - - NSImage *icon = [[[NSImage alloc] initWithData:icon_data] autorelease]; - ERR_FAIL_COND_MSG(!icon, "Error loading icon."); - - [NSApp setApplicationIconImage:icon]; -} - -void OS_OSX::set_icon(const Ref<Image> &p_icon) { - - Ref<Image> img = p_icon; - img = img->duplicate(); - img->convert(Image::FORMAT_RGBA8); - NSBitmapImageRep *imgrep = [[[NSBitmapImageRep alloc] - initWithBitmapDataPlanes:NULL - 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] autorelease]; - ERR_FAIL_COND(imgrep == nil); - uint8_t *pixels = [imgrep bitmapData]; - - int len = img->get_width() * img->get_height(); - PoolVector<uint8_t> data = img->get_data(); - PoolVector<uint8_t>::Read r = data.read(); - - /* Premultiply the alpha channel */ - for (int i = 0; i < len; i++) { - uint8_t alpha = r[i * 4 + 3]; - pixels[i * 4 + 0] = (uint8_t)(((uint16_t)r[i * 4 + 0] * alpha) / 255); - pixels[i * 4 + 1] = (uint8_t)(((uint16_t)r[i * 4 + 1] * alpha) / 255); - pixels[i * 4 + 2] = (uint8_t)(((uint16_t)r[i * 4 + 2] * alpha) / 255); - pixels[i * 4 + 3] = alpha; - } - - NSImage *nsimg = [[[NSImage alloc] initWithSize:NSMakeSize(img->get_width(), img->get_height())] autorelease]; - ERR_FAIL_COND(nsimg == nil); - [nsimg addRepresentation:imgrep]; - - [NSApp setApplicationIconImage:nsimg]; -} - MainLoop *OS_OSX::get_main_loop() const { - return main_loop; } String OS_OSX::get_config_path() const { - if (has_environment("XDG_CONFIG_HOME")) { return get_environment("XDG_CONFIG_HOME"); } else if (has_environment("HOME")) { @@ -2110,7 +198,6 @@ String OS_OSX::get_config_path() const { } String OS_OSX::get_data_path() const { - if (has_environment("XDG_DATA_HOME")) { return get_environment("XDG_DATA_HOME"); } else { @@ -2119,7 +206,6 @@ String OS_OSX::get_data_path() const { } String OS_OSX::get_cache_path() const { - if (has_environment("XDG_CACHE_HOME")) { return get_environment("XDG_CACHE_HOME"); } else if (has_environment("HOME")) { @@ -2130,7 +216,6 @@ String OS_OSX::get_cache_path() const { } String OS_OSX::get_bundle_resource_dir() const { - NSBundle *main = [NSBundle mainBundle]; NSString *resourcePath = [main resourcePath]; @@ -2144,12 +229,10 @@ String OS_OSX::get_bundle_resource_dir() const { // Get properly capitalized engine name for system paths String OS_OSX::get_godot_dir_name() const { - return String(VERSION_SHORT_NAME).capitalize(); } String OS_OSX::get_system_dir(SystemDir p_dir) const { - NSSearchPathDirectory id; bool found = true; @@ -2179,10 +262,8 @@ String OS_OSX::get_system_dir(SystemDir p_dir) const { String ret; if (found) { - NSArray *paths = NSSearchPathForDirectoriesInDomains(id, NSUserDomainMask, YES); if (paths && [paths count] >= 1) { - char *utfs = strdup([[paths firstObject] UTF8String]); ret.parse_utf8(utfs); free(utfs); @@ -2192,57 +273,14 @@ String OS_OSX::get_system_dir(SystemDir p_dir) const { return ret; } -bool OS_OSX::can_draw() const { - - return true; -} - -void OS_OSX::set_clipboard(const String &p_text) { - - NSString *copiedString = [NSString stringWithUTF8String:p_text.utf8().get_data()]; - NSArray *copiedStringArray = [NSArray arrayWithObject:copiedString]; - - NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; - [pasteboard clearContents]; - [pasteboard writeObjects:copiedStringArray]; -} - -String OS_OSX::get_clipboard() const { - - NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; - NSArray *classArray = [NSArray arrayWithObject:[NSString class]]; - NSDictionary *options = [NSDictionary dictionary]; - - BOOL ok = [pasteboard canReadObjectForClasses:classArray options:options]; - - if (!ok) { - return ""; - } - - NSArray *objectsToPaste = [pasteboard readObjectsForClasses:classArray options:options]; - NSString *string = [objectsToPaste objectAtIndex:0]; - - char *utfs = strdup([string UTF8String]); - String ret; - ret.parse_utf8(utfs); - free(utfs); - - return ret; -} - -void OS_OSX::release_rendering_thread() { - - [NSOpenGLContext clearCurrentContext]; -} - -void OS_OSX::make_rendering_thread() { - - [context makeCurrentContext]; -} - Error OS_OSX::shell_open(String p_uri) { - - [[NSWorkspace sharedWorkspace] openURL:[[NSURL alloc] initWithString:[[NSString stringWithUTF8String:p_uri.utf8().get_data()] stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]]]]; + NSString *string = [NSString stringWithUTF8String:p_uri.utf8().get_data()]; + NSURL *uri = [[NSURL alloc] initWithString:string]; + // Escape special characters in filenames + if (!uri || !uri.scheme || [uri.scheme isEqual:@"file"]) { + uri = [[NSURL alloc] initWithString:[string stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]]]; + } + [[NSWorkspace sharedWorkspace] openURL:uri]; return OK; } @@ -2251,472 +289,7 @@ String OS_OSX::get_locale() const { return [locale_code UTF8String]; } -void OS_OSX::swap_buffers() { - [context flushBuffer]; -} - -void OS_OSX::wm_minimized(bool p_minimized) { - - minimized = p_minimized; -}; - -void OS_OSX::set_video_mode(const VideoMode &p_video_mode, int p_screen) { -} - -OS::VideoMode OS_OSX::get_video_mode(int p_screen) const { - - VideoMode vm; - vm.width = window_size.width; - vm.height = window_size.height; - - return vm; -} - -void OS_OSX::get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen) const { -} - -int OS_OSX::get_screen_count() const { - NSArray *screenArray = [NSScreen screens]; - return [screenArray count]; -}; - -// Returns the native top-left screen coordinate of the smallest rectangle -// that encompasses all screens. Needed in get_screen_position(), -// get_window_position, and set_window_position() -// to convert between OS X native screen coordinates and the ones expected by Godot -Point2 OS_OSX::get_screens_origin() const { - static Point2 origin; - - if (displays_arrangement_dirty) { - origin = Point2(); - - for (int i = 0; i < get_screen_count(); i++) { - Point2 position = get_native_screen_position(i); - if (position.x < origin.x) { - origin.x = position.x; - } - if (position.y > origin.y) { - origin.y = position.y; - } - } - - displays_arrangement_dirty = false; - } - - return origin; -} - -static int get_screen_index(NSScreen *screen) { - const NSUInteger index = [[NSScreen screens] indexOfObject:screen]; - return index == NSNotFound ? 0 : index; -} - -int OS_OSX::get_current_screen() const { - if (window_object) { - return get_screen_index([window_object screen]); - } else { - return get_screen_index([NSScreen mainScreen]); - } -}; - -void OS_OSX::set_current_screen(int p_screen) { - Vector2 wpos = get_window_position() - get_screen_position(get_current_screen()); - set_window_position(wpos + get_screen_position(p_screen)); -}; - -Point2 OS_OSX::get_native_screen_position(int p_screen) const { - if (p_screen < 0) { - p_screen = get_current_screen(); - } - - NSArray *screenArray = [NSScreen screens]; - if ((NSUInteger)p_screen < [screenArray count]) { - float display_scale = _display_scale([screenArray objectAtIndex:p_screen]); - NSRect nsrect = [[screenArray objectAtIndex:p_screen] frame]; - // Return the top-left corner of the screen, for OS X the y starts at the bottom - return Point2(nsrect.origin.x, nsrect.origin.y + nsrect.size.height) * display_scale; - } - - return Point2(); -} - -Point2 OS_OSX::get_screen_position(int p_screen) const { - Point2 position = get_native_screen_position(p_screen) - get_screens_origin(); - // OS X native y-coordinate relative to get_screens_origin() is negative, - // Godot expects a positive value - position.y *= -1; - return position; -} - -int OS_OSX::get_screen_dpi(int p_screen) const { - if (p_screen < 0) { - p_screen = get_current_screen(); - } - - NSArray *screenArray = [NSScreen screens]; - if ((NSUInteger)p_screen < [screenArray count]) { - float displayScale = _display_scale([screenArray objectAtIndex:p_screen]); - NSDictionary *description = [[screenArray objectAtIndex:p_screen] deviceDescription]; - NSSize displayPixelSize = [[description objectForKey:NSDeviceSize] sizeValue]; - CGSize displayPhysicalSize = CGDisplayScreenSize( - [[description objectForKey:@"NSScreenNumber"] unsignedIntValue]); - - return (displayPixelSize.width * 25.4f / displayPhysicalSize.width) * displayScale; - } - - return 72; -} - -Size2 OS_OSX::get_screen_size(int p_screen) const { - if (p_screen < 0) { - p_screen = get_current_screen(); - } - - NSArray *screenArray = [NSScreen screens]; - if ((NSUInteger)p_screen < [screenArray count]) { - float displayScale = _display_scale([screenArray objectAtIndex:p_screen]); - // Note: Use frame to get the whole screen size - NSRect nsrect = [[screenArray objectAtIndex:p_screen] frame]; - return Size2(nsrect.size.width, nsrect.size.height) * displayScale; - } - - return Size2(); -} - -void OS_OSX::_update_window() { - bool borderless_full = false; - - if (get_borderless_window()) { - NSRect frameRect = [window_object frame]; - NSRect screenRect = [[window_object screen] frame]; - - // Check if our window covers up the screen - if (frameRect.origin.x <= screenRect.origin.x && frameRect.origin.y <= frameRect.origin.y && - frameRect.size.width >= screenRect.size.width && frameRect.size.height >= screenRect.size.height) { - borderless_full = true; - } - } - - if (borderless_full) { - // If the window covers up the screen set the level to above the main menu and hide on deactivate - [window_object setLevel:NSMainMenuWindowLevel + 1]; - [window_object setHidesOnDeactivate:YES]; - } else { - // Reset these when our window is not a borderless window that covers up the screen - [window_object setLevel:NSNormalWindowLevel]; - [window_object setHidesOnDeactivate:NO]; - } -} - -float OS_OSX::_display_scale() const { - if (window_object) { - return _display_scale([window_object screen]); - } else { - return _display_scale([NSScreen mainScreen]); - } -} - -float OS_OSX::_display_scale(id screen) const { - if (is_hidpi_allowed()) { - if ([screen respondsToSelector:@selector(backingScaleFactor)]) { - return fmax(1.0, [screen backingScaleFactor]); - } - } - return 1.0; -} - -Point2 OS_OSX::get_native_window_position() const { - - NSRect nsrect = [window_object frame]; - Point2 pos; - float display_scale = _display_scale(); - - // Return the position of the top-left corner, for OS X the y starts at the bottom - pos.x = nsrect.origin.x * display_scale; - pos.y = (nsrect.origin.y + nsrect.size.height) * display_scale; - - return pos; -}; - -Point2 OS_OSX::get_window_position() const { - Point2 position = get_native_window_position() - get_screens_origin(); - // OS X native y-coordinate relative to get_screens_origin() is negative, - // Godot expects a positive value - position.y *= -1; - return position; -} - -void OS_OSX::set_native_window_position(const Point2 &p_position) { - - NSPoint pos; - float displayScale = _display_scale(); - - pos.x = p_position.x / displayScale; - pos.y = p_position.y / displayScale; - - [window_object setFrameTopLeftPoint:pos]; - - _update_window(); -}; - -void OS_OSX::set_window_position(const Point2 &p_position) { - Point2 position = p_position; - // OS X native y-coordinate relative to get_screens_origin() is negative, - // Godot passes a positive value - position.y *= -1; - set_native_window_position(get_screens_origin() + position); - - update_real_mouse_position(); -}; - -Size2 OS_OSX::get_window_size() const { - - return window_size; -}; - -Size2 OS_OSX::get_real_window_size() const { - - NSRect frame = [window_object frame]; - return Size2(frame.size.width, frame.size.height) * _display_scale(); -} - -Size2 OS_OSX::get_max_window_size() const { - return max_size; -} - -Size2 OS_OSX::get_min_window_size() const { - return min_size; -} - -void OS_OSX::set_min_window_size(const Size2 p_size) { - - if ((p_size != Size2()) && (max_size != Size2()) && ((p_size.x > max_size.x) || (p_size.y > max_size.y))) { - ERR_PRINT("Minimum window size can't be larger than maximum window size!"); - return; - } - min_size = p_size; - - if ((min_size != Size2()) && !zoomed) { - Size2 size = min_size / _display_scale(); - [window_object setContentMinSize:NSMakeSize(size.x, size.y)]; - } else { - [window_object setContentMinSize:NSMakeSize(0, 0)]; - } -} - -void OS_OSX::set_max_window_size(const Size2 p_size) { - - if ((p_size != Size2()) && ((p_size.x < min_size.x) || (p_size.y < min_size.y))) { - ERR_PRINT("Maximum window size can't be smaller than minimum window size!"); - return; - } - max_size = p_size; - - if ((max_size != Size2()) && !zoomed) { - Size2 size = max_size / _display_scale(); - [window_object setContentMaxSize:NSMakeSize(size.x, size.y)]; - } else { - [window_object setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)]; - } -} - -void OS_OSX::set_window_size(const Size2 p_size) { - - Size2 size = p_size / _display_scale(); - - if (get_borderless_window() == false) { - // NSRect used by setFrame includes the title bar, so add it to our size.y - CGFloat menuBarHeight = [[[NSApplication sharedApplication] mainMenu] menuBarHeight]; - if (menuBarHeight != 0.f) { - size.y += menuBarHeight; - } else { - if (floor(NSAppKitVersionNumber) < NSAppKitVersionNumber10_12) { - size.y += [[NSStatusBar systemStatusBar] thickness]; - } - } - } - - NSRect frame = [window_object frame]; - [window_object setFrame:NSMakeRect(frame.origin.x, frame.origin.y, size.x, size.y) display:YES]; - - _update_window(); -}; - -void OS_OSX::set_window_fullscreen(bool p_enabled) { - - if (zoomed != p_enabled) { - if (layered_window) - set_window_per_pixel_transparency_enabled(false); - if (!resizable) - [window_object setStyleMask:[window_object styleMask] | NSWindowStyleMaskResizable]; - if (p_enabled) { - [window_object setContentMinSize:NSMakeSize(0, 0)]; - [window_object setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)]; - } else { - if (min_size != Size2()) { - Size2 size = min_size / _display_scale(); - [window_object setContentMinSize:NSMakeSize(size.x, size.y)]; - } - if (max_size != Size2()) { - Size2 size = max_size / _display_scale(); - [window_object setContentMaxSize:NSMakeSize(size.x, size.y)]; - } - } - [window_object toggleFullScreen:nil]; - } - zoomed = p_enabled; -}; - -bool OS_OSX::is_window_fullscreen() const { - - return zoomed; -}; - -void OS_OSX::set_window_resizable(bool p_enabled) { - - if (p_enabled) - [window_object setStyleMask:[window_object styleMask] | NSWindowStyleMaskResizable]; - else if (!zoomed) - [window_object setStyleMask:[window_object styleMask] & ~NSWindowStyleMaskResizable]; - - resizable = p_enabled; -}; - -bool OS_OSX::is_window_resizable() const { - - return [window_object styleMask] & NSWindowStyleMaskResizable; -}; - -void OS_OSX::set_window_minimized(bool p_enabled) { - - if (p_enabled) - [window_object performMiniaturize:nil]; - else - [window_object deminiaturize:nil]; -}; - -bool OS_OSX::is_window_minimized() const { - - if ([window_object respondsToSelector:@selector(isMiniaturized)]) - return [window_object isMiniaturized]; - - return minimized; -}; - -void OS_OSX::set_window_maximized(bool p_enabled) { - - if (p_enabled) { - restore_rect = Rect2(get_window_position(), get_window_size()); - [window_object setFrame:[[[NSScreen screens] objectAtIndex:get_current_screen()] visibleFrame] display:YES]; - } else { - set_window_size(restore_rect.size); - set_window_position(restore_rect.position); - }; - maximized = p_enabled; -}; - -bool OS_OSX::is_window_maximized() const { - - // don't know - return maximized; -}; - -void OS_OSX::move_window_to_foreground() { - - [[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; - [window_object makeKeyAndOrderFront:nil]; -} - -void OS_OSX::set_window_always_on_top(bool p_enabled) { - if (is_window_always_on_top() == p_enabled) - return; - - if (p_enabled) - [window_object setLevel:NSFloatingWindowLevel]; - else - [window_object setLevel:NSNormalWindowLevel]; -} - -bool OS_OSX::is_window_always_on_top() const { - return [window_object level] == NSFloatingWindowLevel; -} - -bool OS_OSX::is_window_focused() const { - return window_focused; -} - -void OS_OSX::request_attention() { - - [NSApp requestUserAttention:NSCriticalRequest]; -} - -bool OS_OSX::get_window_per_pixel_transparency_enabled() const { - - if (!is_layered_allowed()) return false; - return layered_window; -} - -void OS_OSX::set_window_per_pixel_transparency_enabled(bool p_enabled) { - - if (!is_layered_allowed()) return; - if (layered_window != p_enabled) { - if (p_enabled) { - set_borderless_window(true); - GLint opacity = 0; - [window_object setBackgroundColor:[NSColor clearColor]]; - [window_object setOpaque:NO]; - [window_object setHasShadow:NO]; - [context setValues:&opacity forParameter:NSOpenGLCPSurfaceOpacity]; - layered_window = true; - } else { - GLint opacity = 1; - [window_object setBackgroundColor:[NSColor colorWithCalibratedWhite:1 alpha:1]]; - [window_object setOpaque:YES]; - [window_object setHasShadow:YES]; - [context setValues:&opacity forParameter:NSOpenGLCPSurfaceOpacity]; - layered_window = false; - } - [context update]; - NSRect frame = [window_object frame]; - [window_object setFrame:NSMakeRect(frame.origin.x, frame.origin.y, 1, 1) display:YES]; - [window_object setFrame:frame display:YES]; - } -} - -void OS_OSX::set_borderless_window(bool p_borderless) { - - // OrderOut prevents a lose focus bug with the window - [window_object orderOut:nil]; - - if (p_borderless) { - [window_object setStyleMask:NSWindowStyleMaskBorderless]; - } else { - if (layered_window) - set_window_per_pixel_transparency_enabled(false); - - [window_object setStyleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | (resizable ? NSWindowStyleMaskResizable : 0)]; - - // Force update of the window styles - NSRect frameRect = [window_object frame]; - [window_object setFrame:NSMakeRect(frameRect.origin.x, frameRect.origin.y, frameRect.size.width + 1, frameRect.size.height) display:NO]; - [window_object setFrame:frameRect display:NO]; - - // Restore the window title - [window_object setTitle:[NSString stringWithUTF8String:title.utf8().get_data()]]; - } - - _update_window(); - - [window_object makeKeyAndOrderFront:nil]; -} - -bool OS_OSX::get_borderless_window() { - - return [window_object styleMask] == NSWindowStyleMaskBorderless; -} - String OS_OSX::get_executable_path() const { - int ret; pid_t pid; char pathbuf[PROC_PIDPATHINFO_MAXSIZE]; @@ -2733,174 +306,7 @@ String OS_OSX::get_executable_path() const { } } -// Returns string representation of keys, if they are printable. -// -static NSString *createStringForKeys(const CGKeyCode *keyCode, int length) { - - TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource(); - if (!currentKeyboard) - return nil; - - CFDataRef layoutData = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData); - if (!layoutData) - return nil; - - const UCKeyboardLayout *keyboardLayout = (const UCKeyboardLayout *)CFDataGetBytePtr(layoutData); - - OSStatus err; - CFMutableStringRef output = CFStringCreateMutable(NULL, 0); - - for (int i = 0; i < length; ++i) { - - UInt32 keysDown = 0; - UniChar chars[4]; - UniCharCount realLength; - - err = UCKeyTranslate(keyboardLayout, - keyCode[i], - kUCKeyActionDisplay, - 0, - LMGetKbdType(), - kUCKeyTranslateNoDeadKeysBit, - &keysDown, - sizeof(chars) / sizeof(chars[0]), - &realLength, - chars); - - if (err != noErr) { - CFRelease(output); - return nil; - } - - CFStringAppendCharacters(output, chars, 1); - } - - //CFStringUppercase(output, NULL); - - return (NSString *)output; -} - -OS::LatinKeyboardVariant OS_OSX::get_latin_keyboard_variant() const { - - static LatinKeyboardVariant layout = LATIN_KEYBOARD_QWERTY; - - if (keyboard_layout_dirty) { - - layout = LATIN_KEYBOARD_QWERTY; - - CGKeyCode keys[] = { kVK_ANSI_Q, kVK_ANSI_W, kVK_ANSI_E, kVK_ANSI_R, kVK_ANSI_T, kVK_ANSI_Y }; - NSString *test = createStringForKeys(keys, 6); - - if ([test isEqualToString:@"qwertz"]) { - layout = LATIN_KEYBOARD_QWERTZ; - } else if ([test isEqualToString:@"azerty"]) { - layout = LATIN_KEYBOARD_AZERTY; - } else if ([test isEqualToString:@"qzerty"]) { - layout = LATIN_KEYBOARD_QZERTY; - } else if ([test isEqualToString:@"',.pyf"]) { - layout = LATIN_KEYBOARD_DVORAK; - } else if ([test isEqualToString:@"xvlcwk"]) { - layout = LATIN_KEYBOARD_NEO; - } else if ([test isEqualToString:@"qwfpgj"]) { - layout = LATIN_KEYBOARD_COLEMAK; - } - - [test release]; - - keyboard_layout_dirty = false; - return layout; - } - - return layout; -} - -void OS_OSX::process_events() { - - while (true) { - NSEvent *event = [NSApp - nextEventMatchingMask:NSEventMaskAny - untilDate:[NSDate distantPast] - inMode:NSDefaultRunLoopMode - dequeue:YES]; - - if (event == nil) - break; - - [NSApp sendEvent:event]; - } - process_key_events(); - - [autoreleasePool drain]; - autoreleasePool = [[NSAutoreleasePool alloc] init]; - - input->flush_accumulated_events(); -} - -void OS_OSX::process_key_events() { - - Ref<InputEventKey> k; - for (int i = 0; i < key_event_pos; i++) { - - const KeyEvent &ke = key_event_buffer[i]; - - if (ke.raw) { - // Non IME input - no composite characters, pass events as is - k.instance(); - - get_key_modifier_state(ke.osx_state, k); - k->set_pressed(ke.pressed); - k->set_echo(ke.echo); - k->set_scancode(ke.scancode); - k->set_unicode(ke.unicode); - - push_input(k); - } else { - // IME input - if ((i == 0 && ke.scancode == 0) || (i > 0 && key_event_buffer[i - 1].scancode == 0)) { - k.instance(); - - get_key_modifier_state(ke.osx_state, k); - k->set_pressed(ke.pressed); - k->set_echo(ke.echo); - k->set_scancode(0); - k->set_unicode(ke.unicode); - - push_input(k); - } - if (ke.scancode != 0) { - k.instance(); - - get_key_modifier_state(ke.osx_state, k); - k->set_pressed(ke.pressed); - k->set_echo(ke.echo); - k->set_scancode(ke.scancode); - - if (i + 1 < key_event_pos && key_event_buffer[i + 1].scancode == 0) { - k->set_unicode(key_event_buffer[i + 1].unicode); - } - - push_input(k); - } - } - } - - key_event_pos = 0; -} - -void OS_OSX::push_input(const Ref<InputEvent> &p_event) { - - Ref<InputEvent> ev = p_event; - input->accumulate_input_event(ev); -} - -void OS_OSX::force_process_input() { - - process_events(); // get rid of pending events - joypad_osx->process_joypads(); -} - void OS_OSX::run() { - force_quit = false; if (!main_loop) @@ -2908,218 +314,50 @@ void OS_OSX::run() { main_loop->init(); - if (zoomed) { - zoomed = false; - set_window_fullscreen(true); - } - - //uint64_t last_ticks=get_ticks_usec(); - - //int frames=0; - //uint64_t frame=0; - bool quit = false; - while (!force_quit && !quit) { - @try { - - process_events(); // get rid of pending events + if (DisplayServer::get_singleton()) { + DisplayServer::get_singleton()->process_events(); // get rid of pending events + } joypad_osx->process_joypads(); - if (Main::iteration() == true) { + if (Main::iteration()) { quit = true; } } @catch (NSException *exception) { - ERR_PRINTS("NSException: " + String([exception reason].UTF8String)); + ERR_PRINT("NSException: " + String([exception reason].UTF8String)); } }; - main_loop->finish(); } -void OS_OSX::set_mouse_mode(MouseMode p_mode) { - - if (p_mode == mouse_mode) - return; - - if (p_mode == MOUSE_MODE_CAPTURED) { - // Apple Docs state that the display parameter is not used. - // "This parameter is not used. By default, you may pass kCGDirectMainDisplay." - // https://developer.apple.com/library/mac/documentation/graphicsimaging/reference/Quartz_Services_Ref/Reference/reference.html - CGDisplayHideCursor(kCGDirectMainDisplay); - CGAssociateMouseAndMouseCursorPosition(false); - } else if (p_mode == MOUSE_MODE_HIDDEN) { - CGDisplayHideCursor(kCGDirectMainDisplay); - CGAssociateMouseAndMouseCursorPosition(true); - } else { - CGDisplayShowCursor(kCGDirectMainDisplay); - CGAssociateMouseAndMouseCursorPosition(true); - } - - mouse_mode = p_mode; -} - -OS::MouseMode OS_OSX::get_mouse_mode() const { - - return mouse_mode; -} - -String OS_OSX::get_joy_guid(int p_device) const { - return input->get_joy_guid_remapped(p_device); -} - -OS::PowerState OS_OSX::get_power_state() { - return power_manager->get_power_state(); -} - -int OS_OSX::get_power_seconds_left() { - return power_manager->get_power_seconds_left(); -} - -int OS_OSX::get_power_percent_left() { - return power_manager->get_power_percent_left(); -} - Error OS_OSX::move_to_trash(const String &p_path) { NSFileManager *fm = [NSFileManager defaultManager]; NSURL *url = [NSURL fileURLWithPath:@(p_path.utf8().get_data())]; NSError *err; if (![fm trashItemAtURL:url resultingItemURL:nil error:&err]) { - ERR_PRINTS("trashItemAtURL error: " + String(err.localizedDescription.UTF8String)); + ERR_PRINT("trashItemAtURL error: " + String(err.localizedDescription.UTF8String)); return FAILED; } return OK; } -void OS_OSX::_set_use_vsync(bool p_enable) { - CGLContextObj ctx = CGLGetCurrentContext(); - if (ctx) { - GLint swapInterval = p_enable ? 1 : 0; - CGLSetParameter(ctx, kCGLCPSwapInterval, &swapInterval); - } -} - -OS_OSX *OS_OSX::singleton = NULL; - OS_OSX::OS_OSX() { - - memset(cursors, 0, sizeof(cursors)); - key_event_pos = 0; - mouse_mode = OS::MOUSE_MODE_VISIBLE; main_loop = NULL; - singleton = this; - im_active = false; - im_position = Point2(); - layered_window = false; - autoreleasePool = [[NSAutoreleasePool alloc] init]; - - eventSource = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); - ERR_FAIL_COND(!eventSource); - - CGEventSourceSetLocalEventsSuppressionInterval(eventSource, 0.0); - - /* - if (pthread_key_create(&_Godot.nsgl.current, NULL) != 0) { - _GodotInputError(Godot_PLATFORM_ERROR, "NSGL: Failed to create context TLS"); - return GL_FALSE; - } -*/ - - framework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl")); - ERR_FAIL_COND(!framework); - - // Implicitly create shared NSApplication instance - [GodotApplication sharedApplication]; - - // In case we are unbundled, make us a proper UI application - [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; - - // Menu bar setup must go between sharedApplication above and - // finishLaunching below, in order to properly emulate the behavior - // of NSApplicationMain - NSMenuItem *menu_item; - NSString *title; - - NSString *nsappname = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"]; - if (nsappname == nil) - nsappname = [[NSProcessInfo processInfo] processName]; - - // Setup Apple menu - NSMenu *apple_menu = [[NSMenu alloc] initWithTitle:@""]; - title = [NSString stringWithFormat:NSLocalizedString(@"About %@", nil), nsappname]; - [apple_menu addItemWithTitle:title action:@selector(showAbout:) keyEquivalent:@""]; - - [apple_menu addItem:[NSMenuItem separatorItem]]; - - NSMenu *services = [[NSMenu alloc] initWithTitle:@""]; - menu_item = [apple_menu addItemWithTitle:NSLocalizedString(@"Services", nil) action:nil keyEquivalent:@""]; - [apple_menu setSubmenu:services forItem:menu_item]; - [NSApp setServicesMenu:services]; - [services release]; - - [apple_menu addItem:[NSMenuItem separatorItem]]; - - title = [NSString stringWithFormat:NSLocalizedString(@"Hide %@", nil), nsappname]; - [apple_menu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h"]; - - menu_item = [apple_menu addItemWithTitle:NSLocalizedString(@"Hide Others", nil) action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; - [menu_item setKeyEquivalentModifierMask:(NSEventModifierFlagOption | NSEventModifierFlagCommand)]; - - [apple_menu addItemWithTitle:NSLocalizedString(@"Show all", nil) action:@selector(unhideAllApplications:) keyEquivalent:@""]; - - [apple_menu addItem:[NSMenuItem separatorItem]]; - - title = [NSString stringWithFormat:NSLocalizedString(@"Quit %@", nil), nsappname]; - [apple_menu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"]; - - // Setup menu bar - NSMenu *main_menu = [[NSMenu alloc] initWithTitle:@""]; - menu_item = [main_menu addItemWithTitle:@"" action:nil keyEquivalent:@""]; - [main_menu setSubmenu:apple_menu forItem:menu_item]; - [NSApp setMainMenu:main_menu]; - - [main_menu release]; - [apple_menu release]; - - [NSApp finishLaunching]; - - delegate = [[GodotApplicationDelegate alloc] init]; - ERR_FAIL_COND(!delegate); - [NSApp setDelegate:delegate]; - - cursor_shape = CURSOR_ARROW; - - maximized = false; - minimized = false; - window_size = Vector2(1024, 600); - zoomed = false; - resizable = false; - window_focused = true; + force_quit = false; Vector<Logger *> loggers; loggers.push_back(memnew(OSXTerminalLogger)); _set_logger(memnew(CompositeLogger(loggers))); - //process application:openFile: event - while (true) { - NSEvent *event = [NSApp - nextEventMatchingMask:NSEventMaskAny - untilDate:[NSDate distantPast] - inMode:NSDefaultRunLoopMode - dequeue:YES]; - - if (event == nil) - break; - - [NSApp sendEvent:event]; - } - #ifdef COREAUDIO_ENABLED AudioDriverManager::add_driver(&audio_driver); #endif + + DisplayServerOSX::register_osx_driver(); } bool OS_OSX::_check_internal_feature_support(const String &p_feature) { diff --git a/platform/osx/platform_config.h b/platform/osx/platform_config.h index 046512ae84..e657aca955 100644 --- a/platform/osx/platform_config.h +++ b/platform/osx/platform_config.h @@ -30,6 +30,4 @@ #include <alloca.h> -#define GLES3_INCLUDE_H "thirdparty/glad/glad/glad.h" -#define GLES2_INCLUDE_H "thirdparty/glad/glad/glad.h" #define PTHREAD_RENAME_SELF diff --git a/platform/osx/platform_osx_builders.py b/platform/osx/platform_osx_builders.py index 81997f674b..953ed479db 100644 --- a/platform/osx/platform_osx_builders.py +++ b/platform/osx/platform_osx_builders.py @@ -8,14 +8,14 @@ from platform_methods import subprocess_main def make_debug_osx(target, source, env): - if (env["macports_clang"] != 'no'): + if env["macports_clang"] != "no": mpprefix = os.environ.get("MACPORTS_PREFIX", "/opt/local") mpclangver = env["macports_clang"] - os.system(mpprefix + '/libexec/llvm-' + mpclangver + '/bin/llvm-dsymutil {0} -o {0}.dSYM'.format(target[0])) + os.system(mpprefix + "/libexec/llvm-" + mpclangver + "/bin/llvm-dsymutil {0} -o {0}.dSYM".format(target[0])) else: - os.system('dsymutil {0} -o {0}.dSYM'.format(target[0])) - os.system('strip -u -r {0}'.format(target[0])) + os.system("dsymutil {0} -o {0}.dSYM".format(target[0])) + os.system("strip -u -r {0}".format(target[0])) -if __name__ == '__main__': +if __name__ == "__main__": subprocess_main(globals()) diff --git a/platform/osx/power_osx.cpp b/platform/osx/power_osx.cpp deleted file mode 100644 index 6d7667c5e8..0000000000 --- a/platform/osx/power_osx.cpp +++ /dev/null @@ -1,252 +0,0 @@ -/*************************************************************************/ -/* power_osx.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -/* -Adapted from corresponding SDL 2.0 code. -*/ - -/* - Simple DirectMedia Layer - Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org> - - This software is provided 'as-is', without any express or implied - warranty. In no event will the authors be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - 3. This notice may not be removed or altered from any source distribution. -*/ - -#include "power_osx.h" - -#include <CoreFoundation/CoreFoundation.h> -#include <IOKit/ps/IOPSKeys.h> -#include <IOKit/ps/IOPowerSources.h> - -// CODE CHUNK IMPORTED FROM SDL 2.0 - -/* CoreFoundation is so verbose... */ -#define STRMATCH(a, b) (CFStringCompare(a, b, 0) == kCFCompareEqualTo) -#define GETVAL(k, v) \ - CFDictionaryGetValueIfPresent(dict, CFSTR(k), (const void **)v) - -/* Note that AC power sources also include a laptop battery it is charging. */ -void PowerOSX::checkps(CFDictionaryRef dict, bool *have_ac, bool *have_battery, bool *charging) { - CFStringRef strval; /* don't CFRelease() this. */ - CFBooleanRef bval; - CFNumberRef numval; - bool charge = false; - bool choose = false; - bool is_ac = false; - int secs = -1; - int maxpct = -1; - int pct = -1; - - if ((GETVAL(kIOPSIsPresentKey, &bval)) && (bval == kCFBooleanFalse)) { - return; /* nothing to see here. */ - } - - if (!GETVAL(kIOPSPowerSourceStateKey, &strval)) { - return; - } - - if (STRMATCH(strval, CFSTR(kIOPSACPowerValue))) { - is_ac = *have_ac = true; - } else if (!STRMATCH(strval, CFSTR(kIOPSBatteryPowerValue))) { - return; /* not a battery? */ - } - - if ((GETVAL(kIOPSIsChargingKey, &bval)) && (bval == kCFBooleanTrue)) { - charge = true; - } - - if (GETVAL(kIOPSMaxCapacityKey, &numval)) { - SInt32 val = -1; - CFNumberGetValue(numval, kCFNumberSInt32Type, &val); - if (val > 0) { - *have_battery = true; - maxpct = (int)val; - } - } - - if (GETVAL(kIOPSMaxCapacityKey, &numval)) { - SInt32 val = -1; - CFNumberGetValue(numval, kCFNumberSInt32Type, &val); - if (val > 0) { - *have_battery = true; - maxpct = (int)val; - } - } - - if (GETVAL(kIOPSTimeToEmptyKey, &numval)) { - SInt32 val = -1; - CFNumberGetValue(numval, kCFNumberSInt32Type, &val); - - /* Mac OS X reports 0 minutes until empty if you're plugged in. :( */ - if ((val == 0) && (is_ac)) { - val = -1; /* !!! FIXME: calc from timeToFull and capacity? */ - } - - secs = (int)val; - if (secs > 0) { - secs *= 60; /* value is in minutes, so convert to seconds. */ - } - } - - if (GETVAL(kIOPSCurrentCapacityKey, &numval)) { - SInt32 val = -1; - CFNumberGetValue(numval, kCFNumberSInt32Type, &val); - pct = (int)val; - } - - if ((pct > 0) && (maxpct > 0)) { - pct = (int)((((double)pct) / ((double)maxpct)) * 100.0); - } - - if (pct > 100) { - pct = 100; - } - - /* - * We pick the battery that claims to have the most minutes left. - * (failing a report of minutes, we'll take the highest percent.) - */ - if ((secs < 0) && (nsecs_left < 0)) { - if ((pct < 0) && (percent_left < 0)) { - choose = true; /* at least we know there's a battery. */ - } - if (pct > percent_left) { - choose = true; - } - } else if (secs > nsecs_left) { - choose = true; - } - - if (choose) { - nsecs_left = secs; - percent_left = pct; - *charging = charge; - } -} - -#undef GETVAL -#undef STRMATCH - -// CODE CHUNK IMPORTED FROM SDL 2.0 -bool PowerOSX::GetPowerInfo_MacOSX() { - CFTypeRef blob = IOPSCopyPowerSourcesInfo(); - - nsecs_left = -1; - percent_left = -1; - power_state = OS::POWERSTATE_UNKNOWN; - - if (blob != NULL) { - CFArrayRef list = IOPSCopyPowerSourcesList(blob); - if (list != NULL) { - /* don't CFRelease() the list items, or dictionaries! */ - bool have_ac = false; - bool have_battery = false; - bool charging = false; - const CFIndex total = CFArrayGetCount(list); - CFIndex i; - for (i = 0; i < total; i++) { - CFTypeRef ps = (CFTypeRef)CFArrayGetValueAtIndex(list, i); - CFDictionaryRef dict = IOPSGetPowerSourceDescription(blob, ps); - if (dict != NULL) { - checkps(dict, &have_ac, &have_battery, &charging); - } - } - - if (!have_battery) { - power_state = OS::POWERSTATE_NO_BATTERY; - } else if (charging) { - power_state = OS::POWERSTATE_CHARGING; - } else if (have_ac) { - power_state = OS::POWERSTATE_CHARGED; - } else { - power_state = OS::POWERSTATE_ON_BATTERY; - } - - CFRelease(list); - } - CFRelease(blob); - } - - return true; /* always the definitive answer on Mac OS X. */ -} - -bool PowerOSX::UpdatePowerInfo() { - if (GetPowerInfo_MacOSX()) { - return true; - } - return false; -} - -OS::PowerState PowerOSX::get_power_state() { - if (UpdatePowerInfo()) { - return power_state; - } else { - return OS::POWERSTATE_UNKNOWN; - } -} - -int PowerOSX::get_power_seconds_left() { - if (UpdatePowerInfo()) { - return nsecs_left; - } else { - return -1; - } -} - -int PowerOSX::get_power_percent_left() { - if (UpdatePowerInfo()) { - return percent_left; - } else { - return -1; - } -} - -PowerOSX::PowerOSX() : - nsecs_left(-1), - percent_left(-1), - power_state(OS::POWERSTATE_UNKNOWN) { -} - -PowerOSX::~PowerOSX() { -} diff --git a/platform/osx/semaphore_osx.h b/platform/osx/vulkan_context_osx.h index 2348c8efa6..e996f176a9 100644 --- a/platform/osx/semaphore_osx.h +++ b/platform/osx/vulkan_context_osx.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* semaphore_osx.h */ +/* vulkan_context_osx.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,32 +28,20 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef SEMAPHORE_OSX_H -#define SEMAPHORE_OSX_H +#ifndef VULKAN_DEVICE_OSX_H +#define VULKAN_DEVICE_OSX_H -struct cgsem { - int pipefd[2]; -}; - -typedef struct cgsem cgsem_t; - -#include "core/os/semaphore.h" +#include "drivers/vulkan/vulkan_context.h" +#include <AppKit/AppKit.h> -class SemaphoreOSX : public Semaphore { - - mutable cgsem_t sem; - - static Semaphore *create_semaphore_osx(); +class VulkanContextOSX : public VulkanContext { + virtual const char *_get_platform_surface_extension() const; public: - virtual Error wait(); - virtual Error post(); - virtual int get() const; - - static void make_default(); - SemaphoreOSX(); + Error window_create(DisplayServer::WindowID p_window_id, id p_window, int p_width, int p_height); - ~SemaphoreOSX(); + VulkanContextOSX(); + ~VulkanContextOSX(); }; -#endif // SEMAPHORE_OSX_H +#endif // VULKAN_DEVICE_OSX_H diff --git a/platform/osx/semaphore_osx.cpp b/platform/osx/vulkan_context_osx.mm index e75f5103cc..ec8745ff01 100644 --- a/platform/osx/semaphore_osx.cpp +++ b/platform/osx/vulkan_context_osx.mm @@ -1,5 +1,5 @@ /*************************************************************************/ -/* semaphore_osx.cpp */ +/* vulkan_context_osx.mm */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,80 +28,28 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "semaphore_osx.h" +#include "vulkan_context_osx.h" +#include <vulkan/vulkan_macos.h> -#include <fcntl.h> -#include <unistd.h> - -void cgsem_init(cgsem_t *cgsem) { - int flags, fd, i; - - pipe(cgsem->pipefd); - - /* Make the pipes FD_CLOEXEC to allow them to close should we call - * execv on restart. */ - for (i = 0; i < 2; i++) { - fd = cgsem->pipefd[i]; - flags = fcntl(fd, F_GETFD, 0); - flags |= FD_CLOEXEC; - fcntl(fd, F_SETFD, flags); - } +const char *VulkanContextOSX::_get_platform_surface_extension() const { + return VK_MVK_MACOS_SURFACE_EXTENSION_NAME; } -void cgsem_post(cgsem_t *cgsem) { - const char buf = 1; - - write(cgsem->pipefd[1], &buf, 1); -} - -void cgsem_wait(cgsem_t *cgsem) { - char buf; - - read(cgsem->pipefd[0], &buf, 1); -} +Error VulkanContextOSX::window_create(DisplayServer::WindowID p_window_id, id p_window, int p_width, int p_height) { + VkMacOSSurfaceCreateInfoMVK createInfo; + createInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK; + createInfo.pNext = NULL; + createInfo.flags = 0; + createInfo.pView = p_window; -void cgsem_destroy(cgsem_t *cgsem) { - close(cgsem->pipefd[1]); - close(cgsem->pipefd[0]); + VkSurfaceKHR surface; + VkResult err = vkCreateMacOSSurfaceMVK(_get_instance(), &createInfo, NULL, &surface); + ERR_FAIL_COND_V(err, ERR_CANT_CREATE); + return _window_create(p_window_id, surface, p_width, p_height); } -#include "core/os/memory.h" - -#include <errno.h> - -Error SemaphoreOSX::wait() { - - cgsem_wait(&sem); - return OK; +VulkanContextOSX::VulkanContextOSX() { } -Error SemaphoreOSX::post() { - - cgsem_post(&sem); - - return OK; -} -int SemaphoreOSX::get() const { - - return 0; -} - -Semaphore *SemaphoreOSX::create_semaphore_osx() { - - return memnew(SemaphoreOSX); -} - -void SemaphoreOSX::make_default() { - - create_func = create_semaphore_osx; -} - -SemaphoreOSX::SemaphoreOSX() { - - cgsem_init(&sem); -} - -SemaphoreOSX::~SemaphoreOSX() { - - cgsem_destroy(&sem); +VulkanContextOSX::~VulkanContextOSX() { } |