diff options
author | bruvzg <7645683+bruvzg@users.noreply.github.com> | 2020-03-07 18:02:54 +0200 |
---|---|---|
committer | bruvzg <7645683+bruvzg@users.noreply.github.com> | 2020-03-26 16:24:05 +0100 |
commit | 15a9f94346c211b7afe96af500cb3405aabcf6b8 (patch) | |
tree | 150287a257a89a0ff579bcc81baa75a0bbca8c79 | |
parent | 197cb4e7718034aba35832a547477dfc858a7280 (diff) |
Add macOS DisplayServer implementation.
Change global menu to use Callable, add support for check items and submenus.
28 files changed, 4245 insertions, 3162 deletions
diff --git a/core/os/main_loop.cpp b/core/os/main_loop.cpp index 4e0743809b..0d1a080682 100644 --- a/core/os/main_loop.cpp +++ b/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()) { diff --git a/core/os/main_loop.h b/core/os/main_loop.h index 9d44af164a..8f6c8c91b1 100644 --- a/core/os/main_loop.h +++ b/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(); diff --git a/core/os/os.h b/core/os/os.h index 17b688efc4..a31b1c1f4b 100644 --- a/core/os/os.h +++ b/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(); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 2cc6bad2a9..86d4b76ee2 100644 --- a/editor/editor_node.cpp +++ b/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: { diff --git a/editor/editor_node.h b/editor/editor_node.h index 88ebf4d475..21025f2a50 100644 --- a/editor/editor_node.h +++ b/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; diff --git a/editor/editor_run.cpp b/editor/editor_run.cpp index 28eff94f36..b4ddb7ebfa 100644 --- a/editor/editor_run.cpp +++ b/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 diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index b4e4ed73f2..d3856cf778 100644 --- a/editor/project_manager.cpp +++ b/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); diff --git a/editor/project_manager.h b/editor/project_manager.h index 8b9c769c59..6385000821 100644 --- a/editor/project_manager.h +++ b/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(); diff --git a/main/main.cpp b/main/main.cpp index c9fde81f7a..bc97f9f8c5 100644 --- a/main/main.cpp +++ b/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 diff --git a/platform/linuxbsd/display_server_x11.cpp b/platform/linuxbsd/display_server_x11.cpp index f284a5f92d..4bea458187 100644 --- a/platform/linuxbsd/display_server_x11.cpp +++ b/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; } diff --git a/platform/linuxbsd/display_server_x11.h b/platform/linuxbsd/display_server_x11.h index 46d33fbe7d..d1680b0508 100644 --- a/platform/linuxbsd/display_server_x11.h +++ b/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); diff --git a/platform/osx/SCsub b/platform/osx/SCsub index 0a4e0a45e1..4ec8aeab6d 100644 --- a/platform/osx/SCsub +++ b/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', diff --git a/platform/osx/display_server_osx.h b/platform/osx/display_server_osx.h new file mode 100644 index 0000000000..d0e2babd06 --- /dev/null +++ b/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 diff --git a/platform/osx/display_server_osx.mm b/platform/osx/display_server_osx.mm new file mode 100644 index 0000000000..a7099c1207 --- /dev/null +++ b/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); +} diff --git a/platform/osx/joypad_osx.cpp b/platform/osx/joypad_osx.cpp index 26735b71d1..f2d9de6fbd 100644 --- a/platform/osx/joypad_osx.cpp +++ b/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[] = { diff --git a/platform/osx/joypad_osx.h b/platform/osx/joypad_osx.h index c8ed8fbfa9..62027c6a30 100644 --- a/platform/osx/joypad_osx.h +++ b/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(); }; diff --git a/platform/osx/os_osx.h b/platform/osx/os_osx.h index de8a9345e3..d2c67cff9f 100644 --- a/platform/osx/os_osx.h +++ b/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 diff --git a/platform/osx/os_osx.mm b/platform/osx/os_osx.mm index 8ba8ca8a33..49cb056c9f 100644 --- a/platform/osx/os_osx.mm +++ b/platform/osx/os_osx.mm @@ -30,1668 +30,21 @@ #include "os_osx.h" -#include "core/os/keyboard.h" -#include "core/print_string.h" #include "core/version_generated.gen.h" -#include "dir_access_osx.h" - -#if defined(OPENGL_ENABLED) -#include "drivers/gles2/rasterizer_gles2.h" -#endif - -#if defined(VULKAN_ENABLED) -#include "servers/visual/rasterizer_rd/rasterizer_rd.h" -#endif +#include "dir_access_osx.h" +#include "display_server_osx.h" #include "main/main.h" -#include "servers/visual/visual_server_raster.h" -#include "servers/visual/visual_server_wrap_mt.h" - -#include <mach-o/dyld.h> - -#include <Carbon/Carbon.h> -#import <Cocoa/Cocoa.h> -#include <IOKit/IOCFPlugIn.h> -#include <IOKit/IOKitLib.h> -#include <IOKit/hid/IOHIDKeys.h> -#include <IOKit/hid/IOHIDLib.h> -#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101200 -#include <os/log.h> -#endif - -#import <QuartzCore/CAMetalLayer.h> -#include <vulkan/vulkan_metal.h> #include <dlfcn.h> -#include <fcntl.h> #include <libproc.h> -#include <stdio.h> -#include <stdlib.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <unistd.h> - -#if MAC_OS_X_VERSION_MAX_ALLOWED < 101200 -#define NSEventMaskAny NSAnyEventMask -#define NSEventTypeKeyDown NSKeyDown -#define NSEventTypeKeyUp NSKeyUp -#define NSEventModifierFlagShift NSShiftKeyMask -#define NSEventModifierFlagCommand NSCommandKeyMask -#define NSEventModifierFlagControl NSControlKeyMask -#define NSEventModifierFlagOption NSAlternateKeyMask -#define NSWindowStyleMaskTitled NSTitledWindowMask -#define NSWindowStyleMaskResizable NSResizableWindowMask -#define NSWindowStyleMaskMiniaturizable NSMiniaturizableWindowMask -#define NSWindowStyleMaskClosable NSClosableWindowMask -#define NSWindowStyleMaskBorderless NSBorderlessWindowMask -#endif - -#ifndef NSAppKitVersionNumber10_12 -#define NSAppKitVersionNumber10_12 1504 -#endif -#ifndef NSAppKitVersionNumber10_14 -#define NSAppKitVersionNumber10_14 1671 -#endif - -static void get_key_modifier_state(unsigned int p_osx_state, Ref<InputEventWithModifiers> state) { - - state->set_shift((p_osx_state & NSEventModifierFlagShift)); - state->set_control((p_osx_state & NSEventModifierFlagControl)); - state->set_alt((p_osx_state & NSEventModifierFlagOption)); - state->set_metakey((p_osx_state & NSEventModifierFlagCommand)); -} - -static void push_to_key_event_buffer(const OS_OSX::KeyEvent &p_event) { - - Vector<OS_OSX::KeyEvent> &buffer = OS_OSX::singleton->key_event_buffer; - if (OS_OSX::singleton->key_event_pos >= buffer.size()) { - buffer.resize(1 + OS_OSX::singleton->key_event_pos); - } - buffer.write[OS_OSX::singleton->key_event_pos++] = p_event; -} - -static int mouse_x = 0; -static int mouse_y = 0; -static int button_mask = 0; -static bool mouse_down_control = false; - -static Vector2 get_mouse_pos(NSPoint locationInWindow, CGFloat backingScaleFactor) { - - const NSRect contentRect = [OS_OSX::singleton->window_view frame]; - const NSPoint p = locationInWindow; - const float s = OS_OSX::singleton->_mouse_scale(backingScaleFactor); - mouse_x = p.x * s; - mouse_y = (contentRect.size.height - p.y) * s; - return Vector2(mouse_x, mouse_y); -} - -static NSCursor *cursorFromSelector(SEL selector, SEL fallback = nil) { - if ([NSCursor respondsToSelector:selector]) { - id object = [NSCursor performSelector:selector]; - if ([object isKindOfClass:[NSCursor class]]) { - return object; - } - } - if (fallback) { - // Fallback should be a reasonable default, no need to check. - return [NSCursor performSelector:fallback]; - } - return [NSCursor arrowCursor]; -} - -@interface GodotApplication : NSApplication -@end - -@implementation GodotApplication - -- (void)sendEvent:(NSEvent *)event { - - // special case handling of command-period, which is traditionally a special - // shortcut in macOS and doesn't arrive at our regular keyDown handler. - if ([event type] == NSEventTypeKeyDown) { - if (([event modifierFlags] & NSEventModifierFlagCommand) && [event keyCode] == 0x2f) { - - Ref<InputEventKey> k; - k.instance(); - - get_key_modifier_state([event modifierFlags], k); - k->set_pressed(true); - k->set_keycode(KEY_PERIOD); - k->set_physical_keycode(KEY_PERIOD); - k->set_echo([event isARepeat]); - - OS_OSX::singleton->push_input(k); - } - } - - // From http://cocoadev.com/index.pl?GameKeyboardHandlingAlmost - // This works around an AppKit bug, where key up events while holding - // down the command key don't get sent to the key window. - if ([event type] == NSEventTypeKeyUp && ([event modifierFlags] & NSEventModifierFlagCommand)) - [[self keyWindow] sendEvent:event]; - else - [super sendEvent:event]; -} - -@end - -@interface GodotApplicationDelegate : NSObject -- (void)forceUnbundledWindowActivationHackStep1; -- (void)forceUnbundledWindowActivationHackStep2; -- (void)forceUnbundledWindowActivationHackStep3; -@end - -@implementation GodotApplicationDelegate - -- (void)forceUnbundledWindowActivationHackStep1 { - // Step1: Switch focus to macOS Dock. - // Required to perform step 2, TransformProcessType will fail if app is already the in focus. - for (NSRunningApplication *app in [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.dock"]) { - [app activateWithOptions:NSApplicationActivateIgnoringOtherApps]; - break; - } - [self performSelector:@selector(forceUnbundledWindowActivationHackStep2) withObject:nil afterDelay:0.02]; -} - -- (void)forceUnbundledWindowActivationHackStep2 { - // Step 2: Register app as foreground process. - ProcessSerialNumber psn = { 0, kCurrentProcess }; - (void)TransformProcessType(&psn, kProcessTransformToForegroundApplication); - - [self performSelector:@selector(forceUnbundledWindowActivationHackStep3) withObject:nil afterDelay:0.02]; -} - -- (void)forceUnbundledWindowActivationHackStep3 { - // Step 3: Switch focus back to app window. - [[NSRunningApplication currentApplication] activateWithOptions:NSApplicationActivateIgnoringOtherApps]; -} - -- (void)applicationDidFinishLaunching:(NSNotification *)notice { - NSString *nsappname = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"]; - if (nsappname == nil) { - // If executable is not a bundled, macOS WindowServer won't register and activate app window correctly (menu and title bar are grayed out and input ignored). - [self performSelector:@selector(forceUnbundledWindowActivationHackStep1) withObject:nil afterDelay:0.02]; - } -} - -- (void)globalMenuCallback:(id)sender { - - if (![sender representedObject]) - return; - - OS_OSX::GlobalMenuItem *item = (OS_OSX::GlobalMenuItem *)[[sender representedObject] pointerValue]; - - if (!item) - return; - - OS_OSX::singleton->main_loop->global_menu_action(item->signal, item->meta); -} - -- (NSMenu *)applicationDockMenu:(NSApplication *)sender { - - NSMenu *menu = [[[NSMenu alloc] initWithTitle:@""] autorelease]; - - Vector<OS_OSX::GlobalMenuItem> &E = OS_OSX::singleton->global_menus["_dock"]; - for (int i = 0; i < E.size(); i++) { - if (E[i].label == String()) { - [menu addItem:[NSMenuItem separatorItem]]; - } else { - NSMenuItem *menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:E[i].label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:@""]; - [menu_item setRepresentedObject:[NSValue valueWithPointer:&(E[i])]]; - } - } - - return menu; -} - -- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename { - // Note: may be called called before main loop init! - char *utfs = strdup([filename UTF8String]); - OS_OSX::singleton->open_with_filename.parse_utf8(utfs); - free(utfs); - -#ifdef TOOLS_ENABLED - // Open new instance - if (OS_OSX::singleton->get_main_loop()) { - List<String> args; - args.push_back(OS_OSX::singleton->open_with_filename); - String exec = OS::get_singleton()->get_executable_path(); - - OS::ProcessID pid = 0; - OS::get_singleton()->execute(exec, args, false, &pid); - } -#endif - return YES; -} - -- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender { - if (OS_OSX::singleton->get_main_loop()) - OS_OSX::singleton->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_QUIT_REQUEST); - - return NSTerminateCancel; -} - -- (void)showAbout:(id)sender { - if (OS_OSX::singleton->get_main_loop()) - OS_OSX::singleton->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_ABOUT); -} - -@end - -@interface GodotWindowDelegate : NSObject { - //_Godotwindow* window; -} - -- (void)windowWillClose:(NSNotification *)notification; - -@end - -@implementation GodotWindowDelegate - -- (BOOL)windowShouldClose:(id)sender { - //_GodotInputWindowCloseRequest(window); - if (OS_OSX::singleton->get_main_loop()) - OS_OSX::singleton->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_QUIT_REQUEST); - - return NO; -} - -- (void)windowWillClose:(NSNotification *)notification { -#if defined(VULKAN_ENABLED) - if (OS_OSX::singleton->video_driver_index == OS::VIDEO_DRIVER_VULKAN) { - - if (OS_OSX::singleton->rendering_device_vulkan) { - OS_OSX::singleton->rendering_device_vulkan->finalize(); - memdelete(OS_OSX::singleton->rendering_device_vulkan); - OS_OSX::singleton->rendering_device_vulkan = NULL; - } - - if (OS_OSX::singleton->context_vulkan) { - memdelete(OS_OSX::singleton->context_vulkan); - OS_OSX::singleton->context_vulkan = NULL; - } - } -#endif -} - -- (void)windowDidEnterFullScreen:(NSNotification *)notification { - OS_OSX::singleton->zoomed = true; - - [OS_OSX::singleton->window_object setContentMinSize:NSMakeSize(0, 0)]; - [OS_OSX::singleton->window_object setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)]; -} - -- (void)windowDidExitFullScreen:(NSNotification *)notification { - OS_OSX::singleton->zoomed = false; - - if (OS_OSX::singleton->min_size != Size2()) { - Size2 size = OS_OSX::singleton->min_size / OS_OSX::singleton->_display_scale(); - [OS_OSX::singleton->window_object setContentMinSize:NSMakeSize(size.x, size.y)]; - } - if (OS_OSX::singleton->max_size != Size2()) { - Size2 size = OS_OSX::singleton->max_size / OS_OSX::singleton->_display_scale(); - [OS_OSX::singleton->window_object setContentMaxSize:NSMakeSize(size.x, size.y)]; - } - - if (!OS_OSX::singleton->resizable) - [OS_OSX::singleton->window_object setStyleMask:[OS_OSX::singleton->window_object styleMask] & ~NSWindowStyleMaskResizable]; -} - -- (void)windowDidChangeBackingProperties:(NSNotification *)notification { - if (!OS_OSX::singleton) - return; - - NSWindow *window = (NSWindow *)[notification object]; - CGFloat newBackingScaleFactor = [window backingScaleFactor]; - CGFloat oldBackingScaleFactor = [[[notification userInfo] objectForKey:@"NSBackingPropertyOldScaleFactorKey"] doubleValue]; - -#if defined(OPENGL_ENABLED) - if (OS_OSX::singleton->video_driver_index == OS::VIDEO_DRIVER_GLES2) { - if (OS_OSX::singleton->is_hidpi_allowed()) { - [OS_OSX::singleton->window_view setWantsBestResolutionOpenGLSurface:YES]; - } else { - [OS_OSX::singleton->window_view setWantsBestResolutionOpenGLSurface:NO]; - } - } -#endif - - if (newBackingScaleFactor != oldBackingScaleFactor) { - //Set new display scale and window size - float newDisplayScale = OS_OSX::singleton->is_hidpi_allowed() ? newBackingScaleFactor : 1.0; - - const NSRect contentRect = [OS_OSX::singleton->window_view frame]; - const NSRect fbRect = contentRect; - - OS_OSX::singleton->window_size.width = fbRect.size.width * newDisplayScale; - OS_OSX::singleton->window_size.height = fbRect.size.height * newDisplayScale; - -#if defined(VULKAN_ENABLED) - if (OS_OSX::singleton->video_driver_index == OS::VIDEO_DRIVER_VULKAN) { - CALayer *layer = [OS_OSX::singleton->window_view layer]; - layer.contentsScale = OS_OSX::singleton->_display_scale(); - } -#endif - //Update context - if (OS_OSX::singleton->main_loop) { - //Force window resize event - [self windowDidResize:notification]; - } - } -} - -- (void)windowDidResize:(NSNotification *)notification { - -#if defined(OPENGL_ENABLED) - if (OS_OSX::singleton->video_driver_index == OS::VIDEO_DRIVER_GLES2) { - OS_OSX::singleton->context_gles2->update(); - } -#endif - const NSRect contentRect = [OS_OSX::singleton->window_view frame]; - const NSRect fbRect = contentRect; - - float displayScale = OS_OSX::singleton->_display_scale(); - OS_OSX::singleton->window_size.width = fbRect.size.width * displayScale; - OS_OSX::singleton->window_size.height = fbRect.size.height * displayScale; - -#if defined(VULKAN_ENABLED) - if (OS_OSX::singleton->video_driver_index == OS::VIDEO_DRIVER_VULKAN) { - CALayer *layer = [OS_OSX::singleton->window_view layer]; - layer.contentsScale = OS_OSX::singleton->_display_scale(); - OS_OSX::singleton->context_vulkan->window_resize(0, OS_OSX::singleton->window_size.width, OS_OSX::singleton->window_size.height); - } -#endif - - if (OS_OSX::singleton->main_loop) { - Main::force_redraw(); - //Event retrieval blocks until resize is over. Call Main::iteration() directly. - if (!Main::is_iterating()) { //avoid cyclic loop - Main::iteration(); - } - } -} - -- (void)windowDidMove:(NSNotification *)notification { - - if (OS_OSX::singleton->get_main_loop()) { - OS_OSX::singleton->input->release_pressed_events(); - } -} - -- (void)windowDidBecomeKey:(NSNotification *)notification { - if (OS_OSX::singleton->get_main_loop()) { - get_mouse_pos( - [OS_OSX::singleton->window_object mouseLocationOutsideOfEventStream], - [OS_OSX::singleton->window_view backingScaleFactor]); - OS_OSX::singleton->input->set_mouse_position(Point2(mouse_x, mouse_y)); - - OS_OSX::singleton->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_FOCUS_IN); - } - - OS_OSX::singleton->window_focused = true; -} - -- (void)windowDidResignKey:(NSNotification *)notification { - if (OS_OSX::singleton->get_main_loop()) - OS_OSX::singleton->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_FOCUS_OUT); - - OS_OSX::singleton->window_focused = false; -} - -- (void)windowDidMiniaturize:(NSNotification *)notification { - OS_OSX::singleton->wm_minimized(true); - if (OS_OSX::singleton->get_main_loop()) - OS_OSX::singleton->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_FOCUS_OUT); - - OS_OSX::singleton->window_focused = false; -}; - -- (void)windowDidDeminiaturize:(NSNotification *)notification { - OS_OSX::singleton->wm_minimized(false); - if (OS_OSX::singleton->get_main_loop()) - OS_OSX::singleton->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_FOCUS_IN); - - OS_OSX::singleton->window_focused = true; -}; - -@end - -@interface GodotContentView : NSView <NSTextInputClient> { - NSTrackingArea *trackingArea; - NSMutableAttributedString *markedText; - bool imeInputEventInProgress; -} -- (void)cancelComposition; - -- (CALayer *)makeBackingLayer; - -- (BOOL)wantsUpdateLayer; -- (void)updateLayer; - -@end - -@implementation GodotContentView - -+ (void)initialize { - if (self == [GodotContentView class]) { - // nothing left to do here at the moment.. - } -} - -- (CALayer *)makeBackingLayer { -#if defined(VULKAN_ENABLED) - if (OS_OSX::singleton->video_driver_index == OS::VIDEO_DRIVER_VULKAN) { - CALayer *layer = [[CAMetalLayer class] layer]; - layer.contentsScale = OS_OSX::singleton->_display_scale(); - return layer; - } -#endif - return [super makeBackingLayer]; -} - -- (void)updateLayer { -#if defined(VULKAN_ENABLED) - if (OS_OSX::singleton->video_driver_index == OS::VIDEO_DRIVER_VULKAN) { - [super updateLayer]; - } -#endif -#if defined(OPENGL_ENABLED) - if (OS_OSX::singleton->video_driver_index == OS::VIDEO_DRIVER_GLES2) { - OS_OSX::singleton->context_gles2->update(); - } -#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; - } - if (OS_OSX::singleton->im_active) { - imeInputEventInProgress = true; - OS_OSX::singleton->im_text.parse_utf8([[markedText mutableString] UTF8String]); - OS_OSX::singleton->im_selection = Point2(selectedRange.location, selectedRange.length); - - if (OS_OSX::singleton->get_main_loop()) - OS_OSX::singleton->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE); - } -} - -- (void)doCommandBySelector:(SEL)aSelector { - if ([self respondsToSelector:aSelector]) - [self performSelector:aSelector]; -} - -- (void)unmarkText { - imeInputEventInProgress = false; - [[markedText mutableString] setString:@""]; - if (OS_OSX::singleton->im_active) { - OS_OSX::singleton->im_text = String(); - OS_OSX::singleton->im_selection = Point2(); - - if (OS_OSX::singleton->get_main_loop()) - OS_OSX::singleton->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE); - } -} - -- (NSArray *)validAttributesForMarkedText { - return [NSArray array]; -} - -- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange { - return nil; -} - -- (NSUInteger)characterIndexForPoint:(NSPoint)aPoint { - return 0; -} - -- (NSRect)firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange { - const NSRect contentRect = [OS_OSX::singleton->window_view frame]; - float displayScale = OS_OSX::singleton->_display_scale(); - NSRect pointInWindowRect = NSMakeRect(OS_OSX::singleton->im_position.x / displayScale, contentRect.size.height - (OS_OSX::singleton->im_position.y / displayScale) - 1, 0, 0); - NSPoint pointOnScreen = [[OS_OSX::singleton->window_view window] convertRectToScreen:pointInWindowRect].origin; - - return NSMakeRect(pointOnScreen.x, pointOnScreen.y, 0, 0); -} - -- (void)cancelComposition { - [self unmarkText]; - NSTextInputContext *currentInputContext = [NSTextInputContext currentInputContext]; - [currentInputContext discardMarkedText]; -} - -- (void)insertText:(id)aString { - [self insertText:aString replacementRange:NSMakeRange(0, 0)]; -} - -- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange { - NSEvent *event = [NSApp currentEvent]; - - NSString *characters; - if ([aString isKindOfClass:[NSAttributedString class]]) { - characters = [aString string]; - } else { - characters = (NSString *)aString; - } - - NSUInteger i, length = [characters length]; - - NSCharacterSet *ctrlChars = [NSCharacterSet controlCharacterSet]; - NSCharacterSet *wsnlChars = [NSCharacterSet whitespaceAndNewlineCharacterSet]; - if ([characters rangeOfCharacterFromSet:ctrlChars].length && [characters rangeOfCharacterFromSet:wsnlChars].length == 0) { - NSTextInputContext *currentInputContext = [NSTextInputContext currentInputContext]; - [currentInputContext discardMarkedText]; - [self cancelComposition]; - return; - } - - for (i = 0; i < length; i++) { - const unichar codepoint = [characters characterAtIndex:i]; - if ((codepoint & 0xFF00) == 0xF700) - continue; - - OS_OSX::KeyEvent ke; - - ke.osx_state = [event modifierFlags]; - ke.pressed = true; - ke.echo = false; - ke.raw = false; // IME input event - ke.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 { - - NSPasteboard *pboard = [sender draggingPasteboard]; - NSArray *filenames = [pboard propertyListForType:NSFilenamesPboardType]; - - Vector<String> files; - for (NSUInteger i = 0; i < filenames.count; i++) { - NSString *ns = [filenames objectAtIndex:i]; - char *utfs = strdup([ns UTF8String]); - String ret; - ret.parse_utf8(utfs); - free(utfs); - files.push_back(ret); - } - - if (files.size()) { - OS_OSX::singleton->main_loop->drop_files(files, 0); - OS_OSX::singleton->move_window_to_foreground(); - } - - return NO; -} - -- (BOOL)isOpaque { - return YES; -} - -- (BOOL)canBecomeKeyView { - return YES; -} - -- (BOOL)acceptsFirstResponder { - return YES; -} - -- (void)cursorUpdate:(NSEvent *)event { - OS::CursorShape p_shape = OS_OSX::singleton->cursor_shape; - OS_OSX::singleton->cursor_shape = OS::CURSOR_MAX; - OS_OSX::singleton->set_cursor_shape(p_shape); -} - -static void _mouseDownEvent(NSEvent *event, int index, int mask, bool pressed) { - if (pressed) { - button_mask |= mask; - } else { - button_mask &= ~mask; - } - - Ref<InputEventMouseButton> mb; - mb.instance(); - const CGFloat backingScaleFactor = [[event window] backingScaleFactor]; - const Vector2 pos = get_mouse_pos([event locationInWindow], backingScaleFactor); - get_key_modifier_state([event modifierFlags], mb); - mb->set_button_index(index); - mb->set_pressed(pressed); - mb->set_position(pos); - mb->set_global_position(pos); - mb->set_button_mask(button_mask); - if (index == BUTTON_LEFT && pressed) { - mb->set_doubleclick([event clickCount] == 2); - } - OS_OSX::singleton->push_input(mb); -} - -- (void)mouseDown:(NSEvent *)event { - if (([event modifierFlags] & NSEventModifierFlagControl)) { - mouse_down_control = true; - _mouseDownEvent(event, BUTTON_RIGHT, BUTTON_MASK_RIGHT, true); - } else { - mouse_down_control = false; - _mouseDownEvent(event, BUTTON_LEFT, BUTTON_MASK_LEFT, true); - } -} - -- (void)mouseDragged:(NSEvent *)event { - [self mouseMoved:event]; -} - -- (void)mouseUp:(NSEvent *)event { - if (mouse_down_control) { - _mouseDownEvent(event, BUTTON_RIGHT, BUTTON_MASK_RIGHT, false); - } else { - _mouseDownEvent(event, BUTTON_LEFT, BUTTON_MASK_LEFT, false); - } -} - -- (void)mouseMoved:(NSEvent *)event { - - Ref<InputEventMouseMotion> mm; - mm.instance(); - - mm->set_button_mask(button_mask); - const CGFloat backingScaleFactor = [[event window] backingScaleFactor]; - const Vector2 pos = get_mouse_pos([event locationInWindow], backingScaleFactor); - mm->set_position(pos); - mm->set_pressure([event pressure]); - if ([event subtype] == NSEventSubtypeTabletPoint) { - const NSPoint p = [event tilt]; - mm->set_tilt(Vector2(p.x, p.y)); - } - mm->set_global_position(pos); - mm->set_speed(OS_OSX::singleton->input->get_last_mouse_speed()); - Vector2 relativeMotion = Vector2(); - relativeMotion.x = [event deltaX] * OS_OSX::singleton -> _mouse_scale(backingScaleFactor); - relativeMotion.y = [event deltaY] * OS_OSX::singleton -> _mouse_scale(backingScaleFactor); - mm->set_relative(relativeMotion); - get_key_modifier_state([event modifierFlags], mm); - - OS_OSX::singleton->input->set_mouse_position(Point2(mouse_x, mouse_y)); - OS_OSX::singleton->push_input(mm); -} - -- (void)rightMouseDown:(NSEvent *)event { - _mouseDownEvent(event, BUTTON_RIGHT, BUTTON_MASK_RIGHT, true); -} - -- (void)rightMouseDragged:(NSEvent *)event { - [self mouseMoved:event]; -} - -- (void)rightMouseUp:(NSEvent *)event { - _mouseDownEvent(event, BUTTON_RIGHT, BUTTON_MASK_RIGHT, false); -} - -- (void)otherMouseDown:(NSEvent *)event { - - if ((int)[event buttonNumber] == 2) { - _mouseDownEvent(event, BUTTON_MIDDLE, BUTTON_MASK_MIDDLE, true); - - } else if ((int)[event buttonNumber] == 3) { - _mouseDownEvent(event, BUTTON_XBUTTON1, BUTTON_MASK_XBUTTON1, true); - - } else if ((int)[event buttonNumber] == 4) { - _mouseDownEvent(event, BUTTON_XBUTTON2, BUTTON_MASK_XBUTTON2, true); - - } else { - return; - } -} - -- (void)otherMouseDragged:(NSEvent *)event { - [self mouseMoved:event]; -} - -- (void)otherMouseUp:(NSEvent *)event { - - if ((int)[event buttonNumber] == 2) { - _mouseDownEvent(event, BUTTON_MIDDLE, BUTTON_MASK_MIDDLE, false); - - } else if ((int)[event buttonNumber] == 3) { - _mouseDownEvent(event, BUTTON_XBUTTON1, BUTTON_MASK_XBUTTON1, false); - - } else if ((int)[event buttonNumber] == 4) { - _mouseDownEvent(event, BUTTON_XBUTTON2, BUTTON_MASK_XBUTTON2, false); - - } else { - return; - } -} - -- (void)mouseExited:(NSEvent *)event { - if (!OS_OSX::singleton) - return; - - if (OS_OSX::singleton->main_loop && OS_OSX::singleton->mouse_mode != OS::MOUSE_MODE_CAPTURED) - OS_OSX::singleton->main_loop->notification(MainLoop::NOTIFICATION_WM_MOUSE_EXIT); -} - -- (void)mouseEntered:(NSEvent *)event { - if (!OS_OSX::singleton) - return; - if (OS_OSX::singleton->main_loop && OS_OSX::singleton->mouse_mode != OS::MOUSE_MODE_CAPTURED) - OS_OSX::singleton->main_loop->notification(MainLoop::NOTIFICATION_WM_MOUSE_ENTER); - - OS::CursorShape p_shape = OS_OSX::singleton->cursor_shape; - OS_OSX::singleton->cursor_shape = OS::CURSOR_MAX; - OS_OSX::singleton->set_cursor_shape(p_shape); -} - -- (void)magnifyWithEvent:(NSEvent *)event { - Ref<InputEventMagnifyGesture> ev; - ev.instance(); - get_key_modifier_state([event modifierFlags], ev); - ev->set_position(get_mouse_pos([event locationInWindow], [[event window] backingScaleFactor])); - ev->set_factor([event magnification] + 1.0); - OS_OSX::singleton->push_input(ev); -} - -- (void)viewDidChangeBackingProperties { - // nothing left to do here -} - -- (void)updateTrackingAreas { - if (trackingArea != nil) { - [self removeTrackingArea:trackingArea]; - [trackingArea release]; - } - - NSTrackingAreaOptions options = - NSTrackingMouseEnteredAndExited | - NSTrackingActiveInKeyWindow | - NSTrackingCursorUpdate | - NSTrackingInVisibleRect; - - trackingArea = [[NSTrackingArea alloc] - initWithRect:[self bounds] - options:options - owner:self - userInfo:nil]; - - [self addTrackingArea:trackingArea]; - [super updateTrackingAreas]; -} - -static bool isNumpadKey(unsigned int key) { - - static const unsigned int table[] = { - 0x41, /* kVK_ANSI_KeypadDecimal */ - 0x43, /* kVK_ANSI_KeypadMultiply */ - 0x45, /* kVK_ANSI_KeypadPlus */ - 0x47, /* kVK_ANSI_KeypadClear */ - 0x4b, /* kVK_ANSI_KeypadDivide */ - 0x4c, /* kVK_ANSI_KeypadEnter */ - 0x4e, /* kVK_ANSI_KeypadMinus */ - 0x51, /* kVK_ANSI_KeypadEquals */ - 0x52, /* kVK_ANSI_Keypad0 */ - 0x53, /* kVK_ANSI_Keypad1 */ - 0x54, /* kVK_ANSI_Keypad2 */ - 0x55, /* kVK_ANSI_Keypad3 */ - 0x56, /* kVK_ANSI_Keypad4 */ - 0x57, /* kVK_ANSI_Keypad5 */ - 0x58, /* kVK_ANSI_Keypad6 */ - 0x59, /* kVK_ANSI_Keypad7 */ - 0x5b, /* kVK_ANSI_Keypad8 */ - 0x5c, /* kVK_ANSI_Keypad9 */ - 0x5f, /* kVK_JIS_KeypadComma */ - 0x00 - }; - for (int i = 0; table[i] != 0; i++) { - if (key == table[i]) - return true; - } - return false; -} - -// Translates a OS X keycode to a Godot keycode -// -static int translateKey(unsigned int key) { - - // Keyboard symbol translation table - static const unsigned int table[128] = { - /* 00 */ KEY_A, - /* 01 */ KEY_S, - /* 02 */ KEY_D, - /* 03 */ KEY_F, - /* 04 */ KEY_H, - /* 05 */ KEY_G, - /* 06 */ KEY_Z, - /* 07 */ KEY_X, - /* 08 */ KEY_C, - /* 09 */ KEY_V, - /* 0a */ KEY_SECTION, /* ISO Section */ - /* 0b */ KEY_B, - /* 0c */ KEY_Q, - /* 0d */ KEY_W, - /* 0e */ KEY_E, - /* 0f */ KEY_R, - /* 10 */ KEY_Y, - /* 11 */ KEY_T, - /* 12 */ KEY_1, - /* 13 */ KEY_2, - /* 14 */ KEY_3, - /* 15 */ KEY_4, - /* 16 */ KEY_6, - /* 17 */ KEY_5, - /* 18 */ KEY_EQUAL, - /* 19 */ KEY_9, - /* 1a */ KEY_7, - /* 1b */ KEY_MINUS, - /* 1c */ KEY_8, - /* 1d */ KEY_0, - /* 1e */ KEY_BRACERIGHT, - /* 1f */ KEY_O, - /* 20 */ KEY_U, - /* 21 */ KEY_BRACELEFT, - /* 22 */ KEY_I, - /* 23 */ KEY_P, - /* 24 */ KEY_ENTER, - /* 25 */ KEY_L, - /* 26 */ KEY_J, - /* 27 */ KEY_APOSTROPHE, - /* 28 */ KEY_K, - /* 29 */ KEY_SEMICOLON, - /* 2a */ KEY_BACKSLASH, - /* 2b */ KEY_COMMA, - /* 2c */ KEY_SLASH, - /* 2d */ KEY_N, - /* 2e */ KEY_M, - /* 2f */ KEY_PERIOD, - /* 30 */ KEY_TAB, - /* 31 */ KEY_SPACE, - /* 32 */ KEY_QUOTELEFT, - /* 33 */ KEY_BACKSPACE, - /* 34 */ KEY_UNKNOWN, - /* 35 */ KEY_ESCAPE, - /* 36 */ KEY_META, - /* 37 */ KEY_META, - /* 38 */ KEY_SHIFT, - /* 39 */ KEY_CAPSLOCK, - /* 3a */ KEY_ALT, - /* 3b */ KEY_CONTROL, - /* 3c */ KEY_SHIFT, - /* 3d */ KEY_ALT, - /* 3e */ KEY_CONTROL, - /* 3f */ KEY_UNKNOWN, /* Function */ - /* 40 */ KEY_UNKNOWN, /* F17 */ - /* 41 */ KEY_KP_PERIOD, - /* 42 */ KEY_UNKNOWN, - /* 43 */ KEY_KP_MULTIPLY, - /* 44 */ KEY_UNKNOWN, - /* 45 */ KEY_KP_ADD, - /* 46 */ KEY_UNKNOWN, - /* 47 */ KEY_NUMLOCK, /* Really KeypadClear... */ - /* 48 */ KEY_VOLUMEUP, /* VolumeUp */ - /* 49 */ KEY_VOLUMEDOWN, /* VolumeDown */ - /* 4a */ KEY_VOLUMEMUTE, /* Mute */ - /* 4b */ KEY_KP_DIVIDE, - /* 4c */ KEY_KP_ENTER, - /* 4d */ KEY_UNKNOWN, - /* 4e */ KEY_KP_SUBTRACT, - /* 4f */ KEY_UNKNOWN, /* F18 */ - /* 50 */ KEY_UNKNOWN, /* F19 */ - /* 51 */ KEY_EQUAL, /* KeypadEqual */ - /* 52 */ KEY_KP_0, - /* 53 */ KEY_KP_1, - /* 54 */ KEY_KP_2, - /* 55 */ KEY_KP_3, - /* 56 */ KEY_KP_4, - /* 57 */ KEY_KP_5, - /* 58 */ KEY_KP_6, - /* 59 */ KEY_KP_7, - /* 5a */ KEY_UNKNOWN, /* F20 */ - /* 5b */ KEY_KP_8, - /* 5c */ KEY_KP_9, - /* 5d */ KEY_YEN, /* JIS Yen */ - /* 5e */ KEY_UNDERSCORE, /* JIS Underscore */ - /* 5f */ KEY_COMMA, /* JIS KeypadComma */ - /* 60 */ KEY_F5, - /* 61 */ KEY_F6, - /* 62 */ KEY_F7, - /* 63 */ KEY_F3, - /* 64 */ KEY_F8, - /* 65 */ KEY_F9, - /* 66 */ KEY_UNKNOWN, /* JIS Eisu */ - /* 67 */ KEY_F11, - /* 68 */ KEY_UNKNOWN, /* JIS Kana */ - /* 69 */ KEY_F13, - /* 6a */ KEY_F16, - /* 6b */ KEY_F14, - /* 6c */ KEY_UNKNOWN, - /* 6d */ KEY_F10, - /* 6e */ KEY_MENU, - /* 6f */ KEY_F12, - /* 70 */ KEY_UNKNOWN, - /* 71 */ KEY_F15, - /* 72 */ KEY_INSERT, /* Really Help... */ - /* 73 */ KEY_HOME, - /* 74 */ KEY_PAGEUP, - /* 75 */ KEY_DELETE, - /* 76 */ KEY_F4, - /* 77 */ KEY_END, - /* 78 */ KEY_F2, - /* 79 */ KEY_PAGEDOWN, - /* 7a */ KEY_F1, - /* 7b */ KEY_LEFT, - /* 7c */ KEY_RIGHT, - /* 7d */ KEY_DOWN, - /* 7e */ KEY_UP, - /* 7f */ KEY_UNKNOWN, - }; - - if (key >= 128) - return KEY_UNKNOWN; - - return table[key]; -} - -struct _KeyCodeMap { - UniChar kchar; - int kcode; -}; - -static const _KeyCodeMap _keycodes[55] = { - { '`', KEY_QUOTELEFT }, - { '~', KEY_ASCIITILDE }, - { '0', KEY_0 }, - { '1', KEY_1 }, - { '2', KEY_2 }, - { '3', KEY_3 }, - { '4', KEY_4 }, - { '5', KEY_5 }, - { '6', KEY_6 }, - { '7', KEY_7 }, - { '8', KEY_8 }, - { '9', KEY_9 }, - { '-', KEY_MINUS }, - { '_', KEY_UNDERSCORE }, - { '=', KEY_EQUAL }, - { '+', KEY_PLUS }, - { 'q', KEY_Q }, - { 'w', KEY_W }, - { 'e', KEY_E }, - { 'r', KEY_R }, - { 't', KEY_T }, - { 'y', KEY_Y }, - { 'u', KEY_U }, - { 'i', KEY_I }, - { 'o', KEY_O }, - { 'p', KEY_P }, - { '[', KEY_BRACELEFT }, - { ']', KEY_BRACERIGHT }, - { '{', KEY_BRACELEFT }, - { '}', KEY_BRACERIGHT }, - { 'a', KEY_A }, - { 's', KEY_S }, - { 'd', KEY_D }, - { 'f', KEY_F }, - { 'g', KEY_G }, - { 'h', KEY_H }, - { 'j', KEY_J }, - { 'k', KEY_K }, - { 'l', KEY_L }, - { ';', KEY_SEMICOLON }, - { ':', KEY_COLON }, - { '\'', KEY_APOSTROPHE }, - { '\"', KEY_QUOTEDBL }, - { '\\', KEY_BACKSLASH }, - { '#', KEY_NUMBERSIGN }, - { 'z', KEY_Z }, - { 'x', KEY_X }, - { 'c', KEY_C }, - { 'v', KEY_V }, - { 'b', KEY_B }, - { 'n', KEY_N }, - { 'm', KEY_M }, - { ',', KEY_COMMA }, - { '.', KEY_PERIOD }, - { '/', KEY_SLASH } -}; - -static int remapKey(unsigned int key, unsigned int state) { - - if (isNumpadKey(key)) - return translateKey(key); - - TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource(); - if (!currentKeyboard) - return translateKey(key); - - CFDataRef layoutData = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData); - if (!layoutData) - return translateKey(key); - - const UCKeyboardLayout *keyboardLayout = (const UCKeyboardLayout *)CFDataGetBytePtr(layoutData); - - UInt32 keysDown = 0; - UniChar chars[4]; - UniCharCount realLength; - - OSStatus err = UCKeyTranslate(keyboardLayout, - key, - kUCKeyActionDisplay, - (state >> 8) & 0xFF, - LMGetKbdType(), - kUCKeyTranslateNoDeadKeysBit, - &keysDown, - sizeof(chars) / sizeof(chars[0]), - &realLength, - chars); - - if (err != noErr) { - return translateKey(key); - } - - for (unsigned int i = 0; i < 55; i++) { - if (_keycodes[i].kchar == chars[0]) { - return _keycodes[i].kcode; - } - } - return translateKey(key); -} - -- (void)keyDown:(NSEvent *)event { - - // Ignore all input if IME input is in progress - if (!imeInputEventInProgress) { - NSString *characters = [event characters]; - NSUInteger length = [characters length]; - - if (!OS_OSX::singleton->im_active && length > 0 && keycode_has_unicode(remapKey([event keyCode], [event modifierFlags]))) { - // Fallback unicode character handler used if IME is not active - for (NSUInteger i = 0; i < length; i++) { - OS_OSX::KeyEvent ke; - - ke.osx_state = [event modifierFlags]; - ke.pressed = true; - ke.echo = [event isARepeat]; - ke.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 { - OS_OSX::KeyEvent ke; - - 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 (OS_OSX::singleton->im_active) - [self interpretKeyEvents:[NSArray arrayWithObject:event]]; -} - -- (void)flagsChanged:(NSEvent *)event { - - // Ignore all input if IME input is in progress - if (!imeInputEventInProgress) { - OS_OSX::KeyEvent ke; - - ke.echo = false; - ke.raw = true; - - 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 { - - // Ignore all input if IME input is in progress - if (!imeInputEventInProgress) { - NSString *characters = [event characters]; - NSUInteger length = [characters length]; - - // Fallback unicode character handler used if IME is not active - if (!OS_OSX::singleton->im_active && length > 0 && keycode_has_unicode(remapKey([event keyCode], [event modifierFlags]))) { - for (NSUInteger i = 0; i < length; i++) { - OS_OSX::KeyEvent ke; - - ke.osx_state = [event modifierFlags]; - ke.pressed = false; - ke.echo = [event isARepeat]; - ke.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 { - OS_OSX::KeyEvent ke; - - 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(int button, double factor, int modifierFlags) { - - unsigned int mask = 1 << (button - 1); - Vector2 mouse_pos = Vector2(mouse_x, mouse_y); - - Ref<InputEventMouseButton> sc; - sc.instance(); - - get_key_modifier_state(modifierFlags, sc); - sc->set_button_index(button); - sc->set_factor(factor); - sc->set_pressed(true); - sc->set_position(mouse_pos); - sc->set_global_position(mouse_pos); - button_mask |= mask; - sc->set_button_mask(button_mask); - OS_OSX::singleton->push_input(sc); - - sc.instance(); - sc->set_button_index(button); - sc->set_factor(factor); - sc->set_pressed(false); - sc->set_position(mouse_pos); - sc->set_global_position(mouse_pos); - button_mask &= ~mask; - sc->set_button_mask(button_mask); - OS_OSX::singleton->push_input(sc); -} - -inline void sendPanEvent(double dx, double dy, int modifierFlags) { - - Ref<InputEventPanGesture> pg; - pg.instance(); - - get_key_modifier_state(modifierFlags, pg); - Vector2 mouse_pos = Vector2(mouse_x, mouse_y); - pg->set_position(mouse_pos); - pg->set_delta(Vector2(-dx, -dy)); - OS_OSX::singleton->push_input(pg); -} - -- (void)scrollWheel:(NSEvent *)event { - double deltaX, deltaY; - - get_mouse_pos([event locationInWindow], [[event window] backingScaleFactor]); - - deltaX = [event scrollingDeltaX]; - deltaY = [event scrollingDeltaY]; - - if ([event hasPreciseScrollingDeltas]) { - deltaX *= 0.03; - deltaY *= 0.03; - } - - if ([event phase] != NSEventPhaseNone || [event momentumPhase] != NSEventPhaseNone) { - sendPanEvent(deltaX, deltaY, [event modifierFlags]); - } else { - if (fabs(deltaX)) { - sendScrollEvent(0 > deltaX ? BUTTON_WHEEL_RIGHT : BUTTON_WHEEL_LEFT, fabs(deltaX * 0.3), [event modifierFlags]); - } - if (fabs(deltaY)) { - sendScrollEvent(0 < deltaY ? BUTTON_WHEEL_UP : BUTTON_WHEEL_DOWN, fabs(deltaY * 0.3), [event modifierFlags]); - } - } -} - -@end - -@interface GodotWindow : NSWindow { -} -@end - -@implementation GodotWindow - -- (BOOL)canBecomeKeyWindow { - // Required for NSBorderlessWindowMask windows - return YES; -} - -@end - -void OS_OSX::_update_global_menu() { - - NSMenu *main_menu = [NSApp mainMenu]; - - for (int i = 1; i < [main_menu numberOfItems]; i++) { - [main_menu removeItemAtIndex:i]; - } - for (Map<String, Vector<GlobalMenuItem>>::Element *E = global_menus.front(); E; E = E->next()) { - if (E->key() != "_dock") { - NSMenu *menu = [[[NSMenu alloc] initWithTitle:[NSString stringWithUTF8String:E->key().utf8().get_data()]] autorelease]; - for (int i = 0; i < E->get().size(); i++) { - if (E->get()[i].label == String()) { - [menu addItem:[NSMenuItem separatorItem]]; - } else { - NSMenuItem *menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:E->get()[i].label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:@""]; - [menu_item setRepresentedObject:[NSValue valueWithPointer:&(E->get()[i])]]; - } - } - NSMenuItem *menu_item = [main_menu addItemWithTitle:[NSString stringWithUTF8String:E->key().utf8().get_data()] action:nil keyEquivalent:@""]; - [main_menu setSubmenu:menu forItem:menu_item]; - } - } -} - -void OS_OSX::global_menu_add_item(const String &p_menu, const String &p_label, const Variant &p_signal, const Variant &p_meta) { - - global_menus[p_menu].push_back(GlobalMenuItem(p_label, p_signal, p_meta)); - _update_global_menu(); -} - -void OS_OSX::global_menu_add_separator(const String &p_menu) { - - global_menus[p_menu].push_back(GlobalMenuItem()); - _update_global_menu(); -} - -void OS_OSX::global_menu_remove_item(const String &p_menu, int p_idx) { - - ERR_FAIL_INDEX(p_idx, global_menus[p_menu].size()); - - global_menus[p_menu].remove(p_idx); - _update_global_menu(); -} - -void OS_OSX::global_menu_clear(const String &p_menu) { - - global_menus[p_menu].clear(); - _update_global_menu(); -} - -Point2 OS_OSX::get_ime_selection() const { - - return im_selection; -} - -String OS_OSX::get_ime_text() const { - - return im_text; -} - -String OS_OSX::get_unique_id() const { - - static String serial_number; - - if (serial_number.empty()) { - io_service_t platformExpert = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice")); - CFStringRef serialNumberAsCFString = NULL; - if (platformExpert) { - serialNumberAsCFString = (CFStringRef)IORegistryEntryCreateCFProperty(platformExpert, CFSTR(kIOPlatformSerialNumberKey), kCFAllocatorDefault, 0); - IOObjectRelease(platformExpert); - } - - NSString *serialNumberAsNSString = nil; - if (serialNumberAsCFString) { - serialNumberAsNSString = [NSString stringWithString:(NSString *)serialNumberAsCFString]; - CFRelease(serialNumberAsCFString); - } - - serial_number = [serialNumberAsNSString UTF8String]; - } - - return serial_number; -} - -void OS_OSX::set_ime_active(const bool p_active) { - - im_active = p_active; - if (!im_active) - [window_view cancelComposition]; -} - -void OS_OSX::set_ime_position(const Point2 &p_pos) { - - im_position = p_pos; -} - -void OS_OSX::initialize_core() { - - crash_handler.initialize(); - - OS_Unix::initialize_core(); - - DirAccess::make_default<DirAccessOSX>(DirAccess::ACCESS_RESOURCES); - DirAccess::make_default<DirAccessOSX>(DirAccess::ACCESS_USERDATA); - DirAccess::make_default<DirAccessOSX>(DirAccess::ACCESS_FILESYSTEM); -} - -static bool keyboard_layout_dirty = true; -static void keyboard_layout_changed(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef user_info) { - keyboard_layout_dirty = true; -} - -static bool displays_arrangement_dirty = true; -static void displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplayChangeSummaryFlags flags, void *user_info) { - displays_arrangement_dirty = true; -} - -int OS_OSX::get_current_video_driver() const { - return video_driver_index; -} - -Error OS_OSX::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) { - - /*** OSX INITIALIZATION ***/ - /*** OSX INITIALIZATION ***/ - /*** OSX INITIALIZATION ***/ - - keyboard_layout_dirty = true; - displays_arrangement_dirty = true; - - // Register to be notified on keyboard layout changes - CFNotificationCenterAddObserver(CFNotificationCenterGetDistributedCenter(), - NULL, keyboard_layout_changed, - kTISNotifySelectedKeyboardInputSourceChanged, NULL, - CFNotificationSuspensionBehaviorDeliverImmediately); - - // Register to be notified on displays arrangement changes - CGDisplayRegisterReconfigurationCallback(displays_arrangement_changed, NULL); - - //!!!!!!!!!!!!!!!!!!!!!!!!!! - //TODO - do Vulkan and GLES2 support checks, driver selection and fallback - video_driver_index = p_video_driver; - print_verbose("Driver: " + String(get_video_driver_name(video_driver_index)) + " [" + itos(video_driver_index) + "]"); - //!!!!!!!!!!!!!!!!!!!!!!!!!! - - //Create window - - window_delegate = [[GodotWindowDelegate alloc] init]; - - // Don't use accumulation buffer support; it's not accelerated - // Aux buffers probably aren't accelerated either - - unsigned int styleMask; - - if (p_desired.borderless_window) { - styleMask = NSWindowStyleMaskBorderless; - } else { - resizable = p_desired.resizable; - styleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | (p_desired.resizable ? NSWindowStyleMaskResizable : 0); - } - - window_object = [[GodotWindow alloc] - initWithContentRect:NSMakeRect(0, 0, p_desired.width, p_desired.height) - styleMask:styleMask - backing:NSBackingStoreBuffered - defer:NO]; - - ERR_FAIL_COND_V(window_object == nil, ERR_UNAVAILABLE); - - window_view = [[GodotContentView alloc] init]; - if (NSAppKitVersionNumber >= NSAppKitVersionNumber10_14) { - [window_view setWantsLayer:TRUE]; - } - - float displayScale = 1.0; - if (is_hidpi_allowed()) { - // note that mainScreen is not screen #0 but the one with the keyboard focus. - NSScreen *screen = [NSScreen mainScreen]; - if ([screen respondsToSelector:@selector(backingScaleFactor)]) { - displayScale = fmax(displayScale, [screen backingScaleFactor]); - } - } - - window_size.width = p_desired.width * displayScale; - window_size.height = p_desired.height * displayScale; - - if (displayScale > 1.0) { -#if defined(OPENGL_ENABLED) - if (video_driver_index == VIDEO_DRIVER_GLES2) { - [window_view setWantsBestResolutionOpenGLSurface:YES]; - } -#endif - [window_object setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary]; - } else { -#if defined(OPENGL_ENABLED) - if (video_driver_index == VIDEO_DRIVER_GLES2) { - [window_view setWantsBestResolutionOpenGLSurface:NO]; - } -#endif - } - - [window_object setContentView:window_view]; - [window_object setDelegate:window_delegate]; - [window_object setAcceptsMouseMovedEvents:YES]; - [(NSWindow *)window_object center]; - - [window_object setRestorable:NO]; - - // Init context and rendering device -#if defined(OPENGL_ENABLED) - if (video_driver_index == VIDEO_DRIVER_GLES2) { - - context_gles2 = memnew(ContextGL_OSX(window_view, false)); - - if (context_gles2->initialize() != OK) { - memdelete(context_gles2); - context_gles2 = NULL; - ERR_FAIL_V(ERR_UNAVAILABLE); - } - - context_gles2->set_use_vsync(p_desired.use_vsync); - - if (RasterizerGLES2::is_viable() == OK) { - RasterizerGLES2::register_config(); - RasterizerGLES2::make_current(); - } else { - memdelete(context_gles2); - context_gles2 = NULL; - ERR_FAIL_V(ERR_UNAVAILABLE); - } - } -#endif -#if defined(VULKAN_ENABLED) - if (video_driver_index == VIDEO_DRIVER_VULKAN) { - - context_vulkan = memnew(VulkanContextOSX); - if (context_vulkan->initialize() != OK) { - memdelete(context_vulkan); - context_vulkan = NULL; - ERR_FAIL_V(ERR_UNAVAILABLE); - } - if (context_vulkan->window_create(window_view, get_video_mode().width, get_video_mode().height) == -1) { - memdelete(context_vulkan); - context_vulkan = NULL; - ERR_FAIL_V(ERR_UNAVAILABLE); - } - - rendering_device_vulkan = memnew(RenderingDeviceVulkan); - rendering_device_vulkan->initialize(context_vulkan); - - RasterizerRD::make_current(); - } -#endif - - [NSApp activateIgnoringOtherApps:YES]; - - _update_window(); - - [window_object makeKeyAndOrderFront:nil]; - - if (p_desired.fullscreen) - zoomed = true; - - visual_server = memnew(VisualServerRaster); - if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) { - visual_server = memnew(VisualServerWrapMT(visual_server, get_render_thread_mode() == RENDER_SEPARATE_THREAD)); - } - - visual_server->init(); - AudioDriverManager::initialize(p_audio_driver); - - input = memnew(InputDefault); - joypad_osx = memnew(JoypadOSX); - - _ensure_user_data_dir(); - - restore_rect = Rect2(get_window_position(), get_window_size()); - - if (p_desired.layered) { - set_window_per_pixel_transparency_enabled(true); - } - - update_real_mouse_position(); - - return OK; -} - -void OS_OSX::finalize() { - -#ifdef COREMIDI_ENABLED - midi_driver.close(); -#endif - -#if defined(OPENGL_ENABLED) - if (video_driver_index == VIDEO_DRIVER_GLES2) { - - if (context_gles2) - memdelete(context_gles2); - } -#endif - - CFNotificationCenterRemoveObserver(CFNotificationCenterGetDistributedCenter(), NULL, kTISNotifySelectedKeyboardInputSourceChanged, NULL); - CGDisplayRemoveReconfigurationCallback(displays_arrangement_changed, NULL); - - delete_main_loop(); - - memdelete(joypad_osx); - memdelete(input); - - cursors_cache.clear(); - visual_server->finish(); - memdelete(visual_server); -} - -void OS_OSX::set_main_loop(MainLoop *p_main_loop) { - - main_loop = p_main_loop; - input->set_main_loop(p_main_loop); -} - -void OS_OSX::delete_main_loop() { - - if (!main_loop) - return; - memdelete(main_loop); - main_loop = NULL; -} - -String OS_OSX::get_name() const { +#include <mach-o/dyld.h> +#include <os/log.h> - return "OSX"; -} +/*************************************************************************/ +/* OSXTerminalLogger */ +/*************************************************************************/ -#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101200 class OSXTerminalLogger : public StdLogger { public: virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, ErrorType p_type = ERR_ERROR) { @@ -1747,333 +100,101 @@ public: } }; -#else - -typedef UnixTerminalLogger OSXTerminalLogger; -#endif - -void OS_OSX::alert(const String &p_alert, const String &p_title) { - // Set OS X-compliant variables - NSAlert *window = [[NSAlert alloc] init]; - NSString *ns_title = [NSString stringWithUTF8String:p_title.utf8().get_data()]; - NSString *ns_alert = [NSString stringWithUTF8String:p_alert.utf8().get_data()]; - - [window addButtonWithTitle:@"OK"]; - [window setMessageText:ns_title]; - [window setInformativeText:ns_alert]; - [window setAlertStyle:NSAlertStyleWarning]; - - // Display it, then release - [window runModal]; - [window release]; -} +/*************************************************************************/ +/* OS_OSX */ +/*************************************************************************/ -Error OS_OSX::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) { +String OS_OSX::get_unique_id() const { + static String serial_number; - String path = p_path; + if (serial_number.empty()) { + io_service_t platformExpert = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice")); + CFStringRef serialNumberAsCFString = NULL; + if (platformExpert) { + serialNumberAsCFString = (CFStringRef)IORegistryEntryCreateCFProperty(platformExpert, CFSTR(kIOPlatformSerialNumberKey), kCFAllocatorDefault, 0); + IOObjectRelease(platformExpert); + } - if (!FileAccess::exists(path)) { - //this code exists so gdnative can load .dylib files from within the executable path - path = get_executable_path().get_base_dir().plus_file(p_path.get_file()); - } + NSString *serialNumberAsNSString = nil; + if (serialNumberAsCFString) { + serialNumberAsNSString = [NSString stringWithString:(NSString *)serialNumberAsCFString]; + CFRelease(serialNumberAsCFString); + } - if (!FileAccess::exists(path)) { - //this code exists so gdnative can load .dylib files from a standard macOS location - path = get_executable_path().get_base_dir().plus_file("../Frameworks").plus_file(p_path.get_file()); + serial_number = [serialNumberAsNSString UTF8String]; } - p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW); - ERR_FAIL_COND_V_MSG(!p_library_handle, ERR_CANT_OPEN, "Can't open dynamic library: " + p_path + ", error: " + dlerror() + "."); - return OK; + return serial_number; } -void OS_OSX::set_cursor_shape(CursorShape p_shape) { - - if (cursor_shape == p_shape) - return; - - if (mouse_mode != MOUSE_MODE_VISIBLE && mouse_mode != MOUSE_MODE_CONFINED) { - cursor_shape = p_shape; - return; - } - - if (cursors[p_shape] != NULL) { - [cursors[p_shape] set]; - } else { - switch (p_shape) { - case CURSOR_ARROW: [[NSCursor arrowCursor] set]; break; - case CURSOR_IBEAM: [[NSCursor IBeamCursor] set]; break; - case CURSOR_POINTING_HAND: [[NSCursor pointingHandCursor] set]; break; - case CURSOR_CROSS: [[NSCursor crosshairCursor] set]; break; - case CURSOR_WAIT: [[NSCursor arrowCursor] set]; break; - case CURSOR_BUSY: [[NSCursor arrowCursor] set]; break; - case CURSOR_DRAG: [[NSCursor closedHandCursor] set]; break; - case CURSOR_CAN_DROP: [[NSCursor openHandCursor] set]; break; - case CURSOR_FORBIDDEN: [[NSCursor operationNotAllowedCursor] set]; break; - case CURSOR_VSIZE: [cursorFromSelector(@selector(_windowResizeNorthSouthCursor), @selector(resizeUpDownCursor)) set]; break; - case CURSOR_HSIZE: [cursorFromSelector(@selector(_windowResizeEastWestCursor), @selector(resizeLeftRightCursor)) set]; break; - case CURSOR_BDIAGSIZE: [cursorFromSelector(@selector(_windowResizeNorthEastSouthWestCursor)) set]; break; - case CURSOR_FDIAGSIZE: [cursorFromSelector(@selector(_windowResizeNorthWestSouthEastCursor)) set]; break; - case CURSOR_MOVE: [[NSCursor arrowCursor] set]; break; - case CURSOR_VSPLIT: [[NSCursor resizeUpDownCursor] set]; break; - case CURSOR_HSPLIT: [[NSCursor resizeLeftRightCursor] set]; break; - case CURSOR_HELP: [cursorFromSelector(@selector(_helpCursor)) set]; break; - default: { - }; - } - } +void OS_OSX::initialize_core() { + OS_Unix::initialize_core(); - cursor_shape = p_shape; + DirAccess::make_default<DirAccessOSX>(DirAccess::ACCESS_RESOURCES); + DirAccess::make_default<DirAccessOSX>(DirAccess::ACCESS_USERDATA); + DirAccess::make_default<DirAccessOSX>(DirAccess::ACCESS_FILESYSTEM); } -OS::CursorShape OS_OSX::get_cursor_shape() const { - - return cursor_shape; +void OS_OSX::initialize_joypads() { + joypad_osx = memnew(JoypadOSX(InputFilter::get_singleton())); } -void OS_OSX::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) { - - if (p_cursor.is_valid()) { - - Map<CursorShape, Vector<Variant>>::Element *cursor_c = cursors_cache.find(p_shape); - - if (cursor_c) { - if (cursor_c->get()[0] == p_cursor && cursor_c->get()[1] == p_hotspot) { - set_cursor_shape(p_shape); - return; - } - - cursors_cache.erase(p_shape); - } - - Ref<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; - set_cursor_shape(c); - - cursors_cache.erase(p_shape); - } -} - -void OS_OSX::set_mouse_show(bool p_show) { -} +void OS_OSX::initialize() { + crash_handler.initialize(); -void OS_OSX::set_mouse_grab(bool p_grab) { + initialize_core(); + //ensure_user_data_dir(); } -bool OS_OSX::is_mouse_grab_enabled() const { - - return mouse_grab; -} +void OS_OSX::finalize() { -void OS_OSX::warp_mouse_position(const Point2 &p_to) { - - //copied from windows impl with osx native calls - if (mouse_mode == MOUSE_MODE_CAPTURED) { - mouse_x = p_to.x; - mouse_y = p_to.y; - } else { //set OS position - - //local point in window coords - const NSRect contentRect = [window_view frame]; - float displayScale = _display_scale(); - NSRect pointInWindowRect = NSMakeRect(p_to.x / displayScale, contentRect.size.height - (p_to.y / displayScale) - 1, 0, 0); - NSPoint pointOnScreen = [[window_view window] convertRectToScreen:pointInWindowRect].origin; - - //point in scren coords - CGPoint lMouseWarpPos = { pointOnScreen.x, CGDisplayBounds(CGMainDisplayID()).size.height - pointOnScreen.y }; - - //do the warping - CGEventSourceRef lEventRef = CGEventSourceCreate(kCGEventSourceStateCombinedSessionState); - CGEventSourceSetLocalEventsSuppressionInterval(lEventRef, 0.0); - CGAssociateMouseAndMouseCursorPosition(false); - CGWarpMouseCursorPosition(lMouseWarpPos); - CGAssociateMouseAndMouseCursorPosition(true); - } -} +#ifdef COREMIDI_ENABLED + midi_driver.close(); +#endif -void OS_OSX::update_real_mouse_position() { + delete_main_loop(); - get_mouse_pos([window_object mouseLocationOutsideOfEventStream], [window_view backingScaleFactor]); - input->set_mouse_position(Point2(mouse_x, mouse_y)); + memdelete(joypad_osx); } -Point2 OS_OSX::get_mouse_position() const { - - return Vector2(mouse_x, mouse_y); +void OS_OSX::set_main_loop(MainLoop *p_main_loop) { + main_loop = p_main_loop; } -int OS_OSX::get_mouse_button_state() const { - return button_mask; +void OS_OSX::delete_main_loop() { + if (!main_loop) + return; + memdelete(main_loop); + main_loop = NULL; } -void OS_OSX::set_window_title(const String &p_title) { - title = p_title; - - [window_object setTitle:[NSString stringWithUTF8String:p_title.utf8().get_data()]]; +String OS_OSX::get_name() const { + return "macOS"; } -void OS_OSX::set_native_icon(const String &p_filename) { - - FileAccess *f = FileAccess::open(p_filename, FileAccess::READ); - ERR_FAIL_COND(!f); - - Vector<uint8_t> data; - uint32_t len = f->get_len(); - data.resize(len); - f->get_buffer((uint8_t *)&data.write[0], len); - memdelete(f); - - NSData *icon_data = [[[NSData alloc] initWithBytes:&data.write[0] length:len] autorelease]; - ERR_FAIL_COND_MSG(!icon_data, "Error reading icon data."); - - NSImage *icon = [[[NSImage alloc] initWithData:icon_data] autorelease]; - ERR_FAIL_COND_MSG(!icon, "Error loading icon."); - - [NSApp setApplicationIconImage:icon]; -} +Error OS_OSX::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) { + String path = p_path; -void OS_OSX::set_icon(const Ref<Image> &p_icon) { - - Ref<Image> img = p_icon; - img = img->duplicate(); - img->convert(Image::FORMAT_RGBA8); - NSBitmapImageRep *imgrep = [[[NSBitmapImageRep alloc] - initWithBitmapDataPlanes:NULL - pixelsWide:img->get_width() - pixelsHigh:img->get_height() - bitsPerSample:8 - samplesPerPixel:4 - hasAlpha:YES - isPlanar:NO - colorSpaceName:NSDeviceRGBColorSpace - bytesPerRow:img->get_width() * 4 - bitsPerPixel:32] autorelease]; - ERR_FAIL_COND(imgrep == nil); - uint8_t *pixels = [imgrep bitmapData]; - - int len = img->get_width() * img->get_height(); - 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; + if (!FileAccess::exists(path)) { + //this code exists so gdnative can load .dylib files from within the executable path + path = get_executable_path().get_base_dir().plus_file(p_path.get_file()); } - NSImage *nsimg = [[[NSImage alloc] initWithSize:NSMakeSize(img->get_width(), img->get_height())] autorelease]; - ERR_FAIL_COND(nsimg == nil); - [nsimg addRepresentation:imgrep]; + if (!FileAccess::exists(path)) { + //this code exists so gdnative can load .dylib files from a standard macOS location + path = get_executable_path().get_base_dir().plus_file("../Frameworks").plus_file(p_path.get_file()); + } - [NSApp setApplicationIconImage:nsimg]; + p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW); + ERR_FAIL_COND_V_MSG(!p_library_handle, ERR_CANT_OPEN, "Can't open dynamic library: " + p_path + ", error: " + dlerror() + "."); + return OK; } MainLoop *OS_OSX::get_main_loop() const { - return main_loop; } String OS_OSX::get_config_path() const { - if (has_environment("XDG_CONFIG_HOME")) { return get_environment("XDG_CONFIG_HOME"); } else if (has_environment("HOME")) { @@ -2084,7 +205,6 @@ String OS_OSX::get_config_path() const { } String OS_OSX::get_data_path() const { - if (has_environment("XDG_DATA_HOME")) { return get_environment("XDG_DATA_HOME"); } else { @@ -2093,7 +213,6 @@ String OS_OSX::get_data_path() const { } String OS_OSX::get_cache_path() const { - if (has_environment("XDG_CACHE_HOME")) { return get_environment("XDG_CACHE_HOME"); } else if (has_environment("HOME")) { @@ -2104,7 +223,6 @@ String OS_OSX::get_cache_path() const { } String OS_OSX::get_bundle_resource_dir() const { - NSBundle *main = [NSBundle mainBundle]; NSString *resourcePath = [main resourcePath]; @@ -2118,12 +236,10 @@ String OS_OSX::get_bundle_resource_dir() const { // Get properly capitalized engine name for system paths String OS_OSX::get_godot_dir_name() const { - return String(VERSION_SHORT_NAME).capitalize(); } String OS_OSX::get_system_dir(SystemDir p_dir) const { - NSSearchPathDirectory id; bool found = true; @@ -2166,62 +282,7 @@ String OS_OSX::get_system_dir(SystemDir p_dir) const { return ret; } -bool OS_OSX::can_draw() const { - - return true; -} - -void OS_OSX::set_clipboard(const String &p_text) { - - NSString *copiedString = [NSString stringWithUTF8String:p_text.utf8().get_data()]; - NSArray *copiedStringArray = [NSArray arrayWithObject:copiedString]; - - NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; - [pasteboard clearContents]; - [pasteboard writeObjects:copiedStringArray]; -} - -String OS_OSX::get_clipboard() const { - - NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; - NSArray *classArray = [NSArray arrayWithObject:[NSString class]]; - NSDictionary *options = [NSDictionary dictionary]; - - BOOL ok = [pasteboard canReadObjectForClasses:classArray options:options]; - - if (!ok) { - return ""; - } - - NSArray *objectsToPaste = [pasteboard readObjectsForClasses:classArray options:options]; - NSString *string = [objectsToPaste objectAtIndex:0]; - - char *utfs = strdup([string UTF8String]); - String ret; - ret.parse_utf8(utfs); - free(utfs); - - return ret; -} - -void OS_OSX::release_rendering_thread() { -#if defined(OPENGL_ENABLED) - if (video_driver_index == VIDEO_DRIVER_GLES2) { - context_gles2->release_current(); - } -#endif -} - -void OS_OSX::make_rendering_thread() { -#if defined(OPENGL_ENABLED) - if (video_driver_index == VIDEO_DRIVER_GLES2) { - context_gles2->make_current(); - } -#endif -} - Error OS_OSX::shell_open(String p_uri) { - [[NSWorkspace sharedWorkspace] openURL:[[NSURL alloc] initWithString:[[NSString stringWithUTF8String:p_uri.utf8().get_data()] stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]]]]; return OK; } @@ -2231,491 +292,7 @@ String OS_OSX::get_locale() const { return [locale_code UTF8String]; } -void OS_OSX::swap_buffers() { -#if defined(OPENGL_ENABLED) - if (video_driver_index == VIDEO_DRIVER_GLES2) { - context_gles2->swap_buffers(); - } -#endif -#if defined(VULKAN_ENABLED) - if (video_driver_index == VIDEO_DRIVER_VULKAN) { - context_vulkan->swap_buffers(); - } -#endif -} - -void OS_OSX::wm_minimized(bool p_minimized) { - - minimized = p_minimized; -}; - -void OS_OSX::set_video_mode(const VideoMode &p_video_mode, int p_screen) { -} - -OS::VideoMode OS_OSX::get_video_mode(int p_screen) const { - - VideoMode vm; - vm.width = window_size.width; - vm.height = window_size.height; - - return vm; -} - -void OS_OSX::get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen) const { -} - -int OS_OSX::get_screen_count() const { - NSArray *screenArray = [NSScreen screens]; - return [screenArray count]; -}; - -// Returns the native top-left screen coordinate of the smallest rectangle -// that encompasses all screens. Needed in get_screen_position(), -// get_window_position, and set_window_position() -// to convert between OS X native screen coordinates and the ones expected by Godot -Point2 OS_OSX::get_screens_origin() const { - static Point2 origin; - - if (displays_arrangement_dirty) { - origin = Point2(); - - for (int i = 0; i < get_screen_count(); i++) { - Point2 position = get_native_screen_position(i); - if (position.x < origin.x) { - origin.x = position.x; - } - if (position.y > origin.y) { - origin.y = position.y; - } - } - - displays_arrangement_dirty = false; - } - - return origin; -} - -static int get_screen_index(NSScreen *screen) { - const NSUInteger index = [[NSScreen screens] indexOfObject:screen]; - return index == NSNotFound ? 0 : index; -} - -int OS_OSX::get_current_screen() const { - if (window_object) { - return get_screen_index([window_object screen]); - } else { - return get_screen_index([NSScreen mainScreen]); - } -}; - -void OS_OSX::set_current_screen(int p_screen) { - Vector2 wpos = get_window_position() - get_screen_position(get_current_screen()); - set_window_position(wpos + get_screen_position(p_screen)); -}; - -Point2 OS_OSX::get_native_screen_position(int p_screen) const { - if (p_screen < 0) { - p_screen = get_current_screen(); - } - - NSArray *screenArray = [NSScreen screens]; - if ((NSUInteger)p_screen < [screenArray count]) { - float display_scale = _display_scale([screenArray objectAtIndex:p_screen]); - NSRect nsrect = [[screenArray objectAtIndex:p_screen] frame]; - // Return the top-left corner of the screen, for OS X the y starts at the bottom - return Point2(nsrect.origin.x, nsrect.origin.y + nsrect.size.height) * display_scale; - } - - return Point2(); -} - -Point2 OS_OSX::get_screen_position(int p_screen) const { - Point2 position = get_native_screen_position(p_screen) - get_screens_origin(); - // OS X native y-coordinate relative to get_screens_origin() is negative, - // Godot expects a positive value - position.y *= -1; - return position; -} - -int OS_OSX::get_screen_dpi(int p_screen) const { - if (p_screen < 0) { - p_screen = get_current_screen(); - } - - NSArray *screenArray = [NSScreen screens]; - if ((NSUInteger)p_screen < [screenArray count]) { - float displayScale = _display_scale([screenArray objectAtIndex:p_screen]); - NSDictionary *description = [[screenArray objectAtIndex:p_screen] deviceDescription]; - NSSize displayPixelSize = [[description objectForKey:NSDeviceSize] sizeValue]; - CGSize displayPhysicalSize = CGDisplayScreenSize( - [[description objectForKey:@"NSScreenNumber"] unsignedIntValue]); - - return (displayPixelSize.width * 25.4f / displayPhysicalSize.width) * displayScale; - } - - return 72; -} - -Size2 OS_OSX::get_screen_size(int p_screen) const { - if (p_screen < 0) { - p_screen = get_current_screen(); - } - - NSArray *screenArray = [NSScreen screens]; - if ((NSUInteger)p_screen < [screenArray count]) { - float displayScale = _display_scale([screenArray objectAtIndex:p_screen]); - // Note: Use frame to get the whole screen size - NSRect nsrect = [[screenArray objectAtIndex:p_screen] frame]; - return Size2(nsrect.size.width, nsrect.size.height) * displayScale; - } - - return Size2(); -} - -void OS_OSX::_update_window() { - bool borderless_full = false; - - if (get_borderless_window()) { - NSRect frameRect = [window_object frame]; - NSRect screenRect = [[window_object screen] frame]; - - // Check if our window covers up the screen - if (frameRect.origin.x <= screenRect.origin.x && frameRect.origin.y <= frameRect.origin.y && - frameRect.size.width >= screenRect.size.width && frameRect.size.height >= screenRect.size.height) { - borderless_full = true; - } - } - - if (borderless_full) { - // If the window covers up the screen set the level to above the main menu and hide on deactivate - [window_object setLevel:NSMainMenuWindowLevel + 1]; - [window_object setHidesOnDeactivate:YES]; - } else { - // Reset these when our window is not a borderless window that covers up the screen - [window_object setLevel:NSNormalWindowLevel]; - [window_object setHidesOnDeactivate:NO]; - } -} - -float OS_OSX::_display_scale() const { - if (window_object) { - return _display_scale([window_object screen]); - } else { - return _display_scale([NSScreen mainScreen]); - } -} - -float OS_OSX::_display_scale(id screen) const { - if (is_hidpi_allowed()) { - if ([screen respondsToSelector:@selector(backingScaleFactor)]) { - return fmax(1.0, [screen backingScaleFactor]); - } - } - return 1.0; -} - -Point2 OS_OSX::get_native_window_position() const { - - NSRect nsrect = [window_object frame]; - Point2 pos; - float display_scale = _display_scale(); - - // Return the position of the top-left corner, for OS X the y starts at the bottom - pos.x = nsrect.origin.x * display_scale; - pos.y = (nsrect.origin.y + nsrect.size.height) * display_scale; - - return pos; -}; - -Point2 OS_OSX::get_window_position() const { - Point2 position = get_native_window_position() - get_screens_origin(); - // OS X native y-coordinate relative to get_screens_origin() is negative, - // Godot expects a positive value - position.y *= -1; - return position; -} - -void OS_OSX::set_native_window_position(const Point2 &p_position) { - - NSPoint pos; - float displayScale = _display_scale(); - - pos.x = p_position.x / displayScale; - pos.y = p_position.y / displayScale; - - [window_object setFrameTopLeftPoint:pos]; - - _update_window(); -}; - -void OS_OSX::set_window_position(const Point2 &p_position) { - Point2 position = p_position; - // OS X native y-coordinate relative to get_screens_origin() is negative, - // Godot passes a positive value - position.y *= -1; - set_native_window_position(get_screens_origin() + position); - - update_real_mouse_position(); -}; - -Size2 OS_OSX::get_window_size() const { - - return window_size; -}; - -Size2 OS_OSX::get_real_window_size() const { - - NSRect frame = [window_object frame]; - return Size2(frame.size.width, frame.size.height) * _display_scale(); -} - -Size2 OS_OSX::get_max_window_size() const { - return max_size; -} - -Size2 OS_OSX::get_min_window_size() const { - return min_size; -} - -void OS_OSX::set_min_window_size(const Size2 p_size) { - - if ((p_size != Size2()) && (max_size != Size2()) && ((p_size.x > max_size.x) || (p_size.y > max_size.y))) { - ERR_PRINT("Minimum window size can't be larger than maximum window size!"); - return; - } - min_size = p_size; - - if ((min_size != Size2()) && !zoomed) { - Size2 size = min_size / _display_scale(); - [window_object setContentMinSize:NSMakeSize(size.x, size.y)]; - } else { - [window_object setContentMinSize:NSMakeSize(0, 0)]; - } -} - -void OS_OSX::set_max_window_size(const Size2 p_size) { - - if ((p_size != Size2()) && ((p_size.x < min_size.x) || (p_size.y < min_size.y))) { - ERR_PRINT("Maximum window size can't be smaller than minimum window size!"); - return; - } - max_size = p_size; - - if ((max_size != Size2()) && !zoomed) { - Size2 size = max_size / _display_scale(); - [window_object setContentMaxSize:NSMakeSize(size.x, size.y)]; - } else { - [window_object setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)]; - } -} - -void OS_OSX::set_window_size(const Size2 p_size) { - - Size2 size = p_size / _display_scale(); - - if (get_borderless_window() == false) { - // NSRect used by setFrame includes the title bar, so add it to our size.y - CGFloat menuBarHeight = [[[NSApplication sharedApplication] mainMenu] menuBarHeight]; - if (menuBarHeight != 0.f) { - size.y += menuBarHeight; - } else { - if (floor(NSAppKitVersionNumber) < NSAppKitVersionNumber10_12) { - size.y += [[NSStatusBar systemStatusBar] thickness]; - } - } - } - - NSRect frame = [window_object frame]; - [window_object setFrame:NSMakeRect(frame.origin.x, frame.origin.y, size.x, size.y) display:YES]; - - _update_window(); -}; - -void OS_OSX::set_window_fullscreen(bool p_enabled) { - - if (zoomed != p_enabled) { - if (layered_window) - set_window_per_pixel_transparency_enabled(false); - if (!resizable) - [window_object setStyleMask:[window_object styleMask] | NSWindowStyleMaskResizable]; - if (p_enabled) { - [window_object setContentMinSize:NSMakeSize(0, 0)]; - [window_object setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)]; - } else { - if (min_size != Size2()) { - Size2 size = min_size / _display_scale(); - [window_object setContentMinSize:NSMakeSize(size.x, size.y)]; - } - if (max_size != Size2()) { - Size2 size = max_size / _display_scale(); - [window_object setContentMaxSize:NSMakeSize(size.x, size.y)]; - } - } - [window_object toggleFullScreen:nil]; - } - zoomed = p_enabled; -}; - -bool OS_OSX::is_window_fullscreen() const { - - return zoomed; -}; - -void OS_OSX::set_window_resizable(bool p_enabled) { - - if (p_enabled) - [window_object setStyleMask:[window_object styleMask] | NSWindowStyleMaskResizable]; - else if (!zoomed) - [window_object setStyleMask:[window_object styleMask] & ~NSWindowStyleMaskResizable]; - - resizable = p_enabled; -}; - -bool OS_OSX::is_window_resizable() const { - - return [window_object styleMask] & NSWindowStyleMaskResizable; -}; - -void OS_OSX::set_window_minimized(bool p_enabled) { - - if (p_enabled) - [window_object performMiniaturize:nil]; - else - [window_object deminiaturize:nil]; -}; - -bool OS_OSX::is_window_minimized() const { - - if ([window_object respondsToSelector:@selector(isMiniaturized)]) - return [window_object isMiniaturized]; - - return minimized; -}; - -void OS_OSX::set_window_maximized(bool p_enabled) { - - if (p_enabled) { - restore_rect = Rect2(get_window_position(), get_window_size()); - [window_object setFrame:[[[NSScreen screens] objectAtIndex:get_current_screen()] visibleFrame] display:YES]; - } else { - set_window_size(restore_rect.size); - set_window_position(restore_rect.position); - }; - maximized = p_enabled; -}; - -bool OS_OSX::is_window_maximized() const { - - // don't know - return maximized; -}; - -void OS_OSX::move_window_to_foreground() { - - [[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; - [window_object makeKeyAndOrderFront:nil]; -} - -void OS_OSX::set_window_always_on_top(bool p_enabled) { - if (is_window_always_on_top() == p_enabled) - return; - - if (p_enabled) - [window_object setLevel:NSFloatingWindowLevel]; - else - [window_object setLevel:NSNormalWindowLevel]; -} - -bool OS_OSX::is_window_always_on_top() const { - return [window_object level] == NSFloatingWindowLevel; -} - -bool OS_OSX::is_window_focused() const { - return window_focused; -} - -void OS_OSX::request_attention() { - - [NSApp requestUserAttention:NSCriticalRequest]; -} - -bool OS_OSX::get_window_per_pixel_transparency_enabled() const { - - if (!is_layered_allowed()) return false; - return layered_window; -} - -void OS_OSX::set_window_per_pixel_transparency_enabled(bool p_enabled) { - - if (!is_layered_allowed()) return; - if (layered_window != p_enabled) { - if (p_enabled) { - set_borderless_window(true); - [window_object setBackgroundColor:[NSColor clearColor]]; - [window_object setOpaque:NO]; - [window_object setHasShadow:NO]; -#if defined(OPENGL_ENABLED) - if (video_driver_index == VIDEO_DRIVER_GLES2) { - context_gles2->set_opacity(0); - } -#endif - layered_window = true; - } else { - [window_object setBackgroundColor:[NSColor colorWithCalibratedWhite:1 alpha:1]]; - [window_object setOpaque:YES]; - [window_object setHasShadow:YES]; -#if defined(OPENGL_ENABLED) - if (video_driver_index == VIDEO_DRIVER_GLES2) { - context_gles2->set_opacity(1); - } -#endif - layered_window = false; - } -#if defined(OPENGL_ENABLED) - if (video_driver_index == VIDEO_DRIVER_GLES2) { - context_gles2->update(); - } -#endif - NSRect frame = [window_object frame]; - [window_object setFrame:NSMakeRect(frame.origin.x, frame.origin.y, 1, 1) display:YES]; - [window_object setFrame:frame display:YES]; - } -} - -void OS_OSX::set_borderless_window(bool p_borderless) { - - // OrderOut prevents a lose focus bug with the window - [window_object orderOut:nil]; - - if (p_borderless) { - [window_object setStyleMask:NSWindowStyleMaskBorderless]; - } else { - if (layered_window) - set_window_per_pixel_transparency_enabled(false); - - [window_object setStyleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | (resizable ? NSWindowStyleMaskResizable : 0)]; - - // Force update of the window styles - NSRect frameRect = [window_object frame]; - [window_object setFrame:NSMakeRect(frameRect.origin.x, frameRect.origin.y, frameRect.size.width + 1, frameRect.size.height) display:NO]; - [window_object setFrame:frameRect display:NO]; - - // Restore the window title - [window_object setTitle:[NSString stringWithUTF8String:title.utf8().get_data()]]; - } - - _update_window(); - - [window_object makeKeyAndOrderFront:nil]; -} - -bool OS_OSX::get_borderless_window() { - - return [window_object styleMask] == NSWindowStyleMaskBorderless; -} - String OS_OSX::get_executable_path() const { - int ret; pid_t pid; char pathbuf[PROC_PIDPATHINFO_MAXSIZE]; @@ -2732,177 +309,7 @@ String OS_OSX::get_executable_path() const { } } -// Returns string representation of keys, if they are printable. -// -static NSString *createStringForKeys(const CGKeyCode *keyCode, int length) { - - TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource(); - if (!currentKeyboard) - return nil; - - CFDataRef layoutData = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData); - if (!layoutData) - return nil; - - const UCKeyboardLayout *keyboardLayout = (const UCKeyboardLayout *)CFDataGetBytePtr(layoutData); - - OSStatus err; - CFMutableStringRef output = CFStringCreateMutable(NULL, 0); - - for (int i = 0; i < length; ++i) { - - UInt32 keysDown = 0; - UniChar chars[4]; - UniCharCount realLength; - - err = UCKeyTranslate(keyboardLayout, - keyCode[i], - kUCKeyActionDisplay, - 0, - LMGetKbdType(), - kUCKeyTranslateNoDeadKeysBit, - &keysDown, - sizeof(chars) / sizeof(chars[0]), - &realLength, - chars); - - if (err != noErr) { - CFRelease(output); - return nil; - } - - CFStringAppendCharacters(output, chars, 1); - } - - //CFStringUppercase(output, NULL); - - return (NSString *)output; -} - -OS::LatinKeyboardVariant OS_OSX::get_latin_keyboard_variant() const { - - static LatinKeyboardVariant layout = LATIN_KEYBOARD_QWERTY; - - if (keyboard_layout_dirty) { - - layout = LATIN_KEYBOARD_QWERTY; - - CGKeyCode keys[] = { kVK_ANSI_Q, kVK_ANSI_W, kVK_ANSI_E, kVK_ANSI_R, kVK_ANSI_T, kVK_ANSI_Y }; - NSString *test = createStringForKeys(keys, 6); - - if ([test isEqualToString:@"qwertz"]) { - layout = LATIN_KEYBOARD_QWERTZ; - } else if ([test isEqualToString:@"azerty"]) { - layout = LATIN_KEYBOARD_AZERTY; - } else if ([test isEqualToString:@"qzerty"]) { - layout = LATIN_KEYBOARD_QZERTY; - } else if ([test isEqualToString:@"',.pyf"]) { - layout = LATIN_KEYBOARD_DVORAK; - } else if ([test isEqualToString:@"xvlcwk"]) { - layout = LATIN_KEYBOARD_NEO; - } else if ([test isEqualToString:@"qwfpgj"]) { - layout = LATIN_KEYBOARD_COLEMAK; - } - - [test release]; - - keyboard_layout_dirty = false; - return layout; - } - - return layout; -} - -void OS_OSX::process_events() { - - while (true) { - NSEvent *event = [NSApp - nextEventMatchingMask:NSEventMaskAny - untilDate:[NSDate distantPast] - inMode:NSDefaultRunLoopMode - dequeue:YES]; - - if (event == nil) - break; - - [NSApp sendEvent:event]; - } - process_key_events(); - - [autoreleasePool drain]; - autoreleasePool = [[NSAutoreleasePool alloc] init]; - - input->flush_accumulated_events(); -} - -void OS_OSX::process_key_events() { - - Ref<InputEventKey> k; - for (int i = 0; i < key_event_pos; i++) { - - const KeyEvent &ke = key_event_buffer[i]; - - if (ke.raw) { - // Non IME input - no composite characters, pass events as is - k.instance(); - - get_key_modifier_state(ke.osx_state, k); - k->set_pressed(ke.pressed); - k->set_echo(ke.echo); - k->set_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(); - - 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(); - - 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 OS_OSX::push_input(const Ref<InputEvent> &p_event) { - - Ref<InputEvent> ev = p_event; - input->accumulate_input_event(ev); -} - -void OS_OSX::force_process_input() { - - process_events(); // get rid of pending events - joypad_osx->process_joypads(); -} - void OS_OSX::run() { - force_quit = false; if (!main_loop) @@ -2910,23 +317,12 @@ void OS_OSX::run() { main_loop->init(); - if (zoomed) { - zoomed = false; - set_window_fullscreen(true); - } - - //uint64_t last_ticks=get_ticks_usec(); - - //int frames=0; - //uint64_t frame=0; - bool quit = false; - while (!force_quit && !quit) { - @try { - - process_events(); // get rid of pending events + if (DisplayServer::get_singleton()) { + DisplayServer::get_singleton()->process_events(); // get rid of pending events + } joypad_osx->process_joypads(); if (Main::iteration() == true) { @@ -2936,41 +332,9 @@ void OS_OSX::run() { ERR_PRINT("NSException: " + String([exception reason].UTF8String)); } }; - main_loop->finish(); } -void OS_OSX::set_mouse_mode(MouseMode p_mode) { - - if (p_mode == mouse_mode) - return; - - if (p_mode == MOUSE_MODE_CAPTURED) { - // Apple Docs state that the display parameter is not used. - // "This parameter is not used. By default, you may pass kCGDirectMainDisplay." - // https://developer.apple.com/library/mac/documentation/graphicsimaging/reference/Quartz_Services_Ref/Reference/reference.html - CGDisplayHideCursor(kCGDirectMainDisplay); - CGAssociateMouseAndMouseCursorPosition(false); - } else if (p_mode == MOUSE_MODE_HIDDEN) { - CGDisplayHideCursor(kCGDirectMainDisplay); - CGAssociateMouseAndMouseCursorPosition(true); - } else { - CGDisplayShowCursor(kCGDirectMainDisplay); - CGAssociateMouseAndMouseCursorPosition(true); - } - - mouse_mode = p_mode; -} - -OS::MouseMode OS_OSX::get_mouse_mode() const { - - return mouse_mode; -} - -String OS_OSX::get_joy_guid(int p_device) const { - return input->get_joy_guid_remapped(p_device); -} - Error OS_OSX::move_to_trash(const String &p_path) { NSFileManager *fm = [NSFileManager defaultManager]; NSURL *url = [NSURL fileURLWithPath:@(p_path.utf8().get_data())]; @@ -2984,123 +348,19 @@ Error OS_OSX::move_to_trash(const String &p_path) { return OK; } -void OS_OSX::_set_use_vsync(bool p_enable) { -#if defined(OPENGL_ENABLED) - if (video_driver_index == VIDEO_DRIVER_GLES2) { - if (context_gles2) - context_gles2->set_use_vsync(p_enable); - } -#endif -} - -OS_OSX *OS_OSX::singleton = NULL; - OS_OSX::OS_OSX() { - - memset(cursors, 0, sizeof(cursors)); - key_event_pos = 0; - mouse_mode = OS::MOUSE_MODE_VISIBLE; main_loop = NULL; - singleton = this; - im_active = false; - im_position = Point2(); - layered_window = false; - autoreleasePool = [[NSAutoreleasePool alloc] init]; - - eventSource = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); - ERR_FAIL_COND(!eventSource); - - CGEventSourceSetLocalEventsSuppressionInterval(eventSource, 0.0); - - // Implicitly create shared NSApplication instance - [GodotApplication sharedApplication]; - - // In case we are unbundled, make us a proper UI application - [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; - - // Menu bar setup must go between sharedApplication above and - // finishLaunching below, in order to properly emulate the behavior - // of NSApplicationMain - NSMenuItem *menu_item; - NSString *title; - - NSString *nsappname = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"]; - if (nsappname == nil) - nsappname = [[NSProcessInfo processInfo] processName]; - - // Setup Apple menu - NSMenu *apple_menu = [[NSMenu alloc] initWithTitle:@""]; - title = [NSString stringWithFormat:NSLocalizedString(@"About %@", nil), nsappname]; - [apple_menu addItemWithTitle:title action:@selector(showAbout:) keyEquivalent:@""]; - - [apple_menu addItem:[NSMenuItem separatorItem]]; - - NSMenu *services = [[NSMenu alloc] initWithTitle:@""]; - menu_item = [apple_menu addItemWithTitle:NSLocalizedString(@"Services", nil) action:nil keyEquivalent:@""]; - [apple_menu setSubmenu:services forItem:menu_item]; - [NSApp setServicesMenu:services]; - [services release]; - - [apple_menu addItem:[NSMenuItem separatorItem]]; - - title = [NSString stringWithFormat:NSLocalizedString(@"Hide %@", nil), nsappname]; - [apple_menu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h"]; - - menu_item = [apple_menu addItemWithTitle:NSLocalizedString(@"Hide Others", nil) action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; - [menu_item setKeyEquivalentModifierMask:(NSEventModifierFlagOption | NSEventModifierFlagCommand)]; - - [apple_menu addItemWithTitle:NSLocalizedString(@"Show all", nil) action:@selector(unhideAllApplications:) keyEquivalent:@""]; - - [apple_menu addItem:[NSMenuItem separatorItem]]; - - title = [NSString stringWithFormat:NSLocalizedString(@"Quit %@", nil), nsappname]; - [apple_menu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"]; - - // Setup menu bar - NSMenu *main_menu = [[NSMenu alloc] initWithTitle:@""]; - menu_item = [main_menu addItemWithTitle:@"" action:nil keyEquivalent:@""]; - [main_menu setSubmenu:apple_menu forItem:menu_item]; - [NSApp setMainMenu:main_menu]; - - [main_menu release]; - [apple_menu release]; - - [NSApp finishLaunching]; - - delegate = [[GodotApplicationDelegate alloc] init]; - ERR_FAIL_COND(!delegate); - [NSApp setDelegate:delegate]; - - cursor_shape = CURSOR_ARROW; - - maximized = false; - minimized = false; - window_size = Vector2(1024, 600); - zoomed = false; - resizable = false; - window_focused = true; + force_quit = false; Vector<Logger *> loggers; loggers.push_back(memnew(OSXTerminalLogger)); _set_logger(memnew(CompositeLogger(loggers))); - //process application:openFile: event - while (true) { - NSEvent *event = [NSApp - nextEventMatchingMask:NSEventMaskAny - untilDate:[NSDate distantPast] - inMode:NSDefaultRunLoopMode - dequeue:YES]; - - if (event == nil) - break; - - [NSApp sendEvent:event]; - } - #ifdef COREAUDIO_ENABLED AudioDriverManager::add_driver(&audio_driver); #endif + + DisplayServerOSX::register_osx_driver(); } bool OS_OSX::_check_internal_feature_support(const String &p_feature) { diff --git a/platform/osx/vulkan_context_osx.h b/platform/osx/vulkan_context_osx.h index 619e91d1f6..09a5494ae8 100644 --- a/platform/osx/vulkan_context_osx.h +++ b/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(); diff --git a/platform/osx/vulkan_context_osx.mm b/platform/osx/vulkan_context_osx.mm index c132bd334a..320401cdcb 100644 --- a/platform/osx/vulkan_context_osx.mm +++ b/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() { diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index fd82c33c4e..734e27b1b6 100644 --- a/scene/gui/line_edit.cpp +++ b/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(); diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 49e89f6c6d..fb477ee3b4 100644 --- a/scene/gui/text_edit.cpp +++ b/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(); diff --git a/scene/main/node.h b/scene/main/node.h index 981477a159..cf25a92be6 100644 --- a/scene/main/node.h +++ b/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, diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index 33cb067db1..fad10524b9 100644 --- a/scene/main/scene_tree.cpp +++ b/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")); diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h index 4fb19a57d5..e6b2575f98 100644 --- a/scene/main/scene_tree.h +++ b/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 diff --git a/scene/main/window.cpp b/scene/main/window.cpp index 5054e18a7a..a2a49aea75 100644 --- a/scene/main/window.cpp +++ b/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); diff --git a/servers/display_server.cpp b/servers/display_server.cpp index 29d46b90fd..1685328cbe 100644 --- a/servers/display_server.cpp +++ b/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) { diff --git a/servers/display_server.h b/servers/display_server.h index 51acf637e5..8ed0b11d03 100644 --- a/servers/display_server.h +++ b/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; |