소스 검색

Add macOS DisplayServer implementation.

Change global menu to use Callable, add support for check items and submenus.
bruvzg 5 년 전
부모
커밋
15a9f94346

+ 0 - 8
core/os/main_loop.cpp

@@ -44,8 +44,6 @@ void MainLoop::_bind_methods() {
 	BIND_VMETHOD(MethodInfo(Variant::BOOL, "_idle", PropertyInfo(Variant::FLOAT, "delta")));
 	BIND_VMETHOD(MethodInfo("_finalize"));
 
-	BIND_VMETHOD(MethodInfo("_global_menu_action", PropertyInfo(Variant::NIL, "id"), PropertyInfo(Variant::NIL, "meta")));
-
 	BIND_CONSTANT(NOTIFICATION_OS_MEMORY_WARNING);
 	BIND_CONSTANT(NOTIFICATION_TRANSLATION_CHANGED);
 	BIND_CONSTANT(NOTIFICATION_WM_ABOUT);
@@ -91,12 +89,6 @@ bool MainLoop::idle(float p_time) {
 	return false;
 }
 
-void MainLoop::global_menu_action(const Variant &p_id, const Variant &p_meta) {
-
-	if (get_script_instance())
-		get_script_instance()->call("_global_menu_action", p_id, p_meta);
-}
-
 void MainLoop::finish() {
 
 	if (get_script_instance()) {

+ 0 - 2
core/os/main_loop.h

@@ -62,8 +62,6 @@ public:
 	virtual bool idle(float p_time);
 	virtual void finish();
 
-	virtual void global_menu_action(const Variant &p_id, const Variant &p_meta);
-
 	void set_init_script(const Ref<Script> &p_init_script);
 
 	MainLoop();

+ 3 - 1
core/os/os.h

@@ -146,6 +146,9 @@ public:
 	virtual List<String> get_cmdline_args() const { return _cmdline; }
 	virtual String get_model_name() const;
 
+	bool is_layered_allowed() const { return _allow_layered; }
+	bool is_hidpi_allowed() const { return _allow_hidpi; }
+
 	void ensure_user_data_dir();
 
 	virtual MainLoop *get_main_loop() const = 0;
@@ -286,7 +289,6 @@ public:
 	virtual bool request_permissions() { return true; }
 	virtual Vector<String> get_granted_permissions() const { return Vector<String>(); }
 
-	bool is_hidpi_allowed() const { return _allow_hidpi; }
 	virtual void process_and_drop_events() {}
 	OS();
 	virtual ~OS();

+ 18 - 16
editor/editor_node.cpp

@@ -199,7 +199,7 @@ void EditorNode::_update_scene_tabs() {
 		scene_tabs->add_tab(editor_data.get_scene_title(i) + (unsaved ? "(*)" : ""), icon);
 
 		if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_GLOBAL_MENU)) {
-			DisplayServer::get_singleton()->global_menu_add_item("_dock", editor_data.get_scene_title(i) + (unsaved ? "(*)" : ""), GLOBAL_SCENE, i);
+			DisplayServer::get_singleton()->global_menu_add_item("_dock", editor_data.get_scene_title(i) + (unsaved ? "(*)" : ""), callable_mp(this, &EditorNode::_global_menu_scene), i);
 		}
 
 		if (show_rb && editor_data.get_scene_root_script(i).is_valid()) {
@@ -209,7 +209,7 @@ void EditorNode::_update_scene_tabs() {
 
 	if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_GLOBAL_MENU)) {
 		DisplayServer::get_singleton()->global_menu_add_separator("_dock");
-		DisplayServer::get_singleton()->global_menu_add_item("_dock", TTR("New Window"), GLOBAL_NEW_WINDOW, Variant());
+		DisplayServer::get_singleton()->global_menu_add_item("_dock", TTR("New Window"), callable_mp(this, &EditorNode::_global_menu_new_window));
 	}
 
 	scene_tabs->set_current_tab(editor_data.get_edited_scene());
@@ -378,7 +378,6 @@ void EditorNode::_notification(int p_what) {
 			get_tree()->get_root()->set_as_audio_listener_2d(false);
 			get_tree()->set_auto_accept_quit(false);
 			get_tree()->get_root()->connect("files_dropped", callable_mp(this, &EditorNode::_dropped_files));
-			get_tree()->connect("global_menu_action", callable_mp(this, &EditorNode::_global_menu_action));
 
 			/* DO NOT LOAD SCENES HERE, WAIT FOR FILE SCANNING AND REIMPORT TO COMPLETE */
 		} break;
@@ -5105,21 +5104,19 @@ void EditorNode::remove_tool_menu_item(const String &p_name) {
 	}
 }
 
-void EditorNode::_global_menu_action(const Variant &p_id, const Variant &p_meta) {
+void EditorNode::_global_menu_scene(const Variant &p_tag) {
+	int idx = (int)p_tag;
+	scene_tabs->set_current_tab(idx);
+}
 
-	int id = (int)p_id;
-	if (id == GLOBAL_NEW_WINDOW) {
-		if (OS::get_singleton()->get_main_loop()) {
-			List<String> args;
-			args.push_back("-e");
-			String exec = OS::get_singleton()->get_executable_path();
+void EditorNode::_global_menu_new_window(const Variant &p_tag) {
+	if (OS::get_singleton()->get_main_loop()) {
+		List<String> args;
+		args.push_back("-p");
+		String exec = OS::get_singleton()->get_executable_path();
 
-			OS::ProcessID pid = 0;
-			OS::get_singleton()->execute(exec, args, false, &pid);
-		}
-	} else if (id == GLOBAL_SCENE) {
-		int idx = (int)p_meta;
-		scene_tabs->set_current_tab(idx);
+		OS::ProcessID pid = 0;
+		OS::get_singleton()->execute(exec, args, false, &pid);
 	}
 }
 
@@ -5439,6 +5436,7 @@ void EditorNode::_bind_methods() {
 	ClassDB::bind_method("_update_recent_scenes", &EditorNode::_update_recent_scenes);
 
 	ClassDB::bind_method("_clear_undo_history", &EditorNode::_clear_undo_history);
+
 	ClassDB::bind_method("edit_item_resource", &EditorNode::edit_item_resource);
 
 	ClassDB::bind_method(D_METHOD("get_gui_base"), &EditorNode::get_gui_base);
@@ -5590,7 +5588,11 @@ EditorNode::EditorNode() {
 			case 0: {
 				// Try applying a suitable display scale automatically
 				const int screen = DisplayServer::get_singleton()->window_get_current_screen();
+#ifdef OSX_ENABLED
+				editor_set_scale(DisplayServer::get_singleton()->screen_get_scale(screen));
+#else
 				editor_set_scale(DisplayServer::get_singleton()->screen_get_dpi(screen) >= 192 && DisplayServer::get_singleton()->screen_get_size(screen).x > 2000 ? 2.0 : 1.0);
+#endif
 			} break;
 
 			case 1: {

+ 2 - 1
editor/editor_node.h

@@ -498,7 +498,8 @@ private:
 	void _add_to_recent_scenes(const String &p_scene);
 	void _update_recent_scenes();
 	void _open_recent_scene(int p_idx);
-	void _global_menu_action(const Variant &p_id, const Variant &p_meta);
+	void _global_menu_scene(const Variant &p_tag);
+	void _global_menu_new_window(const Variant &p_tag);
 	void _dropped_files(const Vector<String> &p_files, int p_screen);
 	void _add_dropped_files_recursive(const Vector<String> &p_files, String to_path);
 	String _recent_scene;

+ 3 - 1
editor/editor_run.cpp

@@ -121,7 +121,9 @@ Error EditorRun::run(const String &p_scene, const String &p_custom_args, const L
 		case 1: { // centered
 			int display_scale = 1;
 #ifdef OSX_ENABLED
-			if (OS::get_singleton()->get_screen_dpi(screen) >= 192 && OS::get_singleton()->get_screen_size(screen).x > 2000) {
+			display_scale = DisplayServer::get_singleton()->screen_get_scale(screen);
+#else
+			if (DisplayServer::get_singleton()->screen_get_dpi(screen) >= 192 && DisplayServer::get_singleton()->screen_get_size(screen).x > 2000) {
 				display_scale = 2;
 			}
 #endif

+ 32 - 27
editor/project_manager.cpp

@@ -1047,6 +1047,9 @@ public:
 	ProjectList();
 	~ProjectList();
 
+	void _global_menu_new_window(const Variant &p_tag);
+	void _global_menu_open_project(const Variant &p_tag);
+
 	void update_dock_menu();
 	void load_projects();
 	void set_search_term(String p_search_term);
@@ -1304,14 +1307,37 @@ void ProjectList::update_dock_menu() {
 				}
 				favs_added = 0;
 			}
-			DisplayServer::get_singleton()->global_menu_add_item("_dock", _projects[i].project_name + " ( " + _projects[i].path + " )", GLOBAL_OPEN_PROJECT, Variant(_projects[i].path.plus_file("project.godot")));
+			DisplayServer::get_singleton()->global_menu_add_item("_dock", _projects[i].project_name + " ( " + _projects[i].path + " )", callable_mp(this, &ProjectList::_global_menu_open_project), i);
 			total_added++;
 		}
 	}
 	if (total_added != 0) {
 		DisplayServer::get_singleton()->global_menu_add_separator("_dock");
 	}
-	DisplayServer::get_singleton()->global_menu_add_item("_dock", TTR("New Window"), GLOBAL_NEW_WINDOW, Variant());
+	DisplayServer::get_singleton()->global_menu_add_item("_dock", TTR("New Window"), callable_mp(this, &ProjectList::_global_menu_new_window));
+}
+
+void ProjectList::_global_menu_new_window(const Variant &p_tag) {
+	List<String> args;
+	args.push_back("-p");
+	String exec = OS::get_singleton()->get_executable_path();
+
+	OS::ProcessID pid = 0;
+	OS::get_singleton()->execute(exec, args, false, &pid);
+}
+
+void ProjectList::_global_menu_open_project(const Variant &p_tag) {
+	int idx = (int)p_tag;
+
+	if (idx >= 0 && idx < _projects.size()) {
+		String conf = _projects[idx].path.plus_file("project.godot");
+		List<String> args;
+		args.push_back(conf);
+		String exec = OS::get_singleton()->get_executable_path();
+
+		OS::ProcessID pid = 0;
+		OS::get_singleton()->execute(exec, args, false, &pid);
+	}
 }
 
 void ProjectList::create_project_item_control(int p_index) {
@@ -2024,30 +2050,6 @@ void ProjectManager::_confirm_update_settings() {
 	_open_selected_projects();
 }
 
-void ProjectManager::_global_menu_action(const Variant &p_id, const Variant &p_meta) {
-
-	int id = (int)p_id;
-	if (id == ProjectList::GLOBAL_NEW_WINDOW) {
-		List<String> args;
-		args.push_back("-p");
-		String exec = OS::get_singleton()->get_executable_path();
-
-		OS::ProcessID pid = 0;
-		OS::get_singleton()->execute(exec, args, false, &pid);
-	} else if (id == ProjectList::GLOBAL_OPEN_PROJECT) {
-		String conf = (String)p_meta;
-
-		if (conf != String()) {
-			List<String> args;
-			args.push_back(conf);
-			String exec = OS::get_singleton()->get_executable_path();
-
-			OS::ProcessID pid = 0;
-			OS::get_singleton()->execute(exec, args, false, &pid);
-		}
-	}
-}
-
 void ProjectManager::_open_selected_projects() {
 
 	const Set<String> &selected_list = _project_list->get_selected_project_keys();
@@ -2420,7 +2422,11 @@ ProjectManager::ProjectManager() {
 			case 0: {
 				// Try applying a suitable display scale automatically
 				const int screen = DisplayServer::get_singleton()->window_get_current_screen();
+#ifdef OSX_ENABLED
+				editor_set_scale(DisplayServer::get_singleton()->screen_get_scale(screen));
+#else
 				editor_set_scale(DisplayServer::get_singleton()->screen_get_dpi(screen) >= 192 && DisplayServer::get_singleton()->screen_get_size(screen).x > 2000 ? 2.0 : 1.0);
+#endif
 			} break;
 
 			case 1: editor_set_scale(0.75); break;
@@ -2696,7 +2702,6 @@ ProjectManager::ProjectManager() {
 	}
 
 	SceneTree::get_singleton()->get_root()->connect("files_dropped", callable_mp(this, &ProjectManager::_files_dropped));
-	SceneTree::get_singleton()->connect("global_menu_action", callable_mp(this, &ProjectManager::_global_menu_action));
 
 	run_error_diag = memnew(AcceptDialog);
 	gui_base->add_child(run_error_diag);

+ 0 - 1
editor/project_manager.h

@@ -97,7 +97,6 @@ class ProjectManager : public Control {
 	void _restart_confirm();
 	void _exit_dialog();
 	void _scan_begin(const String &p_base);
-	void _global_menu_action(const Variant &p_id, const Variant &p_meta);
 
 	void _confirm_update_settings();
 

+ 1 - 1
main/main.cpp

@@ -1964,7 +1964,7 @@ bool Main::start() {
 #ifdef OSX_ENABLED
 				String mac_iconpath = GLOBAL_DEF("application/config/macos_native_icon", "Variant()");
 				if (mac_iconpath != "") {
-					OS::get_singleton()->set_native_icon(mac_iconpath);
+					DisplayServer::get_singleton()->set_native_icon(mac_iconpath);
 					hasicon = true;
 				}
 #endif

+ 18 - 0
platform/linuxbsd/display_server_x11.cpp

@@ -421,9 +421,27 @@ void DisplayServerX11::mouse_warp_to_position(const Point2i &p_to) {
 				0, 0, 0, 0, (int)p_to.x, (int)p_to.y);
 	}
 }
+
 Point2i DisplayServerX11::mouse_get_position() const {
 	return last_mouse_pos;
 }
+
+Point2i DisplayServerX11::mouse_get_absolute_position() const {
+	int number_of_screens = XScreenCount(x11_display);
+	for (int i = 0; i < number_of_screens; i++) {
+		Window root, child;
+		int root_x, root_y, win_x, win_y;
+		unsigned int mask;
+		if (XQueryPointer(x11_display, XRootWindow(x11_display, i), &root, &child, &root_x, &root_y, &win_x, &win_y, &mask)) {
+			XWindowAttributes root_attrs;
+			XGetWindowAttributes(x11_display, root, &root_attrs);
+
+			return Vector2i(root_attrs.x + root_x, root_attrs.y + root_y);
+		}
+	}
+	return Vector2i();
+}
+
 int DisplayServerX11::mouse_get_button_state() const {
 	return last_button_state;
 }

+ 1 - 0
platform/linuxbsd/display_server_x11.h

@@ -254,6 +254,7 @@ public:
 
 	virtual void mouse_warp_to_position(const Point2i &p_to);
 	virtual Point2i mouse_get_position() const;
+	virtual Point2i mouse_get_absolute_position() const;
 	virtual int mouse_get_button_state() const;
 
 	virtual void clipboard_set(const String &p_text);

+ 1 - 0
platform/osx/SCsub

@@ -8,6 +8,7 @@ import platform_osx_builders
 files = [
     'crash_handler_osx.mm',
     'os_osx.mm',
+    'display_server_osx.mm',
     'godot_main_osx.mm',
     'dir_access_osx.mm',
     'joypad_osx.cpp',

+ 306 - 0
platform/osx/display_server_osx.h

@@ -0,0 +1,306 @@
+/*************************************************************************/
+/*  display_server_osx.h                                                 */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#ifndef DISPLAY_SERVER_OSX_H
+#define DISPLAY_SERVER_OSX_H
+
+#define BitMap _QDBitMap // Suppress deprecated QuickDraw definition.
+
+#include "core/input/input_filter.h"
+#include "servers/display_server.h"
+
+#if defined(OPENGL_ENABLED)
+#include "context_gl_osx.h"
+//TODO - reimplement OpenGLES
+#endif
+
+#if defined(VULKAN_ENABLED)
+#include "drivers/vulkan/rendering_device_vulkan.h"
+#include "platform/osx/vulkan_context_osx.h"
+#endif
+
+#include <AppKit/AppKit.h>
+#include <AppKit/NSCursor.h>
+#include <ApplicationServices/ApplicationServices.h>
+#include <CoreVideo/CoreVideo.h>
+
+#undef BitMap
+#undef CursorShape
+
+class DisplayServerOSX : public DisplayServer {
+	GDCLASS(DisplayServerOSX, DisplayServer)
+
+	_THREAD_SAFE_CLASS_
+
+public:
+#if defined(OPENGL_ENABLED)
+	ContextGL_OSX *context_gles2;
+#endif
+#if defined(VULKAN_ENABLED)
+	VulkanContextOSX *context_vulkan;
+	RenderingDeviceVulkan *rendering_device_vulkan;
+#endif
+
+	const NSMenu *_get_menu_root(const String &p_menu_root) const;
+	NSMenu *_get_menu_root(const String &p_menu_root);
+
+	NSMenu *apple_menu = NULL;
+	NSMenu *dock_menu = NULL;
+	Map<String, NSMenu *> submenu;
+
+	struct KeyEvent {
+		WindowID window_id;
+		unsigned int osx_state;
+		bool pressed;
+		bool echo;
+		bool raw;
+		uint32_t keycode;
+		uint32_t physical_keycode;
+		uint32_t unicode;
+	};
+
+	Vector<KeyEvent> key_event_buffer;
+	int key_event_pos;
+
+	struct WindowData {
+		id window_delegate;
+		id window_object;
+		id window_view;
+
+#if defined(OPENGL_ENABLED)
+		ContextGL_OSX *context_gles2 = NULL;
+#endif
+		Point2i mouse_pos;
+
+		Size2i min_size;
+		Size2i max_size;
+		Size2i size;
+
+		bool mouse_down_control = false;
+
+		bool im_active = false;
+		Size2i im_position;
+
+		Callable rect_changed_callback;
+		Callable event_callback;
+		Callable input_event_callback;
+		Callable input_text_callback;
+		Callable drop_files_callback;
+
+		ObjectID instance_id;
+
+		WindowID transient_parent = INVALID_WINDOW_ID;
+		Set<WindowID> transient_children;
+
+		bool layered_window = false;
+		bool fullscreen = false;
+		bool on_top = false;
+		bool borderless = false;
+		bool resize_disabled = false;
+	};
+
+	Point2i im_selection;
+	String im_text;
+
+	Map<WindowID, WindowData> windows;
+
+	WindowID window_id_counter = MAIN_WINDOW_ID;
+
+	WindowID _create_window(WindowMode p_mode, const Rect2i &p_rect);
+	void _update_window(WindowData p_wd);
+	void _send_window_event(const WindowData &wd, WindowEvent p_event);
+	static void _dispatch_input_events(const Ref<InputEvent> &p_event);
+	void _dispatch_input_event(const Ref<InputEvent> &p_event);
+	WindowID _find_window_id(id p_window);
+
+	void _set_window_per_pixel_transparency_enabled(bool p_enabled, WindowID p_window);
+
+	float _display_scale(id screen) const;
+	Point2i _get_screens_origin() const;
+	Point2i _get_native_screen_position(int p_screen) const;
+
+	void _push_input(const Ref<InputEvent> &p_event);
+	void _process_key_events();
+
+	String rendering_driver;
+
+	id delegate;
+	id autoreleasePool;
+	CGEventSourceRef eventSource;
+
+	CursorShape cursor_shape;
+	NSCursor *cursors[CURSOR_MAX];
+	Map<CursorShape, Vector<Variant>> cursors_cache;
+
+	MouseMode mouse_mode;
+	Point2i last_mouse_pos;
+	uint32_t last_button_state;
+
+	bool window_focused;
+	bool drop_events;
+
+public:
+	virtual bool has_feature(Feature p_feature) const;
+	virtual String get_name() const;
+
+	virtual void global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag = Variant());
+	virtual void global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag = Variant());
+	virtual void global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu);
+	virtual void global_menu_add_separator(const String &p_menu_root);
+
+	virtual bool global_menu_is_item_checked(const String &p_menu_root, int p_idx) const;
+	virtual bool global_menu_is_item_checkable(const String &p_menu_root, int p_idx) const;
+	virtual Callable global_menu_get_item_callback(const String &p_menu_root, int p_idx);
+	virtual Variant global_menu_get_item_tag(const String &p_menu_root, int p_idx);
+	virtual String global_menu_get_item_text(const String &p_menu_root, int p_idx);
+	virtual String global_menu_get_item_submenu(const String &p_menu_root, int p_idx);
+
+	virtual void global_menu_set_item_checked(const String &p_menu_root, int p_idx, bool p_checked);
+	virtual void global_menu_set_item_checkable(const String &p_menu_root, int p_idx, bool p_checkable);
+	virtual void global_menu_set_item_callback(const String &p_menu_root, int p_idx, const Callable &p_callback);
+	virtual void global_menu_set_item_tag(const String &p_menu_root, int p_idx, const Variant &p_tag);
+	virtual void global_menu_set_item_text(const String &p_menu_root, int p_idx, const String &p_text);
+	virtual void global_menu_set_item_submenu(const String &p_menu_root, int p_idx, const String &p_submenu);
+
+	virtual int global_menu_get_item_count(const String &p_menu_root) const;
+
+	virtual void global_menu_remove_item(const String &p_menu_root, int p_idx);
+	virtual void global_menu_clear(const String &p_menu_root);
+
+	virtual void alert(const String &p_alert, const String &p_title = "ALERT!");
+	virtual Error dialog_show(String p_title, String p_description, Vector<String> p_buttons, const Callable &p_callback);
+	virtual Error dialog_input_text(String p_title, String p_description, String p_partial, const Callable &p_callback);
+
+	virtual void mouse_set_mode(MouseMode p_mode);
+	virtual MouseMode mouse_get_mode() const;
+
+	virtual void mouse_warp_to_position(const Point2i &p_to);
+	virtual Point2i mouse_get_position() const;
+	virtual Point2i mouse_get_absolute_position() const;
+	virtual int mouse_get_button_state() const;
+
+	virtual void clipboard_set(const String &p_text);
+	virtual String clipboard_get() const;
+
+	virtual int get_screen_count() const;
+	virtual Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const;
+	virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const;
+	virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const;
+	virtual float screen_get_scale(int p_screen = SCREEN_OF_MAIN_WINDOW) const;
+	virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const;
+
+	virtual Vector<int> get_window_list() const;
+
+	virtual WindowID create_sub_window(WindowMode p_mode, uint32_t p_flags, const Rect2i & = Rect2i());
+	virtual void delete_sub_window(WindowID p_id);
+
+	virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID);
+	virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID);
+	virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID);
+	virtual void window_set_input_text_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID);
+	virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID);
+
+	virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID);
+
+	virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const;
+	virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID);
+
+	virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const;
+	virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID);
+
+	virtual void window_set_transient(WindowID p_window, WindowID p_parent);
+
+	virtual void window_set_max_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID);
+	virtual Size2i window_get_max_size(WindowID p_window = MAIN_WINDOW_ID) const;
+
+	virtual void window_set_min_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID);
+	virtual Size2i window_get_min_size(WindowID p_window = MAIN_WINDOW_ID) const;
+
+	virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID);
+	virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const;
+	virtual Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const;
+
+	virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID);
+	virtual WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const;
+
+	virtual bool window_is_maximize_allowed(WindowID p_window = MAIN_WINDOW_ID) const;
+
+	virtual void window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window = MAIN_WINDOW_ID);
+	virtual bool window_get_flag(WindowFlags p_flag, WindowID p_window = MAIN_WINDOW_ID) const;
+
+	virtual void window_request_attention(WindowID p_window = MAIN_WINDOW_ID);
+	virtual void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID);
+
+	virtual bool window_can_draw(WindowID p_window = MAIN_WINDOW_ID) const;
+
+	virtual bool can_any_window_draw() const;
+
+	virtual void window_set_ime_active(const bool p_active, WindowID p_window = MAIN_WINDOW_ID);
+	virtual void window_set_ime_position(const Point2i &p_pos, WindowID p_window = MAIN_WINDOW_ID);
+
+	virtual WindowID get_window_at_screen_position(const Point2i &p_position) const;
+
+	virtual void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID);
+	virtual ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const;
+
+	virtual Point2i ime_get_selection() const;
+	virtual String ime_get_text() const;
+
+	virtual void cursor_set_shape(CursorShape p_shape);
+	virtual CursorShape cursor_get_shape() const;
+	virtual void cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape = CURSOR_ARROW, const Vector2 &p_hotspot = Vector2());
+
+	virtual bool get_swap_ok_cancel();
+
+	virtual LatinKeyboardVariant get_latin_keyboard_variant() const;
+
+	virtual void process_events();
+	virtual void force_process_and_drop_events();
+
+	virtual void release_rendering_thread();
+	virtual void make_rendering_thread();
+	virtual void swap_buffers();
+
+	virtual void set_native_icon(const String &p_filename);
+	virtual void set_icon(const Ref<Image> &p_icon);
+
+	virtual void console_set_visible(bool p_enabled);
+	virtual bool is_console_visible() const;
+
+	static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error);
+	static Vector<String> get_rendering_drivers_func();
+
+	static void register_osx_driver();
+
+	DisplayServerOSX(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error);
+	~DisplayServerOSX();
+};
+
+#endif // DISPLAY_SERVER_OSX_H

