From 593c571d6a1b8922a24ac383fde43d6573b0dd7a Mon Sep 17 00:00:00 2001 From: Riteo Date: Wed, 2 Nov 2022 20:11:55 +0100 Subject: linuxbsd: move all X11 stuff in its own directory This allows implementing `DisplayServer`s like Wayland without making a mess in the source tree. --- platform/linuxbsd/SCsub | 10 +- platform/linuxbsd/detect_prime_x11.cpp | 248 -- platform/linuxbsd/detect_prime_x11.h | 40 - platform/linuxbsd/display_server_x11.cpp | 5196 -------------------------- platform/linuxbsd/display_server_x11.h | 458 --- platform/linuxbsd/gl_manager_x11.cpp | 394 -- platform/linuxbsd/gl_manager_x11.h | 126 - platform/linuxbsd/key_mapping_x11.cpp | 2001 ---------- platform/linuxbsd/key_mapping_x11.h | 55 - platform/linuxbsd/os_linuxbsd.cpp | 2 +- platform/linuxbsd/vulkan_context_x11.cpp | 65 - platform/linuxbsd/vulkan_context_x11.h | 51 - platform/linuxbsd/x11/SCsub | 21 + platform/linuxbsd/x11/detect_prime_x11.cpp | 248 ++ platform/linuxbsd/x11/detect_prime_x11.h | 40 + platform/linuxbsd/x11/display_server_x11.cpp | 5196 ++++++++++++++++++++++++++ platform/linuxbsd/x11/display_server_x11.h | 458 +++ platform/linuxbsd/x11/gl_manager_x11.cpp | 394 ++ platform/linuxbsd/x11/gl_manager_x11.h | 126 + platform/linuxbsd/x11/key_mapping_x11.cpp | 2001 ++++++++++ platform/linuxbsd/x11/key_mapping_x11.h | 55 + platform/linuxbsd/x11/vulkan_context_x11.cpp | 65 + platform/linuxbsd/x11/vulkan_context_x11.h | 51 + 23 files changed, 8657 insertions(+), 8644 deletions(-) delete mode 100644 platform/linuxbsd/detect_prime_x11.cpp delete mode 100644 platform/linuxbsd/detect_prime_x11.h delete mode 100644 platform/linuxbsd/display_server_x11.cpp delete mode 100644 platform/linuxbsd/display_server_x11.h delete mode 100644 platform/linuxbsd/gl_manager_x11.cpp delete mode 100644 platform/linuxbsd/gl_manager_x11.h delete mode 100644 platform/linuxbsd/key_mapping_x11.cpp delete mode 100644 platform/linuxbsd/key_mapping_x11.h delete mode 100644 platform/linuxbsd/vulkan_context_x11.cpp delete mode 100644 platform/linuxbsd/vulkan_context_x11.h create mode 100644 platform/linuxbsd/x11/SCsub create mode 100644 platform/linuxbsd/x11/detect_prime_x11.cpp create mode 100644 platform/linuxbsd/x11/detect_prime_x11.h create mode 100644 platform/linuxbsd/x11/display_server_x11.cpp create mode 100644 platform/linuxbsd/x11/display_server_x11.h create mode 100644 platform/linuxbsd/x11/gl_manager_x11.cpp create mode 100644 platform/linuxbsd/x11/gl_manager_x11.h create mode 100644 platform/linuxbsd/x11/key_mapping_x11.cpp create mode 100644 platform/linuxbsd/x11/key_mapping_x11.h create mode 100644 platform/linuxbsd/x11/vulkan_context_x11.cpp create mode 100644 platform/linuxbsd/x11/vulkan_context_x11.h (limited to 'platform') diff --git a/platform/linuxbsd/SCsub b/platform/linuxbsd/SCsub index 91d45627b9..fcd739cdc9 100644 --- a/platform/linuxbsd/SCsub +++ b/platform/linuxbsd/SCsub @@ -14,15 +14,7 @@ common_linuxbsd = [ ] if env["x11"]: - common_linuxbsd += [ - "gl_manager_x11.cpp", - "detect_prime_x11.cpp", - "display_server_x11.cpp", - "key_mapping_x11.cpp", - ] - - if env["vulkan"]: - common_linuxbsd.append("vulkan_context_x11.cpp") + common_linuxbsd += SConscript("x11/SCsub") if env["speechd"]: common_linuxbsd.append(["speechd-so_wrap.c", "tts_linux.cpp"]) diff --git a/platform/linuxbsd/detect_prime_x11.cpp b/platform/linuxbsd/detect_prime_x11.cpp deleted file mode 100644 index fb833ab5e6..0000000000 --- a/platform/linuxbsd/detect_prime_x11.cpp +++ /dev/null @@ -1,248 +0,0 @@ -/*************************************************************************/ -/* detect_prime_x11.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifdef X11_ENABLED -#if defined(GLES3_ENABLED) - -#include "detect_prime_x11.h" - -#include "core/string/print_string.h" -#include "core/string/ustring.h" - -#include - -#include -#include -#include -#include - -#include - -#include -#include -#include - -#define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091 -#define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092 - -typedef GLXContext (*GLXCREATECONTEXTATTRIBSARBPROC)(Display *, GLXFBConfig, GLXContext, Bool, const int *); - -struct vendor { - const char *glxvendor = nullptr; - int priority = 0; -}; - -vendor vendormap[] = { - { "Advanced Micro Devices, Inc.", 30 }, - { "AMD", 30 }, - { "NVIDIA Corporation", 30 }, - { "X.Org", 30 }, - { "Intel Open Source Technology Center", 20 }, - { "Intel", 20 }, - { "nouveau", 10 }, - { "Mesa Project", 0 }, - { nullptr, 0 } -}; - -// Runs inside a child. Exiting will not quit the engine. -void create_context() { - Display *x11_display = XOpenDisplay(nullptr); - Window x11_window; - GLXContext glx_context; - - GLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB = (GLXCREATECONTEXTATTRIBSARBPROC)glXGetProcAddress((const GLubyte *)"glXCreateContextAttribsARB"); - - static int visual_attribs[] = { - GLX_RENDER_TYPE, GLX_RGBA_BIT, - GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, - GLX_DOUBLEBUFFER, true, - GLX_RED_SIZE, 1, - GLX_GREEN_SIZE, 1, - GLX_BLUE_SIZE, 1, - GLX_DEPTH_SIZE, 24, - None - }; - - int fbcount; - GLXFBConfig fbconfig = nullptr; - XVisualInfo *vi = nullptr; - - XSetWindowAttributes swa; - swa.event_mask = StructureNotifyMask; - swa.border_pixel = 0; - unsigned long valuemask = CWBorderPixel | CWColormap | CWEventMask; - - GLXFBConfig *fbc = glXChooseFBConfig(x11_display, DefaultScreen(x11_display), visual_attribs, &fbcount); - if (!fbc) { - exit(1); - } - - vi = glXGetVisualFromFBConfig(x11_display, fbc[0]); - - fbconfig = fbc[0]; - - static int context_attribs[] = { - GLX_CONTEXT_MAJOR_VERSION_ARB, 3, - GLX_CONTEXT_MINOR_VERSION_ARB, 3, - GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB, - GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, - None - }; - - glx_context = glXCreateContextAttribsARB(x11_display, fbconfig, nullptr, true, context_attribs); - - swa.colormap = XCreateColormap(x11_display, RootWindow(x11_display, vi->screen), vi->visual, AllocNone); - x11_window = XCreateWindow(x11_display, RootWindow(x11_display, vi->screen), 0, 0, 10, 10, 0, vi->depth, InputOutput, vi->visual, valuemask, &swa); - - if (!x11_window) { - exit(1); - } - - glXMakeCurrent(x11_display, x11_window, glx_context); - XFree(vi); -} - -int detect_prime() { - pid_t p; - int priorities[2] = {}; - String vendors[2]; - String renderers[2]; - - vendors[0] = "Unknown"; - vendors[1] = "Unknown"; - renderers[0] = "Unknown"; - renderers[1] = "Unknown"; - - for (int i = 0; i < 2; ++i) { - int fdset[2]; - - if (pipe(fdset) == -1) { - print_verbose("Failed to pipe(), using default GPU"); - return 0; - } - - // Fork so the driver initialization can crash without taking down the engine. - p = fork(); - - if (p > 0) { - // Main thread - - int stat_loc = 0; - char string[201]; - string[200] = '\0'; - - close(fdset[1]); - - waitpid(p, &stat_loc, 0); - - if (!stat_loc) { - // No need to do anything complicated here. Anything less than - // PIPE_BUF will be delivered in one read() call. - // Leave it 'Unknown' otherwise. - if (read(fdset[0], string, sizeof(string) - 1) > 0) { - vendors[i] = string; - renderers[i] = string + strlen(string) + 1; - } - } - - close(fdset[0]); - - } else { - // In child, exit() here will not quit the engine. - - // Prevent false leak reports as we will not be properly - // cleaning up these processes, and fork() makes a copy - // of all globals. - CoreGlobals::leak_reporting_enabled = false; - - char string[201]; - - close(fdset[0]); - - if (i) { - setenv("DRI_PRIME", "1", 1); - } - create_context(); - - const char *vendor = (const char *)glGetString(GL_VENDOR); - const char *renderer = (const char *)glGetString(GL_RENDERER); - - unsigned int vendor_len = strlen(vendor) + 1; - unsigned int renderer_len = strlen(renderer) + 1; - - if (vendor_len + renderer_len >= sizeof(string)) { - renderer_len = 200 - vendor_len; - } - - memcpy(&string, vendor, vendor_len); - memcpy(&string[vendor_len], renderer, renderer_len); - - if (write(fdset[1], string, vendor_len + renderer_len) == -1) { - print_verbose("Couldn't write vendor/renderer string."); - } - close(fdset[1]); - exit(0); - } - } - - int preferred = 0; - int priority = 0; - - if (vendors[0] == vendors[1]) { - print_verbose("Only one GPU found, using default."); - return 0; - } - - for (int i = 1; i >= 0; --i) { - vendor *v = vendormap; - while (v->glxvendor) { - if (v->glxvendor == vendors[i]) { - priorities[i] = v->priority; - - if (v->priority >= priority) { - priority = v->priority; - preferred = i; - } - } - ++v; - } - } - - print_verbose("Found renderers:"); - for (int i = 0; i < 2; ++i) { - print_verbose("Renderer " + itos(i) + ": " + renderers[i] + " with priority: " + itos(priorities[i])); - } - - print_verbose("Using renderer: " + renderers[preferred]); - return preferred; -} - -#endif -#endif diff --git a/platform/linuxbsd/detect_prime_x11.h b/platform/linuxbsd/detect_prime_x11.h deleted file mode 100644 index 7eb7064cc5..0000000000 --- a/platform/linuxbsd/detect_prime_x11.h +++ /dev/null @@ -1,40 +0,0 @@ -/*************************************************************************/ -/* detect_prime_x11.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef DETECT_PRIME_X11_H -#define DETECT_PRIME_X11_H - -#if defined(X11_ENABLED) && defined(GLES3_ENABLED) - -int detect_prime(); - -#endif // X11_ENABLED && GLES3_ENABLED - -#endif // DETECT_PRIME_X11_H diff --git a/platform/linuxbsd/display_server_x11.cpp b/platform/linuxbsd/display_server_x11.cpp deleted file mode 100644 index 88c6500e10..0000000000 --- a/platform/linuxbsd/display_server_x11.cpp +++ /dev/null @@ -1,5196 +0,0 @@ -/*************************************************************************/ -/* display_server_x11.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "display_server_x11.h" - -#ifdef X11_ENABLED - -#include "core/config/project_settings.h" -#include "core/math/math_funcs.h" -#include "core/string/print_string.h" -#include "core/string/ustring.h" -#include "detect_prime_x11.h" -#include "key_mapping_x11.h" -#include "main/main.h" -#include "scene/resources/texture.h" - -#if defined(VULKAN_ENABLED) -#include "servers/rendering/renderer_rd/renderer_compositor_rd.h" -#endif - -#if defined(GLES3_ENABLED) -#include "drivers/gles3/rasterizer_gles3.h" -#endif - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -// ICCCM -#define WM_NormalState 1L // window normal state -#define WM_IconicState 3L // window minimized -// EWMH -#define _NET_WM_STATE_REMOVE 0L // remove/unset property -#define _NET_WM_STATE_ADD 1L // add/set property - -#undef CursorShape -#include - -// 2.2 is the first release with multitouch -#define XINPUT_CLIENT_VERSION_MAJOR 2 -#define XINPUT_CLIENT_VERSION_MINOR 2 - -#define VALUATOR_ABSX 0 -#define VALUATOR_ABSY 1 -#define VALUATOR_PRESSURE 2 -#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; -}; - -static String get_atom_name(Display *p_disp, Atom p_atom) { - char *name = XGetAtomName(p_disp, p_atom); - ERR_FAIL_NULL_V_MSG(name, String(), "Atom is invalid."); - String ret; - ret.parse_utf8(name); - XFree(name); - return ret; -} - -bool DisplayServerX11::has_feature(Feature p_feature) const { - switch (p_feature) { - case FEATURE_SUBWINDOWS: -#ifdef TOUCH_ENABLED - case FEATURE_TOUCHSCREEN: -#endif - case FEATURE_MOUSE: - case FEATURE_MOUSE_WARP: - case FEATURE_CLIPBOARD: - case FEATURE_CURSOR_SHAPE: - case FEATURE_CUSTOM_CURSOR_SHAPE: - case FEATURE_IME: - case FEATURE_WINDOW_TRANSPARENCY: - //case FEATURE_HIDPI: - case FEATURE_ICON: - //case FEATURE_NATIVE_ICON: - case FEATURE_SWAP_BUFFERS: -#ifdef DBUS_ENABLED - case FEATURE_KEEP_SCREEN_ON: -#endif - case FEATURE_CLIPBOARD_PRIMARY: - case FEATURE_TEXT_TO_SPEECH: - return true; - default: { - } - } - - return false; -} - -String DisplayServerX11::get_name() const { - return "X11"; -} - -void DisplayServerX11::_update_real_mouse_position(const WindowData &wd) { - Window root_return, child_return; - int root_x, root_y, win_x, win_y; - unsigned int mask_return; - - Bool xquerypointer_result = XQueryPointer(x11_display, wd.x11_window, &root_return, &child_return, &root_x, &root_y, - &win_x, &win_y, &mask_return); - - 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; - Input::get_singleton()->set_mouse_position(last_mouse_pos); - } - } -} - -bool DisplayServerX11::_refresh_device_info() { - int event_base, error_base; - - print_verbose("XInput: Refreshing devices."); - - if (!XQueryExtension(x11_display, "XInputExtension", &xi.opcode, &event_base, &error_base)) { - print_verbose("XInput extension not available. Please upgrade your distribution."); - return false; - } - - int xi_major_query = XINPUT_CLIENT_VERSION_MAJOR; - int xi_minor_query = XINPUT_CLIENT_VERSION_MINOR; - - if (XIQueryVersion(x11_display, &xi_major_query, &xi_minor_query) != Success) { - print_verbose(vformat("XInput 2 not available (server supports %d.%d).", xi_major_query, xi_minor_query)); - xi.opcode = 0; - return false; - } - - if (xi_major_query < XINPUT_CLIENT_VERSION_MAJOR || (xi_major_query == XINPUT_CLIENT_VERSION_MAJOR && xi_minor_query < XINPUT_CLIENT_VERSION_MINOR)) { - print_verbose(vformat("XInput %d.%d not available (server supports %d.%d). Touch input unavailable.", - XINPUT_CLIENT_VERSION_MAJOR, XINPUT_CLIENT_VERSION_MINOR, xi_major_query, xi_minor_query)); - } - - xi.absolute_devices.clear(); - xi.touch_devices.clear(); - xi.pen_inverted_devices.clear(); - - int dev_count; - XIDeviceInfo *info = XIQueryDevice(x11_display, XIAllDevices, &dev_count); - - for (int i = 0; i < dev_count; i++) { - XIDeviceInfo *dev = &info[i]; - if (!dev->enabled) { - continue; - } - if (!(dev->use == XISlavePointer || dev->use == XIFloatingSlave)) { - continue; - } - - bool direct_touch = false; - bool absolute_mode = false; - int resolution_x = 0; - int resolution_y = 0; - double abs_x_min = 0; - double abs_x_max = 0; - double abs_y_min = 0; - double abs_y_max = 0; - double pressure_min = 0; - double pressure_max = 0; - double tilt_x_min = 0; - double tilt_x_max = 0; - double tilt_y_min = 0; - double tilt_y_max = 0; - for (int j = 0; j < dev->num_classes; j++) { -#ifdef TOUCH_ENABLED - if (dev->classes[j]->type == XITouchClass && ((XITouchClassInfo *)dev->classes[j])->mode == XIDirectTouch) { - direct_touch = true; - } -#endif - if (dev->classes[j]->type == XIValuatorClass) { - XIValuatorClassInfo *class_info = (XIValuatorClassInfo *)dev->classes[j]; - - if (class_info->number == VALUATOR_ABSX && class_info->mode == XIModeAbsolute) { - resolution_x = class_info->resolution; - abs_x_min = class_info->min; - abs_x_max = class_info->max; - absolute_mode = true; - } else if (class_info->number == VALUATOR_ABSY && class_info->mode == XIModeAbsolute) { - resolution_y = class_info->resolution; - abs_y_min = class_info->min; - abs_y_max = class_info->max; - absolute_mode = true; - } else if (class_info->number == VALUATOR_PRESSURE && class_info->mode == XIModeAbsolute) { - pressure_min = class_info->min; - pressure_max = class_info->max; - } else if (class_info->number == VALUATOR_TILTX && class_info->mode == XIModeAbsolute) { - tilt_x_min = class_info->min; - tilt_x_max = class_info->max; - } else if (class_info->number == VALUATOR_TILTY && class_info->mode == XIModeAbsolute) { - tilt_y_min = class_info->min; - tilt_y_max = class_info->max; - } - } - } - if (direct_touch) { - xi.touch_devices.push_back(dev->deviceid); - print_verbose("XInput: Using touch device: " + String(dev->name)); - } - if (absolute_mode) { - // If no resolution was reported, use the min/max ranges. - if (resolution_x <= 0) { - resolution_x = (abs_x_max - abs_x_min) * abs_resolution_range_mult; - } - if (resolution_y <= 0) { - resolution_y = (abs_y_max - abs_y_min) * abs_resolution_range_mult; - } - xi.absolute_devices[dev->deviceid] = Vector2(abs_resolution_mult / resolution_x, abs_resolution_mult / resolution_y); - print_verbose("XInput: Absolute pointing device: " + String(dev->name)); - } - - xi.pressure = 0; - xi.pen_pressure_range[dev->deviceid] = Vector2(pressure_min, pressure_max); - xi.pen_tilt_x_range[dev->deviceid] = Vector2(tilt_x_min, tilt_x_max); - xi.pen_tilt_y_range[dev->deviceid] = Vector2(tilt_y_min, tilt_y_max); - xi.pen_inverted_devices[dev->deviceid] = String(dev->name).findn("eraser") > 0; - } - - XIFreeDeviceInfo(info); -#ifdef TOUCH_ENABLED - if (!xi.touch_devices.size()) { - print_verbose("XInput: No touch devices found."); - } -#endif - - return true; -} - -void DisplayServerX11::_flush_mouse_motion() { - // 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_at(event_index--); - continue; - } - XFreeEventData(x11_display, &event.xcookie); - break; - } - } - - xi.relative_motion.x = 0; - xi.relative_motion.y = 0; -} - -#ifdef SPEECHD_ENABLED - -bool DisplayServerX11::tts_is_speaking() const { - ERR_FAIL_COND_V(!tts, false); - return tts->is_speaking(); -} - -bool DisplayServerX11::tts_is_paused() const { - ERR_FAIL_COND_V(!tts, false); - return tts->is_paused(); -} - -TypedArray DisplayServerX11::tts_get_voices() const { - ERR_FAIL_COND_V(!tts, TypedArray()); - return tts->get_voices(); -} - -void DisplayServerX11::tts_speak(const String &p_text, const String &p_voice, int p_volume, float p_pitch, float p_rate, int p_utterance_id, bool p_interrupt) { - ERR_FAIL_COND(!tts); - tts->speak(p_text, p_voice, p_volume, p_pitch, p_rate, p_utterance_id, p_interrupt); -} - -void DisplayServerX11::tts_pause() { - ERR_FAIL_COND(!tts); - tts->pause(); -} - -void DisplayServerX11::tts_resume() { - ERR_FAIL_COND(!tts); - tts->resume(); -} - -void DisplayServerX11::tts_stop() { - ERR_FAIL_COND(!tts); - tts->stop(); -} - -#endif - -#ifdef DBUS_ENABLED - -bool DisplayServerX11::is_dark_mode_supported() const { - return portal_desktop->is_supported(); -} - -bool DisplayServerX11::is_dark_mode() const { - switch (portal_desktop->get_appearance_color_scheme()) { - case 1: - // Prefers dark theme. - return true; - case 2: - // Prefers light theme. - return false; - default: - // Preference unknown. - return false; - } -} - -#endif - -void DisplayServerX11::mouse_set_mode(MouseMode p_mode) { - _THREAD_SAFE_METHOD_ - - if (p_mode == mouse_mode) { - return; - } - - if (mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN) { - 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 (const KeyValue &E : windows) { - if (showCursor) { - XDefineCursor(x11_display, E.value.x11_window, cursors[current_cursor]); // show cursor - } else { - XDefineCursor(x11_display, E.value.x11_window, null_cursor); // hide cursor - } - } - mouse_mode = p_mode; - - if (mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN) { - //flush pending motion events - _flush_mouse_motion(); - WindowID window_id = _get_focused_window_or_popup(); - if (!windows.has(window_id)) { - window_id = MAIN_WINDOW_ID; - } - WindowData &window = windows[window_id]; - - if (XGrabPointer( - x11_display, window.x11_window, True, - ButtonPressMask | ButtonReleaseMask | PointerMotionMask, - GrabModeAsync, GrabModeAsync, window.x11_window, None, CurrentTime) != GrabSuccess) { - ERR_PRINT("NO GRAB"); - } - - if (mouse_mode == MOUSE_MODE_CAPTURED) { - center.x = window.size.width / 2; - center.y = window.size.height / 2; - - XWarpPointer(x11_display, None, window.x11_window, - 0, 0, 0, 0, (int)center.x, (int)center.y); - - Input::get_singleton()->set_mouse_position(center); - } - } else { - do_mouse_warp = false; - } - - XFlush(x11_display); -} - -DisplayServerX11::MouseMode DisplayServerX11::mouse_get_mode() const { - return mouse_mode; -} - -void DisplayServerX11::warp_mouse(const Point2i &p_position) { - _THREAD_SAFE_METHOD_ - - if (mouse_mode == MOUSE_MODE_CAPTURED) { - last_mouse_pos = p_position; - } else { - WindowID window_id = _get_focused_window_or_popup(); - if (!windows.has(window_id)) { - window_id = MAIN_WINDOW_ID; - } - - XWarpPointer(x11_display, None, windows[window_id].x11_window, - 0, 0, 0, 0, (int)p_position.x, (int)p_position.y); - } -} - -Point2i DisplayServerX11::mouse_get_position() const { - int number_of_screens = XScreenCount(x11_display); - for (int i = 0; i < number_of_screens; i++) { - Window root, child; - int root_x, root_y, win_x, win_y; - unsigned int mask; - if (XQueryPointer(x11_display, XRootWindow(x11_display, i), &root, &child, &root_x, &root_y, &win_x, &win_y, &mask)) { - XWindowAttributes root_attrs; - XGetWindowAttributes(x11_display, root, &root_attrs); - - return Vector2i(root_attrs.x + root_x, root_attrs.y + root_y); - } - } - return Vector2i(); -} - -MouseButton DisplayServerX11::mouse_get_button_state() const { - return last_button_state; -} - -void DisplayServerX11::clipboard_set(const String &p_text) { - _THREAD_SAFE_METHOD_ - - { - // 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); -} - -void DisplayServerX11::clipboard_set_primary(const String &p_text) { - _THREAD_SAFE_METHOD_ - if (!p_text.is_empty()) { - { - // The clipboard content can be accessed while polling for events. - MutexLock mutex_lock(events_mutex); - internal_clipboard_primary = p_text; - } - - XSetSelectionOwner(x11_display, XA_PRIMARY, windows[MAIN_WINDOW_ID].x11_window, CurrentTime); - XSetSelectionOwner(x11_display, XInternAtom(x11_display, "PRIMARY", 0), windows[MAIN_WINDOW_ID].x11_window, CurrentTime); - } -} - -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; - } -} - -Bool DisplayServerX11::_predicate_clipboard_incr(Display *display, XEvent *event, XPointer arg) { - if (event->type == PropertyNotify && event->xproperty.state == PropertyNewValue) { - return True; - } else { - return False; - } -} - -String DisplayServerX11::_clipboard_get_impl(Atom p_source, Window x11_window, Atom target) const { - String ret; - - Window selection_owner = XGetSelectionOwner(x11_display, p_source); - if (selection_owner == x11_window) { - static const char *target_type = "PRIMARY"; - if (p_source != None && get_atom_name(x11_display, p_source) == target_type) { - return internal_clipboard_primary; - } else { - return internal_clipboard; - } - } - - if (selection_owner != None) { - // Block events polling while processing selection events. - MutexLock mutex_lock(events_mutex); - - Atom selection = XA_PRIMARY; - XConvertSelection(x11_display, p_source, target, selection, - x11_window, CurrentTime); - - XFlush(x11_display); - - // 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 - &type, // return type - &format, // return format - &len, &bytes_left, // data length - &data); - - 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 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("Failed to get selection data.\n"); - } - - if (data) { - XFree(data); - } - } - } - - return ret; -} - -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, utf8_atom); - } - 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); - - if (ret.is_empty()) { - ret = _clipboard_get(XA_PRIMARY, windows[MAIN_WINDOW_ID].x11_window); - } - - return ret; -} - -String DisplayServerX11::clipboard_get_primary() const { - _THREAD_SAFE_METHOD_ - - String ret; - ret = _clipboard_get(XInternAtom(x11_display, "PRIMARY", 0), windows[MAIN_WINDOW_ID].x11_window); - - if (ret.is_empty()) { - ret = _clipboard_get(XA_PRIMARY, windows[MAIN_WINDOW_ID].x11_window); - } - - return ret; -} - -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_ - int count = 0; - - // Using Xinerama Extension - int event_base, error_base; - if (XineramaQueryExtension(x11_display, &event_base, &error_base)) { - XineramaScreenInfo *xsi = XineramaQueryScreens(x11_display, &count); - XFree(xsi); - } else { - count = XScreenCount(x11_display); - } - - return count; -} - -Rect2i DisplayServerX11::_screen_get_rect(int p_screen) const { - Rect2i rect(0, 0, 0, 0); - - if (p_screen == SCREEN_OF_MAIN_WINDOW) { - p_screen = window_get_current_screen(); - } - - ERR_FAIL_COND_V(p_screen < 0, rect); - - // Using Xinerama Extension. - int event_base, error_base; - if (XineramaQueryExtension(x11_display, &event_base, &error_base)) { - int count; - XineramaScreenInfo *xsi = XineramaQueryScreens(x11_display, &count); - - // Check if screen is valid. - if (p_screen < count) { - rect.position.x = xsi[p_screen].x_org; - rect.position.y = xsi[p_screen].y_org; - rect.size.width = xsi[p_screen].width; - rect.size.height = xsi[p_screen].height; - } else { - ERR_PRINT("Invalid screen index: " + itos(p_screen) + "(count: " + itos(count) + ")."); - } - - if (xsi) { - XFree(xsi); - } - } else { - int count = XScreenCount(x11_display); - if (p_screen < count) { - Window root = XRootWindow(x11_display, p_screen); - XWindowAttributes xwa; - XGetWindowAttributes(x11_display, root, &xwa); - rect.position.x = xwa.x; - rect.position.y = xwa.y; - rect.size.width = xwa.width; - rect.size.height = xwa.height; - } else { - ERR_PRINT("Invalid screen index: " + itos(p_screen) + "(count: " + itos(count) + ")."); - } - } - - return rect; -} - -Point2i DisplayServerX11::screen_get_position(int p_screen) const { - _THREAD_SAFE_METHOD_ - - return _screen_get_rect(p_screen).position; -} - -Size2i DisplayServerX11::screen_get_size(int p_screen) const { - _THREAD_SAFE_METHOD_ - - return _screen_get_rect(p_screen).size; -} - -bool g_bad_window = false; -int bad_window_error_handler(Display *display, XErrorEvent *error) { - if (error->error_code == BadWindow) { - g_bad_window = true; - } else { - ERR_PRINT("Unhandled XServer error code: " + itos(error->error_code)); - } - return 0; -} - -Rect2i DisplayServerX11::screen_get_usable_rect(int p_screen) const { - _THREAD_SAFE_METHOD_ - - if (p_screen == SCREEN_OF_MAIN_WINDOW) { - p_screen = window_get_current_screen(); - } - - int screen_count = get_screen_count(); - - // Check if screen is valid. - ERR_FAIL_INDEX_V(p_screen, screen_count, Rect2i(0, 0, 0, 0)); - - bool is_multiscreen = screen_count > 1; - - // Use full monitor size as fallback. - Rect2i rect = _screen_get_rect(p_screen); - - // There's generally only one screen reported by xlib even in multi-screen setup, - // in this case it's just one virtual screen composed of all physical monitors. - int x11_screen_count = ScreenCount(x11_display); - Window x11_window = RootWindow(x11_display, p_screen < x11_screen_count ? p_screen : 0); - - Atom type; - int format = 0; - unsigned long remaining = 0; - - // Find active desktop for the root window. - unsigned int desktop_index = 0; - Atom desktop_prop = XInternAtom(x11_display, "_NET_CURRENT_DESKTOP", True); - if (desktop_prop != None) { - unsigned long desktop_len = 0; - unsigned char *desktop_data = nullptr; - if (XGetWindowProperty(x11_display, x11_window, desktop_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &desktop_len, &remaining, &desktop_data) == Success) { - if ((format == 32) && (desktop_len > 0) && desktop_data) { - desktop_index = (unsigned int)desktop_data[0]; - } - if (desktop_data) { - XFree(desktop_data); - } - } - } - - bool use_simple_method = true; - - // First check for GTK work area, which is more accurate for multi-screen setup. - if (is_multiscreen) { - // Use already calculated work area when available. - Atom gtk_workareas_prop = XInternAtom(x11_display, "_GTK_WORKAREAS", False); - if (gtk_workareas_prop != None) { - char gtk_workarea_prop_name[32]; - snprintf(gtk_workarea_prop_name, 32, "_GTK_WORKAREAS_D%d", desktop_index); - Atom gtk_workarea_prop = XInternAtom(x11_display, gtk_workarea_prop_name, True); - if (gtk_workarea_prop != None) { - unsigned long workarea_len = 0; - unsigned char *workarea_data = nullptr; - if (XGetWindowProperty(x11_display, x11_window, gtk_workarea_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &workarea_len, &remaining, &workarea_data) == Success) { - if ((format == 32) && (workarea_len % 4 == 0) && workarea_data) { - long *rect_data = (long *)workarea_data; - for (uint32_t data_offset = 0; data_offset < workarea_len; data_offset += 4) { - Rect2i workarea_rect; - workarea_rect.position.x = rect_data[data_offset]; - workarea_rect.position.y = rect_data[data_offset + 1]; - workarea_rect.size.x = rect_data[data_offset + 2]; - workarea_rect.size.y = rect_data[data_offset + 3]; - - // Intersect with actual monitor size to find the correct area, - // because areas are not in the same order as screens from Xinerama. - if (rect.grow(-1).intersects(workarea_rect)) { - rect = rect.intersection(workarea_rect); - XFree(workarea_data); - return rect; - } - } - } - } - if (workarea_data) { - XFree(workarea_data); - } - } - } - - // Fallback to calculating work area by hand from struts. - Atom client_list_prop = XInternAtom(x11_display, "_NET_CLIENT_LIST", True); - if (client_list_prop != None) { - unsigned long clients_len = 0; - unsigned char *clients_data = nullptr; - if (XGetWindowProperty(x11_display, x11_window, client_list_prop, 0, LONG_MAX, False, XA_WINDOW, &type, &format, &clients_len, &remaining, &clients_data) == Success) { - if ((format == 32) && (clients_len > 0) && clients_data) { - Window *windows_data = (Window *)clients_data; - - Rect2i desktop_rect; - bool desktop_valid = false; - - // Get full desktop size. - { - Atom desktop_geometry_prop = XInternAtom(x11_display, "_NET_DESKTOP_GEOMETRY", True); - if (desktop_geometry_prop != None) { - unsigned long geom_len = 0; - unsigned char *geom_data = nullptr; - if (XGetWindowProperty(x11_display, x11_window, desktop_geometry_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &geom_len, &remaining, &geom_data) == Success) { - if ((format == 32) && (geom_len >= 2) && geom_data) { - desktop_valid = true; - long *size_data = (long *)geom_data; - desktop_rect.size.x = size_data[0]; - desktop_rect.size.y = size_data[1]; - } - } - if (geom_data) { - XFree(geom_data); - } - } - } - - // Get full desktop position. - if (desktop_valid) { - Atom desktop_viewport_prop = XInternAtom(x11_display, "_NET_DESKTOP_VIEWPORT", True); - if (desktop_viewport_prop != None) { - unsigned long viewport_len = 0; - unsigned char *viewport_data = nullptr; - if (XGetWindowProperty(x11_display, x11_window, desktop_viewport_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &viewport_len, &remaining, &viewport_data) == Success) { - if ((format == 32) && (viewport_len >= 2) && viewport_data) { - desktop_valid = true; - long *pos_data = (long *)viewport_data; - desktop_rect.position.x = pos_data[0]; - desktop_rect.position.y = pos_data[1]; - } - } - if (viewport_data) { - XFree(viewport_data); - } - } - } - - if (desktop_valid) { - use_simple_method = false; - - // Handle bad window errors silently because there's no other way to check - // that one of the windows has been destroyed in the meantime. - int (*oldHandler)(Display *, XErrorEvent *) = XSetErrorHandler(&bad_window_error_handler); - - for (unsigned long win_index = 0; win_index < clients_len; ++win_index) { - g_bad_window = false; - - // Remove strut size from desktop size to get a more accurate result. - bool strut_found = false; - unsigned long strut_len = 0; - unsigned char *strut_data = nullptr; - Atom strut_partial_prop = XInternAtom(x11_display, "_NET_WM_STRUT_PARTIAL", True); - if (strut_partial_prop != None) { - if (XGetWindowProperty(x11_display, windows_data[win_index], strut_partial_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &strut_len, &remaining, &strut_data) == Success) { - strut_found = true; - } - } - // Fallback to older strut property. - if (!g_bad_window && !strut_found) { - Atom strut_prop = XInternAtom(x11_display, "_NET_WM_STRUT", True); - if (strut_prop != None) { - if (XGetWindowProperty(x11_display, windows_data[win_index], strut_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &strut_len, &remaining, &strut_data) == Success) { - strut_found = true; - } - } - } - if (!g_bad_window && strut_found && (format == 32) && (strut_len >= 4) && strut_data) { - long *struts = (long *)strut_data; - - long left = struts[0]; - long right = struts[1]; - long top = struts[2]; - long bottom = struts[3]; - - long left_start_y, left_end_y, right_start_y, right_end_y; - long top_start_x, top_end_x, bottom_start_x, bottom_end_x; - - if (strut_len >= 12) { - left_start_y = struts[4]; - left_end_y = struts[5]; - right_start_y = struts[6]; - right_end_y = struts[7]; - top_start_x = struts[8]; - top_end_x = struts[9]; - bottom_start_x = struts[10]; - bottom_end_x = struts[11]; - } else { - left_start_y = 0; - left_end_y = desktop_rect.size.y; - right_start_y = 0; - right_end_y = desktop_rect.size.y; - top_start_x = 0; - top_end_x = desktop_rect.size.x; - bottom_start_x = 0; - bottom_end_x = desktop_rect.size.x; - } - - const Point2i &pos = desktop_rect.position; - const Size2i &size = desktop_rect.size; - - Rect2i left_rect(pos.x, pos.y + left_start_y, left, left_end_y - left_start_y); - if (left_rect.size.x > 0) { - Rect2i intersection = rect.intersection(left_rect); - if (intersection.has_area() && intersection.size.x < rect.size.x) { - rect.position.x = left_rect.size.x; - rect.size.x = rect.size.x - intersection.size.x; - } - } - - Rect2i right_rect(pos.x + size.x - right, pos.y + right_start_y, right, right_end_y - right_start_y); - if (right_rect.size.x > 0) { - Rect2i intersection = rect.intersection(right_rect); - if (intersection.has_area() && right_rect.size.x < rect.size.x) { - rect.size.x = intersection.position.x - rect.position.x; - } - } - - Rect2i top_rect(pos.x + top_start_x, pos.y, top_end_x - top_start_x, top); - if (top_rect.size.y > 0) { - Rect2i intersection = rect.intersection(top_rect); - if (intersection.has_area() && intersection.size.y < rect.size.y) { - rect.position.y = top_rect.size.y; - rect.size.y = rect.size.y - intersection.size.y; - } - } - - Rect2i bottom_rect(pos.x + bottom_start_x, pos.y + size.y - bottom, bottom_end_x - bottom_start_x, bottom); - if (bottom_rect.size.y > 0) { - Rect2i intersection = rect.intersection(bottom_rect); - if (intersection.has_area() && right_rect.size.y < rect.size.y) { - rect.size.y = intersection.position.y - rect.position.y; - } - } - } - if (strut_data) { - XFree(strut_data); - } - } - - // Restore default error handler. - XSetErrorHandler(oldHandler); - } - } - } - if (clients_data) { - XFree(clients_data); - } - } - } - - // Single screen or fallback for multi screen. - if (use_simple_method) { - // Get desktop available size from the global work area. - Atom workarea_prop = XInternAtom(x11_display, "_NET_WORKAREA", True); - if (workarea_prop != None) { - unsigned long workarea_len = 0; - unsigned char *workarea_data = nullptr; - if (XGetWindowProperty(x11_display, x11_window, workarea_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &workarea_len, &remaining, &workarea_data) == Success) { - if ((format == 32) && (workarea_len >= ((desktop_index + 1) * 4)) && workarea_data) { - long *rect_data = (long *)workarea_data; - int data_offset = desktop_index * 4; - Rect2i workarea_rect; - workarea_rect.position.x = rect_data[data_offset]; - workarea_rect.position.y = rect_data[data_offset + 1]; - workarea_rect.size.x = rect_data[data_offset + 2]; - workarea_rect.size.y = rect_data[data_offset + 3]; - - // Intersect with actual monitor size to get a proper approximation in multi-screen setup. - if (!is_multiscreen) { - rect = workarea_rect; - } else if (rect.intersects(workarea_rect)) { - rect = rect.intersection(workarea_rect); - } - } - } - if (workarea_data) { - XFree(workarea_data); - } - } - } - - return rect; -} - -int DisplayServerX11::screen_get_dpi(int p_screen) const { - _THREAD_SAFE_METHOD_ - - if (p_screen == SCREEN_OF_MAIN_WINDOW) { - p_screen = window_get_current_screen(); - } - - //invalid screen? - ERR_FAIL_INDEX_V(p_screen, get_screen_count(), 0); - - //Get physical monitor Dimensions through XRandR and calculate dpi - Size2i sc = screen_get_size(p_screen); - if (xrandr_ext_ok) { - int count = 0; - if (xrr_get_monitors) { - xrr_monitor_info *monitors = xrr_get_monitors(x11_display, windows[MAIN_WINDOW_ID].x11_window, true, &count); - if (p_screen < count) { - double xdpi = sc.width / (double)monitors[p_screen].mwidth * 25.4; - double ydpi = sc.height / (double)monitors[p_screen].mheight * 25.4; - xrr_free_monitors(monitors); - return (xdpi + ydpi) / 2; - } - xrr_free_monitors(monitors); - } else if (p_screen == 0) { - XRRScreenSize *sizes = XRRSizes(x11_display, 0, &count); - if (sizes) { - double xdpi = sc.width / (double)sizes[0].mwidth * 25.4; - double ydpi = sc.height / (double)sizes[0].mheight * 25.4; - return (xdpi + ydpi) / 2; - } - } - } - - int width_mm = DisplayWidthMM(x11_display, p_screen); - 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) { - return (xdpi + ydpi) / (xdpi && ydpi ? 2 : 1); - } - - //could not get dpi - return 96; -} - -float DisplayServerX11::screen_get_refresh_rate(int p_screen) const { - _THREAD_SAFE_METHOD_ - - if (p_screen == SCREEN_OF_MAIN_WINDOW) { - p_screen = window_get_current_screen(); - } - - //invalid screen? - ERR_FAIL_INDEX_V(p_screen, get_screen_count(), SCREEN_REFRESH_RATE_FALLBACK); - - //Use xrandr to get screen refresh rate. - if (xrandr_ext_ok) { - XRRScreenResources *screen_info = XRRGetScreenResources(x11_display, windows[MAIN_WINDOW_ID].x11_window); - if (screen_info) { - RRMode current_mode = 0; - xrr_monitor_info *monitors = nullptr; - - if (xrr_get_monitors) { - int count = 0; - monitors = xrr_get_monitors(x11_display, windows[MAIN_WINDOW_ID].x11_window, true, &count); - ERR_FAIL_INDEX_V(p_screen, count, SCREEN_REFRESH_RATE_FALLBACK); - } else { - ERR_PRINT("An error occurred while trying to get the screen refresh rate."); - return SCREEN_REFRESH_RATE_FALLBACK; - } - - bool found_active_mode = false; - for (int crtc = 0; crtc < screen_info->ncrtc; crtc++) { // Loop through outputs to find which one is currently outputting. - XRRCrtcInfo *monitor_info = XRRGetCrtcInfo(x11_display, screen_info, screen_info->crtcs[crtc]); - if (monitor_info->x != monitors[p_screen].x || monitor_info->y != monitors[p_screen].y) { // If X and Y aren't the same as the monitor we're looking for, this isn't the right monitor. Continue. - continue; - } - - if (monitor_info->mode != None) { - current_mode = monitor_info->mode; - found_active_mode = true; - break; - } - } - - if (found_active_mode) { - for (int mode = 0; mode < screen_info->nmode; mode++) { - XRRModeInfo m_info = screen_info->modes[mode]; - if (m_info.id == current_mode) { - // Snap to nearest 0.01 to stay consistent with other platforms. - return Math::snapped((float)m_info.dotClock / ((float)m_info.hTotal * (float)m_info.vTotal), 0.01); - } - } - } - - ERR_PRINT("An error occurred while trying to get the screen refresh rate."); // We should have returned the refresh rate by now. An error must have occurred. - return SCREEN_REFRESH_RATE_FALLBACK; - } else { - ERR_PRINT("An error occurred while trying to get the screen refresh rate."); - return SCREEN_REFRESH_RATE_FALLBACK; - } - } - ERR_PRINT("An error occurred while trying to get the screen refresh rate."); - return SCREEN_REFRESH_RATE_FALLBACK; -} - -#ifdef DBUS_ENABLED -void DisplayServerX11::screen_set_keep_on(bool p_enable) { - if (screen_is_kept_on() == p_enable) { - return; - } - - if (p_enable) { - screensaver->inhibit(); - } else { - screensaver->uninhibit(); - } - - keep_screen_on = p_enable; -} - -bool DisplayServerX11::screen_is_kept_on() const { - return keep_screen_on; -} -#endif - -Vector DisplayServerX11::get_window_list() const { - _THREAD_SAFE_METHOD_ - - Vector ret; - for (const KeyValue &E : windows) { - ret.push_back(E.key); - } - return ret; -} - -DisplayServer::WindowID DisplayServerX11::create_sub_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect) { - _THREAD_SAFE_METHOD_ - - WindowID id = _create_window(p_mode, p_vsync_mode, p_flags, p_rect); - for (int i = 0; i < WINDOW_FLAG_MAX; i++) { - if (p_flags & (1 << i)) { - window_set_flag(WindowFlags(i), true, id); - } - } - - return id; -} - -void DisplayServerX11::show_window(WindowID p_id) { - _THREAD_SAFE_METHOD_ - - const WindowData &wd = windows[p_id]; - popup_open(p_id); - - DEBUG_LOG_X11("show_window: %lu (%u) \n", wd.x11_window, 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)); - ERR_FAIL_COND_MSG(p_id == MAIN_WINDOW_ID, "Main window can't be deleted"); - - popup_close(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.begin(), INVALID_WINDOW_ID); - } - - if (wd.transient_parent != INVALID_WINDOW_ID) { - window_set_transient(p_id, INVALID_WINDOW_ID); - } - -#ifdef VULKAN_ENABLED - if (context_vulkan) { - context_vulkan->window_destroy(p_id); - } -#endif -#ifdef GLES3_ENABLED - if (gl_manager) { - gl_manager->window_destroy(p_id); - } -#endif - - XUnmapWindow(x11_display, wd.x11_window); - XDestroyWindow(x11_display, wd.x11_window); - if (wd.xic) { - XDestroyIC(wd.xic); - wd.xic = nullptr; - } - - windows.erase(p_id); -} - -int64_t DisplayServerX11::window_get_native_handle(HandleType p_handle_type, WindowID p_window) const { - ERR_FAIL_COND_V(!windows.has(p_window), 0); - switch (p_handle_type) { - case DISPLAY_HANDLE: { - return (int64_t)x11_display; - } - case WINDOW_HANDLE: { - return (int64_t)windows[p_window].x11_window; - } - case WINDOW_VIEW: { - return 0; // Not supported. - } - default: { - return 0; - } - } -} - -void DisplayServerX11::window_attach_instance_id(ObjectID p_instance, WindowID p_window) { - ERR_FAIL_COND(!windows.has(p_window)); - WindowData &wd = windows[p_window]; - - wd.instance_id = p_instance; -} - -ObjectID DisplayServerX11::window_get_attached_instance_id(WindowID p_window) const { - ERR_FAIL_COND_V(!windows.has(p_window), ObjectID()); - const WindowData &wd = windows[p_window]; - return wd.instance_id; -} - -DisplayServerX11::WindowID DisplayServerX11::get_window_at_screen_position(const Point2i &p_position) const { - WindowID found_window = INVALID_WINDOW_ID; - WindowID parent_window = INVALID_WINDOW_ID; - unsigned int focus_order = 0; - for (const KeyValue &E : windows) { - const WindowData &wd = E.value; - - // 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)); - WindowData &wd = windows[p_window]; - - XStoreName(x11_display, wd.x11_window, p_title.utf8().get_data()); - - Atom _net_wm_name = XInternAtom(x11_display, "_NET_WM_NAME", false); - Atom utf8_string = XInternAtom(x11_display, "UTF8_STRING", false); - 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_mouse_passthrough(const Vector &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)); - WindowData &wd = windows[p_window]; - wd.rect_changed_callback = p_callable; -} - -void DisplayServerX11::window_set_window_event_callback(const Callable &p_callable, WindowID p_window) { - _THREAD_SAFE_METHOD_ - - ERR_FAIL_COND(!windows.has(p_window)); - WindowData &wd = windows[p_window]; - wd.event_callback = p_callable; -} - -void DisplayServerX11::window_set_input_event_callback(const Callable &p_callable, WindowID p_window) { - _THREAD_SAFE_METHOD_ - - ERR_FAIL_COND(!windows.has(p_window)); - WindowData &wd = windows[p_window]; - wd.input_event_callback = p_callable; -} - -void DisplayServerX11::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) { - _THREAD_SAFE_METHOD_ - - ERR_FAIL_COND(!windows.has(p_window)); - WindowData &wd = windows[p_window]; - wd.input_text_callback = p_callable; -} - -void DisplayServerX11::window_set_drop_files_callback(const Callable &p_callable, WindowID p_window) { - _THREAD_SAFE_METHOD_ - - ERR_FAIL_COND(!windows.has(p_window)); - WindowData &wd = windows[p_window]; - wd.drop_files_callback = p_callable; -} - -int DisplayServerX11::window_get_current_screen(WindowID p_window) const { - _THREAD_SAFE_METHOD_ - - int count = get_screen_count(); - if (count < 2) { - // Early exit with single monitor. - return 0; - } - - ERR_FAIL_COND_V(!windows.has(p_window), 0); - const WindowData &wd = windows[p_window]; - - const Rect2i window_rect(wd.position, wd.size); - - // Find which monitor has the largest overlap with the given window. - int screen_index = 0; - int max_area = 0; - for (int i = 0; i < count; i++) { - Rect2i screen_rect = _screen_get_rect(i); - Rect2i intersection = screen_rect.intersection(window_rect); - int area = intersection.get_area(); - if (area > max_area) { - max_area = area; - screen_index = i; - } - } - - return screen_index; -} - -void DisplayServerX11::gl_window_make_current(DisplayServer::WindowID p_window_id) { -#if defined(GLES3_ENABLED) - if (gl_manager) { - gl_manager->window_make_current(p_window_id); - } -#endif -} - -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]; - - if (p_screen == SCREEN_OF_MAIN_WINDOW) { - p_screen = window_get_current_screen(); - } - - // Check if screen is valid - ERR_FAIL_INDEX(p_screen, get_screen_count()); - - if (window_get_mode(p_window) == WINDOW_MODE_FULLSCREEN) { - Point2i position = screen_get_position(p_screen); - Size2i size = screen_get_size(p_screen); - - XMoveResizeWindow(x11_display, wd.x11_window, position.x, position.y, size.x, size.y); - } else { - if (p_screen != window_get_current_screen(p_window)) { - Vector2 ofs = window_get_position(p_window) - screen_get_position(window_get_current_screen(p_window)); - window_set_position(ofs + screen_get_position(p_screen), p_window); - } - } -} - -void DisplayServerX11::window_set_transient(WindowID p_window, WindowID p_parent) { - _THREAD_SAFE_METHOD_ - - ERR_FAIL_COND(p_window == p_parent); - - ERR_FAIL_COND(!windows.has(p_window)); - WindowData &wd_window = windows[p_window]; - - WindowID prev_parent = wd_window.transient_parent; - ERR_FAIL_COND(prev_parent == p_parent); - - DEBUG_LOG_X11("window_set_transient: %lu (%u), prev_parent=%u, parent=%u\n", wd_window.x11_window, p_window, 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(prev_parent == INVALID_WINDOW_ID); - ERR_FAIL_COND(!windows.has(prev_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); - - XWindowAttributes xwa; - XSync(x11_display, False); - XGetWindowAttributes(x11_display, wd_parent.x11_window, &xwa); - - // Set focus to parent sub window to avoid losing all focus when closing a nested sub-menu. - // 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.no_focus && !wd_window.is_popup && wd_window.focused) { - if ((xwa.map_state == IsViewable) && !wd_parent.no_focus && !wd_window.is_popup) { - XSetInputFocus(x11_display, wd_parent.x11_window, RevertToPointerRoot, CurrentTime); - } - } - } else { - ERR_FAIL_COND(!windows.has(p_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; - wd_parent.transient_children.insert(p_window); - - XSetTransientForHint(x11_display, wd_window.x11_window, wd_parent.x11_window); - } -} - -// 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()); - const WindowData &wd = windows[p_window]; - - return wd.position; -} - -void DisplayServerX11::window_set_position(const Point2i &p_position, WindowID p_window) { - _THREAD_SAFE_METHOD_ - - ERR_FAIL_COND(!windows.has(p_window)); - WindowData &wd = windows[p_window]; - - int x = 0; - int y = 0; - if (!window_get_flag(WINDOW_FLAG_BORDERLESS, p_window)) { - //exclude window decorations - XSync(x11_display, False); - Atom prop = XInternAtom(x11_display, "_NET_FRAME_EXTENTS", True); - if (prop != None) { - Atom type; - int format; - unsigned long len; - unsigned long remaining; - unsigned char *data = nullptr; - if (XGetWindowProperty(x11_display, wd.x11_window, prop, 0, 4, False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) { - if (format == 32 && len == 4 && data) { - long *extents = (long *)data; - x = extents[0]; - y = extents[2]; - } - XFree(data); - } - } - } - XMoveWindow(x11_display, wd.x11_window, p_position.x - x, p_position.y - y); - _update_real_mouse_position(wd); -} - -void DisplayServerX11::window_set_max_size(const Size2i p_size, WindowID p_window) { - _THREAD_SAFE_METHOD_ - - ERR_FAIL_COND(!windows.has(p_window)); - WindowData &wd = windows[p_window]; - - if ((p_size != Size2i()) && ((p_size.x < wd.min_size.x) || (p_size.y < wd.min_size.y))) { - ERR_PRINT("Maximum window size can't be smaller than minimum window size!"); - return; - } - wd.max_size = p_size; - - _update_size_hints(p_window); - XFlush(x11_display); -} - -Size2i DisplayServerX11::window_get_max_size(WindowID p_window) const { - _THREAD_SAFE_METHOD_ - - ERR_FAIL_COND_V(!windows.has(p_window), Size2i()); - const WindowData &wd = windows[p_window]; - - return wd.max_size; -} - -void DisplayServerX11::window_set_min_size(const Size2i p_size, WindowID p_window) { - _THREAD_SAFE_METHOD_ - - ERR_FAIL_COND(!windows.has(p_window)); - WindowData &wd = windows[p_window]; - - if ((p_size != Size2i()) && (wd.max_size != Size2i()) && ((p_size.x > wd.max_size.x) || (p_size.y > wd.max_size.y))) { - ERR_PRINT("Minimum window size can't be larger than maximum window size!"); - return; - } - wd.min_size = p_size; - - _update_size_hints(p_window); - XFlush(x11_display); -} - -Size2i DisplayServerX11::window_get_min_size(WindowID p_window) const { - _THREAD_SAFE_METHOD_ - - ERR_FAIL_COND_V(!windows.has(p_window), Size2i()); - const WindowData &wd = windows[p_window]; - - return wd.min_size; -} - -void DisplayServerX11::window_set_size(const Size2i p_size, WindowID p_window) { - _THREAD_SAFE_METHOD_ - - ERR_FAIL_COND(!windows.has(p_window)); - - Size2i size = p_size; - size.x = MAX(1, size.x); - size.y = MAX(1, size.y); - - WindowData &wd = windows[p_window]; - - if (wd.size.width == size.width && wd.size.height == size.height) { - return; - } - - XWindowAttributes xwa; - XSync(x11_display, False); - XGetWindowAttributes(x11_display, wd.x11_window, &xwa); - int old_w = xwa.width; - int old_h = xwa.height; - - // 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); - - 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) { - break; - } - - usleep(10000); - } - - // Keep rendering context window size in sync -#if defined(VULKAN_ENABLED) - if (context_vulkan) { - context_vulkan->window_resize(p_window, xwa.width, xwa.height); - } -#endif -#if defined(GLES3_ENABLED) - if (gl_manager) { - gl_manager->window_resize(p_window, xwa.width, xwa.height); - } -#endif -} - -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 { - _THREAD_SAFE_METHOD_ - - ERR_FAIL_COND_V(!windows.has(p_window), Size2i()); - const WindowData &wd = windows[p_window]; - - XWindowAttributes xwa; - XSync(x11_display, False); - XGetWindowAttributes(x11_display, wd.x11_window, &xwa); - int w = xwa.width; - int h = xwa.height; - Atom prop = XInternAtom(x11_display, "_NET_FRAME_EXTENTS", True); - if (prop != None) { - Atom type; - int format; - unsigned long len; - unsigned long remaining; - unsigned char *data = nullptr; - if (XGetWindowProperty(x11_display, wd.x11_window, prop, 0, 4, False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) { - if (format == 32 && len == 4 && data) { - long *extents = (long *)data; - w += extents[0] + extents[1]; // left, right - h += extents[2] + extents[3]; // top, bottom - } - XFree(data); - } - } - return Size2i(w, h); -} - -// 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, 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, - wd.x11_window, - property, - 0, - 1024, - False, - XA_ATOM, - &type, - &format, - &len, - &remaining, - &data); - - if (result == Success && data) { - Atom *atoms = (Atom *)data; - Atom wm_act_max_horz; - Atom wm_act_max_vert; - if (strcmp(p_atom_name, "_NET_WM_STATE") == 0) { - wm_act_max_horz = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_HORZ", False); - wm_act_max_vert = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_VERT", False); - } else { - wm_act_max_horz = XInternAtom(x11_display, "_NET_WM_ACTION_MAXIMIZE_HORZ", False); - wm_act_max_vert = XInternAtom(x11_display, "_NET_WM_ACTION_MAXIMIZE_VERT", False); - } - bool found_wm_act_max_horz = false; - bool found_wm_act_max_vert = false; - - for (uint64_t i = 0; i < len; i++) { - if (atoms[i] == wm_act_max_horz) { - found_wm_act_max_horz = true; - } - if (atoms[i] == wm_act_max_vert) { - found_wm_act_max_vert = true; - } - - if (found_wm_act_max_horz || found_wm_act_max_vert) { - retval = true; - break; - } - } - - XFree(data); - } - - return retval; -} - -bool DisplayServerX11::_window_minimize_check(WindowID p_window) const { - const WindowData &wd = windows[p_window]; - - // Using ICCCM -- Inter-Client Communication Conventions Manual - Atom property = XInternAtom(x11_display, "WM_STATE", True); - if (property == None) { - return false; - } - - Atom type; - int format; - unsigned long len; - unsigned long remaining; - unsigned char *data = nullptr; - - int result = XGetWindowProperty( - x11_display, - wd.x11_window, - property, - 0, - 32, - False, - AnyPropertyType, - &type, - &format, - &len, - &remaining, - &data); - - if (result == Success && data) { - long *state = (long *)data; - if (state[0] == WM_IconicState) { - XFree(data); - return true; - } - XFree(data); - } - - return false; -} - -bool DisplayServerX11::_window_fullscreen_check(WindowID p_window) const { - ERR_FAIL_COND_V(!windows.has(p_window), false); - const WindowData &wd = windows[p_window]; - - // 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; - - if (property == None) { - return retval; - } - - int result = XGetWindowProperty( - x11_display, - wd.x11_window, - property, - 0, - 1024, - False, - XA_ATOM, - &type, - &format, - &len, - &remaining, - &data); - - if (result == Success) { - Atom *atoms = (Atom *)data; - Atom wm_fullscreen = XInternAtom(x11_display, "_NET_WM_STATE_FULLSCREEN", False); - for (uint64_t i = 0; i < len; i++) { - if (atoms[i] == wm_fullscreen) { - retval = true; - break; - } - } - XFree(data); - } - - return retval; -} - -void DisplayServerX11::_validate_mode_on_map(WindowID p_window) { - // Check if we applied any window modes that didn't take effect while unmapped - const WindowData &wd = windows[p_window]; - if (wd.fullscreen && !_window_fullscreen_check(p_window)) { - _set_wm_fullscreen(p_window, true); - } else if (wd.maximized && !_window_maximize_check(p_window, "_NET_WM_STATE")) { - _set_wm_maximized(p_window, true); - } else if (wd.minimized && !_window_minimize_check(p_window)) { - _set_wm_minimized(p_window, true); - } -} - -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]; - - // Using EWMH -- Extended Window Manager Hints - XEvent xev; - Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False); - 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); - - memset(&xev, 0, sizeof(xev)); - xev.type = ClientMessage; - xev.xclient.window = wd.x11_window; - xev.xclient.message_type = wm_state; - xev.xclient.format = 32; - xev.xclient.data.l[0] = p_enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; - xev.xclient.data.l[1] = wm_max_horz; - xev.xclient.data.l[2] = wm_max_vert; - - XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); - - if (p_enabled && window_is_maximize_allowed(p_window)) { - // Wait for effective resizing (so the GLX context is too). - // Give up after 0.5s, it's not going to happen on this WM. - // https://github.com/godotengine/godot/issues/19978 - for (int attempt = 0; window_get_mode(p_window) != WINDOW_MODE_MAXIMIZED && attempt < 50; attempt++) { - usleep(10000); - } - } - wd.maximized = p_enabled; -} - -void DisplayServerX11::_set_wm_minimized(WindowID p_window, bool p_enabled) { - WindowData &wd = windows[p_window]; - // Using ICCCM -- Inter-Client Communication Conventions Manual - XEvent xev; - Atom wm_change = XInternAtom(x11_display, "WM_CHANGE_STATE", False); - - memset(&xev, 0, sizeof(xev)); - xev.type = ClientMessage; - xev.xclient.window = wd.x11_window; - xev.xclient.message_type = wm_change; - xev.xclient.format = 32; - xev.xclient.data.l[0] = p_enabled ? WM_IconicState : WM_NormalState; - - XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); - - Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False); - Atom wm_hidden = XInternAtom(x11_display, "_NET_WM_STATE_HIDDEN", False); - - memset(&xev, 0, sizeof(xev)); - xev.type = ClientMessage; - xev.xclient.window = wd.x11_window; - xev.xclient.message_type = wm_state; - xev.xclient.format = 32; - xev.xclient.data.l[0] = p_enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; - xev.xclient.data.l[1] = wm_hidden; - - XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); - wd.minimized = 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]; - - if (p_enabled && !window_get_flag(WINDOW_FLAG_BORDERLESS, p_window)) { - // remove decorations if the window is not already borderless - Hints hints; - Atom property; - hints.flags = 2; - hints.decorations = 0; - property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True); - if (property != None) { - XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5); - } - } - - if (p_enabled) { - // Set the window as resizable to prevent window managers to ignore the fullscreen state flag. - _update_size_hints(p_window); - } - - // Using EWMH -- Extended Window Manager Hints - XEvent xev; - Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False); - Atom wm_fullscreen = XInternAtom(x11_display, "_NET_WM_STATE_FULLSCREEN", False); - - memset(&xev, 0, sizeof(xev)); - xev.type = ClientMessage; - xev.xclient.window = wd.x11_window; - xev.xclient.message_type = wm_state; - xev.xclient.format = 32; - xev.xclient.data.l[0] = p_enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; - xev.xclient.data.l[1] = wm_fullscreen; - xev.xclient.data.l[2] = 0; - - XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); - - // set bypass compositor hint - Atom bypass_compositor = XInternAtom(x11_display, "_NET_WM_BYPASS_COMPOSITOR", False); - unsigned long compositing_disable_on = p_enabled ? 1 : 0; - 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. - _update_size_hints(p_window); - - // put back or remove decorations according to the last set borderless state - Hints hints; - Atom property; - hints.flags = 2; - hints.decorations = wd.borderless ? 0 : 1; - 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); - } - } -} - -void DisplayServerX11::window_set_mode(WindowMode p_mode, WindowID p_window) { - _THREAD_SAFE_METHOD_ - - ERR_FAIL_COND(!windows.has(p_window)); - WindowData &wd = windows[p_window]; - - WindowMode old_mode = window_get_mode(p_window); - if (old_mode == p_mode) { - return; // do nothing - } - //remove all "extra" modes - - switch (old_mode) { - case WINDOW_MODE_WINDOWED: { - //do nothing - } break; - case WINDOW_MODE_MINIMIZED: { - _set_wm_minimized(p_window, false); - } break; - case WINDOW_MODE_EXCLUSIVE_FULLSCREEN: - 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); - - window_set_position(wd.last_position_before_fs, p_window); - - if (on_top) { - _set_wm_maximized(p_window, false); - } - - } break; - case WINDOW_MODE_MAXIMIZED: { - _set_wm_maximized(p_window, false); - } break; - } - - switch (p_mode) { - case WINDOW_MODE_WINDOWED: { - //do nothing - } break; - case WINDOW_MODE_MINIMIZED: { - _set_wm_minimized(p_window, true); - } break; - case WINDOW_MODE_EXCLUSIVE_FULLSCREEN: - 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); - } - - 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); - const WindowData &wd = windows[p_window]; - - if (wd.fullscreen) { //if fullscreen, it's not in another mode - return WINDOW_MODE_FULLSCREEN; - } - - // Test maximized. - // Using EWMH -- Extended Window Manager Hints - if (_window_maximize_check(p_window, "_NET_WM_STATE")) { - return WINDOW_MODE_MAXIMIZED; - } - - { - if (_window_minimize_check(p_window)) { - return WINDOW_MODE_MINIMIZED; - } - } - - // All other discarded, return windowed. - - return WINDOW_MODE_WINDOWED; -} - -void DisplayServerX11::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) { - _THREAD_SAFE_METHOD_ - - ERR_FAIL_COND(!windows.has(p_window)); - WindowData &wd = windows[p_window]; - - switch (p_flag) { - case WINDOW_FLAG_RESIZE_DISABLED: { - wd.resize_disabled = p_enabled; - - _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); - 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); - - wd.borderless = p_enabled; - } break; - case WINDOW_FLAG_ALWAYS_ON_TOP: { - ERR_FAIL_COND_MSG(wd.transient_parent != INVALID_WINDOW_ID, "Can't make a window transient if the 'on top' flag is active."); - if (p_enabled && wd.fullscreen) { - _set_wm_maximized(p_window, true); - } - - Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False); - Atom wm_above = XInternAtom(x11_display, "_NET_WM_STATE_ABOVE", False); - - XClientMessageEvent xev; - memset(&xev, 0, sizeof(xev)); - xev.type = ClientMessage; - xev.window = wd.x11_window; - xev.message_type = wm_state; - xev.format = 32; - xev.data.l[0] = p_enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; - xev.data.l[1] = wm_above; - xev.data.l[3] = 1; - XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent *)&xev); - - if (!p_enabled && !wd.fullscreen) { - _set_wm_maximized(p_window, false); - } - wd.on_top = p_enabled; - - } break; - case WINDOW_FLAG_TRANSPARENT: { - wd.layered_window = p_enabled; - } break; - case WINDOW_FLAG_NO_FOCUS: { - wd.no_focus = p_enabled; - } break; - case WINDOW_FLAG_POPUP: { - XWindowAttributes xwa; - XSync(x11_display, False); - XGetWindowAttributes(x11_display, wd.x11_window, &xwa); - - ERR_FAIL_COND_MSG(p_window == MAIN_WINDOW_ID, "Main window can't be popup."); - ERR_FAIL_COND_MSG((xwa.map_state == IsViewable) && (wd.is_popup != p_enabled), "Popup flag can't changed while window is opened."); - wd.is_popup = p_enabled; - } break; - default: { - } - } -} - -bool DisplayServerX11::window_get_flag(WindowFlags p_flag, WindowID p_window) const { - _THREAD_SAFE_METHOD_ - - ERR_FAIL_COND_V(!windows.has(p_window), false); - const WindowData &wd = windows[p_window]; - - switch (p_flag) { - case WINDOW_FLAG_RESIZE_DISABLED: { - return wd.resize_disabled; - } break; - case WINDOW_FLAG_BORDERLESS: { - bool borderless = wd.borderless; - Atom prop = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True); - if (prop != None) { - Atom type; - int format; - unsigned long len; - unsigned long remaining; - unsigned char *data = nullptr; - if (XGetWindowProperty(x11_display, wd.x11_window, prop, 0, sizeof(Hints), False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) { - if (data && (format == 32) && (len >= 5)) { - borderless = !(reinterpret_cast(data)->decorations); - } - if (data) { - XFree(data); - } - } - } - return borderless; - } break; - case WINDOW_FLAG_ALWAYS_ON_TOP: { - return wd.on_top; - } break; - case WINDOW_FLAG_TRANSPARENT: { - return wd.layered_window; - } break; - case WINDOW_FLAG_NO_FOCUS: { - return wd.no_focus; - } break; - case WINDOW_FLAG_POPUP: { - return wd.is_popup; - } break; - default: { - } - } - - return false; -} - -void DisplayServerX11::window_request_attention(WindowID p_window) { - _THREAD_SAFE_METHOD_ - - ERR_FAIL_COND(!windows.has(p_window)); - const WindowData &wd = windows[p_window]; - // Using EWMH -- Extended Window Manager Hints - // - // Sets the _NET_WM_STATE_DEMANDS_ATTENTION atom for WM_STATE - // Will be unset by the window manager after user react on the request for attention - - XEvent xev; - Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False); - Atom wm_attention = XInternAtom(x11_display, "_NET_WM_STATE_DEMANDS_ATTENTION", False); - - memset(&xev, 0, sizeof(xev)); - xev.type = ClientMessage; - xev.xclient.window = wd.x11_window; - xev.xclient.message_type = wm_state; - xev.xclient.format = 32; - xev.xclient.data.l[0] = _NET_WM_STATE_ADD; - xev.xclient.data.l[1] = wm_attention; - - XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); - XFlush(x11_display); -} - -void DisplayServerX11::window_move_to_foreground(WindowID p_window) { - _THREAD_SAFE_METHOD_ - - ERR_FAIL_COND(!windows.has(p_window)); - const WindowData &wd = windows[p_window]; - - XEvent xev; - Atom net_active_window = XInternAtom(x11_display, "_NET_ACTIVE_WINDOW", False); - - memset(&xev, 0, sizeof(xev)); - xev.type = ClientMessage; - xev.xclient.window = wd.x11_window; - xev.xclient.message_type = net_active_window; - xev.xclient.format = 32; - xev.xclient.data.l[0] = 1; - xev.xclient.data.l[1] = CurrentTime; - - XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); - XFlush(x11_display); -} - -bool DisplayServerX11::window_can_draw(WindowID p_window) const { - //this seems to be all that is provided by X11 - return window_get_mode(p_window) != WINDOW_MODE_MINIMIZED; -} - -bool DisplayServerX11::can_any_window_draw() const { - _THREAD_SAFE_METHOD_ - - for (const KeyValue &E : windows) { - if (window_get_mode(E.key) != WINDOW_MODE_MINIMIZED) { - return true; - } - } - - return false; -} - -void DisplayServerX11::window_set_ime_active(const bool p_active, WindowID p_window) { - _THREAD_SAFE_METHOD_ - - ERR_FAIL_COND(!windows.has(p_window)); - WindowData &wd = windows[p_window]; - - wd.im_active = p_active; - - if (!wd.xic) { - return; - } - - // Block events polling while changing input focus - // because it triggers some event polling internally. - if (p_active) { - { - 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) { - _THREAD_SAFE_METHOD_ - - ERR_FAIL_COND(!windows.has(p_window)); - WindowData &wd = windows[p_window]; - - wd.im_position = p_pos; - - if (!wd.xic) { - return; - } - - ::XPoint spot; - spot.x = short(p_pos.x); - spot.y = short(p_pos.y); - XVaNestedList preedit_attr = XVaCreateNestedList(0, XNSpotLocation, &spot, 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); - - if (p_shape == current_cursor) { - return; - } - - if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) { - if (cursors[p_shape] != None) { - for (const KeyValue &E : windows) { - XDefineCursor(x11_display, E.value.x11_window, cursors[p_shape]); - } - } else if (cursors[CURSOR_ARROW] != None) { - for (const KeyValue &E : windows) { - XDefineCursor(x11_display, E.value.x11_window, cursors[CURSOR_ARROW]); - } - } - } - - current_cursor = p_shape; -} - -DisplayServerX11::CursorShape DisplayServerX11::cursor_get_shape() const { - return current_cursor; -} - -void DisplayServerX11::cursor_set_custom_image(const Ref &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) { - _THREAD_SAFE_METHOD_ - - if (p_cursor.is_valid()) { - HashMap>::Iterator cursor_c = cursors_cache.find(p_shape); - - if (cursor_c) { - if (cursor_c->value[0] == p_cursor && cursor_c->value[1] == p_hotspot) { - cursor_set_shape(p_shape); - return; - } - - cursors_cache.erase(p_shape); - } - - Ref texture = p_cursor; - Ref atlas_texture = p_cursor; - Ref image; - Size2i texture_size; - Rect2i atlas_rect; - - if (texture.is_valid()) { - image = texture->get_image(); - } - - if (!image.is_valid() && atlas_texture.is_valid()) { - texture = atlas_texture->get_atlas(); - - atlas_rect.size.width = texture->get_width(); - atlas_rect.size.height = texture->get_height(); - atlas_rect.position.x = atlas_texture->get_region().position.x; - atlas_rect.position.y = atlas_texture->get_region().position.y; - - texture_size.width = atlas_texture->get_region().size.x; - texture_size.height = atlas_texture->get_region().size.y; - } else if (image.is_valid()) { - texture_size.width = texture->get_width(); - texture_size.height = texture->get_height(); - } - - ERR_FAIL_COND(!texture.is_valid()); - ERR_FAIL_COND(p_hotspot.x < 0 || p_hotspot.y < 0); - ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256); - ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height); - - image = texture->get_image(); - - ERR_FAIL_COND(!image.is_valid()); - - // Create the cursor structure - XcursorImage *cursor_image = XcursorImageCreate(texture_size.width, texture_size.height); - XcursorUInt image_size = texture_size.width * texture_size.height; - XcursorDim size = sizeof(XcursorPixel) * image_size; - - cursor_image->version = 1; - cursor_image->size = size; - cursor_image->xhot = p_hotspot.x; - cursor_image->yhot = p_hotspot.y; - - // allocate memory to contain the whole file - cursor_image->pixels = (XcursorPixel *)memalloc(size); - - for (XcursorPixel index = 0; index < image_size; index++) { - int row_index = floor(index / texture_size.width) + atlas_rect.position.y; - int column_index = (index % int(texture_size.width)) + atlas_rect.position.x; - - if (atlas_texture.is_valid()) { - column_index = MIN(column_index, atlas_rect.size.width - 1); - row_index = MIN(row_index, atlas_rect.size.height - 1); - } - - *(cursor_image->pixels + index) = image->get_pixel(column_index, row_index).to_argb32(); - } - - ERR_FAIL_COND(cursor_image->pixels == nullptr); - - // Save it for a further usage - cursors[p_shape] = XcursorImageLoadCursor(x11_display, cursor_image); - - Vector params; - params.push_back(p_cursor); - params.push_back(p_hotspot); - cursors_cache.insert(p_shape, params); - - if (p_shape == current_cursor) { - if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) { - for (const KeyValue &E : windows) { - XDefineCursor(x11_display, E.value.x11_window, cursors[p_shape]); - } - } - } - - memfree(cursor_image->pixels); - XcursorImageDestroy(cursor_image); - } else { - // Reset to default system cursor - if (img[p_shape]) { - cursors[p_shape] = XcursorImageLoadCursor(x11_display, img[p_shape]); - } - - CursorShape c = current_cursor; - current_cursor = CURSOR_MAX; - cursor_set_shape(c); - - cursors_cache.erase(p_shape); - } -} - -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 != nullptr) { - _group_count = kbd->ctrls->num_groups; - } else { - while (_group_count < XkbNumKbdGroups && groups[_group_count] != None) { - _group_count++; - } - } - XkbFreeKeyboard(kbd, 0, true); - } - return _group_count; -} - -int DisplayServerX11::keyboard_get_current_layout() const { - XkbStateRec state; - XkbGetState(x11_display, XkbUseCoreKbd, &state); - return state.group; -} - -void DisplayServerX11::keyboard_set_current_layout(int p_index) { - ERR_FAIL_INDEX(p_index, keyboard_get_layout_count()); - XkbLockGroup(x11_display, XkbUseCoreKbd, p_index); -} - -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 != nullptr) { - _group_count = kbd->ctrls->num_groups; - } else { - while (_group_count < XkbNumKbdGroups && groups[_group_count] != None) { - _group_count++; - } - } - - Atom names = kbd->names->symbols; - if (names != None) { - Vector info = get_atom_name(x11_display, names).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) + ")."); - } - } - XkbFreeKeyboard(kbd, 0, true); - } - return ret.substr(0, 2); -} - -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 != nullptr) { - _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) { - ret = get_atom_name(x11_display, groups[p_index]); - } else { - ERR_PRINT("Index " + itos(p_index) + "is out of bounds (" + itos(_group_count) + ")."); - } - XkbFreeKeyboard(kbd, 0, true); - } - return ret; -} - -Key DisplayServerX11::keyboard_get_keycode_from_physical(Key p_keycode) const { - Key modifiers = p_keycode & KeyModifierMask::MODIFIER_MASK; - Key keycode_no_mod = p_keycode & KeyModifierMask::CODE_MASK; - unsigned int xkeycode = KeyMappingX11::get_xlibcode(keycode_no_mod); - KeySym xkeysym = XkbKeycodeToKeysym(x11_display, xkeycode, 0, 0); - if (is_ascii_lower_case(xkeysym)) { - xkeysym -= ('a' - 'A'); - } - - Key key = KeyMappingX11::get_keycode(xkeysym); - // If not found, fallback to QWERTY. - // This should match the behavior of the event pump - if (key == Key::NONE) { - return p_keycode; - } - return (Key)(key | modifiers); -} - -DisplayServerX11::Property DisplayServerX11::_read_property(Display *p_display, Window p_window, Atom p_property) { - Atom actual_type = None; - int actual_format = 0; - unsigned long nitems = 0; - unsigned long bytes_after = 0; - unsigned char *ret = nullptr; - - // Keep trying to read the property until there are no bytes unread. - if (p_property != None) { - int read_bytes = 1024; - 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); - - read_bytes *= 2; - - } while (bytes_after != 0); - } - - Property p = { ret, actual_format, (int)nitems, actual_type }; - - return p; -} - -static Atom pick_target_from_list(Display *p_display, const 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 && get_atom_name(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 && get_atom_name(p_disp, p_t1) == target_type) { - return p_t1; - } - - if (p_t2 != None && get_atom_name(p_disp, p_t2) == target_type) { - return p_t2; - } - - if (p_t3 != None && get_atom_name(p_disp, p_t3) == target_type) { - return p_t3; - } - - return None; -} - -void DisplayServerX11::_get_key_modifier_state(unsigned int p_x11_state, Ref state) { - state->set_shift_pressed((p_x11_state & ShiftMask)); - state->set_ctrl_pressed((p_x11_state & ControlMask)); - state->set_alt_pressed((p_x11_state & Mod1Mask /*|| p_x11_state&Mod5Mask*/)); //altgr should not count as alt - state->set_meta_pressed((p_x11_state & Mod4Mask)); -} - -MouseButton DisplayServerX11::_get_mouse_button_state(MouseButton p_x11_button, int p_x11_type) { - MouseButton mask = mouse_button_to_mask(p_x11_button); - - if (p_x11_type == ButtonPress) { - last_button_state |= mask; - } else { - last_button_state &= ~mask; - } - - return last_button_state; -} - -void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, LocalVector &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; - - // This code was pretty difficult to write. - // The docs stink and every toolkit seems to - // do it in a different way. - - /* Phase 1, obtain a proper keysym */ - - // This was also very difficult to figure out. - // You'd expect you could just use Keysym provided by - // XKeycodeToKeysym to obtain internationalized - // input.. WRONG!! - // you must use XLookupString (???) which not only wastes - // cycles generating an unnecessary string, but also - // still works in half the cases. (won't handle deadkeys) - // For more complex input methods (deadkeys and more advanced) - // you have to use XmbLookupString (??). - // So then you have to choose which of both results - // you want to keep. - // This is a real bizarreness and cpu waster. - - KeySym keysym_keycode = 0; // keysym used to find a keycode - KeySym keysym_unicode = 0; // keysym used to find unicode - - // XLookupString returns keysyms usable as nice keycodes. - char str[256 + 1]; - XKeyEvent xkeyevent_no_mod = *xkeyevent; - xkeyevent_no_mod.state &= ~ShiftMask; - xkeyevent_no_mod.state &= ~ControlMask; - XLookupString(xkeyevent, str, 256, &keysym_unicode, nullptr); - XLookupString(&xkeyevent_no_mod, nullptr, 0, &keysym_keycode, nullptr); - - // Meanwhile, XLookupString returns keysyms useful for unicode. - - if (!xmbstring) { - // keep a temporary buffer for the string - xmbstring = (char *)memalloc(sizeof(char) * 8); - xmblen = 8; - } - - if (xkeyevent->type == KeyPress && wd.xic) { - Status status; -#ifdef X_HAVE_UTF8_STRING - int utf8len = 8; - char *utf8string = (char *)memalloc(sizeof(char) * utf8len); - int utf8bytes = Xutf8LookupString(wd.xic, xkeyevent, utf8string, - utf8len - 1, &keysym_unicode, &status); - if (status == XBufferOverflow) { - utf8len = utf8bytes + 1; - utf8string = (char *)memrealloc(utf8string, utf8len); - utf8bytes = Xutf8LookupString(wd.xic, xkeyevent, utf8string, - utf8len - 1, &keysym_unicode, &status); - } - utf8string[utf8bytes] = '\0'; - - if (status == XLookupChars) { - bool keypress = xkeyevent->type == KeyPress; - Key keycode = KeyMappingX11::get_keycode(keysym_keycode); - Key physical_keycode = KeyMappingX11::get_scancode(xkeyevent->keycode); - - if (keycode >= Key::A + 32 && keycode <= Key::Z + 32) { - keycode -= 'a' - 'A'; - } - - String tmp; - tmp.parse_utf8(utf8string, utf8bytes); - for (int i = 0; i < tmp.length(); i++) { - Ref k; - k.instantiate(); - if (physical_keycode == Key::NONE && keycode == Key::NONE && tmp[i] == 0) { - continue; - } - - if (keycode == Key::NONE) { - keycode = (Key)physical_keycode; - } - - _get_key_modifier_state(xkeyevent->state, k); - - k->set_window_id(p_window); - k->set_unicode(tmp[i]); - - k->set_pressed(keypress); - - k->set_keycode(keycode); - - k->set_physical_keycode((Key)physical_keycode); - - k->set_echo(false); - - if (k->get_keycode() == Key::BACKTAB) { - //make it consistent across platforms. - k->set_keycode(Key::TAB); - k->set_physical_keycode(Key::TAB); - k->set_shift_pressed(true); - } - - Input::get_singleton()->parse_input_event(k); - } - memfree(utf8string); - return; - } - memfree(utf8string); -#else - do { - int mnbytes = XmbLookupString(xic, xkeyevent, xmbstring, xmblen - 1, &keysym_unicode, &status); - xmbstring[mnbytes] = '\0'; - - if (status == XBufferOverflow) { - xmblen = mnbytes + 1; - xmbstring = (char *)memrealloc(xmbstring, xmblen); - } - } while (status == XBufferOverflow); -#endif - } - - /* Phase 2, obtain a Godot keycode from the keysym */ - - // KeyMappingX11 just translated the X11 keysym to a PIGUI - // keysym, so it works in all platforms the same. - - Key keycode = KeyMappingX11::get_keycode(keysym_keycode); - Key physical_keycode = KeyMappingX11::get_scancode(xkeyevent->keycode); - - /* Phase 3, obtain a unicode character from the keysym */ - - // KeyMappingX11 also translates keysym to unicode. - // It does a binary search on a table to translate - // most properly. - unsigned int unicode = keysym_unicode > 0 ? KeyMappingX11::get_unicode_from_keysym(keysym_unicode) : 0; - - /* Phase 4, determine if event must be filtered */ - - // This seems to be a side-effect of using XIM. - // 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. - // Guess it was a design problem of the extension - - bool keypress = xkeyevent->type == KeyPress; - - if (physical_keycode == Key::NONE && keycode == Key::NONE && unicode == 0) { - return; - } - - if (keycode == Key::NONE) { - keycode = (Key)physical_keycode; - } - - /* Phase 5, determine modifier mask */ - - // No problems here, except I had no way to - // know Mod1 was ALT and Mod4 was META (applekey/winkey) - // just tried Mods until i found them. - - //print_verbose("mod1: "+itos(xkeyevent->state&Mod1Mask)+" mod 5: "+itos(xkeyevent->state&Mod5Mask)); - - Ref k; - k.instantiate(); - k->set_window_id(p_window); - - _get_key_modifier_state(xkeyevent->state, k); - - /* Phase 6, determine echo character */ - - // Echo characters in X11 are a keyrelease and a keypress - // one after the other with the (almot) same timestamp. - // 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 (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 - // jitter. I'm still not convinced that all this approach - // is correct, but the xorg developers are - // not very helpful today. - -#define ABSDIFF(x, y) (((x) < (y)) ? ((y) - (x)) : ((x) - (y))) - ::Time threshold = ABSDIFF(peek_event.xkey.time, xkeyevent->time); -#undef ABSDIFF - if (peek_event.type == KeyPress && threshold < 5) { - KeySym rk; - XLookupString((XKeyEvent *)&peek_event, str, 256, &rk, nullptr); - if (rk == keysym_keycode) { - // 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 - } - } - - // use the time from peek_event so it always works - } - - // save the time to check for echo when keypress happens - } - - /* Phase 7, send event to Window */ - - k->set_pressed(keypress); - - if (keycode >= Key::A + 32 && keycode <= Key::Z + 32) { - keycode -= int('a' - 'A'); - } - - k->set_keycode(keycode); - k->set_physical_keycode((Key)physical_keycode); - k->set_unicode(unicode); - k->set_echo(p_echo); - - if (k->get_keycode() == Key::BACKTAB) { - //make it consistent across platforms. - k->set_keycode(Key::TAB); - k->set_physical_keycode(Key::TAB); - k->set_shift_pressed(true); - } - - //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) { - k->set_shift_pressed(false); - } else if (k->get_keycode() == Key::CTRL) { - k->set_ctrl_pressed(false); - } else if (k->get_keycode() == Key::ALT) { - k->set_alt_pressed(false); - } else if (k->get_keycode() == Key::META) { - k->set_meta_pressed(false); - } - } - - bool last_is_pressed = Input::get_singleton()->is_key_pressed(k->get_keycode()); - if (k->is_pressed()) { - if (last_is_pressed) { - k->set_echo(true); - } - } - - Input::get_singleton()->parse_input_event(k); -} - -Atom DisplayServerX11::_process_selection_request_target(Atom p_target, Window p_requestor, Atom p_property, Atom p_selection) 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; - static const char *target_type = "PRIMARY"; - if (p_selection != None && get_atom_name(x11_display, p_selection) == target_type) { - clip = internal_clipboard_primary.utf8(); - } else { - 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, p_event->selection); - } - - 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, p_event->selection); - } - - 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(client_data); - ds->xim = nullptr; - - for (KeyValue &E : ds->windows) { - E.value.xic = nullptr; - } -} - -void DisplayServerX11::_window_changed(XEvent *event) { - WindowID window_id = MAIN_WINDOW_ID; - - // Assign the event to the relevant window - for (const KeyValue &E : windows) { - if (event->xany.window == E.value.x11_window) { - window_id = E.key; - break; - } - } - - Rect2i new_rect; - - WindowData &wd = windows[window_id]; - if (wd.x11_window != event->xany.window) { // Check if the correct window, in case it was not main window or anything else - return; - } - - // Query display server about a possible new window state. - wd.fullscreen = _window_fullscreen_check(window_id); - wd.minimized = _window_minimize_check(window_id); - wd.maximized = _window_maximize_check(window_id, "_NET_WM_STATE"); - - { - //the position in xconfigure is not useful here, obtain it manually - int x = 0, y = 0; - Window child; - XTranslateCoordinates(x11_display, wd.x11_window, DefaultRootWindow(x11_display), 0, 0, &x, &y, &child); - new_rect.position.x = x; - new_rect.position.y = y; - - new_rect.size.width = event->xconfigure.width; - new_rect.size.height = event->xconfigure.height; - } - - if (new_rect == Rect2i(wd.position, wd.size)) { - return; - } - if (wd.xic) { - // Not portable. - window_set_ime_position(Point2(0, 1)); - } - - wd.position = new_rect.position; - wd.size = new_rect.size; - -#if defined(VULKAN_ENABLED) - if (context_vulkan) { - context_vulkan->window_resize(window_id, wd.size.width, wd.size.height); - } -#endif -#if defined(GLES3_ENABLED) - if (gl_manager) { - gl_manager->window_resize(window_id, wd.size.width, wd.size.height); - } -#endif - - if (!wd.rect_changed_callback.is_null()) { - Rect2i r = new_rect; - - Variant rect = r; - - Variant *rectp = ▭ - Variant ret; - Callable::CallError ce; - wd.rect_changed_callback.callp((const Variant **)&rectp, 1, ret, ce); - } -} - -DisplayServer::WindowID DisplayServerX11::_get_focused_window_or_popup() const { - const List::Element *E = popup_list.back(); - if (E) { - return E->get(); - } - - return last_focused_window; -} - -void DisplayServerX11::_dispatch_input_events(const Ref &p_event) { - static_cast(get_singleton())->_dispatch_input_event(p_event); -} - -void DisplayServerX11::_dispatch_input_event(const Ref &p_event) { - Variant ev = p_event; - Variant *evp = &ev; - Variant ret; - Callable::CallError ce; - - { - List::Element *E = popup_list.back(); - if (E && Object::cast_to(*p_event)) { - // Redirect keyboard input to active popup. - if (windows.has(E->get())) { - Callable callable = windows[E->get()].input_event_callback; - if (callable.is_valid()) { - callable.callp((const Variant **)&evp, 1, ret, ce); - } - } - return; - } - } - - Ref event_from_window = p_event; - if (event_from_window.is_valid() && event_from_window->get_window_id() != INVALID_WINDOW_ID) { - // Send to a single window. - if (windows.has(event_from_window->get_window_id())) { - Callable callable = windows[event_from_window->get_window_id()].input_event_callback; - if (callable.is_valid()) { - callable.callp((const Variant **)&evp, 1, ret, ce); - } - } - } else { - // Send to all windows. - for (KeyValue &E : windows) { - Callable callable = E.value.input_event_callback; - if (callable.is_valid()) { - callable.callp((const Variant **)&evp, 1, ret, ce); - } - } - } -} - -void DisplayServerX11::_send_window_event(const WindowData &wd, WindowEvent p_event) { - if (!wd.event_callback.is_null()) { - Variant event = int(p_event); - Variant *eventp = &event; - Variant ret; - Callable::CallError ce; - wd.event_callback.callp((const Variant **)&eventp, 1, ret, ce); - } -} - -void DisplayServerX11::_poll_events_thread(void *ud) { - DisplayServerX11 *display_server = static_cast(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, nullptr, nullptr, &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.is_set()) { - _wait_for_events(); - - // Process events from the queue. - { - MutexLock mutex_lock(events_mutex); - - _check_pending_events(polled_events); - } - } -} - -void DisplayServerX11::_check_pending_events(LocalVector &r_events) { - // Flush to make sure to gather all pending events. - XFlush(x11_display); - - // 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; - } - - r_events.push_back(ev); - } -} - -DisplayServer::WindowID DisplayServerX11::window_get_active_popup() const { - const List::Element *E = popup_list.back(); - if (E) { - return E->get(); - } else { - return INVALID_WINDOW_ID; - } -} - -void DisplayServerX11::window_set_popup_safe_rect(WindowID p_window, const Rect2i &p_rect) { - _THREAD_SAFE_METHOD_ - - ERR_FAIL_COND(!windows.has(p_window)); - WindowData &wd = windows[p_window]; - wd.parent_safe_rect = p_rect; -} - -Rect2i DisplayServerX11::window_get_popup_safe_rect(WindowID p_window) const { - _THREAD_SAFE_METHOD_ - - ERR_FAIL_COND_V(!windows.has(p_window), Rect2i()); - const WindowData &wd = windows[p_window]; - return wd.parent_safe_rect; -} - -void DisplayServerX11::popup_open(WindowID p_window) { - _THREAD_SAFE_METHOD_ - - WindowData &wd = windows[p_window]; - if (wd.is_popup) { - // Find current popup parent, or root popup if new window is not transient. - List::Element *C = nullptr; - List::Element *E = popup_list.back(); - while (E) { - if (wd.transient_parent != E->get() || wd.transient_parent == INVALID_WINDOW_ID) { - C = E; - E = E->prev(); - } else { - break; - } - } - if (C) { - _send_window_event(windows[C->get()], DisplayServerX11::WINDOW_EVENT_CLOSE_REQUEST); - } - - time_since_popup = OS::get_singleton()->get_ticks_msec(); - popup_list.push_back(p_window); - } -} - -void DisplayServerX11::popup_close(WindowID p_window) { - _THREAD_SAFE_METHOD_ - - List::Element *E = popup_list.find(p_window); - while (E) { - List::Element *F = E->next(); - WindowID win_id = E->get(); - popup_list.erase(E); - - _send_window_event(windows[win_id], DisplayServerX11::WINDOW_EVENT_CLOSE_REQUEST); - E = F; - } -} - -bool DisplayServerX11::mouse_process_popups() { - _THREAD_SAFE_METHOD_ - - if (popup_list.is_empty()) { - return false; - } - - uint64_t delta = OS::get_singleton()->get_ticks_msec() - time_since_popup; - if (delta < 250) { - return false; - } - - int number_of_screens = XScreenCount(x11_display); - bool closed = false; - for (int i = 0; i < number_of_screens; i++) { - Window root, child; - int root_x, root_y, win_x, win_y; - unsigned int mask; - if (XQueryPointer(x11_display, XRootWindow(x11_display, i), &root, &child, &root_x, &root_y, &win_x, &win_y, &mask)) { - XWindowAttributes root_attrs; - XGetWindowAttributes(x11_display, root, &root_attrs); - Vector2i pos = Vector2i(root_attrs.x + root_x, root_attrs.y + root_y); - if ((pos != last_mouse_monitor_pos) || (mask != last_mouse_monitor_mask)) { - if (((mask & Button1Mask) || (mask & Button2Mask) || (mask & Button3Mask) || (mask & Button4Mask) || (mask & Button5Mask))) { - List::Element *C = nullptr; - List::Element *E = popup_list.back(); - // Find top popup to close. - while (E) { - // Popup window area. - Rect2i win_rect = Rect2i(window_get_position(E->get()), window_get_size(E->get())); - // Area of the parent window, which responsible for opening sub-menu. - Rect2i safe_rect = window_get_popup_safe_rect(E->get()); - if (win_rect.has_point(pos)) { - break; - } else if (safe_rect != Rect2i() && safe_rect.has_point(pos)) { - break; - } else { - C = E; - E = E->prev(); - } - } - if (C) { - _send_window_event(windows[C->get()], DisplayServerX11::WINDOW_EVENT_CLOSE_REQUEST); - closed = true; - } - } - } - last_mouse_monitor_mask = mask; - last_mouse_monitor_pos = pos; - } - } - return closed; -} - -void DisplayServerX11::process_events() { - _THREAD_SAFE_METHOD_ - -#ifdef DISPLAY_SERVER_X11_DEBUG_LOGS_ENABLED - static int frame = 0; - ++frame; -#endif - - bool ignore_events = mouse_process_popups(); - - if (app_focused) { - //verify that one of the windows has focus, else send focus out notification - bool focus_found = false; - for (const KeyValue &E : windows) { - if (E.value.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. - bool mouse_mode_grab = mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN; - - xi.pressure = 0; - xi.tilt = Vector2(); - xi.pressure_supported = false; - - LocalVector events; - { - // Block events polling while flushing events. - MutexLock mutex_lock(events_mutex); - events = polled_events; - polled_events.clear(); - - // Check for more pending events to avoid an extra frame delay. - _check_pending_events(events); - } - - for (uint32_t event_index = 0; event_index < events.size(); ++event_index) { - XEvent &event = events[event_index]; - if (ignore_events) { - XFreeEventData(x11_display, &event.xcookie); - continue; - } - - WindowID window_id = MAIN_WINDOW_ID; - - // Assign the event to the relevant window - for (const KeyValue &E : windows) { - if (event.xany.window == E.value.x11_window) { - window_id = E.key; - break; - } - } - - 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); - - switch (event_data->evtype) { - case XI_HierarchyChanged: - case XI_DeviceChanged: { - _refresh_device_info(); - } break; - case XI_RawMotion: { - XIRawEvent *raw_event = (XIRawEvent *)event_data; - int device_id = raw_event->sourceid; - - // Determine the axis used (called valuators in XInput for some forsaken reason) - // Mask is a bitmask indicating which axes are involved. - // We are interested in the values of axes 0 and 1. - if (raw_event->valuators.mask_len <= 0) { - break; - } - - const double *values = raw_event->raw_values; - - double rel_x = 0.0; - double rel_y = 0.0; - - if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_ABSX)) { - rel_x = *values; - values++; - } - - if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_ABSY)) { - rel_y = *values; - values++; - } - - if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_PRESSURE)) { - HashMap::Iterator pen_pressure = xi.pen_pressure_range.find(device_id); - if (pen_pressure) { - Vector2 pen_pressure_range = pen_pressure->value; - if (pen_pressure_range != Vector2()) { - xi.pressure_supported = true; - xi.pressure = (*values - pen_pressure_range[0]) / - (pen_pressure_range[1] - pen_pressure_range[0]); - } - } - - values++; - } - - if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_TILTX)) { - HashMap::Iterator pen_tilt_x = xi.pen_tilt_x_range.find(device_id); - if (pen_tilt_x) { - Vector2 pen_tilt_x_range = pen_tilt_x->value; - if (pen_tilt_x_range[0] != 0 && *values < 0) { - xi.tilt.x = *values / -pen_tilt_x_range[0]; - } else if (pen_tilt_x_range[1] != 0) { - xi.tilt.x = *values / pen_tilt_x_range[1]; - } - } - - values++; - } - - if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_TILTY)) { - HashMap::Iterator pen_tilt_y = xi.pen_tilt_y_range.find(device_id); - if (pen_tilt_y) { - Vector2 pen_tilt_y_range = pen_tilt_y->value; - if (pen_tilt_y_range[0] != 0 && *values < 0) { - xi.tilt.y = *values / -pen_tilt_y_range[0]; - } else if (pen_tilt_y_range[1] != 0) { - xi.tilt.y = *values / pen_tilt_y_range[1]; - } - } - - values++; - } - - HashMap::Iterator pen_inverted = xi.pen_inverted_devices.find(device_id); - if (pen_inverted) { - xi.pen_inverted = pen_inverted->value; - } - - // https://bugs.freedesktop.org/show_bug.cgi?id=71609 - // http://lists.libsdl.org/pipermail/commits-libsdl.org/2015-June/000282.html - if (raw_event->time == xi.last_relative_time && rel_x == xi.relative_motion.x && rel_y == xi.relative_motion.y) { - break; // Flush duplicate to avoid overly fast motion - } - - xi.old_raw_pos.x = xi.raw_pos.x; - xi.old_raw_pos.y = xi.raw_pos.y; - xi.raw_pos.x = rel_x; - xi.raw_pos.y = rel_y; - - HashMap::Iterator abs_info = xi.absolute_devices.find(device_id); - - if (abs_info) { - // Absolute mode device - Vector2 mult = abs_info->value; - - xi.relative_motion.x += (xi.raw_pos.x - xi.old_raw_pos.x) * mult.x; - xi.relative_motion.y += (xi.raw_pos.y - xi.old_raw_pos.y) * mult.y; - } else { - // Relative mode device - xi.relative_motion.x = xi.raw_pos.x; - xi.relative_motion.y = xi.raw_pos.y; - } - - xi.last_relative_time = raw_event->time; - } break; -#ifdef TOUCH_ENABLED - case XI_TouchBegin: - case XI_TouchEnd: { - bool is_begin = event_data->evtype == XI_TouchBegin; - - Ref st; - st.instantiate(); - st->set_window_id(window_id); - st->set_index(index); - st->set_position(pos); - st->set_pressed(is_begin); - - if (is_begin) { - 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 - // in a spurious mouse motion event being sent to Godot; remember it to be able to filter it out - xi.mouse_pos_to_filter = pos; - } - Input::get_singleton()->parse_input_event(st); - } else { - if (!xi.state.has(index)) { // Defensive - break; - } - xi.state.erase(index); - Input::get_singleton()->parse_input_event(st); - } - } break; - - case XI_TouchUpdate: { - HashMap::Iterator curr_pos_elem = xi.state.find(index); - if (!curr_pos_elem) { // Defensive - break; - } - - if (curr_pos_elem->value != pos) { - Ref sd; - sd.instantiate(); - sd->set_window_id(window_id); - sd->set_index(index); - sd->set_position(pos); - sd->set_relative(pos - curr_pos_elem->value); - Input::get_singleton()->parse_input_event(sd); - - curr_pos_elem->value = pos; - } - } break; -#endif - } - } - } - XFreeEventData(x11_display, &event.xcookie); - - switch (event.type) { - case MapNotify: { - DEBUG_LOG_X11("[%u] MapNotify window=%lu (%u) \n", frame, event.xmap.window, window_id); - - const WindowData &wd = windows[window_id]; - - XWindowAttributes xwa; - XSync(x11_display, False); - XGetWindowAttributes(x11_display, wd.x11_window, &xwa); - - // 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 ((xwa.map_state == IsViewable) && !wd.no_focus && !wd.is_popup) { - XSetInputFocus(x11_display, wd.x11_window, RevertToPointerRoot, CurrentTime); - } - - // Have we failed to set fullscreen while the window was unmapped? - _validate_mode_on_map(window_id); - } break; - - case Expose: { - DEBUG_LOG_X11("[%u] Expose window=%lu (%u), count='%u' \n", frame, event.xexpose.window, window_id, event.xexpose.count); - - windows[window_id].fullscreen = _window_fullscreen_check(window_id); - - Main::force_redraw(); - } 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); - - windows[window_id].minimized = _window_minimize_check(window_id); - } 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: { - 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]; - last_focused_window = 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 (const KeyValue &E : windows) { - if (mouse_mode == MOUSE_MODE_CONFINED) { - XUndefineCursor(x11_display, E.value.x11_window); - } else if (mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN) { // Or re-hide it. - XDefineCursor(x11_display, E.value.x11_window, null_cursor); - } - - XGrabPointer( - x11_display, E.value.x11_window, True, - ButtonPressMask | ButtonReleaseMask | PointerMotionMask, - GrabModeAsync, GrabModeAsync, E.value.x11_window, None, CurrentTime); - } - } -#ifdef TOUCH_ENABLED - // Grab touch devices to avoid OS gesture interference - /*for (int i = 0; i < xi.touch_devices.size(); ++i) { - XIGrabDevice(x11_display, xi.touch_devices[i], x11_window, CurrentTime, None, XIGrabModeAsync, XIGrabModeAsync, False, &xi.touch_event_mask); - }*/ -#endif - - 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); - } - - Input::get_singleton()->release_pressed_events(); - _send_window_event(wd, WINDOW_EVENT_FOCUS_OUT); - - if (mouse_mode_grab) { - for (const KeyValue &E : windows) { - //dear X11, I try, I really try, but you never work, you do whatever you want. - if (mouse_mode == MOUSE_MODE_CAPTURED) { - // Show the cursor if we're in captured mode so it doesn't look weird. - XUndefineCursor(x11_display, E.value.x11_window); - } - } - XUngrabPointer(x11_display, CurrentTime); - } -#ifdef TOUCH_ENABLED - // Ungrab touch devices so input works as usual while we are unfocused - /*for (int i = 0; i < xi.touch_devices.size(); ++i) { - XIUngrabDevice(x11_display, xi.touch_devices[i], CurrentTime); - }*/ - - // Release every pointer to avoid sticky points - for (const KeyValue &E : xi.state) { - Ref st; - st.instantiate(); - st->set_index(E.key); - st->set_window_id(window_id); - st->set_position(E.value); - Input::get_singleton()->parse_input_event(st); - } - xi.state.clear(); -#endif - } 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]; - - XWindowAttributes xwa; - XSync(x11_display, False); - XGetWindowAttributes(x11_display, wd.x11_window, &xwa); - - // 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 ((xwa.map_state == IsViewable) && !wd.no_focus && !wd.is_popup) { - XSetInputFocus(x11_display, wd.x11_window, RevertToPointerRoot, CurrentTime); - } - - _window_changed(&event); - } break; - - case ButtonPress: - case ButtonRelease: { - /* exit in case of a mouse button press */ - last_timestamp = event.xbutton.time; - if (mouse_mode == MOUSE_MODE_CAPTURED) { - event.xbutton.x = last_mouse_pos.x; - event.xbutton.y = last_mouse_pos.y; - } - - Ref mb; - mb.instantiate(); - - mb->set_window_id(window_id); - _get_key_modifier_state(event.xbutton.state, mb); - mb->set_button_index((MouseButton)event.xbutton.button); - if (mb->get_button_index() == MouseButton::RIGHT) { - mb->set_button_index(MouseButton::MIDDLE); - } else if (mb->get_button_index() == MouseButton::MIDDLE) { - mb->set_button_index(MouseButton::RIGHT); - } - 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 && !wd.is_popup) { - 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 = MouseButton::NONE; - mb->set_double_click(true); - } - - } else if (mb->get_button_index() < MouseButton::WHEEL_UP || mb->get_button_index() > MouseButton::WHEEL_RIGHT) { - last_click_button_index = mb->get_button_index(); - } - - if (!mb->is_double_click()) { - 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 (const KeyValue &E : windows) { - const WindowData &wd_other = E.value; - 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()->parse_input_event(mb); - } - break; - } - } - } - } - - Input::get_singleton()->parse_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. - WindowID focused_window_id = _get_focused_window_or_popup(); - if (!windows.has(focused_window_id)) { - focused_window_id = MAIN_WINDOW_ID; - } - - while (true) { - if (mouse_mode == MOUSE_MODE_CAPTURED && event.xmotion.x == windows[focused_window_id].size.width / 2 && event.xmotion.y == windows[focused_window_id].size.height / 2) { - //this is likely the warp event since it was warped here - center = Vector2(event.xmotion.x, event.xmotion.y); - break; - } - - 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; - } - } else { - break; - } - } - - last_timestamp = event.xmotion.time; - - // Motion is also simple. - // A little hack is in order - // to be able to send relative motion events. - Point2i pos(event.xmotion.x, event.xmotion.y); - - // Avoidance of spurious mouse motion (see handling of touch) - bool filter = false; - // Adding some tolerance to match better Point2i to Vector2 - if (xi.state.size() && Vector2(pos).distance_squared_to(xi.mouse_pos_to_filter) < 2) { - filter = true; - } - // Invalidate to avoid filtering a possible legitimate similar event coming later - xi.mouse_pos_to_filter = Vector2(1e10, 1e10); - if (filter) { - 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; - } - - Point2i new_center = pos; - pos = last_mouse_pos + xi.relative_motion; - center = new_center; - 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; - } - - // Hackish but relative mouse motion is already handled in the RawMotion event. - // RawMotion does not provide the absolute mouse position (whereas MotionNotify does). - // Therefore, RawMotion cannot be the authority on absolute mouse position. - // RawMotion provides more precision than MotionNotify, which doesn't sense subpixel motion. - // Therefore, MotionNotify cannot be the authority on relative mouse motion. - // This means we need to take a combined approach... - Point2i rel; - - // Only use raw input if in capture mode. Otherwise use the classic behavior. - if (mouse_mode == MOUSE_MODE_CAPTURED) { - rel = xi.relative_motion; - } else { - rel = pos - last_mouse_pos; - } - - // Reset to prevent lingering motion - xi.relative_motion.x = 0; - xi.relative_motion.y = 0; - if (mouse_mode == MOUSE_MODE_CAPTURED) { - pos = Point2i(windows[focused_window_id].size.width / 2, windows[focused_window_id].size.height / 2); - } - - Ref mm; - mm.instantiate(); - - mm->set_window_id(window_id); - if (xi.pressure_supported) { - mm->set_pressure(xi.pressure); - } else { - mm->set_pressure(bool(mouse_get_button_state() & MouseButton::MASK_LEFT) ? 1.0f : 0.0f); - } - mm->set_tilt(xi.tilt); - mm->set_pen_inverted(xi.pen_inverted); - - _get_key_modifier_state(event.xmotion.state, mm); - mm->set_button_mask((MouseButton)mouse_get_button_state()); - mm->set_position(pos); - mm->set_global_position(pos); - mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity()); - - mm->set_relative(rel); - - last_mouse_pos = pos; - - // printf("rel: %d,%d\n", rel.x, rel.y ); - // 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 (focused) { - Input::get_singleton()->parse_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 (const KeyValue &E : windows) { - const WindowData &wd_other = E.value; - 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_velocity(Input::get_singleton()->get_last_mouse_velocity()); - Input::get_singleton()->parse_input_event(mm); - - break; - } - } - } - - } break; - case KeyPress: - case KeyRelease: { -#ifdef DISPLAY_SERVER_X11_DEBUG_LOGS_ENABLED - if (event.type == KeyPress) { - DEBUG_LOG_X11("[%u] KeyPress window=%lu (%u), keycode=%u, time=%lu \n", frame, event.xkey.window, window_id, event.xkey.keycode, event.xkey.time); - } else { - DEBUG_LOG_X11("[%u] KeyRelease window=%lu (%u), keycode=%u, time=%lu \n", frame, event.xkey.window, window_id, event.xkey.keycode, event.xkey.time); - } -#endif - 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, &event.xkey, 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 files = String((char *)p.data).split("\n", false); - XFree(p.data); - for (int i = 0; i < files.size(); i++) { - files.write[i] = files[i].replace("file://", "").uri_decode().strip_edges(); - } - - if (!windows[window_id].drop_files_callback.is_null()) { - Variant v = files; - Variant *vp = &v; - Variant ret; - Callable::CallError ce; - windows[window_id].drop_files_callback.callp((const Variant **)&vp, 1, ret, ce); - } - - //Reply that all is well. - XClientMessageEvent m; - memset(&m, 0, sizeof(m)); - m.type = ClientMessage; - m.display = x11_display; - m.window = xdnd_source_window; - m.message_type = xdnd_finished; - m.format = 32; - m.data.l[0] = windows[window_id].x11_window; - m.data.l[1] = 1; - m.data.l[2] = xdnd_action_copy; //We only ever copy. - - XSendEvent(x11_display, xdnd_source_window, False, NoEventMask, (XEvent *)&m); - } - break; - - case ClientMessage: - - if ((unsigned int)event.xclient.data.l[0] == (unsigned int)wm_delete) { - _send_window_event(windows[window_id], WINDOW_EVENT_CLOSE_REQUEST); - } - - else if ((unsigned int)event.xclient.message_type == (unsigned int)xdnd_enter) { - //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]; - bool more_than_3 = event.xclient.data.l[1] & 1; - if (more_than_3) { - Property p = _read_property(x11_display, source, XInternAtom(x11_display, "XdndTypeList", False)); - requested = pick_target_from_list(x11_display, (Atom *)p.data, p.nitems); - XFree(p.data); - } 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; - memset(&m, 0, sizeof(m)); - m.type = ClientMessage; - m.display = event.xclient.display; - m.window = event.xclient.data.l[0]; - m.message_type = xdnd_status; - m.format = 32; - m.data.l[0] = windows[window_id].x11_window; - m.data.l[1] = (requested != None); - m.data.l[2] = 0; //empty rectangle - m.data.l[3] = 0; - m.data.l[4] = xdnd_action_copy; - - 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) { - XConvertSelection(x11_display, xdnd_selection, requested, XInternAtom(x11_display, "PRIMARY", 0), windows[window_id].x11_window, event.xclient.data.l[2]); - } else { - XConvertSelection(x11_display, xdnd_selection, requested, XInternAtom(x11_display, "PRIMARY", 0), windows[window_id].x11_window, CurrentTime); - } - } else { - //Reply that we're not interested. - XClientMessageEvent m; - memset(&m, 0, sizeof(m)); - m.type = ClientMessage; - m.display = event.xclient.display; - m.window = event.xclient.data.l[0]; - m.message_type = xdnd_finished; - m.format = 32; - m.data.l[0] = windows[window_id].x11_window; - m.data.l[1] = 0; - m.data.l[2] = None; //Failed. - XSendEvent(x11_display, event.xclient.data.l[0], False, NoEventMask, (XEvent *)&m); - } - } - break; - default: - break; - } - } - - 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); - - /* - Window root, child; - int root_x, root_y; - int win_x, win_y; - unsigned int mask; - XQueryPointer( x11_display, x11_window, &root, &child, &root_x, &root_y, &win_x, &win_y, &mask ); - - printf("Root: %d,%d\n", root_x, root_y); - printf("Win: %d,%d\n", win_x, win_y); - */ - } - - Input::get_singleton()->flush_buffered_events(); -} - -void DisplayServerX11::release_rendering_thread() { -#if defined(GLES3_ENABLED) - if (gl_manager) { - gl_manager->release_current(); - } -#endif -} - -void DisplayServerX11::make_rendering_thread() { -#if defined(GLES3_ENABLED) - if (gl_manager) { - gl_manager->make_current(); - } -#endif -} - -void DisplayServerX11::swap_buffers() { -#if defined(GLES3_ENABLED) - if (gl_manager) { - gl_manager->swap_buffers(); - } -#endif -} - -void DisplayServerX11::_update_context(WindowData &wd) { - XClassHint *classHint = XAllocClassHint(); - - if (classHint) { - CharString name_str; - switch (context) { - case CONTEXT_EDITOR: - name_str = "Godot_Editor"; - break; - case CONTEXT_PROJECTMAN: - name_str = "Godot_ProjectList"; - break; - case CONTEXT_ENGINE: - name_str = "Godot_Engine"; - break; - } - - CharString class_str; - if (context == CONTEXT_ENGINE) { - String config_name = GLOBAL_GET("application/config/name"); - if (config_name.length() == 0) { - class_str = "Godot_Engine"; - } else { - class_str = config_name.utf8(); - } - } else { - class_str = "Godot"; - } - - classHint->res_class = class_str.ptrw(); - classHint->res_name = name_str.ptrw(); - - XSetClassHint(x11_display, wd.x11_window, classHint); - XFree(classHint); - } -} - -void DisplayServerX11::set_context(Context p_context) { - _THREAD_SAFE_METHOD_ - - context = p_context; - - for (KeyValue &E : windows) { - _update_context(E.value); - } -} - -void DisplayServerX11::set_native_icon(const String &p_filename) { - WARN_PRINT("Native icon not supported by this display server."); -} - -bool g_set_icon_error = false; -int set_icon_errorhandler(Display *dpy, XErrorEvent *ev) { - g_set_icon_error = true; - return 0; -} - -void DisplayServerX11::set_icon(const Ref &p_icon) { - _THREAD_SAFE_METHOD_ - - WindowData &wd = windows[MAIN_WINDOW_ID]; - - int (*oldHandler)(Display *, XErrorEvent *) = XSetErrorHandler(&set_icon_errorhandler); - - Atom net_wm_icon = XInternAtom(x11_display, "_NET_WM_ICON", False); - - if (p_icon.is_valid()) { - Ref img = p_icon->duplicate(); - img->convert(Image::FORMAT_RGBA8); - - while (true) { - int w = img->get_width(); - int h = img->get_height(); - - if (g_set_icon_error) { - g_set_icon_error = false; - - WARN_PRINT("Icon too large, attempting to resize icon."); - - int new_width, new_height; - if (w > h) { - new_width = w / 2; - new_height = h * new_width / w; - } else { - new_height = h / 2; - new_width = w * new_height / h; - } - - w = new_width; - h = new_height; - - if (!w || !h) { - WARN_PRINT("Unable to set icon."); - break; - } - - img->resize(w, h, Image::INTERPOLATE_CUBIC); - } - - // We're using long to have wordsize (32Bit build -> 32 Bits, 64 Bit build -> 64 Bits - Vector pd; - - pd.resize(2 + w * h); - - pd.write[0] = w; - pd.write[1] = h; - - const uint8_t *r = img->get_data().ptr(); - - long *wr = &pd.write[2]; - uint8_t const *pr = r; - - for (int i = 0; i < w * h; i++) { - long v = 0; - // A R G B - v |= pr[3] << 24 | pr[0] << 16 | pr[1] << 8 | pr[2]; - *wr++ = v; - pr += 4; - } - - 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) { - break; - } - } - } else { - XDeleteProperty(x11_display, wd.x11_window, net_wm_icon); - } - - XFlush(x11_display); - XSetErrorHandler(oldHandler); -} - -void DisplayServerX11::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window) { - _THREAD_SAFE_METHOD_ -#if defined(VULKAN_ENABLED) - if (context_vulkan) { - context_vulkan->set_vsync_mode(p_window, p_vsync_mode); - } -#endif - -#if defined(GLES3_ENABLED) - if (gl_manager) { - gl_manager->set_use_vsync(p_vsync_mode == DisplayServer::VSYNC_ENABLED); - } -#endif -} - -DisplayServer::VSyncMode DisplayServerX11::window_get_vsync_mode(WindowID p_window) const { - _THREAD_SAFE_METHOD_ -#if defined(VULKAN_ENABLED) - if (context_vulkan) { - return context_vulkan->get_vsync_mode(p_window); - } -#endif -#if defined(GLES3_ENABLED) - if (gl_manager) { - return gl_manager->is_using_vsync() ? DisplayServer::VSYNC_ENABLED : DisplayServer::VSYNC_DISABLED; - } -#endif - return DisplayServer::VSYNC_ENABLED; -} - -Vector DisplayServerX11::get_rendering_drivers_func() { - Vector drivers; - -#ifdef VULKAN_ENABLED - drivers.push_back("vulkan"); -#endif -#ifdef GLES3_ENABLED - drivers.push_back("opengl3"); -#endif - - return drivers; -} - -DisplayServer *DisplayServerX11::create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, Error &r_error) { - DisplayServer *ds = memnew(DisplayServerX11(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_position, p_resolution, r_error)); - if (r_error != OK) { - if (p_rendering_driver == "vulkan") { - String executable_name = OS::get_singleton()->get_executable_path().get_file(); - OS::get_singleton()->alert("Your video card driver does not support the selected Vulkan version.\n" - "Please try updating your GPU driver or try using the OpenGL 3 driver.\n" - "You can enable the OpenGL 3 driver by starting the engine from the\n" - "command line with the command:\n'./" + - executable_name + " --rendering-driver opengl3'.\n " - "If you have updated your graphics drivers recently, try rebooting.", - "Unable to initialize Video driver"); - } else { - OS::get_singleton()->alert("Your video card driver does not support the selected OpenGL version.\n" - "Please try updating your GPU driver.\n" - "If you have updated your graphics drivers recently, try rebooting.", - "Unable to initialize Video driver"); - } - } - return ds; -} - -DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect) { - //Create window - - XVisualInfo visualInfo; - bool vi_selected = false; - -#ifdef GLES3_ENABLED - if (gl_manager) { - visualInfo = gl_manager->get_vi(x11_display); - vi_selected = true; - } -#endif - - if (!vi_selected) { - long visualMask = VisualScreenMask; - int numberOfVisuals; - XVisualInfo vInfoTemplate = {}; - vInfoTemplate.screen = DefaultScreen(x11_display); - XVisualInfo *vi_list = XGetVisualInfo(x11_display, visualMask, &vInfoTemplate, &numberOfVisuals); - ERR_FAIL_COND_V(!vi_list, INVALID_WINDOW_ID); - - visualInfo = vi_list[0]; - if (OS::get_singleton()->is_layered_allowed()) { - for (int i = 0; i < numberOfVisuals; i++) { - XRenderPictFormat *pict_format = XRenderFindVisualFormat(x11_display, vi_list[i].visual); - if (!pict_format) { - continue; - } - visualInfo = vi_list[i]; - if (pict_format->direct.alphaMask > 0) { - break; - } - } - } - XFree(vi_list); - } - - Colormap colormap = XCreateColormap(x11_display, RootWindow(x11_display, visualInfo.screen), visualInfo.visual, AllocNone); - - XSetWindowAttributes windowAttributes = {}; - windowAttributes.colormap = colormap; - windowAttributes.background_pixel = 0xFFFFFFFF; - windowAttributes.border_pixel = 0; - windowAttributes.event_mask = KeyPressMask | KeyReleaseMask | StructureNotifyMask | ExposureMask; - - unsigned long valuemask = CWBorderPixel | CWColormap | CWEventMask; - - if (OS::get_singleton()->is_layered_allowed()) { - windowAttributes.background_pixmap = None; - windowAttributes.background_pixel = 0; - windowAttributes.border_pixmap = None; - valuemask |= CWBackPixel; - } - - WindowID id = window_id_counter++; - WindowData &wd = windows[id]; - - if (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) { - wd.no_focus = true; - } - - if (p_flags & WINDOW_FLAG_POPUP_BIT) { - wd.is_popup = 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.is_popup || wd.no_focus) { - windowAttributes.override_redirect = True; - windowAttributes.save_under = True; - valuemask |= CWOverrideRedirect | CWSaveUnder; - } - - { - 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); - - // Enable receiving notification when the window is initialized (MapNotify) - // so the focus can be set at the right time. - if (!wd.no_focus && !wd.is_popup) { - 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); - 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; - - { - XIEventMask all_event_mask; - XSetWindowAttributes new_attr; - - new_attr.event_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask | - ButtonReleaseMask | EnterWindowMask | - LeaveWindowMask | PointerMotionMask | - Button1MotionMask | - Button2MotionMask | Button3MotionMask | - Button4MotionMask | Button5MotionMask | - ButtonMotionMask | KeymapStateMask | - ExposureMask | VisibilityChangeMask | - StructureNotifyMask | - SubstructureNotifyMask | SubstructureRedirectMask | - FocusChangeMask | PropertyChangeMask | - ColormapChangeMask | OwnerGrabButtonMask | - im_event_mask; - - XChangeWindowAttributes(x11_display, wd.x11_window, CWEventMask, &new_attr); - - static unsigned char all_mask_data[XIMaskLen(XI_LASTEVENT)] = {}; - - all_event_mask.deviceid = XIAllDevices; - all_event_mask.mask_len = sizeof(all_mask_data); - all_event_mask.mask = all_mask_data; - - XISetMask(all_event_mask.mask, XI_HierarchyChanged); - -#ifdef TOUCH_ENABLED - if (xi.touch_devices.size()) { - XISetMask(all_event_mask.mask, XI_TouchBegin); - XISetMask(all_event_mask.mask, XI_TouchUpdate); - XISetMask(all_event_mask.mask, XI_TouchEnd); - XISetMask(all_event_mask.mask, XI_TouchOwnership); - } -#endif - - XISelectEvents(x11_display, wd.x11_window, &all_event_mask, 1); - } - - /* set the titlebar name */ - XStoreName(x11_display, wd.x11_window, "Godot"); - XSetWMProtocols(x11_display, wd.x11_window, &wm_delete, 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) { - WARN_PRINT("XGetICValues couldn't obtain XNFilterEvents value"); - XDestroyIC(wd.xic); - wd.xic = nullptr; - } - if (wd.xic) { - XUnsetICFocus(wd.xic); - } else { - WARN_PRINT("XCreateIC couldn't create wd.xic"); - } - } else { - wd.xic = nullptr; - WARN_PRINT("XCreateIC couldn't create wd.xic"); - } - - _update_context(wd); - - 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); - } - } - - if (wd.is_popup || wd.no_focus) { - // 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 (wt_atom != None && type_atom != None) { - XChangeProperty(x11_display, wd.x11_window, wt_atom, XA_ATOM, 32, PropModeReplace, (unsigned char *)&type_atom, 1); - } - } - - _update_size_hints(id); - -#if defined(VULKAN_ENABLED) - if (context_vulkan) { - Error err = context_vulkan->window_create(id, p_vsync_mode, wd.x11_window, x11_display, p_rect.size.width, p_rect.size.height); - ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't create a Vulkan window"); - } -#endif -#ifdef GLES3_ENABLED - if (gl_manager) { - Error err = gl_manager->window_create(id, wd.x11_window, x11_display, p_rect.size.width, p_rect.size.height); - ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't create an OpenGL window"); - } -#endif - - //set_class_hint(x11_display, wd.x11_window); - XFlush(x11_display); - - XSync(x11_display, False); - //XSetErrorHandler(oldHandler); - } - - window_set_mode(p_mode, id); - - //sync size - { - XWindowAttributes xwa; - - XSync(x11_display, False); - XGetWindowAttributes(x11_display, wd.x11_window, &xwa); - - wd.position.x = xwa.x; - wd.position.y = xwa.y; - wd.size.width = xwa.width; - wd.size.height = xwa.height; - } - - //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, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, Error &r_error) { - Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events); - - r_error = OK; - - for (int i = 0; i < CURSOR_MAX; i++) { - cursors[i] = None; - img[i] = nullptr; - } - - XInitThreads(); //always use threads - - /** XLIB INITIALIZATION **/ - x11_display = XOpenDisplay(nullptr); - - if (!x11_display) { - ERR_PRINT("X11 Display is not available"); - r_error = ERR_UNAVAILABLE; - return; - } - - char *modifiers = nullptr; - Bool xkb_dar = False; - XAutoRepeatOn(x11_display); - xkb_dar = XkbSetDetectableAutoRepeat(x11_display, True, nullptr); - - // Try to support IME if detectable auto-repeat is supported - if (xkb_dar == True) { -#ifdef X_HAVE_UTF8_STRING - // Xutf8LookupString will be used later instead of XmbLookupString before - // the multibyte sequences can be converted to unicode string. - modifiers = XSetLocaleModifiers(""); -#endif - } - - if (modifiers == nullptr) { - if (OS::get_singleton()->is_stdout_verbose()) { - WARN_PRINT("IME is disabled"); - } - XSetLocaleModifiers("@im=none"); - WARN_PRINT("Error setting locale modifiers"); - } - - const char *err; - int xrandr_major = 0; - int xrandr_minor = 0; - int event_base, error_base; - xrandr_ext_ok = XRRQueryExtension(x11_display, &event_base, &error_base); - xrandr_handle = dlopen("libXrandr.so.2", RTLD_LAZY); - if (!xrandr_handle) { - err = dlerror(); - // 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) { - xrr_get_monitors = (xrr_get_monitors_t)dlsym(xrandr_handle, "XRRGetMonitors"); - if (!xrr_get_monitors) { - err = dlerror(); - fprintf(stderr, "could not find symbol XRRGetMonitors\nError: %s\n", err); - } else { - xrr_free_monitors = (xrr_free_monitors_t)dlsym(xrandr_handle, "XRRFreeMonitors"); - if (!xrr_free_monitors) { - err = dlerror(); - fprintf(stderr, "could not find XRRFreeMonitors\nError: %s\n", err); - xrr_get_monitors = nullptr; - } - } - } - } - - if (!_refresh_device_info()) { - OS::get_singleton()->alert("Your system does not support XInput 2.\n" - "Please upgrade your distribution.", - "Unable to initialize XInput"); - r_error = ERR_UNAVAILABLE; - return; - } - - xim = XOpenIM(x11_display, nullptr, nullptr, nullptr); - - if (xim == nullptr) { - WARN_PRINT("XOpenIM failed"); - xim_style = 0L; - } else { - ::XIMCallback im_destroy_callback; - im_destroy_callback.client_data = (::XPointer)(this); - im_destroy_callback.callback = (::XIMProc)(_xim_destroy_callback); - if (XSetIMValues(xim, XNDestroyCallback, &im_destroy_callback, - nullptr) != nullptr) { - WARN_PRINT("Error setting XIM destroy callback"); - } - - ::XIMStyles *xim_styles = nullptr; - xim_style = 0L; - char *imvalret = XGetIMValues(xim, XNQueryInputStyle, &xim_styles, nullptr); - if (imvalret != nullptr || xim_styles == nullptr) { - fprintf(stderr, "Input method doesn't support any styles\n"); - } - - if (xim_styles) { - xim_style = 0L; - for (int i = 0; i < xim_styles->count_styles; i++) { - if (xim_styles->supported_styles[i] == - (XIMPreeditNothing | XIMStatusNothing)) { - xim_style = xim_styles->supported_styles[i]; - break; - } - } - - XFree(xim_styles); - } - XFree(imvalret); - } - - /* Atom internment */ - wm_delete = XInternAtom(x11_display, "WM_DELETE_WINDOW", true); - // Set Xdnd (drag & drop) support. - xdnd_aware = XInternAtom(x11_display, "XdndAware", False); - xdnd_enter = XInternAtom(x11_display, "XdndEnter", False); - xdnd_position = XInternAtom(x11_display, "XdndPosition", False); - xdnd_status = XInternAtom(x11_display, "XdndStatus", False); - xdnd_action_copy = XInternAtom(x11_display, "XdndActionCopy", False); - xdnd_drop = XInternAtom(x11_display, "XdndDrop", False); - xdnd_finished = XInternAtom(x11_display, "XdndFinished", False); - xdnd_selection = XInternAtom(x11_display, "XdndSelection", False); - -#ifdef SPEECHD_ENABLED - // Init TTS - tts = memnew(TTS_Linux); -#endif - - //!!!!!!!!!!!!!!!!!!!!!!!!!! - //TODO - do Vulkan and OpenGL support checks, driver selection and fallback - rendering_driver = p_rendering_driver; - - bool driver_found = false; -#if defined(VULKAN_ENABLED) - if (rendering_driver == "vulkan") { - context_vulkan = memnew(VulkanContextX11); - if (context_vulkan->initialize() != OK) { - memdelete(context_vulkan); - context_vulkan = nullptr; - r_error = ERR_CANT_CREATE; - ERR_FAIL_MSG("Could not initialize Vulkan"); - } - driver_found = true; - } -#endif - // Initialize context and rendering device. -#if defined(GLES3_ENABLED) - if (rendering_driver == "opengl3") { - if (getenv("DRI_PRIME") == nullptr) { - int use_prime = -1; - - if (getenv("PRIMUS_DISPLAY") || - getenv("PRIMUS_libGLd") || - getenv("PRIMUS_libGLa") || - getenv("PRIMUS_libGL") || - getenv("PRIMUS_LOAD_GLOBAL") || - getenv("BUMBLEBEE_SOCKET")) { - print_verbose("Optirun/primusrun detected. Skipping GPU detection"); - use_prime = 0; - } - - // Some tools use fake libGL libraries and have them override the real one using - // LD_LIBRARY_PATH, so we skip them. *But* Steam also sets LD_LIBRARY_PATH for its - // runtime and includes system `/lib` and `/lib64`... so ignore Steam. - if (use_prime == -1 && getenv("LD_LIBRARY_PATH") && !getenv("STEAM_RUNTIME_LIBRARY_PATH")) { - String ld_library_path(getenv("LD_LIBRARY_PATH")); - Vector libraries = ld_library_path.split(":"); - - 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; - } - } - } - - if (use_prime == -1) { - print_verbose("Detecting GPUs, set DRI_PRIME in the environment to override GPU detection logic."); - use_prime = detect_prime(); - } - - if (use_prime) { - print_line("Found discrete GPU, setting DRI_PRIME=1 to use it."); - print_line("Note: Set DRI_PRIME=0 in the environment to disable Godot from using the discrete GPU."); - setenv("DRI_PRIME", "1", 1); - } - } - - GLManager_X11::ContextType opengl_api_type = GLManager_X11::GLES_3_0_COMPATIBLE; - - gl_manager = memnew(GLManager_X11(p_resolution, opengl_api_type)); - - if (gl_manager->initialize() != OK) { - memdelete(gl_manager); - gl_manager = nullptr; - r_error = ERR_UNAVAILABLE; - return; - } - driver_found = true; - - if (true) { - RasterizerGLES3::make_current(); - } else { - memdelete(gl_manager); - gl_manager = nullptr; - r_error = ERR_UNAVAILABLE; - return; - } - } -#endif - if (!driver_found) { - r_error = ERR_UNAVAILABLE; - ERR_FAIL_MSG("Video driver not found"); - } - - Point2i window_position( - (screen_get_size(0).width - p_resolution.width) / 2, - (screen_get_size(0).height - p_resolution.height) / 2); - - if (p_position != nullptr) { - window_position = *p_position; - } - - WindowID main_window = _create_window(p_mode, p_vsync_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); - XSync(x11_display, False); - _validate_mode_on_map(main_window); - -#if defined(VULKAN_ENABLED) - if (rendering_driver == "vulkan") { - //temporary - rendering_device_vulkan = memnew(RenderingDeviceVulkan); - rendering_device_vulkan->initialize(context_vulkan); - - RendererCompositorRD::make_current(); - } -#endif - - { - //set all event master mask - XIEventMask all_master_event_mask; - static unsigned char all_master_mask_data[XIMaskLen(XI_LASTEVENT)] = {}; - all_master_event_mask.deviceid = XIAllMasterDevices; - all_master_event_mask.mask_len = sizeof(all_master_mask_data); - all_master_event_mask.mask = all_master_mask_data; - XISetMask(all_master_event_mask.mask, XI_DeviceChanged); - XISetMask(all_master_event_mask.mask, XI_RawMotion); - XISelectEvents(x11_display, DefaultRootWindow(x11_display), &all_master_event_mask, 1); - } - - cursor_size = XcursorGetDefaultSize(x11_display); - cursor_theme = XcursorGetTheme(x11_display); - - if (!cursor_theme) { - print_verbose("XcursorGetTheme could not get cursor theme"); - cursor_theme = "default"; - } - - for (int i = 0; i < CURSOR_MAX; i++) { - static const char *cursor_file[] = { - "left_ptr", - "xterm", - "hand2", - "cross", - "watch", - "left_ptr_watch", - "fleur", - "dnd-move", - "crossed_circle", - "v_double_arrow", - "h_double_arrow", - "size_bdiag", - "size_fdiag", - "move", - "row_resize", - "col_resize", - "question_arrow" - }; - - img[i] = XcursorLibraryLoadImage(cursor_file[i], cursor_theme, cursor_size); - if (!img[i]) { - const char *fallback = nullptr; - - switch (i) { - case CURSOR_POINTING_HAND: - fallback = "pointer"; - break; - case CURSOR_CROSS: - fallback = "crosshair"; - break; - case CURSOR_WAIT: - fallback = "wait"; - break; - case CURSOR_BUSY: - fallback = "progress"; - break; - case CURSOR_DRAG: - fallback = "grabbing"; - break; - case CURSOR_CAN_DROP: - fallback = "hand1"; - break; - case CURSOR_FORBIDDEN: - fallback = "forbidden"; - break; - case CURSOR_VSIZE: - fallback = "ns-resize"; - break; - case CURSOR_HSIZE: - fallback = "ew-resize"; - break; - case CURSOR_BDIAGSIZE: - fallback = "fd_double_arrow"; - break; - case CURSOR_FDIAGSIZE: - fallback = "bd_double_arrow"; - break; - case CURSOR_MOVE: - img[i] = img[CURSOR_DRAG]; - break; - case CURSOR_VSPLIT: - fallback = "sb_v_double_arrow"; - break; - case CURSOR_HSPLIT: - fallback = "sb_h_double_arrow"; - break; - case CURSOR_HELP: - fallback = "help"; - break; - } - if (fallback != nullptr) { - img[i] = XcursorLibraryLoadImage(fallback, cursor_theme, cursor_size); - } - } - if (img[i]) { - cursors[i] = XcursorImageLoadCursor(x11_display, img[i]); - } else { - print_verbose("Failed loading custom cursor: " + String(cursor_file[i])); - } - } - - { - // Creating an empty/transparent cursor - - // Create 1x1 bitmap - Pixmap cursormask = XCreatePixmap(x11_display, - RootWindow(x11_display, DefaultScreen(x11_display)), 1, 1, 1); - - // Fill with zero - XGCValues xgc; - xgc.function = GXclear; - GC gc = XCreateGC(x11_display, cursormask, GCFunction, &xgc); - XFillRectangle(x11_display, cursormask, gc, 0, 0, 1, 1); - - // Color value doesn't matter. Mask zero means no foreground or background will be drawn - XColor col = {}; - - Cursor cursor = XCreatePixmapCursor(x11_display, - cursormask, // source (using cursor mask as placeholder, since it'll all be ignored) - cursormask, // mask - &col, &col, 0, 0); - - XFreePixmap(x11_display, cursormask); - XFreeGC(x11_display, gc); - - if (cursor == None) { - ERR_PRINT("FAILED CREATING CURSOR"); - } - - null_cursor = cursor; - } - cursor_set_shape(CURSOR_BUSY); - - // Search the X11 event queue for ConfigureNotify events and process all - // that are currently queued early, so we can get the final window size - // for correctly drawing of the bootsplash. - XEvent config_event; - while (XCheckTypedEvent(x11_display, ConfigureNotify, &config_event)) { - _window_changed(&config_event); - } - events_thread.start(_poll_events_thread, this); - - _update_real_mouse_position(windows[MAIN_WINDOW_ID]); - -#ifdef DBUS_ENABLED - screensaver = memnew(FreeDesktopScreenSaver); - screen_set_keep_on(GLOBAL_GET("display/window/energy_saving/keep_screen_on")); - - portal_desktop = memnew(FreeDesktopPortalDesktop); -#endif - - 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.set(); - events_thread.wait_to_finish(); - - //destroy all windows - for (KeyValue &E : windows) { -#ifdef VULKAN_ENABLED - if (context_vulkan) { - context_vulkan->window_destroy(E.key); - } -#endif -#ifdef GLES3_ENABLED - if (gl_manager) { - gl_manager->window_destroy(E.key); - } -#endif - - WindowData &wd = E.value; - if (wd.xic) { - XDestroyIC(wd.xic); - wd.xic = nullptr; - } - XUnmapWindow(x11_display, wd.x11_window); - XDestroyWindow(x11_display, wd.x11_window); - } - - //destroy drivers -#if defined(VULKAN_ENABLED) - if (rendering_device_vulkan) { - rendering_device_vulkan->finalize(); - memdelete(rendering_device_vulkan); - rendering_device_vulkan = nullptr; - } - - if (context_vulkan) { - memdelete(context_vulkan); - context_vulkan = nullptr; - } -#endif - -#ifdef GLES3_ENABLED - if (gl_manager) { - memdelete(gl_manager); - gl_manager = nullptr; - } -#endif - - if (xrandr_handle) { - dlclose(xrandr_handle); - } - - for (int i = 0; i < CURSOR_MAX; i++) { - if (cursors[i] != None) { - XFreeCursor(x11_display, cursors[i]); - } - if (img[i] != nullptr) { - XcursorImageDestroy(img[i]); - } - } - - if (xim) { - XCloseIM(xim); - } - - XCloseDisplay(x11_display); - if (xmbstring) { - memfree(xmbstring); - } - -#ifdef SPEECHD_ENABLED - memdelete(tts); -#endif - -#ifdef DBUS_ENABLED - memdelete(screensaver); - memdelete(portal_desktop); -#endif -} - -void DisplayServerX11::register_x11_driver() { - register_create_function("x11", create_func, get_rendering_drivers_func); -} - -#endif // X11 enabled diff --git a/platform/linuxbsd/display_server_x11.h b/platform/linuxbsd/display_server_x11.h deleted file mode 100644 index 9ef8f71c05..0000000000 --- a/platform/linuxbsd/display_server_x11.h +++ /dev/null @@ -1,458 +0,0 @@ -/*************************************************************************/ -/* display_server_x11.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef DISPLAY_SERVER_X11_H -#define DISPLAY_SERVER_X11_H - -#ifdef X11_ENABLED - -#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/renderer_compositor.h" -#include "servers/rendering_server.h" - -#if defined(SPEECHD_ENABLED) -#include "tts_linux.h" -#endif - -#if defined(GLES3_ENABLED) -#include "gl_manager_x11.h" -#endif - -#if defined(VULKAN_ENABLED) -#include "drivers/vulkan/rendering_device_vulkan.h" -#include "platform/linuxbsd/vulkan_context_x11.h" -#endif - -#if defined(DBUS_ENABLED) -#include "freedesktop_portal_desktop.h" -#include "freedesktop_screensaver.h" -#endif - -#include -#include -#include -#include -#include - -typedef struct _xrr_monitor_info { - Atom name; - 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 - -class DisplayServerX11 : public DisplayServer { - //No need to register, it's platform-specific and nothing is added - //GDCLASS(DisplayServerX11, DisplayServer) - - _THREAD_SAFE_CLASS_ - - Atom wm_delete; - Atom xdnd_enter; - Atom xdnd_position; - Atom xdnd_status; - Atom xdnd_action_copy; - Atom xdnd_drop; - Atom xdnd_finished; - Atom xdnd_selection; - Atom xdnd_aware; - Atom requested = None; - int xdnd_version = 5; - -#if defined(GLES3_ENABLED) - GLManager_X11 *gl_manager = nullptr; -#endif -#if defined(VULKAN_ENABLED) - VulkanContextX11 *context_vulkan = nullptr; - RenderingDeviceVulkan *rendering_device_vulkan = nullptr; -#endif - -#if defined(DBUS_ENABLED) - FreeDesktopScreenSaver *screensaver = nullptr; - bool keep_screen_on = false; -#endif - -#ifdef SPEECHD_ENABLED - TTS_Linux *tts = nullptr; -#endif - -#if defined(DBUS_ENABLED) - FreeDesktopPortalDesktop *portal_desktop = nullptr; -#endif - - struct WindowData { - Window x11_window; - ::XIC xic; - - Size2i min_size; - Size2i max_size; - Point2i position; - Size2i size; - Point2i im_position; - bool im_active = false; - Callable rect_changed_callback; - Callable event_callback; - Callable input_event_callback; - Callable input_text_callback; - Callable drop_files_callback; - - WindowID transient_parent = INVALID_WINDOW_ID; - HashSet transient_children; - - ObjectID instance_id; - - 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 - bool on_top = false; - bool borderless = false; - bool resize_disabled = false; - Vector2i last_position_before_fs; - bool focused = true; - bool minimized = false; - bool maximized = false; - bool is_popup = false; - bool layered_window = false; - - Rect2i parent_safe_rect; - - unsigned int focus_order = 0; - }; - - HashMap windows; - - unsigned int last_mouse_monitor_mask = 0; - Vector2i last_mouse_monitor_pos; - uint64_t time_since_popup = 0; - - List popup_list; - - WindowID last_focused_window = INVALID_WINDOW_ID; - - WindowID window_id_counter = MAIN_WINDOW_ID; - WindowID _create_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect); - - String internal_clipboard; - String internal_clipboard_primary; - Window xdnd_source_window = 0; - ::Display *x11_display; - char *xmbstring = nullptr; - int xmblen = 0; - unsigned long last_timestamp = 0; - ::Time last_keyrelease_time = 0; - ::XIM xim; - ::XIMStyle xim_style; - static void _xim_destroy_callback(::XIM im, ::XPointer client_data, - ::XPointer call_data); - - Point2i last_mouse_pos; - bool last_mouse_pos_valid = false; - Point2i last_click_pos = Point2i(-100, -100); - uint64_t last_click_ms = 0; - MouseButton last_click_button_index = MouseButton::NONE; - MouseButton last_button_state = MouseButton::NONE; - bool app_focused = false; - uint64_t time_since_no_focus = 0; - - struct { - int opcode; - Vector touch_devices; - HashMap absolute_devices; - HashMap pen_pressure_range; - HashMap pen_tilt_x_range; - HashMap pen_tilt_y_range; - HashMap pen_inverted_devices; - XIEventMask all_event_mask; - HashMap state; - double pressure; - bool pressure_supported; - bool pen_inverted; - Vector2 tilt; - Vector2 mouse_pos_to_filter; - Vector2 relative_motion; - Vector2 raw_pos; - Vector2 old_raw_pos; - ::Time last_relative_time; - } xi; - - bool _refresh_device_info(); - - Rect2i _screen_get_rect(int p_screen) const; - - MouseButton _get_mouse_button_state(MouseButton p_x11_button, int p_x11_type); - void _get_key_modifier_state(unsigned int p_x11_state, Ref state); - void _flush_mouse_motion(); - - MouseMode mouse_mode = MOUSE_MODE_VISIBLE; - Point2i center; - - void _handle_key_event(WindowID p_window, XKeyEvent *p_event, LocalVector &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, Atom p_selection) const; - void _handle_selection_request_event(XSelectionRequestEvent *p_event) const; - - 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 do_mouse_warp = false; - - const char *cursor_theme = nullptr; - int cursor_size = 0; - XcursorImage *img[CURSOR_MAX]; - Cursor cursors[CURSOR_MAX]; - Cursor null_cursor; - CursorShape current_cursor = CURSOR_ARROW; - HashMap> cursors_cache; - - String rendering_driver; - void set_wm_fullscreen(bool p_enabled); - void set_wm_above(bool p_enabled); - - typedef xrr_monitor_info *(*xrr_get_monitors_t)(Display *dpy, Window window, Bool get_active, int *nmonitors); - typedef void (*xrr_free_monitors_t)(xrr_monitor_info *monitors); - xrr_get_monitors_t xrr_get_monitors = nullptr; - xrr_free_monitors_t xrr_free_monitors = nullptr; - void *xrandr_handle = nullptr; - Bool xrandr_ext_ok; - - struct Property { - unsigned char *data; - int format, nitems; - Atom type; - }; - static Property _read_property(Display *p_display, Window p_window, Atom p_property); - - void _update_real_mouse_position(const WindowData &wd); - bool _window_maximize_check(WindowID p_window, const char *p_atom_name) const; - bool _window_fullscreen_check(WindowID p_window) const; - bool _window_minimize_check(WindowID p_window) const; - void _validate_mode_on_map(WindowID p_window); - 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); - void _set_wm_minimized(WindowID p_window, bool p_enabled); - - void _update_context(WindowData &wd); - - Context context = CONTEXT_ENGINE; - - WindowID _get_focused_window_or_popup() const; - - void _send_window_event(const WindowData &wd, WindowEvent p_event); - static void _dispatch_input_events(const Ref &p_event); - void _dispatch_input_event(const Ref &p_event); - - mutable Mutex events_mutex; - Thread events_thread; - SafeFlag events_thread_done; - LocalVector polled_events; - static void _poll_events_thread(void *ud); - bool _wait_for_events() const; - void _poll_events(); - void _check_pending_events(LocalVector &r_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); - -public: - bool mouse_process_popups(); - void popup_open(WindowID p_window); - void popup_close(WindowID p_window); - - virtual bool has_feature(Feature p_feature) const override; - virtual String get_name() const override; - -#ifdef SPEECHD_ENABLED - virtual bool tts_is_speaking() const override; - virtual bool tts_is_paused() const override; - virtual TypedArray tts_get_voices() const override; - - virtual void tts_speak(const String &p_text, const String &p_voice, int p_volume = 50, float p_pitch = 1.f, float p_rate = 1.f, int p_utterance_id = 0, bool p_interrupt = false) override; - virtual void tts_pause() override; - virtual void tts_resume() override; - virtual void tts_stop() override; -#endif - -#if defined(DBUS_ENABLED) - virtual bool is_dark_mode_supported() const override; - virtual bool is_dark_mode() const override; -#endif - - virtual void mouse_set_mode(MouseMode p_mode) override; - virtual MouseMode mouse_get_mode() const override; - - virtual void warp_mouse(const Point2i &p_position) override; - virtual Point2i mouse_get_position() const override; - virtual MouseButton mouse_get_button_state() const override; - - virtual void clipboard_set(const String &p_text) override; - virtual String clipboard_get() const override; - virtual void clipboard_set_primary(const String &p_text) override; - virtual String clipboard_get_primary() const override; - - virtual int get_screen_count() const override; - virtual Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; - virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; - virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; - virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; - virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; - -#if defined(DBUS_ENABLED) - virtual void screen_set_keep_on(bool p_enable) override; - virtual bool screen_is_kept_on() const override; -#endif - - virtual Vector get_window_list() const override; - - virtual WindowID create_sub_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect = Rect2i()) override; - virtual void show_window(WindowID p_id) override; - virtual void delete_sub_window(WindowID p_id) override; - - virtual WindowID window_get_active_popup() const override; - virtual void window_set_popup_safe_rect(WindowID p_window, const Rect2i &p_rect) override; - virtual Rect2i window_get_popup_safe_rect(WindowID p_window) const override; - - virtual WindowID get_window_at_screen_position(const Point2i &p_position) const override; - - virtual int64_t window_get_native_handle(HandleType p_handle_type, WindowID p_window = MAIN_WINDOW_ID) const override; - - virtual void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID) override; - virtual ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const override; - - virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) override; - virtual void window_set_mouse_passthrough(const Vector &p_region, WindowID p_window = MAIN_WINDOW_ID) override; - - virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; - virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; - virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; - virtual void window_set_input_text_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; - virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; - - virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const override; - virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override; - - virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const override; - virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) override; - - virtual void window_set_max_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; - virtual Size2i window_get_max_size(WindowID p_window = MAIN_WINDOW_ID) const override; - virtual void gl_window_make_current(DisplayServer::WindowID p_window_id) override; - - virtual void window_set_transient(WindowID p_window, WindowID p_parent) override; - - virtual void window_set_min_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; - virtual Size2i window_get_min_size(WindowID p_window = MAIN_WINDOW_ID) const override; - - virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; - virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const override; - virtual Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const override; - - virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID) override; - virtual WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const override; - - virtual bool window_is_maximize_allowed(WindowID p_window = MAIN_WINDOW_ID) const override; - - virtual void window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window = MAIN_WINDOW_ID) override; - virtual bool window_get_flag(WindowFlags p_flag, WindowID p_window = MAIN_WINDOW_ID) const override; - - virtual void window_request_attention(WindowID p_window = MAIN_WINDOW_ID) override; - - virtual void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID) override; - - virtual bool window_can_draw(WindowID p_window = MAIN_WINDOW_ID) const override; - - virtual bool can_any_window_draw() const override; - - virtual void window_set_ime_active(const bool p_active, WindowID p_window = MAIN_WINDOW_ID) override; - virtual void window_set_ime_position(const Point2i &p_pos, WindowID p_window = MAIN_WINDOW_ID) override; - - virtual void window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window = MAIN_WINDOW_ID) override; - virtual DisplayServer::VSyncMode window_get_vsync_mode(WindowID p_vsync_mode) const override; - - virtual void cursor_set_shape(CursorShape p_shape) override; - virtual CursorShape cursor_get_shape() const override; - virtual void cursor_set_custom_image(const Ref &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) override; - - virtual int keyboard_get_layout_count() const override; - virtual int keyboard_get_current_layout() const override; - virtual void keyboard_set_current_layout(int p_index) override; - virtual String keyboard_get_layout_language(int p_index) const override; - virtual String keyboard_get_layout_name(int p_index) const override; - virtual Key keyboard_get_keycode_from_physical(Key p_keycode) const override; - - virtual void process_events() override; - - virtual void release_rendering_thread() override; - virtual void make_rendering_thread() override; - virtual void swap_buffers() override; - - virtual void set_context(Context p_context) override; - - virtual void set_native_icon(const String &p_filename) override; - virtual void set_icon(const Ref &p_icon) override; - - static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, Error &r_error); - static Vector get_rendering_drivers_func(); - - static void register_x11_driver(); - - DisplayServerX11(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, Error &r_error); - ~DisplayServerX11(); -}; - -#endif // X11 enabled - -#endif // DISPLAY_SERVER_X11_H diff --git a/platform/linuxbsd/gl_manager_x11.cpp b/platform/linuxbsd/gl_manager_x11.cpp deleted file mode 100644 index f586c57dda..0000000000 --- a/platform/linuxbsd/gl_manager_x11.cpp +++ /dev/null @@ -1,394 +0,0 @@ -/*************************************************************************/ -/* gl_manager_x11.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "gl_manager_x11.h" - -#ifdef X11_ENABLED -#if defined(GLES3_ENABLED) - -#include -#include -#include - -#define GLX_GLXEXT_PROTOTYPES -#include -#include - -#define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091 -#define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092 - -typedef GLXContext (*GLXCREATECONTEXTATTRIBSARBPROC)(Display *, GLXFBConfig, GLXContext, Bool, const int *); - -struct GLManager_X11_Private { - ::GLXContext glx_context; -}; - -GLManager_X11::GLDisplay::~GLDisplay() { - if (context) { - //release_current(); - glXDestroyContext(x11_display, context->glx_context); - memdelete(context); - context = nullptr; - } -} - -static bool ctxErrorOccurred = false; -static int ctxErrorHandler(Display *dpy, XErrorEvent *ev) { - ctxErrorOccurred = true; - return 0; -} - -int GLManager_X11::_find_or_create_display(Display *p_x11_display) { - for (unsigned int n = 0; n < _displays.size(); n++) { - const GLDisplay &d = _displays[n]; - if (d.x11_display == p_x11_display) { - return n; - } - } - - // create - GLDisplay d_temp; - d_temp.x11_display = p_x11_display; - _displays.push_back(d_temp); - int new_display_id = _displays.size() - 1; - - // create context - GLDisplay &d = _displays[new_display_id]; - - d.context = memnew(GLManager_X11_Private); - d.context->glx_context = nullptr; - - //Error err = _create_context(d); - _create_context(d); - return new_display_id; -} - -Error GLManager_X11::_create_context(GLDisplay &gl_display) { - // some aliases - ::Display *x11_display = gl_display.x11_display; - - //const char *extensions = glXQueryExtensionsString(x11_display, DefaultScreen(x11_display)); - - GLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB = (GLXCREATECONTEXTATTRIBSARBPROC)glXGetProcAddress((const GLubyte *)"glXCreateContextAttribsARB"); - - ERR_FAIL_COND_V(!glXCreateContextAttribsARB, ERR_UNCONFIGURED); - - static int visual_attribs[] = { - GLX_RENDER_TYPE, GLX_RGBA_BIT, - GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, - GLX_DOUBLEBUFFER, true, - GLX_RED_SIZE, 1, - GLX_GREEN_SIZE, 1, - GLX_BLUE_SIZE, 1, - GLX_DEPTH_SIZE, 24, - None - }; - - static int visual_attribs_layered[] = { - GLX_RENDER_TYPE, GLX_RGBA_BIT, - GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, - GLX_DOUBLEBUFFER, true, - GLX_RED_SIZE, 8, - GLX_GREEN_SIZE, 8, - GLX_BLUE_SIZE, 8, - GLX_ALPHA_SIZE, 8, - GLX_DEPTH_SIZE, 24, - None - }; - - int fbcount; - GLXFBConfig fbconfig = nullptr; - XVisualInfo *vi = nullptr; - - if (OS::get_singleton()->is_layered_allowed()) { - GLXFBConfig *fbc = glXChooseFBConfig(x11_display, DefaultScreen(x11_display), visual_attribs_layered, &fbcount); - ERR_FAIL_COND_V(!fbc, ERR_UNCONFIGURED); - - for (int i = 0; i < fbcount; i++) { - vi = (XVisualInfo *)glXGetVisualFromFBConfig(x11_display, fbc[i]); - if (!vi) { - continue; - } - - XRenderPictFormat *pict_format = XRenderFindVisualFormat(x11_display, vi->visual); - if (!pict_format) { - XFree(vi); - vi = nullptr; - continue; - } - - fbconfig = fbc[i]; - if (pict_format->direct.alphaMask > 0) { - break; - } - } - XFree(fbc); - - ERR_FAIL_COND_V(!fbconfig, ERR_UNCONFIGURED); - } else { - GLXFBConfig *fbc = glXChooseFBConfig(x11_display, DefaultScreen(x11_display), visual_attribs, &fbcount); - ERR_FAIL_COND_V(!fbc, ERR_UNCONFIGURED); - - vi = glXGetVisualFromFBConfig(x11_display, fbc[0]); - - fbconfig = fbc[0]; - XFree(fbc); - } - - int (*oldHandler)(Display *, XErrorEvent *) = XSetErrorHandler(&ctxErrorHandler); - - switch (context_type) { - case GLES_3_0_COMPATIBLE: { - static int context_attribs[] = { - GLX_CONTEXT_MAJOR_VERSION_ARB, 3, - GLX_CONTEXT_MINOR_VERSION_ARB, 3, - GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB, - GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB /*|GLX_CONTEXT_DEBUG_BIT_ARB*/, - None - }; - - gl_display.context->glx_context = glXCreateContextAttribsARB(x11_display, fbconfig, nullptr, true, context_attribs); - ERR_FAIL_COND_V(ctxErrorOccurred || !gl_display.context->glx_context, ERR_UNCONFIGURED); - } break; - } - - XSync(x11_display, False); - XSetErrorHandler(oldHandler); - - // make our own copy of the vi data - // for later creating windows using this display - if (vi) { - gl_display.x_vi = *vi; - } - - XFree(vi); - - return OK; -} - -XVisualInfo GLManager_X11::get_vi(Display *p_display) { - return _displays[_find_or_create_display(p_display)].x_vi; -} - -Error GLManager_X11::window_create(DisplayServer::WindowID p_window_id, ::Window p_window, Display *p_display, int p_width, int p_height) { - // make sure vector is big enough... - // we can mirror the external vector, it is simpler - // to keep the IDs identical for fast lookup - if (p_window_id >= (int)_windows.size()) { - _windows.resize(p_window_id + 1); - } - - GLWindow &win = _windows[p_window_id]; - win.in_use = true; - win.window_id = p_window_id; - win.width = p_width; - win.height = p_height; - win.x11_window = p_window; - win.gldisplay_id = _find_or_create_display(p_display); - - // the display could be invalid .. check NYI - GLDisplay &gl_display = _displays[win.gldisplay_id]; - ::Display *x11_display = gl_display.x11_display; - ::Window &x11_window = win.x11_window; - - if (!glXMakeCurrent(x11_display, x11_window, gl_display.context->glx_context)) { - ERR_PRINT("glXMakeCurrent failed"); - } - - _internal_set_current_window(&win); - - return OK; -} - -void GLManager_X11::_internal_set_current_window(GLWindow *p_win) { - _current_window = p_win; - - // quick access to x info - _x_windisp.x11_window = _current_window->x11_window; - const GLDisplay &disp = get_current_display(); - _x_windisp.x11_display = disp.x11_display; -} - -void GLManager_X11::window_resize(DisplayServer::WindowID p_window_id, int p_width, int p_height) { - get_window(p_window_id).width = p_width; - get_window(p_window_id).height = p_height; -} - -void GLManager_X11::window_destroy(DisplayServer::WindowID p_window_id) { - GLWindow &win = get_window(p_window_id); - win.in_use = false; - - if (_current_window == &win) { - _current_window = nullptr; - _x_windisp.x11_display = nullptr; - _x_windisp.x11_window = -1; - } -} - -void GLManager_X11::release_current() { - if (!_current_window) { - return; - } - - if (!glXMakeCurrent(_x_windisp.x11_display, None, nullptr)) { - ERR_PRINT("glXMakeCurrent failed"); - } - _current_window = nullptr; -} - -void GLManager_X11::window_make_current(DisplayServer::WindowID p_window_id) { - if (p_window_id == -1) { - return; - } - - GLWindow &win = _windows[p_window_id]; - if (!win.in_use) { - return; - } - - // noop - if (&win == _current_window) { - return; - } - - const GLDisplay &disp = get_display(win.gldisplay_id); - - if (!glXMakeCurrent(disp.x11_display, win.x11_window, disp.context->glx_context)) { - ERR_PRINT("glXMakeCurrent failed"); - } - - _internal_set_current_window(&win); -} - -void GLManager_X11::make_current() { - if (!_current_window) { - return; - } - if (!_current_window->in_use) { - WARN_PRINT("current window not in use!"); - return; - } - const GLDisplay &disp = get_current_display(); - if (!glXMakeCurrent(_x_windisp.x11_display, _x_windisp.x11_window, disp.context->glx_context)) { - ERR_PRINT("glXMakeCurrent failed"); - } -} - -void GLManager_X11::swap_buffers() { - if (!_current_window) { - return; - } - if (!_current_window->in_use) { - WARN_PRINT("current window not in use!"); - return; - } - - // On X11, when enabled, transparency is always active, so clear alpha manually. - if (OS::get_singleton()->is_layered_allowed()) { - if (!DisplayServer::get_singleton()->window_get_flag(DisplayServer::WINDOW_FLAG_TRANSPARENT, _current_window->window_id)) { - glColorMask(false, false, false, true); - glClearColor(0, 0, 0, 1); - glClear(GL_COLOR_BUFFER_BIT); - glColorMask(true, true, true, true); - } - } - - glXSwapBuffers(_x_windisp.x11_display, _x_windisp.x11_window); -} - -Error GLManager_X11::initialize() { - return OK; -} - -void GLManager_X11::set_use_vsync(bool p_use) { - static bool setup = false; - static PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT = nullptr; - static PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalMESA = nullptr; - static PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalSGI = nullptr; - - // force vsync in the editor for now, as a safety measure - bool is_editor = Engine::get_singleton()->is_editor_hint(); - if (is_editor) { - p_use = true; - } - - // we need an active window to get a display to set the vsync - if (!_current_window) { - return; - } - const GLDisplay &disp = get_current_display(); - - if (!setup) { - setup = true; - String extensions = glXQueryExtensionsString(disp.x11_display, DefaultScreen(disp.x11_display)); - if (extensions.find("GLX_EXT_swap_control") != -1) { - glXSwapIntervalEXT = (PFNGLXSWAPINTERVALEXTPROC)glXGetProcAddressARB((const GLubyte *)"glXSwapIntervalEXT"); - } - if (extensions.find("GLX_MESA_swap_control") != -1) { - glXSwapIntervalMESA = (PFNGLXSWAPINTERVALSGIPROC)glXGetProcAddressARB((const GLubyte *)"glXSwapIntervalMESA"); - } - if (extensions.find("GLX_SGI_swap_control") != -1) { - glXSwapIntervalSGI = (PFNGLXSWAPINTERVALSGIPROC)glXGetProcAddressARB((const GLubyte *)"glXSwapIntervalSGI"); - } - } - int val = p_use ? 1 : 0; - if (glXSwapIntervalMESA) { - glXSwapIntervalMESA(val); - } else if (glXSwapIntervalSGI) { - glXSwapIntervalSGI(val); - } else if (glXSwapIntervalEXT) { - GLXDrawable drawable = glXGetCurrentDrawable(); - glXSwapIntervalEXT(disp.x11_display, drawable, val); - } else { - return; - } - use_vsync = p_use; -} - -bool GLManager_X11::is_using_vsync() const { - return use_vsync; -} - -GLManager_X11::GLManager_X11(const Vector2i &p_size, ContextType p_context_type) { - context_type = p_context_type; - - double_buffer = false; - direct_render = false; - glx_minor = glx_major = 0; - use_vsync = false; - _current_window = nullptr; -} - -GLManager_X11::~GLManager_X11() { - release_current(); -} - -#endif -#endif diff --git a/platform/linuxbsd/gl_manager_x11.h b/platform/linuxbsd/gl_manager_x11.h deleted file mode 100644 index 4f78c45c88..0000000000 --- a/platform/linuxbsd/gl_manager_x11.h +++ /dev/null @@ -1,126 +0,0 @@ -/*************************************************************************/ -/* gl_manager_x11.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef GL_MANAGER_X11_H -#define GL_MANAGER_X11_H - -#ifdef X11_ENABLED - -#ifdef GLES3_ENABLED - -#include "core/os/os.h" -#include "core/templates/local_vector.h" -#include "servers/display_server.h" -#include -#include - -struct GLManager_X11_Private; - -class GLManager_X11 { -public: - enum ContextType { - GLES_3_0_COMPATIBLE, - }; - -private: - // any data specific to the window - struct GLWindow { - bool in_use = false; - - // the external ID .. should match the GL window number .. unused I think - DisplayServer::WindowID window_id = DisplayServer::INVALID_WINDOW_ID; - int width = 0; - int height = 0; - ::Window x11_window; - int gldisplay_id = 0; - }; - - struct GLDisplay { - GLDisplay() { context = nullptr; } - ~GLDisplay(); - GLManager_X11_Private *context = nullptr; - ::Display *x11_display; - XVisualInfo x_vi; - }; - - // just for convenience, window and display struct - struct XWinDisp { - ::Window x11_window; - ::Display *x11_display; - } _x_windisp; - - LocalVector _windows; - LocalVector _displays; - - GLWindow *_current_window = nullptr; - - void _internal_set_current_window(GLWindow *p_win); - - GLWindow &get_window(unsigned int id) { return _windows[id]; } - const GLWindow &get_window(unsigned int id) const { return _windows[id]; } - - const GLDisplay &get_current_display() const { return _displays[_current_window->gldisplay_id]; } - const GLDisplay &get_display(unsigned int id) { return _displays[id]; } - - bool double_buffer; - bool direct_render; - int glx_minor, glx_major; - bool use_vsync; - ContextType context_type; - -private: - int _find_or_create_display(Display *p_x11_display); - Error _create_context(GLDisplay &gl_display); - -public: - XVisualInfo get_vi(Display *p_display); - Error window_create(DisplayServer::WindowID p_window_id, ::Window p_window, Display *p_display, int p_width, int p_height); - void window_destroy(DisplayServer::WindowID p_window_id); - void window_resize(DisplayServer::WindowID p_window_id, int p_width, int p_height); - - void release_current(); - void make_current(); - void swap_buffers(); - - void window_make_current(DisplayServer::WindowID p_window_id); - - Error initialize(); - - void set_use_vsync(bool p_use); - bool is_using_vsync() const; - - GLManager_X11(const Vector2i &p_size, ContextType p_context_type); - ~GLManager_X11(); -}; - -#endif // GLES3_ENABLED -#endif // X11_ENABLED - -#endif // GL_MANAGER_X11_H diff --git a/platform/linuxbsd/key_mapping_x11.cpp b/platform/linuxbsd/key_mapping_x11.cpp deleted file mode 100644 index f774c99d99..0000000000 --- a/platform/linuxbsd/key_mapping_x11.cpp +++ /dev/null @@ -1,2001 +0,0 @@ -/*************************************************************************/ -/* key_mapping_x11.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "key_mapping_x11.h" - -/***** SCAN CODE CONVERSION ******/ - -struct _XTranslatePair { - KeySym keysym; - Key keycode; -}; - -static _XTranslatePair _xkeysym_to_keycode[] = { - // misc keys - - { XK_Escape, Key::ESCAPE }, - { XK_Tab, Key::TAB }, - { XK_ISO_Left_Tab, Key::BACKTAB }, - { XK_BackSpace, Key::BACKSPACE }, - { XK_Return, Key::ENTER }, - { XK_Insert, Key::INSERT }, - { XK_Delete, Key::KEY_DELETE }, - { XK_Clear, Key::KEY_DELETE }, - { XK_Pause, Key::PAUSE }, - { XK_Print, Key::PRINT }, - { XK_Home, Key::HOME }, - { XK_End, Key::END }, - { XK_Left, Key::LEFT }, - { XK_Up, Key::UP }, - { XK_Right, Key::RIGHT }, - { XK_Down, Key::DOWN }, - { XK_Prior, Key::PAGEUP }, - { XK_Next, Key::PAGEDOWN }, - { XK_Shift_L, Key::SHIFT }, - { XK_Shift_R, Key::SHIFT }, - { XK_Shift_Lock, Key::SHIFT }, - { XK_Control_L, Key::CTRL }, - { XK_Control_R, Key::CTRL }, - { XK_Meta_L, Key::META }, - { XK_Meta_R, Key::META }, - { XK_Alt_L, Key::ALT }, - { XK_Alt_R, Key::ALT }, - { XK_Caps_Lock, Key::CAPSLOCK }, - { XK_Num_Lock, Key::NUMLOCK }, - { XK_Scroll_Lock, Key::SCROLLLOCK }, - { XK_Super_L, Key::SUPER_L }, - { XK_Super_R, Key::SUPER_R }, - { XK_Menu, Key::MENU }, - { XK_Hyper_L, Key::HYPER_L }, - { XK_Hyper_R, Key::HYPER_R }, - { XK_Help, Key::HELP }, - { XK_KP_Space, Key::SPACE }, - { XK_KP_Tab, Key::TAB }, - { XK_KP_Enter, Key::KP_ENTER }, - { XK_Home, Key::HOME }, - { XK_Left, Key::LEFT }, - { XK_Up, Key::UP }, - { XK_Right, Key::RIGHT }, - { XK_Down, Key::DOWN }, - { XK_Prior, Key::PAGEUP }, - { XK_Next, Key::PAGEDOWN }, - { XK_End, Key::END }, - { XK_Begin, Key::CLEAR }, - { XK_Insert, Key::INSERT }, - { XK_Delete, Key::KEY_DELETE }, - //{ XK_KP_Equal, Key::EQUAL }, - //{ XK_KP_Separator, Key::COMMA }, - { XK_KP_Decimal, Key::KP_PERIOD }, - { XK_KP_Delete, Key::KP_PERIOD }, - { XK_KP_Multiply, Key::KP_MULTIPLY }, - { XK_KP_Divide, Key::KP_DIVIDE }, - { XK_KP_Subtract, Key::KP_SUBTRACT }, - { XK_KP_Add, Key::KP_ADD }, - { XK_KP_0, Key::KP_0 }, - { XK_KP_1, Key::KP_1 }, - { XK_KP_2, Key::KP_2 }, - { XK_KP_3, Key::KP_3 }, - { XK_KP_4, Key::KP_4 }, - { XK_KP_5, Key::KP_5 }, - { XK_KP_6, Key::KP_6 }, - { XK_KP_7, Key::KP_7 }, - { XK_KP_8, Key::KP_8 }, - { XK_KP_9, Key::KP_9 }, - // same keys but with numlock off - { XK_KP_Insert, Key::INSERT }, - { XK_KP_End, Key::END }, - { XK_KP_Down, Key::DOWN }, - { XK_KP_Page_Down, Key::PAGEDOWN }, - { XK_KP_Left, Key::LEFT }, - // X11 documents this (numpad 5) as "begin of line" but no toolkit - // seems to interpret it this way. - // On Windows this is emitting Key::Clear so for consistency it - // will be mapped to Key::Clear - { XK_KP_Begin, Key::CLEAR }, - { XK_KP_Right, Key::RIGHT }, - { XK_KP_Home, Key::HOME }, - { XK_KP_Up, Key::UP }, - { XK_KP_Page_Up, Key::PAGEUP }, - { XK_F1, Key::F1 }, - { XK_F2, Key::F2 }, - { XK_F3, Key::F3 }, - { XK_F4, Key::F4 }, - { XK_F5, Key::F5 }, - { XK_F6, Key::F6 }, - { XK_F7, Key::F7 }, - { XK_F8, Key::F8 }, - { XK_F9, Key::F9 }, - { XK_F10, Key::F10 }, - { XK_F11, Key::F11 }, - { XK_F12, Key::F12 }, - { XK_F13, Key::F13 }, - { XK_F14, Key::F14 }, - { XK_F15, Key::F15 }, - { XK_F16, Key::F16 }, - { XK_F17, Key::F17 }, - { XK_F18, Key::F18 }, - { XK_F19, Key::F19 }, - { XK_F20, Key::F20 }, - { XK_F21, Key::F21 }, - { XK_F22, Key::F22 }, - { XK_F23, Key::F23 }, - { XK_F24, Key::F24 }, - { XK_F25, Key::F25 }, - { XK_F26, Key::F26 }, - { XK_F27, Key::F27 }, - { XK_F28, Key::F28 }, - { XK_F29, Key::F29 }, - { XK_F30, Key::F30 }, - { XK_F31, Key::F31 }, - { XK_F32, Key::F32 }, - { XK_F33, Key::F33 }, - { XK_F34, Key::F34 }, - { XK_F35, Key::F35 }, - - // media keys - { XF86XK_Back, Key::BACK }, - { XF86XK_Forward, Key::FORWARD }, - { XF86XK_Stop, Key::STOP }, - { XF86XK_Refresh, Key::REFRESH }, - { XF86XK_Favorites, Key::FAVORITES }, - { XF86XK_AudioMedia, Key::LAUNCHMEDIA }, - { XF86XK_OpenURL, Key::OPENURL }, - { XF86XK_HomePage, Key::HOMEPAGE }, - { XF86XK_Search, Key::SEARCH }, - { XF86XK_AudioLowerVolume, Key::VOLUMEDOWN }, - { XF86XK_AudioMute, Key::VOLUMEMUTE }, - { XF86XK_AudioRaiseVolume, Key::VOLUMEUP }, - { XF86XK_AudioPlay, Key::MEDIAPLAY }, - { XF86XK_AudioStop, Key::MEDIASTOP }, - { XF86XK_AudioPrev, Key::MEDIAPREVIOUS }, - { XF86XK_AudioNext, Key::MEDIANEXT }, - { XF86XK_AudioRecord, Key::MEDIARECORD }, - - // launch keys - { XF86XK_Mail, Key::LAUNCHMAIL }, - { XF86XK_MyComputer, Key::LAUNCH0 }, - { XF86XK_Calculator, Key::LAUNCH1 }, - { XF86XK_Standby, Key::STANDBY }, - - { XF86XK_Launch0, Key::LAUNCH2 }, - { XF86XK_Launch1, Key::LAUNCH3 }, - { XF86XK_Launch2, Key::LAUNCH4 }, - { XF86XK_Launch3, Key::LAUNCH5 }, - { XF86XK_Launch4, Key::LAUNCH6 }, - { XF86XK_Launch5, Key::LAUNCH7 }, - { XF86XK_Launch6, Key::LAUNCH8 }, - { XF86XK_Launch7, Key::LAUNCH9 }, - { XF86XK_Launch8, Key::LAUNCHA }, - { XF86XK_Launch9, Key::LAUNCHB }, - { XF86XK_LaunchA, Key::LAUNCHC }, - { XF86XK_LaunchB, Key::LAUNCHD }, - { XF86XK_LaunchC, Key::LAUNCHE }, - { XF86XK_LaunchD, Key::LAUNCHF }, - - { 0, Key::NONE } -}; - -struct _TranslatePair { - Key keysym; - unsigned int keycode; -}; - -static _TranslatePair _scancode_to_keycode[] = { - { Key::ESCAPE, 0x09 }, - { Key::KEY_1, 0x0A }, - { Key::KEY_2, 0x0B }, - { Key::KEY_3, 0x0C }, - { Key::KEY_4, 0x0D }, - { Key::KEY_5, 0x0E }, - { Key::KEY_6, 0x0F }, - { Key::KEY_7, 0x10 }, - { Key::KEY_8, 0x11 }, - { Key::KEY_9, 0x12 }, - { Key::KEY_0, 0x13 }, - { Key::MINUS, 0x14 }, - { Key::EQUAL, 0x15 }, - { Key::BACKSPACE, 0x16 }, - { Key::TAB, 0x17 }, - { Key::Q, 0x18 }, - { Key::W, 0x19 }, - { Key::E, 0x1A }, - { Key::R, 0x1B }, - { Key::T, 0x1C }, - { Key::Y, 0x1D }, - { Key::U, 0x1E }, - { Key::I, 0x1F }, - { Key::O, 0x20 }, - { Key::P, 0x21 }, - { Key::BRACELEFT, 0x22 }, - { Key::BRACERIGHT, 0x23 }, - { Key::ENTER, 0x24 }, - { Key::CTRL, 0x25 }, - { Key::A, 0x26 }, - { Key::S, 0x27 }, - { Key::D, 0x28 }, - { Key::F, 0x29 }, - { Key::G, 0x2A }, - { Key::H, 0x2B }, - { Key::J, 0x2C }, - { Key::K, 0x2D }, - { Key::L, 0x2E }, - { Key::SEMICOLON, 0x2F }, - { Key::APOSTROPHE, 0x30 }, - { Key::QUOTELEFT, 0x31 }, - { Key::SHIFT, 0x32 }, - { Key::BACKSLASH, 0x33 }, - { Key::Z, 0x34 }, - { Key::X, 0x35 }, - { Key::C, 0x36 }, - { Key::V, 0x37 }, - { Key::B, 0x38 }, - { Key::N, 0x39 }, - { Key::M, 0x3A }, - { Key::COMMA, 0x3B }, - { Key::PERIOD, 0x3C }, - { Key::SLASH, 0x3D }, - { Key::SHIFT, 0x3E }, - { Key::KP_MULTIPLY, 0x3F }, - { Key::ALT, 0x40 }, - { Key::SPACE, 0x41 }, - { Key::CAPSLOCK, 0x42 }, - { Key::F1, 0x43 }, - { Key::F2, 0x44 }, - { Key::F3, 0x45 }, - { Key::F4, 0x46 }, - { Key::F5, 0x47 }, - { Key::F6, 0x48 }, - { Key::F7, 0x49 }, - { Key::F8, 0x4A }, - { Key::F9, 0x4B }, - { Key::F10, 0x4C }, - { Key::NUMLOCK, 0x4D }, - { Key::SCROLLLOCK, 0x4E }, - { Key::KP_7, 0x4F }, - { Key::KP_8, 0x50 }, - { Key::KP_9, 0x51 }, - { Key::KP_SUBTRACT, 0x52 }, - { Key::KP_4, 0x53 }, - { Key::KP_5, 0x54 }, - { Key::KP_6, 0x55 }, - { Key::KP_ADD, 0x56 }, - { Key::KP_1, 0x57 }, - { Key::KP_2, 0x58 }, - { Key::KP_3, 0x59 }, - { Key::KP_0, 0x5A }, - { Key::KP_PERIOD, 0x5B }, - //{ Key::???, 0x5E }, //NON US BACKSLASH - { Key::F11, 0x5F }, - { Key::F12, 0x60 }, - { Key::KP_ENTER, 0x68 }, - { Key::CTRL, 0x69 }, - { Key::KP_DIVIDE, 0x6A }, - { Key::PRINT, 0x6B }, - { Key::ALT, 0x6C }, - { Key::ENTER, 0x6D }, - { Key::HOME, 0x6E }, - { Key::UP, 0x6F }, - { Key::PAGEUP, 0x70 }, - { Key::LEFT, 0x71 }, - { Key::RIGHT, 0x72 }, - { Key::END, 0x73 }, - { Key::DOWN, 0x74 }, - { Key::PAGEDOWN, 0x75 }, - { Key::INSERT, 0x76 }, - { Key::KEY_DELETE, 0x77 }, - { Key::VOLUMEMUTE, 0x79 }, - { Key::VOLUMEDOWN, 0x7A }, - { Key::VOLUMEUP, 0x7B }, - { Key::PAUSE, 0x7F }, - { Key::SUPER_L, 0x85 }, - { Key::SUPER_R, 0x86 }, - { Key::MENU, 0x87 }, - { Key::F13, 0xBF }, - { Key::F14, 0xC0 }, - { Key::F15, 0xC1 }, - { Key::F16, 0xC2 }, - { Key::F17, 0xC3 }, - { Key::F18, 0xC4 }, - { Key::F19, 0xC5 }, - { Key::F20, 0xC6 }, - { Key::F21, 0xC7 }, - { Key::F22, 0xC8 }, - { Key::F23, 0xC9 }, - { Key::F24, 0xCA }, - { Key::F25, 0xCB }, - { Key::F26, 0xCC }, - { Key::F27, 0xCD }, - { Key::F28, 0xCE }, - { Key::F29, 0xCF }, - { Key::F30, 0xD0 }, - { Key::F31, 0xD1 }, - { Key::F32, 0xD2 }, - { Key::F33, 0xD3 }, - { Key::F34, 0xD4 }, - { Key::F35, 0xD5 }, - { Key::UNKNOWN, 0 } -}; - -Key KeyMappingX11::get_scancode(unsigned int p_code) { - Key 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; - } - } - - return keycode; -} - -unsigned int KeyMappingX11::get_xlibcode(Key p_keysym) { - unsigned int code = 0; - for (int i = 0; _scancode_to_keycode[i].keysym != Key::UNKNOWN; i++) { - if (_scancode_to_keycode[i].keysym == p_keysym) { - code = _scancode_to_keycode[i].keycode; - break; - } - } - - return code; -} - -Key KeyMappingX11::get_keycode(KeySym p_keysym) { - // kinda bruteforce.. could optimize. - - if (p_keysym < 0x100) { // Latin 1, maps 1-1 - return (Key)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) { - return _xkeysym_to_keycode[idx].keycode; - } - } - - return Key::NONE; -} - -KeySym KeyMappingX11::get_keysym(Key p_code) { - // kinda bruteforce.. could optimize. - - if (p_code < Key::END_LATIN1) { // Latin 1, maps 1-1 - return (KeySym)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) { - return _xkeysym_to_keycode[idx].keysym; - } - } - - return (KeySym)Key::NONE; -} - -/***** UNICODE CONVERSION ******/ - -// Tables taken from FOX toolkit - -struct _XTranslateUnicodePair { - KeySym keysym; - unsigned int unicode; -}; - -enum { - _KEYSYM_MAX = 759 -}; - -static _XTranslateUnicodePair _xkeysym_to_unicode[_KEYSYM_MAX] = { - { 0x01A1, 0x0104 }, - { 0x01A2, 0x02D8 }, - { 0x01A3, 0x0141 }, - { 0x01A5, 0x013D }, - { 0x01A6, 0x015A }, - { 0x01A9, 0x0160 }, - { 0x01AA, 0x015E }, - { 0x01AB, 0x0164 }, - { 0x01AC, 0x0179 }, - { 0x01AE, 0x017D }, - { 0x01AF, 0x017B }, - { 0x01B1, 0x0105 }, - { 0x01B2, 0x02DB }, - { 0x01B3, 0x0142 }, - { 0x01B5, 0x013E }, - { 0x01B6, 0x015B }, - { 0x01B7, 0x02C7 }, - { 0x01B9, 0x0161 }, - { 0x01BA, 0x015F }, - { 0x01BB, 0x0165 }, - { 0x01BC, 0x017A }, - { 0x01BD, 0x02DD }, - { 0x01BE, 0x017E }, - { 0x01BF, 0x017C }, - { 0x01C0, 0x0154 }, - { 0x01C3, 0x0102 }, - { 0x01C5, 0x0139 }, - { 0x01C6, 0x0106 }, - { 0x01C8, 0x010C }, - { 0x01CA, 0x0118 }, - { 0x01CC, 0x011A }, - { 0x01CF, 0x010E }, - { 0x01D0, 0x0110 }, - { 0x01D1, 0x0143 }, - { 0x01D2, 0x0147 }, - { 0x01D5, 0x0150 }, - { 0x01D8, 0x0158 }, - { 0x01D9, 0x016E }, - { 0x01DB, 0x0170 }, - { 0x01DE, 0x0162 }, - { 0x01E0, 0x0155 }, - { 0x01E3, 0x0103 }, - { 0x01E5, 0x013A }, - { 0x01E6, 0x0107 }, - { 0x01E8, 0x010D }, - { 0x01EA, 0x0119 }, - { 0x01EC, 0x011B }, - { 0x01EF, 0x010F }, - { 0x01F0, 0x0111 }, - { 0x01F1, 0x0144 }, - { 0x01F2, 0x0148 }, - { 0x01F5, 0x0151 }, - { 0x01F8, 0x0159 }, - { 0x01F9, 0x016F }, - { 0x01FB, 0x0171 }, - { 0x01FE, 0x0163 }, - { 0x01FF, 0x02D9 }, - { 0x02A1, 0x0126 }, - { 0x02A6, 0x0124 }, - { 0x02A9, 0x0130 }, - { 0x02AB, 0x011E }, - { 0x02AC, 0x0134 }, - { 0x02B1, 0x0127 }, - { 0x02B6, 0x0125 }, - { 0x02B9, 0x0131 }, - { 0x02BB, 0x011F }, - { 0x02BC, 0x0135 }, - { 0x02C5, 0x010A }, - { 0x02C6, 0x0108 }, - { 0x02D5, 0x0120 }, - { 0x02D8, 0x011C }, - { 0x02DD, 0x016C }, - { 0x02DE, 0x015C }, - { 0x02E5, 0x010B }, - { 0x02E6, 0x0109 }, - { 0x02F5, 0x0121 }, - { 0x02F8, 0x011D }, - { 0x02FD, 0x016D }, - { 0x02FE, 0x015D }, - { 0x03A2, 0x0138 }, - { 0x03A3, 0x0156 }, - { 0x03A5, 0x0128 }, - { 0x03A6, 0x013B }, - { 0x03AA, 0x0112 }, - { 0x03AB, 0x0122 }, - { 0x03AC, 0x0166 }, - { 0x03B3, 0x0157 }, - { 0x03B5, 0x0129 }, - { 0x03B6, 0x013C }, - { 0x03BA, 0x0113 }, - { 0x03BB, 0x0123 }, - { 0x03BC, 0x0167 }, - { 0x03BD, 0x014A }, - { 0x03BF, 0x014B }, - { 0x03C0, 0x0100 }, - { 0x03C7, 0x012E }, - { 0x03CC, 0x0116 }, - { 0x03CF, 0x012A }, - { 0x03D1, 0x0145 }, - { 0x03D2, 0x014C }, - { 0x03D3, 0x0136 }, - { 0x03D9, 0x0172 }, - { 0x03DD, 0x0168 }, - { 0x03DE, 0x016A }, - { 0x03E0, 0x0101 }, - { 0x03E7, 0x012F }, - { 0x03EC, 0x0117 }, - { 0x03EF, 0x012B }, - { 0x03F1, 0x0146 }, - { 0x03F2, 0x014D }, - { 0x03F3, 0x0137 }, - { 0x03F9, 0x0173 }, - { 0x03FD, 0x0169 }, - { 0x03FE, 0x016B }, - { 0x047E, 0x203E }, - { 0x04A1, 0x3002 }, - { 0x04A2, 0x300C }, - { 0x04A3, 0x300D }, - { 0x04A4, 0x3001 }, - { 0x04A5, 0x30FB }, - { 0x04A6, 0x30F2 }, - { 0x04A7, 0x30A1 }, - { 0x04A8, 0x30A3 }, - { 0x04A9, 0x30A5 }, - { 0x04AA, 0x30A7 }, - { 0x04AB, 0x30A9 }, - { 0x04AC, 0x30E3 }, - { 0x04AD, 0x30E5 }, - { 0x04AE, 0x30E7 }, - { 0x04AF, 0x30C3 }, - { 0x04B0, 0x30FC }, - { 0x04B1, 0x30A2 }, - { 0x04B2, 0x30A4 }, - { 0x04B3, 0x30A6 }, - { 0x04B4, 0x30A8 }, - { 0x04B5, 0x30AA }, - { 0x04B6, 0x30AB }, - { 0x04B7, 0x30AD }, - { 0x04B8, 0x30AF }, - { 0x04B9, 0x30B1 }, - { 0x04BA, 0x30B3 }, - { 0x04BB, 0x30B5 }, - { 0x04BC, 0x30B7 }, - { 0x04BD, 0x30B9 }, - { 0x04BE, 0x30BB }, - { 0x04BF, 0x30BD }, - { 0x04C0, 0x30BF }, - { 0x04C1, 0x30C1 }, - { 0x04C2, 0x30C4 }, - { 0x04C3, 0x30C6 }, - { 0x04C4, 0x30C8 }, - { 0x04C5, 0x30CA }, - { 0x04C6, 0x30CB }, - { 0x04C7, 0x30CC }, - { 0x04C8, 0x30CD }, - { 0x04C9, 0x30CE }, - { 0x04CA, 0x30CF }, - { 0x04CB, 0x30D2 }, - { 0x04CC, 0x30D5 }, - { 0x04CD, 0x30D8 }, - { 0x04CE, 0x30DB }, - { 0x04CF, 0x30DE }, - { 0x04D0, 0x30DF }, - { 0x04D1, 0x30E0 }, - { 0x04D2, 0x30E1 }, - { 0x04D3, 0x30E2 }, - { 0x04D4, 0x30E4 }, - { 0x04D5, 0x30E6 }, - { 0x04D6, 0x30E8 }, - { 0x04D7, 0x30E9 }, - { 0x04D8, 0x30EA }, - { 0x04D9, 0x30EB }, - { 0x04DA, 0x30EC }, - { 0x04DB, 0x30ED }, - { 0x04DC, 0x30EF }, - { 0x04DD, 0x30F3 }, - { 0x04DE, 0x309B }, - { 0x04DF, 0x309C }, - { 0x05AC, 0x060C }, - { 0x05BB, 0x061B }, - { 0x05BF, 0x061F }, - { 0x05C1, 0x0621 }, - { 0x05C2, 0x0622 }, - { 0x05C3, 0x0623 }, - { 0x05C4, 0x0624 }, - { 0x05C5, 0x0625 }, - { 0x05C6, 0x0626 }, - { 0x05C7, 0x0627 }, - { 0x05C8, 0x0628 }, - { 0x05C9, 0x0629 }, - { 0x05CA, 0x062A }, - { 0x05CB, 0x062B }, - { 0x05CC, 0x062C }, - { 0x05CD, 0x062D }, - { 0x05CE, 0x062E }, - { 0x05CF, 0x062F }, - { 0x05D0, 0x0630 }, - { 0x05D1, 0x0631 }, - { 0x05D2, 0x0632 }, - { 0x05D3, 0x0633 }, - { 0x05D4, 0x0634 }, - { 0x05D5, 0x0635 }, - { 0x05D6, 0x0636 }, - { 0x05D7, 0x0637 }, - { 0x05D8, 0x0638 }, - { 0x05D9, 0x0639 }, - { 0x05DA, 0x063A }, - { 0x05E0, 0x0640 }, - { 0x05E1, 0x0641 }, - { 0x05E2, 0x0642 }, - { 0x05E3, 0x0643 }, - { 0x05E4, 0x0644 }, - { 0x05E5, 0x0645 }, - { 0x05E6, 0x0646 }, - { 0x05E7, 0x0647 }, - { 0x05E8, 0x0648 }, - { 0x05E9, 0x0649 }, - { 0x05EA, 0x064A }, - { 0x05EB, 0x064B }, - { 0x05EC, 0x064C }, - { 0x05ED, 0x064D }, - { 0x05EE, 0x064E }, - { 0x05EF, 0x064F }, - { 0x05F0, 0x0650 }, - { 0x05F1, 0x0651 }, - { 0x05F2, 0x0652 }, - { 0x06A1, 0x0452 }, - { 0x06A2, 0x0453 }, - { 0x06A3, 0x0451 }, - { 0x06A4, 0x0454 }, - { 0x06A5, 0x0455 }, - { 0x06A6, 0x0456 }, - { 0x06A7, 0x0457 }, - { 0x06A8, 0x0458 }, - { 0x06A9, 0x0459 }, - { 0x06AA, 0x045A }, - { 0x06AB, 0x045B }, - { 0x06AC, 0x045C }, - { 0x06AE, 0x045E }, - { 0x06AF, 0x045F }, - { 0x06B0, 0x2116 }, - { 0x06B1, 0x0402 }, - { 0x06B2, 0x0403 }, - { 0x06B3, 0x0401 }, - { 0x06B4, 0x0404 }, - { 0x06B5, 0x0405 }, - { 0x06B6, 0x0406 }, - { 0x06B7, 0x0407 }, - { 0x06B8, 0x0408 }, - { 0x06B9, 0x0409 }, - { 0x06BA, 0x040A }, - { 0x06BB, 0x040B }, - { 0x06BC, 0x040C }, - { 0x06BE, 0x040E }, - { 0x06BF, 0x040F }, - { 0x06C0, 0x044E }, - { 0x06C1, 0x0430 }, - { 0x06C2, 0x0431 }, - { 0x06C3, 0x0446 }, - { 0x06C4, 0x0434 }, - { 0x06C5, 0x0435 }, - { 0x06C6, 0x0444 }, - { 0x06C7, 0x0433 }, - { 0x06C8, 0x0445 }, - { 0x06C9, 0x0438 }, - { 0x06CA, 0x0439 }, - { 0x06CB, 0x043A }, - { 0x06CC, 0x043B }, - { 0x06CD, 0x043C }, - { 0x06CE, 0x043D }, - { 0x06CF, 0x043E }, - { 0x06D0, 0x043F }, - { 0x06D1, 0x044F }, - { 0x06D2, 0x0440 }, - { 0x06D3, 0x0441 }, - { 0x06D4, 0x0442 }, - { 0x06D5, 0x0443 }, - { 0x06D6, 0x0436 }, - { 0x06D7, 0x0432 }, - { 0x06D8, 0x044C }, - { 0x06D9, 0x044B }, - { 0x06DA, 0x0437 }, - { 0x06DB, 0x0448 }, - { 0x06DC, 0x044D }, - { 0x06DD, 0x0449 }, - { 0x06DE, 0x0447 }, - { 0x06DF, 0x044A }, - { 0x06E0, 0x042E }, - { 0x06E1, 0x0410 }, - { 0x06E2, 0x0411 }, - { 0x06E3, 0x0426 }, - { 0x06E4, 0x0414 }, - { 0x06E5, 0x0415 }, - { 0x06E6, 0x0424 }, - { 0x06E7, 0x0413 }, - { 0x06E8, 0x0425 }, - { 0x06E9, 0x0418 }, - { 0x06EA, 0x0419 }, - { 0x06EB, 0x041A }, - { 0x06EC, 0x041B }, - { 0x06ED, 0x041C }, - { 0x06EE, 0x041D }, - { 0x06EF, 0x041E }, - { 0x06F0, 0x041F }, - { 0x06F1, 0x042F }, - { 0x06F2, 0x0420 }, - { 0x06F3, 0x0421 }, - { 0x06F4, 0x0422 }, - { 0x06F5, 0x0423 }, - { 0x06F6, 0x0416 }, - { 0x06F7, 0x0412 }, - { 0x06F8, 0x042C }, - { 0x06F9, 0x042B }, - { 0x06FA, 0x0417 }, - { 0x06FB, 0x0428 }, - { 0x06FC, 0x042D }, - { 0x06FD, 0x0429 }, - { 0x06FE, 0x0427 }, - { 0x06FF, 0x042A }, - { 0x07A1, 0x0386 }, - { 0x07A2, 0x0388 }, - { 0x07A3, 0x0389 }, - { 0x07A4, 0x038A }, - { 0x07A5, 0x03AA }, - { 0x07A7, 0x038C }, - { 0x07A8, 0x038E }, - { 0x07A9, 0x03AB }, - { 0x07AB, 0x038F }, - { 0x07AE, 0x0385 }, - { 0x07AF, 0x2015 }, - { 0x07B1, 0x03AC }, - { 0x07B2, 0x03AD }, - { 0x07B3, 0x03AE }, - { 0x07B4, 0x03AF }, - { 0x07B5, 0x03CA }, - { 0x07B6, 0x0390 }, - { 0x07B7, 0x03CC }, - { 0x07B8, 0x03CD }, - { 0x07B9, 0x03CB }, - { 0x07BA, 0x03B0 }, - { 0x07BB, 0x03CE }, - { 0x07C1, 0x0391 }, - { 0x07C2, 0x0392 }, - { 0x07C3, 0x0393 }, - { 0x07C4, 0x0394 }, - { 0x07C5, 0x0395 }, - { 0x07C6, 0x0396 }, - { 0x07C7, 0x0397 }, - { 0x07C8, 0x0398 }, - { 0x07C9, 0x0399 }, - { 0x07CA, 0x039A }, - { 0x07CB, 0x039B }, - { 0x07CC, 0x039C }, - { 0x07CD, 0x039D }, - { 0x07CE, 0x039E }, - { 0x07CF, 0x039F }, - { 0x07D0, 0x03A0 }, - { 0x07D1, 0x03A1 }, - { 0x07D2, 0x03A3 }, - { 0x07D4, 0x03A4 }, - { 0x07D5, 0x03A5 }, - { 0x07D6, 0x03A6 }, - { 0x07D7, 0x03A7 }, - { 0x07D8, 0x03A8 }, - { 0x07D9, 0x03A9 }, - { 0x07E1, 0x03B1 }, - { 0x07E2, 0x03B2 }, - { 0x07E3, 0x03B3 }, - { 0x07E4, 0x03B4 }, - { 0x07E5, 0x03B5 }, - { 0x07E6, 0x03B6 }, - { 0x07E7, 0x03B7 }, - { 0x07E8, 0x03B8 }, - { 0x07E9, 0x03B9 }, - { 0x07EA, 0x03BA }, - { 0x07EB, 0x03BB }, - { 0x07EC, 0x03BC }, - { 0x07ED, 0x03BD }, - { 0x07EE, 0x03BE }, - { 0x07EF, 0x03BF }, - { 0x07F0, 0x03C0 }, - { 0x07F1, 0x03C1 }, - { 0x07F2, 0x03C3 }, - { 0x07F3, 0x03C2 }, - { 0x07F4, 0x03C4 }, - { 0x07F5, 0x03C5 }, - { 0x07F6, 0x03C6 }, - { 0x07F7, 0x03C7 }, - { 0x07F8, 0x03C8 }, - { 0x07F9, 0x03C9 }, - { 0x08A1, 0x23B7 }, - { 0x08A2, 0x250C }, - { 0x08A3, 0x2500 }, - { 0x08A4, 0x2320 }, - { 0x08A5, 0x2321 }, - { 0x08A6, 0x2502 }, - { 0x08A7, 0x23A1 }, - { 0x08A8, 0x23A3 }, - { 0x08A9, 0x23A4 }, - { 0x08AA, 0x23A6 }, - { 0x08AB, 0x239B }, - { 0x08AC, 0x239D }, - { 0x08AD, 0x239E }, - { 0x08AE, 0x23A0 }, - { 0x08AF, 0x23A8 }, - { 0x08B0, 0x23AC }, - { 0x08BC, 0x2264 }, - { 0x08BD, 0x2260 }, - { 0x08BE, 0x2265 }, - { 0x08BF, 0x222B }, - { 0x08C0, 0x2234 }, - { 0x08C1, 0x221D }, - { 0x08C2, 0x221E }, - { 0x08C5, 0x2207 }, - { 0x08C8, 0x223C }, - { 0x08C9, 0x2243 }, - { 0x08CD, 0x21D4 }, - { 0x08CE, 0x21D2 }, - { 0x08CF, 0x2261 }, - { 0x08D6, 0x221A }, - { 0x08DA, 0x2282 }, - { 0x08DB, 0x2283 }, - { 0x08DC, 0x2229 }, - { 0x08DD, 0x222A }, - { 0x08DE, 0x2227 }, - { 0x08DF, 0x2228 }, - { 0x08EF, 0x2202 }, - { 0x08F6, 0x0192 }, - { 0x08FB, 0x2190 }, - { 0x08FC, 0x2191 }, - { 0x08FD, 0x2192 }, - { 0x08FE, 0x2193 }, - { 0x09E0, 0x25C6 }, - { 0x09E1, 0x2592 }, - { 0x09E2, 0x2409 }, - { 0x09E3, 0x240C }, - { 0x09E4, 0x240D }, - { 0x09E5, 0x240A }, - { 0x09E8, 0x2424 }, - { 0x09E9, 0x240B }, - { 0x09EA, 0x2518 }, - { 0x09EB, 0x2510 }, - { 0x09EC, 0x250C }, - { 0x09ED, 0x2514 }, - { 0x09EE, 0x253C }, - { 0x09EF, 0x23BA }, - { 0x09F0, 0x23BB }, - { 0x09F1, 0x2500 }, - { 0x09F2, 0x23BC }, - { 0x09F3, 0x23BD }, - { 0x09F4, 0x251C }, - { 0x09F5, 0x2524 }, - { 0x09F6, 0x2534 }, - { 0x09F7, 0x252C }, - { 0x09F8, 0x2502 }, - { 0x0AA1, 0x2003 }, - { 0x0AA2, 0x2002 }, - { 0x0AA3, 0x2004 }, - { 0x0AA4, 0x2005 }, - { 0x0AA5, 0x2007 }, - { 0x0AA6, 0x2008 }, - { 0x0AA7, 0x2009 }, - { 0x0AA8, 0x200A }, - { 0x0AA9, 0x2014 }, - { 0x0AAA, 0x2013 }, - { 0x0AAE, 0x2026 }, - { 0x0AAF, 0x2025 }, - { 0x0AB0, 0x2153 }, - { 0x0AB1, 0x2154 }, - { 0x0AB2, 0x2155 }, - { 0x0AB3, 0x2156 }, - { 0x0AB4, 0x2157 }, - { 0x0AB5, 0x2158 }, - { 0x0AB6, 0x2159 }, - { 0x0AB7, 0x215A }, - { 0x0AB8, 0x2105 }, - { 0x0ABB, 0x2012 }, - { 0x0ABC, 0x2329 }, - { 0x0ABE, 0x232A }, - { 0x0AC3, 0x215B }, - { 0x0AC4, 0x215C }, - { 0x0AC5, 0x215D }, - { 0x0AC6, 0x215E }, - { 0x0AC9, 0x2122 }, - { 0x0ACA, 0x2613 }, - { 0x0ACC, 0x25C1 }, - { 0x0ACD, 0x25B7 }, - { 0x0ACE, 0x25CB }, - { 0x0ACF, 0x25AF }, - { 0x0AD0, 0x2018 }, - { 0x0AD1, 0x2019 }, - { 0x0AD2, 0x201C }, - { 0x0AD3, 0x201D }, - { 0x0AD4, 0x211E }, - { 0x0AD6, 0x2032 }, - { 0x0AD7, 0x2033 }, - { 0x0AD9, 0x271D }, - { 0x0ADB, 0x25AC }, - { 0x0ADC, 0x25C0 }, - { 0x0ADD, 0x25B6 }, - { 0x0ADE, 0x25CF }, - { 0x0ADF, 0x25AE }, - { 0x0AE0, 0x25E6 }, - { 0x0AE1, 0x25AB }, - { 0x0AE2, 0x25AD }, - { 0x0AE3, 0x25B3 }, - { 0x0AE4, 0x25BD }, - { 0x0AE5, 0x2606 }, - { 0x0AE6, 0x2022 }, - { 0x0AE7, 0x25AA }, - { 0x0AE8, 0x25B2 }, - { 0x0AE9, 0x25BC }, - { 0x0AEA, 0x261C }, - { 0x0AEB, 0x261E }, - { 0x0AEC, 0x2663 }, - { 0x0AED, 0x2666 }, - { 0x0AEE, 0x2665 }, - { 0x0AF0, 0x2720 }, - { 0x0AF1, 0x2020 }, - { 0x0AF2, 0x2021 }, - { 0x0AF3, 0x2713 }, - { 0x0AF4, 0x2717 }, - { 0x0AF5, 0x266F }, - { 0x0AF6, 0x266D }, - { 0x0AF7, 0x2642 }, - { 0x0AF8, 0x2640 }, - { 0x0AF9, 0x260E }, - { 0x0AFA, 0x2315 }, - { 0x0AFB, 0x2117 }, - { 0x0AFC, 0x2038 }, - { 0x0AFD, 0x201A }, - { 0x0AFE, 0x201E }, - { 0x0BA3, 0x003C }, - { 0x0BA6, 0x003E }, - { 0x0BA8, 0x2228 }, - { 0x0BA9, 0x2227 }, - { 0x0BC0, 0x00AF }, - { 0x0BC2, 0x22A5 }, - { 0x0BC3, 0x2229 }, - { 0x0BC4, 0x230A }, - { 0x0BC6, 0x005F }, - { 0x0BCA, 0x2218 }, - { 0x0BCC, 0x2395 }, - { 0x0BCE, 0x22A4 }, - { 0x0BCF, 0x25CB }, - { 0x0BD3, 0x2308 }, - { 0x0BD6, 0x222A }, - { 0x0BD8, 0x2283 }, - { 0x0BDA, 0x2282 }, - { 0x0BDC, 0x22A2 }, - { 0x0BFC, 0x22A3 }, - { 0x0CDF, 0x2017 }, - { 0x0CE0, 0x05D0 }, - { 0x0CE1, 0x05D1 }, - { 0x0CE2, 0x05D2 }, - { 0x0CE3, 0x05D3 }, - { 0x0CE4, 0x05D4 }, - { 0x0CE5, 0x05D5 }, - { 0x0CE6, 0x05D6 }, - { 0x0CE7, 0x05D7 }, - { 0x0CE8, 0x05D8 }, - { 0x0CE9, 0x05D9 }, - { 0x0CEA, 0x05DA }, - { 0x0CEB, 0x05DB }, - { 0x0CEC, 0x05DC }, - { 0x0CED, 0x05DD }, - { 0x0CEE, 0x05DE }, - { 0x0CEF, 0x05DF }, - { 0x0CF0, 0x05E0 }, - { 0x0CF1, 0x05E1 }, - { 0x0CF2, 0x05E2 }, - { 0x0CF3, 0x05E3 }, - { 0x0CF4, 0x05E4 }, - { 0x0CF5, 0x05E5 }, - { 0x0CF6, 0x05E6 }, - { 0x0CF7, 0x05E7 }, - { 0x0CF8, 0x05E8 }, - { 0x0CF9, 0x05E9 }, - { 0x0CFA, 0x05EA }, - { 0x0DA1, 0x0E01 }, - { 0x0DA2, 0x0E02 }, - { 0x0DA3, 0x0E03 }, - { 0x0DA4, 0x0E04 }, - { 0x0DA5, 0x0E05 }, - { 0x0DA6, 0x0E06 }, - { 0x0DA7, 0x0E07 }, - { 0x0DA8, 0x0E08 }, - { 0x0DA9, 0x0E09 }, - { 0x0DAA, 0x0E0A }, - { 0x0DAB, 0x0E0B }, - { 0x0DAC, 0x0E0C }, - { 0x0DAD, 0x0E0D }, - { 0x0DAE, 0x0E0E }, - { 0x0DAF, 0x0E0F }, - { 0x0DB0, 0x0E10 }, - { 0x0DB1, 0x0E11 }, - { 0x0DB2, 0x0E12 }, - { 0x0DB3, 0x0E13 }, - { 0x0DB4, 0x0E14 }, - { 0x0DB5, 0x0E15 }, - { 0x0DB6, 0x0E16 }, - { 0x0DB7, 0x0E17 }, - { 0x0DB8, 0x0E18 }, - { 0x0DB9, 0x0E19 }, - { 0x0DBA, 0x0E1A }, - { 0x0DBB, 0x0E1B }, - { 0x0DBC, 0x0E1C }, - { 0x0DBD, 0x0E1D }, - { 0x0DBE, 0x0E1E }, - { 0x0DBF, 0x0E1F }, - { 0x0DC0, 0x0E20 }, - { 0x0DC1, 0x0E21 }, - { 0x0DC2, 0x0E22 }, - { 0x0DC3, 0x0E23 }, - { 0x0DC4, 0x0E24 }, - { 0x0DC5, 0x0E25 }, - { 0x0DC6, 0x0E26 }, - { 0x0DC7, 0x0E27 }, - { 0x0DC8, 0x0E28 }, - { 0x0DC9, 0x0E29 }, - { 0x0DCA, 0x0E2A }, - { 0x0DCB, 0x0E2B }, - { 0x0DCC, 0x0E2C }, - { 0x0DCD, 0x0E2D }, - { 0x0DCE, 0x0E2E }, - { 0x0DCF, 0x0E2F }, - { 0x0DD0, 0x0E30 }, - { 0x0DD1, 0x0E31 }, - { 0x0DD2, 0x0E32 }, - { 0x0DD3, 0x0E33 }, - { 0x0DD4, 0x0E34 }, - { 0x0DD5, 0x0E35 }, - { 0x0DD6, 0x0E36 }, - { 0x0DD7, 0x0E37 }, - { 0x0DD8, 0x0E38 }, - { 0x0DD9, 0x0E39 }, - { 0x0DDA, 0x0E3A }, - { 0x0DDF, 0x0E3F }, - { 0x0DE0, 0x0E40 }, - { 0x0DE1, 0x0E41 }, - { 0x0DE2, 0x0E42 }, - { 0x0DE3, 0x0E43 }, - { 0x0DE4, 0x0E44 }, - { 0x0DE5, 0x0E45 }, - { 0x0DE6, 0x0E46 }, - { 0x0DE7, 0x0E47 }, - { 0x0DE8, 0x0E48 }, - { 0x0DE9, 0x0E49 }, - { 0x0DEA, 0x0E4A }, - { 0x0DEB, 0x0E4B }, - { 0x0DEC, 0x0E4C }, - { 0x0DED, 0x0E4D }, - { 0x0DF0, 0x0E50 }, - { 0x0DF1, 0x0E51 }, - { 0x0DF2, 0x0E52 }, - { 0x0DF3, 0x0E53 }, - { 0x0DF4, 0x0E54 }, - { 0x0DF5, 0x0E55 }, - { 0x0DF6, 0x0E56 }, - { 0x0DF7, 0x0E57 }, - { 0x0DF8, 0x0E58 }, - { 0x0DF9, 0x0E59 }, - { 0x0EA1, 0x3131 }, - { 0x0EA2, 0x3132 }, - { 0x0EA3, 0x3133 }, - { 0x0EA4, 0x3134 }, - { 0x0EA5, 0x3135 }, - { 0x0EA6, 0x3136 }, - { 0x0EA7, 0x3137 }, - { 0x0EA8, 0x3138 }, - { 0x0EA9, 0x3139 }, - { 0x0EAA, 0x313A }, - { 0x0EAB, 0x313B }, - { 0x0EAC, 0x313C }, - { 0x0EAD, 0x313D }, - { 0x0EAE, 0x313E }, - { 0x0EAF, 0x313F }, - { 0x0EB0, 0x3140 }, - { 0x0EB1, 0x3141 }, - { 0x0EB2, 0x3142 }, - { 0x0EB3, 0x3143 }, - { 0x0EB4, 0x3144 }, - { 0x0EB5, 0x3145 }, - { 0x0EB6, 0x3146 }, - { 0x0EB7, 0x3147 }, - { 0x0EB8, 0x3148 }, - { 0x0EB9, 0x3149 }, - { 0x0EBA, 0x314A }, - { 0x0EBB, 0x314B }, - { 0x0EBC, 0x314C }, - { 0x0EBD, 0x314D }, - { 0x0EBE, 0x314E }, - { 0x0EBF, 0x314F }, - { 0x0EC0, 0x3150 }, - { 0x0EC1, 0x3151 }, - { 0x0EC2, 0x3152 }, - { 0x0EC3, 0x3153 }, - { 0x0EC4, 0x3154 }, - { 0x0EC5, 0x3155 }, - { 0x0EC6, 0x3156 }, - { 0x0EC7, 0x3157 }, - { 0x0EC8, 0x3158 }, - { 0x0EC9, 0x3159 }, - { 0x0ECA, 0x315A }, - { 0x0ECB, 0x315B }, - { 0x0ECC, 0x315C }, - { 0x0ECD, 0x315D }, - { 0x0ECE, 0x315E }, - { 0x0ECF, 0x315F }, - { 0x0ED0, 0x3160 }, - { 0x0ED1, 0x3161 }, - { 0x0ED2, 0x3162 }, - { 0x0ED3, 0x3163 }, - { 0x0ED4, 0x11A8 }, - { 0x0ED5, 0x11A9 }, - { 0x0ED6, 0x11AA }, - { 0x0ED7, 0x11AB }, - { 0x0ED8, 0x11AC }, - { 0x0ED9, 0x11AD }, - { 0x0EDA, 0x11AE }, - { 0x0EDB, 0x11AF }, - { 0x0EDC, 0x11B0 }, - { 0x0EDD, 0x11B1 }, - { 0x0EDE, 0x11B2 }, - { 0x0EDF, 0x11B3 }, - { 0x0EE0, 0x11B4 }, - { 0x0EE1, 0x11B5 }, - { 0x0EE2, 0x11B6 }, - { 0x0EE3, 0x11B7 }, - { 0x0EE4, 0x11B8 }, - { 0x0EE5, 0x11B9 }, - { 0x0EE6, 0x11BA }, - { 0x0EE7, 0x11BB }, - { 0x0EE8, 0x11BC }, - { 0x0EE9, 0x11BD }, - { 0x0EEA, 0x11BE }, - { 0x0EEB, 0x11BF }, - { 0x0EEC, 0x11C0 }, - { 0x0EED, 0x11C1 }, - { 0x0EEE, 0x11C2 }, - { 0x0EEF, 0x316D }, - { 0x0EF0, 0x3171 }, - { 0x0EF1, 0x3178 }, - { 0x0EF2, 0x317F }, - { 0x0EF3, 0x3181 }, - { 0x0EF4, 0x3184 }, - { 0x0EF5, 0x3186 }, - { 0x0EF6, 0x318D }, - { 0x0EF7, 0x318E }, - { 0x0EF8, 0x11EB }, - { 0x0EF9, 0x11F0 }, - { 0x0EFA, 0x11F9 }, - { 0x0EFF, 0x20A9 }, - { 0x13A4, 0x20AC }, - { 0x13BC, 0x0152 }, - { 0x13BD, 0x0153 }, - { 0x13BE, 0x0178 }, - { 0x20AC, 0x20AC }, -}; - -unsigned int KeyMappingX11::get_unicode_from_keysym(KeySym p_keysym) { - /* Latin-1 */ - if (p_keysym >= 0x20 && p_keysym <= 0x7e) { - return p_keysym; - } - if (p_keysym >= 0xa0 && p_keysym <= 0xff) { - return p_keysym; - } - // keypad to latin1 is easy - if (p_keysym >= 0xffaa && p_keysym <= 0xffb9) { - return p_keysym - 0xff80; - } - - /* Unicode (may be present)*/ - - 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) { - return _xkeysym_to_unicode[middle].unicode; - } - if (_xkeysym_to_unicode[middle].keysym <= p_keysym) { - low = middle + 1; - } else { - high = middle - 1; - } - } while (high >= low); - - return 0; -} - -struct _XTranslateUnicodePairReverse { - unsigned int unicode; - KeySym keysym; -}; - -enum { - _UNICODE_MAX = 750 -}; - -static _XTranslateUnicodePairReverse _unicode_to_xkeysym[_UNICODE_MAX] = { - { 0x0ABD, 0x002E }, - { 0x0BA3, 0x003C }, - { 0x0BA6, 0x003E }, - { 0x0BC6, 0x005F }, - { 0x0BC0, 0x00AF }, - { 0x03C0, 0x0100 }, - { 0x03E0, 0x0101 }, - { 0x01C3, 0x0102 }, - { 0x01E3, 0x0103 }, - { 0x01A1, 0x0104 }, - { 0x01B1, 0x0105 }, - { 0x01C6, 0x0106 }, - { 0x01E6, 0x0107 }, - { 0x02C6, 0x0108 }, - { 0x02E6, 0x0109 }, - { 0x02C5, 0x010A }, - { 0x02E5, 0x010B }, - { 0x01C8, 0x010C }, - { 0x01E8, 0x010D }, - { 0x01CF, 0x010E }, - { 0x01EF, 0x010F }, - { 0x01D0, 0x0110 }, - { 0x01F0, 0x0111 }, - { 0x03AA, 0x0112 }, - { 0x03BA, 0x0113 }, - { 0x03CC, 0x0116 }, - { 0x03EC, 0x0117 }, - { 0x01CA, 0x0118 }, - { 0x01EA, 0x0119 }, - { 0x01CC, 0x011A }, - { 0x01EC, 0x011B }, - { 0x02D8, 0x011C }, - { 0x02F8, 0x011D }, - { 0x02AB, 0x011E }, - { 0x02BB, 0x011F }, - { 0x02D5, 0x0120 }, - { 0x02F5, 0x0121 }, - { 0x03AB, 0x0122 }, - { 0x03BB, 0x0123 }, - { 0x02A6, 0x0124 }, - { 0x02B6, 0x0125 }, - { 0x02A1, 0x0126 }, - { 0x02B1, 0x0127 }, - { 0x03A5, 0x0128 }, - { 0x03B5, 0x0129 }, - { 0x03CF, 0x012A }, - { 0x03EF, 0x012B }, - { 0x03C7, 0x012E }, - { 0x03E7, 0x012F }, - { 0x02A9, 0x0130 }, - { 0x02B9, 0x0131 }, - { 0x02AC, 0x0134 }, - { 0x02BC, 0x0135 }, - { 0x03D3, 0x0136 }, - { 0x03F3, 0x0137 }, - { 0x03A2, 0x0138 }, - { 0x01C5, 0x0139 }, - { 0x01E5, 0x013A }, - { 0x03A6, 0x013B }, - { 0x03B6, 0x013C }, - { 0x01A5, 0x013D }, - { 0x01B5, 0x013E }, - { 0x01A3, 0x0141 }, - { 0x01B3, 0x0142 }, - { 0x01D1, 0x0143 }, - { 0x01F1, 0x0144 }, - { 0x03D1, 0x0145 }, - { 0x03F1, 0x0146 }, - { 0x01D2, 0x0147 }, - { 0x01F2, 0x0148 }, - { 0x03BD, 0x014A }, - { 0x03BF, 0x014B }, - { 0x03D2, 0x014C }, - { 0x03F2, 0x014D }, - { 0x01D5, 0x0150 }, - { 0x01F5, 0x0151 }, - { 0x13BC, 0x0152 }, - { 0x13BD, 0x0153 }, - { 0x01C0, 0x0154 }, - { 0x01E0, 0x0155 }, - { 0x03A3, 0x0156 }, - { 0x03B3, 0x0157 }, - { 0x01D8, 0x0158 }, - { 0x01F8, 0x0159 }, - { 0x01A6, 0x015A }, - { 0x01B6, 0x015B }, - { 0x02DE, 0x015C }, - { 0x02FE, 0x015D }, - { 0x01AA, 0x015E }, - { 0x01BA, 0x015F }, - { 0x01A9, 0x0160 }, - { 0x01B9, 0x0161 }, - { 0x01DE, 0x0162 }, - { 0x01FE, 0x0163 }, - { 0x01AB, 0x0164 }, - { 0x01BB, 0x0165 }, - { 0x03AC, 0x0166 }, - { 0x03BC, 0x0167 }, - { 0x03DD, 0x0168 }, - { 0x03FD, 0x0169 }, - { 0x03DE, 0x016A }, - { 0x03FE, 0x016B }, - { 0x02DD, 0x016C }, - { 0x02FD, 0x016D }, - { 0x01D9, 0x016E }, - { 0x01F9, 0x016F }, - { 0x01DB, 0x0170 }, - { 0x01FB, 0x0171 }, - { 0x03D9, 0x0172 }, - { 0x03F9, 0x0173 }, - { 0x13BE, 0x0178 }, - { 0x01AC, 0x0179 }, - { 0x01BC, 0x017A }, - { 0x01AF, 0x017B }, - { 0x01BF, 0x017C }, - { 0x01AE, 0x017D }, - { 0x01BE, 0x017E }, - { 0x08F6, 0x0192 }, - { 0x01B7, 0x02C7 }, - { 0x01A2, 0x02D8 }, - { 0x01FF, 0x02D9 }, - { 0x01B2, 0x02DB }, - { 0x01BD, 0x02DD }, - { 0x07AE, 0x0385 }, - { 0x07A1, 0x0386 }, - { 0x07A2, 0x0388 }, - { 0x07A3, 0x0389 }, - { 0x07A4, 0x038A }, - { 0x07A7, 0x038C }, - { 0x07A8, 0x038E }, - { 0x07AB, 0x038F }, - { 0x07B6, 0x0390 }, - { 0x07C1, 0x0391 }, - { 0x07C2, 0x0392 }, - { 0x07C3, 0x0393 }, - { 0x07C4, 0x0394 }, - { 0x07C5, 0x0395 }, - { 0x07C6, 0x0396 }, - { 0x07C7, 0x0397 }, - { 0x07C8, 0x0398 }, - { 0x07C9, 0x0399 }, - { 0x07CA, 0x039A }, - { 0x07CB, 0x039B }, - { 0x07CC, 0x039C }, - { 0x07CD, 0x039D }, - { 0x07CE, 0x039E }, - { 0x07CF, 0x039F }, - { 0x07D0, 0x03A0 }, - { 0x07D1, 0x03A1 }, - { 0x07D2, 0x03A3 }, - { 0x07D4, 0x03A4 }, - { 0x07D5, 0x03A5 }, - { 0x07D6, 0x03A6 }, - { 0x07D7, 0x03A7 }, - { 0x07D8, 0x03A8 }, - { 0x07D9, 0x03A9 }, - { 0x07A5, 0x03AA }, - { 0x07A9, 0x03AB }, - { 0x07B1, 0x03AC }, - { 0x07B2, 0x03AD }, - { 0x07B3, 0x03AE }, - { 0x07B4, 0x03AF }, - { 0x07BA, 0x03B0 }, - { 0x07E1, 0x03B1 }, - { 0x07E2, 0x03B2 }, - { 0x07E3, 0x03B3 }, - { 0x07E4, 0x03B4 }, - { 0x07E5, 0x03B5 }, - { 0x07E6, 0x03B6 }, - { 0x07E7, 0x03B7 }, - { 0x07E8, 0x03B8 }, - { 0x07E9, 0x03B9 }, - { 0x07EA, 0x03BA }, - { 0x07EB, 0x03BB }, - { 0x07EC, 0x03BC }, - { 0x07ED, 0x03BD }, - { 0x07EE, 0x03BE }, - { 0x07EF, 0x03BF }, - { 0x07F0, 0x03C0 }, - { 0x07F1, 0x03C1 }, - { 0x07F3, 0x03C2 }, - { 0x07F2, 0x03C3 }, - { 0x07F4, 0x03C4 }, - { 0x07F5, 0x03C5 }, - { 0x07F6, 0x03C6 }, - { 0x07F7, 0x03C7 }, - { 0x07F8, 0x03C8 }, - { 0x07F9, 0x03C9 }, - { 0x07B5, 0x03CA }, - { 0x07B9, 0x03CB }, - { 0x07B7, 0x03CC }, - { 0x07B8, 0x03CD }, - { 0x07BB, 0x03CE }, - { 0x06B3, 0x0401 }, - { 0x06B1, 0x0402 }, - { 0x06B2, 0x0403 }, - { 0x06B4, 0x0404 }, - { 0x06B5, 0x0405 }, - { 0x06B6, 0x0406 }, - { 0x06B7, 0x0407 }, - { 0x06B8, 0x0408 }, - { 0x06B9, 0x0409 }, - { 0x06BA, 0x040A }, - { 0x06BB, 0x040B }, - { 0x06BC, 0x040C }, - { 0x06BE, 0x040E }, - { 0x06BF, 0x040F }, - { 0x06E1, 0x0410 }, - { 0x06E2, 0x0411 }, - { 0x06F7, 0x0412 }, - { 0x06E7, 0x0413 }, - { 0x06E4, 0x0414 }, - { 0x06E5, 0x0415 }, - { 0x06F6, 0x0416 }, - { 0x06FA, 0x0417 }, - { 0x06E9, 0x0418 }, - { 0x06EA, 0x0419 }, - { 0x06EB, 0x041A }, - { 0x06EC, 0x041B }, - { 0x06ED, 0x041C }, - { 0x06EE, 0x041D }, - { 0x06EF, 0x041E }, - { 0x06F0, 0x041F }, - { 0x06F2, 0x0420 }, - { 0x06F3, 0x0421 }, - { 0x06F4, 0x0422 }, - { 0x06F5, 0x0423 }, - { 0x06E6, 0x0424 }, - { 0x06E8, 0x0425 }, - { 0x06E3, 0x0426 }, - { 0x06FE, 0x0427 }, - { 0x06FB, 0x0428 }, - { 0x06FD, 0x0429 }, - { 0x06FF, 0x042A }, - { 0x06F9, 0x042B }, - { 0x06F8, 0x042C }, - { 0x06FC, 0x042D }, - { 0x06E0, 0x042E }, - { 0x06F1, 0x042F }, - { 0x06C1, 0x0430 }, - { 0x06C2, 0x0431 }, - { 0x06D7, 0x0432 }, - { 0x06C7, 0x0433 }, - { 0x06C4, 0x0434 }, - { 0x06C5, 0x0435 }, - { 0x06D6, 0x0436 }, - { 0x06DA, 0x0437 }, - { 0x06C9, 0x0438 }, - { 0x06CA, 0x0439 }, - { 0x06CB, 0x043A }, - { 0x06CC, 0x043B }, - { 0x06CD, 0x043C }, - { 0x06CE, 0x043D }, - { 0x06CF, 0x043E }, - { 0x06D0, 0x043F }, - { 0x06D2, 0x0440 }, - { 0x06D3, 0x0441 }, - { 0x06D4, 0x0442 }, - { 0x06D5, 0x0443 }, - { 0x06C6, 0x0444 }, - { 0x06C8, 0x0445 }, - { 0x06C3, 0x0446 }, - { 0x06DE, 0x0447 }, - { 0x06DB, 0x0448 }, - { 0x06DD, 0x0449 }, - { 0x06DF, 0x044A }, - { 0x06D9, 0x044B }, - { 0x06D8, 0x044C }, - { 0x06DC, 0x044D }, - { 0x06C0, 0x044E }, - { 0x06D1, 0x044F }, - { 0x06A3, 0x0451 }, - { 0x06A1, 0x0452 }, - { 0x06A2, 0x0453 }, - { 0x06A4, 0x0454 }, - { 0x06A5, 0x0455 }, - { 0x06A6, 0x0456 }, - { 0x06A7, 0x0457 }, - { 0x06A8, 0x0458 }, - { 0x06A9, 0x0459 }, - { 0x06AA, 0x045A }, - { 0x06AB, 0x045B }, - { 0x06AC, 0x045C }, - { 0x06AE, 0x045E }, - { 0x06AF, 0x045F }, - { 0x0CE0, 0x05D0 }, - { 0x0CE1, 0x05D1 }, - { 0x0CE2, 0x05D2 }, - { 0x0CE3, 0x05D3 }, - { 0x0CE4, 0x05D4 }, - { 0x0CE5, 0x05D5 }, - { 0x0CE6, 0x05D6 }, - { 0x0CE7, 0x05D7 }, - { 0x0CE8, 0x05D8 }, - { 0x0CE9, 0x05D9 }, - { 0x0CEA, 0x05DA }, - { 0x0CEB, 0x05DB }, - { 0x0CEC, 0x05DC }, - { 0x0CED, 0x05DD }, - { 0x0CEE, 0x05DE }, - { 0x0CEF, 0x05DF }, - { 0x0CF0, 0x05E0 }, - { 0x0CF1, 0x05E1 }, - { 0x0CF2, 0x05E2 }, - { 0x0CF3, 0x05E3 }, - { 0x0CF4, 0x05E4 }, - { 0x0CF5, 0x05E5 }, - { 0x0CF6, 0x05E6 }, - { 0x0CF7, 0x05E7 }, - { 0x0CF8, 0x05E8 }, - { 0x0CF9, 0x05E9 }, - { 0x0CFA, 0x05EA }, - { 0x05AC, 0x060C }, - { 0x05BB, 0x061B }, - { 0x05BF, 0x061F }, - { 0x05C1, 0x0621 }, - { 0x05C2, 0x0622 }, - { 0x05C3, 0x0623 }, - { 0x05C4, 0x0624 }, - { 0x05C5, 0x0625 }, - { 0x05C6, 0x0626 }, - { 0x05C7, 0x0627 }, - { 0x05C8, 0x0628 }, - { 0x05C9, 0x0629 }, - { 0x05CA, 0x062A }, - { 0x05CB, 0x062B }, - { 0x05CC, 0x062C }, - { 0x05CD, 0x062D }, - { 0x05CE, 0x062E }, - { 0x05CF, 0x062F }, - { 0x05D0, 0x0630 }, - { 0x05D1, 0x0631 }, - { 0x05D2, 0x0632 }, - { 0x05D3, 0x0633 }, - { 0x05D4, 0x0634 }, - { 0x05D5, 0x0635 }, - { 0x05D6, 0x0636 }, - { 0x05D7, 0x0637 }, - { 0x05D8, 0x0638 }, - { 0x05D9, 0x0639 }, - { 0x05DA, 0x063A }, - { 0x05E0, 0x0640 }, - { 0x05E1, 0x0641 }, - { 0x05E2, 0x0642 }, - { 0x05E3, 0x0643 }, - { 0x05E4, 0x0644 }, - { 0x05E5, 0x0645 }, - { 0x05E6, 0x0646 }, - { 0x05E7, 0x0647 }, - { 0x05E8, 0x0648 }, - { 0x05E9, 0x0649 }, - { 0x05EA, 0x064A }, - { 0x05EB, 0x064B }, - { 0x05EC, 0x064C }, - { 0x05ED, 0x064D }, - { 0x05EE, 0x064E }, - { 0x05EF, 0x064F }, - { 0x05F0, 0x0650 }, - { 0x05F1, 0x0651 }, - { 0x05F2, 0x0652 }, - { 0x0DA1, 0x0E01 }, - { 0x0DA2, 0x0E02 }, - { 0x0DA3, 0x0E03 }, - { 0x0DA4, 0x0E04 }, - { 0x0DA5, 0x0E05 }, - { 0x0DA6, 0x0E06 }, - { 0x0DA7, 0x0E07 }, - { 0x0DA8, 0x0E08 }, - { 0x0DA9, 0x0E09 }, - { 0x0DAA, 0x0E0A }, - { 0x0DAB, 0x0E0B }, - { 0x0DAC, 0x0E0C }, - { 0x0DAD, 0x0E0D }, - { 0x0DAE, 0x0E0E }, - { 0x0DAF, 0x0E0F }, - { 0x0DB0, 0x0E10 }, - { 0x0DB1, 0x0E11 }, - { 0x0DB2, 0x0E12 }, - { 0x0DB3, 0x0E13 }, - { 0x0DB4, 0x0E14 }, - { 0x0DB5, 0x0E15 }, - { 0x0DB6, 0x0E16 }, - { 0x0DB7, 0x0E17 }, - { 0x0DB8, 0x0E18 }, - { 0x0DB9, 0x0E19 }, - { 0x0DBA, 0x0E1A }, - { 0x0DBB, 0x0E1B }, - { 0x0DBC, 0x0E1C }, - { 0x0DBD, 0x0E1D }, - { 0x0DBE, 0x0E1E }, - { 0x0DBF, 0x0E1F }, - { 0x0DC0, 0x0E20 }, - { 0x0DC1, 0x0E21 }, - { 0x0DC2, 0x0E22 }, - { 0x0DC3, 0x0E23 }, - { 0x0DC4, 0x0E24 }, - { 0x0DC5, 0x0E25 }, - { 0x0DC6, 0x0E26 }, - { 0x0DC7, 0x0E27 }, - { 0x0DC8, 0x0E28 }, - { 0x0DC9, 0x0E29 }, - { 0x0DCA, 0x0E2A }, - { 0x0DCB, 0x0E2B }, - { 0x0DCC, 0x0E2C }, - { 0x0DCD, 0x0E2D }, - { 0x0DCE, 0x0E2E }, - { 0x0DCF, 0x0E2F }, - { 0x0DD0, 0x0E30 }, - { 0x0DD1, 0x0E31 }, - { 0x0DD2, 0x0E32 }, - { 0x0DD3, 0x0E33 }, - { 0x0DD4, 0x0E34 }, - { 0x0DD5, 0x0E35 }, - { 0x0DD6, 0x0E36 }, - { 0x0DD7, 0x0E37 }, - { 0x0DD8, 0x0E38 }, - { 0x0DD9, 0x0E39 }, - { 0x0DDA, 0x0E3A }, - { 0x0DDF, 0x0E3F }, - { 0x0DE0, 0x0E40 }, - { 0x0DE1, 0x0E41 }, - { 0x0DE2, 0x0E42 }, - { 0x0DE3, 0x0E43 }, - { 0x0DE4, 0x0E44 }, - { 0x0DE5, 0x0E45 }, - { 0x0DE6, 0x0E46 }, - { 0x0DE7, 0x0E47 }, - { 0x0DE8, 0x0E48 }, - { 0x0DE9, 0x0E49 }, - { 0x0DEA, 0x0E4A }, - { 0x0DEB, 0x0E4B }, - { 0x0DEC, 0x0E4C }, - { 0x0DED, 0x0E4D }, - { 0x0DF0, 0x0E50 }, - { 0x0DF1, 0x0E51 }, - { 0x0DF2, 0x0E52 }, - { 0x0DF3, 0x0E53 }, - { 0x0DF4, 0x0E54 }, - { 0x0DF5, 0x0E55 }, - { 0x0DF6, 0x0E56 }, - { 0x0DF7, 0x0E57 }, - { 0x0DF8, 0x0E58 }, - { 0x0DF9, 0x0E59 }, - { 0x0ED4, 0x11A8 }, - { 0x0ED5, 0x11A9 }, - { 0x0ED6, 0x11AA }, - { 0x0ED7, 0x11AB }, - { 0x0ED8, 0x11AC }, - { 0x0ED9, 0x11AD }, - { 0x0EDA, 0x11AE }, - { 0x0EDB, 0x11AF }, - { 0x0EDC, 0x11B0 }, - { 0x0EDD, 0x11B1 }, - { 0x0EDE, 0x11B2 }, - { 0x0EDF, 0x11B3 }, - { 0x0EE0, 0x11B4 }, - { 0x0EE1, 0x11B5 }, - { 0x0EE2, 0x11B6 }, - { 0x0EE3, 0x11B7 }, - { 0x0EE4, 0x11B8 }, - { 0x0EE5, 0x11B9 }, - { 0x0EE6, 0x11BA }, - { 0x0EE7, 0x11BB }, - { 0x0EE8, 0x11BC }, - { 0x0EE9, 0x11BD }, - { 0x0EEA, 0x11BE }, - { 0x0EEB, 0x11BF }, - { 0x0EEC, 0x11C0 }, - { 0x0EED, 0x11C1 }, - { 0x0EEE, 0x11C2 }, - { 0x0EF8, 0x11EB }, - { 0x0EFA, 0x11F9 }, - { 0x0AA2, 0x2002 }, - { 0x0AA1, 0x2003 }, - { 0x0AA3, 0x2004 }, - { 0x0AA4, 0x2005 }, - { 0x0AA5, 0x2007 }, - { 0x0AA6, 0x2008 }, - { 0x0AA7, 0x2009 }, - { 0x0AA8, 0x200A }, - { 0x0ABB, 0x2012 }, - { 0x0AAA, 0x2013 }, - { 0x0AA9, 0x2014 }, - { 0x07AF, 0x2015 }, - { 0x0CDF, 0x2017 }, - { 0x0AD0, 0x2018 }, - { 0x0AD1, 0x2019 }, - { 0x0AFD, 0x201A }, - { 0x0AD2, 0x201C }, - { 0x0AD3, 0x201D }, - { 0x0AFE, 0x201E }, - { 0x0AF1, 0x2020 }, - { 0x0AF2, 0x2021 }, - { 0x0AE6, 0x2022 }, - { 0x0AAE, 0x2026 }, - { 0x0AD6, 0x2032 }, - { 0x0AD7, 0x2033 }, - { 0x0AFC, 0x2038 }, - { 0x047E, 0x203E }, - { 0x20A0, 0x20A0 }, - { 0x20A1, 0x20A1 }, - { 0x20A2, 0x20A2 }, - { 0x20A3, 0x20A3 }, - { 0x20A4, 0x20A4 }, - { 0x20A5, 0x20A5 }, - { 0x20A6, 0x20A6 }, - { 0x20A7, 0x20A7 }, - { 0x20A8, 0x20A8 }, - { 0x0EFF, 0x20A9 }, - { 0x20A9, 0x20A9 }, - { 0x20AA, 0x20AA }, - { 0x20AB, 0x20AB }, - { 0x20AC, 0x20AC }, - { 0x0AB8, 0x2105 }, - { 0x06B0, 0x2116 }, - { 0x0AFB, 0x2117 }, - { 0x0AD4, 0x211E }, - { 0x0AC9, 0x2122 }, - { 0x0AB0, 0x2153 }, - { 0x0AB1, 0x2154 }, - { 0x0AB2, 0x2155 }, - { 0x0AB3, 0x2156 }, - { 0x0AB4, 0x2157 }, - { 0x0AB5, 0x2158 }, - { 0x0AB6, 0x2159 }, - { 0x0AB7, 0x215A }, - { 0x0AC3, 0x215B }, - { 0x0AC4, 0x215C }, - { 0x0AC5, 0x215D }, - { 0x0AC6, 0x215E }, - { 0x08FB, 0x2190 }, - { 0x08FC, 0x2191 }, - { 0x08FD, 0x2192 }, - { 0x08FE, 0x2193 }, - { 0x08CE, 0x21D2 }, - { 0x08CD, 0x21D4 }, - { 0x08EF, 0x2202 }, - { 0x08C5, 0x2207 }, - { 0x0BCA, 0x2218 }, - { 0x08D6, 0x221A }, - { 0x08C1, 0x221D }, - { 0x08C2, 0x221E }, - { 0x08DE, 0x2227 }, - { 0x0BA9, 0x2227 }, - { 0x08DF, 0x2228 }, - { 0x0BA8, 0x2228 }, - { 0x08DC, 0x2229 }, - { 0x0BC3, 0x2229 }, - { 0x08DD, 0x222A }, - { 0x0BD6, 0x222A }, - { 0x08BF, 0x222B }, - { 0x08C0, 0x2234 }, - { 0x08C8, 0x2245 }, - { 0x08BD, 0x2260 }, - { 0x08CF, 0x2261 }, - { 0x08BC, 0x2264 }, - { 0x08BE, 0x2265 }, - { 0x08DA, 0x2282 }, - { 0x0BDA, 0x2282 }, - { 0x08DB, 0x2283 }, - { 0x0BD8, 0x2283 }, - { 0x0BFC, 0x22A2 }, - { 0x0BDC, 0x22A3 }, - { 0x0BC2, 0x22A4 }, - { 0x0BCE, 0x22A5 }, - { 0x0BD3, 0x2308 }, - { 0x0BC4, 0x230A }, - { 0x0AFA, 0x2315 }, - { 0x08A4, 0x2320 }, - { 0x08A5, 0x2321 }, - { 0x0ABC, 0x2329 }, - { 0x0ABE, 0x232A }, - { 0x0BCC, 0x2395 }, - { 0x09E2, 0x2409 }, - { 0x09E5, 0x240A }, - { 0x09E9, 0x240B }, - { 0x09E3, 0x240C }, - { 0x09E4, 0x240D }, - { 0x09DF, 0x2422 }, - { 0x09E8, 0x2424 }, - { 0x09F1, 0x2500 }, - { 0x08A6, 0x2502 }, - { 0x09F8, 0x2502 }, - { 0x09EC, 0x250C }, - { 0x09EB, 0x2510 }, - { 0x09ED, 0x2514 }, - { 0x09EA, 0x2518 }, - { 0x09F4, 0x251C }, - { 0x09F5, 0x2524 }, - { 0x09F7, 0x252C }, - { 0x09F6, 0x2534 }, - { 0x09EE, 0x253C }, - { 0x09E1, 0x2592 }, - { 0x0ADF, 0x25A0 }, - { 0x0ACF, 0x25A1 }, - { 0x0AE7, 0x25AA }, - { 0x0AE1, 0x25AB }, - { 0x0ADB, 0x25AC }, - { 0x0AE2, 0x25AD }, - { 0x0AE8, 0x25B2 }, - { 0x0AE3, 0x25B3 }, - { 0x0ADD, 0x25B6 }, - { 0x0ACD, 0x25B7 }, - { 0x0AE9, 0x25BC }, - { 0x0AE4, 0x25BD }, - { 0x0ADC, 0x25C0 }, - { 0x0ACC, 0x25C1 }, - { 0x09E0, 0x25C6 }, - { 0x0ACE, 0x25CB }, - { 0x0BCF, 0x25CB }, - { 0x0ADE, 0x25CF }, - { 0x0AE0, 0x25E6 }, - { 0x0AE5, 0x2606 }, - { 0x0AF9, 0x260E }, - { 0x0ACA, 0x2613 }, - { 0x0AEA, 0x261C }, - { 0x0AEB, 0x261E }, - { 0x0AF8, 0x2640 }, - { 0x0AF7, 0x2642 }, - { 0x0AEC, 0x2663 }, - { 0x0AEE, 0x2665 }, - { 0x0AED, 0x2666 }, - { 0x0AF6, 0x266D }, - { 0x0AF5, 0x266F }, - { 0x0AF3, 0x2713 }, - { 0x0AF4, 0x2717 }, - { 0x0AD9, 0x271D }, - { 0x0AF0, 0x2720 }, - { 0x04A4, 0x3001 }, - { 0x04A1, 0x3002 }, - { 0x04A2, 0x300C }, - { 0x04A3, 0x300D }, - { 0x04DE, 0x309B }, - { 0x04DF, 0x309C }, - { 0x04A7, 0x30A1 }, - { 0x04B1, 0x30A2 }, - { 0x04A8, 0x30A3 }, - { 0x04B2, 0x30A4 }, - { 0x04A9, 0x30A5 }, - { 0x04B3, 0x30A6 }, - { 0x04AA, 0x30A7 }, - { 0x04B4, 0x30A8 }, - { 0x04AB, 0x30A9 }, - { 0x04B5, 0x30AA }, - { 0x04B6, 0x30AB }, - { 0x04B7, 0x30AD }, - { 0x04B8, 0x30AF }, - { 0x04B9, 0x30B1 }, - { 0x04BA, 0x30B3 }, - { 0x04BB, 0x30B5 }, - { 0x04BC, 0x30B7 }, - { 0x04BD, 0x30B9 }, - { 0x04BE, 0x30BB }, - { 0x04BF, 0x30BD }, - { 0x04C0, 0x30BF }, - { 0x04C1, 0x30C1 }, - { 0x04AF, 0x30C3 }, - { 0x04C2, 0x30C4 }, - { 0x04C3, 0x30C6 }, - { 0x04C4, 0x30C8 }, - { 0x04C5, 0x30CA }, - { 0x04C6, 0x30CB }, - { 0x04C7, 0x30CC }, - { 0x04C8, 0x30CD }, - { 0x04C9, 0x30CE }, - { 0x04CA, 0x30CF }, - { 0x04CB, 0x30D2 }, - { 0x04CC, 0x30D5 }, - { 0x04CD, 0x30D8 }, - { 0x04CE, 0x30DB }, - { 0x04CF, 0x30DE }, - { 0x04D0, 0x30DF }, - { 0x04D1, 0x30E0 }, - { 0x04D2, 0x30E1 }, - { 0x04D3, 0x30E2 }, - { 0x04AC, 0x30E3 }, - { 0x04D4, 0x30E4 }, - { 0x04AD, 0x30E5 }, - { 0x04D5, 0x30E6 }, - { 0x04AE, 0x30E7 }, - { 0x04D6, 0x30E8 }, - { 0x04D7, 0x30E9 }, - { 0x04D8, 0x30EA }, - { 0x04D9, 0x30EB }, - { 0x04DA, 0x30EC }, - { 0x04DB, 0x30ED }, - { 0x04DC, 0x30EF }, - { 0x04A6, 0x30F2 }, - { 0x04DD, 0x30F3 }, - { 0x04A5, 0x30FB }, - { 0x04B0, 0x30FC }, - { 0x0EA1, 0x3131 }, - { 0x0EA2, 0x3132 }, - { 0x0EA3, 0x3133 }, - { 0x0EA4, 0x3134 }, - { 0x0EA5, 0x3135 }, - { 0x0EA6, 0x3136 }, - { 0x0EA7, 0x3137 }, - { 0x0EA8, 0x3138 }, - { 0x0EA9, 0x3139 }, - { 0x0EAA, 0x313A }, - { 0x0EAB, 0x313B }, - { 0x0EAC, 0x313C }, - { 0x0EAD, 0x313D }, - { 0x0EAE, 0x313E }, - { 0x0EAF, 0x313F }, - { 0x0EB0, 0x3140 }, - { 0x0EB1, 0x3141 }, - { 0x0EB2, 0x3142 }, - { 0x0EB3, 0x3143 }, - { 0x0EB4, 0x3144 }, - { 0x0EB5, 0x3145 }, - { 0x0EB6, 0x3146 }, - { 0x0EB7, 0x3147 }, - { 0x0EB8, 0x3148 }, - { 0x0EB9, 0x3149 }, - { 0x0EBA, 0x314A }, - { 0x0EBB, 0x314B }, - { 0x0EBC, 0x314C }, - { 0x0EBD, 0x314D }, - { 0x0EBE, 0x314E }, - { 0x0EBF, 0x314F }, - { 0x0EC0, 0x3150 }, - { 0x0EC1, 0x3151 }, - { 0x0EC2, 0x3152 }, - { 0x0EC3, 0x3153 }, - { 0x0EC4, 0x3154 }, - { 0x0EC5, 0x3155 }, - { 0x0EC6, 0x3156 }, - { 0x0EC7, 0x3157 }, - { 0x0EC8, 0x3158 }, - { 0x0EC9, 0x3159 }, - { 0x0ECA, 0x315A }, - { 0x0ECB, 0x315B }, - { 0x0ECC, 0x315C }, - { 0x0ECD, 0x315D }, - { 0x0ECE, 0x315E }, - { 0x0ECF, 0x315F }, - { 0x0ED0, 0x3160 }, - { 0x0ED1, 0x3161 }, - { 0x0ED2, 0x3162 }, - { 0x0ED3, 0x3163 }, - { 0x0EEF, 0x316D }, - { 0x0EF0, 0x3171 }, - { 0x0EF1, 0x3178 }, - { 0x0EF2, 0x317F }, - { 0x0EF4, 0x3184 }, - { 0x0EF5, 0x3186 }, - { 0x0EF6, 0x318D }, - { 0x0EF7, 0x318E } -}; - -KeySym KeyMappingX11::get_keysym_from_unicode(unsigned int p_unicode) { - /* Latin 1 */ - - if (p_unicode >= 0x20 && p_unicode <= 0x7e) { - return p_unicode; - } - - 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) { - return _unicode_to_xkeysym[middle].keysym; - } - if (_unicode_to_xkeysym[middle].keysym <= p_unicode) { - low = middle + 1; - } else { - high = middle - 1; - } - } while (high >= low); - - // if not found, let's hope X understands it as unicode - return p_unicode | 0x01000000; -} diff --git a/platform/linuxbsd/key_mapping_x11.h b/platform/linuxbsd/key_mapping_x11.h deleted file mode 100644 index b7b8a3b787..0000000000 --- a/platform/linuxbsd/key_mapping_x11.h +++ /dev/null @@ -1,55 +0,0 @@ -/*************************************************************************/ -/* key_mapping_x11.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef KEY_MAPPING_X11_H -#define KEY_MAPPING_X11_H - -#include -#include -#define XK_MISCELLANY -#define XK_LATIN1 -#define XK_XKB_KEYS -#include - -#include "core/os/keyboard.h" - -class KeyMappingX11 { - KeyMappingX11() {} - -public: - static Key get_keycode(KeySym p_keysym); - static unsigned int get_xlibcode(Key p_keysym); - static Key get_scancode(unsigned int p_code); - static KeySym get_keysym(Key p_code); - static unsigned int get_unicode_from_keysym(KeySym p_keysym); - static KeySym get_keysym_from_unicode(unsigned int p_unicode); -}; - -#endif // KEY_MAPPING_X11_H diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp index 11b667fcef..98ffa1113f 100644 --- a/platform/linuxbsd/os_linuxbsd.cpp +++ b/platform/linuxbsd/os_linuxbsd.cpp @@ -40,7 +40,7 @@ #endif #ifdef X11_ENABLED -#include "display_server_x11.h" +#include "x11/display_server_x11.h" #endif #ifdef HAVE_MNTENT diff --git a/platform/linuxbsd/vulkan_context_x11.cpp b/platform/linuxbsd/vulkan_context_x11.cpp deleted file mode 100644 index 92aaf33b05..0000000000 --- a/platform/linuxbsd/vulkan_context_x11.cpp +++ /dev/null @@ -1,65 +0,0 @@ -/*************************************************************************/ -/* vulkan_context_x11.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifdef VULKAN_ENABLED - -#include "vulkan_context_x11.h" - -#ifdef USE_VOLK -#include -#else -#include -#endif - -const char *VulkanContextX11::_get_platform_surface_extension() const { - return VK_KHR_XLIB_SURFACE_EXTENSION_NAME; -} - -Error VulkanContextX11::window_create(DisplayServer::WindowID p_window_id, DisplayServer::VSyncMode p_vsync_mode, ::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; - createInfo.flags = 0; - createInfo.dpy = p_display; - createInfo.window = p_window; - - VkSurfaceKHR surface; - VkResult err = vkCreateXlibSurfaceKHR(get_instance(), &createInfo, nullptr, &surface); - ERR_FAIL_COND_V(err, ERR_CANT_CREATE); - return _window_create(p_window_id, p_vsync_mode, surface, p_width, p_height); -} - -VulkanContextX11::VulkanContextX11() { -} - -VulkanContextX11::~VulkanContextX11() { -} - -#endif // VULKAN_ENABLED diff --git a/platform/linuxbsd/vulkan_context_x11.h b/platform/linuxbsd/vulkan_context_x11.h deleted file mode 100644 index 0adb50ef44..0000000000 --- a/platform/linuxbsd/vulkan_context_x11.h +++ /dev/null @@ -1,51 +0,0 @@ -/*************************************************************************/ -/* vulkan_context_x11.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef VULKAN_CONTEXT_X11_H -#define VULKAN_CONTEXT_X11_H - -#ifdef VULKAN_ENABLED - -#include "drivers/vulkan/vulkan_context.h" -#include - -class VulkanContextX11 : public VulkanContext { - virtual const char *_get_platform_surface_extension() const; - -public: - Error window_create(DisplayServer::WindowID p_window_id, DisplayServer::VSyncMode p_vsync_mode, ::Window p_window, Display *p_display, int p_width, int p_height); - - VulkanContextX11(); - ~VulkanContextX11(); -}; - -#endif // VULKAN_ENABLED - -#endif // VULKAN_CONTEXT_X11_H diff --git a/platform/linuxbsd/x11/SCsub b/platform/linuxbsd/x11/SCsub new file mode 100644 index 0000000000..974ad98fb9 --- /dev/null +++ b/platform/linuxbsd/x11/SCsub @@ -0,0 +1,21 @@ +#!/usr/bin/env python + +Import("env") + +source_files = [ + "display_server_x11.cpp", + "key_mapping_x11.cpp", +] + +if env["vulkan"]: + source_files.append("vulkan_context_x11.cpp") + +if env["opengl3"]: + source_files.append(["gl_manager_x11.cpp", "detect_prime_x11.cpp"]) + +objects = [] + +for source_file in source_files: + objects.append(env.Object(source_file)) + +Return("objects") diff --git a/platform/linuxbsd/x11/detect_prime_x11.cpp b/platform/linuxbsd/x11/detect_prime_x11.cpp new file mode 100644 index 0000000000..fb833ab5e6 --- /dev/null +++ b/platform/linuxbsd/x11/detect_prime_x11.cpp @@ -0,0 +1,248 @@ +/*************************************************************************/ +/* detect_prime_x11.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifdef X11_ENABLED +#if defined(GLES3_ENABLED) + +#include "detect_prime_x11.h" + +#include "core/string/print_string.h" +#include "core/string/ustring.h" + +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include + +#define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091 +#define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092 + +typedef GLXContext (*GLXCREATECONTEXTATTRIBSARBPROC)(Display *, GLXFBConfig, GLXContext, Bool, const int *); + +struct vendor { + const char *glxvendor = nullptr; + int priority = 0; +}; + +vendor vendormap[] = { + { "Advanced Micro Devices, Inc.", 30 }, + { "AMD", 30 }, + { "NVIDIA Corporation", 30 }, + { "X.Org", 30 }, + { "Intel Open Source Technology Center", 20 }, + { "Intel", 20 }, + { "nouveau", 10 }, + { "Mesa Project", 0 }, + { nullptr, 0 } +}; + +// Runs inside a child. Exiting will not quit the engine. +void create_context() { + Display *x11_display = XOpenDisplay(nullptr); + Window x11_window; + GLXContext glx_context; + + GLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB = (GLXCREATECONTEXTATTRIBSARBPROC)glXGetProcAddress((const GLubyte *)"glXCreateContextAttribsARB"); + + static int visual_attribs[] = { + GLX_RENDER_TYPE, GLX_RGBA_BIT, + GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, + GLX_DOUBLEBUFFER, true, + GLX_RED_SIZE, 1, + GLX_GREEN_SIZE, 1, + GLX_BLUE_SIZE, 1, + GLX_DEPTH_SIZE, 24, + None + }; + + int fbcount; + GLXFBConfig fbconfig = nullptr; + XVisualInfo *vi = nullptr; + + XSetWindowAttributes swa; + swa.event_mask = StructureNotifyMask; + swa.border_pixel = 0; + unsigned long valuemask = CWBorderPixel | CWColormap | CWEventMask; + + GLXFBConfig *fbc = glXChooseFBConfig(x11_display, DefaultScreen(x11_display), visual_attribs, &fbcount); + if (!fbc) { + exit(1); + } + + vi = glXGetVisualFromFBConfig(x11_display, fbc[0]); + + fbconfig = fbc[0]; + + static int context_attribs[] = { + GLX_CONTEXT_MAJOR_VERSION_ARB, 3, + GLX_CONTEXT_MINOR_VERSION_ARB, 3, + GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB, + GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, + None + }; + + glx_context = glXCreateContextAttribsARB(x11_display, fbconfig, nullptr, true, context_attribs); + + swa.colormap = XCreateColormap(x11_display, RootWindow(x11_display, vi->screen), vi->visual, AllocNone); + x11_window = XCreateWindow(x11_display, RootWindow(x11_display, vi->screen), 0, 0, 10, 10, 0, vi->depth, InputOutput, vi->visual, valuemask, &swa); + + if (!x11_window) { + exit(1); + } + + glXMakeCurrent(x11_display, x11_window, glx_context); + XFree(vi); +} + +int detect_prime() { + pid_t p; + int priorities[2] = {}; + String vendors[2]; + String renderers[2]; + + vendors[0] = "Unknown"; + vendors[1] = "Unknown"; + renderers[0] = "Unknown"; + renderers[1] = "Unknown"; + + for (int i = 0; i < 2; ++i) { + int fdset[2]; + + if (pipe(fdset) == -1) { + print_verbose("Failed to pipe(), using default GPU"); + return 0; + } + + // Fork so the driver initialization can crash without taking down the engine. + p = fork(); + + if (p > 0) { + // Main thread + + int stat_loc = 0; + char string[201]; + string[200] = '\0'; + + close(fdset[1]); + + waitpid(p, &stat_loc, 0); + + if (!stat_loc) { + // No need to do anything complicated here. Anything less than + // PIPE_BUF will be delivered in one read() call. + // Leave it 'Unknown' otherwise. + if (read(fdset[0], string, sizeof(string) - 1) > 0) { + vendors[i] = string; + renderers[i] = string + strlen(string) + 1; + } + } + + close(fdset[0]); + + } else { + // In child, exit() here will not quit the engine. + + // Prevent false leak reports as we will not be properly + // cleaning up these processes, and fork() makes a copy + // of all globals. + CoreGlobals::leak_reporting_enabled = false; + + char string[201]; + + close(fdset[0]); + + if (i) { + setenv("DRI_PRIME", "1", 1); + } + create_context(); + + const char *vendor = (const char *)glGetString(GL_VENDOR); + const char *renderer = (const char *)glGetString(GL_RENDERER); + + unsigned int vendor_len = strlen(vendor) + 1; + unsigned int renderer_len = strlen(renderer) + 1; + + if (vendor_len + renderer_len >= sizeof(string)) { + renderer_len = 200 - vendor_len; + } + + memcpy(&string, vendor, vendor_len); + memcpy(&string[vendor_len], renderer, renderer_len); + + if (write(fdset[1], string, vendor_len + renderer_len) == -1) { + print_verbose("Couldn't write vendor/renderer string."); + } + close(fdset[1]); + exit(0); + } + } + + int preferred = 0; + int priority = 0; + + if (vendors[0] == vendors[1]) { + print_verbose("Only one GPU found, using default."); + return 0; + } + + for (int i = 1; i >= 0; --i) { + vendor *v = vendormap; + while (v->glxvendor) { + if (v->glxvendor == vendors[i]) { + priorities[i] = v->priority; + + if (v->priority >= priority) { + priority = v->priority; + preferred = i; + } + } + ++v; + } + } + + print_verbose("Found renderers:"); + for (int i = 0; i < 2; ++i) { + print_verbose("Renderer " + itos(i) + ": " + renderers[i] + " with priority: " + itos(priorities[i])); + } + + print_verbose("Using renderer: " + renderers[preferred]); + return preferred; +} + +#endif +#endif diff --git a/platform/linuxbsd/x11/detect_prime_x11.h b/platform/linuxbsd/x11/detect_prime_x11.h new file mode 100644 index 0000000000..7eb7064cc5 --- /dev/null +++ b/platform/linuxbsd/x11/detect_prime_x11.h @@ -0,0 +1,40 @@ +/*************************************************************************/ +/* detect_prime_x11.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef DETECT_PRIME_X11_H +#define DETECT_PRIME_X11_H + +#if defined(X11_ENABLED) && defined(GLES3_ENABLED) + +int detect_prime(); + +#endif // X11_ENABLED && GLES3_ENABLED + +#endif // DETECT_PRIME_X11_H diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp new file mode 100644 index 0000000000..88c6500e10 --- /dev/null +++ b/platform/linuxbsd/x11/display_server_x11.cpp @@ -0,0 +1,5196 @@ +/*************************************************************************/ +/* display_server_x11.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "display_server_x11.h" + +#ifdef X11_ENABLED + +#include "core/config/project_settings.h" +#include "core/math/math_funcs.h" +#include "core/string/print_string.h" +#include "core/string/ustring.h" +#include "detect_prime_x11.h" +#include "key_mapping_x11.h" +#include "main/main.h" +#include "scene/resources/texture.h" + +#if defined(VULKAN_ENABLED) +#include "servers/rendering/renderer_rd/renderer_compositor_rd.h" +#endif + +#if defined(GLES3_ENABLED) +#include "drivers/gles3/rasterizer_gles3.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +// ICCCM +#define WM_NormalState 1L // window normal state +#define WM_IconicState 3L // window minimized +// EWMH +#define _NET_WM_STATE_REMOVE 0L // remove/unset property +#define _NET_WM_STATE_ADD 1L // add/set property + +#undef CursorShape +#include + +// 2.2 is the first release with multitouch +#define XINPUT_CLIENT_VERSION_MAJOR 2 +#define XINPUT_CLIENT_VERSION_MINOR 2 + +#define VALUATOR_ABSX 0 +#define VALUATOR_ABSY 1 +#define VALUATOR_PRESSURE 2 +#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; +}; + +static String get_atom_name(Display *p_disp, Atom p_atom) { + char *name = XGetAtomName(p_disp, p_atom); + ERR_FAIL_NULL_V_MSG(name, String(), "Atom is invalid."); + String ret; + ret.parse_utf8(name); + XFree(name); + return ret; +} + +bool DisplayServerX11::has_feature(Feature p_feature) const { + switch (p_feature) { + case FEATURE_SUBWINDOWS: +#ifdef TOUCH_ENABLED + case FEATURE_TOUCHSCREEN: +#endif + case FEATURE_MOUSE: + case FEATURE_MOUSE_WARP: + case FEATURE_CLIPBOARD: + case FEATURE_CURSOR_SHAPE: + case FEATURE_CUSTOM_CURSOR_SHAPE: + case FEATURE_IME: + case FEATURE_WINDOW_TRANSPARENCY: + //case FEATURE_HIDPI: + case FEATURE_ICON: + //case FEATURE_NATIVE_ICON: + case FEATURE_SWAP_BUFFERS: +#ifdef DBUS_ENABLED + case FEATURE_KEEP_SCREEN_ON: +#endif + case FEATURE_CLIPBOARD_PRIMARY: + case FEATURE_TEXT_TO_SPEECH: + return true; + default: { + } + } + + return false; +} + +String DisplayServerX11::get_name() const { + return "X11"; +} + +void DisplayServerX11::_update_real_mouse_position(const WindowData &wd) { + Window root_return, child_return; + int root_x, root_y, win_x, win_y; + unsigned int mask_return; + + Bool xquerypointer_result = XQueryPointer(x11_display, wd.x11_window, &root_return, &child_return, &root_x, &root_y, + &win_x, &win_y, &mask_return); + + 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; + Input::get_singleton()->set_mouse_position(last_mouse_pos); + } + } +} + +bool DisplayServerX11::_refresh_device_info() { + int event_base, error_base; + + print_verbose("XInput: Refreshing devices."); + + if (!XQueryExtension(x11_display, "XInputExtension", &xi.opcode, &event_base, &error_base)) { + print_verbose("XInput extension not available. Please upgrade your distribution."); + return false; + } + + int xi_major_query = XINPUT_CLIENT_VERSION_MAJOR; + int xi_minor_query = XINPUT_CLIENT_VERSION_MINOR; + + if (XIQueryVersion(x11_display, &xi_major_query, &xi_minor_query) != Success) { + print_verbose(vformat("XInput 2 not available (server supports %d.%d).", xi_major_query, xi_minor_query)); + xi.opcode = 0; + return false; + } + + if (xi_major_query < XINPUT_CLIENT_VERSION_MAJOR || (xi_major_query == XINPUT_CLIENT_VERSION_MAJOR && xi_minor_query < XINPUT_CLIENT_VERSION_MINOR)) { + print_verbose(vformat("XInput %d.%d not available (server supports %d.%d). Touch input unavailable.", + XINPUT_CLIENT_VERSION_MAJOR, XINPUT_CLIENT_VERSION_MINOR, xi_major_query, xi_minor_query)); + } + + xi.absolute_devices.clear(); + xi.touch_devices.clear(); + xi.pen_inverted_devices.clear(); + + int dev_count; + XIDeviceInfo *info = XIQueryDevice(x11_display, XIAllDevices, &dev_count); + + for (int i = 0; i < dev_count; i++) { + XIDeviceInfo *dev = &info[i]; + if (!dev->enabled) { + continue; + } + if (!(dev->use == XISlavePointer || dev->use == XIFloatingSlave)) { + continue; + } + + bool direct_touch = false; + bool absolute_mode = false; + int resolution_x = 0; + int resolution_y = 0; + double abs_x_min = 0; + double abs_x_max = 0; + double abs_y_min = 0; + double abs_y_max = 0; + double pressure_min = 0; + double pressure_max = 0; + double tilt_x_min = 0; + double tilt_x_max = 0; + double tilt_y_min = 0; + double tilt_y_max = 0; + for (int j = 0; j < dev->num_classes; j++) { +#ifdef TOUCH_ENABLED + if (dev->classes[j]->type == XITouchClass && ((XITouchClassInfo *)dev->classes[j])->mode == XIDirectTouch) { + direct_touch = true; + } +#endif + if (dev->classes[j]->type == XIValuatorClass) { + XIValuatorClassInfo *class_info = (XIValuatorClassInfo *)dev->classes[j]; + + if (class_info->number == VALUATOR_ABSX && class_info->mode == XIModeAbsolute) { + resolution_x = class_info->resolution; + abs_x_min = class_info->min; + abs_x_max = class_info->max; + absolute_mode = true; + } else if (class_info->number == VALUATOR_ABSY && class_info->mode == XIModeAbsolute) { + resolution_y = class_info->resolution; + abs_y_min = class_info->min; + abs_y_max = class_info->max; + absolute_mode = true; + } else if (class_info->number == VALUATOR_PRESSURE && class_info->mode == XIModeAbsolute) { + pressure_min = class_info->min; + pressure_max = class_info->max; + } else if (class_info->number == VALUATOR_TILTX && class_info->mode == XIModeAbsolute) { + tilt_x_min = class_info->min; + tilt_x_max = class_info->max; + } else if (class_info->number == VALUATOR_TILTY && class_info->mode == XIModeAbsolute) { + tilt_y_min = class_info->min; + tilt_y_max = class_info->max; + } + } + } + if (direct_touch) { + xi.touch_devices.push_back(dev->deviceid); + print_verbose("XInput: Using touch device: " + String(dev->name)); + } + if (absolute_mode) { + // If no resolution was reported, use the min/max ranges. + if (resolution_x <= 0) { + resolution_x = (abs_x_max - abs_x_min) * abs_resolution_range_mult; + } + if (resolution_y <= 0) { + resolution_y = (abs_y_max - abs_y_min) * abs_resolution_range_mult; + } + xi.absolute_devices[dev->deviceid] = Vector2(abs_resolution_mult / resolution_x, abs_resolution_mult / resolution_y); + print_verbose("XInput: Absolute pointing device: " + String(dev->name)); + } + + xi.pressure = 0; + xi.pen_pressure_range[dev->deviceid] = Vector2(pressure_min, pressure_max); + xi.pen_tilt_x_range[dev->deviceid] = Vector2(tilt_x_min, tilt_x_max); + xi.pen_tilt_y_range[dev->deviceid] = Vector2(tilt_y_min, tilt_y_max); + xi.pen_inverted_devices[dev->deviceid] = String(dev->name).findn("eraser") > 0; + } + + XIFreeDeviceInfo(info); +#ifdef TOUCH_ENABLED + if (!xi.touch_devices.size()) { + print_verbose("XInput: No touch devices found."); + } +#endif + + return true; +} + +void DisplayServerX11::_flush_mouse_motion() { + // 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_at(event_index--); + continue; + } + XFreeEventData(x11_display, &event.xcookie); + break; + } + } + + xi.relative_motion.x = 0; + xi.relative_motion.y = 0; +} + +#ifdef SPEECHD_ENABLED + +bool DisplayServerX11::tts_is_speaking() const { + ERR_FAIL_COND_V(!tts, false); + return tts->is_speaking(); +} + +bool DisplayServerX11::tts_is_paused() const { + ERR_FAIL_COND_V(!tts, false); + return tts->is_paused(); +} + +TypedArray DisplayServerX11::tts_get_voices() const { + ERR_FAIL_COND_V(!tts, TypedArray()); + return tts->get_voices(); +} + +void DisplayServerX11::tts_speak(const String &p_text, const String &p_voice, int p_volume, float p_pitch, float p_rate, int p_utterance_id, bool p_interrupt) { + ERR_FAIL_COND(!tts); + tts->speak(p_text, p_voice, p_volume, p_pitch, p_rate, p_utterance_id, p_interrupt); +} + +void DisplayServerX11::tts_pause() { + ERR_FAIL_COND(!tts); + tts->pause(); +} + +void DisplayServerX11::tts_resume() { + ERR_FAIL_COND(!tts); + tts->resume(); +} + +void DisplayServerX11::tts_stop() { + ERR_FAIL_COND(!tts); + tts->stop(); +} + +#endif + +#ifdef DBUS_ENABLED + +bool DisplayServerX11::is_dark_mode_supported() const { + return portal_desktop->is_supported(); +} + +bool DisplayServerX11::is_dark_mode() const { + switch (portal_desktop->get_appearance_color_scheme()) { + case 1: + // Prefers dark theme. + return true; + case 2: + // Prefers light theme. + return false; + default: + // Preference unknown. + return false; + } +} + +#endif + +void DisplayServerX11::mouse_set_mode(MouseMode p_mode) { + _THREAD_SAFE_METHOD_ + + if (p_mode == mouse_mode) { + return; + } + + if (mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN) { + 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 (const KeyValue &E : windows) { + if (showCursor) { + XDefineCursor(x11_display, E.value.x11_window, cursors[current_cursor]); // show cursor + } else { + XDefineCursor(x11_display, E.value.x11_window, null_cursor); // hide cursor + } + } + mouse_mode = p_mode; + + if (mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN) { + //flush pending motion events + _flush_mouse_motion(); + WindowID window_id = _get_focused_window_or_popup(); + if (!windows.has(window_id)) { + window_id = MAIN_WINDOW_ID; + } + WindowData &window = windows[window_id]; + + if (XGrabPointer( + x11_display, window.x11_window, True, + ButtonPressMask | ButtonReleaseMask | PointerMotionMask, + GrabModeAsync, GrabModeAsync, window.x11_window, None, CurrentTime) != GrabSuccess) { + ERR_PRINT("NO GRAB"); + } + + if (mouse_mode == MOUSE_MODE_CAPTURED) { + center.x = window.size.width / 2; + center.y = window.size.height / 2; + + XWarpPointer(x11_display, None, window.x11_window, + 0, 0, 0, 0, (int)center.x, (int)center.y); + + Input::get_singleton()->set_mouse_position(center); + } + } else { + do_mouse_warp = false; + } + + XFlush(x11_display); +} + +DisplayServerX11::MouseMode DisplayServerX11::mouse_get_mode() const { + return mouse_mode; +} + +void DisplayServerX11::warp_mouse(const Point2i &p_position) { + _THREAD_SAFE_METHOD_ + + if (mouse_mode == MOUSE_MODE_CAPTURED) { + last_mouse_pos = p_position; + } else { + WindowID window_id = _get_focused_window_or_popup(); + if (!windows.has(window_id)) { + window_id = MAIN_WINDOW_ID; + } + + XWarpPointer(x11_display, None, windows[window_id].x11_window, + 0, 0, 0, 0, (int)p_position.x, (int)p_position.y); + } +} + +Point2i DisplayServerX11::mouse_get_position() const { + int number_of_screens = XScreenCount(x11_display); + for (int i = 0; i < number_of_screens; i++) { + Window root, child; + int root_x, root_y, win_x, win_y; + unsigned int mask; + if (XQueryPointer(x11_display, XRootWindow(x11_display, i), &root, &child, &root_x, &root_y, &win_x, &win_y, &mask)) { + XWindowAttributes root_attrs; + XGetWindowAttributes(x11_display, root, &root_attrs); + + return Vector2i(root_attrs.x + root_x, root_attrs.y + root_y); + } + } + return Vector2i(); +} + +MouseButton DisplayServerX11::mouse_get_button_state() const { + return last_button_state; +} + +void DisplayServerX11::clipboard_set(const String &p_text) { + _THREAD_SAFE_METHOD_ + + { + // 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); +} + +void DisplayServerX11::clipboard_set_primary(const String &p_text) { + _THREAD_SAFE_METHOD_ + if (!p_text.is_empty()) { + { + // The clipboard content can be accessed while polling for events. + MutexLock mutex_lock(events_mutex); + internal_clipboard_primary = p_text; + } + + XSetSelectionOwner(x11_display, XA_PRIMARY, windows[MAIN_WINDOW_ID].x11_window, CurrentTime); + XSetSelectionOwner(x11_display, XInternAtom(x11_display, "PRIMARY", 0), windows[MAIN_WINDOW_ID].x11_window, CurrentTime); + } +} + +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; + } +} + +Bool DisplayServerX11::_predicate_clipboard_incr(Display *display, XEvent *event, XPointer arg) { + if (event->type == PropertyNotify && event->xproperty.state == PropertyNewValue) { + return True; + } else { + return False; + } +} + +String DisplayServerX11::_clipboard_get_impl(Atom p_source, Window x11_window, Atom target) const { + String ret; + + Window selection_owner = XGetSelectionOwner(x11_display, p_source); + if (selection_owner == x11_window) { + static const char *target_type = "PRIMARY"; + if (p_source != None && get_atom_name(x11_display, p_source) == target_type) { + return internal_clipboard_primary; + } else { + return internal_clipboard; + } + } + + if (selection_owner != None) { + // Block events polling while processing selection events. + MutexLock mutex_lock(events_mutex); + + Atom selection = XA_PRIMARY; + XConvertSelection(x11_display, p_source, target, selection, + x11_window, CurrentTime); + + XFlush(x11_display); + + // 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 + &type, // return type + &format, // return format + &len, &bytes_left, // data length + &data); + + 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 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("Failed to get selection data.\n"); + } + + if (data) { + XFree(data); + } + } + } + + return ret; +} + +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, utf8_atom); + } + 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); + + if (ret.is_empty()) { + ret = _clipboard_get(XA_PRIMARY, windows[MAIN_WINDOW_ID].x11_window); + } + + return ret; +} + +String DisplayServerX11::clipboard_get_primary() const { + _THREAD_SAFE_METHOD_ + + String ret; + ret = _clipboard_get(XInternAtom(x11_display, "PRIMARY", 0), windows[MAIN_WINDOW_ID].x11_window); + + if (ret.is_empty()) { + ret = _clipboard_get(XA_PRIMARY, windows[MAIN_WINDOW_ID].x11_window); + } + + return ret; +} + +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_ + int count = 0; + + // Using Xinerama Extension + int event_base, error_base; + if (XineramaQueryExtension(x11_display, &event_base, &error_base)) { + XineramaScreenInfo *xsi = XineramaQueryScreens(x11_display, &count); + XFree(xsi); + } else { + count = XScreenCount(x11_display); + } + + return count; +} + +Rect2i DisplayServerX11::_screen_get_rect(int p_screen) const { + Rect2i rect(0, 0, 0, 0); + + if (p_screen == SCREEN_OF_MAIN_WINDOW) { + p_screen = window_get_current_screen(); + } + + ERR_FAIL_COND_V(p_screen < 0, rect); + + // Using Xinerama Extension. + int event_base, error_base; + if (XineramaQueryExtension(x11_display, &event_base, &error_base)) { + int count; + XineramaScreenInfo *xsi = XineramaQueryScreens(x11_display, &count); + + // Check if screen is valid. + if (p_screen < count) { + rect.position.x = xsi[p_screen].x_org; + rect.position.y = xsi[p_screen].y_org; + rect.size.width = xsi[p_screen].width; + rect.size.height = xsi[p_screen].height; + } else { + ERR_PRINT("Invalid screen index: " + itos(p_screen) + "(count: " + itos(count) + ")."); + } + + if (xsi) { + XFree(xsi); + } + } else { + int count = XScreenCount(x11_display); + if (p_screen < count) { + Window root = XRootWindow(x11_display, p_screen); + XWindowAttributes xwa; + XGetWindowAttributes(x11_display, root, &xwa); + rect.position.x = xwa.x; + rect.position.y = xwa.y; + rect.size.width = xwa.width; + rect.size.height = xwa.height; + } else { + ERR_PRINT("Invalid screen index: " + itos(p_screen) + "(count: " + itos(count) + ")."); + } + } + + return rect; +} + +Point2i DisplayServerX11::screen_get_position(int p_screen) const { + _THREAD_SAFE_METHOD_ + + return _screen_get_rect(p_screen).position; +} + +Size2i DisplayServerX11::screen_get_size(int p_screen) const { + _THREAD_SAFE_METHOD_ + + return _screen_get_rect(p_screen).size; +} + +bool g_bad_window = false; +int bad_window_error_handler(Display *display, XErrorEvent *error) { + if (error->error_code == BadWindow) { + g_bad_window = true; + } else { + ERR_PRINT("Unhandled XServer error code: " + itos(error->error_code)); + } + return 0; +} + +Rect2i DisplayServerX11::screen_get_usable_rect(int p_screen) const { + _THREAD_SAFE_METHOD_ + + if (p_screen == SCREEN_OF_MAIN_WINDOW) { + p_screen = window_get_current_screen(); + } + + int screen_count = get_screen_count(); + + // Check if screen is valid. + ERR_FAIL_INDEX_V(p_screen, screen_count, Rect2i(0, 0, 0, 0)); + + bool is_multiscreen = screen_count > 1; + + // Use full monitor size as fallback. + Rect2i rect = _screen_get_rect(p_screen); + + // There's generally only one screen reported by xlib even in multi-screen setup, + // in this case it's just one virtual screen composed of all physical monitors. + int x11_screen_count = ScreenCount(x11_display); + Window x11_window = RootWindow(x11_display, p_screen < x11_screen_count ? p_screen : 0); + + Atom type; + int format = 0; + unsigned long remaining = 0; + + // Find active desktop for the root window. + unsigned int desktop_index = 0; + Atom desktop_prop = XInternAtom(x11_display, "_NET_CURRENT_DESKTOP", True); + if (desktop_prop != None) { + unsigned long desktop_len = 0; + unsigned char *desktop_data = nullptr; + if (XGetWindowProperty(x11_display, x11_window, desktop_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &desktop_len, &remaining, &desktop_data) == Success) { + if ((format == 32) && (desktop_len > 0) && desktop_data) { + desktop_index = (unsigned int)desktop_data[0]; + } + if (desktop_data) { + XFree(desktop_data); + } + } + } + + bool use_simple_method = true; + + // First check for GTK work area, which is more accurate for multi-screen setup. + if (is_multiscreen) { + // Use already calculated work area when available. + Atom gtk_workareas_prop = XInternAtom(x11_display, "_GTK_WORKAREAS", False); + if (gtk_workareas_prop != None) { + char gtk_workarea_prop_name[32]; + snprintf(gtk_workarea_prop_name, 32, "_GTK_WORKAREAS_D%d", desktop_index); + Atom gtk_workarea_prop = XInternAtom(x11_display, gtk_workarea_prop_name, True); + if (gtk_workarea_prop != None) { + unsigned long workarea_len = 0; + unsigned char *workarea_data = nullptr; + if (XGetWindowProperty(x11_display, x11_window, gtk_workarea_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &workarea_len, &remaining, &workarea_data) == Success) { + if ((format == 32) && (workarea_len % 4 == 0) && workarea_data) { + long *rect_data = (long *)workarea_data; + for (uint32_t data_offset = 0; data_offset < workarea_len; data_offset += 4) { + Rect2i workarea_rect; + workarea_rect.position.x = rect_data[data_offset]; + workarea_rect.position.y = rect_data[data_offset + 1]; + workarea_rect.size.x = rect_data[data_offset + 2]; + workarea_rect.size.y = rect_data[data_offset + 3]; + + // Intersect with actual monitor size to find the correct area, + // because areas are not in the same order as screens from Xinerama. + if (rect.grow(-1).intersects(workarea_rect)) { + rect = rect.intersection(workarea_rect); + XFree(workarea_data); + return rect; + } + } + } + } + if (workarea_data) { + XFree(workarea_data); + } + } + } + + // Fallback to calculating work area by hand from struts. + Atom client_list_prop = XInternAtom(x11_display, "_NET_CLIENT_LIST", True); + if (client_list_prop != None) { + unsigned long clients_len = 0; + unsigned char *clients_data = nullptr; + if (XGetWindowProperty(x11_display, x11_window, client_list_prop, 0, LONG_MAX, False, XA_WINDOW, &type, &format, &clients_len, &remaining, &clients_data) == Success) { + if ((format == 32) && (clients_len > 0) && clients_data) { + Window *windows_data = (Window *)clients_data; + + Rect2i desktop_rect; + bool desktop_valid = false; + + // Get full desktop size. + { + Atom desktop_geometry_prop = XInternAtom(x11_display, "_NET_DESKTOP_GEOMETRY", True); + if (desktop_geometry_prop != None) { + unsigned long geom_len = 0; + unsigned char *geom_data = nullptr; + if (XGetWindowProperty(x11_display, x11_window, desktop_geometry_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &geom_len, &remaining, &geom_data) == Success) { + if ((format == 32) && (geom_len >= 2) && geom_data) { + desktop_valid = true; + long *size_data = (long *)geom_data; + desktop_rect.size.x = size_data[0]; + desktop_rect.size.y = size_data[1]; + } + } + if (geom_data) { + XFree(geom_data); + } + } + } + + // Get full desktop position. + if (desktop_valid) { + Atom desktop_viewport_prop = XInternAtom(x11_display, "_NET_DESKTOP_VIEWPORT", True); + if (desktop_viewport_prop != None) { + unsigned long viewport_len = 0; + unsigned char *viewport_data = nullptr; + if (XGetWindowProperty(x11_display, x11_window, desktop_viewport_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &viewport_len, &remaining, &viewport_data) == Success) { + if ((format == 32) && (viewport_len >= 2) && viewport_data) { + desktop_valid = true; + long *pos_data = (long *)viewport_data; + desktop_rect.position.x = pos_data[0]; + desktop_rect.position.y = pos_data[1]; + } + } + if (viewport_data) { + XFree(viewport_data); + } + } + } + + if (desktop_valid) { + use_simple_method = false; + + // Handle bad window errors silently because there's no other way to check + // that one of the windows has been destroyed in the meantime. + int (*oldHandler)(Display *, XErrorEvent *) = XSetErrorHandler(&bad_window_error_handler); + + for (unsigned long win_index = 0; win_index < clients_len; ++win_index) { + g_bad_window = false; + + // Remove strut size from desktop size to get a more accurate result. + bool strut_found = false; + unsigned long strut_len = 0; + unsigned char *strut_data = nullptr; + Atom strut_partial_prop = XInternAtom(x11_display, "_NET_WM_STRUT_PARTIAL", True); + if (strut_partial_prop != None) { + if (XGetWindowProperty(x11_display, windows_data[win_index], strut_partial_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &strut_len, &remaining, &strut_data) == Success) { + strut_found = true; + } + } + // Fallback to older strut property. + if (!g_bad_window && !strut_found) { + Atom strut_prop = XInternAtom(x11_display, "_NET_WM_STRUT", True); + if (strut_prop != None) { + if (XGetWindowProperty(x11_display, windows_data[win_index], strut_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &strut_len, &remaining, &strut_data) == Success) { + strut_found = true; + } + } + } + if (!g_bad_window && strut_found && (format == 32) && (strut_len >= 4) && strut_data) { + long *struts = (long *)strut_data; + + long left = struts[0]; + long right = struts[1]; + long top = struts[2]; + long bottom = struts[3]; + + long left_start_y, left_end_y, right_start_y, right_end_y; + long top_start_x, top_end_x, bottom_start_x, bottom_end_x; + + if (strut_len >= 12) { + left_start_y = struts[4]; + left_end_y = struts[5]; + right_start_y = struts[6]; + right_end_y = struts[7]; + top_start_x = struts[8]; + top_end_x = struts[9]; + bottom_start_x = struts[10]; + bottom_end_x = struts[11]; + } else { + left_start_y = 0; + left_end_y = desktop_rect.size.y; + right_start_y = 0; + right_end_y = desktop_rect.size.y; + top_start_x = 0; + top_end_x = desktop_rect.size.x; + bottom_start_x = 0; + bottom_end_x = desktop_rect.size.x; + } + + const Point2i &pos = desktop_rect.position; + const Size2i &size = desktop_rect.size; + + Rect2i left_rect(pos.x, pos.y + left_start_y, left, left_end_y - left_start_y); + if (left_rect.size.x > 0) { + Rect2i intersection = rect.intersection(left_rect); + if (intersection.has_area() && intersection.size.x < rect.size.x) { + rect.position.x = left_rect.size.x; + rect.size.x = rect.size.x - intersection.size.x; + } + } + + Rect2i right_rect(pos.x + size.x - right, pos.y + right_start_y, right, right_end_y - right_start_y); + if (right_rect.size.x > 0) { + Rect2i intersection = rect.intersection(right_rect); + if (intersection.has_area() && right_rect.size.x < rect.size.x) { + rect.size.x = intersection.position.x - rect.position.x; + } + } + + Rect2i top_rect(pos.x + top_start_x, pos.y, top_end_x - top_start_x, top); + if (top_rect.size.y > 0) { + Rect2i intersection = rect.intersection(top_rect); + if (intersection.has_area() && intersection.size.y < rect.size.y) { + rect.position.y = top_rect.size.y; + rect.size.y = rect.size.y - intersection.size.y; + } + } + + Rect2i bottom_rect(pos.x + bottom_start_x, pos.y + size.y - bottom, bottom_end_x - bottom_start_x, bottom); + if (bottom_rect.size.y > 0) { + Rect2i intersection = rect.intersection(bottom_rect); + if (intersection.has_area() && right_rect.size.y < rect.size.y) { + rect.size.y = intersection.position.y - rect.position.y; + } + } + } + if (strut_data) { + XFree(strut_data); + } + } + + // Restore default error handler. + XSetErrorHandler(oldHandler); + } + } + } + if (clients_data) { + XFree(clients_data); + } + } + } + + // Single screen or fallback for multi screen. + if (use_simple_method) { + // Get desktop available size from the global work area. + Atom workarea_prop = XInternAtom(x11_display, "_NET_WORKAREA", True); + if (workarea_prop != None) { + unsigned long workarea_len = 0; + unsigned char *workarea_data = nullptr; + if (XGetWindowProperty(x11_display, x11_window, workarea_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &workarea_len, &remaining, &workarea_data) == Success) { + if ((format == 32) && (workarea_len >= ((desktop_index + 1) * 4)) && workarea_data) { + long *rect_data = (long *)workarea_data; + int data_offset = desktop_index * 4; + Rect2i workarea_rect; + workarea_rect.position.x = rect_data[data_offset]; + workarea_rect.position.y = rect_data[data_offset + 1]; + workarea_rect.size.x = rect_data[data_offset + 2]; + workarea_rect.size.y = rect_data[data_offset + 3]; + + // Intersect with actual monitor size to get a proper approximation in multi-screen setup. + if (!is_multiscreen) { + rect = workarea_rect; + } else if (rect.intersects(workarea_rect)) { + rect = rect.intersection(workarea_rect); + } + } + } + if (workarea_data) { + XFree(workarea_data); + } + } + } + + return rect; +} + +int DisplayServerX11::screen_get_dpi(int p_screen) const { + _THREAD_SAFE_METHOD_ + + if (p_screen == SCREEN_OF_MAIN_WINDOW) { + p_screen = window_get_current_screen(); + } + + //invalid screen? + ERR_FAIL_INDEX_V(p_screen, get_screen_count(), 0); + + //Get physical monitor Dimensions through XRandR and calculate dpi + Size2i sc = screen_get_size(p_screen); + if (xrandr_ext_ok) { + int count = 0; + if (xrr_get_monitors) { + xrr_monitor_info *monitors = xrr_get_monitors(x11_display, windows[MAIN_WINDOW_ID].x11_window, true, &count); + if (p_screen < count) { + double xdpi = sc.width / (double)monitors[p_screen].mwidth * 25.4; + double ydpi = sc.height / (double)monitors[p_screen].mheight * 25.4; + xrr_free_monitors(monitors); + return (xdpi + ydpi) / 2; + } + xrr_free_monitors(monitors); + } else if (p_screen == 0) { + XRRScreenSize *sizes = XRRSizes(x11_display, 0, &count); + if (sizes) { + double xdpi = sc.width / (double)sizes[0].mwidth * 25.4; + double ydpi = sc.height / (double)sizes[0].mheight * 25.4; + return (xdpi + ydpi) / 2; + } + } + } + + int width_mm = DisplayWidthMM(x11_display, p_screen); + 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) { + return (xdpi + ydpi) / (xdpi && ydpi ? 2 : 1); + } + + //could not get dpi + return 96; +} + +float DisplayServerX11::screen_get_refresh_rate(int p_screen) const { + _THREAD_SAFE_METHOD_ + + if (p_screen == SCREEN_OF_MAIN_WINDOW) { + p_screen = window_get_current_screen(); + } + + //invalid screen? + ERR_FAIL_INDEX_V(p_screen, get_screen_count(), SCREEN_REFRESH_RATE_FALLBACK); + + //Use xrandr to get screen refresh rate. + if (xrandr_ext_ok) { + XRRScreenResources *screen_info = XRRGetScreenResources(x11_display, windows[MAIN_WINDOW_ID].x11_window); + if (screen_info) { + RRMode current_mode = 0; + xrr_monitor_info *monitors = nullptr; + + if (xrr_get_monitors) { + int count = 0; + monitors = xrr_get_monitors(x11_display, windows[MAIN_WINDOW_ID].x11_window, true, &count); + ERR_FAIL_INDEX_V(p_screen, count, SCREEN_REFRESH_RATE_FALLBACK); + } else { + ERR_PRINT("An error occurred while trying to get the screen refresh rate."); + return SCREEN_REFRESH_RATE_FALLBACK; + } + + bool found_active_mode = false; + for (int crtc = 0; crtc < screen_info->ncrtc; crtc++) { // Loop through outputs to find which one is currently outputting. + XRRCrtcInfo *monitor_info = XRRGetCrtcInfo(x11_display, screen_info, screen_info->crtcs[crtc]); + if (monitor_info->x != monitors[p_screen].x || monitor_info->y != monitors[p_screen].y) { // If X and Y aren't the same as the monitor we're looking for, this isn't the right monitor. Continue. + continue; + } + + if (monitor_info->mode != None) { + current_mode = monitor_info->mode; + found_active_mode = true; + break; + } + } + + if (found_active_mode) { + for (int mode = 0; mode < screen_info->nmode; mode++) { + XRRModeInfo m_info = screen_info->modes[mode]; + if (m_info.id == current_mode) { + // Snap to nearest 0.01 to stay consistent with other platforms. + return Math::snapped((float)m_info.dotClock / ((float)m_info.hTotal * (float)m_info.vTotal), 0.01); + } + } + } + + ERR_PRINT("An error occurred while trying to get the screen refresh rate."); // We should have returned the refresh rate by now. An error must have occurred. + return SCREEN_REFRESH_RATE_FALLBACK; + } else { + ERR_PRINT("An error occurred while trying to get the screen refresh rate."); + return SCREEN_REFRESH_RATE_FALLBACK; + } + } + ERR_PRINT("An error occurred while trying to get the screen refresh rate."); + return SCREEN_REFRESH_RATE_FALLBACK; +} + +#ifdef DBUS_ENABLED +void DisplayServerX11::screen_set_keep_on(bool p_enable) { + if (screen_is_kept_on() == p_enable) { + return; + } + + if (p_enable) { + screensaver->inhibit(); + } else { + screensaver->uninhibit(); + } + + keep_screen_on = p_enable; +} + +bool DisplayServerX11::screen_is_kept_on() const { + return keep_screen_on; +} +#endif + +Vector DisplayServerX11::get_window_list() const { + _THREAD_SAFE_METHOD_ + + Vector ret; + for (const KeyValue &E : windows) { + ret.push_back(E.key); + } + return ret; +} + +DisplayServer::WindowID DisplayServerX11::create_sub_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect) { + _THREAD_SAFE_METHOD_ + + WindowID id = _create_window(p_mode, p_vsync_mode, p_flags, p_rect); + for (int i = 0; i < WINDOW_FLAG_MAX; i++) { + if (p_flags & (1 << i)) { + window_set_flag(WindowFlags(i), true, id); + } + } + + return id; +} + +void DisplayServerX11::show_window(WindowID p_id) { + _THREAD_SAFE_METHOD_ + + const WindowData &wd = windows[p_id]; + popup_open(p_id); + + DEBUG_LOG_X11("show_window: %lu (%u) \n", wd.x11_window, 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)); + ERR_FAIL_COND_MSG(p_id == MAIN_WINDOW_ID, "Main window can't be deleted"); + + popup_close(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.begin(), INVALID_WINDOW_ID); + } + + if (wd.transient_parent != INVALID_WINDOW_ID) { + window_set_transient(p_id, INVALID_WINDOW_ID); + } + +#ifdef VULKAN_ENABLED + if (context_vulkan) { + context_vulkan->window_destroy(p_id); + } +#endif +#ifdef GLES3_ENABLED + if (gl_manager) { + gl_manager->window_destroy(p_id); + } +#endif + + XUnmapWindow(x11_display, wd.x11_window); + XDestroyWindow(x11_display, wd.x11_window); + if (wd.xic) { + XDestroyIC(wd.xic); + wd.xic = nullptr; + } + + windows.erase(p_id); +} + +int64_t DisplayServerX11::window_get_native_handle(HandleType p_handle_type, WindowID p_window) const { + ERR_FAIL_COND_V(!windows.has(p_window), 0); + switch (p_handle_type) { + case DISPLAY_HANDLE: { + return (int64_t)x11_display; + } + case WINDOW_HANDLE: { + return (int64_t)windows[p_window].x11_window; + } + case WINDOW_VIEW: { + return 0; // Not supported. + } + default: { + return 0; + } + } +} + +void DisplayServerX11::window_attach_instance_id(ObjectID p_instance, WindowID p_window) { + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; + + wd.instance_id = p_instance; +} + +ObjectID DisplayServerX11::window_get_attached_instance_id(WindowID p_window) const { + ERR_FAIL_COND_V(!windows.has(p_window), ObjectID()); + const WindowData &wd = windows[p_window]; + return wd.instance_id; +} + +DisplayServerX11::WindowID DisplayServerX11::get_window_at_screen_position(const Point2i &p_position) const { + WindowID found_window = INVALID_WINDOW_ID; + WindowID parent_window = INVALID_WINDOW_ID; + unsigned int focus_order = 0; + for (const KeyValue &E : windows) { + const WindowData &wd = E.value; + + // 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)); + WindowData &wd = windows[p_window]; + + XStoreName(x11_display, wd.x11_window, p_title.utf8().get_data()); + + Atom _net_wm_name = XInternAtom(x11_display, "_NET_WM_NAME", false); + Atom utf8_string = XInternAtom(x11_display, "UTF8_STRING", false); + 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_mouse_passthrough(const Vector &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)); + WindowData &wd = windows[p_window]; + wd.rect_changed_callback = p_callable; +} + +void DisplayServerX11::window_set_window_event_callback(const Callable &p_callable, WindowID p_window) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; + wd.event_callback = p_callable; +} + +void DisplayServerX11::window_set_input_event_callback(const Callable &p_callable, WindowID p_window) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; + wd.input_event_callback = p_callable; +} + +void DisplayServerX11::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; + wd.input_text_callback = p_callable; +} + +void DisplayServerX11::window_set_drop_files_callback(const Callable &p_callable, WindowID p_window) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; + wd.drop_files_callback = p_callable; +} + +int DisplayServerX11::window_get_current_screen(WindowID p_window) const { + _THREAD_SAFE_METHOD_ + + int count = get_screen_count(); + if (count < 2) { + // Early exit with single monitor. + return 0; + } + + ERR_FAIL_COND_V(!windows.has(p_window), 0); + const WindowData &wd = windows[p_window]; + + const Rect2i window_rect(wd.position, wd.size); + + // Find which monitor has the largest overlap with the given window. + int screen_index = 0; + int max_area = 0; + for (int i = 0; i < count; i++) { + Rect2i screen_rect = _screen_get_rect(i); + Rect2i intersection = screen_rect.intersection(window_rect); + int area = intersection.get_area(); + if (area > max_area) { + max_area = area; + screen_index = i; + } + } + + return screen_index; +} + +void DisplayServerX11::gl_window_make_current(DisplayServer::WindowID p_window_id) { +#if defined(GLES3_ENABLED) + if (gl_manager) { + gl_manager->window_make_current(p_window_id); + } +#endif +} + +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]; + + if (p_screen == SCREEN_OF_MAIN_WINDOW) { + p_screen = window_get_current_screen(); + } + + // Check if screen is valid + ERR_FAIL_INDEX(p_screen, get_screen_count()); + + if (window_get_mode(p_window) == WINDOW_MODE_FULLSCREEN) { + Point2i position = screen_get_position(p_screen); + Size2i size = screen_get_size(p_screen); + + XMoveResizeWindow(x11_display, wd.x11_window, position.x, position.y, size.x, size.y); + } else { + if (p_screen != window_get_current_screen(p_window)) { + Vector2 ofs = window_get_position(p_window) - screen_get_position(window_get_current_screen(p_window)); + window_set_position(ofs + screen_get_position(p_screen), p_window); + } + } +} + +void DisplayServerX11::window_set_transient(WindowID p_window, WindowID p_parent) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(p_window == p_parent); + + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd_window = windows[p_window]; + + WindowID prev_parent = wd_window.transient_parent; + ERR_FAIL_COND(prev_parent == p_parent); + + DEBUG_LOG_X11("window_set_transient: %lu (%u), prev_parent=%u, parent=%u\n", wd_window.x11_window, p_window, 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(prev_parent == INVALID_WINDOW_ID); + ERR_FAIL_COND(!windows.has(prev_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); + + XWindowAttributes xwa; + XSync(x11_display, False); + XGetWindowAttributes(x11_display, wd_parent.x11_window, &xwa); + + // Set focus to parent sub window to avoid losing all focus when closing a nested sub-menu. + // 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.no_focus && !wd_window.is_popup && wd_window.focused) { + if ((xwa.map_state == IsViewable) && !wd_parent.no_focus && !wd_window.is_popup) { + XSetInputFocus(x11_display, wd_parent.x11_window, RevertToPointerRoot, CurrentTime); + } + } + } else { + ERR_FAIL_COND(!windows.has(p_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; + wd_parent.transient_children.insert(p_window); + + XSetTransientForHint(x11_display, wd_window.x11_window, wd_parent.x11_window); + } +} + +// 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()); + const WindowData &wd = windows[p_window]; + + return wd.position; +} + +void DisplayServerX11::window_set_position(const Point2i &p_position, WindowID p_window) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; + + int x = 0; + int y = 0; + if (!window_get_flag(WINDOW_FLAG_BORDERLESS, p_window)) { + //exclude window decorations + XSync(x11_display, False); + Atom prop = XInternAtom(x11_display, "_NET_FRAME_EXTENTS", True); + if (prop != None) { + Atom type; + int format; + unsigned long len; + unsigned long remaining; + unsigned char *data = nullptr; + if (XGetWindowProperty(x11_display, wd.x11_window, prop, 0, 4, False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) { + if (format == 32 && len == 4 && data) { + long *extents = (long *)data; + x = extents[0]; + y = extents[2]; + } + XFree(data); + } + } + } + XMoveWindow(x11_display, wd.x11_window, p_position.x - x, p_position.y - y); + _update_real_mouse_position(wd); +} + +void DisplayServerX11::window_set_max_size(const Size2i p_size, WindowID p_window) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; + + if ((p_size != Size2i()) && ((p_size.x < wd.min_size.x) || (p_size.y < wd.min_size.y))) { + ERR_PRINT("Maximum window size can't be smaller than minimum window size!"); + return; + } + wd.max_size = p_size; + + _update_size_hints(p_window); + XFlush(x11_display); +} + +Size2i DisplayServerX11::window_get_max_size(WindowID p_window) const { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND_V(!windows.has(p_window), Size2i()); + const WindowData &wd = windows[p_window]; + + return wd.max_size; +} + +void DisplayServerX11::window_set_min_size(const Size2i p_size, WindowID p_window) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; + + if ((p_size != Size2i()) && (wd.max_size != Size2i()) && ((p_size.x > wd.max_size.x) || (p_size.y > wd.max_size.y))) { + ERR_PRINT("Minimum window size can't be larger than maximum window size!"); + return; + } + wd.min_size = p_size; + + _update_size_hints(p_window); + XFlush(x11_display); +} + +Size2i DisplayServerX11::window_get_min_size(WindowID p_window) const { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND_V(!windows.has(p_window), Size2i()); + const WindowData &wd = windows[p_window]; + + return wd.min_size; +} + +void DisplayServerX11::window_set_size(const Size2i p_size, WindowID p_window) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + + Size2i size = p_size; + size.x = MAX(1, size.x); + size.y = MAX(1, size.y); + + WindowData &wd = windows[p_window]; + + if (wd.size.width == size.width && wd.size.height == size.height) { + return; + } + + XWindowAttributes xwa; + XSync(x11_display, False); + XGetWindowAttributes(x11_display, wd.x11_window, &xwa); + int old_w = xwa.width; + int old_h = xwa.height; + + // 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); + + 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) { + break; + } + + usleep(10000); + } + + // Keep rendering context window size in sync +#if defined(VULKAN_ENABLED) + if (context_vulkan) { + context_vulkan->window_resize(p_window, xwa.width, xwa.height); + } +#endif +#if defined(GLES3_ENABLED) + if (gl_manager) { + gl_manager->window_resize(p_window, xwa.width, xwa.height); + } +#endif +} + +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 { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND_V(!windows.has(p_window), Size2i()); + const WindowData &wd = windows[p_window]; + + XWindowAttributes xwa; + XSync(x11_display, False); + XGetWindowAttributes(x11_display, wd.x11_window, &xwa); + int w = xwa.width; + int h = xwa.height; + Atom prop = XInternAtom(x11_display, "_NET_FRAME_EXTENTS", True); + if (prop != None) { + Atom type; + int format; + unsigned long len; + unsigned long remaining; + unsigned char *data = nullptr; + if (XGetWindowProperty(x11_display, wd.x11_window, prop, 0, 4, False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) { + if (format == 32 && len == 4 && data) { + long *extents = (long *)data; + w += extents[0] + extents[1]; // left, right + h += extents[2] + extents[3]; // top, bottom + } + XFree(data); + } + } + return Size2i(w, h); +} + +// 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, 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, + wd.x11_window, + property, + 0, + 1024, + False, + XA_ATOM, + &type, + &format, + &len, + &remaining, + &data); + + if (result == Success && data) { + Atom *atoms = (Atom *)data; + Atom wm_act_max_horz; + Atom wm_act_max_vert; + if (strcmp(p_atom_name, "_NET_WM_STATE") == 0) { + wm_act_max_horz = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_HORZ", False); + wm_act_max_vert = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_VERT", False); + } else { + wm_act_max_horz = XInternAtom(x11_display, "_NET_WM_ACTION_MAXIMIZE_HORZ", False); + wm_act_max_vert = XInternAtom(x11_display, "_NET_WM_ACTION_MAXIMIZE_VERT", False); + } + bool found_wm_act_max_horz = false; + bool found_wm_act_max_vert = false; + + for (uint64_t i = 0; i < len; i++) { + if (atoms[i] == wm_act_max_horz) { + found_wm_act_max_horz = true; + } + if (atoms[i] == wm_act_max_vert) { + found_wm_act_max_vert = true; + } + + if (found_wm_act_max_horz || found_wm_act_max_vert) { + retval = true; + break; + } + } + + XFree(data); + } + + return retval; +} + +bool DisplayServerX11::_window_minimize_check(WindowID p_window) const { + const WindowData &wd = windows[p_window]; + + // Using ICCCM -- Inter-Client Communication Conventions Manual + Atom property = XInternAtom(x11_display, "WM_STATE", True); + if (property == None) { + return false; + } + + Atom type; + int format; + unsigned long len; + unsigned long remaining; + unsigned char *data = nullptr; + + int result = XGetWindowProperty( + x11_display, + wd.x11_window, + property, + 0, + 32, + False, + AnyPropertyType, + &type, + &format, + &len, + &remaining, + &data); + + if (result == Success && data) { + long *state = (long *)data; + if (state[0] == WM_IconicState) { + XFree(data); + return true; + } + XFree(data); + } + + return false; +} + +bool DisplayServerX11::_window_fullscreen_check(WindowID p_window) const { + ERR_FAIL_COND_V(!windows.has(p_window), false); + const WindowData &wd = windows[p_window]; + + // 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; + + if (property == None) { + return retval; + } + + int result = XGetWindowProperty( + x11_display, + wd.x11_window, + property, + 0, + 1024, + False, + XA_ATOM, + &type, + &format, + &len, + &remaining, + &data); + + if (result == Success) { + Atom *atoms = (Atom *)data; + Atom wm_fullscreen = XInternAtom(x11_display, "_NET_WM_STATE_FULLSCREEN", False); + for (uint64_t i = 0; i < len; i++) { + if (atoms[i] == wm_fullscreen) { + retval = true; + break; + } + } + XFree(data); + } + + return retval; +} + +void DisplayServerX11::_validate_mode_on_map(WindowID p_window) { + // Check if we applied any window modes that didn't take effect while unmapped + const WindowData &wd = windows[p_window]; + if (wd.fullscreen && !_window_fullscreen_check(p_window)) { + _set_wm_fullscreen(p_window, true); + } else if (wd.maximized && !_window_maximize_check(p_window, "_NET_WM_STATE")) { + _set_wm_maximized(p_window, true); + } else if (wd.minimized && !_window_minimize_check(p_window)) { + _set_wm_minimized(p_window, true); + } +} + +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]; + + // Using EWMH -- Extended Window Manager Hints + XEvent xev; + Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False); + 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); + + memset(&xev, 0, sizeof(xev)); + xev.type = ClientMessage; + xev.xclient.window = wd.x11_window; + xev.xclient.message_type = wm_state; + xev.xclient.format = 32; + xev.xclient.data.l[0] = p_enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; + xev.xclient.data.l[1] = wm_max_horz; + xev.xclient.data.l[2] = wm_max_vert; + + XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); + + if (p_enabled && window_is_maximize_allowed(p_window)) { + // Wait for effective resizing (so the GLX context is too). + // Give up after 0.5s, it's not going to happen on this WM. + // https://github.com/godotengine/godot/issues/19978 + for (int attempt = 0; window_get_mode(p_window) != WINDOW_MODE_MAXIMIZED && attempt < 50; attempt++) { + usleep(10000); + } + } + wd.maximized = p_enabled; +} + +void DisplayServerX11::_set_wm_minimized(WindowID p_window, bool p_enabled) { + WindowData &wd = windows[p_window]; + // Using ICCCM -- Inter-Client Communication Conventions Manual + XEvent xev; + Atom wm_change = XInternAtom(x11_display, "WM_CHANGE_STATE", False); + + memset(&xev, 0, sizeof(xev)); + xev.type = ClientMessage; + xev.xclient.window = wd.x11_window; + xev.xclient.message_type = wm_change; + xev.xclient.format = 32; + xev.xclient.data.l[0] = p_enabled ? WM_IconicState : WM_NormalState; + + XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); + + Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False); + Atom wm_hidden = XInternAtom(x11_display, "_NET_WM_STATE_HIDDEN", False); + + memset(&xev, 0, sizeof(xev)); + xev.type = ClientMessage; + xev.xclient.window = wd.x11_window; + xev.xclient.message_type = wm_state; + xev.xclient.format = 32; + xev.xclient.data.l[0] = p_enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; + xev.xclient.data.l[1] = wm_hidden; + + XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); + wd.minimized = 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]; + + if (p_enabled && !window_get_flag(WINDOW_FLAG_BORDERLESS, p_window)) { + // remove decorations if the window is not already borderless + Hints hints; + Atom property; + hints.flags = 2; + hints.decorations = 0; + property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True); + if (property != None) { + XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5); + } + } + + if (p_enabled) { + // Set the window as resizable to prevent window managers to ignore the fullscreen state flag. + _update_size_hints(p_window); + } + + // Using EWMH -- Extended Window Manager Hints + XEvent xev; + Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False); + Atom wm_fullscreen = XInternAtom(x11_display, "_NET_WM_STATE_FULLSCREEN", False); + + memset(&xev, 0, sizeof(xev)); + xev.type = ClientMessage; + xev.xclient.window = wd.x11_window; + xev.xclient.message_type = wm_state; + xev.xclient.format = 32; + xev.xclient.data.l[0] = p_enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; + xev.xclient.data.l[1] = wm_fullscreen; + xev.xclient.data.l[2] = 0; + + XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); + + // set bypass compositor hint + Atom bypass_compositor = XInternAtom(x11_display, "_NET_WM_BYPASS_COMPOSITOR", False); + unsigned long compositing_disable_on = p_enabled ? 1 : 0; + 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. + _update_size_hints(p_window); + + // put back or remove decorations according to the last set borderless state + Hints hints; + Atom property; + hints.flags = 2; + hints.decorations = wd.borderless ? 0 : 1; + 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); + } + } +} + +void DisplayServerX11::window_set_mode(WindowMode p_mode, WindowID p_window) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; + + WindowMode old_mode = window_get_mode(p_window); + if (old_mode == p_mode) { + return; // do nothing + } + //remove all "extra" modes + + switch (old_mode) { + case WINDOW_MODE_WINDOWED: { + //do nothing + } break; + case WINDOW_MODE_MINIMIZED: { + _set_wm_minimized(p_window, false); + } break; + case WINDOW_MODE_EXCLUSIVE_FULLSCREEN: + 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); + + window_set_position(wd.last_position_before_fs, p_window); + + if (on_top) { + _set_wm_maximized(p_window, false); + } + + } break; + case WINDOW_MODE_MAXIMIZED: { + _set_wm_maximized(p_window, false); + } break; + } + + switch (p_mode) { + case WINDOW_MODE_WINDOWED: { + //do nothing + } break; + case WINDOW_MODE_MINIMIZED: { + _set_wm_minimized(p_window, true); + } break; + case WINDOW_MODE_EXCLUSIVE_FULLSCREEN: + 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); + } + + 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); + const WindowData &wd = windows[p_window]; + + if (wd.fullscreen) { //if fullscreen, it's not in another mode + return WINDOW_MODE_FULLSCREEN; + } + + // Test maximized. + // Using EWMH -- Extended Window Manager Hints + if (_window_maximize_check(p_window, "_NET_WM_STATE")) { + return WINDOW_MODE_MAXIMIZED; + } + + { + if (_window_minimize_check(p_window)) { + return WINDOW_MODE_MINIMIZED; + } + } + + // All other discarded, return windowed. + + return WINDOW_MODE_WINDOWED; +} + +void DisplayServerX11::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; + + switch (p_flag) { + case WINDOW_FLAG_RESIZE_DISABLED: { + wd.resize_disabled = p_enabled; + + _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); + 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); + + wd.borderless = p_enabled; + } break; + case WINDOW_FLAG_ALWAYS_ON_TOP: { + ERR_FAIL_COND_MSG(wd.transient_parent != INVALID_WINDOW_ID, "Can't make a window transient if the 'on top' flag is active."); + if (p_enabled && wd.fullscreen) { + _set_wm_maximized(p_window, true); + } + + Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False); + Atom wm_above = XInternAtom(x11_display, "_NET_WM_STATE_ABOVE", False); + + XClientMessageEvent xev; + memset(&xev, 0, sizeof(xev)); + xev.type = ClientMessage; + xev.window = wd.x11_window; + xev.message_type = wm_state; + xev.format = 32; + xev.data.l[0] = p_enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; + xev.data.l[1] = wm_above; + xev.data.l[3] = 1; + XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent *)&xev); + + if (!p_enabled && !wd.fullscreen) { + _set_wm_maximized(p_window, false); + } + wd.on_top = p_enabled; + + } break; + case WINDOW_FLAG_TRANSPARENT: { + wd.layered_window = p_enabled; + } break; + case WINDOW_FLAG_NO_FOCUS: { + wd.no_focus = p_enabled; + } break; + case WINDOW_FLAG_POPUP: { + XWindowAttributes xwa; + XSync(x11_display, False); + XGetWindowAttributes(x11_display, wd.x11_window, &xwa); + + ERR_FAIL_COND_MSG(p_window == MAIN_WINDOW_ID, "Main window can't be popup."); + ERR_FAIL_COND_MSG((xwa.map_state == IsViewable) && (wd.is_popup != p_enabled), "Popup flag can't changed while window is opened."); + wd.is_popup = p_enabled; + } break; + default: { + } + } +} + +bool DisplayServerX11::window_get_flag(WindowFlags p_flag, WindowID p_window) const { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND_V(!windows.has(p_window), false); + const WindowData &wd = windows[p_window]; + + switch (p_flag) { + case WINDOW_FLAG_RESIZE_DISABLED: { + return wd.resize_disabled; + } break; + case WINDOW_FLAG_BORDERLESS: { + bool borderless = wd.borderless; + Atom prop = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True); + if (prop != None) { + Atom type; + int format; + unsigned long len; + unsigned long remaining; + unsigned char *data = nullptr; + if (XGetWindowProperty(x11_display, wd.x11_window, prop, 0, sizeof(Hints), False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) { + if (data && (format == 32) && (len >= 5)) { + borderless = !(reinterpret_cast(data)->decorations); + } + if (data) { + XFree(data); + } + } + } + return borderless; + } break; + case WINDOW_FLAG_ALWAYS_ON_TOP: { + return wd.on_top; + } break; + case WINDOW_FLAG_TRANSPARENT: { + return wd.layered_window; + } break; + case WINDOW_FLAG_NO_FOCUS: { + return wd.no_focus; + } break; + case WINDOW_FLAG_POPUP: { + return wd.is_popup; + } break; + default: { + } + } + + return false; +} + +void DisplayServerX11::window_request_attention(WindowID p_window) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + const WindowData &wd = windows[p_window]; + // Using EWMH -- Extended Window Manager Hints + // + // Sets the _NET_WM_STATE_DEMANDS_ATTENTION atom for WM_STATE + // Will be unset by the window manager after user react on the request for attention + + XEvent xev; + Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False); + Atom wm_attention = XInternAtom(x11_display, "_NET_WM_STATE_DEMANDS_ATTENTION", False); + + memset(&xev, 0, sizeof(xev)); + xev.type = ClientMessage; + xev.xclient.window = wd.x11_window; + xev.xclient.message_type = wm_state; + xev.xclient.format = 32; + xev.xclient.data.l[0] = _NET_WM_STATE_ADD; + xev.xclient.data.l[1] = wm_attention; + + XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); + XFlush(x11_display); +} + +void DisplayServerX11::window_move_to_foreground(WindowID p_window) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + const WindowData &wd = windows[p_window]; + + XEvent xev; + Atom net_active_window = XInternAtom(x11_display, "_NET_ACTIVE_WINDOW", False); + + memset(&xev, 0, sizeof(xev)); + xev.type = ClientMessage; + xev.xclient.window = wd.x11_window; + xev.xclient.message_type = net_active_window; + xev.xclient.format = 32; + xev.xclient.data.l[0] = 1; + xev.xclient.data.l[1] = CurrentTime; + + XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); + XFlush(x11_display); +} + +bool DisplayServerX11::window_can_draw(WindowID p_window) const { + //this seems to be all that is provided by X11 + return window_get_mode(p_window) != WINDOW_MODE_MINIMIZED; +} + +bool DisplayServerX11::can_any_window_draw() const { + _THREAD_SAFE_METHOD_ + + for (const KeyValue &E : windows) { + if (window_get_mode(E.key) != WINDOW_MODE_MINIMIZED) { + return true; + } + } + + return false; +} + +void DisplayServerX11::window_set_ime_active(const bool p_active, WindowID p_window) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; + + wd.im_active = p_active; + + if (!wd.xic) { + return; + } + + // Block events polling while changing input focus + // because it triggers some event polling internally. + if (p_active) { + { + 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) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; + + wd.im_position = p_pos; + + if (!wd.xic) { + return; + } + + ::XPoint spot; + spot.x = short(p_pos.x); + spot.y = short(p_pos.y); + XVaNestedList preedit_attr = XVaCreateNestedList(0, XNSpotLocation, &spot, 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); + + if (p_shape == current_cursor) { + return; + } + + if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) { + if (cursors[p_shape] != None) { + for (const KeyValue &E : windows) { + XDefineCursor(x11_display, E.value.x11_window, cursors[p_shape]); + } + } else if (cursors[CURSOR_ARROW] != None) { + for (const KeyValue &E : windows) { + XDefineCursor(x11_display, E.value.x11_window, cursors[CURSOR_ARROW]); + } + } + } + + current_cursor = p_shape; +} + +DisplayServerX11::CursorShape DisplayServerX11::cursor_get_shape() const { + return current_cursor; +} + +void DisplayServerX11::cursor_set_custom_image(const Ref &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) { + _THREAD_SAFE_METHOD_ + + if (p_cursor.is_valid()) { + HashMap>::Iterator cursor_c = cursors_cache.find(p_shape); + + if (cursor_c) { + if (cursor_c->value[0] == p_cursor && cursor_c->value[1] == p_hotspot) { + cursor_set_shape(p_shape); + return; + } + + cursors_cache.erase(p_shape); + } + + Ref texture = p_cursor; + Ref atlas_texture = p_cursor; + Ref image; + Size2i texture_size; + Rect2i atlas_rect; + + if (texture.is_valid()) { + image = texture->get_image(); + } + + if (!image.is_valid() && atlas_texture.is_valid()) { + texture = atlas_texture->get_atlas(); + + atlas_rect.size.width = texture->get_width(); + atlas_rect.size.height = texture->get_height(); + atlas_rect.position.x = atlas_texture->get_region().position.x; + atlas_rect.position.y = atlas_texture->get_region().position.y; + + texture_size.width = atlas_texture->get_region().size.x; + texture_size.height = atlas_texture->get_region().size.y; + } else if (image.is_valid()) { + texture_size.width = texture->get_width(); + texture_size.height = texture->get_height(); + } + + ERR_FAIL_COND(!texture.is_valid()); + ERR_FAIL_COND(p_hotspot.x < 0 || p_hotspot.y < 0); + ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256); + ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height); + + image = texture->get_image(); + + ERR_FAIL_COND(!image.is_valid()); + + // Create the cursor structure + XcursorImage *cursor_image = XcursorImageCreate(texture_size.width, texture_size.height); + XcursorUInt image_size = texture_size.width * texture_size.height; + XcursorDim size = sizeof(XcursorPixel) * image_size; + + cursor_image->version = 1; + cursor_image->size = size; + cursor_image->xhot = p_hotspot.x; + cursor_image->yhot = p_hotspot.y; + + // allocate memory to contain the whole file + cursor_image->pixels = (XcursorPixel *)memalloc(size); + + for (XcursorPixel index = 0; index < image_size; index++) { + int row_index = floor(index / texture_size.width) + atlas_rect.position.y; + int column_index = (index % int(texture_size.width)) + atlas_rect.position.x; + + if (atlas_texture.is_valid()) { + column_index = MIN(column_index, atlas_rect.size.width - 1); + row_index = MIN(row_index, atlas_rect.size.height - 1); + } + + *(cursor_image->pixels + index) = image->get_pixel(column_index, row_index).to_argb32(); + } + + ERR_FAIL_COND(cursor_image->pixels == nullptr); + + // Save it for a further usage + cursors[p_shape] = XcursorImageLoadCursor(x11_display, cursor_image); + + Vector params; + params.push_back(p_cursor); + params.push_back(p_hotspot); + cursors_cache.insert(p_shape, params); + + if (p_shape == current_cursor) { + if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) { + for (const KeyValue &E : windows) { + XDefineCursor(x11_display, E.value.x11_window, cursors[p_shape]); + } + } + } + + memfree(cursor_image->pixels); + XcursorImageDestroy(cursor_image); + } else { + // Reset to default system cursor + if (img[p_shape]) { + cursors[p_shape] = XcursorImageLoadCursor(x11_display, img[p_shape]); + } + + CursorShape c = current_cursor; + current_cursor = CURSOR_MAX; + cursor_set_shape(c); + + cursors_cache.erase(p_shape); + } +} + +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 != nullptr) { + _group_count = kbd->ctrls->num_groups; + } else { + while (_group_count < XkbNumKbdGroups && groups[_group_count] != None) { + _group_count++; + } + } + XkbFreeKeyboard(kbd, 0, true); + } + return _group_count; +} + +int DisplayServerX11::keyboard_get_current_layout() const { + XkbStateRec state; + XkbGetState(x11_display, XkbUseCoreKbd, &state); + return state.group; +} + +void DisplayServerX11::keyboard_set_current_layout(int p_index) { + ERR_FAIL_INDEX(p_index, keyboard_get_layout_count()); + XkbLockGroup(x11_display, XkbUseCoreKbd, p_index); +} + +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 != nullptr) { + _group_count = kbd->ctrls->num_groups; + } else { + while (_group_count < XkbNumKbdGroups && groups[_group_count] != None) { + _group_count++; + } + } + + Atom names = kbd->names->symbols; + if (names != None) { + Vector info = get_atom_name(x11_display, names).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) + ")."); + } + } + XkbFreeKeyboard(kbd, 0, true); + } + return ret.substr(0, 2); +} + +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 != nullptr) { + _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) { + ret = get_atom_name(x11_display, groups[p_index]); + } else { + ERR_PRINT("Index " + itos(p_index) + "is out of bounds (" + itos(_group_count) + ")."); + } + XkbFreeKeyboard(kbd, 0, true); + } + return ret; +} + +Key DisplayServerX11::keyboard_get_keycode_from_physical(Key p_keycode) const { + Key modifiers = p_keycode & KeyModifierMask::MODIFIER_MASK; + Key keycode_no_mod = p_keycode & KeyModifierMask::CODE_MASK; + unsigned int xkeycode = KeyMappingX11::get_xlibcode(keycode_no_mod); + KeySym xkeysym = XkbKeycodeToKeysym(x11_display, xkeycode, 0, 0); + if (is_ascii_lower_case(xkeysym)) { + xkeysym -= ('a' - 'A'); + } + + Key key = KeyMappingX11::get_keycode(xkeysym); + // If not found, fallback to QWERTY. + // This should match the behavior of the event pump + if (key == Key::NONE) { + return p_keycode; + } + return (Key)(key | modifiers); +} + +DisplayServerX11::Property DisplayServerX11::_read_property(Display *p_display, Window p_window, Atom p_property) { + Atom actual_type = None; + int actual_format = 0; + unsigned long nitems = 0; + unsigned long bytes_after = 0; + unsigned char *ret = nullptr; + + // Keep trying to read the property until there are no bytes unread. + if (p_property != None) { + int read_bytes = 1024; + 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); + + read_bytes *= 2; + + } while (bytes_after != 0); + } + + Property p = { ret, actual_format, (int)nitems, actual_type }; + + return p; +} + +static Atom pick_target_from_list(Display *p_display, const 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 && get_atom_name(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 && get_atom_name(p_disp, p_t1) == target_type) { + return p_t1; + } + + if (p_t2 != None && get_atom_name(p_disp, p_t2) == target_type) { + return p_t2; + } + + if (p_t3 != None && get_atom_name(p_disp, p_t3) == target_type) { + return p_t3; + } + + return None; +} + +void DisplayServerX11::_get_key_modifier_state(unsigned int p_x11_state, Ref state) { + state->set_shift_pressed((p_x11_state & ShiftMask)); + state->set_ctrl_pressed((p_x11_state & ControlMask)); + state->set_alt_pressed((p_x11_state & Mod1Mask /*|| p_x11_state&Mod5Mask*/)); //altgr should not count as alt + state->set_meta_pressed((p_x11_state & Mod4Mask)); +} + +MouseButton DisplayServerX11::_get_mouse_button_state(MouseButton p_x11_button, int p_x11_type) { + MouseButton mask = mouse_button_to_mask(p_x11_button); + + if (p_x11_type == ButtonPress) { + last_button_state |= mask; + } else { + last_button_state &= ~mask; + } + + return last_button_state; +} + +void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, LocalVector &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; + + // This code was pretty difficult to write. + // The docs stink and every toolkit seems to + // do it in a different way. + + /* Phase 1, obtain a proper keysym */ + + // This was also very difficult to figure out. + // You'd expect you could just use Keysym provided by + // XKeycodeToKeysym to obtain internationalized + // input.. WRONG!! + // you must use XLookupString (???) which not only wastes + // cycles generating an unnecessary string, but also + // still works in half the cases. (won't handle deadkeys) + // For more complex input methods (deadkeys and more advanced) + // you have to use XmbLookupString (??). + // So then you have to choose which of both results + // you want to keep. + // This is a real bizarreness and cpu waster. + + KeySym keysym_keycode = 0; // keysym used to find a keycode + KeySym keysym_unicode = 0; // keysym used to find unicode + + // XLookupString returns keysyms usable as nice keycodes. + char str[256 + 1]; + XKeyEvent xkeyevent_no_mod = *xkeyevent; + xkeyevent_no_mod.state &= ~ShiftMask; + xkeyevent_no_mod.state &= ~ControlMask; + XLookupString(xkeyevent, str, 256, &keysym_unicode, nullptr); + XLookupString(&xkeyevent_no_mod, nullptr, 0, &keysym_keycode, nullptr); + + // Meanwhile, XLookupString returns keysyms useful for unicode. + + if (!xmbstring) { + // keep a temporary buffer for the string + xmbstring = (char *)memalloc(sizeof(char) * 8); + xmblen = 8; + } + + if (xkeyevent->type == KeyPress && wd.xic) { + Status status; +#ifdef X_HAVE_UTF8_STRING + int utf8len = 8; + char *utf8string = (char *)memalloc(sizeof(char) * utf8len); + int utf8bytes = Xutf8LookupString(wd.xic, xkeyevent, utf8string, + utf8len - 1, &keysym_unicode, &status); + if (status == XBufferOverflow) { + utf8len = utf8bytes + 1; + utf8string = (char *)memrealloc(utf8string, utf8len); + utf8bytes = Xutf8LookupString(wd.xic, xkeyevent, utf8string, + utf8len - 1, &keysym_unicode, &status); + } + utf8string[utf8bytes] = '\0'; + + if (status == XLookupChars) { + bool keypress = xkeyevent->type == KeyPress; + Key keycode = KeyMappingX11::get_keycode(keysym_keycode); + Key physical_keycode = KeyMappingX11::get_scancode(xkeyevent->keycode); + + if (keycode >= Key::A + 32 && keycode <= Key::Z + 32) { + keycode -= 'a' - 'A'; + } + + String tmp; + tmp.parse_utf8(utf8string, utf8bytes); + for (int i = 0; i < tmp.length(); i++) { + Ref k; + k.instantiate(); + if (physical_keycode == Key::NONE && keycode == Key::NONE && tmp[i] == 0) { + continue; + } + + if (keycode == Key::NONE) { + keycode = (Key)physical_keycode; + } + + _get_key_modifier_state(xkeyevent->state, k); + + k->set_window_id(p_window); + k->set_unicode(tmp[i]); + + k->set_pressed(keypress); + + k->set_keycode(keycode); + + k->set_physical_keycode((Key)physical_keycode); + + k->set_echo(false); + + if (k->get_keycode() == Key::BACKTAB) { + //make it consistent across platforms. + k->set_keycode(Key::TAB); + k->set_physical_keycode(Key::TAB); + k->set_shift_pressed(true); + } + + Input::get_singleton()->parse_input_event(k); + } + memfree(utf8string); + return; + } + memfree(utf8string); +#else + do { + int mnbytes = XmbLookupString(xic, xkeyevent, xmbstring, xmblen - 1, &keysym_unicode, &status); + xmbstring[mnbytes] = '\0'; + + if (status == XBufferOverflow) { + xmblen = mnbytes + 1; + xmbstring = (char *)memrealloc(xmbstring, xmblen); + } + } while (status == XBufferOverflow); +#endif + } + + /* Phase 2, obtain a Godot keycode from the keysym */ + + // KeyMappingX11 just translated the X11 keysym to a PIGUI + // keysym, so it works in all platforms the same. + + Key keycode = KeyMappingX11::get_keycode(keysym_keycode); + Key physical_keycode = KeyMappingX11::get_scancode(xkeyevent->keycode); + + /* Phase 3, obtain a unicode character from the keysym */ + + // KeyMappingX11 also translates keysym to unicode. + // It does a binary search on a table to translate + // most properly. + unsigned int unicode = keysym_unicode > 0 ? KeyMappingX11::get_unicode_from_keysym(keysym_unicode) : 0; + + /* Phase 4, determine if event must be filtered */ + + // This seems to be a side-effect of using XIM. + // 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. + // Guess it was a design problem of the extension + + bool keypress = xkeyevent->type == KeyPress; + + if (physical_keycode == Key::NONE && keycode == Key::NONE && unicode == 0) { + return; + } + + if (keycode == Key::NONE) { + keycode = (Key)physical_keycode; + } + + /* Phase 5, determine modifier mask */ + + // No problems here, except I had no way to + // know Mod1 was ALT and Mod4 was META (applekey/winkey) + // just tried Mods until i found them. + + //print_verbose("mod1: "+itos(xkeyevent->state&Mod1Mask)+" mod 5: "+itos(xkeyevent->state&Mod5Mask)); + + Ref k; + k.instantiate(); + k->set_window_id(p_window); + + _get_key_modifier_state(xkeyevent->state, k); + + /* Phase 6, determine echo character */ + + // Echo characters in X11 are a keyrelease and a keypress + // one after the other with the (almot) same timestamp. + // 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 (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 + // jitter. I'm still not convinced that all this approach + // is correct, but the xorg developers are + // not very helpful today. + +#define ABSDIFF(x, y) (((x) < (y)) ? ((y) - (x)) : ((x) - (y))) + ::Time threshold = ABSDIFF(peek_event.xkey.time, xkeyevent->time); +#undef ABSDIFF + if (peek_event.type == KeyPress && threshold < 5) { + KeySym rk; + XLookupString((XKeyEvent *)&peek_event, str, 256, &rk, nullptr); + if (rk == keysym_keycode) { + // 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 + } + } + + // use the time from peek_event so it always works + } + + // save the time to check for echo when keypress happens + } + + /* Phase 7, send event to Window */ + + k->set_pressed(keypress); + + if (keycode >= Key::A + 32 && keycode <= Key::Z + 32) { + keycode -= int('a' - 'A'); + } + + k->set_keycode(keycode); + k->set_physical_keycode((Key)physical_keycode); + k->set_unicode(unicode); + k->set_echo(p_echo); + + if (k->get_keycode() == Key::BACKTAB) { + //make it consistent across platforms. + k->set_keycode(Key::TAB); + k->set_physical_keycode(Key::TAB); + k->set_shift_pressed(true); + } + + //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) { + k->set_shift_pressed(false); + } else if (k->get_keycode() == Key::CTRL) { + k->set_ctrl_pressed(false); + } else if (k->get_keycode() == Key::ALT) { + k->set_alt_pressed(false); + } else if (k->get_keycode() == Key::META) { + k->set_meta_pressed(false); + } + } + + bool last_is_pressed = Input::get_singleton()->is_key_pressed(k->get_keycode()); + if (k->is_pressed()) { + if (last_is_pressed) { + k->set_echo(true); + } + } + + Input::get_singleton()->parse_input_event(k); +} + +Atom DisplayServerX11::_process_selection_request_target(Atom p_target, Window p_requestor, Atom p_property, Atom p_selection) 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; + static const char *target_type = "PRIMARY"; + if (p_selection != None && get_atom_name(x11_display, p_selection) == target_type) { + clip = internal_clipboard_primary.utf8(); + } else { + 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, p_event->selection); + } + + 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, p_event->selection); + } + + 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(client_data); + ds->xim = nullptr; + + for (KeyValue &E : ds->windows) { + E.value.xic = nullptr; + } +} + +void DisplayServerX11::_window_changed(XEvent *event) { + WindowID window_id = MAIN_WINDOW_ID; + + // Assign the event to the relevant window + for (const KeyValue &E : windows) { + if (event->xany.window == E.value.x11_window) { + window_id = E.key; + break; + } + } + + Rect2i new_rect; + + WindowData &wd = windows[window_id]; + if (wd.x11_window != event->xany.window) { // Check if the correct window, in case it was not main window or anything else + return; + } + + // Query display server about a possible new window state. + wd.fullscreen = _window_fullscreen_check(window_id); + wd.minimized = _window_minimize_check(window_id); + wd.maximized = _window_maximize_check(window_id, "_NET_WM_STATE"); + + { + //the position in xconfigure is not useful here, obtain it manually + int x = 0, y = 0; + Window child; + XTranslateCoordinates(x11_display, wd.x11_window, DefaultRootWindow(x11_display), 0, 0, &x, &y, &child); + new_rect.position.x = x; + new_rect.position.y = y; + + new_rect.size.width = event->xconfigure.width; + new_rect.size.height = event->xconfigure.height; + } + + if (new_rect == Rect2i(wd.position, wd.size)) { + return; + } + if (wd.xic) { + // Not portable. + window_set_ime_position(Point2(0, 1)); + } + + wd.position = new_rect.position; + wd.size = new_rect.size; + +#if defined(VULKAN_ENABLED) + if (context_vulkan) { + context_vulkan->window_resize(window_id, wd.size.width, wd.size.height); + } +#endif +#if defined(GLES3_ENABLED) + if (gl_manager) { + gl_manager->window_resize(window_id, wd.size.width, wd.size.height); + } +#endif + + if (!wd.rect_changed_callback.is_null()) { + Rect2i r = new_rect; + + Variant rect = r; + + Variant *rectp = ▭ + Variant ret; + Callable::CallError ce; + wd.rect_changed_callback.callp((const Variant **)&rectp, 1, ret, ce); + } +} + +DisplayServer::WindowID DisplayServerX11::_get_focused_window_or_popup() const { + const List::Element *E = popup_list.back(); + if (E) { + return E->get(); + } + + return last_focused_window; +} + +void DisplayServerX11::_dispatch_input_events(const Ref &p_event) { + static_cast(get_singleton())->_dispatch_input_event(p_event); +} + +void DisplayServerX11::_dispatch_input_event(const Ref &p_event) { + Variant ev = p_event; + Variant *evp = &ev; + Variant ret; + Callable::CallError ce; + + { + List::Element *E = popup_list.back(); + if (E && Object::cast_to(*p_event)) { + // Redirect keyboard input to active popup. + if (windows.has(E->get())) { + Callable callable = windows[E->get()].input_event_callback; + if (callable.is_valid()) { + callable.callp((const Variant **)&evp, 1, ret, ce); + } + } + return; + } + } + + Ref event_from_window = p_event; + if (event_from_window.is_valid() && event_from_window->get_window_id() != INVALID_WINDOW_ID) { + // Send to a single window. + if (windows.has(event_from_window->get_window_id())) { + Callable callable = windows[event_from_window->get_window_id()].input_event_callback; + if (callable.is_valid()) { + callable.callp((const Variant **)&evp, 1, ret, ce); + } + } + } else { + // Send to all windows. + for (KeyValue &E : windows) { + Callable callable = E.value.input_event_callback; + if (callable.is_valid()) { + callable.callp((const Variant **)&evp, 1, ret, ce); + } + } + } +} + +void DisplayServerX11::_send_window_event(const WindowData &wd, WindowEvent p_event) { + if (!wd.event_callback.is_null()) { + Variant event = int(p_event); + Variant *eventp = &event; + Variant ret; + Callable::CallError ce; + wd.event_callback.callp((const Variant **)&eventp, 1, ret, ce); + } +} + +void DisplayServerX11::_poll_events_thread(void *ud) { + DisplayServerX11 *display_server = static_cast(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, nullptr, nullptr, &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.is_set()) { + _wait_for_events(); + + // Process events from the queue. + { + MutexLock mutex_lock(events_mutex); + + _check_pending_events(polled_events); + } + } +} + +void DisplayServerX11::_check_pending_events(LocalVector &r_events) { + // Flush to make sure to gather all pending events. + XFlush(x11_display); + + // 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; + } + + r_events.push_back(ev); + } +} + +DisplayServer::WindowID DisplayServerX11::window_get_active_popup() const { + const List::Element *E = popup_list.back(); + if (E) { + return E->get(); + } else { + return INVALID_WINDOW_ID; + } +} + +void DisplayServerX11::window_set_popup_safe_rect(WindowID p_window, const Rect2i &p_rect) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; + wd.parent_safe_rect = p_rect; +} + +Rect2i DisplayServerX11::window_get_popup_safe_rect(WindowID p_window) const { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND_V(!windows.has(p_window), Rect2i()); + const WindowData &wd = windows[p_window]; + return wd.parent_safe_rect; +} + +void DisplayServerX11::popup_open(WindowID p_window) { + _THREAD_SAFE_METHOD_ + + WindowData &wd = windows[p_window]; + if (wd.is_popup) { + // Find current popup parent, or root popup if new window is not transient. + List::Element *C = nullptr; + List::Element *E = popup_list.back(); + while (E) { + if (wd.transient_parent != E->get() || wd.transient_parent == INVALID_WINDOW_ID) { + C = E; + E = E->prev(); + } else { + break; + } + } + if (C) { + _send_window_event(windows[C->get()], DisplayServerX11::WINDOW_EVENT_CLOSE_REQUEST); + } + + time_since_popup = OS::get_singleton()->get_ticks_msec(); + popup_list.push_back(p_window); + } +} + +void DisplayServerX11::popup_close(WindowID p_window) { + _THREAD_SAFE_METHOD_ + + List::Element *E = popup_list.find(p_window); + while (E) { + List::Element *F = E->next(); + WindowID win_id = E->get(); + popup_list.erase(E); + + _send_window_event(windows[win_id], DisplayServerX11::WINDOW_EVENT_CLOSE_REQUEST); + E = F; + } +} + +bool DisplayServerX11::mouse_process_popups() { + _THREAD_SAFE_METHOD_ + + if (popup_list.is_empty()) { + return false; + } + + uint64_t delta = OS::get_singleton()->get_ticks_msec() - time_since_popup; + if (delta < 250) { + return false; + } + + int number_of_screens = XScreenCount(x11_display); + bool closed = false; + for (int i = 0; i < number_of_screens; i++) { + Window root, child; + int root_x, root_y, win_x, win_y; + unsigned int mask; + if (XQueryPointer(x11_display, XRootWindow(x11_display, i), &root, &child, &root_x, &root_y, &win_x, &win_y, &mask)) { + XWindowAttributes root_attrs; + XGetWindowAttributes(x11_display, root, &root_attrs); + Vector2i pos = Vector2i(root_attrs.x + root_x, root_attrs.y + root_y); + if ((pos != last_mouse_monitor_pos) || (mask != last_mouse_monitor_mask)) { + if (((mask & Button1Mask) || (mask & Button2Mask) || (mask & Button3Mask) || (mask & Button4Mask) || (mask & Button5Mask))) { + List::Element *C = nullptr; + List::Element *E = popup_list.back(); + // Find top popup to close. + while (E) { + // Popup window area. + Rect2i win_rect = Rect2i(window_get_position(E->get()), window_get_size(E->get())); + // Area of the parent window, which responsible for opening sub-menu. + Rect2i safe_rect = window_get_popup_safe_rect(E->get()); + if (win_rect.has_point(pos)) { + break; + } else if (safe_rect != Rect2i() && safe_rect.has_point(pos)) { + break; + } else { + C = E; + E = E->prev(); + } + } + if (C) { + _send_window_event(windows[C->get()], DisplayServerX11::WINDOW_EVENT_CLOSE_REQUEST); + closed = true; + } + } + } + last_mouse_monitor_mask = mask; + last_mouse_monitor_pos = pos; + } + } + return closed; +} + +void DisplayServerX11::process_events() { + _THREAD_SAFE_METHOD_ + +#ifdef DISPLAY_SERVER_X11_DEBUG_LOGS_ENABLED + static int frame = 0; + ++frame; +#endif + + bool ignore_events = mouse_process_popups(); + + if (app_focused) { + //verify that one of the windows has focus, else send focus out notification + bool focus_found = false; + for (const KeyValue &E : windows) { + if (E.value.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. + bool mouse_mode_grab = mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN; + + xi.pressure = 0; + xi.tilt = Vector2(); + xi.pressure_supported = false; + + LocalVector events; + { + // Block events polling while flushing events. + MutexLock mutex_lock(events_mutex); + events = polled_events; + polled_events.clear(); + + // Check for more pending events to avoid an extra frame delay. + _check_pending_events(events); + } + + for (uint32_t event_index = 0; event_index < events.size(); ++event_index) { + XEvent &event = events[event_index]; + if (ignore_events) { + XFreeEventData(x11_display, &event.xcookie); + continue; + } + + WindowID window_id = MAIN_WINDOW_ID; + + // Assign the event to the relevant window + for (const KeyValue &E : windows) { + if (event.xany.window == E.value.x11_window) { + window_id = E.key; + break; + } + } + + 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); + + switch (event_data->evtype) { + case XI_HierarchyChanged: + case XI_DeviceChanged: { + _refresh_device_info(); + } break; + case XI_RawMotion: { + XIRawEvent *raw_event = (XIRawEvent *)event_data; + int device_id = raw_event->sourceid; + + // Determine the axis used (called valuators in XInput for some forsaken reason) + // Mask is a bitmask indicating which axes are involved. + // We are interested in the values of axes 0 and 1. + if (raw_event->valuators.mask_len <= 0) { + break; + } + + const double *values = raw_event->raw_values; + + double rel_x = 0.0; + double rel_y = 0.0; + + if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_ABSX)) { + rel_x = *values; + values++; + } + + if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_ABSY)) { + rel_y = *values; + values++; + } + + if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_PRESSURE)) { + HashMap::Iterator pen_pressure = xi.pen_pressure_range.find(device_id); + if (pen_pressure) { + Vector2 pen_pressure_range = pen_pressure->value; + if (pen_pressure_range != Vector2()) { + xi.pressure_supported = true; + xi.pressure = (*values - pen_pressure_range[0]) / + (pen_pressure_range[1] - pen_pressure_range[0]); + } + } + + values++; + } + + if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_TILTX)) { + HashMap::Iterator pen_tilt_x = xi.pen_tilt_x_range.find(device_id); + if (pen_tilt_x) { + Vector2 pen_tilt_x_range = pen_tilt_x->value; + if (pen_tilt_x_range[0] != 0 && *values < 0) { + xi.tilt.x = *values / -pen_tilt_x_range[0]; + } else if (pen_tilt_x_range[1] != 0) { + xi.tilt.x = *values / pen_tilt_x_range[1]; + } + } + + values++; + } + + if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_TILTY)) { + HashMap::Iterator pen_tilt_y = xi.pen_tilt_y_range.find(device_id); + if (pen_tilt_y) { + Vector2 pen_tilt_y_range = pen_tilt_y->value; + if (pen_tilt_y_range[0] != 0 && *values < 0) { + xi.tilt.y = *values / -pen_tilt_y_range[0]; + } else if (pen_tilt_y_range[1] != 0) { + xi.tilt.y = *values / pen_tilt_y_range[1]; + } + } + + values++; + } + + HashMap::Iterator pen_inverted = xi.pen_inverted_devices.find(device_id); + if (pen_inverted) { + xi.pen_inverted = pen_inverted->value; + } + + // https://bugs.freedesktop.org/show_bug.cgi?id=71609 + // http://lists.libsdl.org/pipermail/commits-libsdl.org/2015-June/000282.html + if (raw_event->time == xi.last_relative_time && rel_x == xi.relative_motion.x && rel_y == xi.relative_motion.y) { + break; // Flush duplicate to avoid overly fast motion + } + + xi.old_raw_pos.x = xi.raw_pos.x; + xi.old_raw_pos.y = xi.raw_pos.y; + xi.raw_pos.x = rel_x; + xi.raw_pos.y = rel_y; + + HashMap::Iterator abs_info = xi.absolute_devices.find(device_id); + + if (abs_info) { + // Absolute mode device + Vector2 mult = abs_info->value; + + xi.relative_motion.x += (xi.raw_pos.x - xi.old_raw_pos.x) * mult.x; + xi.relative_motion.y += (xi.raw_pos.y - xi.old_raw_pos.y) * mult.y; + } else { + // Relative mode device + xi.relative_motion.x = xi.raw_pos.x; + xi.relative_motion.y = xi.raw_pos.y; + } + + xi.last_relative_time = raw_event->time; + } break; +#ifdef TOUCH_ENABLED + case XI_TouchBegin: + case XI_TouchEnd: { + bool is_begin = event_data->evtype == XI_TouchBegin; + + Ref st; + st.instantiate(); + st->set_window_id(window_id); + st->set_index(index); + st->set_position(pos); + st->set_pressed(is_begin); + + if (is_begin) { + 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 + // in a spurious mouse motion event being sent to Godot; remember it to be able to filter it out + xi.mouse_pos_to_filter = pos; + } + Input::get_singleton()->parse_input_event(st); + } else { + if (!xi.state.has(index)) { // Defensive + break; + } + xi.state.erase(index); + Input::get_singleton()->parse_input_event(st); + } + } break; + + case XI_TouchUpdate: { + HashMap::Iterator curr_pos_elem = xi.state.find(index); + if (!curr_pos_elem) { // Defensive + break; + } + + if (curr_pos_elem->value != pos) { + Ref sd; + sd.instantiate(); + sd->set_window_id(window_id); + sd->set_index(index); + sd->set_position(pos); + sd->set_relative(pos - curr_pos_elem->value); + Input::get_singleton()->parse_input_event(sd); + + curr_pos_elem->value = pos; + } + } break; +#endif + } + } + } + XFreeEventData(x11_display, &event.xcookie); + + switch (event.type) { + case MapNotify: { + DEBUG_LOG_X11("[%u] MapNotify window=%lu (%u) \n", frame, event.xmap.window, window_id); + + const WindowData &wd = windows[window_id]; + + XWindowAttributes xwa; + XSync(x11_display, False); + XGetWindowAttributes(x11_display, wd.x11_window, &xwa); + + // 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 ((xwa.map_state == IsViewable) && !wd.no_focus && !wd.is_popup) { + XSetInputFocus(x11_display, wd.x11_window, RevertToPointerRoot, CurrentTime); + } + + // Have we failed to set fullscreen while the window was unmapped? + _validate_mode_on_map(window_id); + } break; + + case Expose: { + DEBUG_LOG_X11("[%u] Expose window=%lu (%u), count='%u' \n", frame, event.xexpose.window, window_id, event.xexpose.count); + + windows[window_id].fullscreen = _window_fullscreen_check(window_id); + + Main::force_redraw(); + } 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); + + windows[window_id].minimized = _window_minimize_check(window_id); + } 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: { + 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]; + last_focused_window = 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 (const KeyValue &E : windows) { + if (mouse_mode == MOUSE_MODE_CONFINED) { + XUndefineCursor(x11_display, E.value.x11_window); + } else if (mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN) { // Or re-hide it. + XDefineCursor(x11_display, E.value.x11_window, null_cursor); + } + + XGrabPointer( + x11_display, E.value.x11_window, True, + ButtonPressMask | ButtonReleaseMask | PointerMotionMask, + GrabModeAsync, GrabModeAsync, E.value.x11_window, None, CurrentTime); + } + } +#ifdef TOUCH_ENABLED + // Grab touch devices to avoid OS gesture interference + /*for (int i = 0; i < xi.touch_devices.size(); ++i) { + XIGrabDevice(x11_display, xi.touch_devices[i], x11_window, CurrentTime, None, XIGrabModeAsync, XIGrabModeAsync, False, &xi.touch_event_mask); + }*/ +#endif + + 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); + } + + Input::get_singleton()->release_pressed_events(); + _send_window_event(wd, WINDOW_EVENT_FOCUS_OUT); + + if (mouse_mode_grab) { + for (const KeyValue &E : windows) { + //dear X11, I try, I really try, but you never work, you do whatever you want. + if (mouse_mode == MOUSE_MODE_CAPTURED) { + // Show the cursor if we're in captured mode so it doesn't look weird. + XUndefineCursor(x11_display, E.value.x11_window); + } + } + XUngrabPointer(x11_display, CurrentTime); + } +#ifdef TOUCH_ENABLED + // Ungrab touch devices so input works as usual while we are unfocused + /*for (int i = 0; i < xi.touch_devices.size(); ++i) { + XIUngrabDevice(x11_display, xi.touch_devices[i], CurrentTime); + }*/ + + // Release every pointer to avoid sticky points + for (const KeyValue &E : xi.state) { + Ref st; + st.instantiate(); + st->set_index(E.key); + st->set_window_id(window_id); + st->set_position(E.value); + Input::get_singleton()->parse_input_event(st); + } + xi.state.clear(); +#endif + } 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]; + + XWindowAttributes xwa; + XSync(x11_display, False); + XGetWindowAttributes(x11_display, wd.x11_window, &xwa); + + // 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 ((xwa.map_state == IsViewable) && !wd.no_focus && !wd.is_popup) { + XSetInputFocus(x11_display, wd.x11_window, RevertToPointerRoot, CurrentTime); + } + + _window_changed(&event); + } break; + + case ButtonPress: + case ButtonRelease: { + /* exit in case of a mouse button press */ + last_timestamp = event.xbutton.time; + if (mouse_mode == MOUSE_MODE_CAPTURED) { + event.xbutton.x = last_mouse_pos.x; + event.xbutton.y = last_mouse_pos.y; + } + + Ref mb; + mb.instantiate(); + + mb->set_window_id(window_id); + _get_key_modifier_state(event.xbutton.state, mb); + mb->set_button_index((MouseButton)event.xbutton.button); + if (mb->get_button_index() == MouseButton::RIGHT) { + mb->set_button_index(MouseButton::MIDDLE); + } else if (mb->get_button_index() == MouseButton::MIDDLE) { + mb->set_button_index(MouseButton::RIGHT); + } + 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 && !wd.is_popup) { + 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 = MouseButton::NONE; + mb->set_double_click(true); + } + + } else if (mb->get_button_index() < MouseButton::WHEEL_UP || mb->get_button_index() > MouseButton::WHEEL_RIGHT) { + last_click_button_index = mb->get_button_index(); + } + + if (!mb->is_double_click()) { + 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 (const KeyValue &E : windows) { + const WindowData &wd_other = E.value; + 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()->parse_input_event(mb); + } + break; + } + } + } + } + + Input::get_singleton()->parse_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. + WindowID focused_window_id = _get_focused_window_or_popup(); + if (!windows.has(focused_window_id)) { + focused_window_id = MAIN_WINDOW_ID; + } + + while (true) { + if (mouse_mode == MOUSE_MODE_CAPTURED && event.xmotion.x == windows[focused_window_id].size.width / 2 && event.xmotion.y == windows[focused_window_id].size.height / 2) { + //this is likely the warp event since it was warped here + center = Vector2(event.xmotion.x, event.xmotion.y); + break; + } + + 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; + } + } else { + break; + } + } + + last_timestamp = event.xmotion.time; + + // Motion is also simple. + // A little hack is in order + // to be able to send relative motion events. + Point2i pos(event.xmotion.x, event.xmotion.y); + + // Avoidance of spurious mouse motion (see handling of touch) + bool filter = false; + // Adding some tolerance to match better Point2i to Vector2 + if (xi.state.size() && Vector2(pos).distance_squared_to(xi.mouse_pos_to_filter) < 2) { + filter = true; + } + // Invalidate to avoid filtering a possible legitimate similar event coming later + xi.mouse_pos_to_filter = Vector2(1e10, 1e10); + if (filter) { + 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; + } + + Point2i new_center = pos; + pos = last_mouse_pos + xi.relative_motion; + center = new_center; + 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; + } + + // Hackish but relative mouse motion is already handled in the RawMotion event. + // RawMotion does not provide the absolute mouse position (whereas MotionNotify does). + // Therefore, RawMotion cannot be the authority on absolute mouse position. + // RawMotion provides more precision than MotionNotify, which doesn't sense subpixel motion. + // Therefore, MotionNotify cannot be the authority on relative mouse motion. + // This means we need to take a combined approach... + Point2i rel; + + // Only use raw input if in capture mode. Otherwise use the classic behavior. + if (mouse_mode == MOUSE_MODE_CAPTURED) { + rel = xi.relative_motion; + } else { + rel = pos - last_mouse_pos; + } + + // Reset to prevent lingering motion + xi.relative_motion.x = 0; + xi.relative_motion.y = 0; + if (mouse_mode == MOUSE_MODE_CAPTURED) { + pos = Point2i(windows[focused_window_id].size.width / 2, windows[focused_window_id].size.height / 2); + } + + Ref mm; + mm.instantiate(); + + mm->set_window_id(window_id); + if (xi.pressure_supported) { + mm->set_pressure(xi.pressure); + } else { + mm->set_pressure(bool(mouse_get_button_state() & MouseButton::MASK_LEFT) ? 1.0f : 0.0f); + } + mm->set_tilt(xi.tilt); + mm->set_pen_inverted(xi.pen_inverted); + + _get_key_modifier_state(event.xmotion.state, mm); + mm->set_button_mask((MouseButton)mouse_get_button_state()); + mm->set_position(pos); + mm->set_global_position(pos); + mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity()); + + mm->set_relative(rel); + + last_mouse_pos = pos; + + // printf("rel: %d,%d\n", rel.x, rel.y ); + // 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 (focused) { + Input::get_singleton()->parse_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 (const KeyValue &E : windows) { + const WindowData &wd_other = E.value; + 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_velocity(Input::get_singleton()->get_last_mouse_velocity()); + Input::get_singleton()->parse_input_event(mm); + + break; + } + } + } + + } break; + case KeyPress: + case KeyRelease: { +#ifdef DISPLAY_SERVER_X11_DEBUG_LOGS_ENABLED + if (event.type == KeyPress) { + DEBUG_LOG_X11("[%u] KeyPress window=%lu (%u), keycode=%u, time=%lu \n", frame, event.xkey.window, window_id, event.xkey.keycode, event.xkey.time); + } else { + DEBUG_LOG_X11("[%u] KeyRelease window=%lu (%u), keycode=%u, time=%lu \n", frame, event.xkey.window, window_id, event.xkey.keycode, event.xkey.time); + } +#endif + 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, &event.xkey, 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 files = String((char *)p.data).split("\n", false); + XFree(p.data); + for (int i = 0; i < files.size(); i++) { + files.write[i] = files[i].replace("file://", "").uri_decode().strip_edges(); + } + + if (!windows[window_id].drop_files_callback.is_null()) { + Variant v = files; + Variant *vp = &v; + Variant ret; + Callable::CallError ce; + windows[window_id].drop_files_callback.callp((const Variant **)&vp, 1, ret, ce); + } + + //Reply that all is well. + XClientMessageEvent m; + memset(&m, 0, sizeof(m)); + m.type = ClientMessage; + m.display = x11_display; + m.window = xdnd_source_window; + m.message_type = xdnd_finished; + m.format = 32; + m.data.l[0] = windows[window_id].x11_window; + m.data.l[1] = 1; + m.data.l[2] = xdnd_action_copy; //We only ever copy. + + XSendEvent(x11_display, xdnd_source_window, False, NoEventMask, (XEvent *)&m); + } + break; + + case ClientMessage: + + if ((unsigned int)event.xclient.data.l[0] == (unsigned int)wm_delete) { + _send_window_event(windows[window_id], WINDOW_EVENT_CLOSE_REQUEST); + } + + else if ((unsigned int)event.xclient.message_type == (unsigned int)xdnd_enter) { + //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]; + bool more_than_3 = event.xclient.data.l[1] & 1; + if (more_than_3) { + Property p = _read_property(x11_display, source, XInternAtom(x11_display, "XdndTypeList", False)); + requested = pick_target_from_list(x11_display, (Atom *)p.data, p.nitems); + XFree(p.data); + } 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; + memset(&m, 0, sizeof(m)); + m.type = ClientMessage; + m.display = event.xclient.display; + m.window = event.xclient.data.l[0]; + m.message_type = xdnd_status; + m.format = 32; + m.data.l[0] = windows[window_id].x11_window; + m.data.l[1] = (requested != None); + m.data.l[2] = 0; //empty rectangle + m.data.l[3] = 0; + m.data.l[4] = xdnd_action_copy; + + 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) { + XConvertSelection(x11_display, xdnd_selection, requested, XInternAtom(x11_display, "PRIMARY", 0), windows[window_id].x11_window, event.xclient.data.l[2]); + } else { + XConvertSelection(x11_display, xdnd_selection, requested, XInternAtom(x11_display, "PRIMARY", 0), windows[window_id].x11_window, CurrentTime); + } + } else { + //Reply that we're not interested. + XClientMessageEvent m; + memset(&m, 0, sizeof(m)); + m.type = ClientMessage; + m.display = event.xclient.display; + m.window = event.xclient.data.l[0]; + m.message_type = xdnd_finished; + m.format = 32; + m.data.l[0] = windows[window_id].x11_window; + m.data.l[1] = 0; + m.data.l[2] = None; //Failed. + XSendEvent(x11_display, event.xclient.data.l[0], False, NoEventMask, (XEvent *)&m); + } + } + break; + default: + break; + } + } + + 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); + + /* + Window root, child; + int root_x, root_y; + int win_x, win_y; + unsigned int mask; + XQueryPointer( x11_display, x11_window, &root, &child, &root_x, &root_y, &win_x, &win_y, &mask ); + + printf("Root: %d,%d\n", root_x, root_y); + printf("Win: %d,%d\n", win_x, win_y); + */ + } + + Input::get_singleton()->flush_buffered_events(); +} + +void DisplayServerX11::release_rendering_thread() { +#if defined(GLES3_ENABLED) + if (gl_manager) { + gl_manager->release_current(); + } +#endif +} + +void DisplayServerX11::make_rendering_thread() { +#if defined(GLES3_ENABLED) + if (gl_manager) { + gl_manager->make_current(); + } +#endif +} + +void DisplayServerX11::swap_buffers() { +#if defined(GLES3_ENABLED) + if (gl_manager) { + gl_manager->swap_buffers(); + } +#endif +} + +void DisplayServerX11::_update_context(WindowData &wd) { + XClassHint *classHint = XAllocClassHint(); + + if (classHint) { + CharString name_str; + switch (context) { + case CONTEXT_EDITOR: + name_str = "Godot_Editor"; + break; + case CONTEXT_PROJECTMAN: + name_str = "Godot_ProjectList"; + break; + case CONTEXT_ENGINE: + name_str = "Godot_Engine"; + break; + } + + CharString class_str; + if (context == CONTEXT_ENGINE) { + String config_name = GLOBAL_GET("application/config/name"); + if (config_name.length() == 0) { + class_str = "Godot_Engine"; + } else { + class_str = config_name.utf8(); + } + } else { + class_str = "Godot"; + } + + classHint->res_class = class_str.ptrw(); + classHint->res_name = name_str.ptrw(); + + XSetClassHint(x11_display, wd.x11_window, classHint); + XFree(classHint); + } +} + +void DisplayServerX11::set_context(Context p_context) { + _THREAD_SAFE_METHOD_ + + context = p_context; + + for (KeyValue &E : windows) { + _update_context(E.value); + } +} + +void DisplayServerX11::set_native_icon(const String &p_filename) { + WARN_PRINT("Native icon not supported by this display server."); +} + +bool g_set_icon_error = false; +int set_icon_errorhandler(Display *dpy, XErrorEvent *ev) { + g_set_icon_error = true; + return 0; +} + +void DisplayServerX11::set_icon(const Ref &p_icon) { + _THREAD_SAFE_METHOD_ + + WindowData &wd = windows[MAIN_WINDOW_ID]; + + int (*oldHandler)(Display *, XErrorEvent *) = XSetErrorHandler(&set_icon_errorhandler); + + Atom net_wm_icon = XInternAtom(x11_display, "_NET_WM_ICON", False); + + if (p_icon.is_valid()) { + Ref img = p_icon->duplicate(); + img->convert(Image::FORMAT_RGBA8); + + while (true) { + int w = img->get_width(); + int h = img->get_height(); + + if (g_set_icon_error) { + g_set_icon_error = false; + + WARN_PRINT("Icon too large, attempting to resize icon."); + + int new_width, new_height; + if (w > h) { + new_width = w / 2; + new_height = h * new_width / w; + } else { + new_height = h / 2; + new_width = w * new_height / h; + } + + w = new_width; + h = new_height; + + if (!w || !h) { + WARN_PRINT("Unable to set icon."); + break; + } + + img->resize(w, h, Image::INTERPOLATE_CUBIC); + } + + // We're using long to have wordsize (32Bit build -> 32 Bits, 64 Bit build -> 64 Bits + Vector pd; + + pd.resize(2 + w * h); + + pd.write[0] = w; + pd.write[1] = h; + + const uint8_t *r = img->get_data().ptr(); + + long *wr = &pd.write[2]; + uint8_t const *pr = r; + + for (int i = 0; i < w * h; i++) { + long v = 0; + // A R G B + v |= pr[3] << 24 | pr[0] << 16 | pr[1] << 8 | pr[2]; + *wr++ = v; + pr += 4; + } + + 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) { + break; + } + } + } else { + XDeleteProperty(x11_display, wd.x11_window, net_wm_icon); + } + + XFlush(x11_display); + XSetErrorHandler(oldHandler); +} + +void DisplayServerX11::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window) { + _THREAD_SAFE_METHOD_ +#if defined(VULKAN_ENABLED) + if (context_vulkan) { + context_vulkan->set_vsync_mode(p_window, p_vsync_mode); + } +#endif + +#if defined(GLES3_ENABLED) + if (gl_manager) { + gl_manager->set_use_vsync(p_vsync_mode == DisplayServer::VSYNC_ENABLED); + } +#endif +} + +DisplayServer::VSyncMode DisplayServerX11::window_get_vsync_mode(WindowID p_window) const { + _THREAD_SAFE_METHOD_ +#if defined(VULKAN_ENABLED) + if (context_vulkan) { + return context_vulkan->get_vsync_mode(p_window); + } +#endif +#if defined(GLES3_ENABLED) + if (gl_manager) { + return gl_manager->is_using_vsync() ? DisplayServer::VSYNC_ENABLED : DisplayServer::VSYNC_DISABLED; + } +#endif + return DisplayServer::VSYNC_ENABLED; +} + +Vector DisplayServerX11::get_rendering_drivers_func() { + Vector drivers; + +#ifdef VULKAN_ENABLED + drivers.push_back("vulkan"); +#endif +#ifdef GLES3_ENABLED + drivers.push_back("opengl3"); +#endif + + return drivers; +} + +DisplayServer *DisplayServerX11::create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, Error &r_error) { + DisplayServer *ds = memnew(DisplayServerX11(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_position, p_resolution, r_error)); + if (r_error != OK) { + if (p_rendering_driver == "vulkan") { + String executable_name = OS::get_singleton()->get_executable_path().get_file(); + OS::get_singleton()->alert("Your video card driver does not support the selected Vulkan version.\n" + "Please try updating your GPU driver or try using the OpenGL 3 driver.\n" + "You can enable the OpenGL 3 driver by starting the engine from the\n" + "command line with the command:\n'./" + + executable_name + " --rendering-driver opengl3'.\n " + "If you have updated your graphics drivers recently, try rebooting.", + "Unable to initialize Video driver"); + } else { + OS::get_singleton()->alert("Your video card driver does not support the selected OpenGL version.\n" + "Please try updating your GPU driver.\n" + "If you have updated your graphics drivers recently, try rebooting.", + "Unable to initialize Video driver"); + } + } + return ds; +} + +DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect) { + //Create window + + XVisualInfo visualInfo; + bool vi_selected = false; + +#ifdef GLES3_ENABLED + if (gl_manager) { + visualInfo = gl_manager->get_vi(x11_display); + vi_selected = true; + } +#endif + + if (!vi_selected) { + long visualMask = VisualScreenMask; + int numberOfVisuals; + XVisualInfo vInfoTemplate = {}; + vInfoTemplate.screen = DefaultScreen(x11_display); + XVisualInfo *vi_list = XGetVisualInfo(x11_display, visualMask, &vInfoTemplate, &numberOfVisuals); + ERR_FAIL_COND_V(!vi_list, INVALID_WINDOW_ID); + + visualInfo = vi_list[0]; + if (OS::get_singleton()->is_layered_allowed()) { + for (int i = 0; i < numberOfVisuals; i++) { + XRenderPictFormat *pict_format = XRenderFindVisualFormat(x11_display, vi_list[i].visual); + if (!pict_format) { + continue; + } + visualInfo = vi_list[i]; + if (pict_format->direct.alphaMask > 0) { + break; + } + } + } + XFree(vi_list); + } + + Colormap colormap = XCreateColormap(x11_display, RootWindow(x11_display, visualInfo.screen), visualInfo.visual, AllocNone); + + XSetWindowAttributes windowAttributes = {}; + windowAttributes.colormap = colormap; + windowAttributes.background_pixel = 0xFFFFFFFF; + windowAttributes.border_pixel = 0; + windowAttributes.event_mask = KeyPressMask | KeyReleaseMask | StructureNotifyMask | ExposureMask; + + unsigned long valuemask = CWBorderPixel | CWColormap | CWEventMask; + + if (OS::get_singleton()->is_layered_allowed()) { + windowAttributes.background_pixmap = None; + windowAttributes.background_pixel = 0; + windowAttributes.border_pixmap = None; + valuemask |= CWBackPixel; + } + + WindowID id = window_id_counter++; + WindowData &wd = windows[id]; + + if (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) { + wd.no_focus = true; + } + + if (p_flags & WINDOW_FLAG_POPUP_BIT) { + wd.is_popup = 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.is_popup || wd.no_focus) { + windowAttributes.override_redirect = True; + windowAttributes.save_under = True; + valuemask |= CWOverrideRedirect | CWSaveUnder; + } + + { + 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); + + // Enable receiving notification when the window is initialized (MapNotify) + // so the focus can be set at the right time. + if (!wd.no_focus && !wd.is_popup) { + 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); + 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; + + { + XIEventMask all_event_mask; + XSetWindowAttributes new_attr; + + new_attr.event_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask | + ButtonReleaseMask | EnterWindowMask | + LeaveWindowMask | PointerMotionMask | + Button1MotionMask | + Button2MotionMask | Button3MotionMask | + Button4MotionMask | Button5MotionMask | + ButtonMotionMask | KeymapStateMask | + ExposureMask | VisibilityChangeMask | + StructureNotifyMask | + SubstructureNotifyMask | SubstructureRedirectMask | + FocusChangeMask | PropertyChangeMask | + ColormapChangeMask | OwnerGrabButtonMask | + im_event_mask; + + XChangeWindowAttributes(x11_display, wd.x11_window, CWEventMask, &new_attr); + + static unsigned char all_mask_data[XIMaskLen(XI_LASTEVENT)] = {}; + + all_event_mask.deviceid = XIAllDevices; + all_event_mask.mask_len = sizeof(all_mask_data); + all_event_mask.mask = all_mask_data; + + XISetMask(all_event_mask.mask, XI_HierarchyChanged); + +#ifdef TOUCH_ENABLED + if (xi.touch_devices.size()) { + XISetMask(all_event_mask.mask, XI_TouchBegin); + XISetMask(all_event_mask.mask, XI_TouchUpdate); + XISetMask(all_event_mask.mask, XI_TouchEnd); + XISetMask(all_event_mask.mask, XI_TouchOwnership); + } +#endif + + XISelectEvents(x11_display, wd.x11_window, &all_event_mask, 1); + } + + /* set the titlebar name */ + XStoreName(x11_display, wd.x11_window, "Godot"); + XSetWMProtocols(x11_display, wd.x11_window, &wm_delete, 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) { + WARN_PRINT("XGetICValues couldn't obtain XNFilterEvents value"); + XDestroyIC(wd.xic); + wd.xic = nullptr; + } + if (wd.xic) { + XUnsetICFocus(wd.xic); + } else { + WARN_PRINT("XCreateIC couldn't create wd.xic"); + } + } else { + wd.xic = nullptr; + WARN_PRINT("XCreateIC couldn't create wd.xic"); + } + + _update_context(wd); + + 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); + } + } + + if (wd.is_popup || wd.no_focus) { + // 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 (wt_atom != None && type_atom != None) { + XChangeProperty(x11_display, wd.x11_window, wt_atom, XA_ATOM, 32, PropModeReplace, (unsigned char *)&type_atom, 1); + } + } + + _update_size_hints(id); + +#if defined(VULKAN_ENABLED) + if (context_vulkan) { + Error err = context_vulkan->window_create(id, p_vsync_mode, wd.x11_window, x11_display, p_rect.size.width, p_rect.size.height); + ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't create a Vulkan window"); + } +#endif +#ifdef GLES3_ENABLED + if (gl_manager) { + Error err = gl_manager->window_create(id, wd.x11_window, x11_display, p_rect.size.width, p_rect.size.height); + ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't create an OpenGL window"); + } +#endif + + //set_class_hint(x11_display, wd.x11_window); + XFlush(x11_display); + + XSync(x11_display, False); + //XSetErrorHandler(oldHandler); + } + + window_set_mode(p_mode, id); + + //sync size + { + XWindowAttributes xwa; + + XSync(x11_display, False); + XGetWindowAttributes(x11_display, wd.x11_window, &xwa); + + wd.position.x = xwa.x; + wd.position.y = xwa.y; + wd.size.width = xwa.width; + wd.size.height = xwa.height; + } + + //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, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, Error &r_error) { + Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events); + + r_error = OK; + + for (int i = 0; i < CURSOR_MAX; i++) { + cursors[i] = None; + img[i] = nullptr; + } + + XInitThreads(); //always use threads + + /** XLIB INITIALIZATION **/ + x11_display = XOpenDisplay(nullptr); + + if (!x11_display) { + ERR_PRINT("X11 Display is not available"); + r_error = ERR_UNAVAILABLE; + return; + } + + char *modifiers = nullptr; + Bool xkb_dar = False; + XAutoRepeatOn(x11_display); + xkb_dar = XkbSetDetectableAutoRepeat(x11_display, True, nullptr); + + // Try to support IME if detectable auto-repeat is supported + if (xkb_dar == True) { +#ifdef X_HAVE_UTF8_STRING + // Xutf8LookupString will be used later instead of XmbLookupString before + // the multibyte sequences can be converted to unicode string. + modifiers = XSetLocaleModifiers(""); +#endif + } + + if (modifiers == nullptr) { + if (OS::get_singleton()->is_stdout_verbose()) { + WARN_PRINT("IME is disabled"); + } + XSetLocaleModifiers("@im=none"); + WARN_PRINT("Error setting locale modifiers"); + } + + const char *err; + int xrandr_major = 0; + int xrandr_minor = 0; + int event_base, error_base; + xrandr_ext_ok = XRRQueryExtension(x11_display, &event_base, &error_base); + xrandr_handle = dlopen("libXrandr.so.2", RTLD_LAZY); + if (!xrandr_handle) { + err = dlerror(); + // 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) { + xrr_get_monitors = (xrr_get_monitors_t)dlsym(xrandr_handle, "XRRGetMonitors"); + if (!xrr_get_monitors) { + err = dlerror(); + fprintf(stderr, "could not find symbol XRRGetMonitors\nError: %s\n", err); + } else { + xrr_free_monitors = (xrr_free_monitors_t)dlsym(xrandr_handle, "XRRFreeMonitors"); + if (!xrr_free_monitors) { + err = dlerror(); + fprintf(stderr, "could not find XRRFreeMonitors\nError: %s\n", err); + xrr_get_monitors = nullptr; + } + } + } + } + + if (!_refresh_device_info()) { + OS::get_singleton()->alert("Your system does not support XInput 2.\n" + "Please upgrade your distribution.", + "Unable to initialize XInput"); + r_error = ERR_UNAVAILABLE; + return; + } + + xim = XOpenIM(x11_display, nullptr, nullptr, nullptr); + + if (xim == nullptr) { + WARN_PRINT("XOpenIM failed"); + xim_style = 0L; + } else { + ::XIMCallback im_destroy_callback; + im_destroy_callback.client_data = (::XPointer)(this); + im_destroy_callback.callback = (::XIMProc)(_xim_destroy_callback); + if (XSetIMValues(xim, XNDestroyCallback, &im_destroy_callback, + nullptr) != nullptr) { + WARN_PRINT("Error setting XIM destroy callback"); + } + + ::XIMStyles *xim_styles = nullptr; + xim_style = 0L; + char *imvalret = XGetIMValues(xim, XNQueryInputStyle, &xim_styles, nullptr); + if (imvalret != nullptr || xim_styles == nullptr) { + fprintf(stderr, "Input method doesn't support any styles\n"); + } + + if (xim_styles) { + xim_style = 0L; + for (int i = 0; i < xim_styles->count_styles; i++) { + if (xim_styles->supported_styles[i] == + (XIMPreeditNothing | XIMStatusNothing)) { + xim_style = xim_styles->supported_styles[i]; + break; + } + } + + XFree(xim_styles); + } + XFree(imvalret); + } + + /* Atom internment */ + wm_delete = XInternAtom(x11_display, "WM_DELETE_WINDOW", true); + // Set Xdnd (drag & drop) support. + xdnd_aware = XInternAtom(x11_display, "XdndAware", False); + xdnd_enter = XInternAtom(x11_display, "XdndEnter", False); + xdnd_position = XInternAtom(x11_display, "XdndPosition", False); + xdnd_status = XInternAtom(x11_display, "XdndStatus", False); + xdnd_action_copy = XInternAtom(x11_display, "XdndActionCopy", False); + xdnd_drop = XInternAtom(x11_display, "XdndDrop", False); + xdnd_finished = XInternAtom(x11_display, "XdndFinished", False); + xdnd_selection = XInternAtom(x11_display, "XdndSelection", False); + +#ifdef SPEECHD_ENABLED + // Init TTS + tts = memnew(TTS_Linux); +#endif + + //!!!!!!!!!!!!!!!!!!!!!!!!!! + //TODO - do Vulkan and OpenGL support checks, driver selection and fallback + rendering_driver = p_rendering_driver; + + bool driver_found = false; +#if defined(VULKAN_ENABLED) + if (rendering_driver == "vulkan") { + context_vulkan = memnew(VulkanContextX11); + if (context_vulkan->initialize() != OK) { + memdelete(context_vulkan); + context_vulkan = nullptr; + r_error = ERR_CANT_CREATE; + ERR_FAIL_MSG("Could not initialize Vulkan"); + } + driver_found = true; + } +#endif + // Initialize context and rendering device. +#if defined(GLES3_ENABLED) + if (rendering_driver == "opengl3") { + if (getenv("DRI_PRIME") == nullptr) { + int use_prime = -1; + + if (getenv("PRIMUS_DISPLAY") || + getenv("PRIMUS_libGLd") || + getenv("PRIMUS_libGLa") || + getenv("PRIMUS_libGL") || + getenv("PRIMUS_LOAD_GLOBAL") || + getenv("BUMBLEBEE_SOCKET")) { + print_verbose("Optirun/primusrun detected. Skipping GPU detection"); + use_prime = 0; + } + + // Some tools use fake libGL libraries and have them override the real one using + // LD_LIBRARY_PATH, so we skip them. *But* Steam also sets LD_LIBRARY_PATH for its + // runtime and includes system `/lib` and `/lib64`... so ignore Steam. + if (use_prime == -1 && getenv("LD_LIBRARY_PATH") && !getenv("STEAM_RUNTIME_LIBRARY_PATH")) { + String ld_library_path(getenv("LD_LIBRARY_PATH")); + Vector libraries = ld_library_path.split(":"); + + 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; + } + } + } + + if (use_prime == -1) { + print_verbose("Detecting GPUs, set DRI_PRIME in the environment to override GPU detection logic."); + use_prime = detect_prime(); + } + + if (use_prime) { + print_line("Found discrete GPU, setting DRI_PRIME=1 to use it."); + print_line("Note: Set DRI_PRIME=0 in the environment to disable Godot from using the discrete GPU."); + setenv("DRI_PRIME", "1", 1); + } + } + + GLManager_X11::ContextType opengl_api_type = GLManager_X11::GLES_3_0_COMPATIBLE; + + gl_manager = memnew(GLManager_X11(p_resolution, opengl_api_type)); + + if (gl_manager->initialize() != OK) { + memdelete(gl_manager); + gl_manager = nullptr; + r_error = ERR_UNAVAILABLE; + return; + } + driver_found = true; + + if (true) { + RasterizerGLES3::make_current(); + } else { + memdelete(gl_manager); + gl_manager = nullptr; + r_error = ERR_UNAVAILABLE; + return; + } + } +#endif + if (!driver_found) { + r_error = ERR_UNAVAILABLE; + ERR_FAIL_MSG("Video driver not found"); + } + + Point2i window_position( + (screen_get_size(0).width - p_resolution.width) / 2, + (screen_get_size(0).height - p_resolution.height) / 2); + + if (p_position != nullptr) { + window_position = *p_position; + } + + WindowID main_window = _create_window(p_mode, p_vsync_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); + XSync(x11_display, False); + _validate_mode_on_map(main_window); + +#if defined(VULKAN_ENABLED) + if (rendering_driver == "vulkan") { + //temporary + rendering_device_vulkan = memnew(RenderingDeviceVulkan); + rendering_device_vulkan->initialize(context_vulkan); + + RendererCompositorRD::make_current(); + } +#endif + + { + //set all event master mask + XIEventMask all_master_event_mask; + static unsigned char all_master_mask_data[XIMaskLen(XI_LASTEVENT)] = {}; + all_master_event_mask.deviceid = XIAllMasterDevices; + all_master_event_mask.mask_len = sizeof(all_master_mask_data); + all_master_event_mask.mask = all_master_mask_data; + XISetMask(all_master_event_mask.mask, XI_DeviceChanged); + XISetMask(all_master_event_mask.mask, XI_RawMotion); + XISelectEvents(x11_display, DefaultRootWindow(x11_display), &all_master_event_mask, 1); + } + + cursor_size = XcursorGetDefaultSize(x11_display); + cursor_theme = XcursorGetTheme(x11_display); + + if (!cursor_theme) { + print_verbose("XcursorGetTheme could not get cursor theme"); + cursor_theme = "default"; + } + + for (int i = 0; i < CURSOR_MAX; i++) { + static const char *cursor_file[] = { + "left_ptr", + "xterm", + "hand2", + "cross", + "watch", + "left_ptr_watch", + "fleur", + "dnd-move", + "crossed_circle", + "v_double_arrow", + "h_double_arrow", + "size_bdiag", + "size_fdiag", + "move", + "row_resize", + "col_resize", + "question_arrow" + }; + + img[i] = XcursorLibraryLoadImage(cursor_file[i], cursor_theme, cursor_size); + if (!img[i]) { + const char *fallback = nullptr; + + switch (i) { + case CURSOR_POINTING_HAND: + fallback = "pointer"; + break; + case CURSOR_CROSS: + fallback = "crosshair"; + break; + case CURSOR_WAIT: + fallback = "wait"; + break; + case CURSOR_BUSY: + fallback = "progress"; + break; + case CURSOR_DRAG: + fallback = "grabbing"; + break; + case CURSOR_CAN_DROP: + fallback = "hand1"; + break; + case CURSOR_FORBIDDEN: + fallback = "forbidden"; + break; + case CURSOR_VSIZE: + fallback = "ns-resize"; + break; + case CURSOR_HSIZE: + fallback = "ew-resize"; + break; + case CURSOR_BDIAGSIZE: + fallback = "fd_double_arrow"; + break; + case CURSOR_FDIAGSIZE: + fallback = "bd_double_arrow"; + break; + case CURSOR_MOVE: + img[i] = img[CURSOR_DRAG]; + break; + case CURSOR_VSPLIT: + fallback = "sb_v_double_arrow"; + break; + case CURSOR_HSPLIT: + fallback = "sb_h_double_arrow"; + break; + case CURSOR_HELP: + fallback = "help"; + break; + } + if (fallback != nullptr) { + img[i] = XcursorLibraryLoadImage(fallback, cursor_theme, cursor_size); + } + } + if (img[i]) { + cursors[i] = XcursorImageLoadCursor(x11_display, img[i]); + } else { + print_verbose("Failed loading custom cursor: " + String(cursor_file[i])); + } + } + + { + // Creating an empty/transparent cursor + + // Create 1x1 bitmap + Pixmap cursormask = XCreatePixmap(x11_display, + RootWindow(x11_display, DefaultScreen(x11_display)), 1, 1, 1); + + // Fill with zero + XGCValues xgc; + xgc.function = GXclear; + GC gc = XCreateGC(x11_display, cursormask, GCFunction, &xgc); + XFillRectangle(x11_display, cursormask, gc, 0, 0, 1, 1); + + // Color value doesn't matter. Mask zero means no foreground or background will be drawn + XColor col = {}; + + Cursor cursor = XCreatePixmapCursor(x11_display, + cursormask, // source (using cursor mask as placeholder, since it'll all be ignored) + cursormask, // mask + &col, &col, 0, 0); + + XFreePixmap(x11_display, cursormask); + XFreeGC(x11_display, gc); + + if (cursor == None) { + ERR_PRINT("FAILED CREATING CURSOR"); + } + + null_cursor = cursor; + } + cursor_set_shape(CURSOR_BUSY); + + // Search the X11 event queue for ConfigureNotify events and process all + // that are currently queued early, so we can get the final window size + // for correctly drawing of the bootsplash. + XEvent config_event; + while (XCheckTypedEvent(x11_display, ConfigureNotify, &config_event)) { + _window_changed(&config_event); + } + events_thread.start(_poll_events_thread, this); + + _update_real_mouse_position(windows[MAIN_WINDOW_ID]); + +#ifdef DBUS_ENABLED + screensaver = memnew(FreeDesktopScreenSaver); + screen_set_keep_on(GLOBAL_GET("display/window/energy_saving/keep_screen_on")); + + portal_desktop = memnew(FreeDesktopPortalDesktop); +#endif + + 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.set(); + events_thread.wait_to_finish(); + + //destroy all windows + for (KeyValue &E : windows) { +#ifdef VULKAN_ENABLED + if (context_vulkan) { + context_vulkan->window_destroy(E.key); + } +#endif +#ifdef GLES3_ENABLED + if (gl_manager) { + gl_manager->window_destroy(E.key); + } +#endif + + WindowData &wd = E.value; + if (wd.xic) { + XDestroyIC(wd.xic); + wd.xic = nullptr; + } + XUnmapWindow(x11_display, wd.x11_window); + XDestroyWindow(x11_display, wd.x11_window); + } + + //destroy drivers +#if defined(VULKAN_ENABLED) + if (rendering_device_vulkan) { + rendering_device_vulkan->finalize(); + memdelete(rendering_device_vulkan); + rendering_device_vulkan = nullptr; + } + + if (context_vulkan) { + memdelete(context_vulkan); + context_vulkan = nullptr; + } +#endif + +#ifdef GLES3_ENABLED + if (gl_manager) { + memdelete(gl_manager); + gl_manager = nullptr; + } +#endif + + if (xrandr_handle) { + dlclose(xrandr_handle); + } + + for (int i = 0; i < CURSOR_MAX; i++) { + if (cursors[i] != None) { + XFreeCursor(x11_display, cursors[i]); + } + if (img[i] != nullptr) { + XcursorImageDestroy(img[i]); + } + } + + if (xim) { + XCloseIM(xim); + } + + XCloseDisplay(x11_display); + if (xmbstring) { + memfree(xmbstring); + } + +#ifdef SPEECHD_ENABLED + memdelete(tts); +#endif + +#ifdef DBUS_ENABLED + memdelete(screensaver); + memdelete(portal_desktop); +#endif +} + +void DisplayServerX11::register_x11_driver() { + register_create_function("x11", create_func, get_rendering_drivers_func); +} + +#endif // X11 enabled diff --git a/platform/linuxbsd/x11/display_server_x11.h b/platform/linuxbsd/x11/display_server_x11.h new file mode 100644 index 0000000000..861eced88b --- /dev/null +++ b/platform/linuxbsd/x11/display_server_x11.h @@ -0,0 +1,458 @@ +/*************************************************************************/ +/* display_server_x11.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef DISPLAY_SERVER_X11_H +#define DISPLAY_SERVER_X11_H + +#ifdef X11_ENABLED + +#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/renderer_compositor.h" +#include "servers/rendering_server.h" + +#if defined(SPEECHD_ENABLED) +#include "../tts_linux.h" +#endif + +#if defined(GLES3_ENABLED) +#include "gl_manager_x11.h" +#endif + +#if defined(VULKAN_ENABLED) +#include "drivers/vulkan/rendering_device_vulkan.h" +#include "vulkan_context_x11.h" +#endif + +#if defined(DBUS_ENABLED) +#include "../freedesktop_portal_desktop.h" +#include "../freedesktop_screensaver.h" +#endif + +#include +#include +#include +#include +#include + +typedef struct _xrr_monitor_info { + Atom name; + 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 + +class DisplayServerX11 : public DisplayServer { + //No need to register, it's platform-specific and nothing is added + //GDCLASS(DisplayServerX11, DisplayServer) + + _THREAD_SAFE_CLASS_ + + Atom wm_delete; + Atom xdnd_enter; + Atom xdnd_position; + Atom xdnd_status; + Atom xdnd_action_copy; + Atom xdnd_drop; + Atom xdnd_finished; + Atom xdnd_selection; + Atom xdnd_aware; + Atom requested = None; + int xdnd_version = 5; + +#if defined(GLES3_ENABLED) + GLManager_X11 *gl_manager = nullptr; +#endif +#if defined(VULKAN_ENABLED) + VulkanContextX11 *context_vulkan = nullptr; + RenderingDeviceVulkan *rendering_device_vulkan = nullptr; +#endif + +#if defined(DBUS_ENABLED) + FreeDesktopScreenSaver *screensaver = nullptr; + bool keep_screen_on = false; +#endif + +#ifdef SPEECHD_ENABLED + TTS_Linux *tts = nullptr; +#endif + +#if defined(DBUS_ENABLED) + FreeDesktopPortalDesktop *portal_desktop = nullptr; +#endif + + struct WindowData { + Window x11_window; + ::XIC xic; + + Size2i min_size; + Size2i max_size; + Point2i position; + Size2i size; + Point2i im_position; + bool im_active = false; + Callable rect_changed_callback; + Callable event_callback; + Callable input_event_callback; + Callable input_text_callback; + Callable drop_files_callback; + + WindowID transient_parent = INVALID_WINDOW_ID; + HashSet transient_children; + + ObjectID instance_id; + + 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 + bool on_top = false; + bool borderless = false; + bool resize_disabled = false; + Vector2i last_position_before_fs; + bool focused = true; + bool minimized = false; + bool maximized = false; + bool is_popup = false; + bool layered_window = false; + + Rect2i parent_safe_rect; + + unsigned int focus_order = 0; + }; + + HashMap windows; + + unsigned int last_mouse_monitor_mask = 0; + Vector2i last_mouse_monitor_pos; + uint64_t time_since_popup = 0; + + List popup_list; + + WindowID last_focused_window = INVALID_WINDOW_ID; + + WindowID window_id_counter = MAIN_WINDOW_ID; + WindowID _create_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect); + + String internal_clipboard; + String internal_clipboard_primary; + Window xdnd_source_window = 0; + ::Display *x11_display; + char *xmbstring = nullptr; + int xmblen = 0; + unsigned long last_timestamp = 0; + ::Time last_keyrelease_time = 0; + ::XIM xim; + ::XIMStyle xim_style; + static void _xim_destroy_callback(::XIM im, ::XPointer client_data, + ::XPointer call_data); + + Point2i last_mouse_pos; + bool last_mouse_pos_valid = false; + Point2i last_click_pos = Point2i(-100, -100); + uint64_t last_click_ms = 0; + MouseButton last_click_button_index = MouseButton::NONE; + MouseButton last_button_state = MouseButton::NONE; + bool app_focused = false; + uint64_t time_since_no_focus = 0; + + struct { + int opcode; + Vector touch_devices; + HashMap absolute_devices; + HashMap pen_pressure_range; + HashMap pen_tilt_x_range; + HashMap pen_tilt_y_range; + HashMap pen_inverted_devices; + XIEventMask all_event_mask; + HashMap state; + double pressure; + bool pressure_supported; + bool pen_inverted; + Vector2 tilt; + Vector2 mouse_pos_to_filter; + Vector2 relative_motion; + Vector2 raw_pos; + Vector2 old_raw_pos; + ::Time last_relative_time; + } xi; + + bool _refresh_device_info(); + + Rect2i _screen_get_rect(int p_screen) const; + + MouseButton _get_mouse_button_state(MouseButton p_x11_button, int p_x11_type); + void _get_key_modifier_state(unsigned int p_x11_state, Ref state); + void _flush_mouse_motion(); + + MouseMode mouse_mode = MOUSE_MODE_VISIBLE; + Point2i center; + + void _handle_key_event(WindowID p_window, XKeyEvent *p_event, LocalVector &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, Atom p_selection) const; + void _handle_selection_request_event(XSelectionRequestEvent *p_event) const; + + 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 do_mouse_warp = false; + + const char *cursor_theme = nullptr; + int cursor_size = 0; + XcursorImage *img[CURSOR_MAX]; + Cursor cursors[CURSOR_MAX]; + Cursor null_cursor; + CursorShape current_cursor = CURSOR_ARROW; + HashMap> cursors_cache; + + String rendering_driver; + void set_wm_fullscreen(bool p_enabled); + void set_wm_above(bool p_enabled); + + typedef xrr_monitor_info *(*xrr_get_monitors_t)(Display *dpy, Window window, Bool get_active, int *nmonitors); + typedef void (*xrr_free_monitors_t)(xrr_monitor_info *monitors); + xrr_get_monitors_t xrr_get_monitors = nullptr; + xrr_free_monitors_t xrr_free_monitors = nullptr; + void *xrandr_handle = nullptr; + Bool xrandr_ext_ok; + + struct Property { + unsigned char *data; + int format, nitems; + Atom type; + }; + static Property _read_property(Display *p_display, Window p_window, Atom p_property); + + void _update_real_mouse_position(const WindowData &wd); + bool _window_maximize_check(WindowID p_window, const char *p_atom_name) const; + bool _window_fullscreen_check(WindowID p_window) const; + bool _window_minimize_check(WindowID p_window) const; + void _validate_mode_on_map(WindowID p_window); + 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); + void _set_wm_minimized(WindowID p_window, bool p_enabled); + + void _update_context(WindowData &wd); + + Context context = CONTEXT_ENGINE; + + WindowID _get_focused_window_or_popup() const; + + void _send_window_event(const WindowData &wd, WindowEvent p_event); + static void _dispatch_input_events(const Ref &p_event); + void _dispatch_input_event(const Ref &p_event); + + mutable Mutex events_mutex; + Thread events_thread; + SafeFlag events_thread_done; + LocalVector polled_events; + static void _poll_events_thread(void *ud); + bool _wait_for_events() const; + void _poll_events(); + void _check_pending_events(LocalVector &r_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); + +public: + bool mouse_process_popups(); + void popup_open(WindowID p_window); + void popup_close(WindowID p_window); + + virtual bool has_feature(Feature p_feature) const override; + virtual String get_name() const override; + +#ifdef SPEECHD_ENABLED + virtual bool tts_is_speaking() const override; + virtual bool tts_is_paused() const override; + virtual TypedArray tts_get_voices() const override; + + virtual void tts_speak(const String &p_text, const String &p_voice, int p_volume = 50, float p_pitch = 1.f, float p_rate = 1.f, int p_utterance_id = 0, bool p_interrupt = false) override; + virtual void tts_pause() override; + virtual void tts_resume() override; + virtual void tts_stop() override; +#endif + +#if defined(DBUS_ENABLED) + virtual bool is_dark_mode_supported() const override; + virtual bool is_dark_mode() const override; +#endif + + virtual void mouse_set_mode(MouseMode p_mode) override; + virtual MouseMode mouse_get_mode() const override; + + virtual void warp_mouse(const Point2i &p_position) override; + virtual Point2i mouse_get_position() const override; + virtual MouseButton mouse_get_button_state() const override; + + virtual void clipboard_set(const String &p_text) override; + virtual String clipboard_get() const override; + virtual void clipboard_set_primary(const String &p_text) override; + virtual String clipboard_get_primary() const override; + + virtual int get_screen_count() const override; + virtual Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + +#if defined(DBUS_ENABLED) + virtual void screen_set_keep_on(bool p_enable) override; + virtual bool screen_is_kept_on() const override; +#endif + + virtual Vector get_window_list() const override; + + virtual WindowID create_sub_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect = Rect2i()) override; + virtual void show_window(WindowID p_id) override; + virtual void delete_sub_window(WindowID p_id) override; + + virtual WindowID window_get_active_popup() const override; + virtual void window_set_popup_safe_rect(WindowID p_window, const Rect2i &p_rect) override; + virtual Rect2i window_get_popup_safe_rect(WindowID p_window) const override; + + virtual WindowID get_window_at_screen_position(const Point2i &p_position) const override; + + virtual int64_t window_get_native_handle(HandleType p_handle_type, WindowID p_window = MAIN_WINDOW_ID) const override; + + virtual void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID) override; + virtual ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const override; + + virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) override; + virtual void window_set_mouse_passthrough(const Vector &p_region, WindowID p_window = MAIN_WINDOW_ID) override; + + virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; + virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; + virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; + virtual void window_set_input_text_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; + virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; + + virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const override; + virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override; + + virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const override; + virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) override; + + virtual void window_set_max_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; + virtual Size2i window_get_max_size(WindowID p_window = MAIN_WINDOW_ID) const override; + virtual void gl_window_make_current(DisplayServer::WindowID p_window_id) override; + + virtual void window_set_transient(WindowID p_window, WindowID p_parent) override; + + virtual void window_set_min_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; + virtual Size2i window_get_min_size(WindowID p_window = MAIN_WINDOW_ID) const override; + + virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; + virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const override; + virtual Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const override; + + virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID) override; + virtual WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const override; + + virtual bool window_is_maximize_allowed(WindowID p_window = MAIN_WINDOW_ID) const override; + + virtual void window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window = MAIN_WINDOW_ID) override; + virtual bool window_get_flag(WindowFlags p_flag, WindowID p_window = MAIN_WINDOW_ID) const override; + + virtual void window_request_attention(WindowID p_window = MAIN_WINDOW_ID) override; + + virtual void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID) override; + + virtual bool window_can_draw(WindowID p_window = MAIN_WINDOW_ID) const override; + + virtual bool can_any_window_draw() const override; + + virtual void window_set_ime_active(const bool p_active, WindowID p_window = MAIN_WINDOW_ID) override; + virtual void window_set_ime_position(const Point2i &p_pos, WindowID p_window = MAIN_WINDOW_ID) override; + + virtual void window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window = MAIN_WINDOW_ID) override; + virtual DisplayServer::VSyncMode window_get_vsync_mode(WindowID p_vsync_mode) const override; + + virtual void cursor_set_shape(CursorShape p_shape) override; + virtual CursorShape cursor_get_shape() const override; + virtual void cursor_set_custom_image(const Ref &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) override; + + virtual int keyboard_get_layout_count() const override; + virtual int keyboard_get_current_layout() const override; + virtual void keyboard_set_current_layout(int p_index) override; + virtual String keyboard_get_layout_language(int p_index) const override; + virtual String keyboard_get_layout_name(int p_index) const override; + virtual Key keyboard_get_keycode_from_physical(Key p_keycode) const override; + + virtual void process_events() override; + + virtual void release_rendering_thread() override; + virtual void make_rendering_thread() override; + virtual void swap_buffers() override; + + virtual void set_context(Context p_context) override; + + virtual void set_native_icon(const String &p_filename) override; + virtual void set_icon(const Ref &p_icon) override; + + static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, Error &r_error); + static Vector get_rendering_drivers_func(); + + static void register_x11_driver(); + + DisplayServerX11(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, Error &r_error); + ~DisplayServerX11(); +}; + +#endif // X11 enabled + +#endif // DISPLAY_SERVER_X11_H diff --git a/platform/linuxbsd/x11/gl_manager_x11.cpp b/platform/linuxbsd/x11/gl_manager_x11.cpp new file mode 100644 index 0000000000..f586c57dda --- /dev/null +++ b/platform/linuxbsd/x11/gl_manager_x11.cpp @@ -0,0 +1,394 @@ +/*************************************************************************/ +/* gl_manager_x11.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "gl_manager_x11.h" + +#ifdef X11_ENABLED +#if defined(GLES3_ENABLED) + +#include +#include +#include + +#define GLX_GLXEXT_PROTOTYPES +#include +#include + +#define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091 +#define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092 + +typedef GLXContext (*GLXCREATECONTEXTATTRIBSARBPROC)(Display *, GLXFBConfig, GLXContext, Bool, const int *); + +struct GLManager_X11_Private { + ::GLXContext glx_context; +}; + +GLManager_X11::GLDisplay::~GLDisplay() { + if (context) { + //release_current(); + glXDestroyContext(x11_display, context->glx_context); + memdelete(context); + context = nullptr; + } +} + +static bool ctxErrorOccurred = false; +static int ctxErrorHandler(Display *dpy, XErrorEvent *ev) { + ctxErrorOccurred = true; + return 0; +} + +int GLManager_X11::_find_or_create_display(Display *p_x11_display) { + for (unsigned int n = 0; n < _displays.size(); n++) { + const GLDisplay &d = _displays[n]; + if (d.x11_display == p_x11_display) { + return n; + } + } + + // create + GLDisplay d_temp; + d_temp.x11_display = p_x11_display; + _displays.push_back(d_temp); + int new_display_id = _displays.size() - 1; + + // create context + GLDisplay &d = _displays[new_display_id]; + + d.context = memnew(GLManager_X11_Private); + d.context->glx_context = nullptr; + + //Error err = _create_context(d); + _create_context(d); + return new_display_id; +} + +Error GLManager_X11::_create_context(GLDisplay &gl_display) { + // some aliases + ::Display *x11_display = gl_display.x11_display; + + //const char *extensions = glXQueryExtensionsString(x11_display, DefaultScreen(x11_display)); + + GLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB = (GLXCREATECONTEXTATTRIBSARBPROC)glXGetProcAddress((const GLubyte *)"glXCreateContextAttribsARB"); + + ERR_FAIL_COND_V(!glXCreateContextAttribsARB, ERR_UNCONFIGURED); + + static int visual_attribs[] = { + GLX_RENDER_TYPE, GLX_RGBA_BIT, + GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, + GLX_DOUBLEBUFFER, true, + GLX_RED_SIZE, 1, + GLX_GREEN_SIZE, 1, + GLX_BLUE_SIZE, 1, + GLX_DEPTH_SIZE, 24, + None + }; + + static int visual_attribs_layered[] = { + GLX_RENDER_TYPE, GLX_RGBA_BIT, + GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, + GLX_DOUBLEBUFFER, true, + GLX_RED_SIZE, 8, + GLX_GREEN_SIZE, 8, + GLX_BLUE_SIZE, 8, + GLX_ALPHA_SIZE, 8, + GLX_DEPTH_SIZE, 24, + None + }; + + int fbcount; + GLXFBConfig fbconfig = nullptr; + XVisualInfo *vi = nullptr; + + if (OS::get_singleton()->is_layered_allowed()) { + GLXFBConfig *fbc = glXChooseFBConfig(x11_display, DefaultScreen(x11_display), visual_attribs_layered, &fbcount); + ERR_FAIL_COND_V(!fbc, ERR_UNCONFIGURED); + + for (int i = 0; i < fbcount; i++) { + vi = (XVisualInfo *)glXGetVisualFromFBConfig(x11_display, fbc[i]); + if (!vi) { + continue; + } + + XRenderPictFormat *pict_format = XRenderFindVisualFormat(x11_display, vi->visual); + if (!pict_format) { + XFree(vi); + vi = nullptr; + continue; + } + + fbconfig = fbc[i]; + if (pict_format->direct.alphaMask > 0) { + break; + } + } + XFree(fbc); + + ERR_FAIL_COND_V(!fbconfig, ERR_UNCONFIGURED); + } else { + GLXFBConfig *fbc = glXChooseFBConfig(x11_display, DefaultScreen(x11_display), visual_attribs, &fbcount); + ERR_FAIL_COND_V(!fbc, ERR_UNCONFIGURED); + + vi = glXGetVisualFromFBConfig(x11_display, fbc[0]); + + fbconfig = fbc[0]; + XFree(fbc); + } + + int (*oldHandler)(Display *, XErrorEvent *) = XSetErrorHandler(&ctxErrorHandler); + + switch (context_type) { + case GLES_3_0_COMPATIBLE: { + static int context_attribs[] = { + GLX_CONTEXT_MAJOR_VERSION_ARB, 3, + GLX_CONTEXT_MINOR_VERSION_ARB, 3, + GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB, + GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB /*|GLX_CONTEXT_DEBUG_BIT_ARB*/, + None + }; + + gl_display.context->glx_context = glXCreateContextAttribsARB(x11_display, fbconfig, nullptr, true, context_attribs); + ERR_FAIL_COND_V(ctxErrorOccurred || !gl_display.context->glx_context, ERR_UNCONFIGURED); + } break; + } + + XSync(x11_display, False); + XSetErrorHandler(oldHandler); + + // make our own copy of the vi data + // for later creating windows using this display + if (vi) { + gl_display.x_vi = *vi; + } + + XFree(vi); + + return OK; +} + +XVisualInfo GLManager_X11::get_vi(Display *p_display) { + return _displays[_find_or_create_display(p_display)].x_vi; +} + +Error GLManager_X11::window_create(DisplayServer::WindowID p_window_id, ::Window p_window, Display *p_display, int p_width, int p_height) { + // make sure vector is big enough... + // we can mirror the external vector, it is simpler + // to keep the IDs identical for fast lookup + if (p_window_id >= (int)_windows.size()) { + _windows.resize(p_window_id + 1); + } + + GLWindow &win = _windows[p_window_id]; + win.in_use = true; + win.window_id = p_window_id; + win.width = p_width; + win.height = p_height; + win.x11_window = p_window; + win.gldisplay_id = _find_or_create_display(p_display); + + // the display could be invalid .. check NYI + GLDisplay &gl_display = _displays[win.gldisplay_id]; + ::Display *x11_display = gl_display.x11_display; + ::Window &x11_window = win.x11_window; + + if (!glXMakeCurrent(x11_display, x11_window, gl_display.context->glx_context)) { + ERR_PRINT("glXMakeCurrent failed"); + } + + _internal_set_current_window(&win); + + return OK; +} + +void GLManager_X11::_internal_set_current_window(GLWindow *p_win) { + _current_window = p_win; + + // quick access to x info + _x_windisp.x11_window = _current_window->x11_window; + const GLDisplay &disp = get_current_display(); + _x_windisp.x11_display = disp.x11_display; +} + +void GLManager_X11::window_resize(DisplayServer::WindowID p_window_id, int p_width, int p_height) { + get_window(p_window_id).width = p_width; + get_window(p_window_id).height = p_height; +} + +void GLManager_X11::window_destroy(DisplayServer::WindowID p_window_id) { + GLWindow &win = get_window(p_window_id); + win.in_use = false; + + if (_current_window == &win) { + _current_window = nullptr; + _x_windisp.x11_display = nullptr; + _x_windisp.x11_window = -1; + } +} + +void GLManager_X11::release_current() { + if (!_current_window) { + return; + } + + if (!glXMakeCurrent(_x_windisp.x11_display, None, nullptr)) { + ERR_PRINT("glXMakeCurrent failed"); + } + _current_window = nullptr; +} + +void GLManager_X11::window_make_current(DisplayServer::WindowID p_window_id) { + if (p_window_id == -1) { + return; + } + + GLWindow &win = _windows[p_window_id]; + if (!win.in_use) { + return; + } + + // noop + if (&win == _current_window) { + return; + } + + const GLDisplay &disp = get_display(win.gldisplay_id); + + if (!glXMakeCurrent(disp.x11_display, win.x11_window, disp.context->glx_context)) { + ERR_PRINT("glXMakeCurrent failed"); + } + + _internal_set_current_window(&win); +} + +void GLManager_X11::make_current() { + if (!_current_window) { + return; + } + if (!_current_window->in_use) { + WARN_PRINT("current window not in use!"); + return; + } + const GLDisplay &disp = get_current_display(); + if (!glXMakeCurrent(_x_windisp.x11_display, _x_windisp.x11_window, disp.context->glx_context)) { + ERR_PRINT("glXMakeCurrent failed"); + } +} + +void GLManager_X11::swap_buffers() { + if (!_current_window) { + return; + } + if (!_current_window->in_use) { + WARN_PRINT("current window not in use!"); + return; + } + + // On X11, when enabled, transparency is always active, so clear alpha manually. + if (OS::get_singleton()->is_layered_allowed()) { + if (!DisplayServer::get_singleton()->window_get_flag(DisplayServer::WINDOW_FLAG_TRANSPARENT, _current_window->window_id)) { + glColorMask(false, false, false, true); + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + glColorMask(true, true, true, true); + } + } + + glXSwapBuffers(_x_windisp.x11_display, _x_windisp.x11_window); +} + +Error GLManager_X11::initialize() { + return OK; +} + +void GLManager_X11::set_use_vsync(bool p_use) { + static bool setup = false; + static PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT = nullptr; + static PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalMESA = nullptr; + static PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalSGI = nullptr; + + // force vsync in the editor for now, as a safety measure + bool is_editor = Engine::get_singleton()->is_editor_hint(); + if (is_editor) { + p_use = true; + } + + // we need an active window to get a display to set the vsync + if (!_current_window) { + return; + } + const GLDisplay &disp = get_current_display(); + + if (!setup) { + setup = true; + String extensions = glXQueryExtensionsString(disp.x11_display, DefaultScreen(disp.x11_display)); + if (extensions.find("GLX_EXT_swap_control") != -1) { + glXSwapIntervalEXT = (PFNGLXSWAPINTERVALEXTPROC)glXGetProcAddressARB((const GLubyte *)"glXSwapIntervalEXT"); + } + if (extensions.find("GLX_MESA_swap_control") != -1) { + glXSwapIntervalMESA = (PFNGLXSWAPINTERVALSGIPROC)glXGetProcAddressARB((const GLubyte *)"glXSwapIntervalMESA"); + } + if (extensions.find("GLX_SGI_swap_control") != -1) { + glXSwapIntervalSGI = (PFNGLXSWAPINTERVALSGIPROC)glXGetProcAddressARB((const GLubyte *)"glXSwapIntervalSGI"); + } + } + int val = p_use ? 1 : 0; + if (glXSwapIntervalMESA) { + glXSwapIntervalMESA(val); + } else if (glXSwapIntervalSGI) { + glXSwapIntervalSGI(val); + } else if (glXSwapIntervalEXT) { + GLXDrawable drawable = glXGetCurrentDrawable(); + glXSwapIntervalEXT(disp.x11_display, drawable, val); + } else { + return; + } + use_vsync = p_use; +} + +bool GLManager_X11::is_using_vsync() const { + return use_vsync; +} + +GLManager_X11::GLManager_X11(const Vector2i &p_size, ContextType p_context_type) { + context_type = p_context_type; + + double_buffer = false; + direct_render = false; + glx_minor = glx_major = 0; + use_vsync = false; + _current_window = nullptr; +} + +GLManager_X11::~GLManager_X11() { + release_current(); +} + +#endif +#endif diff --git a/platform/linuxbsd/x11/gl_manager_x11.h b/platform/linuxbsd/x11/gl_manager_x11.h new file mode 100644 index 0000000000..4f78c45c88 --- /dev/null +++ b/platform/linuxbsd/x11/gl_manager_x11.h @@ -0,0 +1,126 @@ +/*************************************************************************/ +/* gl_manager_x11.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef GL_MANAGER_X11_H +#define GL_MANAGER_X11_H + +#ifdef X11_ENABLED + +#ifdef GLES3_ENABLED + +#include "core/os/os.h" +#include "core/templates/local_vector.h" +#include "servers/display_server.h" +#include +#include + +struct GLManager_X11_Private; + +class GLManager_X11 { +public: + enum ContextType { + GLES_3_0_COMPATIBLE, + }; + +private: + // any data specific to the window + struct GLWindow { + bool in_use = false; + + // the external ID .. should match the GL window number .. unused I think + DisplayServer::WindowID window_id = DisplayServer::INVALID_WINDOW_ID; + int width = 0; + int height = 0; + ::Window x11_window; + int gldisplay_id = 0; + }; + + struct GLDisplay { + GLDisplay() { context = nullptr; } + ~GLDisplay(); + GLManager_X11_Private *context = nullptr; + ::Display *x11_display; + XVisualInfo x_vi; + }; + + // just for convenience, window and display struct + struct XWinDisp { + ::Window x11_window; + ::Display *x11_display; + } _x_windisp; + + LocalVector _windows; + LocalVector _displays; + + GLWindow *_current_window = nullptr; + + void _internal_set_current_window(GLWindow *p_win); + + GLWindow &get_window(unsigned int id) { return _windows[id]; } + const GLWindow &get_window(unsigned int id) const { return _windows[id]; } + + const GLDisplay &get_current_display() const { return _displays[_current_window->gldisplay_id]; } + const GLDisplay &get_display(unsigned int id) { return _displays[id]; } + + bool double_buffer; + bool direct_render; + int glx_minor, glx_major; + bool use_vsync; + ContextType context_type; + +private: + int _find_or_create_display(Display *p_x11_display); + Error _create_context(GLDisplay &gl_display); + +public: + XVisualInfo get_vi(Display *p_display); + Error window_create(DisplayServer::WindowID p_window_id, ::Window p_window, Display *p_display, int p_width, int p_height); + void window_destroy(DisplayServer::WindowID p_window_id); + void window_resize(DisplayServer::WindowID p_window_id, int p_width, int p_height); + + void release_current(); + void make_current(); + void swap_buffers(); + + void window_make_current(DisplayServer::WindowID p_window_id); + + Error initialize(); + + void set_use_vsync(bool p_use); + bool is_using_vsync() const; + + GLManager_X11(const Vector2i &p_size, ContextType p_context_type); + ~GLManager_X11(); +}; + +#endif // GLES3_ENABLED +#endif // X11_ENABLED + +#endif // GL_MANAGER_X11_H diff --git a/platform/linuxbsd/x11/key_mapping_x11.cpp b/platform/linuxbsd/x11/key_mapping_x11.cpp new file mode 100644 index 0000000000..f774c99d99 --- /dev/null +++ b/platform/linuxbsd/x11/key_mapping_x11.cpp @@ -0,0 +1,2001 @@ +/*************************************************************************/ +/* key_mapping_x11.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "key_mapping_x11.h" + +/***** SCAN CODE CONVERSION ******/ + +struct _XTranslatePair { + KeySym keysym; + Key keycode; +}; + +static _XTranslatePair _xkeysym_to_keycode[] = { + // misc keys + + { XK_Escape, Key::ESCAPE }, + { XK_Tab, Key::TAB }, + { XK_ISO_Left_Tab, Key::BACKTAB }, + { XK_BackSpace, Key::BACKSPACE }, + { XK_Return, Key::ENTER }, + { XK_Insert, Key::INSERT }, + { XK_Delete, Key::KEY_DELETE }, + { XK_Clear, Key::KEY_DELETE }, + { XK_Pause, Key::PAUSE }, + { XK_Print, Key::PRINT }, + { XK_Home, Key::HOME }, + { XK_End, Key::END }, + { XK_Left, Key::LEFT }, + { XK_Up, Key::UP }, + { XK_Right, Key::RIGHT }, + { XK_Down, Key::DOWN }, + { XK_Prior, Key::PAGEUP }, + { XK_Next, Key::PAGEDOWN }, + { XK_Shift_L, Key::SHIFT }, + { XK_Shift_R, Key::SHIFT }, + { XK_Shift_Lock, Key::SHIFT }, + { XK_Control_L, Key::CTRL }, + { XK_Control_R, Key::CTRL }, + { XK_Meta_L, Key::META }, + { XK_Meta_R, Key::META }, + { XK_Alt_L, Key::ALT }, + { XK_Alt_R, Key::ALT }, + { XK_Caps_Lock, Key::CAPSLOCK }, + { XK_Num_Lock, Key::NUMLOCK }, + { XK_Scroll_Lock, Key::SCROLLLOCK }, + { XK_Super_L, Key::SUPER_L }, + { XK_Super_R, Key::SUPER_R }, + { XK_Menu, Key::MENU }, + { XK_Hyper_L, Key::HYPER_L }, + { XK_Hyper_R, Key::HYPER_R }, + { XK_Help, Key::HELP }, + { XK_KP_Space, Key::SPACE }, + { XK_KP_Tab, Key::TAB }, + { XK_KP_Enter, Key::KP_ENTER }, + { XK_Home, Key::HOME }, + { XK_Left, Key::LEFT }, + { XK_Up, Key::UP }, + { XK_Right, Key::RIGHT }, + { XK_Down, Key::DOWN }, + { XK_Prior, Key::PAGEUP }, + { XK_Next, Key::PAGEDOWN }, + { XK_End, Key::END }, + { XK_Begin, Key::CLEAR }, + { XK_Insert, Key::INSERT }, + { XK_Delete, Key::KEY_DELETE }, + //{ XK_KP_Equal, Key::EQUAL }, + //{ XK_KP_Separator, Key::COMMA }, + { XK_KP_Decimal, Key::KP_PERIOD }, + { XK_KP_Delete, Key::KP_PERIOD }, + { XK_KP_Multiply, Key::KP_MULTIPLY }, + { XK_KP_Divide, Key::KP_DIVIDE }, + { XK_KP_Subtract, Key::KP_SUBTRACT }, + { XK_KP_Add, Key::KP_ADD }, + { XK_KP_0, Key::KP_0 }, + { XK_KP_1, Key::KP_1 }, + { XK_KP_2, Key::KP_2 }, + { XK_KP_3, Key::KP_3 }, + { XK_KP_4, Key::KP_4 }, + { XK_KP_5, Key::KP_5 }, + { XK_KP_6, Key::KP_6 }, + { XK_KP_7, Key::KP_7 }, + { XK_KP_8, Key::KP_8 }, + { XK_KP_9, Key::KP_9 }, + // same keys but with numlock off + { XK_KP_Insert, Key::INSERT }, + { XK_KP_End, Key::END }, + { XK_KP_Down, Key::DOWN }, + { XK_KP_Page_Down, Key::PAGEDOWN }, + { XK_KP_Left, Key::LEFT }, + // X11 documents this (numpad 5) as "begin of line" but no toolkit + // seems to interpret it this way. + // On Windows this is emitting Key::Clear so for consistency it + // will be mapped to Key::Clear + { XK_KP_Begin, Key::CLEAR }, + { XK_KP_Right, Key::RIGHT }, + { XK_KP_Home, Key::HOME }, + { XK_KP_Up, Key::UP }, + { XK_KP_Page_Up, Key::PAGEUP }, + { XK_F1, Key::F1 }, + { XK_F2, Key::F2 }, + { XK_F3, Key::F3 }, + { XK_F4, Key::F4 }, + { XK_F5, Key::F5 }, + { XK_F6, Key::F6 }, + { XK_F7, Key::F7 }, + { XK_F8, Key::F8 }, + { XK_F9, Key::F9 }, + { XK_F10, Key::F10 }, + { XK_F11, Key::F11 }, + { XK_F12, Key::F12 }, + { XK_F13, Key::F13 }, + { XK_F14, Key::F14 }, + { XK_F15, Key::F15 }, + { XK_F16, Key::F16 }, + { XK_F17, Key::F17 }, + { XK_F18, Key::F18 }, + { XK_F19, Key::F19 }, + { XK_F20, Key::F20 }, + { XK_F21, Key::F21 }, + { XK_F22, Key::F22 }, + { XK_F23, Key::F23 }, + { XK_F24, Key::F24 }, + { XK_F25, Key::F25 }, + { XK_F26, Key::F26 }, + { XK_F27, Key::F27 }, + { XK_F28, Key::F28 }, + { XK_F29, Key::F29 }, + { XK_F30, Key::F30 }, + { XK_F31, Key::F31 }, + { XK_F32, Key::F32 }, + { XK_F33, Key::F33 }, + { XK_F34, Key::F34 }, + { XK_F35, Key::F35 }, + + // media keys + { XF86XK_Back, Key::BACK }, + { XF86XK_Forward, Key::FORWARD }, + { XF86XK_Stop, Key::STOP }, + { XF86XK_Refresh, Key::REFRESH }, + { XF86XK_Favorites, Key::FAVORITES }, + { XF86XK_AudioMedia, Key::LAUNCHMEDIA }, + { XF86XK_OpenURL, Key::OPENURL }, + { XF86XK_HomePage, Key::HOMEPAGE }, + { XF86XK_Search, Key::SEARCH }, + { XF86XK_AudioLowerVolume, Key::VOLUMEDOWN }, + { XF86XK_AudioMute, Key::VOLUMEMUTE }, + { XF86XK_AudioRaiseVolume, Key::VOLUMEUP }, + { XF86XK_AudioPlay, Key::MEDIAPLAY }, + { XF86XK_AudioStop, Key::MEDIASTOP }, + { XF86XK_AudioPrev, Key::MEDIAPREVIOUS }, + { XF86XK_AudioNext, Key::MEDIANEXT }, + { XF86XK_AudioRecord, Key::MEDIARECORD }, + + // launch keys + { XF86XK_Mail, Key::LAUNCHMAIL }, + { XF86XK_MyComputer, Key::LAUNCH0 }, + { XF86XK_Calculator, Key::LAUNCH1 }, + { XF86XK_Standby, Key::STANDBY }, + + { XF86XK_Launch0, Key::LAUNCH2 }, + { XF86XK_Launch1, Key::LAUNCH3 }, + { XF86XK_Launch2, Key::LAUNCH4 }, + { XF86XK_Launch3, Key::LAUNCH5 }, + { XF86XK_Launch4, Key::LAUNCH6 }, + { XF86XK_Launch5, Key::LAUNCH7 }, + { XF86XK_Launch6, Key::LAUNCH8 }, + { XF86XK_Launch7, Key::LAUNCH9 }, + { XF86XK_Launch8, Key::LAUNCHA }, + { XF86XK_Launch9, Key::LAUNCHB }, + { XF86XK_LaunchA, Key::LAUNCHC }, + { XF86XK_LaunchB, Key::LAUNCHD }, + { XF86XK_LaunchC, Key::LAUNCHE }, + { XF86XK_LaunchD, Key::LAUNCHF }, + + { 0, Key::NONE } +}; + +struct _TranslatePair { + Key keysym; + unsigned int keycode; +}; + +static _TranslatePair _scancode_to_keycode[] = { + { Key::ESCAPE, 0x09 }, + { Key::KEY_1, 0x0A }, + { Key::KEY_2, 0x0B }, + { Key::KEY_3, 0x0C }, + { Key::KEY_4, 0x0D }, + { Key::KEY_5, 0x0E }, + { Key::KEY_6, 0x0F }, + { Key::KEY_7, 0x10 }, + { Key::KEY_8, 0x11 }, + { Key::KEY_9, 0x12 }, + { Key::KEY_0, 0x13 }, + { Key::MINUS, 0x14 }, + { Key::EQUAL, 0x15 }, + { Key::BACKSPACE, 0x16 }, + { Key::TAB, 0x17 }, + { Key::Q, 0x18 }, + { Key::W, 0x19 }, + { Key::E, 0x1A }, + { Key::R, 0x1B }, + { Key::T, 0x1C }, + { Key::Y, 0x1D }, + { Key::U, 0x1E }, + { Key::I, 0x1F }, + { Key::O, 0x20 }, + { Key::P, 0x21 }, + { Key::BRACELEFT, 0x22 }, + { Key::BRACERIGHT, 0x23 }, + { Key::ENTER, 0x24 }, + { Key::CTRL, 0x25 }, + { Key::A, 0x26 }, + { Key::S, 0x27 }, + { Key::D, 0x28 }, + { Key::F, 0x29 }, + { Key::G, 0x2A }, + { Key::H, 0x2B }, + { Key::J, 0x2C }, + { Key::K, 0x2D }, + { Key::L, 0x2E }, + { Key::SEMICOLON, 0x2F }, + { Key::APOSTROPHE, 0x30 }, + { Key::QUOTELEFT, 0x31 }, + { Key::SHIFT, 0x32 }, + { Key::BACKSLASH, 0x33 }, + { Key::Z, 0x34 }, + { Key::X, 0x35 }, + { Key::C, 0x36 }, + { Key::V, 0x37 }, + { Key::B, 0x38 }, + { Key::N, 0x39 }, + { Key::M, 0x3A }, + { Key::COMMA, 0x3B }, + { Key::PERIOD, 0x3C }, + { Key::SLASH, 0x3D }, + { Key::SHIFT, 0x3E }, + { Key::KP_MULTIPLY, 0x3F }, + { Key::ALT, 0x40 }, + { Key::SPACE, 0x41 }, + { Key::CAPSLOCK, 0x42 }, + { Key::F1, 0x43 }, + { Key::F2, 0x44 }, + { Key::F3, 0x45 }, + { Key::F4, 0x46 }, + { Key::F5, 0x47 }, + { Key::F6, 0x48 }, + { Key::F7, 0x49 }, + { Key::F8, 0x4A }, + { Key::F9, 0x4B }, + { Key::F10, 0x4C }, + { Key::NUMLOCK, 0x4D }, + { Key::SCROLLLOCK, 0x4E }, + { Key::KP_7, 0x4F }, + { Key::KP_8, 0x50 }, + { Key::KP_9, 0x51 }, + { Key::KP_SUBTRACT, 0x52 }, + { Key::KP_4, 0x53 }, + { Key::KP_5, 0x54 }, + { Key::KP_6, 0x55 }, + { Key::KP_ADD, 0x56 }, + { Key::KP_1, 0x57 }, + { Key::KP_2, 0x58 }, + { Key::KP_3, 0x59 }, + { Key::KP_0, 0x5A }, + { Key::KP_PERIOD, 0x5B }, + //{ Key::???, 0x5E }, //NON US BACKSLASH + { Key::F11, 0x5F }, + { Key::F12, 0x60 }, + { Key::KP_ENTER, 0x68 }, + { Key::CTRL, 0x69 }, + { Key::KP_DIVIDE, 0x6A }, + { Key::PRINT, 0x6B }, + { Key::ALT, 0x6C }, + { Key::ENTER, 0x6D }, + { Key::HOME, 0x6E }, + { Key::UP, 0x6F }, + { Key::PAGEUP, 0x70 }, + { Key::LEFT, 0x71 }, + { Key::RIGHT, 0x72 }, + { Key::END, 0x73 }, + { Key::DOWN, 0x74 }, + { Key::PAGEDOWN, 0x75 }, + { Key::INSERT, 0x76 }, + { Key::KEY_DELETE, 0x77 }, + { Key::VOLUMEMUTE, 0x79 }, + { Key::VOLUMEDOWN, 0x7A }, + { Key::VOLUMEUP, 0x7B }, + { Key::PAUSE, 0x7F }, + { Key::SUPER_L, 0x85 }, + { Key::SUPER_R, 0x86 }, + { Key::MENU, 0x87 }, + { Key::F13, 0xBF }, + { Key::F14, 0xC0 }, + { Key::F15, 0xC1 }, + { Key::F16, 0xC2 }, + { Key::F17, 0xC3 }, + { Key::F18, 0xC4 }, + { Key::F19, 0xC5 }, + { Key::F20, 0xC6 }, + { Key::F21, 0xC7 }, + { Key::F22, 0xC8 }, + { Key::F23, 0xC9 }, + { Key::F24, 0xCA }, + { Key::F25, 0xCB }, + { Key::F26, 0xCC }, + { Key::F27, 0xCD }, + { Key::F28, 0xCE }, + { Key::F29, 0xCF }, + { Key::F30, 0xD0 }, + { Key::F31, 0xD1 }, + { Key::F32, 0xD2 }, + { Key::F33, 0xD3 }, + { Key::F34, 0xD4 }, + { Key::F35, 0xD5 }, + { Key::UNKNOWN, 0 } +}; + +Key KeyMappingX11::get_scancode(unsigned int p_code) { + Key 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; + } + } + + return keycode; +} + +unsigned int KeyMappingX11::get_xlibcode(Key p_keysym) { + unsigned int code = 0; + for (int i = 0; _scancode_to_keycode[i].keysym != Key::UNKNOWN; i++) { + if (_scancode_to_keycode[i].keysym == p_keysym) { + code = _scancode_to_keycode[i].keycode; + break; + } + } + + return code; +} + +Key KeyMappingX11::get_keycode(KeySym p_keysym) { + // kinda bruteforce.. could optimize. + + if (p_keysym < 0x100) { // Latin 1, maps 1-1 + return (Key)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) { + return _xkeysym_to_keycode[idx].keycode; + } + } + + return Key::NONE; +} + +KeySym KeyMappingX11::get_keysym(Key p_code) { + // kinda bruteforce.. could optimize. + + if (p_code < Key::END_LATIN1) { // Latin 1, maps 1-1 + return (KeySym)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) { + return _xkeysym_to_keycode[idx].keysym; + } + } + + return (KeySym)Key::NONE; +} + +/***** UNICODE CONVERSION ******/ + +// Tables taken from FOX toolkit + +struct _XTranslateUnicodePair { + KeySym keysym; + unsigned int unicode; +}; + +enum { + _KEYSYM_MAX = 759 +}; + +static _XTranslateUnicodePair _xkeysym_to_unicode[_KEYSYM_MAX] = { + { 0x01A1, 0x0104 }, + { 0x01A2, 0x02D8 }, + { 0x01A3, 0x0141 }, + { 0x01A5, 0x013D }, + { 0x01A6, 0x015A }, + { 0x01A9, 0x0160 }, + { 0x01AA, 0x015E }, + { 0x01AB, 0x0164 }, + { 0x01AC, 0x0179 }, + { 0x01AE, 0x017D }, + { 0x01AF, 0x017B }, + { 0x01B1, 0x0105 }, + { 0x01B2, 0x02DB }, + { 0x01B3, 0x0142 }, + { 0x01B5, 0x013E }, + { 0x01B6, 0x015B }, + { 0x01B7, 0x02C7 }, + { 0x01B9, 0x0161 }, + { 0x01BA, 0x015F }, + { 0x01BB, 0x0165 }, + { 0x01BC, 0x017A }, + { 0x01BD, 0x02DD }, + { 0x01BE, 0x017E }, + { 0x01BF, 0x017C }, + { 0x01C0, 0x0154 }, + { 0x01C3, 0x0102 }, + { 0x01C5, 0x0139 }, + { 0x01C6, 0x0106 }, + { 0x01C8, 0x010C }, + { 0x01CA, 0x0118 }, + { 0x01CC, 0x011A }, + { 0x01CF, 0x010E }, + { 0x01D0, 0x0110 }, + { 0x01D1, 0x0143 }, + { 0x01D2, 0x0147 }, + { 0x01D5, 0x0150 }, + { 0x01D8, 0x0158 }, + { 0x01D9, 0x016E }, + { 0x01DB, 0x0170 }, + { 0x01DE, 0x0162 }, + { 0x01E0, 0x0155 }, + { 0x01E3, 0x0103 }, + { 0x01E5, 0x013A }, + { 0x01E6, 0x0107 }, + { 0x01E8, 0x010D }, + { 0x01EA, 0x0119 }, + { 0x01EC, 0x011B }, + { 0x01EF, 0x010F }, + { 0x01F0, 0x0111 }, + { 0x01F1, 0x0144 }, + { 0x01F2, 0x0148 }, + { 0x01F5, 0x0151 }, + { 0x01F8, 0x0159 }, + { 0x01F9, 0x016F }, + { 0x01FB, 0x0171 }, + { 0x01FE, 0x0163 }, + { 0x01FF, 0x02D9 }, + { 0x02A1, 0x0126 }, + { 0x02A6, 0x0124 }, + { 0x02A9, 0x0130 }, + { 0x02AB, 0x011E }, + { 0x02AC, 0x0134 }, + { 0x02B1, 0x0127 }, + { 0x02B6, 0x0125 }, + { 0x02B9, 0x0131 }, + { 0x02BB, 0x011F }, + { 0x02BC, 0x0135 }, + { 0x02C5, 0x010A }, + { 0x02C6, 0x0108 }, + { 0x02D5, 0x0120 }, + { 0x02D8, 0x011C }, + { 0x02DD, 0x016C }, + { 0x02DE, 0x015C }, + { 0x02E5, 0x010B }, + { 0x02E6, 0x0109 }, + { 0x02F5, 0x0121 }, + { 0x02F8, 0x011D }, + { 0x02FD, 0x016D }, + { 0x02FE, 0x015D }, + { 0x03A2, 0x0138 }, + { 0x03A3, 0x0156 }, + { 0x03A5, 0x0128 }, + { 0x03A6, 0x013B }, + { 0x03AA, 0x0112 }, + { 0x03AB, 0x0122 }, + { 0x03AC, 0x0166 }, + { 0x03B3, 0x0157 }, + { 0x03B5, 0x0129 }, + { 0x03B6, 0x013C }, + { 0x03BA, 0x0113 }, + { 0x03BB, 0x0123 }, + { 0x03BC, 0x0167 }, + { 0x03BD, 0x014A }, + { 0x03BF, 0x014B }, + { 0x03C0, 0x0100 }, + { 0x03C7, 0x012E }, + { 0x03CC, 0x0116 }, + { 0x03CF, 0x012A }, + { 0x03D1, 0x0145 }, + { 0x03D2, 0x014C }, + { 0x03D3, 0x0136 }, + { 0x03D9, 0x0172 }, + { 0x03DD, 0x0168 }, + { 0x03DE, 0x016A }, + { 0x03E0, 0x0101 }, + { 0x03E7, 0x012F }, + { 0x03EC, 0x0117 }, + { 0x03EF, 0x012B }, + { 0x03F1, 0x0146 }, + { 0x03F2, 0x014D }, + { 0x03F3, 0x0137 }, + { 0x03F9, 0x0173 }, + { 0x03FD, 0x0169 }, + { 0x03FE, 0x016B }, + { 0x047E, 0x203E }, + { 0x04A1, 0x3002 }, + { 0x04A2, 0x300C }, + { 0x04A3, 0x300D }, + { 0x04A4, 0x3001 }, + { 0x04A5, 0x30FB }, + { 0x04A6, 0x30F2 }, + { 0x04A7, 0x30A1 }, + { 0x04A8, 0x30A3 }, + { 0x04A9, 0x30A5 }, + { 0x04AA, 0x30A7 }, + { 0x04AB, 0x30A9 }, + { 0x04AC, 0x30E3 }, + { 0x04AD, 0x30E5 }, + { 0x04AE, 0x30E7 }, + { 0x04AF, 0x30C3 }, + { 0x04B0, 0x30FC }, + { 0x04B1, 0x30A2 }, + { 0x04B2, 0x30A4 }, + { 0x04B3, 0x30A6 }, + { 0x04B4, 0x30A8 }, + { 0x04B5, 0x30AA }, + { 0x04B6, 0x30AB }, + { 0x04B7, 0x30AD }, + { 0x04B8, 0x30AF }, + { 0x04B9, 0x30B1 }, + { 0x04BA, 0x30B3 }, + { 0x04BB, 0x30B5 }, + { 0x04BC, 0x30B7 }, + { 0x04BD, 0x30B9 }, + { 0x04BE, 0x30BB }, + { 0x04BF, 0x30BD }, + { 0x04C0, 0x30BF }, + { 0x04C1, 0x30C1 }, + { 0x04C2, 0x30C4 }, + { 0x04C3, 0x30C6 }, + { 0x04C4, 0x30C8 }, + { 0x04C5, 0x30CA }, + { 0x04C6, 0x30CB }, + { 0x04C7, 0x30CC }, + { 0x04C8, 0x30CD }, + { 0x04C9, 0x30CE }, + { 0x04CA, 0x30CF }, + { 0x04CB, 0x30D2 }, + { 0x04CC, 0x30D5 }, + { 0x04CD, 0x30D8 }, + { 0x04CE, 0x30DB }, + { 0x04CF, 0x30DE }, + { 0x04D0, 0x30DF }, + { 0x04D1, 0x30E0 }, + { 0x04D2, 0x30E1 }, + { 0x04D3, 0x30E2 }, + { 0x04D4, 0x30E4 }, + { 0x04D5, 0x30E6 }, + { 0x04D6, 0x30E8 }, + { 0x04D7, 0x30E9 }, + { 0x04D8, 0x30EA }, + { 0x04D9, 0x30EB }, + { 0x04DA, 0x30EC }, + { 0x04DB, 0x30ED }, + { 0x04DC, 0x30EF }, + { 0x04DD, 0x30F3 }, + { 0x04DE, 0x309B }, + { 0x04DF, 0x309C }, + { 0x05AC, 0x060C }, + { 0x05BB, 0x061B }, + { 0x05BF, 0x061F }, + { 0x05C1, 0x0621 }, + { 0x05C2, 0x0622 }, + { 0x05C3, 0x0623 }, + { 0x05C4, 0x0624 }, + { 0x05C5, 0x0625 }, + { 0x05C6, 0x0626 }, + { 0x05C7, 0x0627 }, + { 0x05C8, 0x0628 }, + { 0x05C9, 0x0629 }, + { 0x05CA, 0x062A }, + { 0x05CB, 0x062B }, + { 0x05CC, 0x062C }, + { 0x05CD, 0x062D }, + { 0x05CE, 0x062E }, + { 0x05CF, 0x062F }, + { 0x05D0, 0x0630 }, + { 0x05D1, 0x0631 }, + { 0x05D2, 0x0632 }, + { 0x05D3, 0x0633 }, + { 0x05D4, 0x0634 }, + { 0x05D5, 0x0635 }, + { 0x05D6, 0x0636 }, + { 0x05D7, 0x0637 }, + { 0x05D8, 0x0638 }, + { 0x05D9, 0x0639 }, + { 0x05DA, 0x063A }, + { 0x05E0, 0x0640 }, + { 0x05E1, 0x0641 }, + { 0x05E2, 0x0642 }, + { 0x05E3, 0x0643 }, + { 0x05E4, 0x0644 }, + { 0x05E5, 0x0645 }, + { 0x05E6, 0x0646 }, + { 0x05E7, 0x0647 }, + { 0x05E8, 0x0648 }, + { 0x05E9, 0x0649 }, + { 0x05EA, 0x064A }, + { 0x05EB, 0x064B }, + { 0x05EC, 0x064C }, + { 0x05ED, 0x064D }, + { 0x05EE, 0x064E }, + { 0x05EF, 0x064F }, + { 0x05F0, 0x0650 }, + { 0x05F1, 0x0651 }, + { 0x05F2, 0x0652 }, + { 0x06A1, 0x0452 }, + { 0x06A2, 0x0453 }, + { 0x06A3, 0x0451 }, + { 0x06A4, 0x0454 }, + { 0x06A5, 0x0455 }, + { 0x06A6, 0x0456 }, + { 0x06A7, 0x0457 }, + { 0x06A8, 0x0458 }, + { 0x06A9, 0x0459 }, + { 0x06AA, 0x045A }, + { 0x06AB, 0x045B }, + { 0x06AC, 0x045C }, + { 0x06AE, 0x045E }, + { 0x06AF, 0x045F }, + { 0x06B0, 0x2116 }, + { 0x06B1, 0x0402 }, + { 0x06B2, 0x0403 }, + { 0x06B3, 0x0401 }, + { 0x06B4, 0x0404 }, + { 0x06B5, 0x0405 }, + { 0x06B6, 0x0406 }, + { 0x06B7, 0x0407 }, + { 0x06B8, 0x0408 }, + { 0x06B9, 0x0409 }, + { 0x06BA, 0x040A }, + { 0x06BB, 0x040B }, + { 0x06BC, 0x040C }, + { 0x06BE, 0x040E }, + { 0x06BF, 0x040F }, + { 0x06C0, 0x044E }, + { 0x06C1, 0x0430 }, + { 0x06C2, 0x0431 }, + { 0x06C3, 0x0446 }, + { 0x06C4, 0x0434 }, + { 0x06C5, 0x0435 }, + { 0x06C6, 0x0444 }, + { 0x06C7, 0x0433 }, + { 0x06C8, 0x0445 }, + { 0x06C9, 0x0438 }, + { 0x06CA, 0x0439 }, + { 0x06CB, 0x043A }, + { 0x06CC, 0x043B }, + { 0x06CD, 0x043C }, + { 0x06CE, 0x043D }, + { 0x06CF, 0x043E }, + { 0x06D0, 0x043F }, + { 0x06D1, 0x044F }, + { 0x06D2, 0x0440 }, + { 0x06D3, 0x0441 }, + { 0x06D4, 0x0442 }, + { 0x06D5, 0x0443 }, + { 0x06D6, 0x0436 }, + { 0x06D7, 0x0432 }, + { 0x06D8, 0x044C }, + { 0x06D9, 0x044B }, + { 0x06DA, 0x0437 }, + { 0x06DB, 0x0448 }, + { 0x06DC, 0x044D }, + { 0x06DD, 0x0449 }, + { 0x06DE, 0x0447 }, + { 0x06DF, 0x044A }, + { 0x06E0, 0x042E }, + { 0x06E1, 0x0410 }, + { 0x06E2, 0x0411 }, + { 0x06E3, 0x0426 }, + { 0x06E4, 0x0414 }, + { 0x06E5, 0x0415 }, + { 0x06E6, 0x0424 }, + { 0x06E7, 0x0413 }, + { 0x06E8, 0x0425 }, + { 0x06E9, 0x0418 }, + { 0x06EA, 0x0419 }, + { 0x06EB, 0x041A }, + { 0x06EC, 0x041B }, + { 0x06ED, 0x041C }, + { 0x06EE, 0x041D }, + { 0x06EF, 0x041E }, + { 0x06F0, 0x041F }, + { 0x06F1, 0x042F }, + { 0x06F2, 0x0420 }, + { 0x06F3, 0x0421 }, + { 0x06F4, 0x0422 }, + { 0x06F5, 0x0423 }, + { 0x06F6, 0x0416 }, + { 0x06F7, 0x0412 }, + { 0x06F8, 0x042C }, + { 0x06F9, 0x042B }, + { 0x06FA, 0x0417 }, + { 0x06FB, 0x0428 }, + { 0x06FC, 0x042D }, + { 0x06FD, 0x0429 }, + { 0x06FE, 0x0427 }, + { 0x06FF, 0x042A }, + { 0x07A1, 0x0386 }, + { 0x07A2, 0x0388 }, + { 0x07A3, 0x0389 }, + { 0x07A4, 0x038A }, + { 0x07A5, 0x03AA }, + { 0x07A7, 0x038C }, + { 0x07A8, 0x038E }, + { 0x07A9, 0x03AB }, + { 0x07AB, 0x038F }, + { 0x07AE, 0x0385 }, + { 0x07AF, 0x2015 }, + { 0x07B1, 0x03AC }, + { 0x07B2, 0x03AD }, + { 0x07B3, 0x03AE }, + { 0x07B4, 0x03AF }, + { 0x07B5, 0x03CA }, + { 0x07B6, 0x0390 }, + { 0x07B7, 0x03CC }, + { 0x07B8, 0x03CD }, + { 0x07B9, 0x03CB }, + { 0x07BA, 0x03B0 }, + { 0x07BB, 0x03CE }, + { 0x07C1, 0x0391 }, + { 0x07C2, 0x0392 }, + { 0x07C3, 0x0393 }, + { 0x07C4, 0x0394 }, + { 0x07C5, 0x0395 }, + { 0x07C6, 0x0396 }, + { 0x07C7, 0x0397 }, + { 0x07C8, 0x0398 }, + { 0x07C9, 0x0399 }, + { 0x07CA, 0x039A }, + { 0x07CB, 0x039B }, + { 0x07CC, 0x039C }, + { 0x07CD, 0x039D }, + { 0x07CE, 0x039E }, + { 0x07CF, 0x039F }, + { 0x07D0, 0x03A0 }, + { 0x07D1, 0x03A1 }, + { 0x07D2, 0x03A3 }, + { 0x07D4, 0x03A4 }, + { 0x07D5, 0x03A5 }, + { 0x07D6, 0x03A6 }, + { 0x07D7, 0x03A7 }, + { 0x07D8, 0x03A8 }, + { 0x07D9, 0x03A9 }, + { 0x07E1, 0x03B1 }, + { 0x07E2, 0x03B2 }, + { 0x07E3, 0x03B3 }, + { 0x07E4, 0x03B4 }, + { 0x07E5, 0x03B5 }, + { 0x07E6, 0x03B6 }, + { 0x07E7, 0x03B7 }, + { 0x07E8, 0x03B8 }, + { 0x07E9, 0x03B9 }, + { 0x07EA, 0x03BA }, + { 0x07EB, 0x03BB }, + { 0x07EC, 0x03BC }, + { 0x07ED, 0x03BD }, + { 0x07EE, 0x03BE }, + { 0x07EF, 0x03BF }, + { 0x07F0, 0x03C0 }, + { 0x07F1, 0x03C1 }, + { 0x07F2, 0x03C3 }, + { 0x07F3, 0x03C2 }, + { 0x07F4, 0x03C4 }, + { 0x07F5, 0x03C5 }, + { 0x07F6, 0x03C6 }, + { 0x07F7, 0x03C7 }, + { 0x07F8, 0x03C8 }, + { 0x07F9, 0x03C9 }, + { 0x08A1, 0x23B7 }, + { 0x08A2, 0x250C }, + { 0x08A3, 0x2500 }, + { 0x08A4, 0x2320 }, + { 0x08A5, 0x2321 }, + { 0x08A6, 0x2502 }, + { 0x08A7, 0x23A1 }, + { 0x08A8, 0x23A3 }, + { 0x08A9, 0x23A4 }, + { 0x08AA, 0x23A6 }, + { 0x08AB, 0x239B }, + { 0x08AC, 0x239D }, + { 0x08AD, 0x239E }, + { 0x08AE, 0x23A0 }, + { 0x08AF, 0x23A8 }, + { 0x08B0, 0x23AC }, + { 0x08BC, 0x2264 }, + { 0x08BD, 0x2260 }, + { 0x08BE, 0x2265 }, + { 0x08BF, 0x222B }, + { 0x08C0, 0x2234 }, + { 0x08C1, 0x221D }, + { 0x08C2, 0x221E }, + { 0x08C5, 0x2207 }, + { 0x08C8, 0x223C }, + { 0x08C9, 0x2243 }, + { 0x08CD, 0x21D4 }, + { 0x08CE, 0x21D2 }, + { 0x08CF, 0x2261 }, + { 0x08D6, 0x221A }, + { 0x08DA, 0x2282 }, + { 0x08DB, 0x2283 }, + { 0x08DC, 0x2229 }, + { 0x08DD, 0x222A }, + { 0x08DE, 0x2227 }, + { 0x08DF, 0x2228 }, + { 0x08EF, 0x2202 }, + { 0x08F6, 0x0192 }, + { 0x08FB, 0x2190 }, + { 0x08FC, 0x2191 }, + { 0x08FD, 0x2192 }, + { 0x08FE, 0x2193 }, + { 0x09E0, 0x25C6 }, + { 0x09E1, 0x2592 }, + { 0x09E2, 0x2409 }, + { 0x09E3, 0x240C }, + { 0x09E4, 0x240D }, + { 0x09E5, 0x240A }, + { 0x09E8, 0x2424 }, + { 0x09E9, 0x240B }, + { 0x09EA, 0x2518 }, + { 0x09EB, 0x2510 }, + { 0x09EC, 0x250C }, + { 0x09ED, 0x2514 }, + { 0x09EE, 0x253C }, + { 0x09EF, 0x23BA }, + { 0x09F0, 0x23BB }, + { 0x09F1, 0x2500 }, + { 0x09F2, 0x23BC }, + { 0x09F3, 0x23BD }, + { 0x09F4, 0x251C }, + { 0x09F5, 0x2524 }, + { 0x09F6, 0x2534 }, + { 0x09F7, 0x252C }, + { 0x09F8, 0x2502 }, + { 0x0AA1, 0x2003 }, + { 0x0AA2, 0x2002 }, + { 0x0AA3, 0x2004 }, + { 0x0AA4, 0x2005 }, + { 0x0AA5, 0x2007 }, + { 0x0AA6, 0x2008 }, + { 0x0AA7, 0x2009 }, + { 0x0AA8, 0x200A }, + { 0x0AA9, 0x2014 }, + { 0x0AAA, 0x2013 }, + { 0x0AAE, 0x2026 }, + { 0x0AAF, 0x2025 }, + { 0x0AB0, 0x2153 }, + { 0x0AB1, 0x2154 }, + { 0x0AB2, 0x2155 }, + { 0x0AB3, 0x2156 }, + { 0x0AB4, 0x2157 }, + { 0x0AB5, 0x2158 }, + { 0x0AB6, 0x2159 }, + { 0x0AB7, 0x215A }, + { 0x0AB8, 0x2105 }, + { 0x0ABB, 0x2012 }, + { 0x0ABC, 0x2329 }, + { 0x0ABE, 0x232A }, + { 0x0AC3, 0x215B }, + { 0x0AC4, 0x215C }, + { 0x0AC5, 0x215D }, + { 0x0AC6, 0x215E }, + { 0x0AC9, 0x2122 }, + { 0x0ACA, 0x2613 }, + { 0x0ACC, 0x25C1 }, + { 0x0ACD, 0x25B7 }, + { 0x0ACE, 0x25CB }, + { 0x0ACF, 0x25AF }, + { 0x0AD0, 0x2018 }, + { 0x0AD1, 0x2019 }, + { 0x0AD2, 0x201C }, + { 0x0AD3, 0x201D }, + { 0x0AD4, 0x211E }, + { 0x0AD6, 0x2032 }, + { 0x0AD7, 0x2033 }, + { 0x0AD9, 0x271D }, + { 0x0ADB, 0x25AC }, + { 0x0ADC, 0x25C0 }, + { 0x0ADD, 0x25B6 }, + { 0x0ADE, 0x25CF }, + { 0x0ADF, 0x25AE }, + { 0x0AE0, 0x25E6 }, + { 0x0AE1, 0x25AB }, + { 0x0AE2, 0x25AD }, + { 0x0AE3, 0x25B3 }, + { 0x0AE4, 0x25BD }, + { 0x0AE5, 0x2606 }, + { 0x0AE6, 0x2022 }, + { 0x0AE7, 0x25AA }, + { 0x0AE8, 0x25B2 }, + { 0x0AE9, 0x25BC }, + { 0x0AEA, 0x261C }, + { 0x0AEB, 0x261E }, + { 0x0AEC, 0x2663 }, + { 0x0AED, 0x2666 }, + { 0x0AEE, 0x2665 }, + { 0x0AF0, 0x2720 }, + { 0x0AF1, 0x2020 }, + { 0x0AF2, 0x2021 }, + { 0x0AF3, 0x2713 }, + { 0x0AF4, 0x2717 }, + { 0x0AF5, 0x266F }, + { 0x0AF6, 0x266D }, + { 0x0AF7, 0x2642 }, + { 0x0AF8, 0x2640 }, + { 0x0AF9, 0x260E }, + { 0x0AFA, 0x2315 }, + { 0x0AFB, 0x2117 }, + { 0x0AFC, 0x2038 }, + { 0x0AFD, 0x201A }, + { 0x0AFE, 0x201E }, + { 0x0BA3, 0x003C }, + { 0x0BA6, 0x003E }, + { 0x0BA8, 0x2228 }, + { 0x0BA9, 0x2227 }, + { 0x0BC0, 0x00AF }, + { 0x0BC2, 0x22A5 }, + { 0x0BC3, 0x2229 }, + { 0x0BC4, 0x230A }, + { 0x0BC6, 0x005F }, + { 0x0BCA, 0x2218 }, + { 0x0BCC, 0x2395 }, + { 0x0BCE, 0x22A4 }, + { 0x0BCF, 0x25CB }, + { 0x0BD3, 0x2308 }, + { 0x0BD6, 0x222A }, + { 0x0BD8, 0x2283 }, + { 0x0BDA, 0x2282 }, + { 0x0BDC, 0x22A2 }, + { 0x0BFC, 0x22A3 }, + { 0x0CDF, 0x2017 }, + { 0x0CE0, 0x05D0 }, + { 0x0CE1, 0x05D1 }, + { 0x0CE2, 0x05D2 }, + { 0x0CE3, 0x05D3 }, + { 0x0CE4, 0x05D4 }, + { 0x0CE5, 0x05D5 }, + { 0x0CE6, 0x05D6 }, + { 0x0CE7, 0x05D7 }, + { 0x0CE8, 0x05D8 }, + { 0x0CE9, 0x05D9 }, + { 0x0CEA, 0x05DA }, + { 0x0CEB, 0x05DB }, + { 0x0CEC, 0x05DC }, + { 0x0CED, 0x05DD }, + { 0x0CEE, 0x05DE }, + { 0x0CEF, 0x05DF }, + { 0x0CF0, 0x05E0 }, + { 0x0CF1, 0x05E1 }, + { 0x0CF2, 0x05E2 }, + { 0x0CF3, 0x05E3 }, + { 0x0CF4, 0x05E4 }, + { 0x0CF5, 0x05E5 }, + { 0x0CF6, 0x05E6 }, + { 0x0CF7, 0x05E7 }, + { 0x0CF8, 0x05E8 }, + { 0x0CF9, 0x05E9 }, + { 0x0CFA, 0x05EA }, + { 0x0DA1, 0x0E01 }, + { 0x0DA2, 0x0E02 }, + { 0x0DA3, 0x0E03 }, + { 0x0DA4, 0x0E04 }, + { 0x0DA5, 0x0E05 }, + { 0x0DA6, 0x0E06 }, + { 0x0DA7, 0x0E07 }, + { 0x0DA8, 0x0E08 }, + { 0x0DA9, 0x0E09 }, + { 0x0DAA, 0x0E0A }, + { 0x0DAB, 0x0E0B }, + { 0x0DAC, 0x0E0C }, + { 0x0DAD, 0x0E0D }, + { 0x0DAE, 0x0E0E }, + { 0x0DAF, 0x0E0F }, + { 0x0DB0, 0x0E10 }, + { 0x0DB1, 0x0E11 }, + { 0x0DB2, 0x0E12 }, + { 0x0DB3, 0x0E13 }, + { 0x0DB4, 0x0E14 }, + { 0x0DB5, 0x0E15 }, + { 0x0DB6, 0x0E16 }, + { 0x0DB7, 0x0E17 }, + { 0x0DB8, 0x0E18 }, + { 0x0DB9, 0x0E19 }, + { 0x0DBA, 0x0E1A }, + { 0x0DBB, 0x0E1B }, + { 0x0DBC, 0x0E1C }, + { 0x0DBD, 0x0E1D }, + { 0x0DBE, 0x0E1E }, + { 0x0DBF, 0x0E1F }, + { 0x0DC0, 0x0E20 }, + { 0x0DC1, 0x0E21 }, + { 0x0DC2, 0x0E22 }, + { 0x0DC3, 0x0E23 }, + { 0x0DC4, 0x0E24 }, + { 0x0DC5, 0x0E25 }, + { 0x0DC6, 0x0E26 }, + { 0x0DC7, 0x0E27 }, + { 0x0DC8, 0x0E28 }, + { 0x0DC9, 0x0E29 }, + { 0x0DCA, 0x0E2A }, + { 0x0DCB, 0x0E2B }, + { 0x0DCC, 0x0E2C }, + { 0x0DCD, 0x0E2D }, + { 0x0DCE, 0x0E2E }, + { 0x0DCF, 0x0E2F }, + { 0x0DD0, 0x0E30 }, + { 0x0DD1, 0x0E31 }, + { 0x0DD2, 0x0E32 }, + { 0x0DD3, 0x0E33 }, + { 0x0DD4, 0x0E34 }, + { 0x0DD5, 0x0E35 }, + { 0x0DD6, 0x0E36 }, + { 0x0DD7, 0x0E37 }, + { 0x0DD8, 0x0E38 }, + { 0x0DD9, 0x0E39 }, + { 0x0DDA, 0x0E3A }, + { 0x0DDF, 0x0E3F }, + { 0x0DE0, 0x0E40 }, + { 0x0DE1, 0x0E41 }, + { 0x0DE2, 0x0E42 }, + { 0x0DE3, 0x0E43 }, + { 0x0DE4, 0x0E44 }, + { 0x0DE5, 0x0E45 }, + { 0x0DE6, 0x0E46 }, + { 0x0DE7, 0x0E47 }, + { 0x0DE8, 0x0E48 }, + { 0x0DE9, 0x0E49 }, + { 0x0DEA, 0x0E4A }, + { 0x0DEB, 0x0E4B }, + { 0x0DEC, 0x0E4C }, + { 0x0DED, 0x0E4D }, + { 0x0DF0, 0x0E50 }, + { 0x0DF1, 0x0E51 }, + { 0x0DF2, 0x0E52 }, + { 0x0DF3, 0x0E53 }, + { 0x0DF4, 0x0E54 }, + { 0x0DF5, 0x0E55 }, + { 0x0DF6, 0x0E56 }, + { 0x0DF7, 0x0E57 }, + { 0x0DF8, 0x0E58 }, + { 0x0DF9, 0x0E59 }, + { 0x0EA1, 0x3131 }, + { 0x0EA2, 0x3132 }, + { 0x0EA3, 0x3133 }, + { 0x0EA4, 0x3134 }, + { 0x0EA5, 0x3135 }, + { 0x0EA6, 0x3136 }, + { 0x0EA7, 0x3137 }, + { 0x0EA8, 0x3138 }, + { 0x0EA9, 0x3139 }, + { 0x0EAA, 0x313A }, + { 0x0EAB, 0x313B }, + { 0x0EAC, 0x313C }, + { 0x0EAD, 0x313D }, + { 0x0EAE, 0x313E }, + { 0x0EAF, 0x313F }, + { 0x0EB0, 0x3140 }, + { 0x0EB1, 0x3141 }, + { 0x0EB2, 0x3142 }, + { 0x0EB3, 0x3143 }, + { 0x0EB4, 0x3144 }, + { 0x0EB5, 0x3145 }, + { 0x0EB6, 0x3146 }, + { 0x0EB7, 0x3147 }, + { 0x0EB8, 0x3148 }, + { 0x0EB9, 0x3149 }, + { 0x0EBA, 0x314A }, + { 0x0EBB, 0x314B }, + { 0x0EBC, 0x314C }, + { 0x0EBD, 0x314D }, + { 0x0EBE, 0x314E }, + { 0x0EBF, 0x314F }, + { 0x0EC0, 0x3150 }, + { 0x0EC1, 0x3151 }, + { 0x0EC2, 0x3152 }, + { 0x0EC3, 0x3153 }, + { 0x0EC4, 0x3154 }, + { 0x0EC5, 0x3155 }, + { 0x0EC6, 0x3156 }, + { 0x0EC7, 0x3157 }, + { 0x0EC8, 0x3158 }, + { 0x0EC9, 0x3159 }, + { 0x0ECA, 0x315A }, + { 0x0ECB, 0x315B }, + { 0x0ECC, 0x315C }, + { 0x0ECD, 0x315D }, + { 0x0ECE, 0x315E }, + { 0x0ECF, 0x315F }, + { 0x0ED0, 0x3160 }, + { 0x0ED1, 0x3161 }, + { 0x0ED2, 0x3162 }, + { 0x0ED3, 0x3163 }, + { 0x0ED4, 0x11A8 }, + { 0x0ED5, 0x11A9 }, + { 0x0ED6, 0x11AA }, + { 0x0ED7, 0x11AB }, + { 0x0ED8, 0x11AC }, + { 0x0ED9, 0x11AD }, + { 0x0EDA, 0x11AE }, + { 0x0EDB, 0x11AF }, + { 0x0EDC, 0x11B0 }, + { 0x0EDD, 0x11B1 }, + { 0x0EDE, 0x11B2 }, + { 0x0EDF, 0x11B3 }, + { 0x0EE0, 0x11B4 }, + { 0x0EE1, 0x11B5 }, + { 0x0EE2, 0x11B6 }, + { 0x0EE3, 0x11B7 }, + { 0x0EE4, 0x11B8 }, + { 0x0EE5, 0x11B9 }, + { 0x0EE6, 0x11BA }, + { 0x0EE7, 0x11BB }, + { 0x0EE8, 0x11BC }, + { 0x0EE9, 0x11BD }, + { 0x0EEA, 0x11BE }, + { 0x0EEB, 0x11BF }, + { 0x0EEC, 0x11C0 }, + { 0x0EED, 0x11C1 }, + { 0x0EEE, 0x11C2 }, + { 0x0EEF, 0x316D }, + { 0x0EF0, 0x3171 }, + { 0x0EF1, 0x3178 }, + { 0x0EF2, 0x317F }, + { 0x0EF3, 0x3181 }, + { 0x0EF4, 0x3184 }, + { 0x0EF5, 0x3186 }, + { 0x0EF6, 0x318D }, + { 0x0EF7, 0x318E }, + { 0x0EF8, 0x11EB }, + { 0x0EF9, 0x11F0 }, + { 0x0EFA, 0x11F9 }, + { 0x0EFF, 0x20A9 }, + { 0x13A4, 0x20AC }, + { 0x13BC, 0x0152 }, + { 0x13BD, 0x0153 }, + { 0x13BE, 0x0178 }, + { 0x20AC, 0x20AC }, +}; + +unsigned int KeyMappingX11::get_unicode_from_keysym(KeySym p_keysym) { + /* Latin-1 */ + if (p_keysym >= 0x20 && p_keysym <= 0x7e) { + return p_keysym; + } + if (p_keysym >= 0xa0 && p_keysym <= 0xff) { + return p_keysym; + } + // keypad to latin1 is easy + if (p_keysym >= 0xffaa && p_keysym <= 0xffb9) { + return p_keysym - 0xff80; + } + + /* Unicode (may be present)*/ + + 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) { + return _xkeysym_to_unicode[middle].unicode; + } + if (_xkeysym_to_unicode[middle].keysym <= p_keysym) { + low = middle + 1; + } else { + high = middle - 1; + } + } while (high >= low); + + return 0; +} + +struct _XTranslateUnicodePairReverse { + unsigned int unicode; + KeySym keysym; +}; + +enum { + _UNICODE_MAX = 750 +}; + +static _XTranslateUnicodePairReverse _unicode_to_xkeysym[_UNICODE_MAX] = { + { 0x0ABD, 0x002E }, + { 0x0BA3, 0x003C }, + { 0x0BA6, 0x003E }, + { 0x0BC6, 0x005F }, + { 0x0BC0, 0x00AF }, + { 0x03C0, 0x0100 }, + { 0x03E0, 0x0101 }, + { 0x01C3, 0x0102 }, + { 0x01E3, 0x0103 }, + { 0x01A1, 0x0104 }, + { 0x01B1, 0x0105 }, + { 0x01C6, 0x0106 }, + { 0x01E6, 0x0107 }, + { 0x02C6, 0x0108 }, + { 0x02E6, 0x0109 }, + { 0x02C5, 0x010A }, + { 0x02E5, 0x010B }, + { 0x01C8, 0x010C }, + { 0x01E8, 0x010D }, + { 0x01CF, 0x010E }, + { 0x01EF, 0x010F }, + { 0x01D0, 0x0110 }, + { 0x01F0, 0x0111 }, + { 0x03AA, 0x0112 }, + { 0x03BA, 0x0113 }, + { 0x03CC, 0x0116 }, + { 0x03EC, 0x0117 }, + { 0x01CA, 0x0118 }, + { 0x01EA, 0x0119 }, + { 0x01CC, 0x011A }, + { 0x01EC, 0x011B }, + { 0x02D8, 0x011C }, + { 0x02F8, 0x011D }, + { 0x02AB, 0x011E }, + { 0x02BB, 0x011F }, + { 0x02D5, 0x0120 }, + { 0x02F5, 0x0121 }, + { 0x03AB, 0x0122 }, + { 0x03BB, 0x0123 }, + { 0x02A6, 0x0124 }, + { 0x02B6, 0x0125 }, + { 0x02A1, 0x0126 }, + { 0x02B1, 0x0127 }, + { 0x03A5, 0x0128 }, + { 0x03B5, 0x0129 }, + { 0x03CF, 0x012A }, + { 0x03EF, 0x012B }, + { 0x03C7, 0x012E }, + { 0x03E7, 0x012F }, + { 0x02A9, 0x0130 }, + { 0x02B9, 0x0131 }, + { 0x02AC, 0x0134 }, + { 0x02BC, 0x0135 }, + { 0x03D3, 0x0136 }, + { 0x03F3, 0x0137 }, + { 0x03A2, 0x0138 }, + { 0x01C5, 0x0139 }, + { 0x01E5, 0x013A }, + { 0x03A6, 0x013B }, + { 0x03B6, 0x013C }, + { 0x01A5, 0x013D }, + { 0x01B5, 0x013E }, + { 0x01A3, 0x0141 }, + { 0x01B3, 0x0142 }, + { 0x01D1, 0x0143 }, + { 0x01F1, 0x0144 }, + { 0x03D1, 0x0145 }, + { 0x03F1, 0x0146 }, + { 0x01D2, 0x0147 }, + { 0x01F2, 0x0148 }, + { 0x03BD, 0x014A }, + { 0x03BF, 0x014B }, + { 0x03D2, 0x014C }, + { 0x03F2, 0x014D }, + { 0x01D5, 0x0150 }, + { 0x01F5, 0x0151 }, + { 0x13BC, 0x0152 }, + { 0x13BD, 0x0153 }, + { 0x01C0, 0x0154 }, + { 0x01E0, 0x0155 }, + { 0x03A3, 0x0156 }, + { 0x03B3, 0x0157 }, + { 0x01D8, 0x0158 }, + { 0x01F8, 0x0159 }, + { 0x01A6, 0x015A }, + { 0x01B6, 0x015B }, + { 0x02DE, 0x015C }, + { 0x02FE, 0x015D }, + { 0x01AA, 0x015E }, + { 0x01BA, 0x015F }, + { 0x01A9, 0x0160 }, + { 0x01B9, 0x0161 }, + { 0x01DE, 0x0162 }, + { 0x01FE, 0x0163 }, + { 0x01AB, 0x0164 }, + { 0x01BB, 0x0165 }, + { 0x03AC, 0x0166 }, + { 0x03BC, 0x0167 }, + { 0x03DD, 0x0168 }, + { 0x03FD, 0x0169 }, + { 0x03DE, 0x016A }, + { 0x03FE, 0x016B }, + { 0x02DD, 0x016C }, + { 0x02FD, 0x016D }, + { 0x01D9, 0x016E }, + { 0x01F9, 0x016F }, + { 0x01DB, 0x0170 }, + { 0x01FB, 0x0171 }, + { 0x03D9, 0x0172 }, + { 0x03F9, 0x0173 }, + { 0x13BE, 0x0178 }, + { 0x01AC, 0x0179 }, + { 0x01BC, 0x017A }, + { 0x01AF, 0x017B }, + { 0x01BF, 0x017C }, + { 0x01AE, 0x017D }, + { 0x01BE, 0x017E }, + { 0x08F6, 0x0192 }, + { 0x01B7, 0x02C7 }, + { 0x01A2, 0x02D8 }, + { 0x01FF, 0x02D9 }, + { 0x01B2, 0x02DB }, + { 0x01BD, 0x02DD }, + { 0x07AE, 0x0385 }, + { 0x07A1, 0x0386 }, + { 0x07A2, 0x0388 }, + { 0x07A3, 0x0389 }, + { 0x07A4, 0x038A }, + { 0x07A7, 0x038C }, + { 0x07A8, 0x038E }, + { 0x07AB, 0x038F }, + { 0x07B6, 0x0390 }, + { 0x07C1, 0x0391 }, + { 0x07C2, 0x0392 }, + { 0x07C3, 0x0393 }, + { 0x07C4, 0x0394 }, + { 0x07C5, 0x0395 }, + { 0x07C6, 0x0396 }, + { 0x07C7, 0x0397 }, + { 0x07C8, 0x0398 }, + { 0x07C9, 0x0399 }, + { 0x07CA, 0x039A }, + { 0x07CB, 0x039B }, + { 0x07CC, 0x039C }, + { 0x07CD, 0x039D }, + { 0x07CE, 0x039E }, + { 0x07CF, 0x039F }, + { 0x07D0, 0x03A0 }, + { 0x07D1, 0x03A1 }, + { 0x07D2, 0x03A3 }, + { 0x07D4, 0x03A4 }, + { 0x07D5, 0x03A5 }, + { 0x07D6, 0x03A6 }, + { 0x07D7, 0x03A7 }, + { 0x07D8, 0x03A8 }, + { 0x07D9, 0x03A9 }, + { 0x07A5, 0x03AA }, + { 0x07A9, 0x03AB }, + { 0x07B1, 0x03AC }, + { 0x07B2, 0x03AD }, + { 0x07B3, 0x03AE }, + { 0x07B4, 0x03AF }, + { 0x07BA, 0x03B0 }, + { 0x07E1, 0x03B1 }, + { 0x07E2, 0x03B2 }, + { 0x07E3, 0x03B3 }, + { 0x07E4, 0x03B4 }, + { 0x07E5, 0x03B5 }, + { 0x07E6, 0x03B6 }, + { 0x07E7, 0x03B7 }, + { 0x07E8, 0x03B8 }, + { 0x07E9, 0x03B9 }, + { 0x07EA, 0x03BA }, + { 0x07EB, 0x03BB }, + { 0x07EC, 0x03BC }, + { 0x07ED, 0x03BD }, + { 0x07EE, 0x03BE }, + { 0x07EF, 0x03BF }, + { 0x07F0, 0x03C0 }, + { 0x07F1, 0x03C1 }, + { 0x07F3, 0x03C2 }, + { 0x07F2, 0x03C3 }, + { 0x07F4, 0x03C4 }, + { 0x07F5, 0x03C5 }, + { 0x07F6, 0x03C6 }, + { 0x07F7, 0x03C7 }, + { 0x07F8, 0x03C8 }, + { 0x07F9, 0x03C9 }, + { 0x07B5, 0x03CA }, + { 0x07B9, 0x03CB }, + { 0x07B7, 0x03CC }, + { 0x07B8, 0x03CD }, + { 0x07BB, 0x03CE }, + { 0x06B3, 0x0401 }, + { 0x06B1, 0x0402 }, + { 0x06B2, 0x0403 }, + { 0x06B4, 0x0404 }, + { 0x06B5, 0x0405 }, + { 0x06B6, 0x0406 }, + { 0x06B7, 0x0407 }, + { 0x06B8, 0x0408 }, + { 0x06B9, 0x0409 }, + { 0x06BA, 0x040A }, + { 0x06BB, 0x040B }, + { 0x06BC, 0x040C }, + { 0x06BE, 0x040E }, + { 0x06BF, 0x040F }, + { 0x06E1, 0x0410 }, + { 0x06E2, 0x0411 }, + { 0x06F7, 0x0412 }, + { 0x06E7, 0x0413 }, + { 0x06E4, 0x0414 }, + { 0x06E5, 0x0415 }, + { 0x06F6, 0x0416 }, + { 0x06FA, 0x0417 }, + { 0x06E9, 0x0418 }, + { 0x06EA, 0x0419 }, + { 0x06EB, 0x041A }, + { 0x06EC, 0x041B }, + { 0x06ED, 0x041C }, + { 0x06EE, 0x041D }, + { 0x06EF, 0x041E }, + { 0x06F0, 0x041F }, + { 0x06F2, 0x0420 }, + { 0x06F3, 0x0421 }, + { 0x06F4, 0x0422 }, + { 0x06F5, 0x0423 }, + { 0x06E6, 0x0424 }, + { 0x06E8, 0x0425 }, + { 0x06E3, 0x0426 }, + { 0x06FE, 0x0427 }, + { 0x06FB, 0x0428 }, + { 0x06FD, 0x0429 }, + { 0x06FF, 0x042A }, + { 0x06F9, 0x042B }, + { 0x06F8, 0x042C }, + { 0x06FC, 0x042D }, + { 0x06E0, 0x042E }, + { 0x06F1, 0x042F }, + { 0x06C1, 0x0430 }, + { 0x06C2, 0x0431 }, + { 0x06D7, 0x0432 }, + { 0x06C7, 0x0433 }, + { 0x06C4, 0x0434 }, + { 0x06C5, 0x0435 }, + { 0x06D6, 0x0436 }, + { 0x06DA, 0x0437 }, + { 0x06C9, 0x0438 }, + { 0x06CA, 0x0439 }, + { 0x06CB, 0x043A }, + { 0x06CC, 0x043B }, + { 0x06CD, 0x043C }, + { 0x06CE, 0x043D }, + { 0x06CF, 0x043E }, + { 0x06D0, 0x043F }, + { 0x06D2, 0x0440 }, + { 0x06D3, 0x0441 }, + { 0x06D4, 0x0442 }, + { 0x06D5, 0x0443 }, + { 0x06C6, 0x0444 }, + { 0x06C8, 0x0445 }, + { 0x06C3, 0x0446 }, + { 0x06DE, 0x0447 }, + { 0x06DB, 0x0448 }, + { 0x06DD, 0x0449 }, + { 0x06DF, 0x044A }, + { 0x06D9, 0x044B }, + { 0x06D8, 0x044C }, + { 0x06DC, 0x044D }, + { 0x06C0, 0x044E }, + { 0x06D1, 0x044F }, + { 0x06A3, 0x0451 }, + { 0x06A1, 0x0452 }, + { 0x06A2, 0x0453 }, + { 0x06A4, 0x0454 }, + { 0x06A5, 0x0455 }, + { 0x06A6, 0x0456 }, + { 0x06A7, 0x0457 }, + { 0x06A8, 0x0458 }, + { 0x06A9, 0x0459 }, + { 0x06AA, 0x045A }, + { 0x06AB, 0x045B }, + { 0x06AC, 0x045C }, + { 0x06AE, 0x045E }, + { 0x06AF, 0x045F }, + { 0x0CE0, 0x05D0 }, + { 0x0CE1, 0x05D1 }, + { 0x0CE2, 0x05D2 }, + { 0x0CE3, 0x05D3 }, + { 0x0CE4, 0x05D4 }, + { 0x0CE5, 0x05D5 }, + { 0x0CE6, 0x05D6 }, + { 0x0CE7, 0x05D7 }, + { 0x0CE8, 0x05D8 }, + { 0x0CE9, 0x05D9 }, + { 0x0CEA, 0x05DA }, + { 0x0CEB, 0x05DB }, + { 0x0CEC, 0x05DC }, + { 0x0CED, 0x05DD }, + { 0x0CEE, 0x05DE }, + { 0x0CEF, 0x05DF }, + { 0x0CF0, 0x05E0 }, + { 0x0CF1, 0x05E1 }, + { 0x0CF2, 0x05E2 }, + { 0x0CF3, 0x05E3 }, + { 0x0CF4, 0x05E4 }, + { 0x0CF5, 0x05E5 }, + { 0x0CF6, 0x05E6 }, + { 0x0CF7, 0x05E7 }, + { 0x0CF8, 0x05E8 }, + { 0x0CF9, 0x05E9 }, + { 0x0CFA, 0x05EA }, + { 0x05AC, 0x060C }, + { 0x05BB, 0x061B }, + { 0x05BF, 0x061F }, + { 0x05C1, 0x0621 }, + { 0x05C2, 0x0622 }, + { 0x05C3, 0x0623 }, + { 0x05C4, 0x0624 }, + { 0x05C5, 0x0625 }, + { 0x05C6, 0x0626 }, + { 0x05C7, 0x0627 }, + { 0x05C8, 0x0628 }, + { 0x05C9, 0x0629 }, + { 0x05CA, 0x062A }, + { 0x05CB, 0x062B }, + { 0x05CC, 0x062C }, + { 0x05CD, 0x062D }, + { 0x05CE, 0x062E }, + { 0x05CF, 0x062F }, + { 0x05D0, 0x0630 }, + { 0x05D1, 0x0631 }, + { 0x05D2, 0x0632 }, + { 0x05D3, 0x0633 }, + { 0x05D4, 0x0634 }, + { 0x05D5, 0x0635 }, + { 0x05D6, 0x0636 }, + { 0x05D7, 0x0637 }, + { 0x05D8, 0x0638 }, + { 0x05D9, 0x0639 }, + { 0x05DA, 0x063A }, + { 0x05E0, 0x0640 }, + { 0x05E1, 0x0641 }, + { 0x05E2, 0x0642 }, + { 0x05E3, 0x0643 }, + { 0x05E4, 0x0644 }, + { 0x05E5, 0x0645 }, + { 0x05E6, 0x0646 }, + { 0x05E7, 0x0647 }, + { 0x05E8, 0x0648 }, + { 0x05E9, 0x0649 }, + { 0x05EA, 0x064A }, + { 0x05EB, 0x064B }, + { 0x05EC, 0x064C }, + { 0x05ED, 0x064D }, + { 0x05EE, 0x064E }, + { 0x05EF, 0x064F }, + { 0x05F0, 0x0650 }, + { 0x05F1, 0x0651 }, + { 0x05F2, 0x0652 }, + { 0x0DA1, 0x0E01 }, + { 0x0DA2, 0x0E02 }, + { 0x0DA3, 0x0E03 }, + { 0x0DA4, 0x0E04 }, + { 0x0DA5, 0x0E05 }, + { 0x0DA6, 0x0E06 }, + { 0x0DA7, 0x0E07 }, + { 0x0DA8, 0x0E08 }, + { 0x0DA9, 0x0E09 }, + { 0x0DAA, 0x0E0A }, + { 0x0DAB, 0x0E0B }, + { 0x0DAC, 0x0E0C }, + { 0x0DAD, 0x0E0D }, + { 0x0DAE, 0x0E0E }, + { 0x0DAF, 0x0E0F }, + { 0x0DB0, 0x0E10 }, + { 0x0DB1, 0x0E11 }, + { 0x0DB2, 0x0E12 }, + { 0x0DB3, 0x0E13 }, + { 0x0DB4, 0x0E14 }, + { 0x0DB5, 0x0E15 }, + { 0x0DB6, 0x0E16 }, + { 0x0DB7, 0x0E17 }, + { 0x0DB8, 0x0E18 }, + { 0x0DB9, 0x0E19 }, + { 0x0DBA, 0x0E1A }, + { 0x0DBB, 0x0E1B }, + { 0x0DBC, 0x0E1C }, + { 0x0DBD, 0x0E1D }, + { 0x0DBE, 0x0E1E }, + { 0x0DBF, 0x0E1F }, + { 0x0DC0, 0x0E20 }, + { 0x0DC1, 0x0E21 }, + { 0x0DC2, 0x0E22 }, + { 0x0DC3, 0x0E23 }, + { 0x0DC4, 0x0E24 }, + { 0x0DC5, 0x0E25 }, + { 0x0DC6, 0x0E26 }, + { 0x0DC7, 0x0E27 }, + { 0x0DC8, 0x0E28 }, + { 0x0DC9, 0x0E29 }, + { 0x0DCA, 0x0E2A }, + { 0x0DCB, 0x0E2B }, + { 0x0DCC, 0x0E2C }, + { 0x0DCD, 0x0E2D }, + { 0x0DCE, 0x0E2E }, + { 0x0DCF, 0x0E2F }, + { 0x0DD0, 0x0E30 }, + { 0x0DD1, 0x0E31 }, + { 0x0DD2, 0x0E32 }, + { 0x0DD3, 0x0E33 }, + { 0x0DD4, 0x0E34 }, + { 0x0DD5, 0x0E35 }, + { 0x0DD6, 0x0E36 }, + { 0x0DD7, 0x0E37 }, + { 0x0DD8, 0x0E38 }, + { 0x0DD9, 0x0E39 }, + { 0x0DDA, 0x0E3A }, + { 0x0DDF, 0x0E3F }, + { 0x0DE0, 0x0E40 }, + { 0x0DE1, 0x0E41 }, + { 0x0DE2, 0x0E42 }, + { 0x0DE3, 0x0E43 }, + { 0x0DE4, 0x0E44 }, + { 0x0DE5, 0x0E45 }, + { 0x0DE6, 0x0E46 }, + { 0x0DE7, 0x0E47 }, + { 0x0DE8, 0x0E48 }, + { 0x0DE9, 0x0E49 }, + { 0x0DEA, 0x0E4A }, + { 0x0DEB, 0x0E4B }, + { 0x0DEC, 0x0E4C }, + { 0x0DED, 0x0E4D }, + { 0x0DF0, 0x0E50 }, + { 0x0DF1, 0x0E51 }, + { 0x0DF2, 0x0E52 }, + { 0x0DF3, 0x0E53 }, + { 0x0DF4, 0x0E54 }, + { 0x0DF5, 0x0E55 }, + { 0x0DF6, 0x0E56 }, + { 0x0DF7, 0x0E57 }, + { 0x0DF8, 0x0E58 }, + { 0x0DF9, 0x0E59 }, + { 0x0ED4, 0x11A8 }, + { 0x0ED5, 0x11A9 }, + { 0x0ED6, 0x11AA }, + { 0x0ED7, 0x11AB }, + { 0x0ED8, 0x11AC }, + { 0x0ED9, 0x11AD }, + { 0x0EDA, 0x11AE }, + { 0x0EDB, 0x11AF }, + { 0x0EDC, 0x11B0 }, + { 0x0EDD, 0x11B1 }, + { 0x0EDE, 0x11B2 }, + { 0x0EDF, 0x11B3 }, + { 0x0EE0, 0x11B4 }, + { 0x0EE1, 0x11B5 }, + { 0x0EE2, 0x11B6 }, + { 0x0EE3, 0x11B7 }, + { 0x0EE4, 0x11B8 }, + { 0x0EE5, 0x11B9 }, + { 0x0EE6, 0x11BA }, + { 0x0EE7, 0x11BB }, + { 0x0EE8, 0x11BC }, + { 0x0EE9, 0x11BD }, + { 0x0EEA, 0x11BE }, + { 0x0EEB, 0x11BF }, + { 0x0EEC, 0x11C0 }, + { 0x0EED, 0x11C1 }, + { 0x0EEE, 0x11C2 }, + { 0x0EF8, 0x11EB }, + { 0x0EFA, 0x11F9 }, + { 0x0AA2, 0x2002 }, + { 0x0AA1, 0x2003 }, + { 0x0AA3, 0x2004 }, + { 0x0AA4, 0x2005 }, + { 0x0AA5, 0x2007 }, + { 0x0AA6, 0x2008 }, + { 0x0AA7, 0x2009 }, + { 0x0AA8, 0x200A }, + { 0x0ABB, 0x2012 }, + { 0x0AAA, 0x2013 }, + { 0x0AA9, 0x2014 }, + { 0x07AF, 0x2015 }, + { 0x0CDF, 0x2017 }, + { 0x0AD0, 0x2018 }, + { 0x0AD1, 0x2019 }, + { 0x0AFD, 0x201A }, + { 0x0AD2, 0x201C }, + { 0x0AD3, 0x201D }, + { 0x0AFE, 0x201E }, + { 0x0AF1, 0x2020 }, + { 0x0AF2, 0x2021 }, + { 0x0AE6, 0x2022 }, + { 0x0AAE, 0x2026 }, + { 0x0AD6, 0x2032 }, + { 0x0AD7, 0x2033 }, + { 0x0AFC, 0x2038 }, + { 0x047E, 0x203E }, + { 0x20A0, 0x20A0 }, + { 0x20A1, 0x20A1 }, + { 0x20A2, 0x20A2 }, + { 0x20A3, 0x20A3 }, + { 0x20A4, 0x20A4 }, + { 0x20A5, 0x20A5 }, + { 0x20A6, 0x20A6 }, + { 0x20A7, 0x20A7 }, + { 0x20A8, 0x20A8 }, + { 0x0EFF, 0x20A9 }, + { 0x20A9, 0x20A9 }, + { 0x20AA, 0x20AA }, + { 0x20AB, 0x20AB }, + { 0x20AC, 0x20AC }, + { 0x0AB8, 0x2105 }, + { 0x06B0, 0x2116 }, + { 0x0AFB, 0x2117 }, + { 0x0AD4, 0x211E }, + { 0x0AC9, 0x2122 }, + { 0x0AB0, 0x2153 }, + { 0x0AB1, 0x2154 }, + { 0x0AB2, 0x2155 }, + { 0x0AB3, 0x2156 }, + { 0x0AB4, 0x2157 }, + { 0x0AB5, 0x2158 }, + { 0x0AB6, 0x2159 }, + { 0x0AB7, 0x215A }, + { 0x0AC3, 0x215B }, + { 0x0AC4, 0x215C }, + { 0x0AC5, 0x215D }, + { 0x0AC6, 0x215E }, + { 0x08FB, 0x2190 }, + { 0x08FC, 0x2191 }, + { 0x08FD, 0x2192 }, + { 0x08FE, 0x2193 }, + { 0x08CE, 0x21D2 }, + { 0x08CD, 0x21D4 }, + { 0x08EF, 0x2202 }, + { 0x08C5, 0x2207 }, + { 0x0BCA, 0x2218 }, + { 0x08D6, 0x221A }, + { 0x08C1, 0x221D }, + { 0x08C2, 0x221E }, + { 0x08DE, 0x2227 }, + { 0x0BA9, 0x2227 }, + { 0x08DF, 0x2228 }, + { 0x0BA8, 0x2228 }, + { 0x08DC, 0x2229 }, + { 0x0BC3, 0x2229 }, + { 0x08DD, 0x222A }, + { 0x0BD6, 0x222A }, + { 0x08BF, 0x222B }, + { 0x08C0, 0x2234 }, + { 0x08C8, 0x2245 }, + { 0x08BD, 0x2260 }, + { 0x08CF, 0x2261 }, + { 0x08BC, 0x2264 }, + { 0x08BE, 0x2265 }, + { 0x08DA, 0x2282 }, + { 0x0BDA, 0x2282 }, + { 0x08DB, 0x2283 }, + { 0x0BD8, 0x2283 }, + { 0x0BFC, 0x22A2 }, + { 0x0BDC, 0x22A3 }, + { 0x0BC2, 0x22A4 }, + { 0x0BCE, 0x22A5 }, + { 0x0BD3, 0x2308 }, + { 0x0BC4, 0x230A }, + { 0x0AFA, 0x2315 }, + { 0x08A4, 0x2320 }, + { 0x08A5, 0x2321 }, + { 0x0ABC, 0x2329 }, + { 0x0ABE, 0x232A }, + { 0x0BCC, 0x2395 }, + { 0x09E2, 0x2409 }, + { 0x09E5, 0x240A }, + { 0x09E9, 0x240B }, + { 0x09E3, 0x240C }, + { 0x09E4, 0x240D }, + { 0x09DF, 0x2422 }, + { 0x09E8, 0x2424 }, + { 0x09F1, 0x2500 }, + { 0x08A6, 0x2502 }, + { 0x09F8, 0x2502 }, + { 0x09EC, 0x250C }, + { 0x09EB, 0x2510 }, + { 0x09ED, 0x2514 }, + { 0x09EA, 0x2518 }, + { 0x09F4, 0x251C }, + { 0x09F5, 0x2524 }, + { 0x09F7, 0x252C }, + { 0x09F6, 0x2534 }, + { 0x09EE, 0x253C }, + { 0x09E1, 0x2592 }, + { 0x0ADF, 0x25A0 }, + { 0x0ACF, 0x25A1 }, + { 0x0AE7, 0x25AA }, + { 0x0AE1, 0x25AB }, + { 0x0ADB, 0x25AC }, + { 0x0AE2, 0x25AD }, + { 0x0AE8, 0x25B2 }, + { 0x0AE3, 0x25B3 }, + { 0x0ADD, 0x25B6 }, + { 0x0ACD, 0x25B7 }, + { 0x0AE9, 0x25BC }, + { 0x0AE4, 0x25BD }, + { 0x0ADC, 0x25C0 }, + { 0x0ACC, 0x25C1 }, + { 0x09E0, 0x25C6 }, + { 0x0ACE, 0x25CB }, + { 0x0BCF, 0x25CB }, + { 0x0ADE, 0x25CF }, + { 0x0AE0, 0x25E6 }, + { 0x0AE5, 0x2606 }, + { 0x0AF9, 0x260E }, + { 0x0ACA, 0x2613 }, + { 0x0AEA, 0x261C }, + { 0x0AEB, 0x261E }, + { 0x0AF8, 0x2640 }, + { 0x0AF7, 0x2642 }, + { 0x0AEC, 0x2663 }, + { 0x0AEE, 0x2665 }, + { 0x0AED, 0x2666 }, + { 0x0AF6, 0x266D }, + { 0x0AF5, 0x266F }, + { 0x0AF3, 0x2713 }, + { 0x0AF4, 0x2717 }, + { 0x0AD9, 0x271D }, + { 0x0AF0, 0x2720 }, + { 0x04A4, 0x3001 }, + { 0x04A1, 0x3002 }, + { 0x04A2, 0x300C }, + { 0x04A3, 0x300D }, + { 0x04DE, 0x309B }, + { 0x04DF, 0x309C }, + { 0x04A7, 0x30A1 }, + { 0x04B1, 0x30A2 }, + { 0x04A8, 0x30A3 }, + { 0x04B2, 0x30A4 }, + { 0x04A9, 0x30A5 }, + { 0x04B3, 0x30A6 }, + { 0x04AA, 0x30A7 }, + { 0x04B4, 0x30A8 }, + { 0x04AB, 0x30A9 }, + { 0x04B5, 0x30AA }, + { 0x04B6, 0x30AB }, + { 0x04B7, 0x30AD }, + { 0x04B8, 0x30AF }, + { 0x04B9, 0x30B1 }, + { 0x04BA, 0x30B3 }, + { 0x04BB, 0x30B5 }, + { 0x04BC, 0x30B7 }, + { 0x04BD, 0x30B9 }, + { 0x04BE, 0x30BB }, + { 0x04BF, 0x30BD }, + { 0x04C0, 0x30BF }, + { 0x04C1, 0x30C1 }, + { 0x04AF, 0x30C3 }, + { 0x04C2, 0x30C4 }, + { 0x04C3, 0x30C6 }, + { 0x04C4, 0x30C8 }, + { 0x04C5, 0x30CA }, + { 0x04C6, 0x30CB }, + { 0x04C7, 0x30CC }, + { 0x04C8, 0x30CD }, + { 0x04C9, 0x30CE }, + { 0x04CA, 0x30CF }, + { 0x04CB, 0x30D2 }, + { 0x04CC, 0x30D5 }, + { 0x04CD, 0x30D8 }, + { 0x04CE, 0x30DB }, + { 0x04CF, 0x30DE }, + { 0x04D0, 0x30DF }, + { 0x04D1, 0x30E0 }, + { 0x04D2, 0x30E1 }, + { 0x04D3, 0x30E2 }, + { 0x04AC, 0x30E3 }, + { 0x04D4, 0x30E4 }, + { 0x04AD, 0x30E5 }, + { 0x04D5, 0x30E6 }, + { 0x04AE, 0x30E7 }, + { 0x04D6, 0x30E8 }, + { 0x04D7, 0x30E9 }, + { 0x04D8, 0x30EA }, + { 0x04D9, 0x30EB }, + { 0x04DA, 0x30EC }, + { 0x04DB, 0x30ED }, + { 0x04DC, 0x30EF }, + { 0x04A6, 0x30F2 }, + { 0x04DD, 0x30F3 }, + { 0x04A5, 0x30FB }, + { 0x04B0, 0x30FC }, + { 0x0EA1, 0x3131 }, + { 0x0EA2, 0x3132 }, + { 0x0EA3, 0x3133 }, + { 0x0EA4, 0x3134 }, + { 0x0EA5, 0x3135 }, + { 0x0EA6, 0x3136 }, + { 0x0EA7, 0x3137 }, + { 0x0EA8, 0x3138 }, + { 0x0EA9, 0x3139 }, + { 0x0EAA, 0x313A }, + { 0x0EAB, 0x313B }, + { 0x0EAC, 0x313C }, + { 0x0EAD, 0x313D }, + { 0x0EAE, 0x313E }, + { 0x0EAF, 0x313F }, + { 0x0EB0, 0x3140 }, + { 0x0EB1, 0x3141 }, + { 0x0EB2, 0x3142 }, + { 0x0EB3, 0x3143 }, + { 0x0EB4, 0x3144 }, + { 0x0EB5, 0x3145 }, + { 0x0EB6, 0x3146 }, + { 0x0EB7, 0x3147 }, + { 0x0EB8, 0x3148 }, + { 0x0EB9, 0x3149 }, + { 0x0EBA, 0x314A }, + { 0x0EBB, 0x314B }, + { 0x0EBC, 0x314C }, + { 0x0EBD, 0x314D }, + { 0x0EBE, 0x314E }, + { 0x0EBF, 0x314F }, + { 0x0EC0, 0x3150 }, + { 0x0EC1, 0x3151 }, + { 0x0EC2, 0x3152 }, + { 0x0EC3, 0x3153 }, + { 0x0EC4, 0x3154 }, + { 0x0EC5, 0x3155 }, + { 0x0EC6, 0x3156 }, + { 0x0EC7, 0x3157 }, + { 0x0EC8, 0x3158 }, + { 0x0EC9, 0x3159 }, + { 0x0ECA, 0x315A }, + { 0x0ECB, 0x315B }, + { 0x0ECC, 0x315C }, + { 0x0ECD, 0x315D }, + { 0x0ECE, 0x315E }, + { 0x0ECF, 0x315F }, + { 0x0ED0, 0x3160 }, + { 0x0ED1, 0x3161 }, + { 0x0ED2, 0x3162 }, + { 0x0ED3, 0x3163 }, + { 0x0EEF, 0x316D }, + { 0x0EF0, 0x3171 }, + { 0x0EF1, 0x3178 }, + { 0x0EF2, 0x317F }, + { 0x0EF4, 0x3184 }, + { 0x0EF5, 0x3186 }, + { 0x0EF6, 0x318D }, + { 0x0EF7, 0x318E } +}; + +KeySym KeyMappingX11::get_keysym_from_unicode(unsigned int p_unicode) { + /* Latin 1 */ + + if (p_unicode >= 0x20 && p_unicode <= 0x7e) { + return p_unicode; + } + + 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) { + return _unicode_to_xkeysym[middle].keysym; + } + if (_unicode_to_xkeysym[middle].keysym <= p_unicode) { + low = middle + 1; + } else { + high = middle - 1; + } + } while (high >= low); + + // if not found, let's hope X understands it as unicode + return p_unicode | 0x01000000; +} diff --git a/platform/linuxbsd/x11/key_mapping_x11.h b/platform/linuxbsd/x11/key_mapping_x11.h new file mode 100644 index 0000000000..b7b8a3b787 --- /dev/null +++ b/platform/linuxbsd/x11/key_mapping_x11.h @@ -0,0 +1,55 @@ +/*************************************************************************/ +/* key_mapping_x11.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef KEY_MAPPING_X11_H +#define KEY_MAPPING_X11_H + +#include +#include +#define XK_MISCELLANY +#define XK_LATIN1 +#define XK_XKB_KEYS +#include + +#include "core/os/keyboard.h" + +class KeyMappingX11 { + KeyMappingX11() {} + +public: + static Key get_keycode(KeySym p_keysym); + static unsigned int get_xlibcode(Key p_keysym); + static Key get_scancode(unsigned int p_code); + static KeySym get_keysym(Key p_code); + static unsigned int get_unicode_from_keysym(KeySym p_keysym); + static KeySym get_keysym_from_unicode(unsigned int p_unicode); +}; + +#endif // KEY_MAPPING_X11_H diff --git a/platform/linuxbsd/x11/vulkan_context_x11.cpp b/platform/linuxbsd/x11/vulkan_context_x11.cpp new file mode 100644 index 0000000000..92aaf33b05 --- /dev/null +++ b/platform/linuxbsd/x11/vulkan_context_x11.cpp @@ -0,0 +1,65 @@ +/*************************************************************************/ +/* vulkan_context_x11.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifdef VULKAN_ENABLED + +#include "vulkan_context_x11.h" + +#ifdef USE_VOLK +#include +#else +#include +#endif + +const char *VulkanContextX11::_get_platform_surface_extension() const { + return VK_KHR_XLIB_SURFACE_EXTENSION_NAME; +} + +Error VulkanContextX11::window_create(DisplayServer::WindowID p_window_id, DisplayServer::VSyncMode p_vsync_mode, ::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; + createInfo.flags = 0; + createInfo.dpy = p_display; + createInfo.window = p_window; + + VkSurfaceKHR surface; + VkResult err = vkCreateXlibSurfaceKHR(get_instance(), &createInfo, nullptr, &surface); + ERR_FAIL_COND_V(err, ERR_CANT_CREATE); + return _window_create(p_window_id, p_vsync_mode, surface, p_width, p_height); +} + +VulkanContextX11::VulkanContextX11() { +} + +VulkanContextX11::~VulkanContextX11() { +} + +#endif // VULKAN_ENABLED diff --git a/platform/linuxbsd/x11/vulkan_context_x11.h b/platform/linuxbsd/x11/vulkan_context_x11.h new file mode 100644 index 0000000000..0adb50ef44 --- /dev/null +++ b/platform/linuxbsd/x11/vulkan_context_x11.h @@ -0,0 +1,51 @@ +/*************************************************************************/ +/* vulkan_context_x11.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef VULKAN_CONTEXT_X11_H +#define VULKAN_CONTEXT_X11_H + +#ifdef VULKAN_ENABLED + +#include "drivers/vulkan/vulkan_context.h" +#include + +class VulkanContextX11 : public VulkanContext { + virtual const char *_get_platform_surface_extension() const; + +public: + Error window_create(DisplayServer::WindowID p_window_id, DisplayServer::VSyncMode p_vsync_mode, ::Window p_window, Display *p_display, int p_width, int p_height); + + VulkanContextX11(); + ~VulkanContextX11(); +}; + +#endif // VULKAN_ENABLED + +#endif // VULKAN_CONTEXT_X11_H -- cgit v1.2.3