Path: blob/master/platform/linuxbsd/x11/display_server_x11.cpp
10278 views
/**************************************************************************/1/* display_server_x11.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "display_server_x11.h"3132#ifdef X11_ENABLED3334#include "x11/detect_prime_x11.h"35#include "x11/key_mapping_x11.h"3637#include "core/config/project_settings.h"38#include "core/math/math_funcs.h"39#include "core/string/print_string.h"40#include "core/string/ustring.h"41#include "core/version.h"42#include "drivers/png/png_driver_common.h"43#include "main/main.h"4445#include "servers/rendering/dummy/rasterizer_dummy.h"4647#if defined(VULKAN_ENABLED)48#include "servers/rendering/renderer_rd/renderer_compositor_rd.h"49#endif5051#if defined(GLES3_ENABLED)52#include "drivers/gles3/rasterizer_gles3.h"53#endif5455#ifdef ACCESSKIT_ENABLED56#include "drivers/accesskit/accessibility_driver_accesskit.h"57#endif5859#ifdef DBUS_ENABLED60#ifdef SOWRAP_ENABLED61#include "dbus-so_wrap.h"62#else63#include <dbus/dbus.h>64#endif65#endif6667#include <dlfcn.h>68#include <sys/stat.h>69#include <sys/types.h>70#include <unistd.h>71#include <climits>72#include <cstdio>73#include <cstdlib>7475#undef CursorShape76#include <X11/XKBlib.h>7778// ICCCM79#define WM_NormalState 1L // window normal state80#define WM_IconicState 3L // window minimized81// EWMH82#define _NET_WM_STATE_REMOVE 0L // remove/unset property83#define _NET_WM_STATE_ADD 1L // add/set property8485#define _NET_WM_MOVERESIZE_SIZE_TOPLEFT 0L86#define _NET_WM_MOVERESIZE_SIZE_TOP 1L87#define _NET_WM_MOVERESIZE_SIZE_TOPRIGHT 2L88#define _NET_WM_MOVERESIZE_SIZE_RIGHT 3L89#define _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT 4L90#define _NET_WM_MOVERESIZE_SIZE_BOTTOM 5L91#define _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT 6L92#define _NET_WM_MOVERESIZE_SIZE_LEFT 7L93#define _NET_WM_MOVERESIZE_MOVE 8L9495// 2.2 is the first release with multitouch96#define XINPUT_CLIENT_VERSION_MAJOR 297#define XINPUT_CLIENT_VERSION_MINOR 29899#define VALUATOR_ABSX 0100#define VALUATOR_ABSY 1101#define VALUATOR_PRESSURE 2102#define VALUATOR_TILTX 3103#define VALUATOR_TILTY 4104105//#define DISPLAY_SERVER_X11_DEBUG_LOGS_ENABLED106#ifdef DISPLAY_SERVER_X11_DEBUG_LOGS_ENABLED107#define DEBUG_LOG_X11(...) printf(__VA_ARGS__)108#else109#define DEBUG_LOG_X11(...)110#endif111112static const double abs_resolution_mult = 10000.0;113static const double abs_resolution_range_mult = 10.0;114115// Hints for X11 fullscreen116struct Hints {117unsigned long flags = 0;118unsigned long functions = 0;119unsigned long decorations = 0;120long inputMode = 0;121unsigned long status = 0;122};123124static String get_atom_name(Display *p_disp, Atom p_atom) {125char *name = XGetAtomName(p_disp, p_atom);126ERR_FAIL_NULL_V_MSG(name, String(), "Atom is invalid.");127String ret = String::utf8(name);128XFree(name);129return ret;130}131132bool DisplayServerX11::has_feature(Feature p_feature) const {133switch (p_feature) {134#ifndef DISABLE_DEPRECATED135case FEATURE_GLOBAL_MENU: {136return (native_menu && native_menu->has_feature(NativeMenu::FEATURE_GLOBAL_MENU));137} break;138#endif139case FEATURE_SUBWINDOWS:140#ifdef TOUCH_ENABLED141case FEATURE_TOUCHSCREEN:142#endif143case FEATURE_MOUSE:144case FEATURE_MOUSE_WARP:145case FEATURE_CLIPBOARD:146case FEATURE_CURSOR_SHAPE:147case FEATURE_CUSTOM_CURSOR_SHAPE:148case FEATURE_IME:149case FEATURE_WINDOW_TRANSPARENCY:150//case FEATURE_HIDPI:151case FEATURE_ICON:152//case FEATURE_NATIVE_ICON:153case FEATURE_SWAP_BUFFERS:154#ifdef DBUS_ENABLED155case FEATURE_KEEP_SCREEN_ON:156#endif157case FEATURE_CLIPBOARD_PRIMARY:158case FEATURE_WINDOW_EMBEDDING:159case FEATURE_WINDOW_DRAG: {160return true;161} break;162163//case FEATURE_NATIVE_DIALOG:164//case FEATURE_NATIVE_DIALOG_INPUT:165#ifdef DBUS_ENABLED166case FEATURE_NATIVE_DIALOG_FILE:167case FEATURE_NATIVE_DIALOG_FILE_EXTRA:168case FEATURE_NATIVE_DIALOG_FILE_MIME: {169return (portal_desktop && portal_desktop->is_supported() && portal_desktop->is_file_chooser_supported());170} break;171case FEATURE_NATIVE_COLOR_PICKER: {172return (portal_desktop && portal_desktop->is_supported() && portal_desktop->is_screenshot_supported());173} break;174#endif175case FEATURE_SCREEN_CAPTURE: {176return !xwayland;177} break;178179#ifdef SPEECHD_ENABLED180case FEATURE_TEXT_TO_SPEECH: {181return true;182} break;183#endif184185#ifdef ACCESSKIT_ENABLED186case FEATURE_ACCESSIBILITY_SCREEN_READER: {187return (accessibility_driver != nullptr);188} break;189#endif190191default: {192return false;193}194}195}196197String DisplayServerX11::get_name() const {198return "X11";199}200201void DisplayServerX11::_update_real_mouse_position(const WindowData &wd) {202Window root_return, child_return;203int root_x, root_y, win_x, win_y;204unsigned int mask_return;205206Bool xquerypointer_result = XQueryPointer(x11_display, wd.x11_window, &root_return, &child_return, &root_x, &root_y,207&win_x, &win_y, &mask_return);208209if (xquerypointer_result) {210if (win_x > 0 && win_y > 0 && win_x <= wd.size.width && win_y <= wd.size.height) {211last_mouse_pos.x = win_x;212last_mouse_pos.y = win_y;213last_mouse_pos_valid = true;214Input::get_singleton()->set_mouse_position(last_mouse_pos);215}216}217}218219bool DisplayServerX11::_refresh_device_info() {220int event_base, error_base;221222print_verbose("XInput: Refreshing devices.");223224if (!XQueryExtension(x11_display, "XInputExtension", &xi.opcode, &event_base, &error_base)) {225print_verbose("XInput extension not available. Please upgrade your distribution.");226return false;227}228229int xi_major_query = XINPUT_CLIENT_VERSION_MAJOR;230int xi_minor_query = XINPUT_CLIENT_VERSION_MINOR;231232if (XIQueryVersion(x11_display, &xi_major_query, &xi_minor_query) != Success) {233print_verbose(vformat("XInput 2 not available (server supports %d.%d).", xi_major_query, xi_minor_query));234xi.opcode = 0;235return false;236}237238if (xi_major_query < XINPUT_CLIENT_VERSION_MAJOR || (xi_major_query == XINPUT_CLIENT_VERSION_MAJOR && xi_minor_query < XINPUT_CLIENT_VERSION_MINOR)) {239print_verbose(vformat("XInput %d.%d not available (server supports %d.%d). Touch input unavailable.",240XINPUT_CLIENT_VERSION_MAJOR, XINPUT_CLIENT_VERSION_MINOR, xi_major_query, xi_minor_query));241}242243xi.absolute_devices.clear();244xi.touch_devices.clear();245xi.pen_inverted_devices.clear();246xi.last_relative_time = 0;247248int dev_count;249XIDeviceInfo *info = XIQueryDevice(x11_display, XIAllDevices, &dev_count);250251for (int i = 0; i < dev_count; i++) {252XIDeviceInfo *dev = &info[i];253if (!dev->enabled) {254continue;255}256if (!(dev->use == XISlavePointer || dev->use == XIFloatingSlave)) {257continue;258}259260bool direct_touch = false;261bool absolute_mode = false;262int resolution_x = 0;263int resolution_y = 0;264double abs_x_min = 0;265double abs_x_max = 0;266double abs_y_min = 0;267double abs_y_max = 0;268double pressure_min = 0;269double pressure_max = 0;270double tilt_x_min = 0;271double tilt_x_max = 0;272double tilt_y_min = 0;273double tilt_y_max = 0;274for (int j = 0; j < dev->num_classes; j++) {275#ifdef TOUCH_ENABLED276if (dev->classes[j]->type == XITouchClass && ((XITouchClassInfo *)dev->classes[j])->mode == XIDirectTouch) {277direct_touch = true;278}279#endif280if (dev->classes[j]->type == XIValuatorClass) {281XIValuatorClassInfo *class_info = (XIValuatorClassInfo *)dev->classes[j];282283if (class_info->number == VALUATOR_ABSX && class_info->mode == XIModeAbsolute) {284resolution_x = class_info->resolution;285abs_x_min = class_info->min;286abs_x_max = class_info->max;287absolute_mode = true;288} else if (class_info->number == VALUATOR_ABSY && class_info->mode == XIModeAbsolute) {289resolution_y = class_info->resolution;290abs_y_min = class_info->min;291abs_y_max = class_info->max;292absolute_mode = true;293} else if (class_info->number == VALUATOR_PRESSURE && class_info->mode == XIModeAbsolute) {294pressure_min = class_info->min;295pressure_max = class_info->max;296} else if (class_info->number == VALUATOR_TILTX && class_info->mode == XIModeAbsolute) {297tilt_x_min = class_info->min;298tilt_x_max = class_info->max;299} else if (class_info->number == VALUATOR_TILTY && class_info->mode == XIModeAbsolute) {300tilt_y_min = class_info->min;301tilt_y_max = class_info->max;302}303}304}305if (direct_touch) {306xi.touch_devices.push_back(dev->deviceid);307print_verbose("XInput: Using touch device: " + String(dev->name));308}309if (absolute_mode) {310// If no resolution was reported, use the min/max ranges.311if (resolution_x <= 0) {312resolution_x = (abs_x_max - abs_x_min) * abs_resolution_range_mult;313}314if (resolution_y <= 0) {315resolution_y = (abs_y_max - abs_y_min) * abs_resolution_range_mult;316}317xi.absolute_devices[dev->deviceid] = Vector2(abs_resolution_mult / resolution_x, abs_resolution_mult / resolution_y);318print_verbose("XInput: Absolute pointing device: " + String(dev->name));319}320321xi.pressure = 0;322xi.pen_pressure_range[dev->deviceid] = Vector2(pressure_min, pressure_max);323xi.pen_tilt_x_range[dev->deviceid] = Vector2(tilt_x_min, tilt_x_max);324xi.pen_tilt_y_range[dev->deviceid] = Vector2(tilt_y_min, tilt_y_max);325xi.pen_inverted_devices[dev->deviceid] = String(dev->name).findn("eraser") > 0;326}327328XIFreeDeviceInfo(info);329#ifdef TOUCH_ENABLED330if (!xi.touch_devices.size()) {331print_verbose("XInput: No touch devices found.");332}333#endif334335return true;336}337338void DisplayServerX11::_flush_mouse_motion() {339// Block events polling while flushing motion events.340MutexLock mutex_lock(events_mutex);341342for (uint32_t event_index = 0; event_index < polled_events.size(); ++event_index) {343XEvent &event = polled_events[event_index];344if (XGetEventData(x11_display, &event.xcookie) && event.xcookie.type == GenericEvent && event.xcookie.extension == xi.opcode) {345XIDeviceEvent *event_data = (XIDeviceEvent *)event.xcookie.data;346if (event_data->evtype == XI_RawMotion) {347XFreeEventData(x11_display, &event.xcookie);348polled_events.remove_at(event_index--);349continue;350}351XFreeEventData(x11_display, &event.xcookie);352break;353}354}355356xi.relative_motion.x = 0;357xi.relative_motion.y = 0;358}359360#ifdef SPEECHD_ENABLED361362void DisplayServerX11::initialize_tts() const {363const_cast<DisplayServerX11 *>(this)->tts = memnew(TTS_Linux);364}365366bool DisplayServerX11::tts_is_speaking() const {367if (unlikely(!tts)) {368initialize_tts();369}370ERR_FAIL_NULL_V(tts, false);371return tts->is_speaking();372}373374bool DisplayServerX11::tts_is_paused() const {375if (unlikely(!tts)) {376initialize_tts();377}378ERR_FAIL_NULL_V(tts, false);379return tts->is_paused();380}381382TypedArray<Dictionary> DisplayServerX11::tts_get_voices() const {383if (unlikely(!tts)) {384initialize_tts();385}386ERR_FAIL_NULL_V(tts, TypedArray<Dictionary>());387return tts->get_voices();388}389390void 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) {391if (unlikely(!tts)) {392initialize_tts();393}394ERR_FAIL_NULL(tts);395tts->speak(p_text, p_voice, p_volume, p_pitch, p_rate, p_utterance_id, p_interrupt);396}397398void DisplayServerX11::tts_pause() {399if (unlikely(!tts)) {400initialize_tts();401}402ERR_FAIL_NULL(tts);403tts->pause();404}405406void DisplayServerX11::tts_resume() {407if (unlikely(!tts)) {408initialize_tts();409}410ERR_FAIL_NULL(tts);411tts->resume();412}413414void DisplayServerX11::tts_stop() {415if (unlikely(!tts)) {416initialize_tts();417}418ERR_FAIL_NULL(tts);419tts->stop();420}421422#endif423424#ifdef DBUS_ENABLED425426bool DisplayServerX11::is_dark_mode_supported() const {427return portal_desktop && portal_desktop->is_supported() && portal_desktop->is_settings_supported();428}429430bool DisplayServerX11::is_dark_mode() const {431if (!is_dark_mode_supported()) {432return false;433}434switch (portal_desktop->get_appearance_color_scheme()) {435case 1:436// Prefers dark theme.437return true;438case 2:439// Prefers light theme.440return false;441default:442// Preference unknown.443return false;444}445}446447Color DisplayServerX11::get_accent_color() const {448if (!portal_desktop) {449return Color();450}451return portal_desktop->get_appearance_accent_color();452}453454void DisplayServerX11::set_system_theme_change_callback(const Callable &p_callable) {455ERR_FAIL_COND(!portal_desktop);456portal_desktop->set_system_theme_change_callback(p_callable);457}458459Error DisplayServerX11::file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback, WindowID p_window_id) {460ERR_FAIL_COND_V(!portal_desktop, ERR_UNAVAILABLE);461WindowID window_id = p_window_id;462463if (!windows.has(window_id) || windows[window_id].is_popup) {464window_id = MAIN_WINDOW_ID;465}466467String xid = vformat("x11:%x", (uint64_t)windows[window_id].x11_window);468return portal_desktop->file_dialog_show(p_window_id, xid, p_title, p_current_directory, String(), p_filename, p_mode, p_filters, TypedArray<Dictionary>(), p_callback, false);469}470471Error DisplayServerX11::file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback, WindowID p_window_id) {472ERR_FAIL_COND_V(!portal_desktop, ERR_UNAVAILABLE);473WindowID window_id = p_window_id;474475if (!windows.has(window_id) || windows[window_id].is_popup) {476window_id = MAIN_WINDOW_ID;477}478479String xid = vformat("x11:%x", (uint64_t)windows[window_id].x11_window);480return portal_desktop->file_dialog_show(p_window_id, xid, p_title, p_current_directory, p_root, p_filename, p_mode, p_filters, p_options, p_callback, true);481}482483#endif484485void DisplayServerX11::beep() const {486XBell(x11_display, 0);487}488489void DisplayServerX11::_mouse_update_mode() {490_THREAD_SAFE_METHOD_491492MouseMode wanted_mouse_mode = mouse_mode_override_enabled493? mouse_mode_override494: mouse_mode_base;495496if (wanted_mouse_mode == mouse_mode) {497return;498}499500if (mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN) {501XUngrabPointer(x11_display, CurrentTime);502}503504// The only modes that show a cursor are VISIBLE and CONFINED505bool show_cursor = (wanted_mouse_mode == MOUSE_MODE_VISIBLE || wanted_mouse_mode == MOUSE_MODE_CONFINED);506bool previously_shown = (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED);507508if (show_cursor && !previously_shown) {509WindowID window_id = get_window_at_screen_position(mouse_get_position());510if (window_id != INVALID_WINDOW_ID && window_mouseover_id != window_id) {511if (window_mouseover_id != INVALID_WINDOW_ID) {512_send_window_event(windows[window_mouseover_id], WINDOW_EVENT_MOUSE_EXIT);513}514window_mouseover_id = window_id;515_send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_ENTER);516}517}518519for (const KeyValue<WindowID, WindowData> &E : windows) {520if (show_cursor) {521XDefineCursor(x11_display, E.value.x11_window, cursors[current_cursor]); // show cursor522} else {523XDefineCursor(x11_display, E.value.x11_window, null_cursor); // hide cursor524}525}526mouse_mode = wanted_mouse_mode;527528if (mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN) {529//flush pending motion events530_flush_mouse_motion();531WindowID window_id = _get_focused_window_or_popup();532if (!windows.has(window_id)) {533window_id = MAIN_WINDOW_ID;534}535WindowData &window = windows[window_id];536537if (XGrabPointer(538x11_display, window.x11_window, True,539ButtonPressMask | ButtonReleaseMask | PointerMotionMask,540GrabModeAsync, GrabModeAsync, window.x11_window, None, CurrentTime) != GrabSuccess) {541ERR_PRINT("NO GRAB");542}543544if (mouse_mode == MOUSE_MODE_CAPTURED) {545center.x = window.size.width / 2;546center.y = window.size.height / 2;547548XWarpPointer(x11_display, None, window.x11_window,5490, 0, 0, 0, (int)center.x, (int)center.y);550551Input::get_singleton()->set_mouse_position(center);552}553} else {554do_mouse_warp = false;555}556557XFlush(x11_display);558}559560void DisplayServerX11::mouse_set_mode(MouseMode p_mode) {561ERR_FAIL_INDEX(p_mode, MouseMode::MOUSE_MODE_MAX);562if (p_mode == mouse_mode_base) {563return;564}565mouse_mode_base = p_mode;566_mouse_update_mode();567}568569DisplayServerX11::MouseMode DisplayServerX11::mouse_get_mode() const {570return mouse_mode;571}572573void DisplayServerX11::mouse_set_mode_override(MouseMode p_mode) {574ERR_FAIL_INDEX(p_mode, MouseMode::MOUSE_MODE_MAX);575if (p_mode == mouse_mode_override) {576return;577}578mouse_mode_override = p_mode;579_mouse_update_mode();580}581582DisplayServerX11::MouseMode DisplayServerX11::mouse_get_mode_override() const {583return mouse_mode_override;584}585586void DisplayServerX11::mouse_set_mode_override_enabled(bool p_override_enabled) {587if (p_override_enabled == mouse_mode_override_enabled) {588return;589}590mouse_mode_override_enabled = p_override_enabled;591_mouse_update_mode();592}593594bool DisplayServerX11::mouse_is_mode_override_enabled() const {595return mouse_mode_override_enabled;596}597598void DisplayServerX11::warp_mouse(const Point2i &p_position) {599_THREAD_SAFE_METHOD_600601if (mouse_mode == MOUSE_MODE_CAPTURED) {602last_mouse_pos = p_position;603} else {604WindowID window_id = _get_focused_window_or_popup();605if (!windows.has(window_id)) {606window_id = MAIN_WINDOW_ID;607}608609XWarpPointer(x11_display, None, windows[window_id].x11_window,6100, 0, 0, 0, (int)p_position.x, (int)p_position.y);611}612}613614Point2i DisplayServerX11::mouse_get_position() const {615int number_of_screens = XScreenCount(x11_display);616for (int i = 0; i < number_of_screens; i++) {617Window root, child;618int root_x, root_y, win_x, win_y;619unsigned int mask;620if (XQueryPointer(x11_display, XRootWindow(x11_display, i), &root, &child, &root_x, &root_y, &win_x, &win_y, &mask)) {621XWindowAttributes root_attrs;622XGetWindowAttributes(x11_display, root, &root_attrs);623624return Vector2i(root_attrs.x + root_x, root_attrs.y + root_y);625}626}627return Vector2i();628}629630BitField<MouseButtonMask> DisplayServerX11::mouse_get_button_state() const {631int number_of_screens = XScreenCount(x11_display);632for (int i = 0; i < number_of_screens; i++) {633Window root, child;634int root_x, root_y, win_x, win_y;635unsigned int mask;636if (XQueryPointer(x11_display, XRootWindow(x11_display, i), &root, &child, &root_x, &root_y, &win_x, &win_y, &mask)) {637BitField<MouseButtonMask> last_button_state = MouseButtonMask::NONE;638639if (mask & Button1Mask) {640last_button_state.set_flag(MouseButtonMask::LEFT);641}642if (mask & Button2Mask) {643last_button_state.set_flag(MouseButtonMask::MIDDLE);644}645if (mask & Button3Mask) {646last_button_state.set_flag(MouseButtonMask::RIGHT);647}648if (mask & Button4Mask) {649last_button_state.set_flag(MouseButtonMask::MB_XBUTTON1);650}651if (mask & Button5Mask) {652last_button_state.set_flag(MouseButtonMask::MB_XBUTTON2);653}654655return last_button_state;656}657}658return MouseButtonMask::NONE;659}660661void DisplayServerX11::clipboard_set(const String &p_text) {662_THREAD_SAFE_METHOD_663664{665// The clipboard content can be accessed while polling for events.666MutexLock mutex_lock(events_mutex);667internal_clipboard = p_text;668}669670XSetSelectionOwner(x11_display, XA_PRIMARY, windows[MAIN_WINDOW_ID].x11_window, CurrentTime);671XSetSelectionOwner(x11_display, XInternAtom(x11_display, "CLIPBOARD", 0), windows[MAIN_WINDOW_ID].x11_window, CurrentTime);672}673674void DisplayServerX11::clipboard_set_primary(const String &p_text) {675_THREAD_SAFE_METHOD_676if (!p_text.is_empty()) {677{678// The clipboard content can be accessed while polling for events.679MutexLock mutex_lock(events_mutex);680internal_clipboard_primary = p_text;681}682683XSetSelectionOwner(x11_display, XA_PRIMARY, windows[MAIN_WINDOW_ID].x11_window, CurrentTime);684XSetSelectionOwner(x11_display, XInternAtom(x11_display, "PRIMARY", 0), windows[MAIN_WINDOW_ID].x11_window, CurrentTime);685}686}687688Bool DisplayServerX11::_predicate_clipboard_selection(Display *display, XEvent *event, XPointer arg) {689if (event->type == SelectionNotify && event->xselection.requestor == *(Window *)arg) {690return True;691} else {692return False;693}694}695696Bool DisplayServerX11::_predicate_clipboard_incr(Display *display, XEvent *event, XPointer arg) {697if (event->type == PropertyNotify && event->xproperty.state == PropertyNewValue && event->xproperty.atom == *(Atom *)arg) {698return True;699} else {700return False;701}702}703704String DisplayServerX11::_clipboard_get_impl(Atom p_source, Window x11_window, Atom target) const {705String ret;706707Window selection_owner = XGetSelectionOwner(x11_display, p_source);708if (selection_owner == x11_window) {709static const char *target_type = "PRIMARY";710if (p_source != None && get_atom_name(x11_display, p_source) == target_type) {711return internal_clipboard_primary;712} else {713return internal_clipboard;714}715}716717if (selection_owner != None) {718// Block events polling while processing selection events.719MutexLock mutex_lock(events_mutex);720721Atom selection = XA_PRIMARY;722XConvertSelection(x11_display, p_source, target, selection,723x11_window, CurrentTime);724725XFlush(x11_display);726727// Blocking wait for predicate to be True and remove the event from the queue.728XEvent event;729XIfEvent(x11_display, &event, _predicate_clipboard_selection, (XPointer)&x11_window);730731// Do not get any data, see how much data is there.732Atom type;733int format, result;734unsigned long len, bytes_left, dummy;735unsigned char *data;736XGetWindowProperty(x11_display, x11_window,737selection, // Tricky..7380, 0, // offset - len7390, // Delete 0==FALSE740AnyPropertyType, // flag741&type, // return type742&format, // return format743&len, &bytes_left, // data length744&data);745746if (data) {747XFree(data);748}749750if (type == XInternAtom(x11_display, "INCR", 0)) {751// Data is going to be received incrementally.752DEBUG_LOG_X11("INCR selection started.\n");753754LocalVector<uint8_t> incr_data;755uint32_t data_size = 0;756bool success = false;757758// Delete INCR property to notify the owner.759XDeleteProperty(x11_display, x11_window, type);760761// Process events from the queue.762bool done = false;763while (!done) {764if (!_wait_for_events()) {765// Error or timeout, abort.766break;767}768769// Non-blocking wait for next event and remove it from the queue.770XEvent ev;771while (XCheckIfEvent(x11_display, &ev, _predicate_clipboard_incr, (XPointer)&selection)) {772result = XGetWindowProperty(x11_display, x11_window,773selection, // selection type7740, LONG_MAX, // offset - len775True, // delete property to notify the owner776AnyPropertyType, // flag777&type, // return type778&format, // return format779&len, &bytes_left, // data length780&data);781782DEBUG_LOG_X11("PropertyNotify: len=%lu, format=%i\n", len, format);783784if (result == Success) {785if (data && (len > 0)) {786uint32_t prev_size = incr_data.size();787if (prev_size == 0) {788// First property contains initial data size.789unsigned long initial_size = *(unsigned long *)data;790incr_data.resize(initial_size);791} else {792// New chunk, resize to be safe and append data.793incr_data.resize(MAX(data_size + len, prev_size));794memcpy(incr_data.ptr() + data_size, data, len);795data_size += len;796}797} else {798// Last chunk, process finished.799done = true;800success = true;801}802} else {803print_verbose("Failed to get selection data chunk.");804done = true;805}806807if (data) {808XFree(data);809}810811if (done) {812break;813}814}815}816817if (success && (data_size > 0)) {818ret.append_utf8((const char *)incr_data.ptr(), data_size);819}820} else if (bytes_left > 0) {821// Data is ready and can be processed all at once.822result = XGetWindowProperty(x11_display, x11_window,823selection, 0, bytes_left, 0,824AnyPropertyType, &type, &format,825&len, &dummy, &data);826827if (result == Success) {828ret.append_utf8((const char *)data);829} else {830print_verbose("Failed to get selection data.");831}832833if (data) {834XFree(data);835}836}837}838839return ret;840}841842Atom DisplayServerX11::_clipboard_get_image_target(Atom p_source, Window x11_window) const {843Atom target = XInternAtom(x11_display, "TARGETS", 0);844Atom png = XInternAtom(x11_display, "image/png", 0);845Atom *valid_targets = nullptr;846unsigned long atom_count = 0;847848Window selection_owner = XGetSelectionOwner(x11_display, p_source);849if (selection_owner != None && selection_owner != x11_window) {850// Block events polling while processing selection events.851MutexLock mutex_lock(events_mutex);852853Atom selection = XA_PRIMARY;854XConvertSelection(x11_display, p_source, target, selection, x11_window, CurrentTime);855856XFlush(x11_display);857858// Blocking wait for predicate to be True and remove the event from the queue.859XEvent event;860XIfEvent(x11_display, &event, _predicate_clipboard_selection, (XPointer)&x11_window);861// Do not get any data, see how much data is there.862Atom type;863int format, result;864unsigned long len, bytes_left, dummy;865XGetWindowProperty(x11_display, x11_window,866selection, // Tricky..8670, 0, // offset - len8680, // Delete 0==FALSE869XA_ATOM, // flag870&type, // return type871&format, // return format872&len, &bytes_left, // data length873(unsigned char **)&valid_targets);874875if (valid_targets) {876XFree(valid_targets);877valid_targets = nullptr;878}879880if (type == XA_ATOM && bytes_left > 0) {881// Data is ready and can be processed all at once.882result = XGetWindowProperty(x11_display, x11_window,883selection, 0, bytes_left / 4, 0,884XA_ATOM, &type, &format,885&len, &dummy, (unsigned char **)&valid_targets);886if (result == Success) {887atom_count = len;888} else {889print_verbose("Failed to get selection data.");890return None;891}892} else {893return None;894}895} else {896return None;897}898for (unsigned long i = 0; i < atom_count; i++) {899Atom atom = valid_targets[i];900if (atom == png) {901XFree(valid_targets);902return png;903}904}905906XFree(valid_targets);907return None;908}909910String DisplayServerX11::_clipboard_get(Atom p_source, Window x11_window) const {911String ret;912Atom utf8_atom = XInternAtom(x11_display, "UTF8_STRING", True);913if (utf8_atom != None) {914ret = _clipboard_get_impl(p_source, x11_window, utf8_atom);915}916if (ret.is_empty()) {917ret = _clipboard_get_impl(p_source, x11_window, XA_STRING);918}919return ret;920}921922String DisplayServerX11::clipboard_get() const {923_THREAD_SAFE_METHOD_924925String ret;926ret = _clipboard_get(XInternAtom(x11_display, "CLIPBOARD", 0), windows[MAIN_WINDOW_ID].x11_window);927928if (ret.is_empty()) {929ret = _clipboard_get(XA_PRIMARY, windows[MAIN_WINDOW_ID].x11_window);930}931932return ret;933}934935String DisplayServerX11::clipboard_get_primary() const {936_THREAD_SAFE_METHOD_937938String ret;939ret = _clipboard_get(XInternAtom(x11_display, "PRIMARY", 0), windows[MAIN_WINDOW_ID].x11_window);940941if (ret.is_empty()) {942ret = _clipboard_get(XA_PRIMARY, windows[MAIN_WINDOW_ID].x11_window);943}944945return ret;946}947948Ref<Image> DisplayServerX11::clipboard_get_image() const {949_THREAD_SAFE_METHOD_950Atom clipboard = XInternAtom(x11_display, "CLIPBOARD", 0);951Window x11_window = windows[MAIN_WINDOW_ID].x11_window;952Ref<Image> ret;953Atom target = _clipboard_get_image_target(clipboard, x11_window);954if (target == None) {955return ret;956}957958Window selection_owner = XGetSelectionOwner(x11_display, clipboard);959960if (selection_owner != None && selection_owner != x11_window) {961// Block events polling while processing selection events.962MutexLock mutex_lock(events_mutex);963964// Identifier for the property the other window965// will send the converted data to.966Atom transfer_prop = XA_PRIMARY;967XConvertSelection(x11_display,968clipboard, // source selection969target, // format to convert to970transfer_prop, // output property971x11_window, CurrentTime);972973XFlush(x11_display);974975// Blocking wait for predicate to be True and remove the event from the queue.976XEvent event;977XIfEvent(x11_display, &event, _predicate_clipboard_selection, (XPointer)&x11_window);978979// Do not get any data, see how much data is there.980Atom type;981int format, result;982unsigned long len, bytes_left, dummy;983unsigned char *data;984XGetWindowProperty(x11_display, x11_window,985transfer_prop, // Property data is transferred through9860, 1, // offset, len (4 so we can get the size if INCR is used)9870, // Delete 0==FALSE988AnyPropertyType, // flag989&type, // return type990&format, // return format991&len, &bytes_left, // data length992&data);993994if (type == XInternAtom(x11_display, "INCR", 0)) {995ERR_FAIL_COND_V_MSG(len != 1, ret, "Incremental transfer initial value was not length.");996997// Data is going to be received incrementally.998DEBUG_LOG_X11("INCR selection started.\n");9991000LocalVector<uint8_t> incr_data;1001uint32_t data_size = 0;1002bool success = false;10031004// Initial response is the lower bound of the length of the transferred data.1005incr_data.resize(*(unsigned long *)data);1006XFree(data);1007data = nullptr;10081009// Delete INCR property to notify the owner.1010XDeleteProperty(x11_display, x11_window, transfer_prop);10111012// Process events from the queue.1013bool done = false;1014while (!done) {1015if (!_wait_for_events()) {1016// Error or timeout, abort.1017break;1018}1019// Non-blocking wait for next event and remove it from the queue.1020XEvent ev;1021while (XCheckIfEvent(x11_display, &ev, _predicate_clipboard_incr, (XPointer)&transfer_prop)) {1022result = XGetWindowProperty(x11_display, x11_window,1023transfer_prop, // output property10240, LONG_MAX, // offset - len1025True, // delete property to notify the owner1026AnyPropertyType, // flag1027&type, // return type1028&format, // return format1029&len, &bytes_left, // data length1030&data);10311032DEBUG_LOG_X11("PropertyNotify: len=%lu, format=%i\n", len, format);10331034if (result == Success) {1035if (data && (len > 0)) {1036uint32_t prev_size = incr_data.size();1037// New chunk, resize to be safe and append data.1038incr_data.resize(MAX(data_size + len, prev_size));1039memcpy(incr_data.ptr() + data_size, data, len);1040data_size += len;1041} else if (!(format == 0 && len == 0)) {1042// For unclear reasons the first GetWindowProperty always returns a length and format of 0.1043// Otherwise, last chunk, process finished.1044done = true;1045success = true;1046}1047} else {1048print_verbose("Failed to get selection data chunk.");1049done = true;1050}10511052if (data) {1053XFree(data);1054data = nullptr;1055}10561057if (done) {1058break;1059}1060}1061}10621063if (success && (data_size > 0)) {1064ret.instantiate();1065PNGDriverCommon::png_to_image(incr_data.ptr(), incr_data.size(), false, ret);1066}1067} else if (bytes_left > 0) {1068if (data) {1069XFree(data);1070data = nullptr;1071}1072// Data is ready and can be processed all at once.1073result = XGetWindowProperty(x11_display, x11_window,1074transfer_prop, 0, bytes_left + 4, 0,1075AnyPropertyType, &type, &format,1076&len, &dummy, &data);1077if (result == Success) {1078ret.instantiate();1079PNGDriverCommon::png_to_image((uint8_t *)data, bytes_left, false, ret);1080} else {1081print_verbose("Failed to get selection data.");1082}10831084if (data) {1085XFree(data);1086}1087}1088}10891090return ret;1091}10921093bool DisplayServerX11::clipboard_has_image() const {1094Atom target = _clipboard_get_image_target(1095XInternAtom(x11_display, "CLIPBOARD", 0),1096windows[MAIN_WINDOW_ID].x11_window);1097return target != None;1098}10991100Bool DisplayServerX11::_predicate_clipboard_save_targets(Display *display, XEvent *event, XPointer arg) {1101if (event->xany.window == *(Window *)arg) {1102return (event->type == SelectionRequest) ||1103(event->type == SelectionNotify);1104} else {1105return False;1106}1107}11081109void DisplayServerX11::_clipboard_transfer_ownership(Atom p_source, Window x11_window) const {1110_THREAD_SAFE_METHOD_11111112Window selection_owner = XGetSelectionOwner(x11_display, p_source);11131114if (selection_owner != x11_window) {1115return;1116}11171118// Block events polling while processing selection events.1119MutexLock mutex_lock(events_mutex);11201121Atom clipboard_manager = XInternAtom(x11_display, "CLIPBOARD_MANAGER", False);1122Atom save_targets = XInternAtom(x11_display, "SAVE_TARGETS", False);1123XConvertSelection(x11_display, clipboard_manager, save_targets, None,1124x11_window, CurrentTime);11251126// Process events from the queue.1127while (true) {1128if (!_wait_for_events()) {1129// Error or timeout, abort.1130break;1131}11321133// Non-blocking wait for next event and remove it from the queue.1134XEvent ev;1135while (XCheckIfEvent(x11_display, &ev, _predicate_clipboard_save_targets, (XPointer)&x11_window)) {1136switch (ev.type) {1137case SelectionRequest:1138_handle_selection_request_event(&(ev.xselectionrequest));1139break;11401141case SelectionNotify: {1142if (ev.xselection.target == save_targets) {1143// Once SelectionNotify is received, we're done whether it succeeded or not.1144return;1145}11461147break;1148}1149}1150}1151}1152}11531154int DisplayServerX11::get_screen_count() const {1155_THREAD_SAFE_METHOD_1156int count = 0;11571158// Using Xinerama Extension1159int event_base, error_base;1160if (xinerama_ext_ok && XineramaQueryExtension(x11_display, &event_base, &error_base)) {1161XineramaScreenInfo *xsi = XineramaQueryScreens(x11_display, &count);1162XFree(xsi);1163}1164if (count == 0) {1165count = XScreenCount(x11_display);1166}11671168return count;1169}11701171int DisplayServerX11::get_primary_screen() const {1172int event_base, error_base;1173if (xinerama_ext_ok && XineramaQueryExtension(x11_display, &event_base, &error_base)) {1174return 0;1175} else {1176return XDefaultScreen(x11_display);1177}1178}11791180int DisplayServerX11::get_keyboard_focus_screen() const {1181int count = get_screen_count();1182if (count < 2) {1183// Early exit with single monitor.1184return 0;1185}11861187Window focus = 0;1188int revert_to = 0;11891190XGetInputFocus(x11_display, &focus, &revert_to);1191if (focus) {1192Window focus_child = 0;1193int x = 0, y = 0;1194XTranslateCoordinates(x11_display, focus, DefaultRootWindow(x11_display), 0, 0, &x, &y, &focus_child);11951196XWindowAttributes xwa;1197XGetWindowAttributes(x11_display, focus, &xwa);1198Rect2i window_rect = Rect2i(x, y, xwa.width, xwa.height);11991200// Find which monitor has the largest overlap with the given window.1201int screen_index = 0;1202int max_area = 0;1203for (int i = 0; i < count; i++) {1204Rect2i screen_rect = _screen_get_rect(i);1205Rect2i intersection = screen_rect.intersection(window_rect);1206int area = intersection.get_area();1207if (area > max_area) {1208max_area = area;1209screen_index = i;1210}1211}1212return screen_index;1213}12141215return get_primary_screen();1216}12171218Rect2i DisplayServerX11::_screen_get_rect(int p_screen) const {1219Rect2i rect(0, 0, 0, 0);12201221p_screen = _get_screen_index(p_screen);1222int screen_count = get_screen_count();1223ERR_FAIL_INDEX_V(p_screen, screen_count, Rect2i());12241225// Using Xinerama Extension.1226bool found = false;1227int event_base, error_base;1228if (xinerama_ext_ok && XineramaQueryExtension(x11_display, &event_base, &error_base)) {1229int count;1230XineramaScreenInfo *xsi = XineramaQueryScreens(x11_display, &count);1231if (xsi) {1232if (count > 0) {1233// Check if screen is valid.1234if (p_screen < count) {1235rect.position.x = xsi[p_screen].x_org;1236rect.position.y = xsi[p_screen].y_org;1237rect.size.width = xsi[p_screen].width;1238rect.size.height = xsi[p_screen].height;1239found = true;1240} else {1241ERR_PRINT(vformat("Invalid screen index: %d (count: %d).", p_screen, count));1242}1243}1244XFree(xsi);1245}1246}12471248if (!found) {1249int count = XScreenCount(x11_display);1250if (p_screen < count) {1251Window root = XRootWindow(x11_display, p_screen);1252XWindowAttributes xwa;1253XGetWindowAttributes(x11_display, root, &xwa);1254rect.position.x = xwa.x;1255rect.position.y = xwa.y;1256rect.size.width = xwa.width;1257rect.size.height = xwa.height;1258} else {1259ERR_PRINT(vformat("Invalid screen index: %d (count: %d).", p_screen, count));1260}1261}12621263return rect;1264}12651266Point2i DisplayServerX11::screen_get_position(int p_screen) const {1267_THREAD_SAFE_METHOD_12681269return _screen_get_rect(p_screen).position;1270}12711272Size2i DisplayServerX11::screen_get_size(int p_screen) const {1273_THREAD_SAFE_METHOD_12741275return _screen_get_rect(p_screen).size;1276}12771278// A Handler to avoid crashing on non-fatal X errors by default.1279//1280// The original X11 error formatter `_XPrintDefaultError` is defined here:1281// https://gitlab.freedesktop.org/xorg/lib/libx11/-/blob/e45ca7b41dcd3ace7681d6897505f85d374640f2/src/XlibInt.c#L13221282// It is not exposed through the API, accesses X11 internals,1283// and is much more complex, so this is a less complete simplified error X11 printer.1284int default_window_error_handler(Display *display, XErrorEvent *error) {1285static char message[1024];1286XGetErrorText(display, error->error_code, message, sizeof(message));12871288ERR_PRINT(vformat("Unhandled XServer error: %s"1289"\n Major opcode of failed request: %d"1290"\n Serial number of failed request: %d"1291"\n Current serial number in output stream: %d",1292String::utf8(message), (uint64_t)error->request_code, (uint64_t)error->minor_code, (uint64_t)error->serial));1293return 0;1294}12951296bool g_bad_window = false;1297int bad_window_error_handler(Display *display, XErrorEvent *error) {1298if (error->error_code == BadWindow) {1299g_bad_window = true;1300} else {1301return default_window_error_handler(display, error);1302}1303return 0;1304}13051306Rect2i DisplayServerX11::screen_get_usable_rect(int p_screen) const {1307_THREAD_SAFE_METHOD_13081309p_screen = _get_screen_index(p_screen);1310int screen_count = get_screen_count();1311ERR_FAIL_INDEX_V(p_screen, screen_count, Rect2i());13121313bool is_multiscreen = screen_count > 1;13141315// Use full monitor size as fallback.1316Rect2i rect = _screen_get_rect(p_screen);13171318// There's generally only one screen reported by xlib even in multi-screen setup,1319// in this case it's just one virtual screen composed of all physical monitors.1320int x11_screen_count = ScreenCount(x11_display);1321Window x11_window = RootWindow(x11_display, p_screen < x11_screen_count ? p_screen : 0);13221323Atom type;1324int format = 0;1325unsigned long remaining = 0;13261327// Find active desktop for the root window.1328unsigned int desktop_index = 0;1329Atom desktop_prop = XInternAtom(x11_display, "_NET_CURRENT_DESKTOP", True);1330if (desktop_prop != None) {1331unsigned long desktop_len = 0;1332unsigned char *desktop_data = nullptr;1333if (XGetWindowProperty(x11_display, x11_window, desktop_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &desktop_len, &remaining, &desktop_data) == Success) {1334if ((format == 32) && (desktop_len > 0) && desktop_data) {1335desktop_index = (unsigned int)desktop_data[0];1336}1337if (desktop_data) {1338XFree(desktop_data);1339}1340}1341}13421343bool use_simple_method = true;13441345// First check for GTK work area, which is more accurate for multi-screen setup.1346if (is_multiscreen) {1347// Use already calculated work area when available.1348Atom gtk_workareas_prop = XInternAtom(x11_display, "_GTK_WORKAREAS", False);1349if (gtk_workareas_prop != None) {1350char gtk_workarea_prop_name[32];1351snprintf(gtk_workarea_prop_name, 32, "_GTK_WORKAREAS_D%d", desktop_index);1352Atom gtk_workarea_prop = XInternAtom(x11_display, gtk_workarea_prop_name, True);1353if (gtk_workarea_prop != None) {1354unsigned long workarea_len = 0;1355unsigned char *workarea_data = nullptr;1356if (XGetWindowProperty(x11_display, x11_window, gtk_workarea_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &workarea_len, &remaining, &workarea_data) == Success) {1357if ((format == 32) && (workarea_len % 4 == 0) && workarea_data) {1358long *rect_data = (long *)workarea_data;1359for (uint32_t data_offset = 0; data_offset < workarea_len; data_offset += 4) {1360Rect2i workarea_rect;1361workarea_rect.position.x = rect_data[data_offset];1362workarea_rect.position.y = rect_data[data_offset + 1];1363workarea_rect.size.x = rect_data[data_offset + 2];1364workarea_rect.size.y = rect_data[data_offset + 3];13651366// Intersect with actual monitor size to find the correct area,1367// because areas are not in the same order as screens from Xinerama.1368if (rect.grow(-1).intersects(workarea_rect)) {1369rect = rect.intersection(workarea_rect);1370XFree(workarea_data);1371return rect;1372}1373}1374}1375}1376if (workarea_data) {1377XFree(workarea_data);1378}1379}1380}13811382// Fallback to calculating work area by hand from struts.1383Atom client_list_prop = XInternAtom(x11_display, "_NET_CLIENT_LIST", True);1384if (client_list_prop != None) {1385unsigned long clients_len = 0;1386unsigned char *clients_data = nullptr;1387if (XGetWindowProperty(x11_display, x11_window, client_list_prop, 0, LONG_MAX, False, XA_WINDOW, &type, &format, &clients_len, &remaining, &clients_data) == Success) {1388if ((format == 32) && (clients_len > 0) && clients_data) {1389Window *windows_data = (Window *)clients_data;13901391Rect2i desktop_rect;1392bool desktop_valid = false;13931394// Get full desktop size.1395{1396Atom desktop_geometry_prop = XInternAtom(x11_display, "_NET_DESKTOP_GEOMETRY", True);1397if (desktop_geometry_prop != None) {1398unsigned long geom_len = 0;1399unsigned char *geom_data = nullptr;1400if (XGetWindowProperty(x11_display, x11_window, desktop_geometry_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &geom_len, &remaining, &geom_data) == Success) {1401if ((format == 32) && (geom_len >= 2) && geom_data) {1402desktop_valid = true;1403long *size_data = (long *)geom_data;1404desktop_rect.size.x = size_data[0];1405desktop_rect.size.y = size_data[1];1406}1407}1408if (geom_data) {1409XFree(geom_data);1410}1411}1412}14131414// Get full desktop position.1415if (desktop_valid) {1416Atom desktop_viewport_prop = XInternAtom(x11_display, "_NET_DESKTOP_VIEWPORT", True);1417if (desktop_viewport_prop != None) {1418unsigned long viewport_len = 0;1419unsigned char *viewport_data = nullptr;1420if (XGetWindowProperty(x11_display, x11_window, desktop_viewport_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &viewport_len, &remaining, &viewport_data) == Success) {1421if ((format == 32) && (viewport_len >= 2) && viewport_data) {1422desktop_valid = true;1423long *pos_data = (long *)viewport_data;1424desktop_rect.position.x = pos_data[0];1425desktop_rect.position.y = pos_data[1];1426}1427}1428if (viewport_data) {1429XFree(viewport_data);1430}1431}1432}14331434if (desktop_valid) {1435// Handle bad window errors silently because there's no other way to check1436// that one of the windows has been destroyed in the meantime.1437int (*oldHandler)(Display *, XErrorEvent *) = XSetErrorHandler(&bad_window_error_handler);14381439for (unsigned long win_index = 0; win_index < clients_len; ++win_index) {1440g_bad_window = false;14411442// Remove strut size from desktop size to get a more accurate result.1443bool strut_found = false;1444unsigned long strut_len = 0;1445unsigned char *strut_data = nullptr;1446Atom strut_partial_prop = XInternAtom(x11_display, "_NET_WM_STRUT_PARTIAL", True);1447if (strut_partial_prop != None) {1448if (XGetWindowProperty(x11_display, windows_data[win_index], strut_partial_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &strut_len, &remaining, &strut_data) == Success) {1449strut_found = true;1450}1451}1452// Fallback to older strut property.1453if (!g_bad_window && !strut_found) {1454Atom strut_prop = XInternAtom(x11_display, "_NET_WM_STRUT", True);1455if (strut_prop != None) {1456if (XGetWindowProperty(x11_display, windows_data[win_index], strut_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &strut_len, &remaining, &strut_data) == Success) {1457strut_found = true;1458}1459}1460}1461if (!g_bad_window && strut_found && (format == 32) && (strut_len >= 4) && strut_data) {1462use_simple_method = false;14631464long *struts = (long *)strut_data;14651466long left = struts[0];1467long right = struts[1];1468long top = struts[2];1469long bottom = struts[3];14701471long left_start_y, left_end_y, right_start_y, right_end_y;1472long top_start_x, top_end_x, bottom_start_x, bottom_end_x;14731474if (strut_len >= 12) {1475left_start_y = struts[4];1476left_end_y = struts[5];1477right_start_y = struts[6];1478right_end_y = struts[7];1479top_start_x = struts[8];1480top_end_x = struts[9];1481bottom_start_x = struts[10];1482bottom_end_x = struts[11];1483} else {1484left_start_y = 0;1485left_end_y = desktop_rect.size.y;1486right_start_y = 0;1487right_end_y = desktop_rect.size.y;1488top_start_x = 0;1489top_end_x = desktop_rect.size.x;1490bottom_start_x = 0;1491bottom_end_x = desktop_rect.size.x;1492}14931494const Point2i &pos = desktop_rect.position;1495const Size2i &size = desktop_rect.size;14961497Rect2i left_rect(pos.x, pos.y + left_start_y, left, left_end_y - left_start_y);1498if (left_rect.size.x > 0) {1499Rect2i intersection = rect.intersection(left_rect);1500if (intersection.has_area() && intersection.size.x < rect.size.x) {1501rect.position.x = left_rect.size.x;1502rect.size.x = rect.size.x - intersection.size.x;1503}1504}15051506Rect2i right_rect(pos.x + size.x - right, pos.y + right_start_y, right, right_end_y - right_start_y);1507if (right_rect.size.x > 0) {1508Rect2i intersection = rect.intersection(right_rect);1509if (intersection.has_area() && right_rect.size.x < rect.size.x) {1510rect.size.x = intersection.position.x - rect.position.x;1511}1512}15131514Rect2i top_rect(pos.x + top_start_x, pos.y, top_end_x - top_start_x, top);1515if (top_rect.size.y > 0) {1516Rect2i intersection = rect.intersection(top_rect);1517if (intersection.has_area() && intersection.size.y < rect.size.y) {1518rect.position.y = top_rect.size.y;1519rect.size.y = rect.size.y - intersection.size.y;1520}1521}15221523Rect2i bottom_rect(pos.x + bottom_start_x, pos.y + size.y - bottom, bottom_end_x - bottom_start_x, bottom);1524if (bottom_rect.size.y > 0) {1525Rect2i intersection = rect.intersection(bottom_rect);1526if (intersection.has_area() && right_rect.size.y < rect.size.y) {1527rect.size.y = intersection.position.y - rect.position.y;1528}1529}1530}1531if (strut_data) {1532XFree(strut_data);1533}1534}15351536// Restore default error handler.1537XSetErrorHandler(oldHandler);1538}1539}1540}1541if (clients_data) {1542XFree(clients_data);1543}1544}1545}15461547// Single screen or fallback for multi screen.1548if (use_simple_method) {1549// Get desktop available size from the global work area.1550Atom workarea_prop = XInternAtom(x11_display, "_NET_WORKAREA", True);1551if (workarea_prop != None) {1552unsigned long workarea_len = 0;1553unsigned char *workarea_data = nullptr;1554if (XGetWindowProperty(x11_display, x11_window, workarea_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &workarea_len, &remaining, &workarea_data) == Success) {1555if ((format == 32) && (workarea_len >= ((desktop_index + 1) * 4)) && workarea_data) {1556long *rect_data = (long *)workarea_data;1557int data_offset = desktop_index * 4;1558Rect2i workarea_rect;1559workarea_rect.position.x = rect_data[data_offset];1560workarea_rect.position.y = rect_data[data_offset + 1];1561workarea_rect.size.x = rect_data[data_offset + 2];1562workarea_rect.size.y = rect_data[data_offset + 3];15631564// Intersect with actual monitor size to get a proper approximation in multi-screen setup.1565if (!is_multiscreen) {1566rect = workarea_rect;1567} else if (rect.intersects(workarea_rect)) {1568rect = rect.intersection(workarea_rect);1569}1570}1571}1572if (workarea_data) {1573XFree(workarea_data);1574}1575}1576}15771578return rect;1579}15801581Rect2i DisplayServerX11::_screens_get_full_rect() const {1582Rect2i full_rect;15831584int count = get_screen_count();1585for (int i = 0; i < count; i++) {1586if (i == 0) {1587full_rect = _screen_get_rect(i);1588continue;1589}15901591Rect2i screen_rect = _screen_get_rect(i);1592if (full_rect.position.x > screen_rect.position.x) {1593full_rect.size.x += full_rect.position.x - screen_rect.position.x;1594full_rect.position.x = screen_rect.position.x;1595}1596if (full_rect.position.y > screen_rect.position.y) {1597full_rect.size.y += full_rect.position.y - screen_rect.position.y;1598full_rect.position.y = screen_rect.position.y;1599}1600if (full_rect.position.x + full_rect.size.x < screen_rect.position.x + screen_rect.size.x) {1601full_rect.size.x = screen_rect.position.x + screen_rect.size.x - full_rect.position.x;1602}1603if (full_rect.position.y + full_rect.size.y < screen_rect.position.y + screen_rect.size.y) {1604full_rect.size.y = screen_rect.position.y + screen_rect.size.y - full_rect.position.y;1605}1606}16071608return full_rect;1609}16101611int DisplayServerX11::screen_get_dpi(int p_screen) const {1612_THREAD_SAFE_METHOD_16131614p_screen = _get_screen_index(p_screen);1615int screen_count = get_screen_count();1616ERR_FAIL_INDEX_V(p_screen, screen_count, 96);16171618//Get physical monitor Dimensions through XRandR and calculate dpi1619Size2i sc = screen_get_size(p_screen);1620if (xrandr_ext_ok) {1621int count = 0;1622if (xrr_get_monitors) {1623xrr_monitor_info *monitors = xrr_get_monitors(x11_display, windows[MAIN_WINDOW_ID].x11_window, true, &count);1624if (p_screen < count) {1625double xdpi = sc.width / (double)monitors[p_screen].mwidth * 25.4;1626double ydpi = sc.height / (double)monitors[p_screen].mheight * 25.4;1627xrr_free_monitors(monitors);1628return (xdpi + ydpi) / 2;1629}1630xrr_free_monitors(monitors);1631} else if (p_screen == 0) {1632XRRScreenSize *sizes = XRRSizes(x11_display, 0, &count);1633if (sizes) {1634double xdpi = sc.width / (double)sizes[0].mwidth * 25.4;1635double ydpi = sc.height / (double)sizes[0].mheight * 25.4;1636return (xdpi + ydpi) / 2;1637}1638}1639}16401641int width_mm = DisplayWidthMM(x11_display, p_screen);1642int height_mm = DisplayHeightMM(x11_display, p_screen);1643double xdpi = (width_mm ? sc.width / (double)width_mm * 25.4 : 0);1644double ydpi = (height_mm ? sc.height / (double)height_mm * 25.4 : 0);1645if (xdpi || ydpi) {1646return (xdpi + ydpi) / (xdpi && ydpi ? 2 : 1);1647}16481649//could not get dpi1650return 96;1651}16521653int get_image_errorhandler(Display *dpy, XErrorEvent *ev) {1654return 0;1655}16561657Color DisplayServerX11::screen_get_pixel(const Point2i &p_position) const {1658Point2i pos = p_position;16591660if (xwayland) {1661return Color();1662}16631664int (*old_handler)(Display *, XErrorEvent *) = XSetErrorHandler(&get_image_errorhandler);16651666Color color;1667int number_of_screens = XScreenCount(x11_display);1668for (int i = 0; i < number_of_screens; i++) {1669Window root = XRootWindow(x11_display, i);1670XWindowAttributes root_attrs;1671XGetWindowAttributes(x11_display, root, &root_attrs);1672if ((pos.x >= root_attrs.x) && (pos.x <= root_attrs.x + root_attrs.width) && (pos.y >= root_attrs.y) && (pos.y <= root_attrs.y + root_attrs.height)) {1673XImage *image = XGetImage(x11_display, root, pos.x, pos.y, 1, 1, AllPlanes, XYPixmap);1674if (image) {1675XColor c;1676c.pixel = XGetPixel(image, 0, 0);1677XDestroyImage(image);1678XQueryColor(x11_display, XDefaultColormap(x11_display, i), &c);1679color = Color(float(c.red) / 65535.0, float(c.green) / 65535.0, float(c.blue) / 65535.0, 1.0);1680break;1681}1682}1683}16841685XSetErrorHandler(old_handler);16861687return color;1688}16891690Ref<Image> DisplayServerX11::screen_get_image(int p_screen) const {1691ERR_FAIL_INDEX_V(p_screen, get_screen_count(), Ref<Image>());16921693p_screen = _get_screen_index(p_screen);1694int screen_count = get_screen_count();1695ERR_FAIL_INDEX_V(p_screen, screen_count, Ref<Image>());16961697if (xwayland) {1698return Ref<Image>();1699}17001701int (*old_handler)(Display *, XErrorEvent *) = XSetErrorHandler(&get_image_errorhandler);17021703XImage *image = nullptr;17041705bool found = false;1706int event_base, error_base;1707if (xinerama_ext_ok && XineramaQueryExtension(x11_display, &event_base, &error_base)) {1708int xin_count;1709XineramaScreenInfo *xsi = XineramaQueryScreens(x11_display, &xin_count);1710if (xsi) {1711if (xin_count > 0) {1712if (p_screen < xin_count) {1713int x_count = XScreenCount(x11_display);1714for (int i = 0; i < x_count; i++) {1715Window root = XRootWindow(x11_display, i);1716XWindowAttributes root_attrs;1717XGetWindowAttributes(x11_display, root, &root_attrs);1718if ((xsi[p_screen].x_org >= root_attrs.x) && (xsi[p_screen].x_org <= root_attrs.x + root_attrs.width) && (xsi[p_screen].y_org >= root_attrs.y) && (xsi[p_screen].y_org <= root_attrs.y + root_attrs.height)) {1719found = true;1720image = XGetImage(x11_display, root, xsi[p_screen].x_org, xsi[p_screen].y_org, xsi[p_screen].width, xsi[p_screen].height, AllPlanes, ZPixmap);1721break;1722}1723}1724} else {1725ERR_PRINT(vformat("Invalid screen index: %d (count: %d).", p_screen, xin_count));1726}1727}1728XFree(xsi);1729}1730}1731if (!found) {1732int x_count = XScreenCount(x11_display);1733if (p_screen < x_count) {1734Window root = XRootWindow(x11_display, p_screen);17351736XWindowAttributes root_attrs;1737XGetWindowAttributes(x11_display, root, &root_attrs);17381739image = XGetImage(x11_display, root, root_attrs.x, root_attrs.y, root_attrs.width, root_attrs.height, AllPlanes, ZPixmap);1740} else {1741ERR_PRINT(vformat("Invalid screen index: %d (count: %d).", p_screen, x_count));1742}1743}17441745XSetErrorHandler(old_handler);17461747Ref<Image> img;1748if (image) {1749int width = image->width;1750int height = image->height;17511752Vector<uint8_t> img_data;1753img_data.resize(height * width * 4);17541755uint8_t *sr = (uint8_t *)image->data;1756uint8_t *wr = (uint8_t *)img_data.ptrw();17571758if (image->bits_per_pixel == 24 && image->red_mask == 0xff0000 && image->green_mask == 0x00ff00 && image->blue_mask == 0x0000ff) {1759for (int y = 0; y < height; y++) {1760for (int x = 0; x < width; x++) {1761wr[(y * width + x) * 4 + 0] = sr[(y * width + x) * 3 + 2];1762wr[(y * width + x) * 4 + 1] = sr[(y * width + x) * 3 + 1];1763wr[(y * width + x) * 4 + 2] = sr[(y * width + x) * 3 + 0];1764wr[(y * width + x) * 4 + 3] = 255;1765}1766}1767} else if (image->bits_per_pixel == 24 && image->red_mask == 0x0000ff && image->green_mask == 0x00ff00 && image->blue_mask == 0xff0000) {1768for (int y = 0; y < height; y++) {1769for (int x = 0; x < width; x++) {1770wr[(y * width + x) * 4 + 0] = sr[(y * width + x) * 3 + 2];1771wr[(y * width + x) * 4 + 1] = sr[(y * width + x) * 3 + 1];1772wr[(y * width + x) * 4 + 2] = sr[(y * width + x) * 3 + 0];1773wr[(y * width + x) * 4 + 3] = 255;1774}1775}1776} else if (image->bits_per_pixel == 32 && image->red_mask == 0xff0000 && image->green_mask == 0x00ff00 && image->blue_mask == 0x0000ff) {1777for (int y = 0; y < height; y++) {1778for (int x = 0; x < width; x++) {1779wr[(y * width + x) * 4 + 0] = sr[(y * width + x) * 4 + 2];1780wr[(y * width + x) * 4 + 1] = sr[(y * width + x) * 4 + 1];1781wr[(y * width + x) * 4 + 2] = sr[(y * width + x) * 4 + 0];1782wr[(y * width + x) * 4 + 3] = 255;1783}1784}1785} else {1786String msg = vformat("XImage with RGB mask %x %x %x and depth %d is not supported.", (uint64_t)image->red_mask, (uint64_t)image->green_mask, (uint64_t)image->blue_mask, (int64_t)image->bits_per_pixel);1787XDestroyImage(image);1788ERR_FAIL_V_MSG(Ref<Image>(), msg);1789}1790img = Image::create_from_data(width, height, false, Image::FORMAT_RGBA8, img_data);1791XDestroyImage(image);1792}17931794return img;1795}17961797float DisplayServerX11::screen_get_refresh_rate(int p_screen) const {1798_THREAD_SAFE_METHOD_17991800p_screen = _get_screen_index(p_screen);1801int screen_count = get_screen_count();1802ERR_FAIL_INDEX_V(p_screen, screen_count, SCREEN_REFRESH_RATE_FALLBACK);18031804//Use xrandr to get screen refresh rate.1805if (xrandr_ext_ok) {1806XRRScreenResources *screen_info = XRRGetScreenResourcesCurrent(x11_display, windows[MAIN_WINDOW_ID].x11_window);1807if (screen_info) {1808RRMode current_mode = 0;1809xrr_monitor_info *monitors = nullptr;18101811if (xrr_get_monitors) {1812int count = 0;1813monitors = xrr_get_monitors(x11_display, windows[MAIN_WINDOW_ID].x11_window, true, &count);1814ERR_FAIL_INDEX_V(p_screen, count, SCREEN_REFRESH_RATE_FALLBACK);1815} else {1816ERR_PRINT("An error occurred while trying to get the screen refresh rate.");1817return SCREEN_REFRESH_RATE_FALLBACK;1818}18191820bool found_active_mode = false;1821for (int crtc = 0; crtc < screen_info->ncrtc; crtc++) { // Loop through outputs to find which one is currently outputting.1822XRRCrtcInfo *monitor_info = XRRGetCrtcInfo(x11_display, screen_info, screen_info->crtcs[crtc]);1823if (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.1824continue;1825}18261827if (monitor_info->mode != None) {1828current_mode = monitor_info->mode;1829found_active_mode = true;1830break;1831}1832}18331834if (found_active_mode) {1835for (int mode = 0; mode < screen_info->nmode; mode++) {1836XRRModeInfo m_info = screen_info->modes[mode];1837if (m_info.id == current_mode) {1838// Snap to nearest 0.01 to stay consistent with other platforms.1839return Math::snapped((float)m_info.dotClock / ((float)m_info.hTotal * (float)m_info.vTotal), 0.01);1840}1841}1842}18431844ERR_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.1845return SCREEN_REFRESH_RATE_FALLBACK;1846} else {1847ERR_PRINT("An error occurred while trying to get the screen refresh rate.");1848return SCREEN_REFRESH_RATE_FALLBACK;1849}1850}1851ERR_PRINT("An error occurred while trying to get the screen refresh rate.");1852return SCREEN_REFRESH_RATE_FALLBACK;1853}18541855#ifdef DBUS_ENABLED1856void DisplayServerX11::screen_set_keep_on(bool p_enable) {1857if (screen_is_kept_on() == p_enable) {1858return;1859}18601861if (screensaver) {1862if (p_enable) {1863screensaver->inhibit();1864} else {1865screensaver->uninhibit();1866}18671868keep_screen_on = p_enable;1869}1870}18711872bool DisplayServerX11::screen_is_kept_on() const {1873return keep_screen_on;1874}1875#endif18761877Vector<DisplayServer::WindowID> DisplayServerX11::get_window_list() const {1878_THREAD_SAFE_METHOD_18791880Vector<int> ret;1881for (const KeyValue<WindowID, WindowData> &E : windows) {1882ret.push_back(E.key);1883}1884return ret;1885}18861887DisplayServer::WindowID DisplayServerX11::create_sub_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect, bool p_exclusive, WindowID p_transient_parent) {1888_THREAD_SAFE_METHOD_18891890WindowID id = _create_window(p_mode, p_vsync_mode, p_flags, p_rect, 0);1891for (int i = 0; i < WINDOW_FLAG_MAX; i++) {1892if (p_flags & (1 << i)) {1893window_set_flag(WindowFlags(i), true, id);1894}1895}1896#ifdef RD_ENABLED1897if (rendering_device) {1898rendering_device->screen_create(id);1899}1900#endif19011902if (p_transient_parent != INVALID_WINDOW_ID) {1903window_set_transient(id, p_transient_parent);1904}19051906return id;1907}19081909void DisplayServerX11::show_window(WindowID p_id) {1910_THREAD_SAFE_METHOD_19111912WindowData &wd = windows[p_id];1913popup_open(p_id);19141915DEBUG_LOG_X11("show_window: %lu (%u) \n", wd.x11_window, p_id);19161917XMapWindow(x11_display, wd.x11_window);1918XSync(x11_display, False);1919_validate_mode_on_map(p_id);19201921if (p_id == MAIN_WINDOW_ID) {1922// Get main window size for boot splash drawing.1923bool get_config_event = false;19241925// Search the X11 event queue for ConfigureNotify events and process all that are currently queued early.1926{1927MutexLock mutex_lock(events_mutex);19281929for (uint32_t event_index = 0; event_index < polled_events.size(); ++event_index) {1930XEvent &config_event = polled_events[event_index];1931if (config_event.type == ConfigureNotify) {1932_window_changed(&config_event);1933get_config_event = true;1934}1935}1936XEvent config_event;1937while (XCheckTypedEvent(x11_display, ConfigureNotify, &config_event)) {1938_window_changed(&config_event);1939get_config_event = true;1940}1941}19421943// Estimate maximize/full screen window size, ConfigureNotify may arrive only after maximize animation is finished.1944if (!get_config_event && (wd.maximized || wd.fullscreen)) {1945int screen = window_get_current_screen(p_id);1946Size2i sz;1947if (wd.maximized) {1948sz = screen_get_usable_rect(screen).size;1949if (!wd.borderless) {1950Atom prop = XInternAtom(x11_display, "_NET_FRAME_EXTENTS", True);1951if (prop != None) {1952Atom type;1953int format;1954unsigned long len;1955unsigned long remaining;1956unsigned char *data = nullptr;1957if (XGetWindowProperty(x11_display, wd.x11_window, prop, 0, 4, False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) {1958if (format == 32 && len == 4 && data) {1959long *extents = (long *)data;1960sz.width -= extents[0] + extents[1]; // left, right1961sz.height -= extents[2] + extents[3]; // top, bottom1962}1963XFree(data);1964}1965}1966}1967} else {1968sz = screen_get_size(screen);1969}1970if (sz == Size2i()) {1971return;1972}19731974wd.size = sz;1975#if defined(RD_ENABLED)1976if (rendering_context) {1977rendering_context->window_set_size(p_id, sz.width, sz.height);1978}1979#endif1980#if defined(GLES3_ENABLED)1981if (gl_manager) {1982gl_manager->window_resize(p_id, sz.width, sz.height);1983}1984if (gl_manager_egl) {1985gl_manager_egl->window_resize(p_id, sz.width, sz.height);1986}1987#endif1988}1989}1990}19911992void DisplayServerX11::delete_sub_window(WindowID p_id) {1993_THREAD_SAFE_METHOD_19941995ERR_FAIL_COND(!windows.has(p_id));1996ERR_FAIL_COND_MSG(p_id == MAIN_WINDOW_ID, "Main window can't be deleted");19971998popup_close(p_id);19992000WindowData &wd = windows[p_id];20012002DEBUG_LOG_X11("delete_sub_window: %lu (%u) \n", wd.x11_window, p_id);20032004if (window_mouseover_id == p_id) {2005window_mouseover_id = INVALID_WINDOW_ID;2006_send_window_event(windows[p_id], WINDOW_EVENT_MOUSE_EXIT);2007}20082009while (wd.transient_children.size()) {2010window_set_transient(*wd.transient_children.begin(), INVALID_WINDOW_ID);2011}20122013if (wd.transient_parent != INVALID_WINDOW_ID) {2014window_set_transient(p_id, INVALID_WINDOW_ID);2015}20162017#if defined(RD_ENABLED)2018if (rendering_device) {2019rendering_device->screen_free(p_id);2020}20212022if (rendering_context) {2023rendering_context->window_destroy(p_id);2024}2025#endif2026#ifdef GLES3_ENABLED2027if (gl_manager) {2028gl_manager->window_destroy(p_id);2029}2030if (gl_manager_egl) {2031gl_manager_egl->window_destroy(p_id);2032}2033#endif20342035#ifdef ACCESSKIT_ENABLED2036if (accessibility_driver) {2037accessibility_driver->window_destroy(p_id);2038}2039#endif20402041if (wd.xic) {2042XDestroyIC(wd.xic);2043wd.xic = nullptr;2044}2045XDestroyWindow(x11_display, wd.x11_xim_window);2046#ifdef XKB_ENABLED2047if (xkb_loaded_v05p) {2048if (wd.xkb_state) {2049xkb_compose_state_unref(wd.xkb_state);2050wd.xkb_state = nullptr;2051}2052}2053#endif20542055XUnmapWindow(x11_display, wd.x11_window);2056XDestroyWindow(x11_display, wd.x11_window);20572058window_set_rect_changed_callback(Callable(), p_id);2059window_set_window_event_callback(Callable(), p_id);2060window_set_input_event_callback(Callable(), p_id);2061window_set_input_text_callback(Callable(), p_id);2062window_set_drop_files_callback(Callable(), p_id);20632064windows.erase(p_id);20652066if (last_focused_window == p_id) {2067last_focused_window = INVALID_WINDOW_ID;2068}2069}20702071int64_t DisplayServerX11::window_get_native_handle(HandleType p_handle_type, WindowID p_window) const {2072ERR_FAIL_COND_V(!windows.has(p_window), 0);2073switch (p_handle_type) {2074case DISPLAY_HANDLE: {2075return (int64_t)x11_display;2076}2077case WINDOW_HANDLE: {2078return (int64_t)windows[p_window].x11_window;2079}2080case WINDOW_VIEW: {2081return 0; // Not supported.2082}2083#ifdef GLES3_ENABLED2084case OPENGL_CONTEXT: {2085if (gl_manager) {2086return (int64_t)gl_manager->get_glx_context(p_window);2087}2088if (gl_manager_egl) {2089return (int64_t)gl_manager_egl->get_context(p_window);2090}2091return 0;2092}2093case EGL_DISPLAY: {2094if (gl_manager_egl) {2095return (int64_t)gl_manager_egl->get_display(p_window);2096}2097return 0;2098}2099case EGL_CONFIG: {2100if (gl_manager_egl) {2101return (int64_t)gl_manager_egl->get_config(p_window);2102}2103return 0;2104}2105#endif2106default: {2107return 0;2108}2109}2110}21112112void DisplayServerX11::window_attach_instance_id(ObjectID p_instance, WindowID p_window) {2113ERR_FAIL_COND(!windows.has(p_window));2114WindowData &wd = windows[p_window];21152116wd.instance_id = p_instance;2117}21182119ObjectID DisplayServerX11::window_get_attached_instance_id(WindowID p_window) const {2120ERR_FAIL_COND_V(!windows.has(p_window), ObjectID());2121const WindowData &wd = windows[p_window];2122return wd.instance_id;2123}21242125DisplayServerX11::WindowID DisplayServerX11::get_window_at_screen_position(const Point2i &p_position) const {2126WindowID found_window = INVALID_WINDOW_ID;2127WindowID parent_window = INVALID_WINDOW_ID;2128unsigned int focus_order = 0;2129for (const KeyValue<WindowID, WindowData> &E : windows) {2130const WindowData &wd = E.value;21312132// Discard windows with no focus.2133if (wd.focus_order == 0) {2134continue;2135}21362137// Find topmost window which contains the given position.2138WindowID window_id = E.key;2139Rect2i win_rect = Rect2i(window_get_position(window_id), window_get_size(window_id));2140if (win_rect.has_point(p_position)) {2141// For siblings, pick the window which was focused last.2142if ((parent_window != wd.transient_parent) || (wd.focus_order > focus_order)) {2143found_window = window_id;2144parent_window = wd.transient_parent;2145focus_order = wd.focus_order;2146}2147}2148}21492150return found_window;2151}21522153void DisplayServerX11::window_set_title(const String &p_title, WindowID p_window) {2154_THREAD_SAFE_METHOD_21552156ERR_FAIL_COND(!windows.has(p_window));2157WindowData &wd = windows[p_window];21582159XStoreName(x11_display, wd.x11_window, p_title.utf8().get_data());21602161Atom _net_wm_name = XInternAtom(x11_display, "_NET_WM_NAME", false);2162Atom utf8_string = XInternAtom(x11_display, "UTF8_STRING", false);2163if (_net_wm_name != None && utf8_string != None) {2164CharString utf8_title = p_title.utf8();2165XChangeProperty(x11_display, wd.x11_window, _net_wm_name, utf8_string, 8, PropModeReplace, (unsigned char *)utf8_title.get_data(), utf8_title.length());2166}2167}21682169void DisplayServerX11::window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window) {2170_THREAD_SAFE_METHOD_21712172ERR_FAIL_COND(!windows.has(p_window));2173windows[p_window].mpath = p_region;2174_update_window_mouse_passthrough(p_window);2175}21762177void DisplayServerX11::_update_window_mouse_passthrough(WindowID p_window) {2178ERR_FAIL_COND(!windows.has(p_window));2179ERR_FAIL_COND(!xshaped_ext_ok);21802181const Vector<Vector2> region_path = windows[p_window].mpath;21822183int event_base, error_base;2184const Bool ext_okay = XShapeQueryExtension(x11_display, &event_base, &error_base);2185if (ext_okay) {2186if (windows[p_window].mpass) {2187Region region = XCreateRegion();2188XShapeCombineRegion(x11_display, windows[p_window].x11_window, ShapeInput, 0, 0, region, ShapeSet);2189XDestroyRegion(region);2190} else if (region_path.is_empty()) {2191XShapeCombineMask(x11_display, windows[p_window].x11_window, ShapeInput, 0, 0, None, ShapeSet);2192} else {2193XPoint *points = (XPoint *)memalloc(sizeof(XPoint) * region_path.size());2194for (int i = 0; i < region_path.size(); i++) {2195points[i].x = region_path[i].x;2196points[i].y = region_path[i].y;2197}2198Region region = XPolygonRegion(points, region_path.size(), EvenOddRule);2199memfree(points);2200XShapeCombineRegion(x11_display, windows[p_window].x11_window, ShapeInput, 0, 0, region, ShapeSet);2201XDestroyRegion(region);2202}2203}2204}22052206void DisplayServerX11::window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window) {2207_THREAD_SAFE_METHOD_22082209ERR_FAIL_COND(!windows.has(p_window));2210WindowData &wd = windows[p_window];2211wd.rect_changed_callback = p_callable;2212}22132214void DisplayServerX11::window_set_window_event_callback(const Callable &p_callable, WindowID p_window) {2215_THREAD_SAFE_METHOD_22162217ERR_FAIL_COND(!windows.has(p_window));2218WindowData &wd = windows[p_window];2219wd.event_callback = p_callable;2220}22212222void DisplayServerX11::window_set_input_event_callback(const Callable &p_callable, WindowID p_window) {2223_THREAD_SAFE_METHOD_22242225ERR_FAIL_COND(!windows.has(p_window));2226WindowData &wd = windows[p_window];2227wd.input_event_callback = p_callable;2228}22292230void DisplayServerX11::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) {2231_THREAD_SAFE_METHOD_22322233ERR_FAIL_COND(!windows.has(p_window));2234WindowData &wd = windows[p_window];2235wd.input_text_callback = p_callable;2236}22372238void DisplayServerX11::window_set_drop_files_callback(const Callable &p_callable, WindowID p_window) {2239_THREAD_SAFE_METHOD_22402241ERR_FAIL_COND(!windows.has(p_window));2242WindowData &wd = windows[p_window];2243wd.drop_files_callback = p_callable;2244}22452246int DisplayServerX11::window_get_current_screen(WindowID p_window) const {2247_THREAD_SAFE_METHOD_22482249int count = get_screen_count();2250if (count < 2) {2251// Early exit with single monitor.2252return 0;2253}22542255ERR_FAIL_COND_V(!windows.has(p_window), INVALID_SCREEN);2256const WindowData &wd = windows[p_window];22572258const Rect2i window_rect(wd.position, wd.size);22592260// Find which monitor has the largest overlap with the given window.2261int screen_index = 0;2262int max_area = 0;2263for (int i = 0; i < count; i++) {2264Rect2i screen_rect = _screen_get_rect(i);2265Rect2i intersection = screen_rect.intersection(window_rect);2266int area = intersection.get_area();2267if (area > max_area) {2268max_area = area;2269screen_index = i;2270}2271}22722273return screen_index;2274}22752276void DisplayServerX11::gl_window_make_current(DisplayServer::WindowID p_window_id) {2277#if defined(GLES3_ENABLED)2278if (gl_manager) {2279gl_manager->window_make_current(p_window_id);2280}2281if (gl_manager_egl) {2282gl_manager_egl->window_make_current(p_window_id);2283}2284#endif2285}22862287void DisplayServerX11::window_set_current_screen(int p_screen, WindowID p_window) {2288_THREAD_SAFE_METHOD_22892290ERR_FAIL_COND(!windows.has(p_window));22912292p_screen = _get_screen_index(p_screen);2293int screen_count = get_screen_count();2294ERR_FAIL_INDEX(p_screen, screen_count);22952296if (window_get_current_screen(p_window) == p_screen) {2297return;2298}2299WindowData &wd = windows[p_window];23002301if (wd.embed_parent) {2302print_line("Embedded window can't be moved to another screen.");2303return;2304}23052306if (window_get_mode(p_window) == WINDOW_MODE_FULLSCREEN || window_get_mode(p_window) == WINDOW_MODE_MAXIMIZED) {2307Point2i position = screen_get_position(p_screen);2308Size2i size = screen_get_size(p_screen);23092310XMoveResizeWindow(x11_display, wd.x11_window, position.x, position.y, size.x, size.y);2311} else {2312Rect2i srect = screen_get_usable_rect(p_screen);2313Point2i wpos = window_get_position(p_window) - screen_get_position(window_get_current_screen(p_window));2314Size2i wsize = window_get_size(p_window);2315wpos += srect.position;2316if (srect != Rect2i()) {2317wpos = wpos.clamp(srect.position, srect.position + srect.size - wsize / 3);2318}2319window_set_position(wpos, p_window);2320}2321}23222323void DisplayServerX11::window_set_transient(WindowID p_window, WindowID p_parent) {2324_THREAD_SAFE_METHOD_23252326ERR_FAIL_COND(p_window == p_parent);23272328ERR_FAIL_COND(!windows.has(p_window));2329WindowData &wd_window = windows[p_window];23302331WindowID prev_parent = wd_window.transient_parent;2332ERR_FAIL_COND(prev_parent == p_parent);23332334DEBUG_LOG_X11("window_set_transient: %lu (%u), prev_parent=%u, parent=%u\n", wd_window.x11_window, p_window, prev_parent, p_parent);23352336ERR_FAIL_COND_MSG(wd_window.on_top, "Windows with the 'on top' can't become transient.");2337if (p_parent == INVALID_WINDOW_ID) {2338//remove transient23392340ERR_FAIL_COND(prev_parent == INVALID_WINDOW_ID);2341ERR_FAIL_COND(!windows.has(prev_parent));23422343WindowData &wd_parent = windows[prev_parent];23442345wd_window.transient_parent = INVALID_WINDOW_ID;2346wd_parent.transient_children.erase(p_window);23472348XSetTransientForHint(x11_display, wd_window.x11_window, None);23492350XWindowAttributes xwa;2351XSync(x11_display, False);2352XGetWindowAttributes(x11_display, wd_parent.x11_window, &xwa);23532354// Set focus to parent sub window to avoid losing all focus when closing a nested sub-menu.2355// RevertToPointerRoot is used to make sure we don't lose all focus in case2356// a subwindow and its parent are both destroyed.2357if (!wd_window.no_focus && !wd_window.is_popup && wd_window.focused) {2358if ((xwa.map_state == IsViewable) && !wd_parent.no_focus && !wd_window.is_popup && _window_focus_check()) {2359_set_input_focus(wd_parent.x11_window, RevertToPointerRoot);2360}2361}2362} else {2363ERR_FAIL_COND(!windows.has(p_parent));2364ERR_FAIL_COND_MSG(prev_parent != INVALID_WINDOW_ID, "Window already has a transient parent");2365WindowData &wd_parent = windows[p_parent];23662367wd_window.transient_parent = p_parent;2368wd_parent.transient_children.insert(p_window);23692370XSetTransientForHint(x11_display, wd_window.x11_window, wd_parent.x11_window);2371}2372}23732374// Helper method. Assumes that the window id has already been checked and exists.2375void DisplayServerX11::_update_size_hints(WindowID p_window) {2376WindowData &wd = windows[p_window];2377WindowMode window_mode = window_get_mode(p_window);2378XSizeHints *xsh = XAllocSizeHints();23792380// Always set the position and size hints - they should be synchronized with the actual values after the window is mapped anyway2381xsh->flags |= PPosition | PSize;2382xsh->x = wd.position.x;2383xsh->y = wd.position.y;2384xsh->width = wd.size.width;2385xsh->height = wd.size.height;23862387if (window_mode == WINDOW_MODE_FULLSCREEN || window_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) {2388// Do not set any other hints to prevent the window manager from ignoring the fullscreen flags2389} else if (window_get_flag(WINDOW_FLAG_RESIZE_DISABLED, p_window)) {2390// If resizing is disabled, use the forced size2391xsh->flags |= PMinSize | PMaxSize;2392xsh->min_width = wd.size.x;2393xsh->max_width = wd.size.x;2394xsh->min_height = wd.size.y;2395xsh->max_height = wd.size.y;2396} else {2397// Otherwise, just respect min_size and max_size2398if (wd.min_size != Size2i()) {2399xsh->flags |= PMinSize;2400xsh->min_width = wd.min_size.x;2401xsh->min_height = wd.min_size.y;2402}2403if (wd.max_size != Size2i()) {2404xsh->flags |= PMaxSize;2405xsh->max_width = wd.max_size.x;2406xsh->max_height = wd.max_size.y;2407}2408}24092410XSetWMNormalHints(x11_display, wd.x11_window, xsh);2411XFree(xsh);2412}24132414void DisplayServerX11::_update_actions_hints(WindowID p_window) {2415WindowData &wd = windows[p_window];24162417Atom prop = XInternAtom(x11_display, "_NET_WM_ALLOWED_ACTIONS", False);2418if (prop != None) {2419Atom wm_act_max_horz = XInternAtom(x11_display, "_NET_WM_ACTION_MAXIMIZE_HORZ", False);2420Atom wm_act_max_vert = XInternAtom(x11_display, "_NET_WM_ACTION_MAXIMIZE_VERT", False);2421Atom wm_act_min = XInternAtom(x11_display, "_NET_WM_ACTION_MINIMIZE", False);2422Atom type;2423int format;2424unsigned long len;2425unsigned long remaining;2426unsigned char *data = nullptr;2427if (XGetWindowProperty(x11_display, wd.x11_window, prop, 0, 1024, False, XA_ATOM, &type, &format, &len, &remaining, &data) == Success) {2428Atom *atoms = (Atom *)data;2429Vector<Atom> new_atoms;2430for (uint64_t i = 0; i < len; i++) {2431if (atoms[i] != wm_act_max_horz && atoms[i] != wm_act_max_vert && atoms[i] != wm_act_min) {2432new_atoms.push_back(atoms[i]);2433}2434}2435if (!wd.no_max_btn) {2436new_atoms.push_back(wm_act_max_horz);2437new_atoms.push_back(wm_act_max_vert);2438}2439if (!wd.no_min_btn) {2440new_atoms.push_back(wm_act_min);2441}2442XChangeProperty(x11_display, wd.x11_window, prop, XA_ATOM, 32, PropModeReplace, (unsigned char *)new_atoms.ptrw(), new_atoms.size());2443XFree(data);2444}2445}2446}24472448Point2i DisplayServerX11::window_get_position(WindowID p_window) const {2449_THREAD_SAFE_METHOD_24502451ERR_FAIL_COND_V(!windows.has(p_window), Point2i());2452const WindowData &wd = windows[p_window];24532454return wd.position;2455}24562457Point2i DisplayServerX11::window_get_position_with_decorations(WindowID p_window) const {2458_THREAD_SAFE_METHOD_24592460ERR_FAIL_COND_V(!windows.has(p_window), Size2i());2461const WindowData &wd = windows[p_window];24622463if (wd.fullscreen) {2464return wd.position;2465}24662467XWindowAttributes xwa;2468XSync(x11_display, False);2469XGetWindowAttributes(x11_display, wd.x11_window, &xwa);2470int x = wd.position.x;2471int y = wd.position.y;2472Atom prop = XInternAtom(x11_display, "_NET_FRAME_EXTENTS", True);2473if (prop != None) {2474Atom type;2475int format;2476unsigned long len;2477unsigned long remaining;2478unsigned char *data = nullptr;2479if (XGetWindowProperty(x11_display, wd.x11_window, prop, 0, 4, False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) {2480if (format == 32 && len == 4 && data) {2481long *extents = (long *)data;2482x -= extents[0]; // left2483y -= extents[2]; // top2484}2485XFree(data);2486}2487}2488return Size2i(x, y);2489}24902491void DisplayServerX11::window_set_position(const Point2i &p_position, WindowID p_window) {2492_THREAD_SAFE_METHOD_24932494ERR_FAIL_COND(!windows.has(p_window));2495WindowData &wd = windows[p_window];24962497if (wd.embed_parent) {2498print_line("Embedded window can't be moved.");2499return;2500}25012502wd.position = p_position;2503int x = 0;2504int y = 0;2505if (!window_get_flag(WINDOW_FLAG_BORDERLESS, p_window)) {2506//exclude window decorations2507XSync(x11_display, False);2508Atom prop = XInternAtom(x11_display, "_NET_FRAME_EXTENTS", True);2509if (prop != None) {2510Atom type;2511int format;2512unsigned long len;2513unsigned long remaining;2514unsigned char *data = nullptr;2515if (XGetWindowProperty(x11_display, wd.x11_window, prop, 0, 4, False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) {2516if (format == 32 && len == 4 && data) {2517long *extents = (long *)data;2518x = extents[0];2519y = extents[2];2520}2521XFree(data);2522}2523}2524}2525XMoveWindow(x11_display, wd.x11_window, p_position.x - x, p_position.y - y);2526_update_real_mouse_position(wd);2527}25282529void DisplayServerX11::window_set_max_size(const Size2i p_size, WindowID p_window) {2530_THREAD_SAFE_METHOD_25312532ERR_FAIL_COND(!windows.has(p_window));2533WindowData &wd = windows[p_window];25342535if (wd.embed_parent) {2536print_line("Embedded windows can't have a maximum size.");2537return;2538}25392540if ((p_size != Size2i()) && ((p_size.x < wd.min_size.x) || (p_size.y < wd.min_size.y))) {2541ERR_PRINT("Maximum window size can't be smaller than minimum window size!");2542return;2543}2544wd.max_size = p_size;25452546_update_size_hints(p_window);2547XFlush(x11_display);2548}25492550Size2i DisplayServerX11::window_get_max_size(WindowID p_window) const {2551_THREAD_SAFE_METHOD_25522553ERR_FAIL_COND_V(!windows.has(p_window), Size2i());2554const WindowData &wd = windows[p_window];25552556return wd.max_size;2557}25582559void DisplayServerX11::window_set_min_size(const Size2i p_size, WindowID p_window) {2560_THREAD_SAFE_METHOD_25612562ERR_FAIL_COND(!windows.has(p_window));2563WindowData &wd = windows[p_window];25642565if (wd.embed_parent) {2566print_line("Embedded windows can't have a minimum size.");2567return;2568}25692570if ((p_size != Size2i()) && (wd.max_size != Size2i()) && ((p_size.x > wd.max_size.x) || (p_size.y > wd.max_size.y))) {2571ERR_PRINT("Minimum window size can't be larger than maximum window size!");2572return;2573}2574wd.min_size = p_size;25752576_update_size_hints(p_window);2577XFlush(x11_display);2578}25792580Size2i DisplayServerX11::window_get_min_size(WindowID p_window) const {2581_THREAD_SAFE_METHOD_25822583ERR_FAIL_COND_V(!windows.has(p_window), Size2i());2584const WindowData &wd = windows[p_window];25852586return wd.min_size;2587}25882589void DisplayServerX11::window_set_size(const Size2i p_size, WindowID p_window) {2590_THREAD_SAFE_METHOD_25912592ERR_FAIL_COND(!windows.has(p_window));25932594Size2i size = p_size;2595size = size.maxi(1);25962597WindowData &wd = windows[p_window];25982599if (wd.embed_parent) {2600print_line("Embedded window can't be resized.");2601return;2602}26032604if (wd.size.width == size.width && wd.size.height == size.height) {2605return;2606}26072608XWindowAttributes xwa;2609XSync(x11_display, False);2610XGetWindowAttributes(x11_display, wd.x11_window, &xwa);2611int old_w = xwa.width;2612int old_h = xwa.height;26132614// Update our videomode width and height2615wd.size = size;26162617// Update the size hints first to make sure the window size can be set2618_update_size_hints(p_window);26192620// Resize the window2621XResizeWindow(x11_display, wd.x11_window, size.x, size.y);26222623for (int timeout = 0; timeout < 50; ++timeout) {2624XSync(x11_display, False);2625XGetWindowAttributes(x11_display, wd.x11_window, &xwa);26262627if (old_w != xwa.width || old_h != xwa.height) {2628break;2629}26302631OS::get_singleton()->delay_usec(10'000);2632}26332634// Keep rendering context window size in sync2635#if defined(RD_ENABLED)2636if (rendering_context) {2637rendering_context->window_set_size(p_window, xwa.width, xwa.height);2638}2639#endif2640#if defined(GLES3_ENABLED)2641if (gl_manager) {2642gl_manager->window_resize(p_window, xwa.width, xwa.height);2643}2644if (gl_manager_egl) {2645gl_manager_egl->window_resize(p_window, xwa.width, xwa.height);2646}2647#endif2648}26492650Size2i DisplayServerX11::window_get_size(WindowID p_window) const {2651_THREAD_SAFE_METHOD_26522653ERR_FAIL_COND_V(!windows.has(p_window), Size2i());2654const WindowData &wd = windows[p_window];2655return wd.size;2656}26572658Size2i DisplayServerX11::window_get_size_with_decorations(WindowID p_window) const {2659_THREAD_SAFE_METHOD_26602661ERR_FAIL_COND_V(!windows.has(p_window), Size2i());2662const WindowData &wd = windows[p_window];26632664if (wd.fullscreen) {2665return wd.size;2666}26672668XWindowAttributes xwa;2669XSync(x11_display, False);2670XGetWindowAttributes(x11_display, wd.x11_window, &xwa);2671int w = xwa.width;2672int h = xwa.height;2673Atom prop = XInternAtom(x11_display, "_NET_FRAME_EXTENTS", True);2674if (prop != None) {2675Atom type;2676int format;2677unsigned long len;2678unsigned long remaining;2679unsigned char *data = nullptr;2680if (XGetWindowProperty(x11_display, wd.x11_window, prop, 0, 4, False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) {2681if (format == 32 && len == 4 && data) {2682long *extents = (long *)data;2683w += extents[0] + extents[1]; // left, right2684h += extents[2] + extents[3]; // top, bottom2685}2686XFree(data);2687}2688}2689return Size2i(w, h);2690}26912692// Just a helper to reduce code duplication in `window_is_maximize_allowed`2693// and `_set_wm_maximized`.2694bool DisplayServerX11::_window_maximize_check(WindowID p_window, const char *p_atom_name) const {2695ERR_FAIL_COND_V(!windows.has(p_window), false);2696const WindowData &wd = windows[p_window];26972698Atom property = XInternAtom(x11_display, p_atom_name, False);2699Atom type;2700int format;2701unsigned long len;2702unsigned long remaining;2703unsigned char *data = nullptr;2704bool retval = false;27052706if (property == None) {2707return false;2708}27092710int result = XGetWindowProperty(2711x11_display,2712wd.x11_window,2713property,27140,27151024,2716False,2717XA_ATOM,2718&type,2719&format,2720&len,2721&remaining,2722&data);27232724if (result == Success && data) {2725Atom *atoms = (Atom *)data;2726Atom wm_act_max_horz;2727Atom wm_act_max_vert;2728bool checking_state = strcmp(p_atom_name, "_NET_WM_STATE") == 0;2729if (checking_state) {2730wm_act_max_horz = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_HORZ", False);2731wm_act_max_vert = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_VERT", False);2732} else {2733wm_act_max_horz = XInternAtom(x11_display, "_NET_WM_ACTION_MAXIMIZE_HORZ", False);2734wm_act_max_vert = XInternAtom(x11_display, "_NET_WM_ACTION_MAXIMIZE_VERT", False);2735}2736bool found_wm_act_max_horz = false;2737bool found_wm_act_max_vert = false;27382739for (uint64_t i = 0; i < len; i++) {2740if (atoms[i] == wm_act_max_horz) {2741found_wm_act_max_horz = true;2742}2743if (atoms[i] == wm_act_max_vert) {2744found_wm_act_max_vert = true;2745}27462747if (checking_state) {2748if (found_wm_act_max_horz && found_wm_act_max_vert) {2749retval = true;2750break;2751}2752} else {2753if (found_wm_act_max_horz || found_wm_act_max_vert) {2754retval = true;2755break;2756}2757}2758}27592760XFree(data);2761}27622763return retval;2764}27652766bool DisplayServerX11::_window_minimize_check(WindowID p_window) const {2767const WindowData &wd = windows[p_window];27682769// Using EWMH instead of ICCCM, might work better for Wayland users.2770Atom property = XInternAtom(x11_display, "_NET_WM_STATE", True);2771Atom hidden = XInternAtom(x11_display, "_NET_WM_STATE_HIDDEN", True);2772if (property == None || hidden == None) {2773return false;2774}27752776Atom type;2777int format;2778unsigned long len;2779unsigned long remaining;2780Atom *atoms = nullptr;27812782int result = XGetWindowProperty(2783x11_display,2784wd.x11_window,2785property,27860,278732,2788False,2789XA_ATOM,2790&type,2791&format,2792&len,2793&remaining,2794(unsigned char **)&atoms);27952796if (result == Success && atoms) {2797for (unsigned int i = 0; i < len; i++) {2798if (atoms[i] == hidden) {2799XFree(atoms);2800return true;2801}2802}2803XFree(atoms);2804}28052806return false;2807}28082809bool DisplayServerX11::_window_fullscreen_check(WindowID p_window) const {2810ERR_FAIL_COND_V(!windows.has(p_window), false);2811const WindowData &wd = windows[p_window];28122813// Using EWMH -- Extended Window Manager Hints2814Atom property = XInternAtom(x11_display, "_NET_WM_STATE", False);2815Atom type;2816int format;2817unsigned long len;2818unsigned long remaining;2819unsigned char *data = nullptr;2820bool retval = false;28212822if (property == None) {2823return retval;2824}28252826int result = XGetWindowProperty(2827x11_display,2828wd.x11_window,2829property,28300,28311024,2832False,2833XA_ATOM,2834&type,2835&format,2836&len,2837&remaining,2838&data);28392840if (result == Success) {2841Atom *atoms = (Atom *)data;2842Atom wm_fullscreen = XInternAtom(x11_display, "_NET_WM_STATE_FULLSCREEN", False);2843for (uint64_t i = 0; i < len; i++) {2844if (atoms[i] == wm_fullscreen) {2845retval = true;2846break;2847}2848}2849XFree(data);2850}28512852return retval;2853}28542855void DisplayServerX11::_validate_mode_on_map(WindowID p_window) {2856// Check if we applied any window modes that didn't take effect while unmapped2857const WindowData &wd = windows[p_window];2858if (wd.fullscreen && !_window_fullscreen_check(p_window)) {2859_set_wm_fullscreen(p_window, true, wd.exclusive_fullscreen);2860} else if (wd.maximized && !_window_maximize_check(p_window, "_NET_WM_STATE")) {2861_set_wm_maximized(p_window, true);2862} else if (wd.minimized && !_window_minimize_check(p_window)) {2863_set_wm_minimized(p_window, true);2864}28652866if (wd.on_top) {2867Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);2868Atom wm_above = XInternAtom(x11_display, "_NET_WM_STATE_ABOVE", False);28692870XClientMessageEvent xev;2871memset(&xev, 0, sizeof(xev));2872xev.type = ClientMessage;2873xev.window = wd.x11_window;2874xev.message_type = wm_state;2875xev.format = 32;2876xev.data.l[0] = _NET_WM_STATE_ADD;2877xev.data.l[1] = wm_above;2878xev.data.l[3] = 1;2879XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent *)&xev);2880}2881}28822883bool DisplayServerX11::window_is_maximize_allowed(WindowID p_window) const {2884_THREAD_SAFE_METHOD_2885return _window_maximize_check(p_window, "_NET_WM_ALLOWED_ACTIONS");2886}28872888void DisplayServerX11::_set_wm_maximized(WindowID p_window, bool p_enabled) {2889ERR_FAIL_COND(!windows.has(p_window));2890WindowData &wd = windows[p_window];28912892// Using EWMH -- Extended Window Manager Hints2893XEvent xev;2894Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);2895Atom wm_max_horz = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_HORZ", False);2896Atom wm_max_vert = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_VERT", False);28972898memset(&xev, 0, sizeof(xev));2899xev.type = ClientMessage;2900xev.xclient.window = wd.x11_window;2901xev.xclient.message_type = wm_state;2902xev.xclient.format = 32;2903xev.xclient.data.l[0] = p_enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;2904xev.xclient.data.l[1] = wm_max_horz;2905xev.xclient.data.l[2] = wm_max_vert;29062907XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);29082909if (p_enabled && window_is_maximize_allowed(p_window)) {2910// Wait for effective resizing (so the GLX context is too).2911// Give up after 0.5s, it's not going to happen on this WM.2912// https://github.com/godotengine/godot/issues/199782913for (int attempt = 0; window_get_mode(p_window) != WINDOW_MODE_MAXIMIZED && attempt < 50; attempt++) {2914OS::get_singleton()->delay_usec(10'000);2915}2916}2917wd.maximized = p_enabled;2918}29192920void DisplayServerX11::_set_wm_minimized(WindowID p_window, bool p_enabled) {2921WindowData &wd = windows[p_window];2922// Using ICCCM -- Inter-Client Communication Conventions Manual2923XEvent xev;2924Atom wm_change = XInternAtom(x11_display, "WM_CHANGE_STATE", False);29252926memset(&xev, 0, sizeof(xev));2927xev.type = ClientMessage;2928xev.xclient.window = wd.x11_window;2929xev.xclient.message_type = wm_change;2930xev.xclient.format = 32;2931xev.xclient.data.l[0] = p_enabled ? WM_IconicState : WM_NormalState;29322933XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);29342935Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);2936Atom wm_hidden = XInternAtom(x11_display, "_NET_WM_STATE_HIDDEN", False);29372938memset(&xev, 0, sizeof(xev));2939xev.type = ClientMessage;2940xev.xclient.window = wd.x11_window;2941xev.xclient.message_type = wm_state;2942xev.xclient.format = 32;2943xev.xclient.data.l[0] = p_enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;2944xev.xclient.data.l[1] = wm_hidden;29452946XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);2947wd.minimized = p_enabled;2948}29492950void DisplayServerX11::_set_wm_fullscreen(WindowID p_window, bool p_enabled, bool p_exclusive) {2951ERR_FAIL_COND(!windows.has(p_window));2952WindowData &wd = windows[p_window];29532954if (p_enabled && !window_get_flag(WINDOW_FLAG_BORDERLESS, p_window)) {2955// remove decorations if the window is not already borderless2956Hints hints;2957Atom property;2958hints.flags = 2;2959hints.decorations = 0;2960property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True);2961if (property != None) {2962XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5);2963}2964}29652966if (p_enabled) {2967// Set the window as resizable to prevent window managers to ignore the fullscreen state flag.2968_update_size_hints(p_window);2969}29702971// Using EWMH -- Extended Window Manager Hints2972XEvent xev;2973Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);2974Atom wm_fullscreen = XInternAtom(x11_display, "_NET_WM_STATE_FULLSCREEN", False);29752976memset(&xev, 0, sizeof(xev));2977xev.type = ClientMessage;2978xev.xclient.window = wd.x11_window;2979xev.xclient.message_type = wm_state;2980xev.xclient.format = 32;2981xev.xclient.data.l[0] = p_enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;2982xev.xclient.data.l[1] = wm_fullscreen;2983xev.xclient.data.l[2] = 0;29842985XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);29862987// set bypass compositor hint2988Atom bypass_compositor = XInternAtom(x11_display, "_NET_WM_BYPASS_COMPOSITOR", False);2989unsigned long compositing_disable_on = 0; // Use default.2990if (p_enabled) {2991if (p_exclusive) {2992compositing_disable_on = 1; // Force composition OFF to reduce overhead.2993} else {2994compositing_disable_on = 2; // Force composition ON to allow popup windows.2995}2996}2997if (bypass_compositor != None) {2998XChangeProperty(x11_display, wd.x11_window, bypass_compositor, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&compositing_disable_on, 1);2999}30003001XFlush(x11_display);30023003if (!p_enabled) {3004// Reset the non-resizable flags if we un-set these before.3005_update_size_hints(p_window);30063007// put back or remove decorations according to the last set borderless state3008Hints hints;3009Atom property;3010hints.flags = 2;3011hints.decorations = wd.borderless ? 0 : 1;3012property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True);3013if (property != None) {3014XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5);3015}3016}3017}30183019void DisplayServerX11::window_set_mode(WindowMode p_mode, WindowID p_window) {3020_THREAD_SAFE_METHOD_30213022ERR_FAIL_COND(!windows.has(p_window));3023WindowData &wd = windows[p_window];30243025WindowMode old_mode = window_get_mode(p_window);3026if (old_mode == p_mode) {3027return; // do nothing3028}30293030if (p_mode != WINDOW_MODE_WINDOWED && wd.embed_parent) {3031print_line("Embedded window only supports Windowed mode.");3032return;3033}30343035// Remove all "extra" modes.3036switch (old_mode) {3037case WINDOW_MODE_WINDOWED: {3038//do nothing3039} break;3040case WINDOW_MODE_MINIMIZED: {3041_set_wm_minimized(p_window, false);3042} break;3043case WINDOW_MODE_EXCLUSIVE_FULLSCREEN:3044case WINDOW_MODE_FULLSCREEN: {3045//Remove full-screen3046wd.fullscreen = false;3047wd.exclusive_fullscreen = false;30483049_set_wm_fullscreen(p_window, false, false);30503051//un-maximize required for always on top3052bool on_top = window_get_flag(WINDOW_FLAG_ALWAYS_ON_TOP, p_window);30533054window_set_position(wd.last_position_before_fs, p_window);30553056if (on_top) {3057_set_wm_maximized(p_window, false);3058}30593060} break;3061case WINDOW_MODE_MAXIMIZED: {3062_set_wm_maximized(p_window, false);3063} break;3064}30653066switch (p_mode) {3067case WINDOW_MODE_WINDOWED: {3068//do nothing3069} break;3070case WINDOW_MODE_MINIMIZED: {3071_set_wm_minimized(p_window, true);3072} break;3073case WINDOW_MODE_EXCLUSIVE_FULLSCREEN:3074case WINDOW_MODE_FULLSCREEN: {3075wd.last_position_before_fs = wd.position;30763077if (window_get_flag(WINDOW_FLAG_ALWAYS_ON_TOP, p_window)) {3078_set_wm_maximized(p_window, true);3079}30803081wd.fullscreen = true;3082if (p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) {3083wd.exclusive_fullscreen = true;3084_set_wm_fullscreen(p_window, true, true);3085} else {3086wd.exclusive_fullscreen = false;3087_set_wm_fullscreen(p_window, true, false);3088}3089} break;3090case WINDOW_MODE_MAXIMIZED: {3091_set_wm_maximized(p_window, true);3092} break;3093}3094}30953096DisplayServer::WindowMode DisplayServerX11::window_get_mode(WindowID p_window) const {3097_THREAD_SAFE_METHOD_30983099ERR_FAIL_COND_V(!windows.has(p_window), WINDOW_MODE_WINDOWED);3100const WindowData &wd = windows[p_window];31013102if (wd.fullscreen) { //if fullscreen, it's not in another mode3103if (wd.exclusive_fullscreen) {3104return WINDOW_MODE_EXCLUSIVE_FULLSCREEN;3105} else {3106return WINDOW_MODE_FULLSCREEN;3107}3108}31093110// Test maximized.3111// Using EWMH -- Extended Window Manager Hints3112if (_window_maximize_check(p_window, "_NET_WM_STATE")) {3113return WINDOW_MODE_MAXIMIZED;3114}31153116{3117if (_window_minimize_check(p_window)) {3118return WINDOW_MODE_MINIMIZED;3119}3120}31213122// All other discarded, return windowed.31233124return WINDOW_MODE_WINDOWED;3125}31263127void DisplayServerX11::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) {3128_THREAD_SAFE_METHOD_31293130ERR_FAIL_COND(!windows.has(p_window));3131WindowData &wd = windows[p_window];31323133switch (p_flag) {3134case WINDOW_FLAG_MAXIMIZE_DISABLED: {3135wd.no_max_btn = p_enabled;3136_update_actions_hints(p_window);31373138XFlush(x11_display);3139} break;3140case WINDOW_FLAG_MINIMIZE_DISABLED: {3141wd.no_min_btn = p_enabled;3142_update_actions_hints(p_window);31433144XFlush(x11_display);3145} break;3146case WINDOW_FLAG_RESIZE_DISABLED: {3147if (p_enabled && wd.embed_parent) {3148print_line("Embedded window resize can't be disabled.");3149return;3150}31513152wd.resize_disabled = p_enabled;3153_update_size_hints(p_window);3154_update_actions_hints(p_window);31553156XFlush(x11_display);3157} break;3158case WINDOW_FLAG_BORDERLESS: {3159Hints hints;3160Atom property;3161hints.flags = 2;3162hints.decorations = p_enabled ? 0 : 1;3163property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True);3164if (property != None) {3165XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5);3166}31673168// Preserve window size3169if (!wd.embed_parent) {3170window_set_size(window_get_size(p_window), p_window);3171}31723173wd.borderless = p_enabled;3174_update_window_mouse_passthrough(p_window);3175} break;3176case WINDOW_FLAG_ALWAYS_ON_TOP: {3177ERR_FAIL_COND_MSG(wd.transient_parent != INVALID_WINDOW_ID, "Can't make a window transient if the 'on top' flag is active.");3178if (p_enabled && wd.embed_parent) {3179print_line("Embedded window can't become on top.");3180return;3181}3182if (p_enabled && wd.fullscreen) {3183_set_wm_maximized(p_window, true);3184}31853186Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);3187Atom wm_above = XInternAtom(x11_display, "_NET_WM_STATE_ABOVE", False);31883189XClientMessageEvent xev;3190memset(&xev, 0, sizeof(xev));3191xev.type = ClientMessage;3192xev.window = wd.x11_window;3193xev.message_type = wm_state;3194xev.format = 32;3195xev.data.l[0] = p_enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;3196xev.data.l[1] = wm_above;3197xev.data.l[3] = 1;3198XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent *)&xev);31993200if (!p_enabled && !wd.fullscreen) {3201_set_wm_maximized(p_window, false);3202}3203wd.on_top = p_enabled;32043205} break;3206case WINDOW_FLAG_TRANSPARENT: {3207wd.layered_window = p_enabled;3208} break;3209case WINDOW_FLAG_NO_FOCUS: {3210wd.no_focus = p_enabled;3211} break;3212case WINDOW_FLAG_MOUSE_PASSTHROUGH: {3213wd.mpass = p_enabled;3214_update_window_mouse_passthrough(p_window);3215} break;3216case WINDOW_FLAG_POPUP: {3217XWindowAttributes xwa;3218XSync(x11_display, False);3219XGetWindowAttributes(x11_display, wd.x11_window, &xwa);32203221ERR_FAIL_COND_MSG(p_window == MAIN_WINDOW_ID, "Main window can't be popup.");3222ERR_FAIL_COND_MSG((xwa.map_state == IsViewable) && (wd.is_popup != p_enabled), "Popup flag can't changed while window is opened.");3223if (p_enabled && wd.embed_parent) {3224print_line("Embedded window can't be popup.");3225return;3226}3227wd.is_popup = p_enabled;3228} break;3229default: {3230}3231}3232}32333234bool DisplayServerX11::window_get_flag(WindowFlags p_flag, WindowID p_window) const {3235_THREAD_SAFE_METHOD_32363237ERR_FAIL_COND_V(!windows.has(p_window), false);3238const WindowData &wd = windows[p_window];32393240switch (p_flag) {3241case WINDOW_FLAG_MAXIMIZE_DISABLED: {3242return wd.no_max_btn;3243} break;3244case WINDOW_FLAG_MINIMIZE_DISABLED: {3245return wd.no_min_btn;3246} break;3247case WINDOW_FLAG_RESIZE_DISABLED: {3248return wd.resize_disabled;3249} break;3250case WINDOW_FLAG_BORDERLESS: {3251bool borderless = wd.borderless;3252Atom prop = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True);3253if (prop != None) {3254Atom type;3255int format;3256unsigned long len;3257unsigned long remaining;3258unsigned char *data = nullptr;3259if (XGetWindowProperty(x11_display, wd.x11_window, prop, 0, sizeof(Hints), False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) {3260if (data && (format == 32) && (len >= 5)) {3261borderless = !(reinterpret_cast<Hints *>(data)->decorations);3262}3263if (data) {3264XFree(data);3265}3266}3267}3268return borderless;3269} break;3270case WINDOW_FLAG_ALWAYS_ON_TOP: {3271return wd.on_top;3272} break;3273case WINDOW_FLAG_TRANSPARENT: {3274return wd.layered_window;3275} break;3276case WINDOW_FLAG_NO_FOCUS: {3277return wd.no_focus;3278} break;3279case WINDOW_FLAG_MOUSE_PASSTHROUGH: {3280return wd.mpass;3281} break;3282case WINDOW_FLAG_POPUP: {3283return wd.is_popup;3284} break;3285default: {3286}3287}32883289return false;3290}32913292void DisplayServerX11::window_request_attention(WindowID p_window) {3293_THREAD_SAFE_METHOD_32943295ERR_FAIL_COND(!windows.has(p_window));3296const WindowData &wd = windows[p_window];3297// Using EWMH -- Extended Window Manager Hints3298//3299// Sets the _NET_WM_STATE_DEMANDS_ATTENTION atom for WM_STATE3300// Will be unset by the window manager after user react on the request for attention33013302XEvent xev;3303Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);3304Atom wm_attention = XInternAtom(x11_display, "_NET_WM_STATE_DEMANDS_ATTENTION", False);33053306memset(&xev, 0, sizeof(xev));3307xev.type = ClientMessage;3308xev.xclient.window = wd.x11_window;3309xev.xclient.message_type = wm_state;3310xev.xclient.format = 32;3311xev.xclient.data.l[0] = _NET_WM_STATE_ADD;3312xev.xclient.data.l[1] = wm_attention;33133314XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);3315XFlush(x11_display);3316}33173318void DisplayServerX11::window_move_to_foreground(WindowID p_window) {3319_THREAD_SAFE_METHOD_33203321ERR_FAIL_COND(!windows.has(p_window));3322const WindowData &wd = windows[p_window];33233324XEvent xev;3325Atom net_active_window = XInternAtom(x11_display, "_NET_ACTIVE_WINDOW", False);33263327memset(&xev, 0, sizeof(xev));3328xev.type = ClientMessage;3329xev.xclient.window = wd.x11_window;3330xev.xclient.message_type = net_active_window;3331xev.xclient.format = 32;3332xev.xclient.data.l[0] = 1;3333xev.xclient.data.l[1] = CurrentTime;33343335XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);3336XFlush(x11_display);3337}33383339DisplayServerX11::WindowID DisplayServerX11::get_focused_window() const {3340return last_focused_window;3341}33423343bool DisplayServerX11::window_is_focused(WindowID p_window) const {3344_THREAD_SAFE_METHOD_33453346ERR_FAIL_COND_V(!windows.has(p_window), false);33473348const WindowData &wd = windows[p_window];33493350return wd.focused;3351}33523353bool DisplayServerX11::window_can_draw(WindowID p_window) const {3354//this seems to be all that is provided by X113355return window_get_mode(p_window) != WINDOW_MODE_MINIMIZED;3356}33573358bool DisplayServerX11::can_any_window_draw() const {3359_THREAD_SAFE_METHOD_33603361for (const KeyValue<WindowID, WindowData> &E : windows) {3362if (window_get_mode(E.key) != WINDOW_MODE_MINIMIZED) {3363return true;3364}3365}33663367return false;3368}33693370void DisplayServerX11::window_set_ime_active(const bool p_active, WindowID p_window) {3371_THREAD_SAFE_METHOD_33723373ERR_FAIL_COND(!windows.has(p_window));3374WindowData &wd = windows[p_window];33753376if (!wd.xic) {3377return;3378}3379if (!wd.focused) {3380wd.ime_active = false;3381im_text = String();3382im_selection = Vector2i();3383return;3384}33853386// Block events polling while changing input focus3387// because it triggers some event polling internally.3388if (p_active) {3389MutexLock mutex_lock(events_mutex);33903391wd.ime_active = true;33923393XMapWindow(x11_display, wd.x11_xim_window);33943395XWindowAttributes xwa;3396XSync(x11_display, False);3397XGetWindowAttributes(x11_display, wd.x11_xim_window, &xwa);3398if (xwa.map_state == IsViewable && _window_focus_check()) {3399_set_input_focus(wd.x11_xim_window, RevertToParent);3400}3401XSetICFocus(wd.xic);3402} else {3403MutexLock mutex_lock(events_mutex);3404XUnsetICFocus(wd.xic);3405XUnmapWindow(x11_display, wd.x11_xim_window);3406wd.ime_active = false;34073408im_text = String();3409im_selection = Vector2i();3410}3411}34123413void DisplayServerX11::window_set_ime_position(const Point2i &p_pos, WindowID p_window) {3414_THREAD_SAFE_METHOD_34153416ERR_FAIL_COND(!windows.has(p_window));3417WindowData &wd = windows[p_window];34183419if (!wd.xic) {3420return;3421}3422if (!wd.focused) {3423return;3424}34253426if (wd.ime_active) {3427XWindowAttributes xwa;3428XSync(x11_display, False);3429XGetWindowAttributes(x11_display, wd.x11_xim_window, &xwa);3430if (xwa.map_state == IsViewable) {3431XMoveWindow(x11_display, wd.x11_xim_window, p_pos.x, p_pos.y);3432}3433}3434}34353436int DisplayServerX11::accessibility_should_increase_contrast() const {3437#ifdef DBUS_ENABLED3438if (!portal_desktop) {3439return -1;3440}3441return portal_desktop->get_high_contrast();3442#endif3443return -1;3444}34453446int DisplayServerX11::accessibility_screen_reader_active() const {3447#ifdef DBUS_ENABLED3448if (atspi_monitor && atspi_monitor->is_supported()) {3449return atspi_monitor->is_active();3450}3451#endif3452return -1;3453}34543455Point2i DisplayServerX11::ime_get_selection() const {3456return im_selection;3457}34583459String DisplayServerX11::ime_get_text() const {3460return im_text;3461}34623463void DisplayServerX11::cursor_set_shape(CursorShape p_shape) {3464_THREAD_SAFE_METHOD_34653466ERR_FAIL_INDEX(p_shape, CURSOR_MAX);34673468if (p_shape == current_cursor) {3469return;3470}34713472if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) {3473if (cursors[p_shape] != None) {3474for (const KeyValue<WindowID, WindowData> &E : windows) {3475XDefineCursor(x11_display, E.value.x11_window, cursors[p_shape]);3476}3477} else if (cursors[CURSOR_ARROW] != None) {3478for (const KeyValue<WindowID, WindowData> &E : windows) {3479XDefineCursor(x11_display, E.value.x11_window, cursors[CURSOR_ARROW]);3480}3481}3482}34833484current_cursor = p_shape;3485}34863487DisplayServerX11::CursorShape DisplayServerX11::cursor_get_shape() const {3488return current_cursor;3489}34903491void DisplayServerX11::cursor_set_custom_image(const Ref<Resource> &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) {3492_THREAD_SAFE_METHOD_34933494ERR_FAIL_INDEX(p_shape, CURSOR_MAX);34953496if (p_cursor.is_valid()) {3497HashMap<CursorShape, Vector<Variant>>::Iterator cursor_c = cursors_cache.find(p_shape);34983499if (cursor_c) {3500if (cursor_c->value[0] == p_cursor && cursor_c->value[1] == p_hotspot) {3501cursor_set_shape(p_shape);3502return;3503}35043505cursors_cache.erase(p_shape);3506}35073508Ref<Image> image = _get_cursor_image_from_resource(p_cursor, p_hotspot);3509ERR_FAIL_COND(image.is_null());3510Vector2i texture_size = image->get_size();35113512// Create the cursor structure3513XcursorImage *cursor_image = XcursorImageCreate(texture_size.width, texture_size.height);3514XcursorUInt image_size = texture_size.width * texture_size.height;3515XcursorDim size = sizeof(XcursorPixel) * image_size;35163517cursor_image->version = 1;3518cursor_image->size = size;3519cursor_image->xhot = p_hotspot.x;3520cursor_image->yhot = p_hotspot.y;35213522// allocate memory to contain the whole file3523cursor_image->pixels = (XcursorPixel *)memalloc(size);35243525for (XcursorPixel index = 0; index < image_size; index++) {3526int row_index = std::floor(index / texture_size.width);3527int column_index = index % int(texture_size.width);35283529*(cursor_image->pixels + index) = image->get_pixel(column_index, row_index).to_argb32();3530}35313532ERR_FAIL_NULL(cursor_image->pixels);35333534// Save it for a further usage3535cursors[p_shape] = XcursorImageLoadCursor(x11_display, cursor_image);35363537Vector<Variant> params;3538params.push_back(p_cursor);3539params.push_back(p_hotspot);3540cursors_cache.insert(p_shape, params);35413542if (p_shape == current_cursor) {3543if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) {3544for (const KeyValue<WindowID, WindowData> &E : windows) {3545XDefineCursor(x11_display, E.value.x11_window, cursors[p_shape]);3546}3547}3548}35493550memfree(cursor_image->pixels);3551XcursorImageDestroy(cursor_image);3552} else {3553// Reset to default system cursor3554if (cursor_img[p_shape]) {3555cursors[p_shape] = XcursorImageLoadCursor(x11_display, cursor_img[p_shape]);3556}35573558cursors_cache.erase(p_shape);35593560CursorShape c = current_cursor;3561current_cursor = CURSOR_MAX;3562cursor_set_shape(c);3563}3564}35653566bool DisplayServerX11::get_swap_cancel_ok() {3567return swap_cancel_ok;3568}35693570int DisplayServerX11::keyboard_get_layout_count() const {3571int _group_count = 0;3572XkbDescRec *kbd = XkbAllocKeyboard();3573if (kbd) {3574kbd->dpy = x11_display;3575XkbGetControls(x11_display, XkbAllControlsMask, kbd);3576XkbGetNames(x11_display, XkbSymbolsNameMask, kbd);35773578const Atom *groups = kbd->names->groups;3579if (kbd->ctrls != nullptr) {3580_group_count = kbd->ctrls->num_groups;3581} else {3582while (_group_count < XkbNumKbdGroups && groups[_group_count] != None) {3583_group_count++;3584}3585}3586XkbFreeKeyboard(kbd, 0, true);3587}3588return _group_count;3589}35903591int DisplayServerX11::keyboard_get_current_layout() const {3592XkbStateRec state;3593XkbGetState(x11_display, XkbUseCoreKbd, &state);3594return state.group;3595}35963597void DisplayServerX11::keyboard_set_current_layout(int p_index) {3598ERR_FAIL_INDEX(p_index, keyboard_get_layout_count());3599XkbLockGroup(x11_display, XkbUseCoreKbd, p_index);3600}36013602String DisplayServerX11::keyboard_get_layout_language(int p_index) const {3603String ret;3604XkbDescRec *kbd = XkbAllocKeyboard();3605if (kbd) {3606kbd->dpy = x11_display;3607XkbGetControls(x11_display, XkbAllControlsMask, kbd);3608XkbGetNames(x11_display, XkbSymbolsNameMask, kbd);3609XkbGetNames(x11_display, XkbGroupNamesMask, kbd);36103611int _group_count = 0;3612const Atom *groups = kbd->names->groups;3613if (kbd->ctrls != nullptr) {3614_group_count = kbd->ctrls->num_groups;3615} else {3616while (_group_count < XkbNumKbdGroups && groups[_group_count] != None) {3617_group_count++;3618}3619}36203621Atom names = kbd->names->symbols;3622if (names != None) {3623Vector<String> info = get_atom_name(x11_display, names).split("+");3624if (p_index >= 0 && p_index < _group_count) {3625if (p_index + 1 < info.size()) {3626ret = info[p_index + 1]; // Skip "pc" at the start and "inet"/"group" at the end of symbols.3627} else {3628ret = "en"; // No symbol for layout fallback to "en".3629}3630} else {3631ERR_PRINT("Index " + itos(p_index) + "is out of bounds (" + itos(_group_count) + ").");3632}3633}3634XkbFreeKeyboard(kbd, 0, true);3635}3636return ret.substr(0, 2);3637}36383639String DisplayServerX11::keyboard_get_layout_name(int p_index) const {3640String ret;3641XkbDescRec *kbd = XkbAllocKeyboard();3642if (kbd) {3643kbd->dpy = x11_display;3644XkbGetControls(x11_display, XkbAllControlsMask, kbd);3645XkbGetNames(x11_display, XkbSymbolsNameMask, kbd);3646XkbGetNames(x11_display, XkbGroupNamesMask, kbd);36473648int _group_count = 0;3649const Atom *groups = kbd->names->groups;3650if (kbd->ctrls != nullptr) {3651_group_count = kbd->ctrls->num_groups;3652} else {3653while (_group_count < XkbNumKbdGroups && groups[_group_count] != None) {3654_group_count++;3655}3656}36573658if (p_index >= 0 && p_index < _group_count) {3659ret = get_atom_name(x11_display, groups[p_index]);3660} else {3661ERR_PRINT("Index " + itos(p_index) + "is out of bounds (" + itos(_group_count) + ").");3662}3663XkbFreeKeyboard(kbd, 0, true);3664}3665return ret;3666}36673668Key DisplayServerX11::keyboard_get_keycode_from_physical(Key p_keycode) const {3669Key modifiers = p_keycode & KeyModifierMask::MODIFIER_MASK;3670Key keycode_no_mod = p_keycode & KeyModifierMask::CODE_MASK;3671unsigned int xkeycode = KeyMappingX11::get_xlibcode(keycode_no_mod);3672KeySym xkeysym = XkbKeycodeToKeysym(x11_display, xkeycode, keyboard_get_current_layout(), 0);3673if (is_ascii_lower_case(xkeysym)) {3674xkeysym -= ('a' - 'A');3675}36763677Key key = KeyMappingX11::get_keycode(xkeysym);3678// If not found, fallback to QWERTY.3679// This should match the behavior of the event pump3680if (key == Key::NONE) {3681return p_keycode;3682}3683return (Key)(key | modifiers);3684}36853686Key DisplayServerX11::keyboard_get_label_from_physical(Key p_keycode) const {3687Key modifiers = p_keycode & KeyModifierMask::MODIFIER_MASK;3688Key keycode_no_mod = p_keycode & KeyModifierMask::CODE_MASK;3689unsigned int xkeycode = KeyMappingX11::get_xlibcode(keycode_no_mod);3690KeySym xkeysym = XkbKeycodeToKeysym(x11_display, xkeycode, keyboard_get_current_layout(), 0);3691if (is_ascii_lower_case(xkeysym)) {3692xkeysym -= ('a' - 'A');3693}36943695Key key = KeyMappingX11::get_keycode(xkeysym);3696#ifdef XKB_ENABLED3697if (xkb_loaded_v08p) {3698String keysym = String::chr(xkb_keysym_to_utf32(xkb_keysym_to_upper(xkeysym)));3699key = fix_key_label(keysym[0], KeyMappingX11::get_keycode(xkeysym));3700}3701#endif37023703// If not found, fallback to QWERTY.3704// This should match the behavior of the event pump3705if (key == Key::NONE) {3706return p_keycode;3707}3708return (Key)(key | modifiers);3709}37103711bool DisplayServerX11::color_picker(const Callable &p_callback) {3712#ifdef DBUS_ENABLED3713if (!portal_desktop) {3714return false;3715}3716WindowID window_id = last_focused_window;37173718if (!windows.has(window_id)) {3719window_id = MAIN_WINDOW_ID;3720}37213722String xid = vformat("x11:%x", (uint64_t)windows[window_id].x11_window);3723return portal_desktop->color_picker(xid, p_callback);3724#else3725return false;3726#endif3727}37283729DisplayServerX11::Property DisplayServerX11::_read_property(Display *p_display, Window p_window, Atom p_property) {3730Atom actual_type = None;3731int actual_format = 0;3732unsigned long nitems = 0;3733unsigned long bytes_after = 0;3734unsigned char *ret = nullptr;37353736// Keep trying to read the property until there are no bytes unread.3737if (p_property != None) {3738int read_bytes = 1024;3739do {3740if (ret != nullptr) {3741XFree(ret);3742}37433744XGetWindowProperty(p_display, p_window, p_property, 0, read_bytes, False, AnyPropertyType,3745&actual_type, &actual_format, &nitems, &bytes_after,3746&ret);37473748read_bytes *= 2;37493750} while (bytes_after != 0);3751}37523753Property p = { ret, actual_format, (int)nitems, actual_type };37543755return p;3756}37573758static Atom pick_target_from_list(Display *p_display, const Atom *p_list, int p_count) {3759static const char *target_type = "text/uri-list";37603761for (int i = 0; i < p_count; i++) {3762Atom atom = p_list[i];37633764if (atom != None && get_atom_name(p_display, atom) == target_type) {3765return atom;3766}3767}3768return None;3769}37703771static Atom pick_target_from_atoms(Display *p_disp, Atom p_t1, Atom p_t2, Atom p_t3) {3772static const char *target_type = "text/uri-list";3773if (p_t1 != None && get_atom_name(p_disp, p_t1) == target_type) {3774return p_t1;3775}37763777if (p_t2 != None && get_atom_name(p_disp, p_t2) == target_type) {3778return p_t2;3779}37803781if (p_t3 != None && get_atom_name(p_disp, p_t3) == target_type) {3782return p_t3;3783}37843785return None;3786}37873788void DisplayServerX11::_get_key_modifier_state(unsigned int p_x11_state, Ref<InputEventWithModifiers> state) {3789state->set_shift_pressed((p_x11_state & ShiftMask));3790state->set_ctrl_pressed((p_x11_state & ControlMask));3791state->set_alt_pressed((p_x11_state & Mod1Mask /*|| p_x11_state&Mod5Mask*/)); //altgr should not count as alt3792state->set_meta_pressed((p_x11_state & Mod4Mask));3793}37943795void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, LocalVector<XEvent> &p_events, uint32_t &p_event_index, bool p_echo) {3796WindowData &wd = windows[p_window];3797// X11 functions don't know what const is3798XKeyEvent *xkeyevent = p_event;37993800if (wd.ime_in_progress) {3801return;3802}3803if (wd.ime_suppress_next_keyup) {3804wd.ime_suppress_next_keyup = false;3805if (xkeyevent->type != KeyPress) {3806return;3807}3808}38093810// This code was pretty difficult to write.3811// The docs stink and every toolkit seems to3812// do it in a different way.38133814/* Phase 1, obtain a proper keysym */38153816// This was also very difficult to figure out.3817// You'd expect you could just use Keysym provided by3818// XKeycodeToKeysym to obtain internationalized3819// input.. WRONG!!3820// you must use XLookupString (???) which not only wastes3821// cycles generating an unnecessary string, but also3822// still works in half the cases. (won't handle deadkeys)3823// For more complex input methods (deadkeys and more advanced)3824// you have to use XmbLookupString (??).3825// So then you have to choose which of both results3826// you want to keep.3827// This is a real bizarreness and cpu waster.38283829KeySym keysym_keycode = 0; // keysym used to find a keycode3830KeySym keysym_unicode = 0; // keysym used to find unicode38313832// XLookupString returns keysyms usable as nice keycodes.3833char str[256] = {};3834XKeyEvent xkeyevent_no_mod = *xkeyevent;3835xkeyevent_no_mod.state &= 0xFF00;3836XLookupString(xkeyevent, str, 255, &keysym_unicode, nullptr);3837XLookupString(&xkeyevent_no_mod, nullptr, 0, &keysym_keycode, nullptr);38383839String keysym;3840#ifdef XKB_ENABLED3841if (xkb_loaded_v08p) {3842KeySym keysym_unicode_nm = 0; // keysym used to find unicode3843XLookupString(&xkeyevent_no_mod, nullptr, 0, &keysym_unicode_nm, nullptr);3844keysym = String::chr(xkb_keysym_to_utf32(xkb_keysym_to_upper(keysym_unicode_nm)));3845}3846#endif38473848// Meanwhile, XLookupString returns keysyms useful for unicode.38493850if (!xmbstring) {3851// keep a temporary buffer for the string3852xmbstring = (char *)memalloc(sizeof(char) * 8);3853xmblen = 8;3854}38553856if (xkeyevent->type == KeyPress && wd.xic) {3857Status status;3858#ifdef X_HAVE_UTF8_STRING3859int utf8len = 8;3860char *utf8string = (char *)memalloc(sizeof(char) * utf8len);3861int utf8bytes = Xutf8LookupString(wd.xic, xkeyevent, utf8string,3862utf8len - 1, &keysym_unicode, &status);3863if (status == XBufferOverflow) {3864utf8len = utf8bytes + 1;3865utf8string = (char *)memrealloc(utf8string, utf8len);3866utf8bytes = Xutf8LookupString(wd.xic, xkeyevent, utf8string,3867utf8len - 1, &keysym_unicode, &status);3868}3869utf8string[utf8bytes] = '\0';38703871if (status == XLookupChars) {3872bool keypress = xkeyevent->type == KeyPress;38733874Key keycode = Key::NONE;3875if (KeyMappingX11::is_sym_numpad(keysym_unicode)) {3876// Special case for numpad keys.3877keycode = KeyMappingX11::get_keycode(keysym_unicode);3878}38793880if (keycode == Key::NONE) {3881keycode = KeyMappingX11::get_keycode(keysym_keycode);3882}38833884Key physical_keycode = KeyMappingX11::get_scancode(xkeyevent->keycode);38853886if (keycode >= Key::A + 32 && keycode <= Key::Z + 32) {3887keycode -= 'a' - 'A';3888}38893890String tmp = String::utf8(utf8string, utf8bytes);3891for (int i = 0; i < tmp.length(); i++) {3892Ref<InputEventKey> k;3893k.instantiate();3894if (physical_keycode == Key::NONE && keycode == Key::NONE && tmp[i] == 0) {3895continue;3896}38973898if (keycode == Key::NONE) {3899keycode = (Key)physical_keycode;3900}39013902_get_key_modifier_state(xkeyevent->state, k);39033904k->set_window_id(p_window);3905k->set_pressed(keypress);39063907k->set_keycode(keycode);3908k->set_physical_keycode(physical_keycode);3909if (!keysym.is_empty()) {3910k->set_key_label(fix_key_label(keysym[0], keycode));3911} else {3912k->set_key_label(keycode);3913}3914if (keypress) {3915k->set_unicode(fix_unicode(tmp[i]));3916}39173918k->set_echo(false);39193920if (k->get_keycode() == Key::BACKTAB) {3921//make it consistent across platforms.3922k->set_keycode(Key::TAB);3923k->set_physical_keycode(Key::TAB);3924k->set_shift_pressed(true);3925}39263927Input::get_singleton()->parse_input_event(k);3928}3929memfree(utf8string);3930return;3931}3932memfree(utf8string);3933#else3934do {3935int mnbytes = XmbLookupString(xic, xkeyevent, xmbstring, xmblen - 1, &keysym_unicode, &status);3936xmbstring[mnbytes] = '\0';39373938if (status == XBufferOverflow) {3939xmblen = mnbytes + 1;3940xmbstring = (char *)memrealloc(xmbstring, xmblen);3941}3942} while (status == XBufferOverflow);3943#endif3944#ifdef XKB_ENABLED3945} else if (xkeyevent->type == KeyPress && wd.xkb_state && xkb_loaded_v05p) {3946xkb_compose_feed_result res = xkb_compose_state_feed(wd.xkb_state, keysym_unicode);3947if (res == XKB_COMPOSE_FEED_ACCEPTED) {3948if (xkb_compose_state_get_status(wd.xkb_state) == XKB_COMPOSE_COMPOSED) {3949bool keypress = xkeyevent->type == KeyPress;3950Key physical_keycode = KeyMappingX11::get_scancode(xkeyevent->keycode);3951KeyLocation key_location = KeyMappingX11::get_location(xkeyevent->keycode);39523953Key keycode = Key::NONE;3954if (KeyMappingX11::is_sym_numpad(keysym_unicode)) {3955// Special case for numpad keys.3956keycode = KeyMappingX11::get_keycode(keysym_unicode);3957}39583959if (keycode == Key::NONE) {3960keycode = KeyMappingX11::get_keycode(keysym_keycode);3961}39623963if (keycode >= Key::A + 32 && keycode <= Key::Z + 32) {3964keycode -= 'a' - 'A';3965}39663967char str_xkb[256] = {};3968int str_xkb_size = xkb_compose_state_get_utf8(wd.xkb_state, str_xkb, 255);39693970String tmp = String::utf8(str_xkb, str_xkb_size);3971for (int i = 0; i < tmp.length(); i++) {3972Ref<InputEventKey> k;3973k.instantiate();3974if (physical_keycode == Key::NONE && keycode == Key::NONE && tmp[i] == 0) {3975continue;3976}39773978if (keycode == Key::NONE) {3979keycode = (Key)physical_keycode;3980}39813982_get_key_modifier_state(xkeyevent->state, k);39833984k->set_window_id(p_window);3985k->set_pressed(keypress);39863987k->set_keycode(keycode);3988k->set_physical_keycode(physical_keycode);3989if (!keysym.is_empty()) {3990k->set_key_label(fix_key_label(keysym[0], keycode));3991} else {3992k->set_key_label(keycode);3993}3994if (keypress) {3995k->set_unicode(fix_unicode(tmp[i]));3996}39973998k->set_location(key_location);39994000k->set_echo(false);40014002if (k->get_keycode() == Key::BACKTAB) {4003//make it consistent across platforms.4004k->set_keycode(Key::TAB);4005k->set_physical_keycode(Key::TAB);4006k->set_shift_pressed(true);4007}40084009Input::get_singleton()->parse_input_event(k);4010}4011return;4012}4013}4014#endif4015}40164017/* Phase 2, obtain a Godot keycode from the keysym */40184019// KeyMappingX11 just translated the X11 keysym to a PIGUI4020// keysym, so it works in all platforms the same.40214022Key keycode = Key::NONE;4023if (KeyMappingX11::is_sym_numpad(keysym_unicode) || KeyMappingX11::is_sym_numpad(keysym_keycode)) {4024// Special case for numpad keys.4025keycode = KeyMappingX11::get_keycode(keysym_unicode);4026}40274028if (keycode == Key::NONE) {4029keycode = KeyMappingX11::get_keycode(keysym_keycode);4030}40314032Key physical_keycode = KeyMappingX11::get_scancode(xkeyevent->keycode);40334034KeyLocation key_location = KeyMappingX11::get_location(xkeyevent->keycode);40354036/* Phase 3, obtain a unicode character from the keysym */40374038// KeyMappingX11 also translates keysym to unicode.4039// It does a binary search on a table to translate4040// most properly.4041char32_t unicode = keysym_unicode > 0 ? KeyMappingX11::get_unicode_from_keysym(keysym_unicode) : 0;40424043/* Phase 4, determine if event must be filtered */40444045// This seems to be a side-effect of using XIM.4046// XFilterEvent looks like a core X11 function,4047// but it's actually just used to see if we must4048// ignore a deadkey, or events XIM determines4049// must not reach the actual gui.4050// Guess it was a design problem of the extension40514052bool keypress = xkeyevent->type == KeyPress;40534054if (physical_keycode == Key::NONE && keycode == Key::NONE && unicode == 0) {4055return;4056}40574058if (keycode == Key::NONE) {4059keycode = (Key)physical_keycode;4060}40614062/* Phase 5, determine modifier mask */40634064// No problems here, except I had no way to4065// know Mod1 was ALT and Mod4 was META (applekey/winkey)4066// just tried Mods until i found them.40674068//print_verbose("mod1: "+itos(xkeyevent->state&Mod1Mask)+" mod 5: "+itos(xkeyevent->state&Mod5Mask));40694070Ref<InputEventKey> k;4071k.instantiate();4072k->set_window_id(p_window);40734074_get_key_modifier_state(xkeyevent->state, k);40754076/* Phase 6, determine echo character */40774078// Echo characters in X11 are a keyrelease and a keypress4079// one after the other with the (almot) same timestamp.4080// To detect them, i compare to the next event in list and4081// check that their difference in time is below a threshold.40824083if (xkeyevent->type != KeyPress) {4084p_echo = false;40854086// make sure there are events pending,4087// so this call won't block.4088if (p_event_index + 1 < p_events.size()) {4089XEvent &peek_event = p_events[p_event_index + 1];40904091// I'm using a threshold of 5 msecs,4092// since sometimes there seems to be a little4093// jitter. I'm still not convinced that all this approach4094// is correct, but the xorg developers are4095// not very helpful today.40964097#define ABSDIFF(x, y) (((x) < (y)) ? ((y) - (x)) : ((x) - (y)))4098::Time threshold = ABSDIFF(peek_event.xkey.time, xkeyevent->time);4099#undef ABSDIFF4100if (peek_event.type == KeyPress && threshold < 5) {4101KeySym rk;4102XLookupString((XKeyEvent *)&peek_event, str, 256, &rk, nullptr);4103if (rk == keysym_keycode) {4104// Consume to next event.4105++p_event_index;4106_handle_key_event(p_window, (XKeyEvent *)&peek_event, p_events, p_event_index, true);4107return; //ignore current, echo next4108}4109}41104111// use the time from peek_event so it always works4112}41134114// save the time to check for echo when keypress happens4115}41164117/* Phase 7, send event to Window */41184119k->set_pressed(keypress);41204121if (keycode >= Key::A + 32 && keycode <= Key::Z + 32) {4122keycode -= int('a' - 'A');4123}41244125k->set_keycode(keycode);4126k->set_physical_keycode((Key)physical_keycode);4127if (!keysym.is_empty()) {4128k->set_key_label(fix_key_label(keysym[0], keycode));4129} else {4130k->set_key_label(keycode);4131}4132if (keypress) {4133k->set_unicode(fix_unicode(unicode));4134}41354136k->set_location(key_location);41374138k->set_echo(p_echo);41394140if (k->get_keycode() == Key::BACKTAB) {4141//make it consistent across platforms.4142k->set_keycode(Key::TAB);4143k->set_physical_keycode(Key::TAB);4144k->set_shift_pressed(true);4145}41464147//don't set mod state if modifier keys are released by themselves4148//else event.is_action() will not work correctly here4149if (!k->is_pressed()) {4150if (k->get_keycode() == Key::SHIFT) {4151k->set_shift_pressed(false);4152} else if (k->get_keycode() == Key::CTRL) {4153k->set_ctrl_pressed(false);4154} else if (k->get_keycode() == Key::ALT) {4155k->set_alt_pressed(false);4156} else if (k->get_keycode() == Key::META) {4157k->set_meta_pressed(false);4158}4159}41604161bool last_is_pressed = Input::get_singleton()->is_key_pressed(k->get_keycode());4162if (k->is_pressed()) {4163if (last_is_pressed) {4164k->set_echo(true);4165}4166}41674168Input::get_singleton()->parse_input_event(k);4169}41704171Atom DisplayServerX11::_process_selection_request_target(Atom p_target, Window p_requestor, Atom p_property, Atom p_selection) const {4172if (p_target == XInternAtom(x11_display, "TARGETS", 0)) {4173// Request to list all supported targets.4174Atom data[9];4175data[0] = XInternAtom(x11_display, "TARGETS", 0);4176data[1] = XInternAtom(x11_display, "SAVE_TARGETS", 0);4177data[2] = XInternAtom(x11_display, "MULTIPLE", 0);4178data[3] = XInternAtom(x11_display, "UTF8_STRING", 0);4179data[4] = XInternAtom(x11_display, "COMPOUND_TEXT", 0);4180data[5] = XInternAtom(x11_display, "TEXT", 0);4181data[6] = XA_STRING;4182data[7] = XInternAtom(x11_display, "text/plain;charset=utf-8", 0);4183data[8] = XInternAtom(x11_display, "text/plain", 0);41844185XChangeProperty(x11_display,4186p_requestor,4187p_property,4188XA_ATOM,418932,4190PropModeReplace,4191(unsigned char *)&data,4192std::size(data));4193return p_property;4194} else if (p_target == XInternAtom(x11_display, "SAVE_TARGETS", 0)) {4195// Request to check if SAVE_TARGETS is supported, nothing special to do.4196XChangeProperty(x11_display,4197p_requestor,4198p_property,4199XInternAtom(x11_display, "NULL", False),420032,4201PropModeReplace,4202nullptr,42030);4204return p_property;4205} else if (p_target == XInternAtom(x11_display, "UTF8_STRING", 0) ||4206p_target == XInternAtom(x11_display, "COMPOUND_TEXT", 0) ||4207p_target == XInternAtom(x11_display, "TEXT", 0) ||4208p_target == XA_STRING ||4209p_target == XInternAtom(x11_display, "text/plain;charset=utf-8", 0) ||4210p_target == XInternAtom(x11_display, "text/plain", 0)) {4211// Directly using internal clipboard because we know our window4212// is the owner during a selection request.4213CharString clip;4214static const char *target_type = "PRIMARY";4215if (p_selection != None && get_atom_name(x11_display, p_selection) == target_type) {4216clip = internal_clipboard_primary.utf8();4217} else {4218clip = internal_clipboard.utf8();4219}4220XChangeProperty(x11_display,4221p_requestor,4222p_property,4223p_target,42248,4225PropModeReplace,4226(unsigned char *)clip.get_data(),4227clip.length());4228return p_property;4229} else {4230char *target_name = XGetAtomName(x11_display, p_target);4231print_verbose(vformat("Target '%s' not supported.", target_name));4232if (target_name) {4233XFree(target_name);4234}4235return None;4236}4237}42384239void DisplayServerX11::_handle_selection_request_event(XSelectionRequestEvent *p_event) const {4240XEvent respond;4241if (p_event->target == XInternAtom(x11_display, "MULTIPLE", 0)) {4242// Request for multiple target conversions at once.4243Atom atom_pair = XInternAtom(x11_display, "ATOM_PAIR", False);4244respond.xselection.property = None;42454246Atom type;4247int format;4248unsigned long len;4249unsigned long remaining;4250unsigned char *data = nullptr;4251if (XGetWindowProperty(x11_display, p_event->requestor, p_event->property, 0, LONG_MAX, False, atom_pair, &type, &format, &len, &remaining, &data) == Success) {4252if ((len >= 2) && data) {4253Atom *targets = (Atom *)data;4254for (uint64_t i = 0; i < len; i += 2) {4255Atom target = targets[i];4256Atom &property = targets[i + 1];4257property = _process_selection_request_target(target, p_event->requestor, property, p_event->selection);4258}42594260XChangeProperty(x11_display,4261p_event->requestor,4262p_event->property,4263atom_pair,426432,4265PropModeReplace,4266(unsigned char *)targets,4267len);42684269respond.xselection.property = p_event->property;4270}4271XFree(data);4272}4273} else {4274// Request for target conversion.4275respond.xselection.property = _process_selection_request_target(p_event->target, p_event->requestor, p_event->property, p_event->selection);4276}42774278respond.xselection.type = SelectionNotify;4279respond.xselection.display = p_event->display;4280respond.xselection.requestor = p_event->requestor;4281respond.xselection.selection = p_event->selection;4282respond.xselection.target = p_event->target;4283respond.xselection.time = p_event->time;42844285XSendEvent(x11_display, p_event->requestor, True, NoEventMask, &respond);4286XFlush(x11_display);4287}42884289int DisplayServerX11::_xim_preedit_start_callback(::XIM xim, ::XPointer client_data,4290::XPointer call_data) {4291DisplayServerX11 *ds = reinterpret_cast<DisplayServerX11 *>(client_data);4292WindowID window_id = ds->_get_focused_window_or_popup();4293WindowData &wd = ds->windows[window_id];4294if (wd.ime_active) {4295wd.ime_in_progress = true;4296}42974298return -1; // Allow preedit strings of any length (no limit).4299}43004301void DisplayServerX11::_xim_preedit_done_callback(::XIM xim, ::XPointer client_data,4302::XPointer call_data) {4303DisplayServerX11 *ds = reinterpret_cast<DisplayServerX11 *>(client_data);4304WindowID window_id = ds->_get_focused_window_or_popup();4305WindowData &wd = ds->windows[window_id];4306if (wd.ime_active) {4307wd.ime_in_progress = false;4308wd.ime_suppress_next_keyup = true;4309}4310}43114312void DisplayServerX11::_xim_preedit_draw_callback(::XIM xim, ::XPointer client_data,4313::XIMPreeditDrawCallbackStruct *call_data) {4314DisplayServerX11 *ds = reinterpret_cast<DisplayServerX11 *>(client_data);4315WindowID window_id = ds->_get_focused_window_or_popup();4316WindowData &wd = ds->windows[window_id];43174318XIMText *xim_text = call_data->text;4319if (wd.ime_active) {4320if (xim_text != nullptr) {4321String changed_text;4322if (xim_text->encoding_is_wchar) {4323changed_text = String(xim_text->string.wide_char);4324} else {4325changed_text.append_utf8(xim_text->string.multi_byte);4326}43274328if (call_data->chg_length < 0) {4329ds->im_text = ds->im_text.substr(0, call_data->chg_first) + changed_text;4330} else {4331ds->im_text = ds->im_text.substr(0, call_data->chg_first) + changed_text + ds->im_text.substr(call_data->chg_length);4332}43334334// Find the start and end of the selection.4335int start = 0, count = 0;4336for (int i = 0; i < xim_text->length; i++) {4337if (xim_text->feedback[i] & XIMReverse) {4338if (count == 0) {4339start = i;4340count = 1;4341} else {4342count++;4343}4344}4345}4346if (count > 0) {4347ds->im_selection = Point2i(start + call_data->chg_first, count);4348} else {4349ds->im_selection = Point2i(call_data->caret, 0);4350}4351} else {4352ds->im_text = String();4353ds->im_selection = Point2i();4354}43554356callable_mp((Object *)OS_Unix::get_singleton()->get_main_loop(), &Object::notification).call_deferred(MainLoop::NOTIFICATION_OS_IME_UPDATE, false);4357}4358}43594360void DisplayServerX11::_xim_preedit_caret_callback(::XIM xim, ::XPointer client_data,4361::XIMPreeditCaretCallbackStruct *call_data) {4362}43634364void DisplayServerX11::_xim_destroy_callback(::XIM im, ::XPointer client_data,4365::XPointer call_data) {4366WARN_PRINT("Input method stopped");4367DisplayServerX11 *ds = reinterpret_cast<DisplayServerX11 *>(client_data);4368ds->xim = nullptr;43694370for (KeyValue<WindowID, WindowData> &E : ds->windows) {4371E.value.xic = nullptr;4372}4373}43744375void DisplayServerX11::_window_changed(XEvent *event) {4376WindowID window_id = MAIN_WINDOW_ID;43774378// Assign the event to the relevant window4379for (const KeyValue<WindowID, WindowData> &E : windows) {4380if (event->xany.window == E.value.x11_window) {4381window_id = E.key;4382break;4383}4384}43854386Rect2i new_rect;43874388WindowData &wd = windows[window_id];4389if (wd.x11_window != event->xany.window) { // Check if the correct window, in case it was not main window or anything else4390return;4391}43924393// Query display server about a possible new window state.4394wd.fullscreen = _window_fullscreen_check(window_id);4395wd.maximized = _window_maximize_check(window_id, "_NET_WM_STATE") && !wd.fullscreen;4396wd.minimized = _window_minimize_check(window_id) && !wd.fullscreen && !wd.maximized;43974398// Readjusting the window position if the window is being reparented by the window manager for decoration4399Window root, parent, *children;4400unsigned int nchildren;4401if (XQueryTree(x11_display, wd.x11_window, &root, &parent, &children, &nchildren) && wd.parent != parent) {4402wd.parent = parent;4403if (!wd.embed_parent) {4404window_set_position(wd.position, window_id);4405}4406}4407XFree(children);44084409{4410//the position in xconfigure is not useful here, obtain it manually4411int x = 0, y = 0;4412Window child;4413XTranslateCoordinates(x11_display, wd.x11_window, DefaultRootWindow(x11_display), 0, 0, &x, &y, &child);4414new_rect.position.x = x;4415new_rect.position.y = y;44164417new_rect.size.width = event->xconfigure.width;4418new_rect.size.height = event->xconfigure.height;4419}44204421if (new_rect == Rect2i(wd.position, wd.size)) {4422return;4423}44244425wd.position = new_rect.position;4426wd.size = new_rect.size;44274428#if defined(RD_ENABLED)4429if (rendering_context) {4430rendering_context->window_set_size(window_id, wd.size.width, wd.size.height);4431}4432#endif4433#if defined(GLES3_ENABLED)4434if (gl_manager) {4435gl_manager->window_resize(window_id, wd.size.width, wd.size.height);4436}4437if (gl_manager_egl) {4438gl_manager_egl->window_resize(window_id, wd.size.width, wd.size.height);4439}4440#endif44414442if (wd.rect_changed_callback.is_valid()) {4443wd.rect_changed_callback.call(new_rect);4444}4445}44464447DisplayServer::WindowID DisplayServerX11::_get_focused_window_or_popup() const {4448const List<WindowID>::Element *E = popup_list.back();4449if (E) {4450return E->get();4451}44524453return last_focused_window;4454}44554456void DisplayServerX11::_dispatch_input_events(const Ref<InputEvent> &p_event) {4457static_cast<DisplayServerX11 *>(get_singleton())->_dispatch_input_event(p_event);4458}44594460void DisplayServerX11::_dispatch_input_event(const Ref<InputEvent> &p_event) {4461{4462List<WindowID>::Element *E = popup_list.back();4463if (E && Object::cast_to<InputEventKey>(*p_event)) {4464// Redirect keyboard input to active popup.4465if (windows.has(E->get())) {4466Callable callable = windows[E->get()].input_event_callback;4467if (callable.is_valid()) {4468callable.call(p_event);4469}4470}4471return;4472}4473}44744475Ref<InputEventFromWindow> event_from_window = p_event;4476if (event_from_window.is_valid() && event_from_window->get_window_id() != INVALID_WINDOW_ID) {4477// Send to a single window.4478if (windows.has(event_from_window->get_window_id())) {4479Callable callable = windows[event_from_window->get_window_id()].input_event_callback;4480if (callable.is_valid()) {4481callable.call(p_event);4482}4483}4484} else {4485// Send to all windows. Copy all pending callbacks, since callback can erase window.4486Vector<Callable> cbs;4487for (KeyValue<WindowID, WindowData> &E : windows) {4488Callable callable = E.value.input_event_callback;4489if (callable.is_valid()) {4490cbs.push_back(callable);4491}4492}4493for (const Callable &cb : cbs) {4494cb.call(p_event);4495}4496}4497}44984499void DisplayServerX11::_send_window_event(const WindowData &wd, WindowEvent p_event) {4500if (wd.event_callback.is_valid()) {4501Variant event = int(p_event);4502wd.event_callback.call(event);4503}4504}45054506void DisplayServerX11::_set_input_focus(Window p_window, int p_revert_to) {4507Window focused_window;4508int focus_ret_state;4509XGetInputFocus(x11_display, &focused_window, &focus_ret_state);45104511// Only attempt to change focus if the window isn't already focused, in order to4512// prevent issues with Godot stealing input focus with alternative window managers.4513if (p_window != focused_window) {4514XSetInputFocus(x11_display, p_window, p_revert_to, CurrentTime);4515}4516}45174518void DisplayServerX11::_poll_events_thread(void *ud) {4519DisplayServerX11 *display_server = static_cast<DisplayServerX11 *>(ud);4520display_server->_poll_events();4521}45224523Bool DisplayServerX11::_predicate_all_events(Display *display, XEvent *event, XPointer arg) {4524// Just accept all events.4525return True;4526}45274528bool DisplayServerX11::_wait_for_events() const {4529int x11_fd = ConnectionNumber(x11_display);4530fd_set in_fds;45314532XFlush(x11_display);45334534FD_ZERO(&in_fds);4535FD_SET(x11_fd, &in_fds);45364537struct timeval tv;4538tv.tv_usec = 0;4539tv.tv_sec = 1;45404541// Wait for next event or timeout.4542int num_ready_fds = select(x11_fd + 1, &in_fds, nullptr, nullptr, &tv);45434544if (num_ready_fds > 0) {4545// Event received.4546return true;4547} else {4548// Error or timeout.4549if (num_ready_fds < 0) {4550ERR_PRINT("_wait_for_events: select error: " + itos(errno));4551}4552return false;4553}4554}45554556void DisplayServerX11::_poll_events() {4557while (!events_thread_done.is_set()) {4558_wait_for_events();45594560// Process events from the queue.4561{4562MutexLock mutex_lock(events_mutex);45634564_check_pending_events(polled_events);4565}4566}4567}45684569void DisplayServerX11::_check_pending_events(LocalVector<XEvent> &r_events) {4570// Flush to make sure to gather all pending events.4571XFlush(x11_display);45724573// Non-blocking wait for next event and remove it from the queue.4574XEvent ev = {};4575while (XCheckIfEvent(x11_display, &ev, _predicate_all_events, nullptr)) {4576// Check if the input manager wants to process the event.4577if (XFilterEvent(&ev, None)) {4578// Event has been filtered by the Input Manager,4579// it has to be ignored and a new one will be received.4580continue;4581}45824583// Handle selection request events directly in the event thread, because4584// communication through the x server takes several events sent back and forth4585// and we don't want to block other programs while processing only one each frame.4586if (ev.type == SelectionRequest) {4587_handle_selection_request_event(&(ev.xselectionrequest));4588continue;4589}45904591r_events.push_back(ev);4592}4593}45944595DisplayServer::WindowID DisplayServerX11::window_get_active_popup() const {4596const List<WindowID>::Element *E = popup_list.back();4597if (E) {4598return E->get();4599} else {4600return INVALID_WINDOW_ID;4601}4602}46034604void DisplayServerX11::window_set_popup_safe_rect(WindowID p_window, const Rect2i &p_rect) {4605_THREAD_SAFE_METHOD_46064607ERR_FAIL_COND(!windows.has(p_window));4608WindowData &wd = windows[p_window];4609wd.parent_safe_rect = p_rect;4610}46114612Rect2i DisplayServerX11::window_get_popup_safe_rect(WindowID p_window) const {4613_THREAD_SAFE_METHOD_46144615ERR_FAIL_COND_V(!windows.has(p_window), Rect2i());4616const WindowData &wd = windows[p_window];4617return wd.parent_safe_rect;4618}46194620void DisplayServerX11::popup_open(WindowID p_window) {4621_THREAD_SAFE_METHOD_46224623bool has_popup_ancestor = false;4624WindowID transient_root = p_window;4625while (true) {4626WindowID parent = windows[transient_root].transient_parent;4627if (parent == INVALID_WINDOW_ID) {4628break;4629} else {4630transient_root = parent;4631if (windows[parent].is_popup) {4632has_popup_ancestor = true;4633break;4634}4635}4636}46374638// Detect tooltips and other similar popups that shouldn't block input to their parent.4639bool ignores_input = window_get_flag(WINDOW_FLAG_NO_FOCUS, p_window) && window_get_flag(WINDOW_FLAG_MOUSE_PASSTHROUGH, p_window);46404641WindowData &wd = windows[p_window];4642if (wd.is_popup || (has_popup_ancestor && !ignores_input)) {4643// Find current popup parent, or root popup if new window is not transient.4644List<WindowID>::Element *C = nullptr;4645List<WindowID>::Element *E = popup_list.back();4646while (E) {4647if (wd.transient_parent != E->get() || wd.transient_parent == INVALID_WINDOW_ID) {4648C = E;4649E = E->prev();4650} else {4651break;4652}4653}4654if (C) {4655_send_window_event(windows[C->get()], DisplayServerX11::WINDOW_EVENT_CLOSE_REQUEST);4656}46574658time_since_popup = OS::get_singleton()->get_ticks_msec();4659popup_list.push_back(p_window);4660}4661}46624663void DisplayServerX11::popup_close(WindowID p_window) {4664_THREAD_SAFE_METHOD_46654666List<WindowID>::Element *E = popup_list.find(p_window);4667while (E) {4668List<WindowID>::Element *F = E->next();4669WindowID win_id = E->get();4670popup_list.erase(E);46714672if (win_id != p_window) {4673// Only request close on related windows, not this window. We are already processing it.4674_send_window_event(windows[win_id], DisplayServerX11::WINDOW_EVENT_CLOSE_REQUEST);4675}4676E = F;4677}4678}46794680bool DisplayServerX11::mouse_process_popups() {4681_THREAD_SAFE_METHOD_46824683if (popup_list.is_empty()) {4684return false;4685}46864687uint64_t delta = OS::get_singleton()->get_ticks_msec() - time_since_popup;4688if (delta < 250) {4689return false;4690}46914692int number_of_screens = XScreenCount(x11_display);4693bool closed = false;4694for (int i = 0; i < number_of_screens; i++) {4695Window root, child;4696int root_x, root_y, win_x, win_y;4697unsigned int mask;4698if (XQueryPointer(x11_display, XRootWindow(x11_display, i), &root, &child, &root_x, &root_y, &win_x, &win_y, &mask)) {4699XWindowAttributes root_attrs;4700XGetWindowAttributes(x11_display, root, &root_attrs);4701Vector2i pos = Vector2i(root_attrs.x + root_x, root_attrs.y + root_y);4702if (mask != last_mouse_monitor_mask) {4703if (((mask & Button1Mask) || (mask & Button2Mask) || (mask & Button3Mask) || (mask & Button4Mask) || (mask & Button5Mask))) {4704List<WindowID>::Element *C = nullptr;4705List<WindowID>::Element *E = popup_list.back();4706// Find top popup to close.4707while (E) {4708// Popup window area.4709Rect2i win_rect = Rect2i(window_get_position_with_decorations(E->get()), window_get_size_with_decorations(E->get()));4710// Area of the parent window, which responsible for opening sub-menu.4711Rect2i safe_rect = window_get_popup_safe_rect(E->get());4712if (win_rect.has_point(pos)) {4713break;4714} else if (safe_rect != Rect2i() && safe_rect.has_point(pos)) {4715break;4716} else {4717C = E;4718E = E->prev();4719}4720}4721if (C) {4722_send_window_event(windows[C->get()], DisplayServerX11::WINDOW_EVENT_CLOSE_REQUEST);4723closed = true;4724}4725}4726}4727last_mouse_monitor_mask = mask;4728}4729}4730return closed;4731}47324733bool DisplayServerX11::_window_focus_check() {4734Window focused_window;4735int focus_ret_state;4736XGetInputFocus(x11_display, &focused_window, &focus_ret_state);47374738bool has_focus = false;4739for (const KeyValue<int, DisplayServerX11::WindowData> &wid : windows) {4740if (wid.value.x11_window == focused_window || (wid.value.xic && wid.value.ime_active && wid.value.x11_xim_window == focused_window)) {4741has_focus = true;4742break;4743}4744}47454746return has_focus;4747}47484749void DisplayServerX11::process_events() {4750ERR_FAIL_COND(!Thread::is_main_thread());47514752_THREAD_SAFE_LOCK_47534754#ifdef DISPLAY_SERVER_X11_DEBUG_LOGS_ENABLED4755static int frame = 0;4756++frame;4757#endif47584759bool ignore_events = mouse_process_popups();47604761if (app_focused) {4762//verify that one of the windows has focus, else send focus out notification4763bool focus_found = false;4764for (const KeyValue<WindowID, WindowData> &E : windows) {4765if (E.value.focused) {4766focus_found = true;4767break;4768}4769}47704771if (!focus_found) {4772uint64_t delta = OS::get_singleton()->get_ticks_msec() - time_since_no_focus;47734774if (delta > 250) {4775//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.4776if (OS::get_singleton()->get_main_loop()) {4777DEBUG_LOG_X11("All focus lost, triggering NOTIFICATION_APPLICATION_FOCUS_OUT\n");4778OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT);4779}4780app_focused = false;4781}4782} else {4783time_since_no_focus = OS::get_singleton()->get_ticks_msec();4784}4785}47864787do_mouse_warp = false;47884789// Is the current mouse mode one where it needs to be grabbed.4790bool mouse_mode_grab = mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN;47914792xi.pressure = 0;4793xi.tilt = Vector2();4794xi.pressure_supported = false;47954796LocalVector<XEvent> events;4797{4798// Block events polling while flushing events.4799MutexLock mutex_lock(events_mutex);4800events = polled_events;4801polled_events.clear();48024803// Check for more pending events to avoid an extra frame delay.4804_check_pending_events(events);4805}48064807for (uint32_t event_index = 0; event_index < events.size(); ++event_index) {4808XEvent &event = events[event_index];48094810bool ime_window_event = false;4811WindowID window_id = MAIN_WINDOW_ID;48124813// Assign the event to the relevant window4814for (const KeyValue<WindowID, WindowData> &E : windows) {4815if (event.xany.window == E.value.x11_window) {4816window_id = E.key;4817break;4818}4819if (event.xany.window == E.value.x11_xim_window) {4820window_id = E.key;4821ime_window_event = true;4822break;4823}4824}48254826if (XGetEventData(x11_display, &event.xcookie)) {4827if (event.xcookie.type == GenericEvent && event.xcookie.extension == xi.opcode) {4828XIDeviceEvent *event_data = (XIDeviceEvent *)event.xcookie.data;4829switch (event_data->evtype) {4830case XI_HierarchyChanged:4831case XI_DeviceChanged: {4832_refresh_device_info();4833} break;4834case XI_RawMotion: {4835if (ime_window_event || ignore_events) {4836break;4837}4838XIRawEvent *raw_event = (XIRawEvent *)event_data;4839int device_id = raw_event->sourceid;48404841// Determine the axis used (called valuators in XInput for some forsaken reason)4842// Mask is a bitmask indicating which axes are involved.4843// We are interested in the values of axes 0 and 1.4844if (raw_event->valuators.mask_len <= 0) {4845break;4846}48474848const double *values = raw_event->raw_values;48494850double rel_x = 0.0;4851double rel_y = 0.0;48524853if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_ABSX)) {4854rel_x = *values;4855values++;4856}48574858if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_ABSY)) {4859rel_y = *values;4860values++;4861}48624863if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_PRESSURE)) {4864HashMap<int, Vector2>::Iterator pen_pressure = xi.pen_pressure_range.find(device_id);4865if (pen_pressure) {4866Vector2 pen_pressure_range = pen_pressure->value;4867if (pen_pressure_range != Vector2()) {4868xi.pressure_supported = true;4869xi.pressure = (*values - pen_pressure_range[0]) /4870(pen_pressure_range[1] - pen_pressure_range[0]);4871}4872}48734874values++;4875}48764877if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_TILTX)) {4878HashMap<int, Vector2>::Iterator pen_tilt_x = xi.pen_tilt_x_range.find(device_id);4879if (pen_tilt_x) {4880Vector2 pen_tilt_x_range = pen_tilt_x->value;4881if (pen_tilt_x_range[0] != 0 && *values < 0) {4882xi.tilt.x = *values / -pen_tilt_x_range[0];4883} else if (pen_tilt_x_range[1] != 0) {4884xi.tilt.x = *values / pen_tilt_x_range[1];4885}4886}48874888values++;4889}48904891if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_TILTY)) {4892HashMap<int, Vector2>::Iterator pen_tilt_y = xi.pen_tilt_y_range.find(device_id);4893if (pen_tilt_y) {4894Vector2 pen_tilt_y_range = pen_tilt_y->value;4895if (pen_tilt_y_range[0] != 0 && *values < 0) {4896xi.tilt.y = *values / -pen_tilt_y_range[0];4897} else if (pen_tilt_y_range[1] != 0) {4898xi.tilt.y = *values / pen_tilt_y_range[1];4899}4900}49014902values++;4903}49044905HashMap<int, bool>::Iterator pen_inverted = xi.pen_inverted_devices.find(device_id);4906if (pen_inverted) {4907xi.pen_inverted = pen_inverted->value;4908}49094910// https://bugs.freedesktop.org/show_bug.cgi?id=716094911// http://lists.libsdl.org/pipermail/commits-libsdl.org/2015-June/000282.html4912if (raw_event->time == xi.last_relative_time && rel_x == xi.relative_motion.x && rel_y == xi.relative_motion.y) {4913break; // Flush duplicate to avoid overly fast motion4914}49154916xi.old_raw_pos.x = xi.raw_pos.x;4917xi.old_raw_pos.y = xi.raw_pos.y;4918xi.raw_pos.x = rel_x;4919xi.raw_pos.y = rel_y;49204921HashMap<int, Vector2>::Iterator abs_info = xi.absolute_devices.find(device_id);49224923if (abs_info) {4924// Absolute mode device4925Vector2 mult = abs_info->value;49264927xi.relative_motion.x += (xi.raw_pos.x - xi.old_raw_pos.x) * mult.x;4928xi.relative_motion.y += (xi.raw_pos.y - xi.old_raw_pos.y) * mult.y;4929} else {4930// Relative mode device4931xi.relative_motion.x = xi.raw_pos.x;4932xi.relative_motion.y = xi.raw_pos.y;4933}49344935xi.last_relative_time = raw_event->time;4936} break;4937#ifdef TOUCH_ENABLED4938case XI_TouchBegin:4939case XI_TouchEnd: {4940if (ime_window_event || ignore_events) {4941break;4942}4943bool is_begin = event_data->evtype == XI_TouchBegin;49444945int index = event_data->detail;4946Vector2 pos = Vector2(event_data->event_x, event_data->event_y);49474948Ref<InputEventScreenTouch> st;4949st.instantiate();4950st->set_window_id(window_id);4951st->set_index(index);4952st->set_position(pos);4953st->set_pressed(is_begin);49544955if (is_begin) {4956if (xi.state.has(index)) { // Defensive4957break;4958}4959xi.state[index] = pos;4960if (xi.state.size() == 1) {4961// X11 may send a motion event when a touch gesture begins, that would result4962// in a spurious mouse motion event being sent to Godot; remember it to be able to filter it out4963xi.mouse_pos_to_filter = pos;4964}4965Input::get_singleton()->parse_input_event(st);4966} else {4967if (!xi.state.has(index)) { // Defensive4968break;4969}4970xi.state.erase(index);4971Input::get_singleton()->parse_input_event(st);4972}4973} break;49744975case XI_TouchUpdate: {4976if (ime_window_event || ignore_events) {4977break;4978}49794980int index = event_data->detail;4981Vector2 pos = Vector2(event_data->event_x, event_data->event_y);49824983HashMap<int, Vector2>::Iterator curr_pos_elem = xi.state.find(index);4984if (!curr_pos_elem) { // Defensive4985break;4986}49874988if (curr_pos_elem->value != pos) {4989Ref<InputEventScreenDrag> sd;4990sd.instantiate();4991sd->set_window_id(window_id);4992sd->set_index(index);4993sd->set_position(pos);4994sd->set_relative(pos - curr_pos_elem->value);4995sd->set_relative_screen_position(sd->get_relative());4996Input::get_singleton()->parse_input_event(sd);49974998curr_pos_elem->value = pos;4999}5000} break;5001#endif5002}5003}5004}5005XFreeEventData(x11_display, &event.xcookie);50065007switch (event.type) {5008case MapNotify: {5009DEBUG_LOG_X11("[%u] MapNotify window=%lu (%u) \n", frame, event.xmap.window, window_id);5010if (ime_window_event) {5011break;5012}50135014const WindowData &wd = windows[window_id];50155016XWindowAttributes xwa;5017XSync(x11_display, False);5018XGetWindowAttributes(x11_display, wd.x11_window, &xwa);50195020_update_actions_hints(window_id);5021XFlush(x11_display);50225023// Set focus when menu window is started.5024// RevertToPointerRoot is used to make sure we don't lose all focus in case5025// a subwindow and its parent are both destroyed.5026if ((xwa.map_state == IsViewable) && !wd.no_focus && !wd.is_popup && _window_focus_check()) {5027_set_input_focus(wd.x11_window, RevertToPointerRoot);5028}50295030// Have we failed to set fullscreen while the window was unmapped?5031_validate_mode_on_map(window_id);50325033// On KDE Plasma, when the parent window of an embedded process is restored after being minimized,5034// only the embedded window receives the Map notification, causing it to5035// appear without its parent.5036if (wd.embed_parent) {5037XMapWindow(x11_display, wd.embed_parent);5038}5039} break;50405041case Expose: {5042DEBUG_LOG_X11("[%u] Expose window=%lu (%u), count='%u' \n", frame, event.xexpose.window, window_id, event.xexpose.count);5043if (ime_window_event) {5044break;5045}50465047windows[window_id].fullscreen = _window_fullscreen_check(window_id);50485049Main::force_redraw();5050} break;50515052case NoExpose: {5053DEBUG_LOG_X11("[%u] NoExpose drawable=%lu (%u) \n", frame, event.xnoexpose.drawable, window_id);5054if (ime_window_event) {5055break;5056}50575058windows[window_id].minimized = true;5059} break;50605061case VisibilityNotify: {5062DEBUG_LOG_X11("[%u] VisibilityNotify window=%lu (%u), state=%u \n", frame, event.xvisibility.window, window_id, event.xvisibility.state);5063if (ime_window_event) {5064break;5065}50665067windows[window_id].minimized = _window_minimize_check(window_id);5068} break;50695070case LeaveNotify: {5071DEBUG_LOG_X11("[%u] LeaveNotify window=%lu (%u), mode='%u' \n", frame, event.xcrossing.window, window_id, event.xcrossing.mode);5072if (ime_window_event) {5073break;5074}50755076if (!mouse_mode_grab && window_mouseover_id == window_id) {5077window_mouseover_id = INVALID_WINDOW_ID;5078_send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_EXIT);5079}50805081} break;50825083case EnterNotify: {5084DEBUG_LOG_X11("[%u] EnterNotify window=%lu (%u), mode='%u' \n", frame, event.xcrossing.window, window_id, event.xcrossing.mode);5085if (ime_window_event) {5086break;5087}50885089if (!mouse_mode_grab && window_mouseover_id != window_id) {5090if (window_mouseover_id != INVALID_WINDOW_ID) {5091_send_window_event(windows[window_mouseover_id], WINDOW_EVENT_MOUSE_EXIT);5092}5093window_mouseover_id = window_id;5094_send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_ENTER);5095}5096} break;50975098case FocusIn: {5099DEBUG_LOG_X11("[%u] FocusIn window=%lu (%u), mode='%u' \n", frame, event.xfocus.window, window_id, event.xfocus.mode);5100if (ime_window_event || (event.xfocus.detail == NotifyInferior)) {5101break;5102}51035104WindowData &wd = windows[window_id];5105last_focused_window = window_id;5106wd.focused = true;51075108// Keep track of focus order for overlapping windows.5109static unsigned int focus_order = 0;5110wd.focus_order = ++focus_order;51115112#ifdef ACCESSKIT_ENABLED5113if (accessibility_driver) {5114accessibility_driver->accessibility_set_window_focused(window_id, true);5115}5116#endif5117_send_window_event(wd, WINDOW_EVENT_FOCUS_IN);51185119if (mouse_mode_grab) {5120// Show and update the cursor if confined and the window regained focus.51215122for (const KeyValue<WindowID, WindowData> &E : windows) {5123if (mouse_mode == MOUSE_MODE_CONFINED) {5124XUndefineCursor(x11_display, E.value.x11_window);5125} else if (mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN) { // Or re-hide it.5126XDefineCursor(x11_display, E.value.x11_window, null_cursor);5127}51285129XGrabPointer(5130x11_display, E.value.x11_window, True,5131ButtonPressMask | ButtonReleaseMask | PointerMotionMask,5132GrabModeAsync, GrabModeAsync, E.value.x11_window, None, CurrentTime);5133}5134}5135#ifdef TOUCH_ENABLED5136// Grab touch devices to avoid OS gesture interference5137/*for (int i = 0; i < xi.touch_devices.size(); ++i) {5138XIGrabDevice(x11_display, xi.touch_devices[i], x11_window, CurrentTime, None, XIGrabModeAsync, XIGrabModeAsync, False, &xi.touch_event_mask);5139}*/5140#endif51415142if (!app_focused) {5143if (OS::get_singleton()->get_main_loop()) {5144OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN);5145}5146app_focused = true;5147}5148} break;51495150case FocusOut: {5151DEBUG_LOG_X11("[%u] FocusOut window=%lu (%u), mode='%u' \n", frame, event.xfocus.window, window_id, event.xfocus.mode);5152WindowData &wd = windows[window_id];5153if (ime_window_event || (event.xfocus.detail == NotifyInferior)) {5154break;5155}5156if (wd.ime_active) {5157MutexLock mutex_lock(events_mutex);5158XUnsetICFocus(wd.xic);5159XUnmapWindow(x11_display, wd.x11_xim_window);5160wd.ime_active = false;5161im_text = String();5162im_selection = Vector2i();5163OS_Unix::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE);5164}5165wd.focused = false;51665167Input::get_singleton()->release_pressed_events();5168#ifdef ACCESSKIT_ENABLED5169if (accessibility_driver) {5170accessibility_driver->accessibility_set_window_focused(window_id, false);5171}5172#endif5173_send_window_event(wd, WINDOW_EVENT_FOCUS_OUT);51745175if (mouse_mode_grab) {5176for (const KeyValue<WindowID, WindowData> &E : windows) {5177//dear X11, I try, I really try, but you never work, you do whatever you want.5178if (mouse_mode == MOUSE_MODE_CAPTURED) {5179// Show the cursor if we're in captured mode so it doesn't look weird.5180XUndefineCursor(x11_display, E.value.x11_window);5181}5182}5183XUngrabPointer(x11_display, CurrentTime);5184}5185#ifdef TOUCH_ENABLED5186// Ungrab touch devices so input works as usual while we are unfocused5187/*for (int i = 0; i < xi.touch_devices.size(); ++i) {5188XIUngrabDevice(x11_display, xi.touch_devices[i], CurrentTime);5189}*/51905191// Release every pointer to avoid sticky points5192for (const KeyValue<int, Vector2> &E : xi.state) {5193Ref<InputEventScreenTouch> st;5194st.instantiate();5195st->set_index(E.key);5196st->set_window_id(window_id);5197st->set_position(E.value);5198Input::get_singleton()->parse_input_event(st);5199}5200xi.state.clear();5201#endif5202} break;52035204case ConfigureNotify: {5205DEBUG_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);5206if (event.xconfigure.window == windows[window_id].x11_xim_window) {5207break;5208}52095210_window_changed(&event);5211} break;52125213case ButtonPress:5214case ButtonRelease: {5215if (ime_window_event || ignore_events) {5216break;5217}5218/* exit in case of a mouse button press */5219last_timestamp = event.xbutton.time;5220if (mouse_mode == MOUSE_MODE_CAPTURED) {5221event.xbutton.x = last_mouse_pos.x;5222event.xbutton.y = last_mouse_pos.y;5223}52245225Ref<InputEventMouseButton> mb;5226mb.instantiate();52275228mb->set_window_id(window_id);5229_get_key_modifier_state(event.xbutton.state, mb);5230mb->set_button_index((MouseButton)event.xbutton.button);5231if (mb->get_button_index() == MouseButton::RIGHT) {5232mb->set_button_index(MouseButton::MIDDLE);5233} else if (mb->get_button_index() == MouseButton::MIDDLE) {5234mb->set_button_index(MouseButton::RIGHT);5235}5236mb->set_position(Vector2(event.xbutton.x, event.xbutton.y));5237mb->set_global_position(mb->get_position());52385239mb->set_pressed((event.type == ButtonPress));52405241if (mb->is_pressed() && mb->get_button_index() >= MouseButton::WHEEL_UP && mb->get_button_index() <= MouseButton::WHEEL_RIGHT) {5242MouseButtonMask mask = mouse_button_to_mask(mb->get_button_index());5243BitField<MouseButtonMask> scroll_mask = mouse_get_button_state();5244scroll_mask.set_flag(mask);5245mb->set_button_mask(scroll_mask);5246} else {5247mb->set_button_mask(mouse_get_button_state());5248}52495250const WindowData &wd = windows[window_id];52515252if (event.type == ButtonPress) {5253DEBUG_LOG_X11("[%u] ButtonPress window=%lu (%u), button_index=%u \n", frame, event.xbutton.window, window_id, mb->get_button_index());52545255// Ensure window focus on click.5256// RevertToPointerRoot is used to make sure we don't lose all focus in case5257// a subwindow and its parent are both destroyed.5258if (!wd.no_focus && !wd.is_popup) {5259_set_input_focus(wd.x11_window, RevertToPointerRoot);5260}52615262uint64_t diff = OS::get_singleton()->get_ticks_usec() / 1000 - last_click_ms;52635264if (mb->get_button_index() == last_click_button_index) {5265if (diff < 400 && Vector2(last_click_pos).distance_to(Vector2(event.xbutton.x, event.xbutton.y)) < 5) {5266last_click_ms = 0;5267last_click_pos = Point2i(-100, -100);5268last_click_button_index = MouseButton::NONE;5269mb->set_double_click(true);5270}52715272} else if (mb->get_button_index() < MouseButton::WHEEL_UP || mb->get_button_index() > MouseButton::WHEEL_RIGHT) {5273last_click_button_index = mb->get_button_index();5274}52755276if (!mb->is_double_click()) {5277last_click_ms += diff;5278last_click_pos = Point2i(event.xbutton.x, event.xbutton.y);5279}5280} else {5281DEBUG_LOG_X11("[%u] ButtonRelease window=%lu (%u), button_index=%u \n", frame, event.xbutton.window, window_id, mb->get_button_index());52825283WindowID window_id_other = INVALID_WINDOW_ID;5284Window wd_other_x11_window;5285if (!wd.focused) {5286// Propagate the event to the focused window,5287// because it's received only on the topmost window.5288// Note: This is needed for drag & drop to work between windows,5289// because the engine expects events to keep being processed5290// on the same window dragging started.5291for (const KeyValue<WindowID, WindowData> &E : windows) {5292if (E.value.focused) {5293if (E.key != window_id) {5294window_id_other = E.key;5295wd_other_x11_window = E.value.x11_window;5296}5297break;5298}5299}5300}53015302if (window_id_other != INVALID_WINDOW_ID) {5303int x, y;5304Window child;5305XTranslateCoordinates(x11_display, wd.x11_window, wd_other_x11_window, event.xbutton.x, event.xbutton.y, &x, &y, &child);53065307mb->set_window_id(window_id_other);5308mb->set_position(Vector2(x, y));5309mb->set_global_position(mb->get_position());5310}5311}53125313Input::get_singleton()->parse_input_event(mb);53145315} break;5316case MotionNotify: {5317if (ime_window_event || ignore_events) {5318break;5319}5320// The X11 API requires filtering one-by-one through the motion5321// notify events, in order to figure out which event is the one5322// generated by warping the mouse pointer.5323WindowID focused_window_id = _get_focused_window_or_popup();5324if (!windows.has(focused_window_id)) {5325focused_window_id = MAIN_WINDOW_ID;5326}53275328while (true) {5329if (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) {5330//this is likely the warp event since it was warped here5331center = Vector2(event.xmotion.x, event.xmotion.y);5332break;5333}53345335if (event_index + 1 < events.size()) {5336const XEvent &next_event = events[event_index + 1];5337if (next_event.type == MotionNotify) {5338++event_index;5339event = next_event;5340} else {5341break;5342}5343} else {5344break;5345}5346}53475348last_timestamp = event.xmotion.time;53495350// Motion is also simple.5351// A little hack is in order5352// to be able to send relative motion events.5353Point2i pos(event.xmotion.x, event.xmotion.y);53545355// Avoidance of spurious mouse motion (see handling of touch)5356bool filter = false;5357// Adding some tolerance to match better Point2i to Vector25358if (xi.state.size() && Vector2(pos).distance_squared_to(xi.mouse_pos_to_filter) < 2) {5359filter = true;5360}5361// Invalidate to avoid filtering a possible legitimate similar event coming later5362xi.mouse_pos_to_filter = Vector2(1e10, 1e10);5363if (filter) {5364break;5365}53665367const WindowData &wd = windows[window_id];5368bool focused = wd.focused;53695370if (mouse_mode == MOUSE_MODE_CAPTURED) {5371if (xi.relative_motion.x == 0 && xi.relative_motion.y == 0) {5372break;5373}53745375Point2i new_center = pos;5376pos = last_mouse_pos + xi.relative_motion;5377center = new_center;5378do_mouse_warp = focused; // warp the cursor if we're focused in5379}53805381if (!last_mouse_pos_valid) {5382last_mouse_pos = pos;5383last_mouse_pos_valid = true;5384}53855386// Hackish but relative mouse motion is already handled in the RawMotion event.5387// RawMotion does not provide the absolute mouse position (whereas MotionNotify does).5388// Therefore, RawMotion cannot be the authority on absolute mouse position.5389// RawMotion provides more precision than MotionNotify, which doesn't sense subpixel motion.5390// Therefore, MotionNotify cannot be the authority on relative mouse motion.5391// This means we need to take a combined approach...5392Point2i rel;53935394// Only use raw input if in capture mode. Otherwise use the classic behavior.5395if (mouse_mode == MOUSE_MODE_CAPTURED) {5396rel = xi.relative_motion;5397} else {5398rel = pos - last_mouse_pos;5399}54005401// Reset to prevent lingering motion5402xi.relative_motion.x = 0;5403xi.relative_motion.y = 0;5404if (mouse_mode == MOUSE_MODE_CAPTURED) {5405pos = Point2i(windows[focused_window_id].size.width / 2, windows[focused_window_id].size.height / 2);5406}54075408BitField<MouseButtonMask> last_button_state = MouseButtonMask::NONE;5409if (event.xmotion.state & Button1Mask) {5410last_button_state.set_flag(MouseButtonMask::LEFT);5411}5412if (event.xmotion.state & Button2Mask) {5413last_button_state.set_flag(MouseButtonMask::MIDDLE);5414}5415if (event.xmotion.state & Button3Mask) {5416last_button_state.set_flag(MouseButtonMask::RIGHT);5417}5418if (event.xmotion.state & Button4Mask) {5419last_button_state.set_flag(MouseButtonMask::MB_XBUTTON1);5420}5421if (event.xmotion.state & Button5Mask) {5422last_button_state.set_flag(MouseButtonMask::MB_XBUTTON2);5423}54245425Ref<InputEventMouseMotion> mm;5426mm.instantiate();54275428mm->set_window_id(window_id);5429if (xi.pressure_supported) {5430mm->set_pressure(xi.pressure);5431} else {5432mm->set_pressure(bool(last_button_state.has_flag(MouseButtonMask::LEFT)) ? 1.0f : 0.0f);5433}5434mm->set_tilt(xi.tilt);5435mm->set_pen_inverted(xi.pen_inverted);54365437_get_key_modifier_state(event.xmotion.state, mm);5438mm->set_button_mask(last_button_state);5439mm->set_position(pos);5440mm->set_global_position(pos);5441mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity());5442mm->set_screen_velocity(mm->get_velocity());54435444mm->set_relative(rel);5445mm->set_relative_screen_position(rel);54465447last_mouse_pos = pos;54485449// printf("rel: %d,%d\n", rel.x, rel.y );5450// Don't propagate the motion event unless we have focus5451// this is so that the relative motion doesn't get messed up5452// after we regain focus.5453// Adjusted to parse the input event if the window is not focused allowing mouse hovering on the editor5454// the embedding process has focus.5455if (!focused) {5456// Propagate the event to the focused window,5457// because it's received only on the topmost window.5458// Note: This is needed for drag & drop to work between windows,5459// because the engine expects events to keep being processed5460// on the same window dragging started.5461for (const KeyValue<WindowID, WindowData> &E : windows) {5462const WindowData &wd_other = E.value;5463if (wd_other.focused) {5464int x, y;5465Window child;5466XTranslateCoordinates(x11_display, wd.x11_window, wd_other.x11_window, event.xmotion.x, event.xmotion.y, &x, &y, &child);54675468Point2i pos_focused(x, y);54695470mm->set_window_id(E.key);5471mm->set_position(pos_focused);5472mm->set_global_position(pos_focused);5473mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity());54745475break;5476}5477}5478}54795480Input::get_singleton()->parse_input_event(mm);54815482} break;5483case KeyPress:5484case KeyRelease: {5485if (ignore_events) {5486break;5487}5488#ifdef DISPLAY_SERVER_X11_DEBUG_LOGS_ENABLED5489if (event.type == KeyPress) {5490DEBUG_LOG_X11("[%u] KeyPress window=%lu (%u), keycode=%u, time=%lu \n", frame, event.xkey.window, window_id, event.xkey.keycode, event.xkey.time);5491} else {5492DEBUG_LOG_X11("[%u] KeyRelease window=%lu (%u), keycode=%u, time=%lu \n", frame, event.xkey.window, window_id, event.xkey.keycode, event.xkey.time);5493}5494#endif5495last_timestamp = event.xkey.time;54965497// key event is a little complex, so5498// it will be handled in its own function.5499_handle_key_event(window_id, &event.xkey, events, event_index);5500} break;55015502case SelectionNotify:5503if (ime_window_event) {5504break;5505}5506if (event.xselection.target == requested) {5507Property p = _read_property(x11_display, windows[window_id].x11_window, XInternAtom(x11_display, "PRIMARY", 0));55085509Vector<String> files = String((char *)p.data).split("\r\n", false);5510XFree(p.data);5511for (int i = 0; i < files.size(); i++) {5512files.write[i] = files[i].replace("file://", "").uri_file_decode();5513}55145515if (windows[window_id].drop_files_callback.is_valid()) {5516Variant v_files = files;5517const Variant *v_args[1] = { &v_files };5518Variant ret;5519Callable::CallError ce;5520windows[window_id].drop_files_callback.callp((const Variant **)&v_args, 1, ret, ce);5521if (ce.error != Callable::CallError::CALL_OK) {5522ERR_PRINT(vformat("Failed to execute drop files callback: %s.", Variant::get_callable_error_text(windows[window_id].drop_files_callback, v_args, 1, ce)));5523}5524}55255526//Reply that all is well.5527XClientMessageEvent m;5528memset(&m, 0, sizeof(m));5529m.type = ClientMessage;5530m.display = x11_display;5531m.window = xdnd_source_window;5532m.message_type = xdnd_finished;5533m.format = 32;5534m.data.l[0] = windows[window_id].x11_window;5535m.data.l[1] = 1;5536m.data.l[2] = xdnd_action_copy; //We only ever copy.55375538XSendEvent(x11_display, xdnd_source_window, False, NoEventMask, (XEvent *)&m);5539}5540break;55415542case ClientMessage:5543if (ime_window_event) {5544break;5545}5546if ((unsigned int)event.xclient.data.l[0] == (unsigned int)wm_delete) {5547_send_window_event(windows[window_id], WINDOW_EVENT_CLOSE_REQUEST);5548}55495550else if ((unsigned int)event.xclient.message_type == (unsigned int)xdnd_enter) {5551//File(s) have been dragged over the window, check for supported target (text/uri-list)5552xdnd_version = (event.xclient.data.l[1] >> 24);5553Window source = event.xclient.data.l[0];5554bool more_than_3 = event.xclient.data.l[1] & 1;5555if (more_than_3) {5556Property p = _read_property(x11_display, source, XInternAtom(x11_display, "XdndTypeList", False));5557requested = pick_target_from_list(x11_display, (Atom *)p.data, p.nitems);5558XFree(p.data);5559} else {5560requested = pick_target_from_atoms(x11_display, event.xclient.data.l[2], event.xclient.data.l[3], event.xclient.data.l[4]);5561}5562} else if ((unsigned int)event.xclient.message_type == (unsigned int)xdnd_position) {5563//xdnd position event, reply with an XDND status message5564//just depending on type of data for now5565XClientMessageEvent m;5566memset(&m, 0, sizeof(m));5567m.type = ClientMessage;5568m.display = event.xclient.display;5569m.window = event.xclient.data.l[0];5570m.message_type = xdnd_status;5571m.format = 32;5572m.data.l[0] = windows[window_id].x11_window;5573m.data.l[1] = (requested != None);5574m.data.l[2] = 0; //empty rectangle5575m.data.l[3] = 0;5576m.data.l[4] = xdnd_action_copy;55775578XSendEvent(x11_display, event.xclient.data.l[0], False, NoEventMask, (XEvent *)&m);5579XFlush(x11_display);5580} else if ((unsigned int)event.xclient.message_type == (unsigned int)xdnd_drop) {5581if (requested != None) {5582xdnd_source_window = event.xclient.data.l[0];5583if (xdnd_version >= 1) {5584XConvertSelection(x11_display, xdnd_selection, requested, XInternAtom(x11_display, "PRIMARY", 0), windows[window_id].x11_window, event.xclient.data.l[2]);5585} else {5586XConvertSelection(x11_display, xdnd_selection, requested, XInternAtom(x11_display, "PRIMARY", 0), windows[window_id].x11_window, CurrentTime);5587}5588} else {5589//Reply that we're not interested.5590XClientMessageEvent m;5591memset(&m, 0, sizeof(m));5592m.type = ClientMessage;5593m.display = event.xclient.display;5594m.window = event.xclient.data.l[0];5595m.message_type = xdnd_finished;5596m.format = 32;5597m.data.l[0] = windows[window_id].x11_window;5598m.data.l[1] = 0;5599m.data.l[2] = None; //Failed.5600XSendEvent(x11_display, event.xclient.data.l[0], False, NoEventMask, (XEvent *)&m);5601}5602}5603break;5604default:5605break;5606}5607}56085609XFlush(x11_display);56105611if (do_mouse_warp) {5612XWarpPointer(x11_display, None, windows[MAIN_WINDOW_ID].x11_window,56130, 0, 0, 0, (int)windows[MAIN_WINDOW_ID].size.width / 2, (int)windows[MAIN_WINDOW_ID].size.height / 2);56145615/*5616Window root, child;5617int root_x, root_y;5618int win_x, win_y;5619unsigned int mask;5620XQueryPointer( x11_display, x11_window, &root, &child, &root_x, &root_y, &win_x, &win_y, &mask );56215622printf("Root: %d,%d\n", root_x, root_y);5623printf("Win: %d,%d\n", win_x, win_y);5624*/5625}56265627#ifdef DBUS_ENABLED5628if (portal_desktop) {5629portal_desktop->process_callbacks();5630}5631#endif56325633_THREAD_SAFE_UNLOCK_56345635Input::get_singleton()->flush_buffered_events();5636}56375638void DisplayServerX11::release_rendering_thread() {5639#if defined(GLES3_ENABLED)5640if (gl_manager) {5641gl_manager->release_current();5642}5643if (gl_manager_egl) {5644gl_manager_egl->release_current();5645}5646#endif5647}56485649void DisplayServerX11::swap_buffers() {5650#if defined(GLES3_ENABLED)5651if (gl_manager) {5652gl_manager->swap_buffers();5653}5654if (gl_manager_egl) {5655gl_manager_egl->swap_buffers();5656}5657#endif5658}56595660void DisplayServerX11::_update_context(WindowData &wd) {5661XClassHint *classHint = XAllocClassHint();56625663if (classHint) {5664CharString name_str;5665switch (context) {5666case CONTEXT_EDITOR:5667name_str = "Godot_Editor";5668break;5669case CONTEXT_PROJECTMAN:5670name_str = "Godot_ProjectList";5671break;5672case CONTEXT_ENGINE:5673name_str = "Godot_Engine";5674break;5675}56765677CharString class_str;5678if (context == CONTEXT_ENGINE) {5679String config_name = GLOBAL_GET("application/config/name");5680if (config_name.length() == 0) {5681class_str = "Godot_Engine";5682} else {5683class_str = config_name.utf8();5684}5685} else {5686class_str = "Godot";5687}56885689classHint->res_class = class_str.ptrw();5690classHint->res_name = name_str.ptrw();56915692XSetClassHint(x11_display, wd.x11_window, classHint);5693XFree(classHint);5694}5695}56965697void DisplayServerX11::set_context(Context p_context) {5698_THREAD_SAFE_METHOD_56995700context = p_context;57015702for (KeyValue<WindowID, WindowData> &E : windows) {5703_update_context(E.value);5704}5705}57065707bool DisplayServerX11::is_window_transparency_available() const {5708CharString net_wm_cm_name = vformat("_NET_WM_CM_S%d", XDefaultScreen(x11_display)).ascii();5709Atom net_wm_cm = XInternAtom(x11_display, net_wm_cm_name.get_data(), False);5710if (net_wm_cm == None) {5711return false;5712}5713if (XGetSelectionOwner(x11_display, net_wm_cm) == None) {5714return false;5715}5716#if defined(RD_ENABLED)5717if (rendering_device && !rendering_device->is_composite_alpha_supported()) {5718return false;5719}5720#endif5721return OS::get_singleton()->is_layered_allowed();5722}57235724void DisplayServerX11::set_native_icon(const String &p_filename) {5725WARN_PRINT("Native icon not supported by this display server.");5726}57275728bool g_set_icon_error = false;5729int set_icon_errorhandler(Display *dpy, XErrorEvent *ev) {5730g_set_icon_error = true;5731return 0;5732}57335734void DisplayServerX11::set_icon(const Ref<Image> &p_icon) {5735_THREAD_SAFE_METHOD_57365737WindowData &wd = windows[MAIN_WINDOW_ID];57385739int (*oldHandler)(Display *, XErrorEvent *) = XSetErrorHandler(&set_icon_errorhandler);57405741Atom net_wm_icon = XInternAtom(x11_display, "_NET_WM_ICON", False);57425743if (p_icon.is_valid()) {5744ERR_FAIL_COND(p_icon->get_width() <= 0 || p_icon->get_height() <= 0);57455746Ref<Image> img = p_icon->duplicate();5747img->convert(Image::FORMAT_RGBA8);57485749while (true) {5750int w = img->get_width();5751int h = img->get_height();57525753if (g_set_icon_error) {5754g_set_icon_error = false;57555756WARN_PRINT(vformat("Icon too large (%dx%d), attempting to downscale icon.", w, h));57575758int new_width, new_height;5759if (w > h) {5760new_width = w / 2;5761new_height = h * new_width / w;5762} else {5763new_height = h / 2;5764new_width = w * new_height / h;5765}57665767w = new_width;5768h = new_height;57695770if (!w || !h) {5771WARN_PRINT("Unable to set icon.");5772break;5773}57745775img->resize(w, h, Image::INTERPOLATE_CUBIC);5776}57775778// We're using long to have wordsize (32Bit build -> 32 Bits, 64 Bit build -> 64 Bits5779Vector<long> pd;57805781pd.resize(2 + w * h);57825783pd.write[0] = w;5784pd.write[1] = h;57855786const uint8_t *r = img->get_data().ptr();57875788long *wr = &pd.write[2];5789uint8_t const *pr = r;57905791for (int i = 0; i < w * h; i++) {5792long v = 0;5793// A R G B5794v |= pr[3] << 24 | pr[0] << 16 | pr[1] << 8 | pr[2];5795*wr++ = v;5796pr += 4;5797}57985799if (net_wm_icon != None) {5800XChangeProperty(x11_display, wd.x11_window, net_wm_icon, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)pd.ptr(), pd.size());5801}58025803if (!g_set_icon_error) {5804break;5805}5806}5807} else {5808XDeleteProperty(x11_display, wd.x11_window, net_wm_icon);5809}58105811XFlush(x11_display);5812XSetErrorHandler(oldHandler);5813}58145815void DisplayServerX11::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window) {5816_THREAD_SAFE_METHOD_5817#if defined(RD_ENABLED)5818if (rendering_context) {5819rendering_context->window_set_vsync_mode(p_window, p_vsync_mode);5820}5821#endif58225823#if defined(GLES3_ENABLED)5824if (gl_manager) {5825gl_manager->set_use_vsync(p_vsync_mode != DisplayServer::VSYNC_DISABLED);5826}5827if (gl_manager_egl) {5828gl_manager_egl->set_use_vsync(p_vsync_mode != DisplayServer::VSYNC_DISABLED);5829}5830#endif5831}58325833DisplayServer::VSyncMode DisplayServerX11::window_get_vsync_mode(WindowID p_window) const {5834_THREAD_SAFE_METHOD_5835#if defined(RD_ENABLED)5836if (rendering_context) {5837return rendering_context->window_get_vsync_mode(p_window);5838}5839#endif5840#if defined(GLES3_ENABLED)5841if (gl_manager) {5842return gl_manager->is_using_vsync() ? DisplayServer::VSYNC_ENABLED : DisplayServer::VSYNC_DISABLED;5843}5844if (gl_manager_egl) {5845return gl_manager_egl->is_using_vsync() ? DisplayServer::VSYNC_ENABLED : DisplayServer::VSYNC_DISABLED;5846}5847#endif5848return DisplayServer::VSYNC_ENABLED;5849}58505851void DisplayServerX11::window_start_drag(WindowID p_window) {5852_THREAD_SAFE_METHOD_58535854ERR_FAIL_COND(!windows.has(p_window));5855WindowData &wd = windows[p_window];58565857if (wd.embed_parent) {5858return; // Embedded window.5859}58605861XClientMessageEvent m;5862memset(&m, 0, sizeof(m));58635864XUngrabPointer(x11_display, CurrentTime);58655866Window root_return, child_return;5867int root_x, root_y, win_x, win_y;5868unsigned int mask_return;58695870Bool xquerypointer_result = XQueryPointer(x11_display, wd.x11_window, &root_return, &child_return, &root_x, &root_y, &win_x, &win_y, &mask_return);58715872m.type = ClientMessage;5873m.window = wd.x11_window;5874m.message_type = XInternAtom(x11_display, "_NET_WM_MOVERESIZE", True);5875m.format = 32;5876if (xquerypointer_result) {5877m.data.l[0] = root_x;5878m.data.l[1] = root_y;5879m.data.l[3] = Button1;5880}5881m.data.l[2] = _NET_WM_MOVERESIZE_MOVE;5882m.data.l[4] = 1; // Source - normal application.58835884XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent *)&m);58855886XSync(x11_display, 0);5887}58885889void DisplayServerX11::window_start_resize(WindowResizeEdge p_edge, WindowID p_window) {5890_THREAD_SAFE_METHOD_58915892ERR_FAIL_INDEX(int(p_edge), WINDOW_EDGE_MAX);58935894ERR_FAIL_COND(!windows.has(p_window));5895WindowData &wd = windows[p_window];58965897if (wd.embed_parent) {5898return; // Embedded window.5899}59005901XClientMessageEvent m;5902memset(&m, 0, sizeof(m));59035904XUngrabPointer(x11_display, CurrentTime);59055906Window root_return, child_return;5907int root_x, root_y, win_x, win_y;5908unsigned int mask_return;59095910Bool xquerypointer_result = XQueryPointer(x11_display, wd.x11_window, &root_return, &child_return, &root_x, &root_y, &win_x, &win_y, &mask_return);59115912m.type = ClientMessage;5913m.window = wd.x11_window;5914m.message_type = XInternAtom(x11_display, "_NET_WM_MOVERESIZE", True);5915m.format = 32;5916if (xquerypointer_result) {5917m.data.l[0] = root_x;5918m.data.l[1] = root_y;5919m.data.l[3] = Button1;5920}59215922switch (p_edge) {5923case DisplayServer::WINDOW_EDGE_TOP_LEFT: {5924m.data.l[2] = _NET_WM_MOVERESIZE_SIZE_TOPLEFT;5925} break;5926case DisplayServer::WINDOW_EDGE_TOP: {5927m.data.l[2] = _NET_WM_MOVERESIZE_SIZE_TOP;5928} break;5929case DisplayServer::WINDOW_EDGE_TOP_RIGHT: {5930m.data.l[2] = _NET_WM_MOVERESIZE_SIZE_TOPRIGHT;5931} break;5932case DisplayServer::WINDOW_EDGE_LEFT: {5933m.data.l[2] = _NET_WM_MOVERESIZE_SIZE_LEFT;5934} break;5935case DisplayServer::WINDOW_EDGE_RIGHT: {5936m.data.l[2] = _NET_WM_MOVERESIZE_SIZE_RIGHT;5937} break;5938case DisplayServer::WINDOW_EDGE_BOTTOM_LEFT: {5939m.data.l[2] = _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT;5940} break;5941case DisplayServer::WINDOW_EDGE_BOTTOM: {5942m.data.l[2] = _NET_WM_MOVERESIZE_SIZE_BOTTOM;5943} break;5944case DisplayServer::WINDOW_EDGE_BOTTOM_RIGHT: {5945m.data.l[2] = _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT;5946} break;5947default:5948break;5949}5950m.data.l[4] = 1; // Source - normal application.59515952XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent *)&m);59535954XSync(x11_display, 0);5955}59565957pid_t get_window_pid(Display *p_display, Window p_window) {5958Atom atom = XInternAtom(p_display, "_NET_WM_PID", False);5959Atom actualType;5960int actualFormat;5961unsigned long nItems, bytesAfter;5962unsigned char *prop = nullptr;5963if (XGetWindowProperty(p_display, p_window, atom, 0, sizeof(pid_t), False, AnyPropertyType,5964&actualType, &actualFormat, &nItems, &bytesAfter, &prop) == Success) {5965if (nItems > 0) {5966pid_t pid = *(pid_t *)prop;5967XFree(prop);5968return pid;5969}5970}59715972return 0; // PID not found.5973}59745975Window find_window_from_process_id_internal(Display *p_display, pid_t p_process_id, Window p_window) {5976Window dummy;5977Window *children;5978unsigned int num_children;59795980if (!XQueryTree(p_display, p_window, &dummy, &dummy, &children, &num_children)) {5981return 0;5982}59835984for (unsigned int i = 0; i < num_children; i++) {5985const Window child = children[i];5986if (get_window_pid(p_display, child) == p_process_id) {5987XFree(children);5988return child;5989}5990}59915992// Then check children of children.5993for (unsigned int i = 0; i < num_children; i++) {5994Window wnd = find_window_from_process_id_internal(p_display, p_process_id, children[i]);5995if (wnd != 0) {5996XFree(children);5997return wnd;5998}5999}60006001if (children) {6002XFree(children);6003}60046005return 0;6006}60076008Window find_window_from_process_id(Display *p_display, pid_t p_process_id) {6009// Handle bad window errors silently because while looping6010// windows can be destroyed, resulting in BadWindow errors.6011int (*oldHandler)(Display *, XErrorEvent *) = XSetErrorHandler(&bad_window_error_handler);60126013const int screencount = XScreenCount(p_display);6014Window process_window = 0;60156016for (int screen_index = 0; screen_index < screencount; screen_index++) {6017Window root = RootWindow(p_display, screen_index);60186019Window wnd = find_window_from_process_id_internal(p_display, p_process_id, root);60206021if (wnd != 0) {6022process_window = wnd;6023break;6024}6025}60266027// Restore default error handler.6028XSetErrorHandler(oldHandler);60296030return process_window;6031}60326033Point2i DisplayServerX11::_get_window_position(Window p_window) const {6034int x = 0, y = 0;6035Window child;6036XTranslateCoordinates(x11_display, p_window, DefaultRootWindow(x11_display), 0, 0, &x, &y, &child);6037return Point2i(x, y);6038}60396040Rect2i DisplayServerX11::_get_window_rect(Window p_window) const {6041XWindowAttributes xwa;6042XGetWindowAttributes(x11_display, p_window, &xwa);6043return Rect2i(xwa.x, xwa.y, xwa.width, xwa.height);6044}60456046void DisplayServerX11::_set_window_taskbar_pager_enabled(Window p_window, bool p_enabled) {6047Atom wmState = XInternAtom(x11_display, "_NET_WM_STATE", False);6048Atom skipTaskbar = XInternAtom(x11_display, "_NET_WM_STATE_SKIP_TASKBAR", False);6049Atom skipPager = XInternAtom(x11_display, "_NET_WM_STATE_SKIP_PAGER", False);60506051XClientMessageEvent xev;6052memset(&xev, 0, sizeof(xev));6053xev.type = ClientMessage;6054xev.window = p_window;6055xev.message_type = wmState;6056xev.format = 32;6057xev.data.l[0] = p_enabled ? _NET_WM_STATE_REMOVE : _NET_WM_STATE_ADD; // When enabled, we must remove the skip.6058xev.data.l[1] = skipTaskbar;6059xev.data.l[2] = skipPager;6060xev.data.l[3] = 0;6061xev.data.l[4] = 0;60626063// Send the client message to the root window.6064XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent *)&xev);6065}60666067Error DisplayServerX11::embed_process(WindowID p_window, OS::ProcessID p_pid, const Rect2i &p_rect, bool p_visible, bool p_grab_focus) {6068_THREAD_SAFE_METHOD_60696070ERR_FAIL_COND_V(!windows.has(p_window), FAILED);60716072const WindowData &wd = windows[p_window];60736074DEBUG_LOG_X11("Starting embedding %ld to window %lu \n", p_pid, wd.x11_window);60756076EmbeddedProcessData *ep = nullptr;6077if (embedded_processes.has(p_pid)) {6078ep = embedded_processes.get(p_pid);6079} else {6080// New process, trying to find the window.6081Window process_window = find_window_from_process_id(x11_display, p_pid);6082if (!process_window) {6083return ERR_DOES_NOT_EXIST;6084}6085DEBUG_LOG_X11("Process %ld window found: %lu \n", p_pid, process_window);6086ep = memnew(EmbeddedProcessData);6087ep->process_window = process_window;6088ep->visible = true;6089XSetTransientForHint(x11_display, process_window, wd.x11_window);6090_set_window_taskbar_pager_enabled(process_window, false);6091embedded_processes.insert(p_pid, ep);6092}60936094// Handle bad window errors silently because just in case the embedded window was closed.6095int (*oldHandler)(Display *, XErrorEvent *) = XSetErrorHandler(&bad_window_error_handler);60966097if (p_visible) {6098// Resize and move the window to match the desired rectangle.6099// X11 does not allow moving the window entirely outside the screen boundaries.6100// To ensure the window remains visible, we will resize it to fit within both the screen and the specified rectangle.6101Rect2i desired_rect = p_rect;61026103// First resize the desired rect to fit inside all the screens without considering the6104// working area.6105Rect2i screens_full_rect = _screens_get_full_rect();6106Vector2i screens_full_end = screens_full_rect.get_end();6107if (desired_rect.position.x < screens_full_rect.position.x) {6108desired_rect.size.x = MAX(desired_rect.size.x - (screens_full_rect.position.x - desired_rect.position.x), 0);6109desired_rect.position.x = screens_full_rect.position.x;6110}6111if (desired_rect.position.x + desired_rect.size.x > screens_full_end.x) {6112desired_rect.size.x = MAX(screens_full_end.x - desired_rect.position.x, 0);6113}6114if (desired_rect.position.y < screens_full_rect.position.y) {6115desired_rect.size.y = MAX(desired_rect.size.y - (screens_full_rect.position.y - desired_rect.position.y), 0);6116desired_rect.position.y = screens_full_rect.position.y;6117}6118if (desired_rect.position.y + desired_rect.size.y > screens_full_end.y) {6119desired_rect.size.y = MAX(screens_full_end.y - desired_rect.position.y, 0);6120}61216122// Second, for each screen, check if the desired rectangle is within a portion of the screen6123// that is outside the working area. Each screen can have a different working area6124// depending on top, bottom, or side panels.6125int desired_area = desired_rect.get_area();6126int count = get_screen_count();6127for (int i = 0; i < count; i++) {6128Rect2i screen_rect = _screen_get_rect(i);6129if (screen_rect.intersection(desired_rect).get_area() == 0) {6130continue;6131}61326133// The desired rect is inside this screen.6134Rect2i screen_usable_rect = screen_get_usable_rect(i);6135int screen_usable_area = screen_usable_rect.intersection(desired_rect).get_area();6136if (screen_usable_area == desired_area) {6137// The desired rect is fulling inside the usable rect of the screen. No need to resize.6138continue;6139}61406141if (desired_rect.position.x >= screen_rect.position.x && desired_rect.position.x < screen_usable_rect.position.x) {6142int offset = screen_usable_rect.position.x - desired_rect.position.x;6143desired_rect.size.x = MAX(desired_rect.size.x - offset, 0);6144desired_rect.position.x += offset;6145}6146if (desired_rect.position.y >= screen_rect.position.y && desired_rect.position.y < screen_usable_rect.position.y) {6147int offset = screen_usable_rect.position.y - desired_rect.position.y;6148desired_rect.size.y = MAX(desired_rect.size.y - offset, 0);6149desired_rect.position.y += offset;6150}61516152Vector2i desired_end = desired_rect.get_end();6153Vector2i screen_end = screen_rect.get_end();6154Vector2i screen_usable_end = screen_usable_rect.get_end();6155if (desired_end.x > screen_usable_end.x && desired_end.x <= screen_end.x) {6156desired_rect.size.x = MAX(desired_rect.size.x - (desired_end.x - screen_usable_end.x), 0);6157}6158if (desired_end.y > screen_usable_end.y && desired_end.y <= screen_end.y) {6159desired_rect.size.y = MAX(desired_rect.size.y - (desired_end.y - screen_usable_end.y), 0);6160}6161}61626163if (desired_rect.size.x <= 100 || desired_rect.size.y <= 100) {6164p_visible = false;6165}61666167if (p_visible) {6168Rect2i current_process_window_rect = _get_window_rect(ep->process_window);6169if (current_process_window_rect != desired_rect) {6170DEBUG_LOG_X11("Embedding XMoveResizeWindow process %ld, window %lu to %d, %d, %d, %d \n", p_pid, wd.x11_window, desired_rect.position.x, desired_rect.position.y, desired_rect.size.x, desired_rect.size.y);6171XMoveResizeWindow(x11_display, ep->process_window, desired_rect.position.x, desired_rect.position.y, desired_rect.size.x, desired_rect.size.y);6172}6173}6174}61756176if (ep->visible != p_visible) {6177if (p_visible) {6178XMapWindow(x11_display, ep->process_window);6179} else {6180XUnmapWindow(x11_display, ep->process_window);6181}6182ep->visible = p_visible;6183}61846185if (p_grab_focus && p_visible) {6186Window focused_window = 0;6187int revert_to = 0;6188XGetInputFocus(x11_display, &focused_window, &revert_to);6189if (focused_window != ep->process_window) {6190// Be sure that the window is visible to prevent BadMatch error when calling XSetInputFocus on a not viewable window.6191XWindowAttributes attr;6192if (XGetWindowAttributes(x11_display, ep->process_window, &attr) && attr.map_state == IsViewable) {6193XSetInputFocus(x11_display, ep->process_window, RevertToParent, CurrentTime);6194}6195}6196}61976198// Restore default error handler.6199XSetErrorHandler(oldHandler);6200return OK;6201}62026203Error DisplayServerX11::request_close_embedded_process(OS::ProcessID p_pid) {6204_THREAD_SAFE_METHOD_62056206if (!embedded_processes.has(p_pid)) {6207return ERR_DOES_NOT_EXIST;6208}62096210EmbeddedProcessData *ep = embedded_processes.get(p_pid);62116212// Handle bad window errors silently because just in case the embedded window was closed.6213int (*oldHandler)(Display *, XErrorEvent *) = XSetErrorHandler(&bad_window_error_handler);62146215// Check if the window is still valid.6216XWindowAttributes attr;6217if (XGetWindowAttributes(x11_display, ep->process_window, &attr)) {6218// Send the message to gracefully close the window.6219XEvent ev;6220memset(&ev, 0, sizeof(ev));6221ev.xclient.type = ClientMessage;6222ev.xclient.window = ep->process_window;6223ev.xclient.message_type = XInternAtom(x11_display, "WM_PROTOCOLS", True);6224ev.xclient.format = 32;6225ev.xclient.data.l[0] = XInternAtom(x11_display, "WM_DELETE_WINDOW", False);6226ev.xclient.data.l[1] = CurrentTime;6227XSendEvent(x11_display, ep->process_window, False, NoEventMask, &ev);6228}62296230// Restore default error handler.6231XSetErrorHandler(oldHandler);62326233return OK;6234}62356236Error DisplayServerX11::remove_embedded_process(OS::ProcessID p_pid) {6237_THREAD_SAFE_METHOD_62386239if (!embedded_processes.has(p_pid)) {6240return ERR_DOES_NOT_EXIST;6241}62426243EmbeddedProcessData *ep = embedded_processes.get(p_pid);62446245request_close_embedded_process(p_pid);62466247embedded_processes.erase(p_pid);6248memdelete(ep);62496250return OK;6251}62526253OS::ProcessID DisplayServerX11::get_focused_process_id() {6254Window focused_window = 0;6255int revert_to = 0;62566257XGetInputFocus(x11_display, &focused_window, &revert_to);62586259if (focused_window == None) {6260return 0;6261}62626263return get_window_pid(x11_display, focused_window);6264}62656266Vector<String> DisplayServerX11::get_rendering_drivers_func() {6267Vector<String> drivers;62686269#ifdef VULKAN_ENABLED6270drivers.push_back("vulkan");6271#endif6272#ifdef GLES3_ENABLED6273drivers.push_back("opengl3");6274drivers.push_back("opengl3_es");6275#endif6276drivers.push_back("dummy");62776278return drivers;6279}62806281DisplayServer *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, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error) {6282DisplayServer *ds = memnew(DisplayServerX11(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_position, p_resolution, p_screen, p_context, p_parent_window, r_error));6283return ds;6284}62856286DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect, Window p_parent_window) {6287//Create window62886289XVisualInfo visualInfo;6290bool vi_selected = false;62916292#ifdef GLES3_ENABLED6293if (gl_manager) {6294Error err;6295visualInfo = gl_manager->get_vi(x11_display, err);6296ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't acquire visual info from display.");6297vi_selected = true;6298}6299if (gl_manager_egl) {6300XVisualInfo visual_info_template;6301int visual_id = gl_manager_egl->display_get_native_visual_id(x11_display);6302ERR_FAIL_COND_V_MSG(visual_id < 0, INVALID_WINDOW_ID, "Unable to get a visual id.");63036304visual_info_template.visualid = (VisualID)visual_id;63056306int number_of_visuals = 0;6307XVisualInfo *vi_list = XGetVisualInfo(x11_display, VisualIDMask, &visual_info_template, &number_of_visuals);6308ERR_FAIL_COND_V(number_of_visuals <= 0, INVALID_WINDOW_ID);63096310visualInfo = vi_list[0];63116312XFree(vi_list);6313}6314#endif63156316if (!vi_selected) {6317long visualMask = VisualScreenMask;6318int numberOfVisuals;6319XVisualInfo vInfoTemplate = {};6320vInfoTemplate.screen = DefaultScreen(x11_display);6321XVisualInfo *vi_list = XGetVisualInfo(x11_display, visualMask, &vInfoTemplate, &numberOfVisuals);6322ERR_FAIL_NULL_V(vi_list, INVALID_WINDOW_ID);63236324visualInfo = vi_list[0];6325if (OS::get_singleton()->is_layered_allowed()) {6326for (int i = 0; i < numberOfVisuals; i++) {6327XRenderPictFormat *pict_format = XRenderFindVisualFormat(x11_display, vi_list[i].visual);6328if (!pict_format) {6329continue;6330}6331visualInfo = vi_list[i];6332if (pict_format->direct.alphaMask > 0) {6333break;6334}6335}6336}6337XFree(vi_list);6338}63396340Colormap colormap = XCreateColormap(x11_display, RootWindow(x11_display, visualInfo.screen), visualInfo.visual, AllocNone);63416342XSetWindowAttributes windowAttributes = {};6343windowAttributes.colormap = colormap;6344windowAttributes.background_pixel = 0xFFFFFFFF;6345windowAttributes.border_pixel = 0;6346windowAttributes.event_mask = KeyPressMask | KeyReleaseMask | StructureNotifyMask | ExposureMask;63476348unsigned long valuemask = CWBorderPixel | CWColormap | CWEventMask;63496350if (OS::get_singleton()->is_layered_allowed()) {6351windowAttributes.background_pixmap = None;6352windowAttributes.background_pixel = 0;6353windowAttributes.border_pixmap = None;6354valuemask |= CWBackPixel;6355}63566357WindowID id = window_id_counter++;6358WindowData &wd = windows[id];63596360if (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) {6361wd.no_focus = true;6362}63636364if (p_flags & WINDOW_FLAG_POPUP_BIT) {6365wd.is_popup = true;6366}63676368// Setup for menu subwindows:6369// - override_redirect forces the WM not to interfere with the window, to avoid delays due to6370// handling decorations and placement.6371// On the other hand, focus changes need to be handled manually when this is set.6372// - save_under is a hint for the WM to keep the content of windows behind to avoid repaint.6373if (wd.no_focus) {6374windowAttributes.override_redirect = True;6375windowAttributes.save_under = True;6376valuemask |= CWOverrideRedirect | CWSaveUnder;6377}63786379int rq_screen = get_screen_from_rect(p_rect);6380if (rq_screen < 0) {6381rq_screen = get_primary_screen(); // Requested window rect is outside any screen bounds.6382}63836384Rect2i win_rect = p_rect;6385if (!p_parent_window) {6386// No parent.6387if (p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) {6388Rect2i screen_rect = Rect2i(screen_get_position(rq_screen), screen_get_size(rq_screen));63896390win_rect = screen_rect;6391} else {6392Rect2i srect = screen_get_usable_rect(rq_screen);6393Point2i wpos = p_rect.position;6394wpos = wpos.clamp(srect.position, srect.position + srect.size - p_rect.size / 3);63956396win_rect.position = wpos;6397}6398}63996400// Position and size hints are set from these values before they are updated to the actual6401// window size, so we need to initialize them here.6402wd.position = win_rect.position;6403wd.size = win_rect.size;64046405{6406wd.x11_window = XCreateWindow(x11_display, RootWindow(x11_display, visualInfo.screen), win_rect.position.x, win_rect.position.y, win_rect.size.width > 0 ? win_rect.size.width : 1, win_rect.size.height > 0 ? win_rect.size.height : 1, 0, visualInfo.depth, InputOutput, visualInfo.visual, valuemask, &windowAttributes);64076408wd.parent = RootWindow(x11_display, visualInfo.screen);64096410DEBUG_LOG_X11("CreateWindow window=%lu, parent: %lu \n", wd.x11_window, wd.parent);64116412if (p_parent_window) {6413wd.embed_parent = p_parent_window;6414XSetTransientForHint(x11_display, wd.x11_window, p_parent_window);6415}64166417XSetWindowAttributes window_attributes_ime = {};6418window_attributes_ime.event_mask = KeyPressMask | KeyReleaseMask | StructureNotifyMask | ExposureMask;64196420wd.x11_xim_window = XCreateWindow(x11_display, wd.x11_window, 0, 0, 1, 1, 0, CopyFromParent, InputOnly, CopyFromParent, CWEventMask, &window_attributes_ime);6421#ifdef XKB_ENABLED6422if (dead_tbl && xkb_loaded_v05p) {6423wd.xkb_state = xkb_compose_state_new(dead_tbl, XKB_COMPOSE_STATE_NO_FLAGS);6424}6425#endif6426#ifdef ACCESSKIT_ENABLED6427if (accessibility_driver && !accessibility_driver->window_create(id, nullptr)) {6428if (OS::get_singleton()->is_stdout_verbose()) {6429ERR_PRINT("Can't create an accessibility adapter for window, accessibility support disabled!");6430}6431memdelete(accessibility_driver);6432accessibility_driver = nullptr;6433}6434#endif6435// Enable receiving notification when the window is initialized (MapNotify)6436// so the focus can be set at the right time.6437if (!wd.no_focus && !wd.is_popup) {6438XSelectInput(x11_display, wd.x11_window, StructureNotifyMask);6439}64406441//associate PID6442// make PID known to X116443{6444const long pid = OS::get_singleton()->get_process_id();6445Atom net_wm_pid = XInternAtom(x11_display, "_NET_WM_PID", False);6446if (net_wm_pid != None) {6447XChangeProperty(x11_display, wd.x11_window, net_wm_pid, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&pid, 1);6448}6449}64506451long im_event_mask = 0;64526453{6454XIEventMask all_event_mask;6455XSetWindowAttributes new_attr;64566457new_attr.event_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask |6458ButtonReleaseMask | EnterWindowMask |6459LeaveWindowMask | PointerMotionMask |6460Button1MotionMask |6461Button2MotionMask | Button3MotionMask |6462Button4MotionMask | Button5MotionMask |6463ButtonMotionMask | KeymapStateMask |6464ExposureMask | VisibilityChangeMask |6465StructureNotifyMask |6466SubstructureNotifyMask | SubstructureRedirectMask |6467FocusChangeMask | PropertyChangeMask |6468ColormapChangeMask | OwnerGrabButtonMask |6469im_event_mask;64706471XChangeWindowAttributes(x11_display, wd.x11_window, CWEventMask, &new_attr);64726473static unsigned char all_mask_data[XIMaskLen(XI_LASTEVENT)] = {};64746475all_event_mask.deviceid = XIAllDevices;6476all_event_mask.mask_len = sizeof(all_mask_data);6477all_event_mask.mask = all_mask_data;64786479XISetMask(all_event_mask.mask, XI_HierarchyChanged);64806481#ifdef TOUCH_ENABLED6482if (xi.touch_devices.size()) {6483XISetMask(all_event_mask.mask, XI_TouchBegin);6484XISetMask(all_event_mask.mask, XI_TouchUpdate);6485XISetMask(all_event_mask.mask, XI_TouchEnd);6486XISetMask(all_event_mask.mask, XI_TouchOwnership);6487}6488#endif64896490XISelectEvents(x11_display, wd.x11_window, &all_event_mask, 1);6491}64926493/* set the titlebar name */6494XStoreName(x11_display, wd.x11_window, "Godot");6495XSetWMProtocols(x11_display, wd.x11_window, &wm_delete, 1);6496if (xdnd_aware != None) {6497XChangeProperty(x11_display, wd.x11_window, xdnd_aware, XA_ATOM, 32, PropModeReplace, (unsigned char *)&xdnd_version, 1);6498}64996500if (xim && xim_style) {6501// Block events polling while changing input focus6502// because it triggers some event polling internally.6503MutexLock mutex_lock(events_mutex);65046505// Force on-the-spot for the over-the-spot style.6506if ((xim_style & XIMPreeditPosition) != 0) {6507xim_style &= ~XIMPreeditPosition;6508xim_style |= XIMPreeditCallbacks;6509}6510if ((xim_style & XIMPreeditCallbacks) != 0) {6511::XIMCallback preedit_start_callback;6512preedit_start_callback.client_data = (::XPointer)(this);6513preedit_start_callback.callback = (::XIMProc)(void *)(_xim_preedit_start_callback);65146515::XIMCallback preedit_done_callback;6516preedit_done_callback.client_data = (::XPointer)(this);6517preedit_done_callback.callback = (::XIMProc)(_xim_preedit_done_callback);65186519::XIMCallback preedit_draw_callback;6520preedit_draw_callback.client_data = (::XPointer)(this);6521preedit_draw_callback.callback = (::XIMProc)(_xim_preedit_draw_callback);65226523::XIMCallback preedit_caret_callback;6524preedit_caret_callback.client_data = (::XPointer)(this);6525preedit_caret_callback.callback = (::XIMProc)(_xim_preedit_caret_callback);65266527::XVaNestedList preedit_attributes = XVaCreateNestedList(0,6528XNPreeditStartCallback, &preedit_start_callback,6529XNPreeditDoneCallback, &preedit_done_callback,6530XNPreeditDrawCallback, &preedit_draw_callback,6531XNPreeditCaretCallback, &preedit_caret_callback,6532(char *)nullptr);65336534wd.xic = XCreateIC(xim,6535XNInputStyle, xim_style,6536XNClientWindow, wd.x11_xim_window,6537XNFocusWindow, wd.x11_xim_window,6538XNPreeditAttributes, preedit_attributes,6539(char *)nullptr);6540XFree(preedit_attributes);6541} else {6542wd.xic = XCreateIC(xim,6543XNInputStyle, xim_style,6544XNClientWindow, wd.x11_xim_window,6545XNFocusWindow, wd.x11_xim_window,6546(char *)nullptr);6547}65486549if (XGetICValues(wd.xic, XNFilterEvents, &im_event_mask, nullptr) != nullptr) {6550WARN_PRINT("XGetICValues couldn't obtain XNFilterEvents value");6551XDestroyIC(wd.xic);6552wd.xic = nullptr;6553}6554if (wd.xic) {6555XUnsetICFocus(wd.xic);6556} else {6557WARN_PRINT("XCreateIC couldn't create wd.xic");6558}6559} else {6560wd.xic = nullptr;6561WARN_PRINT("XCreateIC couldn't create wd.xic");6562}65636564_update_context(wd);65656566if (p_flags & WINDOW_FLAG_BORDERLESS_BIT) {6567Hints hints;6568Atom property;6569hints.flags = 2;6570hints.decorations = 0;6571property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True);6572if (property != None) {6573XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5);6574}6575}65766577if (wd.is_popup || wd.no_focus || (wd.embed_parent && !kde5_embed_workaround)) {6578// Set Utility type to disable fade animations.6579Atom type_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE_UTILITY", False);6580Atom wt_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE", False);6581if (wt_atom != None && type_atom != None) {6582XChangeProperty(x11_display, wd.x11_window, wt_atom, XA_ATOM, 32, PropModeReplace, (unsigned char *)&type_atom, 1);6583}6584} else {6585Atom type_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE_NORMAL", False);6586Atom wt_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE", False);65876588if (wt_atom != None && type_atom != None) {6589XChangeProperty(x11_display, wd.x11_window, wt_atom, XA_ATOM, 32, PropModeReplace, (unsigned char *)&type_atom, 1);6590}6591}65926593if (p_parent_window) {6594// Disable the window in the taskbar and alt-tab.6595_set_window_taskbar_pager_enabled(wd.x11_window, false);6596}65976598_update_size_hints(id);65996600#if defined(RD_ENABLED)6601if (rendering_context) {6602union {6603#ifdef VULKAN_ENABLED6604RenderingContextDriverVulkanX11::WindowPlatformData vulkan;6605#endif6606} wpd;6607#ifdef VULKAN_ENABLED6608if (rendering_driver == "vulkan") {6609wpd.vulkan.window = wd.x11_window;6610wpd.vulkan.display = x11_display;6611}6612#endif6613Error err = rendering_context->window_create(id, &wpd);6614ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, vformat("Can't create a %s window", rendering_driver));66156616rendering_context->window_set_size(id, win_rect.size.width, win_rect.size.height);6617rendering_context->window_set_vsync_mode(id, p_vsync_mode);6618}6619#endif6620#ifdef GLES3_ENABLED6621if (gl_manager) {6622Error err = gl_manager->window_create(id, wd.x11_window, x11_display, win_rect.size.width, win_rect.size.height);6623ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't create an OpenGL window");6624}6625if (gl_manager_egl) {6626Error err = gl_manager_egl->window_create(id, x11_display, &wd.x11_window, win_rect.size.width, win_rect.size.height);6627ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Failed to create an OpenGLES window.");6628}6629window_set_vsync_mode(p_vsync_mode, id);6630#endif66316632//set_class_hint(x11_display, wd.x11_window);6633XFlush(x11_display);66346635XSync(x11_display, False);6636//XSetErrorHandler(oldHandler);6637}66386639window_set_mode(p_mode, id);66406641//sync size6642{6643XWindowAttributes xwa;66446645XSync(x11_display, False);6646XGetWindowAttributes(x11_display, wd.x11_window, &xwa);66476648wd.position.x = xwa.x;6649wd.position.y = xwa.y;6650wd.size.width = xwa.width;6651wd.size.height = xwa.height;6652}66536654//set cursor6655if (cursors[current_cursor] != None) {6656XDefineCursor(x11_display, wd.x11_window, cursors[current_cursor]);6657}66586659return id;6660}66616662static bool _is_xim_style_supported(const ::XIMStyle &p_style) {6663const ::XIMStyle supported_preedit = XIMPreeditCallbacks | XIMPreeditPosition | XIMPreeditNothing | XIMPreeditNone;6664const ::XIMStyle supported_status = XIMStatusNothing | XIMStatusNone;66656666// Check preedit style is supported6667if ((p_style & supported_preedit) == 0) {6668return false;6669}66706671// Check status style is supported6672if ((p_style & supported_status) == 0) {6673return false;6674}66756676return true;6677}66786679static ::XIMStyle _get_best_xim_style(const ::XIMStyle &p_style_a, const ::XIMStyle &p_style_b) {6680if (p_style_a == 0) {6681return p_style_b;6682}6683if (p_style_b == 0) {6684return p_style_a;6685}66866687const ::XIMStyle preedit = XIMPreeditArea | XIMPreeditCallbacks | XIMPreeditPosition | XIMPreeditNothing | XIMPreeditNone;6688const ::XIMStyle status = XIMStatusArea | XIMStatusCallbacks | XIMStatusNothing | XIMStatusNone;66896690::XIMStyle a = p_style_a & preedit;6691::XIMStyle b = p_style_b & preedit;6692if (a != b) {6693// Compare preedit styles.6694if ((a | b) & XIMPreeditCallbacks) {6695return a == XIMPreeditCallbacks ? p_style_a : p_style_b;6696} else if ((a | b) & XIMPreeditPosition) {6697return a == XIMPreeditPosition ? p_style_a : p_style_b;6698} else if ((a | b) & XIMPreeditArea) {6699return a == XIMPreeditArea ? p_style_a : p_style_b;6700} else if ((a | b) & XIMPreeditNothing) {6701return a == XIMPreeditNothing ? p_style_a : p_style_b;6702}6703} else {6704// Preedit styles are the same, compare status styles.6705a = p_style_a & status;6706b = p_style_b & status;67076708if ((a | b) & XIMStatusCallbacks) {6709return a == XIMStatusCallbacks ? p_style_a : p_style_b;6710} else if ((a | b) & XIMStatusArea) {6711return a == XIMStatusArea ? p_style_a : p_style_b;6712} else if ((a | b) & XIMStatusNothing) {6713return a == XIMStatusNothing ? p_style_a : p_style_b;6714}6715}6716return p_style_a;6717}67186719DisplayServerX11::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, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error) {6720KeyMappingX11::initialize();67216722String current_desk = OS::get_singleton()->get_environment("XDG_CURRENT_DESKTOP").to_lower();6723String session_desk = OS::get_singleton()->get_environment("XDG_SESSION_DESKTOP").to_lower();6724swap_cancel_ok = (current_desk.contains("kde") || session_desk.contains("kde") || current_desk.contains("lxqt") || session_desk.contains("lxqt"));67256726xwayland = OS::get_singleton()->get_environment("XDG_SESSION_TYPE").to_lower() == "wayland";6727kde5_embed_workaround = current_desk == "kde" && OS::get_singleton()->get_environment("KDE_SESSION_VERSION") == "5";67286729native_menu = memnew(NativeMenu);6730context = p_context;67316732#ifdef SOWRAP_ENABLED6733#ifdef DEBUG_ENABLED6734int dylibloader_verbose = 1;6735#else6736int dylibloader_verbose = 0;6737#endif6738if (initialize_xlib(dylibloader_verbose) != 0) {6739r_error = ERR_UNAVAILABLE;6740ERR_FAIL_MSG("Can't load Xlib dynamically.");6741}67426743if (initialize_xcursor(dylibloader_verbose) != 0) {6744r_error = ERR_UNAVAILABLE;6745ERR_FAIL_MSG("Can't load XCursor dynamically.");6746}6747#ifdef XKB_ENABLED6748bool xkb_loaded = (initialize_xkbcommon(dylibloader_verbose) == 0);6749xkb_loaded_v05p = xkb_loaded;6750if (!xkb_context_new || !xkb_compose_table_new_from_locale || !xkb_compose_table_unref || !xkb_context_unref || !xkb_compose_state_feed || !xkb_compose_state_unref || !xkb_compose_state_new || !xkb_compose_state_get_status || !xkb_compose_state_get_utf8) {6751xkb_loaded_v05p = false;6752print_verbose("Detected XKBcommon library version older than 0.5, dead key composition and Unicode key labels disabled.");6753}6754xkb_loaded_v08p = xkb_loaded;6755if (!xkb_keysym_to_utf32 || !xkb_keysym_to_upper) {6756xkb_loaded_v08p = false;6757print_verbose("Detected XKBcommon library version older than 0.8, Unicode key labels disabled.");6758}6759#endif6760if (initialize_xext(dylibloader_verbose) != 0) {6761r_error = ERR_UNAVAILABLE;6762ERR_FAIL_MSG("Can't load Xext dynamically.");6763}67646765if (initialize_xinerama(dylibloader_verbose) != 0) {6766xinerama_ext_ok = false;6767}67686769if (initialize_xrandr(dylibloader_verbose) != 0) {6770xrandr_ext_ok = false;6771}67726773if (initialize_xrender(dylibloader_verbose) != 0) {6774r_error = ERR_UNAVAILABLE;6775ERR_FAIL_MSG("Can't load Xrender dynamically.");6776}67776778if (initialize_xinput2(dylibloader_verbose) != 0) {6779r_error = ERR_UNAVAILABLE;6780ERR_FAIL_MSG("Can't load Xinput2 dynamically.");6781}6782#else6783#ifdef XKB_ENABLED6784bool xkb_loaded = true;6785xkb_loaded_v05p = true;6786xkb_loaded_v08p = true;6787#endif6788#endif67896790#ifdef XKB_ENABLED6791if (xkb_loaded) {6792xkb_ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS);6793if (xkb_ctx) {6794const char *locale = getenv("LC_ALL");6795if (!locale || !*locale) {6796locale = getenv("LC_CTYPE");6797}6798if (!locale || !*locale) {6799locale = getenv("LANG");6800}6801if (!locale || !*locale) {6802locale = "C";6803}6804dead_tbl = xkb_compose_table_new_from_locale(xkb_ctx, locale, XKB_COMPOSE_COMPILE_NO_FLAGS);6805}6806}6807#endif68086809Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events);68106811r_error = OK;68126813#ifdef SOWRAP_ENABLED6814{6815if (!XcursorImageCreate || !XcursorImageLoadCursor || !XcursorImageDestroy || !XcursorGetDefaultSize || !XcursorGetTheme || !XcursorLibraryLoadImage) {6816// There's no API to check version, check if functions are available instead.6817ERR_PRINT("Unsupported Xcursor library version.");6818r_error = ERR_UNAVAILABLE;6819return;6820}6821}6822#endif68236824for (int i = 0; i < CURSOR_MAX; i++) {6825cursors[i] = None;6826cursor_img[i] = nullptr;6827}68286829XInitThreads(); //always use threads68306831/** XLIB INITIALIZATION **/6832x11_display = XOpenDisplay(nullptr);68336834if (!x11_display) {6835ERR_PRINT("X11 Display is not available");6836r_error = ERR_UNAVAILABLE;6837return;6838}68396840if (xshaped_ext_ok) {6841int version_major = 0;6842int version_minor = 0;6843int rc = XShapeQueryVersion(x11_display, &version_major, &version_minor);6844print_verbose(vformat("Xshape %d.%d detected.", version_major, version_minor));6845if (rc != 1 || version_major < 1) {6846xshaped_ext_ok = false;6847print_verbose("Unsupported Xshape library version.");6848}6849}68506851if (xinerama_ext_ok) {6852int version_major = 0;6853int version_minor = 0;6854int rc = XineramaQueryVersion(x11_display, &version_major, &version_minor);6855print_verbose(vformat("Xinerama %d.%d detected.", version_major, version_minor));6856if (rc != 1 || version_major < 1) {6857xinerama_ext_ok = false;6858print_verbose("Unsupported Xinerama library version.");6859}6860}68616862if (xrandr_ext_ok) {6863int version_major = 0;6864int version_minor = 0;6865int rc = XRRQueryVersion(x11_display, &version_major, &version_minor);6866print_verbose(vformat("Xrandr %d.%d detected.", version_major, version_minor));6867if (rc != 1 || (version_major == 1 && version_minor < 3) || (version_major < 1)) {6868xrandr_ext_ok = false;6869print_verbose("Unsupported Xrandr library version.");6870}6871}68726873{6874int version_major = 0;6875int version_minor = 0;6876int rc = XRenderQueryVersion(x11_display, &version_major, &version_minor);6877print_verbose(vformat("Xrender %d.%d detected.", version_major, version_minor));6878if (rc != 1 || (version_major == 0 && version_minor < 11)) {6879ERR_PRINT("Unsupported Xrender library version.");6880r_error = ERR_UNAVAILABLE;6881XCloseDisplay(x11_display);6882return;6883}6884}68856886{6887int version_major = 2; // Report 2.2 as supported by engine, but should work with 2.1 or 2.0 library as well.6888int version_minor = 2;6889int rc = XIQueryVersion(x11_display, &version_major, &version_minor);6890print_verbose(vformat("Xinput %d.%d detected.", version_major, version_minor));6891if (rc != Success || (version_major < 2)) {6892ERR_PRINT("Unsupported Xinput2 library version.");6893r_error = ERR_UNAVAILABLE;6894XCloseDisplay(x11_display);6895return;6896}6897}68986899char *modifiers = nullptr;6900Bool xkb_dar = False;6901XAutoRepeatOn(x11_display);6902xkb_dar = XkbSetDetectableAutoRepeat(x11_display, True, nullptr);69036904// Try to support IME if detectable auto-repeat is supported6905if (xkb_dar == True) {6906#ifdef X_HAVE_UTF8_STRING6907// Xutf8LookupString will be used later instead of XmbLookupString before6908// the multibyte sequences can be converted to unicode string.6909modifiers = XSetLocaleModifiers("");6910#endif6911}69126913if (modifiers == nullptr) {6914if (OS::get_singleton()->is_stdout_verbose()) {6915WARN_PRINT("IME is disabled");6916}6917XSetLocaleModifiers("@im=none");6918WARN_PRINT("Error setting locale modifiers");6919}69206921const char *err;6922int xrandr_major = 0;6923int xrandr_minor = 0;6924int event_base, error_base;6925xrandr_ext_ok = XRRQueryExtension(x11_display, &event_base, &error_base);6926xrandr_handle = dlopen("libXrandr.so.2", RTLD_LAZY);6927if (!xrandr_handle) {6928err = dlerror();6929// For some arcane reason, NetBSD now ships libXrandr.so.3 while the rest of the world has libXrandr.so.2...6930// In case this happens for other X11 platforms in the future, let's give it a try too before failing.6931xrandr_handle = dlopen("libXrandr.so.3", RTLD_LAZY);6932if (!xrandr_handle) {6933fprintf(stderr, "could not load libXrandr.so.2, Error: %s\n", err);6934}6935}69366937if (xrandr_handle) {6938XRRQueryVersion(x11_display, &xrandr_major, &xrandr_minor);6939if (((xrandr_major << 8) | xrandr_minor) >= 0x0105) {6940xrr_get_monitors = (xrr_get_monitors_t)dlsym(xrandr_handle, "XRRGetMonitors");6941if (!xrr_get_monitors) {6942err = dlerror();6943fprintf(stderr, "could not find symbol XRRGetMonitors\nError: %s\n", err);6944} else {6945xrr_free_monitors = (xrr_free_monitors_t)dlsym(xrandr_handle, "XRRFreeMonitors");6946if (!xrr_free_monitors) {6947err = dlerror();6948fprintf(stderr, "could not find XRRFreeMonitors\nError: %s\n", err);6949xrr_get_monitors = nullptr;6950}6951}6952}6953}69546955if (!_refresh_device_info()) {6956OS::get_singleton()->alert("Your system does not support XInput 2.\n"6957"Please upgrade your distribution.",6958"Unable to initialize XInput");6959r_error = ERR_UNAVAILABLE;6960return;6961}69626963xim = XOpenIM(x11_display, nullptr, nullptr, nullptr);69646965if (xim == nullptr) {6966WARN_PRINT("XOpenIM failed");6967xim_style = 0L;6968} else {6969::XIMCallback im_destroy_callback;6970im_destroy_callback.client_data = (::XPointer)(this);6971im_destroy_callback.callback = (::XIMProc)(_xim_destroy_callback);6972if (XSetIMValues(xim, XNDestroyCallback, &im_destroy_callback,6973nullptr) != nullptr) {6974WARN_PRINT("Error setting XIM destroy callback");6975}69766977::XIMStyles *xim_styles = nullptr;6978xim_style = 0L;6979char *imvalret = XGetIMValues(xim, XNQueryInputStyle, &xim_styles, nullptr);6980if (imvalret != nullptr || xim_styles == nullptr) {6981fprintf(stderr, "Input method doesn't support any styles\n");6982}69836984if (xim_styles) {6985xim_style = 0L;6986for (int i = 0; i < xim_styles->count_styles; i++) {6987const ::XIMStyle &style = xim_styles->supported_styles[i];69886989if (!_is_xim_style_supported(style)) {6990continue;6991}69926993xim_style = _get_best_xim_style(xim_style, style);6994}69956996XFree(xim_styles);6997}6998XFree(imvalret);6999}70007001/* Atom internment */7002wm_delete = XInternAtom(x11_display, "WM_DELETE_WINDOW", true);7003// Set Xdnd (drag & drop) support.7004xdnd_aware = XInternAtom(x11_display, "XdndAware", False);7005xdnd_enter = XInternAtom(x11_display, "XdndEnter", False);7006xdnd_position = XInternAtom(x11_display, "XdndPosition", False);7007xdnd_status = XInternAtom(x11_display, "XdndStatus", False);7008xdnd_action_copy = XInternAtom(x11_display, "XdndActionCopy", False);7009xdnd_drop = XInternAtom(x11_display, "XdndDrop", False);7010xdnd_finished = XInternAtom(x11_display, "XdndFinished", False);7011xdnd_selection = XInternAtom(x11_display, "XdndSelection", False);70127013#ifdef SPEECHD_ENABLED7014// Init TTS7015bool tts_enabled = GLOBAL_GET("audio/general/text_to_speech");7016if (tts_enabled) {7017initialize_tts();7018}7019#endif70207021#ifdef ACCESSKIT_ENABLED7022if (accessibility_get_mode() != DisplayServer::AccessibilityMode::ACCESSIBILITY_DISABLED) {7023accessibility_driver = memnew(AccessibilityDriverAccessKit);7024if (accessibility_driver->init() != OK) {7025memdelete(accessibility_driver);7026accessibility_driver = nullptr;7027}7028}7029#endif70307031//!!!!!!!!!!!!!!!!!!!!!!!!!!7032//TODO - do Vulkan and OpenGL support checks, driver selection and fallback7033rendering_driver = p_rendering_driver;70347035bool driver_found = false;7036String executable_name = OS::get_singleton()->get_executable_path().get_file();70377038// Initialize context and rendering device.70397040if (rendering_driver == "dummy") {7041RasterizerDummy::make_current();7042driver_found = true;7043}70447045#if defined(RD_ENABLED)7046#if defined(VULKAN_ENABLED)7047if (rendering_driver == "vulkan") {7048rendering_context = memnew(RenderingContextDriverVulkanX11);7049}7050#endif // VULKAN_ENABLED70517052if (rendering_context) {7053if (rendering_context->initialize() != OK) {7054memdelete(rendering_context);7055rendering_context = nullptr;7056#if defined(GLES3_ENABLED)7057bool fallback_to_opengl3 = GLOBAL_GET("rendering/rendering_device/fallback_to_opengl3");7058if (fallback_to_opengl3 && rendering_driver != "opengl3") {7059WARN_PRINT("Your video card drivers seem not to support the required Vulkan version, switching to OpenGL 3.");7060rendering_driver = "opengl3";7061OS::get_singleton()->set_current_rendering_method("gl_compatibility");7062OS::get_singleton()->set_current_rendering_driver_name(rendering_driver);7063} else7064#endif // GLES3_ENABLED7065{7066r_error = ERR_CANT_CREATE;70677068if (p_rendering_driver == "vulkan") {7069OS::get_singleton()->alert(7070vformat("Your video card drivers seem not to support the required Vulkan version.\n\n"7071"If possible, consider updating your video card drivers or using the OpenGL 3 driver.\n\n"7072"You can enable the OpenGL 3 driver by starting the engine from the\n"7073"command line with the command:\n\n \"%s\" --rendering-driver opengl3\n\n"7074"If you recently updated your video card drivers, try rebooting.",7075executable_name),7076"Unable to initialize Vulkan video driver");7077}70787079ERR_FAIL_MSG(vformat("Could not initialize %s", rendering_driver));7080}7081}7082driver_found = true;7083}7084#endif // RD_ENABLED70857086#if defined(GLES3_ENABLED)7087if (rendering_driver == "opengl3" || rendering_driver == "opengl3_es") {7088if (getenv("DRI_PRIME") == nullptr) {7089int use_prime = -1;70907091if (getenv("PRIMUS_DISPLAY") ||7092getenv("PRIMUS_libGLd") ||7093getenv("PRIMUS_libGLa") ||7094getenv("PRIMUS_libGL") ||7095getenv("PRIMUS_LOAD_GLOBAL") ||7096getenv("BUMBLEBEE_SOCKET")) {7097print_verbose("Optirun/primusrun detected. Skipping GPU detection");7098use_prime = 0;7099}71007101// Some tools use fake libGL libraries and have them override the real one using7102// LD_LIBRARY_PATH, so we skip them. *But* Steam also sets LD_LIBRARY_PATH for its7103// runtime and includes system `/lib` and `/lib64`... so ignore Steam.7104if (use_prime == -1 && getenv("LD_LIBRARY_PATH") && !getenv("STEAM_RUNTIME_LIBRARY_PATH")) {7105String ld_library_path(getenv("LD_LIBRARY_PATH"));7106Vector<String> libraries = ld_library_path.split(":");71077108for (int i = 0; i < libraries.size(); ++i) {7109if (FileAccess::exists(libraries[i] + "/libGL.so.1") ||7110FileAccess::exists(libraries[i] + "/libGL.so")) {7111print_verbose("Custom libGL override detected. Skipping GPU detection");7112use_prime = 0;7113}7114}7115}71167117if (use_prime == -1) {7118print_verbose("Detecting GPUs, set DRI_PRIME in the environment to override GPU detection logic.");7119use_prime = DetectPrimeX11::detect_prime();7120}71217122if (use_prime) {7123print_line("Found discrete GPU, setting DRI_PRIME=1 to use it.");7124print_line("Note: Set DRI_PRIME=0 in the environment to disable Godot from using the discrete GPU.");7125setenv("DRI_PRIME", "1", 1);7126}7127}7128}7129if (rendering_driver == "opengl3") {7130gl_manager = memnew(GLManager_X11(p_resolution, GLManager_X11::GLES_3_0_COMPATIBLE));7131if (gl_manager->initialize(x11_display) != OK || gl_manager->open_display(x11_display) != OK) {7132memdelete(gl_manager);7133gl_manager = nullptr;7134bool fallback = GLOBAL_GET("rendering/gl_compatibility/fallback_to_gles");7135if (fallback) {7136WARN_PRINT("Your video card drivers seem not to support the required OpenGL version, switching to OpenGLES.");7137rendering_driver = "opengl3_es";7138OS::get_singleton()->set_current_rendering_driver_name(rendering_driver);7139} else {7140r_error = ERR_UNAVAILABLE;71417142OS::get_singleton()->alert(7143vformat("Your video card drivers seem not to support the required OpenGL 3.3 version.\n\n"7144"If possible, consider updating your video card drivers or using the Vulkan driver.\n\n"7145"You can enable the Vulkan driver by starting the engine from the\n"7146"command line with the command:\n\n \"%s\" --rendering-driver vulkan\n\n"7147"If you recently updated your video card drivers, try rebooting.",7148executable_name),7149"Unable to initialize OpenGL video driver");71507151ERR_FAIL_MSG("Could not initialize OpenGL.");7152}7153} else {7154driver_found = true;7155RasterizerGLES3::make_current(true);7156}7157}71587159if (rendering_driver == "opengl3_es") {7160gl_manager_egl = memnew(GLManagerEGL_X11);7161if (gl_manager_egl->initialize() != OK || gl_manager_egl->open_display(x11_display) != OK) {7162memdelete(gl_manager_egl);7163gl_manager_egl = nullptr;7164r_error = ERR_UNAVAILABLE;71657166OS::get_singleton()->alert(7167"Your video card drivers seem not to support the required OpenGL ES 3.0 version.\n\n"7168"If possible, consider updating your video card drivers.\n\n"7169"If you recently updated your video card drivers, try rebooting.",7170"Unable to initialize OpenGL ES video driver");71717172ERR_FAIL_MSG("Could not initialize OpenGL ES.");7173}7174driver_found = true;7175RasterizerGLES3::make_current(false);7176}71777178#endif // GLES3_ENABLED71797180if (!driver_found) {7181r_error = ERR_UNAVAILABLE;7182ERR_FAIL_MSG("Video driver not found.");7183}71847185Point2i window_position;7186if (p_position != nullptr) {7187window_position = *p_position;7188} else {7189if (p_screen == SCREEN_OF_MAIN_WINDOW) {7190p_screen = SCREEN_PRIMARY;7191}7192Rect2i scr_rect = screen_get_usable_rect(p_screen);7193window_position = scr_rect.position + (scr_rect.size - p_resolution) / 2;7194}71957196WindowID main_window = _create_window(p_mode, p_vsync_mode, p_flags, Rect2i(window_position, p_resolution), p_parent_window);7197if (main_window == INVALID_WINDOW_ID) {7198r_error = ERR_CANT_CREATE;7199return;7200}7201for (int i = 0; i < WINDOW_FLAG_MAX; i++) {7202if (p_flags & (1 << i)) {7203window_set_flag(WindowFlags(i), true, main_window);7204}7205}72067207#if defined(RD_ENABLED)7208if (rendering_context) {7209rendering_device = memnew(RenderingDevice);7210if (rendering_device->initialize(rendering_context, MAIN_WINDOW_ID) != OK) {7211memdelete(rendering_device);7212rendering_device = nullptr;7213memdelete(rendering_context);7214rendering_context = nullptr;7215r_error = ERR_UNAVAILABLE;7216return;7217}7218rendering_device->screen_create(MAIN_WINDOW_ID);72197220RendererCompositorRD::make_current();7221}7222#endif // RD_ENABLED72237224{7225//set all event master mask7226XIEventMask all_master_event_mask;7227static unsigned char all_master_mask_data[XIMaskLen(XI_LASTEVENT)] = {};7228all_master_event_mask.deviceid = XIAllMasterDevices;7229all_master_event_mask.mask_len = sizeof(all_master_mask_data);7230all_master_event_mask.mask = all_master_mask_data;7231XISetMask(all_master_event_mask.mask, XI_DeviceChanged);7232XISetMask(all_master_event_mask.mask, XI_RawMotion);7233XISelectEvents(x11_display, DefaultRootWindow(x11_display), &all_master_event_mask, 1);7234}72357236cursor_size = XcursorGetDefaultSize(x11_display);7237cursor_theme = XcursorGetTheme(x11_display);72387239if (!cursor_theme) {7240print_verbose("XcursorGetTheme could not get cursor theme");7241cursor_theme = "default";7242}72437244for (int i = 0; i < CURSOR_MAX; i++) {7245static const char *cursor_file[] = {7246"left_ptr",7247"xterm",7248"hand2",7249"cross",7250"watch",7251"left_ptr_watch",7252"fleur",7253"dnd-move",7254"crossed_circle",7255"v_double_arrow",7256"h_double_arrow",7257"size_bdiag",7258"size_fdiag",7259"move",7260"row_resize",7261"col_resize",7262"question_arrow"7263};72647265cursor_img[i] = XcursorLibraryLoadImage(cursor_file[i], cursor_theme, cursor_size);7266if (!cursor_img[i]) {7267const char *fallback = nullptr;72687269switch (i) {7270case CURSOR_POINTING_HAND:7271fallback = "pointer";7272break;7273case CURSOR_CROSS:7274fallback = "crosshair";7275break;7276case CURSOR_WAIT:7277fallback = "wait";7278break;7279case CURSOR_BUSY:7280fallback = "progress";7281break;7282case CURSOR_DRAG:7283fallback = "grabbing";7284break;7285case CURSOR_CAN_DROP:7286fallback = "hand1";7287break;7288case CURSOR_FORBIDDEN:7289fallback = "forbidden";7290break;7291case CURSOR_VSIZE:7292fallback = "ns-resize";7293break;7294case CURSOR_HSIZE:7295fallback = "ew-resize";7296break;7297case CURSOR_BDIAGSIZE:7298fallback = "fd_double_arrow";7299break;7300case CURSOR_FDIAGSIZE:7301fallback = "bd_double_arrow";7302break;7303case CURSOR_MOVE:7304cursor_img[i] = cursor_img[CURSOR_DRAG];7305break;7306case CURSOR_VSPLIT:7307fallback = "sb_v_double_arrow";7308break;7309case CURSOR_HSPLIT:7310fallback = "sb_h_double_arrow";7311break;7312case CURSOR_HELP:7313fallback = "help";7314break;7315}7316if (fallback != nullptr) {7317cursor_img[i] = XcursorLibraryLoadImage(fallback, cursor_theme, cursor_size);7318}7319}7320if (cursor_img[i]) {7321cursors[i] = XcursorImageLoadCursor(x11_display, cursor_img[i]);7322} else {7323print_verbose("Failed loading custom cursor: " + String(cursor_file[i]));7324}7325}73267327{7328// Creating an empty/transparent cursor73297330// Create 1x1 bitmap7331Pixmap cursormask = XCreatePixmap(x11_display,7332RootWindow(x11_display, DefaultScreen(x11_display)), 1, 1, 1);73337334// Fill with zero7335XGCValues xgc;7336xgc.function = GXclear;7337GC gc = XCreateGC(x11_display, cursormask, GCFunction, &xgc);7338XFillRectangle(x11_display, cursormask, gc, 0, 0, 1, 1);73397340// Color value doesn't matter. Mask zero means no foreground or background will be drawn7341XColor col = {};73427343Cursor cursor = XCreatePixmapCursor(x11_display,7344cursormask, // source (using cursor mask as placeholder, since it'll all be ignored)7345cursormask, // mask7346&col, &col, 0, 0);73477348XFreePixmap(x11_display, cursormask);7349XFreeGC(x11_display, gc);73507351if (cursor == None) {7352ERR_PRINT("FAILED CREATING CURSOR");7353}73547355null_cursor = cursor;7356}7357cursor_set_shape(CURSOR_BUSY);73587359// Search the X11 event queue for ConfigureNotify events and process all7360// that are currently queued early, so we can get the final window size7361// for correctly drawing of the bootsplash.7362XEvent config_event;7363while (XCheckTypedEvent(x11_display, ConfigureNotify, &config_event)) {7364_window_changed(&config_event);7365}7366events_thread.start(_poll_events_thread, this);73677368_update_real_mouse_position(windows[MAIN_WINDOW_ID]);73697370#ifdef DBUS_ENABLED7371bool dbus_ok = true;7372#ifdef SOWRAP_ENABLED7373if (initialize_dbus(dylibloader_verbose) != 0) {7374print_verbose("Failed to load DBus library!");7375dbus_ok = false;7376}7377#endif7378if (dbus_ok) {7379bool ver_ok = false;7380int version_major = 0;7381int version_minor = 0;7382int version_rev = 0;7383dbus_get_version(&version_major, &version_minor, &version_rev);7384ver_ok = (version_major == 1 && version_minor >= 10) || (version_major > 1); // 1.10.07385print_verbose(vformat("DBus %d.%d.%d detected.", version_major, version_minor, version_rev));7386if (!ver_ok) {7387print_verbose("Unsupported DBus library version!");7388dbus_ok = false;7389}7390}7391if (dbus_ok) {7392screensaver = memnew(FreeDesktopScreenSaver);7393portal_desktop = memnew(FreeDesktopPortalDesktop);7394atspi_monitor = memnew(FreeDesktopAtSPIMonitor);7395}7396#endif // DBUS_ENABLED73977398screen_set_keep_on(GLOBAL_GET("display/window/energy_saving/keep_screen_on"));73997400XSetErrorHandler(&default_window_error_handler);74017402r_error = OK;7403}74047405DisplayServerX11::~DisplayServerX11() {7406// Send owned clipboard data to clipboard manager before exit.7407Window x11_main_window = windows[MAIN_WINDOW_ID].x11_window;7408_clipboard_transfer_ownership(XA_PRIMARY, x11_main_window);7409_clipboard_transfer_ownership(XInternAtom(x11_display, "CLIPBOARD", 0), x11_main_window);74107411events_thread_done.set();7412events_thread.wait_to_finish();74137414if (native_menu) {7415memdelete(native_menu);7416native_menu = nullptr;7417}74187419//destroy all windows7420for (KeyValue<WindowID, WindowData> &E : windows) {7421#if defined(RD_ENABLED)7422if (rendering_device) {7423rendering_device->screen_free(E.key);7424}74257426if (rendering_context) {7427rendering_context->window_destroy(E.key);7428}7429#endif7430#ifdef GLES3_ENABLED7431if (gl_manager) {7432gl_manager->window_destroy(E.key);7433}7434if (gl_manager_egl) {7435gl_manager_egl->window_destroy(E.key);7436}7437#endif74387439#ifdef ACCESSKIT_ENABLED7440if (accessibility_driver) {7441accessibility_driver->window_destroy(E.key);7442}7443#endif74447445WindowData &wd = E.value;7446if (wd.xic) {7447XDestroyIC(wd.xic);7448wd.xic = nullptr;7449}7450XDestroyWindow(x11_display, wd.x11_xim_window);7451#ifdef XKB_ENABLED7452if (xkb_loaded_v05p) {7453if (wd.xkb_state) {7454xkb_compose_state_unref(wd.xkb_state);7455wd.xkb_state = nullptr;7456}7457}7458#endif7459XUnmapWindow(x11_display, wd.x11_window);7460XDestroyWindow(x11_display, wd.x11_window);7461}74627463#ifdef XKB_ENABLED7464if (xkb_loaded_v05p) {7465if (dead_tbl) {7466xkb_compose_table_unref(dead_tbl);7467}7468if (xkb_ctx) {7469xkb_context_unref(xkb_ctx);7470}7471}7472#endif74737474//destroy drivers7475#if defined(RD_ENABLED)7476if (rendering_device) {7477memdelete(rendering_device);7478rendering_device = nullptr;7479}74807481if (rendering_context) {7482memdelete(rendering_context);7483rendering_context = nullptr;7484}7485#endif74867487#ifdef GLES3_ENABLED7488if (gl_manager) {7489memdelete(gl_manager);7490gl_manager = nullptr;7491}7492if (gl_manager_egl) {7493memdelete(gl_manager_egl);7494gl_manager_egl = nullptr;7495}7496#endif74977498if (xrandr_handle) {7499dlclose(xrandr_handle);7500}75017502for (int i = 0; i < CURSOR_MAX; i++) {7503if (cursors[i] != None) {7504XFreeCursor(x11_display, cursors[i]);7505}7506if (cursor_img[i] != nullptr) {7507XcursorImageDestroy(cursor_img[i]);7508}7509}75107511if (xim) {7512XCloseIM(xim);7513}75147515XCloseDisplay(x11_display);7516if (xmbstring) {7517memfree(xmbstring);7518}7519#ifdef ACCESSKIT_ENABLED7520if (accessibility_driver) {7521memdelete(accessibility_driver);7522}7523#endif7524#ifdef SPEECHD_ENABLED7525if (tts) {7526memdelete(tts);7527}7528#endif75297530#ifdef DBUS_ENABLED7531if (screensaver) {7532memdelete(screensaver);7533}7534if (portal_desktop) {7535memdelete(portal_desktop);7536}7537if (atspi_monitor) {7538memdelete(atspi_monitor);7539}7540#endif7541}75427543void DisplayServerX11::register_x11_driver() {7544register_create_function("x11", create_func, get_rendering_drivers_func);7545}75467547#endif // X11 enabled754875497550