+ 3587 - 0
platform/osx/display_server_osx.mm

@@ -0,0 +1,3587 @@
+/*************************************************************************/
+/*  display_server_osx.mm                                                */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#include "display_server_osx.h"
+
+#include "os_osx.h"
+
+#include "core/io/marshalls.h"
+#include "core/os/keyboard.h"
+#include "main/main.h"
+#include "scene/resources/texture.h"
+
+#include <Carbon/Carbon.h>
+#include <Cocoa/Cocoa.h>
+#include <IOKit/IOCFPlugIn.h>
+#include <IOKit/IOKitLib.h>
+#include <IOKit/hid/IOHIDKeys.h>
+#include <IOKit/hid/IOHIDLib.h>
+
+#if defined(OPENGL_ENABLED)
+#include "drivers/gles2/rasterizer_gles2.h"
+//TODO - reimplement OpenGLES
+#endif
+
+#if defined(VULKAN_ENABLED)
+#include "servers/visual/rasterizer_rd/rasterizer_rd.h"
+
+#include <QuartzCore/CAMetalLayer.h>
+#endif
+
+#define DS_OSX ((DisplayServerOSX *)(DisplayServerOSX::get_singleton()))
+
+static void _get_key_modifier_state(unsigned int p_osx_state, Ref<InputEventWithModifiers> r_state) {
+	r_state->set_shift((p_osx_state & NSEventModifierFlagShift));
+	r_state->set_control((p_osx_state & NSEventModifierFlagControl));
+	r_state->set_alt((p_osx_state & NSEventModifierFlagOption));
+	r_state->set_metakey((p_osx_state & NSEventModifierFlagCommand));
+}
+
+static Vector2i _get_mouse_pos(DisplayServerOSX::WindowData &p_wd, NSPoint p_locationInWindow, CGFloat p_backingScaleFactor) {
+	const NSRect contentRect = [p_wd.window_view frame];
+	const NSPoint p = p_locationInWindow;
+	p_wd.mouse_pos.x = p.x * p_backingScaleFactor;
+	p_wd.mouse_pos.y = (contentRect.size.height - p.y) * p_backingScaleFactor;
+	DS_OSX->last_mouse_pos = p_wd.mouse_pos;
+	InputFilter::get_singleton()->set_mouse_position(p_wd.mouse_pos);
+	return p_wd.mouse_pos;
+}
+
+static void _push_to_key_event_buffer(const DisplayServerOSX::KeyEvent &p_event) {
+	Vector<DisplayServerOSX::KeyEvent> &buffer = DS_OSX->key_event_buffer;
+	if (DS_OSX->key_event_pos >= buffer.size()) {
+		buffer.resize(1 + DS_OSX->key_event_pos);
+	}
+	buffer.write[DS_OSX->key_event_pos++] = p_event;
+}
+
+static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) {
+	if ([NSCursor respondsToSelector:selector]) {
+		id object = [NSCursor performSelector:selector];
+		if ([object isKindOfClass:[NSCursor class]]) {
+			return object;
+		}
+	}
+	if (fallback) {
+		// Fallback should be a reasonable default, no need to check.
+		return [NSCursor performSelector:fallback];
+	}
+	return [NSCursor arrowCursor];
+}
+
+/*************************************************************************/
+/* GodotApplication                                                      */
+/*************************************************************************/
+
+@interface GodotApplication : NSApplication
+@end
+
+@implementation GodotApplication
+
+- (void)sendEvent:(NSEvent *)event {
+	// special case handling of command-period, which is traditionally a special
+	// shortcut in macOS and doesn't arrive at our regular keyDown handler.
+	if ([event type] == NSEventTypeKeyDown) {
+		if (([event modifierFlags] & NSEventModifierFlagCommand) && [event keyCode] == 0x2f) {
+			Ref<InputEventKey> k;
+			k.instance();
+
+			_get_key_modifier_state([event modifierFlags], k);
+			k->set_window_id(DisplayServerOSX::INVALID_WINDOW_ID);
+			k->set_pressed(true);
+			k->set_keycode(KEY_PERIOD);
+			k->set_physical_keycode(KEY_PERIOD);
+			k->set_echo([event isARepeat]);
+
+			InputFilter::get_singleton()->accumulate_input_event(k);
+		}
+	}
+
+	// From http://cocoadev.com/index.pl?GameKeyboardHandlingAlmost
+	// This works around an AppKit bug, where key up events while holding
+	// down the command key don't get sent to the key window.
+	if ([event type] == NSEventTypeKeyUp && ([event modifierFlags] & NSEventModifierFlagCommand))
+		[[self keyWindow] sendEvent:event];
+	else
+		[super sendEvent:event];
+}
+
+@end
+
+/*************************************************************************/
+/* GlobalMenuItem                                                       */
+/*************************************************************************/
+
+@interface GlobalMenuItem : NSObject {
+@public
+	Callable callback;
+	Variant meta;
+	bool checkable;
+}
+@end
+
+@implementation GlobalMenuItem
+@end
+
+/*************************************************************************/
+/* GodotApplicationDelegate                                              */
+/*************************************************************************/
+
+@interface GodotApplicationDelegate : NSObject
+- (void)forceUnbundledWindowActivationHackStep1;
+- (void)forceUnbundledWindowActivationHackStep2;
+- (void)forceUnbundledWindowActivationHackStep3;
+@end
+
+@implementation GodotApplicationDelegate
+
+- (void)forceUnbundledWindowActivationHackStep1 {
+	// Step1: Switch focus to macOS Dock.
+	// Required to perform step 2, TransformProcessType will fail if app is already the in focus.
+	for (NSRunningApplication *app in [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.dock"]) {
+		[app activateWithOptions:NSApplicationActivateIgnoringOtherApps];
+		break;
+	}
+	[self performSelector:@selector(forceUnbundledWindowActivationHackStep2) withObject:nil afterDelay:0.02];
+}
+
+- (void)forceUnbundledWindowActivationHackStep2 {
+	// Step 2: Register app as foreground process.
+	ProcessSerialNumber psn = { 0, kCurrentProcess };
+	(void)TransformProcessType(&psn, kProcessTransformToForegroundApplication);
+	[self performSelector:@selector(forceUnbundledWindowActivationHackStep3) withObject:nil afterDelay:0.02];
+}
+
+- (void)forceUnbundledWindowActivationHackStep3 {
+	// Step 3: Switch focus back to app window.
+	[[NSRunningApplication currentApplication] activateWithOptions:NSApplicationActivateIgnoringOtherApps];
+}
+
+- (void)applicationDidFinishLaunching:(NSNotification *)notice {
+	NSString *nsappname = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"];
+	if (nsappname == nil) {
+		// If executable is not a bundled, macOS WindowServer won't register and activate app window correctly (menu and title bar are grayed out and input ignored).
+		[self performSelector:@selector(forceUnbundledWindowActivationHackStep1) withObject:nil afterDelay:0.02];
+	}
+}
+
+- (void)globalMenuCallback:(id)sender {
+	if (![sender representedObject])
+		return;
+
+	GlobalMenuItem *value = [sender representedObject];
+
+	if (value) {
+		if (value->checkable) {
+			if ([sender state] == NSControlStateValueOff) {
+				[sender setState:NSControlStateValueOn];
+			} else {
+				[sender setState:NSControlStateValueOff];
+			}
+		}
+
+		if (value->callback != Callable()) {
+			Variant tag = value->meta;
+			Variant *tagp = &tag;
+			Variant ret;
+			Callable::CallError ce;
+			value->callback.call((const Variant **)&tagp, 1, ret, ce);
+		}
+	}
+}
+
+- (NSMenu *)applicationDockMenu:(NSApplication *)sender {
+	return DS_OSX->dock_menu;
+}
+
+- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename {
+	// Note: may be called called before main loop init!
+	char *utfs = strdup([filename UTF8String]);
+	((OS_OSX *)(OS_OSX::get_singleton()))->open_with_filename.parse_utf8(utfs);
+	free(utfs);
+
+#ifdef TOOLS_ENABLED
+	// Open new instance
+	if (OS_OSX::get_singleton()->get_main_loop()) {
+		List<String> args;
+		args.push_back(((OS_OSX *)(OS_OSX::get_singleton()))->open_with_filename);
+		String exec = OS::get_singleton()->get_executable_path();
+
+		OS::ProcessID pid = 0;
+		OS::get_singleton()->execute(exec, args, false, &pid);
+	}
+#endif
+	return YES;
+}
+
+- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
+	DS_OSX->_send_window_event(DS_OSX->windows[DisplayServerOSX::MAIN_WINDOW_ID], DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST);
+	return NSTerminateCancel;
+}
+
+- (void)showAbout:(id)sender {
+	if (OS_OSX::get_singleton()->get_main_loop())
+		OS_OSX::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_ABOUT);
+}
+
+@end
+
+/*************************************************************************/
+/* GodotWindowDelegate                                                   */
+/*************************************************************************/
+
+@interface GodotWindowDelegate : NSObject {
+	DisplayServerOSX::WindowID window_id;
+}
+
+- (void)windowWillClose:(NSNotification *)notification;
+- (void)setWindowID:(DisplayServerOSX::WindowID)wid;
+
+@end
+
+@implementation GodotWindowDelegate
+
+- (void)setWindowID:(DisplayServerOSX::WindowID)wid {
+	window_id = wid;
+}
+
+- (BOOL)windowShouldClose:(id)sender {
+	ERR_FAIL_COND_V(!DS_OSX->windows.has(window_id), YES);
+	DS_OSX->_send_window_event(DS_OSX->windows[window_id], DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST);
+	return NO;
+}
+
+- (void)windowWillClose:(NSNotification *)notification {
+	ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
+	DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+	while (wd.transient_children.size()) {
+		DS_OSX->window_set_transient(wd.transient_children.front()->get(), DisplayServerOSX::INVALID_WINDOW_ID);
+	}
+
+	if (wd.transient_parent != DisplayServerOSX::INVALID_WINDOW_ID) {
+		DS_OSX->window_set_transient(window_id, DisplayServerOSX::INVALID_WINDOW_ID);
+	}
+
+#ifdef VULKAN_ENABLED
+	if (DS_OSX->rendering_driver == "vulkan") {
+		DS_OSX->context_vulkan->window_destroy(window_id);
+	}
+#endif
+	DS_OSX->windows.erase(window_id);
+}
+
+- (void)windowDidEnterFullScreen:(NSNotification *)notification {
+	ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
+	DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+	wd.fullscreen = true;
+
+	[wd.window_object setContentMinSize:NSMakeSize(0, 0)];
+	[wd.window_object setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)];
+}
+
+- (void)windowDidExitFullScreen:(NSNotification *)notification {
+	if (!DS_OSX || !DS_OSX->windows.has(window_id))
+		return;
+	DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+	wd.fullscreen = false;
+
+	if (wd.min_size != Size2i()) {
+		Size2i size = wd.min_size / DS_OSX->_display_scale([wd.window_object screen]);
+		[wd.window_object setContentMinSize:NSMakeSize(size.x, size.y)];
+	}
+	if (wd.max_size != Size2i()) {
+		Size2i size = wd.max_size / DS_OSX->_display_scale([wd.window_object screen]);
+		[wd.window_object setContentMaxSize:NSMakeSize(size.x, size.y)];
+	}
+
+	if (wd.resize_disabled)
+		[wd.window_object setStyleMask:[wd.window_object styleMask] & ~NSWindowStyleMaskResizable];
+}
+
+- (void)windowDidChangeBackingProperties:(NSNotification *)notification {
+	if (!DisplayServerOSX::get_singleton())
+		return;
+
+	ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
+	DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+	CGFloat newBackingScaleFactor = [wd.window_object backingScaleFactor];
+	CGFloat oldBackingScaleFactor = [[[notification userInfo] objectForKey:@"NSBackingPropertyOldScaleFactorKey"] doubleValue];
+
+#if defined(OPENGL_ENABLED)
+	if (DS_OSX->rendering_driver == "opengl_es") {
+		//TODO - reimplement OpenGLES
+		if (OS_OSX::get_singleton()->is_hidpi_allowed()) {
+			[wd.window_view setWantsBestResolutionOpenGLSurface:YES];
+		} else {
+			[wd.window_view setWantsBestResolutionOpenGLSurface:NO];
+		}
+	}
+#endif
+
+	if (newBackingScaleFactor != oldBackingScaleFactor) {
+		//Set new display scale and window size
+		float newDisplayScale = OS_OSX::get_singleton()->is_hidpi_allowed() ? newBackingScaleFactor : 1.0;
+
+		const NSRect contentRect = [wd.window_view frame];
+
+		wd.size.width = contentRect.size.width * newDisplayScale;
+		wd.size.height = contentRect.size.height * newDisplayScale;
+
+		DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_DPI_CHANGE);
+
+#if defined(VULKAN_ENABLED)
+		if (DS_OSX->rendering_driver == "vulkan") {
+			CALayer *layer = [wd.window_view layer];
+			layer.contentsScale = DS_OSX->_display_scale([wd.window_object screen]);
+		}
+#endif
+		//Force window resize event
+		[self windowDidResize:notification];
+	}
+}
+
+- (void)windowDidResize:(NSNotification *)notification {
+	if (!DS_OSX || !DS_OSX->windows.has(window_id))
+		return;
+	DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+#if defined(OPENGL_ENABLED)
+	if (DS_OSX->rendering_driver == "opengl_es") {
+		//TODO - reimplement OpenGLES
+		wd.context_gles2->update();
+	}
+#endif
+	const NSRect contentRect = [wd.window_view frame];
+
+	float displayScale = DS_OSX->_display_scale([wd.window_object screen]);
+	wd.size.width = contentRect.size.width * displayScale;
+	wd.size.height = contentRect.size.height * displayScale;
+
+#if defined(VULKAN_ENABLED)
+	if (DS_OSX->rendering_driver == "vulkan") {
+		CALayer *layer = [wd.window_view layer];
+		layer.contentsScale = displayScale;
+		DS_OSX->context_vulkan->window_resize(window_id, wd.size.width, wd.size.height);
+	}
+#endif
+
+	if (!wd.rect_changed_callback.is_null()) {
+		Variant size = Rect2i(DS_OSX->window_get_position(window_id), DS_OSX->window_get_size(window_id));
+		Variant *sizep = &size;
+		Variant ret;
+		Callable::CallError ce;
+		wd.rect_changed_callback.call((const Variant **)&sizep, 1, ret, ce);
+	}
+
+	if (OS_OSX::get_singleton()->get_main_loop()) {
+		Main::force_redraw();
+		//Event retrieval blocks until resize is over. Call Main::iteration() directly.
+		if (!Main::is_iterating()) { //avoid cyclic loop
+			Main::iteration();
+		}
+	}
+}
+
+- (void)windowDidMove:(NSNotification *)notification {
+	if (InputFilter::get_singleton()) {
+		InputFilter::get_singleton()->release_pressed_events();
+	}
+}
+
+- (void)windowDidBecomeKey:(NSNotification *)notification {
+	ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
+	DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+	const CGFloat backingScaleFactor = (OS::get_singleton()->is_hidpi_allowed()) ? [wd.window_view backingScaleFactor] : 1.0;
+	_get_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream], backingScaleFactor);
+	InputFilter::get_singleton()->set_mouse_position(wd.mouse_pos);
+
+	DS_OSX->window_focused = true;
+	DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_FOCUS_IN);
+}
+
+- (void)windowDidResignKey:(NSNotification *)notification {
+	ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
+	DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+	DS_OSX->window_focused = false;
+
+	InputFilter::get_singleton()->release_pressed_events();
+	DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_FOCUS_OUT);
+}
+
+- (void)windowDidMiniaturize:(NSNotification *)notification {
+	ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
+	DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+	DS_OSX->window_focused = false;
+
+	InputFilter::get_singleton()->release_pressed_events();
+	DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_FOCUS_OUT);
+}
+
+- (void)windowDidDeminiaturize:(NSNotification *)notification {
+	ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
+	DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+	DS_OSX->window_focused = true;
+	DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_FOCUS_IN);
+}
+
+@end
+
+/*************************************************************************/
+/* GodotContentView                                                      */
+/*************************************************************************/
+
+@interface GodotContentView : NSView <NSTextInputClient> {
+	DisplayServerOSX::WindowID window_id;
+	NSTrackingArea *trackingArea;
+	NSMutableAttributedString *markedText;
+	bool imeInputEventInProgress;
+}
+
+- (void)cancelComposition;
+- (CALayer *)makeBackingLayer;
+- (BOOL)wantsUpdateLayer;
+- (void)updateLayer;
+- (void)setWindowID:(DisplayServerOSX::WindowID)wid;
+
+@end
+
+@implementation GodotContentView
+
+- (void)setWindowID:(DisplayServerOSX::WindowID)wid {
+	window_id = wid;
+}
+
++ (void)initialize {
+	if (self == [GodotContentView class]) {
+		// nothing left to do here at the moment..
+	}
+}
+
+- (CALayer *)makeBackingLayer {
+#if defined(VULKAN_ENABLED)
+	if (DS_OSX->rendering_driver == "vulkan") {
+		CALayer *layer = [[CAMetalLayer class] layer];
+		return layer;
+	}
+#endif
+	return [super makeBackingLayer];
+}
+
+- (void)updateLayer {
+#if defined(VULKAN_ENABLED)
+	if (DS_OSX->rendering_driver == "vulkan") {
+		[super updateLayer];
+	}
+#endif
+#if defined(OPENGL_ENABLED)
+	if (DS_OSX->rendering_driver == "opengl_es") {
+		ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
+		DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+		wd.context_gles2->update();
+		//TODO - reimplement OpenGLES
+	}
+#endif
+}
+
+- (BOOL)wantsUpdateLayer {
+	return YES;
+}
+
+- (id)init {
+	self = [super init];
+	trackingArea = nil;
+	imeInputEventInProgress = false;
+	[self updateTrackingAreas];
+	[self registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]];
+	markedText = [[NSMutableAttributedString alloc] init];
+	return self;
+}
+
+- (void)dealloc {
+	[trackingArea release];
+	[markedText release];
+	[super dealloc];
+}
+
+static const NSRange kEmptyRange = { NSNotFound, 0 };
+
+- (BOOL)hasMarkedText {
+	return (markedText.length > 0);
+}
+
+- (NSRange)markedRange {
+	return NSMakeRange(0, markedText.length);
+}
+
+- (NSRange)selectedRange {
+	return kEmptyRange;
+}
+
+- (void)setMarkedText:(id)aString selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange {
+	if ([aString isKindOfClass:[NSAttributedString class]]) {
+		[markedText initWithAttributedString:aString];
+	} else {
+		[markedText initWithString:aString];
+	}
+	if (markedText.length == 0) {
+		[self unmarkText];
+		return;
+	}
+
+	ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
+	DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+	if (wd.im_active) {
+		imeInputEventInProgress = true;
+		DS_OSX->im_text.parse_utf8([[markedText mutableString] UTF8String]);
+		DS_OSX->im_selection = Point2i(selectedRange.location, selectedRange.length);
+
+		OS_OSX::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE);
+	}
+}
+
+- (void)doCommandBySelector:(SEL)aSelector {
+	if ([self respondsToSelector:aSelector])
+		[self performSelector:aSelector];
+}
+
+- (void)unmarkText {
+	imeInputEventInProgress = false;
+	[[markedText mutableString] setString:@""];
+
+	ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
+	DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+	if (wd.im_active) {
+		DS_OSX->im_text = String();
+		DS_OSX->im_selection = Point2i();
+
+		OS_OSX::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE);
+	}
+}
+
+- (NSArray *)validAttributesForMarkedText {
+	return [NSArray array];
+}
+
+- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange {
+	return nil;
+}
+
+- (NSUInteger)characterIndexForPoint:(NSPoint)aPoint {
+	return 0;
+}
+
+- (NSRect)firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange {
+	ERR_FAIL_COND_V(!DS_OSX->windows.has(window_id), NSMakeRect(0, 0, 0, 0));
+	DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+	const NSRect contentRect = [wd.window_view frame];
+	float displayScale = DS_OSX->_display_scale([wd.window_object screen]);
+	NSRect pointInWindowRect = NSMakeRect(wd.im_position.x / displayScale, contentRect.size.height - (wd.im_position.y / displayScale) - 1, 0, 0);
+	NSPoint pointOnScreen = [wd.window_object convertRectToScreen:pointInWindowRect].origin;
+
+	return NSMakeRect(pointOnScreen.x, pointOnScreen.y, 0, 0);
+}
+
+- (void)cancelComposition {
+	[self unmarkText];
+	NSTextInputContext *currentInputContext = [NSTextInputContext currentInputContext];
+	[currentInputContext discardMarkedText];
+}
+
+- (void)insertText:(id)aString {
+	[self insertText:aString replacementRange:NSMakeRange(0, 0)];
+}
+
+- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange {
+	NSEvent *event = [NSApp currentEvent];
+
+	NSString *characters;
+	if ([aString isKindOfClass:[NSAttributedString class]]) {
+		characters = [aString string];
+	} else {
+		characters = (NSString *)aString;
+	}
+
+	NSUInteger i, length = [characters length];
+
+	NSCharacterSet *ctrlChars = [NSCharacterSet controlCharacterSet];
+	NSCharacterSet *wsnlChars = [NSCharacterSet whitespaceAndNewlineCharacterSet];
+	if ([characters rangeOfCharacterFromSet:ctrlChars].length && [characters rangeOfCharacterFromSet:wsnlChars].length == 0) {
+		NSTextInputContext *currentInputContext = [NSTextInputContext currentInputContext];
+		[currentInputContext discardMarkedText];
+		[self cancelComposition];
+		return;
+	}
+
+	for (i = 0; i < length; i++) {
+		const unichar codepoint = [characters characterAtIndex:i];
+		if ((codepoint & 0xFF00) == 0xF700)
+			continue;
+
+		DisplayServerOSX::KeyEvent ke;
+
+		ke.window_id = window_id;
+		ke.osx_state = [event modifierFlags];
+		ke.pressed = true;
+		ke.echo = false;
+		ke.raw = false; // IME input event
+		ke.keycode = 0;
+		ke.physical_keycode = 0;
+		ke.unicode = codepoint;
+
+		_push_to_key_event_buffer(ke);
+	}
+	[self cancelComposition];
+}
+
+- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender {
+	return NSDragOperationCopy;
+}
+
+- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender {
+	return NSDragOperationCopy;
+}
+
+- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender {
+	ERR_FAIL_COND_V(!DS_OSX->windows.has(window_id), NO);
+	DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+	NSPasteboard *pboard = [sender draggingPasteboard];
+	NSArray *filenames = [pboard propertyListForType:NSFilenamesPboardType];
+
+	Vector<String> files;
+	for (NSUInteger i = 0; i < filenames.count; i++) {
+		NSString *ns = [filenames objectAtIndex:i];
+		char *utfs = strdup([ns UTF8String]);
+		String ret;
+		ret.parse_utf8(utfs);
+		free(utfs);
+		files.push_back(ret);
+	}
+
+	if (!wd.drop_files_callback.is_null()) {
+		Variant v = files;
+		Variant *vp = &v;
+		Variant ret;
+		Callable::CallError ce;
+		wd.drop_files_callback.call((const Variant **)&vp, 1, ret, ce);
+	}
+
+	return NO;
+}
+
+- (BOOL)isOpaque {
+	return YES;
+}
+
+- (BOOL)canBecomeKeyView {
+	return YES;
+}
+
+- (BOOL)acceptsFirstResponder {
+	return YES;
+}
+
+- (void)cursorUpdate:(NSEvent *)event {
+	DisplayServer::CursorShape p_shape = DS_OSX->cursor_shape;
+	DS_OSX->cursor_shape = DisplayServer::CURSOR_MAX;
+	DS_OSX->cursor_set_shape(p_shape);
+}
+
+static void _mouseDownEvent(DisplayServer::WindowID window_id, NSEvent *event, int index, int mask, bool pressed) {
+	ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
+	DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+	if (pressed) {
+		DS_OSX->last_button_state |= mask;
+	} else {
+		DS_OSX->last_button_state &= ~mask;
+	}
+
+	Ref<InputEventMouseButton> mb;
+	mb.instance();
+	mb->set_window_id(window_id);
+	const CGFloat backingScaleFactor = (OS::get_singleton()->is_hidpi_allowed()) ? [[event window] backingScaleFactor] : 1.0;
+	const Vector2 pos = _get_mouse_pos(wd, [event locationInWindow], backingScaleFactor);
+	_get_key_modifier_state([event modifierFlags], mb);
+	mb->set_button_index(index);
+	mb->set_pressed(pressed);
+	mb->set_position(pos);
+	mb->set_global_position(pos);
+	mb->set_button_mask(DS_OSX->last_button_state);
+	if (index == BUTTON_LEFT && pressed) {
+		mb->set_doubleclick([event clickCount] == 2);
+	}
+
+	InputFilter::get_singleton()->accumulate_input_event(mb);
+}
+
+- (void)mouseDown:(NSEvent *)event {
+	ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
+	DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+	if (([event modifierFlags] & NSEventModifierFlagControl)) {
+		wd.mouse_down_control = true;
+		_mouseDownEvent(window_id, event, BUTTON_RIGHT, BUTTON_MASK_RIGHT, true);
+	} else {
+		wd.mouse_down_control = false;
+		_mouseDownEvent(window_id, event, BUTTON_LEFT, BUTTON_MASK_LEFT, true);
+	}
+}
+
+- (void)mouseDragged:(NSEvent *)event {
+	[self mouseMoved:event];
+}
+
+- (void)mouseUp:(NSEvent *)event {
+	ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
+	DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+	if (wd.mouse_down_control) {
+		_mouseDownEvent(window_id, event, BUTTON_RIGHT, BUTTON_MASK_RIGHT, false);
+	} else {
+		_mouseDownEvent(window_id, event, BUTTON_LEFT, BUTTON_MASK_LEFT, false);
+	}
+}
+
+- (void)mouseMoved:(NSEvent *)event {
+	ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
+	DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+	Ref<InputEventMouseMotion> mm;
+	mm.instance();
+
+	mm->set_window_id(window_id);
+	mm->set_button_mask(DS_OSX->last_button_state);
+	const CGFloat backingScaleFactor = (OS::get_singleton()->is_hidpi_allowed()) ? [[event window] backingScaleFactor] : 1.0;
+	const Vector2i pos = _get_mouse_pos(wd, [event locationInWindow], backingScaleFactor);
+	mm->set_position(pos);
+	mm->set_pressure([event pressure]);
+	if ([event subtype] == NSEventSubtypeTabletPoint) {
+		const NSPoint p = [event tilt];
+		mm->set_tilt(Vector2(p.x, p.y));
+	}
+	mm->set_global_position(pos);
+	mm->set_speed(InputFilter::get_singleton()->get_last_mouse_speed());
+	Vector2i relativeMotion = Vector2i();
+	relativeMotion.x = [event deltaX] * backingScaleFactor;
+	relativeMotion.y = [event deltaY] * backingScaleFactor;
+	mm->set_relative(relativeMotion);
+	_get_key_modifier_state([event modifierFlags], mm);
+
+	InputFilter::get_singleton()->set_mouse_position(wd.mouse_pos);
+	InputFilter::get_singleton()->accumulate_input_event(mm);
+}
+
+- (void)rightMouseDown:(NSEvent *)event {
+	_mouseDownEvent(window_id, event, BUTTON_RIGHT, BUTTON_MASK_RIGHT, true);
+}
+
+- (void)rightMouseDragged:(NSEvent *)event {
+	[self mouseMoved:event];
+}
+
+- (void)rightMouseUp:(NSEvent *)event {
+	_mouseDownEvent(window_id, event, BUTTON_RIGHT, BUTTON_MASK_RIGHT, false);
+}
+
+- (void)otherMouseDown:(NSEvent *)event {
+	if ((int)[event buttonNumber] == 2) {
+		_mouseDownEvent(window_id, event, BUTTON_MIDDLE, BUTTON_MASK_MIDDLE, true);
+	} else if ((int)[event buttonNumber] == 3) {
+		_mouseDownEvent(window_id, event, BUTTON_XBUTTON1, BUTTON_MASK_XBUTTON1, true);
+	} else if ((int)[event buttonNumber] == 4) {
+		_mouseDownEvent(window_id, event, BUTTON_XBUTTON2, BUTTON_MASK_XBUTTON2, true);
+	} else {
+		return;
+	}
+}
+
+- (void)otherMouseDragged:(NSEvent *)event {
+	[self mouseMoved:event];
+}
+
+- (void)otherMouseUp:(NSEvent *)event {
+	if ((int)[event buttonNumber] == 2) {
+		_mouseDownEvent(window_id, event, BUTTON_MIDDLE, BUTTON_MASK_MIDDLE, false);
+	} else if ((int)[event buttonNumber] == 3) {
+		_mouseDownEvent(window_id, event, BUTTON_XBUTTON1, BUTTON_MASK_XBUTTON1, false);
+	} else if ((int)[event buttonNumber] == 4) {
+		_mouseDownEvent(window_id, event, BUTTON_XBUTTON2, BUTTON_MASK_XBUTTON2, false);
+	} else {
+		return;
+	}
+}
+
+- (void)mouseExited:(NSEvent *)event {
+	ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
+	DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+	if (DS_OSX->mouse_mode != DisplayServer::MOUSE_MODE_CAPTURED)
+		DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_MOUSE_EXIT);
+}
+
+- (void)mouseEntered:(NSEvent *)event {
+	ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
+	DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+	if (DS_OSX->mouse_mode != DisplayServer::MOUSE_MODE_CAPTURED)
+		DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_MOUSE_ENTER);
+
+	DisplayServer::CursorShape p_shape = DS_OSX->cursor_shape;
+	DS_OSX->cursor_shape = DisplayServer::CURSOR_MAX;
+	DS_OSX->cursor_set_shape(p_shape);
+}
+
+- (void)magnifyWithEvent:(NSEvent *)event {
+	ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
+	DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+	Ref<InputEventMagnifyGesture> ev;
+	ev.instance();
+	ev->set_window_id(window_id);
+	_get_key_modifier_state([event modifierFlags], ev);
+	const CGFloat backingScaleFactor = (OS::get_singleton()->is_hidpi_allowed()) ? [[event window] backingScaleFactor] : 1.0;
+	ev->set_position(_get_mouse_pos(wd, [event locationInWindow], backingScaleFactor));
+	ev->set_factor([event magnification] + 1.0);
+
+	InputFilter::get_singleton()->accumulate_input_event(ev);
+}
+
+- (void)viewDidChangeBackingProperties {
+	// nothing left to do here
+}
+
+- (void)updateTrackingAreas {
+	if (trackingArea != nil) {
+		[self removeTrackingArea:trackingArea];
+		[trackingArea release];
+	}
+
+	NSTrackingAreaOptions options =
+			NSTrackingMouseEnteredAndExited |
+			NSTrackingActiveInKeyWindow |
+			NSTrackingCursorUpdate |
+			NSTrackingInVisibleRect;
+
+	trackingArea = [[NSTrackingArea alloc]
+			initWithRect:[self bounds]
+				 options:options
+				   owner:self
+				userInfo:nil];
+
+	[self addTrackingArea:trackingArea];
+	[super updateTrackingAreas];
+}
+
+static bool isNumpadKey(unsigned int key) {
+
+	static const unsigned int table[] = {
+		0x41, /* kVK_ANSI_KeypadDecimal */
+		0x43, /* kVK_ANSI_KeypadMultiply */
+		0x45, /* kVK_ANSI_KeypadPlus */
+		0x47, /* kVK_ANSI_KeypadClear */
+		0x4b, /* kVK_ANSI_KeypadDivide */
+		0x4c, /* kVK_ANSI_KeypadEnter */
+		0x4e, /* kVK_ANSI_KeypadMinus */
+		0x51, /* kVK_ANSI_KeypadEquals */
+		0x52, /* kVK_ANSI_Keypad0 */
+		0x53, /* kVK_ANSI_Keypad1 */
+		0x54, /* kVK_ANSI_Keypad2 */
+		0x55, /* kVK_ANSI_Keypad3 */
+		0x56, /* kVK_ANSI_Keypad4 */
+		0x57, /* kVK_ANSI_Keypad5 */
+		0x58, /* kVK_ANSI_Keypad6 */
+		0x59, /* kVK_ANSI_Keypad7 */
+		0x5b, /* kVK_ANSI_Keypad8 */
+		0x5c, /* kVK_ANSI_Keypad9 */
+		0x5f, /* kVK_JIS_KeypadComma */
+		0x00
+	};
+	for (int i = 0; table[i] != 0; i++) {
+		if (key == table[i])
+			return true;
+	}
+	return false;
+}
+
+// Translates a OS X keycode to a Godot keycode
+//
+static int translateKey(unsigned int key) {
+
+	// Keyboard symbol translation table
+	static const unsigned int table[128] = {
+		/* 00 */ KEY_A,
+		/* 01 */ KEY_S,
+		/* 02 */ KEY_D,
+		/* 03 */ KEY_F,
+		/* 04 */ KEY_H,
+		/* 05 */ KEY_G,
+		/* 06 */ KEY_Z,
+		/* 07 */ KEY_X,
+		/* 08 */ KEY_C,
+		/* 09 */ KEY_V,
+		/* 0a */ KEY_SECTION, /* ISO Section */
+		/* 0b */ KEY_B,
+		/* 0c */ KEY_Q,
+		/* 0d */ KEY_W,
+		/* 0e */ KEY_E,
+		/* 0f */ KEY_R,
+		/* 10 */ KEY_Y,
+		/* 11 */ KEY_T,
+		/* 12 */ KEY_1,
+		/* 13 */ KEY_2,
+		/* 14 */ KEY_3,
+		/* 15 */ KEY_4,
+		/* 16 */ KEY_6,
+		/* 17 */ KEY_5,
+		/* 18 */ KEY_EQUAL,
+		/* 19 */ KEY_9,
+		/* 1a */ KEY_7,
+		/* 1b */ KEY_MINUS,
+		/* 1c */ KEY_8,
+		/* 1d */ KEY_0,
+		/* 1e */ KEY_BRACERIGHT,
+		/* 1f */ KEY_O,
+		/* 20 */ KEY_U,
+		/* 21 */ KEY_BRACELEFT,
+		/* 22 */ KEY_I,
+		/* 23 */ KEY_P,
+		/* 24 */ KEY_ENTER,
+		/* 25 */ KEY_L,
+		/* 26 */ KEY_J,
+		/* 27 */ KEY_APOSTROPHE,
+		/* 28 */ KEY_K,
+		/* 29 */ KEY_SEMICOLON,
+		/* 2a */ KEY_BACKSLASH,
+		/* 2b */ KEY_COMMA,
+		/* 2c */ KEY_SLASH,
+		/* 2d */ KEY_N,
+		/* 2e */ KEY_M,
+		/* 2f */ KEY_PERIOD,
+		/* 30 */ KEY_TAB,
+		/* 31 */ KEY_SPACE,
+		/* 32 */ KEY_QUOTELEFT,
+		/* 33 */ KEY_BACKSPACE,
+		/* 34 */ KEY_UNKNOWN,
+		/* 35 */ KEY_ESCAPE,
+		/* 36 */ KEY_META,
+		/* 37 */ KEY_META,
+		/* 38 */ KEY_SHIFT,
+		/* 39 */ KEY_CAPSLOCK,
+		/* 3a */ KEY_ALT,
+		/* 3b */ KEY_CONTROL,
+		/* 3c */ KEY_SHIFT,
+		/* 3d */ KEY_ALT,
+		/* 3e */ KEY_CONTROL,
+		/* 3f */ KEY_UNKNOWN, /* Function */
+		/* 40 */ KEY_UNKNOWN, /* F17 */
+		/* 41 */ KEY_KP_PERIOD,
+		/* 42 */ KEY_UNKNOWN,
+		/* 43 */ KEY_KP_MULTIPLY,
+		/* 44 */ KEY_UNKNOWN,
+		/* 45 */ KEY_KP_ADD,
+		/* 46 */ KEY_UNKNOWN,
+		/* 47 */ KEY_NUMLOCK, /* Really KeypadClear... */
+		/* 48 */ KEY_VOLUMEUP, /* VolumeUp */
+		/* 49 */ KEY_VOLUMEDOWN, /* VolumeDown */
+		/* 4a */ KEY_VOLUMEMUTE, /* Mute */
+		/* 4b */ KEY_KP_DIVIDE,
+		/* 4c */ KEY_KP_ENTER,
+		/* 4d */ KEY_UNKNOWN,
+		/* 4e */ KEY_KP_SUBTRACT,
+		/* 4f */ KEY_UNKNOWN, /* F18 */
+		/* 50 */ KEY_UNKNOWN, /* F19 */
+		/* 51 */ KEY_EQUAL, /* KeypadEqual */
+		/* 52 */ KEY_KP_0,
+		/* 53 */ KEY_KP_1,
+		/* 54 */ KEY_KP_2,
+		/* 55 */ KEY_KP_3,
+		/* 56 */ KEY_KP_4,
+		/* 57 */ KEY_KP_5,
+		/* 58 */ KEY_KP_6,
+		/* 59 */ KEY_KP_7,
+		/* 5a */ KEY_UNKNOWN, /* F20 */
+		/* 5b */ KEY_KP_8,
+		/* 5c */ KEY_KP_9,
+		/* 5d */ KEY_YEN, /* JIS Yen */
+		/* 5e */ KEY_UNDERSCORE, /* JIS Underscore */
+		/* 5f */ KEY_COMMA, /* JIS KeypadComma */
+		/* 60 */ KEY_F5,
+		/* 61 */ KEY_F6,
+		/* 62 */ KEY_F7,
+		/* 63 */ KEY_F3,
+		/* 64 */ KEY_F8,
+		/* 65 */ KEY_F9,
+		/* 66 */ KEY_UNKNOWN, /* JIS Eisu */
+		/* 67 */ KEY_F11,
+		/* 68 */ KEY_UNKNOWN, /* JIS Kana */
+		/* 69 */ KEY_F13,
+		/* 6a */ KEY_F16,
+		/* 6b */ KEY_F14,
+		/* 6c */ KEY_UNKNOWN,
+		/* 6d */ KEY_F10,
+		/* 6e */ KEY_MENU,
+		/* 6f */ KEY_F12,
+		/* 70 */ KEY_UNKNOWN,
+		/* 71 */ KEY_F15,
+		/* 72 */ KEY_INSERT, /* Really Help... */
+		/* 73 */ KEY_HOME,
+		/* 74 */ KEY_PAGEUP,
+		/* 75 */ KEY_DELETE,
+		/* 76 */ KEY_F4,
+		/* 77 */ KEY_END,
+		/* 78 */ KEY_F2,
+		/* 79 */ KEY_PAGEDOWN,
+		/* 7a */ KEY_F1,
+		/* 7b */ KEY_LEFT,
+		/* 7c */ KEY_RIGHT,
+		/* 7d */ KEY_DOWN,
+		/* 7e */ KEY_UP,
+		/* 7f */ KEY_UNKNOWN,
+	};
+
+	if (key >= 128)
+		return KEY_UNKNOWN;
+
+	return table[key];
+}
+
+struct _KeyCodeMap {
+	UniChar kchar;
+	int kcode;
+};
+
+static const _KeyCodeMap _keycodes[55] = {
+	{ '`', KEY_QUOTELEFT },
+	{ '~', KEY_ASCIITILDE },
+	{ '0', KEY_0 },
+	{ '1', KEY_1 },
+	{ '2', KEY_2 },
+	{ '3', KEY_3 },
+	{ '4', KEY_4 },
+	{ '5', KEY_5 },
+	{ '6', KEY_6 },
+	{ '7', KEY_7 },
+	{ '8', KEY_8 },
+	{ '9', KEY_9 },
+	{ '-', KEY_MINUS },
+	{ '_', KEY_UNDERSCORE },
+	{ '=', KEY_EQUAL },
+	{ '+', KEY_PLUS },
+	{ 'q', KEY_Q },
+	{ 'w', KEY_W },
+	{ 'e', KEY_E },
+	{ 'r', KEY_R },
+	{ 't', KEY_T },
+	{ 'y', KEY_Y },
+	{ 'u', KEY_U },
+	{ 'i', KEY_I },
+	{ 'o', KEY_O },
+	{ 'p', KEY_P },
+	{ '[', KEY_BRACELEFT },
+	{ ']', KEY_BRACERIGHT },
+	{ '{', KEY_BRACELEFT },
+	{ '}', KEY_BRACERIGHT },
+	{ 'a', KEY_A },
+	{ 's', KEY_S },
+	{ 'd', KEY_D },
+	{ 'f', KEY_F },
+	{ 'g', KEY_G },
+	{ 'h', KEY_H },
+	{ 'j', KEY_J },
+	{ 'k', KEY_K },
+	{ 'l', KEY_L },
+	{ ';', KEY_SEMICOLON },
+	{ ':', KEY_COLON },
+	{ '\'', KEY_APOSTROPHE },
+	{ '\"', KEY_QUOTEDBL },
+	{ '\\', KEY_BACKSLASH },
+	{ '#', KEY_NUMBERSIGN },
+	{ 'z', KEY_Z },
+	{ 'x', KEY_X },
+	{ 'c', KEY_C },
+	{ 'v', KEY_V },
+	{ 'b', KEY_B },
+	{ 'n', KEY_N },
+	{ 'm', KEY_M },
+	{ ',', KEY_COMMA },
+	{ '.', KEY_PERIOD },
+	{ '/', KEY_SLASH }
+};
+
+static int remapKey(unsigned int key, unsigned int state) {
+	if (isNumpadKey(key))
+		return translateKey(key);
+
+	TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource();
+	if (!currentKeyboard)
+		return translateKey(key);
+
+	CFDataRef layoutData = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData);
+	if (!layoutData)
+		return translateKey(key);
+
+	const UCKeyboardLayout *keyboardLayout = (const UCKeyboardLayout *)CFDataGetBytePtr(layoutData);
+
+	UInt32 keysDown = 0;
+	UniChar chars[4];
+	UniCharCount realLength;
+
+	OSStatus err = UCKeyTranslate(keyboardLayout,
+			key,
+			kUCKeyActionDisplay,
+			(state >> 8) & 0xFF,
+			LMGetKbdType(),
+			kUCKeyTranslateNoDeadKeysBit,
+			&keysDown,
+			sizeof(chars) / sizeof(chars[0]),
+			&realLength,
+			chars);
+
+	if (err != noErr) {
+		return translateKey(key);
+	}
+
+	for (unsigned int i = 0; i < 55; i++) {
+		if (_keycodes[i].kchar == chars[0]) {
+			return _keycodes[i].kcode;
+		}
+	}
+	return translateKey(key);
+}
+
+- (void)keyDown:(NSEvent *)event {
+	ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
+	DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+	// Ignore all input if IME input is in progress
+	if (!imeInputEventInProgress) {
+		NSString *characters = [event characters];
+		NSUInteger length = [characters length];
+
+		if (!wd.im_active && length > 0 && keycode_has_unicode(remapKey([event keyCode], [event modifierFlags]))) {
+			// Fallback unicode character handler used if IME is not active
+			for (NSUInteger i = 0; i < length; i++) {
+				DisplayServerOSX::KeyEvent ke;
+
+				ke.window_id = window_id;
+				ke.osx_state = [event modifierFlags];
+				ke.pressed = true;
+				ke.echo = [event isARepeat];
+				ke.keycode = remapKey([event keyCode], [event modifierFlags]);
+				ke.physical_keycode = translateKey([event keyCode]);
+				ke.raw = true;
+				ke.unicode = [characters characterAtIndex:i];
+
+				_push_to_key_event_buffer(ke);
+			}
+		} else {
+			DisplayServerOSX::KeyEvent ke;
+
+			ke.window_id = window_id;
+			ke.osx_state = [event modifierFlags];
+			ke.pressed = true;
+			ke.echo = [event isARepeat];
+			ke.keycode = remapKey([event keyCode], [event modifierFlags]);
+			ke.physical_keycode = translateKey([event keyCode]);
+			ke.raw = false;
+			ke.unicode = 0;
+
+			_push_to_key_event_buffer(ke);
+		}
+	}
+
+	// Pass events to IME handler
+	if (wd.im_active)
+		[self interpretKeyEvents:[NSArray arrayWithObject:event]];
+}
+
+- (void)flagsChanged:(NSEvent *)event {
+	// Ignore all input if IME input is in progress
+	if (!imeInputEventInProgress) {
+		DisplayServerOSX::KeyEvent ke;
+
+		ke.window_id = window_id;
+		ke.echo = false;
+		ke.raw = true;
+
+		int key = [event keyCode];
+		int mod = [event modifierFlags];
+
+		if (key == 0x36 || key == 0x37) {
+			if (mod & NSEventModifierFlagCommand) {
+				mod &= ~NSEventModifierFlagCommand;
+				ke.pressed = true;
+			} else {
+				ke.pressed = false;
+			}
+		} else if (key == 0x38 || key == 0x3c) {
+			if (mod & NSEventModifierFlagShift) {
+				mod &= ~NSEventModifierFlagShift;
+				ke.pressed = true;
+			} else {
+				ke.pressed = false;
+			}
+		} else if (key == 0x3a || key == 0x3d) {
+			if (mod & NSEventModifierFlagOption) {
+				mod &= ~NSEventModifierFlagOption;
+				ke.pressed = true;
+			} else {
+				ke.pressed = false;
+			}
+		} else if (key == 0x3b || key == 0x3e) {
+			if (mod & NSEventModifierFlagControl) {
+				mod &= ~NSEventModifierFlagControl;
+				ke.pressed = true;
+			} else {
+				ke.pressed = false;
+			}
+		} else {
+			return;
+		}
+
+		ke.osx_state = mod;
+		ke.keycode = remapKey(key, mod);
+		ke.physical_keycode = translateKey(key);
+		ke.unicode = 0;
+
+		_push_to_key_event_buffer(ke);
+	}
+}
+
+- (void)keyUp:(NSEvent *)event {
+	ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
+	DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+	// Ignore all input if IME input is in progress
+	if (!imeInputEventInProgress) {
+		NSString *characters = [event characters];
+		NSUInteger length = [characters length];
+
+		// Fallback unicode character handler used if IME is not active
+		if (!wd.im_active && length > 0 && keycode_has_unicode(remapKey([event keyCode], [event modifierFlags]))) {
+			for (NSUInteger i = 0; i < length; i++) {
+				DisplayServerOSX::KeyEvent ke;
+
+				ke.window_id = window_id;
+				ke.osx_state = [event modifierFlags];
+				ke.pressed = false;
+				ke.echo = [event isARepeat];
+				ke.keycode = remapKey([event keyCode], [event modifierFlags]);
+				ke.physical_keycode = translateKey([event keyCode]);
+				ke.raw = true;
+				ke.unicode = [characters characterAtIndex:i];
+
+				_push_to_key_event_buffer(ke);
+			}
+		} else {
+			DisplayServerOSX::KeyEvent ke;
+
+			ke.window_id = window_id;
+			ke.osx_state = [event modifierFlags];
+			ke.pressed = false;
+			ke.echo = [event isARepeat];
+			ke.keycode = remapKey([event keyCode], [event modifierFlags]);
+			ke.physical_keycode = translateKey([event keyCode]);
+			ke.raw = true;
+			ke.unicode = 0;
+
+			_push_to_key_event_buffer(ke);
+		}
+	}
+}
+
+inline void sendScrollEvent(DisplayServer::WindowID window_id, int button, double factor, int modifierFlags) {
+	ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
+	DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+	unsigned int mask = 1 << (button - 1);
+
+	Ref<InputEventMouseButton> sc;
+	sc.instance();
+
+	sc->set_window_id(window_id);
+	_get_key_modifier_state(modifierFlags, sc);
+	sc->set_button_index(button);
+	sc->set_factor(factor);
+	sc->set_pressed(true);
+	sc->set_position(wd.mouse_pos);
+	sc->set_global_position(wd.mouse_pos);
+	DS_OSX->last_button_state |= mask;
+	sc->set_button_mask(DS_OSX->last_button_state);
+
+	InputFilter::get_singleton()->accumulate_input_event(sc);
+
+	sc.instance();
+	sc->set_window_id(window_id);
+	sc->set_button_index(button);
+	sc->set_factor(factor);
+	sc->set_pressed(false);
+	sc->set_position(wd.mouse_pos);
+	sc->set_global_position(wd.mouse_pos);
+	DS_OSX->last_button_state &= ~mask;
+	sc->set_button_mask(DS_OSX->last_button_state);
+
+	InputFilter::get_singleton()->accumulate_input_event(sc);
+}
+
+inline void sendPanEvent(DisplayServer::WindowID window_id, double dx, double dy, int modifierFlags) {
+	ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
+	DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+	Ref<InputEventPanGesture> pg;
+	pg.instance();
+
+	pg->set_window_id(window_id);
+	_get_key_modifier_state(modifierFlags, pg);
+	pg->set_position(wd.mouse_pos);
+	pg->set_delta(Vector2(-dx, -dy));
+
+	InputFilter::get_singleton()->accumulate_input_event(pg);
+}
+
+- (void)scrollWheel:(NSEvent *)event {
+	ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
+	DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+	double deltaX, deltaY;
+
+	const CGFloat backingScaleFactor = (OS::get_singleton()->is_hidpi_allowed()) ? [[event window] backingScaleFactor] : 1.0;
+	_get_mouse_pos(wd, [event locationInWindow], backingScaleFactor);
+
+	deltaX = [event scrollingDeltaX];
+	deltaY = [event scrollingDeltaY];
+
+	if ([event hasPreciseScrollingDeltas]) {
+		deltaX *= 0.03;
+		deltaY *= 0.03;
+	}
+
+	if ([event phase] != NSEventPhaseNone || [event momentumPhase] != NSEventPhaseNone) {
+		sendPanEvent(window_id, deltaX, deltaY, [event modifierFlags]);
+	} else {
+		if (fabs(deltaX)) {
+			sendScrollEvent(window_id, 0 > deltaX ? BUTTON_WHEEL_RIGHT : BUTTON_WHEEL_LEFT, fabs(deltaX * 0.3), [event modifierFlags]);
+		}
+		if (fabs(deltaY)) {
+			sendScrollEvent(window_id, 0 < deltaY ? BUTTON_WHEEL_UP : BUTTON_WHEEL_DOWN, fabs(deltaY * 0.3), [event modifierFlags]);
+		}
+	}
+}
+
+@end
+
+/*************************************************************************/
+/* GodotWindow                                                           */
+/*************************************************************************/
+
+@interface GodotWindow : NSWindow {
+}
+@end
+
+@implementation GodotWindow
+
+- (BOOL)canBecomeKeyWindow {
+	// Required for NSBorderlessWindowMask windows
+	return YES;
+}
+
+@end
+
+/*************************************************************************/
+/* DisplayServerOSX                                                      */
+/*************************************************************************/
+
+bool DisplayServerOSX::has_feature(Feature p_feature) const {
+	switch (p_feature) {
+		case FEATURE_GLOBAL_MENU:
+		case FEATURE_SUBWINDOWS:
+		//case FEATURE_TOUCHSCREEN:
+		case FEATURE_MOUSE:
+		case FEATURE_MOUSE_WARP:
+		case FEATURE_CLIPBOARD:
+		case FEATURE_CURSOR_SHAPE:
+		case FEATURE_CUSTOM_CURSOR_SHAPE:
+		case FEATURE_NATIVE_DIALOG:
+		//case FEATURE_CONSOLE_WINDOW:
+		case FEATURE_IME:
+		case FEATURE_WINDOW_TRANSPARENCY:
+		case FEATURE_HIDPI:
+		case FEATURE_ICON:
+		case FEATURE_NATIVE_ICON:
+		case FEATURE_SWAP_BUFFERS:
+			return true;
+		default: {
+		}
+	}
+	return false;
+}
+
+String DisplayServerOSX::get_name() const {
+	return "OSX";
+}
+
+const NSMenu *DisplayServerOSX::_get_menu_root(const String &p_menu_root) const {
+	const NSMenu *menu = NULL;
+	if (p_menu_root == "") {
+		// Main menu.x
+		menu = [NSApp mainMenu];
+	} else if (p_menu_root.to_lower() == "_dock") {
+		// macOS dock menu.
+		menu = dock_menu;
+	} else {
+		// Submenu.
+		if (submenu.has(p_menu_root)) {
+			menu = submenu[p_menu_root];
+		}
+	}
+	if (menu == apple_menu) {
+		// Do not allow to change Apple menu.
+		return NULL;
+	}
+	return menu;
+}
+
+NSMenu *DisplayServerOSX::_get_menu_root(const String &p_menu_root) {
+	NSMenu *menu = NULL;
+	if (p_menu_root == "") {
+		// Main menu.
+		menu = [NSApp mainMenu];
+	} else if (p_menu_root.to_lower() == "_dock") {
+		// macOS dock menu.
+		menu = dock_menu;
+	} else {
+		// Submenu.
+		if (!submenu.has(p_menu_root)) {
+			NSMenu *n_menu = [[NSMenu alloc] initWithTitle:[NSString stringWithUTF8String:p_menu_root.utf8().get_data()]];
+			submenu[p_menu_root] = n_menu;
+		}
+		menu = submenu[p_menu_root];
+	}
+	if (menu == apple_menu) {
+		// Do not allow to change Apple menu.
+		return NULL;
+	}
+	return menu;
+}
+
+void DisplayServerOSX::global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag) {
+	_THREAD_SAFE_METHOD_
+
+	NSMenu *menu = _get_menu_root(p_menu_root);
+	if (menu) {
+		NSMenuItem *menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:@""];
+		GlobalMenuItem *obj = [[[GlobalMenuItem alloc] init] autorelease];
+		obj->callback = p_callback;
+		obj->meta = p_tag;
+		obj->checkable = false;
+		[menu_item setRepresentedObject:obj];
+	}
+}
+
+void DisplayServerOSX::global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag) {
+	_THREAD_SAFE_METHOD_
+
+	NSMenu *menu = _get_menu_root(p_menu_root);
+	if (menu) {
+		NSMenuItem *menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:@""];
+		GlobalMenuItem *obj = [[[GlobalMenuItem alloc] init] autorelease];
+		obj->callback = p_callback;
+		obj->meta = p_tag;
+		obj->checkable = true;
+		[menu_item setRepresentedObject:obj];
+	}
+}
+
+void DisplayServerOSX::global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu) {
+	_THREAD_SAFE_METHOD_
+
+	NSMenu *menu = _get_menu_root(p_menu_root);
+	NSMenu *sub_menu = _get_menu_root(p_submenu);
+	if (menu && sub_menu) {
+		if (sub_menu == menu) {
+			ERR_PRINT("Can't set submenu to self!");
+			return;
+		}
+		if ([sub_menu supermenu]) {
+			ERR_PRINT("Can't set submenu to menu that is already a submenu of some other menu!");
+			return;
+		}
+		NSMenuItem *menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:nil keyEquivalent:@""];
+		[menu setSubmenu:sub_menu forItem:menu_item];
+	}
+}
+
+void DisplayServerOSX::global_menu_add_separator(const String &p_menu_root) {
+	_THREAD_SAFE_METHOD_
+
+	NSMenu *menu = _get_menu_root(p_menu_root);
+	if (menu) {
+		[menu addItem:[NSMenuItem separatorItem]];
+	}
+}
+
+bool DisplayServerOSX::global_menu_is_item_checked(const String &p_menu_root, int p_idx) const {
+	_THREAD_SAFE_METHOD_
+
+	const NSMenu *menu = _get_menu_root(p_menu_root);
+	if (menu) {
+		const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+		if (menu_item) {
+			return ([menu_item state] == NSControlStateValueOn);
+		}
+	}
+	return false;
+}
+
+bool DisplayServerOSX::global_menu_is_item_checkable(const String &p_menu_root, int p_idx) const {
+	_THREAD_SAFE_METHOD_
+
+	const NSMenu *menu = _get_menu_root(p_menu_root);
+	if (menu) {
+		const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+		if (menu_item) {
+			GlobalMenuItem *obj = [menu_item representedObject];
+			if (obj) {
+				return obj->checkable;
+			}
+		}
+	}
+	return false;
+}
+
+Callable DisplayServerOSX::global_menu_get_item_callback(const String &p_menu_root, int p_idx) {
+	_THREAD_SAFE_METHOD_
+
+	const NSMenu *menu = _get_menu_root(p_menu_root);
+	if (menu) {
+		const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+		if (menu_item) {
+			GlobalMenuItem *obj = [menu_item representedObject];
+			if (obj) {
+				return obj->callback;
+			}
+		}
+	}
+	return Callable();
+}
+
+Variant DisplayServerOSX::global_menu_get_item_tag(const String &p_menu_root, int p_idx) {
+	_THREAD_SAFE_METHOD_
+
+	const NSMenu *menu = _get_menu_root(p_menu_root);
+	if (menu) {
+		const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+		if (menu_item) {
+			GlobalMenuItem *obj = [menu_item representedObject];
+			if (obj) {
+				return obj->meta;
+			}
+		}
+	}
+	return Variant();
+}
+
+String DisplayServerOSX::global_menu_get_item_text(const String &p_menu_root, int p_idx) {
+	_THREAD_SAFE_METHOD_
+
+	const NSMenu *menu = _get_menu_root(p_menu_root);
+	if (menu) {
+		const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+		if (menu_item) {
+			char *utfs = strdup([[menu_item title] UTF8String]);
+			String ret;
+			ret.parse_utf8(utfs);
+			free(utfs);
+			return ret;
+		}
+	}
+	return String();
+}
+
+String DisplayServerOSX::global_menu_get_item_submenu(const String &p_menu_root, int p_idx) {
+	_THREAD_SAFE_METHOD_
+
+	const NSMenu *menu = _get_menu_root(p_menu_root);
+	if (menu) {
+		const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+		if (menu_item) {
+			const NSMenu *sub_menu = [menu_item submenu];
+			if (sub_menu) {
+				for (Map<String, NSMenu *>::Element *E = submenu.front(); E; E = E->next()) {
+					if (E->get() == sub_menu) return E->key();
+				}
+			}
+		}
+	}
+	return String();
+}
+
+void DisplayServerOSX::global_menu_set_item_checked(const String &p_menu_root, int p_idx, bool p_checked) {
+	_THREAD_SAFE_METHOD_
+
+	NSMenu *menu = _get_menu_root(p_menu_root);
+	if (menu) {
+		if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
+			return;
+		}
+		NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+		if (menu_item) {
+			if (p_checked) {
+				[menu_item setState:NSControlStateValueOn];
+			} else {
+				[menu_item setState:NSControlStateValueOff];
+			}
+		}
+	}
+}
+
+void DisplayServerOSX::global_menu_set_item_checkable(const String &p_menu_root, int p_idx, bool p_checkable) {
+	_THREAD_SAFE_METHOD_
+
+	NSMenu *menu = _get_menu_root(p_menu_root);
+	if (menu) {
+		if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
+			return;
+		}
+		NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+		if (menu_item) {
+			GlobalMenuItem *obj = [menu_item representedObject];
+			obj->checkable = p_checkable;
+		}
+	}
+}
+
+void DisplayServerOSX::global_menu_set_item_callback(const String &p_menu_root, int p_idx, const Callable &p_callback) {
+	_THREAD_SAFE_METHOD_
+
+	NSMenu *menu = _get_menu_root(p_menu_root);
+	if (menu) {
+		if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
+			return;
+		}
+		NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+		if (menu_item) {
+			GlobalMenuItem *obj = [menu_item representedObject];
+			obj->callback = p_callback;
+		}
+	}
+}
+
+void DisplayServerOSX::global_menu_set_item_tag(const String &p_menu_root, int p_idx, const Variant &p_tag) {
+	_THREAD_SAFE_METHOD_
+
+	NSMenu *menu = _get_menu_root(p_menu_root);
+	if (menu) {
+		if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
+			return;
+		}
+		NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+		if (menu_item) {
+			GlobalMenuItem *obj = [menu_item representedObject];
+			obj->meta = p_tag;
+		}
+	}
+}
+
+void DisplayServerOSX::global_menu_set_item_text(const String &p_menu_root, int p_idx, const String &p_text) {
+	_THREAD_SAFE_METHOD_
+
+	NSMenu *menu = _get_menu_root(p_menu_root);
+	if (menu) {
+		if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
+			return;
+		}
+		NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+		if (menu_item) {
+			[menu_item setTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]];
+		}
+	}
+}
+
+void DisplayServerOSX::global_menu_set_item_submenu(const String &p_menu_root, int p_idx, const String &p_submenu) {
+	_THREAD_SAFE_METHOD_
+
+	NSMenu *menu = _get_menu_root(p_menu_root);
+	NSMenu *sub_menu = _get_menu_root(p_submenu);
+	if (menu && sub_menu) {
+		if (sub_menu == menu) {
+			ERR_PRINT("Can't set submenu to self!");
+			return;
+		}
+		if ([sub_menu supermenu]) {
+			ERR_PRINT("Can't set submenu to menu that is already a submenu of some other menu!");
+			return;
+		}
+		if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
+			return;
+		}
+		NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+		if (menu_item) {
+			[menu setSubmenu:sub_menu forItem:menu_item];
+		}
+	}
+}
+
+int DisplayServerOSX::global_menu_get_item_count(const String &p_menu_root) const {
+	_THREAD_SAFE_METHOD_
+
+	const NSMenu *menu = _get_menu_root(p_menu_root);
+	if (menu) {
+		return [menu numberOfItems];
+	} else {
+		return 0;
+	}
+}
+
+void DisplayServerOSX::global_menu_remove_item(const String &p_menu_root, int p_idx) {
+	_THREAD_SAFE_METHOD_
+
+	NSMenu *menu = _get_menu_root(p_menu_root);
+	if (menu) {
+		if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not delete Apple menu.
+			return;
+		}
+		[menu removeItemAtIndex:p_idx];
+	}
+}
+
+void DisplayServerOSX::global_menu_clear(const String &p_menu_root) {
+	_THREAD_SAFE_METHOD_
+
+	NSMenu *menu = _get_menu_root(p_menu_root);
+	if (menu) {
+		[menu removeAllItems];
+		// Restore Apple menu.
+		if (menu == [NSApp mainMenu]) {
+			NSMenuItem *menu_item = [menu addItemWithTitle:@"" action:nil keyEquivalent:@""];
+			[menu setSubmenu:apple_menu forItem:menu_item];
+		}
+	}
+}
+
+void DisplayServerOSX::alert(const String &p_alert, const String &p_title) {
+	_THREAD_SAFE_METHOD_
+
+	NSAlert *window = [[NSAlert alloc] init];
+	NSString *ns_title = [NSString stringWithUTF8String:p_title.utf8().get_data()];
+	NSString *ns_alert = [NSString stringWithUTF8String:p_alert.utf8().get_data()];
+
+	[window addButtonWithTitle:@"OK"];
+	[window setMessageText:ns_title];
+	[window setInformativeText:ns_alert];
+	[window setAlertStyle:NSAlertStyleWarning];
+
+	[window runModal];
+	[window release];
+}
+
+Error DisplayServerOSX::dialog_show(String p_title, String p_description, Vector<String> p_buttons, const Callable &p_callback) {
+	_THREAD_SAFE_METHOD_
+
+	NSAlert *window = [[NSAlert alloc] init];
+	NSString *ns_title = [NSString stringWithUTF8String:p_title.utf8().get_data()];
+	NSString *ns_description = [NSString stringWithUTF8String:p_description.utf8().get_data()];
+
+	for (int i = 0; i < p_buttons.size(); i++) {
+		NSString *ns_button = [NSString stringWithUTF8String:p_buttons[i].utf8().get_data()];
+		[window addButtonWithTitle:ns_button];
+	}
+	[window setMessageText:ns_title];
+	[window setInformativeText:ns_description];
+	[window setAlertStyle:NSAlertStyleInformational];
+
+	int button_pressed;
+	NSInteger ret = [window runModal];
+	if (ret == NSAlertFirstButtonReturn) {
+		button_pressed = 0;
+	} else if (ret == NSAlertSecondButtonReturn) {
+		button_pressed = 1;
+	} else if (ret == NSAlertThirdButtonReturn) {
+		button_pressed = 2;
+	} else {
+		button_pressed = 2 + (ret - NSAlertThirdButtonReturn);
+	}
+
+	if (!p_callback.is_null()) {
+		Variant button = button_pressed;
+		Variant *buttonp = &button;
+		Variant ret;
+		Callable::CallError ce;
+		p_callback.call((const Variant **)&buttonp, 1, ret, ce);
+	}
+
+	[window release];
+	return OK;
+}
+
+Error DisplayServerOSX::dialog_input_text(String p_title, String p_description, String p_partial, const Callable &p_callback) {
+	_THREAD_SAFE_METHOD_
+
+	NSAlert *window = [[NSAlert alloc] init];
+	NSString *ns_title = [NSString stringWithUTF8String:p_title.utf8().get_data()];
+	NSString *ns_description = [NSString stringWithUTF8String:p_description.utf8().get_data()];
+	NSTextField *input = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 250, 30)];
+
+	[window addButtonWithTitle:@"OK"];
+	[window setMessageText:ns_title];
+	[window setInformativeText:ns_description];
+	[window setAlertStyle:NSAlertStyleInformational];
+
+	[input setStringValue:[NSString stringWithUTF8String:p_partial.utf8().get_data()]];
+	[window setAccessoryView:input];
+
+	[window runModal];
+
+	char *utfs = strdup([[input stringValue] UTF8String]);
+	String ret;
+	ret.parse_utf8(utfs);
+	free(utfs);
+
+	if (!p_callback.is_null()) {
+		Variant text = ret;
+		Variant *textp = &text;
+		Variant ret;
+		Callable::CallError ce;
+		p_callback.call((const Variant **)&textp, 1, ret, ce);
+	}
+
+	[window release];
+	return OK;
+}
+
+void DisplayServerOSX::mouse_set_mode(MouseMode p_mode) {
+	_THREAD_SAFE_METHOD_
+
+	if (p_mode == mouse_mode)
+		return;
+
+	if (p_mode == MOUSE_MODE_CAPTURED) {
+		// Apple Docs state that the display parameter is not used.
+		// "This parameter is not used. By default, you may pass kCGDirectMainDisplay."
+		// https://developer.apple.com/library/mac/documentation/graphicsimaging/reference/Quartz_Services_Ref/Reference/reference.html
+		CGDisplayHideCursor(kCGDirectMainDisplay);
+		CGAssociateMouseAndMouseCursorPosition(false);
+	} else if (p_mode == MOUSE_MODE_HIDDEN) {
+		CGDisplayHideCursor(kCGDirectMainDisplay);
+		CGAssociateMouseAndMouseCursorPosition(true);
+	} else {
+		CGDisplayShowCursor(kCGDirectMainDisplay);
+		CGAssociateMouseAndMouseCursorPosition(true);
+	}
+
+	mouse_mode = p_mode;
+}
+
+DisplayServer::MouseMode DisplayServerOSX::mouse_get_mode() const {
+	return mouse_mode;
+}
+
+void DisplayServerOSX::mouse_warp_to_position(const Point2i &p_to) {
+	_THREAD_SAFE_METHOD_
+
+	if (mouse_mode == MOUSE_MODE_CAPTURED) {
+		last_mouse_pos = p_to;
+	} else {
+		WindowData &wd = windows[MAIN_WINDOW_ID];
+
+		//local point in window coords
+		const NSRect contentRect = [wd.window_view frame];
+		float displayScale = _display_scale([wd.window_object screen]);
+		NSRect pointInWindowRect = NSMakeRect(p_to.x / displayScale, contentRect.size.height - (p_to.y / displayScale) - 1, 0, 0);
+		NSPoint pointOnScreen = [[wd.window_view window] convertRectToScreen:pointInWindowRect].origin;
+
+		//point in scren coords
+		CGPoint lMouseWarpPos = { pointOnScreen.x, CGDisplayBounds(CGMainDisplayID()).size.height - pointOnScreen.y };
+
+		//do the warping
+		CGEventSourceRef lEventRef = CGEventSourceCreate(kCGEventSourceStateCombinedSessionState);
+		CGEventSourceSetLocalEventsSuppressionInterval(lEventRef, 0.0);
+		CGAssociateMouseAndMouseCursorPosition(false);
+		CGWarpMouseCursorPosition(lMouseWarpPos);
+		CGAssociateMouseAndMouseCursorPosition(true);
+	}
+}
+
+Point2i DisplayServerOSX::mouse_get_position() const {
+	return last_mouse_pos;
+}
+
+Point2i DisplayServerOSX::mouse_get_absolute_position() const {
+	_THREAD_SAFE_METHOD_
+
+	const NSPoint mouse_pos = [NSEvent mouseLocation];
+
+	for (NSScreen *screen in [NSScreen screens]) {
+		NSRect frame = [screen frame];
+		if (NSMouseInRect(mouse_pos, frame, NO)) {
+			return Vector2i((int)mouse_pos.x, (int)-mouse_pos.y) + _get_screens_origin();
+		}
+	}
+	return Vector2i();
+}
+
+int DisplayServerOSX::mouse_get_button_state() const {
+	return last_button_state;
+}
+
+void DisplayServerOSX::clipboard_set(const String &p_text) {
+	_THREAD_SAFE_METHOD_
+
+	NSString *copiedString = [NSString stringWithUTF8String:p_text.utf8().get_data()];
+	NSArray *copiedStringArray = [NSArray arrayWithObject:copiedString];
+
+	NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
+	[pasteboard clearContents];
+	[pasteboard writeObjects:copiedStringArray];
+}
+
+String DisplayServerOSX::clipboard_get() const {
+	_THREAD_SAFE_METHOD_
+
+	NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
+	NSArray *classArray = [NSArray arrayWithObject:[NSString class]];
+	NSDictionary *options = [NSDictionary dictionary];
+
+	BOOL ok = [pasteboard canReadObjectForClasses:classArray options:options];
+
+	if (!ok) {
+		return "";
+	}
+
+	NSArray *objectsToPaste = [pasteboard readObjectsForClasses:classArray options:options];
+	NSString *string = [objectsToPaste objectAtIndex:0];
+
+	char *utfs = strdup([string UTF8String]);
+	String ret;
+	ret.parse_utf8(utfs);
+	free(utfs);
+
+	return ret;
+}
+
+int DisplayServerOSX::get_screen_count() const {
+	_THREAD_SAFE_METHOD_
+
+	NSArray *screenArray = [NSScreen screens];
+	return [screenArray count];
+}
+
+// Returns the native top-left screen coordinate of the smallest rectangle
+// that encompasses all screens. Needed in get_screen_position(),
+// window_get_position, and window_set_position()
+// to convert between OS X native screen coordinates and the ones expected by Godot
+
+static bool displays_arrangement_dirty = true;
+static void displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplayChangeSummaryFlags flags, void *user_info) {
+	displays_arrangement_dirty = true;
+}
+
+float DisplayServerOSX::_display_scale(id screen) const {
+	if (OS_OSX::get_singleton()->is_hidpi_allowed()) {
+		if ([screen respondsToSelector:@selector(backingScaleFactor)]) {
+			return fmax(1.0, [screen backingScaleFactor]);
+		}
+	}
+	return 1.0;
+}
+
+Point2i DisplayServerOSX::_get_screens_origin() const {
+	static Point2i origin;
+
+	if (displays_arrangement_dirty) {
+		origin = Point2i();
+
+		for (int i = 0; i < get_screen_count(); i++) {
+			Point2i position = _get_native_screen_position(i);
+			if (position.x < origin.x) {
+				origin.x = position.x;
+			}
+			if (position.y > origin.y) {
+				origin.y = position.y;
+			}
+		}
+		displays_arrangement_dirty = false;
+	}
+
+	return origin;
+}
+
+Point2i DisplayServerOSX::_get_native_screen_position(int p_screen) const {
+	NSArray *screenArray = [NSScreen screens];
+	if ((NSUInteger)p_screen < [screenArray count]) {
+		float display_scale = _display_scale([screenArray objectAtIndex:p_screen]);
+		NSRect nsrect = [[screenArray objectAtIndex:p_screen] frame];
+		// Return the top-left corner of the screen, for OS X the y starts at the bottom
+		return Point2i(nsrect.origin.x, nsrect.origin.y + nsrect.size.height) * display_scale;
+	}
+
+	return Point2i();
+}
+
+Point2i DisplayServerOSX::screen_get_position(int p_screen) const {
+	_THREAD_SAFE_METHOD_
+
+	if (p_screen == SCREEN_OF_MAIN_WINDOW) {
+		p_screen = window_get_current_screen();
+	}
+
+	Point2i position = _get_native_screen_position(p_screen) - _get_screens_origin();
+	// OS X native y-coordinate relative to _get_screens_origin() is negative,
+	// Godot expects a positive value
+	position.y *= -1;
+	return position;
+}
+
+Size2i DisplayServerOSX::screen_get_size(int p_screen) const {
+	_THREAD_SAFE_METHOD_
+
+	if (p_screen == SCREEN_OF_MAIN_WINDOW) {
+		p_screen = window_get_current_screen();
+	}
+
+	NSArray *screenArray = [NSScreen screens];
+	if ((NSUInteger)p_screen < [screenArray count]) {
+		float displayScale = _display_scale([screenArray objectAtIndex:p_screen]);
+		// Note: Use frame to get the whole screen size
+		NSRect nsrect = [[screenArray objectAtIndex:p_screen] frame];
+		return Size2i(nsrect.size.width, nsrect.size.height) * displayScale;
+	}
+
+	return Size2i();
+}
+
+int DisplayServerOSX::screen_get_dpi(int p_screen) const {
+	_THREAD_SAFE_METHOD_
+
+	if (p_screen == SCREEN_OF_MAIN_WINDOW) {
+		p_screen = window_get_current_screen();
+	}
+
+	NSArray *screenArray = [NSScreen screens];
+	if ((NSUInteger)p_screen < [screenArray count]) {
+		float displayScale = _display_scale([screenArray objectAtIndex:p_screen]);
+		NSDictionary *description = [[screenArray objectAtIndex:p_screen] deviceDescription];
+		NSSize displayPixelSize = [[description objectForKey:NSDeviceSize] sizeValue];
+		CGSize displayPhysicalSize = CGDisplayScreenSize(
+				[[description objectForKey:@"NSScreenNumber"] unsignedIntValue]);
+
+		return (displayPixelSize.width * 25.4f / displayPhysicalSize.width) * displayScale;
+	}
+
+	return 96;
+}
+
+float DisplayServerOSX::screen_get_scale(int p_screen) const {
+	_THREAD_SAFE_METHOD_
+
+	if (p_screen == SCREEN_OF_MAIN_WINDOW) {
+		p_screen = window_get_current_screen();
+	}
+	NSArray *screenArray = [NSScreen screens];
+	if ((NSUInteger)p_screen < [screenArray count]) {
+		return _display_scale([screenArray objectAtIndex:p_screen]);
+	}
+
+	return 1.f;
+}
+
+Rect2i DisplayServerOSX::screen_get_usable_rect(int p_screen) const {
+	_THREAD_SAFE_METHOD_
+
+	if (p_screen == SCREEN_OF_MAIN_WINDOW) {
+		p_screen = window_get_current_screen();
+	}
+
+	NSArray *screenArray = [NSScreen screens];
+	if ((NSUInteger)p_screen < [screenArray count]) {
+		float displayScale = _display_scale([screenArray objectAtIndex:p_screen]);
+		NSRect nsrect = [[screenArray objectAtIndex:p_screen] visibleFrame];
+
+		Point2i position = Point2i(nsrect.origin.x, nsrect.origin.y + nsrect.size.height) * displayScale - _get_screens_origin();
+		position.y *= -1;
+		Size2i size = Size2i(nsrect.size.width, nsrect.size.height) * displayScale;
+
+		return Rect2i(position, size);
+	}
+
+	return Rect2i();
+}
+
+Vector<DisplayServer::WindowID> DisplayServerOSX::get_window_list() const {
+	_THREAD_SAFE_METHOD_
+
+	Vector<int> ret;
+	for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
+		ret.push_back(E->key());
+	}
+	return ret;
+}
+
+DisplayServer::WindowID DisplayServerOSX::create_sub_window(WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect) {
+	_THREAD_SAFE_METHOD_
+
+	WindowID id = _create_window(p_mode, p_rect);
+	WindowData &wd = windows[id];
+	for (int i = 0; i < WINDOW_FLAG_MAX; i++) {
+		if (p_flags & (1 << i)) {
+			window_set_flag(WindowFlags(i), true, id);
+		}
+	}
+	[wd.window_object makeKeyAndOrderFront:nil];
+	return id;
+}
+
+void DisplayServerOSX::_send_window_event(const WindowData &wd, WindowEvent p_event) {
+	if (!wd.event_callback.is_null()) {
+		Variant event = int(p_event);
+		Variant *eventp = &event;
+		Variant ret;
+		Callable::CallError ce;
+		wd.event_callback.call((const Variant **)&eventp, 1, ret, ce);
+	}
+}
+
+DisplayServerOSX::WindowID DisplayServerOSX::_find_window_id(id p_window) {
+	for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
+		if (E->get().window_object == p_window)
+			return E->key();
+	}
+	return INVALID_WINDOW_ID;
+}
+
+void DisplayServerOSX::_update_window(WindowData p_wd) {
+	bool borderless_full = false;
+
+	if (p_wd.borderless) {
+		NSRect frameRect = [p_wd.window_object frame];
+		NSRect screenRect = [[p_wd.window_object screen] frame];
+
+		// Check if our window covers up the screen
+		if (frameRect.origin.x <= screenRect.origin.x && frameRect.origin.y <= frameRect.origin.y &&
+				frameRect.size.width >= screenRect.size.width && frameRect.size.height >= screenRect.size.height) {
+			borderless_full = true;
+		}
+	}
+
+	if (borderless_full) {
+		// If the window covers up the screen set the level to above the main menu and hide on deactivate
+		[p_wd.window_object setLevel:NSMainMenuWindowLevel + 1];
+		[p_wd.window_object setHidesOnDeactivate:YES];
+	} else {
+		// Reset these when our window is not a borderless window that covers up the screen
+		[p_wd.window_object setLevel:NSNormalWindowLevel];
+		[p_wd.window_object setHidesOnDeactivate:NO];
+	}
+}
+
+void DisplayServerOSX::delete_sub_window(WindowID p_id) {
+	_THREAD_SAFE_METHOD_
+
+	ERR_FAIL_COND(!windows.has(p_id));
+	ERR_FAIL_COND_MSG(p_id == MAIN_WINDOW_ID, "Main window can't be deleted");
+
+	WindowData &wd = windows[p_id];
+
+	while (wd.transient_children.size()) {
+		window_set_transient(wd.transient_children.front()->get(), INVALID_WINDOW_ID);
+	}
+
+	if (wd.transient_parent != INVALID_WINDOW_ID) {
+		WindowData &pwd = windows[wd.transient_parent];
+		[pwd.window_object makeKeyAndOrderFront:nil]; // Move focus back to parent.
+		window_set_transient(p_id, INVALID_WINDOW_ID);
+	}
+
+	[wd.window_object setContentView:nil];
+	[wd.window_object close];
+
+	windows.erase(p_id);
+}
+
+void DisplayServerOSX::window_set_title(const String &p_title, WindowID p_window) {
+	_THREAD_SAFE_METHOD_
+
+	ERR_FAIL_COND(!windows.has(p_window));
+	WindowData &wd = windows[p_window];
+
+	[wd.window_object setTitle:[NSString stringWithUTF8String:p_title.utf8().get_data()]];
+}
+
+void DisplayServerOSX::window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window) {
+	_THREAD_SAFE_METHOD_
+
+	ERR_FAIL_COND(!windows.has(p_window));
+	WindowData &wd = windows[p_window];
+	wd.rect_changed_callback = p_callable;
+}
+
+void DisplayServerOSX::window_set_window_event_callback(const Callable &p_callable, WindowID p_window) {
+	_THREAD_SAFE_METHOD_
+
+	ERR_FAIL_COND(!windows.has(p_window));
+	WindowData &wd = windows[p_window];
+	wd.event_callback = p_callable;
+}
+
+void DisplayServerOSX::window_set_input_event_callback(const Callable &p_callable, WindowID p_window) {
+	_THREAD_SAFE_METHOD_
+
+	ERR_FAIL_COND(!windows.has(p_window));
+	WindowData &wd = windows[p_window];
+	wd.input_event_callback = p_callable;
+}
+
+void DisplayServerOSX::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) {
+	_THREAD_SAFE_METHOD_
+	ERR_FAIL_COND(!windows.has(p_window));
+	WindowData &wd = windows[p_window];
+	wd.input_text_callback = p_callable;
+}
+
+void DisplayServerOSX::window_set_drop_files_callback(const Callable &p_callable, WindowID p_window) {
+	_THREAD_SAFE_METHOD_
+	ERR_FAIL_COND(!windows.has(p_window));
+	WindowData &wd = windows[p_window];
+	wd.drop_files_callback = p_callable;
+}
+
+int DisplayServerOSX::window_get_current_screen(WindowID p_window) const {
+	_THREAD_SAFE_METHOD_
+	ERR_FAIL_COND_V(!windows.has(p_window), -1);
+	const WindowData &wd = windows[p_window];
+
+	const NSUInteger index = [[NSScreen screens] indexOfObject:[wd.window_object screen]];
+	return (index == NSNotFound) ? 0 : index;
+}
+
+void DisplayServerOSX::window_set_current_screen(int p_screen, WindowID p_window) {
+	_THREAD_SAFE_METHOD_
+	Point2i wpos = window_get_position(p_window) - screen_get_position(window_get_current_screen(p_window));
+	window_set_position(wpos + screen_get_position(p_screen), p_window);
+}
+
+void DisplayServerOSX::window_set_transient(WindowID p_window, WindowID p_parent) {
+	_THREAD_SAFE_METHOD_
+	ERR_FAIL_COND(p_window == p_parent);
+
+	ERR_FAIL_COND(!windows.has(p_window));
+	WindowData &wd_window = windows[p_window];
+
+	ERR_FAIL_COND(wd_window.transient_parent == p_parent);
+
+	ERR_FAIL_COND_MSG(wd_window.on_top, "Windows with the 'on top' can't become transient.");
+	if (p_parent == INVALID_WINDOW_ID) {
+		//remove transient
+		ERR_FAIL_COND(wd_window.transient_parent == INVALID_WINDOW_ID);
+		ERR_FAIL_COND(!windows.has(wd_window.transient_parent));
+
+		WindowData &wd_parent = windows[wd_window.transient_parent];
+
+		wd_window.transient_parent = INVALID_WINDOW_ID;
+		wd_parent.transient_children.erase(p_window);
+
+		[wd_window.window_object setParentWindow:nil];
+	} else {
+		ERR_FAIL_COND(!windows.has(p_parent));
+		ERR_FAIL_COND_MSG(wd_window.transient_parent != INVALID_WINDOW_ID, "Window already has a transient parent");
+		WindowData &wd_parent = windows[p_parent];
+
+		wd_window.transient_parent = p_parent;
+		wd_parent.transient_children.insert(p_window);
+
+		[wd_window.window_object setParentWindow:wd_parent.window_object];
+	}
+}
+
+Point2i DisplayServerOSX::window_get_position(WindowID p_window) const {
+	_THREAD_SAFE_METHOD_
+
+	ERR_FAIL_COND_V(!windows.has(p_window), Point2i());
+	const WindowData &wd = windows[p_window];
+
+	NSRect nsrect = [wd.window_object frame];
+	Point2i pos;
+	float display_scale = _display_scale([wd.window_object screen]);
+
+	// Return the position of the top-left corner, for OS X the y starts at the bottom
+	pos.x = nsrect.origin.x * display_scale;
+	pos.y = (nsrect.origin.y + nsrect.size.height) * display_scale;
+	pos -= _get_screens_origin();
+	// OS X native y-coordinate relative to _get_screens_origin() is negative,
+	// Godot expects a positive value
+	pos.y *= -1;
+	return pos;
+}
+
+void DisplayServerOSX::window_set_position(const Point2i &p_position, WindowID p_window) {
+	_THREAD_SAFE_METHOD_
+
+	ERR_FAIL_COND(!windows.has(p_window));
+	WindowData &wd = windows[p_window];
+
+	Point2i position = p_position;
+	// OS X native y-coordinate relative to _get_screens_origin() is negative,
+	// Godot passes a positive value
+	position.y *= -1;
+	position += _get_screens_origin();
+
+	NSPoint pos;
+	float displayScale = _display_scale([wd.window_object screen]);
+
+	pos.x = position.x / displayScale;
+	pos.y = position.y / displayScale;
+
+	[wd.window_object setFrameTopLeftPoint:pos];
+
+	_update_window(wd);
+	_get_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream], displayScale);
+}
+
+void DisplayServerOSX::window_set_max_size(const Size2i p_size, WindowID p_window) {
+	_THREAD_SAFE_METHOD_
+
+	ERR_FAIL_COND(!windows.has(p_window));
+	WindowData &wd = windows[p_window];
+
+	if ((p_size != Size2i()) && ((p_size.x < wd.min_size.x) || (p_size.y < wd.min_size.y))) {
+		ERR_PRINT("Maximum window size can't be smaller than minimum window size!");
+		return;
+	}
+	wd.max_size = p_size;
+
+	if ((wd.max_size != Size2i()) && !wd.fullscreen) {
+		Size2i size = wd.max_size / _display_scale([wd.window_object screen]);
+		[wd.window_object setContentMaxSize:NSMakeSize(size.x, size.y)];
+	} else {
+		[wd.window_object setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)];
+	}
+}
+
+Size2i DisplayServerOSX::window_get_max_size(WindowID p_window) const {
+	_THREAD_SAFE_METHOD_
+
+	ERR_FAIL_COND_V(!windows.has(p_window), Size2i());
+	const WindowData &wd = windows[p_window];
+	return wd.max_size;
+}
+
+void DisplayServerOSX::window_set_min_size(const Size2i p_size, WindowID p_window) {
+	_THREAD_SAFE_METHOD_
+
+	ERR_FAIL_COND(!windows.has(p_window));
+	WindowData &wd = windows[p_window];
+
+	if ((p_size != Size2i()) && (wd.max_size != Size2i()) && ((p_size.x > wd.max_size.x) || (p_size.y > wd.max_size.y))) {
+		ERR_PRINT("Minimum window size can't be larger than maximum window size!");
+		return;
+	}
+	wd.min_size = p_size;
+
+	if ((wd.min_size != Size2i()) && !wd.fullscreen) {
+		Size2i size = wd.min_size / _display_scale([wd.window_object screen]);
+		[wd.window_object setContentMinSize:NSMakeSize(size.x, size.y)];
+	} else {
+		[wd.window_object setContentMinSize:NSMakeSize(0, 0)];
+	}
+}
+
+Size2i DisplayServerOSX::window_get_min_size(WindowID p_window) const {
+	_THREAD_SAFE_METHOD_
+
+	ERR_FAIL_COND_V(!windows.has(p_window), Size2i());
+	const WindowData &wd = windows[p_window];
+
+	return wd.min_size;
+}
+
+void DisplayServerOSX::window_set_size(const Size2i p_size, WindowID p_window) {
+	_THREAD_SAFE_METHOD_
+
+	ERR_FAIL_COND(!windows.has(p_window));
+	WindowData &wd = windows[p_window];
+
+	Size2i size = p_size / _display_scale([wd.window_object screen]);
+
+	if (!wd.borderless) {
+		// NSRect used by setFrame includes the title bar, so add it to our size.y
+		CGFloat menuBarHeight = [[[NSApplication sharedApplication] mainMenu] menuBarHeight];
+		if (menuBarHeight != 0.f) {
+			size.y += menuBarHeight;
+		}
+	}
+
+	NSRect frame = [wd.window_object frame];
+	[wd.window_object setFrame:NSMakeRect(frame.origin.x, frame.origin.y, size.x, size.y) display:YES];
+
+	_update_window(wd);
+}
+
+Size2i DisplayServerOSX::window_get_size(WindowID p_window) const {
+	_THREAD_SAFE_METHOD_
+
+	ERR_FAIL_COND_V(!windows.has(p_window), Size2i());
+	const WindowData &wd = windows[p_window];
+	return wd.size;
+}
+
+Size2i DisplayServerOSX::window_get_real_size(WindowID p_window) const {
+	_THREAD_SAFE_METHOD_
+
+	ERR_FAIL_COND_V(!windows.has(p_window), Size2i());
+	const WindowData &wd = windows[p_window];
+	NSRect frame = [wd.window_object frame];
+	return Size2i(frame.size.width, frame.size.height) * _display_scale([wd.window_object screen]);
+}
+
+bool DisplayServerOSX::window_is_maximize_allowed(WindowID p_window) const {
+	return true;
+}
+
+void DisplayServerOSX::_set_window_per_pixel_transparency_enabled(bool p_enabled, WindowID p_window) {
+	ERR_FAIL_COND(!windows.has(p_window));
+	WindowData &wd = windows[p_window];
+
+	if (!OS_OSX::get_singleton()->is_layered_allowed()) return;
+	if (wd.layered_window != p_enabled) {
+		if (p_enabled) {
+			[wd.window_object setBackgroundColor:[NSColor clearColor]];
+			[wd.window_object setOpaque:NO];
+			[wd.window_object setHasShadow:NO];
+#if defined(VULKAN_ENABLED)
+			if (rendering_driver == "vulkan") {
+				CALayer *layer = [wd.window_view layer];
+				[layer setOpaque:NO];
+				//TODO - implement transparency for Vulkan
+			}
+#endif
+#if defined(OPENGL_ENABLED)
+			if (rendering_driver == "opengl_es") {
+				//TODO - reimplement OpenGLES
+				wd.context_gles2->set_opacity(0);
+			}
+#endif
+			wd.layered_window = true;
+		} else {
+			[wd.window_object setBackgroundColor:[NSColor colorWithCalibratedWhite:1 alpha:1]];
+			[wd.window_object setOpaque:YES];
+			[wd.window_object setHasShadow:YES];
+#if defined(VULKAN_ENABLED)
+			if (rendering_driver == "vulkan") {
+				CALayer *layer = [wd.window_view layer];
+				[layer setOpaque:YES];
+				//TODO - implement transparency for Vulkan
+			}
+#endif
+#if defined(OPENGL_ENABLED)
+			if (rendering_driver == "opengl_es") {
+				//TODO - reimplement OpenGLES
+				wd.context_gles2->set_opacity(1);
+			}
+#endif
+			wd.layered_window = false;
+		}
+#if defined(OPENGL_ENABLED)
+		if (rendering_driver == "opengl_es") {
+			//TODO - reimplement OpenGLES
+			wd.context_gles2->update();
+		}
+#endif
+		NSRect frameRect = [wd.window_object frame];
+		[wd.window_object setFrame:NSMakeRect(frameRect.origin.x, frameRect.origin.y, frameRect.size.width + 1, frameRect.size.height) display:YES];
+		[wd.window_object setFrame:frameRect display:YES];
+	}
+}
+
+void DisplayServerOSX::window_set_mode(WindowMode p_mode, WindowID p_window) {
+	_THREAD_SAFE_METHOD_
+
+	ERR_FAIL_COND(!windows.has(p_window));
+	WindowData &wd = windows[p_window];
+
+	WindowMode old_mode = window_get_mode(p_window);
+	if (old_mode == p_mode) {
+		return; // do nothing
+	}
+
+	switch (old_mode) {
+		case WINDOW_MODE_WINDOWED: {
+			//do nothing
+		} break;
+		case WINDOW_MODE_MINIMIZED: {
+			[wd.window_object deminiaturize:nil];
+		} break;
+		case WINDOW_MODE_FULLSCREEN: {
+			if (wd.layered_window)
+				_set_window_per_pixel_transparency_enabled(true, p_window);
+			if (wd.resize_disabled) //restore resize disabled
+				[wd.window_object setStyleMask:[wd.window_object styleMask] & ~NSWindowStyleMaskResizable];
+			if (wd.min_size != Size2i()) {
+				Size2i size = wd.min_size / _display_scale([wd.window_object screen]);
+				[wd.window_object setContentMinSize:NSMakeSize(size.x, size.y)];
+			}
+			if (wd.max_size != Size2i()) {
+				Size2i size = wd.max_size / _display_scale([wd.window_object screen]);
+				[wd.window_object setContentMaxSize:NSMakeSize(size.x, size.y)];
+			}
+			[wd.window_object toggleFullScreen:nil];
+			wd.fullscreen = false;
+		} break;
+		case WINDOW_MODE_MAXIMIZED: {
+			if ([wd.window_object isZoomed]) {
+				[wd.window_object zoom:nil];
+			}
+		} break;
+	}
+
+	switch (p_mode) {
+		case WINDOW_MODE_WINDOWED: {
+			//do nothing
+		} break;
+		case WINDOW_MODE_MINIMIZED: {
+			[wd.window_object performMiniaturize:nil];
+		} break;
+		case WINDOW_MODE_FULLSCREEN: {
+			if (wd.layered_window)
+				_set_window_per_pixel_transparency_enabled(false, p_window);
+			if (wd.resize_disabled) //fullscreen window should be resizable to work
+				[wd.window_object setStyleMask:[wd.window_object styleMask] | NSWindowStyleMaskResizable];
+			[wd.window_object setContentMinSize:NSMakeSize(0, 0)];
+			[wd.window_object setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)];
+			[wd.window_object toggleFullScreen:nil];
+			wd.fullscreen = true;
+		} break;
+		case WINDOW_MODE_MAXIMIZED: {
+			if (![wd.window_object isZoomed]) {
+				[wd.window_object zoom:nil];
+			}
+		} break;
+	}
+}
+
+DisplayServer::WindowMode DisplayServerOSX::window_get_mode(WindowID p_window) const {
+	_THREAD_SAFE_METHOD_
+
+	ERR_FAIL_COND_V(!windows.has(p_window), WINDOW_MODE_WINDOWED);
+	const WindowData &wd = windows[p_window];
+
+	if (wd.fullscreen) { //if fullscreen, it's not in another mode
+		return WINDOW_MODE_FULLSCREEN;
+	}
+	if ([wd.window_object isZoomed] && !wd.resize_disabled) {
+		return WINDOW_MODE_MAXIMIZED;
+	}
+	if ([wd.window_object respondsToSelector:@selector(isMiniaturized)]) {
+		if ([wd.window_object isMiniaturized]) {
+			return WINDOW_MODE_MINIMIZED;
+		}
+	}
+
+	// all other discarded, return windowed.
+	return WINDOW_MODE_WINDOWED;
+}
+
+void DisplayServerOSX::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) {
+	_THREAD_SAFE_METHOD_
+
+	ERR_FAIL_COND(!windows.has(p_window));
+	WindowData &wd = windows[p_window];
+
+	switch (p_flag) {
+		case WINDOW_FLAG_RESIZE_DISABLED: {
+			wd.resize_disabled = p_enabled;
+			if (wd.fullscreen) //fullscreen window should be resizable, style will be applyed on exiting fs
+				return;
+			if (p_enabled) {
+				[wd.window_object setStyleMask:[wd.window_object styleMask] & ~NSWindowStyleMaskResizable];
+			} else {
+				[wd.window_object setStyleMask:[wd.window_object styleMask] | NSWindowStyleMaskResizable];
+			}
+		} break;
+		case WINDOW_FLAG_BORDERLESS: {
+			// OrderOut prevents a lose focus bug with the window
+			[wd.window_object orderOut:nil];
+			wd.borderless = p_enabled;
+			if (p_enabled) {
+				[wd.window_object setStyleMask:NSWindowStyleMaskBorderless];
+			} else {
+				if (wd.layered_window)
+					_set_window_per_pixel_transparency_enabled(false, p_window);
+				[wd.window_object setStyleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | (wd.resize_disabled ? 0 : NSWindowStyleMaskResizable)];
+				// Force update of the window styles
+				NSRect frameRect = [wd.window_object frame];
+				[wd.window_object setFrame:NSMakeRect(frameRect.origin.x, frameRect.origin.y, frameRect.size.width + 1, frameRect.size.height) display:NO];
+				[wd.window_object setFrame:frameRect display:NO];
+			}
+			_update_window(wd);
+			[wd.window_object makeKeyAndOrderFront:nil];
+		} break;
+		case WINDOW_FLAG_ALWAYS_ON_TOP: {
+			wd.on_top = p_enabled;
+			if (p_enabled) {
+				[wd.window_object setLevel:NSFloatingWindowLevel];
+			} else {
+				[wd.window_object setLevel:NSNormalWindowLevel];
+			}
+		} break;
+		case WINDOW_FLAG_TRANSPARENT: {
+			wd.layered_window = p_enabled;
+			if (p_enabled) {
+				[wd.window_object setStyleMask:NSWindowStyleMaskBorderless]; // force borderless
+			} else if (!wd.borderless) {
+				[wd.window_object setStyleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | (wd.resize_disabled ? 0 : NSWindowStyleMaskResizable)];
+			}
+			_set_window_per_pixel_transparency_enabled(p_enabled, p_window);
+
+		} break;
+		default: {
+		}
+	}
+}
+
+bool DisplayServerOSX::window_get_flag(WindowFlags p_flag, WindowID p_window) const {
+	_THREAD_SAFE_METHOD_
+
+	ERR_FAIL_COND_V(!windows.has(p_window), false);
+	const WindowData &wd = windows[p_window];
+
+	switch (p_flag) {
+		case WINDOW_FLAG_RESIZE_DISABLED: {
+			return wd.resize_disabled;
+		} break;
+		case WINDOW_FLAG_BORDERLESS: {
+			return [wd.window_object styleMask] == NSWindowStyleMaskBorderless;
+		} break;
+		case WINDOW_FLAG_ALWAYS_ON_TOP: {
+			return [wd.window_object level] == NSFloatingWindowLevel;
+		} break;
+		case WINDOW_FLAG_TRANSPARENT: {
+			return wd.layered_window;
+		} break;
+		default: {
+		}
+	}
+
+	return false;
+}
+
+void DisplayServerOSX::window_request_attention(WindowID p_window) {
+	// It's app global, ignore window id.
+	[NSApp requestUserAttention:NSCriticalRequest];
+}
+
+void DisplayServerOSX::window_move_to_foreground(WindowID p_window) {
+	_THREAD_SAFE_METHOD_
+
+	ERR_FAIL_COND(!windows.has(p_window));
+	const WindowData &wd = windows[p_window];
+
+	[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
+	[wd.window_object makeKeyAndOrderFront:nil];
+}
+
+bool DisplayServerOSX::window_can_draw(WindowID p_window) const {
+	return window_get_mode(p_window) != WINDOW_MODE_MINIMIZED;
+}
+
+bool DisplayServerOSX::can_any_window_draw() const {
+	_THREAD_SAFE_METHOD_
+
+	for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
+		if (window_get_mode(E->key()) != WINDOW_MODE_MINIMIZED) {
+			return true;
+		}
+	}
+	return false;
+}
+
+void DisplayServerOSX::window_set_ime_active(const bool p_active, WindowID p_window) {
+	_THREAD_SAFE_METHOD_
+
+	ERR_FAIL_COND(!windows.has(p_window));
+	WindowData &wd = windows[p_window];
+
+	wd.im_active = p_active;
+
+	if (!p_active)
+		[wd.window_view cancelComposition];
+}
+
+void DisplayServerOSX::window_set_ime_position(const Point2i &p_pos, WindowID p_window) {
+	_THREAD_SAFE_METHOD_
+
+	ERR_FAIL_COND(!windows.has(p_window));
+	WindowData &wd = windows[p_window];
+
+	wd.im_position = p_pos;
+}
+
+bool DisplayServerOSX::get_swap_ok_cancel() {
+	return true;
+}
+
+void DisplayServerOSX::cursor_set_shape(CursorShape p_shape) {
+	_THREAD_SAFE_METHOD_
+
+	ERR_FAIL_INDEX(p_shape, CURSOR_MAX);
+
+	if (cursor_shape == p_shape)
+		return;
+
+	if (mouse_mode != MOUSE_MODE_VISIBLE && mouse_mode != MOUSE_MODE_CONFINED) {
+		cursor_shape = p_shape;
+		return;
+	}
+
+	if (cursors[p_shape] != NULL) {
+		[cursors[p_shape] set];
+	} else {
+		switch (p_shape) {
+			case CURSOR_ARROW: [[NSCursor arrowCursor] set]; break;
+			case CURSOR_IBEAM: [[NSCursor IBeamCursor] set]; break;
+			case CURSOR_POINTING_HAND: [[NSCursor pointingHandCursor] set]; break;
+			case CURSOR_CROSS: [[NSCursor crosshairCursor] set]; break;
+			case CURSOR_WAIT: [[NSCursor arrowCursor] set]; break;
+			case CURSOR_BUSY: [[NSCursor arrowCursor] set]; break;
+			case CURSOR_DRAG: [[NSCursor closedHandCursor] set]; break;
+			case CURSOR_CAN_DROP: [[NSCursor openHandCursor] set]; break;
+			case CURSOR_FORBIDDEN: [[NSCursor operationNotAllowedCursor] set]; break;
+			case CURSOR_VSIZE: [_cursorFromSelector(@selector(_windowResizeNorthSouthCursor), @selector(resizeUpDownCursor)) set]; break;
+			case CURSOR_HSIZE: [_cursorFromSelector(@selector(_windowResizeEastWestCursor), @selector(resizeLeftRightCursor)) set]; break;
+			case CURSOR_BDIAGSIZE: [_cursorFromSelector(@selector(_windowResizeNorthEastSouthWestCursor)) set]; break;
+			case CURSOR_FDIAGSIZE: [_cursorFromSelector(@selector(_windowResizeNorthWestSouthEastCursor)) set]; break;
+			case CURSOR_MOVE: [[NSCursor arrowCursor] set]; break;
+			case CURSOR_VSPLIT: [[NSCursor resizeUpDownCursor] set]; break;
+			case CURSOR_HSPLIT: [[NSCursor resizeLeftRightCursor] set]; break;
+			case CURSOR_HELP: [_cursorFromSelector(@selector(_helpCursor)) set]; break;
+			default: {
+			}
+		}
+	}
+
+	cursor_shape = p_shape;
+}
+
+DisplayServerOSX::CursorShape DisplayServerOSX::cursor_get_shape() const {
+	return cursor_shape;
+}
+
+void DisplayServerOSX::cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) {
+	_THREAD_SAFE_METHOD_
+
+	if (p_cursor.is_valid()) {
+		Map<CursorShape, Vector<Variant>>::Element *cursor_c = cursors_cache.find(p_shape);
+
+		if (cursor_c) {
+			if (cursor_c->get()[0] == p_cursor && cursor_c->get()[1] == p_hotspot) {
+				cursor_set_shape(p_shape);
+				return;
+			}
+			cursors_cache.erase(p_shape);
+		}
+
+		Ref<Texture2D> texture = p_cursor;
+		Ref<AtlasTexture> atlas_texture = p_cursor;
+		Ref<Image> image;
+		Size2 texture_size;
+		Rect2 atlas_rect;
+
+		if (texture.is_valid()) {
+			image = texture->get_data();
+		}
+
+		if (!image.is_valid() && atlas_texture.is_valid()) {
+			texture = atlas_texture->get_atlas();
+
+			atlas_rect.size.width = texture->get_width();
+			atlas_rect.size.height = texture->get_height();
+			atlas_rect.position.x = atlas_texture->get_region().position.x;
+			atlas_rect.position.y = atlas_texture->get_region().position.y;
+
+			texture_size.width = atlas_texture->get_region().size.x;
+			texture_size.height = atlas_texture->get_region().size.y;
+		} else if (image.is_valid()) {
+			texture_size.width = texture->get_width();
+			texture_size.height = texture->get_height();
+		}
+
+		ERR_FAIL_COND(!texture.is_valid());
+		ERR_FAIL_COND(p_hotspot.x < 0 || p_hotspot.y < 0);
+		ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256);
+		ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height);
+
+		image = texture->get_data();
+
+		ERR_FAIL_COND(!image.is_valid());
+
+		NSBitmapImageRep *imgrep = [[NSBitmapImageRep alloc]
+				initWithBitmapDataPlanes:NULL
+							  pixelsWide:int(texture_size.width)
+							  pixelsHigh:int(texture_size.height)
+						   bitsPerSample:8
+						 samplesPerPixel:4
+								hasAlpha:YES
+								isPlanar:NO
+						  colorSpaceName:NSDeviceRGBColorSpace
+							 bytesPerRow:int(texture_size.width) * 4
+							bitsPerPixel:32];
+
+		ERR_FAIL_COND(imgrep == nil);
+		uint8_t *pixels = [imgrep bitmapData];
+
+		int len = int(texture_size.width * texture_size.height);
+
+		for (int i = 0; i < len; i++) {
+			int row_index = floor(i / texture_size.width) + atlas_rect.position.y;
+			int column_index = (i % int(texture_size.width)) + atlas_rect.position.x;
+
+			if (atlas_texture.is_valid()) {
+				column_index = MIN(column_index, atlas_rect.size.width - 1);
+				row_index = MIN(row_index, atlas_rect.size.height - 1);
+			}
+
+			uint32_t color = image->get_pixel(column_index, row_index).to_argb32();
+
+			uint8_t alpha = (color >> 24) & 0xFF;
+			pixels[i * 4 + 0] = ((color >> 16) & 0xFF) * alpha / 255;
+			pixels[i * 4 + 1] = ((color >> 8) & 0xFF) * alpha / 255;
+			pixels[i * 4 + 2] = ((color)&0xFF) * alpha / 255;
+			pixels[i * 4 + 3] = alpha;
+		}
+
+		NSImage *nsimage = [[NSImage alloc] initWithSize:NSMakeSize(texture_size.width, texture_size.height)];
+		[nsimage addRepresentation:imgrep];
+
+		NSCursor *cursor = [[NSCursor alloc] initWithImage:nsimage hotSpot:NSMakePoint(p_hotspot.x, p_hotspot.y)];
+
+		[cursors[p_shape] release];
+		cursors[p_shape] = cursor;
+
+		Vector<Variant> params;
+		params.push_back(p_cursor);
+		params.push_back(p_hotspot);
+		cursors_cache.insert(p_shape, params);
+
+		if (p_shape == cursor_shape) {
+			if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) {
+				[cursor set];
+			}
+		}
+
+		[imgrep release];
+		[nsimage release];
+	} else {
+		// Reset to default system cursor
+		if (cursors[p_shape] != NULL) {
+			[cursors[p_shape] release];
+			cursors[p_shape] = NULL;
+		}
+
+		CursorShape c = cursor_shape;
+		cursor_shape = CURSOR_MAX;
+		cursor_set_shape(c);
+
+		cursors_cache.erase(p_shape);
+	}
+}
+
+static bool keyboard_layout_dirty = true;
+static void keyboard_layout_changed(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef user_info) {
+	keyboard_layout_dirty = true;
+}
+
+// Returns string representation of keys, if they are printable.
+static NSString *createStringForKeys(const CGKeyCode *keyCode, int length) {
+	TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource();
+	if (!currentKeyboard)
+		return nil;
+
+	CFDataRef layoutData = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData);
+	if (!layoutData)
+		return nil;
+
+	const UCKeyboardLayout *keyboardLayout = (const UCKeyboardLayout *)CFDataGetBytePtr(layoutData);
+
+	OSStatus err;
+	CFMutableStringRef output = CFStringCreateMutable(NULL, 0);
+
+	for (int i = 0; i < length; ++i) {
+		UInt32 keysDown = 0;
+		UniChar chars[4];
+		UniCharCount realLength;
+
+		err = UCKeyTranslate(keyboardLayout,
+				keyCode[i],
+				kUCKeyActionDisplay,
+				0,
+				LMGetKbdType(),
+				kUCKeyTranslateNoDeadKeysBit,
+				&keysDown,
+				sizeof(chars) / sizeof(chars[0]),
+				&realLength,
+				chars);
+
+		if (err != noErr) {
+			CFRelease(output);
+			return nil;
+		}
+
+		CFStringAppendCharacters(output, chars, 1);
+	}
+
+	return (NSString *)output;
+}
+
+DisplayServerOSX::LatinKeyboardVariant DisplayServerOSX::get_latin_keyboard_variant() const {
+	_THREAD_SAFE_METHOD_
+
+	static LatinKeyboardVariant layout = LATIN_KEYBOARD_QWERTY;
+
+	if (keyboard_layout_dirty) {
+
+		layout = LATIN_KEYBOARD_QWERTY;
+
+		CGKeyCode keys[] = { kVK_ANSI_Q, kVK_ANSI_W, kVK_ANSI_E, kVK_ANSI_R, kVK_ANSI_T, kVK_ANSI_Y };
+		NSString *test = createStringForKeys(keys, 6);
+
+		if ([test isEqualToString:@"qwertz"]) {
+			layout = LATIN_KEYBOARD_QWERTZ;
+		} else if ([test isEqualToString:@"azerty"]) {
+			layout = LATIN_KEYBOARD_AZERTY;
+		} else if ([test isEqualToString:@"qzerty"]) {
+			layout = LATIN_KEYBOARD_QZERTY;
+		} else if ([test isEqualToString:@"',.pyf"]) {
+			layout = LATIN_KEYBOARD_DVORAK;
+		} else if ([test isEqualToString:@"xvlcwk"]) {
+			layout = LATIN_KEYBOARD_NEO;
+		} else if ([test isEqualToString:@"qwfpgj"]) {
+			layout = LATIN_KEYBOARD_COLEMAK;
+		}
+
+		[test release];
+
+		keyboard_layout_dirty = false;
+		return layout;
+	}
+
+	return layout;
+}
+
+void DisplayServerOSX::_push_input(const Ref<InputEvent> &p_event) {
+	Ref<InputEvent> ev = p_event;
+	InputFilter::get_singleton()->accumulate_input_event(ev);
+}
+
+void DisplayServerOSX::_process_key_events() {
+	Ref<InputEventKey> k;
+	for (int i = 0; i < key_event_pos; i++) {
+		const KeyEvent &ke = key_event_buffer[i];
+		if (ke.raw) {
+			// Non IME input - no composite characters, pass events as is
+			k.instance();
+
+			k->set_window_id(ke.window_id);
+			_get_key_modifier_state(ke.osx_state, k);
+			k->set_pressed(ke.pressed);
+			k->set_echo(ke.echo);
+			k->set_keycode(ke.keycode);
+			k->set_physical_keycode(ke.physical_keycode);
+			k->set_unicode(ke.unicode);
+
+			_push_input(k);
+		} else {
+			// IME input
+			if ((i == 0 && ke.keycode == 0) || (i > 0 && key_event_buffer[i - 1].keycode == 0)) {
+				k.instance();
+
+				k->set_window_id(ke.window_id);
+				_get_key_modifier_state(ke.osx_state, k);
+				k->set_pressed(ke.pressed);
+				k->set_echo(ke.echo);
+				k->set_keycode(0);
+				k->set_physical_keycode(0);
+				k->set_unicode(ke.unicode);
+
+				_push_input(k);
+			}
+			if (ke.keycode != 0) {
+				k.instance();
+
+				k->set_window_id(ke.window_id);
+				_get_key_modifier_state(ke.osx_state, k);
+				k->set_pressed(ke.pressed);
+				k->set_echo(ke.echo);
+				k->set_keycode(ke.keycode);
+				k->set_physical_keycode(ke.physical_keycode);
+
+				if (i + 1 < key_event_pos && key_event_buffer[i + 1].keycode == 0) {
+					k->set_unicode(key_event_buffer[i + 1].unicode);
+				}
+
+				_push_input(k);
+			}
+		}
+	}
+
+	key_event_pos = 0;
+}
+
+void DisplayServerOSX::process_events() {
+	_THREAD_SAFE_METHOD_
+
+	while (true) {
+		NSEvent *event = [NSApp
+				nextEventMatchingMask:NSEventMaskAny
+							untilDate:[NSDate distantPast]
+							   inMode:NSDefaultRunLoopMode
+							  dequeue:YES];
+
+		if (event == nil)
+			break;
+
+		[NSApp sendEvent:event];
+	}
+
+	if (!drop_events) {
+		_process_key_events();
+		InputFilter::get_singleton()->flush_accumulated_events();
+	}
+
+	[autoreleasePool drain];
+	autoreleasePool = [[NSAutoreleasePool alloc] init];
+}
+
+void DisplayServerOSX::force_process_and_drop_events() {
+	_THREAD_SAFE_METHOD_
+
+	drop_events = true;
+	process_events();
+	drop_events = false;
+}
+
+void DisplayServerOSX::set_native_icon(const String &p_filename) {
+	_THREAD_SAFE_METHOD_
+
+	FileAccess *f = FileAccess::open(p_filename, FileAccess::READ);
+	ERR_FAIL_COND(!f);
+
+	Vector<uint8_t> data;
+	uint32_t len = f->get_len();
+	data.resize(len);
+	f->get_buffer((uint8_t *)&data.write[0], len);
+	memdelete(f);
+
+	NSData *icon_data = [[[NSData alloc] initWithBytes:&data.write[0] length:len] autorelease];
+	ERR_FAIL_COND_MSG(!icon_data, "Error reading icon data.");
+
+	NSImage *icon = [[[NSImage alloc] initWithData:icon_data] autorelease];
+	ERR_FAIL_COND_MSG(!icon, "Error loading icon.");
+
+	[NSApp setApplicationIconImage:icon];
+}
+
+void DisplayServerOSX::set_icon(const Ref<Image> &p_icon) {
+	_THREAD_SAFE_METHOD_
+
+	Ref<Image> img = p_icon;
+	img = img->duplicate();
+	img->convert(Image::FORMAT_RGBA8);
+	NSBitmapImageRep *imgrep = [[NSBitmapImageRep alloc]
+			initWithBitmapDataPlanes:NULL
+						  pixelsWide:img->get_width()
+						  pixelsHigh:img->get_height()
+					   bitsPerSample:8
+					 samplesPerPixel:4
+							hasAlpha:YES
+							isPlanar:NO
+					  colorSpaceName:NSDeviceRGBColorSpace
+						 bytesPerRow:img->get_width() * 4
+						bitsPerPixel:32];
+	ERR_FAIL_COND(imgrep == nil);
+	uint8_t *pixels = [imgrep bitmapData];
+
+	int len = img->get_width() * img->get_height();
+	const uint8_t *r = img->get_data().ptr();
+
+	/* Premultiply the alpha channel */
+	for (int i = 0; i < len; i++) {
+		uint8_t alpha = r[i * 4 + 3];
+		pixels[i * 4 + 0] = (uint8_t)(((uint16_t)r[i * 4 + 0] * alpha) / 255);
+		pixels[i * 4 + 1] = (uint8_t)(((uint16_t)r[i * 4 + 1] * alpha) / 255);
+		pixels[i * 4 + 2] = (uint8_t)(((uint16_t)r[i * 4 + 2] * alpha) / 255);
+		pixels[i * 4 + 3] = alpha;
+	}
+
+	NSImage *nsimg = [[NSImage alloc] initWithSize:NSMakeSize(img->get_width(), img->get_height())];
+	ERR_FAIL_COND(nsimg == nil);
+
+	[nsimg addRepresentation:imgrep];
+	[NSApp setApplicationIconImage:nsimg];
+
+	[imgrep release];
+	[nsimg release];
+}
+
+Vector<String> DisplayServerOSX::get_rendering_drivers_func() {
+	Vector<String> drivers;
+
+#ifdef VULKAN_ENABLED
+	drivers.push_back("vulkan");
+#endif
+#ifdef OPENGL_ENABLED
+	drivers.push_back("opengl");
+#endif
+
+	return drivers;
+}
+
+Point2i DisplayServerOSX::ime_get_selection() const {
+	return im_selection;
+}
+
+String DisplayServerOSX::ime_get_text() const {
+	return im_text;
+}
+
+DisplayServer::WindowID DisplayServerOSX::get_window_at_screen_position(const Point2i &p_position) const {
+	for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
+		Rect2i win_rect = Rect2i(window_get_position(E->key()), window_get_size(E->key()));
+		if (win_rect.has_point(p_position)) {
+			return E->key();
+		}
+	}
+	return INVALID_WINDOW_ID;
+}
+
+void DisplayServerOSX::window_attach_instance_id(ObjectID p_instance, WindowID p_window) {
+	_THREAD_SAFE_METHOD_
+
+	ERR_FAIL_COND(!windows.has(p_window));
+	windows[p_window].instance_id = p_instance;
+}
+
+ObjectID DisplayServerOSX::window_get_attached_instance_id(WindowID p_window) const {
+	_THREAD_SAFE_METHOD_
+
+	ERR_FAIL_COND_V(!windows.has(p_window), ObjectID());
+	return windows[p_window].instance_id;
+}
+
+DisplayServer *DisplayServerOSX::create_func(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
+	return memnew(DisplayServerOSX(p_rendering_driver, p_mode, p_flags, p_resolution, r_error));
+}
+
+DisplayServerOSX::WindowID DisplayServerOSX::_create_window(WindowMode p_mode, const Rect2i &p_rect) {
+	WindowID id;
+	{
+		WindowData wd;
+
+		float displayScale = 1.0;
+		if (OS_OSX::get_singleton()->is_hidpi_allowed()) {
+			// note that mainScreen is not screen #0 but the one with the keyboard focus.
+			NSScreen *screen = [NSScreen mainScreen];
+			if ([screen respondsToSelector:@selector(backingScaleFactor)]) {
+				displayScale = fmax(displayScale, [screen backingScaleFactor]);
+			}
+		}
+
+		wd.window_delegate = [[GodotWindowDelegate alloc] init];
+		ERR_FAIL_COND_V_MSG(wd.window_delegate == nil, INVALID_WINDOW_ID, "Can't create a window delegate");
+		[wd.window_delegate setWindowID:window_id_counter];
+
+		Point2i position = p_rect.position;
+		// OS X native y-coordinate relative to _get_screens_origin() is negative,
+		// Godot passes a positive value
+		position.y *= -1;
+		position += _get_screens_origin();
+
+		// initWithContentRect uses bottom-left corner of the window’s frame as origin.
+		wd.window_object = [[GodotWindow alloc]
+				initWithContentRect:NSMakeRect(position.x / displayScale, (position.y - p_rect.size.height) / displayScale, p_rect.size.width / displayScale, p_rect.size.height / displayScale)
+						  styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable
+							backing:NSBackingStoreBuffered
+							  defer:NO];
+		ERR_FAIL_COND_V_MSG(wd.window_object == nil, INVALID_WINDOW_ID, "Can't create a window");
+
+		wd.window_view = [[GodotContentView alloc] init];
+		ERR_FAIL_COND_V_MSG(wd.window_view == nil, INVALID_WINDOW_ID, "Can't create a window view");
+		[wd.window_view setWindowID:window_id_counter];
+		if (NSAppKitVersionNumber >= NSAppKitVersionNumber10_14) {
+			[wd.window_view setWantsLayer:TRUE];
+		}
+
+		if (displayScale > 1.0) {
+#if defined(OPENGL_ENABLED)
+			if (rendering_driver == "opengl_es") {
+				[wd.window_view setWantsBestResolutionOpenGLSurface:YES];
+			}
+#endif
+			[wd.window_object setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
+		} else {
+#if defined(OPENGL_ENABLED)
+			if (rendering_driver == "opengl_es") {
+				[wd.window_view setWantsBestResolutionOpenGLSurface:NO];
+			}
+#endif
+		}
+
+		[wd.window_object setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
+		[wd.window_object setContentView:wd.window_view];
+		[wd.window_object setDelegate:wd.window_delegate];
+		[wd.window_object setAcceptsMouseMovedEvents:YES];
+		[wd.window_object setRestorable:NO];
+
+		if ([wd.window_object respondsToSelector:@selector(setTabbingMode:)])
+			[wd.window_object setTabbingMode:NSWindowTabbingModeDisallowed];
+
+#if defined(VULKAN_ENABLED)
+		if (rendering_driver == "vulkan") {
+			if (context_vulkan) {
+				CALayer *layer = [wd.window_view layer];
+				layer.contentsScale = displayScale;
+				Error err = context_vulkan->window_create(window_id_counter, wd.window_view, p_rect.size.width, p_rect.size.height);
+				ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't create a Vulkan context");
+			}
+		}
+#endif
+#ifdef OPENGL_ENABLED
+		if (rendering_driver == "opengl_es") {
+			//TODO - reimplement OpenGLES
+			wd.context_gles2 = memnew(ContextGL_OSX(wd.window_view, false));
+
+			if (wd.context_gles2->initialize() != OK) {
+				memdelete(wd.context_gles2);
+				ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't create a OpenGL context");
+			}
+
+			//if (RasterizerGLES2::is_viable() == OK) {
+			//	RasterizerGLES2::register_config();
+			//	RasterizerGLES2::make_current();
+			//}
+		}
+#endif
+		id = window_id_counter++;
+		windows[id] = wd;
+	}
+
+	WindowData &wd = windows[id];
+	window_set_mode(p_mode, id);
+
+	float displayScale = _display_scale([wd.window_object screen]);
+	const NSRect contentRect = [wd.window_view frame];
+	wd.size.width = contentRect.size.width * displayScale;
+	wd.size.height = contentRect.size.height * displayScale;
+
+#if defined(OPENGL_ENABLED)
+	if (rendering_driver == "opengl_es") {
+		if (OS_OSX::singleton->is_hidpi_allowed()) {
+			[wd.window_view setWantsBestResolutionOpenGLSurface:YES];
+		} else {
+			[wd.window_view setWantsBestResolutionOpenGLSurface:NO];
+		}
+		wd.context_gles2->update();
+	}
+#endif
+#if defined(VULKAN_ENABLED)
+	if (rendering_driver == "vulkan") {
+		CALayer *layer = [wd.window_view layer];
+		layer.contentsScale = displayScale;
+		context_vulkan->window_resize(id, wd.size.width, wd.size.height);
+	}
+#endif
+
+	return id;
+}
+
+void DisplayServerOSX::_dispatch_input_events(const Ref<InputEvent> &p_event) {
+	((DisplayServerOSX *)(get_singleton()))->_dispatch_input_event(p_event);
+}
+
+void DisplayServerOSX::_dispatch_input_event(const Ref<InputEvent> &p_event) {
+	Variant ev = p_event;
+	Variant *evp = &ev;
+	Variant ret;
+	Callable::CallError ce;
+
+	Ref<InputEventFromWindow> event_from_window = p_event;
+	if (event_from_window.is_valid() && event_from_window->get_window_id() != INVALID_WINDOW_ID) {
+		//send to a window
+		if (windows.has(event_from_window->get_window_id())) {
+			Callable callable = windows[event_from_window->get_window_id()].input_event_callback;
+			if (callable.is_null()) {
+				return;
+			}
+			callable.call((const Variant **)&evp, 1, ret, ce);
+		}
+	} else {
+		//send to all windows
+		for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
+			Callable callable = E->get().input_event_callback;
+			if (callable.is_null()) {
+				continue;
+			}
+			callable.call((const Variant **)&evp, 1, ret, ce);
+		}
+	}
+}
+
+void DisplayServerOSX::release_rendering_thread() {
+	//TODO - reimplement OpenGLES
+}
+
+void DisplayServerOSX::make_rendering_thread() {
+	//TODO - reimplement OpenGLES
+}
+
+void DisplayServerOSX::swap_buffers() {
+	//TODO - reimplement OpenGLES
+}
+
+void DisplayServerOSX::console_set_visible(bool p_enabled) {
+	//TODO - open terminal and redirect
+}
+
+bool DisplayServerOSX::is_console_visible() const {
+	return isatty(STDIN_FILENO);
+}
+
+DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
+	InputFilter::get_singleton()->set_event_dispatch_function(_dispatch_input_events);
+
+	r_error = OK;
+	drop_events = false;
+
+	memset(cursors, 0, sizeof(cursors));
+	cursor_shape = CURSOR_ARROW;
+
+	key_event_pos = 0;
+	mouse_mode = MOUSE_MODE_VISIBLE;
+	last_button_state = 0;
+
+	autoreleasePool = [[NSAutoreleasePool alloc] init];
+
+	eventSource = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
+	ERR_FAIL_COND(!eventSource);
+
+	CGEventSourceSetLocalEventsSuppressionInterval(eventSource, 0.0);
+
+	// Implicitly create shared NSApplication instance
+	[GodotApplication sharedApplication];
+
+	// In case we are unbundled, make us a proper UI application
+	[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
+
+	keyboard_layout_dirty = true;
+	displays_arrangement_dirty = true;
+
+	// Register to be notified on keyboard layout changes
+	CFNotificationCenterAddObserver(CFNotificationCenterGetDistributedCenter(),
+			NULL, keyboard_layout_changed,
+			kTISNotifySelectedKeyboardInputSourceChanged, NULL,
+			CFNotificationSuspensionBehaviorDeliverImmediately);
+
+	// Register to be notified on displays arrangement changes
+	CGDisplayRegisterReconfigurationCallback(displays_arrangement_changed, NULL);
+
+	// Menu bar setup must go between sharedApplication above and
+	// finishLaunching below, in order to properly emulate the behavior
+	// of NSApplicationMain
+	NSMenuItem *menu_item;
+	NSString *title;
+
+	NSString *nsappname = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"];
+	if (nsappname == nil)
+		nsappname = [[NSProcessInfo processInfo] processName];
+
+	// Setup Dock menu
+	dock_menu = [[NSMenu alloc] initWithTitle:@"_dock"];
+
+	// Setup Apple menu
+	apple_menu = [[[NSMenu alloc] initWithTitle:@""] autorelease];
+	title = [NSString stringWithFormat:NSLocalizedString(@"About %@", nil), nsappname];
+	[apple_menu addItemWithTitle:title action:@selector(showAbout:) keyEquivalent:@""];
+
+	[apple_menu addItem:[NSMenuItem separatorItem]];
+
+	NSMenu *services = [[NSMenu alloc] initWithTitle:@""];
+	menu_item = [apple_menu addItemWithTitle:NSLocalizedString(@"Services", nil) action:nil keyEquivalent:@""];
+	[apple_menu setSubmenu:services forItem:menu_item];
+	[NSApp setServicesMenu:services];
+	[services release];
+
+	[apple_menu addItem:[NSMenuItem separatorItem]];
+
+	title = [NSString stringWithFormat:NSLocalizedString(@"Hide %@", nil), nsappname];
+	[apple_menu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h"];
+
+	menu_item = [apple_menu addItemWithTitle:NSLocalizedString(@"Hide Others", nil) action:@selector(hideOtherApplications:) keyEquivalent:@"h"];
+	[menu_item setKeyEquivalentModifierMask:(NSEventModifierFlagOption | NSEventModifierFlagCommand)];
+
+	[apple_menu addItemWithTitle:NSLocalizedString(@"Show all", nil) action:@selector(unhideAllApplications:) keyEquivalent:@""];
+
+	[apple_menu addItem:[NSMenuItem separatorItem]];
+
+	title = [NSString stringWithFormat:NSLocalizedString(@"Quit %@", nil), nsappname];
+	[apple_menu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
+
+	// Setup menu bar
+	NSMenu *main_menu = [[[NSMenu alloc] initWithTitle:@""] autorelease];
+	menu_item = [main_menu addItemWithTitle:@"" action:nil keyEquivalent:@""];
+	[main_menu setSubmenu:apple_menu forItem:menu_item];
+	[NSApp setMainMenu:main_menu];
+
+	[NSApp finishLaunching];
+
+	delegate = [[GodotApplicationDelegate alloc] init];
+	ERR_FAIL_COND(!delegate);
+	[NSApp setDelegate:delegate];
+
+	//process application:openFile: event
+	while (true) {
+		NSEvent *event = [NSApp
+				nextEventMatchingMask:NSEventMaskAny
+							untilDate:[NSDate distantPast]
+							   inMode:NSDefaultRunLoopMode
+							  dequeue:YES];
+
+		if (event == nil)
+			break;
+
+		[NSApp sendEvent:event];
+	}
+
+	//!!!!!!!!!!!!!!!!!!!!!!!!!!
+	//TODO - do Vulkan and GLES2 support checks, driver selection and fallback
+	rendering_driver = p_rendering_driver;
+
+#ifndef _MSC_VER
+#warning Forcing vulkan rendering driver because OpenGL not implemented yet
+#endif
+	rendering_driver = "vulkan";
+
+#if defined(OPENGL_ENABLED)
+	if (rendering_driver == "opengl_es") {
+		//TODO - reimplement OpenGLES
+	}
+#endif
+#if defined(VULKAN_ENABLED)
+	if (rendering_driver == "vulkan") {
+
+		context_vulkan = memnew(VulkanContextOSX);
+		if (context_vulkan->initialize() != OK) {
+			memdelete(context_vulkan);
+			context_vulkan = NULL;
+			r_error = ERR_CANT_CREATE;
+			ERR_FAIL_MSG("Could not initialize Vulkan");
+		}
+	}
+#endif
+
+	WindowID main_window = _create_window(p_mode, Rect2i(Point2i(), p_resolution));
+	for (int i = 0; i < WINDOW_FLAG_MAX; i++) {
+		if (p_flags & (1 << i)) {
+			window_set_flag(WindowFlags(i), true, main_window);
+		}
+	}
+	[windows[main_window].window_object makeKeyAndOrderFront:nil];
+
+#if defined(VULKAN_ENABLED)
+	if (rendering_driver == "vulkan") {
+		rendering_device_vulkan = memnew(RenderingDeviceVulkan);
+		rendering_device_vulkan->initialize(context_vulkan);
+
+		RasterizerRD::make_current();
+	}
+#endif
+
+	[NSApp activateIgnoringOtherApps:YES];
+
+	/*
+	visual_server = memnew(VisualServerRaster);
+	if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) {
+		visual_server = memnew(VisualServerWrapMT(visual_server, get_render_thread_mode() == RENDER_SEPARATE_THREAD));
+	}
+	visual_server->init();
+	*/
+}
+
+DisplayServerOSX::~DisplayServerOSX() {
+	if (dock_menu) {
+		[dock_menu release];
+	}
+
+	for (Map<String, NSMenu *>::Element *E = submenu.front(); E; E = E->next()) {
+		[E->get() release];
+	}
+
+	//destroy all windows
+	for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
+		[E->get().window_object setContentView:nil];
+		[E->get().window_object close];
+	}
+
+	//destroy drivers
+#if defined(VULKAN_ENABLED)
+	if (rendering_driver == "vulkan") {
+
+		if (rendering_device_vulkan) {
+			rendering_device_vulkan->finalize();
+			memdelete(rendering_device_vulkan);
+		}
+
+		if (context_vulkan)
+			memdelete(context_vulkan);
+	}
+#endif
+
+	CFNotificationCenterRemoveObserver(CFNotificationCenterGetDistributedCenter(), NULL, kTISNotifySelectedKeyboardInputSourceChanged, NULL);
+	CGDisplayRemoveReconfigurationCallback(displays_arrangement_changed, NULL);
+
+	cursors_cache.clear();
+
+	//visual_server->finish();
+	//memdelete(visual_server);
+}
+
+void DisplayServerOSX::register_osx_driver() {
+	register_create_function("osx", create_func, get_rendering_drivers_func);
+}

+ 14 - 14
platform/osx/joypad_osx.cpp

@@ -395,38 +395,38 @@ bool joypad::check_ff_features() {
 static int process_hat_value(int p_min, int p_max, int p_value) {
 	int range = (p_max - p_min + 1);
 	int value = p_value - p_min;
-	int hat_value = InputDefault::HAT_MASK_CENTER;
+	int hat_value = InputFilter::HAT_MASK_CENTER;
 	if (range == 4) {
 		value *= 2;
 	}
 
 	switch (value) {
 		case 0:
-			hat_value = InputDefault::HAT_MASK_UP;
+			hat_value = InputFilter::HAT_MASK_UP;
 			break;
 		case 1:
-			hat_value = InputDefault::HAT_MASK_UP | InputDefault::HAT_MASK_RIGHT;
+			hat_value = InputFilter::HAT_MASK_UP | InputFilter::HAT_MASK_RIGHT;
 			break;
 		case 2:
-			hat_value = InputDefault::HAT_MASK_RIGHT;
+			hat_value = InputFilter::HAT_MASK_RIGHT;
 			break;
 		case 3:
-			hat_value = InputDefault::HAT_MASK_DOWN | InputDefault::HAT_MASK_RIGHT;
+			hat_value = InputFilter::HAT_MASK_DOWN | InputFilter::HAT_MASK_RIGHT;
 			break;
 		case 4:
-			hat_value = InputDefault::HAT_MASK_DOWN;
+			hat_value = InputFilter::HAT_MASK_DOWN;
 			break;
 		case 5:
-			hat_value = InputDefault::HAT_MASK_DOWN | InputDefault::HAT_MASK_LEFT;
+			hat_value = InputFilter::HAT_MASK_DOWN | InputFilter::HAT_MASK_LEFT;
 			break;
 		case 6:
-			hat_value = InputDefault::HAT_MASK_LEFT;
+			hat_value = InputFilter::HAT_MASK_LEFT;
 			break;
 		case 7:
-			hat_value = InputDefault::HAT_MASK_UP | InputDefault::HAT_MASK_LEFT;
+			hat_value = InputFilter::HAT_MASK_UP | InputFilter::HAT_MASK_LEFT;
 			break;
 		default:
-			hat_value = InputDefault::HAT_MASK_CENTER;
+			hat_value = InputFilter::HAT_MASK_CENTER;
 			break;
 	}
 	return hat_value;
@@ -438,8 +438,8 @@ void JoypadOSX::poll_joypads() const {
 	}
 }
 
-static const InputDefault::JoyAxis axis_correct(int p_value, int p_min, int p_max) {
-	InputDefault::JoyAxis jx;
+static const InputFilter::JoyAxis axis_correct(int p_value, int p_min, int p_max) {
+	InputFilter::JoyAxis jx;
 	if (p_min < 0) {
 		jx.min = -1;
 		if (p_value < 0) {
@@ -571,9 +571,9 @@ void JoypadOSX::config_hid_manager(CFArrayRef p_matching_array) const {
 	}
 }
 
-JoypadOSX::JoypadOSX() {
+JoypadOSX::JoypadOSX(InputFilter *in) {
 	self = this;
-	input = (InputDefault *)InputFilter::get_singleton();
+	input = in;
 
 	int okay = 1;
 	const void *vals[] = {

+ 2 - 2
platform/osx/joypad_osx.h

@@ -94,7 +94,7 @@ class JoypadOSX {
 	};
 
 private:
-	InputDefault *input;
+	InputFilter *input;
 	IOHIDManagerRef hid_manager;
 
 	Vector<joypad> device_list;
@@ -118,7 +118,7 @@ public:
 	void _device_added(IOReturn p_res, IOHIDDeviceRef p_device);
 	void _device_removed(IOReturn p_res, IOHIDDeviceRef p_device);
 
-	JoypadOSX();
+	JoypadOSX(InputFilter *in);
 	~JoypadOSX();
 };
 

+ 10 - 233
platform/osx/os_osx.h

@@ -31,56 +31,20 @@
 #ifndef OS_OSX_H
 #define OS_OSX_H
 
-#define BitMap _QDBitMap // Suppress deprecated QuickDraw definition.
-
-#include "core/input/inpu_filter.h"
+#include "core/input/input_filter.h"
 #include "crash_handler_osx.h"
 #include "drivers/coreaudio/audio_driver_coreaudio.h"
 #include "drivers/coremidi/midi_driver_coremidi.h"
 #include "drivers/unix/os_unix.h"
 #include "joypad_osx.h"
 #include "servers/audio_server.h"
-#include "servers/visual/rasterizer.h"
-#include "servers/visual/visual_server_wrap_mt.h"
-#include "servers/visual_server.h"
-
-#if defined(OPENGL_ENABLED)
-#include "context_gl_osx.h"
-#endif
-
-#if defined(VULKAN_ENABLED)
-#include "drivers/vulkan/rendering_device_vulkan.h"
-#include "platform/osx/vulkan_context_osx.h"
-#endif
-
-#include <AppKit/AppKit.h>
-#include <AppKit/NSCursor.h>
-#include <ApplicationServices/ApplicationServices.h>
-#include <CoreVideo/CoreVideo.h>
-
-#undef BitMap
-#undef CursorShape
 
 class OS_OSX : public OS_Unix {
-public:
-	struct KeyEvent {
-		unsigned int osx_state;
-		bool pressed;
-		bool echo;
-		bool raw;
-		uint32_t keycode;
-		uint32_t physical_keycode;
-		uint32_t unicode;
-	};
-
-	Vector<KeyEvent> key_event_buffer;
-	int key_event_pos;
+	virtual void delete_main_loop();
 
 	bool force_quit;
-	VisualServer *visual_server;
 
-	List<String> args;
-	MainLoop *main_loop;
+	JoypadOSX *joypad_osx;
 
 #ifdef COREAUDIO_ENABLED
 	AudioDriverCoreAudio audio_driver;
@@ -89,143 +53,27 @@ public:
 	MIDIDriverCoreMidi midi_driver;
 #endif
 
-	InputDefault *input;
-	JoypadOSX *joypad_osx;
-
-	/* objc */
-
-	CGEventSourceRef eventSource;
-
-	void process_events();
-	void process_key_events();
-
-	//          pthread_key_t   current;
-	bool mouse_grab;
-	Point2 mouse_pos;
-
-	id delegate;
-	id window_delegate;
-	id window_object;
-	id window_view;
-	id autoreleasePool;
-	id cursor;
-
-#if defined(OPENGL_ENABLED)
-	ContextGL_OSX *context_gles2;
-#endif
-
-#if defined(VULKAN_ENABLED)
-	VulkanContextOSX *context_vulkan;
-	RenderingDeviceVulkan *rendering_device_vulkan;
-#endif
-
-	bool layered_window;
-
-	CursorShape cursor_shape;
-	NSCursor *cursors[CURSOR_MAX];
-	Map<CursorShape, Vector<Variant>> cursors_cache;
-	MouseMode mouse_mode;
-
-	String title;
-	bool minimized;
-	bool maximized;
-	bool zoomed;
-	bool resizable;
-	bool window_focused;
-
-	Size2 window_size;
-	Rect2 restore_rect;
-
-	String open_with_filename;
-
-	Point2 im_position;
-	bool im_active;
-	String im_text;
-	Point2 im_selection;
-
-	Size2 min_size;
-	Size2 max_size;
-
 	CrashHandler crash_handler;
 
-	float _mouse_scale(float p_scale) {
-		if (_display_scale() > 1.0)
-			return p_scale;
-		else
-			return 1.0;
-	}
-
-	float _display_scale() const;
-	float _display_scale(id screen) const;
-
-	void _update_window();
-
-	int video_driver_index;
-	virtual int get_current_video_driver() const;
-
-	struct GlobalMenuItem {
-		String label;
-		Variant signal;
-		Variant meta;
-
-		GlobalMenuItem() {
-			//NOP
-		}
-
-		GlobalMenuItem(const String &p_label, const Variant &p_signal, const Variant &p_meta) {
-			label = p_label;
-			signal = p_signal;
-			meta = p_meta;
-		}
-	};
-
-	Map<String, Vector<GlobalMenuItem>> global_menus;
+	MainLoop *main_loop;
 
-	void _update_global_menu();
+public:
+	String open_with_filename;
 
 protected:
 	virtual void initialize_core();
-	virtual Error initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver);
+	virtual void initialize();
 	virtual void finalize();
 
+	virtual void initialize_joypads();
+
 	virtual void set_main_loop(MainLoop *p_main_loop);
-	virtual void delete_main_loop();
 
 public:
-	static OS_OSX *singleton;
-
-	void global_menu_add_item(const String &p_menu, const String &p_label, const Variant &p_signal, const Variant &p_meta);
-	void global_menu_add_separator(const String &p_menu);
-	void global_menu_remove_item(const String &p_menu, int p_idx);
-	void global_menu_clear(const String &p_menu);
-
-	void wm_minimized(bool p_minimized);
-
 	virtual String get_name() const;
 
-	virtual void alert(const String &p_alert, const String &p_title = "ALERT!");
-
 	virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false);
 
-	virtual void set_cursor_shape(CursorShape p_shape);
-	virtual CursorShape get_cursor_shape() const;
-	virtual void set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot);
-
-	virtual void set_mouse_show(bool p_show);
-	virtual void set_mouse_grab(bool p_grab);
-	virtual bool is_mouse_grab_enabled() const;
-	virtual void warp_mouse_position(const Point2 &p_to);
-	virtual Point2 get_mouse_position() const;
-	virtual int get_mouse_button_state() const;
-	void update_real_mouse_position();
-	virtual void set_window_title(const String &p_title);
-
-	virtual Size2 get_window_size() const;
-	virtual Size2 get_real_window_size() const;
-
-	virtual void set_native_icon(const String &p_filename);
-	virtual void set_icon(const Ref<Image> &p_icon);
-
 	virtual MainLoop *get_main_loop() const;
 
 	virtual String get_config_path() const;
@@ -236,95 +84,24 @@ public:
 
 	virtual String get_system_dir(SystemDir p_dir) const;
 
-	virtual bool can_draw() const;
-
-	virtual void set_clipboard(const String &p_text);
-	virtual String get_clipboard() const;
-
-	virtual void release_rendering_thread();
-	virtual void make_rendering_thread();
-	virtual void swap_buffers();
-
 	Error shell_open(String p_uri);
-	void push_input(const Ref<InputEvent> &p_event);
 
 	String get_locale() const;
 
-	virtual void set_video_mode(const VideoMode &p_video_mode, int p_screen = 0);
-	virtual VideoMode get_video_mode(int p_screen = 0) const;
-	virtual void get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen = 0) const;
-
 	virtual String get_executable_path() const;
 
-	virtual LatinKeyboardVariant get_latin_keyboard_variant() const;
-
-	virtual void move_window_to_foreground();
-
-	virtual int get_screen_count() const;
-	virtual int get_current_screen() const;
-	virtual void set_current_screen(int p_screen);
-	virtual Point2 get_screen_position(int p_screen = -1) const;
-	virtual Size2 get_screen_size(int p_screen = -1) const;
-	virtual int get_screen_dpi(int p_screen = -1) const;
-
-	virtual Point2 get_window_position() const;
-	virtual void set_window_position(const Point2 &p_position);
-	virtual Size2 get_max_window_size() const;
-	virtual Size2 get_min_window_size() const;
-	virtual void set_min_window_size(const Size2 p_size);
-	virtual void set_max_window_size(const Size2 p_size);
-	virtual void set_window_size(const Size2 p_size);
-	virtual void set_window_fullscreen(bool p_enabled);
-	virtual bool is_window_fullscreen() const;
-	virtual void set_window_resizable(bool p_enabled);
-	virtual bool is_window_resizable() const;
-	virtual void set_window_minimized(bool p_enabled);
-	virtual bool is_window_minimized() const;
-	virtual void set_window_maximized(bool p_enabled);
-	virtual bool is_window_maximized() const;
-	virtual void set_window_always_on_top(bool p_enabled);
-	virtual bool is_window_always_on_top() const;
-	virtual bool is_window_focused() const;
-	virtual void request_attention();
-	virtual String get_joy_guid(int p_device) const;
-
-	virtual void set_borderless_window(bool p_borderless);
-	virtual bool get_borderless_window();
-
-	virtual bool get_window_per_pixel_transparency_enabled() const;
-	virtual void set_window_per_pixel_transparency_enabled(bool p_enabled);
-
-	virtual void set_ime_active(const bool p_active);
-	virtual void set_ime_position(const Point2 &p_pos);
-	virtual Point2 get_ime_selection() const;
-	virtual String get_ime_text() const;
-
-	virtual String get_unique_id() const;
+	virtual String get_unique_id() const; //++
 
 	virtual bool _check_internal_feature_support(const String &p_feature);
 
-	virtual void _set_use_vsync(bool p_enable);
-	//virtual bool is_vsync_enabled() const;
-
 	void run();
 
-	void set_mouse_mode(MouseMode p_mode);
-	MouseMode get_mouse_mode() const;
-
 	void disable_crash_handler();
 	bool is_disable_crash_handler() const;
 
 	virtual Error move_to_trash(const String &p_path);
 
-	void force_process_input();
-
 	OS_OSX();
-
-private:
-	Point2 get_native_screen_position(int p_screen) const;
-	Point2 get_native_window_position() const;
-	void set_native_window_position(const Point2 &p_position);
-	Point2 get_screens_origin() const;
 };
 
 #endif

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 202 - 2795
platform/osx/os_osx.mm


+ 1 - 1
platform/osx/vulkan_context_osx.h

@@ -39,7 +39,7 @@ class VulkanContextOSX : public VulkanContext {
 	virtual const char *_get_platform_surface_extension() const;
 
 public:
-	int window_create(id p_window, int p_width, int p_height);
+	Error window_create(DisplayServer::WindowID p_window_id, id p_window, int p_width, int p_height);
 
 	VulkanContextOSX();
 	~VulkanContextOSX();

+ 3 - 3
platform/osx/vulkan_context_osx.mm

@@ -35,7 +35,7 @@ const char *VulkanContextOSX::_get_platform_surface_extension() const {
 	return VK_MVK_MACOS_SURFACE_EXTENSION_NAME;
 }
 
-int VulkanContextOSX::window_create(id p_window, int p_width, int p_height) {
+Error VulkanContextOSX::window_create(DisplayServer::WindowID p_window_id, id p_window, int p_width, int p_height) {
 
 	VkMacOSSurfaceCreateInfoMVK createInfo;
 	createInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK;
@@ -45,8 +45,8 @@ int VulkanContextOSX::window_create(id p_window, int p_width, int p_height) {
 
 	VkSurfaceKHR surface;
 	VkResult err = vkCreateMacOSSurfaceMVK(_get_instance(), &createInfo, NULL, &surface);
-	ERR_FAIL_COND_V(err, -1);
-	return _window_create(surface, p_width, p_height);
+	ERR_FAIL_COND_V(err, ERR_CANT_CREATE);
+	return _window_create(p_window_id, surface, p_width, p_height);
 }
 
 VulkanContextOSX::VulkanContextOSX() {

+ 13 - 8
scene/gui/line_edit.cpp

@@ -913,9 +913,10 @@ void LineEdit::_notification(int p_what) {
 			}
 
 			if (has_focus()) {
-
-				DisplayServer::get_singleton()->window_set_ime_active(true, get_viewport()->get_window_id());
-				DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + Point2(using_placeholder ? 0 : x_ofs, y_ofs + caret_height), get_viewport()->get_window_id());
+				if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID) {
+					DisplayServer::get_singleton()->window_set_ime_active(true, get_viewport()->get_window_id());
+					DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + Point2(using_placeholder ? 0 : x_ofs, y_ofs + caret_height), get_viewport()->get_window_id());
+				}
 			}
 		} break;
 		case NOTIFICATION_FOCUS_ENTER: {
@@ -926,9 +927,11 @@ void LineEdit::_notification(int p_what) {
 				draw_caret = true;
 			}
 
-			DisplayServer::get_singleton()->window_set_ime_active(true, get_viewport()->get_window_id());
-			Point2 cursor_pos = Point2(get_cursor_position(), 1) * get_minimum_size().height;
-			DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + cursor_pos, get_viewport()->get_window_id());
+			if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID) {
+				DisplayServer::get_singleton()->window_set_ime_active(true, get_viewport()->get_window_id());
+				Point2 cursor_pos = Point2(get_cursor_position(), 1) * get_minimum_size().height;
+				DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + cursor_pos, get_viewport()->get_window_id());
+			}
 
 			if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD))
 				DisplayServer::get_singleton()->virtual_keyboard_show(text, get_global_rect(), max_length);
