diff options
Diffstat (limited to 'platform/linuxbsd')
22 files changed, 1542 insertions, 996 deletions
diff --git a/platform/linuxbsd/SCsub b/platform/linuxbsd/SCsub index ae75a75830..6e43ffcedb 100644 --- a/platform/linuxbsd/SCsub +++ b/platform/linuxbsd/SCsub @@ -18,5 +18,5 @@ common_x11 = [ 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"]: +if 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/linuxbsd/context_gl_x11.cpp b/platform/linuxbsd/context_gl_x11.cpp index 308d68521a..1f92370ab7 100644 --- a/platform/linuxbsd/context_gl_x11.cpp +++ b/platform/linuxbsd/context_gl_x11.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ @@ -46,22 +46,18 @@ typedef GLXContext (*GLXCREATECONTEXTATTRIBSARBPROC)(Display *, GLXFBConfig, GLXContext, Bool, const int *); struct ContextGL_X11_Private { - ::GLXContext glx_context; }; void ContextGL_X11::release_current() { - glXMakeCurrent(x11_display, None, nullptr); } void ContextGL_X11::make_current() { - glXMakeCurrent(x11_display, x11_window, p->glx_context); } void ContextGL_X11::swap_buffers() { - glXSwapBuffers(x11_display, x11_window); } @@ -85,7 +81,6 @@ static void set_class_hint(Display *p_display, Window p_window) { } Error ContextGL_X11::initialize() { - //const char *extensions = glXQueryExtensionsString(x11_display, DefaultScreen(x11_display)); GLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB = (GLXCREATECONTEXTATTRIBSARBPROC)glXGetProcAddress((const GLubyte *)"glXCreateContextAttribsARB"); @@ -167,7 +162,6 @@ Error ContextGL_X11::initialize() { switch (context_type) { case GLES_2_0_COMPATIBLE: { - p->glx_context = glXCreateNewContext(x11_display, fbconfig, GLX_RGBA_TYPE, 0, true); ERR_FAIL_COND_V(!p->glx_context, ERR_UNCONFIGURED); } break; @@ -192,7 +186,6 @@ Error ContextGL_X11::initialize() { } int ContextGL_X11::get_window_width() { - XWindowAttributes xwa; XGetWindowAttributes(x11_display, x11_window, &xwa); @@ -234,14 +227,13 @@ void ContextGL_X11::set_use_vsync(bool p_use) { return; use_vsync = p_use; } -bool ContextGL_X11::is_using_vsync() const { +bool ContextGL_X11::is_using_vsync() const { return use_vsync; } ContextGL_X11::ContextGL_X11(::Display *p_x11_display, ::Window &p_x11_window, const OS::VideoMode &p_default_video_mode, ContextType p_context_type) : x11_window(p_x11_window) { - default_video_mode = p_default_video_mode; x11_display = p_x11_display; diff --git a/platform/linuxbsd/context_gl_x11.h b/platform/linuxbsd/context_gl_x11.h index 2c0643c95a..d089886f4d 100644 --- a/platform/linuxbsd/context_gl_x11.h +++ b/platform/linuxbsd/context_gl_x11.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ @@ -42,7 +42,6 @@ struct ContextGL_X11_Private; class ContextGL_X11 { - public: enum ContextType { GLES_2_0_COMPATIBLE, diff --git a/platform/linuxbsd/crash_handler_linuxbsd.cpp b/platform/linuxbsd/crash_handler_linuxbsd.cpp index dbdb15918e..ea0222cb19 100644 --- a/platform/linuxbsd/crash_handler_linuxbsd.cpp +++ b/platform/linuxbsd/crash_handler_linuxbsd.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ @@ -30,8 +30,8 @@ #include "crash_handler_linuxbsd.h" +#include "core/config/project_settings.h" #include "core/os/os.h" -#include "core/project_settings.h" #include "main/main.h" #ifdef DEBUG_ENABLED @@ -63,10 +63,11 @@ static void handle_crash(int sig) { // Dump the backtrace to stderr with a message to the user fprintf(stderr, "%s: Program crashed with signal %d\n", __FUNCTION__, sig); - if (OS::get_singleton()->get_main_loop()) + if (OS::get_singleton()->get_main_loop()) { OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_CRASH); + } - fprintf(stderr, "Dumping the backtrace. %ls\n", msg.c_str()); + fprintf(stderr, "Dumping the backtrace. %s\n", msg.utf8().get_data()); char **strings = backtrace_symbols(bt_buffer, size); if (strings) { for (size_t i = 1; i < size; i++) { @@ -85,8 +86,9 @@ static void handle_crash(int sig) { snprintf(fname, 1024, "%s", demangled); } - if (demangled) + if (demangled) { free(demangled); + } } } @@ -102,12 +104,12 @@ static void handle_crash(int sig) { // Try to get the file/line number using addr2line int ret; - Error err = OS::get_singleton()->execute(String("addr2line"), args, true, nullptr, &output, &ret); + Error err = OS::get_singleton()->execute(String("addr2line"), args, &output, &ret); if (err == OK) { output.erase(output.length() - 1, 1); } - fprintf(stderr, "[%ld] %s (%ls)\n", (long int)i, fname, output.c_str()); + fprintf(stderr, "[%ld] %s (%s)\n", (long int)i, fname, output.utf8().get_data()); } free(strings); @@ -128,8 +130,9 @@ CrashHandler::~CrashHandler() { } void CrashHandler::disable() { - if (disabled) + if (disabled) { return; + } #ifdef CRASH_HANDLER_ENABLED signal(SIGSEGV, nullptr); diff --git a/platform/linuxbsd/crash_handler_linuxbsd.h b/platform/linuxbsd/crash_handler_linuxbsd.h index 94b4649690..a3dae0cc22 100644 --- a/platform/linuxbsd/crash_handler_linuxbsd.h +++ b/platform/linuxbsd/crash_handler_linuxbsd.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ @@ -32,7 +32,6 @@ #define CRASH_HANDLER_X11_H class CrashHandler { - bool disabled; public: diff --git a/platform/linuxbsd/detect.py b/platform/linuxbsd/detect.py index 07fa06bc06..a819731328 100644 --- a/platform/linuxbsd/detect.py +++ b/platform/linuxbsd/detect.py @@ -12,7 +12,6 @@ def get_name(): def can_build(): - if os.name != "posix" or sys.platform == "darwin": return False @@ -35,6 +34,11 @@ def can_build(): print("xinerama not found.. x11 disabled.") return False + x11_error = os.system("pkg-config xext --modversion > /dev/null ") + if x11_error: + print("xext not found.. x11 disabled.") + return False + x11_error = os.system("pkg-config xrandr --modversion > /dev/null ") if x11_error: print("xrandr not found.. x11 disabled.") @@ -68,7 +72,7 @@ def get_opts(): BoolVariable("use_tsan", "Use LLVM/GCC compiler thread sanitizer (TSAN))", False), BoolVariable("pulseaudio", "Detect and use PulseAudio", True), BoolVariable("udev", "Use udev for gamepad connection callbacks", False), - EnumVariable("debug_symbols", "Add debugging symbols to release builds", "yes", ("yes", "no", "full")), + EnumVariable("debug_symbols", "Add debugging symbols to release/release_debug builds", "yes", ("yes", "no")), BoolVariable("separate_debug_symbols", "Create a separate file containing debugging symbols", False), BoolVariable("touch", "Enable touch events", True), BoolVariable("execinfo", "Use libexecinfo on systems where glibc is not available", False), @@ -76,12 +80,10 @@ def get_opts(): def get_flags(): - return [] def configure(env): - ## Build type if env["target"] == "release": @@ -91,8 +93,6 @@ def configure(env): env.Prepend(CCFLAGS=["-Os"]) if env["debug_symbols"] == "yes": - env.Prepend(CCFLAGS=["-g1"]) - if env["debug_symbols"] == "full": env.Prepend(CCFLAGS=["-g2"]) elif env["target"] == "release_debug": @@ -103,13 +103,11 @@ def configure(env): env.Prepend(CPPDEFINES=["DEBUG_ENABLED"]) if env["debug_symbols"] == "yes": - env.Prepend(CCFLAGS=["-g1"]) - if env["debug_symbols"] == "full": env.Prepend(CCFLAGS=["-g2"]) elif env["target"] == "debug": env.Prepend(CCFLAGS=["-g3"]) - env.Prepend(CPPDEFINES=["DEBUG_ENABLED", "DEBUG_MEMORY_ENABLED"]) + env.Prepend(CPPDEFINES=["DEBUG_ENABLED"]) env.Append(LINKFLAGS=["-rdynamic"]) ## Architecture @@ -128,8 +126,6 @@ def configure(env): if "clang++" not in os.path.basename(env["CXX"]): env["CC"] = "clang" env["CXX"] = "clang++" - env["LINK"] = "clang++" - env.Append(CPPDEFINES=["TYPED_METHOD_BIND"]) env.extra_suffix = ".llvm" + env.extra_suffix if env["use_lld"]: @@ -194,6 +190,7 @@ def configure(env): env.ParseConfig("pkg-config x11 --cflags --libs") env.ParseConfig("pkg-config xcursor --cflags --libs") env.ParseConfig("pkg-config xinerama --cflags --libs") + env.ParseConfig("pkg-config xext --cflags --libs") env.ParseConfig("pkg-config xrandr --cflags --libs") env.ParseConfig("pkg-config xrender --cflags --libs") env.ParseConfig("pkg-config xi --cflags --libs") @@ -205,14 +202,31 @@ def configure(env): # freetype depends on libpng and zlib, so bundling one of them while keeping others # as shared libraries leads to weird issues - if env["builtin_freetype"] or env["builtin_libpng"] or env["builtin_zlib"]: + if ( + env["builtin_freetype"] + or env["builtin_libpng"] + or env["builtin_zlib"] + or env["builtin_graphite"] + or env["builtin_harfbuzz"] + ): env["builtin_freetype"] = True env["builtin_libpng"] = True env["builtin_zlib"] = True + env["builtin_graphite"] = True + env["builtin_harfbuzz"] = True if not env["builtin_freetype"]: env.ParseConfig("pkg-config freetype2 --cflags --libs") + if not env["builtin_graphite"]: + env.ParseConfig("pkg-config graphite2 --cflags --libs") + + if not env["builtin_icu"]: + env.ParseConfig("pkg-config icu-uc --cflags --libs") + + if not env["builtin_harfbuzz"]: + env.ParseConfig("pkg-config harfbuzz harfbuzz-icu --cflags --libs") + if not env["builtin_libpng"]: env.ParseConfig("pkg-config libpng16 --cflags --libs") diff --git a/platform/linuxbsd/detect_prime_x11.cpp b/platform/linuxbsd/detect_prime_x11.cpp index 1bec65ff04..0f8d108dff 100644 --- a/platform/linuxbsd/detect_prime_x11.cpp +++ b/platform/linuxbsd/detect_prime_x11.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ @@ -33,8 +33,8 @@ #include "detect_prime.h" -#include "core/print_string.h" -#include "core/ustring.h" +#include "core/string/print_string.h" +#include "core/string/ustring.h" #include <stdlib.h> @@ -56,7 +56,7 @@ typedef GLXContext (*GLXCREATECONTEXTATTRIBSARBPROC)(Display *, GLXFBConfig, GLX struct vendor { const char *glxvendor; - int priority; + int priority = 0; }; vendor vendormap[] = { @@ -178,7 +178,8 @@ int detect_prime() { close(fdset[0]); - if (i) setenv("DRI_PRIME", "1", 1); + if (i) + setenv("DRI_PRIME", "1", 1); create_context(); const char *vendor = (const char *)glGetString(GL_VENDOR); diff --git a/platform/linuxbsd/detect_prime_x11.h b/platform/linuxbsd/detect_prime_x11.h index 039bdee76b..0b548b849e 100644 --- a/platform/linuxbsd/detect_prime_x11.h +++ b/platform/linuxbsd/detect_prime_x11.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ diff --git a/platform/linuxbsd/display_server_x11.cpp b/platform/linuxbsd/display_server_x11.cpp index dd9298d667..00b90923de 100644 --- a/platform/linuxbsd/display_server_x11.cpp +++ b/platform/linuxbsd/display_server_x11.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ @@ -32,35 +32,27 @@ #ifdef X11_ENABLED +#include "core/config/project_settings.h" +#include "core/string/print_string.h" #include "detect_prime_x11.h" - -#include "core/os/dir_access.h" -#include "core/print_string.h" -#include "errno.h" #include "key_mapping_x11.h" - -#if defined(OPENGL_ENABLED) -#include "drivers/gles2/rasterizer_gles2.h" -#endif - -#if defined(VULKAN_ENABLED) -#include "servers/rendering/rasterizer_rd/rasterizer_rd.h" -#endif - +#include "main/main.h" #include "scene/resources/texture.h" -#ifdef HAVE_MNTENT -#include <mntent.h> +#if defined(VULKAN_ENABLED) +#include "servers/rendering/renderer_rd/renderer_compositor_rd.h" #endif +#include <limits.h> #include <stdio.h> #include <stdlib.h> #include <string.h> -#include "X11/Xutil.h" +#include <X11/Xatom.h> +#include <X11/Xutil.h> +#include <X11/extensions/Xinerama.h> +#include <X11/extensions/shape.h> -#include "X11/Xatom.h" -#include "X11/extensions/Xinerama.h" // ICCCM #define WM_NormalState 1L // window normal state #define WM_IconicState 3L // window minimized @@ -69,8 +61,6 @@ #define _NET_WM_STATE_ADD 1L // add/set property #define _NET_WM_STATE_TOGGLE 2L // toggle property -#include "main/main.h" - #include <dlfcn.h> #include <fcntl.h> #include <sys/stat.h> @@ -82,14 +72,9 @@ #undef KEY_TAB #endif -#include <X11/Xatom.h> - #undef CursorShape - #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 @@ -100,9 +85,25 @@ #define VALUATOR_TILTX 3 #define VALUATOR_TILTY 4 +//#define DISPLAY_SERVER_X11_DEBUG_LOGS_ENABLED +#ifdef DISPLAY_SERVER_X11_DEBUG_LOGS_ENABLED +#define DEBUG_LOG_X11(...) printf(__VA_ARGS__) +#else +#define DEBUG_LOG_X11(...) +#endif + static const double abs_resolution_mult = 10000.0; static const double abs_resolution_range_mult = 10.0; +// Hints for X11 fullscreen +struct Hints { + unsigned long flags = 0; + unsigned long functions = 0; + unsigned long decorations = 0; + long inputMode = 0; + unsigned long status = 0; +}; + bool DisplayServerX11::has_feature(Feature p_feature) const { switch (p_feature) { case FEATURE_SUBWINDOWS: @@ -127,6 +128,7 @@ bool DisplayServerX11::has_feature(Feature p_feature) const { return false; } + String DisplayServerX11::get_name() const { return "X11"; } @@ -148,8 +150,9 @@ void DisplayServerX11::alert(const String &p_alert, const String &p_title) { } } - if (program.length()) + if (program.length()) { break; + } } List<String> args; @@ -188,7 +191,7 @@ void DisplayServerX11::alert(const String &p_alert, const String &p_title) { } if (program.length()) { - OS::get_singleton()->execute(program, args, true); + OS::get_singleton()->execute(program, args); } else { print_line(p_alert); } @@ -204,7 +207,6 @@ void DisplayServerX11::_update_real_mouse_position(const WindowData &wd) { if (xquerypointer_result) { if (win_x > 0 && win_y > 0 && win_x <= wd.size.width && win_y <= wd.size.height) { - last_mouse_pos.x = win_x; last_mouse_pos.y = win_y; last_mouse_pos_valid = true; @@ -245,10 +247,12 @@ bool DisplayServerX11::_refresh_device_info() { for (int i = 0; i < dev_count; i++) { XIDeviceInfo *dev = &info[i]; - if (!dev->enabled) + if (!dev->enabled) { continue; - if (!(dev->use == XIMasterPointer || dev->use == XIFloatingSlave)) + } + if (!(dev->use == XIMasterPointer || dev->use == XIFloatingSlave)) { continue; + } bool direct_touch = false; bool absolute_mode = false; @@ -328,23 +332,19 @@ bool DisplayServerX11::_refresh_device_info() { } void DisplayServerX11::_flush_mouse_motion() { - while (true) { - if (XPending(x11_display) > 0) { - XEvent event; - XPeekEvent(x11_display, &event); - - if (XGetEventData(x11_display, &event.xcookie) && event.xcookie.type == GenericEvent && event.xcookie.extension == xi.opcode) { - XIDeviceEvent *event_data = (XIDeviceEvent *)event.xcookie.data; - - if (event_data->evtype == XI_RawMotion) { - XNextEvent(x11_display, &event); - } else { - break; - } - } else { - break; + // Block events polling while flushing motion events. + MutexLock mutex_lock(events_mutex); + + for (uint32_t event_index = 0; event_index < polled_events.size(); ++event_index) { + XEvent &event = polled_events[event_index]; + if (XGetEventData(x11_display, &event.xcookie) && event.xcookie.type == GenericEvent && event.xcookie.extension == xi.opcode) { + XIDeviceEvent *event_data = (XIDeviceEvent *)event.xcookie.data; + if (event_data->evtype == XI_RawMotion) { + XFreeEventData(x11_display, &event.xcookie); + polled_events.remove(event_index--); + continue; } - } else { + XFreeEventData(x11_display, &event.xcookie); break; } } @@ -354,20 +354,20 @@ void DisplayServerX11::_flush_mouse_motion() { } void DisplayServerX11::mouse_set_mode(MouseMode p_mode) { - _THREAD_SAFE_METHOD_ - if (p_mode == mouse_mode) + if (p_mode == mouse_mode) { return; + } - if (mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED) + if (mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED) { XUngrabPointer(x11_display, CurrentTime); + } // The only modes that show a cursor are VISIBLE and CONFINED bool showCursor = (p_mode == MOUSE_MODE_VISIBLE || p_mode == MOUSE_MODE_CONFINED); 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 { @@ -377,7 +377,6 @@ void DisplayServerX11::mouse_set_mode(MouseMode p_mode) { mouse_mode = p_mode; if (mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED) { - //flush pending motion events _flush_mouse_motion(); WindowData &main_window = windows[MAIN_WINDOW_ID]; @@ -404,30 +403,35 @@ void DisplayServerX11::mouse_set_mode(MouseMode p_mode) { XFlush(x11_display); } + DisplayServerX11::MouseMode DisplayServerX11::mouse_get_mode() const { return mouse_mode; } void DisplayServerX11::mouse_warp_to_position(const Point2i &p_to) { - _THREAD_SAFE_METHOD_ if (mouse_mode == MOUSE_MODE_CAPTURED) { - last_mouse_pos = p_to; } else { - - /*XWindowAttributes xwa; - XGetWindowAttributes(x11_display, x11_window, &xwa); - printf("%d %d\n", xwa.x, xwa.y); needed? */ - XWarpPointer(x11_display, None, windows[MAIN_WINDOW_ID].x11_window, 0, 0, 0, 0, (int)p_to.x, (int)p_to.y); } } Point2i DisplayServerX11::mouse_get_position() const { - return last_mouse_pos; + int root_x, root_y; + int win_x, win_y; + unsigned int mask_return; + Window window_returned; + + Bool result = XQueryPointer(x11_display, RootWindow(x11_display, DefaultScreen(x11_display)), &window_returned, + &window_returned, &root_x, &root_y, &win_x, &win_y, + &mask_return); + if (result == True) { + return Point2i(root_x, root_y); + } + return Point2i(); } Point2i DisplayServerX11::mouse_get_absolute_position() const { @@ -451,64 +455,158 @@ int DisplayServerX11::mouse_get_button_state() const { } void DisplayServerX11::clipboard_set(const String &p_text) { - _THREAD_SAFE_METHOD_ - internal_clipboard = p_text; + { + // The clipboard content can be accessed while polling for events. + MutexLock mutex_lock(events_mutex); + 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); } -static String _clipboard_get_impl(Atom p_source, Window x11_window, ::Display *x11_display, String p_internal_clipboard, Atom target) { +Bool DisplayServerX11::_predicate_clipboard_selection(Display *display, XEvent *event, XPointer arg) { + if (event->type == SelectionNotify && event->xselection.requestor == *(Window *)arg) { + return True; + } else { + return False; + } +} - String ret; +Bool DisplayServerX11::_predicate_clipboard_incr(Display *display, XEvent *event, XPointer arg) { + if (event->type == PropertyNotify && event->xproperty.state == PropertyNewValue) { + return True; + } else { + return False; + } +} - 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); +String DisplayServerX11::_clipboard_get_impl(Atom p_source, Window x11_window, Atom target) const { + String ret; - if (Sown == x11_window) { + Window selection_owner = XGetSelectionOwner(x11_display, p_source); + if (selection_owner == x11_window) { + return internal_clipboard; + } - return p_internal_clipboard; - }; + if (selection_owner != None) { + // Block events polling while processing selection events. + MutexLock mutex_lock(events_mutex); - if (Sown != None) { + Atom selection = XA_PRIMARY; 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; - }; - }; - // - // Do not get any data, see how much data is there - // + // Blocking wait for predicate to be True and remove the event from the queue. + XEvent event; + XIfEvent(x11_display, &event, _predicate_clipboard_selection, (XPointer)&x11_window); + + // Do not get any data, see how much data is there. + Atom type; + int format, result; + unsigned long len, bytes_left, dummy; + unsigned char *data; XGetWindowProperty(x11_display, x11_window, selection, // Tricky.. 0, 0, // offset - len 0, // Delete 0==FALSE - AnyPropertyType, //flag + AnyPropertyType, // flag &type, // return type &format, // return format - &len, &bytes_left, //that + &len, &bytes_left, // data length &data); - // DATA is There - if (bytes_left > 0) { + + if (data) { + XFree(data); + } + + if (type == XInternAtom(x11_display, "INCR", 0)) { + // Data is going to be received incrementally. + DEBUG_LOG_X11("INCR selection started.\n"); + + LocalVector<uint8_t> incr_data; + uint32_t data_size = 0; + bool success = false; + + // Delete INCR property to notify the owner. + XDeleteProperty(x11_display, x11_window, type); + + // Process events from the queue. + bool done = false; + while (!done) { + if (!_wait_for_events()) { + // Error or timeout, abort. + break; + } + + // Non-blocking wait for next event and remove it from the queue. + XEvent ev; + while (XCheckIfEvent(x11_display, &ev, _predicate_clipboard_incr, nullptr)) { + result = XGetWindowProperty(x11_display, x11_window, + selection, // selection type + 0, LONG_MAX, // offset - len + True, // delete property to notify the owner + AnyPropertyType, // flag + &type, // return type + &format, // return format + &len, &bytes_left, // data length + &data); + + DEBUG_LOG_X11("PropertyNotify: len=%lu, format=%i\n", len, format); + + if (result == Success) { + if (data && (len > 0)) { + uint32_t prev_size = incr_data.size(); + if (prev_size == 0) { + // First property contains initial data size. + unsigned long initial_size = *(unsigned long *)data; + incr_data.resize(initial_size); + } else { + // New chunk, resize to be safe and append data. + incr_data.resize(MAX(data_size + len, prev_size)); + memcpy(incr_data.ptr() + data_size, data, len); + data_size += len; + } + } else { + // Last chunk, process finished. + done = true; + success = true; + } + } else { + printf("Failed to get selection data chunk.\n"); + done = true; + } + + if (data) { + XFree(data); + } + + if (done) { + break; + } + } + } + + if (success && (data_size > 0)) { + ret.parse_utf8((const char *)incr_data.ptr(), data_size); + } + } else if (bytes_left > 0) { + // Data is ready and can be processed all at once. 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"); + } else { + printf("Failed to get selection data.\n"); + } + if (data) { XFree(data); } @@ -518,48 +616,102 @@ static String _clipboard_get_impl(Atom p_source, Window x11_window, ::Display *x return ret; } -static String _clipboard_get(Atom p_source, Window x11_window, ::Display *x11_display, String p_internal_clipboard) { +String DisplayServerX11::_clipboard_get(Atom p_source, Window x11_window) const { 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); + ret = _clipboard_get_impl(p_source, x11_window, utf8_atom); } - if (ret == "") { - ret = _clipboard_get_impl(p_source, x11_window, x11_display, p_internal_clipboard, XA_STRING); + if (ret.is_empty()) { + ret = _clipboard_get_impl(p_source, x11_window, XA_STRING); } return ret; } String DisplayServerX11::clipboard_get() const { - _THREAD_SAFE_METHOD_ String ret; - ret = _clipboard_get(XInternAtom(x11_display, "CLIPBOARD", 0), windows[MAIN_WINDOW_ID].x11_window, x11_display, internal_clipboard); + ret = _clipboard_get(XInternAtom(x11_display, "CLIPBOARD", 0), windows[MAIN_WINDOW_ID].x11_window); - if (ret == "") { - ret = _clipboard_get(XA_PRIMARY, windows[MAIN_WINDOW_ID].x11_window, x11_display, internal_clipboard); - }; + if (ret.is_empty()) { + ret = _clipboard_get(XA_PRIMARY, windows[MAIN_WINDOW_ID].x11_window); + } return ret; } -int DisplayServerX11::get_screen_count() const { +Bool DisplayServerX11::_predicate_clipboard_save_targets(Display *display, XEvent *event, XPointer arg) { + if (event->xany.window == *(Window *)arg) { + return (event->type == SelectionRequest) || + (event->type == SelectionNotify); + } else { + return False; + } +} +void DisplayServerX11::_clipboard_transfer_ownership(Atom p_source, Window x11_window) const { + _THREAD_SAFE_METHOD_ + + Window selection_owner = XGetSelectionOwner(x11_display, p_source); + + if (selection_owner != x11_window) { + return; + } + + // Block events polling while processing selection events. + MutexLock mutex_lock(events_mutex); + + Atom clipboard_manager = XInternAtom(x11_display, "CLIPBOARD_MANAGER", False); + Atom save_targets = XInternAtom(x11_display, "SAVE_TARGETS", False); + XConvertSelection(x11_display, clipboard_manager, save_targets, None, + x11_window, CurrentTime); + + // Process events from the queue. + while (true) { + if (!_wait_for_events()) { + // Error or timeout, abort. + break; + } + + // Non-blocking wait for next event and remove it from the queue. + XEvent ev; + while (XCheckIfEvent(x11_display, &ev, _predicate_clipboard_save_targets, (XPointer)&x11_window)) { + switch (ev.type) { + case SelectionRequest: + _handle_selection_request_event(&(ev.xselectionrequest)); + break; + + case SelectionNotify: { + if (ev.xselection.target == save_targets) { + // Once SelectionNotify is received, we're done whether it succeeded or not. + return; + } + + break; + } + } + } + } +} + +int DisplayServerX11::get_screen_count() const { _THREAD_SAFE_METHOD_ // Using Xinerama Extension int event_base, error_base; const Bool ext_okay = XineramaQueryExtension(x11_display, &event_base, &error_base); - if (!ext_okay) return 0; + if (!ext_okay) { + return 0; + } int count; XineramaScreenInfo *xsi = XineramaQueryScreens(x11_display, &count); XFree(xsi); return count; } -Point2i DisplayServerX11::screen_get_position(int p_screen) const { +Point2i DisplayServerX11::screen_get_position(int p_screen) const { _THREAD_SAFE_METHOD_ if (p_screen == SCREEN_OF_MAIN_WINDOW) { @@ -600,11 +752,15 @@ Rect2i DisplayServerX11::screen_get_usable_rect(int p_screen) const { // Using Xinerama Extension int event_base, error_base; const Bool ext_okay = XineramaQueryExtension(x11_display, &event_base, &error_base); - if (!ext_okay) return Rect2i(0, 0, 0, 0); + if (!ext_okay) { + return Rect2i(0, 0, 0, 0); + } int count; XineramaScreenInfo *xsi = XineramaQueryScreens(x11_display, &count); - if (p_screen >= count) return Rect2i(0, 0, 0, 0); + if (p_screen >= count) { + return Rect2i(0, 0, 0, 0); + } Rect2i rect = Rect2i(xsi[p_screen].x_org, xsi[p_screen].y_org, xsi[p_screen].width, xsi[p_screen].height); XFree(xsi); @@ -612,7 +768,6 @@ Rect2i DisplayServerX11::screen_get_usable_rect(int p_screen) const { } int DisplayServerX11::screen_get_dpi(int p_screen) const { - _THREAD_SAFE_METHOD_ if (p_screen == SCREEN_OF_MAIN_WINDOW) { @@ -649,14 +804,15 @@ int DisplayServerX11::screen_get_dpi(int p_screen) const { int height_mm = DisplayHeightMM(x11_display, p_screen); double xdpi = (width_mm ? sc.width / (double)width_mm * 25.4 : 0); double ydpi = (height_mm ? sc.height / (double)height_mm * 25.4 : 0); - if (xdpi || ydpi) + if (xdpi || ydpi) { return (xdpi + ydpi) / (xdpi && ydpi ? 2 : 1); + } //could not get dpi return 96; } -bool DisplayServerX11::screen_is_touchscreen(int p_screen) const { +bool DisplayServerX11::screen_is_touchscreen(int p_screen) const { _THREAD_SAFE_METHOD_ #ifndef _MSC_VER @@ -677,7 +833,6 @@ Vector<DisplayServer::WindowID> DisplayServerX11::get_window_list() const { } 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); @@ -690,8 +845,15 @@ DisplayServer::WindowID DisplayServerX11::create_sub_window(WindowMode p_mode, u return id; } -void DisplayServerX11::delete_sub_window(WindowID p_id) { +void DisplayServerX11::show_window(WindowID p_id) { + _THREAD_SAFE_METHOD_ + + WindowData &wd = windows[p_id]; + + XMapWindow(x11_display, wd.x11_window); +} +void DisplayServerX11::delete_sub_window(WindowID p_id) { _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_id)); @@ -699,6 +861,8 @@ void DisplayServerX11::delete_sub_window(WindowID p_id) { WindowData &wd = windows[p_id]; + DEBUG_LOG_X11("delete_sub_window: %lu (%u) \n", wd.x11_window, p_id); + while (wd.transient_children.size()) { window_set_transient(wd.transient_children.front()->get(), INVALID_WINDOW_ID); } @@ -716,13 +880,13 @@ void DisplayServerX11::delete_sub_window(WindowID p_id) { XDestroyWindow(x11_display, wd.x11_window); if (wd.xic) { XDestroyIC(wd.xic); + wd.xic = nullptr; } 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]; @@ -730,19 +894,40 @@ void DisplayServerX11::window_attach_instance_id(ObjectID p_instance, WindowID p } 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 { + WindowID found_window = INVALID_WINDOW_ID; + WindowID parent_window = INVALID_WINDOW_ID; + unsigned int focus_order = 0; + for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { + const WindowData &wd = E->get(); - return INVALID_WINDOW_ID; + // Discard windows with no focus. + if (wd.focus_order == 0) { + continue; + } + + // Find topmost window which contains the given position. + WindowID window_id = E->key(); + Rect2i win_rect = Rect2i(window_get_position(window_id), window_get_size(window_id)); + if (win_rect.has_point(p_position)) { + // For siblings, pick the window which was focused last. + if ((parent_window != wd.transient_parent) || (wd.focus_order > focus_order)) { + found_window = window_id; + parent_window = wd.transient_parent; + focus_order = wd.focus_order; + } + } + } + + return found_window; } void DisplayServerX11::window_set_title(const String &p_title, WindowID p_window) { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); @@ -752,11 +937,44 @@ void DisplayServerX11::window_set_title(const String &p_title, WindowID p_window 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()); + if (_net_wm_name != None && utf8_string != None) { + 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) { +void DisplayServerX11::window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window) { + _THREAD_SAFE_METHOD_ + ERR_FAIL_COND(!windows.has(p_window)); + const WindowData &wd = windows[p_window]; + + int event_base, error_base; + const Bool ext_okay = XShapeQueryExtension(x11_display, &event_base, &error_base); + if (ext_okay) { + Region region; + if (p_region.size() == 0) { + region = XCreateRegion(); + XRectangle rect; + rect.x = 0; + rect.y = 0; + rect.width = window_get_real_size(p_window).x; + rect.height = window_get_real_size(p_window).y; + XUnionRectWithRegion(&rect, region, region); + } else { + XPoint *points = (XPoint *)memalloc(sizeof(XPoint) * p_region.size()); + for (int i = 0; i < p_region.size(); i++) { + points[i].x = p_region[i].x; + points[i].y = p_region[i].y; + } + region = XPolygonRegion(points, p_region.size(), EvenOddRule); + memfree(points); + } + XShapeCombineRegion(x11_display, wd.x11_window, ShapeInput, 0, 0, region, ShapeSet); + XDestroyRegion(region); + } +} + +void DisplayServerX11::window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window) { _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); @@ -765,7 +983,6 @@ void DisplayServerX11::window_set_rect_changed_callback(const Callable &p_callab } void DisplayServerX11::window_set_window_event_callback(const Callable &p_callable, WindowID p_window) { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); @@ -774,15 +991,14 @@ void DisplayServerX11::window_set_window_event_callback(const Callable &p_callab } 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) { +void DisplayServerX11::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) { _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); @@ -791,7 +1007,6 @@ void DisplayServerX11::window_set_input_text_callback(const Callable &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)); @@ -800,7 +1015,6 @@ void DisplayServerX11::window_set_drop_files_callback(const Callable &p_callable } int DisplayServerX11::window_get_current_screen(WindowID p_window) const { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND_V(!windows.has(p_window), -1); @@ -814,20 +1028,23 @@ int DisplayServerX11::window_get_current_screen(WindowID p_window) const { 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)) + 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) { +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 (p_screen >= count) { + return; + } if (window_get_mode(p_window) == WINDOW_MODE_FULLSCREEN) { Point2i position = screen_get_position(p_screen); @@ -843,7 +1060,6 @@ void DisplayServerX11::window_set_current_screen(int p_screen, WindowID p_window } void DisplayServerX11::window_set_transient(WindowID p_window, WindowID p_parent) { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND(p_window == p_parent); @@ -851,24 +1067,34 @@ void DisplayServerX11::window_set_transient(WindowID p_window, WindowID p_parent ERR_FAIL_COND(!windows.has(p_window)); WindowData &wd_window = windows[p_window]; - ERR_FAIL_COND(wd_window.transient_parent == p_parent); + WindowID prev_parent = wd_window.transient_parent; + ERR_FAIL_COND(prev_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)); + ERR_FAIL_COND(prev_parent == INVALID_WINDOW_ID); + ERR_FAIL_COND(!windows.has(prev_parent)); - WindowData &wd_parent = windows[wd_window.transient_parent]; + WindowData &wd_parent = windows[prev_parent]; wd_window.transient_parent = INVALID_WINDOW_ID; wd_parent.transient_children.erase(p_window); XSetTransientForHint(x11_display, wd_window.x11_window, None); + + // Set focus to parent sub window to avoid losing all focus with nested menus. + // RevertToPointerRoot is used to make sure we don't lose all focus in case + // a subwindow and its parent are both destroyed. + if (wd_window.menu_type && !wd_window.no_focus) { + if (!wd_parent.no_focus) { + XSetInputFocus(x11_display, wd_parent.x11_window, RevertToPointerRoot, CurrentTime); + } + } } 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"); + ERR_FAIL_COND_MSG(prev_parent != INVALID_WINDOW_ID, "Window already has a transient parent"); WindowData &wd_parent = windows[p_parent]; wd_window.transient_parent = p_parent; @@ -878,8 +1104,47 @@ void DisplayServerX11::window_set_transient(WindowID p_window, WindowID p_parent } } -Point2i DisplayServerX11::window_get_position(WindowID p_window) const { +// Helper method. Assumes that the window id has already been checked and exists. +void DisplayServerX11::_update_size_hints(WindowID p_window) { + WindowData &wd = windows[p_window]; + WindowMode window_mode = window_get_mode(p_window); + XSizeHints *xsh = XAllocSizeHints(); + + // Always set the position and size hints - they should be synchronized with the actual values after the window is mapped anyway + xsh->flags |= PPosition | PSize; + xsh->x = wd.position.x; + xsh->y = wd.position.y; + xsh->width = wd.size.width; + xsh->height = wd.size.height; + + if (window_mode == WINDOW_MODE_FULLSCREEN) { + // Do not set any other hints to prevent the window manager from ignoring the fullscreen flags + } else if (window_get_flag(WINDOW_FLAG_RESIZE_DISABLED, p_window)) { + // If resizing is disabled, use the forced size + xsh->flags |= PMinSize | PMaxSize; + xsh->min_width = wd.size.x; + xsh->max_width = wd.size.x; + xsh->min_height = wd.size.y; + xsh->max_height = wd.size.y; + } else { + // Otherwise, just respect min_size and max_size + 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); +} + +Point2i DisplayServerX11::window_get_position(WindowID p_window) const { _THREAD_SAFE_METHOD_ ERR_FAIL_COND_V(!windows.has(p_window), Point2i()); @@ -889,7 +1154,6 @@ Point2i DisplayServerX11::window_get_position(WindowID p_window) const { } void DisplayServerX11::window_set_position(const Point2i &p_position, WindowID p_window) { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); @@ -922,7 +1186,6 @@ void DisplayServerX11::window_set_position(const Point2i &p_position, WindowID p } void DisplayServerX11::window_set_max_size(const Size2i p_size, WindowID p_window) { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); @@ -934,28 +1197,11 @@ void DisplayServerX11::window_set_max_size(const Size2i p_size, WindowID p_windo } wd.max_size = p_size; - if (!window_get_flag(WINDOW_FLAG_RESIZE_DISABLED, p_window)) { - XSizeHints *xsh; - xsh = XAllocSizeHints(); - 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); - - XFlush(x11_display); - } + _update_size_hints(p_window); + XFlush(x11_display); } -Size2i DisplayServerX11::window_get_max_size(WindowID p_window) const { +Size2i DisplayServerX11::window_get_max_size(WindowID p_window) const { _THREAD_SAFE_METHOD_ ERR_FAIL_COND_V(!windows.has(p_window), Size2i()); @@ -965,7 +1211,6 @@ Size2i DisplayServerX11::window_get_max_size(WindowID p_window) const { } void DisplayServerX11::window_set_min_size(const Size2i p_size, WindowID p_window) { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); @@ -977,28 +1222,11 @@ void DisplayServerX11::window_set_min_size(const Size2i p_size, WindowID p_windo } wd.min_size = p_size; - if (!window_get_flag(WINDOW_FLAG_RESIZE_DISABLED, p_window)) { - XSizeHints *xsh; - xsh = XAllocSizeHints(); - 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); - - XFlush(x11_display); - } + _update_size_hints(p_window); + XFlush(x11_display); } -Size2i DisplayServerX11::window_get_min_size(WindowID p_window) const { +Size2i DisplayServerX11::window_get_min_size(WindowID p_window) const { _THREAD_SAFE_METHOD_ ERR_FAIL_COND_V(!windows.has(p_window), Size2i()); @@ -1008,7 +1236,6 @@ Size2i DisplayServerX11::window_get_min_size(WindowID p_window) const { } void DisplayServerX11::window_set_size(const Size2i p_size, WindowID p_window) { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); @@ -1019,8 +1246,9 @@ void DisplayServerX11::window_set_size(const Size2i p_size, WindowID p_window) { WindowData &wd = windows[p_window]; - if (wd.size.width == size.width && wd.size.height == size.height) + if (wd.size.width == size.width && wd.size.height == size.height) { return; + } XWindowAttributes xwa; XSync(x11_display, False); @@ -1028,57 +1256,36 @@ void DisplayServerX11::window_set_size(const Size2i p_size, WindowID p_window) { 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 (!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); + // Update our videomode width and height + wd.size = size; + + // Update the size hints first to make sure the window size can be set + _update_size_hints(p_window); // Resize the window XResizeWindow(x11_display, wd.x11_window, size.x, size.y); - // Update our videomode width and height - wd.size = size; - for (int timeout = 0; timeout < 50; ++timeout) { XSync(x11_display, False); XGetWindowAttributes(x11_display, wd.x11_window, &xwa); - if (old_w != xwa.width || old_h != xwa.height) + if (old_w != xwa.width || old_h != xwa.height) { break; + } usleep(10000); } } -Size2i DisplayServerX11::window_get_size(WindowID p_window) const { +Size2i DisplayServerX11::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 DisplayServerX11::window_get_real_size(WindowID p_window) const { +Size2i DisplayServerX11::window_get_real_size(WindowID p_window) const { _THREAD_SAFE_METHOD_ ERR_FAIL_COND_V(!windows.has(p_window), Size2i()); @@ -1108,19 +1315,23 @@ Size2i DisplayServerX11::window_get_real_size(WindowID p_window) const { return Size2i(w, h); } -bool DisplayServerX11::window_is_maximize_allowed(WindowID p_window) const { - - _THREAD_SAFE_METHOD_ - +// Just a helper to reduce code duplication in `window_is_maximize_allowed` +// and `_set_wm_maximized`. +bool DisplayServerX11::_window_maximize_check(WindowID p_window, const char *p_atom_name) const { ERR_FAIL_COND_V(!windows.has(p_window), false); const WindowData &wd = windows[p_window]; - Atom property = XInternAtom(x11_display, "_NET_WM_ALLOWED_ACTIONS", False); + Atom property = XInternAtom(x11_display, p_atom_name, False); Atom type; int format; unsigned long len; unsigned long remaining; unsigned char *data = nullptr; + bool retval = false; + + if (property == None) { + return false; + } int result = XGetWindowProperty( x11_display, @@ -1144,22 +1355,31 @@ bool DisplayServerX11::window_is_maximize_allowed(WindowID p_window) const { bool found_wm_act_max_vert = false; for (uint64_t i = 0; i < len; i++) { - if (atoms[i] == wm_act_max_horz) + if (atoms[i] == wm_act_max_horz) { found_wm_act_max_horz = true; - if (atoms[i] == wm_act_max_vert) + } + 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; + if (found_wm_act_max_horz || found_wm_act_max_vert) { + retval = true; + break; + } } - XFree(atoms); + + XFree(data); } - return false; + return retval; } -void DisplayServerX11::_set_wm_maximized(WindowID p_window, bool p_enabled) { +bool DisplayServerX11::window_is_maximize_allowed(WindowID p_window) const { + _THREAD_SAFE_METHOD_ + return _window_maximize_check(p_window, "_NET_WM_ALLOWED_ACTIONS"); +} +void DisplayServerX11::_set_wm_maximized(WindowID p_window, bool p_enabled) { ERR_FAIL_COND(!windows.has(p_window)); WindowData &wd = windows[p_window]; @@ -1191,7 +1411,6 @@ void DisplayServerX11::_set_wm_maximized(WindowID p_window, bool p_enabled) { } void DisplayServerX11::_set_wm_fullscreen(WindowID p_window, bool p_enabled) { - ERR_FAIL_COND(!windows.has(p_window)); WindowData &wd = windows[p_window]; @@ -1202,17 +1421,14 @@ void DisplayServerX11::_set_wm_fullscreen(WindowID p_window, bool p_enabled) { 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 (property != None) { + XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5); + } } - if (p_enabled && window_get_flag(WINDOW_FLAG_RESIZE_DISABLED, p_window)) { + if (p_enabled) { // Set the window as resizable to prevent window managers to ignore the fullscreen state flag. - XSizeHints *xsh; - - xsh = XAllocSizeHints(); - xsh->flags = 0L; - XSetWMNormalHints(x11_display, wd.x11_window, xsh); - XFree(xsh); + _update_size_hints(p_window); } // Using EWMH -- Extended Window Manager Hints @@ -1234,36 +1450,15 @@ void DisplayServerX11::_set_wm_fullscreen(WindowID p_window, bool p_enabled) { // 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 (bypass_compositor != None) { + XChangeProperty(x11_display, wd.x11_window, bypass_compositor, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&compositing_disable_on, 1); + } 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); + _update_size_hints(p_window); // put back or remove decorations according to the last set borderless state Hints hints; @@ -1271,12 +1466,13 @@ void DisplayServerX11::_set_wm_fullscreen(WindowID p_window, bool p_enabled) { 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); + if (property != None) { + XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5); + } } } void DisplayServerX11::window_set_mode(WindowMode p_mode, WindowID p_window) { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); @@ -1322,13 +1518,13 @@ void DisplayServerX11::window_set_mode(WindowMode p_mode, WindowID p_window) { } break; case WINDOW_MODE_FULLSCREEN: { //Remove full-screen + wd.fullscreen = false; + _set_wm_fullscreen(p_window, false); //un-maximize required for always on top bool on_top = window_get_flag(WINDOW_FLAG_ALWAYS_ON_TOP, p_window); - wd.fullscreen = false; - window_set_position(wd.last_position_before_fs, p_window); if (on_top) { @@ -1337,7 +1533,6 @@ void DisplayServerX11::window_set_mode(WindowMode p_mode, WindowID p_window) { } break; case WINDOW_MODE_MAXIMIZED: { - _set_wm_maximized(p_window, false); } break; } @@ -1375,22 +1570,21 @@ void DisplayServerX11::window_set_mode(WindowMode p_mode, WindowID p_window) { } 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; + _set_wm_fullscreen(p_window, true); } break; case WINDOW_MODE_MAXIMIZED: { - _set_wm_maximized(p_window, true); - } break; } } DisplayServer::WindowMode DisplayServerX11::window_get_mode(WindowID p_window) const { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND_V(!windows.has(p_window), WINDOW_MODE_WINDOWED); @@ -1399,60 +1593,20 @@ DisplayServer::WindowMode DisplayServerX11::window_get_mode(WindowID p_window) c 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 = nullptr; - 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 maximized. + // Using EWMH -- Extended Window Manager Hints + if (_window_maximize_check(p_window, "_NET_WM_STATE")) { + return WINDOW_MODE_MAXIMIZED; } - { // test minimzed + { // Test minimized. // Using ICCCM -- Inter-Client Communication Conventions Manual Atom property = XInternAtom(x11_display, "WM_STATE", True); + if (property == None) { + return WINDOW_MODE_WINDOWED; + } + Atom type; int format; unsigned long len; @@ -1483,13 +1637,12 @@ DisplayServer::WindowMode DisplayServerX11::window_get_mode(WindowID p_window) c } } - // all other discarded, return windowed. + // 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)); @@ -1497,46 +1650,21 @@ void DisplayServerX11::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo 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); + _update_size_hints(p_window); + 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); + if (property != None) { + 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); @@ -1544,7 +1672,6 @@ void DisplayServerX11::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo 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); @@ -1577,8 +1704,8 @@ void DisplayServerX11::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo } } } -bool DisplayServerX11::window_get_flag(WindowFlags p_flag, WindowID p_window) const { +bool DisplayServerX11::window_get_flag(WindowFlags p_flag, WindowID p_window) const { _THREAD_SAFE_METHOD_ ERR_FAIL_COND_V(!windows.has(p_window), false); @@ -1586,15 +1713,12 @@ bool DisplayServerX11::window_get_flag(WindowFlags p_flag, WindowID p_window) co 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; @@ -1612,7 +1736,6 @@ bool DisplayServerX11::window_get_flag(WindowFlags p_flag, WindowID p_window) co return borderless; } break; case WINDOW_FLAG_ALWAYS_ON_TOP: { - return wd.on_top; } break; case WINDOW_FLAG_TRANSPARENT: { @@ -1626,7 +1749,6 @@ bool DisplayServerX11::window_get_flag(WindowFlags p_flag, WindowID p_window) co } void DisplayServerX11::window_request_attention(WindowID p_window) { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); @@ -1653,7 +1775,6 @@ void DisplayServerX11::window_request_attention(WindowID p_window) { } void DisplayServerX11::window_move_to_foreground(WindowID p_window) { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); @@ -1675,12 +1796,11 @@ void DisplayServerX11::window_move_to_foreground(WindowID p_window) { } 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 { +bool DisplayServerX11::can_any_window_draw() const { _THREAD_SAFE_METHOD_ for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { @@ -1693,7 +1813,6 @@ bool DisplayServerX11::can_any_window_draw() const { } void DisplayServerX11::window_set_ime_active(const bool p_active, WindowID p_window) { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); @@ -1701,18 +1820,25 @@ void DisplayServerX11::window_set_ime_active(const bool p_active, WindowID p_win wd.im_active = p_active; - if (!wd.xic) + if (!wd.xic) { return; + } + // Block events polling while changing input focus + // because it triggers some event polling internally. if (p_active) { - XSetICFocus(wd.xic); + { + MutexLock mutex_lock(events_mutex); + XSetICFocus(wd.xic); + } window_set_ime_position(wd.im_position, p_window); } else { + MutexLock mutex_lock(events_mutex); XUnsetICFocus(wd.xic); } } -void DisplayServerX11::window_set_ime_position(const Point2i &p_pos, WindowID p_window) { +void DisplayServerX11::window_set_ime_position(const Point2i &p_pos, WindowID p_window) { _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); @@ -1720,19 +1846,26 @@ void DisplayServerX11::window_set_ime_position(const Point2i &p_pos, WindowID p_ wd.im_position = p_pos; - if (!wd.xic) + 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, nullptr); - XSetICValues(wd.xic, XNPreeditAttributes, preedit_attr, nullptr); + + { + // Block events polling during this call + // because it triggers some event polling internally. + MutexLock mutex_lock(events_mutex); + XSetICValues(wd.xic, XNPreeditAttributes, preedit_attr, nullptr); + } + XFree(preedit_attr); } void DisplayServerX11::cursor_set_shape(CursorShape p_shape) { - _THREAD_SAFE_METHOD_ ERR_FAIL_INDEX(p_shape, CURSOR_MAX); @@ -1755,15 +1888,15 @@ void DisplayServerX11::cursor_set_shape(CursorShape p_shape) { 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) { +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) { @@ -1868,62 +2001,132 @@ void DisplayServerX11::cursor_set_custom_image(const RES &p_cursor, CursorShape } } -DisplayServerX11::LatinKeyboardVariant DisplayServerX11::get_latin_keyboard_variant() const { - _THREAD_SAFE_METHOD_ - - XkbDescRec *xkbdesc = XkbAllocKeyboard(); - ERR_FAIL_COND_V(!xkbdesc, LATIN_KEYBOARD_QWERTY); +int DisplayServerX11::keyboard_get_layout_count() const { + int _group_count = 0; + XkbDescRec *kbd = XkbAllocKeyboard(); + if (kbd) { + kbd->dpy = x11_display; + XkbGetControls(x11_display, XkbAllControlsMask, kbd); + XkbGetNames(x11_display, XkbSymbolsNameMask, kbd); + + const Atom *groups = kbd->names->groups; + if (kbd->ctrls != NULL) { + _group_count = kbd->ctrls->num_groups; + } else { + while (_group_count < XkbNumKbdGroups && groups[_group_count] != None) { + _group_count++; + } + } + XkbFreeKeyboard(kbd, 0, true); + } + return _group_count; +} - XkbGetNames(x11_display, XkbSymbolsNameMask, xkbdesc); - ERR_FAIL_COND_V(!xkbdesc->names, LATIN_KEYBOARD_QWERTY); - ERR_FAIL_COND_V(!xkbdesc->names->symbols, LATIN_KEYBOARD_QWERTY); +int DisplayServerX11::keyboard_get_current_layout() const { + XkbStateRec state; + XkbGetState(x11_display, XkbUseCoreKbd, &state); + return state.group; +} - char *layout = XGetAtomName(x11_display, xkbdesc->names->symbols); - ERR_FAIL_COND_V(!layout, LATIN_KEYBOARD_QWERTY); +void DisplayServerX11::keyboard_set_current_layout(int p_index) { + ERR_FAIL_INDEX(p_index, keyboard_get_layout_count()); + XkbLockGroup(x11_display, XkbUseCoreKbd, p_index); +} - Vector<String> info = String(layout).split("+"); - ERR_FAIL_INDEX_V(1, info.size(), LATIN_KEYBOARD_QWERTY); +String DisplayServerX11::keyboard_get_layout_language(int p_index) const { + String ret; + XkbDescRec *kbd = XkbAllocKeyboard(); + if (kbd) { + kbd->dpy = x11_display; + XkbGetControls(x11_display, XkbAllControlsMask, kbd); + XkbGetNames(x11_display, XkbSymbolsNameMask, kbd); + XkbGetNames(x11_display, XkbGroupNamesMask, kbd); + + int _group_count = 0; + const Atom *groups = kbd->names->groups; + if (kbd->ctrls != NULL) { + _group_count = kbd->ctrls->num_groups; + } else { + while (_group_count < XkbNumKbdGroups && groups[_group_count] != None) { + _group_count++; + } + } - 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; + Atom names = kbd->names->symbols; + if (names != None) { + char *name = XGetAtomName(x11_display, names); + Vector<String> info = String(name).split("+"); + if (p_index >= 0 && p_index < _group_count) { + if (p_index + 1 < info.size()) { + ret = info[p_index + 1]; // Skip "pc" at the start and "inet"/"group" at the end of symbols. + } else { + ret = "en"; // No symbol for layout fallback to "en". + } + } else { + ERR_PRINT("Index " + itos(p_index) + "is out of bounds (" + itos(_group_count) + ")."); + } + XFree(name); + } + XkbFreeKeyboard(kbd, 0, true); } + return ret.substr(0, 2); +} - return LATIN_KEYBOARD_QWERTY; +String DisplayServerX11::keyboard_get_layout_name(int p_index) const { + String ret; + XkbDescRec *kbd = XkbAllocKeyboard(); + if (kbd) { + kbd->dpy = x11_display; + XkbGetControls(x11_display, XkbAllControlsMask, kbd); + XkbGetNames(x11_display, XkbSymbolsNameMask, kbd); + XkbGetNames(x11_display, XkbGroupNamesMask, kbd); + + int _group_count = 0; + const Atom *groups = kbd->names->groups; + if (kbd->ctrls != NULL) { + _group_count = kbd->ctrls->num_groups; + } else { + while (_group_count < XkbNumKbdGroups && groups[_group_count] != None) { + _group_count++; + } + } + + if (p_index >= 0 && p_index < _group_count) { + char *full_name = XGetAtomName(x11_display, groups[p_index]); + ret.parse_utf8(full_name); + XFree(full_name); + } else { + ERR_PRINT("Index " + itos(p_index) + "is out of bounds (" + itos(_group_count) + ")."); + } + XkbFreeKeyboard(kbd, 0, true); + } + return ret; } 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; + Atom actual_type = None; + int actual_format = 0; + unsigned long nitems = 0; + unsigned long bytes_after = 0; unsigned char *ret = nullptr; int read_bytes = 1024; - //Keep trying to read the property until there are no - //bytes unread. - do { - if (ret != nullptr) - XFree(ret); + // Keep trying to read the property until there are no bytes unread. + if (p_property != None) { + do { + if (ret != nullptr) { + XFree(ret); + } - XGetWindowProperty(p_display, p_window, p_property, 0, read_bytes, False, AnyPropertyType, - &actual_type, &actual_format, &nitems, &bytes_after, - &ret); + XGetWindowProperty(p_display, p_window, p_property, 0, read_bytes, False, AnyPropertyType, + &actual_type, &actual_format, &nitems, &bytes_after, + &ret); - read_bytes *= 2; + read_bytes *= 2; - } while (bytes_after != 0); + } while (bytes_after != 0); + } Property p = { ret, actual_format, (int)nitems, actual_type }; @@ -1931,36 +2134,36 @@ DisplayServerX11::Property DisplayServerX11::_read_property(Display *p_display, } 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) + 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) + 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) + 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) + 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)); state->set_alt((p_x11_state & Mod1Mask /*|| p_x11_state&Mod5Mask*/)); //altgr should not count as alt @@ -1968,7 +2171,6 @@ void DisplayServerX11::_get_key_modifier_state(unsigned int p_x11_state, Ref<Inp } unsigned int DisplayServerX11::_get_mouse_button_state(unsigned int p_x11_button, int p_x11_type) { - unsigned int mask = 1 << (p_x11_button - 1); if (p_x11_type == ButtonPress) { @@ -1980,8 +2182,7 @@ unsigned int DisplayServerX11::_get_mouse_button_state(unsigned int p_x11_button return last_button_state; } -void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, bool p_echo) { - +void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, LocalVector<XEvent> &p_events, uint32_t &p_event_index, bool p_echo) { WindowData wd = windows[p_window]; // X11 functions don't know what const is XKeyEvent *xkeyevent = p_event; @@ -2025,7 +2226,6 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, } if (xkeyevent->type == KeyPress && wd.xic) { - Status status; #ifdef X_HAVE_UTF8_STRING int utf8len = 8; @@ -2045,8 +2245,9 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, unsigned int keycode = KeyMappingX11::get_keycode(keysym_keycode); unsigned int physical_keycode = KeyMappingX11::get_scancode(xkeyevent->keycode); - if (keycode >= 'a' && keycode <= 'z') + if (keycode >= 'a' && keycode <= 'z') { keycode -= 'a' - 'A'; + } String tmp; tmp.parse_utf8(utf8string, utf8bytes); @@ -2089,7 +2290,6 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, memfree(utf8string); #else do { - int mnbytes = XmbLookupString(xic, xkeyevent, xmbstring, xmblen - 1, &keysym_unicode, &status); xmbstring[mnbytes] = '\0'; @@ -2119,7 +2319,7 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, /* Phase 4, determine if event must be filtered */ // This seems to be a side-effect of using XIM. - // XEventFilter looks like a core X11 function, + // XFilterEvent looks like a core X11 function, // but it's actually just used to see if we must // ignore a deadkey, or events XIM determines // must not reach the actual gui. @@ -2153,18 +2353,16 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, // Echo characters in X11 are a keyrelease and a keypress // one after the other with the (almot) same timestamp. - // To detect them, i use XPeekEvent and check that their - // difference in time is below a threshold. + // To detect them, i compare to the next event in list and + // check that their difference in time is below a threshold. if (xkeyevent->type != KeyPress) { - p_echo = false; // make sure there are events pending, // so this call won't block. - if (XPending(x11_display) > 0) { - XEvent peek_event; - XPeekEvent(x11_display, &peek_event); + if (p_event_index + 1 < p_events.size()) { + XEvent &peek_event = p_events[p_event_index + 1]; // I'm using a threshold of 5 msecs, // since sometimes there seems to be a little @@ -2179,9 +2377,9 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, KeySym rk; XLookupString((XKeyEvent *)&peek_event, str, 256, &rk, nullptr); if (rk == keysym_keycode) { - XEvent event; - XNextEvent(x11_display, &event); //erase next event - _handle_key_event(p_window, (XKeyEvent *)&event, true); + // Consume to next event. + ++p_event_index; + _handle_key_event(p_window, (XKeyEvent *)&peek_event, p_events, p_event_index, true); return; //ignore current, echo next } } @@ -2196,8 +2394,9 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, k->set_pressed(keypress); - if (keycode >= 'a' && keycode <= 'z') + if (keycode >= 'a' && keycode <= 'z') { keycode -= 'a' - 'A'; + } k->set_keycode(keycode); k->set_physical_keycode(physical_keycode); @@ -2214,14 +2413,15 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, //don't set mod state if modifier keys are released by themselves //else event.is_action() will not work correctly here if (!k->is_pressed()) { - if (k->get_keycode() == KEY_SHIFT) + if (k->get_keycode() == KEY_SHIFT) { k->set_shift(false); - else if (k->get_keycode() == KEY_CONTROL) + } else if (k->get_keycode() == KEY_CONTROL) { k->set_control(false); - else if (k->get_keycode() == KEY_ALT) + } else if (k->get_keycode() == KEY_ALT) { k->set_alt(false); - else if (k->get_keycode() == KEY_META) + } else if (k->get_keycode() == KEY_META) { k->set_metakey(false); + } } bool last_is_pressed = Input::get_singleton()->is_key_pressed(k->get_keycode()); @@ -2234,9 +2434,120 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, Input::get_singleton()->accumulate_input_event(k); } +Atom DisplayServerX11::_process_selection_request_target(Atom p_target, Window p_requestor, Atom p_property) const { + if (p_target == XInternAtom(x11_display, "TARGETS", 0)) { + // Request to list all supported targets. + Atom data[9]; + data[0] = XInternAtom(x11_display, "TARGETS", 0); + data[1] = XInternAtom(x11_display, "SAVE_TARGETS", 0); + data[2] = XInternAtom(x11_display, "MULTIPLE", 0); + data[3] = XInternAtom(x11_display, "UTF8_STRING", 0); + data[4] = XInternAtom(x11_display, "COMPOUND_TEXT", 0); + data[5] = XInternAtom(x11_display, "TEXT", 0); + data[6] = XA_STRING; + data[7] = XInternAtom(x11_display, "text/plain;charset=utf-8", 0); + data[8] = XInternAtom(x11_display, "text/plain", 0); + + XChangeProperty(x11_display, + p_requestor, + p_property, + XA_ATOM, + 32, + PropModeReplace, + (unsigned char *)&data, + sizeof(data) / sizeof(data[0])); + return p_property; + } else if (p_target == XInternAtom(x11_display, "SAVE_TARGETS", 0)) { + // Request to check if SAVE_TARGETS is supported, nothing special to do. + XChangeProperty(x11_display, + p_requestor, + p_property, + XInternAtom(x11_display, "NULL", False), + 32, + PropModeReplace, + nullptr, + 0); + return p_property; + } else if (p_target == XInternAtom(x11_display, "UTF8_STRING", 0) || + p_target == XInternAtom(x11_display, "COMPOUND_TEXT", 0) || + p_target == XInternAtom(x11_display, "TEXT", 0) || + p_target == XA_STRING || + p_target == XInternAtom(x11_display, "text/plain;charset=utf-8", 0) || + p_target == XInternAtom(x11_display, "text/plain", 0)) { + // Directly using internal clipboard because we know our window + // is the owner during a selection request. + CharString clip = internal_clipboard.utf8(); + XChangeProperty(x11_display, + p_requestor, + p_property, + p_target, + 8, + PropModeReplace, + (unsigned char *)clip.get_data(), + clip.length()); + return p_property; + } else { + char *target_name = XGetAtomName(x11_display, p_target); + printf("Target '%s' not supported.\n", target_name); + if (target_name) { + XFree(target_name); + } + return None; + } +} + +void DisplayServerX11::_handle_selection_request_event(XSelectionRequestEvent *p_event) const { + XEvent respond; + if (p_event->target == XInternAtom(x11_display, "MULTIPLE", 0)) { + // Request for multiple target conversions at once. + Atom atom_pair = XInternAtom(x11_display, "ATOM_PAIR", False); + respond.xselection.property = None; + + Atom type; + int format; + unsigned long len; + unsigned long remaining; + unsigned char *data = nullptr; + if (XGetWindowProperty(x11_display, p_event->requestor, p_event->property, 0, LONG_MAX, False, atom_pair, &type, &format, &len, &remaining, &data) == Success) { + if ((len >= 2) && data) { + Atom *targets = (Atom *)data; + for (uint64_t i = 0; i < len; i += 2) { + Atom target = targets[i]; + Atom &property = targets[i + 1]; + property = _process_selection_request_target(target, p_event->requestor, property); + } + + XChangeProperty(x11_display, + p_event->requestor, + p_event->property, + atom_pair, + 32, + PropModeReplace, + (unsigned char *)targets, + len); + + respond.xselection.property = p_event->property; + } + XFree(data); + } + } else { + // Request for target conversion. + respond.xselection.property = _process_selection_request_target(p_event->target, p_event->requestor, p_event->property); + } + + respond.xselection.type = SelectionNotify; + respond.xselection.display = p_event->display; + respond.xselection.requestor = p_event->requestor; + respond.xselection.selection = p_event->selection; + respond.xselection.target = p_event->target; + respond.xselection.time = p_event->time; + + XSendEvent(x11_display, p_event->requestor, True, NoEventMask, &respond); + XFlush(x11_display); +} + void DisplayServerX11::_xim_destroy_callback(::XIM im, ::XPointer client_data, ::XPointer call_data) { - WARN_PRINT("Input method stopped"); DisplayServerX11 *ds = reinterpret_cast<DisplayServerX11 *>(client_data); ds->xim = nullptr; @@ -2247,7 +2558,6 @@ void DisplayServerX11::_xim_destroy_callback(::XIM im, ::XPointer client_data, } void DisplayServerX11::_window_changed(XEvent *event) { - WindowID window_id = MAIN_WINDOW_ID; // Assign the event to the relevant window @@ -2312,7 +2622,6 @@ void DisplayServerX11::_dispatch_input_events(const Ref<InputEvent> &p_event) { } void DisplayServerX11::_dispatch_input_event(const Ref<InputEvent> &p_event) { - Variant ev = p_event; Variant *evp = &ev; Variant ret; @@ -2348,10 +2657,111 @@ void DisplayServerX11::_send_window_event(const WindowData &wd, WindowEvent p_ev wd.event_callback.call((const Variant **)&eventp, 1, ret, ce); } } -void DisplayServerX11::process_events() { +void DisplayServerX11::_poll_events_thread(void *ud) { + DisplayServerX11 *display_server = (DisplayServerX11 *)ud; + display_server->_poll_events(); +} + +Bool DisplayServerX11::_predicate_all_events(Display *display, XEvent *event, XPointer arg) { + // Just accept all events. + return True; +} + +bool DisplayServerX11::_wait_for_events() const { + int x11_fd = ConnectionNumber(x11_display); + fd_set in_fds; + + XFlush(x11_display); + + FD_ZERO(&in_fds); + FD_SET(x11_fd, &in_fds); + + struct timeval tv; + tv.tv_usec = 0; + tv.tv_sec = 1; + + // Wait for next event or timeout. + int num_ready_fds = select(x11_fd + 1, &in_fds, NULL, NULL, &tv); + + if (num_ready_fds > 0) { + // Event received. + return true; + } else { + // Error or timeout. + if (num_ready_fds < 0) { + ERR_PRINT("_wait_for_events: select error: " + itos(errno)); + } + return false; + } +} + +void DisplayServerX11::_poll_events() { + while (!events_thread_done) { + _wait_for_events(); + + // Process events from the queue. + { + MutexLock mutex_lock(events_mutex); + + // Non-blocking wait for next event and remove it from the queue. + XEvent ev; + while (XCheckIfEvent(x11_display, &ev, _predicate_all_events, nullptr)) { + // Check if the input manager wants to process the event. + if (XFilterEvent(&ev, None)) { + // Event has been filtered by the Input Manager, + // it has to be ignored and a new one will be received. + continue; + } + + // Handle selection request events directly in the event thread, because + // communication through the x server takes several events sent back and forth + // and we don't want to block other programs while processing only one each frame. + if (ev.type == SelectionRequest) { + _handle_selection_request_event(&(ev.xselectionrequest)); + continue; + } + + polled_events.push_back(ev); + } + } + } +} + +void DisplayServerX11::process_events() { _THREAD_SAFE_METHOD_ +#ifdef DISPLAY_SERVER_X11_DEBUG_LOGS_ENABLED + static int frame = 0; + ++frame; +#endif + + if (app_focused) { + //verify that one of the windows has focus, else send focus out notification + bool focus_found = false; + for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { + if (E->get().focused) { + focus_found = true; + break; + } + } + + if (!focus_found) { + uint64_t delta = OS::get_singleton()->get_ticks_msec() - time_since_no_focus; + + if (delta > 250) { + //X11 can go between windows and have no focus for a while, when creating them or something else. Use this as safety to avoid unnecessary focus in/outs. + if (OS::get_singleton()->get_main_loop()) { + DEBUG_LOG_X11("All focus lost, triggering NOTIFICATION_APPLICATION_FOCUS_OUT\n"); + OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT); + } + app_focused = false; + } + } else { + time_since_no_focus = OS::get_singleton()->get_ticks_msec(); + } + } + do_mouse_warp = false; // Is the current mouse mode one where it needs to be grabbed. @@ -2361,9 +2771,16 @@ void DisplayServerX11::process_events() { xi.tilt = Vector2(); xi.pressure_supported = false; - while (XPending(x11_display) > 0) { - XEvent event; - XNextEvent(x11_display, &event); + LocalVector<XEvent> events; + { + // Block events polling while flushing events. + MutexLock mutex_lock(events_mutex); + events = polled_events; + polled_events.clear(); + } + + for (uint32_t event_index = 0; event_index < events.size(); ++event_index) { + XEvent &event = events[event_index]; WindowID window_id = MAIN_WINDOW_ID; @@ -2375,14 +2792,8 @@ void DisplayServerX11::process_events() { } } - if (XFilterEvent(&event, None)) { - continue; - } - if (XGetEventData(x11_display, &event.xcookie)) { - if (event.xcookie.type == GenericEvent && event.xcookie.extension == xi.opcode) { - XIDeviceEvent *event_data = (XIDeviceEvent *)event.xcookie.data; int index = event_data->detail; Vector2 pos = Vector2(event_data->event_x, event_data->event_y); @@ -2489,7 +2900,6 @@ void DisplayServerX11::process_events() { //XIAllowTouchEvents(x11_display, event_data->deviceid, event_data->detail, x11_window, XIAcceptTouch); case XI_TouchEnd: { - bool is_begin = event_data->evtype == XI_TouchBegin; Ref<InputEventScreenTouch> st; @@ -2500,8 +2910,9 @@ void DisplayServerX11::process_events() { st->set_pressed(is_begin); if (is_begin) { - if (xi.state.has(index)) // Defensive + if (xi.state.has(index)) { // Defensive break; + } xi.state[index] = pos; if (xi.state.size() == 1) { // X11 may send a motion event when a touch gesture begins, that would result @@ -2510,22 +2921,21 @@ void DisplayServerX11::process_events() { } Input::get_singleton()->accumulate_input_event(st); } else { - if (!xi.state.has(index)) // Defensive + if (!xi.state.has(index)) { // Defensive break; + } xi.state.erase(index); Input::get_singleton()->accumulate_input_event(st); } } break; case XI_TouchUpdate: { - Map<int, Vector2>::Element *curr_pos_elem = xi.state.find(index); if (!curr_pos_elem) { // Defensive break; } if (curr_pos_elem->value() != pos) { - Ref<InputEventScreenDrag> sd; sd.instance(); sd->set_window_id(window_id); @@ -2544,44 +2954,84 @@ void DisplayServerX11::process_events() { XFreeEventData(x11_display, &event.xcookie); switch (event.type) { - case Expose: + case MapNotify: { + DEBUG_LOG_X11("[%u] MapNotify window=%lu (%u) \n", frame, event.xmap.window, window_id); + + const WindowData &wd = windows[window_id]; + + // Set focus when menu window is started. + // RevertToPointerRoot is used to make sure we don't lose all focus in case + // a subwindow and its parent are both destroyed. + if (wd.menu_type && !wd.no_focus) { + XSetInputFocus(x11_display, wd.x11_window, RevertToPointerRoot, CurrentTime); + } + } break; + + case Expose: { + DEBUG_LOG_X11("[%u] Expose window=%lu (%u), count='%u' \n", frame, event.xexpose.window, window_id, event.xexpose.count); + Main::force_redraw(); - break; + } break; - case NoExpose: - minimized = true; - break; + case NoExpose: { + DEBUG_LOG_X11("[%u] NoExpose drawable=%lu (%u) \n", frame, event.xnoexpose.drawable, window_id); + + windows[window_id].minimized = true; + } break; case VisibilityNotify: { + DEBUG_LOG_X11("[%u] VisibilityNotify window=%lu (%u), state=%u \n", frame, event.xvisibility.window, window_id, event.xvisibility.state); + XVisibilityEvent *visibility = (XVisibilityEvent *)&event; - minimized = (visibility->state == VisibilityFullyObscured); + windows[window_id].minimized = (visibility->state == VisibilityFullyObscured); } break; + case LeaveNotify: { + DEBUG_LOG_X11("[%u] LeaveNotify window=%lu (%u), mode='%u' \n", frame, event.xcrossing.window, window_id, event.xcrossing.mode); + if (!mouse_mode_grab) { _send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_EXIT); } } break; + case EnterNotify: { + DEBUG_LOG_X11("[%u] EnterNotify window=%lu (%u), mode='%u' \n", frame, event.xcrossing.window, window_id, event.xcrossing.mode); + if (!mouse_mode_grab) { _send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_ENTER); } } break; - case FocusIn: - minimized = false; - window_has_focus = true; - _send_window_event(windows[window_id], WINDOW_EVENT_FOCUS_IN); - window_focused = true; + + case FocusIn: { + DEBUG_LOG_X11("[%u] FocusIn window=%lu (%u), mode='%u' \n", frame, event.xfocus.window, window_id, event.xfocus.mode); + + WindowData &wd = windows[window_id]; + + wd.focused = true; + + if (wd.xic) { + // Block events polling while changing input focus + // because it triggers some event polling internally. + MutexLock mutex_lock(events_mutex); + XSetICFocus(wd.xic); + } + + // Keep track of focus order for overlapping windows. + static unsigned int focus_order = 0; + wd.focus_order = ++focus_order; + + _send_window_event(wd, WINDOW_EVENT_FOCUS_IN); if (mouse_mode_grab) { // Show and update the cursor if confined and the window regained focus. for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { - - if (mouse_mode == MOUSE_MODE_CONFINED) + 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 + } 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, @@ -2595,20 +3045,34 @@ void DisplayServerX11::process_events() { XIGrabDevice(x11_display, xi.touch_devices[i], x11_window, CurrentTime, None, XIGrabModeAsync, XIGrabModeAsync, False, &xi.touch_event_mask); }*/ #endif - if (windows[window_id].xic) { - XSetICFocus(windows[window_id].xic); + + if (!app_focused) { + if (OS::get_singleton()->get_main_loop()) { + OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN); + } + app_focused = true; + } + } break; + + case FocusOut: { + DEBUG_LOG_X11("[%u] FocusOut window=%lu (%u), mode='%u' \n", frame, event.xfocus.window, window_id, event.xfocus.mode); + + WindowData &wd = windows[window_id]; + + wd.focused = false; + + if (wd.xic) { + // Block events polling while changing input focus + // because it triggers some event polling internally. + MutexLock mutex_lock(events_mutex); + XUnsetICFocus(wd.xic); } - break; - case FocusOut: - window_has_focus = false; Input::get_singleton()->release_pressed_events(); - _send_window_event(windows[window_id], WINDOW_EVENT_FOCUS_OUT); - window_focused = false; + _send_window_event(wd, WINDOW_EVENT_FOCUS_OUT); if (mouse_mode_grab) { 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. @@ -2625,7 +3089,6 @@ void DisplayServerX11::process_events() { // Release every pointer to avoid sticky points for (Map<int, Vector2>::Element *E = xi.state.front(); E; E = E->next()) { - Ref<InputEventScreenTouch> st; st.instance(); st->set_index(E->key()); @@ -2635,17 +3098,25 @@ void DisplayServerX11::process_events() { } xi.state.clear(); #endif - if (windows[window_id].xic) { - XSetICFocus(windows[window_id].xic); + } break; + + case ConfigureNotify: { + DEBUG_LOG_X11("[%u] ConfigureNotify window=%lu (%u), event=%lu, above=%lu, override_redirect=%u \n", frame, event.xconfigure.window, window_id, event.xconfigure.event, event.xconfigure.above, event.xconfigure.override_redirect); + + const WindowData &wd = windows[window_id]; + + // Set focus when menu window is re-used. + // RevertToPointerRoot is used to make sure we don't lose all focus in case + // a subwindow and its parent are both destroyed. + if (wd.menu_type && !wd.no_focus) { + XSetInputFocus(x11_display, wd.x11_window, RevertToPointerRoot, CurrentTime); } - break; - case ConfigureNotify: _window_changed(&event); - break; + } break; + case ButtonPress: case ButtonRelease: { - /* exit in case of a mouse button press */ last_timestamp = event.xbutton.time; if (mouse_mode == MOUSE_MODE_CAPTURED) { @@ -2659,24 +3130,33 @@ void DisplayServerX11::process_events() { 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) + if (mb->get_button_index() == 2) { mb->set_button_index(3); - else if (mb->get_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_position(Vector2(event.xbutton.x, event.xbutton.y)); mb->set_global_position(mb->get_position()); mb->set_pressed((event.type == ButtonPress)); + const WindowData &wd = windows[window_id]; + if (event.type == ButtonPress) { + DEBUG_LOG_X11("[%u] ButtonPress window=%lu (%u), button_index=%u \n", frame, event.xbutton.window, window_id, mb->get_button_index()); + + // Ensure window focus on click. + // RevertToPointerRoot is used to make sure we don't lose all focus in case + // a subwindow and its parent are both destroyed. + if (!wd.no_focus) { + XSetInputFocus(x11_display, wd.x11_window, RevertToPointerRoot, CurrentTime); + } 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 && Vector2(last_click_pos).distance_to(Vector2(event.xbutton.x, event.xbutton.y)) < 5) { - last_click_ms = 0; last_click_pos = Point2i(-100, -100); last_click_button_index = -1; @@ -2691,13 +3171,39 @@ void DisplayServerX11::process_events() { last_click_ms += diff; last_click_pos = Point2i(event.xbutton.x, event.xbutton.y); } + } else { + DEBUG_LOG_X11("[%u] ButtonRelease window=%lu (%u), button_index=%u \n", frame, event.xbutton.window, window_id, mb->get_button_index()); + + if (!wd.focused) { + // Propagate the event to the focused window, + // because it's received only on the topmost window. + // Note: This is needed for drag & drop to work between windows, + // because the engine expects events to keep being processed + // on the same window dragging started. + for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { + const WindowData &wd_other = E->get(); + WindowID window_id_other = E->key(); + if (wd_other.focused) { + if (window_id_other != window_id) { + int x, y; + Window child; + XTranslateCoordinates(x11_display, wd.x11_window, wd_other.x11_window, event.xbutton.x, event.xbutton.y, &x, &y, &child); + + mb->set_window_id(window_id_other); + mb->set_position(Vector2(x, y)); + mb->set_global_position(mb->get_position()); + Input::get_singleton()->accumulate_input_event(mb); + } + break; + } + } + } } Input::get_singleton()->accumulate_input_event(mb); } break; case MotionNotify: { - // The X11 API requires filtering one-by-one through the motion // notify events, in order to figure out which event is the one // generated by warping the mouse pointer. @@ -2709,11 +3215,11 @@ void DisplayServerX11::process_events() { break; } - if (XPending(x11_display) > 0) { - XEvent tevent; - XPeekEvent(x11_display, &tevent); - if (tevent.type == MotionNotify) { - XNextEvent(x11_display, &event); + if (event_index + 1 < events.size()) { + const XEvent &next_event = events[event_index + 1]; + if (next_event.type == MotionNotify) { + ++event_index; + event = next_event; } else { break; } @@ -2741,6 +3247,9 @@ void DisplayServerX11::process_events() { break; } + const WindowData &wd = windows[window_id]; + bool focused = wd.focused; + if (mouse_mode == MOUSE_MODE_CAPTURED) { if (xi.relative_motion.x == 0 && xi.relative_motion.y == 0) { break; @@ -2749,11 +3258,10 @@ void DisplayServerX11::process_events() { Point2i new_center = pos; pos = last_mouse_pos + xi.relative_motion; center = new_center; - do_mouse_warp = window_has_focus; // warp the cursor if we're focused in + do_mouse_warp = focused; // warp the cursor if we're focused in } if (!last_mouse_pos_valid) { - last_mouse_pos = pos; last_mouse_pos_valid = true; } @@ -2792,14 +3300,11 @@ void DisplayServerX11::process_events() { } 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(mouse_get_button_state()); - mm->set_position(posi); - mm->set_global_position(posi); - Input::get_singleton()->set_mouse_position(posi); + mm->set_position(pos); + mm->set_global_position(pos); + Input::get_singleton()->set_mouse_position(pos); mm->set_speed(Input::get_singleton()->get_last_mouse_speed()); mm->set_relative(rel); @@ -2810,85 +3315,47 @@ void DisplayServerX11::process_events() { // Don't propagate the motion event unless we have focus // this is so that the relative motion doesn't get messed up // after we regain focus. - if (window_has_focus || !mouse_mode_grab) + if (focused) { Input::get_singleton()->accumulate_input_event(mm); + } else { + // Propagate the event to the focused window, + // because it's received only on the topmost window. + // Note: This is needed for drag & drop to work between windows, + // because the engine expects events to keep being processed + // on the same window dragging started. + for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { + const WindowData &wd_other = E->get(); + if (wd_other.focused) { + int x, y; + Window child; + XTranslateCoordinates(x11_display, wd.x11_window, wd_other.x11_window, event.xmotion.x, event.xmotion.y, &x, &y, &child); + + Point2i pos_focused(x, y); + + mm->set_window_id(E->key()); + mm->set_position(pos_focused); + mm->set_global_position(pos_focused); + mm->set_speed(Input::get_singleton()->get_last_mouse_speed()); + Input::get_singleton()->accumulate_input_event(mm); + + break; + } + } + } } break; case KeyPress: case KeyRelease: { - last_timestamp = event.xkey.time; // key event is a little complex, so // it will be handled in its own function. - _handle_key_event(window_id, (XKeyEvent *)&event); - } break; - case SelectionRequest: { - - XSelectionRequestEvent *req; - XEvent e, respond; - e = event; - - req = &(e.xselectionrequest); - if (req->target == XInternAtom(x11_display, "UTF8_STRING", 0) || - req->target == XInternAtom(x11_display, "COMPOUND_TEXT", 0) || - req->target == XInternAtom(x11_display, "TEXT", 0) || - 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 = clipboard_get().utf8(); - XChangeProperty(x11_display, - req->requestor, - req->property, - req->target, - 8, - PropModeReplace, - (unsigned char *)clip.get_data(), - clip.length()); - respond.xselection.property = req->property; - } else if (req->target == XInternAtom(x11_display, "TARGETS", 0)) { - - Atom data[7]; - data[0] = XInternAtom(x11_display, "TARGETS", 0); - data[1] = XInternAtom(x11_display, "UTF8_STRING", 0); - data[2] = XInternAtom(x11_display, "COMPOUND_TEXT", 0); - data[3] = XInternAtom(x11_display, "TEXT", 0); - data[4] = XA_STRING; - data[5] = XInternAtom(x11_display, "text/plain;charset=utf-8", 0); - data[6] = XInternAtom(x11_display, "text/plain", 0); - - XChangeProperty(x11_display, - req->requestor, - req->property, - XA_ATOM, - 32, - PropModeReplace, - (unsigned char *)&data, - sizeof(data) / sizeof(data[0])); - respond.xselection.property = req->property; - - } else { - char *targetname = XGetAtomName(x11_display, req->target); - printf("No Target '%s'\n", targetname); - if (targetname) - XFree(targetname); - respond.xselection.property = None; - } - - respond.xselection.type = SelectionNotify; - respond.xselection.display = req->display; - respond.xselection.requestor = req->requestor; - respond.xselection.selection = req->selection; - respond.xselection.target = req->target; - respond.xselection.time = req->time; - XSendEvent(x11_display, req->requestor, True, NoEventMask, &respond); - XFlush(x11_display); + _handle_key_event(window_id, (XKeyEvent *)&event, events, event_index); } break; case SelectionNotify: if (event.xselection.target == requested) { - 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); @@ -2927,7 +3394,6 @@ void DisplayServerX11::process_events() { } else if ((unsigned int)event.xclient.message_type == (unsigned int)xdnd_enter) { - //File(s) have been dragged over the window, check for supported target (text/uri-list) xdnd_version = (event.xclient.data.l[1] >> 24); Window source = event.xclient.data.l[0]; @@ -2935,10 +3401,10 @@ void DisplayServerX11::process_events() { if (more_than_3) { 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 + } else { requested = pick_target_from_atoms(x11_display, event.xclient.data.l[2], event.xclient.data.l[3], event.xclient.data.l[4]); + } } else if ((unsigned int)event.xclient.message_type == (unsigned int)xdnd_position) { - //xdnd position event, reply with an XDND status message //just depending on type of data for now XClientMessageEvent m; @@ -2957,13 +3423,13 @@ void DisplayServerX11::process_events() { XSendEvent(x11_display, event.xclient.data.l[0], False, NoEventMask, (XEvent *)&m); XFlush(x11_display); } else if ((unsigned int)event.xclient.message_type == (unsigned int)xdnd_drop) { - if (requested != None) { xdnd_source_window = event.xclient.data.l[0]; - if (xdnd_version >= 1) + if (xdnd_version >= 1) { XConvertSelection(x11_display, xdnd_selection, requested, XInternAtom(x11_display, "PRIMARY", 0), windows[window_id].x11_window, event.xclient.data.l[2]); - else + } else { 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; @@ -2988,7 +3454,6 @@ void DisplayServerX11::process_events() { XFlush(x11_display); if (do_mouse_warp) { - 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); @@ -3020,7 +3485,6 @@ void DisplayServerX11::_update_context(WindowData &wd) { XClassHint *classHint = XAllocClassHint(); if (classHint) { - CharString name_str; switch (context) { case CONTEXT_EDITOR: @@ -3053,8 +3517,8 @@ void DisplayServerX11::_update_context(WindowData &wd) { XFree(classHint); } } -void DisplayServerX11::set_context(Context p_context) { +void DisplayServerX11::set_context(Context p_context) { _THREAD_SAFE_METHOD_ context = p_context; @@ -3063,6 +3527,7 @@ void DisplayServerX11::set_context(Context p_context) { _update_context(E->get()); } } + void DisplayServerX11::set_native_icon(const String &p_filename) { WARN_PRINT("Native icon not supported by this display server."); } @@ -3136,10 +3601,13 @@ void DisplayServerX11::set_icon(const Ref<Image> &p_icon) { pr += 4; } - XChangeProperty(x11_display, wd.x11_window, net_wm_icon, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)pd.ptr(), pd.size()); + if (net_wm_icon != None) { + XChangeProperty(x11_display, wd.x11_window, net_wm_icon, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)pd.ptr(), pd.size()); + } - if (!g_set_icon_error) + if (!g_set_icon_error) { break; + } } } else { XDeleteProperty(x11_display, wd.x11_window, net_wm_icon); @@ -3163,12 +3631,16 @@ Vector<String> DisplayServerX11::get_rendering_drivers_func() { } DisplayServer *DisplayServerX11::create_func(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { - - return memnew(DisplayServerX11(p_rendering_driver, p_mode, p_flags, p_resolution, r_error)); + DisplayServer *ds = memnew(DisplayServerX11(p_rendering_driver, p_mode, p_flags, p_resolution, r_error)); + if (r_error != OK) { + ds->alert("Your video card driver does not support any of the supported Vulkan versions.\n" + "Please update your drivers or if you have a very old or integrated GPU upgrade it.", + "Unable to initialize Video driver"); + } + return ds; } DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect) { - //Create window long visualMask = VisualScreenMask; @@ -3187,19 +3659,46 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, u unsigned long valuemask = CWBorderPixel | CWColormap | CWEventMask; - WindowID id; + WindowID id = window_id_counter++; + WindowData &wd = windows[id]; + + if ((id != MAIN_WINDOW_ID) && (p_flags & WINDOW_FLAG_BORDERLESS_BIT)) { + wd.menu_type = true; + } + + if (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) { + wd.menu_type = true; + wd.no_focus = true; + } + + // Setup for menu subwindows: + // - override_redirect forces the WM not to interfere with the window, to avoid delays due to + // handling decorations and placement. + // On the other hand, focus changes need to be handled manually when this is set. + // - save_under is a hint for the WM to keep the content of windows behind to avoid repaint. + if (wd.menu_type) { + windowAttributes.override_redirect = True; + windowAttributes.save_under = True; + valuemask |= CWOverrideRedirect | CWSaveUnder; + } + { - 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); - XMapWindow(x11_display, wd.x11_window); + // Enable receiving notification when the window is initialized (MapNotify) + // so the focus can be set at the right time. + if (wd.menu_type && !wd.no_focus) { + XSelectInput(x11_display, wd.x11_window, StructureNotifyMask); + } //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); + if (net_wm_pid != None) { + XChangeProperty(x11_display, wd.x11_window, net_wm_pid, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&pid, 1); + } } long im_event_mask = 0; @@ -3247,9 +3746,14 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, u /* 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); + if (xdnd_aware != None) { + XChangeProperty(x11_display, wd.x11_window, xdnd_aware, XA_ATOM, 32, PropModeReplace, (unsigned char *)&xdnd_version, 1); + } if (xim && xim_style) { + // Block events polling while changing input focus + // because it triggers some event polling internally. + MutexLock mutex_lock(events_mutex); wd.xic = XCreateIC(xim, XNInputStyle, xim_style, XNClientWindow, wd.x11_window, XNFocusWindow, wd.x11_window, (char *)nullptr); if (XGetICValues(wd.xic, XNFilterEvents, &im_event_mask, nullptr) != nullptr) { @@ -3263,96 +3767,40 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, u WARN_PRINT("XCreateIC couldn't create wd.xic"); } } else { - wd.xic = nullptr; WARN_PRINT("XCreateIC couldn't create wd.xic"); } _update_context(wd); - id = window_id_counter++; - - windows[id] = wd; - - { - - if (p_flags & WINDOW_FLAG_RESIZE_DISABLED_BIT) { - - XSizeHints *xsh; - xsh = XAllocSizeHints(); - - 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); - } - - bool make_utility = false; - - 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); + 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); + if (property != None) { XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5); - - make_utility = true; } - if (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) { - make_utility = true; - } - - 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 - - Atom type_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE_UTILITY", False); - Atom wt_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE", False); + } + if (wd.menu_type) { + // Set Utility type to disable fade animations. + Atom type_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE_UTILITY", False); + Atom wt_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE", False); + if (wt_atom != None && type_atom != None) { XChangeProperty(x11_display, wd.x11_window, wt_atom, XA_ATOM, 32, PropModeReplace, (unsigned char *)&type_atom, 1); + } + } else { + Atom type_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE_NORMAL", False); + Atom wt_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE", False); - 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); - - 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); - } - } else { - Atom type_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE_NORMAL", False); - Atom wt_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE", False); - + if (wt_atom != None && type_atom != None) { XChangeProperty(x11_display, wd.x11_window, wt_atom, XA_ATOM, 32, PropModeReplace, (unsigned char *)&type_atom, 1); } } - if (id != MAIN_WINDOW_ID) { - - XSizeHints my_hints = XSizeHints(); - - 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; - - XSetNormalHints(x11_display, wd.x11_window, &my_hints); - XMoveWindow(x11_display, wd.x11_window, p_rect.position.x, p_rect.position.y); - } + _update_size_hints(id); #if defined(VULKAN_ENABLED) if (context_vulkan) { @@ -3370,8 +3818,6 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, u XFree(visualInfo); } - WindowData &wd = windows[id]; - window_set_mode(p_mode, id); //sync size @@ -3391,14 +3837,13 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, u //set cursor if (cursors[current_cursor] != None) { - XDefineCursor(x11_display, wd.x11_window, cursors[current_cursor]); } + return id; } DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { - Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events); r_error = OK; @@ -3407,7 +3852,6 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode mouse_mode = MOUSE_MODE_VISIBLE; for (int i = 0; i < CURSOR_MAX; i++) { - cursors[i] = None; img[i] = nullptr; } @@ -3442,7 +3886,6 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode // 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. @@ -3468,7 +3911,12 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode 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); + // For some arcane reason, NetBSD now ships libXrandr.so.3 while the rest of the world has libXrandr.so.2... + // In case this happens for other X11 platforms in the future, let's give it a try too before failing. + xrandr_handle = dlopen("libXrandr.so.3", RTLD_LAZY); + if (!xrandr_handle) { + 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) { @@ -3519,10 +3967,8 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode 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; } @@ -3557,7 +4003,6 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode #if defined(VULKAN_ENABLED) if (rendering_driver == "vulkan") { - context_vulkan = memnew(VulkanContextX11); if (context_vulkan->initialize() != OK) { memdelete(context_vulkan); @@ -3579,7 +4024,6 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode getenv("PRIMUS_libGL") || getenv("PRIMUS_LOAD_GLOBAL") || getenv("BUMBLEBEE_SOCKET")) { - print_verbose("Optirun/primusrun detected. Skipping GPU detection"); use_prime = 0; } @@ -3591,7 +4035,6 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode for (int i = 0; i < libraries.size(); ++i) { if (FileAccess::exists(libraries[i] + "/libGL.so.1") || FileAccess::exists(libraries[i] + "/libGL.so")) { - print_verbose("Custom libGL override detected. Skipping GPU detection"); use_prime = 0; } @@ -3636,26 +4079,30 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode (screen_get_size(0).width - p_resolution.width) / 2, (screen_get_size(0).height - p_resolution.height) / 2); WindowID main_window = _create_window(p_mode, p_flags, Rect2i(window_position, p_resolution)); + if (main_window == INVALID_WINDOW_ID) { + r_error = ERR_CANT_CREATE; + return; + } for (int i = 0; i < WINDOW_FLAG_MAX; i++) { if (p_flags & (1 << i)) { window_set_flag(WindowFlags(i), true, main_window); } } + show_window(main_window); //create RenderingDevice if used #if defined(VULKAN_ENABLED) if (rendering_driver == "vulkan") { - //temporary rendering_device_vulkan = memnew(RenderingDeviceVulkan); rendering_device_vulkan->initialize(context_vulkan); - RasterizerRD::make_current(); + RendererCompositorRD::make_current(); } #endif /* - rendering_server = memnew(RenderingServerRaster); + rendering_server = memnew(RenderingServerDefault); if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) { rendering_server = memnew(RenderingServerWrapMT(rendering_server, get_render_thread_mode() == RENDER_SEPARATE_THREAD)); } @@ -3691,7 +4138,6 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode } for (int i = 0; i < CURSOR_MAX; i++) { - static const char *cursor_file[] = { "left_ptr", "xterm", @@ -3808,8 +4254,6 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode requested = None; - window_has_focus = true; // Set focus to true at init - /*if (p_desired.layered) { set_window_per_pixel_transparency_enabled(true); }*/ @@ -3822,11 +4266,23 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode } } + events_thread = Thread::create(_poll_events_thread, this); + _update_real_mouse_position(windows[MAIN_WINDOW_ID]); r_error = OK; } + DisplayServerX11::~DisplayServerX11() { + // Send owned clipboard data to clipboard manager before exit. + Window x11_main_window = windows[MAIN_WINDOW_ID].x11_window; + _clipboard_transfer_ownership(XA_PRIMARY, x11_main_window); + _clipboard_transfer_ownership(XInternAtom(x11_display, "CLIPBOARD", 0), x11_main_window); + + events_thread_done = true; + Thread::wait_to_finish(events_thread); + memdelete(events_thread); + events_thread = nullptr; //destroy all windows for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { @@ -3836,35 +4292,40 @@ DisplayServerX11::~DisplayServerX11() { } #endif - if (E->get().xic) { - XDestroyIC(E->get().xic); + WindowData &wd = E->get(); + if (wd.xic) { + XDestroyIC(wd.xic); + wd.xic = nullptr; } - XUnmapWindow(x11_display, E->get().x11_window); - XDestroyWindow(x11_display, E->get().x11_window); + XUnmapWindow(x11_display, wd.x11_window); + XDestroyWindow(x11_display, wd.x11_window); } //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) + if (context_vulkan) { memdelete(context_vulkan); + } } #endif - if (xrandr_handle) + if (xrandr_handle) { dlclose(xrandr_handle); + } for (int i = 0; i < CURSOR_MAX; i++) { - if (cursors[i] != None) + if (cursors[i] != None) { XFreeCursor(x11_display, cursors[i]); - if (img[i] != nullptr) + } + if (img[i] != nullptr) { XcursorImageDestroy(img[i]); + } }; if (xim) { @@ -3872,12 +4333,12 @@ DisplayServerX11::~DisplayServerX11() { } XCloseDisplay(x11_display); - if (xmbstring) + if (xmbstring) { memfree(xmbstring); + } } void DisplayServerX11::register_x11_driver() { - register_create_function("x11", create_func, get_rendering_drivers_func); } diff --git a/platform/linuxbsd/display_server_x11.h b/platform/linuxbsd/display_server_x11.h index b5ea71f72a..7784ba82b5 100644 --- a/platform/linuxbsd/display_server_x11.h +++ b/platform/linuxbsd/display_server_x11.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ @@ -36,14 +36,14 @@ #include "servers/display_server.h" #include "core/input/input.h" - +#include "core/templates/local_vector.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/rendering/rasterizer.h" +#include "servers/rendering/renderer_compositor.h" #include "servers/rendering_server.h" #if defined(OPENGL_ENABLED) @@ -61,27 +61,18 @@ #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; + Bool primary = false; + Bool automatic = false; + int noutput = 0; + int x = 0; + int y = 0; + int width = 0; + int height = 0; + int mwidth = 0; + int mheight = 0; + RROutput *outputs = nullptr; } xrr_monitor_info; #undef CursorShape @@ -133,6 +124,9 @@ class DisplayServerX11 : public DisplayServer { ObjectID instance_id; + bool menu_type = false; + bool no_focus = false; + //better to guess on the fly, given WM can change it //WindowMode mode; bool fullscreen = false; //OS can't exit from this mode @@ -140,6 +134,10 @@ class DisplayServerX11 : public DisplayServer { bool borderless = false; bool resize_disabled = false; Vector2i last_position_before_fs; + bool focused = false; + bool minimized = false; + + unsigned int focus_order = 0; }; Map<WindowID, WindowData> windows; @@ -165,6 +163,8 @@ class DisplayServerX11 : public DisplayServer { uint64_t last_click_ms; int last_click_button_index; uint32_t last_button_state; + bool app_focused = false; + uint64_t time_since_no_focus = 0; struct { int opcode; @@ -194,10 +194,17 @@ class DisplayServerX11 : public DisplayServer { MouseMode mouse_mode; Point2i center; - void _handle_key_event(WindowID p_window, XKeyEvent *p_event, bool p_echo = false); + void _handle_key_event(WindowID p_window, XKeyEvent *p_event, LocalVector<XEvent> &p_events, uint32_t &p_event_index, bool p_echo = false); + + Atom _process_selection_request_target(Atom p_target, Window p_requestor, Atom p_property) const; + void _handle_selection_request_event(XSelectionRequestEvent *p_event) const; - bool minimized; - bool window_has_focus; + String _clipboard_get_impl(Atom p_source, Window x11_window, Atom target) const; + String _clipboard_get(Atom p_source, Window x11_window) const; + void _clipboard_transfer_ownership(Atom p_source, Window x11_window) const; + + //bool minimized; + //bool window_has_focus; bool do_mouse_warp; const char *cursor_theme; @@ -211,7 +218,7 @@ class DisplayServerX11 : public DisplayServer { bool layered_window; String rendering_driver; - bool window_focused; + //bool window_focused; //void set_wm_border(bool p_enabled); void set_wm_fullscreen(bool p_enabled); void set_wm_above(bool p_enabled); @@ -231,6 +238,8 @@ class DisplayServerX11 : public DisplayServer { static Property _read_property(Display *p_display, Window p_window, Atom p_property); void _update_real_mouse_position(const WindowData &wd); + bool _window_maximize_check(WindowID p_window, const char *p_atom_name) const; + void _update_size_hints(WindowID p_window); void _set_wm_fullscreen(WindowID p_window, bool p_enabled); void _set_wm_maximized(WindowID p_window, bool p_enabled); @@ -242,6 +251,19 @@ class DisplayServerX11 : public DisplayServer { static void _dispatch_input_events(const Ref<InputEvent> &p_event); void _dispatch_input_event(const Ref<InputEvent> &p_event); + mutable Mutex events_mutex; + Thread *events_thread = nullptr; + bool events_thread_done = false; + LocalVector<XEvent> polled_events; + static void _poll_events_thread(void *ud); + bool _wait_for_events() const; + void _poll_events(); + + static Bool _predicate_all_events(Display *display, XEvent *event, XPointer arg); + static Bool _predicate_clipboard_selection(Display *display, XEvent *event, XPointer arg); + static Bool _predicate_clipboard_incr(Display *display, XEvent *event, XPointer arg); + static Bool _predicate_clipboard_save_targets(Display *display, XEvent *event, XPointer arg); + protected: void _window_changed(XEvent *event); @@ -272,6 +294,7 @@ public: 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 show_window(WindowID p_id); virtual void delete_sub_window(WindowID p_id); virtual WindowID get_window_at_screen_position(const Point2i &p_position) const; @@ -280,6 +303,8 @@ public: 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_mouse_passthrough(const Vector<Vector2> &p_region, 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); @@ -327,7 +352,11 @@ public: 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 int keyboard_get_layout_count() const; + virtual int keyboard_get_current_layout() const; + virtual void keyboard_set_current_layout(int p_index); + virtual String keyboard_get_layout_language(int p_index) const; + virtual String keyboard_get_layout_name(int p_index) const; virtual void process_events(); diff --git a/platform/linuxbsd/export/export.cpp b/platform/linuxbsd/export/export.cpp index 53e3ce8f85..cb95068314 100644 --- a/platform/linuxbsd/export/export.cpp +++ b/platform/linuxbsd/export/export.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ @@ -38,7 +38,6 @@ static Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size); void register_linuxbsd_exporter() { - Ref<EditorExportPlatformPC> platform; platform.instance(); @@ -62,7 +61,6 @@ void register_linuxbsd_exporter() { } static Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) { - // Patch the header of the "pck" section in the ELF file so that it corresponds to the embedded data FileAccess *f = FileAccess::open(p_path, FileAccess::READ_WRITE); @@ -139,7 +137,6 @@ static Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, bool found = false; for (int i = 0; i < num_sections; ++i) { - int64_t section_header_pos = section_table_pos + i * section_header_size; f->seek(section_header_pos); diff --git a/platform/linuxbsd/export/export.h b/platform/linuxbsd/export/export.h index 5ee81f485e..61e96aa2f6 100644 --- a/platform/linuxbsd/export/export.h +++ b/platform/linuxbsd/export/export.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ diff --git a/platform/linuxbsd/godot_linuxbsd.cpp b/platform/linuxbsd/godot_linuxbsd.cpp index 710ba3ca40..6f5c46b59c 100644 --- a/platform/linuxbsd/godot_linuxbsd.cpp +++ b/platform/linuxbsd/godot_linuxbsd.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ @@ -37,11 +37,13 @@ #include "os_linuxbsd.h" int main(int argc, char *argv[]) { - OS_LinuxBSD os; setlocale(LC_CTYPE, ""); + // We must override main when testing is enabled + TEST_MAIN_OVERRIDE + char *cwd = (char *)malloc(PATH_MAX); ERR_FAIL_COND_V(!cwd, ERR_OUT_OF_MEMORY); char *ret = getcwd(cwd, PATH_MAX); @@ -52,8 +54,9 @@ int main(int argc, char *argv[]) { return 255; } - if (Main::start()) + if (Main::start()) { os.run(); // it is actually the OS that decides how to run + } Main::cleanup(); if (ret) { // Previous getcwd was successful diff --git a/platform/linuxbsd/joypad_linux.cpp b/platform/linuxbsd/joypad_linux.cpp index 5ceea788e0..291ca49585 100644 --- a/platform/linuxbsd/joypad_linux.cpp +++ b/platform/linuxbsd/joypad_linux.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ @@ -49,17 +49,7 @@ static const char *ignore_str = "/dev/input/js"; #endif -JoypadLinux::Joypad::Joypad() { - fd = -1; - dpad = 0; - devpath = ""; - for (int i = 0; i < MAX_ABS; i++) { - abs_info[i] = nullptr; - } -} - JoypadLinux::Joypad::~Joypad() { - for (int i = 0; i < MAX_ABS; i++) { if (abs_info[i]) { memdelete(abs_info[i]); @@ -94,7 +84,6 @@ JoypadLinux::~JoypadLinux() { } void JoypadLinux::joy_thread_func(void *p_user) { - if (p_user) { JoypadLinux *joy = (JoypadLinux *)p_user; joy->run_joypad_thread(); @@ -115,7 +104,6 @@ void JoypadLinux::run_joypad_thread() { #ifdef UDEV_ENABLED void JoypadLinux::enumerate_joypads(udev *p_udev) { - udev_enumerate *enumerate; udev_list_entry *devices, *dev_list_entry; udev_device *dev; @@ -126,13 +114,11 @@ void JoypadLinux::enumerate_joypads(udev *p_udev) { udev_enumerate_scan_devices(enumerate); devices = udev_enumerate_get_list_entry(enumerate); udev_list_entry_foreach(dev_list_entry, devices) { - const char *path = udev_list_entry_get_name(dev_list_entry); dev = udev_device_new_from_syspath(p_udev, path); const char *devnode = udev_device_get_devnode(dev); if (devnode) { - String devnode_str = devnode; if (devnode_str.find(ignore_str) == -1) { MutexLock lock(joy_mutex); @@ -145,7 +131,6 @@ void JoypadLinux::enumerate_joypads(udev *p_udev) { } void JoypadLinux::monitor_joypads(udev *p_udev) { - udev_device *dev = nullptr; udev_monitor *mon = udev_monitor_new_from_netlink(p_udev, "udev"); udev_monitor_filter_add_match_subsystem_devtype(mon, "input", nullptr); @@ -153,7 +138,6 @@ void JoypadLinux::monitor_joypads(udev *p_udev) { int fd = udev_monitor_get_fd(mon); while (!exit_udev) { - fd_set fds; struct timeval tv; int ret; @@ -172,15 +156,12 @@ void JoypadLinux::monitor_joypads(udev *p_udev) { dev = udev_monitor_receive_device(mon); if (dev && udev_device_get_devnode(dev) != 0) { - MutexLock lock(joy_mutex); String action = udev_device_get_action(dev); const char *devnode = udev_device_get_devnode(dev); if (devnode) { - String devnode_str = devnode; if (devnode_str.find(ignore_str) == -1) { - if (action == "add") open_joypad(devnode); else if (String(action) == "remove") @@ -198,7 +179,6 @@ void JoypadLinux::monitor_joypads(udev *p_udev) { #endif void JoypadLinux::monitor_joypads() { - while (!exit_udev) { { MutexLock lock(joy_mutex); @@ -216,9 +196,7 @@ void JoypadLinux::monitor_joypads() { } int JoypadLinux::get_joy_from_path(String p_path) const { - for (int i = 0; i < JOYPADS_MAX; i++) { - if (joypads[i].devpath == p_path) { return i; } @@ -229,17 +207,16 @@ int JoypadLinux::get_joy_from_path(String p_path) const { void JoypadLinux::close_joypad(int p_id) { if (p_id == -1) { for (int i = 0; i < JOYPADS_MAX; i++) { - close_joypad(i); }; return; - } else if (p_id < 0) + } else if (p_id < 0) { return; + } Joypad &joy = joypads[p_id]; if (joy.fd != -1) { - close(joy.fd); joy.fd = -1; attached_devices.remove(attached_devices.find(joy.devpath)); @@ -248,7 +225,6 @@ void JoypadLinux::close_joypad(int p_id) { } static String _hex_str(uint8_t p_byte) { - static const char *dict = "0123456789abcdef"; char ret[3]; ret[2] = 0; @@ -260,7 +236,6 @@ static String _hex_str(uint8_t p_byte) { } void JoypadLinux::setup_joypad_properties(int p_id) { - Joypad *joy = &joypads[p_id]; unsigned long keybit[NBITS(KEY_MAX)] = { 0 }; @@ -274,16 +249,12 @@ void JoypadLinux::setup_joypad_properties(int p_id) { return; } for (int i = BTN_JOYSTICK; i < KEY_MAX; ++i) { - if (test_bit(i, keybit)) { - joy->key_map[i] = num_buttons++; } } for (int i = BTN_MISC; i < BTN_JOYSTICK; ++i) { - if (test_bit(i, keybit)) { - joy->key_map[i] = num_buttons++; } } @@ -294,7 +265,6 @@ void JoypadLinux::setup_joypad_properties(int p_id) { continue; } if (test_bit(i, absbit)) { - joy->abs_map[i] = num_axes++; joy->abs_info[i] = memnew(input_absinfo); if (ioctl(joy->fd, EVIOCGABS(i), joy->abs_info[i]) < 0) { @@ -315,11 +285,9 @@ void JoypadLinux::setup_joypad_properties(int p_id) { } void JoypadLinux::open_joypad(const char *p_path) { - int joy_num = input->get_unused_joy_id(); int fd = open(p_path, O_RDWR | O_NONBLOCK); if (fd != -1 && joy_num != -1) { - unsigned long evbit[NBITS(EV_MAX)] = { 0 }; unsigned long keybit[NBITS(KEY_MAX)] = { 0 }; unsigned long absbit[NBITS(ABS_MAX)] = { 0 }; @@ -334,16 +302,9 @@ void JoypadLinux::open_joypad(const char *p_path) { return; } - //check if the device supports basic gamepad events, prevents certain keyboards from - //being detected as joypads + // Check if the device supports basic gamepad events if (!(test_bit(EV_KEY, evbit) && test_bit(EV_ABS, evbit) && - (test_bit(ABS_X, absbit) || test_bit(ABS_Y, absbit) || test_bit(ABS_HAT0X, absbit) || - test_bit(ABS_GAS, absbit) || test_bit(ABS_RUDDER, absbit)) && - (test_bit(BTN_A, keybit) || test_bit(BTN_THUMBL, keybit) || - test_bit(BTN_TRIGGER, keybit) || test_bit(BTN_1, keybit))) && - !(test_bit(EV_ABS, evbit) && - test_bit(ABS_X, absbit) && test_bit(ABS_Y, absbit) && - test_bit(ABS_RX, absbit) && test_bit(ABS_RY, absbit))) { + test_bit(ABS_X, absbit) && test_bit(ABS_Y, absbit))) { close(fd); return; } @@ -369,7 +330,6 @@ void JoypadLinux::open_joypad(const char *p_path) { setup_joypad_properties(joy_num); sprintf(uid, "%04x%04x", BSWAP16(inpid.bustype), 0); if (inpid.vendor && inpid.product && inpid.version) { - uint16_t vendor = BSWAP16(inpid.vendor); uint16_t product = BSWAP16(inpid.product); uint16_t version = BSWAP16(inpid.version); @@ -380,7 +340,6 @@ void JoypadLinux::open_joypad(const char *p_path) { String uidname = uid; int uidlen = MIN(name.length(), 11); for (int i = 0; i < uidlen; i++) { - uidname = uidname + _hex_str(name[i]); } uidname += "00"; @@ -437,7 +396,6 @@ void JoypadLinux::joypad_vibration_stop(int p_id, uint64_t p_timestamp) { } Input::JoyAxis JoypadLinux::axis_correct(const input_absinfo *p_abs, int p_value) const { - int min = p_abs->minimum; int max = p_abs->maximum; Input::JoyAxis jx; @@ -457,13 +415,13 @@ Input::JoyAxis JoypadLinux::axis_correct(const input_absinfo *p_abs, int p_value } void JoypadLinux::process_joypads() { - if (joy_mutex.try_lock() != OK) { return; } for (int i = 0; i < JOYPADS_MAX; i++) { - - if (joypads[i].fd == -1) continue; + if (joypads[i].fd == -1) { + continue; + } input_event events[32]; Joypad *joy = &joypads[i]; @@ -473,13 +431,13 @@ void JoypadLinux::process_joypads() { while ((len = read(joy->fd, events, (sizeof events))) > 0) { len /= sizeof(events[0]); for (int j = 0; j < len; j++) { - input_event &ev = events[j]; // ev may be tainted and out of MAX_KEY range, which will cause // joy->key_map[ev.code] to crash - if (ev.code >= MAX_KEY) + if (ev.code >= MAX_KEY) { return; + } switch (ev.type) { case EV_KEY: @@ -491,31 +449,36 @@ void JoypadLinux::process_joypads() { switch (ev.code) { case ABS_HAT0X: if (ev.value != 0) { - if (ev.value < 0) - joy->dpad |= Input::HAT_MASK_LEFT; - else - joy->dpad |= Input::HAT_MASK_RIGHT; - } else + if (ev.value < 0) { + joy->dpad = (joy->dpad | Input::HAT_MASK_LEFT) & ~Input::HAT_MASK_RIGHT; + } else { + joy->dpad = (joy->dpad | Input::HAT_MASK_RIGHT) & ~Input::HAT_MASK_LEFT; + } + } else { joy->dpad &= ~(Input::HAT_MASK_LEFT | Input::HAT_MASK_RIGHT); + } input->joy_hat(i, joy->dpad); break; case ABS_HAT0Y: if (ev.value != 0) { - if (ev.value < 0) - joy->dpad |= Input::HAT_MASK_UP; - else - joy->dpad |= Input::HAT_MASK_DOWN; - } else + if (ev.value < 0) { + joy->dpad = (joy->dpad | Input::HAT_MASK_UP) & ~Input::HAT_MASK_DOWN; + } else { + joy->dpad = (joy->dpad | Input::HAT_MASK_DOWN) & ~Input::HAT_MASK_UP; + } + } else { joy->dpad &= ~(Input::HAT_MASK_UP | Input::HAT_MASK_DOWN); + } input->joy_hat(i, joy->dpad); break; default: - if (ev.code >= MAX_ABS) + if (ev.code >= MAX_ABS) { return; + } if (joy->abs_map[ev.code] != -1 && joy->abs_info[ev.code]) { Input::JoyAxis value = axis_correct(joy->abs_info[ev.code], ev.value); joy->curr_axis[joy->abs_map[ev.code]] = value; diff --git a/platform/linuxbsd/joypad_linux.h b/platform/linuxbsd/joypad_linux.h index 0d175193a5..20d30b510c 100644 --- a/platform/linuxbsd/joypad_linux.h +++ b/platform/linuxbsd/joypad_linux.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ @@ -56,17 +56,16 @@ private: Input::JoyAxis curr_axis[MAX_ABS]; int key_map[MAX_KEY]; int abs_map[MAX_ABS]; - int dpad; - int fd; + int dpad = 0; + int fd = -1; String devpath; - input_absinfo *abs_info[MAX_ABS]; + input_absinfo *abs_info[MAX_ABS] = {}; - bool force_feedback; - int ff_effect_id; - uint64_t ff_effect_timestamp; + bool force_feedback = false; + int ff_effect_id = 0; + uint64_t ff_effect_timestamp = 0; - Joypad(); ~Joypad(); void reset(); }; diff --git a/platform/linuxbsd/key_mapping_x11.cpp b/platform/linuxbsd/key_mapping_x11.cpp index 78bd2b71a0..f9f612fa74 100644 --- a/platform/linuxbsd/key_mapping_x11.cpp +++ b/platform/linuxbsd/key_mapping_x11.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ @@ -33,7 +33,6 @@ /***** SCAN CODE CONVERSION ******/ struct _XTranslatePair { - KeySym keysym; unsigned int keycode; }; @@ -181,13 +180,11 @@ static _XTranslatePair _xkeysym_to_keycode[] = { }; struct _TranslatePair { - unsigned int keysym; unsigned int keycode; }; static _TranslatePair _scancode_to_keycode[] = { - { KEY_ESCAPE, 0x09 }, { KEY_1, 0x0A }, { KEY_2, 0x0B }, @@ -301,10 +298,8 @@ static _TranslatePair _scancode_to_keycode[] = { }; unsigned int KeyMappingX11::get_scancode(unsigned int p_code) { - unsigned int keycode = KEY_UNKNOWN; for (int i = 0; _scancode_to_keycode[i].keysym != KEY_UNKNOWN; i++) { - if (_scancode_to_keycode[i].keycode == p_code) { keycode = _scancode_to_keycode[i].keysym; break; @@ -315,34 +310,34 @@ unsigned int KeyMappingX11::get_scancode(unsigned int p_code) { } unsigned int KeyMappingX11::get_keycode(KeySym p_keysym) { - // kinda bruteforce.. could optimize. - if (p_keysym < 0x100) // Latin 1, maps 1-1 + if (p_keysym < 0x100) { // Latin 1, maps 1-1 return p_keysym; + } // look for special key for (int idx = 0; _xkeysym_to_keycode[idx].keysym != 0; idx++) { - - if (_xkeysym_to_keycode[idx].keysym == p_keysym) + if (_xkeysym_to_keycode[idx].keysym == p_keysym) { return _xkeysym_to_keycode[idx].keycode; + } } return 0; } KeySym KeyMappingX11::get_keysym(unsigned int p_code) { - // kinda bruteforce.. could optimize. - if (p_code < 0x100) // Latin 1, maps 1-1 + if (p_code < 0x100) { // Latin 1, maps 1-1 return p_code; + } // look for special key for (int idx = 0; _xkeysym_to_keycode[idx].keysym != 0; idx++) { - - if (_xkeysym_to_keycode[idx].keycode == p_code) + if (_xkeysym_to_keycode[idx].keycode == p_code) { return _xkeysym_to_keycode[idx].keysym; + } } return 0; @@ -353,13 +348,11 @@ KeySym KeyMappingX11::get_keysym(unsigned int p_code) { // Tables taken from FOX toolkit struct _XTranslateUnicodePair { - KeySym keysym; unsigned int unicode; }; enum { - _KEYSYM_MAX = 759 }; @@ -1125,43 +1118,46 @@ static _XTranslateUnicodePair _xkeysym_to_unicode[_KEYSYM_MAX] = { }; unsigned int KeyMappingX11::get_unicode_from_keysym(KeySym p_keysym) { - /* Latin-1 */ - if (p_keysym >= 0x20 && p_keysym <= 0x7e) + if (p_keysym >= 0x20 && p_keysym <= 0x7e) { return p_keysym; - if (p_keysym >= 0xa0 && p_keysym <= 0xff) + } + if (p_keysym >= 0xa0 && p_keysym <= 0xff) { return p_keysym; + } // keypad to latin1 is easy - if (p_keysym >= 0xffaa && p_keysym <= 0xffb9) + if (p_keysym >= 0xffaa && p_keysym <= 0xffb9) { return p_keysym - 0xff80; + } /* Unicode (may be present)*/ - if ((p_keysym & 0xff000000) == 0x01000000) + if ((p_keysym & 0xff000000) == 0x01000000) { return p_keysym & 0x00ffffff; + } int middle, low = 0, high = _KEYSYM_MAX - 1; do { middle = (high + low) / 2; - if (_xkeysym_to_unicode[middle].keysym == p_keysym) + if (_xkeysym_to_unicode[middle].keysym == p_keysym) { return _xkeysym_to_unicode[middle].unicode; - if (_xkeysym_to_unicode[middle].keysym <= p_keysym) + } + if (_xkeysym_to_unicode[middle].keysym <= p_keysym) { low = middle + 1; - else + } else { high = middle - 1; + } } while (high >= low); return 0; } struct _XTranslateUnicodePairReverse { - unsigned int unicode; KeySym keysym; }; enum { - _UNICODE_MAX = 750 }; @@ -1919,24 +1915,27 @@ static _XTranslateUnicodePairReverse _unicode_to_xkeysym[_UNICODE_MAX] = { }; KeySym KeyMappingX11::get_keysym_from_unicode(unsigned int p_unicode) { - /* Latin 1 */ - if (p_unicode >= 0x20 && p_unicode <= 0x7e) + if (p_unicode >= 0x20 && p_unicode <= 0x7e) { return p_unicode; + } - if (p_unicode >= 0xa0 && p_unicode <= 0xff) + if (p_unicode >= 0xa0 && p_unicode <= 0xff) { return p_unicode; + } int middle, low = 0, high = _UNICODE_MAX - 1; do { middle = (high + low) / 2; - if (_unicode_to_xkeysym[middle].keysym == p_unicode) + if (_unicode_to_xkeysym[middle].keysym == p_unicode) { return _unicode_to_xkeysym[middle].keysym; - if (_unicode_to_xkeysym[middle].keysym <= p_unicode) + } + if (_unicode_to_xkeysym[middle].keysym <= p_unicode) { low = middle + 1; - else + } else { high = middle - 1; + } } while (high >= low); // if not found, let's hope X understands it as unicode diff --git a/platform/linuxbsd/key_mapping_x11.h b/platform/linuxbsd/key_mapping_x11.h index 10db43bcc4..163a8e21db 100644 --- a/platform/linuxbsd/key_mapping_x11.h +++ b/platform/linuxbsd/key_mapping_x11.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ @@ -41,7 +41,7 @@ #include "core/os/keyboard.h" class KeyMappingX11 { - KeyMappingX11(){}; + KeyMappingX11() {} public: static unsigned int get_keycode(KeySym p_keysym); diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp index 7b76f7394b..44b3930d6c 100644 --- a/platform/linuxbsd/os_linuxbsd.cpp +++ b/platform/linuxbsd/os_linuxbsd.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ @@ -31,8 +31,11 @@ #include "os_linuxbsd.h" #include "core/os/dir_access.h" -#include "core/print_string.h" -#include "errno.h" +#include "main/main.h" + +#ifdef X11_ENABLED +#include "display_server_x11.h" +#endif #ifdef HAVE_MNTENT #include <mntent.h> @@ -48,32 +51,23 @@ #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(Input::get_singleton())); #endif } String OS_LinuxBSD::get_unique_id() const { - static String machine_id; - if (machine_id.empty()) { + if (machine_id.is_empty()) { if (FileAccess *f = FileAccess::open("/etc/machine-id", FileAccess::READ)) { - while (machine_id.empty() && !f->eof_reached()) { + while (machine_id.is_empty() && !f->eof_reached()) { machine_id = f->get_line().strip_edges(); } f->close(); @@ -84,9 +78,9 @@ String OS_LinuxBSD::get_unique_id() const { } void OS_LinuxBSD::finalize() { - - if (main_loop) + if (main_loop) { memdelete(main_loop); + } main_loop = nullptr; #ifdef ALSAMIDI_ENABLED @@ -94,29 +88,28 @@ void OS_LinuxBSD::finalize() { #endif #ifdef JOYDEV_ENABLED - memdelete(joypad); + if (joypad) { + memdelete(joypad); + } #endif } MainLoop *OS_LinuxBSD::get_main_loop() const { - return main_loop; } void OS_LinuxBSD::delete_main_loop() { - - if (main_loop) + if (main_loop) { memdelete(main_loop); + } main_loop = nullptr; } 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__) @@ -129,27 +122,47 @@ String OS_LinuxBSD::get_name() const { } Error OS_LinuxBSD::shell_open(String p_uri) { - Error ok; + int err_code; List<String> args; args.push_back(p_uri); - ok = execute("xdg-open", args, false); - if (ok == OK) + + // Agnostic + ok = execute("xdg-open", args, nullptr, &err_code); + if (ok == OK && !err_code) { return OK; - ok = execute("gnome-open", args, false); - if (ok == OK) + } else if (err_code == 2) { + return ERR_FILE_NOT_FOUND; + } + // GNOME + args.push_front("open"); // The command is `gio open`, so we need to add it to args + ok = execute("gio", args, nullptr, &err_code); + if (ok == OK && !err_code) { return OK; - ok = execute("kde-open", args, false); - return ok; + } else if (err_code == 2) { + return ERR_FILE_NOT_FOUND; + } + args.pop_front(); + ok = execute("gvfs-open", args, nullptr, &err_code); + if (ok == OK && !err_code) { + return OK; + } else if (err_code == 2) { + return ERR_FILE_NOT_FOUND; + } + // KDE + ok = execute("kde-open5", args, nullptr, &err_code); + if (ok == OK && !err_code) { + return OK; + } + ok = execute("kde-open", args, nullptr, &err_code); + return !err_code ? ok : FAILED; } 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")) { @@ -160,7 +173,6 @@ String OS_LinuxBSD::get_config_path() const { } String OS_LinuxBSD::get_data_path() const { - if (has_environment("XDG_DATA_HOME")) { return get_environment("XDG_DATA_HOME"); } else if (has_environment("HOME")) { @@ -171,7 +183,6 @@ String OS_LinuxBSD::get_data_path() const { } String OS_LinuxBSD::get_cache_path() const { - if (has_environment("XDG_CACHE_HOME")) { return get_environment("XDG_CACHE_HOME"); } else if (has_environment("HOME")) { @@ -182,46 +193,37 @@ String OS_LinuxBSD::get_cache_path() const { } 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; @@ -230,20 +232,21 @@ String OS_LinuxBSD::get_system_dir(SystemDir p_dir) const { String pipe; List<String> arg; arg.push_back(xdgparam); - Error err = const_cast<OS_LinuxBSD *>(this)->execute("xdg-user-dir", arg, true, nullptr, &pipe); - if (err != OK) + Error err = const_cast<OS_LinuxBSD *>(this)->execute("xdg-user-dir", arg, &pipe); + if (err != OK) { return "."; + } return pipe.strip_edges(); } void OS_LinuxBSD::run() { - force_quit = false; - if (!main_loop) + if (!main_loop) { return; + } - main_loop->init(); + main_loop->initialize(); //uint64_t last_ticks=get_ticks_usec(); @@ -251,16 +254,16 @@ void OS_LinuxBSD::run() { //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()) + if (Main::iteration()) { break; + } }; - main_loop->finish(); + main_loop->finalize(); } void OS_LinuxBSD::disable_crash_handler() { @@ -300,70 +303,151 @@ static String get_mountpoint(const String &p_path) { } Error OS_LinuxBSD::move_to_trash(const String &p_path) { - String trash_can = ""; + int err_code; + List<String> args; + args.push_back(p_path); + args.push_front("trash"); // The command is `gio trash <file_name>` so we need to add it to args. + Error result = execute("gio", args, nullptr, &err_code); // For GNOME based machines. + if (result == OK && !err_code) { + return OK; + } else if (err_code == 2) { + return ERR_FILE_NOT_FOUND; + } + + args.pop_front(); + args.push_front("move"); + args.push_back("trash:/"); // The command is `kioclient5 move <file_name> trash:/`. + result = execute("kioclient5", args, nullptr, &err_code); // For KDE based machines. + if (result == OK && !err_code) { + return OK; + } else if (err_code == 2) { + return ERR_FILE_NOT_FOUND; + } + + args.pop_front(); + args.pop_back(); + result = execute("gvfs-trash", args, nullptr, &err_code); // For older Linux machines. + if (result == OK && !err_code) { + return OK; + } else if (err_code == 2) { + return ERR_FILE_NOT_FOUND; + } + + // If the commands `kioclient5`, `gio` or `gvfs-trash` don't exist on the system we do it manually. + String trash_path = ""; String mnt = get_mountpoint(p_path); - // If there is a directory "[Mountpoint]/.Trash-[UID]/files", use it as the trash can. + // If there is a directory "[Mountpoint]/.Trash-[UID], use it as the trash can. if (mnt != "") { - String path(mnt + "/.Trash-" + itos(getuid()) + "/files"); + String path(mnt + "/.Trash-" + itos(getuid())); struct stat s; if (!stat(path.utf8().get_data(), &s)) { - trash_can = path; + trash_path = path; } } - // Otherwise, if ${XDG_DATA_HOME} is defined, use "${XDG_DATA_HOME}/Trash/files" as the trash can. - if (trash_can == "") { + // Otherwise, if ${XDG_DATA_HOME} is defined, use "${XDG_DATA_HOME}/Trash" as the trash can. + if (trash_path == "") { char *dhome = getenv("XDG_DATA_HOME"); if (dhome) { - trash_can = String(dhome) + "/Trash/files"; + trash_path = String(dhome) + "/Trash"; } } - // Otherwise, if ${HOME} is defined, use "${HOME}/.local/share/Trash/files" as the trash can. - if (trash_can == "") { + // Otherwise, if ${HOME} is defined, use "${HOME}/.local/share/Trash" as the trash can. + if (trash_path == "") { char *home = getenv("HOME"); if (home) { - trash_can = String(home) + "/.local/share/Trash/files"; + trash_path = String(home) + "/.local/share/Trash"; } } // 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; - } + ERR_FAIL_COND_V_MSG(trash_path == "", FAILED, "Could not determine the trash can location"); // 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); + { + DirAccess *dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + Error err = dir_access->make_dir_recursive(trash_path); + + // Issue an error if trash can is not created proprely. + ERR_FAIL_COND_V_MSG(err != OK, err, "Could not create the trash path \"" + trash_path + "\""); + err = dir_access->make_dir_recursive(trash_path + "/files"); + ERR_FAIL_COND_V_MSG(err != OK, err, "Could not create the trash path \"" + trash_path + "\"/files"); + err = dir_access->make_dir_recursive(trash_path + "/info"); + ERR_FAIL_COND_V_MSG(err != OK, err, "Could not create the trash path \"" + trash_path + "\"/info"); + 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 we check that we don't exceed our file name length limit. + // If the file name is too long trim it so we can add the identifying number and ".trashinfo". + // Assumes that the file name length limit is 255 characters. + String file_name = basename(p_path.utf8().get_data()); + if (file_name.length() > 240) { + file_name = file_name.substr(0, file_name.length() - 15); + } + + String dest_path = trash_path + "/files/" + file_name; + struct stat buff; + int id_number = 0; + String fn = file_name; + + // Checks if a resource with the same name already exist in the trash can, + // if there is, add an identifying number to our resource's name. + while (stat(dest_path.utf8().get_data(), &buff) == 0) { + id_number++; + + // Added a limit to check for identically named files already on the trash can + // if there are too many it could make the editor unresponsive. + ERR_FAIL_COND_V_MSG(id_number > 99, FAILED, "Too many identically named resources already in the trash can."); + fn = file_name + "." + itos(id_number); + dest_path = trash_path + "/files/" + fn; + } + file_name = fn; + + // Generates the .trashinfo file + OS::Date date = OS::get_singleton()->get_date(false); + OS::Time time = OS::get_singleton()->get_time(false); + String timestamp = vformat("%04d-%02d-%02dT%02d:%02d:", date.year, date.month, date.day, time.hour, time.min); + timestamp = vformat("%s%02d", timestamp, time.sec); // vformat only supports up to 6 arguments. + String trash_info = "[Trash Info]\nPath=" + p_path.http_escape() + "\nDeletionDate=" + timestamp + "\n"; + { + Error err; + FileAccess *file = FileAccess::open(trash_path + "/info/" + file_name + ".trashinfo", FileAccess::WRITE, &err); + ERR_FAIL_COND_V_MSG(err != OK, err, "Can't create trashinfo file:" + trash_path + "/info/" + file_name + ".trashinfo"); + file->store_string(trash_info); + file->close(); + + // Rename our resource before moving it to the trash can. + DirAccess *dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + err = dir_access->rename(p_path, p_path.get_base_dir() + "/" + file_name); + ERR_FAIL_COND_V_MSG(err != OK, err, "Can't rename file \"" + p_path + "\""); + memdelete(dir_access); } - // The trash can is successfully created, now move the given resource to it. + // Move the given resource to the trash can. // 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, nullptr, nullptr, &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; + mv_args.push_back(p_path.get_base_dir() + "/" + file_name); + mv_args.push_back(trash_path + "/files"); + { + int retval; + Error err = execute("mv", mv_args, nullptr, &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_path + "/files\""); + DirAccess *dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + err = dir_access->rename(p_path.get_base_dir() + "/" + file_name, p_path); + memdelete(dir_access); + ERR_FAIL_COND_V_MSG(err != OK, err, "Could not rename " + p_path.get_base_dir() + "/" + file_name + " back to its original name:" + p_path); + return FAILED; + } } - return OK; } OS_LinuxBSD::OS_LinuxBSD() { - main_loop = nullptr; force_quit = false; diff --git a/platform/linuxbsd/os_linuxbsd.h b/platform/linuxbsd/os_linuxbsd.h index 391f29e8a3..b6cf93c551 100644 --- a/platform/linuxbsd/os_linuxbsd.h +++ b/platform/linuxbsd/os_linuxbsd.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ @@ -39,17 +39,16 @@ #include "drivers/unix/os_unix.h" #include "joypad_linux.h" #include "servers/audio_server.h" -#include "servers/rendering/rasterizer.h" +#include "servers/rendering/renderer_compositor.h" #include "servers/rendering_server.h" class OS_LinuxBSD : public OS_Unix { - - virtual void delete_main_loop(); + virtual void delete_main_loop() override; bool force_quit; #ifdef JOYDEV_ENABLED - JoypadLinux *joypad; + JoypadLinux *joypad = nullptr; #endif #ifdef ALSA_ENABLED @@ -69,36 +68,36 @@ class OS_LinuxBSD : public OS_Unix { MainLoop *main_loop; protected: - virtual void initialize(); - virtual void finalize(); + virtual void initialize() override; + virtual void finalize() override; - virtual void initialize_joypads(); + virtual void initialize_joypads() override; - virtual void set_main_loop(MainLoop *p_main_loop); + virtual void set_main_loop(MainLoop *p_main_loop) override; public: - virtual String get_name() const; + virtual String get_name() const override; - virtual MainLoop *get_main_loop() const; + virtual MainLoop *get_main_loop() const override; - virtual String get_config_path() const; - virtual String get_data_path() const; - virtual String get_cache_path() const; + virtual String get_config_path() const override; + virtual String get_data_path() const override; + virtual String get_cache_path() const override; - virtual String get_system_dir(SystemDir p_dir) const; + virtual String get_system_dir(SystemDir p_dir) const override; - virtual Error shell_open(String p_uri); + virtual Error shell_open(String p_uri) override; - virtual String get_unique_id() const; + virtual String get_unique_id() const override; - virtual bool _check_internal_feature_support(const String &p_feature); + virtual bool _check_internal_feature_support(const String &p_feature) override; void run(); - void disable_crash_handler(); - bool is_disable_crash_handler() const; + virtual void disable_crash_handler() override; + virtual bool is_disable_crash_handler() const override; - virtual Error move_to_trash(const String &p_path); + virtual Error move_to_trash(const String &p_path) override; OS_LinuxBSD(); }; diff --git a/platform/linuxbsd/platform_config.h b/platform/linuxbsd/platform_config.h index ac30519132..3195d08935 100644 --- a/platform/linuxbsd/platform_config.h +++ b/platform/linuxbsd/platform_config.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ @@ -31,9 +31,15 @@ #ifdef __linux__ #include <alloca.h> #endif -#if defined(__FreeBSD__) || defined(__OpenBSD__) -#include <stdlib.h> + +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) +#include <stdlib.h> // alloca +// FreeBSD and OpenBSD use pthread_set_name_np, while other platforms, +// include NetBSD, use pthread_setname_np. NetBSD's version however requires +// a different format, we handle this directly in thread_posix. +#ifdef __NetBSD__ +#define PTHREAD_NETBSD_SET_NAME +#else #define PTHREAD_BSD_SET_NAME #endif - -#define GLES2_INCLUDE_H "thirdparty/glad/glad/glad.h" +#endif diff --git a/platform/linuxbsd/vulkan_context_x11.cpp b/platform/linuxbsd/vulkan_context_x11.cpp index 1798a7026e..021db630e0 100644 --- a/platform/linuxbsd/vulkan_context_x11.cpp +++ b/platform/linuxbsd/vulkan_context_x11.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ @@ -36,7 +36,6 @@ const char *VulkanContextX11::_get_platform_surface_extension() const { } 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; createInfo.pNext = nullptr; diff --git a/platform/linuxbsd/vulkan_context_x11.h b/platform/linuxbsd/vulkan_context_x11.h index 6e144ab2d9..26472444ad 100644 --- a/platform/linuxbsd/vulkan_context_x11.h +++ b/platform/linuxbsd/vulkan_context_x11.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ @@ -35,7 +35,6 @@ #include <X11/Xlib.h> class VulkanContextX11 : public VulkanContext { - virtual const char *_get_platform_surface_extension() const; public: |