Browse Source

[macOS] Cleanup and split Objective-C objects to the separate files

bruvzg 3 years ago
parent
commit
b84ef16aa7

+ 9 - 2
platform/osx/SCsub

@@ -6,14 +6,21 @@ from platform_methods import run_in_subprocess
 import platform_osx_builders
 
 files = [
-    "crash_handler_osx.mm",
     "os_osx.mm",
+    "godot_application.mm",
+    "godot_application_delegate.mm",
+    "crash_handler_osx.mm",
+    "osx_terminal_logger.mm",
     "display_server_osx.mm",
+    "godot_content_view.mm",
+    "godot_window_delegate.mm",
+    "godot_window.mm",
+    "key_mapping_osx.mm",
     "godot_main_osx.mm",
     "dir_access_osx.mm",
     "joypad_osx.cpp",
     "vulkan_context_osx.mm",
-    "gl_manager_osx.mm",
+    "gl_manager_osx_legacy.mm",
 ]
 
 prog = env.add_program("#bin/godot", files)

+ 5 - 3
platform/osx/detect.py

@@ -78,9 +78,9 @@ def configure(env):
         env["osxcross"] = True
 
     if env["arch"] == "arm64":
-        print("Building for macOS 10.15+, platform arm64.")
-        env.Append(CCFLAGS=["-arch", "arm64", "-mmacosx-version-min=10.15"])
-        env.Append(LINKFLAGS=["-arch", "arm64", "-mmacosx-version-min=10.15"])
+        print("Building for macOS 11.00+, platform arm64.")
+        env.Append(CCFLAGS=["-arch", "arm64", "-mmacosx-version-min=11.00"])
+        env.Append(LINKFLAGS=["-arch", "arm64", "-mmacosx-version-min=11.00"])
     else:
         print("Building for macOS 10.12+, platform x86_64.")
         env.Append(CCFLAGS=["-arch", "x86_64", "-mmacosx-version-min=10.12"])
@@ -190,6 +190,8 @@ def configure(env):
         env.Append(CCFLAGS=["-Wno-deprecated-declarations"])  # Disable deprecation warnings
         env.Append(LINKFLAGS=["-framework", "OpenGL"])
 
+    env.Append(LINKFLAGS=["-rpath", "@executable_path/../Frameworks", "-rpath", "@executable_path"])
+
     if env["vulkan"]:
         env.Append(CPPDEFINES=["VULKAN_ENABLED"])
         env.Append(LINKFLAGS=["-framework", "Metal", "-framework", "QuartzCore", "-framework", "IOSurface"])

+ 82 - 60
platform/osx/display_server_osx.h

@@ -37,13 +37,13 @@
 #include "servers/display_server.h"
 
 #if defined(GLES3_ENABLED)
-#include "gl_manager_osx.h"
-#endif
+#include "gl_manager_osx_legacy.h"
+#endif // GLES3_ENABLED
 
 #if defined(VULKAN_ENABLED)
 #include "drivers/vulkan/rendering_device_vulkan.h"
 #include "platform/osx/vulkan_context_osx.h"
-#endif
+#endif // VULKAN_ENABLED
 
 #include <AppKit/AppKit.h>
 #include <AppKit/NSCursor.h>
@@ -59,27 +59,8 @@ class DisplayServerOSX : public DisplayServer {
 	_THREAD_SAFE_CLASS_
 
 public:
-	void _send_event(NSEvent *p_event);
-	NSMenu *_get_dock_menu() const;
-	void _menu_callback(id p_sender);
-
-#if defined(GLES3_ENABLED)
-	GLManager_OSX *gl_manager = nullptr;
-#endif
-#if defined(VULKAN_ENABLED)
-	VulkanContextOSX *context_vulkan = nullptr;
-	RenderingDeviceVulkan *rendering_device_vulkan = nullptr;
-#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;
+		WindowID window_id = INVALID_WINDOW_ID;
 		unsigned int osx_state = false;
 		bool pressed = false;
 		bool echo = false;
@@ -89,20 +70,6 @@ public:
 		uint32_t unicode = 0;
 	};
 
-	struct WarpEvent {
-		NSTimeInterval timestamp;
-		NSPoint delta;
-	};
-
-	List<WarpEvent> warp_events;
-	NSTimeInterval last_warp = 0;
-	bool ignore_warp = false;
-
-	float display_max_scale = 1.f;
-
-	Vector<KeyEvent> key_event_buffer;
-	int key_event_pos;
-
 	struct WindowData {
 		id window_delegate;
 		id window_object;
@@ -116,8 +83,6 @@ public:
 		Size2i max_size;
 		Size2i size;
 
-		bool mouse_down_control = false;
-
 		bool im_active = false;
 		Size2i im_position;
 
@@ -140,48 +105,102 @@ public:
 		bool no_focus = false;
 	};
 
+private:
+#if defined(GLES3_ENABLED)
+	GLManager_OSX *gl_manager = nullptr;
+#endif
+#if defined(VULKAN_ENABLED)
+	VulkanContextOSX *context_vulkan = nullptr;
+	RenderingDeviceVulkan *rendering_device_vulkan = nullptr;
+#endif
+	String rendering_driver;
+
+	NSMenu *apple_menu = nullptr;
+	NSMenu *dock_menu = nullptr;
+	Map<String, NSMenu *> submenu;
+
+	struct WarpEvent {
+		NSTimeInterval timestamp;
+		NSPoint delta;
+	};
+	List<WarpEvent> warp_events;
+	NSTimeInterval last_warp = 0;
+	bool ignore_warp = false;
+
+	Vector<KeyEvent> key_event_buffer;
+	int key_event_pos = 0;
+
 	Point2i im_selection;
 	String im_text;
 
-	Map<WindowID, WindowData> windows;
+	CGEventSourceRef event_source;
+	MouseMode mouse_mode = MOUSE_MODE_VISIBLE;
+	MouseButton last_button_state = MouseButton::NONE;
 
-	WindowID last_focused_window = INVALID_WINDOW_ID;
+	bool drop_events = false;
+	bool in_dispatch_input_event = false;
+
+	struct LayoutInfo {
+		String name;
+		String code;
+	};
+	Vector<LayoutInfo> kbd_layouts;
+	int current_layout = 0;
+	bool keyboard_layout_dirty = true;
 
+	WindowID last_focused_window = INVALID_WINDOW_ID;
 	WindowID window_id_counter = MAIN_WINDOW_ID;
+	float display_max_scale = 1.f;
+	Point2i origin;
+	bool displays_arrangement_dirty = true;
 
-	WindowID _create_window(WindowMode p_mode, VSyncMode p_vsync_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);
+	CursorShape cursor_shape = CURSOR_ARROW;
+	NSCursor *cursors[CURSOR_MAX];
+	Map<CursorShape, Vector<Variant>> cursors_cache;
+
+	Map<WindowID, WindowData> windows;
+
+	const NSMenu *_get_menu_root(const String &p_menu_root) const;
+	NSMenu *_get_menu_root(const String &p_menu_root);
 
+	WindowID _create_window(WindowMode p_mode, VSyncMode p_vsync_mode, const Rect2i &p_rect);
+	void _update_window_style(WindowData p_wd);
 	void _set_window_per_pixel_transparency_enabled(bool p_enabled, WindowID p_window);
 
+	void _update_displays_arrangement();
 	Point2i _get_screens_origin() const;
 	Point2i _get_native_screen_position(int p_screen) const;
+	static void _displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplayChangeSummaryFlags flags, void *user_info);
 
+	static void _dispatch_input_events(const Ref<InputEvent> &p_event);
+	void _dispatch_input_event(const Ref<InputEvent> &p_event);
 	void _push_input(const Ref<InputEvent> &p_event);
 	void _process_key_events();
-	void _release_pressed_events();
+	void _update_keyboard_layouts();
+	static void _keyboard_layout_changed(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef user_info);
 
-	String rendering_driver;
+	static NSCursor *_cursor_from_selector(SEL p_selector, SEL p_fallback = nil);
 
-	CGEventSourceRef eventSource;
+public:
+	NSMenu *get_dock_menu() const;
+	void menu_callback(id p_sender);
 
-	CursorShape cursor_shape;
-	NSCursor *cursors[CURSOR_MAX];
-	Map<CursorShape, Vector<Variant>> cursors_cache;
+	bool has_window(WindowID p_window) const;
+	WindowData &get_window(WindowID p_window);
 
-	MouseMode mouse_mode;
-	Point2i last_mouse_pos;
-	MouseButton last_button_state = MouseButton::NONE;
+	void send_event(NSEvent *p_event);
+	void send_window_event(const WindowData &p_wd, WindowEvent p_event);
+	void release_pressed_events();
+	void get_key_modifier_state(unsigned int p_osx_state, Ref<InputEventWithModifiers> r_state) const;
+	void update_mouse_pos(WindowData &p_wd, NSPoint p_location_in_window);
+	void push_to_key_event_buffer(const KeyEvent &p_event);
+	void update_im_text(const Point2i &p_selection, const String &p_text);
+	void set_last_focused_window(WindowID p_window);
 
-	bool window_focused;
-	bool drop_events;
-	bool in_dispatch_input_event = false;
+	void window_update(WindowID p_window);
+	void window_destroy(WindowID p_window);
+	void window_resize(WindowID p_window, int p_width, int p_height);
 
-public:
 	virtual bool has_feature(Feature p_feature) const override;
 	virtual String get_name() const override;
 
@@ -215,8 +234,10 @@ public:
 	virtual void mouse_set_mode(MouseMode p_mode) override;
 	virtual MouseMode mouse_get_mode() const override;
 
+	bool update_mouse_wrap(WindowData &p_wd, NSPoint &r_delta, NSPoint &r_mpos, NSTimeInterval p_timestamp);
 	virtual void mouse_warp_to_position(const Point2i &p_to) override;
 	virtual Point2i mouse_get_position() const override;
+	void mouse_set_button_state(MouseButton p_state);
 	virtual MouseButton mouse_get_button_state() const override;
 
 	virtual void clipboard_set(const String &p_text) override;
@@ -295,6 +316,7 @@ public:
 	virtual Point2i ime_get_selection() const override;
 	virtual String ime_get_text() const override;
 
+	void cursor_update_shape();
 	virtual void cursor_set_shape(CursorShape p_shape) override;
 	virtual CursorShape cursor_get_shape() const override;
 	virtual void cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape = CURSOR_ARROW, const Vector2 &p_hotspot = Vector2()) override;

File diff suppressed because it is too large
+ 408 - 1242
platform/osx/display_server_osx.mm


+ 15 - 24
platform/osx/gl_manager_osx.h → platform/osx/gl_manager_osx_legacy.h

@@ -1,5 +1,5 @@
 /*************************************************************************/
-/*  gl_manager_osx.h                                                     */
+/*  gl_manager_osx_legacy.h                                              */
 /*************************************************************************/
 /*                       This file is part of:                           */
 /*                           GODOT ENGINE                                */
@@ -28,8 +28,8 @@
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
 /*************************************************************************/
 
-#ifndef GL_MANAGER_OSX_H
-#define GL_MANAGER_OSX_H
+#ifndef GL_MANAGER_OSX_LEGACY_H
+#define GL_MANAGER_OSX_LEGACY_H
 
 #if defined(OSX_ENABLED) && defined(GLES3_ENABLED)
 
@@ -50,29 +50,21 @@ public:
 
 private:
 	struct GLWindow {
-		GLWindow() { in_use = false; }
-		bool in_use;
+		int width = 0;
+		int height = 0;
 
-		DisplayServer::WindowID window_id;
-		int width;
-		int height;
-
-		id window_view;
-		NSOpenGLContext *context;
+		id window_view = nullptr;
+		NSOpenGLContext *context = nullptr;
 	};
 