@@ -940,8 +943,10 @@ void LineEdit::_notification(int p_what) {
 				caret_blink_timer->stop();
 			}
 
-			DisplayServer::get_singleton()->window_set_ime_position(Point2(), get_viewport()->get_window_id());
-			DisplayServer::get_singleton()->window_set_ime_active(false, get_viewport()->get_window_id());
+			if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID) {
+				DisplayServer::get_singleton()->window_set_ime_position(Point2(), get_viewport()->get_window_id());
+				DisplayServer::get_singleton()->window_set_ime_active(false, get_viewport()->get_window_id());
+			}
 			ime_text = "";
 			ime_selection = Point2();
 

+ 13 - 7
scene/gui/text_edit.cpp

@@ -1754,8 +1754,10 @@ void TextEdit::_notification(int p_what) {
 			}
 
 			if (has_focus()) {
-				DisplayServer::get_singleton()->window_set_ime_active(true);
-				DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + cursor_pos + Point2(0, get_row_height()));
+				if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID) {
+					DisplayServer::get_singleton()->window_set_ime_active(true, get_viewport()->get_window_id());
+					DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + cursor_pos + Point2(0, get_row_height()), get_viewport()->get_window_id());
+				}
 			}
 		} break;
 		case NOTIFICATION_FOCUS_ENTER: {
@@ -1766,9 +1768,11 @@ void TextEdit::_notification(int p_what) {
 				draw_caret = true;
 			}
 
-			DisplayServer::get_singleton()->window_set_ime_active(true);
-			Point2 cursor_pos = Point2(cursor_get_column(), cursor_get_line()) * get_row_height();
-			DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + cursor_pos);
+			if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID) {
+				DisplayServer::get_singleton()->window_set_ime_active(true, get_viewport()->get_window_id());
+				Point2 cursor_pos = Point2(cursor_get_column(), cursor_get_line()) * get_row_height();
+				DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + cursor_pos, get_viewport()->get_window_id());
+			}
 
 			if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD))
 				DisplayServer::get_singleton()->virtual_keyboard_show(get_text(), get_global_rect());
