diff options
author | RĂ©mi Verschelde <rverschelde@gmail.com> | 2020-03-26 17:23:11 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-03-26 17:23:11 +0100 |
commit | 5f1107aa30295e686be6f41cb6d17fc2cff1e036 (patch) | |
tree | 7bce4c680e6686c9d29be8b479be5b39205ce7a3 /platform | |
parent | a2da99f40cf2123c0906c734a2eb01e9b65a45a2 (diff) | |
parent | be07f86f85ab70a48b310b42faa64e72a74ca694 (diff) |
Merge pull request #37317 from akien-mga/display-server-rebased
Separate DisplayServer from OS and add multiple windows support
Diffstat (limited to 'platform')
59 files changed, 10523 insertions, 8316 deletions
diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp index 8bbf41d82d..e018ee69f0 100644 --- a/platform/android/java_godot_lib_jni.cpp +++ b/platform/android/java_godot_lib_jni.cpp @@ -29,6 +29,7 @@ /*************************************************************************/ #include "java_godot_lib_jni.h" + #include "java_godot_io_wrapper.h" #include "java_godot_wrapper.h" @@ -37,17 +38,18 @@ #include "api/java_class_wrapper.h" #include "audio_driver_jandroid.h" #include "core/engine.h" +#include "core/input/input_filter.h" #include "core/project_settings.h" #include "dir_access_jandroid.h" #include "file_access_android.h" #include "file_access_jandroid.h" #include "jni_utils.h" -#include "main/input_default.h" #include "main/main.h" #include "net_socket_android.h" #include "os_android.h" #include "string_android.h" #include "thread_jandroid.h" + #include <unistd.h> static JavaClassWrapper *java_class_wrapper = NULL; diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp index 8021d6dd07..e3792b1f31 100644 --- a/platform/android/os_android.cpp +++ b/platform/android/os_android.cpp @@ -306,14 +306,14 @@ void OS_Android::main_loop_end() { void OS_Android::main_loop_focusout() { if (main_loop) - main_loop->notification(MainLoop::NOTIFICATION_WM_FOCUS_OUT); + main_loop->notification(NOTIFICATION_WM_FOCUS_OUT); audio_driver_android.set_pause(true); } void OS_Android::main_loop_focusin() { if (main_loop) - main_loop->notification(MainLoop::NOTIFICATION_WM_FOCUS_IN); + main_loop->notification(NOTIFICATION_WM_FOCUS_IN); audio_driver_android.set_pause(false); } @@ -568,7 +568,7 @@ void OS_Android::init_video_mode(int p_video_width, int p_video_height) { void OS_Android::main_loop_request_go_back() { if (main_loop) - main_loop->notification(MainLoop::NOTIFICATION_WM_GO_BACK_REQUEST); + main_loop->notification(NOTIFICATION_WM_GO_BACK_REQUEST); } void OS_Android::set_display_size(Size2 p_size) { diff --git a/platform/android/os_android.h b/platform/android/os_android.h index ec6ffe5438..d09f4e10d6 100644 --- a/platform/android/os_android.h +++ b/platform/android/os_android.h @@ -33,10 +33,9 @@ #include "audio_driver_jandroid.h" #include "audio_driver_opensl.h" -#include "core/os/input.h" +#include "core/input/input_filter.h" #include "core/os/main_loop.h" #include "drivers/unix/os_unix.h" -#include "main/input_default.h" #include "servers/audio_server.h" #include "servers/visual/rasterizer.h" diff --git a/platform/haiku/haiku_direct_window.cpp b/platform/haiku/haiku_direct_window.cpp index 2d7efe6b61..18e1862d33 100644 --- a/platform/haiku/haiku_direct_window.cpp +++ b/platform/haiku/haiku_direct_window.cpp @@ -74,7 +74,7 @@ void HaikuDirectWindow::SetMainLoop(MainLoop *p_main_loop) { bool HaikuDirectWindow::QuitRequested() { StopMessageRunner(); - main_loop->notification(MainLoop::NOTIFICATION_WM_QUIT_REQUEST); + main_loop->notification(NOTIFICATION_WM_CLOSE_REQUEST); return false; } diff --git a/platform/haiku/haiku_direct_window.h b/platform/haiku/haiku_direct_window.h index ccc9542f0e..925bb9ac5d 100644 --- a/platform/haiku/haiku_direct_window.h +++ b/platform/haiku/haiku_direct_window.h @@ -35,8 +35,8 @@ #include <DirectWindow.h> +#include "core/input/input_filter.h" #include "core/os/os.h" -#include "main/input_default.h" #include "haiku_gl_view.h" diff --git a/platform/haiku/os_haiku.h b/platform/haiku/os_haiku.h index fc8cb77a91..90c0abc3ef 100644 --- a/platform/haiku/os_haiku.h +++ b/platform/haiku/os_haiku.h @@ -33,10 +33,10 @@ #include "audio_driver_media_kit.h" #include "context_gl_haiku.h" +#include "core/input/input_filter.h" #include "drivers/unix/os_unix.h" #include "haiku_application.h" #include "haiku_direct_window.h" -#include "main/input_default.h" #include "servers/audio_server.h" #include "servers/visual_server.h" diff --git a/platform/iphone/os_iphone.h b/platform/iphone/os_iphone.h index f42679e754..2efb7af7c2 100644 --- a/platform/iphone/os_iphone.h +++ b/platform/iphone/os_iphone.h @@ -33,15 +33,13 @@ #ifndef OS_IPHONE_H #define OS_IPHONE_H -#include "core/os/input.h" +#include "core/input/input_filter.h" #include "drivers/coreaudio/audio_driver_coreaudio.h" #include "drivers/unix/os_unix.h" - #include "game_center.h" #include "icloud.h" #include "in_app_store.h" #include "ios.h" -#include "main/input_default.h" #include "servers/audio_server.h" #include "servers/visual/rasterizer.h" #include "servers/visual_server.h" diff --git a/platform/javascript/os_javascript.cpp b/platform/javascript/os_javascript.cpp index db90b01f8f..b7feb5bfbd 100644 --- a/platform/javascript/os_javascript.cpp +++ b/platform/javascript/os_javascript.cpp @@ -1009,10 +1009,10 @@ Error OS_JavaScript::initialize(const VideoMode &p_desired, int p_video_driver, update_clipboard(evt.clipboardData.getData('text')); }, true); }, - MainLoop::NOTIFICATION_WM_MOUSE_ENTER, - MainLoop::NOTIFICATION_WM_MOUSE_EXIT, - MainLoop::NOTIFICATION_WM_FOCUS_IN, - MainLoop::NOTIFICATION_WM_FOCUS_OUT + NOTIFICATION_WM_MOUSE_ENTER, + NOTIFICATION_WM_MOUSE_EXIT, + NOTIFICATION_WM_FOCUS_IN, + NOTIFICATION_WM_FOCUS_OUT ); /* clang-format on */ @@ -1121,8 +1121,8 @@ int OS_JavaScript::get_process_id() const { extern "C" EMSCRIPTEN_KEEPALIVE void send_notification(int p_notification) { - if (p_notification == MainLoop::NOTIFICATION_WM_MOUSE_ENTER || p_notification == MainLoop::NOTIFICATION_WM_MOUSE_EXIT) { - cursor_inside_canvas = p_notification == MainLoop::NOTIFICATION_WM_MOUSE_ENTER; + if (p_notification == NOTIFICATION_WM_MOUSE_ENTER || p_notification == NOTIFICATION_WM_MOUSE_EXIT) { + cursor_inside_canvas = p_notification == NOTIFICATION_WM_MOUSE_ENTER; } OS_JavaScript::get_singleton()->get_main_loop()->notification(p_notification); } diff --git a/platform/javascript/os_javascript.h b/platform/javascript/os_javascript.h index ed59477914..1a3a6616b0 100644 --- a/platform/javascript/os_javascript.h +++ b/platform/javascript/os_javascript.h @@ -32,8 +32,8 @@ #define OS_JAVASCRIPT_H #include "audio_driver_javascript.h" +#include "core/input/input_filter.h" #include "drivers/unix/os_unix.h" -#include "main/input_default.h" #include "servers/audio_server.h" #include "servers/visual/rasterizer.h" diff --git a/platform/linuxbsd/SCsub b/platform/linuxbsd/SCsub new file mode 100644 index 0000000000..f3f65e216e --- /dev/null +++ b/platform/linuxbsd/SCsub @@ -0,0 +1,23 @@ +#!/usr/bin/env python + +Import('env') + +from platform_methods import run_in_subprocess +import platform_linuxbsd_builders + +common_x11 = [ + "crash_handler_linuxbsd.cpp", + "os_linuxbsd.cpp", + "joypad_linux.cpp", + "context_gl_x11.cpp", + "detect_prime_x11.cpp", + "display_server_x11.cpp", + "vulkan_context_x11.cpp", + "key_mapping_x11.cpp", + +] + +prog = env.add_program('#bin/godot', ['godot_linuxbsd.cpp'] + common_x11) + +if (env["debug_symbols"] == "full" or env["debug_symbols"] == "yes") and env["separate_debug_symbols"]: + env.AddPostAction(prog, run_in_subprocess(platform_linuxbsd_builders.make_debug_linuxbsd)) diff --git a/platform/x11/context_gl_x11.cpp b/platform/linuxbsd/context_gl_x11.cpp index 5442af3bef..5442af3bef 100644 --- a/platform/x11/context_gl_x11.cpp +++ b/platform/linuxbsd/context_gl_x11.cpp diff --git a/platform/x11/context_gl_x11.h b/platform/linuxbsd/context_gl_x11.h index 2c0643c95a..2c0643c95a 100644 --- a/platform/x11/context_gl_x11.h +++ b/platform/linuxbsd/context_gl_x11.h diff --git a/platform/x11/crash_handler_x11.cpp b/platform/linuxbsd/crash_handler_linuxbsd.cpp index 19c8f71d0e..1b3804e3ed 100644 --- a/platform/x11/crash_handler_x11.cpp +++ b/platform/linuxbsd/crash_handler_linuxbsd.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* crash_handler_x11.cpp */ +/* crash_handler_linuxbsd.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,7 +28,7 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "crash_handler_x11.h" +#include "crash_handler_linuxbsd.h" #include "core/os/os.h" #include "core/project_settings.h" diff --git a/platform/x11/crash_handler_x11.h b/platform/linuxbsd/crash_handler_linuxbsd.h index 98620cc789..94b4649690 100644 --- a/platform/x11/crash_handler_x11.h +++ b/platform/linuxbsd/crash_handler_linuxbsd.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* crash_handler_x11.h */ +/* crash_handler_linuxbsd.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ diff --git a/platform/x11/detect.py b/platform/linuxbsd/detect.py index 9a9ab86068..1a395efffe 100644 --- a/platform/x11/detect.py +++ b/platform/linuxbsd/detect.py @@ -8,7 +8,7 @@ def is_active(): def get_name(): - return "X11" + return "LinuxBSD" def can_build(): @@ -317,7 +317,7 @@ def configure(env): if not env['builtin_zlib']: env.ParseConfig('pkg-config zlib --cflags --libs') - env.Prepend(CPPPATH=['#platform/x11']) + env.Prepend(CPPPATH=['#platform/linuxbsd']) env.Append(CPPDEFINES=['X11_ENABLED', 'UNIX_ENABLED']) env.Append(CPPDEFINES=['VULKAN_ENABLED']) @@ -350,9 +350,9 @@ def configure(env): print("Warning: Creating template binaries enabled for PCK embedding is currently only supported with GNU ld") else: if float(gnu_ld_version.group(1)) >= 2.30: - env.Append(LINKFLAGS=['-T', 'platform/x11/pck_embed.ld']) + env.Append(LINKFLAGS=['-T', 'platform/linuxbsd/pck_embed.ld']) else: - env.Append(LINKFLAGS=['-T', 'platform/x11/pck_embed.legacy.ld']) + env.Append(LINKFLAGS=['-T', 'platform/linuxbsd/pck_embed.legacy.ld']) ## Cross-compilation diff --git a/platform/x11/detect_prime.cpp b/platform/linuxbsd/detect_prime_x11.cpp index d79fd00118..69b0837e6c 100644 --- a/platform/x11/detect_prime.cpp +++ b/platform/linuxbsd/detect_prime_x11.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* detect_prime.cpp */ +/* detect_prime_x11.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ diff --git a/platform/x11/detect_prime.h b/platform/linuxbsd/detect_prime_x11.h index df636449f4..039bdee76b 100644 --- a/platform/x11/detect_prime.h +++ b/platform/linuxbsd/detect_prime_x11.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* detect_prime.h */ +/* detect_prime_x11.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ diff --git a/platform/x11/os_x11.cpp b/platform/linuxbsd/display_server_x11.cpp index 772913980b..4bea458187 100644 --- a/platform/x11/os_x11.cpp +++ b/platform/linuxbsd/display_server_x11.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* os_x11.cpp */ +/* display_server_x11.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,8 +28,11 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "os_x11.h" -#include "detect_prime.h" +#include "display_server_x11.h" + +#ifdef X11_ENABLED + +#include "detect_prime_x11.h" #include "core/os/dir_access.h" #include "core/print_string.h" @@ -44,8 +47,7 @@ #include "servers/visual/rasterizer_rd/rasterizer_rd.h" #endif -#include "servers/visual/visual_server_raster.h" -#include "servers/visual/visual_server_wrap_mt.h" +#include "scene/resources/texture.h" #ifdef HAVE_MNTENT #include <mntent.h> @@ -86,6 +88,8 @@ #include <X11/XKBlib.h> +#include "core/project_settings.h" + // 2.2 is the first release with multitouch #define XINPUT_CLIENT_VERSION_MAJOR 2 #define XINPUT_CLIENT_VERSION_MINOR 2 @@ -99,584 +103,117 @@ static const double abs_resolution_mult = 10000.0; static const double abs_resolution_range_mult = 10.0; -void OS_X11::initialize_core() { - - crash_handler.initialize(); - - OS_Unix::initialize_core(); -} - -int OS_X11::get_current_video_driver() const { - return video_driver_index; -} - -Error OS_X11::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) { - - long im_event_mask = 0; - last_button_state = 0; - - xmbstring = NULL; - x11_window = 0; - last_click_ms = 0; - last_click_button_index = -1; - last_click_pos = Point2(-100, -100); - args = OS::get_singleton()->get_cmdline_args(); - current_videomode = p_desired; - main_loop = NULL; - last_timestamp = 0; - last_mouse_pos_valid = false; - last_keyrelease_time = 0; - xdnd_version = 0; - - if (get_render_thread_mode() == RENDER_SEPARATE_THREAD) { - XInitThreads(); - } - - /** XLIB INITIALIZATION **/ - x11_display = XOpenDisplay(NULL); - - if (!x11_display) { - ERR_PRINT("X11 Display is not available"); - return ERR_UNAVAILABLE; - } - - char *modifiers = NULL; - Bool xkb_dar = False; - XAutoRepeatOn(x11_display); - xkb_dar = XkbSetDetectableAutoRepeat(x11_display, True, NULL); - - // Try to support IME if detectable auto-repeat is supported - if (xkb_dar == True) { - -#ifdef X_HAVE_UTF8_STRING - // Xutf8LookupString will be used later instead of XmbLookupString before - // the multibyte sequences can be converted to unicode string. - modifiers = XSetLocaleModifiers(""); +bool DisplayServerX11::has_feature(Feature p_feature) const { + switch (p_feature) { + case FEATURE_SUBWINDOWS: +#ifdef TOUCH_ENABLED + case FEATURE_TOUCHSCREEN: #endif - } - - if (modifiers == NULL) { - if (is_stdout_verbose()) { - WARN_PRINT("IME is disabled"); - } - XSetLocaleModifiers("@im=none"); - WARN_PRINT("Error setting locale modifiers"); - } - - const char *err; - xrr_get_monitors = NULL; - xrr_free_monitors = NULL; - int xrandr_major = 0; - int xrandr_minor = 0; - int event_base, error_base; - xrandr_ext_ok = XRRQueryExtension(x11_display, &event_base, &error_base); - xrandr_handle = dlopen("libXrandr.so.2", RTLD_LAZY); - if (!xrandr_handle) { - err = dlerror(); - fprintf(stderr, "could not load libXrandr.so.2, Error: %s\n", err); - } else { - XRRQueryVersion(x11_display, &xrandr_major, &xrandr_minor); - if (((xrandr_major << 8) | xrandr_minor) >= 0x0105) { - xrr_get_monitors = (xrr_get_monitors_t)dlsym(xrandr_handle, "XRRGetMonitors"); - if (!xrr_get_monitors) { - err = dlerror(); - fprintf(stderr, "could not find symbol XRRGetMonitors\nError: %s\n", err); - } else { - xrr_free_monitors = (xrr_free_monitors_t)dlsym(xrandr_handle, "XRRFreeMonitors"); - if (!xrr_free_monitors) { - err = dlerror(); - fprintf(stderr, "could not find XRRFreeMonitors\nError: %s\n", err); - xrr_get_monitors = NULL; - } - } - } - } - - if (!refresh_device_info()) { - OS::get_singleton()->alert("Your system does not support XInput 2.\n" - "Please upgrade your distribution.", - "Unable to initialize XInput"); - return ERR_UNAVAILABLE; - } - - xim = XOpenIM(x11_display, NULL, NULL, NULL); - - if (xim == NULL) { - WARN_PRINT("XOpenIM failed"); - xim_style = 0L; - } else { - ::XIMCallback im_destroy_callback; - im_destroy_callback.client_data = (::XPointer)(this); - im_destroy_callback.callback = (::XIMProc)(xim_destroy_callback); - if (XSetIMValues(xim, XNDestroyCallback, &im_destroy_callback, - NULL) != NULL) { - WARN_PRINT("Error setting XIM destroy callback"); - } - - ::XIMStyles *xim_styles = NULL; - xim_style = 0L; - char *imvalret = XGetIMValues(xim, XNQueryInputStyle, &xim_styles, NULL); - if (imvalret != NULL || xim_styles == NULL) { - fprintf(stderr, "Input method doesn't support any styles\n"); - } - - if (xim_styles) { - xim_style = 0L; - for (int i = 0; i < xim_styles->count_styles; i++) { - - if (xim_styles->supported_styles[i] == - (XIMPreeditNothing | XIMStatusNothing)) { - - xim_style = xim_styles->supported_styles[i]; - break; - } - } - - XFree(xim_styles); + case FEATURE_MOUSE: + case FEATURE_MOUSE_WARP: + case FEATURE_CLIPBOARD: + case FEATURE_CURSOR_SHAPE: + case FEATURE_CUSTOM_CURSOR_SHAPE: + case FEATURE_IME: + case FEATURE_WINDOW_TRANSPARENCY: + //case FEATURE_HIDPI: + case FEATURE_ICON: + case FEATURE_NATIVE_ICON: + case FEATURE_SWAP_BUFFERS: + return true; + default: { } - XFree(imvalret); } - //!!!!!!!!!!!!!!!!!!!!!!!!!! - //TODO - do Vulkan and GLES2 support checks, driver selection and fallback - video_driver_index = p_video_driver; -#ifndef _MSC_VER -#warning Forcing vulkan video driver because OpenGL not implemented yet -#endif - video_driver_index = VIDEO_DRIVER_VULKAN; - - print_verbose("Driver: " + String(get_video_driver_name(video_driver_index)) + " [" + itos(video_driver_index) + "]"); - //!!!!!!!!!!!!!!!!!!!!!!!!!! - - //Create window - - long visualMask = VisualScreenMask; - int numberOfVisuals; - XVisualInfo vInfoTemplate = {}; - vInfoTemplate.screen = DefaultScreen(x11_display); - XVisualInfo *visualInfo = XGetVisualInfo(x11_display, visualMask, &vInfoTemplate, &numberOfVisuals); - - Colormap colormap = XCreateColormap(x11_display, RootWindow(x11_display, vInfoTemplate.screen), visualInfo->visual, AllocNone); - - XSetWindowAttributes windowAttributes = {}; - windowAttributes.colormap = colormap; - windowAttributes.background_pixel = 0xFFFFFFFF; - windowAttributes.border_pixel = 0; - windowAttributes.event_mask = KeyPressMask | KeyReleaseMask | StructureNotifyMask | ExposureMask; - - unsigned long valuemask = CWBorderPixel | CWColormap | CWEventMask; - x11_window = XCreateWindow(x11_display, RootWindow(x11_display, visualInfo->screen), 0, 0, OS::get_singleton()->get_video_mode().width, OS::get_singleton()->get_video_mode().height, 0, visualInfo->depth, InputOutput, visualInfo->visual, valuemask, &windowAttributes); - - wm_delete = XInternAtom(x11_display, "WM_DELETE_WINDOW", true); - XSetWMProtocols(x11_display, x11_window, &wm_delete, 1); - - //set_class_hint(x11_display, x11_window); - XMapWindow(x11_display, x11_window); - XFlush(x11_display); - - XSync(x11_display, False); - //XSetErrorHandler(oldHandler); - - XFree(visualInfo); - - // Init context and rendering device -#if defined(OPENGL_ENABLED) - if (video_driver_index == VIDEO_DRIVER_GLES2) { - if (getenv("DRI_PRIME") == NULL) { - int use_prime = -1; - - if (getenv("PRIMUS_DISPLAY") || - getenv("PRIMUS_libGLd") || - getenv("PRIMUS_libGLa") || - getenv("PRIMUS_libGL") || - getenv("PRIMUS_LOAD_GLOBAL") || - getenv("BUMBLEBEE_SOCKET")) { - - print_verbose("Optirun/primusrun detected. Skipping GPU detection"); - use_prime = 0; - } - - if (getenv("LD_LIBRARY_PATH")) { - String ld_library_path(getenv("LD_LIBRARY_PATH")); - Vector<String> libraries = ld_library_path.split(":"); + return false; +} +String DisplayServerX11::get_name() const { + return "X11"; +} - for (int i = 0; i < libraries.size(); ++i) { - if (FileAccess::exists(libraries[i] + "/libGL.so.1") || - FileAccess::exists(libraries[i] + "/libGL.so")) { +void DisplayServerX11::alert(const String &p_alert, const String &p_title) { + const char *message_programs[] = { "zenity", "kdialog", "Xdialog", "xmessage" }; - print_verbose("Custom libGL override detected. Skipping GPU detection"); - use_prime = 0; - } - } - } + String path = OS::get_singleton()->get_environment("PATH"); + Vector<String> path_elems = path.split(":", false); + String program; - if (use_prime == -1) { - print_verbose("Detecting GPUs, set DRI_PRIME in the environment to override GPU detection logic."); - use_prime = detect_prime(); - } + for (int i = 0; i < path_elems.size(); i++) { + for (uint64_t k = 0; k < sizeof(message_programs) / sizeof(char *); k++) { + String tested_path = path_elems[i].plus_file(message_programs[k]); - if (use_prime) { - print_line("Found discrete GPU, setting DRI_PRIME=1 to use it."); - print_line("Note: Set DRI_PRIME=0 in the environment to disable Godot from using the discrete GPU."); - setenv("DRI_PRIME", "1", 1); + if (FileAccess::exists(tested_path)) { + program = tested_path; + break; } } - ContextGL_X11::ContextType opengl_api_type = ContextGL_X11::GLES_2_0_COMPATIBLE; - - context_gles2 = memnew(ContextGL_X11(x11_display, x11_window, current_videomode, opengl_api_type)); - - if (context_gles2->initialize() != OK) { - memdelete(context_gles2); - context_gles2 = NULL; - ERR_FAIL_V(ERR_UNAVAILABLE); - } - - context_gles2->set_use_vsync(current_videomode.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(VulkanContextX11); - if (context_vulkan->initialize() != OK) { - memdelete(context_vulkan); - context_vulkan = NULL; - ERR_FAIL_V(ERR_UNAVAILABLE); - } - if (context_vulkan->window_create(x11_window, x11_display, get_video_mode().width, get_video_mode().height) == -1) { - memdelete(context_vulkan); - context_vulkan = NULL; - ERR_FAIL_V(ERR_UNAVAILABLE); - } - - //temporary - rendering_device_vulkan = memnew(RenderingDeviceVulkan); - rendering_device_vulkan->initialize(context_vulkan); - - RasterizerRD::make_current(); - } -#endif - - 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)); + if (program.length()) + break; } - if (current_videomode.maximized) { - current_videomode.maximized = false; - set_window_maximized(true); - // borderless fullscreen window mode - } else if (current_videomode.fullscreen) { - current_videomode.fullscreen = false; - set_window_fullscreen(true); - } else if (current_videomode.borderless_window) { - Hints hints; - Atom property; - hints.flags = 2; - hints.decorations = 0; - property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True); - XChangeProperty(x11_display, x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5); - } + List<String> args; - // make PID known to X11 - { - const long pid = this->get_process_id(); - Atom net_wm_pid = XInternAtom(x11_display, "_NET_WM_PID", False); - XChangeProperty(x11_display, x11_window, net_wm_pid, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&pid, 1); + if (program.ends_with("zenity")) { + args.push_back("--error"); + args.push_back("--width"); + args.push_back("500"); + args.push_back("--title"); + args.push_back(p_title); + args.push_back("--text"); + args.push_back(p_alert); } - // disable resizable window - if (!current_videomode.resizable && !current_videomode.fullscreen) { - XSizeHints *xsh; - xsh = XAllocSizeHints(); - xsh->flags = PMinSize | PMaxSize; - XWindowAttributes xwa; - if (current_videomode.fullscreen) { - XGetWindowAttributes(x11_display, DefaultRootWindow(x11_display), &xwa); - } else { - XGetWindowAttributes(x11_display, x11_window, &xwa); - } - xsh->min_width = xwa.width; - xsh->max_width = xwa.width; - xsh->min_height = xwa.height; - xsh->max_height = xwa.height; - XSetWMNormalHints(x11_display, x11_window, xsh); - XFree(xsh); + if (program.ends_with("kdialog")) { + args.push_back("--error"); + args.push_back(p_alert); + args.push_back("--title"); + args.push_back(p_title); } - if (current_videomode.always_on_top) { - current_videomode.always_on_top = false; - set_window_always_on_top(true); + if (program.ends_with("Xdialog")) { + args.push_back("--title"); + args.push_back(p_title); + args.push_back("--msgbox"); + args.push_back(p_alert); + args.push_back("0"); + args.push_back("0"); } - ERR_FAIL_COND_V(!visual_server, ERR_UNAVAILABLE); - ERR_FAIL_COND_V(x11_window == 0, ERR_UNAVAILABLE); - - XSetWindowAttributes new_attr; - - new_attr.event_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask | - ButtonReleaseMask | EnterWindowMask | - LeaveWindowMask | PointerMotionMask | - Button1MotionMask | - Button2MotionMask | Button3MotionMask | - Button4MotionMask | Button5MotionMask | - ButtonMotionMask | KeymapStateMask | - ExposureMask | VisibilityChangeMask | - StructureNotifyMask | - SubstructureNotifyMask | SubstructureRedirectMask | - FocusChangeMask | PropertyChangeMask | - ColormapChangeMask | OwnerGrabButtonMask | - im_event_mask; - - XChangeWindowAttributes(x11_display, x11_window, CWEventMask, &new_attr); - - static unsigned char all_mask_data[XIMaskLen(XI_LASTEVENT)] = {}; - static unsigned char all_master_mask_data[XIMaskLen(XI_LASTEVENT)] = {}; - - xi.all_event_mask.deviceid = XIAllDevices; - xi.all_event_mask.mask_len = sizeof(all_mask_data); - xi.all_event_mask.mask = all_mask_data; - - xi.all_master_event_mask.deviceid = XIAllMasterDevices; - xi.all_master_event_mask.mask_len = sizeof(all_master_mask_data); - xi.all_master_event_mask.mask = all_master_mask_data; - - XISetMask(xi.all_event_mask.mask, XI_HierarchyChanged); - XISetMask(xi.all_master_event_mask.mask, XI_DeviceChanged); - XISetMask(xi.all_master_event_mask.mask, XI_RawMotion); - -#ifdef TOUCH_ENABLED - if (xi.touch_devices.size()) { - XISetMask(xi.all_event_mask.mask, XI_TouchBegin); - XISetMask(xi.all_event_mask.mask, XI_TouchUpdate); - XISetMask(xi.all_event_mask.mask, XI_TouchEnd); - XISetMask(xi.all_event_mask.mask, XI_TouchOwnership); + if (program.ends_with("xmessage")) { + args.push_back("-center"); + args.push_back("-title"); + args.push_back(p_title); + args.push_back(p_alert); } -#endif - - XISelectEvents(x11_display, x11_window, &xi.all_event_mask, 1); - XISelectEvents(x11_display, DefaultRootWindow(x11_display), &xi.all_master_event_mask, 1); - - // Disabled by now since grabbing also blocks mouse events - // (they are received as extended events instead of standard events) - /*XIClearMask(xi.touch_event_mask.mask, XI_TouchOwnership); - - // Grab touch devices to avoid OS gesture interference - for (int i = 0; i < xi.touch_devices.size(); ++i) { - XIGrabDevice(x11_display, xi.touch_devices[i], x11_window, CurrentTime, None, XIGrabModeAsync, XIGrabModeAsync, False, &xi.touch_event_mask); - }*/ - - /* set the titlebar name */ - XStoreName(x11_display, x11_window, "Godot"); - - im_active = false; - im_position = Vector2(); - if (xim && xim_style) { - - xic = XCreateIC(xim, XNInputStyle, xim_style, XNClientWindow, x11_window, XNFocusWindow, x11_window, (char *)NULL); - if (XGetICValues(xic, XNFilterEvents, &im_event_mask, NULL) != NULL) { - WARN_PRINT("XGetICValues couldn't obtain XNFilterEvents value"); - XDestroyIC(xic); - xic = NULL; - } - if (xic) { - XUnsetICFocus(xic); - } else { - WARN_PRINT("XCreateIC couldn't create xic"); - } + if (program.length()) { + OS::get_singleton()->execute(program, args, true); } else { - - xic = NULL; - WARN_PRINT("XCreateIC couldn't create xic"); - } - - cursor_size = XcursorGetDefaultSize(x11_display); - cursor_theme = XcursorGetTheme(x11_display); - - if (!cursor_theme) { - print_verbose("XcursorGetTheme could not get cursor theme"); - cursor_theme = "default"; - } - - for (int i = 0; i < CURSOR_MAX; i++) { - - cursors[i] = None; - img[i] = NULL; - } - - current_cursor = CURSOR_ARROW; - - for (int i = 0; i < CURSOR_MAX; i++) { - - static const char *cursor_file[] = { - "left_ptr", - "xterm", - "hand2", - "cross", - "watch", - "left_ptr_watch", - "fleur", - "dnd-move", - "crossed_circle", - "v_double_arrow", - "h_double_arrow", - "size_bdiag", - "size_fdiag", - "move", - "row_resize", - "col_resize", - "question_arrow" - }; - - img[i] = XcursorLibraryLoadImage(cursor_file[i], cursor_theme, cursor_size); - if (!img[i]) { - const char *fallback = NULL; - - switch (i) { - case CURSOR_POINTING_HAND: - fallback = "pointer"; - break; - case CURSOR_CROSS: - fallback = "crosshair"; - break; - case CURSOR_WAIT: - fallback = "wait"; - break; - case CURSOR_BUSY: - fallback = "progress"; - break; - case CURSOR_DRAG: - fallback = "grabbing"; - break; - case CURSOR_CAN_DROP: - fallback = "hand1"; - break; - case CURSOR_FORBIDDEN: - fallback = "forbidden"; - break; - case CURSOR_VSIZE: - fallback = "ns-resize"; - break; - case CURSOR_HSIZE: - fallback = "ew-resize"; - break; - case CURSOR_BDIAGSIZE: - fallback = "fd_double_arrow"; - break; - case CURSOR_FDIAGSIZE: - fallback = "bd_double_arrow"; - break; - case CURSOR_MOVE: - img[i] = img[CURSOR_DRAG]; - break; - case CURSOR_VSPLIT: - fallback = "sb_v_double_arrow"; - break; - case CURSOR_HSPLIT: - fallback = "sb_h_double_arrow"; - break; - case CURSOR_HELP: - fallback = "help"; - break; - } - if (fallback != NULL) { - img[i] = XcursorLibraryLoadImage(fallback, cursor_theme, cursor_size); - } - } - if (img[i]) { - cursors[i] = XcursorImageLoadCursor(x11_display, img[i]); - } else { - print_verbose("Failed loading custom cursor: " + String(cursor_file[i])); - } - } - - { - // Creating an empty/transparent cursor - - // Create 1x1 bitmap - Pixmap cursormask = XCreatePixmap(x11_display, - RootWindow(x11_display, DefaultScreen(x11_display)), 1, 1, 1); - - // Fill with zero - XGCValues xgc; - xgc.function = GXclear; - GC gc = XCreateGC(x11_display, cursormask, GCFunction, &xgc); - XFillRectangle(x11_display, cursormask, gc, 0, 0, 1, 1); - - // Color value doesn't matter. Mask zero means no foreground or background will be drawn - XColor col = {}; - - Cursor cursor = XCreatePixmapCursor(x11_display, - cursormask, // source (using cursor mask as placeholder, since it'll all be ignored) - cursormask, // mask - &col, &col, 0, 0); - - XFreePixmap(x11_display, cursormask); - XFreeGC(x11_display, gc); - - if (cursor == None) { - ERR_PRINT("FAILED CREATING CURSOR"); - } - - null_cursor = cursor; + print_line(p_alert); } - set_cursor_shape(CURSOR_BUSY); - - //Set Xdnd (drag & drop) support - Atom XdndAware = XInternAtom(x11_display, "XdndAware", False); - Atom version = 5; - XChangeProperty(x11_display, x11_window, XdndAware, XA_ATOM, 32, PropModeReplace, (unsigned char *)&version, 1); - - xdnd_enter = XInternAtom(x11_display, "XdndEnter", False); - xdnd_position = XInternAtom(x11_display, "XdndPosition", False); - xdnd_status = XInternAtom(x11_display, "XdndStatus", False); - xdnd_action_copy = XInternAtom(x11_display, "XdndActionCopy", False); - xdnd_drop = XInternAtom(x11_display, "XdndDrop", False); - xdnd_finished = XInternAtom(x11_display, "XdndFinished", False); - xdnd_selection = XInternAtom(x11_display, "XdndSelection", False); - requested = None; - - visual_server->init(); - - AudioDriverManager::initialize(p_audio_driver); +} - input = memnew(InputDefault); +void DisplayServerX11::_update_real_mouse_position(const WindowData &wd) { + Window root_return, child_return; + int root_x, root_y, win_x, win_y; + unsigned int mask_return; - window_has_focus = true; // Set focus to true at init -#ifdef JOYDEV_ENABLED - joypad = memnew(JoypadLinux(input)); -#endif - _ensure_user_data_dir(); + Bool xquerypointer_result = XQueryPointer(x11_display, wd.x11_window, &root_return, &child_return, &root_x, &root_y, + &win_x, &win_y, &mask_return); - if (p_desired.layered) { - set_window_per_pixel_transparency_enabled(true); - } + if (xquerypointer_result) { + if (win_x > 0 && win_y > 0 && win_x <= wd.size.width && win_y <= wd.size.height) { - XEvent xevent; - while (XPending(x11_display) > 0) { - XNextEvent(x11_display, &xevent); - if (xevent.type == ConfigureNotify) { - _window_changed(&xevent); + last_mouse_pos.x = win_x; + last_mouse_pos.y = win_y; + last_mouse_pos_valid = true; + InputFilter::get_singleton()->set_mouse_position(last_mouse_pos); } } - - update_real_mouse_position(); - - return OK; } -bool OS_X11::refresh_device_info() { +bool DisplayServerX11::_refresh_device_info() { int event_base, error_base; print_verbose("XInput: Refreshing devices."); @@ -786,136 +323,35 @@ bool OS_X11::refresh_device_info() { return true; } -void OS_X11::xim_destroy_callback(::XIM im, ::XPointer client_data, - ::XPointer call_data) { - - WARN_PRINT("Input method stopped"); - OS_X11 *os = reinterpret_cast<OS_X11 *>(client_data); - os->xim = NULL; - os->xic = NULL; -} - -void OS_X11::set_ime_active(const bool p_active) { - - im_active = p_active; - - if (!xic) - return; - - if (p_active) { - XSetICFocus(xic); - set_ime_position(im_position); - } else { - XUnsetICFocus(xic); - } -} - -void OS_X11::set_ime_position(const Point2 &p_pos) { - - im_position = p_pos; - - if (!xic) - return; - - ::XPoint spot; - spot.x = short(p_pos.x); - spot.y = short(p_pos.y); - XVaNestedList preedit_attr = XVaCreateNestedList(0, XNSpotLocation, &spot, NULL); - XSetICValues(xic, XNPreeditAttributes, preedit_attr, NULL); - XFree(preedit_attr); -} +void DisplayServerX11::_flush_mouse_motion() { + while (true) { + if (XPending(x11_display) > 0) { + XEvent event; + XPeekEvent(x11_display, &event); -String OS_X11::get_unique_id() const { + if (XGetEventData(x11_display, &event.xcookie) && event.xcookie.type == GenericEvent && event.xcookie.extension == xi.opcode) { + XIDeviceEvent *event_data = (XIDeviceEvent *)event.xcookie.data; - static String machine_id; - if (machine_id.empty()) { - if (FileAccess *f = FileAccess::open("/etc/machine-id", FileAccess::READ)) { - while (machine_id.empty() && !f->eof_reached()) { - machine_id = f->get_line().strip_edges(); + if (event_data->evtype == XI_RawMotion) { + XNextEvent(x11_display, &event); + } else { + break; + } + } else { + break; } - f->close(); - memdelete(f); - } - } - return machine_id; -} - -void OS_X11::finalize() { - - if (main_loop) - memdelete(main_loop); - main_loop = NULL; - - /* - if (debugger_connection_console) { - memdelete(debugger_connection_console); - } - */ -#ifdef ALSAMIDI_ENABLED - driver_alsamidi.close(); -#endif - -#ifdef JOYDEV_ENABLED - memdelete(joypad); -#endif - - xi.touch_devices.clear(); - xi.state.clear(); - - memdelete(input); - - cursors_cache.clear(); - visual_server->finish(); - memdelete(visual_server); - -#if defined(OPENGL_ENABLED) - if (video_driver_index == VIDEO_DRIVER_GLES2) { - - if (context_gles2) - memdelete(context_gles2); - } -#endif -#if defined(VULKAN_ENABLED) - if (video_driver_index == VIDEO_DRIVER_VULKAN) { - - if (rendering_device_vulkan) { - rendering_device_vulkan->finalize(); - memdelete(rendering_device_vulkan); + } else { + break; } - - if (context_vulkan) - memdelete(context_vulkan); - } -#endif - - if (xrandr_handle) - dlclose(xrandr_handle); - - XUnmapWindow(x11_display, x11_window); - XDestroyWindow(x11_display, x11_window); - - for (int i = 0; i < CURSOR_MAX; i++) { - if (cursors[i] != None) - XFreeCursor(x11_display, cursors[i]); - if (img[i] != NULL) - XcursorImageDestroy(img[i]); - }; - - if (xic) { - XDestroyIC(xic); } - if (xim) { - XCloseIM(xim); - } - - XCloseDisplay(x11_display); - if (xmbstring) - memfree(xmbstring); - args.clear(); + xi.relative_motion.x = 0; + xi.relative_motion.y = 0; } -void OS_X11::set_mouse_mode(MouseMode p_mode) { +void DisplayServerX11::mouse_set_mode(MouseMode p_mode) { + + _THREAD_SAFE_METHOD_ if (p_mode == mouse_mode) return; @@ -926,34 +362,37 @@ void OS_X11::set_mouse_mode(MouseMode p_mode) { // The only modes that show a cursor are VISIBLE and CONFINED bool showCursor = (p_mode == MOUSE_MODE_VISIBLE || p_mode == MOUSE_MODE_CONFINED); - if (showCursor) { - XDefineCursor(x11_display, x11_window, cursors[current_cursor]); // show cursor - } else { - XDefineCursor(x11_display, x11_window, null_cursor); // hide cursor - } + for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { + if (showCursor) { + XDefineCursor(x11_display, E->get().x11_window, cursors[current_cursor]); // show cursor + } else { + XDefineCursor(x11_display, E->get().x11_window, null_cursor); // hide cursor + } + } mouse_mode = p_mode; if (mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED) { //flush pending motion events - flush_mouse_motion(); + _flush_mouse_motion(); + WindowData &main_window = windows[MAIN_WINDOW_ID]; if (XGrabPointer( - x11_display, x11_window, True, + x11_display, main_window.x11_window, True, ButtonPressMask | ButtonReleaseMask | PointerMotionMask, - GrabModeAsync, GrabModeAsync, x11_window, None, CurrentTime) != GrabSuccess) { + GrabModeAsync, GrabModeAsync, windows[MAIN_WINDOW_ID].x11_window, None, CurrentTime) != GrabSuccess) { ERR_PRINT("NO GRAB"); } if (mouse_mode == MOUSE_MODE_CAPTURED) { - center.x = current_videomode.width / 2; - center.y = current_videomode.height / 2; + center.x = main_window.size.width / 2; + center.y = main_window.size.height / 2; - XWarpPointer(x11_display, None, x11_window, + XWarpPointer(x11_display, None, main_window.x11_window, 0, 0, 0, 0, (int)center.x, (int)center.y); - input->set_mouse_position(center); + InputFilter::get_singleton()->set_mouse_position(center); } } else { do_mouse_warp = false; @@ -961,8 +400,13 @@ void OS_X11::set_mouse_mode(MouseMode p_mode) { XFlush(x11_display); } +DisplayServerX11::MouseMode DisplayServerX11::mouse_get_mode() const { + return mouse_mode; +} -void OS_X11::warp_mouse_position(const Point2 &p_to) { +void DisplayServerX11::mouse_warp_to_position(const Point2i &p_to) { + + _THREAD_SAFE_METHOD_ if (mouse_mode == MOUSE_MODE_CAPTURED) { @@ -973,184 +417,133 @@ void OS_X11::warp_mouse_position(const Point2 &p_to) { XGetWindowAttributes(x11_display, x11_window, &xwa); printf("%d %d\n", xwa.x, xwa.y); needed? */ - XWarpPointer(x11_display, None, x11_window, + XWarpPointer(x11_display, None, windows[MAIN_WINDOW_ID].x11_window, 0, 0, 0, 0, (int)p_to.x, (int)p_to.y); } } -void OS_X11::flush_mouse_motion() { - while (true) { - if (XPending(x11_display) > 0) { - XEvent event; - XPeekEvent(x11_display, &event); +Point2i DisplayServerX11::mouse_get_position() const { + return last_mouse_pos; +} - if (XGetEventData(x11_display, &event.xcookie) && event.xcookie.type == GenericEvent && event.xcookie.extension == xi.opcode) { - XIDeviceEvent *event_data = (XIDeviceEvent *)event.xcookie.data; +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); - if (event_data->evtype == XI_RawMotion) { - XNextEvent(x11_display, &event); - } else { - break; - } - } else { - break; - } - } else { - break; + return Vector2i(root_attrs.x + root_x, root_attrs.y + root_y); } } - - xi.relative_motion.x = 0; - xi.relative_motion.y = 0; -} - -OS::MouseMode OS_X11::get_mouse_mode() const { - return mouse_mode; + return Vector2i(); } -int OS_X11::get_mouse_button_state() const { +int DisplayServerX11::mouse_get_button_state() const { return last_button_state; } -Point2 OS_X11::get_mouse_position() const { - return last_mouse_pos; -} +void DisplayServerX11::clipboard_set(const String &p_text) { -bool OS_X11::get_window_per_pixel_transparency_enabled() const { + _THREAD_SAFE_METHOD_ - if (!is_layered_allowed()) return false; - return layered_window; + internal_clipboard = p_text; + XSetSelectionOwner(x11_display, XA_PRIMARY, windows[MAIN_WINDOW_ID].x11_window, CurrentTime); + XSetSelectionOwner(x11_display, XInternAtom(x11_display, "CLIPBOARD", 0), windows[MAIN_WINDOW_ID].x11_window, CurrentTime); } -void OS_X11::set_window_per_pixel_transparency_enabled(bool p_enabled) { +static String _clipboard_get_impl(Atom p_source, Window x11_window, ::Display *x11_display, String p_internal_clipboard, Atom target) { - if (!is_layered_allowed()) return; - if (layered_window != p_enabled) { - if (p_enabled) { - set_borderless_window(true); - layered_window = true; - } else { - layered_window = false; - } - } -} - -void OS_X11::set_window_title(const String &p_title) { - XStoreName(x11_display, x11_window, p_title.utf8().get_data()); + String ret; - Atom _net_wm_name = XInternAtom(x11_display, "_NET_WM_NAME", false); - Atom utf8_string = XInternAtom(x11_display, "UTF8_STRING", false); - XChangeProperty(x11_display, x11_window, _net_wm_name, utf8_string, 8, PropModeReplace, (unsigned char *)p_title.utf8().get_data(), p_title.utf8().length()); -} + Atom type; + Atom selection = XA_PRIMARY; + int format, result; + unsigned long len, bytes_left, dummy; + unsigned char *data; + Window Sown = XGetSelectionOwner(x11_display, p_source); -void OS_X11::set_video_mode(const VideoMode &p_video_mode, int p_screen) { -} + if (Sown == x11_window) { -OS::VideoMode OS_X11::get_video_mode(int p_screen) const { - return current_videomode; -} + return p_internal_clipboard; + }; -void OS_X11::get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen) const { -} + if (Sown != None) { + XConvertSelection(x11_display, p_source, target, selection, + x11_window, CurrentTime); + XFlush(x11_display); + while (true) { + XEvent event; + XNextEvent(x11_display, &event); + if (event.type == SelectionNotify && event.xselection.requestor == x11_window) { + break; + }; + }; -void OS_X11::set_wm_fullscreen(bool p_enabled) { - if (p_enabled && !get_borderless_window()) { - // remove decorations if the window is not already borderless - Hints hints; - Atom property; - hints.flags = 2; - hints.decorations = 0; - property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True); - XChangeProperty(x11_display, x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5); + // + // Do not get any data, see how much data is there + // + XGetWindowProperty(x11_display, x11_window, + selection, // Tricky.. + 0, 0, // offset - len + 0, // Delete 0==FALSE + AnyPropertyType, //flag + &type, // return type + &format, // return format + &len, &bytes_left, //that + &data); + // DATA is There + if (bytes_left > 0) { + result = XGetWindowProperty(x11_display, x11_window, + selection, 0, bytes_left, 0, + AnyPropertyType, &type, &format, + &len, &dummy, &data); + if (result == Success) { + ret.parse_utf8((const char *)data); + } else + printf("FAIL\n"); + if (data) { + XFree(data); + } + } } - if (p_enabled && !is_window_resizable()) { - // Set the window as resizable to prevent window managers to ignore the fullscreen state flag. - XSizeHints *xsh; + return ret; +} - xsh = XAllocSizeHints(); - xsh->flags = 0L; - XSetWMNormalHints(x11_display, x11_window, xsh); - XFree(xsh); +static String _clipboard_get(Atom p_source, Window x11_window, ::Display *x11_display, String p_internal_clipboard) { + String ret; + Atom utf8_atom = XInternAtom(x11_display, "UTF8_STRING", True); + if (utf8_atom != None) { + ret = _clipboard_get_impl(p_source, x11_window, x11_display, p_internal_clipboard, utf8_atom); } + if (ret == "") { + ret = _clipboard_get_impl(p_source, x11_window, x11_display, p_internal_clipboard, XA_STRING); + } + return ret; +} - // Using EWMH -- Extended Window Manager Hints - XEvent xev; - Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False); - Atom wm_fullscreen = XInternAtom(x11_display, "_NET_WM_STATE_FULLSCREEN", False); - - memset(&xev, 0, sizeof(xev)); - xev.type = ClientMessage; - xev.xclient.window = x11_window; - xev.xclient.message_type = wm_state; - xev.xclient.format = 32; - xev.xclient.data.l[0] = p_enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; - xev.xclient.data.l[1] = wm_fullscreen; - xev.xclient.data.l[2] = 0; - - XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); +String DisplayServerX11::clipboard_get() const { - // set bypass compositor hint - Atom bypass_compositor = XInternAtom(x11_display, "_NET_WM_BYPASS_COMPOSITOR", False); - unsigned long compositing_disable_on = p_enabled ? 1 : 0; - XChangeProperty(x11_display, x11_window, bypass_compositor, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&compositing_disable_on, 1); + _THREAD_SAFE_METHOD_ - XFlush(x11_display); + String ret; + ret = _clipboard_get(XInternAtom(x11_display, "CLIPBOARD", 0), windows[MAIN_WINDOW_ID].x11_window, x11_display, internal_clipboard); - if (!p_enabled) { - // Reset the non-resizable flags if we un-set these before. - Size2 size = get_window_size(); - XSizeHints *xsh; - xsh = XAllocSizeHints(); - if (!is_window_resizable()) { - xsh->flags = PMinSize | PMaxSize; - xsh->min_width = size.x; - xsh->max_width = size.x; - xsh->min_height = size.y; - xsh->max_height = size.y; - } else { - xsh->flags = 0L; - if (min_size != Size2()) { - xsh->flags |= PMinSize; - xsh->min_width = min_size.x; - xsh->min_height = min_size.y; - } - if (max_size != Size2()) { - xsh->flags |= PMaxSize; - xsh->max_width = max_size.x; - xsh->max_height = max_size.y; - } - } - XSetWMNormalHints(x11_display, x11_window, xsh); - XFree(xsh); + if (ret == "") { + ret = _clipboard_get(XA_PRIMARY, windows[MAIN_WINDOW_ID].x11_window, x11_display, internal_clipboard); + }; - // put back or remove decorations according to the last set borderless state - Hints hints; - Atom property; - hints.flags = 2; - hints.decorations = current_videomode.borderless_window ? 0 : 1; - property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True); - XChangeProperty(x11_display, x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5); - } + return ret; } -void OS_X11::set_wm_above(bool p_enabled) { - Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False); - Atom wm_above = XInternAtom(x11_display, "_NET_WM_STATE_ABOVE", False); +int DisplayServerX11::get_screen_count() const { - XClientMessageEvent xev; - memset(&xev, 0, sizeof(xev)); - xev.type = ClientMessage; - xev.window = x11_window; - xev.message_type = wm_state; - xev.format = 32; - xev.data.l[0] = p_enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; - xev.data.l[1] = wm_above; - xev.data.l[3] = 1; - XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent *)&xev); -} + _THREAD_SAFE_METHOD_ -int OS_X11::get_screen_count() const { // Using Xinerama Extension int event_base, error_base; const Bool ext_okay = XineramaQueryExtension(x11_display, &event_base, &error_base); @@ -1161,42 +554,12 @@ int OS_X11::get_screen_count() const { XFree(xsi); return count; } +Point2i DisplayServerX11::screen_get_position(int p_screen) const { -int OS_X11::get_current_screen() const { - int x, y; - Window child; - XTranslateCoordinates(x11_display, x11_window, DefaultRootWindow(x11_display), 0, 0, &x, &y, &child); + _THREAD_SAFE_METHOD_ - int count = get_screen_count(); - for (int i = 0; i < count; i++) { - Point2i pos = get_screen_position(i); - Size2i size = get_screen_size(i); - if ((x >= pos.x && x < pos.x + size.width) && (y >= pos.y && y < pos.y + size.height)) - return i; - } - return 0; -} - -void OS_X11::set_current_screen(int p_screen) { - int count = get_screen_count(); - if (p_screen >= count) return; - - if (current_videomode.fullscreen) { - Point2i position = get_screen_position(p_screen); - Size2i size = get_screen_size(p_screen); - - XMoveResizeWindow(x11_display, x11_window, position.x, position.y, size.x, size.y); - } else { - if (p_screen != get_current_screen()) { - Point2i position = get_screen_position(p_screen); - XMoveWindow(x11_display, x11_window, position.x, position.y); - } - } -} - -Point2 OS_X11::get_screen_position(int p_screen) const { - if (p_screen == -1) { - p_screen = get_current_screen(); + if (p_screen == SCREEN_OF_MAIN_WINDOW) { + p_screen = window_get_current_screen(); } // Using Xinerama Extension @@ -1219,39 +582,48 @@ Point2 OS_X11::get_screen_position(int p_screen) const { return position; } -Size2 OS_X11::get_screen_size(int p_screen) const { - if (p_screen == -1) { - p_screen = get_current_screen(); +Size2i DisplayServerX11::screen_get_size(int p_screen) const { + return screen_get_usable_rect(p_screen).size; +} + +Rect2i DisplayServerX11::screen_get_usable_rect(int p_screen) const { + _THREAD_SAFE_METHOD_ + + if (p_screen == SCREEN_OF_MAIN_WINDOW) { + p_screen = window_get_current_screen(); } // Using Xinerama Extension int event_base, error_base; const Bool ext_okay = XineramaQueryExtension(x11_display, &event_base, &error_base); - if (!ext_okay) return Size2i(0, 0); + if (!ext_okay) return Rect2i(0, 0, 0, 0); int count; XineramaScreenInfo *xsi = XineramaQueryScreens(x11_display, &count); - if (p_screen >= count) return Size2i(0, 0); + if (p_screen >= count) return Rect2i(0, 0, 0, 0); - Size2i size = Point2i(xsi[p_screen].width, xsi[p_screen].height); + Rect2i rect = Rect2i(xsi[p_screen].x_org, xsi[p_screen].y_org, xsi[p_screen].width, xsi[p_screen].height); XFree(xsi); - return size; + return rect; } -int OS_X11::get_screen_dpi(int p_screen) const { - if (p_screen == -1) { - p_screen = get_current_screen(); +int DisplayServerX11::screen_get_dpi(int p_screen) const { + + _THREAD_SAFE_METHOD_ + + if (p_screen == SCREEN_OF_MAIN_WINDOW) { + p_screen = window_get_current_screen(); } //invalid screen? ERR_FAIL_INDEX_V(p_screen, get_screen_count(), 0); //Get physical monitor Dimensions through XRandR and calculate dpi - Size2 sc = get_screen_size(p_screen); + Size2i sc = screen_get_size(p_screen); if (xrandr_ext_ok) { int count = 0; if (xrr_get_monitors) { - xrr_monitor_info *monitors = xrr_get_monitors(x11_display, x11_window, true, &count); + xrr_monitor_info *monitors = xrr_get_monitors(x11_display, windows[MAIN_WINDOW_ID].x11_window, true, &count); if (p_screen < count) { double xdpi = sc.width / (double)monitors[p_screen].mwidth * 25.4; double ydpi = sc.height / (double)monitors[p_screen].mheight * 25.4; @@ -1279,18 +651,249 @@ int OS_X11::get_screen_dpi(int p_screen) const { //could not get dpi return 96; } +bool DisplayServerX11::screen_is_touchscreen(int p_screen) const { + + _THREAD_SAFE_METHOD_ + +#ifndef _MSC_VER +#warning Need to get from proper window +#endif + + return DisplayServer::screen_is_touchscreen(p_screen); +} + +Vector<DisplayServer::WindowID> DisplayServerX11::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 DisplayServerX11::create_sub_window(WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect) { + + _THREAD_SAFE_METHOD_ + + WindowID id = _create_window(p_mode, p_flags, p_rect); + for (int i = 0; i < WINDOW_FLAG_MAX; i++) { + if (p_flags & (1 << i)) { + window_set_flag(WindowFlags(i), true, id); + } + } + + return id; +} + +void DisplayServerX11::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"); //ma + + 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) { + window_set_transient(p_id, INVALID_WINDOW_ID); + } + +#ifdef VULKAN_ENABLED + if (rendering_driver == "vulkan") { + context_vulkan->window_destroy(p_id); + } +#endif + XUnmapWindow(x11_display, wd.x11_window); + XDestroyWindow(x11_display, wd.x11_window); + if (wd.xic) { + XDestroyIC(wd.xic); + } + + windows.erase(p_id); +} + +void DisplayServerX11::window_attach_instance_id(ObjectID p_instance, WindowID p_window) { + + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; + + wd.instance_id = p_instance; +} + +ObjectID DisplayServerX11::window_get_attached_instance_id(WindowID p_window) const { + + ERR_FAIL_COND_V(!windows.has(p_window), ObjectID()); + const WindowData &wd = windows[p_window]; + return wd.instance_id; +} + +DisplayServerX11::WindowID DisplayServerX11::get_window_at_screen_position(const Point2i &p_position) const { + + return INVALID_WINDOW_ID; +} + +void DisplayServerX11::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]; + + XStoreName(x11_display, wd.x11_window, p_title.utf8().get_data()); + + Atom _net_wm_name = XInternAtom(x11_display, "_NET_WM_NAME", false); + Atom utf8_string = XInternAtom(x11_display, "UTF8_STRING", false); + XChangeProperty(x11_display, wd.x11_window, _net_wm_name, utf8_string, 8, PropModeReplace, (unsigned char *)p_title.utf8().get_data(), p_title.utf8().length()); +} + +void DisplayServerX11::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 DisplayServerX11::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 DisplayServerX11::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 DisplayServerX11::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 DisplayServerX11::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 DisplayServerX11::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]; -Point2 OS_X11::get_window_position() const { int x, y; Window child; - XTranslateCoordinates(x11_display, x11_window, DefaultRootWindow(x11_display), 0, 0, &x, &y, &child); - return Point2i(x, y); + XTranslateCoordinates(x11_display, wd.x11_window, DefaultRootWindow(x11_display), 0, 0, &x, &y, &child); + + int count = get_screen_count(); + for (int i = 0; i < count; i++) { + Point2i pos = screen_get_position(i); + Size2i size = screen_get_size(i); + if ((x >= pos.x && x < pos.x + size.width) && (y >= pos.y && y < pos.y + size.height)) + return i; + } + return 0; +} +void DisplayServerX11::window_set_current_screen(int p_screen, WindowID p_window) { + + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; + + int count = get_screen_count(); + if (p_screen >= count) return; + + if (window_get_mode(p_window) == WINDOW_MODE_FULLSCREEN) { + Point2i position = screen_get_position(p_screen); + Size2i size = screen_get_size(p_screen); + + XMoveResizeWindow(x11_display, wd.x11_window, position.x, position.y, size.x, size.y); + } else { + if (p_screen != window_get_current_screen(p_window)) { + Point2i position = screen_get_position(p_screen); + XMoveWindow(x11_display, wd.x11_window, position.x, position.y); + } + } +} + +void DisplayServerX11::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); + + XSetTransientForHint(x11_display, wd_window.x11_window, None); + } 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); + + XSetTransientForHint(x11_display, wd_window.x11_window, wd_parent.x11_window); + } +} + +Point2i DisplayServerX11::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]; + + return wd.position; } -void OS_X11::set_window_position(const Point2 &p_position) { +void DisplayServerX11::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]; + int x = 0; int y = 0; - if (!get_borderless_window()) { + if (!window_get_flag(WINDOW_FLAG_BORDERLESS, p_window)) { //exclude window decorations XSync(x11_display, False); Atom prop = XInternAtom(x11_display, "_NET_FRAME_EXTENTS", True); @@ -1300,8 +903,8 @@ void OS_X11::set_window_position(const Point2 &p_position) { unsigned long len; unsigned long remaining; unsigned char *data = NULL; - if (XGetWindowProperty(x11_display, x11_window, prop, 0, 4, False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) { - if (format == 32 && len == 4) { + if (XGetWindowProperty(x11_display, wd.x11_window, prop, 0, 4, False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) { + if (format == 32 && len == 4 && data) { long *extents = (long *)data; x = extents[0]; y = extents[2]; @@ -1310,153 +913,151 @@ void OS_X11::set_window_position(const Point2 &p_position) { } } } - XMoveWindow(x11_display, x11_window, p_position.x - x, p_position.y - y); - update_real_mouse_position(); + XMoveWindow(x11_display, wd.x11_window, p_position.x - x, p_position.y - y); + _update_real_mouse_position(wd); } -Size2 OS_X11::get_window_size() const { - // Use current_videomode width and height instead of XGetWindowAttributes - // since right after a XResizeWindow the attributes may not be updated yet - return Size2i(current_videomode.width, current_videomode.height); -} +void DisplayServerX11::window_set_max_size(const Size2i p_size, WindowID p_window) { -Size2 OS_X11::get_real_window_size() const { - XWindowAttributes xwa; - XSync(x11_display, False); - XGetWindowAttributes(x11_display, x11_window, &xwa); - int w = xwa.width; - int h = xwa.height; - Atom prop = XInternAtom(x11_display, "_NET_FRAME_EXTENTS", True); - if (prop != None) { - Atom type; - int format; - unsigned long len; - unsigned long remaining; - unsigned char *data = NULL; - if (XGetWindowProperty(x11_display, x11_window, prop, 0, 4, False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) { - if (format == 32 && len == 4) { - long *extents = (long *)data; - w += extents[0] + extents[1]; // left, right - h += extents[2] + extents[3]; // top, bottom - } - XFree(data); - } - } - return Size2(w, h); -} + _THREAD_SAFE_METHOD_ -Size2 OS_X11::get_max_window_size() const { - return max_size; -} - -Size2 OS_X11::get_min_window_size() const { - return min_size; -} + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; -void OS_X11::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!"); + 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; } - min_size = p_size; + wd.max_size = p_size; - if (is_window_resizable()) { + if (!window_get_flag(WINDOW_FLAG_RESIZE_DISABLED, p_window)) { XSizeHints *xsh; xsh = XAllocSizeHints(); xsh->flags = 0L; - if (min_size != Size2()) { + if (wd.min_size != Size2i()) { xsh->flags |= PMinSize; - xsh->min_width = min_size.x; - xsh->min_height = min_size.y; + xsh->min_width = wd.min_size.x; + xsh->min_height = wd.min_size.y; } - if (max_size != Size2()) { + if (wd.max_size != Size2i()) { xsh->flags |= PMaxSize; - xsh->max_width = max_size.x; - xsh->max_height = max_size.y; + xsh->max_width = wd.max_size.x; + xsh->max_height = wd.max_size.y; } - XSetWMNormalHints(x11_display, x11_window, xsh); + XSetWMNormalHints(x11_display, wd.x11_window, xsh); XFree(xsh); XFlush(x11_display); } } +Size2i DisplayServerX11::window_get_max_size(WindowID p_window) const { -void OS_X11::set_max_window_size(const Size2 p_size) { + _THREAD_SAFE_METHOD_ - 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!"); + ERR_FAIL_COND_V(!windows.has(p_window), Size2i()); + const WindowData &wd = windows[p_window]; + + return wd.max_size; +} + +void DisplayServerX11::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; } - max_size = p_size; + wd.min_size = p_size; - if (is_window_resizable()) { + if (!window_get_flag(WINDOW_FLAG_RESIZE_DISABLED, p_window)) { XSizeHints *xsh; xsh = XAllocSizeHints(); xsh->flags = 0L; - if (min_size != Size2()) { + if (wd.min_size != Size2i()) { xsh->flags |= PMinSize; - xsh->min_width = min_size.x; - xsh->min_height = min_size.y; + xsh->min_width = wd.min_size.x; + xsh->min_height = wd.min_size.y; } - if (max_size != Size2()) { + if (wd.max_size != Size2i()) { xsh->flags |= PMaxSize; - xsh->max_width = max_size.x; - xsh->max_height = max_size.y; + xsh->max_width = wd.max_size.x; + xsh->max_height = wd.max_size.y; } - XSetWMNormalHints(x11_display, x11_window, xsh); + XSetWMNormalHints(x11_display, wd.x11_window, xsh); XFree(xsh); XFlush(x11_display); } } +Size2i DisplayServerX11::window_get_min_size(WindowID p_window) const { + + _THREAD_SAFE_METHOD_ -void OS_X11::set_window_size(const Size2 p_size) { + ERR_FAIL_COND_V(!windows.has(p_window), Size2i()); + const WindowData &wd = windows[p_window]; + + return wd.min_size; +} - if (current_videomode.width == p_size.width && current_videomode.height == p_size.height) +void DisplayServerX11::window_set_size(const Size2i p_size, WindowID p_window) { + + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + + Size2i size = p_size; + size.x = MAX(1, size.x); + size.y = MAX(1, size.y); + + WindowData &wd = windows[p_window]; + + if (wd.size.width == size.width && wd.size.height == size.height) return; XWindowAttributes xwa; XSync(x11_display, False); - XGetWindowAttributes(x11_display, x11_window, &xwa); + XGetWindowAttributes(x11_display, wd.x11_window, &xwa); int old_w = xwa.width; int old_h = xwa.height; // If window resizable is disabled we need to update the attributes first XSizeHints *xsh; xsh = XAllocSizeHints(); - if (!is_window_resizable()) { + if (!window_get_flag(WINDOW_FLAG_RESIZE_DISABLED, p_window)) { xsh->flags = PMinSize | PMaxSize; - xsh->min_width = p_size.x; - xsh->max_width = p_size.x; - xsh->min_height = p_size.y; - xsh->max_height = p_size.y; + xsh->min_width = size.x; + xsh->max_width = size.x; + xsh->min_height = size.y; + xsh->max_height = size.y; } else { xsh->flags = 0L; - if (min_size != Size2()) { + if (wd.min_size != Size2i()) { xsh->flags |= PMinSize; - xsh->min_width = min_size.x; - xsh->min_height = min_size.y; + xsh->min_width = wd.min_size.x; + xsh->min_height = wd.min_size.y; } - if (max_size != Size2()) { + if (wd.max_size != Size2i()) { xsh->flags |= PMaxSize; - xsh->max_width = max_size.x; - xsh->max_height = max_size.y; + xsh->max_width = wd.max_size.x; + xsh->max_height = wd.max_size.y; } } - XSetWMNormalHints(x11_display, x11_window, xsh); + XSetWMNormalHints(x11_display, wd.x11_window, xsh); XFree(xsh); // Resize the window - XResizeWindow(x11_display, x11_window, p_size.x, p_size.y); + XResizeWindow(x11_display, wd.x11_window, size.x, size.y); // Update our videomode width and height - current_videomode.width = p_size.x; - current_videomode.height = p_size.y; + wd.size = size; for (int timeout = 0; timeout < 50; ++timeout) { XSync(x11_display, False); - XGetWindowAttributes(x11_display, x11_window, &xwa); + XGetWindowAttributes(x11_display, wd.x11_window, &xwa); if (old_w != xwa.width || old_h != xwa.height) break; @@ -1464,105 +1065,53 @@ void OS_X11::set_window_size(const Size2 p_size) { usleep(10000); } } +Size2i DisplayServerX11::window_get_size(WindowID p_window) const { -void OS_X11::set_window_fullscreen(bool p_enabled) { - - if (current_videomode.fullscreen == p_enabled) - return; + _THREAD_SAFE_METHOD_ - if (layered_window) - set_window_per_pixel_transparency_enabled(false); - - if (p_enabled && current_videomode.always_on_top) { - // Fullscreen + Always-on-top requires a maximized window on some window managers (Metacity) - set_window_maximized(true); - } - set_wm_fullscreen(p_enabled); - if (!p_enabled && current_videomode.always_on_top) { - // Restore - set_window_maximized(false); - } - if (!p_enabled) { - set_window_position(last_position_before_fs); - } else { - last_position_before_fs = get_window_position(); - } - current_videomode.fullscreen = p_enabled; + ERR_FAIL_COND_V(!windows.has(p_window), Size2i()); + const WindowData &wd = windows[p_window]; + return wd.size; } +Size2i DisplayServerX11::window_get_real_size(WindowID p_window) const { -bool OS_X11::is_window_fullscreen() const { - return current_videomode.fullscreen; -} + _THREAD_SAFE_METHOD_ -void OS_X11::set_window_resizable(bool p_enabled) { + ERR_FAIL_COND_V(!windows.has(p_window), Size2i()); + const WindowData &wd = windows[p_window]; - XSizeHints *xsh; - xsh = XAllocSizeHints(); - if (!p_enabled) { - Size2 size = get_window_size(); - - xsh->flags = PMinSize | PMaxSize; - xsh->min_width = size.x; - xsh->max_width = size.x; - xsh->min_height = size.y; - xsh->max_height = size.y; - } else { - xsh->flags = 0L; - if (min_size != Size2()) { - xsh->flags |= PMinSize; - xsh->min_width = min_size.x; - xsh->min_height = min_size.y; - } - if (max_size != Size2()) { - xsh->flags |= PMaxSize; - xsh->max_width = max_size.x; - xsh->max_height = max_size.y; + XWindowAttributes xwa; + XSync(x11_display, False); + XGetWindowAttributes(x11_display, wd.x11_window, &xwa); + int w = xwa.width; + int h = xwa.height; + Atom prop = XInternAtom(x11_display, "_NET_FRAME_EXTENTS", True); + if (prop != None) { + Atom type; + int format; + unsigned long len; + unsigned long remaining; + unsigned char *data = NULL; + if (XGetWindowProperty(x11_display, wd.x11_window, prop, 0, 4, False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) { + if (format == 32 && len == 4 && data) { + long *extents = (long *)data; + w += extents[0] + extents[1]; // left, right + h += extents[2] + extents[3]; // top, bottom + } + XFree(data); } } - - XSetWMNormalHints(x11_display, x11_window, xsh); - XFree(xsh); - - current_videomode.resizable = p_enabled; - - XFlush(x11_display); -} - -bool OS_X11::is_window_resizable() const { - return current_videomode.resizable; + return Size2i(w, h); } -void OS_X11::set_window_minimized(bool p_enabled) { - // Using ICCCM -- Inter-Client Communication Conventions Manual - XEvent xev; - Atom wm_change = XInternAtom(x11_display, "WM_CHANGE_STATE", False); - - memset(&xev, 0, sizeof(xev)); - xev.type = ClientMessage; - xev.xclient.window = x11_window; - xev.xclient.message_type = wm_change; - xev.xclient.format = 32; - xev.xclient.data.l[0] = p_enabled ? WM_IconicState : WM_NormalState; - - XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); - - Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False); - Atom wm_hidden = XInternAtom(x11_display, "_NET_WM_STATE_HIDDEN", False); +bool DisplayServerX11::window_is_maximize_allowed(WindowID p_window) const { - memset(&xev, 0, sizeof(xev)); - xev.type = ClientMessage; - xev.xclient.window = x11_window; - xev.xclient.message_type = wm_state; - xev.xclient.format = 32; - xev.xclient.data.l[0] = _NET_WM_STATE_ADD; - xev.xclient.data.l[1] = wm_hidden; + _THREAD_SAFE_METHOD_ - XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); -} + ERR_FAIL_COND_V(!windows.has(p_window), false); + const WindowData &wd = windows[p_window]; -bool OS_X11::is_window_minimized() const { - // Using ICCCM -- Inter-Client Communication Conventions Manual - Atom property = XInternAtom(x11_display, "WM_STATE", True); + Atom property = XInternAtom(x11_display, "_NET_WM_ALLOWED_ACTIONS", False); Atom type; int format; unsigned long len; @@ -1571,29 +1120,44 @@ bool OS_X11::is_window_minimized() const { int result = XGetWindowProperty( x11_display, - x11_window, + wd.x11_window, property, 0, - 32, + 1024, False, - AnyPropertyType, + XA_ATOM, &type, &format, &len, &remaining, &data); - if (result == Success) { - long *state = (long *)data; - if (state[0] == WM_IconicState) - return true; + if (result == Success && data) { + Atom *atoms = (Atom *)data; + Atom wm_act_max_horz = XInternAtom(x11_display, "_NET_WM_ACTION_MAXIMIZE_HORZ", False); + Atom wm_act_max_vert = XInternAtom(x11_display, "_NET_WM_ACTION_MAXIMIZE_VERT", False); + bool found_wm_act_max_horz = false; + bool found_wm_act_max_vert = false; + + for (uint64_t i = 0; i < len; i++) { + if (atoms[i] == wm_act_max_horz) + found_wm_act_max_horz = true; + if (atoms[i] == wm_act_max_vert) + found_wm_act_max_vert = true; + + if (found_wm_act_max_horz || found_wm_act_max_vert) + return true; + } + XFree(atoms); } + return false; } -void OS_X11::set_window_maximized(bool p_enabled) { - if (is_window_maximized() == p_enabled) - return; +void DisplayServerX11::_set_wm_maximized(WindowID p_window, bool p_enabled) { + + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; // Using EWMH -- Extended Window Manager Hints XEvent xev; @@ -1603,7 +1167,7 @@ void OS_X11::set_window_maximized(bool p_enabled) { memset(&xev, 0, sizeof(xev)); xev.type = ClientMessage; - xev.xclient.window = x11_window; + xev.xclient.window = wd.x11_window; xev.xclient.message_type = wm_state; xev.xclient.format = 32; xev.xclient.data.l[0] = p_enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; @@ -1612,178 +1176,454 @@ void OS_X11::set_window_maximized(bool p_enabled) { XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); - if (p_enabled && is_window_maximize_allowed()) { + if (p_enabled && window_is_maximize_allowed(p_window)) { // Wait for effective resizing (so the GLX context is too). // Give up after 0.5s, it's not going to happen on this WM. // https://github.com/godotengine/godot/issues/19978 - for (int attempt = 0; !is_window_maximized() && attempt < 50; attempt++) { + for (int attempt = 0; window_get_mode(p_window) != WINDOW_MODE_MAXIMIZED && attempt < 50; attempt++) { usleep(10000); } } - - maximized = p_enabled; } -bool OS_X11::is_window_maximize_allowed() { - Atom property = XInternAtom(x11_display, "_NET_WM_ALLOWED_ACTIONS", False); - Atom type; - int format; - unsigned long len; - unsigned long remaining; - unsigned char *data = NULL; +void DisplayServerX11::_set_wm_fullscreen(WindowID p_window, bool p_enabled) { - int result = XGetWindowProperty( - x11_display, - x11_window, - property, - 0, - 1024, - False, - XA_ATOM, - &type, - &format, - &len, - &remaining, - &data); + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; - if (result == Success) { - Atom *atoms = (Atom *)data; - Atom wm_act_max_horz = XInternAtom(x11_display, "_NET_WM_ACTION_MAXIMIZE_HORZ", False); - Atom wm_act_max_vert = XInternAtom(x11_display, "_NET_WM_ACTION_MAXIMIZE_VERT", False); - bool found_wm_act_max_horz = false; - bool found_wm_act_max_vert = false; + if (p_enabled && !window_get_flag(WINDOW_FLAG_BORDERLESS, p_window)) { + // remove decorations if the window is not already borderless + Hints hints; + Atom property; + hints.flags = 2; + hints.decorations = 0; + property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True); + XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5); + } - for (uint64_t i = 0; i < len; i++) { - if (atoms[i] == wm_act_max_horz) - found_wm_act_max_horz = true; - if (atoms[i] == wm_act_max_vert) - found_wm_act_max_vert = true; + if (p_enabled && window_get_flag(WINDOW_FLAG_RESIZE_DISABLED, p_window)) { + // Set the window as resizable to prevent window managers to ignore the fullscreen state flag. + XSizeHints *xsh; - if (found_wm_act_max_horz || found_wm_act_max_vert) - return true; - } - XFree(atoms); + xsh = XAllocSizeHints(); + xsh->flags = 0L; + XSetWMNormalHints(x11_display, wd.x11_window, xsh); + XFree(xsh); } - return false; -} - -bool OS_X11::is_window_maximized() const { // Using EWMH -- Extended Window Manager Hints - Atom property = XInternAtom(x11_display, "_NET_WM_STATE", False); - Atom type; - int format; - unsigned long len; - unsigned long remaining; - unsigned char *data = NULL; - bool retval = false; + XEvent xev; + Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False); + Atom wm_fullscreen = XInternAtom(x11_display, "_NET_WM_STATE_FULLSCREEN", False); - int result = XGetWindowProperty( - x11_display, - x11_window, - property, - 0, - 1024, - False, - XA_ATOM, - &type, - &format, - &len, - &remaining, - &data); + memset(&xev, 0, sizeof(xev)); + xev.type = ClientMessage; + xev.xclient.window = wd.x11_window; + xev.xclient.message_type = wm_state; + xev.xclient.format = 32; + xev.xclient.data.l[0] = p_enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; + xev.xclient.data.l[1] = wm_fullscreen; + xev.xclient.data.l[2] = 0; - if (result == Success) { - Atom *atoms = (Atom *)data; - Atom wm_max_horz = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_HORZ", False); - Atom wm_max_vert = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_VERT", False); - bool found_wm_max_horz = false; - bool found_wm_max_vert = false; + XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); - for (uint64_t i = 0; i < len; i++) { - if (atoms[i] == wm_max_horz) - found_wm_max_horz = true; - if (atoms[i] == wm_max_vert) - found_wm_max_vert = true; + // set bypass compositor hint + Atom bypass_compositor = XInternAtom(x11_display, "_NET_WM_BYPASS_COMPOSITOR", False); + unsigned long compositing_disable_on = p_enabled ? 1 : 0; + XChangeProperty(x11_display, wd.x11_window, bypass_compositor, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&compositing_disable_on, 1); - if (found_wm_max_horz && found_wm_max_vert) { - retval = true; - break; + XFlush(x11_display); + + if (!p_enabled) { + // Reset the non-resizable flags if we un-set these before. + Size2i size = window_get_size(p_window); + XSizeHints *xsh; + xsh = XAllocSizeHints(); + if (window_get_flag(WINDOW_FLAG_RESIZE_DISABLED, p_window)) { + xsh->flags = PMinSize | PMaxSize; + xsh->min_width = size.x; + xsh->max_width = size.x; + xsh->min_height = size.y; + xsh->max_height = size.y; + } else { + xsh->flags = 0L; + if (wd.min_size != Size2i()) { + xsh->flags |= PMinSize; + xsh->min_width = wd.min_size.x; + xsh->min_height = wd.min_size.y; + } + if (wd.max_size != Size2i()) { + xsh->flags |= PMaxSize; + xsh->max_width = wd.max_size.x; + xsh->max_height = wd.max_size.y; } } - } + XSetWMNormalHints(x11_display, wd.x11_window, xsh); + XFree(xsh); - XFree(data); - return retval; + // put back or remove decorations according to the last set borderless state + Hints hints; + Atom property; + hints.flags = 2; + hints.decorations = window_get_flag(WINDOW_FLAG_BORDERLESS, p_window) ? 0 : 1; + property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True); + XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5); + } } -void OS_X11::set_window_always_on_top(bool p_enabled) { - if (is_window_always_on_top() == p_enabled) - return; +void DisplayServerX11::window_set_mode(WindowMode p_mode, WindowID p_window) { - if (p_enabled && current_videomode.fullscreen) { - // Fullscreen + Always-on-top requires a maximized window on some window managers (Metacity) - set_window_maximized(true); - } - set_wm_above(p_enabled); - if (!p_enabled && !current_videomode.fullscreen) { - // Restore - set_window_maximized(false); + _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 } + //remove all "extra" modes - current_videomode.always_on_top = p_enabled; -} + switch (old_mode) { + case WINDOW_MODE_WINDOWED: { + //do nothing + } break; + case WINDOW_MODE_MINIMIZED: { + //Un-Minimize + // Using ICCCM -- Inter-Client Communication Conventions Manual + XEvent xev; + Atom wm_change = XInternAtom(x11_display, "WM_CHANGE_STATE", False); + + memset(&xev, 0, sizeof(xev)); + xev.type = ClientMessage; + xev.xclient.window = wd.x11_window; + xev.xclient.message_type = wm_change; + xev.xclient.format = 32; + xev.xclient.data.l[0] = WM_NormalState; + + XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); + + Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False); + Atom wm_hidden = XInternAtom(x11_display, "_NET_WM_STATE_HIDDEN", False); + + memset(&xev, 0, sizeof(xev)); + xev.type = ClientMessage; + xev.xclient.window = wd.x11_window; + xev.xclient.message_type = wm_state; + xev.xclient.format = 32; + xev.xclient.data.l[0] = _NET_WM_STATE_ADD; + xev.xclient.data.l[1] = wm_hidden; + + XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); + } break; + case WINDOW_MODE_FULLSCREEN: { + //Remove full-screen + _set_wm_fullscreen(p_window, false); -bool OS_X11::is_window_always_on_top() const { - return current_videomode.always_on_top; -} + //un-maximize required for always on top + bool on_top = window_get_flag(WINDOW_FLAG_ALWAYS_ON_TOP, p_window); -bool OS_X11::is_window_focused() const { - return window_focused; -} + wd.fullscreen = false; -void OS_X11::set_borderless_window(bool p_borderless) { + window_set_position(wd.last_position_before_fs, p_window); - if (get_borderless_window() == p_borderless) - return; + if (on_top) { + _set_wm_maximized(p_window, false); + } - if (!p_borderless && layered_window) - set_window_per_pixel_transparency_enabled(false); + } break; + case WINDOW_MODE_MAXIMIZED: { - current_videomode.borderless_window = p_borderless; + _set_wm_maximized(p_window, false); + } break; + } - Hints hints; - Atom property; - hints.flags = 2; - hints.decorations = current_videomode.borderless_window ? 0 : 1; - property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True); - XChangeProperty(x11_display, x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5); + switch (p_mode) { + case WINDOW_MODE_WINDOWED: { + //do nothing + } break; + case WINDOW_MODE_MINIMIZED: { + // Using ICCCM -- Inter-Client Communication Conventions Manual + XEvent xev; + Atom wm_change = XInternAtom(x11_display, "WM_CHANGE_STATE", False); + + memset(&xev, 0, sizeof(xev)); + xev.type = ClientMessage; + xev.xclient.window = wd.x11_window; + xev.xclient.message_type = wm_change; + xev.xclient.format = 32; + xev.xclient.data.l[0] = WM_IconicState; + + XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); + + Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False); + Atom wm_hidden = XInternAtom(x11_display, "_NET_WM_STATE_HIDDEN", False); + + memset(&xev, 0, sizeof(xev)); + xev.type = ClientMessage; + xev.xclient.window = wd.x11_window; + xev.xclient.message_type = wm_state; + xev.xclient.format = 32; + xev.xclient.data.l[0] = _NET_WM_STATE_ADD; + xev.xclient.data.l[1] = wm_hidden; + + XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); + } break; + case WINDOW_MODE_FULLSCREEN: { + wd.last_position_before_fs = wd.position; + if (window_get_flag(WINDOW_FLAG_ALWAYS_ON_TOP, p_window)) { + _set_wm_maximized(p_window, true); + } + _set_wm_fullscreen(p_window, true); + wd.fullscreen = true; + } break; + case WINDOW_MODE_MAXIMIZED: { + + _set_wm_maximized(p_window, true); - // Preserve window size - set_window_size(Size2(current_videomode.width, current_videomode.height)); + } break; + } } -bool OS_X11::get_borderless_window() { +DisplayServer::WindowMode DisplayServerX11::window_get_mode(WindowID p_window) const { - bool borderless = current_videomode.borderless_window; - Atom prop = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True); - if (prop != None) { + _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; + } + { //test maximized + // Using EWMH -- Extended Window Manager Hints + Atom property = XInternAtom(x11_display, "_NET_WM_STATE", False); Atom type; int format; unsigned long len; unsigned long remaining; unsigned char *data = NULL; - if (XGetWindowProperty(x11_display, x11_window, prop, 0, sizeof(Hints), False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) { - if (data && (format == 32) && (len >= 5)) { - borderless = !((Hints *)data)->decorations; + bool retval = false; + + int result = XGetWindowProperty( + x11_display, + wd.x11_window, + property, + 0, + 1024, + False, + XA_ATOM, + &type, + &format, + &len, + &remaining, + &data); + + if (result == Success && data) { + Atom *atoms = (Atom *)data; + Atom wm_max_horz = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_HORZ", False); + Atom wm_max_vert = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_VERT", False); + bool found_wm_max_horz = false; + bool found_wm_max_vert = false; + + for (uint64_t i = 0; i < len; i++) { + if (atoms[i] == wm_max_horz) + found_wm_max_horz = true; + if (atoms[i] == wm_max_vert) + found_wm_max_vert = true; + + if (found_wm_max_horz && found_wm_max_vert) { + retval = true; + break; + } } + XFree(data); } + + if (retval) { + return WINDOW_MODE_MAXIMIZED; + } + } + + { // test minimzed + // Using ICCCM -- Inter-Client Communication Conventions Manual + Atom property = XInternAtom(x11_display, "WM_STATE", True); + Atom type; + int format; + unsigned long len; + unsigned long remaining; + unsigned char *data = NULL; + + int result = XGetWindowProperty( + x11_display, + wd.x11_window, + property, + 0, + 32, + False, + AnyPropertyType, + &type, + &format, + &len, + &remaining, + &data); + + if (result == Success && data) { + long *state = (long *)data; + if (state[0] == WM_IconicState) + return WINDOW_MODE_MINIMIZED; + } + } + + // all other discarded, return windowed. + + return WINDOW_MODE_WINDOWED; +} + +void DisplayServerX11::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: { + XSizeHints *xsh; + xsh = XAllocSizeHints(); + if (p_enabled) { + Size2i size = window_get_size(p_window); + + xsh->flags = PMinSize | PMaxSize; + xsh->min_width = size.x; + xsh->max_width = size.x; + xsh->min_height = size.y; + xsh->max_height = size.y; + } else { + xsh->flags = 0L; + if (wd.min_size != Size2i()) { + xsh->flags |= PMinSize; + xsh->min_width = wd.min_size.x; + xsh->min_height = wd.min_size.y; + } + if (wd.max_size != Size2i()) { + xsh->flags |= PMaxSize; + xsh->max_width = wd.max_size.x; + xsh->max_height = wd.max_size.y; + } + } + + XSetWMNormalHints(x11_display, wd.x11_window, xsh); + XFree(xsh); + + wd.resize_disabled = p_enabled; + + XFlush(x11_display); + + } break; + case WINDOW_FLAG_BORDERLESS: { + + Hints hints; + Atom property; + hints.flags = 2; + hints.decorations = p_enabled ? 0 : 1; + property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True); + XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5); + + // Preserve window size + window_set_size(window_get_size(p_window), p_window); + + wd.borderless = p_enabled; + } break; + case WINDOW_FLAG_ALWAYS_ON_TOP: { + + ERR_FAIL_COND_MSG(wd.transient_parent != INVALID_WINDOW_ID, "Can't make a window transient if the 'on top' flag is active."); + if (p_enabled && wd.fullscreen) { + _set_wm_maximized(p_window, true); + } + + Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False); + Atom wm_above = XInternAtom(x11_display, "_NET_WM_STATE_ABOVE", False); + + XClientMessageEvent xev; + memset(&xev, 0, sizeof(xev)); + xev.type = ClientMessage; + xev.window = wd.x11_window; + xev.message_type = wm_state; + xev.format = 32; + xev.data.l[0] = p_enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; + xev.data.l[1] = wm_above; + xev.data.l[3] = 1; + XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent *)&xev); + + if (!p_enabled && !wd.fullscreen) { + _set_wm_maximized(p_window, false); + } + wd.on_top = p_enabled; + + } break; + case WINDOW_FLAG_TRANSPARENT: { + //todo reimplement + } break; + default: { + } } - return borderless; } +bool DisplayServerX11::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: { + + bool borderless = wd.borderless; + Atom prop = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True); + if (prop != None) { + + Atom type; + int format; + unsigned long len; + unsigned long remaining; + unsigned char *data = NULL; + if (XGetWindowProperty(x11_display, wd.x11_window, prop, 0, sizeof(Hints), False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) { + if (data && (format == 32) && (len >= 5)) { + borderless = !((Hints *)data)->decorations; + } + if (data) { + XFree(data); + } + } + } + return borderless; + } break; + case WINDOW_FLAG_ALWAYS_ON_TOP: { -void OS_X11::request_attention() { + return wd.on_top; + } break; + case WINDOW_FLAG_TRANSPARENT: { + //todo reimplement + } break; + default: { + } + } + + return false; +} + +void DisplayServerX11::window_request_attention(WindowID p_window) { + + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; // Using EWMH -- Extended Window Manager Hints // // Sets the _NET_WM_STATE_DEMANDS_ATTENTION atom for WM_STATE @@ -1795,7 +1635,7 @@ void OS_X11::request_attention() { memset(&xev, 0, sizeof(xev)); xev.type = ClientMessage; - xev.xclient.window = x11_window; + xev.xclient.window = wd.x11_window; xev.xclient.message_type = wm_state; xev.xclient.format = 32; xev.xclient.data.l[0] = _NET_WM_STATE_ADD; @@ -1805,7 +1645,314 @@ void OS_X11::request_attention() { XFlush(x11_display); } -void OS_X11::get_key_modifier_state(unsigned int p_x11_state, Ref<InputEventWithModifiers> state) { +void DisplayServerX11::window_move_to_foreground(WindowID p_window) { + + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; + + XEvent xev; + Atom net_active_window = XInternAtom(x11_display, "_NET_ACTIVE_WINDOW", False); + + memset(&xev, 0, sizeof(xev)); + xev.type = ClientMessage; + xev.xclient.window = wd.x11_window; + xev.xclient.message_type = net_active_window; + xev.xclient.format = 32; + xev.xclient.data.l[0] = 1; + xev.xclient.data.l[1] = CurrentTime; + + XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); + XFlush(x11_display); +} + +bool DisplayServerX11::window_can_draw(WindowID p_window) const { + + //this seems to be all that is provided by X11 + return window_get_mode(p_window) != WINDOW_MODE_MINIMIZED; +} +bool DisplayServerX11::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 DisplayServerX11::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 (!wd.xic) + return; + + if (p_active) { + XSetICFocus(wd.xic); + window_set_ime_position(wd.im_position, p_window); + } else { + XUnsetICFocus(wd.xic); + } +} +void DisplayServerX11::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; + + if (!wd.xic) + return; + + ::XPoint spot; + spot.x = short(p_pos.x); + spot.y = short(p_pos.y); + XVaNestedList preedit_attr = XVaCreateNestedList(0, XNSpotLocation, &spot, NULL); + XSetICValues(wd.xic, XNPreeditAttributes, preedit_attr, NULL); + XFree(preedit_attr); +} + +void DisplayServerX11::cursor_set_shape(CursorShape p_shape) { + + _THREAD_SAFE_METHOD_ + + ERR_FAIL_INDEX(p_shape, CURSOR_MAX); + + if (p_shape == current_cursor) { + return; + } + + if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) { + if (cursors[p_shape] != None) { + for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { + XDefineCursor(x11_display, E->get().x11_window, cursors[p_shape]); + } + } else if (cursors[CURSOR_ARROW] != None) { + for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { + XDefineCursor(x11_display, E->get().x11_window, cursors[CURSOR_ARROW]); + } + } + } + + current_cursor = p_shape; +} +DisplayServerX11::CursorShape DisplayServerX11::cursor_get_shape() const { + return current_cursor; +} +void DisplayServerX11::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; + Size2i texture_size; + Rect2i 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()); + + // Create the cursor structure + XcursorImage *cursor_image = XcursorImageCreate(texture_size.width, texture_size.height); + XcursorUInt image_size = texture_size.width * texture_size.height; + XcursorDim size = sizeof(XcursorPixel) * image_size; + + cursor_image->version = 1; + cursor_image->size = size; + cursor_image->xhot = p_hotspot.x; + cursor_image->yhot = p_hotspot.y; + + // allocate memory to contain the whole file + cursor_image->pixels = (XcursorPixel *)memalloc(size); + + for (XcursorPixel index = 0; index < image_size; index++) { + int row_index = floor(index / texture_size.width) + atlas_rect.position.y; + int column_index = (index % 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); + } + + *(cursor_image->pixels + index) = image->get_pixel(column_index, row_index).to_argb32(); + } + + ERR_FAIL_COND(cursor_image->pixels == NULL); + + // Save it for a further usage + cursors[p_shape] = XcursorImageLoadCursor(x11_display, cursor_image); + + Vector<Variant> params; + params.push_back(p_cursor); + params.push_back(p_hotspot); + cursors_cache.insert(p_shape, params); + + if (p_shape == current_cursor) { + if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) { + for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { + XDefineCursor(x11_display, E->get().x11_window, cursors[p_shape]); + } + } + } + + memfree(cursor_image->pixels); + XcursorImageDestroy(cursor_image); + } else { + // Reset to default system cursor + if (img[p_shape]) { + cursors[p_shape] = XcursorImageLoadCursor(x11_display, img[p_shape]); + } + + CursorShape c = current_cursor; + current_cursor = CURSOR_MAX; + cursor_set_shape(c); + + cursors_cache.erase(p_shape); + } +} + +DisplayServerX11::LatinKeyboardVariant DisplayServerX11::get_latin_keyboard_variant() const { + _THREAD_SAFE_METHOD_ + + XkbDescRec *xkbdesc = XkbAllocKeyboard(); + ERR_FAIL_COND_V(!xkbdesc, LATIN_KEYBOARD_QWERTY); + + XkbGetNames(x11_display, XkbSymbolsNameMask, xkbdesc); + ERR_FAIL_COND_V(!xkbdesc->names, LATIN_KEYBOARD_QWERTY); + ERR_FAIL_COND_V(!xkbdesc->names->symbols, LATIN_KEYBOARD_QWERTY); + + char *layout = XGetAtomName(x11_display, xkbdesc->names->symbols); + ERR_FAIL_COND_V(!layout, LATIN_KEYBOARD_QWERTY); + + Vector<String> info = String(layout).split("+"); + ERR_FAIL_INDEX_V(1, info.size(), LATIN_KEYBOARD_QWERTY); + + if (info[1].find("colemak") != -1) { + return LATIN_KEYBOARD_COLEMAK; + } else if (info[1].find("qwertz") != -1) { + return LATIN_KEYBOARD_QWERTZ; + } else if (info[1].find("azerty") != -1) { + return LATIN_KEYBOARD_AZERTY; + } else if (info[1].find("qzerty") != -1) { + return LATIN_KEYBOARD_QZERTY; + } else if (info[1].find("dvorak") != -1) { + return LATIN_KEYBOARD_DVORAK; + } else if (info[1].find("neo") != -1) { + return LATIN_KEYBOARD_NEO; + } + + return LATIN_KEYBOARD_QWERTY; +} + +DisplayServerX11::Property DisplayServerX11::_read_property(Display *p_display, Window p_window, Atom p_property) { + + Atom actual_type; + int actual_format; + unsigned long nitems; + unsigned long bytes_after; + unsigned char *ret = 0; + + int read_bytes = 1024; + + //Keep trying to read the property until there are no + //bytes unread. + do { + if (ret != 0) + XFree(ret); + + XGetWindowProperty(p_display, p_window, p_property, 0, read_bytes, False, AnyPropertyType, + &actual_type, &actual_format, &nitems, &bytes_after, + &ret); + + read_bytes *= 2; + + } while (bytes_after != 0); + + Property p = { ret, actual_format, (int)nitems, actual_type }; + + return p; +} + +static Atom pick_target_from_list(Display *p_display, Atom *p_list, int p_count) { + + static const char *target_type = "text/uri-list"; + + for (int i = 0; i < p_count; i++) { + + Atom atom = p_list[i]; + + if (atom != None && String(XGetAtomName(p_display, atom)) == target_type) + return atom; + } + return None; +} + +static Atom pick_target_from_atoms(Display *p_disp, Atom p_t1, Atom p_t2, Atom p_t3) { + + static const char *target_type = "text/uri-list"; + if (p_t1 != None && String(XGetAtomName(p_disp, p_t1)) == target_type) + return p_t1; + + if (p_t2 != None && String(XGetAtomName(p_disp, p_t2)) == target_type) + return p_t2; + + if (p_t3 != None && String(XGetAtomName(p_disp, p_t3)) == target_type) + return p_t3; + + return None; +} + +void DisplayServerX11::_get_key_modifier_state(unsigned int p_x11_state, Ref<InputEventWithModifiers> state) { state->set_shift((p_x11_state & ShiftMask)); state->set_control((p_x11_state & ControlMask)); @@ -1813,7 +1960,7 @@ void OS_X11::get_key_modifier_state(unsigned int p_x11_state, Ref<InputEventWith state->set_metakey((p_x11_state & Mod4Mask)); } -unsigned int OS_X11::get_mouse_button_state(unsigned int p_x11_button, int p_x11_type) { +unsigned int DisplayServerX11::_get_mouse_button_state(unsigned int p_x11_button, int p_x11_type) { unsigned int mask = 1 << (p_x11_button - 1); @@ -1826,8 +1973,9 @@ unsigned int OS_X11::get_mouse_button_state(unsigned int p_x11_button, int p_x11 return last_button_state; } -void OS_X11::handle_key_event(XKeyEvent *p_event, bool p_echo) { +void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, bool p_echo) { + WindowData wd = windows[p_window]; // X11 functions don't know what const is XKeyEvent *xkeyevent = p_event; @@ -1853,7 +2001,7 @@ void OS_X11::handle_key_event(XKeyEvent *p_event, bool p_echo) { KeySym keysym_keycode = 0; // keysym used to find a keycode KeySym keysym_unicode = 0; // keysym used to find unicode - // XLookupString returns keysyms usable as nice scancodes/ + // XLookupString returns keysyms usable as nice keycodes. char str[256 + 1]; XKeyEvent xkeyevent_no_mod = *xkeyevent; xkeyevent_no_mod.state &= ~ShiftMask; @@ -1869,18 +2017,18 @@ void OS_X11::handle_key_event(XKeyEvent *p_event, bool p_echo) { xmblen = 8; } - if (xkeyevent->type == KeyPress && xic) { + if (xkeyevent->type == KeyPress && wd.xic) { Status status; #ifdef X_HAVE_UTF8_STRING int utf8len = 8; char *utf8string = (char *)memalloc(sizeof(char) * utf8len); - int utf8bytes = Xutf8LookupString(xic, xkeyevent, utf8string, + int utf8bytes = Xutf8LookupString(wd.xic, xkeyevent, utf8string, utf8len - 1, &keysym_unicode, &status); if (status == XBufferOverflow) { utf8len = utf8bytes + 1; utf8string = (char *)memrealloc(utf8string, utf8len); - utf8bytes = Xutf8LookupString(xic, xkeyevent, utf8string, + utf8bytes = Xutf8LookupString(wd.xic, xkeyevent, utf8string, utf8len - 1, &keysym_unicode, &status); } utf8string[utf8bytes] = '\0'; @@ -1902,11 +2050,13 @@ void OS_X11::handle_key_event(XKeyEvent *p_event, bool p_echo) { continue; } - if (keycode == 0) + if (keycode == 0) { keycode = physical_keycode; + } - get_key_modifier_state(xkeyevent->state, k); + _get_key_modifier_state(xkeyevent->state, k); + k->set_window_id(p_window); k->set_unicode(tmp[i]); k->set_pressed(keypress); @@ -1924,7 +2074,7 @@ void OS_X11::handle_key_event(XKeyEvent *p_event, bool p_echo) { k->set_shift(true); } - input->accumulate_input_event(k); + InputFilter::get_singleton()->accumulate_input_event(k); } memfree(utf8string); return; @@ -1944,7 +2094,7 @@ void OS_X11::handle_key_event(XKeyEvent *p_event, bool p_echo) { #endif } - /* Phase 2, obtain a pigui keycode from the keysym */ + /* Phase 2, obtain a Godot keycode from the keysym */ // KeyMappingX11 just translated the X11 keysym to a PIGUI // keysym, so it works in all platforms the same. @@ -1970,11 +2120,13 @@ void OS_X11::handle_key_event(XKeyEvent *p_event, bool p_echo) { bool keypress = xkeyevent->type == KeyPress; - if (physical_keycode == 0 && keycode == 0 && unicode == 0) + if (physical_keycode == 0 && keycode == 0 && unicode == 0) { return; + } - if (keycode == 0) + if (keycode == 0) { keycode = physical_keycode; + } /* Phase 5, determine modifier mask */ @@ -1986,8 +2138,9 @@ void OS_X11::handle_key_event(XKeyEvent *p_event, bool p_echo) { Ref<InputEventKey> k; k.instance(); + k->set_window_id(p_window); - get_key_modifier_state(xkeyevent->state, k); + _get_key_modifier_state(xkeyevent->state, k); /* Phase 6, determine echo character */ @@ -2021,7 +2174,7 @@ void OS_X11::handle_key_event(XKeyEvent *p_event, bool p_echo) { if (rk == keysym_keycode) { XEvent event; XNextEvent(x11_display, &event); //erase next event - handle_key_event((XKeyEvent *)&event, true); + _handle_key_event(p_window, (XKeyEvent *)&event, true); return; //ignore current, echo next } } @@ -2064,104 +2217,133 @@ void OS_X11::handle_key_event(XKeyEvent *p_event, bool p_echo) { k->set_metakey(false); } - bool last_is_pressed = Input::get_singleton()->is_key_pressed(k->get_keycode()); + bool last_is_pressed = InputFilter::get_singleton()->is_key_pressed(k->get_keycode()); if (k->is_pressed()) { if (last_is_pressed) { k->set_echo(true); } } - //printf("key: %x\n",k->get_keycode()); - input->accumulate_input_event(k); + InputFilter::get_singleton()->accumulate_input_event(k); } -struct Property { - unsigned char *data; - int format, nitems; - Atom type; -}; - -static Property read_property(Display *p_display, Window p_window, Atom p_property) { - - Atom actual_type; - int actual_format; - unsigned long nitems; - unsigned long bytes_after; - unsigned char *ret = 0; - - int read_bytes = 1024; - - //Keep trying to read the property until there are no - //bytes unread. - do { - if (ret != 0) - XFree(ret); +void DisplayServerX11::_xim_destroy_callback(::XIM im, ::XPointer client_data, + ::XPointer call_data) { - XGetWindowProperty(p_display, p_window, p_property, 0, read_bytes, False, AnyPropertyType, - &actual_type, &actual_format, &nitems, &bytes_after, - &ret); + WARN_PRINT("Input method stopped"); + DisplayServerX11 *ds = reinterpret_cast<DisplayServerX11 *>(client_data); + ds->xim = NULL; - read_bytes *= 2; + for (Map<WindowID, WindowData>::Element *E = ds->windows.front(); E; E = E->next()) { + E->get().xic = NULL; + } +} - } while (bytes_after != 0); +void DisplayServerX11::_window_changed(XEvent *event) { - Property p = { ret, actual_format, (int)nitems, actual_type }; + WindowID window_id = MAIN_WINDOW_ID; - return p; -} + // Assign the event to the relevant window + for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { + if (event->xany.window == E->get().x11_window) { + window_id = E->key(); + break; + } + } -static Atom pick_target_from_list(Display *p_display, Atom *p_list, int p_count) { + Rect2i new_rect; - static const char *target_type = "text/uri-list"; + WindowData &wd = windows[window_id]; + if (wd.x11_window != event->xany.window) { // Check if the correct window, in case it was not main window or anything else + return; + } - for (int i = 0; i < p_count; i++) { + { + //the position in xconfigure is not useful here, obtain it manually + int x, y; + Window child; + XTranslateCoordinates(x11_display, wd.x11_window, DefaultRootWindow(x11_display), 0, 0, &x, &y, &child); + new_rect.position.x = x; + new_rect.position.y = y; - Atom atom = p_list[i]; + new_rect.size.width = event->xconfigure.width; + new_rect.size.height = event->xconfigure.height; + } - if (atom != None && String(XGetAtomName(p_display, atom)) == target_type) - return atom; + if (new_rect == Rect2i(wd.position, wd.size)) { + return; + } + if (wd.xic) { + // Not portable. + window_set_ime_position(Point2(0, 1)); } - return None; -} -static Atom pick_target_from_atoms(Display *p_disp, Atom p_t1, Atom p_t2, Atom p_t3) { + wd.position = new_rect.position; + wd.size = new_rect.size; - static const char *target_type = "text/uri-list"; - if (p_t1 != None && String(XGetAtomName(p_disp, p_t1)) == target_type) - return p_t1; +#if defined(VULKAN_ENABLED) + if (rendering_driver == "vulkan") { + context_vulkan->window_resize(window_id, wd.size.width, wd.size.height); + } +#endif - if (p_t2 != None && String(XGetAtomName(p_disp, p_t2)) == target_type) - return p_t2; + print_line("DisplayServer::_window_changed: " + itos(window_id) + " rect: " + new_rect); + if (!wd.rect_changed_callback.is_null()) { + Rect2i r = new_rect; - if (p_t3 != None && String(XGetAtomName(p_disp, p_t3)) == target_type) - return p_t3; + Variant rect = r; - return None; + Variant *rectp = ▭ + Variant ret; + Callable::CallError ce; + wd.rect_changed_callback.call((const Variant **)&rectp, 1, ret, ce); + } } -void OS_X11::_window_changed(XEvent *event) { +void DisplayServerX11::_dispatch_input_events(const Ref<InputEvent> &p_event) { + ((DisplayServerX11 *)(get_singleton()))->_dispatch_input_event(p_event); +} - if (xic) { - // Not portable. - set_ime_position(Point2(0, 1)); - } - if ((event->xconfigure.width == current_videomode.width) && - (event->xconfigure.height == current_videomode.height)) - return; +void DisplayServerX11::_dispatch_input_event(const Ref<InputEvent> &p_event) { - current_videomode.width = event->xconfigure.width; - current_videomode.height = event->xconfigure.height; + Variant ev = p_event; + Variant *evp = &ev; + Variant ret; + Callable::CallError ce; -#if defined(VULKAN_ENABLED) - if (video_driver_index == VIDEO_DRIVER_VULKAN) { - context_vulkan->window_resize(0, current_videomode.width, current_videomode.height); + 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 + ERR_FAIL_COND(!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); + } } -#endif } -void OS_X11::process_xevents() { +void DisplayServerX11::_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); + } +} +void DisplayServerX11::process_events() { - //printf("checking events %i\n", XPending(x11_display)); + _THREAD_SAFE_METHOD_ do_mouse_warp = false; @@ -2172,6 +2354,16 @@ void OS_X11::process_xevents() { XEvent event; XNextEvent(x11_display, &event); + WindowID window_id = MAIN_WINDOW_ID; + + // Assign the event to the relevant window + for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { + if (event.xany.window == E->get().x11_window) { + window_id = E->key(); + break; + } + } + if (XFilterEvent(&event, None)) { continue; } @@ -2187,7 +2379,7 @@ void OS_X11::process_xevents() { switch (event_data->evtype) { case XI_HierarchyChanged: case XI_DeviceChanged: { - refresh_device_info(); + _refresh_device_info(); } break; case XI_RawMotion: { XIRawEvent *raw_event = (XIRawEvent *)event_data; @@ -2277,6 +2469,7 @@ void OS_X11::process_xevents() { Ref<InputEventScreenTouch> st; st.instance(); + st->set_window_id(window_id); st->set_index(index); st->set_position(pos); st->set_pressed(is_begin); @@ -2290,12 +2483,12 @@ void OS_X11::process_xevents() { // in a spurious mouse motion event being sent to Godot; remember it to be able to filter it out xi.mouse_pos_to_filter = pos; } - input->accumulate_input_event(st); + InputFilter::get_singleton()->accumulate_input_event(st); } else { if (!xi.state.has(index)) // Defensive break; xi.state.erase(index); - input->accumulate_input_event(st); + InputFilter::get_singleton()->accumulate_input_event(st); } } break; @@ -2310,10 +2503,11 @@ void OS_X11::process_xevents() { Ref<InputEventScreenDrag> sd; sd.instance(); + sd->set_window_id(window_id); sd->set_index(index); sd->set_position(pos); sd->set_relative(pos - curr_pos_elem->value()); - input->accumulate_input_event(sd); + InputFilter::get_singleton()->accumulate_input_event(sd); curr_pos_elem->value() = pos; } @@ -2338,31 +2532,37 @@ void OS_X11::process_xevents() { minimized = (visibility->state == VisibilityFullyObscured); } break; case LeaveNotify: { - if (main_loop && !mouse_mode_grab) - main_loop->notification(MainLoop::NOTIFICATION_WM_MOUSE_EXIT); + if (!mouse_mode_grab) { + _send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_EXIT); + } } break; case EnterNotify: { - if (main_loop && !mouse_mode_grab) - main_loop->notification(MainLoop::NOTIFICATION_WM_MOUSE_ENTER); + if (!mouse_mode_grab) { + _send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_ENTER); + } } break; case FocusIn: minimized = false; window_has_focus = true; - main_loop->notification(MainLoop::NOTIFICATION_WM_FOCUS_IN); + _send_window_event(windows[window_id], WINDOW_EVENT_FOCUS_IN); window_focused = true; if (mouse_mode_grab) { // Show and update the cursor if confined and the window regained focus. - if (mouse_mode == MOUSE_MODE_CONFINED) - XUndefineCursor(x11_display, x11_window); - else if (mouse_mode == MOUSE_MODE_CAPTURED) // or re-hide it in captured mode - XDefineCursor(x11_display, x11_window, null_cursor); - - XGrabPointer( - x11_display, x11_window, True, - ButtonPressMask | ButtonReleaseMask | PointerMotionMask, - GrabModeAsync, GrabModeAsync, x11_window, None, CurrentTime); + + for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { + + if (mouse_mode == MOUSE_MODE_CONFINED) + XUndefineCursor(x11_display, E->get().x11_window); + else if (mouse_mode == MOUSE_MODE_CAPTURED) // or re-hide it in captured mode + XDefineCursor(x11_display, E->get().x11_window, null_cursor); + + XGrabPointer( + x11_display, E->get().x11_window, True, + ButtonPressMask | ButtonReleaseMask | PointerMotionMask, + GrabModeAsync, GrabModeAsync, E->get().x11_window, None, CurrentTime); + } } #ifdef TOUCH_ENABLED // Grab touch devices to avoid OS gesture interference @@ -2370,22 +2570,25 @@ void OS_X11::process_xevents() { XIGrabDevice(x11_display, xi.touch_devices[i], x11_window, CurrentTime, None, XIGrabModeAsync, XIGrabModeAsync, False, &xi.touch_event_mask); }*/ #endif - if (xic) { - XSetICFocus(xic); + if (windows[window_id].xic) { + XSetICFocus(windows[window_id].xic); } break; case FocusOut: window_has_focus = false; - input->release_pressed_events(); - main_loop->notification(MainLoop::NOTIFICATION_WM_FOCUS_OUT); + InputFilter::get_singleton()->release_pressed_events(); + _send_window_event(windows[window_id], WINDOW_EVENT_FOCUS_OUT); window_focused = false; if (mouse_mode_grab) { - //dear X11, I try, I really try, but you never work, you do whathever you want. - if (mouse_mode == MOUSE_MODE_CAPTURED) { - // Show the cursor if we're in captured mode so it doesn't look weird. - XUndefineCursor(x11_display, x11_window); + for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { + + //dear X11, I try, I really try, but you never work, you do whathever you want. + if (mouse_mode == MOUSE_MODE_CAPTURED) { + // Show the cursor if we're in captured mode so it doesn't look weird. + XUndefineCursor(x11_display, E->get().x11_window); + } } XUngrabPointer(x11_display, CurrentTime); } @@ -2401,13 +2604,14 @@ void OS_X11::process_xevents() { Ref<InputEventScreenTouch> st; st.instance(); st->set_index(E->key()); + st->set_window_id(window_id); st->set_position(E->get()); - input->accumulate_input_event(st); + InputFilter::get_singleton()->accumulate_input_event(st); } xi.state.clear(); #endif - if (xic) { - XUnsetICFocus(xic); + if (windows[window_id].xic) { + XSetICFocus(windows[window_id].xic); } break; @@ -2427,13 +2631,14 @@ void OS_X11::process_xevents() { Ref<InputEventMouseButton> mb; mb.instance(); - get_key_modifier_state(event.xbutton.state, mb); + mb->set_window_id(window_id); + _get_key_modifier_state(event.xbutton.state, mb); mb->set_button_index(event.xbutton.button); if (mb->get_button_index() == 2) mb->set_button_index(3); else if (mb->get_button_index() == 3) mb->set_button_index(2); - mb->set_button_mask(get_mouse_button_state(mb->get_button_index(), event.xbutton.type)); + mb->set_button_mask(_get_mouse_button_state(mb->get_button_index(), event.xbutton.type)); mb->set_position(Vector2(event.xbutton.x, event.xbutton.y)); mb->set_global_position(mb->get_position()); @@ -2441,14 +2646,14 @@ void OS_X11::process_xevents() { if (event.type == ButtonPress) { - uint64_t diff = get_ticks_usec() / 1000 - last_click_ms; + uint64_t diff = OS::get_singleton()->get_ticks_usec() / 1000 - last_click_ms; if (mb->get_button_index() == last_click_button_index) { - if (diff < 400 && Point2(last_click_pos).distance_to(Point2(event.xbutton.x, event.xbutton.y)) < 5) { + if (diff < 400 && Vector2(last_click_pos).distance_to(Vector2(event.xbutton.x, event.xbutton.y)) < 5) { last_click_ms = 0; - last_click_pos = Point2(-100, -100); + last_click_pos = Point2i(-100, -100); last_click_button_index = -1; mb->set_doubleclick(true); } @@ -2459,11 +2664,11 @@ void OS_X11::process_xevents() { if (!mb->is_doubleclick()) { last_click_ms += diff; - last_click_pos = Point2(event.xbutton.x, event.xbutton.y); + last_click_pos = Point2i(event.xbutton.x, event.xbutton.y); } } - input->accumulate_input_event(mb); + InputFilter::get_singleton()->accumulate_input_event(mb); } break; case MotionNotify: { @@ -2473,7 +2678,7 @@ void OS_X11::process_xevents() { // generated by warping the mouse pointer. while (true) { - if (mouse_mode == MOUSE_MODE_CAPTURED && event.xmotion.x == current_videomode.width / 2 && event.xmotion.y == current_videomode.height / 2) { + if (mouse_mode == MOUSE_MODE_CAPTURED && event.xmotion.x == windows[MAIN_WINDOW_ID].size.width / 2 && event.xmotion.y == windows[MAIN_WINDOW_ID].size.height / 2) { //this is likely the warp event since it was warped here center = Vector2(event.xmotion.x, event.xmotion.y); break; @@ -2497,7 +2702,7 @@ void OS_X11::process_xevents() { // Motion is also simple. // A little hack is in order // to be able to send relative motion events. - Point2 pos(event.xmotion.x, event.xmotion.y); + Point2i pos(event.xmotion.x, event.xmotion.y); // Avoidance of spurious mouse motion (see handling of touch) bool filter = false; @@ -2534,7 +2739,7 @@ void OS_X11::process_xevents() { // RawMotion provides more precision than MotionNotify, which doesn't sense subpixel motion. // Therefore, MotionNotify cannot be the authority on relative mouse motion. // This means we need to take a combined approach... - Point2 rel; + Point2i rel; // Only use raw input if in capture mode. Otherwise use the classic behavior. if (mouse_mode == MOUSE_MODE_CAPTURED) { @@ -2548,24 +2753,25 @@ void OS_X11::process_xevents() { xi.relative_motion.y = 0; if (mouse_mode == MOUSE_MODE_CAPTURED) { - pos = Point2i(current_videomode.width / 2, current_videomode.height / 2); + pos = Point2i(windows[MAIN_WINDOW_ID].size.width / 2, windows[MAIN_WINDOW_ID].size.height / 2); } Ref<InputEventMouseMotion> mm; mm.instance(); + mm->set_window_id(window_id); mm->set_pressure(xi.pressure); mm->set_tilt(xi.tilt); // Make the absolute position integral so it doesn't look _too_ weird :) Point2i posi(pos); - get_key_modifier_state(event.xmotion.state, mm); - mm->set_button_mask(get_mouse_button_state()); + _get_key_modifier_state(event.xmotion.state, mm); + mm->set_button_mask(mouse_get_button_state()); mm->set_position(posi); mm->set_global_position(posi); - input->set_mouse_position(posi); - mm->set_speed(input->get_last_mouse_speed()); + InputFilter::get_singleton()->set_mouse_position(posi); + mm->set_speed(InputFilter::get_singleton()->get_last_mouse_speed()); mm->set_relative(rel); @@ -2576,7 +2782,7 @@ void OS_X11::process_xevents() { // this is so that the relative motion doesn't get messed up // after we regain focus. if (window_has_focus || !mouse_mode_grab) - input->accumulate_input_event(mm); + InputFilter::get_singleton()->accumulate_input_event(mm); } break; case KeyPress: @@ -2586,7 +2792,7 @@ void OS_X11::process_xevents() { // key event is a little complex, so // it will be handled in its own function. - handle_key_event((XKeyEvent *)&event); + _handle_key_event(window_id, (XKeyEvent *)&event); } break; case SelectionRequest: { @@ -2601,7 +2807,7 @@ void OS_X11::process_xevents() { req->target == XA_STRING || req->target == XInternAtom(x11_display, "text/plain;charset=utf-8", 0) || req->target == XInternAtom(x11_display, "text/plain", 0)) { - CharString clip = OS::get_clipboard().utf8(); + CharString clip = clipboard_get().utf8(); XChangeProperty(x11_display, req->requestor, req->property, @@ -2654,13 +2860,20 @@ void OS_X11::process_xevents() { if (event.xselection.target == requested) { - Property p = read_property(x11_display, x11_window, XInternAtom(x11_display, "PRIMARY", 0)); + Property p = _read_property(x11_display, windows[window_id].x11_window, XInternAtom(x11_display, "PRIMARY", 0)); Vector<String> files = String((char *)p.data).split("\n", false); for (int i = 0; i < files.size(); i++) { files.write[i] = files[i].replace("file://", "").http_unescape().strip_edges(); } - main_loop->drop_files(files); + + if (!windows[window_id].drop_files_callback.is_null()) { + Variant v = files; + Variant *vp = &v; + Variant ret; + Callable::CallError ce; + windows[window_id].drop_files_callback.call((const Variant **)&vp, 1, ret, ce); + } //Reply that all is well. XClientMessageEvent m; @@ -2670,7 +2883,7 @@ void OS_X11::process_xevents() { m.window = xdnd_source_window; m.message_type = xdnd_finished; m.format = 32; - m.data.l[0] = x11_window; + m.data.l[0] = windows[window_id].x11_window; m.data.l[1] = 1; m.data.l[2] = xdnd_action_copy; //We only ever copy. @@ -2680,8 +2893,9 @@ void OS_X11::process_xevents() { case ClientMessage: - if ((unsigned int)event.xclient.data.l[0] == (unsigned int)wm_delete) - main_loop->notification(MainLoop::NOTIFICATION_WM_QUIT_REQUEST); + if ((unsigned int)event.xclient.data.l[0] == (unsigned int)wm_delete) { + _send_window_event(windows[window_id], WINDOW_EVENT_CLOSE_REQUEST); + } else if ((unsigned int)event.xclient.message_type == (unsigned int)xdnd_enter) { @@ -2690,7 +2904,7 @@ void OS_X11::process_xevents() { Window source = event.xclient.data.l[0]; bool more_than_3 = event.xclient.data.l[1] & 1; if (more_than_3) { - Property p = read_property(x11_display, source, XInternAtom(x11_display, "XdndTypeList", False)); + Property p = _read_property(x11_display, source, XInternAtom(x11_display, "XdndTypeList", False)); requested = pick_target_from_list(x11_display, (Atom *)p.data, p.nitems); } else requested = pick_target_from_atoms(x11_display, event.xclient.data.l[2], event.xclient.data.l[3], event.xclient.data.l[4]); @@ -2705,7 +2919,7 @@ void OS_X11::process_xevents() { m.window = event.xclient.data.l[0]; m.message_type = xdnd_status; m.format = 32; - m.data.l[0] = x11_window; + m.data.l[0] = windows[window_id].x11_window; m.data.l[1] = (requested != None); m.data.l[2] = 0; //empty rectangle m.data.l[3] = 0; @@ -2718,9 +2932,9 @@ void OS_X11::process_xevents() { if (requested != None) { xdnd_source_window = event.xclient.data.l[0]; if (xdnd_version >= 1) - XConvertSelection(x11_display, xdnd_selection, requested, XInternAtom(x11_display, "PRIMARY", 0), x11_window, event.xclient.data.l[2]); + XConvertSelection(x11_display, xdnd_selection, requested, XInternAtom(x11_display, "PRIMARY", 0), windows[window_id].x11_window, event.xclient.data.l[2]); else - XConvertSelection(x11_display, xdnd_selection, requested, XInternAtom(x11_display, "PRIMARY", 0), x11_window, CurrentTime); + XConvertSelection(x11_display, xdnd_selection, requested, XInternAtom(x11_display, "PRIMARY", 0), windows[window_id].x11_window, CurrentTime); } else { //Reply that we're not interested. XClientMessageEvent m; @@ -2730,7 +2944,7 @@ void OS_X11::process_xevents() { m.window = event.xclient.data.l[0]; m.message_type = xdnd_finished; m.format = 32; - m.data.l[0] = x11_window; + m.data.l[0] = windows[window_id].x11_window; m.data.l[1] = 0; m.data.l[2] = None; //Failed. XSendEvent(x11_display, event.xclient.data.l[0], False, NoEventMask, (XEvent *)&m); @@ -2746,8 +2960,8 @@ void OS_X11::process_xevents() { if (do_mouse_warp) { - XWarpPointer(x11_display, None, x11_window, - 0, 0, 0, 0, (int)current_videomode.width / 2, (int)current_videomode.height / 2); + XWarpPointer(x11_display, None, windows[MAIN_WINDOW_ID].x11_window, + 0, 0, 0, 0, (int)windows[MAIN_WINDOW_ID].size.width / 2, (int)windows[MAIN_WINDOW_ID].size.height / 2); /* Window root, child; @@ -2761,808 +2975,878 @@ void OS_X11::process_xevents() { */ } - input->flush_accumulated_events(); + InputFilter::get_singleton()->flush_accumulated_events(); } -MainLoop *OS_X11::get_main_loop() const { - - return main_loop; +void DisplayServerX11::release_rendering_thread() { } -void OS_X11::delete_main_loop() { - - if (main_loop) - memdelete(main_loop); - main_loop = NULL; +void DisplayServerX11::make_rendering_thread() { } -void OS_X11::set_main_loop(MainLoop *p_main_loop) { - - main_loop = p_main_loop; - input->set_main_loop(p_main_loop); +void DisplayServerX11::swap_buffers() { } -bool OS_X11::can_draw() const { +void DisplayServerX11::_update_context(WindowData &wd) { + XClassHint *classHint = XAllocClassHint(); - return !minimized; -}; + if (classHint) { -void OS_X11::set_clipboard(const String &p_text) { + CharString name_str; + switch (context) { + case CONTEXT_EDITOR: + name_str = "Godot_Editor"; + break; + case CONTEXT_PROJECTMAN: + name_str = "Godot_ProjectList"; + break; + case CONTEXT_ENGINE: + name_str = "Godot_Engine"; + break; + } - OS::set_clipboard(p_text); + CharString class_str; + if (context == CONTEXT_ENGINE) { + String config_name = GLOBAL_GET("application/config/name"); + if (config_name.length() == 0) { + class_str = "Godot_Engine"; + } else { + class_str = config_name.utf8(); + } + } else { + class_str = "Godot"; + } - XSetSelectionOwner(x11_display, XA_PRIMARY, x11_window, CurrentTime); - XSetSelectionOwner(x11_display, XInternAtom(x11_display, "CLIPBOARD", 0), x11_window, CurrentTime); -}; + classHint->res_class = class_str.ptrw(); + classHint->res_name = name_str.ptrw(); -static String _get_clipboard_impl(Atom p_source, Window x11_window, ::Display *x11_display, String p_internal_clipboard, Atom target) { + XSetClassHint(x11_display, wd.x11_window, classHint); + XFree(classHint); + } +} +void DisplayServerX11::set_context(Context p_context) { - String ret; + _THREAD_SAFE_METHOD_ - Atom type; - Atom selection = XA_PRIMARY; - int format, result; - unsigned long len, bytes_left, dummy; - unsigned char *data; - Window Sown = XGetSelectionOwner(x11_display, p_source); + context = p_context; - if (Sown == x11_window) { + for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { + _update_context(E->get()); + } +} +void DisplayServerX11::set_native_icon(const String &p_filename) { + WARN_PRINT("Native icon not supported by this display server."); +} - return p_internal_clipboard; - }; +bool g_set_icon_error = false; +int set_icon_errorhandler(Display *dpy, XErrorEvent *ev) { + g_set_icon_error = true; + return 0; +} - if (Sown != None) { - XConvertSelection(x11_display, p_source, target, selection, - x11_window, CurrentTime); - XFlush(x11_display); - while (true) { - XEvent event; - XNextEvent(x11_display, &event); - if (event.type == SelectionNotify && event.xselection.requestor == x11_window) { - break; - }; - }; +void DisplayServerX11::set_icon(const Ref<Image> &p_icon) { + _THREAD_SAFE_METHOD_ - // - // Do not get any data, see how much data is there - // - XGetWindowProperty(x11_display, x11_window, - selection, // Tricky.. - 0, 0, // offset - len - 0, // Delete 0==FALSE - AnyPropertyType, //flag - &type, // return type - &format, // return format - &len, &bytes_left, //that - &data); - // DATA is There - if (bytes_left > 0) { - result = XGetWindowProperty(x11_display, x11_window, - selection, 0, bytes_left, 0, - AnyPropertyType, &type, &format, - &len, &dummy, &data); - if (result == Success) { - ret.parse_utf8((const char *)data); - } else - printf("FAIL\n"); - XFree(data); - } - } + WindowData &wd = windows[MAIN_WINDOW_ID]; - return ret; -} + int (*oldHandler)(Display *, XErrorEvent *) = XSetErrorHandler(&set_icon_errorhandler); -static String _get_clipboard(Atom p_source, Window x11_window, ::Display *x11_display, String p_internal_clipboard) { - String ret; - Atom utf8_atom = XInternAtom(x11_display, "UTF8_STRING", True); - if (utf8_atom != None) { - ret = _get_clipboard_impl(p_source, x11_window, x11_display, p_internal_clipboard, utf8_atom); - } - if (ret == "") { - ret = _get_clipboard_impl(p_source, x11_window, x11_display, p_internal_clipboard, XA_STRING); - } - return ret; -} + Atom net_wm_icon = XInternAtom(x11_display, "_NET_WM_ICON", False); -String OS_X11::get_clipboard() const { + if (p_icon.is_valid()) { + Ref<Image> img = p_icon->duplicate(); + img->convert(Image::FORMAT_RGBA8); - String ret; - ret = _get_clipboard(XInternAtom(x11_display, "CLIPBOARD", 0), x11_window, x11_display, OS::get_clipboard()); + while (true) { + int w = img->get_width(); + int h = img->get_height(); - if (ret == "") { - ret = _get_clipboard(XA_PRIMARY, x11_window, x11_display, OS::get_clipboard()); - }; + if (g_set_icon_error) { + g_set_icon_error = false; - return ret; -} + WARN_PRINT("Icon too large, attempting to resize icon."); -String OS_X11::get_name() const { + int new_width, new_height; + if (w > h) { + new_width = w / 2; + new_height = h * new_width / w; + } else { + new_height = h / 2; + new_width = w * new_height / h; + } - return "X11"; -} + w = new_width; + h = new_height; -Error OS_X11::shell_open(String p_uri) { + if (!w || !h) { + WARN_PRINT("Unable to set icon."); + break; + } - Error ok; - List<String> args; - args.push_back(p_uri); - ok = execute("xdg-open", args, false); - if (ok == OK) - return OK; - ok = execute("gnome-open", args, false); - if (ok == OK) - return OK; - ok = execute("kde-open", args, false); - return ok; -} + img->resize(w, h, Image::INTERPOLATE_CUBIC); + } -bool OS_X11::_check_internal_feature_support(const String &p_feature) { + // We're using long to have wordsize (32Bit build -> 32 Bits, 64 Bit build -> 64 Bits + Vector<long> pd; - return p_feature == "pc"; -} + pd.resize(2 + w * h); -String OS_X11::get_config_path() const { + pd.write[0] = w; + pd.write[1] = h; - if (has_environment("XDG_CONFIG_HOME")) { - return get_environment("XDG_CONFIG_HOME"); - } else if (has_environment("HOME")) { - return get_environment("HOME").plus_file(".config"); - } else { - return "."; - } -} + const uint8_t *r = img->get_data().ptr(); -String OS_X11::get_data_path() const { + long *wr = &pd.write[2]; + uint8_t const *pr = r; - if (has_environment("XDG_DATA_HOME")) { - return get_environment("XDG_DATA_HOME"); - } else if (has_environment("HOME")) { - return get_environment("HOME").plus_file(".local/share"); - } else { - return get_config_path(); - } -} + for (int i = 0; i < w * h; i++) { + long v = 0; + // A R G B + v |= pr[3] << 24 | pr[0] << 16 | pr[1] << 8 | pr[2]; + *wr++ = v; + pr += 4; + } -String OS_X11::get_cache_path() const { + XChangeProperty(x11_display, wd.x11_window, net_wm_icon, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)pd.ptr(), pd.size()); - if (has_environment("XDG_CACHE_HOME")) { - return get_environment("XDG_CACHE_HOME"); - } else if (has_environment("HOME")) { - return get_environment("HOME").plus_file(".cache"); + if (!g_set_icon_error) + break; + } } else { - return get_config_path(); + XDeleteProperty(x11_display, wd.x11_window, net_wm_icon); } + + XFlush(x11_display); + XSetErrorHandler(oldHandler); } -String OS_X11::get_system_dir(SystemDir p_dir) const { +Vector<String> DisplayServerX11::get_rendering_drivers_func() { + Vector<String> drivers; - String xdgparam; +#ifdef VULKAN_ENABLED + drivers.push_back("vulkan"); +#endif +#ifdef OPENGL_ENABLED + drivers.push_back("opengl"); +#endif - switch (p_dir) { - case SYSTEM_DIR_DESKTOP: { + return drivers; +} - xdgparam = "DESKTOP"; - } break; - case SYSTEM_DIR_DCIM: { +DisplayServer *DisplayServerX11::create_func(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { - xdgparam = "PICTURES"; + return memnew(DisplayServerX11(p_rendering_driver, p_mode, p_flags, p_resolution, r_error)); +} - } break; - case SYSTEM_DIR_DOCUMENTS: { +DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect) { - xdgparam = "DOCUMENTS"; + //Create window - } break; - case SYSTEM_DIR_DOWNLOADS: { + long visualMask = VisualScreenMask; + int numberOfVisuals; + XVisualInfo vInfoTemplate = {}; + vInfoTemplate.screen = DefaultScreen(x11_display); + XVisualInfo *visualInfo = XGetVisualInfo(x11_display, visualMask, &vInfoTemplate, &numberOfVisuals); - xdgparam = "DOWNLOAD"; + Colormap colormap = XCreateColormap(x11_display, RootWindow(x11_display, vInfoTemplate.screen), visualInfo->visual, AllocNone); - } break; - case SYSTEM_DIR_MOVIES: { + XSetWindowAttributes windowAttributes = {}; + windowAttributes.colormap = colormap; + windowAttributes.background_pixel = 0xFFFFFFFF; + windowAttributes.border_pixel = 0; + windowAttributes.event_mask = KeyPressMask | KeyReleaseMask | StructureNotifyMask | ExposureMask; - xdgparam = "VIDEOS"; + unsigned long valuemask = CWBorderPixel | CWColormap | CWEventMask; - } break; - case SYSTEM_DIR_MUSIC: { + WindowID id; + { + WindowData wd; + wd.x11_window = XCreateWindow(x11_display, RootWindow(x11_display, visualInfo->screen), p_rect.position.x, p_rect.position.y, p_rect.size.width > 0 ? p_rect.size.width : 1, p_rect.size.height > 0 ? p_rect.size.height : 1, 0, visualInfo->depth, InputOutput, visualInfo->visual, valuemask, &windowAttributes); - xdgparam = "MUSIC"; + XMapWindow(x11_display, wd.x11_window); - } break; - case SYSTEM_DIR_PICTURES: { + //associate PID + // make PID known to X11 + { + const long pid = OS::get_singleton()->get_process_id(); + Atom net_wm_pid = XInternAtom(x11_display, "_NET_WM_PID", False); + XChangeProperty(x11_display, wd.x11_window, net_wm_pid, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&pid, 1); + } - xdgparam = "PICTURES"; + long im_event_mask = 0; - } break; - case SYSTEM_DIR_RINGTONES: { + { + XIEventMask all_event_mask; + XSetWindowAttributes new_attr; - xdgparam = "MUSIC"; + new_attr.event_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask | + ButtonReleaseMask | EnterWindowMask | + LeaveWindowMask | PointerMotionMask | + Button1MotionMask | + Button2MotionMask | Button3MotionMask | + Button4MotionMask | Button5MotionMask | + ButtonMotionMask | KeymapStateMask | + ExposureMask | VisibilityChangeMask | + StructureNotifyMask | + SubstructureNotifyMask | SubstructureRedirectMask | + FocusChangeMask | PropertyChangeMask | + ColormapChangeMask | OwnerGrabButtonMask | + im_event_mask; - } break; - } + XChangeWindowAttributes(x11_display, wd.x11_window, CWEventMask, &new_attr); - String pipe; - List<String> arg; - arg.push_back(xdgparam); - Error err = const_cast<OS_X11 *>(this)->execute("xdg-user-dir", arg, true, NULL, &pipe); - if (err != OK) - return "."; - return pipe.strip_edges(); -} + static unsigned char all_mask_data[XIMaskLen(XI_LASTEVENT)] = {}; -void OS_X11::move_window_to_foreground() { + all_event_mask.deviceid = XIAllDevices; + all_event_mask.mask_len = sizeof(all_mask_data); + all_event_mask.mask = all_mask_data; - XEvent xev; - Atom net_active_window = XInternAtom(x11_display, "_NET_ACTIVE_WINDOW", False); + XISetMask(all_event_mask.mask, XI_HierarchyChanged); - memset(&xev, 0, sizeof(xev)); - xev.type = ClientMessage; - xev.xclient.window = x11_window; - xev.xclient.message_type = net_active_window; - xev.xclient.format = 32; - xev.xclient.data.l[0] = 1; - xev.xclient.data.l[1] = CurrentTime; +#ifdef TOUCH_ENABLED + if (xi.touch_devices.size()) { + XISetMask(all_event_mask.mask, XI_TouchBegin); + XISetMask(all_event_mask.mask, XI_TouchUpdate); + XISetMask(all_event_mask.mask, XI_TouchEnd); + XISetMask(all_event_mask.mask, XI_TouchOwnership); + } +#endif - XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); - XFlush(x11_display); -} + XISelectEvents(x11_display, wd.x11_window, &all_event_mask, 1); + } -void OS_X11::set_cursor_shape(CursorShape p_shape) { + /* set the titlebar name */ + XStoreName(x11_display, wd.x11_window, "Godot"); + XSetWMProtocols(x11_display, wd.x11_window, &wm_delete, 1); + XChangeProperty(x11_display, wd.x11_window, xdnd_aware, XA_ATOM, 32, PropModeReplace, (unsigned char *)&xdnd_version, 1); - ERR_FAIL_INDEX(p_shape, CURSOR_MAX); + if (xim && xim_style) { - if (p_shape == current_cursor) { - return; - } + wd.xic = XCreateIC(xim, XNInputStyle, xim_style, XNClientWindow, wd.x11_window, XNFocusWindow, wd.x11_window, (char *)NULL); + if (XGetICValues(wd.xic, XNFilterEvents, &im_event_mask, NULL) != NULL) { + WARN_PRINT("XGetICValues couldn't obtain XNFilterEvents value"); + XDestroyIC(wd.xic); + wd.xic = NULL; + } + if (wd.xic) { + XUnsetICFocus(wd.xic); + } else { + WARN_PRINT("XCreateIC couldn't create wd.xic"); + } + } else { - if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) { - if (cursors[p_shape] != None) { - XDefineCursor(x11_display, x11_window, cursors[p_shape]); - } else if (cursors[CURSOR_ARROW] != None) { - XDefineCursor(x11_display, x11_window, cursors[CURSOR_ARROW]); + wd.xic = NULL; + WARN_PRINT("XCreateIC couldn't create wd.xic"); } - } - current_cursor = p_shape; -} + _update_context(wd); -OS::CursorShape OS_X11::get_cursor_shape() const { + id = window_id_counter++; - return current_cursor; -} + windows[id] = wd; -void OS_X11::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) { + { - if (p_cursor.is_valid()) { + if (p_flags & WINDOW_FLAG_RESIZE_DISABLED_BIT) { - Map<CursorShape, Vector<Variant>>::Element *cursor_c = cursors_cache.find(p_shape); + XSizeHints *xsh; + xsh = XAllocSizeHints(); - if (cursor_c) { - if (cursor_c->get()[0] == p_cursor && cursor_c->get()[1] == p_hotspot) { - set_cursor_shape(p_shape); - return; + xsh->flags = PMinSize | PMaxSize; + xsh->min_width = p_rect.size.width; + xsh->max_width = p_rect.size.width; + xsh->min_height = p_rect.size.height; + xsh->max_height = p_rect.size.height; + + XSetWMNormalHints(x11_display, wd.x11_window, xsh); + XFree(xsh); } - cursors_cache.erase(p_shape); - } + bool make_utility = false; - Ref<Texture2D> texture = p_cursor; - Ref<AtlasTexture> atlas_texture = p_cursor; - Ref<Image> image; - Size2 texture_size; - Rect2 atlas_rect; + if (p_flags & WINDOW_FLAG_BORDERLESS_BIT) { + Hints hints; + Atom property; + hints.flags = 2; + hints.decorations = 0; + property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True); + XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5); - if (texture.is_valid()) { - image = texture->get_data(); - } + make_utility = true; + } + if (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) { + make_utility = true; + } - if (!image.is_valid() && atlas_texture.is_valid()) { - texture = atlas_texture->get_atlas(); + if (make_utility) { + //this one seems to disable the fade animations for regular windows + //but has the drawback that will not get focus by default, so + //we need fo force it, unless no focus requested - 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; + Atom type_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE_UTILITY", False); + Atom wt_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE", False); - 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(); - } + XChangeProperty(x11_display, wd.x11_window, wt_atom, XA_ATOM, 32, PropModeReplace, (unsigned char *)&type_atom, 1); - 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); + if (!(p_flags & WINDOW_FLAG_NO_FOCUS_BIT)) { + //but as utility appears unfocused, it needs to be forcefuly focused, unless no focus requested + XEvent xev; + Atom net_active_window = XInternAtom(x11_display, "_NET_ACTIVE_WINDOW", False); - image = texture->get_data(); + memset(&xev, 0, sizeof(xev)); + xev.type = ClientMessage; + xev.xclient.window = wd.x11_window; + xev.xclient.message_type = net_active_window; + xev.xclient.format = 32; + xev.xclient.data.l[0] = 1; + xev.xclient.data.l[1] = CurrentTime; - ERR_FAIL_COND(!image.is_valid()); + XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); + } + } else { + Atom type_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE_NORMAL", False); + Atom wt_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE", False); - // Create the cursor structure - XcursorImage *cursor_image = XcursorImageCreate(texture_size.width, texture_size.height); - XcursorUInt image_size = texture_size.width * texture_size.height; - XcursorDim size = sizeof(XcursorPixel) * image_size; + XChangeProperty(x11_display, wd.x11_window, wt_atom, XA_ATOM, 32, PropModeReplace, (unsigned char *)&type_atom, 1); + } + } - cursor_image->version = 1; - cursor_image->size = size; - cursor_image->xhot = p_hotspot.x; - cursor_image->yhot = p_hotspot.y; + if (id != MAIN_WINDOW_ID) { - // allocate memory to contain the whole file - cursor_image->pixels = (XcursorPixel *)memalloc(size); + XSizeHints my_hints = XSizeHints(); - for (XcursorPixel index = 0; index < image_size; index++) { - int row_index = floor(index / texture_size.width) + atlas_rect.position.y; - int column_index = (index % int(texture_size.width)) + atlas_rect.position.x; + my_hints.flags = PPosition | PSize; /* I want to specify position and size */ + my_hints.x = p_rect.position.x; /* The origin and size coords I want */ + my_hints.y = p_rect.position.y; + my_hints.width = p_rect.size.width; + my_hints.height = p_rect.size.height; - 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); - } + XSetNormalHints(x11_display, wd.x11_window, &my_hints); + XMoveWindow(x11_display, wd.x11_window, p_rect.position.x, p_rect.position.y); + } - *(cursor_image->pixels + index) = image->get_pixel(column_index, row_index).to_argb32(); +#if defined(VULKAN_ENABLED) + if (context_vulkan) { + Error err = context_vulkan->window_create(id, wd.x11_window, x11_display, p_rect.size.width, p_rect.size.height); + ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't create a Vulkan window"); } +#endif - ERR_FAIL_COND(cursor_image->pixels == NULL); + //set_class_hint(x11_display, wd.x11_window); + XFlush(x11_display); - // Save it for a further usage - cursors[p_shape] = XcursorImageLoadCursor(x11_display, cursor_image); + XSync(x11_display, False); + //XSetErrorHandler(oldHandler); - Vector<Variant> params; - params.push_back(p_cursor); - params.push_back(p_hotspot); - cursors_cache.insert(p_shape, params); + XFree(visualInfo); + } - if (p_shape == current_cursor) { - if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) { - XDefineCursor(x11_display, x11_window, cursors[p_shape]); - } - } + WindowData &wd = windows[id]; - memfree(cursor_image->pixels); - XcursorImageDestroy(cursor_image); - } else { - // Reset to default system cursor - if (img[p_shape]) { - cursors[p_shape] = XcursorImageLoadCursor(x11_display, img[p_shape]); - } + window_set_mode(p_mode, id); - CursorShape c = current_cursor; - current_cursor = CURSOR_MAX; - set_cursor_shape(c); + //sync size + { + XWindowAttributes xwa; - cursors_cache.erase(p_shape); - } -} + XSync(x11_display, False); + XGetWindowAttributes(x11_display, wd.x11_window, &xwa); -void OS_X11::release_rendering_thread() { -#if defined(OPENGL_ENABLED) - if (video_driver_index == VIDEO_DRIVER_GLES2) { - context_gles2->release_current(); - } -#endif -} + wd.position.x = xwa.x; + wd.position.y = xwa.y; + wd.size.width = xwa.width; + wd.size.height = xwa.height; -void OS_X11::make_rendering_thread() { -#if defined(OPENGL_ENABLED) - if (video_driver_index == VIDEO_DRIVER_GLES2) { - context_gles2->make_current(); + print_line("DisplayServer::_create_window " + itos(id) + " want rect: " + p_rect + " got rect " + Rect2i(xwa.x, xwa.y, xwa.width, xwa.height)); } -#endif -} -void OS_X11::swap_buffers() { -#if defined(OPENGL_ENABLED) - if (video_driver_index == VIDEO_DRIVER_GLES2) { - context_gles2->swap_buffers(); - } -#endif - /* not needed for now -#if defined(VULKAN_ENABLED) - if (video_driver_index == VIDEO_DRIVER_VULKAN) { - context_vulkan->swap_buffers(); + //set cursor + if (cursors[current_cursor] != None) { + + XDefineCursor(x11_display, wd.x11_window, cursors[current_cursor]); } -#endif*/ + return id; } -void OS_X11::alert(const String &p_alert, const String &p_title) { - const char *message_programs[] = { "zenity", "kdialog", "Xdialog", "xmessage" }; +DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { - String path = get_environment("PATH"); - Vector<String> path_elems = path.split(":", false); - String program; + InputFilter::get_singleton()->set_event_dispatch_function(_dispatch_input_events); - for (int i = 0; i < path_elems.size(); i++) { - for (uint64_t k = 0; k < sizeof(message_programs) / sizeof(char *); k++) { - String tested_path = path_elems[i].plus_file(message_programs[k]); + r_error = OK; - if (FileAccess::exists(tested_path)) { - program = tested_path; - break; - } - } + last_button_state = 0; - if (program.length()) - break; - } + xmbstring = NULL; - List<String> args; + last_click_ms = 0; + last_click_button_index = -1; + last_click_pos = Point2i(-100, -100); - if (program.ends_with("zenity")) { - args.push_back("--error"); - args.push_back("--width"); - args.push_back("500"); - args.push_back("--title"); - args.push_back(p_title); - args.push_back("--text"); - args.push_back(p_alert); - } + last_timestamp = 0; + last_mouse_pos_valid = false; + last_keyrelease_time = 0; - if (program.ends_with("kdialog")) { - args.push_back("--error"); - args.push_back(p_alert); - args.push_back("--title"); - args.push_back(p_title); + XInitThreads(); //always use threads + + /** XLIB INITIALIZATION **/ + x11_display = XOpenDisplay(NULL); + + if (!x11_display) { + ERR_PRINT("X11 Display is not available"); + r_error = ERR_UNAVAILABLE; + return; } - if (program.ends_with("Xdialog")) { - args.push_back("--title"); - args.push_back(p_title); - args.push_back("--msgbox"); - args.push_back(p_alert); - args.push_back("0"); - args.push_back("0"); + char *modifiers = NULL; + Bool xkb_dar = False; + XAutoRepeatOn(x11_display); + xkb_dar = XkbSetDetectableAutoRepeat(x11_display, True, NULL); + + // Try to support IME if detectable auto-repeat is supported + if (xkb_dar == True) { + +#ifdef X_HAVE_UTF8_STRING + // Xutf8LookupString will be used later instead of XmbLookupString before + // the multibyte sequences can be converted to unicode string. + modifiers = XSetLocaleModifiers(""); +#endif } - if (program.ends_with("xmessage")) { - args.push_back("-center"); - args.push_back("-title"); - args.push_back(p_title); - args.push_back(p_alert); + if (modifiers == NULL) { + if (OS::get_singleton()->is_stdout_verbose()) { + WARN_PRINT("IME is disabled"); + } + XSetLocaleModifiers("@im=none"); + WARN_PRINT("Error setting locale modifiers"); } - if (program.length()) { - execute(program, args, true); + const char *err; + xrr_get_monitors = NULL; + xrr_free_monitors = NULL; + int xrandr_major = 0; + int xrandr_minor = 0; + int event_base, error_base; + xrandr_ext_ok = XRRQueryExtension(x11_display, &event_base, &error_base); + xrandr_handle = dlopen("libXrandr.so.2", RTLD_LAZY); + if (!xrandr_handle) { + err = dlerror(); + fprintf(stderr, "could not load libXrandr.so.2, Error: %s\n", err); } else { - print_line(p_alert); + XRRQueryVersion(x11_display, &xrandr_major, &xrandr_minor); + if (((xrandr_major << 8) | xrandr_minor) >= 0x0105) { + xrr_get_monitors = (xrr_get_monitors_t)dlsym(xrandr_handle, "XRRGetMonitors"); + if (!xrr_get_monitors) { + err = dlerror(); + fprintf(stderr, "could not find symbol XRRGetMonitors\nError: %s\n", err); + } else { + xrr_free_monitors = (xrr_free_monitors_t)dlsym(xrandr_handle, "XRRFreeMonitors"); + if (!xrr_free_monitors) { + err = dlerror(); + fprintf(stderr, "could not find XRRFreeMonitors\nError: %s\n", err); + xrr_get_monitors = NULL; + } + } + } } -} - -bool g_set_icon_error = false; -int set_icon_errorhandler(Display *dpy, XErrorEvent *ev) { - g_set_icon_error = true; - return 0; -} - -void OS_X11::set_icon(const Ref<Image> &p_icon) { - int (*oldHandler)(Display *, XErrorEvent *) = XSetErrorHandler(&set_icon_errorhandler); - Atom net_wm_icon = XInternAtom(x11_display, "_NET_WM_ICON", False); - - if (p_icon.is_valid()) { - Ref<Image> img = p_icon->duplicate(); - img->convert(Image::FORMAT_RGBA8); + if (!_refresh_device_info()) { + alert("Your system does not support XInput 2.\n" + "Please upgrade your distribution.", + "Unable to initialize XInput"); + r_error = ERR_UNAVAILABLE; + return; + } - while (true) { - int w = img->get_width(); - int h = img->get_height(); + xim = XOpenIM(x11_display, NULL, NULL, NULL); - if (g_set_icon_error) { - g_set_icon_error = false; + if (xim == NULL) { + WARN_PRINT("XOpenIM failed"); + xim_style = 0L; + } else { + ::XIMCallback im_destroy_callback; + im_destroy_callback.client_data = (::XPointer)(this); + im_destroy_callback.callback = (::XIMProc)(_xim_destroy_callback); + if (XSetIMValues(xim, XNDestroyCallback, &im_destroy_callback, + NULL) != NULL) { + WARN_PRINT("Error setting XIM destroy callback"); + } - WARN_PRINT("Icon too large, attempting to resize icon."); + ::XIMStyles *xim_styles = NULL; + xim_style = 0L; + char *imvalret = XGetIMValues(xim, XNQueryInputStyle, &xim_styles, NULL); + if (imvalret != NULL || xim_styles == NULL) { + fprintf(stderr, "Input method doesn't support any styles\n"); + } - int new_width, new_height; - if (w > h) { - new_width = w / 2; - new_height = h * new_width / w; - } else { - new_height = h / 2; - new_width = w * new_height / h; - } + if (xim_styles) { + xim_style = 0L; + for (int i = 0; i < xim_styles->count_styles; i++) { - w = new_width; - h = new_height; + if (xim_styles->supported_styles[i] == + (XIMPreeditNothing | XIMStatusNothing)) { - if (!w || !h) { - WARN_PRINT("Unable to set icon."); + xim_style = xim_styles->supported_styles[i]; break; } - - img->resize(w, h, Image::INTERPOLATE_CUBIC); } - // We're using long to have wordsize (32Bit build -> 32 Bits, 64 Bit build -> 64 Bits - Vector<long> pd; - - pd.resize(2 + w * h); - - pd.write[0] = w; - pd.write[1] = h; + XFree(xim_styles); + } + XFree(imvalret); + } - const uint8_t *r = img->get_data().ptr(); + /* Atorm internment */ + wm_delete = XInternAtom(x11_display, "WM_DELETE_WINDOW", true); + //Set Xdnd (drag & drop) support + xdnd_aware = XInternAtom(x11_display, "XdndAware", False); + xdnd_version = 5; + xdnd_enter = XInternAtom(x11_display, "XdndEnter", False); + xdnd_position = XInternAtom(x11_display, "XdndPosition", False); + xdnd_status = XInternAtom(x11_display, "XdndStatus", False); + xdnd_action_copy = XInternAtom(x11_display, "XdndActionCopy", False); + xdnd_drop = XInternAtom(x11_display, "XdndDrop", False); + xdnd_finished = XInternAtom(x11_display, "XdndFinished", False); + xdnd_selection = XInternAtom(x11_display, "XdndSelection", False); - long *wr = &pd.write[2]; - uint8_t const *pr = r; + //!!!!!!!!!!!!!!!!!!!!!!!!!! + //TODO - do Vulkan and GLES2 support checks, driver selection and fallback + rendering_driver = p_rendering_driver; - for (int i = 0; i < w * h; i++) { - long v = 0; - // A R G B - v |= pr[3] << 24 | pr[0] << 16 | pr[1] << 8 | pr[2]; - *wr++ = v; - pr += 4; - } +#ifndef _MSC_VER +#warning Forcing vulkan rendering driver because OpenGL not implemented yet +#endif + rendering_driver = "vulkan"; - XChangeProperty(x11_display, x11_window, net_wm_icon, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)pd.ptr(), pd.size()); +#if defined(VULKAN_ENABLED) + if (rendering_driver == "vulkan") { - if (!g_set_icon_error) - break; + context_vulkan = memnew(VulkanContextX11); + if (context_vulkan->initialize() != OK) { + memdelete(context_vulkan); + context_vulkan = NULL; + r_error = ERR_CANT_CREATE; + ERR_FAIL_MSG("Could not initialize Vulkan"); } - } else { - XDeleteProperty(x11_display, x11_window, net_wm_icon); } - - XFlush(x11_display); - XSetErrorHandler(oldHandler); -} - -void OS_X11::force_process_input() { - process_xevents(); // get rid of pending events -#ifdef JOYDEV_ENABLED - joypad->process_joypads(); #endif -} + // Init context and rendering device +#if defined(OPENGL_ENABLED) + if (rendering_driver == "opengl_es") { + if (getenv("DRI_PRIME") == NULL) { + int use_prime = -1; -void OS_X11::run() { + if (getenv("PRIMUS_DISPLAY") || + getenv("PRIMUS_libGLd") || + getenv("PRIMUS_libGLa") || + getenv("PRIMUS_libGL") || + getenv("PRIMUS_LOAD_GLOBAL") || + getenv("BUMBLEBEE_SOCKET")) { - force_quit = false; + print_verbose("Optirun/primusrun detected. Skipping GPU detection"); + use_prime = 0; + } - if (!main_loop) - return; + if (getenv("LD_LIBRARY_PATH")) { + String ld_library_path(getenv("LD_LIBRARY_PATH")); + Vector<String> libraries = ld_library_path.split(":"); - main_loop->init(); + for (int i = 0; i < libraries.size(); ++i) { + if (FileAccess::exists(libraries[i] + "/libGL.so.1") || + FileAccess::exists(libraries[i] + "/libGL.so")) { - //uint64_t last_ticks=get_ticks_usec(); + print_verbose("Custom libGL override detected. Skipping GPU detection"); + use_prime = 0; + } + } + } - //int frames=0; - //uint64_t frame=0; + if (use_prime == -1) { + print_verbose("Detecting GPUs, set DRI_PRIME in the environment to override GPU detection logic."); + use_prime = detect_prime(); + } - while (!force_quit) { + if (use_prime) { + print_line("Found discrete GPU, setting DRI_PRIME=1 to use it."); + print_line("Note: Set DRI_PRIME=0 in the environment to disable Godot from using the discrete GPU."); + setenv("DRI_PRIME", "1", 1); + } + } - process_xevents(); // get rid of pending events -#ifdef JOYDEV_ENABLED - joypad->process_joypads(); -#endif - if (Main::iteration()) - break; - }; + ContextGL_X11::ContextType opengl_api_type = ContextGL_X11::GLES_2_0_COMPATIBLE; - main_loop->finish(); -} + context_gles2 = memnew(ContextGL_X11(x11_display, x11_window, current_videomode, opengl_api_type)); -bool OS_X11::is_joy_known(int p_device) { - return input->is_joy_mapped(p_device); -} + if (context_gles2->initialize() != OK) { + memdelete(context_gles2); + context_gles2 = NULL; + ERR_FAIL_V(ERR_UNAVAILABLE); + } -String OS_X11::get_joy_guid(int p_device) const { - return input->get_joy_guid_remapped(p_device); -} + context_gles2->set_use_vsync(current_videomode.use_vsync); -void OS_X11::_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); + if (RasterizerGLES2::is_viable() == OK) { + RasterizerGLES2::register_config(); + RasterizerGLES2::make_current(); + } else { + memdelete(context_gles2); + context_gles2 = NULL; + ERR_FAIL_V(ERR_UNAVAILABLE); + } } #endif -} - -void OS_X11::set_context(int p_context) { - XClassHint *classHint = XAllocClassHint(); + WindowID main_window = _create_window(p_mode, p_flags, Rect2i(Point2(), p_resolution)); + for (int i = 0; i < WINDOW_FLAG_MAX; i++) { + if (p_flags & (1 << i)) { + window_set_flag(WindowFlags(i), true, main_window); + } + } - if (classHint) { +//create RenderingDevice if used +#if defined(VULKAN_ENABLED) + if (rendering_driver == "vulkan") { - CharString name_str; - switch (p_context) { - case CONTEXT_EDITOR: - name_str = "Godot_Editor"; - break; - case CONTEXT_PROJECTMAN: - name_str = "Godot_ProjectList"; - break; - case CONTEXT_ENGINE: - name_str = "Godot_Engine"; - break; - } + //temporary + rendering_device_vulkan = memnew(RenderingDeviceVulkan); + rendering_device_vulkan->initialize(context_vulkan); - CharString class_str; - if (p_context == CONTEXT_ENGINE) { - String config_name = GLOBAL_GET("application/config/name"); - if (config_name.length() == 0) { - class_str = "Godot_Engine"; - } else { - class_str = config_name.utf8(); - } - } else { - class_str = "Godot"; - } + RasterizerRD::make_current(); + } +#endif - classHint->res_class = class_str.ptrw(); - classHint->res_name = name_str.ptrw(); + /* + 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)); + } + */ - XSetClassHint(x11_display, x11_window, classHint); - XFree(classHint); + { + //set all event master mask + XIEventMask all_master_event_mask; + static unsigned char all_master_mask_data[XIMaskLen(XI_LASTEVENT)] = {}; + all_master_event_mask.deviceid = XIAllMasterDevices; + all_master_event_mask.mask_len = sizeof(all_master_mask_data); + all_master_event_mask.mask = all_master_mask_data; + XISetMask(all_master_event_mask.mask, XI_DeviceChanged); + XISetMask(all_master_event_mask.mask, XI_RawMotion); + XISelectEvents(x11_display, DefaultRootWindow(x11_display), &all_master_event_mask, 1); } -} -void OS_X11::disable_crash_handler() { - crash_handler.disable(); -} + // Disabled by now since grabbing also blocks mouse events + // (they are received as extended events instead of standard events) + /*XIClearMask(xi.touch_event_mask.mask, XI_TouchOwnership); -bool OS_X11::is_disable_crash_handler() const { - return crash_handler.is_disabled(); -} + // Grab touch devices to avoid OS gesture interference + for (int i = 0; i < xi.touch_devices.size(); ++i) { + XIGrabDevice(x11_display, xi.touch_devices[i], x11_window, CurrentTime, None, XIGrabModeAsync, XIGrabModeAsync, False, &xi.touch_event_mask); + }*/ -static String get_mountpoint(const String &p_path) { - struct stat s; - if (stat(p_path.utf8().get_data(), &s)) { - return ""; - } + cursor_size = XcursorGetDefaultSize(x11_display); + cursor_theme = XcursorGetTheme(x11_display); -#ifdef HAVE_MNTENT - dev_t dev = s.st_dev; - FILE *fd = setmntent("/proc/mounts", "r"); - if (!fd) { - return ""; + if (!cursor_theme) { + print_verbose("XcursorGetTheme could not get cursor theme"); + cursor_theme = "default"; } - struct mntent mnt; - char buf[1024]; - size_t buflen = 1024; - while (getmntent_r(fd, &mnt, buf, buflen)) { - if (!stat(mnt.mnt_dir, &s) && s.st_dev == dev) { - endmntent(fd); - return String(mnt.mnt_dir); - } + for (int i = 0; i < CURSOR_MAX; i++) { + + cursors[i] = None; + img[i] = NULL; } - endmntent(fd); -#endif - return ""; -} + current_cursor = CURSOR_ARROW; -Error OS_X11::move_to_trash(const String &p_path) { - String trash_can = ""; - String mnt = get_mountpoint(p_path); + for (int i = 0; i < CURSOR_MAX; i++) { - // If there is a directory "[Mountpoint]/.Trash-[UID]/files", use it as the trash can. - if (mnt != "") { - String path(mnt + "/.Trash-" + itos(getuid()) + "/files"); - struct stat s; - if (!stat(path.utf8().get_data(), &s)) { - trash_can = path; - } - } + static const char *cursor_file[] = { + "left_ptr", + "xterm", + "hand2", + "cross", + "watch", + "left_ptr_watch", + "fleur", + "dnd-move", + "crossed_circle", + "v_double_arrow", + "h_double_arrow", + "size_bdiag", + "size_fdiag", + "move", + "row_resize", + "col_resize", + "question_arrow" + }; - // Otherwise, if ${XDG_DATA_HOME} is defined, use "${XDG_DATA_HOME}/Trash/files" as the trash can. - if (trash_can == "") { - char *dhome = getenv("XDG_DATA_HOME"); - if (dhome) { - trash_can = String(dhome) + "/Trash/files"; - } - } + img[i] = XcursorLibraryLoadImage(cursor_file[i], cursor_theme, cursor_size); + if (!img[i]) { + const char *fallback = NULL; - // Otherwise, if ${HOME} is defined, use "${HOME}/.local/share/Trash/files" as the trash can. - if (trash_can == "") { - char *home = getenv("HOME"); - if (home) { - trash_can = String(home) + "/.local/share/Trash/files"; + switch (i) { + case CURSOR_POINTING_HAND: + fallback = "pointer"; + break; + case CURSOR_CROSS: + fallback = "crosshair"; + break; + case CURSOR_WAIT: + fallback = "wait"; + break; + case CURSOR_BUSY: + fallback = "progress"; + break; + case CURSOR_DRAG: + fallback = "grabbing"; + break; + case CURSOR_CAN_DROP: + fallback = "hand1"; + break; + case CURSOR_FORBIDDEN: + fallback = "forbidden"; + break; + case CURSOR_VSIZE: + fallback = "ns-resize"; + break; + case CURSOR_HSIZE: + fallback = "ew-resize"; + break; + case CURSOR_BDIAGSIZE: + fallback = "fd_double_arrow"; + break; + case CURSOR_FDIAGSIZE: + fallback = "bd_double_arrow"; + break; + case CURSOR_MOVE: + img[i] = img[CURSOR_DRAG]; + break; + case CURSOR_VSPLIT: + fallback = "sb_v_double_arrow"; + break; + case CURSOR_HSPLIT: + fallback = "sb_h_double_arrow"; + break; + case CURSOR_HELP: + fallback = "help"; + break; + } + if (fallback != NULL) { + img[i] = XcursorLibraryLoadImage(fallback, cursor_theme, cursor_size); + } + } + if (img[i]) { + cursors[i] = XcursorImageLoadCursor(x11_display, img[i]); + } else { + print_verbose("Failed loading custom cursor: " + String(cursor_file[i])); } } - // Issue an error if none of the previous locations is appropriate for the trash can. - if (trash_can == "") { - ERR_PRINT("move_to_trash: Could not determine the trash can location"); - return FAILED; - } + { + // Creating an empty/transparent cursor - // Create needed directories for decided trash can location. - DirAccess *dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - Error err = dir_access->make_dir_recursive(trash_can); - memdelete(dir_access); + // Create 1x1 bitmap + Pixmap cursormask = XCreatePixmap(x11_display, + RootWindow(x11_display, DefaultScreen(x11_display)), 1, 1, 1); - // Issue an error if trash can is not created proprely. - if (err != OK) { - ERR_PRINT("move_to_trash: Could not create the trash can \"" + trash_can + "\""); - return err; - } + // Fill with zero + XGCValues xgc; + xgc.function = GXclear; + GC gc = XCreateGC(x11_display, cursormask, GCFunction, &xgc); + XFillRectangle(x11_display, cursormask, gc, 0, 0, 1, 1); - // The trash can is successfully created, now move the given resource to it. - // Do not use DirAccess:rename() because it can't move files across multiple mountpoints. - List<String> mv_args; - mv_args.push_back(p_path); - mv_args.push_back(trash_can); - int retval; - err = execute("mv", mv_args, true, NULL, NULL, &retval); + // Color value doesn't matter. Mask zero means no foreground or background will be drawn + XColor col = {}; - // Issue an error if "mv" failed to move the given resource to the trash can. - if (err != OK || retval != 0) { - ERR_PRINT("move_to_trash: Could not move the resource \"" + p_path + "\" to the trash can \"" + trash_can + "\""); - return FAILED; - } + Cursor cursor = XCreatePixmapCursor(x11_display, + cursormask, // source (using cursor mask as placeholder, since it'll all be ignored) + cursormask, // mask + &col, &col, 0, 0); - return OK; -} + XFreePixmap(x11_display, cursormask); + XFreeGC(x11_display, gc); -OS::LatinKeyboardVariant OS_X11::get_latin_keyboard_variant() const { + if (cursor == None) { + ERR_PRINT("FAILED CREATING CURSOR"); + } - XkbDescRec *xkbdesc = XkbAllocKeyboard(); - ERR_FAIL_COND_V(!xkbdesc, LATIN_KEYBOARD_QWERTY); + null_cursor = cursor; + } + cursor_set_shape(CURSOR_BUSY); - XkbGetNames(x11_display, XkbSymbolsNameMask, xkbdesc); - ERR_FAIL_COND_V(!xkbdesc->names, LATIN_KEYBOARD_QWERTY); - ERR_FAIL_COND_V(!xkbdesc->names->symbols, LATIN_KEYBOARD_QWERTY); + requested = None; - char *layout = XGetAtomName(x11_display, xkbdesc->names->symbols); - ERR_FAIL_COND_V(!layout, LATIN_KEYBOARD_QWERTY); + window_has_focus = true; // Set focus to true at init - Vector<String> info = String(layout).split("+"); - ERR_FAIL_INDEX_V(1, info.size(), LATIN_KEYBOARD_QWERTY); + /*if (p_desired.layered) { + set_window_per_pixel_transparency_enabled(true); + }*/ - if (info[1].find("colemak") != -1) { - return LATIN_KEYBOARD_COLEMAK; - } else if (info[1].find("qwertz") != -1) { - return LATIN_KEYBOARD_QWERTZ; - } else if (info[1].find("azerty") != -1) { - return LATIN_KEYBOARD_AZERTY; - } else if (info[1].find("qzerty") != -1) { - return LATIN_KEYBOARD_QZERTY; - } else if (info[1].find("dvorak") != -1) { - return LATIN_KEYBOARD_DVORAK; - } else if (info[1].find("neo") != -1) { - return LATIN_KEYBOARD_NEO; + XEvent xevent; + while (XPending(x11_display) > 0) { + XNextEvent(x11_display, &xevent); + if (xevent.type == ConfigureNotify) { + _window_changed(&xevent); + } } - return LATIN_KEYBOARD_QWERTY; + _update_real_mouse_position(windows[MAIN_WINDOW_ID]); + + r_error = OK; } +DisplayServerX11::~DisplayServerX11() { -void OS_X11::update_real_mouse_position() { - Window root_return, child_return; - int root_x, root_y, win_x, win_y; - unsigned int mask_return; + //destroy all windows + for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { +#ifdef VULKAN_ENABLED + if (rendering_driver == "vulkan") { + context_vulkan->window_destroy(E->key()); + } +#endif - Bool xquerypointer_result = XQueryPointer(x11_display, x11_window, &root_return, &child_return, &root_x, &root_y, - &win_x, &win_y, &mask_return); + if (E->get().xic) { + XDestroyIC(E->get().xic); + } + XUnmapWindow(x11_display, E->get().x11_window); + XDestroyWindow(x11_display, E->get().x11_window); + } - if (xquerypointer_result) { - if (win_x > 0 && win_y > 0 && win_x <= current_videomode.width && win_y <= current_videomode.height) { + //destroy drivers +#if defined(VULKAN_ENABLED) + if (rendering_driver == "vulkan") { - last_mouse_pos.x = win_x; - last_mouse_pos.y = win_y; - last_mouse_pos_valid = true; - input->set_mouse_position(last_mouse_pos); + if (rendering_device_vulkan) { + rendering_device_vulkan->finalize(); + memdelete(rendering_device_vulkan); } + + if (context_vulkan) + memdelete(context_vulkan); } -} +#endif -OS_X11::OS_X11() { + if (xrandr_handle) + dlclose(xrandr_handle); -#ifdef PULSEAUDIO_ENABLED - AudioDriverManager::add_driver(&driver_pulseaudio); -#endif + for (int i = 0; i < CURSOR_MAX; i++) { + if (cursors[i] != None) + XFreeCursor(x11_display, cursors[i]); + if (img[i] != NULL) + XcursorImageDestroy(img[i]); + }; -#ifdef ALSA_ENABLED - AudioDriverManager::add_driver(&driver_alsa); -#endif + if (xim) { + XCloseIM(xim); + } - xi.opcode = 0; - xi.last_relative_time = 0; - layered_window = false; - minimized = false; - window_focused = true; - xim_style = 0L; - mouse_mode = MOUSE_MODE_VISIBLE; - last_position_before_fs = Vector2(); + XCloseDisplay(x11_display); + if (xmbstring) + memfree(xmbstring); } + +void DisplayServerX11::register_x11_driver() { + + register_create_function("x11", create_func, get_rendering_drivers_func); +} + +#endif // X11 enabled diff --git a/platform/linuxbsd/display_server_x11.h b/platform/linuxbsd/display_server_x11.h new file mode 100644 index 0000000000..d1680b0508 --- /dev/null +++ b/platform/linuxbsd/display_server_x11.h @@ -0,0 +1,352 @@ +/*************************************************************************/ +/* display_server_x11.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_X11_H +#define DISPLAY_SERVER_X11_H + +#ifdef X11_ENABLED + +#include "servers/display_server.h" + +#include "core/input/input_filter.h" + +#include "drivers/alsa/audio_driver_alsa.h" +#include "drivers/alsamidi/midi_driver_alsamidi.h" +#include "drivers/pulseaudio/audio_driver_pulseaudio.h" +#include "drivers/unix/os_unix.h" +#include "joypad_linux.h" +#include "servers/audio_server.h" +#include "servers/visual/rasterizer.h" +#include "servers/visual_server.h" + +#if defined(OPENGL_ENABLED) +#include "context_gl_x11.h" +#endif + +#if defined(VULKAN_ENABLED) +#include "drivers/vulkan/rendering_device_vulkan.h" +#include "platform/linuxbsd/vulkan_context_x11.h" +#endif + +#include <X11/Xcursor/Xcursor.h> +#include <X11/Xlib.h> +#include <X11/extensions/XInput2.h> +#include <X11/extensions/Xrandr.h> +#include <X11/keysym.h> + +// Hints for X11 fullscreen +typedef struct { + unsigned long flags; + unsigned long functions; + unsigned long decorations; + long inputMode; + unsigned long status; +} Hints; + +typedef struct _xrr_monitor_info { + Atom name; + Bool primary; + Bool automatic; + int noutput; + int x; + int y; + int width; + int height; + int mwidth; + int mheight; + RROutput *outputs; +} xrr_monitor_info; + +#undef CursorShape + +class DisplayServerX11 : public DisplayServer { + //No need to register, it's platform-specific and nothing is added + //GDCLASS(DisplayServerX11, DisplayServer) + + _THREAD_SAFE_CLASS_ + + Atom wm_delete; + Atom xdnd_enter; + Atom xdnd_position; + Atom xdnd_status; + Atom xdnd_action_copy; + Atom xdnd_drop; + Atom xdnd_finished; + Atom xdnd_selection; + Atom xdnd_aware; + Atom requested; + int xdnd_version; + +#if defined(OPENGL_ENABLED) + ContextGL_X11 *context_gles2; +#endif +#if defined(VULKAN_ENABLED) + VulkanContextX11 *context_vulkan; + RenderingDeviceVulkan *rendering_device_vulkan; +#endif + + struct WindowData { + Window x11_window; + ::XIC xic; + + Size2i min_size; + Size2i max_size; + Point2i position; + Size2i size; + Point2i im_position; + bool im_active = false; + Callable rect_changed_callback; + Callable event_callback; + Callable input_event_callback; + Callable input_text_callback; + Callable drop_files_callback; + + WindowID transient_parent = INVALID_WINDOW_ID; + Set<WindowID> transient_children; + + ObjectID instance_id; + + //better to guess on the fly, given WM can change it + //WindowMode mode; + bool fullscreen = false; //OS can't exit from this mode + bool on_top = false; + bool borderless = false; + bool resize_disabled = false; + Vector2i last_position_before_fs; + }; + + Map<WindowID, WindowData> windows; + + WindowID window_id_counter = MAIN_WINDOW_ID; + WindowID _create_window(WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect); + + String internal_clipboard; + Window xdnd_source_window; + ::Display *x11_display; + char *xmbstring; + int xmblen; + unsigned long last_timestamp; + ::Time last_keyrelease_time; + ::XIM xim; + ::XIMStyle xim_style; + static void _xim_destroy_callback(::XIM im, ::XPointer client_data, + ::XPointer call_data); + + Point2i last_mouse_pos; + bool last_mouse_pos_valid; + Point2i last_click_pos; + uint64_t last_click_ms; + int last_click_button_index; + uint32_t last_button_state; + + struct { + int opcode; + Vector<int> touch_devices; + Map<int, Vector2> absolute_devices; + Map<int, Vector3> pen_devices; + XIEventMask all_event_mask; + Map<int, Vector2> state; + double pressure; + Vector2 tilt; + Vector2 mouse_pos_to_filter; + Vector2 relative_motion; + Vector2 raw_pos; + Vector2 old_raw_pos; + ::Time last_relative_time; + } xi; + + bool _refresh_device_info(); + + unsigned int _get_mouse_button_state(unsigned int p_x11_button, int p_x11_type); + void _get_key_modifier_state(unsigned int p_x11_state, Ref<InputEventWithModifiers> state); + void _flush_mouse_motion(); + + MouseMode mouse_mode; + Point2i center; + + void _handle_key_event(WindowID p_window, XKeyEvent *p_event, bool p_echo = false); + + bool force_quit; + bool minimized; + bool window_has_focus; + bool do_mouse_warp; + + const char *cursor_theme; + int cursor_size; + XcursorImage *img[CURSOR_MAX]; + Cursor cursors[CURSOR_MAX]; + Cursor null_cursor; + CursorShape current_cursor; + Map<CursorShape, Vector<Variant>> cursors_cache; + + bool layered_window; + + String rendering_driver; + bool window_focused; + //void set_wm_border(bool p_enabled); + void set_wm_fullscreen(bool p_enabled); + void set_wm_above(bool p_enabled); + + typedef xrr_monitor_info *(*xrr_get_monitors_t)(Display *dpy, Window window, Bool get_active, int *nmonitors); + typedef void (*xrr_free_monitors_t)(xrr_monitor_info *monitors); + xrr_get_monitors_t xrr_get_monitors; + xrr_free_monitors_t xrr_free_monitors; + void *xrandr_handle; + Bool xrandr_ext_ok; + + struct Property { + unsigned char *data; + int format, nitems; + Atom type; + }; + static Property _read_property(Display *p_display, Window p_window, Atom p_property); + + void _update_real_mouse_position(const WindowData &wd); + void _set_wm_fullscreen(WindowID p_window, bool p_enabled); + void _set_wm_maximized(WindowID p_window, bool p_enabled); + + void _update_context(WindowData &wd); + + Context context = CONTEXT_ENGINE; + + 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); + +protected: + void _window_changed(XEvent *event); + +public: + virtual bool has_feature(Feature p_feature) const; + virtual String get_name() const; + + virtual void alert(const String &p_alert, const String &p_title = "ALERT!"); + + 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 Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const; + virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const; + virtual bool screen_is_touchscreen(int p_screen = SCREEN_OF_MAIN_WINDOW) const; + + virtual Vector<DisplayServer::WindowID> get_window_list() const; + + virtual WindowID create_sub_window(WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect = Rect2i()); + virtual void delete_sub_window(WindowID p_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 void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_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 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_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_transient(WindowID p_window, WindowID p_parent); + + 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 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, const Vector2 &p_hotspot); + + virtual LatinKeyboardVariant get_latin_keyboard_variant() const; + + virtual void process_events(); + + virtual void release_rendering_thread(); + virtual void make_rendering_thread(); + virtual void swap_buffers(); + + virtual void set_context(Context p_context); + + virtual void set_native_icon(const String &p_filename); + virtual void set_icon(const Ref<Image> &p_icon); + + 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_x11_driver(); + + DisplayServerX11(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error); + ~DisplayServerX11(); +}; + +#endif // X11 enabled + +#endif // DISPLAY_SERVER_X11_H diff --git a/platform/x11/export/export.cpp b/platform/linuxbsd/export/export.cpp index 1c0c6ec096..53e3ce8f85 100644 --- a/platform/x11/export/export.cpp +++ b/platform/linuxbsd/export/export.cpp @@ -32,17 +32,17 @@ #include "core/os/file_access.h" #include "editor/editor_export.h" -#include "platform/x11/logo.gen.h" +#include "platform/linuxbsd/logo.gen.h" #include "scene/resources/texture.h" static Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size); -void register_x11_exporter() { +void register_linuxbsd_exporter() { Ref<EditorExportPlatformPC> platform; platform.instance(); - Ref<Image> img = memnew(Image(_x11_logo)); + Ref<Image> img = memnew(Image(_linuxbsd_logo)); Ref<ImageTexture> logo; logo.instance(); logo->create_from_image(img); diff --git a/platform/x11/export/export.h b/platform/linuxbsd/export/export.h index 4049e6a8bf..5ee81f485e 100644 --- a/platform/x11/export/export.h +++ b/platform/linuxbsd/export/export.h @@ -28,9 +28,9 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef X11_EXPORT_H -#define X11_EXPORT_H +#ifndef LINUXBSD_EXPORT_H +#define LINUXBSD_EXPORT_H -void register_x11_exporter(); +void register_linuxbsd_exporter(); -#endif // X11_EXPORT_H +#endif // LINUXBSD_EXPORT_H diff --git a/platform/x11/godot_x11.cpp b/platform/linuxbsd/godot_linuxbsd.cpp index 77b74184ad..710ba3ca40 100644 --- a/platform/x11/godot_x11.cpp +++ b/platform/linuxbsd/godot_linuxbsd.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* godot_x11.cpp */ +/* godot_linuxbsd.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -34,11 +34,11 @@ #include <unistd.h> #include "main/main.h" -#include "os_x11.h" +#include "os_linuxbsd.h" int main(int argc, char *argv[]) { - OS_X11 os; + OS_LinuxBSD os; setlocale(LC_CTYPE, ""); diff --git a/platform/x11/joypad_linux.cpp b/platform/linuxbsd/joypad_linux.cpp index 1aacc6a250..c4c793093d 100644 --- a/platform/x11/joypad_linux.cpp +++ b/platform/linuxbsd/joypad_linux.cpp @@ -71,7 +71,7 @@ void JoypadLinux::Joypad::reset() { dpad = 0; fd = -1; - InputDefault::JoyAxis jx; + InputFilter::JoyAxis jx; jx.min = -1; jx.value = 0.0f; for (int i = 0; i < MAX_ABS; i++) { @@ -80,7 +80,7 @@ void JoypadLinux::Joypad::reset() { } } -JoypadLinux::JoypadLinux(InputDefault *in) { +JoypadLinux::JoypadLinux(InputFilter *in) { exit_udev = false; input = in; joy_thread = Thread::create(joy_thread_func, this); @@ -436,11 +436,11 @@ void JoypadLinux::joypad_vibration_stop(int p_id, uint64_t p_timestamp) { joy.ff_effect_timestamp = p_timestamp; } -InputDefault::JoyAxis JoypadLinux::axis_correct(const input_absinfo *p_abs, int p_value) const { +InputFilter::JoyAxis JoypadLinux::axis_correct(const input_absinfo *p_abs, int p_value) const { int min = p_abs->minimum; int max = p_abs->maximum; - InputDefault::JoyAxis jx; + InputFilter::JoyAxis jx; if (min < 0) { jx.min = -1; @@ -492,11 +492,11 @@ void JoypadLinux::process_joypads() { case ABS_HAT0X: if (ev.value != 0) { if (ev.value < 0) - joy->dpad |= InputDefault::HAT_MASK_LEFT; + joy->dpad |= InputFilter::HAT_MASK_LEFT; else - joy->dpad |= InputDefault::HAT_MASK_RIGHT; + joy->dpad |= InputFilter::HAT_MASK_RIGHT; } else - joy->dpad &= ~(InputDefault::HAT_MASK_LEFT | InputDefault::HAT_MASK_RIGHT); + joy->dpad &= ~(InputFilter::HAT_MASK_LEFT | InputFilter::HAT_MASK_RIGHT); input->joy_hat(i, joy->dpad); break; @@ -504,11 +504,11 @@ void JoypadLinux::process_joypads() { case ABS_HAT0Y: if (ev.value != 0) { if (ev.value < 0) - joy->dpad |= InputDefault::HAT_MASK_UP; + joy->dpad |= InputFilter::HAT_MASK_UP; else - joy->dpad |= InputDefault::HAT_MASK_DOWN; + joy->dpad |= InputFilter::HAT_MASK_DOWN; } else - joy->dpad &= ~(InputDefault::HAT_MASK_UP | InputDefault::HAT_MASK_DOWN); + joy->dpad &= ~(InputFilter::HAT_MASK_UP | InputFilter::HAT_MASK_DOWN); input->joy_hat(i, joy->dpad); break; @@ -517,7 +517,7 @@ void JoypadLinux::process_joypads() { if (ev.code >= MAX_ABS) return; if (joy->abs_map[ev.code] != -1 && joy->abs_info[ev.code]) { - InputDefault::JoyAxis value = axis_correct(joy->abs_info[ev.code], ev.value); + InputFilter::JoyAxis value = axis_correct(joy->abs_info[ev.code], ev.value); joy->curr_axis[joy->abs_map[ev.code]] = value; } break; diff --git a/platform/x11/joypad_linux.h b/platform/linuxbsd/joypad_linux.h index d5719b6dbe..1d2ed5bbc1 100644 --- a/platform/x11/joypad_linux.h +++ b/platform/linuxbsd/joypad_linux.h @@ -33,15 +33,15 @@ #define JOYPAD_LINUX_H #ifdef JOYDEV_ENABLED +#include "core/input/input_filter.h" #include "core/os/mutex.h" #include "core/os/thread.h" -#include "main/input_default.h" struct input_absinfo; class JoypadLinux { public: - JoypadLinux(InputDefault *in); + JoypadLinux(InputFilter *in); ~JoypadLinux(); void process_joypads(); @@ -53,7 +53,7 @@ private: }; struct Joypad { - InputDefault::JoyAxis curr_axis[MAX_ABS]; + InputFilter::JoyAxis curr_axis[MAX_ABS]; int key_map[MAX_KEY]; int abs_map[MAX_ABS]; int dpad; @@ -74,7 +74,7 @@ private: bool exit_udev; Mutex joy_mutex; Thread *joy_thread; - InputDefault *input; + InputFilter *input; Joypad joypads[JOYPADS_MAX]; Vector<String> attached_devices; @@ -95,7 +95,7 @@ private: void joypad_vibration_start(int p_id, float p_weak_magnitude, float p_strong_magnitude, float p_duration, uint64_t p_timestamp); void joypad_vibration_stop(int p_id, uint64_t p_timestamp); - InputDefault::JoyAxis axis_correct(const input_absinfo *p_abs, int p_value) const; + InputFilter::JoyAxis axis_correct(const input_absinfo *p_abs, int p_value) const; }; #endif diff --git a/platform/x11/key_mapping_x11.cpp b/platform/linuxbsd/key_mapping_x11.cpp index 78bd2b71a0..78bd2b71a0 100644 --- a/platform/x11/key_mapping_x11.cpp +++ b/platform/linuxbsd/key_mapping_x11.cpp diff --git a/platform/x11/key_mapping_x11.h b/platform/linuxbsd/key_mapping_x11.h index 10db43bcc4..10db43bcc4 100644 --- a/platform/x11/key_mapping_x11.h +++ b/platform/linuxbsd/key_mapping_x11.h diff --git a/platform/x11/logo.png b/platform/linuxbsd/logo.png Binary files differindex 078654b757..078654b757 100644 --- a/platform/x11/logo.png +++ b/platform/linuxbsd/logo.png diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp new file mode 100644 index 0000000000..084453bdc6 --- /dev/null +++ b/platform/linuxbsd/os_linuxbsd.cpp @@ -0,0 +1,381 @@ +/*************************************************************************/ +/* os_linuxbsd.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "os_linuxbsd.h" + +#include "core/os/dir_access.h" +#include "core/print_string.h" +#include "errno.h" + +#ifdef HAVE_MNTENT +#include <mntent.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <dlfcn.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "main/main.h" + +#ifdef X11_ENABLED +#include "display_server_x11.h" +#endif + +void OS_LinuxBSD::initialize() { + + crash_handler.initialize(); + + OS_Unix::initialize_core(); +} + +void OS_LinuxBSD::initialize_joypads() { + +#ifdef JOYDEV_ENABLED + joypad = memnew(JoypadLinux(InputFilter::get_singleton())); +#endif +} + +String OS_LinuxBSD::get_unique_id() const { + + static String machine_id; + if (machine_id.empty()) { + if (FileAccess *f = FileAccess::open("/etc/machine-id", FileAccess::READ)) { + while (machine_id.empty() && !f->eof_reached()) { + machine_id = f->get_line().strip_edges(); + } + f->close(); + memdelete(f); + } + } + return machine_id; +} + +void OS_LinuxBSD::finalize() { + + if (main_loop) + memdelete(main_loop); + main_loop = NULL; + +#ifdef ALSAMIDI_ENABLED + driver_alsamidi.close(); +#endif + +#ifdef JOYDEV_ENABLED + memdelete(joypad); +#endif +} + +MainLoop *OS_LinuxBSD::get_main_loop() const { + + return main_loop; +} + +void OS_LinuxBSD::delete_main_loop() { + + if (main_loop) + memdelete(main_loop); + main_loop = NULL; +} + +void OS_LinuxBSD::set_main_loop(MainLoop *p_main_loop) { + + main_loop = p_main_loop; +} + +String OS_LinuxBSD::get_name() const { + +#ifdef __linux__ + return "Linux"; +#elif defined(__FreeBSD__) + return "FreeBSD"; +#elif defined(__NetBSD__) + return "NetBSD"; +#else + return "BSD"; +#endif +} + +Error OS_LinuxBSD::shell_open(String p_uri) { + + Error ok; + List<String> args; + args.push_back(p_uri); + ok = execute("xdg-open", args, false); + if (ok == OK) + return OK; + ok = execute("gnome-open", args, false); + if (ok == OK) + return OK; + ok = execute("kde-open", args, false); + return ok; +} + +bool OS_LinuxBSD::_check_internal_feature_support(const String &p_feature) { + + return p_feature == "pc"; +} + +String OS_LinuxBSD::get_config_path() const { + + if (has_environment("XDG_CONFIG_HOME")) { + return get_environment("XDG_CONFIG_HOME"); + } else if (has_environment("HOME")) { + return get_environment("HOME").plus_file(".config"); + } else { + return "."; + } +} + +String OS_LinuxBSD::get_data_path() const { + + if (has_environment("XDG_DATA_HOME")) { + return get_environment("XDG_DATA_HOME"); + } else if (has_environment("HOME")) { + return get_environment("HOME").plus_file(".local/share"); + } else { + return get_config_path(); + } +} + +String OS_LinuxBSD::get_cache_path() const { + + if (has_environment("XDG_CACHE_HOME")) { + return get_environment("XDG_CACHE_HOME"); + } else if (has_environment("HOME")) { + return get_environment("HOME").plus_file(".cache"); + } else { + return get_config_path(); + } +} + +String OS_LinuxBSD::get_system_dir(SystemDir p_dir) const { + + String xdgparam; + + switch (p_dir) { + case SYSTEM_DIR_DESKTOP: { + + xdgparam = "DESKTOP"; + } break; + case SYSTEM_DIR_DCIM: { + + xdgparam = "PICTURES"; + + } break; + case SYSTEM_DIR_DOCUMENTS: { + + xdgparam = "DOCUMENTS"; + + } break; + case SYSTEM_DIR_DOWNLOADS: { + + xdgparam = "DOWNLOAD"; + + } break; + case SYSTEM_DIR_MOVIES: { + + xdgparam = "VIDEOS"; + + } break; + case SYSTEM_DIR_MUSIC: { + + xdgparam = "MUSIC"; + + } break; + case SYSTEM_DIR_PICTURES: { + + xdgparam = "PICTURES"; + + } break; + case SYSTEM_DIR_RINGTONES: { + + xdgparam = "MUSIC"; + + } break; + } + + String pipe; + List<String> arg; + arg.push_back(xdgparam); + Error err = const_cast<OS_LinuxBSD *>(this)->execute("xdg-user-dir", arg, true, NULL, &pipe); + if (err != OK) + return "."; + return pipe.strip_edges(); +} + +void OS_LinuxBSD::run() { + + force_quit = false; + + if (!main_loop) + return; + + main_loop->init(); + + //uint64_t last_ticks=get_ticks_usec(); + + //int frames=0; + //uint64_t frame=0; + + while (!force_quit) { + + DisplayServer::get_singleton()->process_events(); // get rid of pending events +#ifdef JOYDEV_ENABLED + joypad->process_joypads(); +#endif + if (Main::iteration()) + break; + }; + + main_loop->finish(); +} + +void OS_LinuxBSD::disable_crash_handler() { + crash_handler.disable(); +} + +bool OS_LinuxBSD::is_disable_crash_handler() const { + return crash_handler.is_disabled(); +} + +static String get_mountpoint(const String &p_path) { + struct stat s; + if (stat(p_path.utf8().get_data(), &s)) { + return ""; + } + +#ifdef HAVE_MNTENT + dev_t dev = s.st_dev; + FILE *fd = setmntent("/proc/mounts", "r"); + if (!fd) { + return ""; + } + + struct mntent mnt; + char buf[1024]; + size_t buflen = 1024; + while (getmntent_r(fd, &mnt, buf, buflen)) { + if (!stat(mnt.mnt_dir, &s) && s.st_dev == dev) { + endmntent(fd); + return String(mnt.mnt_dir); + } + } + + endmntent(fd); +#endif + return ""; +} + +Error OS_LinuxBSD::move_to_trash(const String &p_path) { + String trash_can = ""; + String mnt = get_mountpoint(p_path); + + // If there is a directory "[Mountpoint]/.Trash-[UID]/files", use it as the trash can. + if (mnt != "") { + String path(mnt + "/.Trash-" + itos(getuid()) + "/files"); + struct stat s; + if (!stat(path.utf8().get_data(), &s)) { + trash_can = path; + } + } + + // Otherwise, if ${XDG_DATA_HOME} is defined, use "${XDG_DATA_HOME}/Trash/files" as the trash can. + if (trash_can == "") { + char *dhome = getenv("XDG_DATA_HOME"); + if (dhome) { + trash_can = String(dhome) + "/Trash/files"; + } + } + + // Otherwise, if ${HOME} is defined, use "${HOME}/.local/share/Trash/files" as the trash can. + if (trash_can == "") { + char *home = getenv("HOME"); + if (home) { + trash_can = String(home) + "/.local/share/Trash/files"; + } + } + + // Issue an error if none of the previous locations is appropriate for the trash can. + if (trash_can == "") { + ERR_PRINT("move_to_trash: Could not determine the trash can location"); + return FAILED; + } + + // Create needed directories for decided trash can location. + DirAccess *dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + Error err = dir_access->make_dir_recursive(trash_can); + memdelete(dir_access); + + // Issue an error if trash can is not created proprely. + if (err != OK) { + ERR_PRINT("move_to_trash: Could not create the trash can \"" + trash_can + "\""); + return err; + } + + // The trash can is successfully created, now move the given resource to it. + // Do not use DirAccess:rename() because it can't move files across multiple mountpoints. + List<String> mv_args; + mv_args.push_back(p_path); + mv_args.push_back(trash_can); + int retval; + err = execute("mv", mv_args, true, NULL, NULL, &retval); + + // Issue an error if "mv" failed to move the given resource to the trash can. + if (err != OK || retval != 0) { + ERR_PRINT("move_to_trash: Could not move the resource \"" + p_path + "\" to the trash can \"" + trash_can + "\""); + return FAILED; + } + + return OK; +} + +OS_LinuxBSD::OS_LinuxBSD() { + + main_loop = NULL; + force_quit = false; + +#ifdef PULSEAUDIO_ENABLED + AudioDriverManager::add_driver(&driver_pulseaudio); +#endif + +#ifdef ALSA_ENABLED + AudioDriverManager::add_driver(&driver_alsa); +#endif + +#ifdef X11_ENABLED + DisplayServerX11::register_x11_driver(); +#endif +} diff --git a/platform/linuxbsd/os_linuxbsd.h b/platform/linuxbsd/os_linuxbsd.h new file mode 100644 index 0000000000..6c656c1945 --- /dev/null +++ b/platform/linuxbsd/os_linuxbsd.h @@ -0,0 +1,106 @@ +/*************************************************************************/ +/* os_linuxbsd.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 OS_LINUXBSD_H +#define OS_LINUXBSD_H + +#include "core/input/input_filter.h" +#include "crash_handler_linuxbsd.h" +#include "drivers/alsa/audio_driver_alsa.h" +#include "drivers/alsamidi/midi_driver_alsamidi.h" +#include "drivers/pulseaudio/audio_driver_pulseaudio.h" +#include "drivers/unix/os_unix.h" +#include "joypad_linux.h" +#include "servers/audio_server.h" +#include "servers/visual/rasterizer.h" +#include "servers/visual_server.h" + +class OS_LinuxBSD : public OS_Unix { + + virtual void delete_main_loop(); + + bool force_quit; + +#ifdef JOYDEV_ENABLED + JoypadLinux *joypad; +#endif + +#ifdef ALSA_ENABLED + AudioDriverALSA driver_alsa; +#endif + +#ifdef ALSAMIDI_ENABLED + MIDIDriverALSAMidi driver_alsamidi; +#endif + +#ifdef PULSEAUDIO_ENABLED + AudioDriverPulseAudio driver_pulseaudio; +#endif + + CrashHandler crash_handler; + + MainLoop *main_loop; + +protected: + virtual void initialize(); + virtual void finalize(); + + virtual void initialize_joypads(); + + virtual void set_main_loop(MainLoop *p_main_loop); + +public: + virtual String get_name() const; + + virtual MainLoop *get_main_loop() const; + + virtual String get_config_path() const; + virtual String get_data_path() const; + virtual String get_cache_path() const; + + virtual String get_system_dir(SystemDir p_dir) const; + + virtual Error shell_open(String p_uri); + + virtual String get_unique_id() const; + + virtual bool _check_internal_feature_support(const String &p_feature); + + void run(); + + void disable_crash_handler(); + bool is_disable_crash_handler() const; + + virtual Error move_to_trash(const String &p_path); + + OS_LinuxBSD(); +}; + +#endif diff --git a/platform/x11/pck_embed.ld b/platform/linuxbsd/pck_embed.ld index 57a1994043..57a1994043 100644 --- a/platform/x11/pck_embed.ld +++ b/platform/linuxbsd/pck_embed.ld diff --git a/platform/x11/pck_embed.legacy.ld b/platform/linuxbsd/pck_embed.legacy.ld index a23013ba7a..a23013ba7a 100644 --- a/platform/x11/pck_embed.legacy.ld +++ b/platform/linuxbsd/pck_embed.legacy.ld diff --git a/platform/x11/platform_config.h b/platform/linuxbsd/platform_config.h index ac30519132..ac30519132 100644 --- a/platform/x11/platform_config.h +++ b/platform/linuxbsd/platform_config.h diff --git a/platform/x11/platform_x11_builders.py b/platform/linuxbsd/platform_linuxbsd_builders.py index 5ff0c6fb14..a72306a9c0 100644 --- a/platform/x11/platform_x11_builders.py +++ b/platform/linuxbsd/platform_linuxbsd_builders.py @@ -7,7 +7,7 @@ import os from platform_methods import subprocess_main -def make_debug_x11(target, source, env): +def make_debug_linuxbsd(target, source, env): os.system('objcopy --only-keep-debug {0} {0}.debugsymbols'.format(target[0])) os.system('strip --strip-debug --strip-unneeded {0}'.format(target[0])) os.system('objcopy --add-gnu-debuglink={0}.debugsymbols {0}'.format(target[0])) diff --git a/platform/x11/vulkan_context_x11.cpp b/platform/linuxbsd/vulkan_context_x11.cpp index 602dbc77a2..d0e1b1678c 100644 --- a/platform/x11/vulkan_context_x11.cpp +++ b/platform/linuxbsd/vulkan_context_x11.cpp @@ -35,7 +35,7 @@ const char *VulkanContextX11::_get_platform_surface_extension() const { return VK_KHR_XLIB_SURFACE_EXTENSION_NAME; } -int VulkanContextX11::window_create(::Window p_window, Display *p_display, int p_width, int p_height) { +Error VulkanContextX11::window_create(DisplayServer::WindowID p_window_id, ::Window p_window, Display *p_display, int p_width, int p_height) { VkXlibSurfaceCreateInfoKHR createInfo; createInfo.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR; @@ -46,8 +46,8 @@ int VulkanContextX11::window_create(::Window p_window, Display *p_display, int p VkSurfaceKHR surface; VkResult err = vkCreateXlibSurfaceKHR(_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); } VulkanContextX11::VulkanContextX11() { diff --git a/platform/x11/vulkan_context_x11.h b/platform/linuxbsd/vulkan_context_x11.h index 573f994ea6..6e144ab2d9 100644 --- a/platform/x11/vulkan_context_x11.h +++ b/platform/linuxbsd/vulkan_context_x11.h @@ -39,7 +39,7 @@ class VulkanContextX11 : public VulkanContext { virtual const char *_get_platform_surface_extension() const; public: - int window_create(::Window p_window, Display *p_display, int p_width, int p_height); + Error window_create(DisplayServer::WindowID p_window_id, ::Window p_window, Display *p_display, int p_width, int p_height); VulkanContextX11(); ~VulkanContextX11(); 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 e9f46fb5a4..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 *)Input::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 2c076b3680..62027c6a30 100644 --- a/platform/osx/joypad_osx.h +++ b/platform/osx/joypad_osx.h @@ -40,7 +40,7 @@ #include <ForceFeedback/ForceFeedbackConstants.h> #include <IOKit/hid/IOHIDLib.h> -#include "main/input_default.h" +#include "core/input/input_filter.h" struct rec_element { IOHIDElementRef ref; @@ -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 e865c3078f..d2c67cff9f 100644 --- a/platform/osx/os_osx.h +++ b/platform/osx/os_osx.h @@ -31,57 +31,20 @@ #ifndef OS_OSX_H #define OS_OSX_H -#define BitMap _QDBitMap // Suppress deprecated QuickDraw definition. - -#include "core/os/input.h" +#include "core/input/input_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 "main/input_default.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; @@ -90,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; @@ -237,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/platform/server/detect.py b/platform/server/detect.py index db9ba8d036..4ea4bddddd 100644 --- a/platform/server/detect.py +++ b/platform/server/detect.py @@ -16,7 +16,7 @@ def get_name(): def get_program_suffix(): if (sys.platform == "darwin"): return "osx" - return "x11" + return "linuxbsd" def can_build(): diff --git a/platform/server/os_server.h b/platform/server/os_server.h index 7584293722..d40905718e 100644 --- a/platform/server/os_server.h +++ b/platform/server/os_server.h @@ -31,14 +31,14 @@ #ifndef OS_SERVER_H #define OS_SERVER_H +#include "core/input/input_filter.h" #include "drivers/dummy/texture_loader_dummy.h" #include "drivers/unix/os_unix.h" -#include "main/input_default.h" #ifdef __APPLE__ #include "platform/osx/crash_handler_osx.h" #include "platform/osx/semaphore_osx.h" #else -#include "platform/x11/crash_handler_x11.h" +#include "platform/x11/crash_handler_linuxbsd.h" #endif #include "servers/audio_server.h" #include "servers/visual/rasterizer.h" diff --git a/platform/uwp/joypad_uwp.h b/platform/uwp/joypad_uwp.h index f2a721f3dd..054b67ddc8 100644 --- a/platform/uwp/joypad_uwp.h +++ b/platform/uwp/joypad_uwp.h @@ -31,7 +31,7 @@ #ifndef JOYPAD_UWP_H #define JOYPAD_UWP_H -#include "main/input_default.h" +#include "core/input/input_filter.h" ref class JoypadUWP sealed { diff --git a/platform/uwp/os_uwp.h b/platform/uwp/os_uwp.h index ac6e0f3dd5..cc4dfcc79f 100644 --- a/platform/uwp/os_uwp.h +++ b/platform/uwp/os_uwp.h @@ -32,13 +32,12 @@ #define OS_UWP_H #include "context_egl_uwp.h" +#include "core/input/input_filter.h" #include "core/math/transform_2d.h" -#include "core/os/input.h" #include "core/os/os.h" #include "core/ustring.h" #include "drivers/xaudio2/audio_driver_xaudio2.h" #include "joypad_uwp.h" -#include "main/input_default.h" #include "servers/audio_server.h" #include "servers/visual/rasterizer.h" #include "servers/visual_server.h" diff --git a/platform/windows/SCsub b/platform/windows/SCsub index 8e94c7b35d..89ee4dfa7a 100644 --- a/platform/windows/SCsub +++ b/platform/windows/SCsub @@ -10,6 +10,7 @@ common_win = [ "godot_windows.cpp", "crash_handler_windows.cpp", "os_windows.cpp", + "display_server_windows.cpp", "key_mapping_windows.cpp", "joypad_windows.cpp", "windows_terminal_logger.cpp", diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp new file mode 100644 index 0000000000..707337bc8d --- /dev/null +++ b/platform/windows/display_server_windows.cpp @@ -0,0 +1,2968 @@ +#include "display_server_windows.h" +#include "core/io/marshalls.h" +#include "main/main.h" +#include "os_windows.h" +#include "scene/resources/texture.h" + +#include <avrt.h> + +#ifndef WM_POINTERUPDATE +#define WM_POINTERUPDATE 0x0245 +#endif + +#ifdef DEBUG_ENABLED +static String format_error_message(DWORD id) { + + LPWSTR messageBuffer = NULL; + size_t size = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&messageBuffer, 0, NULL); + + String msg = "Error " + itos(id) + ": " + String(messageBuffer, size); + + LocalFree(messageBuffer); + + return msg; +} +#endif // DEBUG_ENABLED + +bool DisplayServerWindows::has_feature(Feature p_feature) const { + switch (p_feature) { + 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_CONSOLE_WINDOW: + case FEATURE_IME: + case FEATURE_WINDOW_TRANSPARENCY: + case FEATURE_HIDPI: + case FEATURE_ICON: + case FEATURE_NATIVE_ICON: + case FEATURE_SWAP_BUFFERS: + case FEATURE_KEEP_SCREEN_ON: + return true; + default: + return false; + } +} + +String DisplayServerWindows::get_name() const { + return "Windows"; +} + +void DisplayServerWindows::alert(const String &p_alert, const String &p_title) { + MessageBoxW(NULL, p_alert.c_str(), p_title.c_str(), MB_OK | MB_ICONEXCLAMATION | MB_TASKMODAL); +} + +void DisplayServerWindows::_set_mouse_mode_impl(MouseMode p_mode) { + + if (p_mode == MOUSE_MODE_CAPTURED || p_mode == MOUSE_MODE_CONFINED) { + WindowData &wd = windows[MAIN_WINDOW_ID]; + + RECT clipRect; + GetClientRect(wd.hWnd, &clipRect); + ClientToScreen(wd.hWnd, (POINT *)&clipRect.left); + ClientToScreen(wd.hWnd, (POINT *)&clipRect.right); + ClipCursor(&clipRect); + if (p_mode == MOUSE_MODE_CAPTURED) { + + center = window_get_size() / 2; + POINT pos = { (int)center.x, (int)center.y }; + ClientToScreen(wd.hWnd, &pos); + SetCursorPos(pos.x, pos.y); + SetCapture(wd.hWnd); + } + } else { + ReleaseCapture(); + ClipCursor(NULL); + } + + if (p_mode == MOUSE_MODE_CAPTURED || p_mode == MOUSE_MODE_HIDDEN) { + hCursor = SetCursor(NULL); + } else { + CursorShape c = cursor_shape; + cursor_shape = CURSOR_MAX; + cursor_set_shape(c); + } +} +void DisplayServerWindows::mouse_set_mode(MouseMode p_mode) { + + _THREAD_SAFE_METHOD_ + + if (mouse_mode == p_mode) + return; + + _set_mouse_mode_impl(p_mode); + + mouse_mode = p_mode; +} +DisplayServer::MouseMode DisplayServerWindows::mouse_get_mode() const { + return mouse_mode; +} + +void DisplayServerWindows::mouse_warp_to_position(const Point2i &p_to) { + + _THREAD_SAFE_METHOD_ + + if (!windows.has(last_focused_window)) { + return; //no window focused? + } + + if (mouse_mode == MOUSE_MODE_CAPTURED) { + + old_x = p_to.x; + old_y = p_to.y; + } else { + + POINT p; + p.x = p_to.x; + p.y = p_to.y; + ClientToScreen(windows[last_focused_window].hWnd, &p); + + SetCursorPos(p.x, p.y); + } +} +Point2i DisplayServerWindows::mouse_get_position() const { + POINT p; + GetCursorPos(&p); + return Point2i(p.x, p.y); + //return Point2(old_x, old_y); +} +int DisplayServerWindows::mouse_get_button_state() const { + return last_button_state; +} + +void DisplayServerWindows::clipboard_set(const String &p_text) { + + _THREAD_SAFE_METHOD_ + + if (!windows.has(last_focused_window)) { + return; //no window focused? + } + + // Convert LF line endings to CRLF in clipboard content + // Otherwise, line endings won't be visible when pasted in other software + String text = p_text.replace("\n", "\r\n"); + + if (!OpenClipboard(windows[last_focused_window].hWnd)) { + ERR_FAIL_MSG("Unable to open clipboard."); + } + EmptyClipboard(); + + HGLOBAL mem = GlobalAlloc(GMEM_MOVEABLE, (text.length() + 1) * sizeof(CharType)); + ERR_FAIL_COND_MSG(mem == NULL, "Unable to allocate memory for clipboard contents."); + + LPWSTR lptstrCopy = (LPWSTR)GlobalLock(mem); + memcpy(lptstrCopy, text.c_str(), (text.length() + 1) * sizeof(CharType)); + GlobalUnlock(mem); + + SetClipboardData(CF_UNICODETEXT, mem); + + // set the CF_TEXT version (not needed?) + CharString utf8 = text.utf8(); + mem = GlobalAlloc(GMEM_MOVEABLE, utf8.length() + 1); + ERR_FAIL_COND_MSG(mem == NULL, "Unable to allocate memory for clipboard contents."); + + LPTSTR ptr = (LPTSTR)GlobalLock(mem); + memcpy(ptr, utf8.get_data(), utf8.length()); + ptr[utf8.length()] = 0; + GlobalUnlock(mem); + + SetClipboardData(CF_TEXT, mem); + + CloseClipboard(); +} +String DisplayServerWindows::clipboard_get() const { + + _THREAD_SAFE_METHOD_ + + if (!windows.has(last_focused_window)) { + return String(); //no window focused? + } + + String ret; + if (!OpenClipboard(windows[last_focused_window].hWnd)) { + ERR_FAIL_V_MSG("", "Unable to open clipboard."); + }; + + if (IsClipboardFormatAvailable(CF_UNICODETEXT)) { + + HGLOBAL mem = GetClipboardData(CF_UNICODETEXT); + if (mem != NULL) { + + LPWSTR ptr = (LPWSTR)GlobalLock(mem); + if (ptr != NULL) { + + ret = String((CharType *)ptr); + GlobalUnlock(mem); + }; + }; + + } else if (IsClipboardFormatAvailable(CF_TEXT)) { + + HGLOBAL mem = GetClipboardData(CF_UNICODETEXT); + if (mem != NULL) { + + LPTSTR ptr = (LPTSTR)GlobalLock(mem); + if (ptr != NULL) { + + ret.parse_utf8((const char *)ptr); + GlobalUnlock(mem); + }; + }; + }; + + CloseClipboard(); + + return ret; +} + +typedef struct { + int count; + int screen; + HMONITOR monitor; +} EnumScreenData; + +static BOOL CALLBACK _MonitorEnumProcScreen(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { + + EnumScreenData *data = (EnumScreenData *)dwData; + if (data->monitor == hMonitor) { + data->screen = data->count; + } + + data->count++; + return TRUE; +} + +static BOOL CALLBACK _MonitorEnumProcCount(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { + + int *data = (int *)dwData; + (*data)++; + return TRUE; +} + +int DisplayServerWindows::get_screen_count() const { + _THREAD_SAFE_METHOD_ + + int data = 0; + EnumDisplayMonitors(NULL, NULL, _MonitorEnumProcCount, (LPARAM)&data); + return data; +} + +typedef struct { + int count; + int screen; + Point2 pos; +} EnumPosData; + +static BOOL CALLBACK _MonitorEnumProcPos(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { + + EnumPosData *data = (EnumPosData *)dwData; + if (data->count == data->screen) { + data->pos.x = lprcMonitor->left; + data->pos.y = lprcMonitor->top; + } + + data->count++; + return TRUE; +} +Point2i DisplayServerWindows::screen_get_position(int p_screen) const { + + _THREAD_SAFE_METHOD_ + + EnumPosData data = { 0, p_screen == SCREEN_OF_MAIN_WINDOW ? window_get_current_screen() : p_screen, Point2() }; + EnumDisplayMonitors(NULL, NULL, _MonitorEnumProcPos, (LPARAM)&data); + return data.pos; +} + +typedef struct { + int count; + int screen; + Size2 size; +} EnumSizeData; + +typedef struct { + int count; + int screen; + Rect2i rect; +} EnumRectData; + +static BOOL CALLBACK _MonitorEnumProcSize(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { + + EnumSizeData *data = (EnumSizeData *)dwData; + if (data->count == data->screen) { + data->size.x = lprcMonitor->right - lprcMonitor->left; + data->size.y = lprcMonitor->bottom - lprcMonitor->top; + } + + data->count++; + return TRUE; +} + +Size2i DisplayServerWindows::screen_get_size(int p_screen) const { + + _THREAD_SAFE_METHOD_ + + EnumSizeData data = { 0, p_screen == SCREEN_OF_MAIN_WINDOW ? window_get_current_screen() : p_screen, Size2() }; + EnumDisplayMonitors(NULL, NULL, _MonitorEnumProcSize, (LPARAM)&data); + return data.size; +} + +static BOOL CALLBACK _MonitorEnumProcUsableSize(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { + + EnumRectData *data = (EnumRectData *)dwData; + if (data->count == data->screen) { + MONITORINFO minfo; + zeromem(&minfo, sizeof(MONITORINFO)); + minfo.cbSize = sizeof(MONITORINFO); + GetMonitorInfoA(hMonitor, &minfo); + + data->rect.position.x = minfo.rcWork.left; + data->rect.position.y = minfo.rcWork.top; + data->rect.size.x = minfo.rcWork.right - minfo.rcWork.left; + data->rect.size.y = minfo.rcWork.bottom - minfo.rcWork.top; + } + + data->count++; + return TRUE; +} + +Rect2i DisplayServerWindows::screen_get_usable_rect(int p_screen) const { + + _THREAD_SAFE_METHOD_ + + EnumRectData data = { 0, p_screen == SCREEN_OF_MAIN_WINDOW ? window_get_current_screen() : p_screen, Rect2i() }; + EnumDisplayMonitors(NULL, NULL, _MonitorEnumProcUsableSize, (LPARAM)&data); + return data.rect; +} + +typedef struct { + int count; + int screen; + int dpi; +} EnumDpiData; + +enum _MonitorDpiType { + MDT_Effective_DPI = 0, + MDT_Angular_DPI = 1, + MDT_Raw_DPI = 2, + MDT_Default = MDT_Effective_DPI +}; + +static int QueryDpiForMonitor(HMONITOR hmon, _MonitorDpiType dpiType = MDT_Default) { + + int dpiX = 96, dpiY = 96; + + static HMODULE Shcore = NULL; + typedef HRESULT(WINAPI * GetDPIForMonitor_t)(HMONITOR hmonitor, _MonitorDpiType dpiType, UINT * dpiX, UINT * dpiY); + static GetDPIForMonitor_t getDPIForMonitor = NULL; + + if (Shcore == NULL) { + Shcore = LoadLibraryW(L"Shcore.dll"); + getDPIForMonitor = Shcore ? (GetDPIForMonitor_t)GetProcAddress(Shcore, "GetDpiForMonitor") : NULL; + + if ((Shcore == NULL) || (getDPIForMonitor == NULL)) { + if (Shcore) + FreeLibrary(Shcore); + Shcore = (HMODULE)INVALID_HANDLE_VALUE; + } + } + + UINT x = 0, y = 0; + HRESULT hr = E_FAIL; + if (hmon && (Shcore != (HMODULE)INVALID_HANDLE_VALUE)) { + hr = getDPIForMonitor(hmon, dpiType /*MDT_Effective_DPI*/, &x, &y); + if (SUCCEEDED(hr) && (x > 0) && (y > 0)) { + + dpiX = (int)x; + dpiY = (int)y; + } + } else { + static int overallX = 0, overallY = 0; + if (overallX <= 0 || overallY <= 0) { + HDC hdc = GetDC(NULL); + if (hdc) { + overallX = GetDeviceCaps(hdc, LOGPIXELSX); + overallY = GetDeviceCaps(hdc, LOGPIXELSY); + ReleaseDC(NULL, hdc); + } + } + if (overallX > 0 && overallY > 0) { + dpiX = overallX; + dpiY = overallY; + } + } + + return (dpiX + dpiY) / 2; +} + +static BOOL CALLBACK _MonitorEnumProcDpi(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { + + EnumDpiData *data = (EnumDpiData *)dwData; + if (data->count == data->screen) { + data->dpi = QueryDpiForMonitor(hMonitor); + } + + data->count++; + return TRUE; +} + +int DisplayServerWindows::screen_get_dpi(int p_screen) const { + _THREAD_SAFE_METHOD_ + + EnumDpiData data = { 0, p_screen == SCREEN_OF_MAIN_WINDOW ? window_get_current_screen() : p_screen, 72 }; + EnumDisplayMonitors(NULL, NULL, _MonitorEnumProcDpi, (LPARAM)&data); + return data.dpi; +} +bool DisplayServerWindows::screen_is_touchscreen(int p_screen) const { +#ifndef _MSC_VER +#warning touchscreen not working +#endif + return false; +} + +void DisplayServerWindows::screen_set_orientation(ScreenOrientation p_orientation, int p_screen) { +} +DisplayServer::ScreenOrientation DisplayServerWindows::screen_get_orientation(int p_screen) const { + return SCREEN_LANDSCAPE; +} + +void DisplayServerWindows::screen_set_keep_on(bool p_enable) { +} +bool DisplayServerWindows::screen_is_kept_on() const { + return false; +} + +Vector<DisplayServer::WindowID> DisplayServerWindows::get_window_list() const { + + _THREAD_SAFE_METHOD_ + + Vector<DisplayServer::WindowID> ret; + for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { + ret.push_back(E->key()); + } + return ret; +} + +DisplayServer::WindowID DisplayServerWindows::get_window_at_screen_position(const Point2i &p_position) const { + + POINT p; + p.x = p_position.x; + p.y = p_position.y; + HWND hwnd = WindowFromPoint(p); + for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { + if (E->get().hWnd == hwnd) { + return E->key(); + } + } + + return INVALID_WINDOW_ID; +} + +DisplayServer::WindowID DisplayServerWindows::create_sub_window(WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect) { + + _THREAD_SAFE_METHOD_ + + WindowID window_id = _create_window(p_mode, p_flags, p_rect); + + WindowData &wd = windows[window_id]; + + if (p_flags & WINDOW_FLAG_RESIZE_DISABLED_BIT) { + wd.resizable = false; + } + if (p_flags & WINDOW_FLAG_BORDERLESS_BIT) { + wd.borderless = true; + } + if (p_flags & WINDOW_FLAG_ALWAYS_ON_TOP_BIT && p_mode != WINDOW_MODE_FULLSCREEN) { + wd.always_on_top = true; + } + if (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) { + wd.no_focus = true; + } + + _update_window_style(window_id); + + ShowWindow(wd.hWnd, (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) ? SW_SHOWNOACTIVATE : SW_SHOW); // Show The Window + if (!(p_flags & WINDOW_FLAG_NO_FOCUS_BIT)) { + SetForegroundWindow(wd.hWnd); // Slightly Higher Priority + SetFocus(wd.hWnd); // Sets Keyboard Focus To + } + + return window_id; +} +void DisplayServerWindows::delete_sub_window(WindowID p_window) { + + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + ERR_FAIL_COND_MSG(p_window == MAIN_WINDOW_ID, "Main window cannot be deleted."); + + WindowData &wd = windows[p_window]; + + while (wd.transient_children.size()) { + window_set_transient(wd.transient_children.front()->get(), INVALID_WINDOW_ID); + } + + if (wd.transient_parent != INVALID_WINDOW_ID) { + window_set_transient(p_window, INVALID_WINDOW_ID); + } + +#ifdef VULKAN_ENABLED + if (rendering_driver == "vulkan") { + context_vulkan->window_destroy(p_window); + } +#endif + + DestroyWindow(windows[p_window].hWnd); + windows.erase(p_window); +} + +void DisplayServerWindows::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 DisplayServerWindows::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; +} + +void DisplayServerWindows::window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window) { + + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + windows[p_window].rect_changed_callback = p_callable; +} + +void DisplayServerWindows::window_set_window_event_callback(const Callable &p_callable, WindowID p_window) { + + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + windows[p_window].event_callback = p_callable; +} +void DisplayServerWindows::window_set_input_event_callback(const Callable &p_callable, WindowID p_window) { + + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + windows[p_window].input_event_callback = p_callable; +} +void DisplayServerWindows::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) { + + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + windows[p_window].input_text_callback = p_callable; +} + +void DisplayServerWindows::window_set_drop_files_callback(const Callable &p_callable, WindowID p_window) { + + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + windows[p_window].drop_files_callback = p_callable; +} + +void DisplayServerWindows::window_set_title(const String &p_title, WindowID p_window) { + + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + SetWindowTextW(windows[p_window].hWnd, p_title.c_str()); +} + +int DisplayServerWindows::window_get_current_screen(WindowID p_window) const { + + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND_V(!windows.has(p_window), -1); + + EnumScreenData data = { 0, 0, MonitorFromWindow(windows[p_window].hWnd, MONITOR_DEFAULTTONEAREST) }; + EnumDisplayMonitors(NULL, NULL, _MonitorEnumProcScreen, (LPARAM)&data); + return data.screen; +} +void DisplayServerWindows::window_set_current_screen(int p_screen, WindowID p_window) { + + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + ERR_FAIL_INDEX(p_screen, get_screen_count()); + + Vector2 ofs = window_get_position(p_window) - screen_get_position(window_get_current_screen(p_window)); + window_set_position(ofs + screen_get_position(p_screen), p_window); +} + +Point2i DisplayServerWindows::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]; + + if (wd.minimized) { + return wd.last_pos; + } + + POINT point; + point.x = 0; + point.y = 0; + + ClientToScreen(wd.hWnd, &point); + + return Point2i(point.x, point.y); + +#if 0 + //do not use this method, as it includes windows decorations + RECT r; + GetWindowRect(wd.hWnd, &r); + return Point2(r.left, r.top); +#endif +} +void DisplayServerWindows::_update_real_mouse_position(WindowID p_window) { + + POINT mouse_pos; + if (GetCursorPos(&mouse_pos) && ScreenToClient(windows[p_window].hWnd, &mouse_pos)) { + if (mouse_pos.x > 0 && mouse_pos.y > 0 && mouse_pos.x <= windows[p_window].width && mouse_pos.y <= windows[p_window].height) { + old_x = mouse_pos.x; + old_y = mouse_pos.y; + old_invalid = false; + InputFilter::get_singleton()->set_mouse_position(Point2i(mouse_pos.x, mouse_pos.y)); + } + } +} +void DisplayServerWindows::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]; + + if (wd.fullscreen) return; +#if 0 + //wrong needs to account properly for decorations + RECT r; + GetWindowRect(wd.hWnd, &r); + MoveWindow(wd.hWnd, p_position.x, p_position.y, r.right - r.left, r.bottom - r.top, TRUE); +#else + + RECT rc; + rc.left = p_position.x; + rc.right = p_position.x + wd.width; + rc.bottom = p_position.y + wd.height; + rc.top = p_position.y; + + const DWORD style = GetWindowLongPtr(wd.hWnd, GWL_STYLE); + const DWORD exStyle = GetWindowLongPtr(wd.hWnd, GWL_EXSTYLE); + + AdjustWindowRectEx(&rc, style, false, exStyle); + MoveWindow(wd.hWnd, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, TRUE); +#endif + // Don't let the mouse leave the window when moved + if (mouse_mode == MOUSE_MODE_CONFINED) { + RECT rect; + GetClientRect(wd.hWnd, &rect); + ClientToScreen(wd.hWnd, (POINT *)&rect.left); + ClientToScreen(wd.hWnd, (POINT *)&rect.right); + ClipCursor(&rect); + } + + wd.last_pos = p_position; + _update_real_mouse_position(p_window); +} + +void DisplayServerWindows::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.always_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); + + SetWindowLongPtr(wd_window.hWnd, GWLP_HWNDPARENT, NULL); + } 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); + + SetWindowLongPtr(wd_window.hWnd, GWLP_HWNDPARENT, (LONG_PTR)wd_parent.hWnd); + } +} + +void DisplayServerWindows::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 != Size2()) && ((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; +} +Size2i DisplayServerWindows::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 DisplayServerWindows::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 != Size2()) && (wd.max_size != Size2()) && ((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; +} +Size2i DisplayServerWindows::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 DisplayServerWindows::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]; + + int w = p_size.width; + int h = p_size.height; + + wd.width = w; + wd.height = h; + +#if defined(VULKAN_ENABLED) + if (rendering_driver == "vulkan") { + context_vulkan->window_resize(p_window, w, h); + } +#endif + + if (wd.fullscreen) { + return; + } + + RECT rect; + GetWindowRect(wd.hWnd, &rect); + + if (!wd.borderless) { + RECT crect; + GetClientRect(wd.hWnd, &crect); + + w += (rect.right - rect.left) - (crect.right - crect.left); + h += (rect.bottom - rect.top) - (crect.bottom - crect.top); + } + + MoveWindow(wd.hWnd, rect.left, rect.top, w, h, TRUE); + + // Don't let the mouse leave the window when resizing to a smaller resolution + if (mouse_mode == MOUSE_MODE_CONFINED) { + RECT crect; + GetClientRect(wd.hWnd, &crect); + ClientToScreen(wd.hWnd, (POINT *)&crect.left); + ClientToScreen(wd.hWnd, (POINT *)&crect.right); + ClipCursor(&crect); + } +} +Size2i DisplayServerWindows::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]; + + if (wd.minimized) { + return Size2(wd.width, wd.height); + } + + RECT r; + if (GetClientRect(wd.hWnd, &r)) { // Only area inside of window border + return Size2(r.right - r.left, r.bottom - r.top); + } + return Size2(); +} +Size2i DisplayServerWindows::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]; + + RECT r; + if (GetWindowRect(wd.hWnd, &r)) { // Includes area of the window border + return Size2(r.right - r.left, r.bottom - r.top); + } + return Size2(); +} + +void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscreen, bool p_borderless, bool p_resizable, bool p_maximized, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex) { + + r_style = 0; + r_style_ex = WS_EX_WINDOWEDGE; + if (p_main_window) { + r_style_ex |= WS_EX_APPWINDOW; + } + + if (p_fullscreen || p_borderless) { + r_style |= WS_POPUP; + //if (p_borderless) { + // r_style_ex |= WS_EX_TOOLWINDOW; + //} + } else { + + if (p_resizable) { + if (p_maximized) { + r_style = WS_OVERLAPPEDWINDOW | WS_MAXIMIZE; + } else { + r_style = WS_OVERLAPPEDWINDOW; + } + } else { + r_style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU; + } + } + + if (p_no_activate_focus) { + r_style_ex |= WS_EX_TOPMOST | WS_EX_NOACTIVATE; + } + r_style |= WS_CLIPCHILDREN | WS_CLIPSIBLINGS; +} + +void DisplayServerWindows::_update_window_style(WindowID p_window, bool p_repaint, bool p_maximized) { + + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; + + DWORD style = 0; + DWORD style_ex = 0; + + _get_window_style(p_window == MAIN_WINDOW_ID, wd.fullscreen, wd.borderless, wd.resizable, wd.maximized, wd.no_focus, style, style_ex); + + SetWindowLongPtr(wd.hWnd, GWL_STYLE, style); + SetWindowLongPtr(wd.hWnd, GWL_EXSTYLE, style_ex); + + SetWindowPos(wd.hWnd, wd.always_on_top ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | (wd.no_focus ? SWP_NOACTIVATE : 0)); + + if (p_repaint) { + RECT rect; + GetWindowRect(wd.hWnd, &rect); + MoveWindow(wd.hWnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE); + } +} + +void DisplayServerWindows::window_set_mode(WindowMode p_mode, WindowID p_window) { + + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; + + if (wd.fullscreen && p_mode != WINDOW_MODE_FULLSCREEN) { + + RECT rect; + + wd.fullscreen = false; + + if (wd.pre_fs_valid) { + rect = wd.pre_fs_rect; + } else { + rect.left = 0; + rect.right = wd.width; + rect.top = 0; + rect.bottom = wd.height; + } + + _update_window_style(p_window, false, wd.was_maximized); + + MoveWindow(wd.hWnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE); + + wd.pre_fs_valid = true; + } + + if (p_mode == WINDOW_MODE_MAXIMIZED) { + + ShowWindow(wd.hWnd, SW_MAXIMIZE); + wd.maximized = true; + wd.minimized = false; + } + + if (p_mode == WINDOW_MODE_WINDOWED) { + + ShowWindow(wd.hWnd, SW_RESTORE); + wd.maximized = false; + wd.minimized = false; + } + + if (p_mode == WINDOW_MODE_MINIMIZED) { + + ShowWindow(wd.hWnd, SW_MINIMIZE); + wd.maximized = false; + wd.minimized = true; + } + + if (p_mode == WINDOW_MODE_FULLSCREEN && !wd.fullscreen) { + if (wd.minimized) { + ShowWindow(wd.hWnd, SW_RESTORE); + } + wd.was_maximized = wd.maximized; + + if (wd.pre_fs_valid) { + GetWindowRect(wd.hWnd, &wd.pre_fs_rect); + } + + int cs = window_get_current_screen(p_window); + Point2 pos = screen_get_position(cs); + Size2 size = screen_get_size(cs); + + wd.fullscreen = true; + wd.maximized = false; + wd.minimized = false; + + _update_window_style(false); + + MoveWindow(wd.hWnd, pos.x, pos.y, size.width, size.height, TRUE); + } +} +DisplayServer::WindowMode DisplayServerWindows::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) { + return WINDOW_MODE_FULLSCREEN; + } else if (wd.minimized) { + return WINDOW_MODE_MINIMIZED; + } else if (wd.maximized) { + return WINDOW_MODE_MAXIMIZED; + } else { + return WINDOW_MODE_WINDOWED; + } +} + +bool DisplayServerWindows::window_is_maximize_allowed(WindowID p_window) const { + + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND_V(!windows.has(p_window), false); + const WindowData &wd = windows[p_window]; + + return true; //no idea +} + +void DisplayServerWindows::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.resizable = !p_enabled; + _update_window_style(p_window); + } break; + case WINDOW_FLAG_BORDERLESS: { + + wd.borderless = p_enabled; + _update_window_style(p_window); + } break; + case WINDOW_FLAG_ALWAYS_ON_TOP: { + + ERR_FAIL_COND_MSG(wd.transient_parent != INVALID_WINDOW_ID && p_enabled, "Transient windows can't become on top"); + wd.always_on_top = p_enabled; + _update_window_style(p_window); + } break; + case WINDOW_FLAG_TRANSPARENT: { + + } break; + case WINDOW_FLAG_NO_FOCUS: { + + wd.no_focus = p_enabled; + _update_window_style(p_window); + } break; + } +} +bool DisplayServerWindows::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.resizable; + } break; + case WINDOW_FLAG_BORDERLESS: { + + return wd.borderless; + } break; + case WINDOW_FLAG_ALWAYS_ON_TOP: { + + return wd.always_on_top; + } break; + case WINDOW_FLAG_TRANSPARENT: { + + } break; + } + + return false; +} + +void DisplayServerWindows::window_request_attention(WindowID p_window) { + + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; + + FLASHWINFO info; + info.cbSize = sizeof(FLASHWINFO); + info.hwnd = wd.hWnd; + info.dwFlags = FLASHW_TRAY; + info.dwTimeout = 0; + info.uCount = 2; + FlashWindowEx(&info); +} +void DisplayServerWindows::window_move_to_foreground(WindowID p_window) { + + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; + + SetForegroundWindow(wd.hWnd); +} + +bool DisplayServerWindows::window_can_draw(WindowID p_window) const { + + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND_V(!windows.has(p_window), false); + const WindowData &wd = windows[p_window]; + return wd.minimized; +} + +bool DisplayServerWindows::can_any_window_draw() const { + + _THREAD_SAFE_METHOD_ + + for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { + if (!E->get().minimized) { + return true; + } + } + + return false; +} + +void DisplayServerWindows::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]; + + if (p_active) { + ImmAssociateContext(wd.hWnd, wd.im_himc); + + window_set_ime_position(wd.im_position, p_window); + } else { + ImmAssociateContext(wd.hWnd, (HIMC)0); + } +} +void DisplayServerWindows::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; + + HIMC himc = ImmGetContext(wd.hWnd); + if (himc == (HIMC)0) + return; + + COMPOSITIONFORM cps; + cps.dwStyle = CFS_FORCE_POSITION; + cps.ptCurrentPos.x = wd.im_position.x; + cps.ptCurrentPos.y = wd.im_position.y; + ImmSetCompositionWindow(himc, &cps); + ImmReleaseContext(wd.hWnd, himc); +} + +void DisplayServerWindows::console_set_visible(bool p_enabled) { + + _THREAD_SAFE_METHOD_ + + if (console_visible == p_enabled) + return; + ShowWindow(GetConsoleWindow(), p_enabled ? SW_SHOW : SW_HIDE); + console_visible = p_enabled; +} +bool DisplayServerWindows::is_console_visible() const { + return console_visible; +} + +void DisplayServerWindows::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; + } + + static const LPCTSTR win_cursors[CURSOR_MAX] = { + IDC_ARROW, + IDC_IBEAM, + IDC_HAND, //finger + IDC_CROSS, + IDC_WAIT, + IDC_APPSTARTING, + IDC_ARROW, + IDC_ARROW, + IDC_NO, + IDC_SIZENS, + IDC_SIZEWE, + IDC_SIZENESW, + IDC_SIZENWSE, + IDC_SIZEALL, + IDC_SIZENS, + IDC_SIZEWE, + IDC_HELP + }; + + if (cursors[p_shape] != NULL) { + SetCursor(cursors[p_shape]); + } else { + SetCursor(LoadCursor(hInstance, win_cursors[p_shape])); + } + + cursor_shape = p_shape; +} +DisplayServer::CursorShape DisplayServerWindows::cursor_get_shape() const { + return cursor_shape; +} + +void DisplayServerWindows::GetMaskBitmaps(HBITMAP hSourceBitmap, COLORREF clrTransparent, OUT HBITMAP &hAndMaskBitmap, OUT HBITMAP &hXorMaskBitmap) { + + // Get the system display DC + HDC hDC = GetDC(NULL); + + // Create helper DC + HDC hMainDC = CreateCompatibleDC(hDC); + HDC hAndMaskDC = CreateCompatibleDC(hDC); + HDC hXorMaskDC = CreateCompatibleDC(hDC); + + // Get the dimensions of the source bitmap + BITMAP bm; + GetObject(hSourceBitmap, sizeof(BITMAP), &bm); + + // Create the mask bitmaps + hAndMaskBitmap = CreateCompatibleBitmap(hDC, bm.bmWidth, bm.bmHeight); // color + hXorMaskBitmap = CreateCompatibleBitmap(hDC, bm.bmWidth, bm.bmHeight); // color + + // Release the system display DC + ReleaseDC(NULL, hDC); + + // Select the bitmaps to helper DC + HBITMAP hOldMainBitmap = (HBITMAP)SelectObject(hMainDC, hSourceBitmap); + HBITMAP hOldAndMaskBitmap = (HBITMAP)SelectObject(hAndMaskDC, hAndMaskBitmap); + HBITMAP hOldXorMaskBitmap = (HBITMAP)SelectObject(hXorMaskDC, hXorMaskBitmap); + + // Assign the monochrome AND mask bitmap pixels so that a pixels of the source bitmap + // with 'clrTransparent' will be white pixels of the monochrome bitmap + SetBkColor(hMainDC, clrTransparent); + BitBlt(hAndMaskDC, 0, 0, bm.bmWidth, bm.bmHeight, hMainDC, 0, 0, SRCCOPY); + + // Assign the color XOR mask bitmap pixels so that a pixels of the source bitmap + // with 'clrTransparent' will be black and rest the pixels same as corresponding + // pixels of the source bitmap + SetBkColor(hXorMaskDC, RGB(0, 0, 0)); + SetTextColor(hXorMaskDC, RGB(255, 255, 255)); + BitBlt(hXorMaskDC, 0, 0, bm.bmWidth, bm.bmHeight, hAndMaskDC, 0, 0, SRCCOPY); + BitBlt(hXorMaskDC, 0, 0, bm.bmWidth, bm.bmHeight, hMainDC, 0, 0, SRCAND); + + // Deselect bitmaps from the helper DC + SelectObject(hMainDC, hOldMainBitmap); + SelectObject(hAndMaskDC, hOldAndMaskBitmap); + SelectObject(hXorMaskDC, hOldXorMaskBitmap); + + // Delete the helper DC + DeleteDC(hXorMaskDC); + DeleteDC(hAndMaskDC); + DeleteDC(hMainDC); +} + +void DisplayServerWindows::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()); + + UINT image_size = texture_size.width * texture_size.height; + + // Create the BITMAP with alpha channel + COLORREF *buffer = (COLORREF *)memalloc(sizeof(COLORREF) * image_size); + + for (UINT index = 0; index < image_size; index++) { + int row_index = floor(index / texture_size.width) + atlas_rect.position.y; + int column_index = (index % 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); + } + + *(buffer + index) = image->get_pixel(column_index, row_index).to_argb32(); + } + + // Using 4 channels, so 4 * 8 bits + HBITMAP bitmap = CreateBitmap(texture_size.width, texture_size.height, 1, 4 * 8, buffer); + COLORREF clrTransparent = -1; + + // Create the AND and XOR masks for the bitmap + HBITMAP hAndMask = NULL; + HBITMAP hXorMask = NULL; + + GetMaskBitmaps(bitmap, clrTransparent, hAndMask, hXorMask); + + if (NULL == hAndMask || NULL == hXorMask) { + memfree(buffer); + DeleteObject(bitmap); + return; + } + + // Finally, create the icon + ICONINFO iconinfo; + iconinfo.fIcon = FALSE; + iconinfo.xHotspot = p_hotspot.x; + iconinfo.yHotspot = p_hotspot.y; + iconinfo.hbmMask = hAndMask; + iconinfo.hbmColor = hXorMask; + + if (cursors[p_shape]) + DestroyIcon(cursors[p_shape]); + + cursors[p_shape] = CreateIconIndirect(&iconinfo); + + 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) { + SetCursor(cursors[p_shape]); + } + } + + if (hAndMask != NULL) { + DeleteObject(hAndMask); + } + + if (hXorMask != NULL) { + DeleteObject(hXorMask); + } + + memfree(buffer); + DeleteObject(bitmap); + } else { + // Reset to default system cursor + if (cursors[p_shape]) { + DestroyIcon(cursors[p_shape]); + cursors[p_shape] = NULL; + } + + CursorShape c = cursor_shape; + cursor_shape = CURSOR_MAX; + cursor_set_shape(c); + + cursors_cache.erase(p_shape); + } +} + +bool DisplayServerWindows::get_swap_ok_cancel() { + return true; +} + +void DisplayServerWindows::enable_for_stealing_focus(OS::ProcessID pid) { + _THREAD_SAFE_METHOD_ + + AllowSetForegroundWindow(pid); +} + +DisplayServer::LatinKeyboardVariant DisplayServerWindows::get_latin_keyboard_variant() const { + + _THREAD_SAFE_METHOD_ + + unsigned long azerty[] = { + 0x00020401, // Arabic (102) AZERTY + 0x0001080c, // Belgian (Comma) + 0x0000080c, // Belgian French + 0x0000040c, // French + 0 // <--- STOP MARK + }; + unsigned long qwertz[] = { + 0x0000041a, // Croation + 0x00000405, // Czech + 0x00000407, // German + 0x00010407, // German (IBM) + 0x0000040e, // Hungarian + 0x0000046e, // Luxembourgish + 0x00010415, // Polish (214) + 0x00000418, // Romanian (Legacy) + 0x0000081a, // Serbian (Latin) + 0x0000041b, // Slovak + 0x00000424, // Slovenian + 0x0001042e, // Sorbian Extended + 0x0002042e, // Sorbian Standard + 0x0000042e, // Sorbian Standard (Legacy) + 0x0000100c, // Swiss French + 0x00000807, // Swiss German + 0 // <--- STOP MARK + }; + unsigned long dvorak[] = { + 0x00010409, // US-Dvorak + 0x00030409, // US-Dvorak for left hand + 0x00040409, // US-Dvorak for right hand + 0 // <--- STOP MARK + }; + + char name[KL_NAMELENGTH + 1]; + name[0] = 0; + GetKeyboardLayoutNameA(name); + + unsigned long hex = strtoul(name, NULL, 16); + + int i = 0; + while (azerty[i] != 0) { + if (azerty[i] == hex) return LATIN_KEYBOARD_AZERTY; + i++; + } + + i = 0; + while (qwertz[i] != 0) { + if (qwertz[i] == hex) return LATIN_KEYBOARD_QWERTZ; + i++; + } + + i = 0; + while (dvorak[i] != 0) { + if (dvorak[i] == hex) return LATIN_KEYBOARD_DVORAK; + i++; + } + + return LATIN_KEYBOARD_QWERTY; +} + +void DisplayServerWindows::process_events() { + + _THREAD_SAFE_METHOD_ + + MSG msg; + + if (!drop_events) { + joypad->process_joypads(); + } + + while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) { + + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + + if (!drop_events) { + _process_key_events(); + InputFilter::get_singleton()->flush_accumulated_events(); + } +} + +void DisplayServerWindows::force_process_and_drop_events() { + + _THREAD_SAFE_METHOD_ + + drop_events = true; + process_events(); + drop_events = false; +} + +void DisplayServerWindows::release_rendering_thread() { +} +void DisplayServerWindows::make_rendering_thread() { +} +void DisplayServerWindows::swap_buffers() { +} + +void DisplayServerWindows::set_native_icon(const String &p_filename) { + + _THREAD_SAFE_METHOD_ + + FileAccess *f = FileAccess::open(p_filename, FileAccess::READ); + ERR_FAIL_COND_MSG(!f, "Cannot open file with icon '" + p_filename + "'."); + + ICONDIR *icon_dir = (ICONDIR *)memalloc(sizeof(ICONDIR)); + int pos = 0; + + icon_dir->idReserved = f->get_32(); + pos += sizeof(WORD); + f->seek(pos); + + icon_dir->idType = f->get_32(); + pos += sizeof(WORD); + f->seek(pos); + + ERR_FAIL_COND_MSG(icon_dir->idType != 1, "Invalid icon file format!"); + + icon_dir->idCount = f->get_32(); + pos += sizeof(WORD); + f->seek(pos); + + icon_dir = (ICONDIR *)memrealloc(icon_dir, 3 * sizeof(WORD) + icon_dir->idCount * sizeof(ICONDIRENTRY)); + f->get_buffer((uint8_t *)&icon_dir->idEntries[0], icon_dir->idCount * sizeof(ICONDIRENTRY)); + + int small_icon_index = -1; // Select 16x16 with largest color count + int small_icon_cc = 0; + int big_icon_index = -1; // Select largest + int big_icon_width = 16; + int big_icon_cc = 0; + + for (int i = 0; i < icon_dir->idCount; i++) { + int colors = (icon_dir->idEntries[i].bColorCount == 0) ? 32768 : icon_dir->idEntries[i].bColorCount; + int width = (icon_dir->idEntries[i].bWidth == 0) ? 256 : icon_dir->idEntries[i].bWidth; + if (width == 16) { + if (colors >= small_icon_cc) { + small_icon_index = i; + small_icon_cc = colors; + } + } + if (width >= big_icon_width) { + if (colors >= big_icon_cc) { + big_icon_index = i; + big_icon_width = width; + big_icon_cc = colors; + } + } + } + + ERR_FAIL_COND_MSG(big_icon_index == -1, "No valid icons found!"); + + if (small_icon_index == -1) { + WARN_PRINT("No small icon found, reusing " + itos(big_icon_width) + "x" + itos(big_icon_width) + " @" + itos(big_icon_cc) + " icon!"); + small_icon_index = big_icon_index; + small_icon_cc = big_icon_cc; + } + + // Read the big icon + DWORD bytecount_big = icon_dir->idEntries[big_icon_index].dwBytesInRes; + Vector<uint8_t> data_big; + data_big.resize(bytecount_big); + pos = icon_dir->idEntries[big_icon_index].dwImageOffset; + f->seek(pos); + f->get_buffer((uint8_t *)&data_big.write[0], bytecount_big); + HICON icon_big = CreateIconFromResource((PBYTE)&data_big.write[0], bytecount_big, TRUE, 0x00030000); + ERR_FAIL_COND_MSG(!icon_big, "Could not create " + itos(big_icon_width) + "x" + itos(big_icon_width) + " @" + itos(big_icon_cc) + " icon, error: " + format_error_message(GetLastError()) + "."); + + // Read the small icon + DWORD bytecount_small = icon_dir->idEntries[small_icon_index].dwBytesInRes; + Vector<uint8_t> data_small; + data_small.resize(bytecount_small); + pos = icon_dir->idEntries[small_icon_index].dwImageOffset; + f->seek(pos); + f->get_buffer((uint8_t *)&data_small.write[0], bytecount_small); + HICON icon_small = CreateIconFromResource((PBYTE)&data_small.write[0], bytecount_small, TRUE, 0x00030000); + ERR_FAIL_COND_MSG(!icon_small, "Could not create 16x16 @" + itos(small_icon_cc) + " icon, error: " + format_error_message(GetLastError()) + "."); + + // Online tradition says to be sure last error is cleared and set the small icon first + int err = 0; + SetLastError(err); + + SendMessage(windows[MAIN_WINDOW_ID].hWnd, WM_SETICON, ICON_SMALL, (LPARAM)icon_small); + err = GetLastError(); + ERR_FAIL_COND_MSG(err, "Error setting ICON_SMALL: " + format_error_message(err) + "."); + + SendMessage(windows[MAIN_WINDOW_ID].hWnd, WM_SETICON, ICON_BIG, (LPARAM)icon_big); + err = GetLastError(); + ERR_FAIL_COND_MSG(err, "Error setting ICON_BIG: " + format_error_message(err) + "."); + + memdelete(f); + memdelete(icon_dir); +} +void DisplayServerWindows::set_icon(const Ref<Image> &p_icon) { + + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!p_icon.is_valid()); + Ref<Image> icon = p_icon->duplicate(); + if (icon->get_format() != Image::FORMAT_RGBA8) + icon->convert(Image::FORMAT_RGBA8); + int w = icon->get_width(); + int h = icon->get_height(); + + /* Create temporary bitmap buffer */ + int icon_len = 40 + h * w * 4; + Vector<BYTE> v; + v.resize(icon_len); + BYTE *icon_bmp = v.ptrw(); + + encode_uint32(40, &icon_bmp[0]); + encode_uint32(w, &icon_bmp[4]); + encode_uint32(h * 2, &icon_bmp[8]); + encode_uint16(1, &icon_bmp[12]); + encode_uint16(32, &icon_bmp[14]); + encode_uint32(BI_RGB, &icon_bmp[16]); + encode_uint32(w * h * 4, &icon_bmp[20]); + encode_uint32(0, &icon_bmp[24]); + encode_uint32(0, &icon_bmp[28]); + encode_uint32(0, &icon_bmp[32]); + encode_uint32(0, &icon_bmp[36]); + + uint8_t *wr = &icon_bmp[40]; + const uint8_t *r = icon->get_data().ptr(); + + for (int i = 0; i < h; i++) { + + for (int j = 0; j < w; j++) { + + const uint8_t *rpx = &r[((h - i - 1) * w + j) * 4]; + uint8_t *wpx = &wr[(i * w + j) * 4]; + wpx[0] = rpx[2]; + wpx[1] = rpx[1]; + wpx[2] = rpx[0]; + wpx[3] = rpx[3]; + } + } + + HICON hicon = CreateIconFromResource(icon_bmp, icon_len, TRUE, 0x00030000); + + /* Set the icon for the window */ + SendMessage(windows[MAIN_WINDOW_ID].hWnd, WM_SETICON, ICON_SMALL, (LPARAM)hicon); + + /* Set the icon in the task manager (should we do this?) */ + SendMessage(windows[MAIN_WINDOW_ID].hWnd, WM_SETICON, ICON_BIG, (LPARAM)hicon); +} + +void DisplayServerWindows::vsync_set_use_via_compositor(bool p_enable) { +} +bool DisplayServerWindows::vsync_is_using_via_compositor() const { + return false; +} + +void DisplayServerWindows::set_context(Context p_context) { +} + +#define MI_WP_SIGNATURE 0xFF515700 +#define SIGNATURE_MASK 0xFFFFFF00 +// Keeping the name suggested by Microsoft, but this macro really answers: +// Is this mouse event emulated from touch or pen input? +#define IsPenEvent(dw) (((dw)&SIGNATURE_MASK) == MI_WP_SIGNATURE) +// This one tells whether the event comes from touchscreen (and not from pen) +#define IsTouchEvent(dw) (IsPenEvent(dw) && ((dw)&0x80)) + +void DisplayServerWindows::_touch_event(WindowID p_window, bool p_pressed, float p_x, float p_y, int idx) { + + // Defensive + if (touch_state.has(idx) == p_pressed) + return; + + if (p_pressed) { + touch_state.insert(idx, Vector2(p_x, p_y)); + } else { + touch_state.erase(idx); + } + + Ref<InputEventScreenTouch> event; + event.instance(); + event->set_index(idx); + event->set_window_id(p_window); + event->set_pressed(p_pressed); + event->set_position(Vector2(p_x, p_y)); + + InputFilter::get_singleton()->accumulate_input_event(event); +} + +void DisplayServerWindows::_drag_event(WindowID p_window, float p_x, float p_y, int idx) { + + Map<int, Vector2>::Element *curr = touch_state.find(idx); + // Defensive + if (!curr) + return; + + if (curr->get() == Vector2(p_x, p_y)) + return; + + Ref<InputEventScreenDrag> event; + event.instance(); + event->set_window_id(p_window); + event->set_index(idx); + event->set_position(Vector2(p_x, p_y)); + event->set_relative(Vector2(p_x, p_y) - curr->get()); + + InputFilter::get_singleton()->accumulate_input_event(event); + + curr->get() = Vector2(p_x, p_y); +} + +void DisplayServerWindows::_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); + } +} + +void DisplayServerWindows::_dispatch_input_events(const Ref<InputEvent> &p_event) { + ((DisplayServerWindows *)(get_singleton()))->_dispatch_input_event(p_event); +} + +void DisplayServerWindows::_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 + ERR_FAIL_COND(!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); + } + } +} + +LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + + if (drop_events) { + + if (user_proc) { + + return CallWindowProcW(user_proc, hWnd, uMsg, wParam, lParam); + } else { + return DefWindowProcW(hWnd, uMsg, wParam, lParam); + } + }; + + WindowID window_id = INVALID_WINDOW_ID; + + for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { + if (E->get().hWnd == hWnd) { + window_id = E->key(); + break; + } + } + + switch (uMsg) // Check For Windows Messages + { + case WM_SETFOCUS: { + + windows[window_id].window_has_focus = true; + last_focused_window = window_id; + + // Restore mouse mode + _set_mouse_mode_impl(mouse_mode); + + break; + } + case WM_KILLFOCUS: { + windows[window_id].window_has_focus = false; + last_focused_window = window_id; + + // Release capture unconditionally because it can be set due to dragging, in addition to captured mode + ReleaseCapture(); + + // Release every touch to avoid sticky points + for (Map<int, Vector2>::Element *E = touch_state.front(); E; E = E->next()) { + _touch_event(window_id, false, E->get().x, E->get().y, E->key()); + } + touch_state.clear(); + + break; + } + case WM_ACTIVATE: // Watch For Window Activate Message + { + windows[window_id].minimized = HIWORD(wParam) != 0; + + if (LOWORD(wParam) == WA_ACTIVE || LOWORD(wParam) == WA_CLICKACTIVE) { + + _send_window_event(windows[window_id], WINDOW_EVENT_FOCUS_IN); + windows[window_id].window_focused = true; + alt_mem = false; + control_mem = false; + shift_mem = false; + } else { // WM_INACTIVE + InputFilter::get_singleton()->release_pressed_events(); + _send_window_event(windows[window_id], WINDOW_EVENT_FOCUS_OUT); + windows[window_id].window_focused = false; + alt_mem = false; + }; + + return 0; // Return To The Message Loop + } + case WM_GETMINMAXINFO: { + if (windows[window_id].resizable && !windows[window_id].fullscreen) { + Size2 decor = window_get_size(window_id) - window_get_real_size(window_id); // Size of window decorations + MINMAXINFO *min_max_info = (MINMAXINFO *)lParam; + if (windows[window_id].min_size != Size2()) { + min_max_info->ptMinTrackSize.x = windows[window_id].min_size.x + decor.x; + min_max_info->ptMinTrackSize.y = windows[window_id].min_size.y + decor.y; + } + if (windows[window_id].max_size != Size2()) { + min_max_info->ptMaxTrackSize.x = windows[window_id].max_size.x + decor.x; + min_max_info->ptMaxTrackSize.y = windows[window_id].max_size.y + decor.y; + } + return 0; + } else { + break; + } + } + case WM_PAINT: + + Main::force_redraw(); + break; + + case WM_SYSCOMMAND: // Intercept System Commands + { + switch (wParam) // Check System Calls + { + case SC_SCREENSAVE: // Screensaver Trying To Start? + case SC_MONITORPOWER: // Monitor Trying To Enter Powersave? + return 0; // Prevent From Happening + case SC_KEYMENU: + if ((lParam >> 16) <= 0) + return 0; + } + break; // Exit + } + + case WM_CLOSE: // Did We Receive A Close Message? + { + + _send_window_event(windows[window_id], WINDOW_EVENT_CLOSE_REQUEST); + + //force_quit=true; + return 0; // Jump Back + } + case WM_MOUSELEAVE: { + + old_invalid = true; + outside = true; + + _send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_EXIT); + + } break; + case WM_INPUT: { + if (mouse_mode != MOUSE_MODE_CAPTURED || !use_raw_input) { + break; + } + + UINT dwSize; + + GetRawInputData((HRAWINPUT)lParam, RID_INPUT, NULL, &dwSize, sizeof(RAWINPUTHEADER)); + LPBYTE lpb = new BYTE[dwSize]; + if (lpb == NULL) { + return 0; + } + + if (GetRawInputData((HRAWINPUT)lParam, RID_INPUT, lpb, &dwSize, sizeof(RAWINPUTHEADER)) != dwSize) + OutputDebugString(TEXT("GetRawInputData does not return correct size !\n")); + + RAWINPUT *raw = (RAWINPUT *)lpb; + + if (raw->header.dwType == RIM_TYPEMOUSE) { + Ref<InputEventMouseMotion> mm; + mm.instance(); + + mm->set_window_id(window_id); + mm->set_control(control_mem); + mm->set_shift(shift_mem); + mm->set_alt(alt_mem); + + mm->set_button_mask(last_button_state); + + Point2i c(windows[window_id].width / 2, windows[window_id].height / 2); + + // centering just so it works as before + POINT pos = { (int)c.x, (int)c.y }; + ClientToScreen(windows[window_id].hWnd, &pos); + SetCursorPos(pos.x, pos.y); + + mm->set_position(c); + mm->set_global_position(c); + InputFilter::get_singleton()->set_mouse_position(c); + mm->set_speed(Vector2(0, 0)); + + if (raw->data.mouse.usFlags == MOUSE_MOVE_RELATIVE) { + mm->set_relative(Vector2(raw->data.mouse.lLastX, raw->data.mouse.lLastY)); + + } else if (raw->data.mouse.usFlags == MOUSE_MOVE_ABSOLUTE) { + + int nScreenWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN); + int nScreenHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN); + int nScreenLeft = GetSystemMetrics(SM_XVIRTUALSCREEN); + int nScreenTop = GetSystemMetrics(SM_YVIRTUALSCREEN); + + Vector2 abs_pos( + (double(raw->data.mouse.lLastX) - 65536.0 / (nScreenWidth)) * nScreenWidth / 65536.0 + nScreenLeft, + (double(raw->data.mouse.lLastY) - 65536.0 / (nScreenHeight)) * nScreenHeight / 65536.0 + nScreenTop); + + POINT coords; //client coords + coords.x = abs_pos.x; + coords.y = abs_pos.y; + + ScreenToClient(hWnd, &coords); + + mm->set_relative(Vector2(coords.x - old_x, coords.y - old_y)); + old_x = coords.x; + old_y = coords.y; + + /*Input.mi.dx = (int)((((double)(pos.x)-nScreenLeft) * 65536) / nScreenWidth + 65536 / (nScreenWidth)); + Input.mi.dy = (int)((((double)(pos.y)-nScreenTop) * 65536) / nScreenHeight + 65536 / (nScreenHeight)); + */ + } + + if (windows[window_id].window_has_focus && mm->get_relative() != Vector2()) + InputFilter::get_singleton()->accumulate_input_event(mm); + } + delete[] lpb; + } break; + case WM_POINTERUPDATE: { + if (mouse_mode == MOUSE_MODE_CAPTURED && use_raw_input) { + break; + } + + if (!win8p_GetPointerType || !win8p_GetPointerPenInfo) { + break; + } + + uint32_t pointer_id = LOWORD(wParam); + POINTER_INPUT_TYPE pointer_type = PT_POINTER; + if (!win8p_GetPointerType(pointer_id, &pointer_type)) { + break; + } + + if (pointer_type != PT_PEN) { + break; + } + + POINTER_PEN_INFO pen_info; + if (!win8p_GetPointerPenInfo(pointer_id, &pen_info)) { + break; + } + + if (InputFilter::get_singleton()->is_emulating_mouse_from_touch()) { + // Universal translation enabled; ignore OS translation + LPARAM extra = GetMessageExtraInfo(); + if (IsTouchEvent(extra)) { + break; + } + } + + if (outside) { + //mouse enter + + if (mouse_mode != MOUSE_MODE_CAPTURED) { + _send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_ENTER); + } + + CursorShape c = cursor_shape; + cursor_shape = CURSOR_MAX; + cursor_set_shape(c); + outside = false; + + //Once-Off notification, must call again.... + TRACKMOUSEEVENT tme; + tme.cbSize = sizeof(TRACKMOUSEEVENT); + tme.dwFlags = TME_LEAVE; + tme.hwndTrack = hWnd; + tme.dwHoverTime = HOVER_DEFAULT; + TrackMouseEvent(&tme); + } + + // Don't calculate relative mouse movement if we don't have focus in CAPTURED mode. + if (!windows[window_id].window_has_focus && mouse_mode == MOUSE_MODE_CAPTURED) + break; + + Ref<InputEventMouseMotion> mm; + mm.instance(); + + mm->set_window_id(window_id); + mm->set_pressure(pen_info.pressure ? (float)pen_info.pressure / 1024 : 0); + mm->set_tilt(Vector2(pen_info.tiltX ? (float)pen_info.tiltX / 90 : 0, pen_info.tiltY ? (float)pen_info.tiltY / 90 : 0)); + + mm->set_control((wParam & MK_CONTROL) != 0); + mm->set_shift((wParam & MK_SHIFT) != 0); + mm->set_alt(alt_mem); + + mm->set_button_mask(last_button_state); + + POINT coords; //client coords + coords.x = GET_X_LPARAM(lParam); + coords.y = GET_Y_LPARAM(lParam); + + ScreenToClient(windows[window_id].hWnd, &coords); + + mm->set_position(Vector2(coords.x, coords.y)); + mm->set_global_position(Vector2(coords.x, coords.y)); + + if (mouse_mode == MOUSE_MODE_CAPTURED) { + + Point2i c(windows[window_id].width / 2, windows[window_id].height / 2); + old_x = c.x; + old_y = c.y; + + if (mm->get_position() == c) { + center = c; + return 0; + } + + Point2i ncenter = mm->get_position(); + center = ncenter; + POINT pos = { (int)c.x, (int)c.y }; + ClientToScreen(hWnd, &pos); + SetCursorPos(pos.x, pos.y); + } + + InputFilter::get_singleton()->set_mouse_position(mm->get_position()); + mm->set_speed(InputFilter::get_singleton()->get_last_mouse_speed()); + + if (old_invalid) { + + old_x = mm->get_position().x; + old_y = mm->get_position().y; + old_invalid = false; + } + + mm->set_relative(Vector2(mm->get_position() - Vector2(old_x, old_y))); + old_x = mm->get_position().x; + old_y = mm->get_position().y; + if (windows[window_id].window_has_focus) { + InputFilter::get_singleton()->parse_input_event(mm); + } + + return 0; //Pointer event handled return 0 to avoid duplicate WM_MOUSEMOVE event + } break; + case WM_MOUSEMOVE: { + if (mouse_mode == MOUSE_MODE_CAPTURED && use_raw_input) { + break; + } + + if (InputFilter::get_singleton()->is_emulating_mouse_from_touch()) { + // Universal translation enabled; ignore OS translation + LPARAM extra = GetMessageExtraInfo(); + if (IsTouchEvent(extra)) { + break; + } + } + + if (outside) { + //mouse enter + + if (mouse_mode != MOUSE_MODE_CAPTURED) { + _send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_ENTER); + } + + CursorShape c = cursor_shape; + cursor_shape = CURSOR_MAX; + cursor_set_shape(c); + outside = false; + + //Once-Off notification, must call again.... + TRACKMOUSEEVENT tme; + tme.cbSize = sizeof(TRACKMOUSEEVENT); + tme.dwFlags = TME_LEAVE; + tme.hwndTrack = hWnd; + tme.dwHoverTime = HOVER_DEFAULT; + TrackMouseEvent(&tme); + } + + // Don't calculate relative mouse movement if we don't have focus in CAPTURED mode. + if (!windows[window_id].window_has_focus && mouse_mode == MOUSE_MODE_CAPTURED) + break; + + Ref<InputEventMouseMotion> mm; + mm.instance(); + mm->set_window_id(window_id); + mm->set_control((wParam & MK_CONTROL) != 0); + mm->set_shift((wParam & MK_SHIFT) != 0); + mm->set_alt(alt_mem); + + mm->set_button_mask(last_button_state); + + mm->set_position(Vector2(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))); + mm->set_global_position(Vector2(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))); + + if (mouse_mode == MOUSE_MODE_CAPTURED) { + + Point2i c(windows[window_id].width / 2, windows[window_id].height / 2); + old_x = c.x; + old_y = c.y; + + if (mm->get_position() == c) { + center = c; + return 0; + } + + Point2i ncenter = mm->get_position(); + center = ncenter; + POINT pos = { (int)c.x, (int)c.y }; + ClientToScreen(windows[window_id].hWnd, &pos); + SetCursorPos(pos.x, pos.y); + } + + InputFilter::get_singleton()->set_mouse_position(mm->get_position()); + mm->set_speed(InputFilter::get_singleton()->get_last_mouse_speed()); + + if (old_invalid) { + + old_x = mm->get_position().x; + old_y = mm->get_position().y; + old_invalid = false; + } + + mm->set_relative(Vector2(mm->get_position() - Vector2(old_x, old_y))); + old_x = mm->get_position().x; + old_y = mm->get_position().y; + if (windows[window_id].window_has_focus) + InputFilter::get_singleton()->accumulate_input_event(mm); + + } break; + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + if (InputFilter::get_singleton()->is_emulating_mouse_from_touch()) { + // Universal translation enabled; ignore OS translations for left button + LPARAM extra = GetMessageExtraInfo(); + if (IsTouchEvent(extra)) { + break; + } + } + [[fallthrough]]; + case WM_MBUTTONDOWN: + case WM_MBUTTONUP: + case WM_RBUTTONDOWN: + case WM_RBUTTONUP: + case WM_MOUSEWHEEL: + case WM_MOUSEHWHEEL: + case WM_LBUTTONDBLCLK: + case WM_MBUTTONDBLCLK: + case WM_RBUTTONDBLCLK: + case WM_XBUTTONDBLCLK: + case WM_XBUTTONDOWN: + case WM_XBUTTONUP: { + + Ref<InputEventMouseButton> mb; + mb.instance(); + mb->set_window_id(window_id); + + switch (uMsg) { + case WM_LBUTTONDOWN: { + mb->set_pressed(true); + mb->set_button_index(1); + } break; + case WM_LBUTTONUP: { + mb->set_pressed(false); + mb->set_button_index(1); + } break; + case WM_MBUTTONDOWN: { + mb->set_pressed(true); + mb->set_button_index(3); + } break; + case WM_MBUTTONUP: { + mb->set_pressed(false); + mb->set_button_index(3); + } break; + case WM_RBUTTONDOWN: { + mb->set_pressed(true); + mb->set_button_index(2); + } break; + case WM_RBUTTONUP: { + mb->set_pressed(false); + mb->set_button_index(2); + } break; + case WM_LBUTTONDBLCLK: { + mb->set_pressed(true); + mb->set_button_index(1); + mb->set_doubleclick(true); + } break; + case WM_RBUTTONDBLCLK: { + mb->set_pressed(true); + mb->set_button_index(2); + mb->set_doubleclick(true); + } break; + case WM_MBUTTONDBLCLK: { + mb->set_pressed(true); + mb->set_button_index(3); + mb->set_doubleclick(true); + } break; + case WM_MOUSEWHEEL: { + + mb->set_pressed(true); + int motion = (short)HIWORD(wParam); + if (!motion) + return 0; + + if (motion > 0) + mb->set_button_index(BUTTON_WHEEL_UP); + else + mb->set_button_index(BUTTON_WHEEL_DOWN); + + } break; + case WM_MOUSEHWHEEL: { + + mb->set_pressed(true); + int motion = (short)HIWORD(wParam); + if (!motion) + return 0; + + if (motion < 0) { + mb->set_button_index(BUTTON_WHEEL_LEFT); + mb->set_factor(fabs((double)motion / (double)WHEEL_DELTA)); + } else { + mb->set_button_index(BUTTON_WHEEL_RIGHT); + mb->set_factor(fabs((double)motion / (double)WHEEL_DELTA)); + } + } break; + case WM_XBUTTONDOWN: { + + mb->set_pressed(true); + if (HIWORD(wParam) == XBUTTON1) + mb->set_button_index(BUTTON_XBUTTON1); + else + mb->set_button_index(BUTTON_XBUTTON2); + } break; + case WM_XBUTTONUP: { + + mb->set_pressed(false); + if (HIWORD(wParam) == XBUTTON1) + mb->set_button_index(BUTTON_XBUTTON1); + else + mb->set_button_index(BUTTON_XBUTTON2); + } break; + case WM_XBUTTONDBLCLK: { + + mb->set_pressed(true); + if (HIWORD(wParam) == XBUTTON1) + mb->set_button_index(BUTTON_XBUTTON1); + else + mb->set_button_index(BUTTON_XBUTTON2); + mb->set_doubleclick(true); + } break; + default: { + return 0; + } + } + + mb->set_control((wParam & MK_CONTROL) != 0); + mb->set_shift((wParam & MK_SHIFT) != 0); + mb->set_alt(alt_mem); + //mb->get_alt()=(wParam&MK_MENU)!=0; + if (mb->is_pressed()) + last_button_state |= (1 << (mb->get_button_index() - 1)); + else + last_button_state &= ~(1 << (mb->get_button_index() - 1)); + mb->set_button_mask(last_button_state); + + mb->set_position(Vector2(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))); + + if (mouse_mode == MOUSE_MODE_CAPTURED && !use_raw_input) { + + mb->set_position(Vector2(old_x, old_y)); + } + + if (uMsg != WM_MOUSEWHEEL && uMsg != WM_MOUSEHWHEEL) { + if (mb->is_pressed()) { + + if (++pressrc > 0 && mouse_mode != MOUSE_MODE_CAPTURED) + SetCapture(hWnd); + } else { + + if (--pressrc <= 0) { + if (mouse_mode != MOUSE_MODE_CAPTURED) { + ReleaseCapture(); + } + pressrc = 0; + } + } + } else { + // for reasons unknown to mankind, wheel comes in screen coordinates + POINT coords; + coords.x = mb->get_position().x; + coords.y = mb->get_position().y; + + ScreenToClient(hWnd, &coords); + + mb->set_position(Vector2(coords.x, coords.y)); + } + + mb->set_global_position(mb->get_position()); + + InputFilter::get_singleton()->accumulate_input_event(mb); + if (mb->is_pressed() && mb->get_button_index() > 3 && mb->get_button_index() < 8) { + //send release for mouse wheel + Ref<InputEventMouseButton> mbd = mb->duplicate(); + mbd->set_window_id(window_id); + last_button_state &= ~(1 << (mbd->get_button_index() - 1)); + mbd->set_button_mask(last_button_state); + mbd->set_pressed(false); + InputFilter::get_singleton()->accumulate_input_event(mbd); + } + + } break; + + case WM_MOVE: { + if (!IsIconic(windows[window_id].hWnd)) { + int x = int16_t(LOWORD(lParam)); + int y = int16_t(HIWORD(lParam)); + windows[window_id].last_pos = Point2(x, y); + + if (!windows[window_id].rect_changed_callback.is_null()) { + + Variant size = Rect2i(windows[window_id].last_pos.x, windows[window_id].last_pos.y, windows[window_id].width, windows[window_id].height); + Variant *sizep = &size; + Variant ret; + Callable::CallError ce; + windows[window_id].rect_changed_callback.call((const Variant **)&sizep, 1, ret, ce); + } + } + } break; + + case WM_SIZE: { + // Ignore size when a SIZE_MINIMIZED event is triggered + if (wParam != SIZE_MINIMIZED) { + int window_w = LOWORD(lParam); + int window_h = HIWORD(lParam); + if (window_w > 0 && window_h > 0 && !windows[window_id].preserve_window_size) { + windows[window_id].width = window_w; + windows[window_id].height = window_h; + +#if defined(VULKAN_ENABLED) + if (rendering_driver == "vulkan") { + context_vulkan->window_resize(window_id, windows[window_id].width, windows[window_id].height); + } +#endif + + } else { + windows[window_id].preserve_window_size = false; + window_set_size(Size2(windows[window_id].width, windows[window_id].height), window_id); + } + } + + if (!windows[window_id].rect_changed_callback.is_null()) { + + Variant size = Rect2i(windows[window_id].last_pos.x, windows[window_id].last_pos.y, windows[window_id].width, windows[window_id].height); + Variant *sizep = &size; + Variant ret; + Callable::CallError ce; + windows[window_id].rect_changed_callback.call((const Variant **)&sizep, 1, ret, ce); + } + + if (wParam == SIZE_MAXIMIZED) { + windows[window_id].maximized = true; + windows[window_id].minimized = false; + } else if (wParam == SIZE_MINIMIZED) { + windows[window_id].maximized = false; + windows[window_id].minimized = true; + } else if (wParam == SIZE_RESTORED) { + windows[window_id].maximized = false; + windows[window_id].minimized = false; + } +#if 0 + if (is_layered_allowed() && layered_window) { + DeleteObject(hBitmap); + + RECT r; + GetWindowRect(hWnd, &r); + dib_size = Size2i(r.right - r.left, r.bottom - r.top); + + BITMAPINFO bmi; + ZeroMemory(&bmi, sizeof(BITMAPINFO)); + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = dib_size.x; + bmi.bmiHeader.biHeight = dib_size.y; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; + bmi.bmiHeader.biCompression = BI_RGB; + bmi.bmiHeader.biSizeImage = dib_size.x * dib_size.y * 4; + hBitmap = CreateDIBSection(hDC_dib, &bmi, DIB_RGB_COLORS, (void **)&dib_data, NULL, 0x0); + SelectObject(hDC_dib, hBitmap); + + ZeroMemory(dib_data, dib_size.x * dib_size.y * 4); + } +#endif + //return 0; // Jump Back + } break; + + case WM_ENTERSIZEMOVE: { + InputFilter::get_singleton()->release_pressed_events(); + move_timer_id = SetTimer(windows[window_id].hWnd, 1, USER_TIMER_MINIMUM, (TIMERPROC)NULL); + } break; + case WM_EXITSIZEMOVE: { + KillTimer(windows[window_id].hWnd, move_timer_id); + } break; + case WM_TIMER: { + if (wParam == move_timer_id) { + _process_key_events(); + if (!Main::is_iterating()) { + Main::iteration(); + } + } + } break; + + case WM_SYSKEYDOWN: + case WM_SYSKEYUP: + case WM_KEYUP: + case WM_KEYDOWN: { + + if (wParam == VK_SHIFT) + shift_mem = uMsg == WM_KEYDOWN; + if (wParam == VK_CONTROL) + control_mem = uMsg == WM_KEYDOWN; + if (wParam == VK_MENU) { + alt_mem = (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN); + if (lParam & (1 << 24)) + gr_mem = alt_mem; + } + + if (mouse_mode == MOUSE_MODE_CAPTURED) { + // When SetCapture is used, ALT+F4 hotkey is ignored by Windows, so handle it ourselves + if (wParam == VK_F4 && alt_mem && (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN)) { + _send_window_event(windows[window_id], WINDOW_EVENT_CLOSE_REQUEST); + } + } + /* + if (wParam==VK_WIN) TODO wtf is this? + meta_mem=uMsg==WM_KEYDOWN; + */ + [[fallthrough]]; + } + case WM_CHAR: { + + ERR_BREAK(key_event_pos >= KEY_EVENT_BUFFER_SIZE); + + // Make sure we don't include modifiers for the modifier key itself. + KeyEvent ke; + ke.shift = (wParam != VK_SHIFT) ? shift_mem : false; + ke.alt = (!(wParam == VK_MENU && (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN))) ? alt_mem : false; + ke.control = (wParam != VK_CONTROL) ? control_mem : false; + ke.meta = meta_mem; + ke.uMsg = uMsg; + ke.window_id = window_id; + + if (ke.uMsg == WM_SYSKEYDOWN) + ke.uMsg = WM_KEYDOWN; + if (ke.uMsg == WM_SYSKEYUP) + ke.uMsg = WM_KEYUP; + + ke.wParam = wParam; + ke.lParam = lParam; + key_event_buffer[key_event_pos++] = ke; + + } break; + case WM_INPUTLANGCHANGEREQUEST: { + + // FIXME: Do something? + } break; + + case WM_TOUCH: { + + BOOL bHandled = FALSE; + UINT cInputs = LOWORD(wParam); + PTOUCHINPUT pInputs = memnew_arr(TOUCHINPUT, cInputs); + if (pInputs) { + if (GetTouchInputInfo((HTOUCHINPUT)lParam, cInputs, pInputs, sizeof(TOUCHINPUT))) { + for (UINT i = 0; i < cInputs; i++) { + TOUCHINPUT ti = pInputs[i]; + POINT touch_pos = { + TOUCH_COORD_TO_PIXEL(ti.x), + TOUCH_COORD_TO_PIXEL(ti.y), + }; + ScreenToClient(hWnd, &touch_pos); + //do something with each touch input entry + if (ti.dwFlags & TOUCHEVENTF_MOVE) { + + _drag_event(window_id, touch_pos.x, touch_pos.y, ti.dwID); + } else if (ti.dwFlags & (TOUCHEVENTF_UP | TOUCHEVENTF_DOWN)) { + + _touch_event(window_id, ti.dwFlags & TOUCHEVENTF_DOWN, touch_pos.x, touch_pos.y, ti.dwID); + }; + } + bHandled = TRUE; + } else { + /* handle the error here */ + } + memdelete_arr(pInputs); + } else { + /* handle the error here, probably out of memory */ + } + if (bHandled) { + CloseTouchInputHandle((HTOUCHINPUT)lParam); + return 0; + }; + + } break; + + case WM_DEVICECHANGE: { + + joypad->probe_joypads(); + } break; + case WM_SETCURSOR: { + if (LOWORD(lParam) == HTCLIENT) { + if (windows[window_id].window_has_focus && (mouse_mode == MOUSE_MODE_HIDDEN || mouse_mode == MOUSE_MODE_CAPTURED)) { + //Hide the cursor + if (hCursor == NULL) + hCursor = SetCursor(NULL); + else + SetCursor(NULL); + } else { + if (hCursor != NULL) { + CursorShape c = cursor_shape; + cursor_shape = CURSOR_MAX; + cursor_set_shape(c); + hCursor = NULL; + } + } + } + + } break; + case WM_DROPFILES: { + + HDROP hDropInfo = (HDROP)wParam; + const int buffsize = 4096; + wchar_t buf[buffsize]; + + int fcount = DragQueryFileW(hDropInfo, 0xFFFFFFFF, NULL, 0); + + Vector<String> files; + + for (int i = 0; i < fcount; i++) { + + DragQueryFileW(hDropInfo, i, buf, buffsize); + String file = buf; + files.push_back(file); + } + + if (files.size() && !windows[window_id].drop_files_callback.is_null()) { + Variant v = files; + Variant *vp = &v; + Variant ret; + Callable::CallError ce; + windows[window_id].drop_files_callback.call((const Variant **)&vp, 1, ret, ce); + } + + } break; + + default: { + + if (user_proc) { + + return CallWindowProcW(user_proc, hWnd, uMsg, wParam, lParam); + }; + }; + } + + return DefWindowProcW(hWnd, uMsg, wParam, lParam); +} + +LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + + DisplayServerWindows *ds_win = static_cast<DisplayServerWindows *>(DisplayServer::get_singleton()); + if (ds_win) + return ds_win->WndProc(hWnd, uMsg, wParam, lParam); + else + return DefWindowProcW(hWnd, uMsg, wParam, lParam); +} + +void DisplayServerWindows::_process_key_events() { + + for (int i = 0; i < key_event_pos; i++) { + + KeyEvent &ke = key_event_buffer[i]; + switch (ke.uMsg) { + + case WM_CHAR: { + if ((i == 0 && ke.uMsg == WM_CHAR) || (i > 0 && key_event_buffer[i - 1].uMsg == WM_CHAR)) { + Ref<InputEventKey> k; + k.instance(); + + k->set_window_id(ke.window_id); + k->set_shift(ke.shift); + k->set_alt(ke.alt); + k->set_control(ke.control); + k->set_metakey(ke.meta); + k->set_pressed(true); + k->set_keycode(KeyMappingWindows::get_keysym(ke.wParam)); + k->set_physical_keycode(KeyMappingWindows::get_scansym((ke.lParam >> 16) & 0xFF, ke.lParam & (1 << 24))); + k->set_unicode(ke.wParam); + if (k->get_unicode() && gr_mem) { + k->set_alt(false); + k->set_control(false); + } + + if (k->get_unicode() < 32) + k->set_unicode(0); + + InputFilter::get_singleton()->accumulate_input_event(k); + } + + //do nothing + } break; + case WM_KEYUP: + case WM_KEYDOWN: { + + Ref<InputEventKey> k; + k.instance(); + + k->set_window_id(ke.window_id); + k->set_shift(ke.shift); + k->set_alt(ke.alt); + k->set_control(ke.control); + k->set_metakey(ke.meta); + + k->set_pressed(ke.uMsg == WM_KEYDOWN); + + if ((ke.lParam & (1 << 24)) && (ke.wParam == VK_RETURN)) { + // Special case for Numpad Enter key + k->set_keycode(KEY_KP_ENTER); + } else { + k->set_keycode(KeyMappingWindows::get_keysym(ke.wParam)); + } + + k->set_physical_keycode(KeyMappingWindows::get_scansym((ke.lParam >> 16) & 0xFF, ke.lParam & (1 << 24))); + + if (i + 1 < key_event_pos && key_event_buffer[i + 1].uMsg == WM_CHAR) { + k->set_unicode(key_event_buffer[i + 1].wParam); + } + if (k->get_unicode() && gr_mem) { + k->set_alt(false); + k->set_control(false); + } + + if (k->get_unicode() < 32) + k->set_unicode(0); + + k->set_echo((ke.uMsg == WM_KEYDOWN && (ke.lParam & (1 << 30)))); + + InputFilter::get_singleton()->accumulate_input_event(k); + + } break; + } + } + + key_event_pos = 0; +} + +DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect) { + + DWORD dwExStyle; + DWORD dwStyle; + + _get_window_style(window_id_counter == MAIN_WINDOW_ID, p_mode == WINDOW_MODE_FULLSCREEN, p_flags & WINDOW_FLAG_BORDERLESS_BIT, !(p_flags & WINDOW_FLAG_RESIZE_DISABLED_BIT), p_mode == WINDOW_MODE_MAXIMIZED, (p_flags & WINDOW_FLAG_NO_FOCUS_BIT), dwStyle, dwExStyle); + + RECT WindowRect; + + WindowRect.left = p_rect.position.x; + WindowRect.right = p_rect.position.x + p_rect.size.x; + WindowRect.top = p_rect.position.y; + WindowRect.bottom = p_rect.position.y + p_rect.size.y; + + AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle); + + WindowID id = window_id_counter; + { + WindowData wd; + + wd.hWnd = CreateWindowExW( + dwExStyle, + L"Engine", L"", + dwStyle, + // (GetSystemMetrics(SM_CXSCREEN) - WindowRect.right) / 2, + // (GetSystemMetrics(SM_CYSCREEN) - WindowRect.bottom) / 2, + WindowRect.left, + WindowRect.top, + WindowRect.right - WindowRect.left, + WindowRect.bottom - WindowRect.top, + NULL, NULL, hInstance, NULL); + if (!wd.hWnd) { + MessageBoxW(NULL, L"Window Creation Error.", L"ERROR", MB_OK | MB_ICONEXCLAMATION); + return INVALID_WINDOW_ID; + } +#ifdef VULKAN_ENABLED + + if (rendering_driver == "vulkan") { + if (context_vulkan->window_create(id, wd.hWnd, hInstance, WindowRect.right - WindowRect.left, WindowRect.bottom - WindowRect.top) == -1) { + memdelete(context_vulkan); + context_vulkan = NULL; + ERR_FAIL_V(INVALID_WINDOW_ID); + } + } +#endif + + RegisterTouchWindow(wd.hWnd, 0); + + TRACKMOUSEEVENT tme; + tme.cbSize = sizeof(TRACKMOUSEEVENT); + tme.dwFlags = TME_LEAVE; + tme.hwndTrack = wd.hWnd; + tme.dwHoverTime = HOVER_DEFAULT; + TrackMouseEvent(&tme); + + DragAcceptFiles(wd.hWnd, true); + + // IME + wd.im_himc = ImmGetContext(wd.hWnd); + ImmReleaseContext(wd.hWnd, wd.im_himc); + + wd.im_position = Vector2(); + wd.last_pos = p_rect.position; + wd.width = p_rect.size.width; + wd.height = p_rect.size.height; + + windows[id] = wd; + + window_id_counter++; + } + + return id; +} + +GetPointerTypePtr DisplayServerWindows::win8p_GetPointerType = nullptr; +GetPointerPenInfoPtr DisplayServerWindows::win8p_GetPointerPenInfo = nullptr; + +typedef enum _SHC_PROCESS_DPI_AWARENESS { + SHC_PROCESS_DPI_UNAWARE = 0, + SHC_PROCESS_SYSTEM_DPI_AWARE = 1, + SHC_PROCESS_PER_MONITOR_DPI_AWARE = 2 +} SHC_PROCESS_DPI_AWARENESS; + +DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { + + //Note: Functions for pen input, available on Windows 8+ + HMODULE user32_lib = LoadLibraryW(L"user32.dll"); + if (user32_lib) { + win8p_GetPointerType = (GetPointerTypePtr)GetProcAddress(user32_lib, "GetPointerType"); + win8p_GetPointerPenInfo = (GetPointerPenInfoPtr)GetProcAddress(user32_lib, "GetPointerPenInfo"); + } + + drop_events = false; + key_event_pos = 0; + + alt_mem = false; + gr_mem = false; + shift_mem = false; + control_mem = false; + meta_mem = false; + console_visible = IsWindowVisible(GetConsoleWindow()); + hInstance = ((OS_Windows *)OS::get_singleton())->get_hinstance(); + + pressrc = 0; + old_invalid = true; + mouse_mode = MOUSE_MODE_VISIBLE; + + outside = true; + + if (OS::get_singleton()->is_hidpi_allowed()) { + HMODULE Shcore = LoadLibraryW(L"Shcore.dll"); + + if (Shcore != NULL) { + typedef HRESULT(WINAPI * SetProcessDpiAwareness_t)(SHC_PROCESS_DPI_AWARENESS); + + SetProcessDpiAwareness_t SetProcessDpiAwareness = (SetProcessDpiAwareness_t)GetProcAddress(Shcore, "SetProcessDpiAwareness"); + + if (SetProcessDpiAwareness) { + SetProcessDpiAwareness(SHC_PROCESS_SYSTEM_DPI_AWARE); + } + } + } + + memset(&wc, 0, sizeof(WNDCLASSEXW)); + wc.cbSize = sizeof(WNDCLASSEXW); + wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC | CS_DBLCLKS; + wc.lpfnWndProc = (WNDPROC)::WndProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + //wc.hInstance = hInstance; + wc.hInstance = hInstance ? hInstance : GetModuleHandle(NULL); + wc.hIcon = LoadIcon(NULL, IDI_WINLOGO); + wc.hCursor = NULL; //LoadCursor(NULL, IDC_ARROW); + wc.hbrBackground = NULL; + wc.lpszMenuName = NULL; + wc.lpszClassName = L"Engine"; + + if (!RegisterClassExW(&wc)) { + MessageBox(NULL, "Failed To Register The Window Class.", "ERROR", MB_OK | MB_ICONEXCLAMATION); + r_error = ERR_UNAVAILABLE; + return; + } + + use_raw_input = true; + + RAWINPUTDEVICE Rid[1]; + + Rid[0].usUsagePage = 0x01; + Rid[0].usUsage = 0x02; + Rid[0].dwFlags = 0; + Rid[0].hwndTarget = 0; + + if (RegisterRawInputDevices(Rid, 1, sizeof(Rid[0])) == FALSE) { + //registration failed. + use_raw_input = false; + } + + rendering_driver = "vulkan"; + +#if defined(VULKAN_ENABLED) + if (rendering_driver == "vulkan") { + + context_vulkan = memnew(VulkanContextWindows); + if (context_vulkan->initialize() != OK) { + memdelete(context_vulkan); + context_vulkan = NULL; + r_error = ERR_UNAVAILABLE; + return; + } + } +#endif +#if defined(OPENGL_ENABLED) + if (rendering_driver_index == VIDEO_DRIVER_GLES2) { + + context_gles2 = memnew(ContextGL_Windows(hWnd, false)); + + if (context_gles2->initialize() != OK) { + memdelete(context_gles2); + context_gles2 = NULL; + ERR_FAIL_V(ERR_UNAVAILABLE); + } + + context_gles2->set_use_vsync(video_mode.use_vsync); + set_vsync_via_compositor(video_mode.vsync_via_compositor); + + if (RasterizerGLES2::is_viable() == OK) { + RasterizerGLES2::register_config(); + RasterizerGLES2::make_current(); + } else { + memdelete(context_gles2); + context_gles2 = NULL; + ERR_FAIL_V(ERR_UNAVAILABLE); + } + } +#endif + WindowID main_window = _create_window(p_mode, 0, 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); + } + } + + ShowWindow(windows[MAIN_WINDOW_ID].hWnd, SW_SHOW); // Show The Window + SetForegroundWindow(windows[MAIN_WINDOW_ID].hWnd); // Slightly Higher Priority + SetFocus(windows[MAIN_WINDOW_ID].hWnd); // Sets Keyboard Focus To + +#if defined(VULKAN_ENABLED) + + if (rendering_driver == "vulkan") { + + rendering_device_vulkan = memnew(RenderingDeviceVulkan); + rendering_device_vulkan->initialize(context_vulkan); + + RasterizerRD::make_current(); + } +#endif + + move_timer_id = 1; + + //set_ime_active(false); + + if (!OS::get_singleton()->is_in_low_processor_usage_mode()) { + //SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS); + SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS); + DWORD index = 0; + HANDLE handle = AvSetMmThreadCharacteristics("Games", &index); + if (handle) + AvSetMmThreadPriority(handle, AVRT_PRIORITY_CRITICAL); + + // This is needed to make sure that background work does not starve the main thread. + // This is only setting priority of this thread, not the whole process. + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); + } + + cursor_shape = CURSOR_ARROW; + + _update_real_mouse_position(MAIN_WINDOW_ID); + + joypad = new JoypadWindows(&windows[MAIN_WINDOW_ID].hWnd); + + r_error = OK; + + ((OS_Windows *)OS::get_singleton())->set_main_window(windows[MAIN_WINDOW_ID].hWnd); + InputFilter::get_singleton()->set_event_dispatch_function(_dispatch_input_events); +} + +Vector<String> DisplayServerWindows::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; +} + +DisplayServer *DisplayServerWindows::create_func(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { + + return memnew(DisplayServerWindows(p_rendering_driver, p_mode, p_flags, p_resolution, r_error)); +} + +void DisplayServerWindows::register_windows_driver() { + + register_create_function("windows", create_func, get_rendering_drivers_func); +} + +DisplayServerWindows::~DisplayServerWindows() { + + delete joypad; + touch_state.clear(); + + cursors_cache.clear(); + +#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 + + if (user_proc) { + SetWindowLongPtr(windows[MAIN_WINDOW_ID].hWnd, GWLP_WNDPROC, (LONG_PTR)user_proc); + }; + + if (windows.has(MAIN_WINDOW_ID)) { +#ifdef VULKAN_ENABLED + if (rendering_driver == "vulkan") { + context_vulkan->window_destroy(MAIN_WINDOW_ID); + } +#endif + + DestroyWindow(windows[MAIN_WINDOW_ID].hWnd); + } +} diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h new file mode 100644 index 0000000000..47bb3f59a6 --- /dev/null +++ b/platform/windows/display_server_windows.h @@ -0,0 +1,389 @@ +#ifndef DISPLAY_SERVER_WINDOWS_H +#define DISPLAY_SERVER_WINDOWS_H + +#include "servers/display_server.h" + +#include "core/input/input_filter.h" +#include "core/os/os.h" +#include "core/project_settings.h" +#include "crash_handler_windows.h" +#include "drivers/unix/ip_unix.h" +#include "drivers/wasapi/audio_driver_wasapi.h" +#include "drivers/winmidi/midi_driver_winmidi.h" +#include "joypad_windows.h" +#include "key_mapping_windows.h" +#include "servers/audio_server.h" +#include "servers/visual/rasterizer.h" +#include "servers/visual/rasterizer_rd/rasterizer_rd.h" +#include "servers/visual_server.h" + +#ifdef XAUDIO2_ENABLED +#include "drivers/xaudio2/audio_driver_xaudio2.h" +#endif + +#if defined(OPENGL_ENABLED) +#include "context_gl_windows.h" +#endif + +#if defined(VULKAN_ENABLED) +#include "drivers/vulkan/rendering_device_vulkan.h" +#include "platform/windows/vulkan_context_win.h" +#endif + +#include <fcntl.h> +#include <io.h> +#include <stdio.h> +#include <windows.h> +#include <windowsx.h> + +#ifndef POINTER_STRUCTURES + +#define POINTER_STRUCTURES + +typedef DWORD POINTER_INPUT_TYPE; +typedef UINT32 POINTER_FLAGS; +typedef UINT32 PEN_FLAGS; +typedef UINT32 PEN_MASK; + +enum tagPOINTER_INPUT_TYPE { + PT_POINTER = 0x00000001, + PT_TOUCH = 0x00000002, + PT_PEN = 0x00000003, + PT_MOUSE = 0x00000004, + PT_TOUCHPAD = 0x00000005 +}; + +typedef enum tagPOINTER_BUTTON_CHANGE_TYPE { + POINTER_CHANGE_NONE, + POINTER_CHANGE_FIRSTBUTTON_DOWN, + POINTER_CHANGE_FIRSTBUTTON_UP, + POINTER_CHANGE_SECONDBUTTON_DOWN, + POINTER_CHANGE_SECONDBUTTON_UP, + POINTER_CHANGE_THIRDBUTTON_DOWN, + POINTER_CHANGE_THIRDBUTTON_UP, + POINTER_CHANGE_FOURTHBUTTON_DOWN, + POINTER_CHANGE_FOURTHBUTTON_UP, + POINTER_CHANGE_FIFTHBUTTON_DOWN, + POINTER_CHANGE_FIFTHBUTTON_UP, +} POINTER_BUTTON_CHANGE_TYPE; + +typedef struct tagPOINTER_INFO { + POINTER_INPUT_TYPE pointerType; + UINT32 pointerId; + UINT32 frameId; + POINTER_FLAGS pointerFlags; + HANDLE sourceDevice; + HWND hwndTarget; + POINT ptPixelLocation; + POINT ptHimetricLocation; + POINT ptPixelLocationRaw; + POINT ptHimetricLocationRaw; + DWORD dwTime; + UINT32 historyCount; + INT32 InputData; + DWORD dwKeyStates; + UINT64 PerformanceCount; + POINTER_BUTTON_CHANGE_TYPE ButtonChangeType; +} POINTER_INFO; + +typedef struct tagPOINTER_PEN_INFO { + POINTER_INFO pointerInfo; + PEN_FLAGS penFlags; + PEN_MASK penMask; + UINT32 pressure; + UINT32 rotation; + INT32 tiltX; + INT32 tiltY; +} POINTER_PEN_INFO; + +#endif //POINTER_STRUCTURES + +typedef BOOL(WINAPI *GetPointerTypePtr)(uint32_t p_id, POINTER_INPUT_TYPE *p_type); +typedef BOOL(WINAPI *GetPointerPenInfoPtr)(uint32_t p_id, POINTER_PEN_INFO *p_pen_info); + +typedef struct { + BYTE bWidth; // Width, in pixels, of the image + BYTE bHeight; // Height, in pixels, of the image + BYTE bColorCount; // Number of colors in image (0 if >=8bpp) + BYTE bReserved; // Reserved ( must be 0) + WORD wPlanes; // Color Planes + WORD wBitCount; // Bits per pixel + DWORD dwBytesInRes; // How many bytes in this resource? + DWORD dwImageOffset; // Where in the file is this image? +} ICONDIRENTRY, *LPICONDIRENTRY; + +typedef struct { + WORD idReserved; // Reserved (must be 0) + WORD idType; // Resource Type (1 for icons) + WORD idCount; // How many images? + ICONDIRENTRY idEntries[1]; // An entry for each image (idCount of 'em) +} ICONDIR, *LPICONDIR; + +class DisplayServerWindows : public DisplayServer { + //No need to register, it's platform-specific and nothing is added + //GDCLASS(DisplayServerWindows, DisplayServer) + + _THREAD_SAFE_CLASS_ + + static GetPointerTypePtr win8p_GetPointerType; + static GetPointerPenInfoPtr win8p_GetPointerPenInfo; + + void GetMaskBitmaps(HBITMAP hSourceBitmap, COLORREF clrTransparent, OUT HBITMAP &hAndMaskBitmap, OUT HBITMAP &hXorMaskBitmap); + + enum { + KEY_EVENT_BUFFER_SIZE = 512 + }; + + struct KeyEvent { + + WindowID window_id; + bool alt, shift, control, meta; + UINT uMsg; + WPARAM wParam; + LPARAM lParam; + }; + + KeyEvent key_event_buffer[KEY_EVENT_BUFFER_SIZE]; + int key_event_pos; + + bool old_invalid; + bool outside; + int old_x, old_y; + Point2i center; + +#if defined(OPENGL_ENABLED) + ContextGL_Windows *context_gles2; +#endif + +#if defined(VULKAN_ENABLED) + VulkanContextWindows *context_vulkan; + RenderingDeviceVulkan *rendering_device_vulkan; +#endif + + Map<int, Vector2> touch_state; + + int pressrc; + HINSTANCE hInstance; // Holds The Instance Of The Application + String rendering_driver; + + struct WindowData { + HWND hWnd; + //layered window + + bool preserve_window_size = false; + bool pre_fs_valid = false; + RECT pre_fs_rect; + bool maximized = false; + bool minimized = false; + bool fullscreen = false; + bool borderless = false; + bool resizable = true; + bool window_focused = false; + bool was_maximized = false; + bool always_on_top = false; + bool no_focus = false; + bool window_has_focus = false; + + HBITMAP hBitmap; //DIB section for layered window + uint8_t *dib_data = nullptr; + Size2 dib_size; + HDC hDC_dib; + Size2 min_size; + Size2 max_size; + int width = 0, height = 0; + + Size2 window_rect; + Point2 last_pos; + + ObjectID instance_id; + + // IME + HIMC im_himc; + Vector2 im_position; + + bool layered_window = false; + + Callable rect_changed_callback; + Callable event_callback; + Callable input_event_callback; + Callable input_text_callback; + Callable drop_files_callback; + + WindowID transient_parent = INVALID_WINDOW_ID; + Set<WindowID> transient_children; + }; + + JoypadWindows *joypad; + + WindowID _create_window(WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect); + WindowID window_id_counter = MAIN_WINDOW_ID; + Map<WindowID, WindowData> windows; + + WindowID last_focused_window = INVALID_WINDOW_ID; + + uint32_t move_timer_id; + + HCURSOR hCursor; + + WNDPROC user_proc = nullptr; + + void _send_window_event(const WindowData &wd, WindowEvent p_event); + void _get_window_style(bool p_main_window, bool p_fullscreen, bool p_borderless, bool p_resizable, bool p_maximized, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex); + + MouseMode mouse_mode; + bool alt_mem = false; + bool gr_mem = false; + bool shift_mem = false; + bool control_mem = false; + bool meta_mem = false; + bool force_quit = false; + uint32_t last_button_state = 0; + bool use_raw_input = false; + bool drop_events = false; + bool console_visible = false; + + WNDCLASSEXW wc; + + HCURSOR cursors[CURSOR_MAX] = { NULL }; + CursorShape cursor_shape; + Map<CursorShape, Vector<Variant>> cursors_cache; + + void _drag_event(WindowID p_window, float p_x, float p_y, int idx); + void _touch_event(WindowID p_window, bool p_pressed, float p_x, float p_y, int idx); + + void _update_window_style(WindowID p_window, bool p_repaint = true, bool p_maximized = false); + + void _update_real_mouse_position(WindowID p_window); + + void _set_mouse_mode_impl(MouseMode p_mode); + + void _process_key_events(); + + static void _dispatch_input_events(const Ref<InputEvent> &p_event); + void _dispatch_input_event(const Ref<InputEvent> &p_event); + +public: + LRESULT WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + + virtual bool has_feature(Feature p_feature) const; + virtual String get_name() const; + + virtual void alert(const String &p_alert, const String &p_title = "ALERT!"); + + 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 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 Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const; + virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const; + virtual bool screen_is_touchscreen(int p_screen = SCREEN_OF_MAIN_WINDOW) const; + + virtual void screen_set_orientation(ScreenOrientation p_orientation, int p_screen = SCREEN_OF_MAIN_WINDOW); + ScreenOrientation screen_get_orientation(int p_screen = SCREEN_OF_MAIN_WINDOW) const; + + virtual void screen_set_keep_on(bool p_enable); //disable screensaver + virtual bool screen_is_kept_on() const; + + virtual Vector<DisplayServer::WindowID> get_window_list() const; + + virtual WindowID create_sub_window(WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect = Rect2i()); + virtual void delete_sub_window(WindowID p_window); + + 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 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; //wtf is this? should probable use proper name + + 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 void console_set_visible(bool p_enabled); + virtual bool is_console_visible() 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 void enable_for_stealing_focus(OS::ProcessID pid); + + 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 vsync_set_use_via_compositor(bool p_enable); + virtual bool vsync_is_using_via_compositor() const; + + virtual void set_context(Context p_context); + + 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_windows_driver(); + + DisplayServerWindows(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error); + ~DisplayServerWindows(); +}; + +#endif // DISPLAY_SERVER_WINDOWS_H diff --git a/platform/windows/joypad_windows.cpp b/platform/windows/joypad_windows.cpp index 49432435b9..9de1b7b194 100644 --- a/platform/windows/joypad_windows.cpp +++ b/platform/windows/joypad_windows.cpp @@ -52,9 +52,9 @@ DWORD WINAPI _xinput_set_state(DWORD dwUserIndex, XINPUT_VIBRATION *pVibration) JoypadWindows::JoypadWindows() { } -JoypadWindows::JoypadWindows(InputDefault *_input, HWND *hwnd) { +JoypadWindows::JoypadWindows(HWND *hwnd) { - input = _input; + input = InputFilter::get_singleton(); hWnd = hwnd; joypad_count = 0; dinput = NULL; @@ -436,46 +436,46 @@ void JoypadWindows::post_hat(int p_device, DWORD p_dpad) { // BOOL POVCentered = (LOWORD(dwPOV) == 0xFFFF);" // https://docs.microsoft.com/en-us/previous-versions/windows/desktop/ee416628(v%3Dvs.85)#remarks if (LOWORD(p_dpad) == 0xFFFF) { - dpad_val = InputDefault::HAT_MASK_CENTER; + dpad_val = InputFilter::HAT_MASK_CENTER; } if (p_dpad == 0) { - dpad_val = InputDefault::HAT_MASK_UP; + dpad_val = InputFilter::HAT_MASK_UP; } else if (p_dpad == 4500) { - dpad_val = (InputDefault::HAT_MASK_UP | InputDefault::HAT_MASK_RIGHT); + dpad_val = (InputFilter::HAT_MASK_UP | InputFilter::HAT_MASK_RIGHT); } else if (p_dpad == 9000) { - dpad_val = InputDefault::HAT_MASK_RIGHT; + dpad_val = InputFilter::HAT_MASK_RIGHT; } else if (p_dpad == 13500) { - dpad_val = (InputDefault::HAT_MASK_RIGHT | InputDefault::HAT_MASK_DOWN); + dpad_val = (InputFilter::HAT_MASK_RIGHT | InputFilter::HAT_MASK_DOWN); } else if (p_dpad == 18000) { - dpad_val = InputDefault::HAT_MASK_DOWN; + dpad_val = InputFilter::HAT_MASK_DOWN; } else if (p_dpad == 22500) { - dpad_val = (InputDefault::HAT_MASK_DOWN | InputDefault::HAT_MASK_LEFT); + dpad_val = (InputFilter::HAT_MASK_DOWN | InputFilter::HAT_MASK_LEFT); } else if (p_dpad == 27000) { - dpad_val = InputDefault::HAT_MASK_LEFT; + dpad_val = InputFilter::HAT_MASK_LEFT; } else if (p_dpad == 31500) { - dpad_val = (InputDefault::HAT_MASK_LEFT | InputDefault::HAT_MASK_UP); + dpad_val = (InputFilter::HAT_MASK_LEFT | InputFilter::HAT_MASK_UP); } input->joy_hat(p_device, dpad_val); }; -InputDefault::JoyAxis JoypadWindows::axis_correct(int p_val, bool p_xinput, bool p_trigger, bool p_negate) const { +InputFilter::JoyAxis JoypadWindows::axis_correct(int p_val, bool p_xinput, bool p_trigger, bool p_negate) const { - InputDefault::JoyAxis jx; + InputFilter::JoyAxis jx; if (Math::abs(p_val) < MIN_JOY_AXIS) { jx.min = p_trigger ? 0 : -1; jx.value = 0.0f; diff --git a/platform/windows/joypad_windows.h b/platform/windows/joypad_windows.h index ab85bc60ac..f010fd08ff 100644 --- a/platform/windows/joypad_windows.h +++ b/platform/windows/joypad_windows.h @@ -52,7 +52,7 @@ class JoypadWindows { public: JoypadWindows(); - JoypadWindows(InputDefault *_input, HWND *hwnd); + JoypadWindows(HWND *hwnd); ~JoypadWindows(); void probe_joypads(); @@ -117,7 +117,7 @@ private: HWND *hWnd; HANDLE xinput_dll; LPDIRECTINPUT8 dinput; - InputDefault *input; + InputFilter *input; int id_to_change; int joypad_count; @@ -141,7 +141,7 @@ private: void joypad_vibration_start_xinput(int p_device, float p_weak_magnitude, float p_strong_magnitude, float p_duration, uint64_t p_timestamp); void joypad_vibration_stop_xinput(int p_device, uint64_t p_timestamp); - InputDefault::JoyAxis axis_correct(int p_val, bool p_xinput = false, bool p_trigger = false, bool p_negate = false) const; + InputFilter::JoyAxis axis_correct(int p_val, bool p_xinput = false, bool p_trigger = false, bool p_negate = false) const; XInputGetState_t xinput_get_state; XInputSetState_t xinput_set_state; }; diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index 041a5bffa6..f78c87be93 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -37,15 +37,6 @@ #include "core/debugger/script_debugger.h" #include "core/io/marshalls.h" #include "core/version_generated.gen.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 "drivers/windows/dir_access_windows.h" #include "drivers/windows/file_access_windows.h" #include "drivers/windows/rw_lock_windows.h" @@ -53,6 +44,7 @@ #include "joypad_windows.h" #include "lang_table.h" #include "main/main.h" +#include "platform/windows/display_server_windows.h" #include "servers/audio_server.h" #include "servers/visual/visual_server_raster.h" #include "servers/visual/visual_server_wrap_mt.h" @@ -86,30 +78,6 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; #define GetProcAddress (void *)GetProcAddress #endif -typedef struct { - int count; - int screen; - Size2 size; -} EnumSizeData; - -typedef struct { - int count; - int screen; - Point2 pos; -} EnumPosData; - -static BOOL CALLBACK _MonitorEnumProcSize(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { - - EnumSizeData *data = (EnumSizeData *)dwData; - if (data->count == data->screen) { - data->size.x = lprcMonitor->right - lprcMonitor->left; - data->size.y = lprcMonitor->bottom - lprcMonitor->top; - } - - data->count++; - return TRUE; -} - #ifdef DEBUG_ENABLED static String format_error_message(DWORD id) { @@ -125,8 +93,6 @@ static String format_error_message(DWORD id) { } #endif // DEBUG_ENABLED -extern HINSTANCE godot_hinstance; - void RedirectIOToConsole() { int hConHandle; @@ -208,24 +174,16 @@ BOOL WINAPI HandlerRoutine(_In_ DWORD dwCtrlType) { } } -GetPointerTypePtr OS_Windows::win8p_GetPointerType = NULL; -GetPointerPenInfoPtr OS_Windows::win8p_GetPointerPenInfo = NULL; - void OS_Windows::initialize_debugging() { SetConsoleCtrlHandler(HandlerRoutine, TRUE); } -void OS_Windows::initialize_core() { +void OS_Windows::initialize() { crash_handler.initialize(); - last_button_state = 0; - //RedirectIOToConsole(); - maximized = false; - minimized = false; - borderless = false; ThreadWindows::make_default(); RWLockWindows::make_default(); @@ -255,1371 +213,9 @@ void OS_Windows::initialize_core() { process_map = memnew((Map<ProcessID, ProcessInfo>)); IP_Unix::make_default(); - - cursor_shape = CURSOR_ARROW; -} - -bool OS_Windows::can_draw() const { - - return !minimized; -}; - -#define MI_WP_SIGNATURE 0xFF515700 -#define SIGNATURE_MASK 0xFFFFFF00 -// Keeping the name suggested by Microsoft, but this macro really answers: -// Is this mouse event emulated from touch or pen input? -#define IsPenEvent(dw) (((dw)&SIGNATURE_MASK) == MI_WP_SIGNATURE) -// This one tells whether the event comes from touchscreen (and not from pen) -#define IsTouchEvent(dw) (IsPenEvent(dw) && ((dw)&0x80)) - -void OS_Windows::_touch_event(bool p_pressed, float p_x, float p_y, int idx) { - - // Defensive - if (touch_state.has(idx) == p_pressed) - return; - - if (p_pressed) { - touch_state.insert(idx, Vector2(p_x, p_y)); - } else { - touch_state.erase(idx); - } - - Ref<InputEventScreenTouch> event; - event.instance(); - event->set_index(idx); - event->set_pressed(p_pressed); - event->set_position(Vector2(p_x, p_y)); - - if (main_loop) { - input->accumulate_input_event(event); - } -}; - -void OS_Windows::_drag_event(float p_x, float p_y, int idx) { - - Map<int, Vector2>::Element *curr = touch_state.find(idx); - // Defensive - if (!curr) - return; - - if (curr->get() == Vector2(p_x, p_y)) - return; - - Ref<InputEventScreenDrag> event; - event.instance(); - event->set_index(idx); - event->set_position(Vector2(p_x, p_y)); - event->set_relative(Vector2(p_x, p_y) - curr->get()); - - if (main_loop) - input->accumulate_input_event(event); - - curr->get() = Vector2(p_x, p_y); -}; - -LRESULT OS_Windows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - - if (drop_events) { - - if (user_proc) { - - return CallWindowProcW(user_proc, hWnd, uMsg, wParam, lParam); - } else { - return DefWindowProcW(hWnd, uMsg, wParam, lParam); - } - }; - - switch (uMsg) // Check For Windows Messages - { - case WM_SETFOCUS: { - window_has_focus = true; - - // Restore mouse mode - _set_mouse_mode_impl(mouse_mode); - - break; - } - case WM_KILLFOCUS: { - window_has_focus = false; - - // Release capture unconditionally because it can be set due to dragging, in addition to captured mode - ReleaseCapture(); - - // Release every touch to avoid sticky points - for (Map<int, Vector2>::Element *E = touch_state.front(); E; E = E->next()) { - _touch_event(false, E->get().x, E->get().y, E->key()); - } - touch_state.clear(); - - break; - } - case WM_ACTIVATE: // Watch For Window Activate Message - { - minimized = HIWORD(wParam) != 0; - if (!main_loop) { - return 0; - }; - if (LOWORD(wParam) == WA_ACTIVE || LOWORD(wParam) == WA_CLICKACTIVE) { - - main_loop->notification(MainLoop::NOTIFICATION_WM_FOCUS_IN); - window_focused = true; - alt_mem = false; - control_mem = false; - shift_mem = false; - } else { // WM_INACTIVE - input->release_pressed_events(); - main_loop->notification(MainLoop::NOTIFICATION_WM_FOCUS_OUT); - window_focused = false; - alt_mem = false; - }; - - return 0; // Return To The Message Loop - } - case WM_GETMINMAXINFO: { - if (video_mode.resizable && !video_mode.fullscreen) { - Size2 decor = get_real_window_size() - get_window_size(); // Size of window decorations - MINMAXINFO *min_max_info = (MINMAXINFO *)lParam; - if (min_size != Size2()) { - min_max_info->ptMinTrackSize.x = min_size.x + decor.x; - min_max_info->ptMinTrackSize.y = min_size.y + decor.y; - } - if (max_size != Size2()) { - min_max_info->ptMaxTrackSize.x = max_size.x + decor.x; - min_max_info->ptMaxTrackSize.y = max_size.y + decor.y; - } - return 0; - } else { - break; - } - } - case WM_PAINT: - - Main::force_redraw(); - break; - - case WM_SYSCOMMAND: // Intercept System Commands - { - switch (wParam) // Check System Calls - { - case SC_SCREENSAVE: // Screensaver Trying To Start? - case SC_MONITORPOWER: // Monitor Trying To Enter Powersave? - return 0; // Prevent From Happening - case SC_KEYMENU: - if ((lParam >> 16) <= 0) - return 0; - } - break; // Exit - } - - case WM_CLOSE: // Did We Receive A Close Message? - { - if (main_loop) - main_loop->notification(MainLoop::NOTIFICATION_WM_QUIT_REQUEST); - //force_quit=true; - return 0; // Jump Back - } - case WM_MOUSELEAVE: { - - old_invalid = true; - outside = true; - if (main_loop && mouse_mode != MOUSE_MODE_CAPTURED) - main_loop->notification(MainLoop::NOTIFICATION_WM_MOUSE_EXIT); - - } break; - case WM_INPUT: { - if (mouse_mode != MOUSE_MODE_CAPTURED || !use_raw_input) { - break; - } - - UINT dwSize; - - GetRawInputData((HRAWINPUT)lParam, RID_INPUT, NULL, &dwSize, sizeof(RAWINPUTHEADER)); - LPBYTE lpb = new BYTE[dwSize]; - if (lpb == NULL) { - return 0; - } - - if (GetRawInputData((HRAWINPUT)lParam, RID_INPUT, lpb, &dwSize, sizeof(RAWINPUTHEADER)) != dwSize) - OutputDebugString(TEXT("GetRawInputData does not return correct size !\n")); - - RAWINPUT *raw = (RAWINPUT *)lpb; - - if (raw->header.dwType == RIM_TYPEMOUSE) { - Ref<InputEventMouseMotion> mm; - mm.instance(); - - mm->set_control(control_mem); - mm->set_shift(shift_mem); - mm->set_alt(alt_mem); - - mm->set_button_mask(last_button_state); - - Point2i c(video_mode.width / 2, video_mode.height / 2); - - // centering just so it works as before - POINT pos = { (int)c.x, (int)c.y }; - ClientToScreen(hWnd, &pos); - SetCursorPos(pos.x, pos.y); - - mm->set_position(c); - mm->set_global_position(c); - input->set_mouse_position(c); - mm->set_speed(Vector2(0, 0)); - - if (raw->data.mouse.usFlags == MOUSE_MOVE_RELATIVE) { - mm->set_relative(Vector2(raw->data.mouse.lLastX, raw->data.mouse.lLastY)); - - } else if (raw->data.mouse.usFlags == MOUSE_MOVE_ABSOLUTE) { - - int nScreenWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN); - int nScreenHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN); - int nScreenLeft = GetSystemMetrics(SM_XVIRTUALSCREEN); - int nScreenTop = GetSystemMetrics(SM_YVIRTUALSCREEN); - - Vector2 abs_pos( - (double(raw->data.mouse.lLastX) - 65536.0 / (nScreenWidth)) * nScreenWidth / 65536.0 + nScreenLeft, - (double(raw->data.mouse.lLastY) - 65536.0 / (nScreenHeight)) * nScreenHeight / 65536.0 + nScreenTop); - - POINT coords; //client coords - coords.x = abs_pos.x; - coords.y = abs_pos.y; - - ScreenToClient(hWnd, &coords); - - mm->set_relative(Vector2(coords.x - old_x, coords.y - old_y)); - old_x = coords.x; - old_y = coords.y; - - /*Input.mi.dx = (int)((((double)(pos.x)-nScreenLeft) * 65536) / nScreenWidth + 65536 / (nScreenWidth)); - Input.mi.dy = (int)((((double)(pos.y)-nScreenTop) * 65536) / nScreenHeight + 65536 / (nScreenHeight)); - */ - } - - if (window_has_focus && main_loop && mm->get_relative() != Vector2()) - input->accumulate_input_event(mm); - } - delete[] lpb; - } break; - case WM_POINTERUPDATE: { - if (mouse_mode == MOUSE_MODE_CAPTURED && use_raw_input) { - break; - } - - if (!win8p_GetPointerType || !win8p_GetPointerPenInfo) { - break; - } - - uint32_t pointer_id = LOWORD(wParam); - POINTER_INPUT_TYPE pointer_type = PT_POINTER; - if (!win8p_GetPointerType(pointer_id, &pointer_type)) { - break; - } - - if (pointer_type != PT_PEN) { - break; - } - - POINTER_PEN_INFO pen_info; - if (!win8p_GetPointerPenInfo(pointer_id, &pen_info)) { - break; - } - - if (input->is_emulating_mouse_from_touch()) { - // Universal translation enabled; ignore OS translation - LPARAM extra = GetMessageExtraInfo(); - if (IsTouchEvent(extra)) { - break; - } - } - - if (outside) { - //mouse enter - - if (main_loop && mouse_mode != MOUSE_MODE_CAPTURED) - main_loop->notification(MainLoop::NOTIFICATION_WM_MOUSE_ENTER); - - CursorShape c = cursor_shape; - cursor_shape = CURSOR_MAX; - set_cursor_shape(c); - outside = false; - - //Once-Off notification, must call again.... - TRACKMOUSEEVENT tme; - tme.cbSize = sizeof(TRACKMOUSEEVENT); - tme.dwFlags = TME_LEAVE; - tme.hwndTrack = hWnd; - tme.dwHoverTime = HOVER_DEFAULT; - TrackMouseEvent(&tme); - } - - // Don't calculate relative mouse movement if we don't have focus in CAPTURED mode. - if (!window_has_focus && mouse_mode == MOUSE_MODE_CAPTURED) - break; - - Ref<InputEventMouseMotion> mm; - mm.instance(); - - mm->set_pressure(pen_info.pressure ? (float)pen_info.pressure / 1024 : 0); - mm->set_tilt(Vector2(pen_info.tiltX ? (float)pen_info.tiltX / 90 : 0, pen_info.tiltY ? (float)pen_info.tiltY / 90 : 0)); - - mm->set_control((wParam & MK_CONTROL) != 0); - mm->set_shift((wParam & MK_SHIFT) != 0); - mm->set_alt(alt_mem); - - mm->set_button_mask(last_button_state); - - POINT coords; //client coords - coords.x = GET_X_LPARAM(lParam); - coords.y = GET_Y_LPARAM(lParam); - - ScreenToClient(hWnd, &coords); - - mm->set_position(Vector2(coords.x, coords.y)); - mm->set_global_position(Vector2(coords.x, coords.y)); - - if (mouse_mode == MOUSE_MODE_CAPTURED) { - - Point2i c(video_mode.width / 2, video_mode.height / 2); - old_x = c.x; - old_y = c.y; - - if (mm->get_position() == c) { - center = c; - return 0; - } - - Point2i ncenter = mm->get_position(); - center = ncenter; - POINT pos = { (int)c.x, (int)c.y }; - ClientToScreen(hWnd, &pos); - SetCursorPos(pos.x, pos.y); - } - - input->set_mouse_position(mm->get_position()); - mm->set_speed(input->get_last_mouse_speed()); - - if (old_invalid) { - - old_x = mm->get_position().x; - old_y = mm->get_position().y; - old_invalid = false; - } - - mm->set_relative(Vector2(mm->get_position() - Vector2(old_x, old_y))); - old_x = mm->get_position().x; - old_y = mm->get_position().y; - if (window_has_focus && main_loop) - input->parse_input_event(mm); - - return 0; //Pointer event handled return 0 to avoid duplicate WM_MOUSEMOVE event - } break; - case WM_MOUSEMOVE: { - if (mouse_mode == MOUSE_MODE_CAPTURED && use_raw_input) { - break; - } - - if (input->is_emulating_mouse_from_touch()) { - // Universal translation enabled; ignore OS translation - LPARAM extra = GetMessageExtraInfo(); - if (IsTouchEvent(extra)) { - break; - } - } - - if (outside) { - //mouse enter - - if (main_loop && mouse_mode != MOUSE_MODE_CAPTURED) - main_loop->notification(MainLoop::NOTIFICATION_WM_MOUSE_ENTER); - - CursorShape c = cursor_shape; - cursor_shape = CURSOR_MAX; - set_cursor_shape(c); - outside = false; - - //Once-Off notification, must call again.... - TRACKMOUSEEVENT tme; - tme.cbSize = sizeof(TRACKMOUSEEVENT); - tme.dwFlags = TME_LEAVE; - tme.hwndTrack = hWnd; - tme.dwHoverTime = HOVER_DEFAULT; - TrackMouseEvent(&tme); - } - - // Don't calculate relative mouse movement if we don't have focus in CAPTURED mode. - if (!window_has_focus && mouse_mode == MOUSE_MODE_CAPTURED) - break; - - Ref<InputEventMouseMotion> mm; - mm.instance(); - - mm->set_control((wParam & MK_CONTROL) != 0); - mm->set_shift((wParam & MK_SHIFT) != 0); - mm->set_alt(alt_mem); - - mm->set_button_mask(last_button_state); - - mm->set_position(Vector2(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))); - mm->set_global_position(Vector2(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))); - - if (mouse_mode == MOUSE_MODE_CAPTURED) { - - Point2i c(video_mode.width / 2, video_mode.height / 2); - old_x = c.x; - old_y = c.y; - - if (mm->get_position() == c) { - center = c; - return 0; - } - - Point2i ncenter = mm->get_position(); - center = ncenter; - POINT pos = { (int)c.x, (int)c.y }; - ClientToScreen(hWnd, &pos); - SetCursorPos(pos.x, pos.y); - } - - input->set_mouse_position(mm->get_position()); - mm->set_speed(input->get_last_mouse_speed()); - - if (old_invalid) { - - old_x = mm->get_position().x; - old_y = mm->get_position().y; - old_invalid = false; - } - - mm->set_relative(Vector2(mm->get_position() - Vector2(old_x, old_y))); - old_x = mm->get_position().x; - old_y = mm->get_position().y; - if (window_has_focus && main_loop) - input->accumulate_input_event(mm); - - } break; - case WM_LBUTTONDOWN: - case WM_LBUTTONUP: - if (input->is_emulating_mouse_from_touch()) { - // Universal translation enabled; ignore OS translations for left button - LPARAM extra = GetMessageExtraInfo(); - if (IsTouchEvent(extra)) { - break; - } - } - [[fallthrough]]; - case WM_MBUTTONDOWN: - case WM_MBUTTONUP: - case WM_RBUTTONDOWN: - case WM_RBUTTONUP: - case WM_MOUSEWHEEL: - case WM_MOUSEHWHEEL: - case WM_LBUTTONDBLCLK: - case WM_MBUTTONDBLCLK: - case WM_RBUTTONDBLCLK: - case WM_XBUTTONDBLCLK: - case WM_XBUTTONDOWN: - case WM_XBUTTONUP: { - - Ref<InputEventMouseButton> mb; - mb.instance(); - - switch (uMsg) { - case WM_LBUTTONDOWN: { - mb->set_pressed(true); - mb->set_button_index(1); - } break; - case WM_LBUTTONUP: { - mb->set_pressed(false); - mb->set_button_index(1); - } break; - case WM_MBUTTONDOWN: { - mb->set_pressed(true); - mb->set_button_index(3); - } break; - case WM_MBUTTONUP: { - mb->set_pressed(false); - mb->set_button_index(3); - } break; - case WM_RBUTTONDOWN: { - mb->set_pressed(true); - mb->set_button_index(2); - } break; - case WM_RBUTTONUP: { - mb->set_pressed(false); - mb->set_button_index(2); - } break; - case WM_LBUTTONDBLCLK: { - mb->set_pressed(true); - mb->set_button_index(1); - mb->set_doubleclick(true); - } break; - case WM_RBUTTONDBLCLK: { - mb->set_pressed(true); - mb->set_button_index(2); - mb->set_doubleclick(true); - } break; - case WM_MBUTTONDBLCLK: { - mb->set_pressed(true); - mb->set_button_index(3); - mb->set_doubleclick(true); - } break; - case WM_MOUSEWHEEL: { - - mb->set_pressed(true); - int motion = (short)HIWORD(wParam); - if (!motion) - return 0; - - if (motion > 0) - mb->set_button_index(BUTTON_WHEEL_UP); - else - mb->set_button_index(BUTTON_WHEEL_DOWN); - - } break; - case WM_MOUSEHWHEEL: { - - mb->set_pressed(true); - int motion = (short)HIWORD(wParam); - if (!motion) - return 0; - - if (motion < 0) { - mb->set_button_index(BUTTON_WHEEL_LEFT); - mb->set_factor(fabs((double)motion / (double)WHEEL_DELTA)); - } else { - mb->set_button_index(BUTTON_WHEEL_RIGHT); - mb->set_factor(fabs((double)motion / (double)WHEEL_DELTA)); - } - } break; - case WM_XBUTTONDOWN: { - - mb->set_pressed(true); - if (HIWORD(wParam) == XBUTTON1) - mb->set_button_index(BUTTON_XBUTTON1); - else - mb->set_button_index(BUTTON_XBUTTON2); - } break; - case WM_XBUTTONUP: { - - mb->set_pressed(false); - if (HIWORD(wParam) == XBUTTON1) - mb->set_button_index(BUTTON_XBUTTON1); - else - mb->set_button_index(BUTTON_XBUTTON2); - } break; - case WM_XBUTTONDBLCLK: { - - mb->set_pressed(true); - if (HIWORD(wParam) == XBUTTON1) - mb->set_button_index(BUTTON_XBUTTON1); - else - mb->set_button_index(BUTTON_XBUTTON2); - mb->set_doubleclick(true); - } break; - default: { - return 0; - } - } - - mb->set_control((wParam & MK_CONTROL) != 0); - mb->set_shift((wParam & MK_SHIFT) != 0); - mb->set_alt(alt_mem); - //mb->get_alt()=(wParam&MK_MENU)!=0; - if (mb->is_pressed()) - last_button_state |= (1 << (mb->get_button_index() - 1)); - else - last_button_state &= ~(1 << (mb->get_button_index() - 1)); - mb->set_button_mask(last_button_state); - - mb->set_position(Vector2(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))); - - if (mouse_mode == MOUSE_MODE_CAPTURED && !use_raw_input) { - - mb->set_position(Vector2(old_x, old_y)); - } - - if (uMsg != WM_MOUSEWHEEL && uMsg != WM_MOUSEHWHEEL) { - if (mb->is_pressed()) { - - if (++pressrc > 0 && mouse_mode != MOUSE_MODE_CAPTURED) - SetCapture(hWnd); - } else { - - if (--pressrc <= 0) { - if (mouse_mode != MOUSE_MODE_CAPTURED) { - ReleaseCapture(); - } - pressrc = 0; - } - } - } else { - // for reasons unknown to mankind, wheel comes in screen coordinates - POINT coords; - coords.x = mb->get_position().x; - coords.y = mb->get_position().y; - - ScreenToClient(hWnd, &coords); - - mb->set_position(Vector2(coords.x, coords.y)); - } - - mb->set_global_position(mb->get_position()); - - if (main_loop) { - input->accumulate_input_event(mb); - if (mb->is_pressed() && mb->get_button_index() > 3 && mb->get_button_index() < 8) { - //send release for mouse wheel - Ref<InputEventMouseButton> mbd = mb->duplicate(); - last_button_state &= ~(1 << (mbd->get_button_index() - 1)); - mbd->set_button_mask(last_button_state); - mbd->set_pressed(false); - input->accumulate_input_event(mbd); - } - } - } break; - - case WM_MOVE: { - if (!IsIconic(hWnd)) { - int x = LOWORD(lParam); - int y = HIWORD(lParam); - last_pos = Point2(x, y); - } - } break; - - case WM_SIZE: { - // Ignore size when a SIZE_MINIMIZED event is triggered - if (wParam != SIZE_MINIMIZED) { - int window_w = LOWORD(lParam); - int window_h = HIWORD(lParam); - if (window_w > 0 && window_h > 0 && !preserve_window_size) { - video_mode.width = window_w; - video_mode.height = window_h; - } else { - preserve_window_size = false; - set_window_size(Size2(video_mode.width, video_mode.height)); - } -#if defined(VULKAN_ENABLED) - if (video_driver_index == VIDEO_DRIVER_VULKAN) { - context_vulkan->window_resize(0, video_mode.width, video_mode.height); - } -#endif - } - - if (wParam == SIZE_MAXIMIZED) { - maximized = true; - minimized = false; - } else if (wParam == SIZE_MINIMIZED) { - maximized = false; - minimized = true; - } else if (wParam == SIZE_RESTORED) { - maximized = false; - minimized = false; - } - if (is_layered_allowed() && layered_window) { - DeleteObject(hBitmap); - - RECT r; - GetWindowRect(hWnd, &r); - dib_size = Size2i(r.right - r.left, r.bottom - r.top); - - BITMAPINFO bmi; - ZeroMemory(&bmi, sizeof(BITMAPINFO)); - bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); - bmi.bmiHeader.biWidth = dib_size.x; - bmi.bmiHeader.biHeight = dib_size.y; - bmi.bmiHeader.biPlanes = 1; - bmi.bmiHeader.biBitCount = 32; - bmi.bmiHeader.biCompression = BI_RGB; - bmi.bmiHeader.biSizeImage = dib_size.x * dib_size.y * 4; - hBitmap = CreateDIBSection(hDC_dib, &bmi, DIB_RGB_COLORS, (void **)&dib_data, NULL, 0x0); - SelectObject(hDC_dib, hBitmap); - - ZeroMemory(dib_data, dib_size.x * dib_size.y * 4); - } - //return 0; // Jump Back - } break; - - case WM_ENTERSIZEMOVE: { - input->release_pressed_events(); - move_timer_id = SetTimer(hWnd, 1, USER_TIMER_MINIMUM, (TIMERPROC)NULL); - } break; - case WM_EXITSIZEMOVE: { - KillTimer(hWnd, move_timer_id); - } break; - case WM_TIMER: { - if (wParam == move_timer_id) { - process_key_events(); - if (!Main::is_iterating()) { - Main::iteration(); - } - } - } break; - - case WM_SYSKEYDOWN: - case WM_SYSKEYUP: - case WM_KEYUP: - case WM_KEYDOWN: { - - if (wParam == VK_SHIFT) - shift_mem = uMsg == WM_KEYDOWN; - if (wParam == VK_CONTROL) - control_mem = uMsg == WM_KEYDOWN; - if (wParam == VK_MENU) { - alt_mem = (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN); - if (lParam & (1 << 24)) - gr_mem = alt_mem; - } - - if (mouse_mode == MOUSE_MODE_CAPTURED) { - // When SetCapture is used, ALT+F4 hotkey is ignored by Windows, so handle it ourselves - if (wParam == VK_F4 && alt_mem && (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN)) { - if (main_loop) - main_loop->notification(MainLoop::NOTIFICATION_WM_QUIT_REQUEST); - } - } - /* - if (wParam==VK_WIN) TODO wtf is this? - meta_mem=uMsg==WM_KEYDOWN; - */ - [[fallthrough]]; - } - case WM_CHAR: { - - ERR_BREAK(key_event_pos >= KEY_EVENT_BUFFER_SIZE); - - // Make sure we don't include modifiers for the modifier key itself. - KeyEvent ke; - ke.shift = (wParam != VK_SHIFT) ? shift_mem : false; - ke.alt = (!(wParam == VK_MENU && (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN))) ? alt_mem : false; - ke.control = (wParam != VK_CONTROL) ? control_mem : false; - ke.meta = meta_mem; - ke.uMsg = uMsg; - - if (ke.uMsg == WM_SYSKEYDOWN) - ke.uMsg = WM_KEYDOWN; - if (ke.uMsg == WM_SYSKEYUP) - ke.uMsg = WM_KEYUP; - - ke.wParam = wParam; - ke.lParam = lParam; - key_event_buffer[key_event_pos++] = ke; - - } break; - case WM_INPUTLANGCHANGEREQUEST: { - - // FIXME: Do something? - } break; - - case WM_TOUCH: { - - BOOL bHandled = FALSE; - UINT cInputs = LOWORD(wParam); - PTOUCHINPUT pInputs = memnew_arr(TOUCHINPUT, cInputs); - if (pInputs) { - if (GetTouchInputInfo((HTOUCHINPUT)lParam, cInputs, pInputs, sizeof(TOUCHINPUT))) { - for (UINT i = 0; i < cInputs; i++) { - TOUCHINPUT ti = pInputs[i]; - POINT touch_pos = { - TOUCH_COORD_TO_PIXEL(ti.x), - TOUCH_COORD_TO_PIXEL(ti.y), - }; - ScreenToClient(hWnd, &touch_pos); - //do something with each touch input entry - if (ti.dwFlags & TOUCHEVENTF_MOVE) { - - _drag_event(touch_pos.x, touch_pos.y, ti.dwID); - } else if (ti.dwFlags & (TOUCHEVENTF_UP | TOUCHEVENTF_DOWN)) { - - _touch_event(ti.dwFlags & TOUCHEVENTF_DOWN, touch_pos.x, touch_pos.y, ti.dwID); - }; - } - bHandled = TRUE; - } else { - /* handle the error here */ - } - memdelete_arr(pInputs); - } else { - /* handle the error here, probably out of memory */ - } - if (bHandled) { - CloseTouchInputHandle((HTOUCHINPUT)lParam); - return 0; - }; - - } break; - - case WM_DEVICECHANGE: { - - joypad->probe_joypads(); - } break; - case WM_SETCURSOR: { - if (LOWORD(lParam) == HTCLIENT) { - if (window_has_focus && (mouse_mode == MOUSE_MODE_HIDDEN || mouse_mode == MOUSE_MODE_CAPTURED)) { - //Hide the cursor - if (hCursor == NULL) - hCursor = SetCursor(NULL); - else - SetCursor(NULL); - } else { - if (hCursor != NULL) { - CursorShape c = cursor_shape; - cursor_shape = CURSOR_MAX; - set_cursor_shape(c); - hCursor = NULL; - } - } - } - - } break; - case WM_DROPFILES: { - - HDROP hDropInfo = (HDROP)wParam; - const int buffsize = 4096; - wchar_t buf[buffsize]; - - int fcount = DragQueryFileW(hDropInfo, 0xFFFFFFFF, NULL, 0); - - Vector<String> files; - - for (int i = 0; i < fcount; i++) { - - DragQueryFileW(hDropInfo, i, buf, buffsize); - String file = buf; - files.push_back(file); - } - - if (files.size() && main_loop) { - main_loop->drop_files(files, 0); - } - - } break; - - default: { - - if (user_proc) { - - return CallWindowProcW(user_proc, hWnd, uMsg, wParam, lParam); - }; - }; - } - - return DefWindowProcW(hWnd, uMsg, wParam, lParam); -} - -LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - - OS_Windows *os_win = static_cast<OS_Windows *>(OS::get_singleton()); - if (os_win) - return os_win->WndProc(hWnd, uMsg, wParam, lParam); - else - return DefWindowProcW(hWnd, uMsg, wParam, lParam); -} - -void OS_Windows::process_key_events() { - - for (int i = 0; i < key_event_pos; i++) { - - KeyEvent &ke = key_event_buffer[i]; - switch (ke.uMsg) { - - case WM_CHAR: { - if ((i == 0 && ke.uMsg == WM_CHAR) || (i > 0 && key_event_buffer[i - 1].uMsg == WM_CHAR)) { - Ref<InputEventKey> k; - k.instance(); - - k->set_shift(ke.shift); - k->set_alt(ke.alt); - k->set_control(ke.control); - k->set_metakey(ke.meta); - k->set_pressed(true); - k->set_keycode(KeyMappingWindows::get_keysym(ke.wParam)); - k->set_physical_keycode(KeyMappingWindows::get_scansym((ke.lParam >> 16) & 0xFF, ke.lParam & (1 << 24))); - k->set_unicode(ke.wParam); - if (k->get_unicode() && gr_mem) { - k->set_alt(false); - k->set_control(false); - } - - if (k->get_unicode() < 32) - k->set_unicode(0); - - input->accumulate_input_event(k); - } - - //do nothing - } break; - case WM_KEYUP: - case WM_KEYDOWN: { - - Ref<InputEventKey> k; - k.instance(); - - k->set_shift(ke.shift); - k->set_alt(ke.alt); - k->set_control(ke.control); - k->set_metakey(ke.meta); - - k->set_pressed(ke.uMsg == WM_KEYDOWN); - - if ((ke.lParam & (1 << 24)) && (ke.wParam == VK_RETURN)) { - // Special case for Numpad Enter key - k->set_keycode(KEY_KP_ENTER); - } else { - k->set_keycode(KeyMappingWindows::get_keysym(ke.wParam)); - } - - k->set_physical_keycode(KeyMappingWindows::get_scansym((ke.lParam >> 16) & 0xFF, ke.lParam & (1 << 24))); - - if (i + 1 < key_event_pos && key_event_buffer[i + 1].uMsg == WM_CHAR) { - k->set_unicode(key_event_buffer[i + 1].wParam); - } - if (k->get_unicode() && gr_mem) { - k->set_alt(false); - k->set_control(false); - } - - if (k->get_unicode() < 32) - k->set_unicode(0); - - k->set_echo((ke.uMsg == WM_KEYDOWN && (ke.lParam & (1 << 30)))); - - input->accumulate_input_event(k); - - } break; - } - } - - key_event_pos = 0; -} - -enum _MonitorDpiType { - MDT_Effective_DPI = 0, - MDT_Angular_DPI = 1, - MDT_Raw_DPI = 2, - MDT_Default = MDT_Effective_DPI -}; - -static int QueryDpiForMonitor(HMONITOR hmon, _MonitorDpiType dpiType = MDT_Default) { - - int dpiX = 96, dpiY = 96; - - static HMODULE Shcore = NULL; - typedef HRESULT(WINAPI * GetDPIForMonitor_t)(HMONITOR hmonitor, _MonitorDpiType dpiType, UINT * dpiX, UINT * dpiY); - static GetDPIForMonitor_t getDPIForMonitor = NULL; - - if (Shcore == NULL) { - Shcore = LoadLibraryW(L"Shcore.dll"); - getDPIForMonitor = Shcore ? (GetDPIForMonitor_t)GetProcAddress(Shcore, "GetDpiForMonitor") : NULL; - - if ((Shcore == NULL) || (getDPIForMonitor == NULL)) { - if (Shcore) - FreeLibrary(Shcore); - Shcore = (HMODULE)INVALID_HANDLE_VALUE; - } - } - - UINT x = 0, y = 0; - HRESULT hr = E_FAIL; - if (hmon && (Shcore != (HMODULE)INVALID_HANDLE_VALUE)) { - hr = getDPIForMonitor(hmon, dpiType /*MDT_Effective_DPI*/, &x, &y); - if (SUCCEEDED(hr) && (x > 0) && (y > 0)) { - - dpiX = (int)x; - dpiY = (int)y; - } - } else { - static int overallX = 0, overallY = 0; - if (overallX <= 0 || overallY <= 0) { - HDC hdc = GetDC(NULL); - if (hdc) { - overallX = GetDeviceCaps(hdc, LOGPIXELSX); - overallY = GetDeviceCaps(hdc, LOGPIXELSY); - ReleaseDC(NULL, hdc); - } - } - if (overallX > 0 && overallY > 0) { - dpiX = overallX; - dpiY = overallY; - } - } - - return (dpiX + dpiY) / 2; -} - -typedef enum _SHC_PROCESS_DPI_AWARENESS { - SHC_PROCESS_DPI_UNAWARE = 0, - SHC_PROCESS_SYSTEM_DPI_AWARE = 1, - SHC_PROCESS_PER_MONITOR_DPI_AWARE = 2 -} SHC_PROCESS_DPI_AWARENESS; - -int OS_Windows::get_current_video_driver() const { - return video_driver_index; -} - -Error OS_Windows::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) { - main_loop = NULL; - outside = true; - window_has_focus = true; - WNDCLASSEXW wc; - - if (is_hidpi_allowed()) { - HMODULE Shcore = LoadLibraryW(L"Shcore.dll"); - - if (Shcore != NULL) { - typedef HRESULT(WINAPI * SetProcessDpiAwareness_t)(SHC_PROCESS_DPI_AWARENESS); - - SetProcessDpiAwareness_t SetProcessDpiAwareness = (SetProcessDpiAwareness_t)GetProcAddress(Shcore, "SetProcessDpiAwareness"); - - if (SetProcessDpiAwareness) { - SetProcessDpiAwareness(SHC_PROCESS_SYSTEM_DPI_AWARE); - } - } - } - - video_mode = p_desired; - //printf("**************** desired %s, mode %s\n", p_desired.fullscreen?"true":"false", video_mode.fullscreen?"true":"false"); - RECT WindowRect; - - WindowRect.left = 0; - WindowRect.right = video_mode.width; - WindowRect.top = 0; - WindowRect.bottom = video_mode.height; - - memset(&wc, 0, sizeof(WNDCLASSEXW)); - wc.cbSize = sizeof(WNDCLASSEXW); - wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC | CS_DBLCLKS; - wc.lpfnWndProc = (WNDPROC)::WndProc; - wc.cbClsExtra = 0; - wc.cbWndExtra = 0; - //wc.hInstance = hInstance; - wc.hInstance = godot_hinstance ? godot_hinstance : GetModuleHandle(NULL); - wc.hIcon = LoadIcon(NULL, IDI_WINLOGO); - wc.hCursor = NULL; //LoadCursor(NULL, IDC_ARROW); - wc.hbrBackground = NULL; - wc.lpszMenuName = NULL; - wc.lpszClassName = L"Engine"; - - if (!RegisterClassExW(&wc)) { - MessageBox(NULL, "Failed To Register The Window Class.", "ERROR", MB_OK | MB_ICONEXCLAMATION); - return ERR_UNAVAILABLE; - } - - use_raw_input = true; - - RAWINPUTDEVICE Rid[1]; - - Rid[0].usUsagePage = 0x01; - Rid[0].usUsage = 0x02; - Rid[0].dwFlags = 0; - Rid[0].hwndTarget = 0; - - if (RegisterRawInputDevices(Rid, 1, sizeof(Rid[0])) == FALSE) { - //registration failed. - use_raw_input = false; - } - - pre_fs_valid = true; - if (video_mode.fullscreen) { - - /* this returns DPI unaware size, commenting - DEVMODE current; - memset(¤t, 0, sizeof(current)); - EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, ¤t); - - WindowRect.right = current.dmPelsWidth; - WindowRect.bottom = current.dmPelsHeight; - - */ - - EnumSizeData data = { 0, 0, Size2() }; - EnumDisplayMonitors(NULL, NULL, _MonitorEnumProcSize, (LPARAM)&data); - - WindowRect.right = data.size.width; - WindowRect.bottom = data.size.height; - - /* DEVMODE dmScreenSettings; - memset(&dmScreenSettings,0,sizeof(dmScreenSettings)); - dmScreenSettings.dmSize=sizeof(dmScreenSettings); - dmScreenSettings.dmPelsWidth = video_mode.width; - dmScreenSettings.dmPelsHeight = video_mode.height; - dmScreenSettings.dmBitsPerPel = current.dmBitsPerPel; - dmScreenSettings.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT; - - LONG err = ChangeDisplaySettings(&dmScreenSettings,CDS_FULLSCREEN); - if (err!=DISP_CHANGE_SUCCESSFUL) { - - video_mode.fullscreen=false; - }*/ - pre_fs_valid = false; - } - - DWORD dwExStyle; - DWORD dwStyle; - - if (video_mode.fullscreen || video_mode.borderless_window) { - - dwExStyle = WS_EX_APPWINDOW; - dwStyle = WS_POPUP; - - } else { - dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; - dwStyle = WS_OVERLAPPEDWINDOW; - if (!video_mode.resizable) { - dwStyle &= ~WS_THICKFRAME; - dwStyle &= ~WS_MAXIMIZEBOX; - } - } - - AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle); - - char *windowid; -#ifdef MINGW_ENABLED - windowid = getenv("GODOT_WINDOWID"); -#else - size_t len; - _dupenv_s(&windowid, &len, "GODOT_WINDOWID"); -#endif - - if (windowid) { - -// strtoull on mingw -#ifdef MINGW_ENABLED - hWnd = (HWND)strtoull(windowid, NULL, 0); -#else - hWnd = (HWND)_strtoui64(windowid, NULL, 0); -#endif - free(windowid); - SetLastError(0); - user_proc = (WNDPROC)GetWindowLongPtr(hWnd, GWLP_WNDPROC); - SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)(WNDPROC)::WndProc); - DWORD le = GetLastError(); - if (user_proc == 0 && le != 0) { - - printf("Error setting WNDPROC: %li\n", le); - }; - GetWindowLongPtr(hWnd, GWLP_WNDPROC); - - RECT rect; - if (!GetClientRect(hWnd, &rect)) { - MessageBoxW(NULL, L"Window Creation Error.", L"ERROR", MB_OK | MB_ICONEXCLAMATION); - return ERR_UNAVAILABLE; - }; - video_mode.width = rect.right; - video_mode.height = rect.bottom; - video_mode.fullscreen = false; - } else { - - hWnd = CreateWindowExW( - dwExStyle, - L"Engine", L"", - dwStyle | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, - (GetSystemMetrics(SM_CXSCREEN) - WindowRect.right) / 2, - (GetSystemMetrics(SM_CYSCREEN) - WindowRect.bottom) / 2, - WindowRect.right - WindowRect.left, - WindowRect.bottom - WindowRect.top, - NULL, NULL, hInstance, NULL); - if (!hWnd) { - MessageBoxW(NULL, L"Window Creation Error.", L"ERROR", MB_OK | MB_ICONEXCLAMATION); - return ERR_UNAVAILABLE; - } - }; - - if (video_mode.always_on_top) { - SetWindowPos(hWnd, video_mode.always_on_top ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); - } - - //!!!!!!!!!!!!!!!!!!!!!!!!!! - //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) + "]"); - //!!!!!!!!!!!!!!!!!!!!!!!!!! - - // Init context and rendering device -#if defined(OPENGL_ENABLED) - if (video_driver_index == VIDEO_DRIVER_GLES2) { - - context_gles2 = memnew(ContextGL_Windows(hWnd, false)); - - if (context_gles2->initialize() != OK) { - memdelete(context_gles2); - context_gles2 = NULL; - ERR_FAIL_V(ERR_UNAVAILABLE); - } - - context_gles2->set_use_vsync(video_mode.use_vsync); - set_vsync_via_compositor(video_mode.vsync_via_compositor); - - 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(VulkanContextWindows); - if (context_vulkan->initialize() != OK) { - memdelete(context_vulkan); - context_vulkan = NULL; - ERR_FAIL_V(ERR_UNAVAILABLE); - } - if (context_vulkan->window_create(hWnd, hInstance, get_video_mode().width, get_video_mode().height) == -1) { - memdelete(context_vulkan); - context_vulkan = NULL; - ERR_FAIL_V(ERR_UNAVAILABLE); - } - - //temporary - rendering_device_vulkan = memnew(RenderingDeviceVulkan); - rendering_device_vulkan->initialize(context_vulkan); - - RasterizerRD::make_current(); - } -#endif - - 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(); - - input = memnew(InputDefault); - joypad = memnew(JoypadWindows(input, &hWnd)); - - AudioDriverManager::initialize(p_audio_driver); - - TRACKMOUSEEVENT tme; - tme.cbSize = sizeof(TRACKMOUSEEVENT); - tme.dwFlags = TME_LEAVE; - tme.hwndTrack = hWnd; - tme.dwHoverTime = HOVER_DEFAULT; - TrackMouseEvent(&tme); - - RegisterTouchWindow(hWnd, 0); - - _ensure_user_data_dir(); - - DragAcceptFiles(hWnd, true); - - move_timer_id = 1; - - if (!is_no_window_mode_enabled()) { - ShowWindow(hWnd, SW_SHOW); // Show The Window - SetForegroundWindow(hWnd); // Slightly Higher Priority - SetFocus(hWnd); // Sets Keyboard Focus To - } - - if (p_desired.layered) { - set_window_per_pixel_transparency_enabled(true); - } - - // IME - im_himc = ImmGetContext(hWnd); - ImmReleaseContext(hWnd, im_himc); - - im_position = Vector2(); - - set_ime_active(false); - - if (!OS::get_singleton()->is_in_low_processor_usage_mode()) { - //SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS); - SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS); - DWORD index = 0; - HANDLE handle = AvSetMmThreadCharacteristics("Games", &index); - if (handle) - AvSetMmThreadPriority(handle, AVRT_PRIORITY_CRITICAL); - - // This is needed to make sure that background work does not starve the main thread. - // This is only setting priority of this thread, not the whole process. - SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); - } - - update_real_mouse_position(); - - return OK; } -void OS_Windows::set_clipboard(const String &p_text) { - - // Convert LF line endings to CRLF in clipboard content - // Otherwise, line endings won't be visible when pasted in other software - String text = p_text.replace("\n", "\r\n"); - - if (!OpenClipboard(hWnd)) { - ERR_FAIL_MSG("Unable to open clipboard."); - } - EmptyClipboard(); - - HGLOBAL mem = GlobalAlloc(GMEM_MOVEABLE, (text.length() + 1) * sizeof(CharType)); - ERR_FAIL_COND_MSG(mem == NULL, "Unable to allocate memory for clipboard contents."); - - LPWSTR lptstrCopy = (LPWSTR)GlobalLock(mem); - memcpy(lptstrCopy, text.c_str(), (text.length() + 1) * sizeof(CharType)); - GlobalUnlock(mem); - - SetClipboardData(CF_UNICODETEXT, mem); - - // set the CF_TEXT version (not needed?) - CharString utf8 = text.utf8(); - mem = GlobalAlloc(GMEM_MOVEABLE, utf8.length() + 1); - ERR_FAIL_COND_MSG(mem == NULL, "Unable to allocate memory for clipboard contents."); - - LPTSTR ptr = (LPTSTR)GlobalLock(mem); - memcpy(ptr, utf8.get_data(), utf8.length()); - ptr[utf8.length()] = 0; - GlobalUnlock(mem); - - SetClipboardData(CF_TEXT, mem); - - CloseClipboard(); -}; - -String OS_Windows::get_clipboard() const { - - String ret; - if (!OpenClipboard(hWnd)) { - ERR_FAIL_V_MSG("", "Unable to open clipboard."); - }; - - if (IsClipboardFormatAvailable(CF_UNICODETEXT)) { - - HGLOBAL mem = GetClipboardData(CF_UNICODETEXT); - if (mem != NULL) { - - LPWSTR ptr = (LPWSTR)GlobalLock(mem); - if (ptr != NULL) { - - ret = String((CharType *)ptr); - GlobalUnlock(mem); - }; - }; - - } else if (IsClipboardFormatAvailable(CF_TEXT)) { - - HGLOBAL mem = GetClipboardData(CF_UNICODETEXT); - if (mem != NULL) { - - LPTSTR ptr = (LPTSTR)GlobalLock(mem); - if (ptr != NULL) { - - ret.parse_utf8((const char *)ptr); - GlobalUnlock(mem); - }; - }; - }; - - CloseClipboard(); - - return ret; -}; - void OS_Windows::delete_main_loop() { if (main_loop) @@ -1629,7 +225,6 @@ void OS_Windows::delete_main_loop() { void OS_Windows::set_main_loop(MainLoop *p_main_loop) { - input->set_main_loop(p_main_loop); main_loop = p_main_loop; } @@ -1643,38 +238,6 @@ void OS_Windows::finalize() { memdelete(main_loop); main_loop = NULL; - - memdelete(joypad); - memdelete(input); - touch_state.clear(); - - cursors_cache.clear(); - visual_server->finish(); - memdelete(visual_server); - -#if defined(OPENGL_ENABLED) - if (video_driver_index == VIDEO_DRIVER_GLES2) { - - if (context_gles2) - memdelete(context_gles2); - } -#endif -#if defined(VULKAN_ENABLED) - if (video_driver_index == VIDEO_DRIVER_VULKAN) { - - if (rendering_device_vulkan) { - rendering_device_vulkan->finalize(); - memdelete(rendering_device_vulkan); - } - - if (context_vulkan) - memdelete(context_vulkan); - } -#endif - - if (user_proc) { - SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)user_proc); - }; } void OS_Windows::finalize_core() { @@ -1685,576 +248,6 @@ void OS_Windows::finalize_core() { NetSocketPosix::cleanup(); } -void OS_Windows::alert(const String &p_alert, const String &p_title) { - - if (!is_no_window_mode_enabled()) - MessageBoxW(NULL, p_alert.c_str(), p_title.c_str(), MB_OK | MB_ICONEXCLAMATION | MB_TASKMODAL); - else - print_line("ALERT: " + p_alert); -} - -void OS_Windows::set_mouse_mode(MouseMode p_mode) { - - if (mouse_mode == p_mode) - return; - - _set_mouse_mode_impl(p_mode); - - mouse_mode = p_mode; -} - -void OS_Windows::_set_mouse_mode_impl(MouseMode p_mode) { - - if (p_mode == MOUSE_MODE_CAPTURED || p_mode == MOUSE_MODE_CONFINED) { - RECT clipRect; - GetClientRect(hWnd, &clipRect); - ClientToScreen(hWnd, (POINT *)&clipRect.left); - ClientToScreen(hWnd, (POINT *)&clipRect.right); - ClipCursor(&clipRect); - if (p_mode == MOUSE_MODE_CAPTURED) { - center = Point2i(video_mode.width / 2, video_mode.height / 2); - POINT pos = { (int)center.x, (int)center.y }; - ClientToScreen(hWnd, &pos); - SetCursorPos(pos.x, pos.y); - SetCapture(hWnd); - } - } else { - ReleaseCapture(); - ClipCursor(NULL); - } - - if (p_mode == MOUSE_MODE_CAPTURED || p_mode == MOUSE_MODE_HIDDEN) { - hCursor = SetCursor(NULL); - } else { - CursorShape c = cursor_shape; - cursor_shape = CURSOR_MAX; - set_cursor_shape(c); - } -} -OS_Windows::MouseMode OS_Windows::get_mouse_mode() const { - - return mouse_mode; -} - -void OS_Windows::warp_mouse_position(const Point2 &p_to) { - - if (mouse_mode == MOUSE_MODE_CAPTURED) { - - old_x = p_to.x; - old_y = p_to.y; - } else { - - POINT p; - p.x = p_to.x; - p.y = p_to.y; - ClientToScreen(hWnd, &p); - - SetCursorPos(p.x, p.y); - } -} - -Point2 OS_Windows::get_mouse_position() const { - - return Point2(old_x, old_y); -} - -void OS_Windows::update_real_mouse_position() { - - POINT mouse_pos; - if (GetCursorPos(&mouse_pos) && ScreenToClient(hWnd, &mouse_pos)) { - if (mouse_pos.x > 0 && mouse_pos.y > 0 && mouse_pos.x <= video_mode.width && mouse_pos.y <= video_mode.height) { - old_x = mouse_pos.x; - old_y = mouse_pos.y; - old_invalid = false; - input->set_mouse_position(Point2i(mouse_pos.x, mouse_pos.y)); - } - } -} - -int OS_Windows::get_mouse_button_state() const { - - return last_button_state; -} - -void OS_Windows::set_window_title(const String &p_title) { - - SetWindowTextW(hWnd, p_title.c_str()); -} - -void OS_Windows::set_video_mode(const VideoMode &p_video_mode, int p_screen) { -} - -OS::VideoMode OS_Windows::get_video_mode(int p_screen) const { - - return video_mode; -} -void OS_Windows::get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen) const { -} - -static BOOL CALLBACK _MonitorEnumProcCount(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { - - int *data = (int *)dwData; - (*data)++; - return TRUE; -} - -int OS_Windows::get_screen_count() const { - - int data = 0; - EnumDisplayMonitors(NULL, NULL, _MonitorEnumProcCount, (LPARAM)&data); - return data; -} - -typedef struct { - int count; - int screen; - HMONITOR monitor; -} EnumScreenData; - -static BOOL CALLBACK _MonitorEnumProcScreen(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { - - EnumScreenData *data = (EnumScreenData *)dwData; - if (data->monitor == hMonitor) { - data->screen = data->count; - } - - data->count++; - return TRUE; -} - -int OS_Windows::get_current_screen() const { - - EnumScreenData data = { 0, 0, MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST) }; - EnumDisplayMonitors(NULL, NULL, _MonitorEnumProcScreen, (LPARAM)&data); - return data.screen; -} - -void OS_Windows::set_current_screen(int p_screen) { - - Vector2 ofs = get_window_position() - get_screen_position(get_current_screen()); - set_window_position(ofs + get_screen_position(p_screen)); -} - -static BOOL CALLBACK _MonitorEnumProcPos(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { - - EnumPosData *data = (EnumPosData *)dwData; - if (data->count == data->screen) { - data->pos.x = lprcMonitor->left; - data->pos.y = lprcMonitor->top; - } - - data->count++; - return TRUE; -} - -Point2 OS_Windows::get_screen_position(int p_screen) const { - - EnumPosData data = { 0, p_screen == -1 ? get_current_screen() : p_screen, Point2() }; - EnumDisplayMonitors(NULL, NULL, _MonitorEnumProcPos, (LPARAM)&data); - return data.pos; -} - -Size2 OS_Windows::get_screen_size(int p_screen) const { - - EnumSizeData data = { 0, p_screen == -1 ? get_current_screen() : p_screen, Size2() }; - EnumDisplayMonitors(NULL, NULL, _MonitorEnumProcSize, (LPARAM)&data); - return data.size; -} - -typedef struct { - int count; - int screen; - int dpi; -} EnumDpiData; - -static BOOL CALLBACK _MonitorEnumProcDpi(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { - - EnumDpiData *data = (EnumDpiData *)dwData; - if (data->count == data->screen) { - data->dpi = QueryDpiForMonitor(hMonitor); - } - - data->count++; - return TRUE; -} - -int OS_Windows::get_screen_dpi(int p_screen) const { - - EnumDpiData data = { 0, p_screen == -1 ? get_current_screen() : p_screen, 72 }; - EnumDisplayMonitors(NULL, NULL, _MonitorEnumProcDpi, (LPARAM)&data); - return data.dpi; -} - -Point2 OS_Windows::get_window_position() const { - - if (minimized) { - return last_pos; - } - - RECT r; - GetWindowRect(hWnd, &r); - return Point2(r.left, r.top); -} - -void OS_Windows::set_window_position(const Point2 &p_position) { - - if (video_mode.fullscreen) return; - RECT r; - GetWindowRect(hWnd, &r); - MoveWindow(hWnd, p_position.x, p_position.y, r.right - r.left, r.bottom - r.top, TRUE); - - // Don't let the mouse leave the window when moved - if (mouse_mode == MOUSE_MODE_CONFINED) { - RECT rect; - GetClientRect(hWnd, &rect); - ClientToScreen(hWnd, (POINT *)&rect.left); - ClientToScreen(hWnd, (POINT *)&rect.right); - ClipCursor(&rect); - } - - last_pos = p_position; - update_real_mouse_position(); -} - -Size2 OS_Windows::get_window_size() const { - - if (minimized) { - return Size2(video_mode.width, video_mode.height); - } - - RECT r; - if (GetClientRect(hWnd, &r)) { // Only area inside of window border - return Size2(r.right - r.left, r.bottom - r.top); - } - return Size2(); -} - -Size2 OS_Windows::get_max_window_size() const { - return max_size; -} - -Size2 OS_Windows::get_min_window_size() const { - return min_size; -} - -void OS_Windows::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; -} - -void OS_Windows::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; -} - -Size2 OS_Windows::get_real_window_size() const { - - RECT r; - if (GetWindowRect(hWnd, &r)) { // Includes area of the window border - return Size2(r.right - r.left, r.bottom - r.top); - } - return Size2(); -} - -void OS_Windows::set_window_size(const Size2 p_size) { - - int w = p_size.width; - int h = p_size.height; - - video_mode.width = w; - video_mode.height = h; -#if defined(VULKAN_ENABLED) - if (video_driver_index == VIDEO_DRIVER_VULKAN) { - context_vulkan->window_resize(0, video_mode.width, video_mode.height); - } -#endif - - if (video_mode.fullscreen) { - return; - } - - RECT rect; - GetWindowRect(hWnd, &rect); - - if (!video_mode.borderless_window) { - RECT crect; - GetClientRect(hWnd, &crect); - - w += (rect.right - rect.left) - (crect.right - crect.left); - h += (rect.bottom - rect.top) - (crect.bottom - crect.top); - } - - MoveWindow(hWnd, rect.left, rect.top, w, h, TRUE); - - // Don't let the mouse leave the window when resizing to a smaller resolution - if (mouse_mode == MOUSE_MODE_CONFINED) { - RECT crect; - GetClientRect(hWnd, &crect); - ClientToScreen(hWnd, (POINT *)&crect.left); - ClientToScreen(hWnd, (POINT *)&crect.right); - ClipCursor(&crect); - } -} -void OS_Windows::set_window_fullscreen(bool p_enabled) { - - if (video_mode.fullscreen == p_enabled) - return; - - if (layered_window) - set_window_per_pixel_transparency_enabled(false); - - if (p_enabled) { - - was_maximized = maximized; - - if (pre_fs_valid) { - GetWindowRect(hWnd, &pre_fs_rect); - } - - int cs = get_current_screen(); - Point2 pos = get_screen_position(cs); - Size2 size = get_screen_size(cs); - - video_mode.fullscreen = true; - - _update_window_style(false); - - MoveWindow(hWnd, pos.x, pos.y, size.width, size.height, TRUE); - - } else { - - RECT rect; - - video_mode.fullscreen = false; - - if (pre_fs_valid) { - rect = pre_fs_rect; - } else { - rect.left = 0; - rect.right = video_mode.width; - rect.top = 0; - rect.bottom = video_mode.height; - } - - _update_window_style(false, was_maximized); - - MoveWindow(hWnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE); - - pre_fs_valid = true; - } -} -bool OS_Windows::is_window_fullscreen() const { - - return video_mode.fullscreen; -} -void OS_Windows::set_window_resizable(bool p_enabled) { - - if (video_mode.resizable == p_enabled) - return; - - video_mode.resizable = p_enabled; - - _update_window_style(); -} -bool OS_Windows::is_window_resizable() const { - - return video_mode.resizable; -} -void OS_Windows::set_window_minimized(bool p_enabled) { - - if (p_enabled) { - maximized = false; - minimized = true; - ShowWindow(hWnd, SW_MINIMIZE); - } else { - ShowWindow(hWnd, SW_RESTORE); - maximized = false; - minimized = false; - } -} -bool OS_Windows::is_window_minimized() const { - - return minimized; -} -void OS_Windows::set_window_maximized(bool p_enabled) { - - if (p_enabled) { - maximized = true; - minimized = false; - ShowWindow(hWnd, SW_MAXIMIZE); - } else { - ShowWindow(hWnd, SW_RESTORE); - maximized = false; - minimized = false; - } -} -bool OS_Windows::is_window_maximized() const { - - return maximized; -} - -void OS_Windows::set_window_always_on_top(bool p_enabled) { - if (video_mode.always_on_top == p_enabled) - return; - - video_mode.always_on_top = p_enabled; - - _update_window_style(); -} - -bool OS_Windows::is_window_always_on_top() const { - return video_mode.always_on_top; -} - -bool OS_Windows::is_window_focused() const { - - return window_focused; -} - -void OS_Windows::set_console_visible(bool p_enabled) { - if (console_visible == p_enabled) - return; - ShowWindow(GetConsoleWindow(), p_enabled ? SW_SHOW : SW_HIDE); - console_visible = p_enabled; -} - -bool OS_Windows::is_console_visible() const { - return console_visible; -} - -bool OS_Windows::get_window_per_pixel_transparency_enabled() const { - - if (!is_layered_allowed()) return false; - return layered_window; -} - -void OS_Windows::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); - //enable per-pixel alpha - hDC_dib = CreateCompatibleDC(GetDC(hWnd)); - - SetWindowLong(hWnd, GWL_EXSTYLE, GetWindowLong(hWnd, GWL_EXSTYLE) | WS_EX_LAYERED); - - RECT r; - GetWindowRect(hWnd, &r); - dib_size = Size2(r.right - r.left, r.bottom - r.top); - - BITMAPINFO bmi; - ZeroMemory(&bmi, sizeof(BITMAPINFO)); - bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); - bmi.bmiHeader.biWidth = dib_size.x; - bmi.bmiHeader.biHeight = dib_size.y; - bmi.bmiHeader.biPlanes = 1; - bmi.bmiHeader.biBitCount = 32; - bmi.bmiHeader.biCompression = BI_RGB; - bmi.bmiHeader.biSizeImage = dib_size.x * dib_size.y * 4; - hBitmap = CreateDIBSection(hDC_dib, &bmi, DIB_RGB_COLORS, (void **)&dib_data, NULL, 0x0); - SelectObject(hDC_dib, hBitmap); - - ZeroMemory(dib_data, dib_size.x * dib_size.y * 4); - - layered_window = true; - } else { - //disable per-pixel alpha - layered_window = false; - - SetWindowLong(hWnd, GWL_EXSTYLE, GetWindowLong(hWnd, GWL_EXSTYLE) & ~WS_EX_LAYERED); - - //cleanup - DeleteObject(hBitmap); - DeleteDC(hDC_dib); - } - } -} - -uint8_t *OS_Windows::get_layered_buffer_data() { - - return (is_layered_allowed() && layered_window) ? dib_data : NULL; -} - -Size2 OS_Windows::get_layered_buffer_size() { - - return (is_layered_allowed() && layered_window) ? dib_size : Size2(); -} - -void OS_Windows::swap_layered_buffer() { - - if (is_layered_allowed() && layered_window) { - - //premultiply alpha - for (int y = 0; y < dib_size.y; y++) { - for (int x = 0; x < dib_size.x; x++) { - float alpha = (float)dib_data[y * (int)dib_size.x * 4 + x * 4 + 3] / (float)0xFF; - dib_data[y * (int)dib_size.x * 4 + x * 4 + 0] *= alpha; - dib_data[y * (int)dib_size.x * 4 + x * 4 + 1] *= alpha; - dib_data[y * (int)dib_size.x * 4 + x * 4 + 2] *= alpha; - } - } - //swap layered window buffer - POINT ptSrc = { 0, 0 }; - SIZE sizeWnd = { (long)dib_size.x, (long)dib_size.y }; - BLENDFUNCTION bf; - bf.BlendOp = AC_SRC_OVER; - bf.BlendFlags = 0; - bf.AlphaFormat = AC_SRC_ALPHA; - bf.SourceConstantAlpha = 0xFF; - UpdateLayeredWindow(hWnd, NULL, NULL, &sizeWnd, hDC_dib, &ptSrc, 0, &bf, ULW_ALPHA); - } -} - -void OS_Windows::set_borderless_window(bool p_borderless) { - if (video_mode.borderless_window == p_borderless) - return; - - if (!p_borderless && layered_window) - set_window_per_pixel_transparency_enabled(false); - - video_mode.borderless_window = p_borderless; - - preserve_window_size = true; - _update_window_style(); -} - -bool OS_Windows::get_borderless_window() { - return video_mode.borderless_window; -} - -void OS_Windows::_update_window_style(bool p_repaint, bool p_maximized) { - if (video_mode.fullscreen || video_mode.borderless_window) { - SetWindowLongPtr(hWnd, GWL_STYLE, WS_SYSMENU | WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_VISIBLE); - } else { - if (video_mode.resizable) { - if (p_maximized) { - SetWindowLongPtr(hWnd, GWL_STYLE, WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_MAXIMIZE); - } else { - SetWindowLongPtr(hWnd, GWL_STYLE, WS_OVERLAPPEDWINDOW | WS_VISIBLE); - } - } else { - SetWindowLongPtr(hWnd, GWL_STYLE, WS_CAPTION | WS_MINIMIZEBOX | WS_POPUPWINDOW | WS_VISIBLE); - } - } - - SetWindowPos(hWnd, video_mode.always_on_top ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE); - - if (p_repaint) { - RECT rect; - GetWindowRect(hWnd, &rect); - MoveWindow(hWnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE); - } -} - Error OS_Windows::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) { String path = p_path; @@ -2306,17 +299,6 @@ Error OS_Windows::get_dynamic_library_symbol_handle(void *p_library_handle, cons return OK; } -void OS_Windows::request_attention() { - - FLASHWINFO info; - info.cbSize = sizeof(FLASHWINFO); - info.hwnd = hWnd; - info.dwFlags = FLASHW_TRAY; - info.dwTimeout = 0; - info.uCount = 2; - FlashWindowEx(&info); -} - String OS_Windows::get_name() const { return "Windows"; @@ -2448,253 +430,6 @@ uint64_t OS_Windows::get_ticks_usec() const { return time; } -void OS_Windows::process_events() { - - MSG msg; - - if (!drop_events) { - joypad->process_joypads(); - } - - while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) { - - TranslateMessage(&msg); - DispatchMessageW(&msg); - } - - if (!drop_events) { - process_key_events(); - input->flush_accumulated_events(); - } -} - -void OS_Windows::set_cursor_shape(CursorShape p_shape) { - - 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; - } - - static const LPCTSTR win_cursors[CURSOR_MAX] = { - IDC_ARROW, - IDC_IBEAM, - IDC_HAND, //finger - IDC_CROSS, - IDC_WAIT, - IDC_APPSTARTING, - IDC_ARROW, - IDC_ARROW, - IDC_NO, - IDC_SIZENS, - IDC_SIZEWE, - IDC_SIZENESW, - IDC_SIZENWSE, - IDC_SIZEALL, - IDC_SIZENS, - IDC_SIZEWE, - IDC_HELP - }; - - if (cursors[p_shape] != NULL) { - SetCursor(cursors[p_shape]); - } else { - SetCursor(LoadCursor(hInstance, win_cursors[p_shape])); - } - - cursor_shape = p_shape; -} - -OS::CursorShape OS_Windows::get_cursor_shape() const { - - return cursor_shape; -} - -void OS_Windows::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()); - - UINT image_size = texture_size.width * texture_size.height; - - // Create the BITMAP with alpha channel - COLORREF *buffer = (COLORREF *)memalloc(sizeof(COLORREF) * image_size); - - for (UINT index = 0; index < image_size; index++) { - int row_index = floor(index / texture_size.width) + atlas_rect.position.y; - int column_index = (index % 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); - } - - *(buffer + index) = image->get_pixel(column_index, row_index).to_argb32(); - } - - // Using 4 channels, so 4 * 8 bits - HBITMAP bitmap = CreateBitmap(texture_size.width, texture_size.height, 1, 4 * 8, buffer); - COLORREF clrTransparent = -1; - - // Create the AND and XOR masks for the bitmap - HBITMAP hAndMask = NULL; - HBITMAP hXorMask = NULL; - - GetMaskBitmaps(bitmap, clrTransparent, hAndMask, hXorMask); - - if (NULL == hAndMask || NULL == hXorMask) { - memfree(buffer); - DeleteObject(bitmap); - return; - } - - // Finally, create the icon - ICONINFO iconinfo; - iconinfo.fIcon = FALSE; - iconinfo.xHotspot = p_hotspot.x; - iconinfo.yHotspot = p_hotspot.y; - iconinfo.hbmMask = hAndMask; - iconinfo.hbmColor = hXorMask; - - if (cursors[p_shape]) - DestroyIcon(cursors[p_shape]); - - cursors[p_shape] = CreateIconIndirect(&iconinfo); - - 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) { - SetCursor(cursors[p_shape]); - } - } - - if (hAndMask != NULL) { - DeleteObject(hAndMask); - } - - if (hXorMask != NULL) { - DeleteObject(hXorMask); - } - - memfree(buffer); - DeleteObject(bitmap); - } else { - // Reset to default system cursor - if (cursors[p_shape]) { - DestroyIcon(cursors[p_shape]); - cursors[p_shape] = NULL; - } - - CursorShape c = cursor_shape; - cursor_shape = CURSOR_MAX; - set_cursor_shape(c); - - cursors_cache.erase(p_shape); - } -} - -void OS_Windows::GetMaskBitmaps(HBITMAP hSourceBitmap, COLORREF clrTransparent, OUT HBITMAP &hAndMaskBitmap, OUT HBITMAP &hXorMaskBitmap) { - - // Get the system display DC - HDC hDC = GetDC(NULL); - - // Create helper DC - HDC hMainDC = CreateCompatibleDC(hDC); - HDC hAndMaskDC = CreateCompatibleDC(hDC); - HDC hXorMaskDC = CreateCompatibleDC(hDC); - - // Get the dimensions of the source bitmap - BITMAP bm; - GetObject(hSourceBitmap, sizeof(BITMAP), &bm); - - // Create the mask bitmaps - hAndMaskBitmap = CreateCompatibleBitmap(hDC, bm.bmWidth, bm.bmHeight); // color - hXorMaskBitmap = CreateCompatibleBitmap(hDC, bm.bmWidth, bm.bmHeight); // color - - // Release the system display DC - ReleaseDC(NULL, hDC); - - // Select the bitmaps to helper DC - HBITMAP hOldMainBitmap = (HBITMAP)SelectObject(hMainDC, hSourceBitmap); - HBITMAP hOldAndMaskBitmap = (HBITMAP)SelectObject(hAndMaskDC, hAndMaskBitmap); - HBITMAP hOldXorMaskBitmap = (HBITMAP)SelectObject(hXorMaskDC, hXorMaskBitmap); - - // Assign the monochrome AND mask bitmap pixels so that a pixels of the source bitmap - // with 'clrTransparent' will be white pixels of the monochrome bitmap - SetBkColor(hMainDC, clrTransparent); - BitBlt(hAndMaskDC, 0, 0, bm.bmWidth, bm.bmHeight, hMainDC, 0, 0, SRCCOPY); - - // Assign the color XOR mask bitmap pixels so that a pixels of the source bitmap - // with 'clrTransparent' will be black and rest the pixels same as corresponding - // pixels of the source bitmap - SetBkColor(hXorMaskDC, RGB(0, 0, 0)); - SetTextColor(hXorMaskDC, RGB(255, 255, 255)); - BitBlt(hXorMaskDC, 0, 0, bm.bmWidth, bm.bmHeight, hAndMaskDC, 0, 0, SRCCOPY); - BitBlt(hXorMaskDC, 0, 0, bm.bmWidth, bm.bmHeight, hMainDC, 0, 0, SRCAND); - - // Deselect bitmaps from the helper DC - SelectObject(hMainDC, hOldMainBitmap); - SelectObject(hAndMaskDC, hOldAndMaskBitmap); - SelectObject(hXorMaskDC, hOldXorMaskBitmap); - - // Delete the helper DC - DeleteDC(hXorMaskDC); - DeleteDC(hAndMaskDC); - DeleteDC(hMainDC); -} - Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments, bool p_blocking, ProcessID *r_child_id, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex) { if (p_blocking && r_pipe) { @@ -2812,151 +547,6 @@ String OS_Windows::get_executable_path() const { return s; } -void OS_Windows::set_native_icon(const String &p_filename) { - - FileAccess *f = FileAccess::open(p_filename, FileAccess::READ); - ERR_FAIL_COND_MSG(!f, "Cannot open file with icon '" + p_filename + "'."); - - ICONDIR *icon_dir = (ICONDIR *)memalloc(sizeof(ICONDIR)); - int pos = 0; - - icon_dir->idReserved = f->get_32(); - pos += sizeof(WORD); - f->seek(pos); - - icon_dir->idType = f->get_32(); - pos += sizeof(WORD); - f->seek(pos); - - ERR_FAIL_COND_MSG(icon_dir->idType != 1, "Invalid icon file format!"); - - icon_dir->idCount = f->get_32(); - pos += sizeof(WORD); - f->seek(pos); - - icon_dir = (ICONDIR *)memrealloc(icon_dir, 3 * sizeof(WORD) + icon_dir->idCount * sizeof(ICONDIRENTRY)); - f->get_buffer((uint8_t *)&icon_dir->idEntries[0], icon_dir->idCount * sizeof(ICONDIRENTRY)); - - int small_icon_index = -1; // Select 16x16 with largest color count - int small_icon_cc = 0; - int big_icon_index = -1; // Select largest - int big_icon_width = 16; - int big_icon_cc = 0; - - for (int i = 0; i < icon_dir->idCount; i++) { - int colors = (icon_dir->idEntries[i].bColorCount == 0) ? 32768 : icon_dir->idEntries[i].bColorCount; - int width = (icon_dir->idEntries[i].bWidth == 0) ? 256 : icon_dir->idEntries[i].bWidth; - if (width == 16) { - if (colors >= small_icon_cc) { - small_icon_index = i; - small_icon_cc = colors; - } - } - if (width >= big_icon_width) { - if (colors >= big_icon_cc) { - big_icon_index = i; - big_icon_width = width; - big_icon_cc = colors; - } - } - } - - ERR_FAIL_COND_MSG(big_icon_index == -1, "No valid icons found!"); - - if (small_icon_index == -1) { - WARN_PRINT("No small icon found, reusing " + itos(big_icon_width) + "x" + itos(big_icon_width) + " @" + itos(big_icon_cc) + " icon!"); - small_icon_index = big_icon_index; - small_icon_cc = big_icon_cc; - } - - // Read the big icon - DWORD bytecount_big = icon_dir->idEntries[big_icon_index].dwBytesInRes; - Vector<uint8_t> data_big; - data_big.resize(bytecount_big); - pos = icon_dir->idEntries[big_icon_index].dwImageOffset; - f->seek(pos); - f->get_buffer((uint8_t *)&data_big.write[0], bytecount_big); - HICON icon_big = CreateIconFromResource((PBYTE)&data_big.write[0], bytecount_big, TRUE, 0x00030000); - ERR_FAIL_COND_MSG(!icon_big, "Could not create " + itos(big_icon_width) + "x" + itos(big_icon_width) + " @" + itos(big_icon_cc) + " icon, error: " + format_error_message(GetLastError()) + "."); - - // Read the small icon - DWORD bytecount_small = icon_dir->idEntries[small_icon_index].dwBytesInRes; - Vector<uint8_t> data_small; - data_small.resize(bytecount_small); - pos = icon_dir->idEntries[small_icon_index].dwImageOffset; - f->seek(pos); - f->get_buffer((uint8_t *)&data_small.write[0], bytecount_small); - HICON icon_small = CreateIconFromResource((PBYTE)&data_small.write[0], bytecount_small, TRUE, 0x00030000); - ERR_FAIL_COND_MSG(!icon_small, "Could not create 16x16 @" + itos(small_icon_cc) + " icon, error: " + format_error_message(GetLastError()) + "."); - - // Online tradition says to be sure last error is cleared and set the small icon first - int err = 0; - SetLastError(err); - - SendMessage(hWnd, WM_SETICON, ICON_SMALL, (LPARAM)icon_small); - err = GetLastError(); - ERR_FAIL_COND_MSG(err, "Error setting ICON_SMALL: " + format_error_message(err) + "."); - - SendMessage(hWnd, WM_SETICON, ICON_BIG, (LPARAM)icon_big); - err = GetLastError(); - ERR_FAIL_COND_MSG(err, "Error setting ICON_BIG: " + format_error_message(err) + "."); - - memdelete(f); - memdelete(icon_dir); -} - -void OS_Windows::set_icon(const Ref<Image> &p_icon) { - - ERR_FAIL_COND(!p_icon.is_valid()); - Ref<Image> icon = p_icon->duplicate(); - if (icon->get_format() != Image::FORMAT_RGBA8) - icon->convert(Image::FORMAT_RGBA8); - int w = icon->get_width(); - int h = icon->get_height(); - - /* Create temporary bitmap buffer */ - int icon_len = 40 + h * w * 4; - Vector<BYTE> v; - v.resize(icon_len); - BYTE *icon_bmp = v.ptrw(); - - encode_uint32(40, &icon_bmp[0]); - encode_uint32(w, &icon_bmp[4]); - encode_uint32(h * 2, &icon_bmp[8]); - encode_uint16(1, &icon_bmp[12]); - encode_uint16(32, &icon_bmp[14]); - encode_uint32(BI_RGB, &icon_bmp[16]); - encode_uint32(w * h * 4, &icon_bmp[20]); - encode_uint32(0, &icon_bmp[24]); - encode_uint32(0, &icon_bmp[28]); - encode_uint32(0, &icon_bmp[32]); - encode_uint32(0, &icon_bmp[36]); - - uint8_t *wr = &icon_bmp[40]; - const uint8_t *r = icon->get_data().ptr(); - - for (int i = 0; i < h; i++) { - - for (int j = 0; j < w; j++) { - - const uint8_t *rpx = &r[((h - i - 1) * w + j) * 4]; - uint8_t *wpx = &wr[(i * w + j) * 4]; - wpx[0] = rpx[2]; - wpx[1] = rpx[1]; - wpx[2] = rpx[0]; - wpx[3] = rpx[3]; - } - } - - HICON hicon = CreateIconFromResource(icon_bmp, icon_len, TRUE, 0x00030000); - - /* Set the icon for the window */ - SendMessage(hWnd, WM_SETICON, ICON_SMALL, (LPARAM)hicon); - - /* Set the icon in the task manager (should we do this?) */ - SendMessage(hWnd, WM_SETICON, ICON_BIG, (LPARAM)hicon); -} - bool OS_Windows::has_environment(const String &p_var) const { #ifdef MINGW_ENABLED @@ -2996,16 +586,6 @@ String OS_Windows::get_stdin_string(bool p_block) { return String(); } -void OS_Windows::enable_for_stealing_focus(ProcessID pid) { - - AllowSetForegroundWindow(pid); -} - -void OS_Windows::move_window_to_foreground() { - - SetForegroundWindow(hWnd); -} - Error OS_Windows::shell_open(String p_uri) { ShellExecuteW(NULL, NULL, p_uri.c_str(), NULL, NULL, SW_SHOWNORMAL); @@ -3068,101 +648,6 @@ int OS_Windows::get_processor_count() const { return sysinfo.dwNumberOfProcessors; } -OS::LatinKeyboardVariant OS_Windows::get_latin_keyboard_variant() const { - - unsigned long azerty[] = { - 0x00020401, // Arabic (102) AZERTY - 0x0001080c, // Belgian (Comma) - 0x0000080c, // Belgian French - 0x0000040c, // French - 0 // <--- STOP MARK - }; - unsigned long qwertz[] = { - 0x0000041a, // Croation - 0x00000405, // Czech - 0x00000407, // German - 0x00010407, // German (IBM) - 0x0000040e, // Hungarian - 0x0000046e, // Luxembourgish - 0x00010415, // Polish (214) - 0x00000418, // Romanian (Legacy) - 0x0000081a, // Serbian (Latin) - 0x0000041b, // Slovak - 0x00000424, // Slovenian - 0x0001042e, // Sorbian Extended - 0x0002042e, // Sorbian Standard - 0x0000042e, // Sorbian Standard (Legacy) - 0x0000100c, // Swiss French - 0x00000807, // Swiss German - 0 // <--- STOP MARK - }; - unsigned long dvorak[] = { - 0x00010409, // US-Dvorak - 0x00030409, // US-Dvorak for left hand - 0x00040409, // US-Dvorak for right hand - 0 // <--- STOP MARK - }; - - char name[KL_NAMELENGTH + 1]; - name[0] = 0; - GetKeyboardLayoutNameA(name); - - unsigned long hex = strtoul(name, NULL, 16); - - int i = 0; - while (azerty[i] != 0) { - if (azerty[i] == hex) return LATIN_KEYBOARD_AZERTY; - i++; - } - - i = 0; - while (qwertz[i] != 0) { - if (qwertz[i] == hex) return LATIN_KEYBOARD_QWERTZ; - i++; - } - - i = 0; - while (dvorak[i] != 0) { - if (dvorak[i] == hex) return LATIN_KEYBOARD_DVORAK; - i++; - } - - return LATIN_KEYBOARD_QWERTY; -} - -void OS_Windows::release_rendering_thread() { -#if defined(OPENGL_ENABLED) - if (video_driver_index == VIDEO_DRIVER_GLES2) { - context_gles2->release_current(); - } -#endif -} - -void OS_Windows::make_rendering_thread() { -#if defined(OPENGL_ENABLED) - if (video_driver_index == VIDEO_DRIVER_GLES2) { - context_gles2->make_current(); - } -#endif -} - -void OS_Windows::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_Windows::force_process_input() { - process_events(); // get rid of pending events -} - void OS_Windows::run() { if (!main_loop) @@ -3172,7 +657,7 @@ void OS_Windows::run() { while (!force_quit) { - process_events(); // get rid of pending events + DisplayServer::get_singleton()->process_events(); // get rid of pending events if (Main::iteration()) break; }; @@ -3287,50 +772,6 @@ String OS_Windows::get_unique_id() const { return String(HwProfInfo.szHwProfileGuid); } -void OS_Windows::set_ime_active(const bool p_active) { - - if (p_active) { - ImmAssociateContext(hWnd, im_himc); - - set_ime_position(im_position); - } else { - ImmAssociateContext(hWnd, (HIMC)0); - } -} - -void OS_Windows::set_ime_position(const Point2 &p_pos) { - - im_position = p_pos; - - HIMC himc = ImmGetContext(hWnd); - if (himc == (HIMC)0) - return; - - COMPOSITIONFORM cps; - cps.dwStyle = CFS_FORCE_POSITION; - cps.ptCurrentPos.x = im_position.x; - cps.ptCurrentPos.y = im_position.y; - ImmSetCompositionWindow(himc, &cps); - ImmReleaseContext(hWnd, himc); -} - -bool OS_Windows::is_joy_known(int p_device) { - return input->is_joy_mapped(p_device); -} - -String OS_Windows::get_joy_guid(int p_device) const { - return input->get_joy_guid_remapped(p_device); -} - -void OS_Windows::_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 -} - bool OS_Windows::_check_internal_feature_support(const String &p_feature) { return p_feature == "pc"; @@ -3344,20 +785,13 @@ bool OS_Windows::is_disable_crash_handler() const { return crash_handler.is_disabled(); } -void OS_Windows::process_and_drop_events() { - - drop_events = true; - process_events(); - drop_events = false; -} - Error OS_Windows::move_to_trash(const String &p_path) { SHFILEOPSTRUCTW sf; WCHAR *from = new WCHAR[p_path.length() + 2]; wcscpy_s(from, p_path.length() + 1, p_path.c_str()); from[p_path.length() + 1] = 0; - sf.hwnd = hWnd; + sf.hwnd = main_window; sf.wFunc = FO_DELETE; sf.pFrom = from; sf.pTo = NULL; @@ -3379,36 +813,12 @@ Error OS_Windows::move_to_trash(const String &p_path) { OS_Windows::OS_Windows(HINSTANCE _hInstance) { - drop_events = false; - key_event_pos = 0; - layered_window = false; - hBitmap = NULL; force_quit = false; - alt_mem = false; - gr_mem = false; - shift_mem = false; - control_mem = false; - meta_mem = false; - minimized = false; - was_maximized = false; - window_focused = true; - console_visible = IsWindowVisible(GetConsoleWindow()); - - //Note: Functions for pen input, available on Windows 8+ - HMODULE user32_lib = LoadLibraryW(L"user32.dll"); - if (user32_lib) { - win8p_GetPointerType = (GetPointerTypePtr)GetProcAddress(user32_lib, "GetPointerType"); - win8p_GetPointerPenInfo = (GetPointerPenInfoPtr)GetProcAddress(user32_lib, "GetPointerPenInfo"); - } hInstance = _hInstance; - pressrc = 0; - old_invalid = true; - mouse_mode = MOUSE_MODE_VISIBLE; #ifdef STDOUT_FILE stdo = fopen("stdout.txt", "wb"); #endif - user_proc = NULL; #ifdef WASAPI_ENABLED AudioDriverManager::add_driver(&driver_wasapi); @@ -3417,16 +827,14 @@ OS_Windows::OS_Windows(HINSTANCE _hInstance) { AudioDriverManager::add_driver(&driver_xaudio2); #endif + DisplayServerWindows::register_windows_driver(); + Vector<Logger *> loggers; loggers.push_back(memnew(WindowsTerminalLogger)); _set_logger(memnew(CompositeLogger(loggers))); } OS_Windows::~OS_Windows() { - if (is_layered_allowed() && layered_window) { - DeleteObject(hBitmap); - DeleteDC(hDC_dib); - } #ifdef STDOUT_FILE fclose(stdo); #endif diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h index 8506aa7b20..040951668d 100644 --- a/platform/windows/os_windows.h +++ b/platform/windows/os_windows.h @@ -31,7 +31,7 @@ #ifndef OS_WINDOWS_H #define OS_WINDOWS_H -#include "core/os/input.h" +#include "core/input/input_filter.h" #include "core/os/os.h" #include "core/project_settings.h" #include "crash_handler_windows.h" @@ -39,7 +39,6 @@ #include "drivers/wasapi/audio_driver_wasapi.h" #include "drivers/winmidi/midi_driver_winmidi.h" #include "key_mapping_windows.h" -#include "main/input_default.h" #include "servers/audio_server.h" #include "servers/visual/rasterizer.h" #include "servers/visual_server.h" @@ -62,183 +61,19 @@ #include <windows.h> #include <windowsx.h> -#ifndef POINTER_STRUCTURES - -#define POINTER_STRUCTURES - -typedef DWORD POINTER_INPUT_TYPE; -typedef UINT32 POINTER_FLAGS; -typedef UINT32 PEN_FLAGS; -typedef UINT32 PEN_MASK; - -enum tagPOINTER_INPUT_TYPE { - PT_POINTER = 0x00000001, - PT_TOUCH = 0x00000002, - PT_PEN = 0x00000003, - PT_MOUSE = 0x00000004, - PT_TOUCHPAD = 0x00000005 -}; - -typedef enum tagPOINTER_BUTTON_CHANGE_TYPE { - POINTER_CHANGE_NONE, - POINTER_CHANGE_FIRSTBUTTON_DOWN, - POINTER_CHANGE_FIRSTBUTTON_UP, - POINTER_CHANGE_SECONDBUTTON_DOWN, - POINTER_CHANGE_SECONDBUTTON_UP, - POINTER_CHANGE_THIRDBUTTON_DOWN, - POINTER_CHANGE_THIRDBUTTON_UP, - POINTER_CHANGE_FOURTHBUTTON_DOWN, - POINTER_CHANGE_FOURTHBUTTON_UP, - POINTER_CHANGE_FIFTHBUTTON_DOWN, - POINTER_CHANGE_FIFTHBUTTON_UP, -} POINTER_BUTTON_CHANGE_TYPE; - -typedef struct tagPOINTER_INFO { - POINTER_INPUT_TYPE pointerType; - UINT32 pointerId; - UINT32 frameId; - POINTER_FLAGS pointerFlags; - HANDLE sourceDevice; - HWND hwndTarget; - POINT ptPixelLocation; - POINT ptHimetricLocation; - POINT ptPixelLocationRaw; - POINT ptHimetricLocationRaw; - DWORD dwTime; - UINT32 historyCount; - INT32 InputData; - DWORD dwKeyStates; - UINT64 PerformanceCount; - POINTER_BUTTON_CHANGE_TYPE ButtonChangeType; -} POINTER_INFO; - -typedef struct tagPOINTER_PEN_INFO { - POINTER_INFO pointerInfo; - PEN_FLAGS penFlags; - PEN_MASK penMask; - UINT32 pressure; - UINT32 rotation; - INT32 tiltX; - INT32 tiltY; -} POINTER_PEN_INFO; - -#endif - -typedef BOOL(WINAPI *GetPointerTypePtr)(uint32_t p_id, POINTER_INPUT_TYPE *p_type); -typedef BOOL(WINAPI *GetPointerPenInfoPtr)(uint32_t p_id, POINTER_PEN_INFO *p_pen_info); - -typedef struct { - BYTE bWidth; // Width, in pixels, of the image - BYTE bHeight; // Height, in pixels, of the image - BYTE bColorCount; // Number of colors in image (0 if >=8bpp) - BYTE bReserved; // Reserved ( must be 0) - WORD wPlanes; // Color Planes - WORD wBitCount; // Bits per pixel - DWORD dwBytesInRes; // How many bytes in this resource? - DWORD dwImageOffset; // Where in the file is this image? -} ICONDIRENTRY, *LPICONDIRENTRY; - -typedef struct { - WORD idReserved; // Reserved (must be 0) - WORD idType; // Resource Type (1 for icons) - WORD idCount; // How many images? - ICONDIRENTRY idEntries[1]; // An entry for each image (idCount of 'em) -} ICONDIR, *LPICONDIR; - class JoypadWindows; class OS_Windows : public OS { - static GetPointerTypePtr win8p_GetPointerType; - static GetPointerPenInfoPtr win8p_GetPointerPenInfo; - - enum { - KEY_EVENT_BUFFER_SIZE = 512 - }; - #ifdef STDOUT_FILE FILE *stdo; #endif - struct KeyEvent { - - bool alt, shift, control, meta; - UINT uMsg; - WPARAM wParam; - LPARAM lParam; - }; - - KeyEvent key_event_buffer[KEY_EVENT_BUFFER_SIZE]; - int key_event_pos; - uint64_t ticks_start; uint64_t ticks_per_second; - bool old_invalid; - bool outside; - int old_x, old_y; - Point2i center; - -#if defined(OPENGL_ENABLED) - ContextGL_Windows *context_gles2; -#endif - -#if defined(VULKAN_ENABLED) - VulkanContextWindows *context_vulkan; - RenderingDeviceVulkan *rendering_device_vulkan; -#endif - - VisualServer *visual_server; - int pressrc; - HINSTANCE hInstance; // Holds The Instance Of The Application - HWND hWnd; - Point2 last_pos; - - HBITMAP hBitmap; //DIB section for layered window - uint8_t *dib_data; - Size2 dib_size; - HDC hDC_dib; - bool layered_window; - - uint32_t move_timer_id; - - HCURSOR hCursor; - - Size2 min_size; - Size2 max_size; - - Size2 window_rect; - VideoMode video_mode; - bool preserve_window_size = false; - + HINSTANCE hInstance; MainLoop *main_loop; - WNDPROC user_proc; - - // IME - HIMC im_himc; - Vector2 im_position; - - MouseMode mouse_mode; - bool alt_mem; - bool gr_mem; - bool shift_mem; - bool control_mem; - bool meta_mem; - bool force_quit; - bool window_has_focus; - uint32_t last_button_state; - bool use_raw_input; - bool drop_events; - - HCURSOR cursors[CURSOR_MAX] = { NULL }; - CursorShape cursor_shape; - Map<CursorShape, Vector<Variant>> cursors_cache; - - InputDefault *input; - JoypadWindows *joypad; - Map<int, Vector2> touch_state; - - int video_driver_index; #ifdef WASAPI_ENABLED AudioDriverWASAPI driver_wasapi; #endif @@ -251,28 +86,19 @@ class OS_Windows : public OS { CrashHandler crash_handler; - void _drag_event(float p_x, float p_y, int idx); - void _touch_event(bool p_pressed, float p_x, float p_y, int idx); - - void _update_window_style(bool p_repaint = true, bool p_maximized = false); - - void _set_mouse_mode_impl(MouseMode p_mode); + bool force_quit; + HWND main_window; // functions used by main to initialize/deinitialize the OS protected: - virtual int get_current_video_driver() const; - - virtual void initialize_core(); - virtual Error initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver); + virtual void initialize(); virtual void set_main_loop(MainLoop *p_main_loop); virtual void delete_main_loop(); virtual void finalize(); virtual void finalize_core(); - - void process_events(); - void process_key_events(); + virtual String get_stdin_string(bool p_block); struct ProcessInfo { @@ -281,75 +107,7 @@ protected: }; Map<ProcessID, ProcessInfo> *process_map; - bool pre_fs_valid; - RECT pre_fs_rect; - bool maximized; - bool minimized; - bool borderless; - bool window_focused; - bool console_visible; - bool was_maximized; - public: - LRESULT WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - - virtual void alert(const String &p_alert, const String &p_title = "ALERT!"); - String get_stdin_string(bool p_block); - - void set_mouse_mode(MouseMode p_mode); - MouseMode get_mouse_mode() const; - - virtual void warp_mouse_position(const Point2 &p_to); - virtual Point2 get_mouse_position() const; - void update_real_mouse_position(); - virtual int get_mouse_button_state() const; - virtual void set_window_title(const String &p_title); - - 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 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_window_size() const; - virtual Size2 get_real_window_size() const; - 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 set_console_visible(bool p_enabled); - virtual bool is_console_visible() const; - virtual void request_attention(); - - 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 uint8_t *get_layered_buffer_data(); - virtual Size2 get_layered_buffer_size(); - virtual void swap_layered_buffer(); - virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false); virtual Error close_dynamic_library(void *p_library_handle); virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional = false); @@ -358,6 +116,8 @@ public: virtual String get_name() const; + virtual void initialize_joypads() {} + virtual Date get_date(bool utc) const; virtual Time get_time(bool utc) const; virtual TimeZoneInfo get_time_zone_info() const; @@ -365,7 +125,6 @@ public: virtual uint64_t get_system_time_secs() const; virtual uint64_t get_system_time_msecs() const; - virtual bool can_draw() const; virtual Error set_cwd(const String &p_cwd); virtual void delay_usec(uint32_t p_usec) const; @@ -379,28 +138,12 @@ public: virtual String get_environment(const String &p_var) const; virtual bool set_environment(const String &p_var, const String &p_value) const; - virtual void set_clipboard(const String &p_text); - virtual String get_clipboard() const; - - void set_cursor_shape(CursorShape p_shape); - CursorShape get_cursor_shape() const; - virtual void set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot); - void GetMaskBitmaps(HBITMAP hSourceBitmap, COLORREF clrTransparent, OUT HBITMAP &hAndMaskBitmap, OUT HBITMAP &hXorMaskBitmap); - - void set_native_icon(const String &p_filename); - void set_icon(const Ref<Image> &p_icon); - virtual String get_executable_path() const; virtual String get_locale() const; virtual int get_processor_count() const; - virtual LatinKeyboardVariant get_latin_keyboard_variant() const; - - virtual void enable_for_stealing_focus(ProcessID pid); - virtual void move_window_to_foreground(); - virtual String get_config_path() const; virtual String get_data_path() const; virtual String get_cache_path() const; @@ -411,37 +154,21 @@ public: virtual String get_unique_id() const; - virtual void set_ime_active(const bool p_active); - virtual void set_ime_position(const Point2 &p_pos); - - virtual void release_rendering_thread(); - virtual void make_rendering_thread(); - virtual void swap_buffers(); - virtual Error shell_open(String p_uri); void run(); - virtual bool get_swap_ok_cancel() { return true; } - - virtual bool is_joy_known(int p_device); - virtual String get_joy_guid(int p_device) const; - - virtual void _set_use_vsync(bool p_enable); - //virtual bool is_vsync_enabled() const; - virtual bool _check_internal_feature_support(const String &p_feature); void disable_crash_handler(); bool is_disable_crash_handler() const; virtual void initialize_debugging(); - void force_process_input(); - virtual Error move_to_trash(const String &p_path); - virtual void process_and_drop_events(); + void set_main_window(HWND p_main_window) { main_window = p_main_window; } + HINSTANCE get_hinstance() { return hInstance; } OS_Windows(HINSTANCE _hInstance); ~OS_Windows(); }; diff --git a/platform/windows/vulkan_context_win.cpp b/platform/windows/vulkan_context_win.cpp index 20e1b46682..66b5cf8113 100644 --- a/platform/windows/vulkan_context_win.cpp +++ b/platform/windows/vulkan_context_win.cpp @@ -35,7 +35,7 @@ const char *VulkanContextWindows::_get_platform_surface_extension() const { return VK_KHR_WIN32_SURFACE_EXTENSION_NAME; } -int VulkanContextWindows::window_create(HWND p_window, HINSTANCE p_instance, int p_width, int p_height) { +int VulkanContextWindows::window_create(DisplayServer::WindowID p_window_id, HWND p_window, HINSTANCE p_instance, int p_width, int p_height) { VkWin32SurfaceCreateInfoKHR createInfo; createInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR; @@ -47,7 +47,7 @@ int VulkanContextWindows::window_create(HWND p_window, HINSTANCE p_instance, int VkSurfaceKHR surface; VkResult err = vkCreateWin32SurfaceKHR(_get_instance(), &createInfo, NULL, &surface); ERR_FAIL_COND_V(err, -1); - return _window_create(surface, p_width, p_height); + return _window_create(p_window_id, surface, p_width, p_height); } VulkanContextWindows::VulkanContextWindows() { diff --git a/platform/windows/vulkan_context_win.h b/platform/windows/vulkan_context_win.h index 1289f2a299..c9fea9369b 100644 --- a/platform/windows/vulkan_context_win.h +++ b/platform/windows/vulkan_context_win.h @@ -39,7 +39,7 @@ class VulkanContextWindows : public VulkanContext { virtual const char *_get_platform_surface_extension() const; public: - int window_create(HWND p_window, HINSTANCE p_instance, int p_width, int p_height); + int window_create(DisplayServer::WindowID p_window_id, HWND p_window, HINSTANCE p_instance, int p_width, int p_height); VulkanContextWindows(); ~VulkanContextWindows(); diff --git a/platform/x11/SCsub b/platform/x11/SCsub deleted file mode 100644 index 2268e4cc3d..0000000000 --- a/platform/x11/SCsub +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env python - -Import('env') - -from platform_methods import run_in_subprocess -import platform_x11_builders - -common_x11 = [ - "context_gl_x11.cpp", - "vulkan_context_x11.cpp", - "crash_handler_x11.cpp", - "os_x11.cpp", - "key_mapping_x11.cpp", - "joypad_linux.cpp", - "detect_prime.cpp" -] - -prog = env.add_program('#bin/godot', ['godot_x11.cpp'] + common_x11) - -if (env["debug_symbols"] == "full" or env["debug_symbols"] == "yes") and env["separate_debug_symbols"]: - env.AddPostAction(prog, run_in_subprocess(platform_x11_builders.make_debug_x11)) diff --git a/platform/x11/os_x11.h b/platform/x11/os_x11.h deleted file mode 100644 index 997a6cc053..0000000000 --- a/platform/x11/os_x11.h +++ /dev/null @@ -1,339 +0,0 @@ -/*************************************************************************/ -/* os_x11.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 OS_X11_H -#define OS_X11_H - -#include "core/os/input.h" -#include "crash_handler_x11.h" -#include "drivers/alsa/audio_driver_alsa.h" -#include "drivers/alsamidi/midi_driver_alsamidi.h" -#include "drivers/pulseaudio/audio_driver_pulseaudio.h" -#include "drivers/unix/os_unix.h" -#include "joypad_linux.h" -#include "main/input_default.h" -#include "servers/audio_server.h" -#include "servers/visual/rasterizer.h" -#include "servers/visual_server.h" - -#if defined(OPENGL_ENABLED) -#include "context_gl_x11.h" -#endif - -#if defined(VULKAN_ENABLED) -#include "drivers/vulkan/rendering_device_vulkan.h" -#include "platform/x11/vulkan_context_x11.h" -#endif - -#include <X11/Xcursor/Xcursor.h> -#include <X11/Xlib.h> -#include <X11/extensions/XInput2.h> -#include <X11/extensions/Xrandr.h> -#include <X11/keysym.h> - -// Hints for X11 fullscreen -typedef struct { - unsigned long flags; - unsigned long functions; - unsigned long decorations; - long inputMode; - unsigned long status; -} Hints; - -typedef struct _xrr_monitor_info { - Atom name; - Bool primary; - Bool automatic; - int noutput; - int x; - int y; - int width; - int height; - int mwidth; - int mheight; - RROutput *outputs; -} xrr_monitor_info; - -#undef CursorShape - -class OS_X11 : public OS_Unix { - - Atom wm_delete; - Atom xdnd_enter; - Atom xdnd_position; - Atom xdnd_status; - Atom xdnd_action_copy; - Atom xdnd_drop; - Atom xdnd_finished; - Atom xdnd_selection; - Atom requested; - - int xdnd_version; - -#if defined(OPENGL_ENABLED) - ContextGL_X11 *context_gles2; -#endif -#if defined(VULKAN_ENABLED) - VulkanContextX11 *context_vulkan; - RenderingDeviceVulkan *rendering_device_vulkan; -#endif - - //Rasterizer *rasterizer; - VisualServer *visual_server; - VideoMode current_videomode; - List<String> args; - Window x11_window; - Window xdnd_source_window; - MainLoop *main_loop; - ::Display *x11_display; - char *xmbstring; - int xmblen; - unsigned long last_timestamp; - ::Time last_keyrelease_time; - ::XIC xic; - ::XIM xim; - ::XIMStyle xim_style; - static void xim_destroy_callback(::XIM im, ::XPointer client_data, - ::XPointer call_data); - - // IME - bool im_active; - Vector2 im_position; - Vector2 last_position_before_fs; - - Size2 min_size; - Size2 max_size; - - Point2 last_mouse_pos; - bool last_mouse_pos_valid; - Point2i last_click_pos; - uint64_t last_click_ms; - int last_click_button_index; - uint32_t last_button_state; - - struct { - int opcode; - Vector<int> touch_devices; - Map<int, Vector2> absolute_devices; - Map<int, Vector3> pen_devices; - XIEventMask all_event_mask; - XIEventMask all_master_event_mask; - Map<int, Vector2> state; - double pressure; - Vector2 tilt; - Vector2 mouse_pos_to_filter; - Vector2 relative_motion; - Vector2 raw_pos; - Vector2 old_raw_pos; - ::Time last_relative_time; - } xi; - - bool refresh_device_info(); - - unsigned int get_mouse_button_state(unsigned int p_x11_button, int p_x11_type); - void get_key_modifier_state(unsigned int p_x11_state, Ref<InputEventWithModifiers> state); - void flush_mouse_motion(); - - MouseMode mouse_mode; - Point2i center; - - void handle_key_event(XKeyEvent *p_event, bool p_echo = false); - void process_xevents(); - virtual void delete_main_loop(); - - bool force_quit; - bool minimized; - bool window_has_focus; - bool do_mouse_warp; - - const char *cursor_theme; - int cursor_size; - XcursorImage *img[CURSOR_MAX]; - Cursor cursors[CURSOR_MAX]; - Cursor null_cursor; - CursorShape current_cursor; - Map<CursorShape, Vector<Variant>> cursors_cache; - - InputDefault *input; - -#ifdef JOYDEV_ENABLED - JoypadLinux *joypad; -#endif - -#ifdef ALSA_ENABLED - AudioDriverALSA driver_alsa; -#endif - -#ifdef ALSAMIDI_ENABLED - MIDIDriverALSAMidi driver_alsamidi; -#endif - -#ifdef PULSEAUDIO_ENABLED - AudioDriverPulseAudio driver_pulseaudio; -#endif - - bool layered_window; - - CrashHandler crash_handler; - - int video_driver_index; - bool maximized; - bool window_focused; - //void set_wm_border(bool p_enabled); - void set_wm_fullscreen(bool p_enabled); - void set_wm_above(bool p_enabled); - - typedef xrr_monitor_info *(*xrr_get_monitors_t)(Display *dpy, Window window, Bool get_active, int *nmonitors); - typedef void (*xrr_free_monitors_t)(xrr_monitor_info *monitors); - xrr_get_monitors_t xrr_get_monitors; - xrr_free_monitors_t xrr_free_monitors; - void *xrandr_handle; - Bool xrandr_ext_ok; - -protected: - virtual int get_current_video_driver() const; - - virtual void initialize_core(); - virtual Error initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver); - virtual void finalize(); - - virtual void set_main_loop(MainLoop *p_main_loop); - - void _window_changed(XEvent *event); - - bool is_window_maximize_allowed(); - -public: - virtual String get_name() const; - - 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); - - void set_mouse_mode(MouseMode p_mode); - MouseMode get_mouse_mode() const; - - virtual void warp_mouse_position(const Point2 &p_to); - virtual Point2 get_mouse_position() const; - virtual int get_mouse_button_state() const; - virtual void set_window_title(const String &p_title); - - virtual void set_icon(const Ref<Image> &p_icon); - - virtual MainLoop *get_main_loop() 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(); - - virtual String get_config_path() const; - virtual String get_data_path() const; - virtual String get_cache_path() const; - - virtual String get_system_dir(SystemDir p_dir) const; - - virtual Error shell_open(String p_uri); - - 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 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_window_size() const; - virtual Size2 get_real_window_size() const; - 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 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 String get_unique_id() const; - - virtual void move_window_to_foreground(); - virtual void alert(const String &p_alert, const String &p_title = "ALERT!"); - - virtual bool is_joy_known(int p_device); - virtual String get_joy_guid(int p_device) const; - - virtual void set_context(int p_context); - - virtual void _set_use_vsync(bool p_enable); - //virtual bool is_vsync_enabled() const; - - virtual bool _check_internal_feature_support(const String &p_feature); - - virtual void force_process_input(); - void run(); - - void disable_crash_handler(); - bool is_disable_crash_handler() const; - - virtual Error move_to_trash(const String &p_path); - - virtual LatinKeyboardVariant get_latin_keyboard_variant() const; - - void update_real_mouse_position(); - OS_X11(); -}; - -#endif |