-	LocalVector<GLWindow> _windows;
-
-	NSOpenGLContext *_shared_context = nullptr;
-	GLWindow *_current_window;
+	Map<DisplayServer::WindowID, GLWindow> windows;
 
-	Error _create_context(GLWindow &win);
-	void _internal_set_current_window(GLWindow *p_win);
+	NSOpenGLContext *shared_context = nullptr;
+	DisplayServer::WindowID current_window = DisplayServer::INVALID_WINDOW_ID;
 
-	GLWindow &get_window(unsigned int id) { return _windows[id]; }
-	const GLWindow &get_window(unsigned int id) const { return _windows[id]; }
+	Error create_context(GLWindow &win);
 
-	bool use_vsync;
+	bool use_vsync = false;
 	ContextType context_type;
 
 public:
@@ -80,7 +72,6 @@ public:
 	void window_destroy(DisplayServer::WindowID p_window_id);
 	void window_resize(DisplayServer::WindowID p_window_id, int p_width, int p_height);
 
-	// get directly from the cached GLWindow
 	int window_get_width(DisplayServer::WindowID p_window_id = 0);
 	int window_get_height(DisplayServer::WindowID p_window_id = 0);
 
@@ -91,6 +82,7 @@ public:
 	void window_make_current(DisplayServer::WindowID p_window_id);
 
 	void window_update(DisplayServer::WindowID p_window_id);
+	void window_set_per_pixel_transparency_enabled(DisplayServer::WindowID p_window_id, bool p_enabled);
 
 	Error initialize();
 
@@ -101,6 +93,5 @@ public:
 	~GLManager_OSX();
 };
 
-#endif // defined(OSX_ENABLED) && defined(GLES3_ENABLED)
-
-#endif // GL_MANAGER_OSX_H
+#endif // OSX_ENABLED && GLES3_ENABLED
+#endif // GL_MANAGER_OSX_LEGACY_H

+ 58 - 63
platform/osx/gl_manager_osx.mm → platform/osx/gl_manager_osx_legacy.mm

@@ -1,5 +1,5 @@
 /*************************************************************************/
-/*  gl_manager_osx.mm                                                    */
+/*  gl_manager_osx_legacy.mm                                             */
 /*************************************************************************/
 /*                       This file is part of:                           */
 /*                           GODOT ENGINE                                */
@@ -28,7 +28,7 @@
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
 /*************************************************************************/
 
-#include "gl_manager_osx.h"
+#include "gl_manager_osx_legacy.h"
 
 #ifdef OSX_ENABLED
 #ifdef GLES3_ENABLED