@@ -1779,8 +1783,10 @@ void TextEdit::_notification(int p_what) {
 				caret_blink_timer->stop();
 			}
 
-			DisplayServer::get_singleton()->window_set_ime_position(Point2());
-			DisplayServer::get_singleton()->window_set_ime_active(false);
+			if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID) {
+				DisplayServer::get_singleton()->window_set_ime_position(Point2(), get_viewport()->get_window_id());
+				DisplayServer::get_singleton()->window_set_ime_active(false, get_viewport()->get_window_id());
+			}
 			ime_text = "";
 			ime_selection = Point2();
 

+ 1 - 0
scene/main/node.h

@@ -252,6 +252,7 @@ public:
 		NOTIFICATION_WM_CLOSE_REQUEST = 1006,
 		NOTIFICATION_WM_GO_BACK_REQUEST = 1007,
 		NOTIFICATION_WM_SIZE_CHANGED = 1008,
+		NOTIFICATION_WM_DPI_CHANGE = 1009,
 
 		NOTIFICATION_OS_MEMORY_WARNING = MainLoop::NOTIFICATION_OS_MEMORY_WARNING,
 		NOTIFICATION_TRANSLATION_CHANGED = MainLoop::NOTIFICATION_TRANSLATION_CHANGED,

+ 0 - 7
scene/main/scene_tree.cpp

@@ -1104,12 +1104,6 @@ void SceneTree::add_current_scene(Node *p_current) {
 	root->add_child(p_current);
 }
 
-void SceneTree::global_menu_action(const Variant &p_id, const Variant &p_meta) {
-
-	emit_signal("global_menu_action", p_id, p_meta);
-	MainLoop::global_menu_action(p_id, p_meta);
-}
-
 Ref<SceneTreeTimer> SceneTree::create_timer(float p_delay_sec, bool p_process_pause) {
 
 	Ref<SceneTreeTimer> stt;
@@ -1315,7 +1309,6 @@ void SceneTree::_bind_methods() {
 	ADD_SIGNAL(MethodInfo("physics_frame"));
 
 	ADD_SIGNAL(MethodInfo("files_dropped", PropertyInfo(Variant::PACKED_STRING_ARRAY, "files"), PropertyInfo(Variant::INT, "screen")));
-	ADD_SIGNAL(MethodInfo("global_menu_action", PropertyInfo(Variant::NIL, "id"), PropertyInfo(Variant::NIL, "meta")));
 	ADD_SIGNAL(MethodInfo("network_peer_connected", PropertyInfo(Variant::INT, "id")));
 	ADD_SIGNAL(MethodInfo("network_peer_disconnected", PropertyInfo(Variant::INT, "id")));
 	ADD_SIGNAL(MethodInfo("connected_to_server"));

+ 0 - 1
scene/main/scene_tree.h

@@ -331,7 +331,6 @@ public:
 
 	static SceneTree *get_singleton() { return singleton; }
 
-	void global_menu_action(const Variant &p_id, const Variant &p_meta);
 	void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const;
 
 	//network API

+ 11 - 5
scene/main/window.cpp

@@ -104,19 +104,22 @@ void Window::set_max_size(const Size2i &p_max_size) {
 	}
 	_update_window_size();
 }
-Size2i Window::get_max_size() const {
 
-	return min_size;
+Size2i Window::get_max_size() const {
+	if (window_id != DisplayServer::INVALID_WINDOW_ID) {
+		max_size = DisplayServer::get_singleton()->window_get_max_size(window_id);
+	}
+	return max_size;
 }
 
 void Window::set_min_size(const Size2i &p_min_size) {
-
 	min_size = p_min_size;
 	if (window_id != DisplayServer::INVALID_WINDOW_ID) {
-		DisplayServer::get_singleton()->window_set_min_size(max_size, window_id);
+		DisplayServer::get_singleton()->window_set_min_size(min_size, window_id);
 	}
 	_update_window_size();
 }
+
 Size2i Window::get_min_size() const {
 	if (window_id != DisplayServer::INVALID_WINDOW_ID) {
 		min_size = DisplayServer::get_singleton()->window_get_min_size(window_id);
@@ -338,6 +341,10 @@ void Window::_event_callback(DisplayServer::WindowEvent p_event) {
 			_propagate_window_notification(this, NOTIFICATION_WM_GO_BACK_REQUEST);
 			emit_signal("go_back_requested");
 		} break;
+		case DisplayServer::WINDOW_EVENT_DPI_CHANGE: {
+			_propagate_window_notification(this, NOTIFICATION_WM_DPI_CHANGE);
+			emit_signal("dpi_changed");
+		} break;
 	}
 }
 
@@ -1360,7 +1367,6 @@ void Window::_bind_methods() {
 	ADD_SIGNAL(MethodInfo("close_requested"));
 	ADD_SIGNAL(MethodInfo("go_back_requested"));
 	ADD_SIGNAL(MethodInfo("visibility_changed"));
-	ADD_SIGNAL(MethodInfo("files_dropped"));
 	ADD_SIGNAL(MethodInfo("about_to_popup"));
 
 	BIND_CONSTANT(NOTIFICATION_VISIBILITY_CHANGED);

+ 106 - 8
servers/display_server.cpp

@@ -30,6 +30,7 @@
 
 #include "display_server.h"
 #include "core/input/input_filter.h"
+#include "scene/resources/texture.h"
 
 DisplayServer *DisplayServer::singleton = nullptr;
 DisplayServer::SwitchVSyncCallbackInThread DisplayServer::switch_vsync_function = nullptr;
@@ -39,16 +40,86 @@ bool DisplayServer::hidpi_allowed = false;
 DisplayServer::DisplayServerCreate DisplayServer::server_create_functions[DisplayServer::MAX_SERVERS];
 int DisplayServer::server_create_count = 0;
 
-void DisplayServer::global_menu_add_item(const String &p_menu, const String &p_label, const Variant &p_signal, const Variant &p_meta) {
+void DisplayServer::global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag) {
 	WARN_PRINT("Global menus not supported by this display server.");
 }
-void DisplayServer::global_menu_add_separator(const String &p_menu) {
+
+void DisplayServer::global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag) {
+	WARN_PRINT("Global menus not supported by this display server.");
+}
+
+void DisplayServer::global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu) {
+	WARN_PRINT("Global menus not supported by this display server.");
+}
+
+void DisplayServer::global_menu_add_separator(const String &p_menu_root) {
+	WARN_PRINT("Global menus not supported by this display server.");
+}
+
+void DisplayServer::global_menu_set_item_callback(const String &p_menu_root, int p_idx, const Callable &p_callback) {
+	WARN_PRINT("Global menus not supported by this display server.");
+}
+
+bool DisplayServer::global_menu_is_item_checked(const String &p_menu_root, int p_idx) const {
+	WARN_PRINT("Global menus not supported by this display server.");
+	return false;
+}
+
+bool DisplayServer::global_menu_is_item_checkable(const String &p_menu_root, int p_idx) const {
+	WARN_PRINT("Global menus not supported by this display server.");
+	return false;
+}
+
+Callable DisplayServer::global_menu_get_item_callback(const String &p_menu_root, int p_idx) {
+	WARN_PRINT("Global menus not supported by this display server.");
+	return Callable();
+}
+
+Variant DisplayServer::global_menu_get_item_tag(const String &p_menu_root, int p_idx) {
+	WARN_PRINT("Global menus not supported by this display server.");
+	return Variant();
+}
+
+String DisplayServer::global_menu_get_item_text(const String &p_menu_root, int p_idx) {
+	WARN_PRINT("Global menus not supported by this display server.");
+	return String();
+}
+
+String DisplayServer::global_menu_get_item_submenu(const String &p_menu_root, int p_idx) {
+	WARN_PRINT("Global menus not supported by this display server.");
+	return String();
+}
+
+void DisplayServer::global_menu_set_item_checked(const String &p_menu_root, int p_idx, bool p_checked) {
+	WARN_PRINT("Global menus not supported by this display server.");
+}
+
+void DisplayServer::global_menu_set_item_checkable(const String &p_menu_root, int p_idx, bool p_checkable) {
+	WARN_PRINT("Global menus not supported by this display server.");
+}
+
+void DisplayServer::global_menu_set_item_tag(const String &p_menu_root, int p_idx, const Variant &p_tag) {
+	WARN_PRINT("Global menus not supported by this display server.");
+}
+
+void DisplayServer::global_menu_set_item_text(const String &p_menu_root, int p_idx, const String &p_text) {
 	WARN_PRINT("Global menus not supported by this display server.");
 }
-void DisplayServer::global_menu_remove_item(const String &p_menu, int p_idx) {
+
+void DisplayServer::global_menu_set_item_submenu(const String &p_menu_root, int p_idx, const String &p_submenu) {
+	WARN_PRINT("Global menus not supported by this display server.");
+}
+
+int DisplayServer::global_menu_get_item_count(const String &p_menu_root) const {
+	WARN_PRINT("Global menus not supported by this display server.");
+	return 0;
+}
+
+void DisplayServer::global_menu_remove_item(const String &p_menu_root, int p_idx) {
 	WARN_PRINT("Global menus not supported by this display server.");
 }
-void DisplayServer::global_menu_clear(const String &p_menu) {
+
+void DisplayServer::global_menu_clear(const String &p_menu_root) {
 	WARN_PRINT("Global menus not supported by this display server.");
 }
 
@@ -62,6 +133,9 @@ DisplayServer::MouseMode DisplayServer::mouse_get_mode() const {
 void DisplayServer::mouse_warp_to_position(const Point2i &p_to) {
 	WARN_PRINT("Mouse warping is not supported by this display server.");
 }
+Point2i DisplayServer::mouse_get_absolute_position() const {
+	ERR_FAIL_V_MSG(Point2i(), "Mouse is not supported by this display server.");
+}
 Point2i DisplayServer::mouse_get_position() const {
 	ERR_FAIL_V_MSG(Point2i(), "Mouse is not supported by this display server.");
 }
@@ -83,6 +157,10 @@ DisplayServer::ScreenOrientation DisplayServer::screen_get_orientation(int p_scr
 	return SCREEN_LANDSCAPE;
 }
 
+float DisplayServer::screen_get_scale(int p_screen) const {
+	return 1.0f;
+};
+
 bool DisplayServer::screen_is_touchscreen(int p_screen) const {
 	//return false;
 	return InputFilter::get_singleton() && InputFilter::get_singleton()->is_emulating_touch_from_mouse();
@@ -232,10 +310,27 @@ void DisplayServer::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("has_feature", "feature"), &DisplayServer::has_feature);
 	ClassDB::bind_method(D_METHOD("get_name"), &DisplayServer::get_name);
 
-	ClassDB::bind_method(D_METHOD("global_menu_add_item", "menu", "label", "id", "meta"), &DisplayServer::global_menu_add_item);
-	ClassDB::bind_method(D_METHOD("global_menu_add_separator", "menu"), &DisplayServer::global_menu_add_separator);
-	ClassDB::bind_method(D_METHOD("global_menu_remove_item", "menu", "idx"), &DisplayServer::global_menu_remove_item);
-	ClassDB::bind_method(D_METHOD("global_menu_clear", "menu"), &DisplayServer::global_menu_clear);
+	ClassDB::bind_method(D_METHOD("global_menu_add_item", "menu_root", "label", "callback", "tag"), &DisplayServer::global_menu_add_item, DEFVAL(Variant()));
+	ClassDB::bind_method(D_METHOD("global_menu_add_check_item", "menu_root", "label", "callback", "tag"), &DisplayServer::global_menu_add_check_item, DEFVAL(Variant()));
+	ClassDB::bind_method(D_METHOD("global_menu_add_submenu_item", "menu_root", "label", "submenu"), &DisplayServer::global_menu_add_submenu_item);
+	ClassDB::bind_method(D_METHOD("global_menu_add_separator", "menu_root"), &DisplayServer::global_menu_add_separator);
+
+	ClassDB::bind_method(D_METHOD("global_menu_is_item_checked", "menu_root", "idx"), &DisplayServer::global_menu_is_item_checked);
+	ClassDB::bind_method(D_METHOD("global_menu_is_item_checkable", "menu_root", "idx"), &DisplayServer::global_menu_is_item_checkable);
+	ClassDB::bind_method(D_METHOD("global_menu_get_item_callback", "menu_root", "idx"), &DisplayServer::global_menu_get_item_callback);
+	ClassDB::bind_method(D_METHOD("global_menu_get_item_tag", "menu_root", "idx"), &DisplayServer::global_menu_get_item_tag);
+	ClassDB::bind_method(D_METHOD("global_menu_get_item_text", "menu_root", "idx"), &DisplayServer::global_menu_get_item_text);
+	ClassDB::bind_method(D_METHOD("global_menu_get_item_submenu", "menu_root", "idx"), &DisplayServer::global_menu_get_item_submenu);
+
+	ClassDB::bind_method(D_METHOD("global_menu_set_item_checked", "menu_root", "idx", "checked"), &DisplayServer::global_menu_set_item_checked);
+	ClassDB::bind_method(D_METHOD("global_menu_set_item_checkable", "menu_root", "idx", "checkable"), &DisplayServer::global_menu_set_item_checkable);
+	ClassDB::bind_method(D_METHOD("global_menu_set_item_callback", "menu_root", "idx", "callback"), &DisplayServer::global_menu_set_item_callback);
+	ClassDB::bind_method(D_METHOD("global_menu_set_item_tag", "menu_root", "idx", "tag"), &DisplayServer::global_menu_set_item_tag);
+	ClassDB::bind_method(D_METHOD("global_menu_set_item_text", "menu_root", "idx", "text"), &DisplayServer::global_menu_set_item_text);
+	ClassDB::bind_method(D_METHOD("global_menu_set_item_submenu", "menu_root", "idx", "submenu"), &DisplayServer::global_menu_set_item_submenu);
+
+	ClassDB::bind_method(D_METHOD("global_menu_remove_item", "menu_root", "idx"), &DisplayServer::global_menu_remove_item);
+	ClassDB::bind_method(D_METHOD("global_menu_clear", "menu_root"), &DisplayServer::global_menu_clear);
 
 	ClassDB::bind_method(D_METHOD("alert", "text", "title"), &DisplayServer::alert, DEFVAL("Alert!"));
 
@@ -244,6 +339,7 @@ void DisplayServer::_bind_methods() {
 
 	ClassDB::bind_method(D_METHOD("mouse_warp_to_position", "position"), &DisplayServer::mouse_warp_to_position);
 	ClassDB::bind_method(D_METHOD("mouse_get_position"), &DisplayServer::mouse_get_position);
+	ClassDB::bind_method(D_METHOD("mouse_get_absolute_position"), &DisplayServer::mouse_get_absolute_position);
 	ClassDB::bind_method(D_METHOD("mouse_get_button_state"), &DisplayServer::mouse_get_button_state);
 
 	ClassDB::bind_method(D_METHOD("clipboard_set", "clipboard"), &DisplayServer::clipboard_set);
@@ -254,6 +350,7 @@ void DisplayServer::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("screen_get_size", "screen"), &DisplayServer::screen_get_size, DEFVAL(SCREEN_OF_MAIN_WINDOW));
 	ClassDB::bind_method(D_METHOD("screen_get_usable_rect", "screen"), &DisplayServer::screen_get_usable_rect, DEFVAL(SCREEN_OF_MAIN_WINDOW));
 	ClassDB::bind_method(D_METHOD("screen_get_dpi", "screen"), &DisplayServer::screen_get_dpi, DEFVAL(SCREEN_OF_MAIN_WINDOW));
+	ClassDB::bind_method(D_METHOD("screen_get_scale", "screen"), &DisplayServer::screen_get_scale, DEFVAL(SCREEN_OF_MAIN_WINDOW));
 	ClassDB::bind_method(D_METHOD("screen_is_touchscreen", "screen"), &DisplayServer::screen_is_touchscreen, DEFVAL(SCREEN_OF_MAIN_WINDOW));
 
 	ClassDB::bind_method(D_METHOD("screen_set_orientation", "orientation", "screen"), &DisplayServer::screen_set_orientation, DEFVAL(SCREEN_OF_MAIN_WINDOW));
@@ -440,6 +537,7 @@ void DisplayServer::_bind_methods() {
 	BIND_ENUM_CONSTANT(WINDOW_EVENT_FOCUS_OUT);
 	BIND_ENUM_CONSTANT(WINDOW_EVENT_CLOSE_REQUEST);
 	BIND_ENUM_CONSTANT(WINDOW_EVENT_GO_BACK_REQUEST);
+	BIND_ENUM_CONSTANT(WINDOW_EVENT_DPI_CHANGE);
 }
 
 void DisplayServer::register_create_function(const char *p_name, CreateFunction p_function, GetVideoDriversFunction p_get_drivers) {

+ 29 - 5
servers/display_server.h

@@ -36,6 +36,8 @@
 #include "core/os/os.h"
 #include "core/resource.h"
 
+class Texture2D;
+
 class DisplayServer : public Object {
 	GDCLASS(DisplayServer, Object)
 
@@ -47,6 +49,7 @@ public:
 	_FORCE_INLINE_ static DisplayServer *get_singleton() {
 		return singleton;
 	}
+
 	enum WindowMode {
 		WINDOW_MODE_WINDOWED,
 		WINDOW_MODE_MINIMIZED,
@@ -103,16 +106,34 @@ public:
 		FEATURE_ORIENTATION,
 		FEATURE_SWAP_BUFFERS,
 		FEATURE_KEEP_SCREEN_ON,
-
 	};
 
 	virtual bool has_feature(Feature p_feature) const = 0;
 	virtual String get_name() const = 0;
 
-	virtual void global_menu_add_item(const String &p_menu, const String &p_label, const Variant &p_signal, const Variant &p_meta);
-	virtual void global_menu_add_separator(const String &p_menu);
-	virtual void global_menu_remove_item(const String &p_menu, int p_idx);
-	virtual void global_menu_clear(const String &p_menu);
+	virtual void global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag = Variant());
+	virtual void global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag = Variant());
+	virtual void global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu);
+	virtual void global_menu_add_separator(const String &p_menu_root);
+
+	virtual bool global_menu_is_item_checked(const String &p_menu_root, int p_idx) const;
+	virtual bool global_menu_is_item_checkable(const String &p_menu_root, int p_idx) const;
+	virtual Callable global_menu_get_item_callback(const String &p_menu_root, int p_idx);
+	virtual Variant global_menu_get_item_tag(const String &p_menu_root, int p_idx);
+	virtual String global_menu_get_item_text(const String &p_menu_root, int p_idx);
+	virtual String global_menu_get_item_submenu(const String &p_menu_root, int p_idx);
+
+	virtual void global_menu_set_item_checked(const String &p_menu_root, int p_idx, bool p_checked);
+	virtual void global_menu_set_item_checkable(const String &p_menu_root, int p_idx, bool p_checkable);
+	virtual void global_menu_set_item_callback(const String &p_menu_root, int p_idx, const Callable &p_callback);
+	virtual void global_menu_set_item_tag(const String &p_menu_root, int p_idx, const Variant &p_tag);
+	virtual void global_menu_set_item_text(const String &p_menu_root, int p_idx, const String &p_text);
+	virtual void global_menu_set_item_submenu(const String &p_menu_root, int p_idx, const String &p_submenu);
+
+	virtual int global_menu_get_item_count(const String &p_menu_root) const;
+
+	virtual void global_menu_remove_item(const String &p_menu_root, int p_idx);
+	virtual void global_menu_clear(const String &p_menu_root);
 
 	virtual void alert(const String &p_alert, const String &p_title = "ALERT!") = 0;
 
@@ -128,6 +149,7 @@ public:
 
 	virtual void mouse_warp_to_position(const Point2i &p_to);
 	virtual Point2i mouse_get_position() const;
+	virtual Point2i mouse_get_absolute_position() const;
 	virtual int mouse_get_button_state() const;
 
 	virtual void clipboard_set(const String &p_text);
@@ -142,6 +164,7 @@ public:
 	virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const = 0;
 	virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const = 0;
 	virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const = 0;
+	virtual float screen_get_scale(int p_screen = SCREEN_OF_MAIN_WINDOW) const;
 	virtual bool screen_is_touchscreen(int p_screen = SCREEN_OF_MAIN_WINDOW) const;
 	enum ScreenOrientation {
 
@@ -199,6 +222,7 @@ public:
 		WINDOW_EVENT_FOCUS_OUT,
 		WINDOW_EVENT_CLOSE_REQUEST,
 		WINDOW_EVENT_GO_BACK_REQUEST,
+		WINDOW_EVENT_DPI_CHANGE,
 	};
 	virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) = 0;
 	virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) = 0;

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.