@@ -36,7 +36,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 
-Error GLManager_OSX::_create_context(GLWindow &win) {
+Error GLManager_OSX::create_context(GLWindow &win) {
 	NSOpenGLPixelFormatAttribute attributes[] = {
 		NSOpenGLPFADoubleBuffer,
 		NSOpenGLPFAClosestPolicy,
@@ -50,10 +50,10 @@ Error GLManager_OSX::_create_context(GLWindow &win) {
 	NSOpenGLPixelFormat *pixel_format = [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes];
 	ERR_FAIL_COND_V(pixel_format == nil, ERR_CANT_CREATE);
 
-	win.context = [[NSOpenGLContext alloc] initWithFormat:pixel_format shareContext:_shared_context];
+	win.context = [[NSOpenGLContext alloc] initWithFormat:pixel_format shareContext:shared_context];
 	ERR_FAIL_COND_V(win.context == nil, ERR_CANT_CREATE);
-	if (_shared_context == nullptr) {
-		_shared_context = win.context;
+	if (shared_context == nullptr) {
+		shared_context = win.context;
 	}
 
 	[win.context setView:win.window_view];
@@ -63,40 +63,27 @@ Error GLManager_OSX::_create_context(GLWindow &win) {
 }
 
 Error GLManager_OSX::window_create(DisplayServer::WindowID p_window_id, id p_view, int p_width, int p_height) {
-	if (p_window_id >= (int)_windows.size()) {
-		_windows.resize(p_window_id + 1);
-	}
-
-	GLWindow &win = _windows[p_window_id];
-	win.in_use = true;
-	win.window_id = p_window_id;
+	GLWindow win;
 	win.width = p_width;
 	win.height = p_height;
 	win.window_view = p_view;
 
-	if (_create_context(win) != OK) {
-		_windows.remove_at(_windows.size() - 1);
+	if (create_context(win) != OK) {
 		return FAILED;
 	}
 
-	window_make_current(_windows.size() - 1);
+	windows[p_window_id] = win;
+	window_make_current(p_window_id);
 
 	return OK;
 }
 
-void GLManager_OSX::_internal_set_current_window(GLWindow *p_win) {
-	_current_window = p_win;
-}
-
 void GLManager_OSX::window_resize(DisplayServer::WindowID p_window_id, int p_width, int p_height) {
-	if (p_window_id == -1) {
+	if (!windows.has(p_window_id)) {
 		return;
 	}
 
-	GLWindow &win = _windows[p_window_id];
-	if (!win.in_use) {
-		return;
-	}
+	GLWindow &win = windows[p_window_id];
 
 	win.width = p_width;
 	win.height = p_height;
@@ -116,24 +103,37 @@ void GLManager_OSX::window_resize(DisplayServer::WindowID p_window_id, int p_wid
 }
 
 int GLManager_OSX::window_get_width(DisplayServer::WindowID p_window_id) {
-	return get_window(p_window_id).width;
+	if (!windows.has(p_window_id)) {
+		return 0;
+	}
+
+	GLWindow &win = windows[p_window_id];
+	return win.width;
 }
 
 int GLManager_OSX::window_get_height(DisplayServer::WindowID p_window_id) {
-	return get_window(p_window_id).height;
+	if (!windows.has(p_window_id)) {
+		return 0;
+	}
+
+	GLWindow &win = windows[p_window_id];
+	return win.height;
 }
 
 void GLManager_OSX::window_destroy(DisplayServer::WindowID p_window_id) {
-	GLWindow &win = get_window(p_window_id);
-	win.in_use = false;
+	if (!windows.has(p_window_id)) {
+		return;
+	}
 
-	if (_current_window == &win) {
-		_current_window = nullptr;
+	if (current_window == p_window_id) {
+		current_window = DisplayServer::INVALID_WINDOW_ID;
 	}
+
+	windows.erase(p_window_id);
 }
 
 void GLManager_OSX::release_current() {
-	if (!_current_window) {
+	if (current_window == DisplayServer::INVALID_WINDOW_ID) {
 		return;
 	}
 
@@ -141,63 +141,59 @@ void GLManager_OSX::release_current() {
 }
 
 void GLManager_OSX::window_make_current(DisplayServer::WindowID p_window_id) {
-	if (p_window_id == -1) {
-		return;
-	}
-
-	GLWindow &win = _windows[p_window_id];
-	if (!win.in_use) {
+	if (current_window == p_window_id) {
 		return;
 	}
-
-	if (&win == _current_window) {
+	if (!windows.has(p_window_id)) {
 		return;
 	}
 
+	GLWindow &win = windows[p_window_id];
 	[win.context makeCurrentContext];
 
-	_internal_set_current_window(&win);
+	current_window = p_window_id;
 }
 
 void GLManager_OSX::make_current() {
-	if (!_current_window) {
+	if (current_window == DisplayServer::INVALID_WINDOW_ID) {
 		return;
 	}
-	if (!_current_window->in_use) {
-		WARN_PRINT("current window not in use!");
+	if (!windows.has(current_window)) {
 		return;
 	}
-	[_current_window->context makeCurrentContext];
+
+	GLWindow &win = windows[current_window];
+	[win.context makeCurrentContext];
 }
 
 void GLManager_OSX::swap_buffers() {
-	// NO NEED TO CALL SWAP BUFFERS for each window...
-	// see https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glXSwapBuffers.xml
-
-	if (!_current_window) {
-		return;
-	}
-	if (!_current_window->in_use) {
-		WARN_PRINT("current window not in use!");
-		return;
+	for (Map<DisplayServer::WindowID, GLWindow>::Element *E = windows.front(); E; E = E->next()) {
+		[E->get().context flushBuffer];
 	}
-	[_current_window->context flushBuffer];
 }
 
 void GLManager_OSX::window_update(DisplayServer::WindowID p_window_id) {
-	if (p_window_id == -1) {
+	if (!windows.has(p_window_id)) {
 		return;
 	}
 
-	GLWindow &win = _windows[p_window_id];
-	if (!win.in_use) {
-		return;
-	}
+	GLWindow &win = windows[p_window_id];
+	[win.context update];
+}
 
-	if (&win == _current_window) {
+void GLManager_OSX::window_set_per_pixel_transparency_enabled(DisplayServer::WindowID p_window_id, bool p_enabled) {
+	if (!windows.has(p_window_id)) {
 		return;
 	}
 
+	GLWindow &win = windows[p_window_id];
+	if (p_enabled) {
+		GLint opacity = 0;
+		[win.context setValues:&opacity forParameter:NSOpenGLContextParameterSurfaceOpacity];
+	} else {
+		GLint opacity = 1;
+		[win.context setValues:&opacity forParameter:NSOpenGLContextParameterSurfaceOpacity];
+	}
 	[win.context update];
 }
 
@@ -207,6 +203,7 @@ Error GLManager_OSX::initialize() {
 
 void GLManager_OSX::set_use_vsync(bool p_use) {
 	use_vsync = p_use;
+
 	CGLContextObj ctx = CGLGetCurrentContext();
 	if (ctx) {
 		GLint swapInterval = p_use ? 1 : 0;
@@ -221,8 +218,6 @@ bool GLManager_OSX::is_using_vsync() const {
 
 GLManager_OSX::GLManager_OSX(ContextType p_context_type) {
 	context_type = p_context_type;
-	use_vsync = false;
-	_current_window = nullptr;
 }
 
 GLManager_OSX::~GLManager_OSX() {

+ 42 - 0
platform/osx/godot_application.h

@@ -0,0 +1,42 @@
+/*************************************************************************/
+/*  godot_application.h                                                  */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#ifndef GODOT_APPLICATION_H
+#define GODOT_APPLICATION_H
+
+#include "core/os/os.h"
+
+#import <AppKit/AppKit.h>
+#import <Foundation/Foundation.h>
+
+@interface GodotApplication : NSApplication
+@end
+
+#endif // GODOT_APPLICATION_H

+ 53 - 0
platform/osx/godot_application.mm

@@ -0,0 +1,53 @@
+/*************************************************************************/
+/*  godot_application.mm                                                 */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#include "godot_application.h"
+
+#include "display_server_osx.h"
+
+@implementation GodotApplication
+
+- (void)sendEvent:(NSEvent *)event {
+	DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+	if (ds) {
+		ds->send_event(event);
+	}
+
+	// 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

+ 45 - 0
platform/osx/godot_application_delegate.h

@@ -0,0 +1,45 @@
+/*************************************************************************/
+/*  godot_application_delegate.h                                         */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#ifndef GODOT_APPLICATION_DELEGATE_H
+#define GODOT_APPLICATION_DELEGATE_H
+
+#include "core/os/os.h"
+
+#import <AppKit/AppKit.h>
+#import <Foundation/Foundation.h>
+
+@interface GodotApplicationDelegate : NSObject
+- (void)forceUnbundledWindowActivationHackStep1;
+- (void)forceUnbundledWindowActivationHackStep2;
+- (void)forceUnbundledWindowActivationHackStep3;
+@end
+
+#endif // GODOT_APPLICATION_DELEGATE_H

+ 132 - 0
platform/osx/godot_application_delegate.mm

@@ -0,0 +1,132 @@
+/*************************************************************************/
+/*  godot_application_delegate.mm                                        */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#include "godot_application_delegate.h"
+
+#include "display_server_osx.h"
+#include "os_osx.h"
+
+@implementation GodotApplicationDelegate
+
+- (void)forceUnbundledWindowActivationHackStep1 {
+	// Step 1: Switch focus to macOS SystemUIServer process.
+	// Required to perform step 2, TransformProcessType will fail if app is already the in focus.
+	for (NSRunningApplication *app in [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.systemuiserver"]) {
+		[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 || isatty(STDOUT_FILENO) || isatty(STDIN_FILENO) || isatty(STDERR_FILENO)) {
+		// If the executable is started from terminal or is not bundled, macOS WindowServer won't register and activate app window correctly (menu and title bar are grayed out and input ignored).
+		[self performSelector:@selector(forceUnbundledWindowActivationHackStep1) withObject:nil afterDelay:0.02];
+	}
+}
+
+- (void)applicationDidResignActive:(NSNotification *)notification {
+	if (OS::get_singleton()->get_main_loop()) {
+		OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT);
+	}
+}
+
+- (void)applicationDidBecomeActive:(NSNotification *)notification {
+	if (OS::get_singleton()->get_main_loop()) {
+		OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN);
+	}
+}
+
+- (void)globalMenuCallback:(id)sender {
+	DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+	if (ds) {
+		return ds->menu_callback(sender);
+	}
+}
+
+- (NSMenu *)applicationDockMenu:(NSApplication *)sender {
+	DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+	if (ds) {
+		return ds->get_dock_menu();
+	} else {
+		return nullptr;
+	}
+}
+
+- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename {
+	// Note: may be called called before main loop init!
+	OS_OSX *os = (OS_OSX *)OS::get_singleton();
+	if (os) {
+		os->set_open_with_filename(String::utf8([filename UTF8String]));
+	}
+
+#ifdef TOOLS_ENABLED
+	// Open new instance.
+	if (os && os->get_main_loop()) {
+		List<String> args;
+		args.push_back(os->get_open_with_filename());
+		String exec = os->get_executable_path();
+		os->create_process(exec, args);
+	}
+#endif
+	return YES;
+}
+
+- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
+	DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+	if (ds) {
+		ds->send_window_event(ds->get_window(DisplayServerOSX::MAIN_WINDOW_ID), DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST);
+	}
+	return NSTerminateCancel;
+}
+
+- (void)showAbout:(id)sender {
+	OS_OSX *os = (OS_OSX *)OS::get_singleton();
+	if (os && os->get_main_loop()) {
+		os->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_ABOUT);
+	}
+}
+
+@end

+ 65 - 0
platform/osx/godot_content_view.h

@@ -0,0 +1,65 @@
+/*************************************************************************/
+/*  godot_content_view.h                                                 */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#ifndef GODOT_CONTENT_VIEW_H
+#define GODOT_CONTENT_VIEW_H
+
+#include "servers/display_server.h"
+
+#import <AppKit/AppKit.h>
+#import <Foundation/Foundation.h>
+
+#if defined(GLES3_ENABLED)
+#import <AppKit/NSOpenGLView.h>
+#define RootView NSOpenGLView
+#else
+#define RootView NSView
+#endif
+
+#import <QuartzCore/CAMetalLayer.h>
+
+@interface GodotContentView : RootView <NSTextInputClient> {
+	DisplayServer::WindowID window_id;
+	NSTrackingArea *tracking_area;
+	NSMutableAttributedString *marked_text;
+	bool ime_input_event_in_progress;
+	bool mouse_down_control;
+	bool ignore_momentum_scroll;
+}
+
+- (void)processScrollEvent:(NSEvent *)event button:(MouseButton)button factor:(double)factor;
+- (void)processPanEvent:(NSEvent *)event dx:(double)dx dy:(double)dy;
+- (void)processMouseEvent:(NSEvent *)event index:(MouseButton)index mask:(MouseButton)mask pressed:(bool)pressed;
+- (void)setWindowID:(DisplayServer::WindowID)wid;
+- (void)cancelComposition;
+
+@end
+
+#endif // GODOT_CONTENT_VIEW_H

+ 760 - 0
platform/osx/godot_content_view.mm

@@ -0,0 +1,760 @@
+/*************************************************************************/
+/*  godot_content_view.mm                                                */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#include "godot_content_view.h"
+
+#include "display_server_osx.h"
+#include "key_mapping_osx.h"
+
+@implementation GodotContentView
+
+- (id)init {
+	self = [super init];
+	window_id = DisplayServer::INVALID_WINDOW_ID;
+	tracking_area = nil;
+	ime_input_event_in_progress = false;
+	mouse_down_control = false;
+	ignore_momentum_scroll = false;
+	[self updateTrackingAreas];
+
+	if (@available(macOS 10.13, *)) {
+		[self registerForDraggedTypes:[NSArray arrayWithObject:NSPasteboardTypeFileURL]];
+#if !defined(__aarch64__) // Do not build deprectead 10.13 code on ARM.
+	} else {
+		[self registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]];
+#endif
+	}
+	marked_text = [[NSMutableAttributedString alloc] init];
+	return self;
+}
+
+- (void)setWindowID:(DisplayServerOSX::WindowID)wid {
+	window_id = wid;
+}
+
+// MARK: Backing Layer
+
+- (CALayer *)makeBackingLayer {
+	return [[CAMetalLayer class] layer];
+}
+
+- (void)updateLayer {
+	DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+	ds->window_update(window_id);
+	[super updateLayer];
+}
+
+- (BOOL)wantsUpdateLayer {
+	return YES;
+}
+
+- (BOOL)isOpaque {
+	return YES;
+}
+
+// MARK: IME
+
+- (BOOL)hasMarkedText {
+	return (marked_text.length > 0);
+}
+
+- (NSRange)markedRange {
+	return NSMakeRange(0, marked_text.length);
+}
+
+- (NSRange)selectedRange {
+	static const NSRange kEmptyRange = { NSNotFound, 0 };
+	return kEmptyRange;
+}
+
+- (void)setMarkedText:(id)aString selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange {
+	if ([aString isKindOfClass:[NSAttributedString class]]) {
+		marked_text = [[NSMutableAttributedString alloc] initWithAttributedString:aString];
+	} else {
+		marked_text = [[NSMutableAttributedString alloc] initWithString:aString];
+	}
+	if (marked_text.length == 0) {
+		[self unmarkText];
+		return;
+	}
+
+	DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+	if (!ds || !ds->has_window(window_id)) {
+		return;
+	}
+
+	DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+	if (wd.im_active) {
+		ime_input_event_in_progress = true;
+		ds->update_im_text(Point2i(selectedRange.location, selectedRange.length), String::utf8([[marked_text mutableString] UTF8String]));
+	}
+}
+
+- (void)unmarkText {
+	ime_input_event_in_progress = false;
+	[[marked_text mutableString] setString:@""];
+
+	DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+	if (!ds || !ds->has_window(window_id)) {
+		return;
+	}
+
+	DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+	if (wd.im_active) {
+		ds->update_im_text(Point2i(), String());
+	}
+}
+
+- (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 {
+	DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+	if (!ds || !ds->has_window(window_id)) {
+		return NSMakeRect(0, 0, 0, 0);
+	}
+
+	DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+	const NSRect content_rect = [wd.window_view frame];
+	const float scale = ds->screen_get_max_scale();
+	NSRect point_in_window_rect = NSMakeRect(wd.im_position.x / scale, content_rect.size.height - (wd.im_position.y / scale) - 1, 0, 0);
+	NSPoint point_on_screen = [wd.window_object convertRectToScreen:point_in_window_rect].origin;
+
+	return NSMakeRect(point_on_screen.x, point_on_screen.y, 0, 0);
+}
+
+- (void)cancelComposition {
+	[self unmarkText];
+	[[NSTextInputContext 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;
+	}
+
+	NSCharacterSet *ctrl_chars = [NSCharacterSet controlCharacterSet];
+	NSCharacterSet *wsnl_chars = [NSCharacterSet whitespaceAndNewlineCharacterSet];
+	if ([characters rangeOfCharacterFromSet:ctrl_chars].length && [characters rangeOfCharacterFromSet:wsnl_chars].length == 0) {
+		[[NSTextInputContext currentInputContext] discardMarkedText];
+		[self cancelComposition];
+		return;
+	}
+
+	DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+	if (!ds || !ds->has_window(window_id)) {
+		[self cancelComposition];
+		return;
+	}
+
+	Char16String text;
+	text.resize([characters length] + 1);
+	[characters getCharacters:(unichar *)text.ptrw() range:NSMakeRange(0, [characters length])];
+
+	String u32text;
+	u32text.parse_utf16(text.ptr(), text.length());
+
+	for (int i = 0; i < u32text.length(); i++) {
+		const char32_t codepoint = u32text[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 = Key::NONE;
+		ke.physical_keycode = Key::NONE;
+		ke.unicode = codepoint;
+
+		ds->push_to_key_event_buffer(ke);
+	}
+	[self cancelComposition];
+}
+
+// MARK: Drag and drop
+
+- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender {
+	return NSDragOperationCopy;
+}
+
+- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender {
+	return NSDragOperationCopy;
+}
+
+- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender {
+	DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+	if (!ds || !ds->has_window(window_id)) {
+		return NO;
+	}
+
+	DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+	if (!wd.drop_files_callback.is_null()) {
+		Vector<String> files;
+		NSPasteboard *pboard = [sender draggingPasteboard];
+
+		if (@available(macOS 10.13, *)) {
+			NSArray *items = pboard.pasteboardItems;
+			for (NSPasteboardItem *item in items) {
+				NSString *url = [item stringForType:NSPasteboardTypeFileURL];
+				NSString *file = [NSURL URLWithString:url].path;
+				files.push_back(String::utf8([file UTF8String]));
+			}
+#if !defined(__aarch64__) // Do not build deprectead 10.13 code on ARM.
+		} else {
+			NSArray *filenames = [pboard propertyListForType:NSFilenamesPboardType];
+			for (NSString *file in filenames) {
+				files.push_back(String::utf8([file UTF8String]));
+			}
+#endif
+		}
+
+		Variant v = files;
+		Variant *vp = &v;
+		Variant ret;
+		Callable::CallError ce;
+		wd.drop_files_callback.call((const Variant **)&vp, 1, ret, ce);
+	}
+
+	return NO;
+}
+
+// MARK: Focus
+
+- (BOOL)canBecomeKeyView {
+	DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+	if (!ds || !ds->has_window(window_id)) {
+		return YES;
+	}
+
+	DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+	return !wd.no_focus;
+}
+
+- (BOOL)acceptsFirstResponder {
+	return YES;
+}
+
+// MARK: Mouse
+
+- (void)cursorUpdate:(NSEvent *)event {
+	DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+	if (!ds) {
+		return;
+	}
+
+	ds->cursor_update_shape();
+}
+
+- (void)processMouseEvent:(NSEvent *)event index:(MouseButton)index mask:(MouseButton)mask pressed:(bool)pressed {
+	DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+	if (!ds || !ds->has_window(window_id)) {
+		return;
+	}
+
+	DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+	MouseButton last_button_state = ds->mouse_get_button_state();
+
+	if (pressed) {
+		last_button_state |= mask;
+	} else {
+		last_button_state &= (MouseButton)~mask;
+	}
+	ds->mouse_set_button_state(last_button_state);
+
+	Ref<InputEventMouseButton> mb;
+	mb.instantiate();
+	mb->set_window_id(window_id);
+	ds->update_mouse_pos(wd, [event locationInWindow]);
+	ds->get_key_modifier_state([event modifierFlags], mb);
+	mb->set_button_index(index);
+	mb->set_pressed(pressed);
+	mb->set_position(wd.mouse_pos);
+	mb->set_global_position(wd.mouse_pos);
+	mb->set_button_mask(last_button_state);
+	if (index == MouseButton::LEFT && pressed) {
+		mb->set_double_click([event clickCount] == 2);
+	}
+
+	Input::get_singleton()->parse_input_event(mb);
+}
+
+- (void)mouseDown:(NSEvent *)event {
+	if (([event modifierFlags] & NSEventModifierFlagControl)) {
+		mouse_down_control = true;
+		[self processMouseEvent:event index:MouseButton::RIGHT mask:MouseButton::MASK_RIGHT pressed:true];
+	} else {
+		mouse_down_control = false;
+		[self processMouseEvent:event index:MouseButton::LEFT mask:MouseButton::MASK_LEFT pressed:true];
+	}
+}
+
+- (void)mouseDragged:(NSEvent *)event {
+	[self mouseMoved:event];
+}
+
+- (void)mouseUp:(NSEvent *)event {
+	if (mouse_down_control) {
+		[self processMouseEvent:event index:MouseButton::RIGHT mask:MouseButton::MASK_RIGHT pressed:false];
+	} else {
+		[self processMouseEvent:event index:MouseButton::LEFT mask:MouseButton::MASK_LEFT pressed:false];
+	}
+}
+
+- (void)mouseMoved:(NSEvent *)event {
+	DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+	if (!ds || !ds->has_window(window_id)) {
+		return;
+	}
+
+	DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+
+	NSPoint delta = NSMakePoint([event deltaX], [event deltaY]);
+	NSPoint mpos = [event locationInWindow];
+
+	if (ds->update_mouse_wrap(wd, delta, mpos, [event timestamp])) {
+		return;
+	}
+
+	Ref<InputEventMouseMotion> mm;
+	mm.instantiate();
+
+	mm->set_window_id(window_id);
+	mm->set_button_mask(ds->mouse_get_button_state());
+	ds->update_mouse_pos(wd, mpos);
+	mm->set_position(wd.mouse_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(wd.mouse_pos);
+	mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity());
+	const Vector2i relativeMotion = Vector2i(delta.x, delta.y) * ds->screen_get_max_scale();
+	mm->set_relative(relativeMotion);
+	ds->get_key_modifier_state([event modifierFlags], mm);
+
+	Input::get_singleton()->parse_input_event(mm);
+}
+
+- (void)rightMouseDown:(NSEvent *)event {
+	[self processMouseEvent:event index:MouseButton::RIGHT mask:MouseButton::MASK_RIGHT pressed:true];
+}
+
+- (void)rightMouseDragged:(NSEvent *)event {
+	[self mouseMoved:event];
+}
+
+- (void)rightMouseUp:(NSEvent *)event {
+	[self processMouseEvent:event index:MouseButton::RIGHT mask:MouseButton::MASK_RIGHT pressed:false];
+}
+
+- (void)otherMouseDown:(NSEvent *)event {
+	if ((int)[event buttonNumber] == 2) {
+		[self processMouseEvent:event index:MouseButton::MIDDLE mask:MouseButton::MASK_MIDDLE pressed:true];
+	} else if ((int)[event buttonNumber] == 3) {
+		[self processMouseEvent:event index:MouseButton::MB_XBUTTON1 mask:MouseButton::MASK_XBUTTON1 pressed:true];
+	} else if ((int)[event buttonNumber] == 4) {
+		[self processMouseEvent:event index:MouseButton::MB_XBUTTON2 mask:MouseButton::MASK_XBUTTON2 pressed:true];
+	} else {
+		return;
+	}
+}
+
+- (void)otherMouseDragged:(NSEvent *)event {
+	[self mouseMoved:event];
+}
+
+- (void)otherMouseUp:(NSEvent *)event {
+	if ((int)[event buttonNumber] == 2) {
+		[self processMouseEvent:event index:MouseButton::MIDDLE mask:MouseButton::MASK_MIDDLE pressed:false];
+	} else if ((int)[event buttonNumber] == 3) {
+		[self processMouseEvent:event index:MouseButton::MB_XBUTTON1 mask:MouseButton::MASK_XBUTTON1 pressed:false];
+	} else if ((int)[event buttonNumber] == 4) {
+		[self processMouseEvent:event index:MouseButton::MB_XBUTTON2 mask:MouseButton::MASK_XBUTTON2 pressed:false];
+	} else {
+		return;
+	}
+}
+
+- (void)mouseExited:(NSEvent *)event {
+	DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+	if (!ds || !ds->has_window(window_id)) {
+		return;
+	}
+
+	DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+	if (ds->mouse_get_mode() != DisplayServer::MOUSE_MODE_CAPTURED) {
+		ds->send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_MOUSE_EXIT);
+	}
+}
+
+- (void)mouseEntered:(NSEvent *)event {
+	DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+	if (!ds || !ds->has_window(window_id)) {
+		return;
+	}
+
+	DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+	if (ds->mouse_get_mode() != DisplayServer::MOUSE_MODE_CAPTURED) {
+		ds->send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_MOUSE_ENTER);
+	}
+
+	ds->cursor_update_shape();
+}
+
+- (void)magnifyWithEvent:(NSEvent *)event {
+	DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+	if (!ds || !ds->has_window(window_id)) {
+		return;
+	}
+
+	DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+
+	Ref<InputEventMagnifyGesture> ev;
+	ev.instantiate();
+	ev->set_window_id(window_id);
+	ds->get_key_modifier_state([event modifierFlags], ev);
+	ds->update_mouse_pos(wd, [event locationInWindow]);
+	ev->set_position(wd.mouse_pos);
+	ev->set_factor([event magnification] + 1.0);
+
+	Input::get_singleton()->parse_input_event(ev);
+}
+
+- (void)updateTrackingAreas {
+	if (tracking_area != nil) {
+		[self removeTrackingArea:tracking_area];
+	}
+
+	NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | NSTrackingActiveInKeyWindow | NSTrackingCursorUpdate | NSTrackingInVisibleRect;
+	tracking_area = [[NSTrackingArea alloc] initWithRect:[self bounds] options:options owner:self userInfo:nil];
+
+	[self addTrackingArea:tracking_area];
+	[super updateTrackingAreas];
+}
+
+// MARK: Keyboard
+
+- (void)keyDown:(NSEvent *)event {
+	DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+	if (!ds || !ds->has_window(window_id)) {
+		return;
+	}
+
+	DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+
+	ignore_momentum_scroll = true;
+
+	// Ignore all input if IME input is in progress.
+	if (!ime_input_event_in_progress) {
+		NSString *characters = [event characters];
+		NSUInteger length = [characters length];
+
+		if (!wd.im_active && length > 0 && keycode_has_unicode(KeyMappingOSX::remap_key([event keyCode], [event modifierFlags]))) {
+			// Fallback unicode character handler used if IME is not active.
+			Char16String text;
+			text.resize([characters length] + 1);
+			[characters getCharacters:(unichar *)text.ptrw() range:NSMakeRange(0, [characters length])];
+
+			String u32text;
+			u32text.parse_utf16(text.ptr(), text.length());
+
+			for (int i = 0; i < u32text.length(); i++) {
+				const char32_t codepoint = u32text[i];
+
+				DisplayServerOSX::KeyEvent ke;
+
+				ke.window_id = window_id;
+				ke.osx_state = [event modifierFlags];
+				ke.pressed = true;
+				ke.echo = [event isARepeat];
+				ke.keycode = KeyMappingOSX::remap_key([event keyCode], [event modifierFlags]);
+				ke.physical_keycode = KeyMappingOSX::translate_key([event keyCode]);
+				ke.raw = true;
+				ke.unicode = codepoint;
+
+				ds->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 = KeyMappingOSX::remap_key([event keyCode], [event modifierFlags]);
+			ke.physical_keycode = KeyMappingOSX::translate_key([event keyCode]);
+			ke.raw = false;
+			ke.unicode = 0;
+
+			ds->push_to_key_event_buffer(ke);
+		}
+	}
+
+	// Pass events to IME handler
+	if (wd.im_active) {
+		[self interpretKeyEvents:[NSArray arrayWithObject:event]];
+	}
+}
+
+- (void)flagsChanged:(NSEvent *)event {
+	DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+	if (!ds || !ds->has_window(window_id)) {
+		return;
+	}
+	ignore_momentum_scroll = true;
+
+	// Ignore all input if IME input is in progress
+	if (!ime_input_event_in_progress) {
+		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 = KeyMappingOSX::remap_key(key, mod);
+		ke.physical_keycode = KeyMappingOSX::translate_key(key);
+		ke.unicode = 0;
+
+		ds->push_to_key_event_buffer(ke);
+	}
+}
+
+- (void)keyUp:(NSEvent *)event {
+	DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+	if (!ds || !ds->has_window(window_id)) {
+		return;
+	}
+
+	DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+
+	// Ignore all input if IME input is in progress.
+	if (!ime_input_event_in_progress) {
+		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(KeyMappingOSX::remap_key([event keyCode], [event modifierFlags]))) {
+			Char16String text;
+			text.resize([characters length] + 1);
+			[characters getCharacters:(unichar *)text.ptrw() range:NSMakeRange(0, [characters length])];
+
+			String u32text;
+			u32text.parse_utf16(text.ptr(), text.length());
+
+			for (int i = 0; i < u32text.length(); i++) {
+				const char32_t codepoint = u32text[i];
+				DisplayServerOSX::KeyEvent ke;
+
+				ke.window_id = window_id;
+				ke.osx_state = [event modifierFlags];
+				ke.pressed = false;
+				ke.echo = [event isARepeat];
+				ke.keycode = KeyMappingOSX::remap_key([event keyCode], [event modifierFlags]);
+				ke.physical_keycode = KeyMappingOSX::translate_key([event keyCode]);
+				ke.raw = true;
+				ke.unicode = codepoint;
+
+				ds->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 = KeyMappingOSX::remap_key([event keyCode], [event modifierFlags]);
+			ke.physical_keycode = KeyMappingOSX::translate_key([event keyCode]);
+			ke.raw = true;
+			ke.unicode = 0;
+
+			ds->push_to_key_event_buffer(ke);
+		}
+	}
+}
+
+// MARK: Scroll and pan
+
+- (void)processScrollEvent:(NSEvent *)event button:(MouseButton)button factor:(double)factor {
+	DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+	if (!ds || !ds->has_window(window_id)) {
+		return;
+	}
+
+	DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+	MouseButton mask = mouse_button_to_mask(button);
+
+	Ref<InputEventMouseButton> sc;
+	sc.instantiate();
+
+	sc->set_window_id(window_id);
+	ds->get_key_modifier_state([event 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);
+	MouseButton last_button_state = ds->mouse_get_button_state() | (MouseButton)mask;
+	sc->set_button_mask(last_button_state);
+	ds->mouse_set_button_state(last_button_state);
+
+	Input::get_singleton()->parse_input_event(sc);
+
+	sc.instantiate();
+	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);
+	last_button_state &= (MouseButton)~mask;
+	sc->set_button_mask(last_button_state);
+	ds->mouse_set_button_state(last_button_state);
+
+	Input::get_singleton()->parse_input_event(sc);
+}
+
+- (void)processPanEvent:(NSEvent *)event dx:(double)dx dy:(double)dy {
+	DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+	if (!ds || !ds->has_window(window_id)) {
+		return;
+	}
+
+	DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+
+	Ref<InputEventPanGesture> pg;
+	pg.instantiate();
+
+	pg->set_window_id(window_id);
+	ds->get_key_modifier_state([event modifierFlags], pg);
+	pg->set_position(wd.mouse_pos);
+	pg->set_delta(Vector2(-dx, -dy));
+
+	Input::get_singleton()->parse_input_event(pg);
+}
+
+- (void)scrollWheel:(NSEvent *)event {
+	DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+	if (!ds || !ds->has_window(window_id)) {
+		return;
+	}
+
+	DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+	ds->update_mouse_pos(wd, [event locationInWindow]);
+
+	double delta_x = [event scrollingDeltaX];
+	double delta_y = [event scrollingDeltaY];
+
+	if ([event hasPreciseScrollingDeltas]) {
+		delta_x *= 0.03;
+		delta_y *= 0.03;
+	}
+
+	if ([event momentumPhase] != NSEventPhaseNone) {
+		if (ignore_momentum_scroll) {
+			return;
+		}
+	} else {
+		ignore_momentum_scroll = false;
+	}
+
+	if ([event phase] != NSEventPhaseNone || [event momentumPhase] != NSEventPhaseNone) {
+		[self processPanEvent:event dx:delta_x dy:delta_y];
+	} else {
+		if (fabs(delta_x)) {
+			[self processScrollEvent:event button:(0 > delta_x ? MouseButton::WHEEL_RIGHT : MouseButton::WHEEL_LEFT) factor:fabs(delta_x * 0.3)];
+		}
+		if (fabs(delta_y)) {
+			[self processScrollEvent:event button:(0 < delta_y ? MouseButton::WHEEL_UP : MouseButton::WHEEL_DOWN) factor:fabs(delta_y * 0.3)];
+		}
+	}
+}
+
+@end

+ 13 - 10
platform/osx/godot_main_osx.mm

@@ -37,7 +37,7 @@
 
 int main(int argc, char **argv) {
 #if defined(VULKAN_ENABLED)
-	// MoltenVK - enable full component swizzling support
+	// MoltenVK - enable full component swizzling support.
 	setenv("MVK_CONFIG_FULL_IMAGE_VIEW_SWIZZLE", "1", 1);
 #endif
 
@@ -45,13 +45,14 @@ int main(int argc, char **argv) {
 	const char *dbg_arg = "-NSDocumentRevisionsDebugMode";
 	printf("arguments\n");
 	for (int i = 0; i < argc; i++) {
-		if (strcmp(dbg_arg, argv[i]) == 0)
+		if (strcmp(dbg_arg, argv[i]) == 0) {
 			first_arg = i + 2;
+		}
 		printf("%i: %s\n", i, argv[i]);
 	};
 
 #ifdef DEBUG_ENABLED
-	// lets report the path we made current after all that
+	// Lets report the path we made current after all that.
 	char cwd[4096];
 	getcwd(cwd, 4096);
 	printf("Current path: %s\n", cwd);
@@ -60,23 +61,25 @@ int main(int argc, char **argv) {
 	OS_OSX os;
 	Error err;
 
-	// We must override main when testing is enabled
+	// 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());
+	if (os.get_open_with_filename() != "") {
+		char *argv_c = (char *)malloc(os.get_open_with_filename().utf8().size());
+		memcpy(argv_c, os.get_open_with_filename().utf8().get_data(), os.get_open_with_filename().utf8().size());
 		err = Main::setup(argv[0], 1, &argv_c);
 		free(argv_c);
 	} else {
 		err = Main::setup(argv[0], argc - first_arg, &argv[first_arg]);
 	}
 
-	if (err != OK)
+	if (err != OK) {
 		return 255;
+	}
 
-	if (Main::start())
-		os.run(); // it is actually the OS that decides how to run
+	if (Main::start()) {
+		os.run(); // It is actually the OS that decides how to run.
+	}
 
 	Main::cleanup();
 

+ 52 - 0
platform/osx/godot_menu_item.h

@@ -0,0 +1,52 @@
+/*************************************************************************/
+/*  godot_menu_item.h                                                    */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#ifndef GODOT_MENU_ITEM_H
+#define GODOT_MENU_ITEM_H
+
+#include "servers/display_server.h"
+
+#import <AppKit/AppKit.h>
+#import <Foundation/Foundation.h>
+
+@interface GodotMenuItem : NSObject {
+@public
+	Callable callback;
+	Variant meta;
+	int id;
+	bool checkable;
+}
+
+@end
+
+@implementation GodotMenuItem
+@end
+
+#endif // GODOT_MENU_ITEM_H

+ 47 - 0
platform/osx/godot_window.h

@@ -0,0 +1,47 @@
+/*************************************************************************/
+/*  godot_window.h                                                       */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#ifndef GODOT_WINDOW_H
+#define GODOT_WINDOW_H
+
+#include "servers/display_server.h"
+
+#import <AppKit/AppKit.h>
+#import <Foundation/Foundation.h>
+
+@interface GodotWindow : NSWindow {
+	DisplayServer::WindowID window_id;
+}
+
+- (void)setWindowID:(DisplayServer::WindowID)wid;
+
+@end
+
+#endif //GODOT_WINDOW_H

+ 69 - 0
platform/osx/godot_window.mm

@@ -0,0 +1,69 @@
+/*************************************************************************/
+/*  godot_window.mm                                                      */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#include "godot_window.h"
+
+#include "display_server_osx.h"
+
+@implementation GodotWindow
+
+- (id)init {
+	self = [super init];
+	window_id = DisplayServer::INVALID_WINDOW_ID;
+	return self;
+}
+
+- (void)setWindowID:(DisplayServerOSX::WindowID)wid {
+	window_id = wid;
+}
+
+- (BOOL)canBecomeKeyWindow {
+	// Required for NSBorderlessWindowMask windows.
+	DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+	if (!ds || !ds->has_window(window_id)) {
+		return YES;
+	}
+
+	DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+	return !wd.no_focus;
+}
+
+- (BOOL)canBecomeMainWindow {
+	// Required for NSBorderlessWindowMask windows.
+	DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+	if (!ds || !ds->has_window(window_id)) {
+		return YES;
+	}
+
+	DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+	return !wd.no_focus;
+}
+
+@end

+ 47 - 0
platform/osx/godot_window_delegate.h

@@ -0,0 +1,47 @@
+/*************************************************************************/
+/*  godot_window_delegate.h                                              */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#ifndef GODOT_WINDOW_DELEGATE_H
+#define GODOT_WINDOW_DELEGATE_H
+
+#include "servers/display_server.h"
+
+#import <AppKit/AppKit.h>
+#import <Foundation/Foundation.h>
+
+@interface GodotWindowDelegate : NSObject <NSWindowDelegate> {
+	DisplayServer::WindowID window_id;
+}
+
+- (void)setWindowID:(DisplayServer::WindowID)wid;
+
+@end
+
+#endif //GODOT_WINDOW_DELEGATE_H

+ 254 - 0
platform/osx/godot_window_delegate.mm

@@ -0,0 +1,254 @@
+/*************************************************************************/
+/*  godot_window_delegate.mm                                             */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#include "godot_window_delegate.h"
+
+#include "display_server_osx.h"
+
+@implementation GodotWindowDelegate
+
+- (void)setWindowID:(DisplayServer::WindowID)wid {
+	window_id = wid;
+}
+
+- (BOOL)windowShouldClose:(id)sender {
+	DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+	if (!ds || !ds->has_window(window_id)) {
+		return YES;
+	}
+
+	ds->send_window_event(ds->get_window(window_id), DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST);
+	return NO;
+}
+
+- (void)windowWillClose:(NSNotification *)notification {
+	DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+	if (!ds || !ds->has_window(window_id)) {
+		return;
+	}
+
+	DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+	while (wd.transient_children.size()) {
+		ds->window_set_transient(wd.transient_children.front()->get(), DisplayServerOSX::INVALID_WINDOW_ID);
+	}
+
+	if (wd.transient_parent != DisplayServerOSX::INVALID_WINDOW_ID) {
+		ds->window_set_transient(window_id, DisplayServerOSX::INVALID_WINDOW_ID);
+	}
+
+	ds->window_destroy(window_id);
+}
+
+- (void)windowDidEnterFullScreen:(NSNotification *)notification {
+	DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+	if (!ds || !ds->has_window(window_id)) {
+		return;
+	}
+
+	DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+	wd.fullscreen = true;
+	// Reset window size limits.
+	[wd.window_object setContentMinSize:NSMakeSize(0, 0)];
+	[wd.window_object setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)];
+
+	// Force window resize event.
+	[self windowDidResize:notification];
+}
+
+- (void)windowDidExitFullScreen:(NSNotification *)notification {
+	DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+	if (!ds || !ds->has_window(window_id)) {
+		return;
+	}
+
+	DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+	wd.fullscreen = false;
+
+	// Set window size limits.
+	const float scale = ds->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)];
+	}
+
+	// Restore resizability state.
+	if (wd.resize_disabled) {
+		[wd.window_object setStyleMask:[wd.window_object styleMask] & ~NSWindowStyleMaskResizable];
+	}
+
+	// Restore on-top state.
+	if (wd.on_top) {
+		[wd.window_object setLevel:NSFloatingWindowLevel];
+	}
+
+	// Force window resize event.
+	[self windowDidResize:notification];
+}
+
+- (void)windowDidChangeBackingProperties:(NSNotification *)notification {
+	DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+	if (!ds || !ds->has_window(window_id)) {
+		return;
+	}
+
+	DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+
+	CGFloat new_scale_factor = [wd.window_object backingScaleFactor];
+	CGFloat old_scale_factor = [[[notification userInfo] objectForKey:@"NSBackingPropertyOldScaleFactorKey"] doubleValue];
+
+	if (new_scale_factor != old_scale_factor) {
+		// Set new display scale and window size.
+		const float scale = ds->screen_get_max_scale();
+		const NSRect content_rect = [wd.window_view frame];
+
+		wd.size.width = content_rect.size.width * scale;
+		wd.size.height = content_rect.size.height * scale;
+
+		ds->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 {
+	DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+	if (!ds || !ds->has_window(window_id)) {
+		return;
+	}
+
+	DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+	const NSRect content_rect = [wd.window_view frame];
+	const float scale = ds->screen_get_max_scale();
+	wd.size.width = content_rect.size.width * scale;
+	wd.size.height = content_rect.size.height * scale;
+
+	CALayer *layer = [wd.window_view layer];
+	if (layer) {
+		layer.contentsScale = scale;
+	}
+
+	ds->window_resize(window_id, wd.size.width, wd.size.height);
+
+	if (!wd.rect_changed_callback.is_null()) {
+		Variant size = Rect2i(ds->window_get_position(window_id), ds->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)windowDidMove:(NSNotification *)notification {
+	DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+	if (!ds || !ds->has_window(window_id)) {
+		return;
+	}
+
+	DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+	ds->release_pressed_events();
+
+	if (!wd.rect_changed_callback.is_null()) {
+		Variant size = Rect2i(ds->window_get_position(window_id), ds->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 {
+	DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+	if (!ds || !ds->has_window(window_id)) {
+		return;
+	}
+
+	DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+
+	if (ds->mouse_get_mode() == DisplayServer::MOUSE_MODE_CAPTURED) {
+		const NSRect content_rect = [wd.window_view frame];
+		NSRect point_in_window_rect = NSMakeRect(content_rect.size.width / 2, content_rect.size.height / 2, 0, 0);
+		NSPoint point_on_screen = [[wd.window_view window] convertRectToScreen:point_in_window_rect].origin;
+		CGPoint mouse_warp_pos = { point_on_screen.x, CGDisplayBounds(CGMainDisplayID()).size.height - point_on_screen.y };
+		CGWarpMouseCursorPosition(mouse_warp_pos);
+	} else {
+		ds->update_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream]);
+	}
+
+	ds->set_last_focused_window(window_id);
+	ds->send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_FOCUS_IN);
+}
+
+- (void)windowDidResignKey:(NSNotification *)notification {
+	DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+	if (!ds || !ds->has_window(window_id)) {
+		return;
+	}
+
+	DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+
+	ds->release_pressed_events();
+	ds->send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_FOCUS_OUT);
+}
+
+- (void)windowDidMiniaturize:(NSNotification *)notification {
+	DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+	if (!ds || !ds->has_window(window_id)) {
+		return;
+	}
+
+	DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+
+	ds->release_pressed_events();
+	ds->send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_FOCUS_OUT);
+}
+
+- (void)windowDidDeminiaturize:(NSNotification *)notification {
+	DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+	if (!ds || !ds->has_window(window_id)) {
+		return;
+	}
+
+	DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+
+	ds->set_last_focused_window(window_id);
+	ds->send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_FOCUS_IN);
+}
+
+@end

+ 6 - 6
platform/osx/joypad_osx.cpp

@@ -80,7 +80,7 @@ int joypad::get_hid_element_state(rec_element *p_element) const {
 		if (IOHIDDeviceGetValue(device_ref, p_element->ref, &valueRef) == kIOReturnSuccess) {
 			value = (SInt32)IOHIDValueGetIntegerValue(valueRef);
 
-			/* record min and max for auto calibration */
+			// Record min and max for auto calibration.
 			if (value < p_element->min) {
 				p_element->min = value;
 			}
@@ -179,7 +179,7 @@ void joypad::add_hid_element(IOHIDElementRef p_element) {
 				break;
 		}
 
-		if (list) { /* add to list */
+		if (list) { // Add to list.
 			rec_element element;
 
 			element.ref = p_element;
@@ -280,7 +280,7 @@ 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 */
+	// Get device name.
 	String name;
 	char c_name[256];
 	CFTypeRef refCF = IOHIDDeviceGetProperty(p_device_ref, CFSTR(kIOHIDProductKey));
@@ -319,7 +319,7 @@ bool JoypadOSX::configure_joypad(IOHIDDeviceRef p_device_ref, joypad *p_joy) {
 		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
+		// Bluetooth device.
 		String guid = "05000000";
 		for (int i = 0; i < 12; i++) {
 			if (i < name.size())
@@ -445,7 +445,7 @@ static HatMask process_hat_value(int p_min, int p_max, int p_value, bool p_offse
 
 void JoypadOSX::poll_joypads() const {
 	while (CFRunLoopRunInMode(GODOT_JOY_LOOP_RUN_MODE, 0, TRUE) == kCFRunLoopRunHandledSource) {
-		/* no-op. Pending callbacks will fire. */
+		// No-op. Pending callbacks will fire.
 	}
 }
 
@@ -568,7 +568,7 @@ void JoypadOSX::config_hid_manager(CFArrayRef p_matching_array) const {
 	IOHIDManagerScheduleWithRunLoop(hid_manager, runloop, GODOT_JOY_LOOP_RUN_MODE);
 
 	while (CFRunLoopRunInMode(GODOT_JOY_LOOP_RUN_MODE, 0, TRUE) == kCFRunLoopRunHandledSource) {
-		/* no-op. Callback fires once per existing device. */
+		// No-op. Callback fires once per existing device.
 	}
 }
 

+ 1 - 1
platform/osx/joypad_osx.h

@@ -66,7 +66,7 @@ struct joypad {
 	int id = 0;
 	bool offset_hat = false;
 
-	io_service_t ffservice = 0; /* Interface for force feedback, 0 = no ff */
+	io_service_t ffservice = 0; // Interface for force feedback, 0 = no ff.
 	FFCONSTANTFORCE ff_constant_force;
 	FFDeviceObjectReference ff_device = nullptr;
 	FFEffectObjectReference ff_object = nullptr;

+ 52 - 0
platform/osx/key_mapping_osx.h

@@ -0,0 +1,52 @@
+/*************************************************************************/
+/*  key_mapping_osx.h                                                    */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#ifndef KEY_MAPPING_OSX_H
+#define KEY_MAPPING_OSX_H
+
+#include "core/os/keyboard.h"
+
+class KeyMappingOSX {
+	KeyMappingOSX() {}
+
+	static bool is_numpad_key(unsigned int key);
+
+public:
+	// Mappings input.
+	static Key translate_key(unsigned int key);
+	static unsigned int unmap_key(Key key);
+	static Key remap_key(unsigned int key, unsigned int state);
+
+	// Mapping for menu shortcuts.
+	static String keycode_get_native_string(Key p_keycode);
+	static unsigned int keycode_get_native_mask(Key p_keycode);
+};
+
+#endif // KEY_MAPPING_OSX_H

+ 477 - 0
platform/osx/key_mapping_osx.mm

@@ -0,0 +1,477 @@
+/*************************************************************************/
+/*  key_mapping_osx.mm                                                   */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#include "key_mapping_osx.h"
+
+#include <Carbon/Carbon.h>
+#include <Cocoa/Cocoa.h>
+
+bool KeyMappingOSX::is_numpad_key(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;
+}
+
+// Keyboard symbol translation table.
+static const Key _osx_to_godot_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::KEY_1,
+	/* 13 */ Key::KEY_2,
+	/* 14 */ Key::KEY_3,
+	/* 15 */ Key::KEY_4,
+	/* 16 */ Key::KEY_6,
+	/* 17 */ Key::KEY_5,
+	/* 18 */ Key::EQUAL,
+	/* 19 */ Key::KEY_9,
+	/* 1a */ Key::KEY_7,
+	/* 1b */ Key::MINUS,
+	/* 1c */ Key::KEY_8,
+	/* 1d */ Key::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::CTRL,
+	/* 3c */ Key::SHIFT,
+	/* 3d */ Key::ALT,
+	/* 3e */ Key::CTRL,
+	/* 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::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,
+};
+
+// Translates a OS X keycode to a Godot keycode.
+Key KeyMappingOSX::translate_key(unsigned int key) {
+	if (key >= 128) {
+		return Key::UNKNOWN;
+	}
+
+	return _osx_to_godot_table[key];
+}
+
+// Translates a Godot keycode back to a OSX keycode.
+unsigned int KeyMappingOSX::unmap_key(Key key) {
+	for (int i = 0; i <= 126; i++) {
+		if (_osx_to_godot_table[i] == key) {
+			return i;
+		}
+	}
+	return 127;
+}
+
+struct _KeyCodeMap {
+	UniChar kchar;
+	Key kcode;
+};
+
+static const _KeyCodeMap _keycodes[55] = {
+	{ '`', Key::QUOTELEFT },
+	{ '~', Key::ASCIITILDE },
+	{ '0', Key::KEY_0 },
+	{ '1', Key::KEY_1 },
+	{ '2', Key::KEY_2 },
+	{ '3', Key::KEY_3 },
+	{ '4', Key::KEY_4 },
+	{ '5', Key::KEY_5 },
+	{ '6', Key::KEY_6 },
+	{ '7', Key::KEY_7 },
+	{ '8', Key::KEY_8 },
+	{ '9', Key::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 }
+};
+
+// Remap key according to current keyboard layout.
+Key KeyMappingOSX::remap_key(unsigned int key, unsigned int state) {
+	if (is_numpad_key(key)) {
+		return translate_key(key);
+	}
+
+	TISInputSourceRef current_keyboard = TISCopyCurrentKeyboardInputSource();
+	if (!current_keyboard) {
+		return translate_key(key);
+	}
+
+	CFDataRef layout_data = (CFDataRef)TISGetInputSourceProperty(current_keyboard, kTISPropertyUnicodeKeyLayoutData);
+	if (!layout_data) {
+		return translate_key(key);
+	}
+
+	const UCKeyboardLayout *keyboard_layout = (const UCKeyboardLayout *)CFDataGetBytePtr(layout_data);
+
+	UInt32 keys_down = 0;
+	UniChar chars[4];
+	UniCharCount real_length;
+
+	OSStatus err = UCKeyTranslate(keyboard_layout,
+			key,
+			kUCKeyActionDisplay,
+			(state >> 8) & 0xFF,
+			LMGetKbdType(),
+			kUCKeyTranslateNoDeadKeysBit,
+			&keys_down,
+			sizeof(chars) / sizeof(chars[0]),
+			&real_length,
+			chars);
+
+	if (err != noErr) {
+		return translate_key(key);
+	}
+
+	for (unsigned int i = 0; i < 55; i++) {
+		if (_keycodes[i].kchar == chars[0]) {
+			return _keycodes[i].kcode;
+		}
+	}
+	return translate_key(key);
+}
+
+struct _KeyCodeText {
+	Key code;
+	char32_t text;
+};
+
+static const _KeyCodeText _native_keycodes[] = {
+	/* clang-format off */
+		{Key::ESCAPE                        ,0x001B},
+		{Key::TAB                           ,0x0009},
+		{Key::BACKTAB                       ,0x007F},
+		{Key::BACKSPACE                     ,0x0008},
+		{Key::ENTER                         ,0x000D},
+		{Key::INSERT                        ,NSInsertFunctionKey},
+		{Key::KEY_DELETE                    ,0x007F},
+		{Key::PAUSE                         ,NSPauseFunctionKey},
+		{Key::PRINT                         ,NSPrintScreenFunctionKey},
+		{Key::SYSREQ                        ,NSSysReqFunctionKey},
+		{Key::CLEAR                         ,NSClearLineFunctionKey},
+		{Key::HOME                          ,0x2196},
+		{Key::END                           ,0x2198},
+		{Key::LEFT                          ,0x001C},
+		{Key::UP                            ,0x001E},
+		{Key::RIGHT                         ,0x001D},
+		{Key::DOWN                          ,0x001F},
+		{Key::PAGEUP                        ,0x21DE},
+		{Key::PAGEDOWN                      ,0x21DF},
+		{Key::NUMLOCK                       ,NSClearLineFunctionKey},
+		{Key::SCROLLLOCK                    ,NSScrollLockFunctionKey},
+		{Key::F1                            ,NSF1FunctionKey},
+		{Key::F2                            ,NSF2FunctionKey},
+		{Key::F3                            ,NSF3FunctionKey},
+		{Key::F4                            ,NSF4FunctionKey},
+		{Key::F5                            ,NSF5FunctionKey},
+		{Key::F6                            ,NSF6FunctionKey},
+		{Key::F7                            ,NSF7FunctionKey},
+		{Key::F8                            ,NSF8FunctionKey},
+		{Key::F9                            ,NSF9FunctionKey},
+		{Key::F10                           ,NSF10FunctionKey},
+		{Key::F11                           ,NSF11FunctionKey},
+		{Key::F12                           ,NSF12FunctionKey},
+		{Key::F13                           ,NSF13FunctionKey},
+		{Key::F14                           ,NSF14FunctionKey},
+		{Key::F15                           ,NSF15FunctionKey},
+		{Key::F16                           ,NSF16FunctionKey}, //* ... NSF35FunctionKey */
+		{Key::MENU                          ,NSMenuFunctionKey},
+		{Key::HELP                          ,NSHelpFunctionKey},
+		{Key::STOP                          ,NSStopFunctionKey},
+		{Key::LAUNCH0                       ,NSUserFunctionKey},
+		{Key::SPACE                         ,0x0020},
+		{Key::EXCLAM                        ,'!'},
+		{Key::QUOTEDBL                      ,'\"'},
+		{Key::NUMBERSIGN                    ,'#'},
+		{Key::DOLLAR                        ,'$'},
+		{Key::PERCENT                       ,'\%'},
+		{Key::AMPERSAND                     ,'&'},
+		{Key::APOSTROPHE                    ,'\''},
+		{Key::PARENLEFT                     ,'('},
+		{Key::PARENRIGHT                    ,')'},
+		{Key::ASTERISK                      ,'*'},
+		{Key::PLUS                          ,'+'},
+		{Key::COMMA                         ,','},
+		{Key::MINUS                         ,'-'},
+		{Key::PERIOD                        ,'.'},
+		{Key::SLASH                         ,'/'},
+		{Key::KEY_0                         ,'0'},
+		{Key::KEY_1                         ,'1'},
+		{Key::KEY_2                         ,'2'},
+		{Key::KEY_3                         ,'3'},
+		{Key::KEY_4                         ,'4'},
+		{Key::KEY_5                         ,'5'},
+		{Key::KEY_6                         ,'6'},
+		{Key::KEY_7                         ,'7'},
+		{Key::KEY_8                         ,'8'},
+		{Key::KEY_9                         ,'9'},
+		{Key::COLON                         ,':'},
+		{Key::SEMICOLON                     ,';'},
+		{Key::LESS                          ,'<'},
+		{Key::EQUAL                         ,'='},
+		{Key::GREATER                       ,'>'},
+		{Key::QUESTION                      ,'?'},
+		{Key::AT                            ,'@'},
+		{Key::A                             ,'a'},
+		{Key::B                             ,'b'},
+		{Key::C                             ,'c'},
+		{Key::D                             ,'d'},
+		{Key::E                             ,'e'},
+		{Key::F                             ,'f'},
+		{Key::G                             ,'g'},
+		{Key::H                             ,'h'},
+		{Key::I                             ,'i'},
+		{Key::J                             ,'j'},
+		{Key::K                             ,'k'},
+		{Key::L                             ,'l'},
+		{Key::M                             ,'m'},
+		{Key::N                             ,'n'},
+		{Key::O                             ,'o'},
+		{Key::P                             ,'p'},
+		{Key::Q                             ,'q'},
+		{Key::R                             ,'r'},
+		{Key::S                             ,'s'},
+		{Key::T                             ,'t'},
+		{Key::U                             ,'u'},
+		{Key::V                             ,'v'},
+		{Key::W                             ,'w'},
+		{Key::X                             ,'x'},
+		{Key::Y                             ,'y'},
+		{Key::Z                             ,'z'},
+		{Key::BRACKETLEFT                   ,'['},
+		{Key::BACKSLASH                     ,'\\'},
+		{Key::BRACKETRIGHT                  ,']'},
+		{Key::ASCIICIRCUM                   ,'^'},
+		{Key::UNDERSCORE                    ,'_'},
+		{Key::QUOTELEFT                     ,'`'},
+		{Key::BRACELEFT                     ,'{'},
+		{Key::BAR                           ,'|'},
+		{Key::BRACERIGHT                    ,'}'},
+		{Key::ASCIITILDE                    ,'~'},
+		{Key::NONE                          ,0x0000}
+	/* clang-format on */
+};
+
+String KeyMappingOSX::keycode_get_native_string(Key p_keycode) {
+	const _KeyCodeText *kct = &_native_keycodes[0];
+
+	while (kct->text) {
+		if (kct->code == p_keycode) {
+			return String::chr(kct->text);
+		}
+		kct++;
+	}
+	return String();
+}
+
+unsigned int KeyMappingOSX::keycode_get_native_mask(Key p_keycode) {
+	unsigned int mask = 0;
+	if ((p_keycode & KeyModifierMask::CTRL) != Key::NONE) {
+		mask |= NSEventModifierFlagControl;
+	}
+	if ((p_keycode & KeyModifierMask::ALT) != Key::NONE) {
+		mask |= NSEventModifierFlagOption;
+	}
+	if ((p_keycode & KeyModifierMask::SHIFT) != Key::NONE) {
+		mask |= NSEventModifierFlagShift;
+	}
+	if ((p_keycode & KeyModifierMask::META) != Key::NONE) {
+		mask |= NSEventModifierFlagCommand;
+	}
+	if ((p_keycode & KeyModifierMask::KPAD) != Key::NONE) {
+		mask |= NSEventModifierFlagNumericPad;
+	}
+	return mask;
+}

+ 16 - 11
platform/osx/os_osx.h

@@ -40,9 +40,7 @@
 #include "servers/audio_server.h"
 
 class OS_OSX : public OS_Unix {
-	virtual void delete_main_loop() override;
-
-	bool force_quit;
+	bool force_quit = false;
 
 	JoypadOSX *joypad_osx = nullptr;
 
@@ -55,13 +53,15 @@ class OS_OSX : public OS_Unix {
 
 	CrashHandler crash_handler;
 
-	MainLoop *main_loop;
+	CFRunLoopObserverRef pre_wait_observer;
 
-	static void pre_wait_observer_cb(CFRunLoopObserverRef p_observer, CFRunLoopActivity p_activiy, void *p_context);
+	MainLoop *main_loop = nullptr;
 
-public:
 	String open_with_filename;
 
+	static _FORCE_INLINE_ String get_framework_executable(const String &p_path);
+	static void pre_wait_observer_cb(CFRunLoopObserverRef p_observer, CFRunLoopActivity p_activiy, void *p_context);
+
 protected:
 	virtual void initialize_core() override;
 	virtual void initialize() override;
@@ -70,8 +70,12 @@ protected:
 	virtual void initialize_joypads() override;
 
 	virtual void set_main_loop(MainLoop *p_main_loop) override;
+	virtual void delete_main_loop() override;
 
 public:
+	String get_open_with_filename() const;
+	void set_open_with_filename(const String &p_path);
+
 	virtual String get_name() const override;
 
 	virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override;
@@ -89,26 +93,27 @@ public:
 
 	virtual String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const override;
 
-	Error shell_open(String p_uri) override;
+	virtual Error shell_open(String p_uri) override;
 
-	String get_locale() const override;
+	virtual String get_locale() const override;
 
 	virtual String get_executable_path() const override;
 	virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override;
 	virtual Error create_instance(const List<String> &p_arguments, ProcessID *r_child_id = nullptr) override;
 
-	virtual String get_unique_id() const override; //++
+	virtual String get_unique_id() const override;
 
 	virtual bool _check_internal_feature_support(const String &p_feature) override;
 
-	void run();
-
 	virtual void disable_crash_handler() override;
 	virtual bool is_disable_crash_handler() const override;
 
 	virtual Error move_to_trash(const String &p_path) override;
 
+	void run();
+
 	OS_OSX();
+	~OS_OSX();
 };
 
 #endif

+ 129 - 305
platform/osx/os_osx.mm

@@ -31,240 +31,45 @@
 #include "os_osx.h"
 
 #include "core/version_generated.gen.h"
+#include "main/main.h"
 
 #include "dir_access_osx.h"
 #include "display_server_osx.h"
-#include "main/main.h"
+#include "godot_application.h"
+#include "godot_application_delegate.h"
+#include "osx_terminal_logger.h"
 
 #include <dlfcn.h>
 #include <libproc.h>
 #include <mach-o/dyld.h>
-#include <os/log.h>
-
-#define DS_OSX ((DisplayServerOSX *)(DisplayServerOSX::get_singleton()))
-
-/*************************************************************************/
-/* GodotApplication                                                      */
-/*************************************************************************/
-
-@interface GodotApplication : NSApplication
-@end
-
-@implementation GodotApplication
-
-- (void)sendEvent:(NSEvent *)event {
-	if (DS_OSX) {
-		DS_OSX->_send_event(event);
-	}
-
-	// 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
-
-/*************************************************************************/
-/* GodotApplicationDelegate                                              */
-/*************************************************************************/
-
-@interface GodotApplicationDelegate : NSObject
-- (void)forceUnbundledWindowActivationHackStep1;
-- (void)forceUnbundledWindowActivationHackStep2;
-- (void)forceUnbundledWindowActivationHackStep3;
-@end
-
-@implementation GodotApplicationDelegate
-
-- (void)forceUnbundledWindowActivationHackStep1 {
-	// Step 1: Switch focus to macOS SystemUIServer process.
-	// Required to perform step 2, TransformProcessType will fail if app is already the in focus.
-	for (NSRunningApplication *app in [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.systemuiserver"]) {
-		[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 || isatty(STDOUT_FILENO) || isatty(STDIN_FILENO) || isatty(STDERR_FILENO)) {
-		// If the executable is started from terminal or is not bundled, macOS WindowServer won't register and activate app window correctly (menu and title bar are grayed out and input ignored).
-		[self performSelector:@selector(forceUnbundledWindowActivationHackStep1) withObject:nil afterDelay:0.02];
-	}
-}
-
-- (void)applicationDidResignActive:(NSNotification *)notification {
-	if (OS::get_singleton()->get_main_loop()) {
-		OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT);
-	}
-}
 
-- (void)applicationDidBecomeActive:(NSNotification *)notification {
-	if (OS::get_singleton()->get_main_loop()) {
-		OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN);
-	}
-}
-
-- (void)globalMenuCallback:(id)sender {
-	if (DS_OSX) {
-		return DS_OSX->_menu_callback(sender);
-	}
-}
-
-- (NSMenu *)applicationDockMenu:(NSApplication *)sender {
-	if (DS_OSX) {
-		return DS_OSX->_get_dock_menu();
+_FORCE_INLINE_ String OS_OSX::get_framework_executable(const String &p_path) {
+	// Append framework executable name, or return as is if p_path is not a framework.
+	DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+	if (da->dir_exists(p_path) && da->file_exists(p_path.plus_file(p_path.get_file().get_basename()))) {
+		return p_path.plus_file(p_path.get_file().get_basename());
 	} else {
-		return nullptr;
-	}
-}
-
-- (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_OSX::get_singleton()->get_executable_path();
-		OS_OSX::get_singleton()->create_process(exec, args);
-	}
-#endif
-	return YES;
-}
-
-- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
-	if (DS_OSX) {
-		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);
+		return p_path;
 	}
 }
 
-@end
-
-/*************************************************************************/
-/* OSXTerminalLogger                                                     */
-/*************************************************************************/
-
-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, bool p_editor_notify = false, 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:
-				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;
-		}
-	}
-};
-
-/*************************************************************************/
-/* OS_OSX                                                                */
-/*************************************************************************/
-
-String OS_OSX::get_unique_id() const {
-	static String serial_number;
-
-	if (serial_number.is_empty()) {
-		io_service_t platformExpert = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice"));
-		CFStringRef serialNumberAsCFString = nullptr;
-		if (platformExpert) {
-			serialNumberAsCFString = (CFStringRef)IORegistryEntryCreateCFProperty(platformExpert, CFSTR(kIOPlatformSerialNumberKey), kCFAllocatorDefault, 0);
-			IOObjectRelease(platformExpert);
-		}
+void OS_OSX::pre_wait_observer_cb(CFRunLoopObserverRef p_observer, CFRunLoopActivity p_activiy, void *p_context) {
+	// Prevent main loop from sleeping and redraw window during resize / modal popups.
 
-		NSString *serialNumberAsNSString = nil;
-		if (serialNumberAsCFString) {
-			serialNumberAsNSString = [NSString stringWithString:(__bridge NSString *)serialNumberAsCFString];
-			CFRelease(serialNumberAsCFString);
+	if (get_singleton()->get_main_loop()) {
+		Main::force_redraw();
+		if (!Main::is_iterating()) { // Avoid cyclic loop.
+			Main::iteration();
 		}
-
-		serial_number = [serialNumberAsNSString UTF8String];
 	}
 
-	return serial_number;
+	CFRunLoopWakeUp(CFRunLoopGetCurrent()); // Prevent main loop from sleeping.
 }
 
-void OS_OSX::alert(const String &p_alert, const String &p_title) {
-	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];
+void OS_OSX::initialize() {
+	crash_handler.initialize();
 
-	id key_window = [[NSApplication sharedApplication] keyWindow];
-	[window runModal];
-	if (key_window) {
-		[key_window makeKeyAndOrderFront:nil];
-	}
+	initialize_core();
 }
 
 void OS_OSX::initialize_core() {
@@ -275,17 +80,6 @@ void OS_OSX::initialize_core() {
 	DirAccess::make_default<DirAccessOSX>(DirAccess::ACCESS_FILESYSTEM);
 }
 
-void OS_OSX::initialize_joypads() {
-	joypad_osx = memnew(JoypadOSX(Input::get_singleton()));
-}
-
-void OS_OSX::initialize() {
-	crash_handler.initialize();
-
-	initialize_core();
-	//ensure_user_data_dir();
-}
-
 void OS_OSX::finalize() {
 #ifdef COREMIDI_ENABLED
 	midi_driver.close();
@@ -298,42 +92,63 @@ void OS_OSX::finalize() {
 	}
 }
 
+void OS_OSX::initialize_joypads() {
+	joypad_osx = memnew(JoypadOSX(Input::get_singleton()));
+}
+
 void OS_OSX::set_main_loop(MainLoop *p_main_loop) {
 	main_loop = p_main_loop;
 }
 
 void OS_OSX::delete_main_loop() {
-	if (!main_loop)
+	if (!main_loop) {
 		return;
+	}
+
 	memdelete(main_loop);
 	main_loop = nullptr;
 }
 
+String OS_OSX::get_open_with_filename() const {
+	return open_with_filename;
+}
+
+void OS_OSX::set_open_with_filename(const String &p_path) {
+	open_with_filename = p_path;
+}
+
 String OS_OSX::get_name() const {
 	return "macOS";
 }
 
-_FORCE_INLINE_ String _get_framework_executable(const String p_path) {
-	// Append framework executable name, or return as is if p_path is not a framework.
-	DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
-	if (da->dir_exists(p_path) && da->file_exists(p_path.plus_file(p_path.get_file().get_basename()))) {
-		return p_path.plus_file(p_path.get_file().get_basename());
-	} else {
-		return p_path;
+void OS_OSX::alert(const String &p_alert, const String &p_title) {
+	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];
+	if (key_window) {
+		[key_window makeKeyAndOrderFront:nil];
 	}
 }
 
 Error OS_OSX::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) {
-	String path = _get_framework_executable(p_path);
+	String path = get_framework_executable(p_path);
 
 	if (!FileAccess::exists(path)) {
-		// This code exists so gdnative can load .dylib files from within the executable path.
-		path = _get_framework_executable(get_executable_path().get_base_dir().plus_file(p_path.get_file()));
+		// Load .dylib or framework from within the executable path.
+		path = get_framework_executable(get_executable_path().get_base_dir().plus_file(p_path.get_file()));
 	}
 
 	if (!FileAccess::exists(path)) {
-		// This code exists so gdnative can load .dylib files from a standard macOS location.
-		path = _get_framework_executable(get_executable_path().get_base_dir().plus_file("../Frameworks").plus_file(p_path.get_file()));
+		// Load .dylib or framework from a standard macOS location.
+		path = get_framework_executable(get_executable_path().get_base_dir().plus_file("../Frameworks").plus_file(p_path.get_file()));
 	}
 
 	p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW);
@@ -392,8 +207,8 @@ String OS_OSX::get_bundle_resource_dir() const {
 
 	NSBundle *main = [NSBundle mainBundle];
 	if (main) {
-		NSString *resourcePath = [main resourcePath];
-		ret.parse_utf8([resourcePath UTF8String]);
+		NSString *resource_path = [main resourcePath];
+		ret.parse_utf8([resource_path UTF8String]);
 	}
 	return ret;
 }
@@ -403,9 +218,9 @@ String OS_OSX::get_bundle_icon_path() const {
 
 	NSBundle *main = [NSBundle mainBundle];
 	if (main) {
-		NSString *iconPath = [[main infoDictionary] objectForKey:@"CFBundleIconFile"];
-		if (iconPath) {
-			ret.parse_utf8([iconPath UTF8String]);
+		NSString *icon_path = [[main infoDictionary] objectForKey:@"CFBundleIconFile"];
+		if (icon_path) {
+			ret.parse_utf8([icon_path UTF8String]);
 		}
 	}
 	return ret;
@@ -448,9 +263,7 @@ String OS_OSX::get_system_dir(SystemDir p_dir, bool p_shared_storage) const {
 	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);
+			ret.parse_utf8([[paths firstObject] UTF8String]);
 		}
 	}
 
@@ -474,12 +287,9 @@ String OS_OSX::get_locale() const {
 }
 
 String OS_OSX::get_executable_path() const {
-	int ret;
-	pid_t pid;
 	char pathbuf[PROC_PIDPATHINFO_MAXSIZE];
-
-	pid = getpid();
-	ret = proc_pidpath(pid, pathbuf, sizeof(pathbuf));
+	int pid = getpid();
+	pid_t ret = proc_pidpath(pid, pathbuf, sizeof(pathbuf));
 	if (ret <= 0) {
 		return OS::get_executable_path();
 	} else {
@@ -490,18 +300,6 @@ String OS_OSX::get_executable_path() const {
 	}
 }
 
-Error OS_OSX::create_instance(const List<String> &p_arguments, ProcessID *r_child_id) {
-	// If executable is bundled, always execute editor instances as an app bundle to ensure app window is registered and activated correctly.
-	NSString *nsappname = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"];
-	if (nsappname != nil) {
-		String path;
-		path.parse_utf8([[[NSBundle mainBundle] bundlePath] UTF8String]);
-		return create_process(path, p_arguments, r_child_id, false);
-	} else {
-		return create_process(get_executable_path(), p_arguments, r_child_id, false);
-	}
-}
-
 Error OS_OSX::create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id, bool p_open_console) {
 	if (@available(macOS 10.15, *)) {
 		// Use NSWorkspace if path is an .app bundle.
@@ -547,17 +345,66 @@ Error OS_OSX::create_process(const String &p_path, const List<String> &p_argumen
 	}
 }
 
-void OS_OSX::pre_wait_observer_cb(CFRunLoopObserverRef p_observer, CFRunLoopActivity p_activiy, void *p_context) {
-	// Prevent main loop from sleeping and redraw window during resize / modal popups.
+Error OS_OSX::create_instance(const List<String> &p_arguments, ProcessID *r_child_id) {
+	// If executable is bundled, always execute editor instances as an app bundle to ensure app window is registered and activated correctly.
+	NSString *nsappname = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"];
+	if (nsappname != nil) {
+		String path;
+		path.parse_utf8([[[NSBundle mainBundle] bundlePath] UTF8String]);
+		return create_process(path, p_arguments, r_child_id, false);
+	} else {
+		return create_process(get_executable_path(), p_arguments, r_child_id, false);
+	}
+}
 
-	if (get_singleton()->get_main_loop()) {
-		Main::force_redraw();
-		if (!Main::is_iterating()) { // Avoid cyclic loop.
-			Main::iteration();
+String OS_OSX::get_unique_id() const {
+	static String serial_number;
+
+	if (serial_number.is_empty()) {
+		io_service_t platform_expert = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice"));
+		CFStringRef serial_number_cf_string = nullptr;
+		if (platform_expert) {
+			serial_number_cf_string = (CFStringRef)IORegistryEntryCreateCFProperty(platform_expert, CFSTR(kIOPlatformSerialNumberKey), kCFAllocatorDefault, 0);
+			IOObjectRelease(platform_expert);
+		}
+
+		NSString *serial_number_ns_string = nil;
+		if (serial_number_cf_string) {
+			serial_number_ns_string = [NSString stringWithString:(__bridge NSString *)serial_number_cf_string];
+			CFRelease(serial_number_cf_string);
+		}
+
+		if (serial_number_ns_string) {
+			serial_number.parse_utf8([serial_number_ns_string UTF8String]);
 		}
 	}
 
-	CFRunLoopWakeUp(CFRunLoopGetCurrent()); // Prevent main loop from sleeping.
+	return serial_number;
+}
+
+bool OS_OSX::_check_internal_feature_support(const String &p_feature) {
+	return p_feature == "pc";
+}
+
+void OS_OSX::disable_crash_handler() {
+	crash_handler.disable();
+}
+
+bool OS_OSX::is_disable_crash_handler() const {
+	return crash_handler.is_disabled();
+}
+
+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_PRINT("trashItemAtURL error: " + String::utf8(err.localizedDescription.UTF8String));
+		return FAILED;
+	}
+
+	return OK;
 }
 
 void OS_OSX::run() {
@@ -569,14 +416,11 @@ void OS_OSX::run() {
 
 	main_loop->initialize();
 
-	CFRunLoopObserverRef pre_wait_observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, &pre_wait_observer_cb, nullptr);
-	CFRunLoopAddObserver(CFRunLoopGetCurrent(), pre_wait_observer, kCFRunLoopCommonModes);
-
 	bool quit = false;
 	while (!force_quit && !quit) {
 		@try {
 			if (DisplayServer::get_singleton()) {
-				DisplayServer::get_singleton()->process_events(); // get rid of pending events
+				DisplayServer::get_singleton()->process_events(); // Get rid of pending events.
 			}
 			joypad_osx->process_joypads();
 
@@ -588,25 +432,9 @@ void OS_OSX::run() {
 		}
 	};
 
-	CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), pre_wait_observer, kCFRunLoopCommonModes);
-	CFRelease(pre_wait_observer);
-
 	main_loop->finalize();
 }
 
-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_PRINT("trashItemAtURL error: " + String::utf8(err.localizedDescription.UTF8String));
-		return FAILED;
-	}
-
-	return OK;
-}
-
 OS_OSX::OS_OSX() {
 	main_loop = nullptr;
 	force_quit = false;
@@ -621,15 +449,15 @@ OS_OSX::OS_OSX() {
 
 	DisplayServerOSX::register_osx_driver();
 
-	// Implicitly create shared NSApplication instance
+	// Implicitly create shared NSApplication instance.
 	[GodotApplication sharedApplication];
 
-	// In case we are unbundled, make us a proper UI application
+	// 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
+	// of NSApplicationMain.
 
 	NSMenu *main_menu = [[NSMenu alloc] initWithTitle:@""];
 	[NSApp setMainMenu:main_menu];
@@ -639,7 +467,10 @@ OS_OSX::OS_OSX() {
 	ERR_FAIL_COND(!delegate);
 	[NSApp setDelegate:delegate];
 
-	//process application:openFile: event
+	pre_wait_observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, &pre_wait_observer_cb, nullptr);
+	CFRunLoopAddObserver(CFRunLoopGetCurrent(), pre_wait_observer, kCFRunLoopCommonModes);
+
+	// Process application:openFile: event.
 	while (true) {
 		NSEvent *event = [NSApp
 				nextEventMatchingMask:NSEventMaskAny
@@ -657,14 +488,7 @@ OS_OSX::OS_OSX() {
 	[NSApp activateIgnoringOtherApps:YES];
 }
 
-bool OS_OSX::_check_internal_feature_support(const String &p_feature) {
-	return p_feature == "pc";
-}
-
-void OS_OSX::disable_crash_handler() {
-	crash_handler.disable();
-}
-
-bool OS_OSX::is_disable_crash_handler() const {
-	return crash_handler.is_disabled();
+OS_OSX::~OS_OSX() {
+	CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), pre_wait_observer, kCFRunLoopCommonModes);
+	CFRelease(pre_wait_observer);
 }

+ 44 - 0
platform/osx/osx_terminal_logger.h

@@ -0,0 +1,44 @@
+/*************************************************************************/
+/*  osx_terminal_logger.h                                                */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#ifndef OSX_TERMINAL_LOGGER_H
+#define OSX_TERMINAL_LOGGER_H
+
+#ifdef OSX_ENABLED
+
+#include "core/io/logger.h"
+
+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, bool p_editor_notify = false, ErrorType p_type = ERR_ERROR) override;
+};
+
+#endif // OSX_ENABLED
+#endif // OSX_TERMINAL_LOGGER_H

+ 81 - 0
platform/osx/osx_terminal_logger.mm

@@ -0,0 +1,81 @@
+/*************************************************************************/
+/*  osx_terminal_logger.mm                                               */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#include "osx_terminal_logger.h"
+
+#ifdef OSX_ENABLED
+
+#include <os/log.h>
+
+void OSXTerminalLogger::log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type) {
+	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:
+			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;
+	}
+}
+
+#endif // OSX_ENABLED

Some files were not shown because too many files changed in this diff