Path: blob/master/platform/linuxbsd/x11/display_server_x11.cpp
20934 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/input/input.h"39#include "core/io/file_access.h"40#include "core/math/math_funcs.h"41#include "core/os/main_loop.h"42#include "core/string/print_string.h"43#include "core/string/ustring.h"44#include "core/version.h"45#include "drivers/png/png_driver_common.h"46#include "main/main.h"4748#include "servers/rendering/dummy/rasterizer_dummy.h"4950#if defined(VULKAN_ENABLED)51#include "servers/rendering/renderer_rd/renderer_compositor_rd.h"52#endif5354#if defined(GLES3_ENABLED)55#include "drivers/gles3/rasterizer_gles3.h"56#endif5758#ifdef ACCESSKIT_ENABLED59#include "drivers/accesskit/accessibility_driver_accesskit.h"60#endif6162#ifdef DBUS_ENABLED63#ifdef SOWRAP_ENABLED64#include "dbus-so_wrap.h"65#else66#include <dbus/dbus.h>67#endif68#endif6970#include <dlfcn.h>71#include <sys/stat.h>72#include <sys/types.h>73#include <unistd.h>74#include <climits>75#include <cstdio>76#include <cstdlib>7778#undef CursorShape79#include <X11/XKBlib.h>8081// ICCCM82#define WM_NormalState 1L // window normal state83#define WM_IconicState 3L // window minimized84// EWMH85#define _NET_WM_STATE_REMOVE 0L // remove/unset property86#define _NET_WM_STATE_ADD 1L // add/set property8788#define _NET_WM_MOVERESIZE_SIZE_TOPLEFT 0L89#define _NET_WM_MOVERESIZE_SIZE_TOP 1L90#define _NET_WM_MOVERESIZE_SIZE_TOPRIGHT 2L91#define _NET_WM_MOVERESIZE_SIZE_RIGHT 3L92#define _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT 4L93#define _NET_WM_MOVERESIZE_SIZE_BOTTOM 5L94#define _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT 6L95#define _NET_WM_MOVERESIZE_SIZE_LEFT 7L96#define _NET_WM_MOVERESIZE_MOVE 8L9798// 2.2 is the first release with multitouch99#define XINPUT_CLIENT_VERSION_MAJOR 2100#define XINPUT_CLIENT_VERSION_MINOR 2101102#define VALUATOR_ABSX 0103#define VALUATOR_ABSY 1104#define VALUATOR_PRESSURE 2105#define VALUATOR_TILTX 3106#define VALUATOR_TILTY 4107108//#define DISPLAY_SERVER_X11_DEBUG_LOGS_ENABLED109#ifdef DISPLAY_SERVER_X11_DEBUG_LOGS_ENABLED110#define DEBUG_LOG_X11(...) printf(__VA_ARGS__)111#else112#define DEBUG_LOG_X11(...)113#endif114115static const double abs_resolution_mult = 10000.0;116static const double abs_resolution_range_mult = 10.0;117118struct MotifWmHints {119unsigned long flags;120unsigned long functions;121unsigned long decorations;122long input_mode;123unsigned long status;124};125126enum {127MWM_HINTS_FUNCTIONS = (1L << 0),128MWM_HINTS_DECORATIONS = (1L << 1),129130MWM_FUNC_ALL = (1L << 0),131MWM_FUNC_RESIZE = (1L << 1),132MWM_FUNC_MOVE = (1L << 2),133MWM_FUNC_MINIMIZE = (1L << 3),134MWM_FUNC_MAXIMIZE = (1L << 4),135MWM_FUNC_CLOSE = (1L << 5),136137MWM_DECOR_ALL = (1L << 0),138MWM_DECOR_BORDER = (1L << 1),139MWM_DECOR_RESIZEH = (1L << 2),140MWM_DECOR_TITLE = (1L << 3),141MWM_DECOR_MENU = (1L << 4),142MWM_DECOR_MINIMIZE = (1L << 5),143MWM_DECOR_MAXIMIZE = (1L << 6),144};145146static String get_atom_name(Display *p_disp, Atom p_atom) {147char *name = XGetAtomName(p_disp, p_atom);148ERR_FAIL_NULL_V_MSG(name, String(), "Atom is invalid.");149String ret = String::utf8(name);150XFree(name);151return ret;152}153154bool DisplayServerX11::has_feature(Feature p_feature) const {155switch (p_feature) {156#ifndef DISABLE_DEPRECATED157case FEATURE_GLOBAL_MENU: {158return (native_menu && native_menu->has_feature(NativeMenu::FEATURE_GLOBAL_MENU));159} break;160#endif161case FEATURE_SUBWINDOWS:162#ifdef TOUCH_ENABLED163case FEATURE_TOUCHSCREEN:164#endif165case FEATURE_MOUSE:166case FEATURE_MOUSE_WARP:167case FEATURE_CLIPBOARD:168case FEATURE_CURSOR_SHAPE:169case FEATURE_CUSTOM_CURSOR_SHAPE:170case FEATURE_IME:171case FEATURE_WINDOW_TRANSPARENCY:172//case FEATURE_HIDPI:173case FEATURE_ICON:174//case FEATURE_NATIVE_ICON:175case FEATURE_SWAP_BUFFERS:176#ifdef DBUS_ENABLED177case FEATURE_KEEP_SCREEN_ON:178#endif179case FEATURE_CLIPBOARD_PRIMARY:180case FEATURE_WINDOW_EMBEDDING:181case FEATURE_WINDOW_DRAG: {182return true;183} break;184185//case FEATURE_NATIVE_DIALOG:186//case FEATURE_NATIVE_DIALOG_INPUT:187#ifdef DBUS_ENABLED188case FEATURE_NATIVE_DIALOG_FILE:189case FEATURE_NATIVE_DIALOG_FILE_EXTRA:190case FEATURE_NATIVE_DIALOG_FILE_MIME: {191return (portal_desktop && portal_desktop->is_supported() && portal_desktop->is_file_chooser_supported());192} break;193case FEATURE_NATIVE_COLOR_PICKER: {194return (portal_desktop && portal_desktop->is_supported() && portal_desktop->is_screenshot_supported());195} break;196#endif197case FEATURE_SCREEN_CAPTURE: {198return !xwayland;199} break;200201#ifdef SPEECHD_ENABLED202case FEATURE_TEXT_TO_SPEECH: {203return true;204} break;205#endif206207#ifdef ACCESSKIT_ENABLED208case FEATURE_ACCESSIBILITY_SCREEN_READER: {209return (accessibility_driver != nullptr);210} break;211#endif212213default: {214return false;215}216}217}218219String DisplayServerX11::get_name() const {220return "X11";221}222223void DisplayServerX11::_update_real_mouse_position(const WindowData &wd) {224Window root_return, child_return;225int root_x, root_y, win_x, win_y;226unsigned int mask_return;227228Bool xquerypointer_result = XQueryPointer(x11_display, wd.x11_window, &root_return, &child_return, &root_x, &root_y,229&win_x, &win_y, &mask_return);230231if (xquerypointer_result) {232if (win_x > 0 && win_y > 0 && win_x <= wd.size.width && win_y <= wd.size.height) {233last_mouse_pos.x = win_x;234last_mouse_pos.y = win_y;235last_mouse_pos_valid = true;236Input::get_singleton()->set_mouse_position(last_mouse_pos);237}238}239}240241bool DisplayServerX11::_refresh_device_info() {242int event_base, error_base;243244print_verbose("XInput: Refreshing devices.");245246if (!XQueryExtension(x11_display, "XInputExtension", &xi.opcode, &event_base, &error_base)) {247print_verbose("XInput extension not available. Please upgrade your distribution.");248return false;249}250251int xi_major_query = XINPUT_CLIENT_VERSION_MAJOR;252int xi_minor_query = XINPUT_CLIENT_VERSION_MINOR;253254if (XIQueryVersion(x11_display, &xi_major_query, &xi_minor_query) != Success) {255print_verbose(vformat("XInput 2 not available (server supports %d.%d).", xi_major_query, xi_minor_query));256xi.opcode = 0;257return false;258}259260if (xi_major_query < XINPUT_CLIENT_VERSION_MAJOR || (xi_major_query == XINPUT_CLIENT_VERSION_MAJOR && xi_minor_query < XINPUT_CLIENT_VERSION_MINOR)) {261print_verbose(vformat("XInput %d.%d not available (server supports %d.%d). Touch input unavailable.",262XINPUT_CLIENT_VERSION_MAJOR, XINPUT_CLIENT_VERSION_MINOR, xi_major_query, xi_minor_query));263}264265xi.absolute_devices.clear();266xi.touch_devices.clear();267xi.pen_inverted_devices.clear();268xi.last_relative_time = 0;269270int dev_count;271XIDeviceInfo *info = XIQueryDevice(x11_display, XIAllDevices, &dev_count);272273for (int i = 0; i < dev_count; i++) {274XIDeviceInfo *dev = &info[i];275if (!dev->enabled) {276continue;277}278if (!(dev->use == XISlavePointer || dev->use == XIFloatingSlave)) {279continue;280}281282bool direct_touch = false;283bool absolute_mode = false;284int resolution_x = 0;285int resolution_y = 0;286double abs_x_min = 0;287double abs_x_max = 0;288double abs_y_min = 0;289double abs_y_max = 0;290double pressure_min = 0;291double pressure_max = 0;292double tilt_x_min = 0;293double tilt_x_max = 0;294double tilt_y_min = 0;295double tilt_y_max = 0;296for (int j = 0; j < dev->num_classes; j++) {297#ifdef TOUCH_ENABLED298if (dev->classes[j]->type == XITouchClass && ((XITouchClassInfo *)dev->classes[j])->mode == XIDirectTouch) {299direct_touch = true;300}301#endif302if (dev->classes[j]->type == XIValuatorClass) {303XIValuatorClassInfo *class_info = (XIValuatorClassInfo *)dev->classes[j];304305if (class_info->number == VALUATOR_ABSX && class_info->mode == XIModeAbsolute) {306resolution_x = class_info->resolution;307abs_x_min = class_info->min;308abs_x_max = class_info->max;309absolute_mode = true;310} else if (class_info->number == VALUATOR_ABSY && class_info->mode == XIModeAbsolute) {311resolution_y = class_info->resolution;312abs_y_min = class_info->min;313abs_y_max = class_info->max;314absolute_mode = true;315} else if (class_info->number == VALUATOR_PRESSURE && class_info->mode == XIModeAbsolute) {316pressure_min = class_info->min;317pressure_max = class_info->max;318} else if (class_info->number == VALUATOR_TILTX && class_info->mode == XIModeAbsolute) {319tilt_x_min = class_info->min;320tilt_x_max = class_info->max;321} else if (class_info->number == VALUATOR_TILTY && class_info->mode == XIModeAbsolute) {322tilt_y_min = class_info->min;323tilt_y_max = class_info->max;324}325}326}327if (direct_touch) {328xi.touch_devices.push_back(dev->deviceid);329print_verbose("XInput: Using touch device: " + String(dev->name));330}331if (absolute_mode) {332// If no resolution was reported, use the min/max ranges.333if (resolution_x <= 0) {334resolution_x = (abs_x_max - abs_x_min) * abs_resolution_range_mult;335}336if (resolution_y <= 0) {337resolution_y = (abs_y_max - abs_y_min) * abs_resolution_range_mult;338}339xi.absolute_devices[dev->deviceid] = Vector2(abs_resolution_mult / resolution_x, abs_resolution_mult / resolution_y);340print_verbose("XInput: Absolute pointing device: " + String(dev->name));341}342343xi.pressure = 0;344xi.pen_pressure_range[dev->deviceid] = Vector2(pressure_min, pressure_max);345xi.pen_tilt_x_range[dev->deviceid] = Vector2(tilt_x_min, tilt_x_max);346xi.pen_tilt_y_range[dev->deviceid] = Vector2(tilt_y_min, tilt_y_max);347xi.pen_inverted_devices[dev->deviceid] = String(dev->name).findn("eraser") > 0;348}349350XIFreeDeviceInfo(info);351#ifdef TOUCH_ENABLED352if (!xi.touch_devices.size()) {353print_verbose("XInput: No touch devices found.");354}355#endif356357return true;358}359360void DisplayServerX11::_flush_mouse_motion() {361// Block events polling while flushing motion events.362MutexLock mutex_lock(events_mutex);363364for (uint32_t event_index = 0; event_index < polled_events.size(); ++event_index) {365XEvent &event = polled_events[event_index];366if (XGetEventData(x11_display, &event.xcookie) && event.xcookie.type == GenericEvent && event.xcookie.extension == xi.opcode) {367XIDeviceEvent *event_data = (XIDeviceEvent *)event.xcookie.data;368if (event_data->evtype == XI_RawMotion) {369XFreeEventData(x11_display, &event.xcookie);370polled_events.remove_at(event_index--);371continue;372}373XFreeEventData(x11_display, &event.xcookie);374break;375}376}377378xi.relative_motion.x = 0;379xi.relative_motion.y = 0;380}381382#ifdef SPEECHD_ENABLED383384void DisplayServerX11::initialize_tts() const {385const_cast<DisplayServerX11 *>(this)->tts = memnew(TTS_Linux);386}387388bool DisplayServerX11::tts_is_speaking() const {389if (unlikely(!tts)) {390initialize_tts();391}392ERR_FAIL_NULL_V(tts, false);393return tts->is_speaking();394}395396bool DisplayServerX11::tts_is_paused() const {397if (unlikely(!tts)) {398initialize_tts();399}400ERR_FAIL_NULL_V(tts, false);401return tts->is_paused();402}403404TypedArray<Dictionary> DisplayServerX11::tts_get_voices() const {405if (unlikely(!tts)) {406initialize_tts();407}408ERR_FAIL_NULL_V(tts, TypedArray<Dictionary>());409return tts->get_voices();410}411412void DisplayServerX11::tts_speak(const String &p_text, const String &p_voice, int p_volume, float p_pitch, float p_rate, int64_t p_utterance_id, bool p_interrupt) {413if (unlikely(!tts)) {414initialize_tts();415}416ERR_FAIL_NULL(tts);417tts->speak(p_text, p_voice, p_volume, p_pitch, p_rate, p_utterance_id, p_interrupt);418}419420void DisplayServerX11::tts_pause() {421if (unlikely(!tts)) {422initialize_tts();423}424ERR_FAIL_NULL(tts);425tts->pause();426}427428void DisplayServerX11::tts_resume() {429if (unlikely(!tts)) {430initialize_tts();431}432ERR_FAIL_NULL(tts);433tts->resume();434}435436void DisplayServerX11::tts_stop() {437if (unlikely(!tts)) {438initialize_tts();439}440ERR_FAIL_NULL(tts);441tts->stop();442}443444#endif445446#ifdef DBUS_ENABLED447448bool DisplayServerX11::is_dark_mode_supported() const {449return portal_desktop && portal_desktop->is_supported() && portal_desktop->is_settings_supported();450}451452bool DisplayServerX11::is_dark_mode() const {453if (!is_dark_mode_supported()) {454return false;455}456switch (portal_desktop->get_appearance_color_scheme()) {457case 1:458// Prefers dark theme.459return true;460case 2:461// Prefers light theme.462return false;463default:464// Preference unknown.465return false;466}467}468469Color DisplayServerX11::get_accent_color() const {470if (!portal_desktop) {471return Color();472}473return portal_desktop->get_appearance_accent_color();474}475476void DisplayServerX11::set_system_theme_change_callback(const Callable &p_callable) {477ERR_FAIL_COND(!portal_desktop);478portal_desktop->set_system_theme_change_callback(p_callable);479}480481Error 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) {482ERR_FAIL_COND_V(!portal_desktop, ERR_UNAVAILABLE);483WindowID window_id = p_window_id;484485if (!windows.has(window_id) || windows[window_id].is_popup) {486window_id = MAIN_WINDOW_ID;487}488489String xid = vformat("x11:%x", (uint64_t)windows[window_id].x11_window);490return 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);491}492493Error 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) {494ERR_FAIL_COND_V(!portal_desktop, ERR_UNAVAILABLE);495WindowID window_id = p_window_id;496497if (!windows.has(window_id) || windows[window_id].is_popup) {498window_id = MAIN_WINDOW_ID;499}500501String xid = vformat("x11:%x", (uint64_t)windows[window_id].x11_window);502return 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);503}504505#endif506507void DisplayServerX11::beep() const {508XBell(x11_display, 0);509}510511void DisplayServerX11::_mouse_update_mode() {512_THREAD_SAFE_METHOD_513514MouseMode wanted_mouse_mode = mouse_mode_override_enabled515? mouse_mode_override516: mouse_mode_base;517518if (wanted_mouse_mode == mouse_mode) {519return;520}521522if (mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN) {523XUngrabPointer(x11_display, CurrentTime);524}525526// The only modes that show a cursor are VISIBLE and CONFINED527bool show_cursor = (wanted_mouse_mode == MOUSE_MODE_VISIBLE || wanted_mouse_mode == MOUSE_MODE_CONFINED);528bool previously_shown = (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED);529530if (show_cursor && !previously_shown) {531WindowID window_id = get_window_at_screen_position(mouse_get_position());532if (window_id != INVALID_WINDOW_ID && window_mouseover_id != window_id) {533if (window_mouseover_id != INVALID_WINDOW_ID) {534_send_window_event(windows[window_mouseover_id], WINDOW_EVENT_MOUSE_EXIT);535}536window_mouseover_id = window_id;537_send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_ENTER);538}539}540541for (const KeyValue<WindowID, WindowData> &E : windows) {542if (show_cursor) {543XDefineCursor(x11_display, E.value.x11_window, cursors[current_cursor]); // show cursor544} else {545XDefineCursor(x11_display, E.value.x11_window, null_cursor); // hide cursor546}547}548mouse_mode = wanted_mouse_mode;549550if (mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN) {551//flush pending motion events552_flush_mouse_motion();553WindowID window_id = _get_focused_window_or_popup();554if (!windows.has(window_id)) {555window_id = MAIN_WINDOW_ID;556}557WindowData &window = windows[window_id];558559if (XGrabPointer(560x11_display, window.x11_window, True,561ButtonPressMask | ButtonReleaseMask | PointerMotionMask,562GrabModeAsync, GrabModeAsync, window.x11_window, None, CurrentTime) != GrabSuccess) {563ERR_PRINT("NO GRAB");564}565566if (mouse_mode == MOUSE_MODE_CAPTURED) {567center.x = window.size.width / 2;568center.y = window.size.height / 2;569570XWarpPointer(x11_display, None, window.x11_window,5710, 0, 0, 0, (int)center.x, (int)center.y);572573Input::get_singleton()->set_mouse_position(center);574}575} else {576do_mouse_warp = false;577}578579XFlush(x11_display);580}581582void DisplayServerX11::mouse_set_mode(MouseMode p_mode) {583ERR_FAIL_INDEX(p_mode, MouseMode::MOUSE_MODE_MAX);584if (p_mode == mouse_mode_base) {585return;586}587mouse_mode_base = p_mode;588_mouse_update_mode();589}590591DisplayServerX11::MouseMode DisplayServerX11::mouse_get_mode() const {592return mouse_mode;593}594595void DisplayServerX11::mouse_set_mode_override(MouseMode p_mode) {596ERR_FAIL_INDEX(p_mode, MouseMode::MOUSE_MODE_MAX);597if (p_mode == mouse_mode_override) {598return;599}600mouse_mode_override = p_mode;601_mouse_update_mode();602}603604DisplayServerX11::MouseMode DisplayServerX11::mouse_get_mode_override() const {605return mouse_mode_override;606}607608void DisplayServerX11::mouse_set_mode_override_enabled(bool p_override_enabled) {609if (p_override_enabled == mouse_mode_override_enabled) {610return;611}612mouse_mode_override_enabled = p_override_enabled;613_mouse_update_mode();614}615616bool DisplayServerX11::mouse_is_mode_override_enabled() const {617return mouse_mode_override_enabled;618}619620void DisplayServerX11::warp_mouse(const Point2i &p_position) {621_THREAD_SAFE_METHOD_622623if (mouse_mode == MOUSE_MODE_CAPTURED) {624last_mouse_pos = p_position;625} else {626WindowID window_id = _get_focused_window_or_popup();627if (!windows.has(window_id)) {628window_id = MAIN_WINDOW_ID;629}630631XWarpPointer(x11_display, None, windows[window_id].x11_window,6320, 0, 0, 0, (int)p_position.x, (int)p_position.y);633}634}635636Point2i DisplayServerX11::mouse_get_position() const {637int number_of_screens = XScreenCount(x11_display);638for (int i = 0; i < number_of_screens; i++) {639Window root, child;640int root_x, root_y, win_x, win_y;641unsigned int mask;642if (XQueryPointer(x11_display, XRootWindow(x11_display, i), &root, &child, &root_x, &root_y, &win_x, &win_y, &mask)) {643XWindowAttributes root_attrs;644XGetWindowAttributes(x11_display, root, &root_attrs);645646return Vector2i(root_attrs.x + root_x, root_attrs.y + root_y);647}648}649return Vector2i();650}651652BitField<MouseButtonMask> DisplayServerX11::mouse_get_button_state() const {653int number_of_screens = XScreenCount(x11_display);654for (int i = 0; i < number_of_screens; i++) {655Window root, child;656int root_x, root_y, win_x, win_y;657unsigned int mask;658if (XQueryPointer(x11_display, XRootWindow(x11_display, i), &root, &child, &root_x, &root_y, &win_x, &win_y, &mask)) {659BitField<MouseButtonMask> last_button_state = MouseButtonMask::NONE;660661if (mask & Button1Mask) {662last_button_state.set_flag(MouseButtonMask::LEFT);663}664if (mask & Button2Mask) {665last_button_state.set_flag(MouseButtonMask::MIDDLE);666}667if (mask & Button3Mask) {668last_button_state.set_flag(MouseButtonMask::RIGHT);669}670if (mask & Button4Mask) {671last_button_state.set_flag(MouseButtonMask::MB_XBUTTON1);672}673if (mask & Button5Mask) {674last_button_state.set_flag(MouseButtonMask::MB_XBUTTON2);675}676677return last_button_state;678}679}680return MouseButtonMask::NONE;681}682683void DisplayServerX11::clipboard_set(const String &p_text) {684_THREAD_SAFE_METHOD_685686{687// The clipboard content can be accessed while polling for events.688MutexLock mutex_lock(events_mutex);689internal_clipboard = p_text;690}691692XSetSelectionOwner(x11_display, XA_PRIMARY, windows[MAIN_WINDOW_ID].x11_window, CurrentTime);693XSetSelectionOwner(x11_display, XInternAtom(x11_display, "CLIPBOARD", 0), windows[MAIN_WINDOW_ID].x11_window, CurrentTime);694}695696void DisplayServerX11::clipboard_set_primary(const String &p_text) {697_THREAD_SAFE_METHOD_698if (!p_text.is_empty()) {699{700// The clipboard content can be accessed while polling for events.701MutexLock mutex_lock(events_mutex);702internal_clipboard_primary = p_text;703}704705XSetSelectionOwner(x11_display, XA_PRIMARY, windows[MAIN_WINDOW_ID].x11_window, CurrentTime);706XSetSelectionOwner(x11_display, XInternAtom(x11_display, "PRIMARY", 0), windows[MAIN_WINDOW_ID].x11_window, CurrentTime);707}708}709710Bool DisplayServerX11::_predicate_clipboard_selection(Display *display, XEvent *event, XPointer arg) {711if (event->type == SelectionNotify && event->xselection.requestor == *(Window *)arg) {712return True;713} else {714return False;715}716}717718Bool DisplayServerX11::_predicate_clipboard_incr(Display *display, XEvent *event, XPointer arg) {719if (event->type == PropertyNotify && event->xproperty.state == PropertyNewValue && event->xproperty.atom == *(Atom *)arg) {720return True;721} else {722return False;723}724}725726String DisplayServerX11::_clipboard_get_impl(Atom p_source, Window x11_window, Atom target) const {727String ret;728729Window selection_owner = XGetSelectionOwner(x11_display, p_source);730if (selection_owner == x11_window) {731static const char *target_type = "PRIMARY";732if (p_source != None && get_atom_name(x11_display, p_source) == target_type) {733return internal_clipboard_primary;734} else {735return internal_clipboard;736}737}738739if (selection_owner != None) {740// Block events polling while processing selection events.741MutexLock mutex_lock(events_mutex);742743Atom selection = XA_PRIMARY;744XConvertSelection(x11_display, p_source, target, selection,745x11_window, CurrentTime);746747XFlush(x11_display);748749// Blocking wait for predicate to be True and remove the event from the queue.750XEvent event;751XIfEvent(x11_display, &event, _predicate_clipboard_selection, (XPointer)&x11_window);752753// Do not get any data, see how much data is there.754Atom type;755int format, result;756unsigned long len, bytes_left, dummy;757unsigned char *data;758XGetWindowProperty(x11_display, x11_window,759selection, // Tricky..7600, 0, // offset - len7610, // Delete 0==FALSE762AnyPropertyType, // flag763&type, // return type764&format, // return format765&len, &bytes_left, // data length766&data);767768if (data) {769XFree(data);770}771772if (type == XInternAtom(x11_display, "INCR", 0)) {773// Data is going to be received incrementally.774DEBUG_LOG_X11("INCR selection started.\n");775776LocalVector<uint8_t> incr_data;777uint32_t data_size = 0;778bool success = false;779780// Delete INCR property to notify the owner.781XDeleteProperty(x11_display, x11_window, type);782783// Process events from the queue.784bool done = false;785while (!done) {786if (!_wait_for_events()) {787// Error or timeout, abort.788break;789}790791// Non-blocking wait for next event and remove it from the queue.792XEvent ev;793while (XCheckIfEvent(x11_display, &ev, _predicate_clipboard_incr, (XPointer)&selection)) {794result = XGetWindowProperty(x11_display, x11_window,795selection, // selection type7960, LONG_MAX, // offset - len797True, // delete property to notify the owner798AnyPropertyType, // flag799&type, // return type800&format, // return format801&len, &bytes_left, // data length802&data);803804DEBUG_LOG_X11("PropertyNotify: len=%lu, format=%i\n", len, format);805806if (result == Success) {807if (data && (len > 0)) {808uint32_t prev_size = incr_data.size();809if (prev_size == 0) {810// First property contains initial data size.811unsigned long initial_size = *(unsigned long *)data;812incr_data.resize(initial_size);813} else {814// New chunk, resize to be safe and append data.815incr_data.resize(MAX(data_size + len, prev_size));816memcpy(incr_data.ptr() + data_size, data, len);817data_size += len;818}819} else {820// Last chunk, process finished.821done = true;822success = true;823}824} else {825print_verbose("Failed to get selection data chunk.");826done = true;827}828829if (data) {830XFree(data);831}832833if (done) {834break;835}836}837}838839if (success && (data_size > 0)) {840ret.append_utf8((const char *)incr_data.ptr(), data_size);841}842} else if (bytes_left > 0) {843// Data is ready and can be processed all at once.844result = XGetWindowProperty(x11_display, x11_window,845selection, 0, bytes_left, 0,846AnyPropertyType, &type, &format,847&len, &dummy, &data);848849if (result == Success) {850ret.append_utf8((const char *)data);851} else {852print_verbose("Failed to get selection data.");853}854855if (data) {856XFree(data);857}858}859}860861return ret;862}863864Atom DisplayServerX11::_clipboard_get_image_target(Atom p_source, Window x11_window) const {865Atom target = XInternAtom(x11_display, "TARGETS", 0);866Atom png = XInternAtom(x11_display, "image/png", 0);867Atom *valid_targets = nullptr;868unsigned long atom_count = 0;869870Window selection_owner = XGetSelectionOwner(x11_display, p_source);871if (selection_owner != None && selection_owner != x11_window) {872// Block events polling while processing selection events.873MutexLock mutex_lock(events_mutex);874875Atom selection = XA_PRIMARY;876XConvertSelection(x11_display, p_source, target, selection, x11_window, CurrentTime);877878XFlush(x11_display);879880// Blocking wait for predicate to be True and remove the event from the queue.881XEvent event;882XIfEvent(x11_display, &event, _predicate_clipboard_selection, (XPointer)&x11_window);883// Do not get any data, see how much data is there.884Atom type;885int format, result;886unsigned long len, bytes_left, dummy;887XGetWindowProperty(x11_display, x11_window,888selection, // Tricky..8890, 0, // offset - len8900, // Delete 0==FALSE891XA_ATOM, // flag892&type, // return type893&format, // return format894&len, &bytes_left, // data length895(unsigned char **)&valid_targets);896897if (valid_targets) {898XFree(valid_targets);899valid_targets = nullptr;900}901902if (type == XA_ATOM && bytes_left > 0) {903// Data is ready and can be processed all at once.904result = XGetWindowProperty(x11_display, x11_window,905selection, 0, bytes_left / 4, 0,906XA_ATOM, &type, &format,907&len, &dummy, (unsigned char **)&valid_targets);908if (result == Success) {909atom_count = len;910} else {911print_verbose("Failed to get selection data.");912return None;913}914} else {915return None;916}917} else {918return None;919}920for (unsigned long i = 0; i < atom_count; i++) {921Atom atom = valid_targets[i];922if (atom == png) {923XFree(valid_targets);924return png;925}926}927928XFree(valid_targets);929return None;930}931932String DisplayServerX11::_clipboard_get(Atom p_source, Window x11_window) const {933String ret;934Atom utf8_atom = XInternAtom(x11_display, "UTF8_STRING", True);935if (utf8_atom != None) {936ret = _clipboard_get_impl(p_source, x11_window, utf8_atom);937}938if (ret.is_empty()) {939ret = _clipboard_get_impl(p_source, x11_window, XA_STRING);940}941return ret;942}943944String DisplayServerX11::clipboard_get() const {945_THREAD_SAFE_METHOD_946947String ret;948ret = _clipboard_get(XInternAtom(x11_display, "CLIPBOARD", 0), windows[MAIN_WINDOW_ID].x11_window);949950if (ret.is_empty()) {951ret = _clipboard_get(XA_PRIMARY, windows[MAIN_WINDOW_ID].x11_window);952}953954return ret;955}956957String DisplayServerX11::clipboard_get_primary() const {958_THREAD_SAFE_METHOD_959960String ret;961ret = _clipboard_get(XInternAtom(x11_display, "PRIMARY", 0), windows[MAIN_WINDOW_ID].x11_window);962963if (ret.is_empty()) {964ret = _clipboard_get(XA_PRIMARY, windows[MAIN_WINDOW_ID].x11_window);965}966967return ret;968}969970Ref<Image> DisplayServerX11::clipboard_get_image() const {971_THREAD_SAFE_METHOD_972Atom clipboard = XInternAtom(x11_display, "CLIPBOARD", 0);973Window x11_window = windows[MAIN_WINDOW_ID].x11_window;974Ref<Image> ret;975Atom target = _clipboard_get_image_target(clipboard, x11_window);976if (target == None) {977return ret;978}979980Window selection_owner = XGetSelectionOwner(x11_display, clipboard);981982if (selection_owner != None && selection_owner != x11_window) {983// Block events polling while processing selection events.984MutexLock mutex_lock(events_mutex);985986// Identifier for the property the other window987// will send the converted data to.988Atom transfer_prop = XA_PRIMARY;989XConvertSelection(x11_display,990clipboard, // source selection991target, // format to convert to992transfer_prop, // output property993x11_window, CurrentTime);994995XFlush(x11_display);996997// Blocking wait for predicate to be True and remove the event from the queue.998XEvent event;999XIfEvent(x11_display, &event, _predicate_clipboard_selection, (XPointer)&x11_window);10001001// Do not get any data, see how much data is there.1002Atom type;1003int format, result;1004unsigned long len, bytes_left, dummy;1005unsigned char *data;1006XGetWindowProperty(x11_display, x11_window,1007transfer_prop, // Property data is transferred through10080, 1, // offset, len (4 so we can get the size if INCR is used)10090, // Delete 0==FALSE1010AnyPropertyType, // flag1011&type, // return type1012&format, // return format1013&len, &bytes_left, // data length1014&data);10151016if (type == XInternAtom(x11_display, "INCR", 0)) {1017ERR_FAIL_COND_V_MSG(len != 1, ret, "Incremental transfer initial value was not length.");10181019// Data is going to be received incrementally.1020DEBUG_LOG_X11("INCR selection started.\n");10211022LocalVector<uint8_t> incr_data;1023uint32_t data_size = 0;1024bool success = false;10251026// Initial response is the lower bound of the length of the transferred data.1027incr_data.resize(*(unsigned long *)data);1028XFree(data);1029data = nullptr;10301031// Delete INCR property to notify the owner.1032XDeleteProperty(x11_display, x11_window, transfer_prop);10331034// Process events from the queue.1035bool done = false;1036while (!done) {1037if (!_wait_for_events()) {1038// Error or timeout, abort.1039break;1040}1041// Non-blocking wait for next event and remove it from the queue.1042XEvent ev;1043while (XCheckIfEvent(x11_display, &ev, _predicate_clipboard_incr, (XPointer)&transfer_prop)) {1044result = XGetWindowProperty(x11_display, x11_window,1045transfer_prop, // output property10460, LONG_MAX, // offset - len1047True, // delete property to notify the owner1048AnyPropertyType, // flag1049&type, // return type1050&format, // return format1051&len, &bytes_left, // data length1052&data);10531054DEBUG_LOG_X11("PropertyNotify: len=%lu, format=%i\n", len, format);10551056if (result == Success) {1057if (data && (len > 0)) {1058uint32_t prev_size = incr_data.size();1059// New chunk, resize to be safe and append data.1060incr_data.resize(MAX(data_size + len, prev_size));1061memcpy(incr_data.ptr() + data_size, data, len);1062data_size += len;1063} else if (!(format == 0 && len == 0)) {1064// For unclear reasons the first GetWindowProperty always returns a length and format of 0.1065// Otherwise, last chunk, process finished.1066done = true;1067success = true;1068}1069} else {1070print_verbose("Failed to get selection data chunk.");1071done = true;1072}10731074if (data) {1075XFree(data);1076data = nullptr;1077}10781079if (done) {1080break;1081}1082}1083}10841085if (success && (data_size > 0)) {1086ret.instantiate();1087PNGDriverCommon::png_to_image(incr_data.ptr(), incr_data.size(), false, ret);1088}1089} else if (bytes_left > 0) {1090if (data) {1091XFree(data);1092data = nullptr;1093}1094// Data is ready and can be processed all at once.1095result = XGetWindowProperty(x11_display, x11_window,1096transfer_prop, 0, bytes_left + 4, 0,1097AnyPropertyType, &type, &format,1098&len, &dummy, &data);1099if (result == Success) {1100ret.instantiate();1101PNGDriverCommon::png_to_image((uint8_t *)data, bytes_left, false, ret);1102} else {1103print_verbose("Failed to get selection data.");1104}11051106if (data) {1107XFree(data);1108}1109}1110}11111112return ret;1113}11141115bool DisplayServerX11::clipboard_has_image() const {1116Atom target = _clipboard_get_image_target(1117XInternAtom(x11_display, "CLIPBOARD", 0),1118windows[MAIN_WINDOW_ID].x11_window);1119return target != None;1120}11211122Bool DisplayServerX11::_predicate_clipboard_save_targets(Display *display, XEvent *event, XPointer arg) {1123if (event->xany.window == *(Window *)arg) {1124return (event->type == SelectionRequest) ||1125(event->type == SelectionNotify);1126} else {1127return False;1128}1129}11301131void DisplayServerX11::_clipboard_transfer_ownership(Atom p_source, Window x11_window) const {1132_THREAD_SAFE_METHOD_11331134Window selection_owner = XGetSelectionOwner(x11_display, p_source);11351136if (selection_owner != x11_window) {1137return;1138}11391140// Block events polling while processing selection events.1141MutexLock mutex_lock(events_mutex);11421143Atom clipboard_manager = XInternAtom(x11_display, "CLIPBOARD_MANAGER", False);1144Atom save_targets = XInternAtom(x11_display, "SAVE_TARGETS", False);1145XConvertSelection(x11_display, clipboard_manager, save_targets, None,1146x11_window, CurrentTime);11471148// Process events from the queue.1149while (true) {1150if (!_wait_for_events()) {1151// Error or timeout, abort.1152break;1153}11541155// Non-blocking wait for next event and remove it from the queue.1156XEvent ev;1157while (XCheckIfEvent(x11_display, &ev, _predicate_clipboard_save_targets, (XPointer)&x11_window)) {1158switch (ev.type) {1159case SelectionRequest:1160_handle_selection_request_event(&(ev.xselectionrequest));1161break;11621163case SelectionNotify: {1164if (ev.xselection.target == save_targets) {1165// Once SelectionNotify is received, we're done whether it succeeded or not.1166return;1167}11681169break;1170}1171}1172}1173}1174}11751176int DisplayServerX11::get_screen_count() const {1177_THREAD_SAFE_METHOD_1178int count = 0;11791180// Using Xinerama Extension1181int event_base, error_base;1182if (xinerama_ext_ok && XineramaQueryExtension(x11_display, &event_base, &error_base)) {1183XineramaScreenInfo *xsi = XineramaQueryScreens(x11_display, &count);1184XFree(xsi);1185}1186if (count == 0) {1187count = XScreenCount(x11_display);1188}11891190return count;1191}11921193int DisplayServerX11::get_primary_screen() const {1194int event_base, error_base;1195if (xinerama_ext_ok && XineramaQueryExtension(x11_display, &event_base, &error_base)) {1196return 0;1197} else {1198return XDefaultScreen(x11_display);1199}1200}12011202int DisplayServerX11::get_keyboard_focus_screen() const {1203int count = get_screen_count();1204if (count < 2) {1205// Early exit with single monitor.1206return 0;1207}12081209Window focus = 0;1210int revert_to = 0;12111212XGetInputFocus(x11_display, &focus, &revert_to);1213if (focus) {1214Window focus_child = 0;1215int x = 0, y = 0;1216XTranslateCoordinates(x11_display, focus, DefaultRootWindow(x11_display), 0, 0, &x, &y, &focus_child);12171218XWindowAttributes xwa;1219XGetWindowAttributes(x11_display, focus, &xwa);1220Rect2i window_rect = Rect2i(x, y, xwa.width, xwa.height);12211222// Find which monitor has the largest overlap with the given window.1223int screen_index = 0;1224int max_area = 0;1225for (int i = 0; i < count; i++) {1226Rect2i screen_rect = _screen_get_rect(i);1227Rect2i intersection = screen_rect.intersection(window_rect);1228int area = intersection.get_area();1229if (area > max_area) {1230max_area = area;1231screen_index = i;1232}1233}1234return screen_index;1235}12361237return get_primary_screen();1238}12391240Rect2i DisplayServerX11::_screen_get_rect(int p_screen) const {1241Rect2i rect(0, 0, 0, 0);12421243p_screen = _get_screen_index(p_screen);1244int screen_count = get_screen_count();1245ERR_FAIL_INDEX_V(p_screen, screen_count, Rect2i());12461247// Using Xinerama Extension.1248bool found = false;1249int event_base, error_base;1250if (xinerama_ext_ok && XineramaQueryExtension(x11_display, &event_base, &error_base)) {1251int count;1252XineramaScreenInfo *xsi = XineramaQueryScreens(x11_display, &count);1253if (xsi) {1254if (count > 0) {1255// Check if screen is valid.1256if (p_screen < count) {1257rect.position.x = xsi[p_screen].x_org;1258rect.position.y = xsi[p_screen].y_org;1259rect.size.width = xsi[p_screen].width;1260rect.size.height = xsi[p_screen].height;1261found = true;1262} else {1263ERR_PRINT(vformat("Invalid screen index: %d (count: %d).", p_screen, count));1264}1265}1266XFree(xsi);1267}1268}12691270if (!found) {1271int count = XScreenCount(x11_display);1272if (p_screen < count) {1273Window root = XRootWindow(x11_display, p_screen);1274XWindowAttributes xwa;1275XGetWindowAttributes(x11_display, root, &xwa);1276rect.position.x = xwa.x;1277rect.position.y = xwa.y;1278rect.size.width = xwa.width;1279rect.size.height = xwa.height;1280} else {1281ERR_PRINT(vformat("Invalid screen index: %d (count: %d).", p_screen, count));1282}1283}12841285return rect;1286}12871288Point2i DisplayServerX11::screen_get_position(int p_screen) const {1289_THREAD_SAFE_METHOD_12901291return _screen_get_rect(p_screen).position;1292}12931294Size2i DisplayServerX11::screen_get_size(int p_screen) const {1295_THREAD_SAFE_METHOD_12961297return _screen_get_rect(p_screen).size;1298}12991300// A Handler to avoid crashing on non-fatal X errors by default.1301//1302// The original X11 error formatter `_XPrintDefaultError` is defined here:1303// https://gitlab.freedesktop.org/xorg/lib/libx11/-/blob/e45ca7b41dcd3ace7681d6897505f85d374640f2/src/XlibInt.c#L13221304// It is not exposed through the API, accesses X11 internals,1305// and is much more complex, so this is a less complete simplified error X11 printer.1306int default_window_error_handler(Display *display, XErrorEvent *error) {1307static char message[1024];1308XGetErrorText(display, error->error_code, message, sizeof(message));13091310ERR_PRINT(vformat("Unhandled XServer error: %s"1311"\n Major opcode of failed request: %d"1312"\n Serial number of failed request: %d"1313"\n Current serial number in output stream: %d",1314String::utf8(message), (uint64_t)error->request_code, (uint64_t)error->minor_code, (uint64_t)error->serial));1315return 0;1316}13171318bool g_bad_window = false;1319int bad_window_error_handler(Display *display, XErrorEvent *error) {1320if (error->error_code == BadWindow) {1321g_bad_window = true;1322} else {1323return default_window_error_handler(display, error);1324}1325return 0;1326}13271328Rect2i DisplayServerX11::screen_get_usable_rect(int p_screen) const {1329_THREAD_SAFE_METHOD_13301331p_screen = _get_screen_index(p_screen);1332int screen_count = get_screen_count();1333ERR_FAIL_INDEX_V(p_screen, screen_count, Rect2i());13341335bool is_multiscreen = screen_count > 1;13361337// Use full monitor size as fallback.1338Rect2i rect = _screen_get_rect(p_screen);13391340// There's generally only one screen reported by xlib even in multi-screen setup,1341// in this case it's just one virtual screen composed of all physical monitors.1342int x11_screen_count = ScreenCount(x11_display);1343Window x11_window = RootWindow(x11_display, p_screen < x11_screen_count ? p_screen : 0);13441345Atom type;1346int format = 0;1347unsigned long remaining = 0;13481349// Find active desktop for the root window.1350unsigned int desktop_index = 0;1351Atom desktop_prop = XInternAtom(x11_display, "_NET_CURRENT_DESKTOP", True);1352if (desktop_prop != None) {1353unsigned long desktop_len = 0;1354unsigned char *desktop_data = nullptr;1355if (XGetWindowProperty(x11_display, x11_window, desktop_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &desktop_len, &remaining, &desktop_data) == Success) {1356if ((format == 32) && (desktop_len > 0) && desktop_data) {1357desktop_index = (unsigned int)desktop_data[0];1358}1359if (desktop_data) {1360XFree(desktop_data);1361}1362}1363}13641365bool use_simple_method = true;13661367// First check for GTK work area, which is more accurate for multi-screen setup.1368if (is_multiscreen) {1369// Use already calculated work area when available.1370Atom gtk_workareas_prop = XInternAtom(x11_display, "_GTK_WORKAREAS", False);1371if (gtk_workareas_prop != None) {1372char gtk_workarea_prop_name[32];1373snprintf(gtk_workarea_prop_name, 32, "_GTK_WORKAREAS_D%d", desktop_index);1374Atom gtk_workarea_prop = XInternAtom(x11_display, gtk_workarea_prop_name, True);1375if (gtk_workarea_prop != None) {1376unsigned long workarea_len = 0;1377unsigned char *workarea_data = nullptr;1378if (XGetWindowProperty(x11_display, x11_window, gtk_workarea_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &workarea_len, &remaining, &workarea_data) == Success) {1379if ((format == 32) && (workarea_len % 4 == 0) && workarea_data) {1380long *rect_data = (long *)workarea_data;1381for (uint32_t data_offset = 0; data_offset < workarea_len; data_offset += 4) {1382Rect2i workarea_rect;1383workarea_rect.position.x = rect_data[data_offset];1384workarea_rect.position.y = rect_data[data_offset + 1];1385workarea_rect.size.x = rect_data[data_offset + 2];1386workarea_rect.size.y = rect_data[data_offset + 3];13871388// Intersect with actual monitor size to find the correct area,1389// because areas are not in the same order as screens from Xinerama.1390if (rect.grow(-1).intersects(workarea_rect)) {1391rect = rect.intersection(workarea_rect);1392XFree(workarea_data);1393return rect;1394}1395}1396}1397}1398if (workarea_data) {1399XFree(workarea_data);1400}1401}1402}14031404// Fallback to calculating work area by hand from struts.1405Atom client_list_prop = XInternAtom(x11_display, "_NET_CLIENT_LIST", True);1406if (client_list_prop != None) {1407unsigned long clients_len = 0;1408unsigned char *clients_data = nullptr;1409if (XGetWindowProperty(x11_display, x11_window, client_list_prop, 0, LONG_MAX, False, XA_WINDOW, &type, &format, &clients_len, &remaining, &clients_data) == Success) {1410if ((format == 32) && (clients_len > 0) && clients_data) {1411Window *windows_data = (Window *)clients_data;14121413Rect2i desktop_rect;1414bool desktop_valid = false;14151416// Get full desktop size.1417{1418Atom desktop_geometry_prop = XInternAtom(x11_display, "_NET_DESKTOP_GEOMETRY", True);1419if (desktop_geometry_prop != None) {1420unsigned long geom_len = 0;1421unsigned char *geom_data = nullptr;1422if (XGetWindowProperty(x11_display, x11_window, desktop_geometry_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &geom_len, &remaining, &geom_data) == Success) {1423if ((format == 32) && (geom_len >= 2) && geom_data) {1424desktop_valid = true;1425long *size_data = (long *)geom_data;1426desktop_rect.size.x = size_data[0];1427desktop_rect.size.y = size_data[1];1428}1429}1430if (geom_data) {1431XFree(geom_data);1432}1433}1434}14351436// Get full desktop position.1437if (desktop_valid) {1438Atom desktop_viewport_prop = XInternAtom(x11_display, "_NET_DESKTOP_VIEWPORT", True);1439if (desktop_viewport_prop != None) {1440unsigned long viewport_len = 0;1441unsigned char *viewport_data = nullptr;1442if (XGetWindowProperty(x11_display, x11_window, desktop_viewport_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &viewport_len, &remaining, &viewport_data) == Success) {1443if ((format == 32) && (viewport_len >= 2) && viewport_data) {1444desktop_valid = true;1445long *pos_data = (long *)viewport_data;1446desktop_rect.position.x = pos_data[0];1447desktop_rect.position.y = pos_data[1];1448}1449}1450if (viewport_data) {1451XFree(viewport_data);1452}1453}1454}14551456if (desktop_valid) {1457// Handle bad window errors silently because there's no other way to check1458// that one of the windows has been destroyed in the meantime.1459int (*oldHandler)(Display *, XErrorEvent *) = XSetErrorHandler(&bad_window_error_handler);14601461for (unsigned long win_index = 0; win_index < clients_len; ++win_index) {1462g_bad_window = false;14631464// Remove strut size from desktop size to get a more accurate result.1465bool strut_found = false;1466unsigned long strut_len = 0;1467unsigned char *strut_data = nullptr;1468Atom strut_partial_prop = XInternAtom(x11_display, "_NET_WM_STRUT_PARTIAL", True);1469if (strut_partial_prop != None) {1470if (XGetWindowProperty(x11_display, windows_data[win_index], strut_partial_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &strut_len, &remaining, &strut_data) == Success) {1471strut_found = true;1472}1473}1474// Fallback to older strut property.1475if (!g_bad_window && !strut_found) {1476Atom strut_prop = XInternAtom(x11_display, "_NET_WM_STRUT", True);1477if (strut_prop != None) {1478if (XGetWindowProperty(x11_display, windows_data[win_index], strut_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &strut_len, &remaining, &strut_data) == Success) {1479strut_found = true;1480}1481}1482}1483if (!g_bad_window && strut_found && (format == 32) && (strut_len >= 4) && strut_data) {1484use_simple_method = false;14851486long *struts = (long *)strut_data;14871488long left = struts[0];1489long right = struts[1];1490long top = struts[2];1491long bottom = struts[3];14921493long left_start_y, left_end_y, right_start_y, right_end_y;1494long top_start_x, top_end_x, bottom_start_x, bottom_end_x;14951496if (strut_len >= 12) {1497left_start_y = struts[4];1498left_end_y = struts[5];1499right_start_y = struts[6];1500right_end_y = struts[7];1501top_start_x = struts[8];1502top_end_x = struts[9];1503bottom_start_x = struts[10];1504bottom_end_x = struts[11];1505} else {1506left_start_y = 0;1507left_end_y = desktop_rect.size.y;1508right_start_y = 0;1509right_end_y = desktop_rect.size.y;1510top_start_x = 0;1511top_end_x = desktop_rect.size.x;1512bottom_start_x = 0;1513bottom_end_x = desktop_rect.size.x;1514}15151516const Point2i &pos = desktop_rect.position;1517const Size2i &size = desktop_rect.size;15181519Rect2i left_rect(pos.x, pos.y + left_start_y, left, left_end_y - left_start_y);1520if (left_rect.size.x > 0) {1521Rect2i intersection = rect.intersection(left_rect);1522if (intersection.has_area() && intersection.size.x < rect.size.x) {1523rect.position.x = left_rect.size.x;1524rect.size.x = rect.size.x - intersection.size.x;1525}1526}15271528Rect2i right_rect(pos.x + size.x - right, pos.y + right_start_y, right, right_end_y - right_start_y);1529if (right_rect.size.x > 0) {1530Rect2i intersection = rect.intersection(right_rect);1531if (intersection.has_area() && right_rect.size.x < rect.size.x) {1532rect.size.x = intersection.position.x - rect.position.x;1533}1534}15351536Rect2i top_rect(pos.x + top_start_x, pos.y, top_end_x - top_start_x, top);1537if (top_rect.size.y > 0) {1538Rect2i intersection = rect.intersection(top_rect);1539if (intersection.has_area() && intersection.size.y < rect.size.y) {1540rect.position.y = top_rect.size.y;1541rect.size.y = rect.size.y - intersection.size.y;1542}1543}15441545Rect2i bottom_rect(pos.x + bottom_start_x, pos.y + size.y - bottom, bottom_end_x - bottom_start_x, bottom);1546if (bottom_rect.size.y > 0) {1547Rect2i intersection = rect.intersection(bottom_rect);1548if (intersection.has_area() && right_rect.size.y < rect.size.y) {1549rect.size.y = intersection.position.y - rect.position.y;1550}1551}1552}1553if (strut_data) {1554XFree(strut_data);1555}1556}15571558// Restore default error handler.1559XSetErrorHandler(oldHandler);1560}1561}1562}1563if (clients_data) {1564XFree(clients_data);1565}1566}1567}15681569// Single screen or fallback for multi screen.1570if (use_simple_method) {1571// Get desktop available size from the global work area.1572Atom workarea_prop = XInternAtom(x11_display, "_NET_WORKAREA", True);1573if (workarea_prop != None) {1574unsigned long workarea_len = 0;1575unsigned char *workarea_data = nullptr;1576if (XGetWindowProperty(x11_display, x11_window, workarea_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &workarea_len, &remaining, &workarea_data) == Success) {1577if ((format == 32) && (workarea_len >= ((desktop_index + 1) * 4)) && workarea_data) {1578long *rect_data = (long *)workarea_data;1579int data_offset = desktop_index * 4;1580Rect2i workarea_rect;1581workarea_rect.position.x = rect_data[data_offset];1582workarea_rect.position.y = rect_data[data_offset + 1];1583workarea_rect.size.x = rect_data[data_offset + 2];1584workarea_rect.size.y = rect_data[data_offset + 3];15851586// Intersect with actual monitor size to get a proper approximation in multi-screen setup.1587if (!is_multiscreen) {1588rect = workarea_rect;1589} else if (rect.intersects(workarea_rect)) {1590rect = rect.intersection(workarea_rect);1591}1592}1593}1594if (workarea_data) {1595XFree(workarea_data);1596}1597}1598}15991600return rect;1601}16021603Rect2i DisplayServerX11::_screens_get_full_rect() const {1604Rect2i full_rect;16051606int count = get_screen_count();1607for (int i = 0; i < count; i++) {1608if (i == 0) {1609full_rect = _screen_get_rect(i);1610continue;1611}16121613Rect2i screen_rect = _screen_get_rect(i);1614if (full_rect.position.x > screen_rect.position.x) {1615full_rect.size.x += full_rect.position.x - screen_rect.position.x;1616full_rect.position.x = screen_rect.position.x;1617}1618if (full_rect.position.y > screen_rect.position.y) {1619full_rect.size.y += full_rect.position.y - screen_rect.position.y;1620full_rect.position.y = screen_rect.position.y;1621}1622if (full_rect.position.x + full_rect.size.x < screen_rect.position.x + screen_rect.size.x) {1623full_rect.size.x = screen_rect.position.x + screen_rect.size.x - full_rect.position.x;1624}1625if (full_rect.position.y + full_rect.size.y < screen_rect.position.y + screen_rect.size.y) {1626full_rect.size.y = screen_rect.position.y + screen_rect.size.y - full_rect.position.y;1627}1628}16291630return full_rect;1631}16321633int DisplayServerX11::screen_get_dpi(int p_screen) const {1634_THREAD_SAFE_METHOD_16351636p_screen = _get_screen_index(p_screen);1637int screen_count = get_screen_count();1638ERR_FAIL_INDEX_V(p_screen, screen_count, 96);16391640//Get physical monitor Dimensions through XRandR and calculate dpi1641Size2i sc = screen_get_size(p_screen);1642if (xrandr_ext_ok) {1643int count = 0;1644if (xrr_get_monitors) {1645xrr_monitor_info *monitors = xrr_get_monitors(x11_display, windows[MAIN_WINDOW_ID].x11_window, true, &count);1646if (p_screen < count) {1647double xdpi = sc.width / (double)monitors[p_screen].mwidth * 25.4;1648double ydpi = sc.height / (double)monitors[p_screen].mheight * 25.4;1649xrr_free_monitors(monitors);1650return (xdpi + ydpi) / 2;1651}1652xrr_free_monitors(monitors);1653} else if (p_screen == 0) {1654XRRScreenSize *sizes = XRRSizes(x11_display, 0, &count);1655if (sizes) {1656double xdpi = sc.width / (double)sizes[0].mwidth * 25.4;1657double ydpi = sc.height / (double)sizes[0].mheight * 25.4;1658return (xdpi + ydpi) / 2;1659}1660}1661}16621663int width_mm = DisplayWidthMM(x11_display, p_screen);1664int height_mm = DisplayHeightMM(x11_display, p_screen);1665double xdpi = (width_mm ? sc.width / (double)width_mm * 25.4 : 0);1666double ydpi = (height_mm ? sc.height / (double)height_mm * 25.4 : 0);1667if (xdpi || ydpi) {1668return (xdpi + ydpi) / (xdpi && ydpi ? 2 : 1);1669}16701671//could not get dpi1672return 96;1673}16741675int get_image_errorhandler(Display *dpy, XErrorEvent *ev) {1676return 0;1677}16781679Color DisplayServerX11::screen_get_pixel(const Point2i &p_position) const {1680Point2i pos = p_position;16811682if (xwayland) {1683return Color();1684}16851686int (*old_handler)(Display *, XErrorEvent *) = XSetErrorHandler(&get_image_errorhandler);16871688Color color;1689int number_of_screens = XScreenCount(x11_display);1690for (int i = 0; i < number_of_screens; i++) {1691Window root = XRootWindow(x11_display, i);1692XWindowAttributes root_attrs;1693XGetWindowAttributes(x11_display, root, &root_attrs);1694if ((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)) {1695XImage *image = XGetImage(x11_display, root, pos.x, pos.y, 1, 1, AllPlanes, XYPixmap);1696if (image) {1697XColor c;1698c.pixel = XGetPixel(image, 0, 0);1699XDestroyImage(image);1700XQueryColor(x11_display, XDefaultColormap(x11_display, i), &c);1701color = Color(float(c.red) / 65535.0, float(c.green) / 65535.0, float(c.blue) / 65535.0, 1.0);1702break;1703}1704}1705}17061707XSetErrorHandler(old_handler);17081709return color;1710}17111712Ref<Image> DisplayServerX11::screen_get_image(int p_screen) const {1713ERR_FAIL_INDEX_V(p_screen, get_screen_count(), Ref<Image>());17141715p_screen = _get_screen_index(p_screen);1716int screen_count = get_screen_count();1717ERR_FAIL_INDEX_V(p_screen, screen_count, Ref<Image>());17181719if (xwayland) {1720return Ref<Image>();1721}17221723int (*old_handler)(Display *, XErrorEvent *) = XSetErrorHandler(&get_image_errorhandler);17241725XImage *image = nullptr;17261727bool found = false;1728int event_base, error_base;1729if (xinerama_ext_ok && XineramaQueryExtension(x11_display, &event_base, &error_base)) {1730int xin_count;1731XineramaScreenInfo *xsi = XineramaQueryScreens(x11_display, &xin_count);1732if (xsi) {1733if (xin_count > 0) {1734if (p_screen < xin_count) {1735int x_count = XScreenCount(x11_display);1736for (int i = 0; i < x_count; i++) {1737Window root = XRootWindow(x11_display, i);1738XWindowAttributes root_attrs;1739XGetWindowAttributes(x11_display, root, &root_attrs);1740if ((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)) {1741found = true;1742image = XGetImage(x11_display, root, xsi[p_screen].x_org, xsi[p_screen].y_org, xsi[p_screen].width, xsi[p_screen].height, AllPlanes, ZPixmap);1743break;1744}1745}1746} else {1747ERR_PRINT(vformat("Invalid screen index: %d (count: %d).", p_screen, xin_count));1748}1749}1750XFree(xsi);1751}1752}1753if (!found) {1754int x_count = XScreenCount(x11_display);1755if (p_screen < x_count) {1756Window root = XRootWindow(x11_display, p_screen);17571758XWindowAttributes root_attrs;1759XGetWindowAttributes(x11_display, root, &root_attrs);17601761image = XGetImage(x11_display, root, root_attrs.x, root_attrs.y, root_attrs.width, root_attrs.height, AllPlanes, ZPixmap);1762} else {1763ERR_PRINT(vformat("Invalid screen index: %d (count: %d).", p_screen, x_count));1764}1765}17661767XSetErrorHandler(old_handler);17681769Ref<Image> img;1770if (image) {1771int width = image->width;1772int height = image->height;17731774Vector<uint8_t> img_data;1775img_data.resize(height * width * 4);17761777uint8_t *sr = (uint8_t *)image->data;1778uint8_t *wr = (uint8_t *)img_data.ptrw();17791780if (image->bits_per_pixel == 24 && image->red_mask == 0xff0000 && image->green_mask == 0x00ff00 && image->blue_mask == 0x0000ff) {1781for (int y = 0; y < height; y++) {1782for (int x = 0; x < width; x++) {1783wr[(y * width + x) * 4 + 0] = sr[(y * width + x) * 3 + 2];1784wr[(y * width + x) * 4 + 1] = sr[(y * width + x) * 3 + 1];1785wr[(y * width + x) * 4 + 2] = sr[(y * width + x) * 3 + 0];1786wr[(y * width + x) * 4 + 3] = 255;1787}1788}1789} else if (image->bits_per_pixel == 24 && image->red_mask == 0x0000ff && image->green_mask == 0x00ff00 && image->blue_mask == 0xff0000) {1790for (int y = 0; y < height; y++) {1791for (int x = 0; x < width; x++) {1792wr[(y * width + x) * 4 + 0] = sr[(y * width + x) * 3 + 2];1793wr[(y * width + x) * 4 + 1] = sr[(y * width + x) * 3 + 1];1794wr[(y * width + x) * 4 + 2] = sr[(y * width + x) * 3 + 0];1795wr[(y * width + x) * 4 + 3] = 255;1796}1797}1798} else if (image->bits_per_pixel == 32 && image->red_mask == 0xff0000 && image->green_mask == 0x00ff00 && image->blue_mask == 0x0000ff) {1799for (int y = 0; y < height; y++) {1800for (int x = 0; x < width; x++) {1801wr[(y * width + x) * 4 + 0] = sr[(y * width + x) * 4 + 2];1802wr[(y * width + x) * 4 + 1] = sr[(y * width + x) * 4 + 1];1803wr[(y * width + x) * 4 + 2] = sr[(y * width + x) * 4 + 0];1804wr[(y * width + x) * 4 + 3] = 255;1805}1806}1807} else {1808String 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);1809XDestroyImage(image);1810ERR_FAIL_V_MSG(Ref<Image>(), msg);1811}1812img = Image::create_from_data(width, height, false, Image::FORMAT_RGBA8, img_data);1813XDestroyImage(image);1814}18151816return img;1817}18181819float DisplayServerX11::screen_get_refresh_rate(int p_screen) const {1820_THREAD_SAFE_METHOD_18211822ERR_FAIL_COND_V_MSG(!xrandr_ext_ok || !xrr_get_monitors, SCREEN_REFRESH_RATE_FALLBACK, "XRandR extension is not available.");18231824p_screen = _get_screen_index(p_screen);1825int screen_count = get_screen_count();1826ERR_FAIL_INDEX_V(p_screen, screen_count, SCREEN_REFRESH_RATE_FALLBACK);18271828int target_x;1829int target_y;1830{1831int count = 0;1832xrr_monitor_info *monitors = xrr_get_monitors(x11_display, windows[MAIN_WINDOW_ID].x11_window, true, &count);1833ERR_FAIL_NULL_V(monitors, SCREEN_REFRESH_RATE_FALLBACK);1834if (count <= p_screen) {1835xrr_free_monitors(monitors);1836ERR_FAIL_V_MSG(SCREEN_REFRESH_RATE_FALLBACK, vformat("Invalid screen index: %d (count: %d).", p_screen, count));1837}1838target_x = monitors[p_screen].x;1839target_y = monitors[p_screen].y;1840xrr_free_monitors(monitors);1841}18421843XRRScreenResources *screen_res = XRRGetScreenResourcesCurrent(x11_display, windows[MAIN_WINDOW_ID].x11_window);1844ERR_FAIL_NULL_V(screen_res, SCREEN_REFRESH_RATE_FALLBACK);18451846XRRModeInfo *mode_info = nullptr;1847for (int crtc = 0; crtc < screen_res->ncrtc; crtc++) { // Loop through outputs to find which one is currently outputting.1848XRRCrtcInfo *monitor_info = XRRGetCrtcInfo(x11_display, screen_res, screen_res->crtcs[crtc]);1849if (monitor_info->x != target_x || monitor_info->y != target_y || monitor_info->mode == None) {1850XRRFreeCrtcInfo(monitor_info);1851continue;1852}1853for (int mode = 0; mode < screen_res->nmode; mode++) {1854if (screen_res->modes[mode].id == monitor_info->mode) {1855mode_info = &screen_res->modes[mode];1856}1857}1858XRRFreeCrtcInfo(monitor_info);1859break;1860}18611862float result;1863if (mode_info) {1864// Snap to nearest 0.01 to stay consistent with other platforms.1865result = Math::snapped((float)mode_info->dotClock / ((float)mode_info->hTotal * (float)mode_info->vTotal), 0.01);1866} else {1867ERR_PRINT("An error occurred while trying to get the screen refresh rate.");1868result = SCREEN_REFRESH_RATE_FALLBACK;1869}18701871XRRFreeScreenResources(screen_res);1872return result;1873}18741875#ifdef DBUS_ENABLED1876void DisplayServerX11::screen_set_keep_on(bool p_enable) {1877if (screen_is_kept_on() == p_enable) {1878return;1879}18801881if (portal_desktop && portal_desktop->is_inhibit_supported()) {1882if (p_enable) {1883// Attach the inhibit request to the main window, not the last focused window,1884// on the basis that inhibiting the screensaver is global state for the application.1885WindowID window_id = MAIN_WINDOW_ID;1886String xid = vformat("x11:%x", (uint64_t)windows[window_id].x11_window);1887keep_screen_on = portal_desktop->inhibit(xid);1888} else {1889portal_desktop->uninhibit();1890keep_screen_on = false;1891}1892} else if (screensaver) {1893if (p_enable) {1894screensaver->inhibit();1895} else {1896screensaver->uninhibit();1897}18981899keep_screen_on = p_enable;1900}1901}19021903bool DisplayServerX11::screen_is_kept_on() const {1904return keep_screen_on;1905}1906#endif19071908Vector<DisplayServer::WindowID> DisplayServerX11::get_window_list() const {1909_THREAD_SAFE_METHOD_19101911Vector<int> ret;1912for (const KeyValue<WindowID, WindowData> &E : windows) {1913ret.push_back(E.key);1914}1915return ret;1916}19171918DisplayServer::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) {1919_THREAD_SAFE_METHOD_19201921WindowID id = _create_window(p_mode, p_vsync_mode, p_flags, p_rect, 0);1922for (int i = 0; i < WINDOW_FLAG_MAX; i++) {1923if (p_flags & (1 << i)) {1924window_set_flag(WindowFlags(i), true, id);1925}1926}1927#ifdef RD_ENABLED1928if (rendering_device) {1929rendering_device->screen_create(id);1930}1931#endif19321933if (p_transient_parent != INVALID_WINDOW_ID) {1934window_set_transient(id, p_transient_parent);1935}19361937return id;1938}19391940void DisplayServerX11::show_window(WindowID p_id) {1941_THREAD_SAFE_METHOD_19421943WindowData &wd = windows[p_id];1944popup_open(p_id);19451946DEBUG_LOG_X11("show_window: %lu (%u) \n", wd.x11_window, p_id);19471948// Setup initial minimize/maximize state.1949// `_NET_WM_STATE` can be set directly when the window is unmapped.1950LocalVector<Atom> hints;1951if (wd.maximized) {1952hints.push_back(XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_VERT", False));1953hints.push_back(XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_HORZ", False));1954}1955if (wd.minimized) {1956hints.push_back(XInternAtom(x11_display, "_NET_WM_STATE_HIDDEN", False));1957}1958XChangeProperty(x11_display, wd.x11_window, XInternAtom(x11_display, "_NET_WM_STATE", False), XA_ATOM, 32, PropModeReplace, (unsigned char *)hints.ptr(), hints.size());19591960_update_motif_wm_hints(p_id);19611962XMapWindow(x11_display, wd.x11_window);1963XSync(x11_display, False);19641965_validate_fullscreen_on_map(p_id);19661967if (p_id == MAIN_WINDOW_ID) {1968// Get main window size for boot splash drawing.1969bool get_config_event = false;19701971// Search the X11 event queue for ConfigureNotify events and process all that are currently queued early.1972{1973MutexLock mutex_lock(events_mutex);19741975for (uint32_t event_index = 0; event_index < polled_events.size(); ++event_index) {1976XEvent &config_event = polled_events[event_index];1977if (config_event.type == ConfigureNotify) {1978_window_changed(&config_event);1979get_config_event = true;1980}1981}1982XEvent config_event;1983while (XCheckTypedEvent(x11_display, ConfigureNotify, &config_event)) {1984_window_changed(&config_event);1985get_config_event = true;1986}1987}19881989// Estimate maximize/full screen window size, ConfigureNotify may arrive only after maximize animation is finished.1990if (!get_config_event && (wd.maximized || wd.fullscreen)) {1991int screen = window_get_current_screen(p_id);1992Size2i sz;1993if (wd.maximized) {1994sz = screen_get_usable_rect(screen).size;1995if (!wd.borderless) {1996Atom prop = XInternAtom(x11_display, "_NET_FRAME_EXTENTS", True);1997if (prop != None) {1998Atom type;1999int format;2000unsigned long len;2001unsigned long remaining;2002unsigned char *data = nullptr;2003if (XGetWindowProperty(x11_display, wd.x11_window, prop, 0, 4, False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) {2004if (format == 32 && len == 4 && data) {2005long *extents = (long *)data;2006sz.width -= extents[0] + extents[1]; // left, right2007sz.height -= extents[2] + extents[3]; // top, bottom2008}2009XFree(data);2010}2011}2012}2013} else {2014sz = screen_get_size(screen);2015}2016if (sz == Size2i()) {2017return;2018}20192020wd.size = sz;2021#if defined(RD_ENABLED)2022if (rendering_context) {2023rendering_context->window_set_size(p_id, sz.width, sz.height);2024}2025#endif2026#if defined(GLES3_ENABLED)2027if (gl_manager) {2028gl_manager->window_resize(p_id, sz.width, sz.height);2029}2030if (gl_manager_egl) {2031gl_manager_egl->window_resize(p_id, sz.width, sz.height);2032}2033#endif2034}2035}2036}20372038void DisplayServerX11::delete_sub_window(WindowID p_id) {2039_THREAD_SAFE_METHOD_20402041ERR_FAIL_COND(!windows.has(p_id));2042ERR_FAIL_COND_MSG(p_id == MAIN_WINDOW_ID, "Main window can't be deleted");20432044popup_close(p_id);20452046WindowData &wd = windows[p_id];20472048DEBUG_LOG_X11("delete_sub_window: %lu (%u) \n", wd.x11_window, p_id);20492050if (window_mouseover_id == p_id) {2051window_mouseover_id = INVALID_WINDOW_ID;2052_send_window_event(windows[p_id], WINDOW_EVENT_MOUSE_EXIT);2053}20542055while (wd.transient_children.size()) {2056window_set_transient(*wd.transient_children.begin(), INVALID_WINDOW_ID);2057}20582059if (wd.transient_parent != INVALID_WINDOW_ID) {2060window_set_transient(p_id, INVALID_WINDOW_ID);2061}20622063#if defined(RD_ENABLED)2064if (rendering_device) {2065rendering_device->screen_free(p_id);2066}20672068if (rendering_context) {2069rendering_context->window_destroy(p_id);2070}2071#endif2072#ifdef GLES3_ENABLED2073if (gl_manager) {2074gl_manager->window_destroy(p_id);2075}2076if (gl_manager_egl) {2077gl_manager_egl->window_destroy(p_id);2078}2079#endif20802081#ifdef ACCESSKIT_ENABLED2082if (accessibility_driver) {2083accessibility_driver->window_destroy(p_id);2084}2085#endif20862087if (wd.xic) {2088XDestroyIC(wd.xic);2089wd.xic = nullptr;2090}2091XDestroyWindow(x11_display, wd.x11_xim_window);2092#ifdef XKB_ENABLED2093if (xkb_loaded_v05p) {2094if (wd.xkb_state) {2095xkb_compose_state_unref(wd.xkb_state);2096wd.xkb_state = nullptr;2097}2098}2099#endif21002101XUnmapWindow(x11_display, wd.x11_window);2102XDestroyWindow(x11_display, wd.x11_window);21032104window_set_rect_changed_callback(Callable(), p_id);2105window_set_window_event_callback(Callable(), p_id);2106window_set_input_event_callback(Callable(), p_id);2107window_set_input_text_callback(Callable(), p_id);2108window_set_drop_files_callback(Callable(), p_id);21092110windows.erase(p_id);21112112if (last_focused_window == p_id) {2113last_focused_window = INVALID_WINDOW_ID;2114}2115}21162117int64_t DisplayServerX11::window_get_native_handle(HandleType p_handle_type, WindowID p_window) const {2118ERR_FAIL_COND_V(!windows.has(p_window), 0);2119switch (p_handle_type) {2120case DISPLAY_HANDLE: {2121return (int64_t)x11_display;2122}2123case WINDOW_HANDLE: {2124return (int64_t)windows[p_window].x11_window;2125}2126case WINDOW_VIEW: {2127return 0; // Not supported.2128}2129#ifdef GLES3_ENABLED2130case OPENGL_CONTEXT: {2131if (gl_manager) {2132return (int64_t)gl_manager->get_glx_context(p_window);2133}2134if (gl_manager_egl) {2135return (int64_t)gl_manager_egl->get_context(p_window);2136}2137return 0;2138}2139case EGL_DISPLAY: {2140if (gl_manager_egl) {2141return (int64_t)gl_manager_egl->get_display(p_window);2142}2143return 0;2144}2145case EGL_CONFIG: {2146if (gl_manager_egl) {2147return (int64_t)gl_manager_egl->get_config(p_window);2148}2149return 0;2150}2151#endif2152default: {2153return 0;2154}2155}2156}21572158void DisplayServerX11::window_attach_instance_id(ObjectID p_instance, WindowID p_window) {2159ERR_FAIL_COND(!windows.has(p_window));2160WindowData &wd = windows[p_window];21612162wd.instance_id = p_instance;2163}21642165ObjectID DisplayServerX11::window_get_attached_instance_id(WindowID p_window) const {2166ERR_FAIL_COND_V(!windows.has(p_window), ObjectID());2167const WindowData &wd = windows[p_window];2168return wd.instance_id;2169}21702171DisplayServerX11::WindowID DisplayServerX11::get_window_at_screen_position(const Point2i &p_position) const {2172WindowID found_window = INVALID_WINDOW_ID;2173WindowID parent_window = INVALID_WINDOW_ID;2174unsigned int focus_order = 0;2175for (const KeyValue<WindowID, WindowData> &E : windows) {2176const WindowData &wd = E.value;21772178// Discard windows with no focus.2179if (wd.focus_order == 0) {2180continue;2181}21822183// Find topmost window which contains the given position.2184WindowID window_id = E.key;2185Rect2i win_rect = Rect2i(window_get_position(window_id), window_get_size(window_id));2186if (win_rect.has_point(p_position)) {2187// For siblings, pick the window which was focused last.2188if ((parent_window != wd.transient_parent) || (wd.focus_order > focus_order)) {2189found_window = window_id;2190parent_window = wd.transient_parent;2191focus_order = wd.focus_order;2192}2193}2194}21952196return found_window;2197}21982199void DisplayServerX11::window_set_title(const String &p_title, WindowID p_window) {2200_THREAD_SAFE_METHOD_22012202ERR_FAIL_COND(!windows.has(p_window));2203WindowData &wd = windows[p_window];22042205XStoreName(x11_display, wd.x11_window, p_title.utf8().get_data());22062207Atom _net_wm_name = XInternAtom(x11_display, "_NET_WM_NAME", false);2208Atom utf8_string = XInternAtom(x11_display, "UTF8_STRING", false);2209if (_net_wm_name != None && utf8_string != None) {2210CharString utf8_title = p_title.utf8();2211XChangeProperty(x11_display, wd.x11_window, _net_wm_name, utf8_string, 8, PropModeReplace, (unsigned char *)utf8_title.get_data(), utf8_title.length());2212}2213}22142215void DisplayServerX11::window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window) {2216_THREAD_SAFE_METHOD_22172218ERR_FAIL_COND(!windows.has(p_window));2219windows[p_window].mpath = p_region;2220_update_window_mouse_passthrough(p_window);2221}22222223void DisplayServerX11::_update_window_mouse_passthrough(WindowID p_window) {2224ERR_FAIL_COND(!windows.has(p_window));2225ERR_FAIL_COND(!xshaped_ext_ok);22262227const Vector<Vector2> region_path = windows[p_window].mpath;22282229int event_base, error_base;2230const Bool ext_okay = XShapeQueryExtension(x11_display, &event_base, &error_base);2231if (ext_okay) {2232if (windows[p_window].mpass) {2233Region region = XCreateRegion();2234XShapeCombineRegion(x11_display, windows[p_window].x11_window, ShapeInput, 0, 0, region, ShapeSet);2235XDestroyRegion(region);2236} else if (region_path.is_empty()) {2237XShapeCombineMask(x11_display, windows[p_window].x11_window, ShapeInput, 0, 0, None, ShapeSet);2238} else {2239XPoint *points = (XPoint *)memalloc(sizeof(XPoint) * region_path.size());2240for (int i = 0; i < region_path.size(); i++) {2241points[i].x = region_path[i].x;2242points[i].y = region_path[i].y;2243}2244Region region = XPolygonRegion(points, region_path.size(), EvenOddRule);2245memfree(points);2246XShapeCombineRegion(x11_display, windows[p_window].x11_window, ShapeInput, 0, 0, region, ShapeSet);2247XDestroyRegion(region);2248}2249}2250}22512252void DisplayServerX11::window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window) {2253_THREAD_SAFE_METHOD_22542255ERR_FAIL_COND(!windows.has(p_window));2256WindowData &wd = windows[p_window];2257wd.rect_changed_callback = p_callable;2258}22592260void DisplayServerX11::window_set_window_event_callback(const Callable &p_callable, WindowID p_window) {2261_THREAD_SAFE_METHOD_22622263ERR_FAIL_COND(!windows.has(p_window));2264WindowData &wd = windows[p_window];2265wd.event_callback = p_callable;2266}22672268void DisplayServerX11::window_set_input_event_callback(const Callable &p_callable, WindowID p_window) {2269_THREAD_SAFE_METHOD_22702271ERR_FAIL_COND(!windows.has(p_window));2272WindowData &wd = windows[p_window];2273wd.input_event_callback = p_callable;2274}22752276void DisplayServerX11::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) {2277_THREAD_SAFE_METHOD_22782279ERR_FAIL_COND(!windows.has(p_window));2280WindowData &wd = windows[p_window];2281wd.input_text_callback = p_callable;2282}22832284void DisplayServerX11::window_set_drop_files_callback(const Callable &p_callable, WindowID p_window) {2285_THREAD_SAFE_METHOD_22862287ERR_FAIL_COND(!windows.has(p_window));2288WindowData &wd = windows[p_window];2289wd.drop_files_callback = p_callable;2290}22912292int DisplayServerX11::window_get_current_screen(WindowID p_window) const {2293_THREAD_SAFE_METHOD_22942295int count = get_screen_count();2296if (count < 2) {2297// Early exit with single monitor.2298return 0;2299}23002301ERR_FAIL_COND_V(!windows.has(p_window), INVALID_SCREEN);2302const WindowData &wd = windows[p_window];23032304const Rect2i window_rect(wd.position, wd.size);23052306// Find which monitor has the largest overlap with the given window.2307int screen_index = 0;2308int max_area = 0;2309for (int i = 0; i < count; i++) {2310Rect2i screen_rect = _screen_get_rect(i);2311Rect2i intersection = screen_rect.intersection(window_rect);2312int area = intersection.get_area();2313if (area > max_area) {2314max_area = area;2315screen_index = i;2316}2317}23182319return screen_index;2320}23212322void DisplayServerX11::gl_window_make_current(DisplayServer::WindowID p_window_id) {2323#if defined(GLES3_ENABLED)2324if (gl_manager) {2325gl_manager->window_make_current(p_window_id);2326}2327if (gl_manager_egl) {2328gl_manager_egl->window_make_current(p_window_id);2329}2330#endif2331}23322333void DisplayServerX11::window_set_current_screen(int p_screen, WindowID p_window) {2334_THREAD_SAFE_METHOD_23352336ERR_FAIL_COND(!windows.has(p_window));23372338p_screen = _get_screen_index(p_screen);2339int screen_count = get_screen_count();2340ERR_FAIL_INDEX(p_screen, screen_count);23412342if (window_get_current_screen(p_window) == p_screen) {2343return;2344}2345WindowData &wd = windows[p_window];23462347if (wd.embed_parent) {2348print_line("Embedded window can't be moved to another screen.");2349return;2350}23512352if (window_get_mode(p_window) == WINDOW_MODE_FULLSCREEN || window_get_mode(p_window) == WINDOW_MODE_MAXIMIZED) {2353WindowMode current_mode = window_get_mode(p_window);2354window_set_mode(WINDOW_MODE_WINDOWED, p_window);2355Point2i position = screen_get_position(p_screen);2356Size2i size = screen_get_size(p_screen);2357XMoveResizeWindow(x11_display, wd.x11_window, position.x, position.y, size.x, size.y);2358window_set_mode(current_mode, p_window);2359} else {2360Rect2i srect = screen_get_usable_rect(p_screen);2361Point2i wpos = window_get_position(p_window) - screen_get_position(window_get_current_screen(p_window));2362Size2i wsize = window_get_size(p_window);2363wpos += srect.position;2364if (srect != Rect2i()) {2365wpos = wpos.clamp(srect.position, srect.position + srect.size - wsize / 3);2366}2367window_set_position(wpos, p_window);2368}2369}23702371void DisplayServerX11::window_set_transient(WindowID p_window, WindowID p_parent) {2372_THREAD_SAFE_METHOD_23732374ERR_FAIL_COND(p_window == p_parent);23752376ERR_FAIL_COND(!windows.has(p_window));2377WindowData &wd_window = windows[p_window];23782379WindowID prev_parent = wd_window.transient_parent;2380ERR_FAIL_COND(prev_parent == p_parent);23812382DEBUG_LOG_X11("window_set_transient: %lu (%u), prev_parent=%u, parent=%u\n", wd_window.x11_window, p_window, prev_parent, p_parent);23832384ERR_FAIL_COND_MSG(wd_window.on_top, "Windows with the 'on top' can't become transient.");2385if (p_parent == INVALID_WINDOW_ID) {2386//remove transient23872388ERR_FAIL_COND(prev_parent == INVALID_WINDOW_ID);2389ERR_FAIL_COND(!windows.has(prev_parent));23902391WindowData &wd_parent = windows[prev_parent];23922393wd_window.transient_parent = INVALID_WINDOW_ID;2394wd_parent.transient_children.erase(p_window);23952396XSetTransientForHint(x11_display, wd_window.x11_window, None);23972398XWindowAttributes xwa;2399XSync(x11_display, False);2400XGetWindowAttributes(x11_display, wd_parent.x11_window, &xwa);24012402// Set focus to parent sub window to avoid losing all focus when closing a nested sub-menu.2403// RevertToPointerRoot is used to make sure we don't lose all focus in case2404// a subwindow and its parent are both destroyed.2405if (!wd_window.no_focus && !wd_window.is_popup && wd_window.focused) {2406if ((xwa.map_state == IsViewable) && !wd_parent.no_focus && !wd_window.is_popup && _window_focus_check()) {2407_set_input_focus(wd_parent.x11_window, RevertToPointerRoot);2408}2409}2410} else {2411ERR_FAIL_COND(!windows.has(p_parent));2412ERR_FAIL_COND_MSG(prev_parent != INVALID_WINDOW_ID, "Window already has a transient parent");2413WindowData &wd_parent = windows[p_parent];24142415wd_window.transient_parent = p_parent;2416wd_parent.transient_children.insert(p_window);24172418XSetTransientForHint(x11_display, wd_window.x11_window, wd_parent.x11_window);2419}2420}24212422// Helper method. Assumes that the window id has already been checked and exists.2423void DisplayServerX11::_update_size_hints(WindowID p_window) {2424WindowData &wd = windows[p_window];2425WindowMode window_mode = window_get_mode(p_window);2426XSizeHints *xsh = XAllocSizeHints();24272428// Always set the position and size hints - they should be synchronized with the actual values after the window is mapped anyway2429xsh->flags |= PPosition | PSize | PWinGravity;2430xsh->x = wd.position.x;2431xsh->y = wd.position.y;2432xsh->width = wd.size.width;2433xsh->height = wd.size.height;2434xsh->win_gravity = StaticGravity;24352436if (window_mode == WINDOW_MODE_FULLSCREEN || window_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) {2437// Do not set any other hints to prevent the window manager from ignoring the fullscreen flags2438} else if (window_get_flag(WINDOW_FLAG_RESIZE_DISABLED, p_window)) {2439// If resizing is disabled, use the forced size2440xsh->flags |= PMinSize | PMaxSize;2441xsh->min_width = wd.size.x;2442xsh->max_width = wd.size.x;2443xsh->min_height = wd.size.y;2444xsh->max_height = wd.size.y;2445} else {2446// Otherwise, just respect min_size and max_size2447if (wd.min_size != Size2i()) {2448xsh->flags |= PMinSize;2449xsh->min_width = wd.min_size.x;2450xsh->min_height = wd.min_size.y;2451}2452if (wd.max_size != Size2i()) {2453xsh->flags |= PMaxSize;2454xsh->max_width = wd.max_size.x;2455xsh->max_height = wd.max_size.y;2456}2457}24582459XSetWMNormalHints(x11_display, wd.x11_window, xsh);2460XFree(xsh);2461}24622463void DisplayServerX11::_update_motif_wm_hints(WindowID p_window) {2464Atom motif_wm_hints = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True);2465if (motif_wm_hints == None) {2466return;2467}24682469WindowData &wd = windows[p_window];24702471MotifWmHints hints = {};2472hints.flags = MWM_HINTS_DECORATIONS;24732474if (!wd.borderless) {2475hints.flags |= MWM_HINTS_FUNCTIONS;24762477hints.decorations = MWM_DECOR_BORDER | MWM_DECOR_MENU | MWM_DECOR_TITLE;2478hints.functions = MWM_FUNC_MOVE | MWM_FUNC_CLOSE;24792480if (!wd.no_min_btn) {2481hints.decorations |= MWM_DECOR_MINIMIZE;2482hints.functions |= MWM_FUNC_MINIMIZE;2483}2484if (!wd.no_max_btn) {2485hints.decorations |= MWM_DECOR_MAXIMIZE;2486hints.functions |= MWM_FUNC_MAXIMIZE;2487}2488if (!wd.resize_disabled) {2489hints.decorations |= MWM_DECOR_RESIZEH;2490hints.functions |= MWM_FUNC_RESIZE;2491}2492}24932494XChangeProperty(x11_display, wd.x11_window, motif_wm_hints, motif_wm_hints, 32, PropModeReplace, (unsigned char *)&hints, 5);2495}24962497void DisplayServerX11::_update_wm_state_hints(WindowID p_window) {2498WindowData &wd = windows[p_window];24992500Atom type;2501int format;2502unsigned long len;2503unsigned long remaining;2504unsigned char *data = nullptr;25052506int result = XGetWindowProperty(2507x11_display,2508wd.x11_window,2509XInternAtom(x11_display, "_NET_WM_STATE", False),25100,25111024,2512False,2513XA_ATOM,2514&type,2515&format,2516&len,2517&remaining,2518&data);2519if (result != Success) {2520return;2521}25222523LocalVector<Atom> hints;2524if (data) {2525hints.resize(len);2526Atom *atoms = (Atom *)data;2527for (unsigned long i = 0; i < len; i++) {2528hints[i] = atoms[i];2529}2530XFree(data);2531}25322533Atom fullscreen_atom = XInternAtom(x11_display, "_NET_WM_STATE_FULLSCREEN", False);2534Atom maximized_horz_atom = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_HORZ", False);2535Atom maximized_vert_atom = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_VERT", False);2536Atom hidden_atom = XInternAtom(x11_display, "_NET_WM_STATE_HIDDEN", False);25372538wd.fullscreen = hints.has(fullscreen_atom);2539wd.maximized = hints.has(maximized_horz_atom) && hints.has(maximized_vert_atom);2540wd.minimized = hints.has(hidden_atom);2541}25422543Point2i DisplayServerX11::window_get_position(WindowID p_window) const {2544_THREAD_SAFE_METHOD_25452546ERR_FAIL_COND_V(!windows.has(p_window), Point2i());2547const WindowData &wd = windows[p_window];25482549return wd.position;2550}25512552Point2i DisplayServerX11::window_get_position_with_decorations(WindowID p_window) const {2553_THREAD_SAFE_METHOD_25542555ERR_FAIL_COND_V(!windows.has(p_window), Size2i());2556const WindowData &wd = windows[p_window];25572558if (wd.fullscreen) {2559return wd.position;2560}25612562XWindowAttributes xwa;2563XSync(x11_display, False);2564XGetWindowAttributes(x11_display, wd.x11_window, &xwa);2565int x = wd.position.x;2566int y = wd.position.y;2567Atom prop = XInternAtom(x11_display, "_NET_FRAME_EXTENTS", True);2568if (prop != None) {2569Atom type;2570int format;2571unsigned long len;2572unsigned long remaining;2573unsigned char *data = nullptr;2574if (XGetWindowProperty(x11_display, wd.x11_window, prop, 0, 4, False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) {2575if (format == 32 && len == 4 && data) {2576long *extents = (long *)data;2577x -= extents[0]; // left2578y -= extents[2]; // top2579}2580XFree(data);2581}2582}2583return Size2i(x, y);2584}25852586void DisplayServerX11::window_set_position(const Point2i &p_position, WindowID p_window) {2587_THREAD_SAFE_METHOD_25882589ERR_FAIL_COND(!windows.has(p_window));2590WindowData &wd = windows[p_window];25912592if (wd.embed_parent) {2593print_line("Embedded window can't be moved.");2594return;2595}25962597wd.position = p_position;2598XMoveWindow(x11_display, wd.x11_window, p_position.x, p_position.y);2599_update_real_mouse_position(wd);2600}26012602void DisplayServerX11::window_set_max_size(const Size2i p_size, WindowID p_window) {2603_THREAD_SAFE_METHOD_26042605ERR_FAIL_COND(!windows.has(p_window));2606WindowData &wd = windows[p_window];26072608if (wd.embed_parent) {2609print_line("Embedded windows can't have a maximum size.");2610return;2611}26122613if ((p_size != Size2i()) && ((p_size.x < wd.min_size.x) || (p_size.y < wd.min_size.y))) {2614ERR_PRINT("Maximum window size can't be smaller than minimum window size!");2615return;2616}2617wd.max_size = p_size;26182619_update_size_hints(p_window);2620XFlush(x11_display);2621}26222623Size2i DisplayServerX11::window_get_max_size(WindowID p_window) const {2624_THREAD_SAFE_METHOD_26252626ERR_FAIL_COND_V(!windows.has(p_window), Size2i());2627const WindowData &wd = windows[p_window];26282629return wd.max_size;2630}26312632void DisplayServerX11::window_set_min_size(const Size2i p_size, WindowID p_window) {2633_THREAD_SAFE_METHOD_26342635ERR_FAIL_COND(!windows.has(p_window));2636WindowData &wd = windows[p_window];26372638if (wd.embed_parent) {2639print_line("Embedded windows can't have a minimum size.");2640return;2641}26422643if ((p_size != Size2i()) && (wd.max_size != Size2i()) && ((p_size.x > wd.max_size.x) || (p_size.y > wd.max_size.y))) {2644ERR_PRINT("Minimum window size can't be larger than maximum window size!");2645return;2646}2647wd.min_size = p_size;26482649_update_size_hints(p_window);2650XFlush(x11_display);2651}26522653Size2i DisplayServerX11::window_get_min_size(WindowID p_window) const {2654_THREAD_SAFE_METHOD_26552656ERR_FAIL_COND_V(!windows.has(p_window), Size2i());2657const WindowData &wd = windows[p_window];26582659return wd.min_size;2660}26612662void DisplayServerX11::window_set_size(const Size2i p_size, WindowID p_window) {2663_THREAD_SAFE_METHOD_26642665ERR_FAIL_COND(!windows.has(p_window));26662667Size2i size = p_size;2668size = size.maxi(1);26692670WindowData &wd = windows[p_window];26712672if (wd.embed_parent) {2673print_line("Embedded window can't be resized.");2674return;2675}26762677if (wd.size.width == size.width && wd.size.height == size.height) {2678return;2679}26802681XWindowAttributes xwa;2682XSync(x11_display, False);2683XGetWindowAttributes(x11_display, wd.x11_window, &xwa);2684int old_w = xwa.width;2685int old_h = xwa.height;26862687// Update our videomode width and height2688wd.size = size;26892690// Update the size hints first to make sure the window size can be set2691_update_size_hints(p_window);26922693// Resize the window2694XResizeWindow(x11_display, wd.x11_window, size.x, size.y);26952696for (int timeout = 0; timeout < 50; ++timeout) {2697XSync(x11_display, False);2698XGetWindowAttributes(x11_display, wd.x11_window, &xwa);26992700if (old_w != xwa.width || old_h != xwa.height) {2701break;2702}27032704OS::get_singleton()->delay_usec(10'000);2705}27062707// Keep rendering context window size in sync2708#if defined(RD_ENABLED)2709if (rendering_context) {2710rendering_context->window_set_size(p_window, xwa.width, xwa.height);2711}2712#endif2713#if defined(GLES3_ENABLED)2714if (gl_manager) {2715gl_manager->window_resize(p_window, xwa.width, xwa.height);2716}2717if (gl_manager_egl) {2718gl_manager_egl->window_resize(p_window, xwa.width, xwa.height);2719}2720#endif2721}27222723Size2i DisplayServerX11::window_get_size(WindowID p_window) const {2724_THREAD_SAFE_METHOD_27252726ERR_FAIL_COND_V(!windows.has(p_window), Size2i());2727const WindowData &wd = windows[p_window];2728return wd.size;2729}27302731Size2i DisplayServerX11::window_get_size_with_decorations(WindowID p_window) const {2732_THREAD_SAFE_METHOD_27332734ERR_FAIL_COND_V(!windows.has(p_window), Size2i());2735const WindowData &wd = windows[p_window];27362737if (wd.fullscreen) {2738return wd.size;2739}27402741XWindowAttributes xwa;2742XSync(x11_display, False);2743XGetWindowAttributes(x11_display, wd.x11_window, &xwa);2744int w = xwa.width;2745int h = xwa.height;2746Atom prop = XInternAtom(x11_display, "_NET_FRAME_EXTENTS", True);2747if (prop != None) {2748Atom type;2749int format;2750unsigned long len;2751unsigned long remaining;2752unsigned char *data = nullptr;2753if (XGetWindowProperty(x11_display, wd.x11_window, prop, 0, 4, False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) {2754if (format == 32 && len == 4 && data) {2755long *extents = (long *)data;2756w += extents[0] + extents[1]; // left, right2757h += extents[2] + extents[3]; // top, bottom2758}2759XFree(data);2760}2761}2762return Size2i(w, h);2763}27642765// Just a helper to reduce code duplication in `window_is_maximize_allowed`2766// and `_set_wm_maximized`.2767bool DisplayServerX11::_window_maximize_check(WindowID p_window, const char *p_atom_name) const {2768ERR_FAIL_COND_V(!windows.has(p_window), false);2769const WindowData &wd = windows[p_window];27702771Atom property = XInternAtom(x11_display, p_atom_name, False);2772Atom type;2773int format;2774unsigned long len;2775unsigned long remaining;2776unsigned char *data = nullptr;2777bool retval = false;27782779if (property == None) {2780return false;2781}27822783int result = XGetWindowProperty(2784x11_display,2785wd.x11_window,2786property,27870,27881024,2789False,2790XA_ATOM,2791&type,2792&format,2793&len,2794&remaining,2795&data);27962797if (result == Success && data) {2798Atom *atoms = (Atom *)data;2799Atom wm_act_max_horz;2800Atom wm_act_max_vert;2801bool checking_state = strcmp(p_atom_name, "_NET_WM_STATE") == 0;2802if (checking_state) {2803wm_act_max_horz = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_HORZ", False);2804wm_act_max_vert = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_VERT", False);2805} else {2806wm_act_max_horz = XInternAtom(x11_display, "_NET_WM_ACTION_MAXIMIZE_HORZ", False);2807wm_act_max_vert = XInternAtom(x11_display, "_NET_WM_ACTION_MAXIMIZE_VERT", False);2808}2809bool found_wm_act_max_horz = false;2810bool found_wm_act_max_vert = false;28112812for (uint64_t i = 0; i < len; i++) {2813if (atoms[i] == wm_act_max_horz) {2814found_wm_act_max_horz = true;2815}2816if (atoms[i] == wm_act_max_vert) {2817found_wm_act_max_vert = true;2818}28192820if (checking_state) {2821if (found_wm_act_max_horz && found_wm_act_max_vert) {2822retval = true;2823break;2824}2825} else {2826if (found_wm_act_max_horz || found_wm_act_max_vert) {2827retval = true;2828break;2829}2830}2831}28322833XFree(data);2834}28352836return retval;2837}28382839bool DisplayServerX11::_window_minimize_check(WindowID p_window) const {2840const WindowData &wd = windows[p_window];28412842// Using EWMH instead of ICCCM, might work better for Wayland users.2843Atom property = XInternAtom(x11_display, "_NET_WM_STATE", True);2844Atom hidden = XInternAtom(x11_display, "_NET_WM_STATE_HIDDEN", True);2845if (property == None || hidden == None) {2846return false;2847}28482849Atom type;2850int format;2851unsigned long len;2852unsigned long remaining;2853Atom *atoms = nullptr;28542855int result = XGetWindowProperty(2856x11_display,2857wd.x11_window,2858property,28590,286032,2861False,2862XA_ATOM,2863&type,2864&format,2865&len,2866&remaining,2867(unsigned char **)&atoms);28682869if (result == Success && atoms) {2870for (unsigned int i = 0; i < len; i++) {2871if (atoms[i] == hidden) {2872XFree(atoms);2873return true;2874}2875}2876XFree(atoms);2877}28782879return false;2880}28812882bool DisplayServerX11::_window_fullscreen_check(WindowID p_window) const {2883ERR_FAIL_COND_V(!windows.has(p_window), false);2884const WindowData &wd = windows[p_window];28852886// Using EWMH -- Extended Window Manager Hints2887Atom property = XInternAtom(x11_display, "_NET_WM_STATE", False);2888Atom type;2889int format;2890unsigned long len;2891unsigned long remaining;2892unsigned char *data = nullptr;2893bool retval = false;28942895if (property == None) {2896return retval;2897}28982899int result = XGetWindowProperty(2900x11_display,2901wd.x11_window,2902property,29030,29041024,2905False,2906XA_ATOM,2907&type,2908&format,2909&len,2910&remaining,2911&data);29122913if (result == Success) {2914Atom *atoms = (Atom *)data;2915Atom wm_fullscreen = XInternAtom(x11_display, "_NET_WM_STATE_FULLSCREEN", False);2916for (uint64_t i = 0; i < len; i++) {2917if (atoms[i] == wm_fullscreen) {2918retval = true;2919break;2920}2921}2922XFree(data);2923}29242925return retval;2926}29272928void DisplayServerX11::_validate_fullscreen_on_map(WindowID p_window) {2929// Check if we applied any window modes that didn't take effect while unmapped2930const WindowData &wd = windows[p_window];2931if (wd.fullscreen && !_window_fullscreen_check(p_window)) {2932_set_wm_fullscreen(p_window, true, wd.exclusive_fullscreen);2933}29342935if (wd.on_top) {2936Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);2937Atom wm_above = XInternAtom(x11_display, "_NET_WM_STATE_ABOVE", False);29382939XClientMessageEvent xev;2940memset(&xev, 0, sizeof(xev));2941xev.type = ClientMessage;2942xev.window = wd.x11_window;2943xev.message_type = wm_state;2944xev.format = 32;2945xev.data.l[0] = _NET_WM_STATE_ADD;2946xev.data.l[1] = wm_above;2947xev.data.l[3] = 1;2948XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent *)&xev);2949}2950}29512952bool DisplayServerX11::window_is_maximize_allowed(WindowID p_window) const {2953_THREAD_SAFE_METHOD_2954return _window_maximize_check(p_window, "_NET_WM_ALLOWED_ACTIONS");2955}29562957void DisplayServerX11::_set_wm_maximized(WindowID p_window, bool p_enabled) {2958ERR_FAIL_COND(!windows.has(p_window));2959WindowData &wd = windows[p_window];29602961// Using EWMH -- Extended Window Manager Hints2962XEvent xev;2963Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);2964Atom wm_max_horz = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_HORZ", False);2965Atom wm_max_vert = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_VERT", False);29662967memset(&xev, 0, sizeof(xev));2968xev.type = ClientMessage;2969xev.xclient.window = wd.x11_window;2970xev.xclient.message_type = wm_state;2971xev.xclient.format = 32;2972xev.xclient.data.l[0] = p_enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;2973xev.xclient.data.l[1] = wm_max_horz;2974xev.xclient.data.l[2] = wm_max_vert;29752976XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);29772978if (p_enabled && window_is_maximize_allowed(p_window)) {2979// Wait for effective resizing (so the GLX context is too).2980// Give up after 0.5s, it's not going to happen on this WM.2981// https://github.com/godotengine/godot/issues/199782982for (int attempt = 0; window_get_mode(p_window) != WINDOW_MODE_MAXIMIZED && attempt < 50; attempt++) {2983OS::get_singleton()->delay_usec(10'000);2984}2985}2986wd.maximized = p_enabled;2987}29882989void DisplayServerX11::_set_wm_minimized(WindowID p_window, bool p_enabled) {2990WindowData &wd = windows[p_window];2991// Using ICCCM -- Inter-Client Communication Conventions Manual2992XEvent xev;2993Atom wm_change = XInternAtom(x11_display, "WM_CHANGE_STATE", False);29942995memset(&xev, 0, sizeof(xev));2996xev.type = ClientMessage;2997xev.xclient.window = wd.x11_window;2998xev.xclient.message_type = wm_change;2999xev.xclient.format = 32;3000xev.xclient.data.l[0] = p_enabled ? WM_IconicState : WM_NormalState;30013002XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);30033004Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);3005Atom wm_hidden = XInternAtom(x11_display, "_NET_WM_STATE_HIDDEN", False);30063007memset(&xev, 0, sizeof(xev));3008xev.type = ClientMessage;3009xev.xclient.window = wd.x11_window;3010xev.xclient.message_type = wm_state;3011xev.xclient.format = 32;3012xev.xclient.data.l[0] = p_enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;3013xev.xclient.data.l[1] = wm_hidden;30143015XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);3016wd.minimized = p_enabled;3017}30183019void DisplayServerX11::_set_wm_fullscreen(WindowID p_window, bool p_enabled, bool p_exclusive) {3020ERR_FAIL_COND(!windows.has(p_window));3021WindowData &wd = windows[p_window];30223023if (p_enabled) {3024// Set the window as resizable to prevent window managers to ignore the fullscreen state flag.3025_update_size_hints(p_window);3026}30273028// Using EWMH -- Extended Window Manager Hints3029XEvent xev;3030Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);3031Atom wm_fullscreen = XInternAtom(x11_display, "_NET_WM_STATE_FULLSCREEN", False);30323033memset(&xev, 0, sizeof(xev));3034xev.type = ClientMessage;3035xev.xclient.window = wd.x11_window;3036xev.xclient.message_type = wm_state;3037xev.xclient.format = 32;3038xev.xclient.data.l[0] = p_enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;3039xev.xclient.data.l[1] = wm_fullscreen;3040xev.xclient.data.l[2] = 0;30413042XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);30433044// set bypass compositor hint3045Atom bypass_compositor = XInternAtom(x11_display, "_NET_WM_BYPASS_COMPOSITOR", False);3046unsigned long compositing_disable_on = 0; // Use default.3047if (p_enabled) {3048if (p_exclusive) {3049compositing_disable_on = 1; // Force composition OFF to reduce overhead.3050} else {3051compositing_disable_on = 2; // Force composition ON to allow popup windows.3052}3053}3054if (bypass_compositor != None) {3055XChangeProperty(x11_display, wd.x11_window, bypass_compositor, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&compositing_disable_on, 1);3056}30573058XFlush(x11_display);30593060if (!p_enabled) {3061// Reset the non-resizable flags if we un-set these before.3062_update_size_hints(p_window);3063}3064}30653066void DisplayServerX11::window_set_mode(WindowMode p_mode, WindowID p_window) {3067_THREAD_SAFE_METHOD_30683069ERR_FAIL_COND(!windows.has(p_window));3070WindowData &wd = windows[p_window];30713072WindowMode old_mode = window_get_mode(p_window);3073if (old_mode == p_mode) {3074return; // do nothing3075}30763077if (p_mode != WINDOW_MODE_WINDOWED && wd.embed_parent) {3078print_line("Embedded window only supports Windowed mode.");3079return;3080}30813082// Remove all "extra" modes.3083switch (old_mode) {3084case WINDOW_MODE_WINDOWED: {3085//do nothing3086} break;3087case WINDOW_MODE_MINIMIZED: {3088_set_wm_minimized(p_window, false);3089} break;3090case WINDOW_MODE_EXCLUSIVE_FULLSCREEN:3091case WINDOW_MODE_FULLSCREEN: {3092// Only remove fullscreen when necessary.3093if (p_mode == WINDOW_MODE_WINDOWED || p_mode == WINDOW_MODE_MAXIMIZED) {3094wd.fullscreen = false;3095wd.exclusive_fullscreen = false;3096_set_wm_fullscreen(p_window, false, false);3097}3098} break;3099case WINDOW_MODE_MAXIMIZED: {3100// Varies between target modes, so do nothing here.3101} break;3102}31033104switch (p_mode) {3105case WINDOW_MODE_WINDOWED: {3106if (wd.maximized) {3107_set_wm_maximized(p_window, false);3108}3109} break;3110case WINDOW_MODE_MINIMIZED: {3111_set_wm_minimized(p_window, true);3112} break;3113case WINDOW_MODE_EXCLUSIVE_FULLSCREEN:3114case WINDOW_MODE_FULLSCREEN: {3115if (window_get_flag(WINDOW_FLAG_ALWAYS_ON_TOP, p_window)) {3116_set_wm_maximized(p_window, true);3117}31183119wd.fullscreen = true;3120if (p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) {3121wd.exclusive_fullscreen = true;3122_set_wm_fullscreen(p_window, true, true);3123} else {3124wd.exclusive_fullscreen = false;3125_set_wm_fullscreen(p_window, true, false);3126}3127} break;3128case WINDOW_MODE_MAXIMIZED: {3129_set_wm_maximized(p_window, true);3130} break;3131}3132}31333134DisplayServer::WindowMode DisplayServerX11::window_get_mode(WindowID p_window) const {3135_THREAD_SAFE_METHOD_31363137ERR_FAIL_COND_V(!windows.has(p_window), WINDOW_MODE_WINDOWED);3138const WindowData &wd = windows[p_window];31393140if (_window_minimize_check(p_window)) {3141return WINDOW_MODE_MINIMIZED;3142}31433144if (wd.fullscreen) {3145if (wd.exclusive_fullscreen) {3146return WINDOW_MODE_EXCLUSIVE_FULLSCREEN;3147}3148return WINDOW_MODE_FULLSCREEN;3149}31503151if (_window_maximize_check(p_window, "_NET_WM_STATE")) {3152return WINDOW_MODE_MAXIMIZED;3153}31543155return WINDOW_MODE_WINDOWED;3156}31573158void DisplayServerX11::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) {3159_THREAD_SAFE_METHOD_31603161ERR_FAIL_COND(!windows.has(p_window));3162WindowData &wd = windows[p_window];31633164switch (p_flag) {3165case WINDOW_FLAG_MAXIMIZE_DISABLED: {3166wd.no_max_btn = p_enabled;3167_update_motif_wm_hints(p_window);31683169XFlush(x11_display);3170} break;3171case WINDOW_FLAG_MINIMIZE_DISABLED: {3172wd.no_min_btn = p_enabled;3173_update_motif_wm_hints(p_window);31743175XFlush(x11_display);3176} break;3177case WINDOW_FLAG_RESIZE_DISABLED: {3178if (p_enabled && wd.embed_parent) {3179print_line("Embedded window resize can't be disabled.");3180return;3181}31823183wd.resize_disabled = p_enabled;3184_update_size_hints(p_window);3185_update_motif_wm_hints(p_window);31863187XFlush(x11_display);3188} break;3189case WINDOW_FLAG_BORDERLESS: {3190wd.borderless = p_enabled;3191_update_motif_wm_hints(p_window);3192_update_window_mouse_passthrough(p_window);3193} break;3194case WINDOW_FLAG_ALWAYS_ON_TOP: {3195ERR_FAIL_COND_MSG(wd.transient_parent != INVALID_WINDOW_ID, "Can't make a window transient if the 'on top' flag is active.");3196if (p_enabled && wd.embed_parent) {3197print_line("Embedded window can't become on top.");3198return;3199}3200if (p_enabled && wd.fullscreen) {3201_set_wm_maximized(p_window, true);3202}32033204Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);3205Atom wm_above = XInternAtom(x11_display, "_NET_WM_STATE_ABOVE", False);32063207XClientMessageEvent xev;3208memset(&xev, 0, sizeof(xev));3209xev.type = ClientMessage;3210xev.window = wd.x11_window;3211xev.message_type = wm_state;3212xev.format = 32;3213xev.data.l[0] = p_enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;3214xev.data.l[1] = wm_above;3215xev.data.l[3] = 1;3216XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent *)&xev);32173218if (!p_enabled && !wd.fullscreen) {3219_set_wm_maximized(p_window, false);3220}3221wd.on_top = p_enabled;32223223} break;3224case WINDOW_FLAG_TRANSPARENT: {3225wd.layered_window = p_enabled;3226} break;3227case WINDOW_FLAG_NO_FOCUS: {3228wd.no_focus = p_enabled;3229} break;3230case WINDOW_FLAG_MOUSE_PASSTHROUGH: {3231wd.mpass = p_enabled;3232_update_window_mouse_passthrough(p_window);3233} break;3234case WINDOW_FLAG_POPUP: {3235XWindowAttributes xwa;3236XSync(x11_display, False);3237XGetWindowAttributes(x11_display, wd.x11_window, &xwa);32383239ERR_FAIL_COND_MSG(p_window == MAIN_WINDOW_ID, "Main window can't be popup.");3240ERR_FAIL_COND_MSG((xwa.map_state == IsViewable) && (wd.is_popup != p_enabled), "Popup flag can't changed while window is opened.");3241if (p_enabled && wd.embed_parent) {3242print_line("Embedded window can't be popup.");3243return;3244}3245wd.is_popup = p_enabled;3246} break;3247default: {3248}3249}3250}32513252bool DisplayServerX11::window_get_flag(WindowFlags p_flag, WindowID p_window) const {3253_THREAD_SAFE_METHOD_32543255ERR_FAIL_COND_V(!windows.has(p_window), false);3256const WindowData &wd = windows[p_window];32573258switch (p_flag) {3259case WINDOW_FLAG_MAXIMIZE_DISABLED: {3260return wd.no_max_btn;3261} break;3262case WINDOW_FLAG_MINIMIZE_DISABLED: {3263return wd.no_min_btn;3264} break;3265case WINDOW_FLAG_RESIZE_DISABLED: {3266return wd.resize_disabled;3267} break;3268case WINDOW_FLAG_BORDERLESS: {3269return wd.borderless;3270} break;3271case WINDOW_FLAG_ALWAYS_ON_TOP: {3272return wd.on_top;3273} break;3274case WINDOW_FLAG_TRANSPARENT: {3275return wd.layered_window;3276} break;3277case WINDOW_FLAG_NO_FOCUS: {3278return wd.no_focus;3279} break;3280case WINDOW_FLAG_MOUSE_PASSTHROUGH: {3281return wd.mpass;3282} break;3283case WINDOW_FLAG_POPUP: {3284return wd.is_popup;3285} break;3286default: {3287}3288}32893290return false;3291}32923293void DisplayServerX11::window_request_attention(WindowID p_window) {3294_THREAD_SAFE_METHOD_32953296ERR_FAIL_COND(!windows.has(p_window));3297const WindowData &wd = windows[p_window];3298// Using EWMH -- Extended Window Manager Hints3299//3300// Sets the _NET_WM_STATE_DEMANDS_ATTENTION atom for WM_STATE3301// Will be unset by the window manager after user react on the request for attention33023303XEvent xev;3304Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);3305Atom wm_attention = XInternAtom(x11_display, "_NET_WM_STATE_DEMANDS_ATTENTION", False);33063307memset(&xev, 0, sizeof(xev));3308xev.type = ClientMessage;3309xev.xclient.window = wd.x11_window;3310xev.xclient.message_type = wm_state;3311xev.xclient.format = 32;3312xev.xclient.data.l[0] = _NET_WM_STATE_ADD;3313xev.xclient.data.l[1] = wm_attention;33143315XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);3316XFlush(x11_display);3317}33183319void DisplayServerX11::window_move_to_foreground(WindowID p_window) {3320_THREAD_SAFE_METHOD_33213322ERR_FAIL_COND(!windows.has(p_window));3323const WindowData &wd = windows[p_window];33243325XEvent xev;3326Atom net_active_window = XInternAtom(x11_display, "_NET_ACTIVE_WINDOW", False);33273328memset(&xev, 0, sizeof(xev));3329xev.type = ClientMessage;3330xev.xclient.window = wd.x11_window;3331xev.xclient.message_type = net_active_window;3332xev.xclient.format = 32;3333xev.xclient.data.l[0] = 1;3334xev.xclient.data.l[1] = CurrentTime;33353336XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);3337XFlush(x11_display);3338}33393340DisplayServerX11::WindowID DisplayServerX11::get_focused_window() const {3341return last_focused_window;3342}33433344bool DisplayServerX11::window_is_focused(WindowID p_window) const {3345_THREAD_SAFE_METHOD_33463347ERR_FAIL_COND_V(!windows.has(p_window), false);33483349const WindowData &wd = windows[p_window];33503351return wd.focused;3352}33533354bool DisplayServerX11::window_can_draw(WindowID p_window) const {3355//this seems to be all that is provided by X113356return window_get_mode(p_window) != WINDOW_MODE_MINIMIZED;3357}33583359bool DisplayServerX11::can_any_window_draw() const {3360_THREAD_SAFE_METHOD_33613362for (const KeyValue<WindowID, WindowData> &E : windows) {3363if (window_get_mode(E.key) != WINDOW_MODE_MINIMIZED) {3364return true;3365}3366}33673368return false;3369}33703371void DisplayServerX11::window_set_ime_active(const bool p_active, WindowID p_window) {3372_THREAD_SAFE_METHOD_33733374ERR_FAIL_COND(!windows.has(p_window));3375WindowData &wd = windows[p_window];33763377if (!wd.xic) {3378return;3379}3380if (!wd.focused) {3381wd.ime_active = false;3382im_text = String();3383im_selection = Vector2i();3384return;3385}33863387// Block events polling while changing input focus3388// because it triggers some event polling internally.3389if (p_active) {3390MutexLock mutex_lock(events_mutex);33913392wd.ime_active = true;33933394XMapWindow(x11_display, wd.x11_xim_window);33953396XWindowAttributes xwa;3397XSync(x11_display, False);3398XGetWindowAttributes(x11_display, wd.x11_xim_window, &xwa);3399if (xwa.map_state == IsViewable && _window_focus_check()) {3400_set_input_focus(wd.x11_xim_window, RevertToParent);3401}3402XSetICFocus(wd.xic);3403} else {3404MutexLock mutex_lock(events_mutex);3405XUnsetICFocus(wd.xic);3406XUnmapWindow(x11_display, wd.x11_xim_window);3407wd.ime_active = false;34083409im_text = String();3410im_selection = Vector2i();3411}3412}34133414void DisplayServerX11::window_set_ime_position(const Point2i &p_pos, WindowID p_window) {3415_THREAD_SAFE_METHOD_34163417ERR_FAIL_COND(!windows.has(p_window));3418WindowData &wd = windows[p_window];34193420if (!wd.xic) {3421return;3422}3423if (!wd.focused) {3424return;3425}34263427if (wd.ime_active) {3428XWindowAttributes xwa;3429XSync(x11_display, False);3430XGetWindowAttributes(x11_display, wd.x11_xim_window, &xwa);3431if (xwa.map_state == IsViewable) {3432XMoveWindow(x11_display, wd.x11_xim_window, p_pos.x, p_pos.y);3433}3434}3435}34363437int DisplayServerX11::accessibility_should_increase_contrast() const {3438#ifdef DBUS_ENABLED3439if (!portal_desktop) {3440return -1;3441}3442return portal_desktop->get_high_contrast();3443#endif3444return -1;3445}34463447int DisplayServerX11::accessibility_screen_reader_active() const {3448#ifdef DBUS_ENABLED3449if (atspi_monitor && atspi_monitor->is_supported()) {3450return atspi_monitor->is_active();3451}3452#endif3453return -1;3454}34553456Point2i DisplayServerX11::ime_get_selection() const {3457return im_selection;3458}34593460String DisplayServerX11::ime_get_text() const {3461return im_text;3462}34633464void DisplayServerX11::cursor_set_shape(CursorShape p_shape) {3465_THREAD_SAFE_METHOD_34663467ERR_FAIL_INDEX(p_shape, CURSOR_MAX);34683469if (p_shape == current_cursor) {3470return;3471}34723473if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) {3474if (cursors[p_shape] != None) {3475for (const KeyValue<WindowID, WindowData> &E : windows) {3476XDefineCursor(x11_display, E.value.x11_window, cursors[p_shape]);3477}3478} else if (cursors[CURSOR_ARROW] != None) {3479for (const KeyValue<WindowID, WindowData> &E : windows) {3480XDefineCursor(x11_display, E.value.x11_window, cursors[CURSOR_ARROW]);3481}3482}3483}34843485current_cursor = p_shape;3486}34873488DisplayServerX11::CursorShape DisplayServerX11::cursor_get_shape() const {3489return current_cursor;3490}34913492void DisplayServerX11::cursor_set_custom_image(const Ref<Resource> &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) {3493_THREAD_SAFE_METHOD_34943495ERR_FAIL_INDEX(p_shape, CURSOR_MAX);34963497if (p_cursor.is_valid()) {3498HashMap<CursorShape, Vector<Variant>>::Iterator cursor_c = cursors_cache.find(p_shape);34993500if (cursor_c) {3501if (cursor_c->value[0] == p_cursor && cursor_c->value[1] == p_hotspot) {3502cursor_set_shape(p_shape);3503return;3504}35053506cursors_cache.erase(p_shape);3507}35083509Ref<Image> image = _get_cursor_image_from_resource(p_cursor, p_hotspot);3510ERR_FAIL_COND(image.is_null());3511Vector2i texture_size = image->get_size();35123513// Create the cursor structure3514XcursorImage *cursor_image = XcursorImageCreate(texture_size.width, texture_size.height);3515XcursorUInt image_size = texture_size.width * texture_size.height;3516XcursorDim size = sizeof(XcursorPixel) * image_size;35173518cursor_image->version = 1;3519cursor_image->size = size;3520cursor_image->xhot = p_hotspot.x;3521cursor_image->yhot = p_hotspot.y;35223523// allocate memory to contain the whole file3524cursor_image->pixels = (XcursorPixel *)memalloc(size);35253526for (XcursorPixel index = 0; index < image_size; index++) {3527int row_index = std::floor(index / texture_size.width);3528int column_index = index % int(texture_size.width);35293530*(cursor_image->pixels + index) = image->get_pixel(column_index, row_index).to_argb32();3531}35323533ERR_FAIL_NULL(cursor_image->pixels);35343535// Save it for a further usage3536cursors[p_shape] = XcursorImageLoadCursor(x11_display, cursor_image);35373538Vector<Variant> params;3539params.push_back(p_cursor);3540params.push_back(p_hotspot);3541cursors_cache.insert(p_shape, params);35423543if (p_shape == current_cursor) {3544if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) {3545for (const KeyValue<WindowID, WindowData> &E : windows) {3546XDefineCursor(x11_display, E.value.x11_window, cursors[p_shape]);3547}3548}3549}35503551memfree(cursor_image->pixels);3552XcursorImageDestroy(cursor_image);3553} else {3554// Reset to default system cursor3555if (cursor_img[p_shape]) {3556cursors[p_shape] = XcursorImageLoadCursor(x11_display, cursor_img[p_shape]);3557}35583559cursors_cache.erase(p_shape);35603561CursorShape c = current_cursor;3562current_cursor = CURSOR_MAX;3563cursor_set_shape(c);3564}3565}35663567bool DisplayServerX11::get_swap_cancel_ok() {3568return swap_cancel_ok;3569}35703571int DisplayServerX11::keyboard_get_layout_count() const {3572int _group_count = 0;3573XkbDescRec *kbd = XkbAllocKeyboard();3574if (kbd) {3575kbd->dpy = x11_display;3576XkbGetControls(x11_display, XkbAllControlsMask, kbd);3577XkbGetNames(x11_display, XkbSymbolsNameMask, kbd);35783579const Atom *groups = kbd->names->groups;3580if (kbd->ctrls != nullptr) {3581_group_count = kbd->ctrls->num_groups;3582} else {3583while (_group_count < XkbNumKbdGroups && groups[_group_count] != None) {3584_group_count++;3585}3586}3587XkbFreeKeyboard(kbd, 0, true);3588}3589return _group_count;3590}35913592int DisplayServerX11::keyboard_get_current_layout() const {3593XkbStateRec state;3594XkbGetState(x11_display, XkbUseCoreKbd, &state);3595return state.group;3596}35973598void DisplayServerX11::keyboard_set_current_layout(int p_index) {3599ERR_FAIL_INDEX(p_index, keyboard_get_layout_count());3600XkbLockGroup(x11_display, XkbUseCoreKbd, p_index);3601}36023603String DisplayServerX11::keyboard_get_layout_language(int p_index) const {3604String ret;3605XkbDescRec *kbd = XkbAllocKeyboard();3606if (kbd) {3607kbd->dpy = x11_display;3608XkbGetControls(x11_display, XkbAllControlsMask, kbd);3609XkbGetNames(x11_display, XkbSymbolsNameMask, kbd);3610XkbGetNames(x11_display, XkbGroupNamesMask, kbd);36113612int _group_count = 0;3613const Atom *groups = kbd->names->groups;3614if (kbd->ctrls != nullptr) {3615_group_count = kbd->ctrls->num_groups;3616} else {3617while (_group_count < XkbNumKbdGroups && groups[_group_count] != None) {3618_group_count++;3619}3620}36213622Atom names = kbd->names->symbols;3623if (names != None) {3624Vector<String> info = get_atom_name(x11_display, names).split("+");3625if (p_index >= 0 && p_index < _group_count) {3626if (p_index + 1 < info.size()) {3627ret = info[p_index + 1]; // Skip "pc" at the start and "inet"/"group" at the end of symbols.3628} else {3629ret = "en"; // No symbol for layout fallback to "en".3630}3631} else {3632ERR_PRINT("Index " + itos(p_index) + "is out of bounds (" + itos(_group_count) + ").");3633}3634}3635XkbFreeKeyboard(kbd, 0, true);3636}3637return ret.substr(0, 2);3638}36393640String DisplayServerX11::keyboard_get_layout_name(int p_index) const {3641String ret;3642XkbDescRec *kbd = XkbAllocKeyboard();3643if (kbd) {3644kbd->dpy = x11_display;3645XkbGetControls(x11_display, XkbAllControlsMask, kbd);3646XkbGetNames(x11_display, XkbSymbolsNameMask, kbd);3647XkbGetNames(x11_display, XkbGroupNamesMask, kbd);36483649int _group_count = 0;3650const Atom *groups = kbd->names->groups;3651if (kbd->ctrls != nullptr) {3652_group_count = kbd->ctrls->num_groups;3653} else {3654while (_group_count < XkbNumKbdGroups && groups[_group_count] != None) {3655_group_count++;3656}3657}36583659if (p_index >= 0 && p_index < _group_count) {3660ret = get_atom_name(x11_display, groups[p_index]);3661} else {3662ERR_PRINT("Index " + itos(p_index) + "is out of bounds (" + itos(_group_count) + ").");3663}3664XkbFreeKeyboard(kbd, 0, true);3665}3666return ret;3667}36683669Key DisplayServerX11::keyboard_get_keycode_from_physical(Key p_keycode) const {3670Key modifiers = p_keycode & KeyModifierMask::MODIFIER_MASK;3671Key keycode_no_mod = p_keycode & KeyModifierMask::CODE_MASK;3672unsigned int xkeycode = KeyMappingX11::get_xlibcode(keycode_no_mod);3673KeySym xkeysym = XkbKeycodeToKeysym(x11_display, xkeycode, keyboard_get_current_layout(), 0);3674if (is_ascii_lower_case(xkeysym)) {3675xkeysym -= ('a' - 'A');3676}36773678Key key = KeyMappingX11::get_keycode(xkeysym);3679// If not found, fallback to QWERTY.3680// This should match the behavior of the event pump3681if (key == Key::NONE) {3682return p_keycode;3683}3684return (Key)(key | modifiers);3685}36863687Key DisplayServerX11::keyboard_get_label_from_physical(Key p_keycode) const {3688Key modifiers = p_keycode & KeyModifierMask::MODIFIER_MASK;3689Key keycode_no_mod = p_keycode & KeyModifierMask::CODE_MASK;3690unsigned int xkeycode = KeyMappingX11::get_xlibcode(keycode_no_mod);3691KeySym xkeysym = XkbKeycodeToKeysym(x11_display, xkeycode, keyboard_get_current_layout(), 0);3692if (is_ascii_lower_case(xkeysym)) {3693xkeysym -= ('a' - 'A');3694}36953696Key key = KeyMappingX11::get_keycode(xkeysym);3697#ifdef XKB_ENABLED3698if (xkb_loaded_v08p) {3699char32_t chr = xkb_keysym_to_utf32(xkb_keysym_to_upper(xkeysym));3700if (chr != 0) {3701String keysym = String::chr(chr);3702key = fix_key_label(keysym[0], KeyMappingX11::get_keycode(xkeysym));3703}3704}3705#endif37063707// If not found, fallback to QWERTY.3708// This should match the behavior of the event pump3709if (key == Key::NONE) {3710return p_keycode;3711}3712return (Key)(key | modifiers);3713}37143715bool DisplayServerX11::color_picker(const Callable &p_callback) {3716#ifdef DBUS_ENABLED3717if (!portal_desktop) {3718return false;3719}3720WindowID window_id = last_focused_window;37213722if (!windows.has(window_id)) {3723window_id = MAIN_WINDOW_ID;3724}37253726String xid = vformat("x11:%x", (uint64_t)windows[window_id].x11_window);3727return portal_desktop->color_picker(xid, p_callback);3728#else3729return false;3730#endif3731}37323733DisplayServerX11::Property DisplayServerX11::_read_property(Display *p_display, Window p_window, Atom p_property) {3734Atom actual_type = None;3735int actual_format = 0;3736unsigned long nitems = 0;3737unsigned long bytes_after = 0;3738unsigned char *ret = nullptr;37393740// Keep trying to read the property until there are no bytes unread.3741if (p_property != None) {3742int read_bytes = 1024;3743do {3744if (ret != nullptr) {3745XFree(ret);3746}37473748XGetWindowProperty(p_display, p_window, p_property, 0, read_bytes, False, AnyPropertyType,3749&actual_type, &actual_format, &nitems, &bytes_after,3750&ret);37513752read_bytes *= 2;37533754} while (bytes_after != 0);3755}37563757Property p = { ret, actual_format, (int)nitems, actual_type };37583759return p;3760}37613762static Atom pick_target_from_list(Display *p_display, const Atom *p_list, int p_count) {3763static const char *target_type = "text/uri-list";37643765for (int i = 0; i < p_count; i++) {3766Atom atom = p_list[i];37673768if (atom != None && get_atom_name(p_display, atom) == target_type) {3769return atom;3770}3771}3772return None;3773}37743775static Atom pick_target_from_atoms(Display *p_disp, Atom p_t1, Atom p_t2, Atom p_t3) {3776static const char *target_type = "text/uri-list";3777if (p_t1 != None && get_atom_name(p_disp, p_t1) == target_type) {3778return p_t1;3779}37803781if (p_t2 != None && get_atom_name(p_disp, p_t2) == target_type) {3782return p_t2;3783}37843785if (p_t3 != None && get_atom_name(p_disp, p_t3) == target_type) {3786return p_t3;3787}37883789return None;3790}37913792void DisplayServerX11::_get_key_modifier_state(unsigned int p_x11_state, Ref<InputEventWithModifiers> state) {3793state->set_shift_pressed((p_x11_state & ShiftMask));3794state->set_ctrl_pressed((p_x11_state & ControlMask));3795state->set_alt_pressed((p_x11_state & Mod1Mask /*|| p_x11_state&Mod5Mask*/)); //altgr should not count as alt3796state->set_meta_pressed((p_x11_state & Mod4Mask));3797}37983799void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, LocalVector<XEvent> &p_events, uint32_t &p_event_index, bool p_echo) {3800WindowData &wd = windows[p_window];3801// X11 functions don't know what const is3802XKeyEvent *xkeyevent = p_event;38033804if (wd.ime_in_progress) {3805return;3806}3807if (wd.ime_suppress_next_keyup) {3808wd.ime_suppress_next_keyup = false;3809if (xkeyevent->type != KeyPress) {3810return;3811}3812}38133814// This code was pretty difficult to write.3815// The docs stink and every toolkit seems to3816// do it in a different way.38173818/* Phase 1, obtain a proper keysym */38193820// This was also very difficult to figure out.3821// You'd expect you could just use Keysym provided by3822// XKeycodeToKeysym to obtain internationalized3823// input.. WRONG!!3824// you must use XLookupString (???) which not only wastes3825// cycles generating an unnecessary string, but also3826// still works in half the cases. (won't handle deadkeys)3827// For more complex input methods (deadkeys and more advanced)3828// you have to use XmbLookupString (??).3829// So then you have to choose which of both results3830// you want to keep.3831// This is a real bizarreness and cpu waster.38323833KeySym keysym_keycode = 0; // keysym used to find a keycode3834KeySym keysym_unicode = 0; // keysym used to find unicode38353836// XLookupString returns keysyms usable as nice keycodes.3837char str[256] = {};3838XKeyEvent xkeyevent_no_mod = *xkeyevent;3839xkeyevent_no_mod.state &= 0xFF00;3840XLookupString(xkeyevent, str, 255, &keysym_unicode, nullptr);3841XLookupString(&xkeyevent_no_mod, nullptr, 0, &keysym_keycode, nullptr);38423843// Get a normalized keysym (ignoring modifiers like Shift/Ctrl).3844String keysym;3845#ifdef XKB_ENABLED3846if (xkb_loaded_v08p) {3847KeySym keysym_unicode_nm = 0; // keysym used to find unicode3848XLookupString(&xkeyevent_no_mod, nullptr, 0, &keysym_unicode_nm, nullptr);38493850// Unicode codepoint corresponding to the pressed key.3851// Printable keys (letters, numbers, symbols) return a valid codepoint.3852u_int32_t unicode_cp = xkb_keysym_to_utf32(xkb_keysym_to_upper(keysym_unicode_nm));38533854// Non-printable keys (Ctrl, Home, CapsLock, F1, etc.) return 0, so we skip them.3855if (unicode_cp != 0) {3856keysym = String::chr(unicode_cp);3857}3858}3859#endif38603861// Meanwhile, XLookupString returns keysyms useful for unicode.38623863if (!xmbstring) {3864// keep a temporary buffer for the string3865xmbstring = (char *)memalloc(sizeof(char) * 8);3866xmblen = 8;3867}38683869if (xkeyevent->type == KeyPress && wd.xic) {3870Status status;3871#ifdef X_HAVE_UTF8_STRING3872int utf8len = 8;3873char *utf8string = (char *)memalloc(sizeof(char) * utf8len);3874int utf8bytes = Xutf8LookupString(wd.xic, xkeyevent, utf8string,3875utf8len - 1, &keysym_unicode, &status);3876if (status == XBufferOverflow) {3877utf8len = utf8bytes + 1;3878utf8string = (char *)memrealloc(utf8string, utf8len);3879utf8bytes = Xutf8LookupString(wd.xic, xkeyevent, utf8string,3880utf8len - 1, &keysym_unicode, &status);3881}3882utf8string[utf8bytes] = '\0';38833884if (status == XLookupChars) {3885bool keypress = xkeyevent->type == KeyPress;38863887Key keycode = Key::NONE;3888if (KeyMappingX11::is_sym_numpad(keysym_unicode)) {3889// Special case for numpad keys.3890keycode = KeyMappingX11::get_keycode(keysym_unicode);3891}38923893if (keycode == Key::NONE) {3894keycode = KeyMappingX11::get_keycode(keysym_keycode);3895}38963897Key physical_keycode = KeyMappingX11::get_scancode(xkeyevent->keycode);38983899if (keycode >= Key::A + 32 && keycode <= Key::Z + 32) {3900keycode -= 'a' - 'A';3901}39023903String tmp = String::utf8(utf8string, utf8bytes);3904for (int i = 0; i < tmp.length(); i++) {3905Ref<InputEventKey> k;3906k.instantiate();3907if (physical_keycode == Key::NONE && keycode == Key::NONE && tmp[i] == 0) {3908continue;3909}39103911if (keycode == Key::NONE) {3912keycode = (Key)physical_keycode;3913}39143915_get_key_modifier_state(xkeyevent->state, k);39163917k->set_window_id(p_window);3918k->set_pressed(keypress);39193920k->set_keycode(keycode);3921k->set_physical_keycode(physical_keycode);3922if (!keysym.is_empty()) {3923k->set_key_label(fix_key_label(keysym[0], keycode));3924} else {3925k->set_key_label(keycode);3926}3927if (keypress) {3928k->set_unicode(fix_unicode(tmp[i]));3929}39303931k->set_echo(false);39323933if (k->get_keycode() == Key::BACKTAB) {3934//make it consistent across platforms.3935k->set_keycode(Key::TAB);3936k->set_physical_keycode(Key::TAB);3937k->set_shift_pressed(true);3938}39393940Input::get_singleton()->parse_input_event(k);3941}3942memfree(utf8string);3943return;3944}3945memfree(utf8string);3946#else3947do {3948int mnbytes = XmbLookupString(xic, xkeyevent, xmbstring, xmblen - 1, &keysym_unicode, &status);3949xmbstring[mnbytes] = '\0';39503951if (status == XBufferOverflow) {3952xmblen = mnbytes + 1;3953xmbstring = (char *)memrealloc(xmbstring, xmblen);3954}3955} while (status == XBufferOverflow);3956#endif3957#ifdef XKB_ENABLED3958} else if (xkeyevent->type == KeyPress && wd.xkb_state && xkb_loaded_v05p) {3959xkb_compose_feed_result res = xkb_compose_state_feed(wd.xkb_state, keysym_unicode);3960if (res == XKB_COMPOSE_FEED_ACCEPTED) {3961if (xkb_compose_state_get_status(wd.xkb_state) == XKB_COMPOSE_COMPOSED) {3962bool keypress = xkeyevent->type == KeyPress;3963Key physical_keycode = KeyMappingX11::get_scancode(xkeyevent->keycode);3964KeyLocation key_location = KeyMappingX11::get_location(xkeyevent->keycode);39653966Key keycode = Key::NONE;3967if (KeyMappingX11::is_sym_numpad(keysym_unicode)) {3968// Special case for numpad keys.3969keycode = KeyMappingX11::get_keycode(keysym_unicode);3970}39713972if (keycode == Key::NONE) {3973keycode = KeyMappingX11::get_keycode(keysym_keycode);3974}39753976if (keycode >= Key::A + 32 && keycode <= Key::Z + 32) {3977keycode -= 'a' - 'A';3978}39793980char str_xkb[256] = {};3981int str_xkb_size = xkb_compose_state_get_utf8(wd.xkb_state, str_xkb, 255);39823983String tmp = String::utf8(str_xkb, str_xkb_size);3984for (int i = 0; i < tmp.length(); i++) {3985Ref<InputEventKey> k;3986k.instantiate();3987if (physical_keycode == Key::NONE && keycode == Key::NONE && tmp[i] == 0) {3988continue;3989}39903991if (keycode == Key::NONE) {3992keycode = (Key)physical_keycode;3993}39943995_get_key_modifier_state(xkeyevent->state, k);39963997k->set_window_id(p_window);3998k->set_pressed(keypress);39994000k->set_keycode(keycode);4001k->set_physical_keycode(physical_keycode);4002if (!keysym.is_empty()) {4003k->set_key_label(fix_key_label(keysym[0], keycode));4004} else {4005k->set_key_label(keycode);4006}4007if (keypress) {4008k->set_unicode(fix_unicode(tmp[i]));4009}40104011k->set_location(key_location);40124013k->set_echo(false);40144015if (k->get_keycode() == Key::BACKTAB) {4016//make it consistent across platforms.4017k->set_keycode(Key::TAB);4018k->set_physical_keycode(Key::TAB);4019k->set_shift_pressed(true);4020}40214022Input::get_singleton()->parse_input_event(k);4023}4024return;4025}4026}4027#endif4028}40294030/* Phase 2, obtain a Godot keycode from the keysym */40314032// KeyMappingX11 just translated the X11 keysym to a PIGUI4033// keysym, so it works in all platforms the same.40344035Key keycode = Key::NONE;4036if (KeyMappingX11::is_sym_numpad(keysym_unicode) || KeyMappingX11::is_sym_numpad(keysym_keycode)) {4037// Special case for numpad keys.4038keycode = KeyMappingX11::get_keycode(keysym_unicode);4039}40404041if (keycode == Key::NONE) {4042keycode = KeyMappingX11::get_keycode(keysym_keycode);4043}40444045Key physical_keycode = KeyMappingX11::get_scancode(xkeyevent->keycode);40464047KeyLocation key_location = KeyMappingX11::get_location(xkeyevent->keycode);40484049/* Phase 3, obtain a unicode character from the keysym */40504051// KeyMappingX11 also translates keysym to unicode.4052// It does a binary search on a table to translate4053// most properly.4054char32_t unicode = keysym_unicode > 0 ? KeyMappingX11::get_unicode_from_keysym(keysym_unicode) : 0;40554056/* Phase 4, determine if event must be filtered */40574058// This seems to be a side-effect of using XIM.4059// XFilterEvent looks like a core X11 function,4060// but it's actually just used to see if we must4061// ignore a deadkey, or events XIM determines4062// must not reach the actual gui.4063// Guess it was a design problem of the extension40644065bool keypress = xkeyevent->type == KeyPress;40664067if (physical_keycode == Key::NONE && keycode == Key::NONE && unicode == 0) {4068return;4069}40704071if (keycode == Key::NONE) {4072keycode = (Key)physical_keycode;4073}40744075/* Phase 5, determine modifier mask */40764077// No problems here, except I had no way to4078// know Mod1 was ALT and Mod4 was META (applekey/winkey)4079// just tried Mods until i found them.40804081//print_verbose("mod1: "+itos(xkeyevent->state&Mod1Mask)+" mod 5: "+itos(xkeyevent->state&Mod5Mask));40824083Ref<InputEventKey> k;4084k.instantiate();4085k->set_window_id(p_window);40864087_get_key_modifier_state(xkeyevent->state, k);40884089/* Phase 6, determine echo character */40904091// Echo characters in X11 are a keyrelease and a keypress4092// one after the other with the (almot) same timestamp.4093// To detect them, i compare to the next event in list and4094// check that their difference in time is below a threshold.40954096if (xkeyevent->type != KeyPress) {4097p_echo = false;40984099// make sure there are events pending,4100// so this call won't block.4101if (p_event_index + 1 < p_events.size()) {4102XEvent &peek_event = p_events[p_event_index + 1];41034104// I'm using a threshold of 5 msecs,4105// since sometimes there seems to be a little4106// jitter. I'm still not convinced that all this approach4107// is correct, but the xorg developers are4108// not very helpful today.41094110#define ABSDIFF(x, y) (((x) < (y)) ? ((y) - (x)) : ((x) - (y)))4111::Time threshold = ABSDIFF(peek_event.xkey.time, xkeyevent->time);4112#undef ABSDIFF4113if (peek_event.type == KeyPress && threshold < 5) {4114KeySym rk;4115XLookupString((XKeyEvent *)&peek_event, str, 256, &rk, nullptr);4116if (rk == keysym_keycode) {4117// Consume to next event.4118++p_event_index;4119_handle_key_event(p_window, (XKeyEvent *)&peek_event, p_events, p_event_index, true);4120return; //ignore current, echo next4121}4122}41234124// use the time from peek_event so it always works4125}41264127// save the time to check for echo when keypress happens4128}41294130/* Phase 7, send event to Window */41314132k->set_pressed(keypress);41334134if (keycode >= Key::A + 32 && keycode <= Key::Z + 32) {4135keycode -= int('a' - 'A');4136}41374138k->set_keycode(keycode);4139k->set_physical_keycode((Key)physical_keycode);4140if (!keysym.is_empty()) {4141k->set_key_label(fix_key_label(keysym[0], keycode));4142} else {4143k->set_key_label(keycode);4144}4145if (keypress) {4146k->set_unicode(fix_unicode(unicode));4147}41484149k->set_location(key_location);41504151k->set_echo(p_echo);41524153if (k->get_keycode() == Key::BACKTAB) {4154//make it consistent across platforms.4155k->set_keycode(Key::TAB);4156k->set_physical_keycode(Key::TAB);4157k->set_shift_pressed(true);4158}41594160//don't set mod state if modifier keys are released by themselves4161//else event.is_action() will not work correctly here4162if (!k->is_pressed()) {4163if (k->get_keycode() == Key::SHIFT) {4164k->set_shift_pressed(false);4165} else if (k->get_keycode() == Key::CTRL) {4166k->set_ctrl_pressed(false);4167} else if (k->get_keycode() == Key::ALT) {4168k->set_alt_pressed(false);4169} else if (k->get_keycode() == Key::META) {4170k->set_meta_pressed(false);4171}4172}41734174bool last_is_pressed = Input::get_singleton()->is_key_pressed(k->get_keycode());4175if (k->is_pressed()) {4176if (last_is_pressed) {4177k->set_echo(true);4178}4179}41804181Input::get_singleton()->parse_input_event(k);4182}41834184Atom DisplayServerX11::_process_selection_request_target(Atom p_target, Window p_requestor, Atom p_property, Atom p_selection) const {4185if (p_target == XInternAtom(x11_display, "TARGETS", 0)) {4186// Request to list all supported targets.4187Atom data[9];4188data[0] = XInternAtom(x11_display, "TARGETS", 0);4189data[1] = XInternAtom(x11_display, "SAVE_TARGETS", 0);4190data[2] = XInternAtom(x11_display, "MULTIPLE", 0);4191data[3] = XInternAtom(x11_display, "UTF8_STRING", 0);4192data[4] = XInternAtom(x11_display, "COMPOUND_TEXT", 0);4193data[5] = XInternAtom(x11_display, "TEXT", 0);4194data[6] = XA_STRING;4195data[7] = XInternAtom(x11_display, "text/plain;charset=utf-8", 0);4196data[8] = XInternAtom(x11_display, "text/plain", 0);41974198XChangeProperty(x11_display,4199p_requestor,4200p_property,4201XA_ATOM,420232,4203PropModeReplace,4204(unsigned char *)&data,4205std_size(data));4206return p_property;4207} else if (p_target == XInternAtom(x11_display, "SAVE_TARGETS", 0)) {4208// Request to check if SAVE_TARGETS is supported, nothing special to do.4209XChangeProperty(x11_display,4210p_requestor,4211p_property,4212XInternAtom(x11_display, "NULL", False),421332,4214PropModeReplace,4215nullptr,42160);4217return p_property;4218} else if (p_target == XInternAtom(x11_display, "UTF8_STRING", 0) ||4219p_target == XInternAtom(x11_display, "COMPOUND_TEXT", 0) ||4220p_target == XInternAtom(x11_display, "TEXT", 0) ||4221p_target == XA_STRING ||4222p_target == XInternAtom(x11_display, "text/plain;charset=utf-8", 0) ||4223p_target == XInternAtom(x11_display, "text/plain", 0)) {4224// Directly using internal clipboard because we know our window4225// is the owner during a selection request.4226CharString clip;4227static const char *target_type = "PRIMARY";4228if (p_selection != None && get_atom_name(x11_display, p_selection) == target_type) {4229clip = internal_clipboard_primary.utf8();4230} else {4231clip = internal_clipboard.utf8();4232}4233XChangeProperty(x11_display,4234p_requestor,4235p_property,4236p_target,42378,4238PropModeReplace,4239(unsigned char *)clip.get_data(),4240clip.length());4241return p_property;4242} else {4243char *target_name = XGetAtomName(x11_display, p_target);4244print_verbose(vformat("Target '%s' not supported.", target_name));4245if (target_name) {4246XFree(target_name);4247}4248return None;4249}4250}42514252void DisplayServerX11::_handle_selection_request_event(XSelectionRequestEvent *p_event) const {4253XEvent respond;4254if (p_event->target == XInternAtom(x11_display, "MULTIPLE", 0)) {4255// Request for multiple target conversions at once.4256Atom atom_pair = XInternAtom(x11_display, "ATOM_PAIR", False);4257respond.xselection.property = None;42584259Atom type;4260int format;4261unsigned long len;4262unsigned long remaining;4263unsigned char *data = nullptr;4264if (XGetWindowProperty(x11_display, p_event->requestor, p_event->property, 0, LONG_MAX, False, atom_pair, &type, &format, &len, &remaining, &data) == Success) {4265if ((len >= 2) && data) {4266Atom *targets = (Atom *)data;4267for (uint64_t i = 0; i < len; i += 2) {4268Atom target = targets[i];4269Atom &property = targets[i + 1];4270property = _process_selection_request_target(target, p_event->requestor, property, p_event->selection);4271}42724273XChangeProperty(x11_display,4274p_event->requestor,4275p_event->property,4276atom_pair,427732,4278PropModeReplace,4279(unsigned char *)targets,4280len);42814282respond.xselection.property = p_event->property;4283}4284XFree(data);4285}4286} else {4287// Request for target conversion.4288respond.xselection.property = _process_selection_request_target(p_event->target, p_event->requestor, p_event->property, p_event->selection);4289}42904291respond.xselection.type = SelectionNotify;4292respond.xselection.display = p_event->display;4293respond.xselection.requestor = p_event->requestor;4294respond.xselection.selection = p_event->selection;4295respond.xselection.target = p_event->target;4296respond.xselection.time = p_event->time;42974298XSendEvent(x11_display, p_event->requestor, True, NoEventMask, &respond);4299XFlush(x11_display);4300}43014302int DisplayServerX11::_xim_preedit_start_callback(::XIM xim, ::XPointer client_data,4303::XPointer call_data) {4304DisplayServerX11 *ds = reinterpret_cast<DisplayServerX11 *>(client_data);4305WindowID window_id = ds->_get_focused_window_or_popup();4306WindowData &wd = ds->windows[window_id];4307if (wd.ime_active) {4308wd.ime_in_progress = true;4309}43104311return -1; // Allow preedit strings of any length (no limit).4312}43134314void DisplayServerX11::_xim_preedit_done_callback(::XIM xim, ::XPointer client_data,4315::XPointer call_data) {4316DisplayServerX11 *ds = reinterpret_cast<DisplayServerX11 *>(client_data);4317WindowID window_id = ds->_get_focused_window_or_popup();4318WindowData &wd = ds->windows[window_id];4319if (wd.ime_active) {4320wd.ime_in_progress = false;4321wd.ime_suppress_next_keyup = true;4322}4323}43244325void DisplayServerX11::_xim_preedit_draw_callback(::XIM xim, ::XPointer client_data,4326::XIMPreeditDrawCallbackStruct *call_data) {4327DisplayServerX11 *ds = reinterpret_cast<DisplayServerX11 *>(client_data);4328WindowID window_id = ds->_get_focused_window_or_popup();4329WindowData &wd = ds->windows[window_id];43304331XIMText *xim_text = call_data->text;4332if (wd.ime_active) {4333if (xim_text != nullptr) {4334String changed_text;4335if (xim_text->encoding_is_wchar) {4336changed_text = String(xim_text->string.wide_char);4337} else {4338changed_text.append_utf8(xim_text->string.multi_byte);4339}43404341if (call_data->chg_length < 0) {4342ds->im_text = ds->im_text.substr(0, call_data->chg_first) + changed_text;4343} else {4344ds->im_text = ds->im_text.substr(0, call_data->chg_first) + changed_text + ds->im_text.substr(call_data->chg_length);4345}43464347// Find the start and end of the selection.4348int start = 0, count = 0;4349for (int i = 0; i < xim_text->length; i++) {4350if (xim_text->feedback[i] & XIMReverse) {4351if (count == 0) {4352start = i;4353count = 1;4354} else {4355count++;4356}4357}4358}4359if (count > 0) {4360ds->im_selection = Point2i(start + call_data->chg_first, count);4361} else {4362ds->im_selection = Point2i(call_data->caret, 0);4363}4364} else {4365ds->im_text = String();4366ds->im_selection = Point2i();4367}43684369callable_mp((Object *)OS_Unix::get_singleton()->get_main_loop(), &Object::notification).call_deferred(MainLoop::NOTIFICATION_OS_IME_UPDATE, false);4370}4371}43724373void DisplayServerX11::_xim_preedit_caret_callback(::XIM xim, ::XPointer client_data,4374::XIMPreeditCaretCallbackStruct *call_data) {4375}43764377void DisplayServerX11::_xim_destroy_callback(::XIM im, ::XPointer client_data,4378::XPointer call_data) {4379DisplayServerX11 *ds = reinterpret_cast<DisplayServerX11 *>(client_data);4380ds->xim = nullptr;43814382for (KeyValue<WindowID, WindowData> &E : ds->windows) {4383E.value.xic = nullptr;4384}4385}43864387void DisplayServerX11::_window_changed(XEvent *event) {4388WindowID window_id = MAIN_WINDOW_ID;43894390// Assign the event to the relevant window4391for (const KeyValue<WindowID, WindowData> &E : windows) {4392if (event->xany.window == E.value.x11_window) {4393window_id = E.key;4394break;4395}4396}43974398Rect2i new_rect;43994400WindowData &wd = windows[window_id];4401if (wd.x11_window != event->xany.window) { // Check if the correct window, in case it was not main window or anything else4402return;4403}44044405{4406//the position in xconfigure is not useful here, obtain it manually4407int x = 0, y = 0;4408Window child;4409XTranslateCoordinates(x11_display, wd.x11_window, DefaultRootWindow(x11_display), 0, 0, &x, &y, &child);4410new_rect.position.x = x;4411new_rect.position.y = y;44124413new_rect.size.width = event->xconfigure.width;4414new_rect.size.height = event->xconfigure.height;4415}44164417if (new_rect == Rect2i(wd.position, wd.size)) {4418return;4419}44204421wd.position = new_rect.position;4422wd.size = new_rect.size;44234424#if defined(RD_ENABLED)4425if (rendering_context) {4426rendering_context->window_set_size(window_id, wd.size.width, wd.size.height);4427}4428#endif4429#if defined(GLES3_ENABLED)4430if (gl_manager) {4431gl_manager->window_resize(window_id, wd.size.width, wd.size.height);4432}4433if (gl_manager_egl) {4434gl_manager_egl->window_resize(window_id, wd.size.width, wd.size.height);4435}4436#endif44374438if (wd.rect_changed_callback.is_valid()) {4439wd.rect_changed_callback.call(new_rect);4440}4441}44424443DisplayServer::WindowID DisplayServerX11::_get_focused_window_or_popup() const {4444const List<WindowID>::Element *E = popup_list.back();4445if (E) {4446return E->get();4447}44484449return last_focused_window;4450}44514452void DisplayServerX11::_dispatch_input_events(const Ref<InputEvent> &p_event) {4453static_cast<DisplayServerX11 *>(get_singleton())->_dispatch_input_event(p_event);4454}44554456void DisplayServerX11::_dispatch_input_event(const Ref<InputEvent> &p_event) {4457{4458List<WindowID>::Element *E = popup_list.back();4459if (E && Object::cast_to<InputEventKey>(*p_event)) {4460// Redirect keyboard input to active popup.4461if (windows.has(E->get())) {4462Callable callable = windows[E->get()].input_event_callback;4463if (callable.is_valid()) {4464callable.call(p_event);4465}4466}4467return;4468}4469}44704471Ref<InputEventFromWindow> event_from_window = p_event;4472if (event_from_window.is_valid() && event_from_window->get_window_id() != INVALID_WINDOW_ID) {4473// Send to a single window.4474if (windows.has(event_from_window->get_window_id())) {4475Callable callable = windows[event_from_window->get_window_id()].input_event_callback;4476if (callable.is_valid()) {4477callable.call(p_event);4478}4479}4480} else {4481// Send to all windows. Copy all pending callbacks, since callback can erase window.4482Vector<Callable> cbs;4483for (KeyValue<WindowID, WindowData> &E : windows) {4484Callable callable = E.value.input_event_callback;4485if (callable.is_valid()) {4486cbs.push_back(callable);4487}4488}4489for (const Callable &cb : cbs) {4490cb.call(p_event);4491}4492}4493}44944495void DisplayServerX11::_send_window_event(const WindowData &wd, WindowEvent p_event) {4496if (wd.event_callback.is_valid()) {4497Variant event = int(p_event);4498wd.event_callback.call(event);4499}4500}45014502void DisplayServerX11::_set_input_focus(Window p_window, int p_revert_to) {4503Window focused_window;4504int focus_ret_state;4505XGetInputFocus(x11_display, &focused_window, &focus_ret_state);45064507// Only attempt to change focus if the window isn't already focused, in order to4508// prevent issues with Godot stealing input focus with alternative window managers.4509if (p_window != focused_window) {4510XSetInputFocus(x11_display, p_window, p_revert_to, CurrentTime);4511}4512}45134514void DisplayServerX11::_poll_events_thread(void *ud) {4515DisplayServerX11 *display_server = static_cast<DisplayServerX11 *>(ud);4516display_server->_poll_events();4517}45184519Bool DisplayServerX11::_predicate_all_events(Display *display, XEvent *event, XPointer arg) {4520// Just accept all events.4521return True;4522}45234524bool DisplayServerX11::_wait_for_events(int timeout_seconds, int timeout_microseconds) const {4525int x11_fd = ConnectionNumber(x11_display);4526fd_set in_fds;45274528XFlush(x11_display);45294530FD_ZERO(&in_fds);4531FD_SET(x11_fd, &in_fds);45324533struct timeval tv;4534tv.tv_sec = timeout_seconds;4535tv.tv_usec = timeout_microseconds;45364537// Wait for next event or timeout.4538int num_ready_fds = select(x11_fd + 1, &in_fds, nullptr, nullptr, &tv);45394540if (num_ready_fds > 0) {4541// Event received.4542return true;4543} else {4544// Error or timeout.4545if (num_ready_fds < 0) {4546ERR_PRINT("_wait_for_events: select error: " + itos(errno));4547}4548return false;4549}4550}45514552void DisplayServerX11::_poll_events() {4553while (!events_thread_done.is_set()) {4554// Wait with a shorter timeout from the events thread to avoid delayed inputs.4555_wait_for_events(0, 1000);45564557// Process events from the queue.4558{4559MutexLock mutex_lock(events_mutex);45604561_check_pending_events(polled_events);4562}4563}4564}45654566void DisplayServerX11::_check_pending_events(LocalVector<XEvent> &r_events) {4567// Flush to make sure to gather all pending events.4568XFlush(x11_display);45694570// Non-blocking wait for next event and remove it from the queue.4571XEvent ev = {};4572while (XCheckIfEvent(x11_display, &ev, _predicate_all_events, nullptr)) {4573// Check if the input manager wants to process the event.4574if (XFilterEvent(&ev, None)) {4575// Event has been filtered by the Input Manager,4576// it has to be ignored and a new one will be received.4577continue;4578}45794580// Handle selection request events directly in the event thread, because4581// communication through the x server takes several events sent back and forth4582// and we don't want to block other programs while processing only one each frame.4583if (ev.type == SelectionRequest) {4584_handle_selection_request_event(&(ev.xselectionrequest));4585continue;4586}45874588r_events.push_back(ev);4589}4590}45914592DisplayServer::WindowID DisplayServerX11::window_get_active_popup() const {4593const List<WindowID>::Element *E = popup_list.back();4594if (E) {4595return E->get();4596} else {4597return INVALID_WINDOW_ID;4598}4599}46004601void DisplayServerX11::window_set_popup_safe_rect(WindowID p_window, const Rect2i &p_rect) {4602_THREAD_SAFE_METHOD_46034604ERR_FAIL_COND(!windows.has(p_window));4605WindowData &wd = windows[p_window];4606wd.parent_safe_rect = p_rect;4607}46084609Rect2i DisplayServerX11::window_get_popup_safe_rect(WindowID p_window) const {4610_THREAD_SAFE_METHOD_46114612ERR_FAIL_COND_V(!windows.has(p_window), Rect2i());4613const WindowData &wd = windows[p_window];4614return wd.parent_safe_rect;4615}46164617void DisplayServerX11::popup_open(WindowID p_window) {4618_THREAD_SAFE_METHOD_46194620bool has_popup_ancestor = false;4621WindowID transient_root = p_window;4622while (true) {4623WindowID parent = windows[transient_root].transient_parent;4624if (parent == INVALID_WINDOW_ID) {4625break;4626} else {4627transient_root = parent;4628if (windows[parent].is_popup) {4629has_popup_ancestor = true;4630break;4631}4632}4633}46344635// Detect tooltips and other similar popups that shouldn't block input to their parent.4636bool ignores_input = window_get_flag(WINDOW_FLAG_NO_FOCUS, p_window) && window_get_flag(WINDOW_FLAG_MOUSE_PASSTHROUGH, p_window);46374638WindowData &wd = windows[p_window];4639if (wd.is_popup || (has_popup_ancestor && !ignores_input)) {4640// Find current popup parent, or root popup if new window is not transient.4641List<WindowID>::Element *C = nullptr;4642List<WindowID>::Element *E = popup_list.back();4643while (E) {4644if (wd.transient_parent != E->get() || wd.transient_parent == INVALID_WINDOW_ID) {4645C = E;4646E = E->prev();4647} else {4648break;4649}4650}4651if (C) {4652_send_window_event(windows[C->get()], DisplayServerX11::WINDOW_EVENT_CLOSE_REQUEST);4653}46544655time_since_popup = OS::get_singleton()->get_ticks_msec();4656popup_list.push_back(p_window);4657}4658}46594660void DisplayServerX11::popup_close(WindowID p_window) {4661_THREAD_SAFE_METHOD_46624663List<WindowID>::Element *E = popup_list.find(p_window);4664while (E) {4665List<WindowID>::Element *F = E->next();4666WindowID win_id = E->get();4667popup_list.erase(E);46684669if (win_id != p_window) {4670// Only request close on related windows, not this window. We are already processing it.4671_send_window_event(windows[win_id], DisplayServerX11::WINDOW_EVENT_CLOSE_REQUEST);4672}4673E = F;4674}4675}46764677bool DisplayServerX11::mouse_process_popups() {4678_THREAD_SAFE_METHOD_46794680if (popup_list.is_empty()) {4681return false;4682}46834684uint64_t delta = OS::get_singleton()->get_ticks_msec() - time_since_popup;4685if (delta < 250) {4686return false;4687}46884689int number_of_screens = XScreenCount(x11_display);4690bool closed = false;4691for (int i = 0; i < number_of_screens; i++) {4692Window root, child;4693int root_x, root_y, win_x, win_y;4694unsigned int mask;4695if (XQueryPointer(x11_display, XRootWindow(x11_display, i), &root, &child, &root_x, &root_y, &win_x, &win_y, &mask)) {4696XWindowAttributes root_attrs;4697XGetWindowAttributes(x11_display, root, &root_attrs);4698Vector2i pos = Vector2i(root_attrs.x + root_x, root_attrs.y + root_y);4699if (mask != last_mouse_monitor_mask) {4700if (((mask & Button1Mask) || (mask & Button2Mask) || (mask & Button3Mask) || (mask & Button4Mask) || (mask & Button5Mask))) {4701List<WindowID>::Element *C = nullptr;4702List<WindowID>::Element *E = popup_list.back();4703// Find top popup to close.4704while (E) {4705// Popup window area.4706Rect2i win_rect = Rect2i(window_get_position_with_decorations(E->get()), window_get_size_with_decorations(E->get()));4707// Area of the parent window, which responsible for opening sub-menu.4708Rect2i safe_rect = window_get_popup_safe_rect(E->get());4709if (win_rect.has_point(pos)) {4710break;4711} else if (safe_rect != Rect2i() && safe_rect.has_point(pos)) {4712break;4713} else {4714C = E;4715E = E->prev();4716}4717}4718if (C) {4719_send_window_event(windows[C->get()], DisplayServerX11::WINDOW_EVENT_CLOSE_REQUEST);4720closed = true;4721}4722}4723}4724last_mouse_monitor_mask = mask;4725}4726}4727return closed;4728}47294730bool DisplayServerX11::_window_focus_check() {4731Window focused_window;4732int focus_ret_state;4733XGetInputFocus(x11_display, &focused_window, &focus_ret_state);47344735bool has_focus = false;4736for (const KeyValue<int, DisplayServerX11::WindowData> &wid : windows) {4737if (wid.value.x11_window == focused_window || (wid.value.xic && wid.value.ime_active && wid.value.x11_xim_window == focused_window)) {4738has_focus = true;4739break;4740}4741}47424743return has_focus;4744}47454746void DisplayServerX11::process_events() {4747ERR_FAIL_COND(!Thread::is_main_thread());47484749_THREAD_SAFE_LOCK_47504751#ifdef DISPLAY_SERVER_X11_DEBUG_LOGS_ENABLED4752static int frame = 0;4753++frame;4754#endif47554756bool ignore_events = mouse_process_popups();47574758if (app_focused) {4759//verify that one of the windows has focus, else send focus out notification4760bool focus_found = false;4761for (const KeyValue<WindowID, WindowData> &E : windows) {4762if (E.value.focused) {4763focus_found = true;4764break;4765}4766}47674768if (!focus_found) {4769uint64_t delta = OS::get_singleton()->get_ticks_msec() - time_since_no_focus;47704771if (delta > 250) {4772//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.4773if (OS::get_singleton()->get_main_loop()) {4774DEBUG_LOG_X11("All focus lost, triggering NOTIFICATION_APPLICATION_FOCUS_OUT\n");4775OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT);4776}4777app_focused = false;4778}4779} else {4780time_since_no_focus = OS::get_singleton()->get_ticks_msec();4781}4782}47834784do_mouse_warp = false;47854786// Is the current mouse mode one where it needs to be grabbed.4787bool mouse_mode_grab = mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN;47884789xi.pressure = 0;4790xi.tilt = Vector2();4791xi.pressure_supported = false;47924793LocalVector<XEvent> events;4794{4795// Block events polling while flushing events.4796MutexLock mutex_lock(events_mutex);4797events = polled_events;4798polled_events.clear();4799}48004801for (uint32_t event_index = 0; event_index < events.size(); ++event_index) {4802XEvent &event = events[event_index];48034804bool ime_window_event = false;4805WindowID window_id = MAIN_WINDOW_ID;48064807// Assign the event to the relevant window4808for (const KeyValue<WindowID, WindowData> &E : windows) {4809if (event.xany.window == E.value.x11_window) {4810window_id = E.key;4811break;4812}4813if (event.xany.window == E.value.x11_xim_window) {4814window_id = E.key;4815ime_window_event = true;4816break;4817}4818}48194820if (XGetEventData(x11_display, &event.xcookie)) {4821if (event.xcookie.type == GenericEvent && event.xcookie.extension == xi.opcode) {4822XIDeviceEvent *event_data = (XIDeviceEvent *)event.xcookie.data;4823switch (event_data->evtype) {4824case XI_HierarchyChanged:4825case XI_DeviceChanged: {4826_refresh_device_info();4827} break;4828case XI_RawMotion: {4829if (ime_window_event || ignore_events) {4830break;4831}4832XIRawEvent *raw_event = (XIRawEvent *)event_data;4833int device_id = raw_event->sourceid;48344835// Determine the axis used (called valuators in XInput for some forsaken reason)4836// Mask is a bitmask indicating which axes are involved.4837// We are interested in the values of axes 0 and 1.4838if (raw_event->valuators.mask_len <= 0) {4839break;4840}48414842const double *values = raw_event->raw_values;48434844double rel_x = 0.0;4845double rel_y = 0.0;48464847if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_ABSX)) {4848rel_x = *values;4849values++;4850}48514852if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_ABSY)) {4853rel_y = *values;4854values++;4855}48564857if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_PRESSURE)) {4858HashMap<int, Vector2>::Iterator pen_pressure = xi.pen_pressure_range.find(device_id);4859if (pen_pressure) {4860Vector2 pen_pressure_range = pen_pressure->value;4861if (pen_pressure_range != Vector2()) {4862xi.pressure_supported = true;4863xi.pressure = (*values - pen_pressure_range[0]) /4864(pen_pressure_range[1] - pen_pressure_range[0]);4865}4866}48674868values++;4869}48704871if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_TILTX)) {4872HashMap<int, Vector2>::Iterator pen_tilt_x = xi.pen_tilt_x_range.find(device_id);4873if (pen_tilt_x) {4874Vector2 pen_tilt_x_range = pen_tilt_x->value;4875if (pen_tilt_x_range[0] != 0 && *values < 0) {4876xi.tilt.x = *values / -pen_tilt_x_range[0];4877} else if (pen_tilt_x_range[1] != 0) {4878xi.tilt.x = *values / pen_tilt_x_range[1];4879}4880}48814882values++;4883}48844885if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_TILTY)) {4886HashMap<int, Vector2>::Iterator pen_tilt_y = xi.pen_tilt_y_range.find(device_id);4887if (pen_tilt_y) {4888Vector2 pen_tilt_y_range = pen_tilt_y->value;4889if (pen_tilt_y_range[0] != 0 && *values < 0) {4890xi.tilt.y = *values / -pen_tilt_y_range[0];4891} else if (pen_tilt_y_range[1] != 0) {4892xi.tilt.y = *values / pen_tilt_y_range[1];4893}4894}48954896values++;4897}48984899HashMap<int, bool>::Iterator pen_inverted = xi.pen_inverted_devices.find(device_id);4900if (pen_inverted) {4901xi.pen_inverted = pen_inverted->value;4902}49034904// https://bugs.freedesktop.org/show_bug.cgi?id=716094905// http://lists.libsdl.org/pipermail/commits-libsdl.org/2015-June/000282.html4906if (raw_event->time == xi.last_relative_time && rel_x == xi.relative_motion.x && rel_y == xi.relative_motion.y) {4907break; // Flush duplicate to avoid overly fast motion4908}49094910xi.old_raw_pos.x = xi.raw_pos.x;4911xi.old_raw_pos.y = xi.raw_pos.y;4912xi.raw_pos.x = rel_x;4913xi.raw_pos.y = rel_y;49144915HashMap<int, Vector2>::Iterator abs_info = xi.absolute_devices.find(device_id);49164917if (abs_info) {4918// Absolute mode device4919Vector2 mult = abs_info->value;49204921xi.relative_motion.x += (xi.raw_pos.x - xi.old_raw_pos.x) * mult.x;4922xi.relative_motion.y += (xi.raw_pos.y - xi.old_raw_pos.y) * mult.y;4923} else {4924// Relative mode device4925xi.relative_motion.x = xi.raw_pos.x;4926xi.relative_motion.y = xi.raw_pos.y;4927}49284929xi.last_relative_time = raw_event->time;4930} break;4931#ifdef TOUCH_ENABLED4932case XI_TouchBegin:4933case XI_TouchEnd: {4934if (ime_window_event || ignore_events) {4935break;4936}4937bool is_begin = event_data->evtype == XI_TouchBegin;49384939int index = event_data->detail;4940Vector2 pos = Vector2(event_data->event_x, event_data->event_y);49414942Ref<InputEventScreenTouch> st;4943st.instantiate();4944st->set_window_id(window_id);4945st->set_index(index);4946st->set_position(pos);4947st->set_pressed(is_begin);49484949if (is_begin) {4950if (xi.state.has(index)) { // Defensive4951break;4952}4953xi.state[index] = pos;4954if (xi.state.size() == 1) {4955// X11 may send a motion event when a touch gesture begins, that would result4956// in a spurious mouse motion event being sent to Godot; remember it to be able to filter it out4957xi.mouse_pos_to_filter = pos;4958}4959Input::get_singleton()->parse_input_event(st);4960} else {4961if (!xi.state.has(index)) { // Defensive4962break;4963}4964xi.state.erase(index);4965Input::get_singleton()->parse_input_event(st);4966}4967} break;49684969case XI_TouchUpdate: {4970if (ime_window_event || ignore_events) {4971break;4972}49734974int index = event_data->detail;4975Vector2 pos = Vector2(event_data->event_x, event_data->event_y);49764977HashMap<int, Vector2>::Iterator curr_pos_elem = xi.state.find(index);4978if (!curr_pos_elem) { // Defensive4979break;4980}49814982if (curr_pos_elem->value != pos) {4983Ref<InputEventScreenDrag> sd;4984sd.instantiate();4985sd->set_window_id(window_id);4986sd->set_index(index);4987sd->set_position(pos);4988sd->set_relative(pos - curr_pos_elem->value);4989sd->set_relative_screen_position(sd->get_relative());4990Input::get_singleton()->parse_input_event(sd);49914992curr_pos_elem->value = pos;4993}4994} break;4995#endif4996}4997}4998}4999XFreeEventData(x11_display, &event.xcookie);50005001switch (event.type) {5002case MapNotify: {5003DEBUG_LOG_X11("[%u] MapNotify window=%lu (%u) \n", frame, event.xmap.window, window_id);5004if (ime_window_event) {5005break;5006}50075008const WindowData &wd = windows[window_id];50095010XWindowAttributes xwa;5011XSync(x11_display, False);5012XGetWindowAttributes(x11_display, wd.x11_window, &xwa);50135014XFlush(x11_display);50155016// Set focus when menu window is started.5017// RevertToPointerRoot is used to make sure we don't lose all focus in case5018// a subwindow and its parent are both destroyed.5019if ((xwa.map_state == IsViewable) && !wd.no_focus && !wd.is_popup && _window_focus_check()) {5020_set_input_focus(wd.x11_window, RevertToPointerRoot);5021}50225023// Have we failed to set fullscreen while the window was unmapped?5024_validate_fullscreen_on_map(window_id);50255026// On KDE Plasma, when the parent window of an embedded process is restored after being minimized,5027// only the embedded window receives the Map notification, causing it to5028// appear without its parent.5029if (wd.embed_parent) {5030XMapWindow(x11_display, wd.embed_parent);5031}5032} break;50335034case Expose: {5035DEBUG_LOG_X11("[%u] Expose window=%lu (%u), count='%u' \n", frame, event.xexpose.window, window_id, event.xexpose.count);5036if (ime_window_event) {5037break;5038}50395040windows[window_id].fullscreen = _window_fullscreen_check(window_id);50415042Main::force_redraw();5043} break;50445045case LeaveNotify: {5046DEBUG_LOG_X11("[%u] LeaveNotify window=%lu (%u), mode='%u' \n", frame, event.xcrossing.window, window_id, event.xcrossing.mode);5047if (ime_window_event) {5048break;5049}50505051if (!mouse_mode_grab && window_mouseover_id == window_id) {5052window_mouseover_id = INVALID_WINDOW_ID;5053_send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_EXIT);5054}50555056} break;50575058case EnterNotify: {5059DEBUG_LOG_X11("[%u] EnterNotify window=%lu (%u), mode='%u' \n", frame, event.xcrossing.window, window_id, event.xcrossing.mode);5060if (ime_window_event) {5061break;5062}50635064if (!mouse_mode_grab && window_mouseover_id != window_id) {5065if (window_mouseover_id != INVALID_WINDOW_ID) {5066_send_window_event(windows[window_mouseover_id], WINDOW_EVENT_MOUSE_EXIT);5067}5068window_mouseover_id = window_id;5069_send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_ENTER);5070}5071} break;50725073case FocusIn: {5074DEBUG_LOG_X11("[%u] FocusIn window=%lu (%u), mode='%u' \n", frame, event.xfocus.window, window_id, event.xfocus.mode);5075if (ime_window_event || (event.xfocus.detail == NotifyInferior)) {5076break;5077}50785079WindowData &wd = windows[window_id];5080last_focused_window = window_id;5081wd.focused = true;50825083// Keep track of focus order for overlapping windows.5084static unsigned int focus_order = 0;5085wd.focus_order = ++focus_order;50865087#ifdef ACCESSKIT_ENABLED5088if (accessibility_driver) {5089accessibility_driver->accessibility_set_window_focused(window_id, true);5090}5091#endif5092_send_window_event(wd, WINDOW_EVENT_FOCUS_IN);50935094if (mouse_mode_grab) {5095// Show and update the cursor if confined and the window regained focus.50965097for (const KeyValue<WindowID, WindowData> &E : windows) {5098if (mouse_mode == MOUSE_MODE_CONFINED) {5099XUndefineCursor(x11_display, E.value.x11_window);5100} else if (mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN) { // Or re-hide it.5101XDefineCursor(x11_display, E.value.x11_window, null_cursor);5102}51035104XGrabPointer(5105x11_display, E.value.x11_window, True,5106ButtonPressMask | ButtonReleaseMask | PointerMotionMask,5107GrabModeAsync, GrabModeAsync, E.value.x11_window, None, CurrentTime);5108}5109}5110#ifdef TOUCH_ENABLED5111// Grab touch devices to avoid OS gesture interference5112/*for (int i = 0; i < xi.touch_devices.size(); ++i) {5113XIGrabDevice(x11_display, xi.touch_devices[i], x11_window, CurrentTime, None, XIGrabModeAsync, XIGrabModeAsync, False, &xi.touch_event_mask);5114}*/5115#endif51165117if (!app_focused) {5118if (OS::get_singleton()->get_main_loop()) {5119OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN);5120}5121app_focused = true;5122}5123} break;51245125case FocusOut: {5126DEBUG_LOG_X11("[%u] FocusOut window=%lu (%u), mode='%u' \n", frame, event.xfocus.window, window_id, event.xfocus.mode);5127WindowData &wd = windows[window_id];5128if (ime_window_event || (event.xfocus.detail == NotifyInferior)) {5129break;5130}5131if (wd.ime_active) {5132MutexLock mutex_lock(events_mutex);5133XUnsetICFocus(wd.xic);5134XUnmapWindow(x11_display, wd.x11_xim_window);5135wd.ime_active = false;5136im_text = String();5137im_selection = Vector2i();5138OS_Unix::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE);5139}5140wd.focused = false;51415142Input::get_singleton()->release_pressed_events();5143#ifdef ACCESSKIT_ENABLED5144if (accessibility_driver) {5145accessibility_driver->accessibility_set_window_focused(window_id, false);5146}5147#endif5148_send_window_event(wd, WINDOW_EVENT_FOCUS_OUT);51495150if (mouse_mode_grab) {5151for (const KeyValue<WindowID, WindowData> &E : windows) {5152//dear X11, I try, I really try, but you never work, you do whatever you want.5153if (mouse_mode == MOUSE_MODE_CAPTURED) {5154// Show the cursor if we're in captured mode so it doesn't look weird.5155XUndefineCursor(x11_display, E.value.x11_window);5156}5157}5158XUngrabPointer(x11_display, CurrentTime);5159}5160#ifdef TOUCH_ENABLED5161// Ungrab touch devices so input works as usual while we are unfocused5162/*for (int i = 0; i < xi.touch_devices.size(); ++i) {5163XIUngrabDevice(x11_display, xi.touch_devices[i], CurrentTime);5164}*/51655166// Release every pointer to avoid sticky points5167for (const KeyValue<int, Vector2> &E : xi.state) {5168Ref<InputEventScreenTouch> st;5169st.instantiate();5170st->set_index(E.key);5171st->set_window_id(window_id);5172st->set_position(E.value);5173Input::get_singleton()->parse_input_event(st);5174}5175xi.state.clear();5176#endif5177} break;51785179case ConfigureNotify: {5180DEBUG_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);5181if (event.xconfigure.window == windows[window_id].x11_xim_window) {5182break;5183}51845185_window_changed(&event);5186} break;51875188case PropertyNotify: {5189if (event.xproperty.atom == XInternAtom(x11_display, "_NET_WM_STATE", False)) {5190_update_wm_state_hints(window_id);5191}5192} break;51935194case ButtonPress:5195case ButtonRelease: {5196if (ime_window_event || ignore_events) {5197break;5198}5199/* exit in case of a mouse button press */5200last_timestamp = event.xbutton.time;5201if (mouse_mode == MOUSE_MODE_CAPTURED) {5202event.xbutton.x = last_mouse_pos.x;5203event.xbutton.y = last_mouse_pos.y;5204}52055206Ref<InputEventMouseButton> mb;5207mb.instantiate();52085209mb->set_window_id(window_id);5210_get_key_modifier_state(event.xbutton.state, mb);5211mb->set_button_index((MouseButton)event.xbutton.button);5212if (mb->get_button_index() == MouseButton::RIGHT) {5213mb->set_button_index(MouseButton::MIDDLE);5214} else if (mb->get_button_index() == MouseButton::MIDDLE) {5215mb->set_button_index(MouseButton::RIGHT);5216}5217mb->set_position(Vector2(event.xbutton.x, event.xbutton.y));5218mb->set_global_position(mb->get_position());52195220mb->set_pressed((event.type == ButtonPress));52215222if (mb->is_pressed() && mb->get_button_index() >= MouseButton::WHEEL_UP && mb->get_button_index() <= MouseButton::WHEEL_RIGHT) {5223MouseButtonMask mask = mouse_button_to_mask(mb->get_button_index());5224BitField<MouseButtonMask> scroll_mask = mouse_get_button_state();5225scroll_mask.set_flag(mask);5226mb->set_button_mask(scroll_mask);5227} else {5228mb->set_button_mask(mouse_get_button_state());5229}52305231const WindowData &wd = windows[window_id];52325233if (event.type == ButtonPress) {5234DEBUG_LOG_X11("[%u] ButtonPress window=%lu (%u), button_index=%u \n", frame, event.xbutton.window, window_id, mb->get_button_index());52355236// Ensure window focus on click.5237// RevertToPointerRoot is used to make sure we don't lose all focus in case5238// a subwindow and its parent are both destroyed.5239if (!wd.no_focus && !wd.is_popup) {5240_set_input_focus(wd.x11_window, RevertToPointerRoot);5241}52425243uint64_t diff = OS::get_singleton()->get_ticks_usec() / 1000 - last_click_ms;52445245if (mb->get_button_index() == last_click_button_index) {5246if (diff < 400 && Vector2(last_click_pos).distance_to(Vector2(event.xbutton.x, event.xbutton.y)) < 5) {5247last_click_ms = 0;5248last_click_pos = Point2i(-100, -100);5249last_click_button_index = MouseButton::NONE;5250mb->set_double_click(true);5251}52525253} else if (mb->get_button_index() < MouseButton::WHEEL_UP || mb->get_button_index() > MouseButton::WHEEL_RIGHT) {5254last_click_button_index = mb->get_button_index();5255}52565257if (!mb->is_double_click()) {5258last_click_ms += diff;5259last_click_pos = Point2i(event.xbutton.x, event.xbutton.y);5260}5261} else {5262DEBUG_LOG_X11("[%u] ButtonRelease window=%lu (%u), button_index=%u \n", frame, event.xbutton.window, window_id, mb->get_button_index());52635264WindowID window_id_other = INVALID_WINDOW_ID;5265Window wd_other_x11_window;5266if (!wd.focused) {5267// Propagate the event to the focused window,5268// because it's received only on the topmost window.5269// Note: This is needed for drag & drop to work between windows,5270// because the engine expects events to keep being processed5271// on the same window dragging started.5272for (const KeyValue<WindowID, WindowData> &E : windows) {5273if (E.value.focused) {5274if (E.key != window_id) {5275window_id_other = E.key;5276wd_other_x11_window = E.value.x11_window;5277}5278break;5279}5280}5281}52825283if (window_id_other != INVALID_WINDOW_ID) {5284int x, y;5285Window child;5286XTranslateCoordinates(x11_display, wd.x11_window, wd_other_x11_window, event.xbutton.x, event.xbutton.y, &x, &y, &child);52875288mb->set_window_id(window_id_other);5289mb->set_position(Vector2(x, y));5290mb->set_global_position(mb->get_position());5291}5292}52935294Input::get_singleton()->parse_input_event(mb);52955296} break;5297case MotionNotify: {5298if (ime_window_event || ignore_events) {5299break;5300}5301// The X11 API requires filtering one-by-one through the motion5302// notify events, in order to figure out which event is the one5303// generated by warping the mouse pointer.5304WindowID focused_window_id = _get_focused_window_or_popup();5305if (!windows.has(focused_window_id)) {5306focused_window_id = MAIN_WINDOW_ID;5307}53085309while (true) {5310if (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) {5311//this is likely the warp event since it was warped here5312center = Vector2(event.xmotion.x, event.xmotion.y);5313break;5314}53155316if (event_index + 1 < events.size()) {5317const XEvent &next_event = events[event_index + 1];5318if (next_event.type == MotionNotify) {5319++event_index;5320event = next_event;5321} else {5322break;5323}5324} else {5325break;5326}5327}53285329last_timestamp = event.xmotion.time;53305331// Motion is also simple.5332// A little hack is in order5333// to be able to send relative motion events.5334Point2i pos(event.xmotion.x, event.xmotion.y);53355336// Avoidance of spurious mouse motion (see handling of touch)5337bool filter = false;5338// Adding some tolerance to match better Point2i to Vector25339if (xi.state.size() && Vector2(pos).distance_squared_to(xi.mouse_pos_to_filter) < 2) {5340filter = true;5341}5342// Invalidate to avoid filtering a possible legitimate similar event coming later5343xi.mouse_pos_to_filter = Vector2(1e10, 1e10);5344if (filter) {5345break;5346}53475348const WindowData &wd = windows[window_id];5349bool focused = wd.focused;53505351if (mouse_mode == MOUSE_MODE_CAPTURED) {5352if (xi.relative_motion.x == 0 && xi.relative_motion.y == 0) {5353break;5354}53555356Point2i new_center = pos;5357pos = last_mouse_pos + xi.relative_motion;5358center = new_center;5359do_mouse_warp = focused; // warp the cursor if we're focused in5360}53615362if (!last_mouse_pos_valid) {5363last_mouse_pos = pos;5364last_mouse_pos_valid = true;5365}53665367// Hackish but relative mouse motion is already handled in the RawMotion event.5368// RawMotion does not provide the absolute mouse position (whereas MotionNotify does).5369// Therefore, RawMotion cannot be the authority on absolute mouse position.5370// RawMotion provides more precision than MotionNotify, which doesn't sense subpixel motion.5371// Therefore, MotionNotify cannot be the authority on relative mouse motion.5372// This means we need to take a combined approach...5373Point2i rel;53745375// Only use raw input if in capture mode. Otherwise use the classic behavior.5376if (mouse_mode == MOUSE_MODE_CAPTURED) {5377rel = xi.relative_motion;5378} else {5379rel = pos - last_mouse_pos;5380}53815382// Reset to prevent lingering motion5383xi.relative_motion.x = 0;5384xi.relative_motion.y = 0;5385if (mouse_mode == MOUSE_MODE_CAPTURED) {5386pos = Point2i(windows[focused_window_id].size.width / 2, windows[focused_window_id].size.height / 2);5387}53885389BitField<MouseButtonMask> last_button_state = MouseButtonMask::NONE;5390if (event.xmotion.state & Button1Mask) {5391last_button_state.set_flag(MouseButtonMask::LEFT);5392}5393if (event.xmotion.state & Button2Mask) {5394last_button_state.set_flag(MouseButtonMask::MIDDLE);5395}5396if (event.xmotion.state & Button3Mask) {5397last_button_state.set_flag(MouseButtonMask::RIGHT);5398}5399if (event.xmotion.state & Button4Mask) {5400last_button_state.set_flag(MouseButtonMask::MB_XBUTTON1);5401}5402if (event.xmotion.state & Button5Mask) {5403last_button_state.set_flag(MouseButtonMask::MB_XBUTTON2);5404}54055406Ref<InputEventMouseMotion> mm;5407mm.instantiate();54085409mm->set_window_id(window_id);5410if (xi.pressure_supported) {5411mm->set_pressure(xi.pressure);5412} else {5413mm->set_pressure(bool(last_button_state.has_flag(MouseButtonMask::LEFT)) ? 1.0f : 0.0f);5414}5415mm->set_tilt(xi.tilt);5416mm->set_pen_inverted(xi.pen_inverted);54175418_get_key_modifier_state(event.xmotion.state, mm);5419mm->set_button_mask(last_button_state);5420mm->set_position(pos);5421mm->set_global_position(pos);5422mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity());5423mm->set_screen_velocity(mm->get_velocity());54245425mm->set_relative(rel);5426mm->set_relative_screen_position(rel);54275428last_mouse_pos = pos;54295430// printf("rel: %d,%d\n", rel.x, rel.y );5431// Don't propagate the motion event unless we have focus5432// this is so that the relative motion doesn't get messed up5433// after we regain focus.5434// Adjusted to parse the input event if the window is not focused allowing mouse hovering on the editor5435// the embedding process has focus.5436if (!focused) {5437// Propagate the event to the focused window,5438// because it's received only on the topmost window.5439// Note: This is needed for drag & drop to work between windows,5440// because the engine expects events to keep being processed5441// on the same window dragging started.5442for (const KeyValue<WindowID, WindowData> &E : windows) {5443const WindowData &wd_other = E.value;5444if (wd_other.focused) {5445int x, y;5446Window child;5447XTranslateCoordinates(x11_display, wd.x11_window, wd_other.x11_window, event.xmotion.x, event.xmotion.y, &x, &y, &child);54485449Point2i pos_focused(x, y);54505451mm->set_window_id(E.key);5452mm->set_position(pos_focused);5453mm->set_global_position(pos_focused);5454mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity());54555456break;5457}5458}5459}54605461Input::get_singleton()->parse_input_event(mm);54625463} break;5464case KeyPress:5465case KeyRelease: {5466if (ignore_events) {5467break;5468}5469#ifdef DISPLAY_SERVER_X11_DEBUG_LOGS_ENABLED5470if (event.type == KeyPress) {5471DEBUG_LOG_X11("[%u] KeyPress window=%lu (%u), keycode=%u, time=%lu \n", frame, event.xkey.window, window_id, event.xkey.keycode, event.xkey.time);5472} else {5473DEBUG_LOG_X11("[%u] KeyRelease window=%lu (%u), keycode=%u, time=%lu \n", frame, event.xkey.window, window_id, event.xkey.keycode, event.xkey.time);5474}5475#endif5476last_timestamp = event.xkey.time;54775478// key event is a little complex, so5479// it will be handled in its own function.5480_handle_key_event(window_id, &event.xkey, events, event_index);5481} break;54825483case SelectionNotify:5484if (ime_window_event) {5485break;5486}5487if (event.xselection.target == requested) {5488Property p = _read_property(x11_display, windows[window_id].x11_window, XInternAtom(x11_display, "PRIMARY", 0));54895490Vector<String> files = String((char *)p.data).split("\r\n", false);5491XFree(p.data);5492for (int i = 0; i < files.size(); i++) {5493files.write[i] = files[i].replace("file://", "").uri_file_decode();5494}54955496if (windows[window_id].drop_files_callback.is_valid()) {5497Variant v_files = files;5498const Variant *v_args[1] = { &v_files };5499Variant ret;5500Callable::CallError ce;5501windows[window_id].drop_files_callback.callp((const Variant **)&v_args, 1, ret, ce);5502if (ce.error != Callable::CallError::CALL_OK) {5503ERR_PRINT(vformat("Failed to execute drop files callback: %s.", Variant::get_callable_error_text(windows[window_id].drop_files_callback, v_args, 1, ce)));5504}5505}55065507//Reply that all is well.5508XClientMessageEvent m;5509memset(&m, 0, sizeof(m));5510m.type = ClientMessage;5511m.display = x11_display;5512m.window = xdnd_source_window;5513m.message_type = xdnd_finished;5514m.format = 32;5515m.data.l[0] = windows[window_id].x11_window;5516m.data.l[1] = 1;5517m.data.l[2] = xdnd_action_copy; //We only ever copy.55185519XSendEvent(x11_display, xdnd_source_window, False, NoEventMask, (XEvent *)&m);5520}5521break;55225523case ClientMessage:5524if (ime_window_event) {5525break;5526}5527if ((unsigned int)event.xclient.data.l[0] == (unsigned int)wm_delete) {5528_send_window_event(windows[window_id], WINDOW_EVENT_CLOSE_REQUEST);5529}55305531else if ((unsigned int)event.xclient.message_type == (unsigned int)xdnd_enter) {5532//File(s) have been dragged over the window, check for supported target (text/uri-list)5533xdnd_version = (event.xclient.data.l[1] >> 24);5534Window source = event.xclient.data.l[0];5535bool more_than_3 = event.xclient.data.l[1] & 1;5536if (more_than_3) {5537Property p = _read_property(x11_display, source, XInternAtom(x11_display, "XdndTypeList", False));5538requested = pick_target_from_list(x11_display, (Atom *)p.data, p.nitems);5539XFree(p.data);5540} else {5541requested = pick_target_from_atoms(x11_display, event.xclient.data.l[2], event.xclient.data.l[3], event.xclient.data.l[4]);5542}5543} else if ((unsigned int)event.xclient.message_type == (unsigned int)xdnd_position) {5544//xdnd position event, reply with an XDND status message5545//just depending on type of data for now5546XClientMessageEvent m;5547memset(&m, 0, sizeof(m));5548m.type = ClientMessage;5549m.display = event.xclient.display;5550m.window = event.xclient.data.l[0];5551m.message_type = xdnd_status;5552m.format = 32;5553m.data.l[0] = windows[window_id].x11_window;5554m.data.l[1] = (requested != None);5555m.data.l[2] = 0; //empty rectangle5556m.data.l[3] = 0;5557m.data.l[4] = xdnd_action_copy;55585559XSendEvent(x11_display, event.xclient.data.l[0], False, NoEventMask, (XEvent *)&m);5560XFlush(x11_display);5561} else if ((unsigned int)event.xclient.message_type == (unsigned int)xdnd_drop) {5562if (requested != None) {5563xdnd_source_window = event.xclient.data.l[0];5564if (xdnd_version >= 1) {5565XConvertSelection(x11_display, xdnd_selection, requested, XInternAtom(x11_display, "PRIMARY", 0), windows[window_id].x11_window, event.xclient.data.l[2]);5566} else {5567XConvertSelection(x11_display, xdnd_selection, requested, XInternAtom(x11_display, "PRIMARY", 0), windows[window_id].x11_window, CurrentTime);5568}5569} else {5570//Reply that we're not interested.5571XClientMessageEvent m;5572memset(&m, 0, sizeof(m));5573m.type = ClientMessage;5574m.display = event.xclient.display;5575m.window = event.xclient.data.l[0];5576m.message_type = xdnd_finished;5577m.format = 32;5578m.data.l[0] = windows[window_id].x11_window;5579m.data.l[1] = 0;5580m.data.l[2] = None; //Failed.5581XSendEvent(x11_display, event.xclient.data.l[0], False, NoEventMask, (XEvent *)&m);5582}5583}5584break;5585default:5586break;5587}5588}55895590XFlush(x11_display);55915592if (do_mouse_warp) {5593XWarpPointer(x11_display, None, windows[MAIN_WINDOW_ID].x11_window,55940, 0, 0, 0, (int)windows[MAIN_WINDOW_ID].size.width / 2, (int)windows[MAIN_WINDOW_ID].size.height / 2);55955596/*5597Window root, child;5598int root_x, root_y;5599int win_x, win_y;5600unsigned int mask;5601XQueryPointer( x11_display, x11_window, &root, &child, &root_x, &root_y, &win_x, &win_y, &mask );56025603printf("Root: %d,%d\n", root_x, root_y);5604printf("Win: %d,%d\n", win_x, win_y);5605*/5606}56075608#ifdef DBUS_ENABLED5609if (portal_desktop) {5610portal_desktop->process_callbacks();5611}5612#endif56135614_THREAD_SAFE_UNLOCK_56155616Input::get_singleton()->flush_buffered_events();5617}56185619void DisplayServerX11::release_rendering_thread() {5620#if defined(GLES3_ENABLED)5621if (gl_manager) {5622gl_manager->release_current();5623}5624if (gl_manager_egl) {5625gl_manager_egl->release_current();5626}5627#endif5628}56295630void DisplayServerX11::swap_buffers() {5631#if defined(GLES3_ENABLED)5632if (gl_manager) {5633gl_manager->swap_buffers();5634}5635if (gl_manager_egl) {5636gl_manager_egl->swap_buffers();5637}5638#endif5639}56405641void DisplayServerX11::_update_context(WindowData &wd) {5642XClassHint *classHint = XAllocClassHint();56435644if (classHint) {5645CharString name_str;5646switch (context) {5647case CONTEXT_EDITOR:5648name_str = "Godot_Editor";5649break;5650case CONTEXT_PROJECTMAN:5651name_str = "Godot_ProjectList";5652break;5653case CONTEXT_ENGINE:5654name_str = "Godot_Engine";5655break;5656}56575658CharString class_str;5659if (context == CONTEXT_ENGINE) {5660String config_name = GLOBAL_GET("application/config/name");5661if (config_name.length() == 0) {5662class_str = "Godot_Engine";5663} else {5664class_str = config_name.utf8();5665}5666} else {5667class_str = "Godot";5668}56695670classHint->res_class = class_str.ptrw();5671classHint->res_name = name_str.ptrw();56725673XSetClassHint(x11_display, wd.x11_window, classHint);5674XFree(classHint);5675}5676}56775678void DisplayServerX11::set_context(Context p_context) {5679_THREAD_SAFE_METHOD_56805681context = p_context;56825683for (KeyValue<WindowID, WindowData> &E : windows) {5684_update_context(E.value);5685}5686}56875688bool DisplayServerX11::is_window_transparency_available() const {5689CharString net_wm_cm_name = vformat("_NET_WM_CM_S%d", XDefaultScreen(x11_display)).ascii();5690Atom net_wm_cm = XInternAtom(x11_display, net_wm_cm_name.get_data(), False);5691if (net_wm_cm == None) {5692return false;5693}5694if (XGetSelectionOwner(x11_display, net_wm_cm) == None) {5695return false;5696}5697#if defined(RD_ENABLED)5698if (rendering_device && !rendering_device->is_composite_alpha_supported()) {5699return false;5700}5701#endif5702return OS::get_singleton()->is_layered_allowed();5703}57045705void DisplayServerX11::set_native_icon(const String &p_filename) {5706WARN_PRINT("Native icon not supported by this display server.");5707}57085709bool g_set_icon_error = false;5710int set_icon_errorhandler(Display *dpy, XErrorEvent *ev) {5711g_set_icon_error = true;5712return 0;5713}57145715void DisplayServerX11::set_icon(const Ref<Image> &p_icon) {5716_THREAD_SAFE_METHOD_57175718WindowData &wd = windows[MAIN_WINDOW_ID];57195720int (*oldHandler)(Display *, XErrorEvent *) = XSetErrorHandler(&set_icon_errorhandler);57215722Atom net_wm_icon = XInternAtom(x11_display, "_NET_WM_ICON", False);57235724if (p_icon.is_valid()) {5725ERR_FAIL_COND(p_icon->get_width() <= 0 || p_icon->get_height() <= 0);57265727Ref<Image> img = p_icon->duplicate();5728img->convert(Image::FORMAT_RGBA8);57295730while (true) {5731int w = img->get_width();5732int h = img->get_height();57335734if (g_set_icon_error) {5735g_set_icon_error = false;57365737WARN_PRINT(vformat("Icon too large (%dx%d), attempting to downscale icon.", w, h));57385739int new_width, new_height;5740if (w > h) {5741new_width = w / 2;5742new_height = h * new_width / w;5743} else {5744new_height = h / 2;5745new_width = w * new_height / h;5746}57475748w = new_width;5749h = new_height;57505751if (!w || !h) {5752WARN_PRINT("Unable to set icon.");5753break;5754}57555756img->resize(w, h, Image::INTERPOLATE_CUBIC);5757}57585759// We're using long to have wordsize (32Bit build -> 32 Bits, 64 Bit build -> 64 Bits5760Vector<long> pd;57615762pd.resize(2 + w * h);57635764pd.write[0] = w;5765pd.write[1] = h;57665767const uint8_t *r = img->get_data().ptr();57685769long *wr = &pd.write[2];5770uint8_t const *pr = r;57715772for (int i = 0; i < w * h; i++) {5773long v = 0;5774// A R G B5775v |= pr[3] << 24 | pr[0] << 16 | pr[1] << 8 | pr[2];5776*wr++ = v;5777pr += 4;5778}57795780if (net_wm_icon != None) {5781XChangeProperty(x11_display, wd.x11_window, net_wm_icon, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)pd.ptr(), pd.size());5782}57835784if (!g_set_icon_error) {5785break;5786}5787}5788} else {5789XDeleteProperty(x11_display, wd.x11_window, net_wm_icon);5790}57915792XFlush(x11_display);5793XSetErrorHandler(oldHandler);5794}57955796void DisplayServerX11::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window) {5797_THREAD_SAFE_METHOD_5798#if defined(RD_ENABLED)5799if (rendering_context) {5800rendering_context->window_set_vsync_mode(p_window, p_vsync_mode);5801}5802#endif58035804#if defined(GLES3_ENABLED)5805if (gl_manager) {5806gl_manager->set_use_vsync(p_vsync_mode != DisplayServer::VSYNC_DISABLED);5807}5808if (gl_manager_egl) {5809gl_manager_egl->set_use_vsync(p_vsync_mode != DisplayServer::VSYNC_DISABLED);5810}5811#endif5812}58135814DisplayServer::VSyncMode DisplayServerX11::window_get_vsync_mode(WindowID p_window) const {5815_THREAD_SAFE_METHOD_5816#if defined(RD_ENABLED)5817if (rendering_context) {5818return rendering_context->window_get_vsync_mode(p_window);5819}5820#endif5821#if defined(GLES3_ENABLED)5822if (gl_manager) {5823return gl_manager->is_using_vsync() ? DisplayServer::VSYNC_ENABLED : DisplayServer::VSYNC_DISABLED;5824}5825if (gl_manager_egl) {5826return gl_manager_egl->is_using_vsync() ? DisplayServer::VSYNC_ENABLED : DisplayServer::VSYNC_DISABLED;5827}5828#endif5829return DisplayServer::VSYNC_ENABLED;5830}58315832void DisplayServerX11::window_start_drag(WindowID p_window) {5833_THREAD_SAFE_METHOD_58345835ERR_FAIL_COND(!windows.has(p_window));5836WindowData &wd = windows[p_window];58375838if (wd.embed_parent) {5839return; // Embedded window.5840}58415842XClientMessageEvent m;5843memset(&m, 0, sizeof(m));58445845XUngrabPointer(x11_display, CurrentTime);58465847Window root_return, child_return;5848int root_x, root_y, win_x, win_y;5849unsigned int mask_return;58505851Bool xquerypointer_result = XQueryPointer(x11_display, wd.x11_window, &root_return, &child_return, &root_x, &root_y, &win_x, &win_y, &mask_return);58525853m.type = ClientMessage;5854m.window = wd.x11_window;5855m.message_type = XInternAtom(x11_display, "_NET_WM_MOVERESIZE", True);5856m.format = 32;5857if (xquerypointer_result) {5858m.data.l[0] = root_x;5859m.data.l[1] = root_y;5860m.data.l[3] = Button1;5861}5862m.data.l[2] = _NET_WM_MOVERESIZE_MOVE;5863m.data.l[4] = 1; // Source - normal application.58645865XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent *)&m);58665867XSync(x11_display, 0);5868}58695870void DisplayServerX11::window_start_resize(WindowResizeEdge p_edge, WindowID p_window) {5871_THREAD_SAFE_METHOD_58725873ERR_FAIL_INDEX(int(p_edge), WINDOW_EDGE_MAX);58745875ERR_FAIL_COND(!windows.has(p_window));5876WindowData &wd = windows[p_window];58775878if (wd.embed_parent) {5879return; // Embedded window.5880}58815882XClientMessageEvent m;5883memset(&m, 0, sizeof(m));58845885XUngrabPointer(x11_display, CurrentTime);58865887Window root_return, child_return;5888int root_x, root_y, win_x, win_y;5889unsigned int mask_return;58905891Bool xquerypointer_result = XQueryPointer(x11_display, wd.x11_window, &root_return, &child_return, &root_x, &root_y, &win_x, &win_y, &mask_return);58925893m.type = ClientMessage;5894m.window = wd.x11_window;5895m.message_type = XInternAtom(x11_display, "_NET_WM_MOVERESIZE", True);5896m.format = 32;5897if (xquerypointer_result) {5898m.data.l[0] = root_x;5899m.data.l[1] = root_y;5900m.data.l[3] = Button1;5901}59025903switch (p_edge) {5904case DisplayServer::WINDOW_EDGE_TOP_LEFT: {5905m.data.l[2] = _NET_WM_MOVERESIZE_SIZE_TOPLEFT;5906} break;5907case DisplayServer::WINDOW_EDGE_TOP: {5908m.data.l[2] = _NET_WM_MOVERESIZE_SIZE_TOP;5909} break;5910case DisplayServer::WINDOW_EDGE_TOP_RIGHT: {5911m.data.l[2] = _NET_WM_MOVERESIZE_SIZE_TOPRIGHT;5912} break;5913case DisplayServer::WINDOW_EDGE_LEFT: {5914m.data.l[2] = _NET_WM_MOVERESIZE_SIZE_LEFT;5915} break;5916case DisplayServer::WINDOW_EDGE_RIGHT: {5917m.data.l[2] = _NET_WM_MOVERESIZE_SIZE_RIGHT;5918} break;5919case DisplayServer::WINDOW_EDGE_BOTTOM_LEFT: {5920m.data.l[2] = _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT;5921} break;5922case DisplayServer::WINDOW_EDGE_BOTTOM: {5923m.data.l[2] = _NET_WM_MOVERESIZE_SIZE_BOTTOM;5924} break;5925case DisplayServer::WINDOW_EDGE_BOTTOM_RIGHT: {5926m.data.l[2] = _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT;5927} break;5928default:5929break;5930}5931m.data.l[4] = 1; // Source - normal application.59325933XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent *)&m);59345935XSync(x11_display, 0);5936}59375938pid_t get_window_pid(Display *p_display, Window p_window) {5939Atom atom = XInternAtom(p_display, "_NET_WM_PID", False);5940Atom actualType;5941int actualFormat;5942unsigned long nItems, bytesAfter;5943unsigned char *prop = nullptr;5944if (XGetWindowProperty(p_display, p_window, atom, 0, sizeof(pid_t), False, AnyPropertyType,5945&actualType, &actualFormat, &nItems, &bytesAfter, &prop) == Success) {5946if (nItems > 0) {5947pid_t pid = *(pid_t *)prop;5948XFree(prop);5949return pid;5950}5951}59525953return 0; // PID not found.5954}59555956Window find_window_from_process_id_internal(Display *p_display, pid_t p_process_id, Window p_window) {5957Window dummy;5958Window *children;5959unsigned int num_children;59605961if (!XQueryTree(p_display, p_window, &dummy, &dummy, &children, &num_children)) {5962return 0;5963}59645965for (unsigned int i = 0; i < num_children; i++) {5966const Window child = children[i];5967if (get_window_pid(p_display, child) == p_process_id) {5968XFree(children);5969return child;5970}5971}59725973// Then check children of children.5974for (unsigned int i = 0; i < num_children; i++) {5975Window wnd = find_window_from_process_id_internal(p_display, p_process_id, children[i]);5976if (wnd != 0) {5977XFree(children);5978return wnd;5979}5980}59815982if (children) {5983XFree(children);5984}59855986return 0;5987}59885989Window find_window_from_process_id(Display *p_display, pid_t p_process_id) {5990// Handle bad window errors silently because while looping5991// windows can be destroyed, resulting in BadWindow errors.5992int (*oldHandler)(Display *, XErrorEvent *) = XSetErrorHandler(&bad_window_error_handler);59935994const int screencount = XScreenCount(p_display);5995Window process_window = 0;59965997for (int screen_index = 0; screen_index < screencount; screen_index++) {5998Window root = RootWindow(p_display, screen_index);59996000Window wnd = find_window_from_process_id_internal(p_display, p_process_id, root);60016002if (wnd != 0) {6003process_window = wnd;6004break;6005}6006}60076008// Restore default error handler.6009XSetErrorHandler(oldHandler);60106011return process_window;6012}60136014Point2i DisplayServerX11::_get_window_position(Window p_window) const {6015int x = 0, y = 0;6016Window child;6017XTranslateCoordinates(x11_display, p_window, DefaultRootWindow(x11_display), 0, 0, &x, &y, &child);6018return Point2i(x, y);6019}60206021Rect2i DisplayServerX11::_get_window_rect(Window p_window) const {6022XWindowAttributes xwa;6023XGetWindowAttributes(x11_display, p_window, &xwa);6024return Rect2i(xwa.x, xwa.y, xwa.width, xwa.height);6025}60266027void DisplayServerX11::_set_window_taskbar_pager_enabled(Window p_window, bool p_enabled) {6028Atom wmState = XInternAtom(x11_display, "_NET_WM_STATE", False);6029Atom skipTaskbar = XInternAtom(x11_display, "_NET_WM_STATE_SKIP_TASKBAR", False);6030Atom skipPager = XInternAtom(x11_display, "_NET_WM_STATE_SKIP_PAGER", False);60316032XClientMessageEvent xev;6033memset(&xev, 0, sizeof(xev));6034xev.type = ClientMessage;6035xev.window = p_window;6036xev.message_type = wmState;6037xev.format = 32;6038xev.data.l[0] = p_enabled ? _NET_WM_STATE_REMOVE : _NET_WM_STATE_ADD; // When enabled, we must remove the skip.6039xev.data.l[1] = skipTaskbar;6040xev.data.l[2] = skipPager;6041xev.data.l[3] = 0;6042xev.data.l[4] = 0;60436044// Send the client message to the root window.6045XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent *)&xev);6046}60476048Error DisplayServerX11::embed_process(WindowID p_window, OS::ProcessID p_pid, const Rect2i &p_rect, bool p_visible, bool p_grab_focus) {6049_THREAD_SAFE_METHOD_60506051ERR_FAIL_COND_V(!windows.has(p_window), FAILED);60526053const WindowData &wd = windows[p_window];60546055DEBUG_LOG_X11("Starting embedding %ld to window %lu \n", p_pid, wd.x11_window);60566057EmbeddedProcessData *ep = nullptr;6058if (embedded_processes.has(p_pid)) {6059ep = embedded_processes.get(p_pid);6060} else {6061// New process, trying to find the window.6062Window process_window = find_window_from_process_id(x11_display, p_pid);6063if (!process_window) {6064return ERR_DOES_NOT_EXIST;6065}6066DEBUG_LOG_X11("Process %ld window found: %lu \n", p_pid, process_window);6067ep = memnew(EmbeddedProcessData);6068ep->process_window = process_window;6069ep->visible = true;6070XSetTransientForHint(x11_display, process_window, wd.x11_window);6071_set_window_taskbar_pager_enabled(process_window, false);6072embedded_processes.insert(p_pid, ep);6073}60746075// Handle bad window errors silently because just in case the embedded window was closed.6076int (*oldHandler)(Display *, XErrorEvent *) = XSetErrorHandler(&bad_window_error_handler);60776078if (p_visible) {6079// Resize and move the window to match the desired rectangle.6080// X11 does not allow moving the window entirely outside the screen boundaries.6081// To ensure the window remains visible, we will resize it to fit within both the screen and the specified rectangle.6082Rect2i desired_rect = p_rect;60836084// First resize the desired rect to fit inside all the screens without considering the6085// working area.6086Rect2i screens_full_rect = _screens_get_full_rect();6087Vector2i screens_full_end = screens_full_rect.get_end();6088if (desired_rect.position.x < screens_full_rect.position.x) {6089desired_rect.size.x = MAX(desired_rect.size.x - (screens_full_rect.position.x - desired_rect.position.x), 0);6090desired_rect.position.x = screens_full_rect.position.x;6091}6092if (desired_rect.position.x + desired_rect.size.x > screens_full_end.x) {6093desired_rect.size.x = MAX(screens_full_end.x - desired_rect.position.x, 0);6094}6095if (desired_rect.position.y < screens_full_rect.position.y) {6096desired_rect.size.y = MAX(desired_rect.size.y - (screens_full_rect.position.y - desired_rect.position.y), 0);6097desired_rect.position.y = screens_full_rect.position.y;6098}6099if (desired_rect.position.y + desired_rect.size.y > screens_full_end.y) {6100desired_rect.size.y = MAX(screens_full_end.y - desired_rect.position.y, 0);6101}61026103// Second, for each screen, check if the desired rectangle is within a portion of the screen6104// that is outside the working area. Each screen can have a different working area6105// depending on top, bottom, or side panels.6106int desired_area = desired_rect.get_area();6107int count = get_screen_count();6108for (int i = 0; i < count; i++) {6109Rect2i screen_rect = _screen_get_rect(i);6110if (screen_rect.intersection(desired_rect).get_area() == 0) {6111continue;6112}61136114// The desired rect is inside this screen.6115Rect2i screen_usable_rect = screen_get_usable_rect(i);6116int screen_usable_area = screen_usable_rect.intersection(desired_rect).get_area();6117if (screen_usable_area == desired_area) {6118// The desired rect is fulling inside the usable rect of the screen. No need to resize.6119continue;6120}61216122if (desired_rect.position.x >= screen_rect.position.x && desired_rect.position.x < screen_usable_rect.position.x) {6123int offset = screen_usable_rect.position.x - desired_rect.position.x;6124desired_rect.size.x = MAX(desired_rect.size.x - offset, 0);6125desired_rect.position.x += offset;6126}6127if (desired_rect.position.y >= screen_rect.position.y && desired_rect.position.y < screen_usable_rect.position.y) {6128int offset = screen_usable_rect.position.y - desired_rect.position.y;6129desired_rect.size.y = MAX(desired_rect.size.y - offset, 0);6130desired_rect.position.y += offset;6131}61326133Vector2i desired_end = desired_rect.get_end();6134Vector2i screen_end = screen_rect.get_end();6135Vector2i screen_usable_end = screen_usable_rect.get_end();6136if (desired_end.x > screen_usable_end.x && desired_end.x <= screen_end.x) {6137desired_rect.size.x = MAX(desired_rect.size.x - (desired_end.x - screen_usable_end.x), 0);6138}6139if (desired_end.y > screen_usable_end.y && desired_end.y <= screen_end.y) {6140desired_rect.size.y = MAX(desired_rect.size.y - (desired_end.y - screen_usable_end.y), 0);6141}6142}61436144if (desired_rect.size.x <= 100 || desired_rect.size.y <= 100) {6145p_visible = false;6146}61476148if (p_visible) {6149Rect2i current_process_window_rect = _get_window_rect(ep->process_window);6150if (current_process_window_rect != desired_rect) {6151DEBUG_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);6152XMoveResizeWindow(x11_display, ep->process_window, desired_rect.position.x, desired_rect.position.y, desired_rect.size.x, desired_rect.size.y);6153}6154}6155}61566157if (ep->visible != p_visible) {6158if (p_visible) {6159XMapWindow(x11_display, ep->process_window);6160} else {6161XUnmapWindow(x11_display, ep->process_window);6162}6163ep->visible = p_visible;6164}61656166if (p_grab_focus && p_visible) {6167Window focused_window = 0;6168int revert_to = 0;6169XGetInputFocus(x11_display, &focused_window, &revert_to);6170if (focused_window != ep->process_window) {6171// Be sure that the window is visible to prevent BadMatch error when calling XSetInputFocus on a not viewable window.6172XWindowAttributes attr;6173if (XGetWindowAttributes(x11_display, ep->process_window, &attr) && attr.map_state == IsViewable) {6174XSetInputFocus(x11_display, ep->process_window, RevertToParent, CurrentTime);6175}6176}6177}61786179// Restore default error handler.6180XSetErrorHandler(oldHandler);6181return OK;6182}61836184Error DisplayServerX11::request_close_embedded_process(OS::ProcessID p_pid) {6185_THREAD_SAFE_METHOD_61866187if (!embedded_processes.has(p_pid)) {6188return ERR_DOES_NOT_EXIST;6189}61906191EmbeddedProcessData *ep = embedded_processes.get(p_pid);61926193// Handle bad window errors silently because just in case the embedded window was closed.6194int (*oldHandler)(Display *, XErrorEvent *) = XSetErrorHandler(&bad_window_error_handler);61956196// Check if the window is still valid.6197XWindowAttributes attr;6198if (XGetWindowAttributes(x11_display, ep->process_window, &attr)) {6199// Send the message to gracefully close the window.6200XEvent ev;6201memset(&ev, 0, sizeof(ev));6202ev.xclient.type = ClientMessage;6203ev.xclient.window = ep->process_window;6204ev.xclient.message_type = XInternAtom(x11_display, "WM_PROTOCOLS", True);6205ev.xclient.format = 32;6206ev.xclient.data.l[0] = XInternAtom(x11_display, "WM_DELETE_WINDOW", False);6207ev.xclient.data.l[1] = CurrentTime;6208XSendEvent(x11_display, ep->process_window, False, NoEventMask, &ev);6209}62106211// Restore default error handler.6212XSetErrorHandler(oldHandler);62136214return OK;6215}62166217Error DisplayServerX11::remove_embedded_process(OS::ProcessID p_pid) {6218_THREAD_SAFE_METHOD_62196220if (!embedded_processes.has(p_pid)) {6221return ERR_DOES_NOT_EXIST;6222}62236224EmbeddedProcessData *ep = embedded_processes.get(p_pid);62256226request_close_embedded_process(p_pid);62276228embedded_processes.erase(p_pid);6229memdelete(ep);62306231return OK;6232}62336234OS::ProcessID DisplayServerX11::get_focused_process_id() {6235Window focused_window = 0;6236int revert_to = 0;62376238XGetInputFocus(x11_display, &focused_window, &revert_to);62396240if (focused_window == None) {6241return 0;6242}62436244return get_window_pid(x11_display, focused_window);6245}62466247Vector<String> DisplayServerX11::get_rendering_drivers_func() {6248Vector<String> drivers;62496250#ifdef VULKAN_ENABLED6251drivers.push_back("vulkan");6252#endif6253#ifdef GLES3_ENABLED6254drivers.push_back("opengl3");6255drivers.push_back("opengl3_es");6256#endif6257drivers.push_back("dummy");62586259return drivers;6260}62616262DisplayServer *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) {6263DisplayServer *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));6264return ds;6265}62666267void DisplayServerX11::_create_xic(WindowData &wd) {6268if (xim && xim_style) {6269// Block events polling while changing input focus6270// because it triggers some event polling internally.6271MutexLock mutex_lock(events_mutex);62726273// Force on-the-spot for the over-the-spot style.6274if ((xim_style & XIMPreeditPosition) != 0) {6275xim_style &= ~XIMPreeditPosition;6276xim_style |= XIMPreeditCallbacks;6277}6278if ((xim_style & XIMPreeditCallbacks) != 0) {6279::XIMCallback preedit_start_callback;6280preedit_start_callback.client_data = (::XPointer)(this);6281preedit_start_callback.callback = (::XIMProc)(void *)(_xim_preedit_start_callback);62826283::XIMCallback preedit_done_callback;6284preedit_done_callback.client_data = (::XPointer)(this);6285preedit_done_callback.callback = (::XIMProc)(_xim_preedit_done_callback);62866287::XIMCallback preedit_draw_callback;6288preedit_draw_callback.client_data = (::XPointer)(this);6289preedit_draw_callback.callback = (::XIMProc)(_xim_preedit_draw_callback);62906291::XIMCallback preedit_caret_callback;6292preedit_caret_callback.client_data = (::XPointer)(this);6293preedit_caret_callback.callback = (::XIMProc)(_xim_preedit_caret_callback);62946295::XVaNestedList preedit_attributes = XVaCreateNestedList(0,6296XNPreeditStartCallback, &preedit_start_callback,6297XNPreeditDoneCallback, &preedit_done_callback,6298XNPreeditDrawCallback, &preedit_draw_callback,6299XNPreeditCaretCallback, &preedit_caret_callback,6300(char *)nullptr);63016302wd.xic = XCreateIC(xim,6303XNInputStyle, xim_style,6304XNClientWindow, wd.x11_xim_window,6305XNFocusWindow, wd.x11_xim_window,6306XNPreeditAttributes, preedit_attributes,6307(char *)nullptr);6308XFree(preedit_attributes);6309} else {6310wd.xic = XCreateIC(xim,6311XNInputStyle, xim_style,6312XNClientWindow, wd.x11_xim_window,6313XNFocusWindow, wd.x11_xim_window,6314(char *)nullptr);6315}63166317long im_event_mask = 0;6318if (XGetICValues(wd.xic, XNFilterEvents, &im_event_mask, nullptr) != nullptr) {6319WARN_PRINT("XGetICValues couldn't obtain XNFilterEvents value.");6320XDestroyIC(wd.xic);6321wd.xic = nullptr;6322}6323if (wd.xic) {6324XUnsetICFocus(wd.xic);6325} else {6326WARN_PRINT("XCreateIC couldn't create wd.xic.");6327}6328} else {6329wd.xic = nullptr;6330WARN_PRINT("XCreateIC couldn't create wd.xic.");6331}6332}63336334DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect, Window p_parent_window) {6335//Create window63366337XVisualInfo visualInfo;6338bool vi_selected = false;63396340#ifdef GLES3_ENABLED6341if (gl_manager) {6342Error err;6343visualInfo = gl_manager->get_vi(x11_display, err);6344ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't acquire visual info from display.");6345vi_selected = true;6346}6347if (gl_manager_egl) {6348XVisualInfo visual_info_template;6349int visual_id = gl_manager_egl->display_get_native_visual_id(x11_display);6350ERR_FAIL_COND_V_MSG(visual_id < 0, INVALID_WINDOW_ID, "Unable to get a visual id.");63516352visual_info_template.visualid = (VisualID)visual_id;63536354int number_of_visuals = 0;6355XVisualInfo *vi_list = XGetVisualInfo(x11_display, VisualIDMask, &visual_info_template, &number_of_visuals);6356ERR_FAIL_COND_V(number_of_visuals <= 0, INVALID_WINDOW_ID);63576358visualInfo = vi_list[0];63596360XFree(vi_list);6361}6362#endif63636364if (!vi_selected) {6365long visualMask = VisualScreenMask;6366int numberOfVisuals;6367XVisualInfo vInfoTemplate = {};6368vInfoTemplate.screen = DefaultScreen(x11_display);6369XVisualInfo *vi_list = XGetVisualInfo(x11_display, visualMask, &vInfoTemplate, &numberOfVisuals);6370ERR_FAIL_NULL_V(vi_list, INVALID_WINDOW_ID);63716372visualInfo = vi_list[0];6373if (OS::get_singleton()->is_layered_allowed()) {6374for (int i = 0; i < numberOfVisuals; i++) {6375XRenderPictFormat *pict_format = XRenderFindVisualFormat(x11_display, vi_list[i].visual);6376if (!pict_format) {6377continue;6378}6379visualInfo = vi_list[i];6380if (pict_format->direct.alphaMask > 0) {6381break;6382}6383}6384}6385XFree(vi_list);6386}63876388Colormap colormap = XCreateColormap(x11_display, RootWindow(x11_display, visualInfo.screen), visualInfo.visual, AllocNone);63896390XSetWindowAttributes windowAttributes = {};6391windowAttributes.colormap = colormap;6392windowAttributes.background_pixel = 0xFFFFFFFF;6393windowAttributes.border_pixel = 0;6394windowAttributes.event_mask = KeyPressMask | KeyReleaseMask | StructureNotifyMask | ExposureMask;63956396unsigned long valuemask = CWBorderPixel | CWColormap | CWEventMask;63976398if (OS::get_singleton()->is_layered_allowed()) {6399windowAttributes.background_pixmap = None;6400windowAttributes.background_pixel = 0;6401windowAttributes.border_pixmap = None;6402valuemask |= CWBackPixel;6403}64046405WindowID id = window_id_counter++;6406WindowData &wd = windows[id];64076408if (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) {6409wd.no_focus = true;6410}64116412if (p_flags & WINDOW_FLAG_POPUP_BIT) {6413wd.is_popup = true;6414}64156416// Setup for menu subwindows:6417// - override_redirect forces the WM not to interfere with the window, to avoid delays due to6418// handling decorations and placement.6419// On the other hand, focus changes need to be handled manually when this is set.6420// - save_under is a hint for the WM to keep the content of windows behind to avoid repaint.6421if (wd.no_focus) {6422windowAttributes.override_redirect = True;6423windowAttributes.save_under = True;6424valuemask |= CWOverrideRedirect | CWSaveUnder;6425}64266427int rq_screen = get_screen_from_rect(p_rect);6428if (rq_screen < 0) {6429rq_screen = get_primary_screen(); // Requested window rect is outside any screen bounds.6430}64316432Rect2i win_rect = p_rect;6433if (!p_parent_window) {6434// No parent.6435if (p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) {6436Rect2i screen_rect = Rect2i(screen_get_position(rq_screen), screen_get_size(rq_screen));64376438win_rect = screen_rect;6439} else {6440Rect2i srect = screen_get_usable_rect(rq_screen);6441Point2i wpos = p_rect.position;6442wpos = wpos.clamp(srect.position, srect.position + srect.size - p_rect.size / 3);64436444win_rect.position = wpos;6445}6446}64476448// Position and size hints are set from these values before they are updated to the actual6449// window size, so we need to initialize them here.6450wd.position = win_rect.position;6451wd.size = win_rect.size;64526453{6454wd.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);64556456DEBUG_LOG_X11("CreateWindow window=%lu, parent: %lu \n", wd.x11_window, wd.parent);64576458if (p_parent_window) {6459wd.embed_parent = p_parent_window;6460XSetTransientForHint(x11_display, wd.x11_window, p_parent_window);6461}64626463XSetWindowAttributes window_attributes_ime = {};6464window_attributes_ime.event_mask = KeyPressMask | KeyReleaseMask | StructureNotifyMask | ExposureMask;64656466wd.x11_xim_window = XCreateWindow(x11_display, wd.x11_window, 0, 0, 1, 1, 0, CopyFromParent, InputOnly, CopyFromParent, CWEventMask, &window_attributes_ime);6467#ifdef XKB_ENABLED6468if (dead_tbl && xkb_loaded_v05p) {6469wd.xkb_state = xkb_compose_state_new(dead_tbl, XKB_COMPOSE_STATE_NO_FLAGS);6470}6471#endif6472#ifdef ACCESSKIT_ENABLED6473if (accessibility_driver && !accessibility_driver->window_create(id, nullptr)) {6474if (OS::get_singleton()->is_stdout_verbose()) {6475ERR_PRINT("Can't create an accessibility adapter for window, accessibility support disabled!");6476}6477memdelete(accessibility_driver);6478accessibility_driver = nullptr;6479}6480#endif6481// Enable receiving notification when the window is initialized (MapNotify)6482// so the focus can be set at the right time.6483if (!wd.no_focus && !wd.is_popup) {6484XSelectInput(x11_display, wd.x11_window, StructureNotifyMask);6485}64866487//associate PID6488// make PID known to X116489{6490const long pid = OS::get_singleton()->get_process_id();6491Atom net_wm_pid = XInternAtom(x11_display, "_NET_WM_PID", False);6492if (net_wm_pid != None) {6493XChangeProperty(x11_display, wd.x11_window, net_wm_pid, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&pid, 1);6494}6495}64966497long im_event_mask = 0;64986499{6500XIEventMask all_event_mask;6501XSetWindowAttributes new_attr;65026503new_attr.event_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask |6504ButtonReleaseMask | EnterWindowMask |6505LeaveWindowMask | PointerMotionMask |6506Button1MotionMask |6507Button2MotionMask | Button3MotionMask |6508Button4MotionMask | Button5MotionMask |6509ButtonMotionMask | KeymapStateMask |6510ExposureMask | VisibilityChangeMask |6511StructureNotifyMask |6512SubstructureNotifyMask | SubstructureRedirectMask |6513FocusChangeMask | PropertyChangeMask |6514ColormapChangeMask | OwnerGrabButtonMask |6515im_event_mask;65166517XChangeWindowAttributes(x11_display, wd.x11_window, CWEventMask, &new_attr);65186519static unsigned char all_mask_data[XIMaskLen(XI_LASTEVENT)] = {};65206521all_event_mask.deviceid = XIAllDevices;6522all_event_mask.mask_len = sizeof(all_mask_data);6523all_event_mask.mask = all_mask_data;65246525XISetMask(all_event_mask.mask, XI_HierarchyChanged);65266527#ifdef TOUCH_ENABLED6528if (xi.touch_devices.size()) {6529XISetMask(all_event_mask.mask, XI_TouchBegin);6530XISetMask(all_event_mask.mask, XI_TouchUpdate);6531XISetMask(all_event_mask.mask, XI_TouchEnd);6532XISetMask(all_event_mask.mask, XI_TouchOwnership);6533}6534#endif65356536XISelectEvents(x11_display, wd.x11_window, &all_event_mask, 1);6537}65386539/* set the titlebar name */6540XStoreName(x11_display, wd.x11_window, "Godot");6541XSetWMProtocols(x11_display, wd.x11_window, &wm_delete, 1);6542if (xdnd_aware != None) {6543XChangeProperty(x11_display, wd.x11_window, xdnd_aware, XA_ATOM, 32, PropModeReplace, (unsigned char *)&xdnd_version, 1);6544}65456546_create_xic(wd);6547_update_context(wd);65486549if (wd.is_popup || wd.no_focus || (wd.embed_parent && !kde5_embed_workaround)) {6550// Set Utility type to disable fade animations.6551Atom type_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE_UTILITY", False);6552Atom wt_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE", False);6553if (wt_atom != None && type_atom != None) {6554XChangeProperty(x11_display, wd.x11_window, wt_atom, XA_ATOM, 32, PropModeReplace, (unsigned char *)&type_atom, 1);6555}6556} else {6557Atom type_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE_NORMAL", False);6558Atom wt_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE", False);65596560if (wt_atom != None && type_atom != None) {6561XChangeProperty(x11_display, wd.x11_window, wt_atom, XA_ATOM, 32, PropModeReplace, (unsigned char *)&type_atom, 1);6562}6563}65646565if (p_parent_window) {6566// Disable the window in the taskbar and alt-tab.6567_set_window_taskbar_pager_enabled(wd.x11_window, false);6568}65696570_update_size_hints(id);65716572#if defined(RD_ENABLED)6573if (rendering_context) {6574union {6575#ifdef VULKAN_ENABLED6576RenderingContextDriverVulkanX11::WindowPlatformData vulkan;6577#endif6578} wpd;6579#ifdef VULKAN_ENABLED6580if (rendering_driver == "vulkan") {6581wpd.vulkan.window = wd.x11_window;6582wpd.vulkan.display = x11_display;6583}6584#endif6585Error err = rendering_context->window_create(id, &wpd);6586ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, vformat("Can't create a %s window", rendering_driver));65876588rendering_context->window_set_size(id, win_rect.size.width, win_rect.size.height);6589rendering_context->window_set_vsync_mode(id, p_vsync_mode);6590}6591#endif6592#ifdef GLES3_ENABLED6593if (gl_manager) {6594Error err = gl_manager->window_create(id, wd.x11_window, x11_display, win_rect.size.width, win_rect.size.height);6595ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't create an OpenGL window");6596}6597if (gl_manager_egl) {6598Error err = gl_manager_egl->window_create(id, x11_display, &wd.x11_window, win_rect.size.width, win_rect.size.height);6599ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Failed to create an OpenGLES window.");6600}6601window_set_vsync_mode(p_vsync_mode, id);6602#endif66036604//set_class_hint(x11_display, wd.x11_window);6605XFlush(x11_display);66066607XSync(x11_display, False);6608//XSetErrorHandler(oldHandler);6609}66106611window_set_mode(p_mode, id);66126613//sync size6614{6615XWindowAttributes xwa;66166617XSync(x11_display, False);6618XGetWindowAttributes(x11_display, wd.x11_window, &xwa);66196620wd.position.x = xwa.x;6621wd.position.y = xwa.y;6622wd.size.width = xwa.width;6623wd.size.height = xwa.height;6624}66256626//set cursor6627if (cursors[current_cursor] != None) {6628XDefineCursor(x11_display, wd.x11_window, cursors[current_cursor]);6629}66306631return id;6632}66336634static bool _is_xim_style_supported(const ::XIMStyle &p_style) {6635const ::XIMStyle supported_preedit = XIMPreeditCallbacks | XIMPreeditPosition | XIMPreeditNothing | XIMPreeditNone;6636const ::XIMStyle supported_status = XIMStatusNothing | XIMStatusNone;66376638// Check preedit style is supported6639if ((p_style & supported_preedit) == 0) {6640return false;6641}66426643// Check status style is supported6644if ((p_style & supported_status) == 0) {6645return false;6646}66476648return true;6649}66506651static ::XIMStyle _get_best_xim_style(const ::XIMStyle &p_style_a, const ::XIMStyle &p_style_b) {6652if (p_style_a == 0) {6653return p_style_b;6654}6655if (p_style_b == 0) {6656return p_style_a;6657}66586659const ::XIMStyle preedit = XIMPreeditArea | XIMPreeditCallbacks | XIMPreeditPosition | XIMPreeditNothing | XIMPreeditNone;6660const ::XIMStyle status = XIMStatusArea | XIMStatusCallbacks | XIMStatusNothing | XIMStatusNone;66616662::XIMStyle a = p_style_a & preedit;6663::XIMStyle b = p_style_b & preedit;6664if (a != b) {6665// Compare preedit styles.6666if ((a | b) & XIMPreeditCallbacks) {6667return a == XIMPreeditCallbacks ? p_style_a : p_style_b;6668} else if ((a | b) & XIMPreeditPosition) {6669return a == XIMPreeditPosition ? p_style_a : p_style_b;6670} else if ((a | b) & XIMPreeditArea) {6671return a == XIMPreeditArea ? p_style_a : p_style_b;6672} else if ((a | b) & XIMPreeditNothing) {6673return a == XIMPreeditNothing ? p_style_a : p_style_b;6674}6675} else {6676// Preedit styles are the same, compare status styles.6677a = p_style_a & status;6678b = p_style_b & status;66796680if ((a | b) & XIMStatusCallbacks) {6681return a == XIMStatusCallbacks ? p_style_a : p_style_b;6682} else if ((a | b) & XIMStatusArea) {6683return a == XIMStatusArea ? p_style_a : p_style_b;6684} else if ((a | b) & XIMStatusNothing) {6685return a == XIMStatusNothing ? p_style_a : p_style_b;6686}6687}6688return p_style_a;6689}66906691void DisplayServerX11::_xim_instantiate_callback(::Display *display, ::XPointer client_data,6692::XPointer call_data) {6693DisplayServerX11 *ds = reinterpret_cast<DisplayServerX11 *>(client_data);66946695ds->xim = XOpenIM(display, nullptr, nullptr, nullptr);66966697if (ds->xim == nullptr) {6698WARN_PRINT("XOpenIM failed.");6699ds->xim_style = 0L;6700} else {6701::XIMCallback im_destroy_callback;6702im_destroy_callback.client_data = client_data;6703im_destroy_callback.callback = (::XIMProc)(_xim_destroy_callback);6704if (XSetIMValues(ds->xim, XNDestroyCallback, &im_destroy_callback,6705nullptr) != nullptr) {6706WARN_PRINT("Error setting XIM destroy callback.");6707}67086709::XIMStyles *xim_styles = nullptr;6710ds->xim_style = 0L;6711char *imvalret = XGetIMValues(ds->xim, XNQueryInputStyle, &xim_styles, nullptr);6712if (imvalret != nullptr || xim_styles == nullptr) {6713fprintf(stderr, "Input method doesn't support any styles\n");6714}67156716if (xim_styles) {6717ds->xim_style = 0L;6718for (int i = 0; i < xim_styles->count_styles; i++) {6719const ::XIMStyle &style = xim_styles->supported_styles[i];67206721if (!_is_xim_style_supported(style)) {6722continue;6723}67246725ds->xim_style = _get_best_xim_style(ds->xim_style, style);6726}67276728XFree(xim_styles);6729}6730XFree(imvalret);6731}67326733// The input method has been (re)started.6734for (KeyValue<WindowID, WindowData> &E : ds->windows) {6735ds->_create_xic(E.value);6736}6737}67386739DisplayServerX11::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) {6740KeyMappingX11::initialize();67416742String current_desk = OS::get_singleton()->get_environment("XDG_CURRENT_DESKTOP").to_lower();6743String session_desk = OS::get_singleton()->get_environment("XDG_SESSION_DESKTOP").to_lower();6744swap_cancel_ok = (current_desk.contains("kde") || session_desk.contains("kde") || current_desk.contains("lxqt") || session_desk.contains("lxqt"));67456746xwayland = OS::get_singleton()->get_environment("XDG_SESSION_TYPE").to_lower() == "wayland";6747kde5_embed_workaround = current_desk == "kde" && OS::get_singleton()->get_environment("KDE_SESSION_VERSION") == "5";67486749native_menu = memnew(NativeMenu);6750context = p_context;67516752#ifdef SOWRAP_ENABLED6753#ifdef DEBUG_ENABLED6754int dylibloader_verbose = 1;6755#else6756int dylibloader_verbose = 0;6757#endif6758if (initialize_xlib(dylibloader_verbose) != 0) {6759r_error = ERR_UNAVAILABLE;6760ERR_FAIL_MSG("Can't load Xlib dynamically.");6761}67626763if (initialize_xcursor(dylibloader_verbose) != 0) {6764r_error = ERR_UNAVAILABLE;6765ERR_FAIL_MSG("Can't load XCursor dynamically.");6766}6767#ifdef XKB_ENABLED6768bool xkb_loaded = (initialize_xkbcommon(dylibloader_verbose) == 0);6769xkb_loaded_v05p = xkb_loaded;6770if (!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) {6771xkb_loaded_v05p = false;6772print_verbose("Detected XKBcommon library version older than 0.5, dead key composition and Unicode key labels disabled.");6773}6774xkb_loaded_v08p = xkb_loaded;6775if (!xkb_keysym_to_utf32 || !xkb_keysym_to_upper) {6776xkb_loaded_v08p = false;6777print_verbose("Detected XKBcommon library version older than 0.8, Unicode key labels disabled.");6778}6779#endif6780if (initialize_xext(dylibloader_verbose) != 0) {6781r_error = ERR_UNAVAILABLE;6782ERR_FAIL_MSG("Can't load Xext dynamically.");6783}67846785if (initialize_xinerama(dylibloader_verbose) != 0) {6786xinerama_ext_ok = false;6787}67886789if (initialize_xrandr(dylibloader_verbose) != 0) {6790xrandr_ext_ok = false;6791}67926793if (initialize_xrender(dylibloader_verbose) != 0) {6794r_error = ERR_UNAVAILABLE;6795ERR_FAIL_MSG("Can't load Xrender dynamically.");6796}67976798if (initialize_xinput2(dylibloader_verbose) != 0) {6799r_error = ERR_UNAVAILABLE;6800ERR_FAIL_MSG("Can't load Xinput2 dynamically.");6801}6802#else6803#ifdef XKB_ENABLED6804bool xkb_loaded = true;6805xkb_loaded_v05p = true;6806xkb_loaded_v08p = true;6807#endif6808#endif68096810#ifdef XKB_ENABLED6811if (xkb_loaded) {6812xkb_ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS);6813if (xkb_ctx) {6814const char *locale = getenv("LC_ALL");6815if (!locale || !*locale) {6816locale = getenv("LC_CTYPE");6817}6818if (!locale || !*locale) {6819locale = getenv("LANG");6820}6821if (!locale || !*locale) {6822locale = "C";6823}6824dead_tbl = xkb_compose_table_new_from_locale(xkb_ctx, locale, XKB_COMPOSE_COMPILE_NO_FLAGS);6825}6826}6827#endif68286829Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events);68306831r_error = OK;68326833#ifdef SOWRAP_ENABLED6834{6835if (!XcursorImageCreate || !XcursorImageLoadCursor || !XcursorImageDestroy || !XcursorGetDefaultSize || !XcursorGetTheme || !XcursorLibraryLoadImage) {6836// There's no API to check version, check if functions are available instead.6837ERR_PRINT("Unsupported Xcursor library version.");6838r_error = ERR_UNAVAILABLE;6839return;6840}6841}6842#endif68436844for (int i = 0; i < CURSOR_MAX; i++) {6845cursors[i] = None;6846cursor_img[i] = nullptr;6847}68486849XInitThreads(); //always use threads68506851/** XLIB INITIALIZATION **/6852x11_display = XOpenDisplay(nullptr);68536854if (!x11_display) {6855ERR_PRINT("X11 Display is not available");6856r_error = ERR_UNAVAILABLE;6857return;6858}68596860if (xshaped_ext_ok) {6861int version_major = 0;6862int version_minor = 0;6863int rc = XShapeQueryVersion(x11_display, &version_major, &version_minor);6864print_verbose(vformat("Xshape %d.%d detected.", version_major, version_minor));6865if (rc != 1 || version_major < 1) {6866xshaped_ext_ok = false;6867print_verbose("Unsupported Xshape library version.");6868}6869}68706871if (xinerama_ext_ok) {6872int version_major = 0;6873int version_minor = 0;6874int rc = XineramaQueryVersion(x11_display, &version_major, &version_minor);6875print_verbose(vformat("Xinerama %d.%d detected.", version_major, version_minor));6876if (rc != 1 || version_major < 1) {6877xinerama_ext_ok = false;6878print_verbose("Unsupported Xinerama library version.");6879}6880}68816882if (xrandr_ext_ok) {6883int version_major = 0;6884int version_minor = 0;6885int rc = XRRQueryVersion(x11_display, &version_major, &version_minor);6886print_verbose(vformat("Xrandr %d.%d detected.", version_major, version_minor));6887if (rc != 1 || (version_major == 1 && version_minor < 3) || (version_major < 1)) {6888xrandr_ext_ok = false;6889print_verbose("Unsupported Xrandr library version.");6890}6891}68926893{6894int version_major = 0;6895int version_minor = 0;6896int rc = XRenderQueryVersion(x11_display, &version_major, &version_minor);6897print_verbose(vformat("Xrender %d.%d detected.", version_major, version_minor));6898if (rc != 1 || (version_major == 0 && version_minor < 11)) {6899ERR_PRINT("Unsupported Xrender library version.");6900r_error = ERR_UNAVAILABLE;6901XCloseDisplay(x11_display);6902return;6903}6904}69056906{6907int version_major = 2; // Report 2.2 as supported by engine, but should work with 2.1 or 2.0 library as well.6908int version_minor = 2;6909int rc = XIQueryVersion(x11_display, &version_major, &version_minor);6910print_verbose(vformat("Xinput %d.%d detected.", version_major, version_minor));6911if (rc != Success || (version_major < 2)) {6912ERR_PRINT("Unsupported Xinput2 library version.");6913r_error = ERR_UNAVAILABLE;6914XCloseDisplay(x11_display);6915return;6916}6917}69186919char *modifiers = nullptr;6920Bool xkb_dar = False;6921XAutoRepeatOn(x11_display);6922xkb_dar = XkbSetDetectableAutoRepeat(x11_display, True, nullptr);69236924// Try to support IME if detectable auto-repeat is supported6925if (xkb_dar == True) {6926#ifdef X_HAVE_UTF8_STRING6927// Xutf8LookupString will be used later instead of XmbLookupString before6928// the multibyte sequences can be converted to unicode string.6929modifiers = XSetLocaleModifiers("");6930#endif6931}69326933if (modifiers == nullptr) {6934if (OS::get_singleton()->is_stdout_verbose()) {6935WARN_PRINT("IME is disabled.");6936}6937XSetLocaleModifiers("@im=none");6938WARN_PRINT("Error setting locale modifiers.");6939}69406941const char *err;6942int xrandr_major = 0;6943int xrandr_minor = 0;6944int event_base, error_base;6945xrandr_ext_ok = XRRQueryExtension(x11_display, &event_base, &error_base);6946xrandr_handle = dlopen("libXrandr.so.2", RTLD_LAZY);6947if (!xrandr_handle) {6948err = dlerror();6949// For some arcane reason, NetBSD now ships libXrandr.so.3 while the rest of the world has libXrandr.so.2...6950// In case this happens for other X11 platforms in the future, let's give it a try too before failing.6951xrandr_handle = dlopen("libXrandr.so.3", RTLD_LAZY);6952if (!xrandr_handle) {6953fprintf(stderr, "could not load libXrandr.so.2, Error: %s\n", err);6954}6955}69566957if (xrandr_handle) {6958XRRQueryVersion(x11_display, &xrandr_major, &xrandr_minor);6959if (((xrandr_major << 8) | xrandr_minor) >= 0x0105) {6960xrr_get_monitors = (xrr_get_monitors_t)dlsym(xrandr_handle, "XRRGetMonitors");6961if (!xrr_get_monitors) {6962err = dlerror();6963fprintf(stderr, "could not find symbol XRRGetMonitors\nError: %s\n", err);6964} else {6965xrr_free_monitors = (xrr_free_monitors_t)dlsym(xrandr_handle, "XRRFreeMonitors");6966if (!xrr_free_monitors) {6967err = dlerror();6968fprintf(stderr, "could not find XRRFreeMonitors\nError: %s\n", err);6969xrr_get_monitors = nullptr;6970}6971}6972}6973}69746975if (!_refresh_device_info()) {6976OS::get_singleton()->alert("Your system does not support XInput 2.\n"6977"Please upgrade your distribution.",6978"Unable to initialize XInput");6979r_error = ERR_UNAVAILABLE;6980return;6981}69826983if (!XRegisterIMInstantiateCallback(x11_display, nullptr, nullptr, nullptr, (::XIDProc)(_xim_instantiate_callback), (::XPointer)this)) {6984WARN_PRINT("Error registering XIM instantiate callback.");6985_xim_instantiate_callback(x11_display, (::XPointer)this, nullptr);6986}69876988/* Atom internment */6989wm_delete = XInternAtom(x11_display, "WM_DELETE_WINDOW", true);6990// Set Xdnd (drag & drop) support.6991xdnd_aware = XInternAtom(x11_display, "XdndAware", False);6992xdnd_enter = XInternAtom(x11_display, "XdndEnter", False);6993xdnd_position = XInternAtom(x11_display, "XdndPosition", False);6994xdnd_status = XInternAtom(x11_display, "XdndStatus", False);6995xdnd_action_copy = XInternAtom(x11_display, "XdndActionCopy", False);6996xdnd_drop = XInternAtom(x11_display, "XdndDrop", False);6997xdnd_finished = XInternAtom(x11_display, "XdndFinished", False);6998xdnd_selection = XInternAtom(x11_display, "XdndSelection", False);69997000#ifdef SPEECHD_ENABLED7001// Init TTS7002bool tts_enabled = GLOBAL_GET("audio/general/text_to_speech");7003if (tts_enabled) {7004initialize_tts();7005}7006#endif70077008#ifdef ACCESSKIT_ENABLED7009if (accessibility_get_mode() != DisplayServer::AccessibilityMode::ACCESSIBILITY_DISABLED) {7010accessibility_driver = memnew(AccessibilityDriverAccessKit);7011if (accessibility_driver->init() != OK) {7012memdelete(accessibility_driver);7013accessibility_driver = nullptr;7014}7015}7016#endif70177018//!!!!!!!!!!!!!!!!!!!!!!!!!!7019//TODO - do Vulkan and OpenGL support checks, driver selection and fallback7020rendering_driver = p_rendering_driver;70217022bool driver_found = false;7023String executable_name = OS::get_singleton()->get_executable_path().get_file();70247025// Initialize context and rendering device.70267027if (rendering_driver == "dummy") {7028RasterizerDummy::make_current();7029driver_found = true;7030}70317032#if defined(RD_ENABLED)7033#if defined(VULKAN_ENABLED)7034if (rendering_driver == "vulkan") {7035rendering_context = memnew(RenderingContextDriverVulkanX11);7036}7037#endif // VULKAN_ENABLED70387039if (rendering_context) {7040if (rendering_context->initialize() != OK) {7041memdelete(rendering_context);7042rendering_context = nullptr;7043#if defined(GLES3_ENABLED)7044bool fallback_to_opengl3 = GLOBAL_GET("rendering/rendering_device/fallback_to_opengl3");7045if (fallback_to_opengl3 && rendering_driver != "opengl3") {7046WARN_PRINT("Your video card drivers seem not to support the required Vulkan version, switching to OpenGL 3.");7047rendering_driver = "opengl3";7048OS::get_singleton()->set_current_rendering_method("gl_compatibility", OS::RENDERING_SOURCE_FALLBACK);7049OS::get_singleton()->set_current_rendering_driver_name(rendering_driver, OS::RENDERING_SOURCE_FALLBACK);7050} else7051#endif // GLES3_ENABLED7052{7053r_error = ERR_CANT_CREATE;70547055if (p_rendering_driver == "vulkan") {7056OS::get_singleton()->alert(7057vformat("Your video card drivers seem not to support the required Vulkan version.\n\n"7058"If possible, consider updating your video card drivers or using the OpenGL 3 driver.\n\n"7059"You can enable the OpenGL 3 driver by starting the engine from the\n"7060"command line with the command:\n\n \"%s\" --rendering-driver opengl3\n\n"7061"If you recently updated your video card drivers, try rebooting.",7062executable_name),7063"Unable to initialize Vulkan video driver");7064}70657066ERR_FAIL_MSG(vformat("Could not initialize %s", rendering_driver));7067}7068}7069driver_found = true;7070}7071#endif // RD_ENABLED70727073#if defined(GLES3_ENABLED)7074if (rendering_driver == "opengl3" || rendering_driver == "opengl3_es") {7075if (getenv("DRI_PRIME") == nullptr) {7076int use_prime = -1;70777078if (getenv("PRIMUS_DISPLAY") ||7079getenv("PRIMUS_libGLd") ||7080getenv("PRIMUS_libGLa") ||7081getenv("PRIMUS_libGL") ||7082getenv("PRIMUS_LOAD_GLOBAL") ||7083getenv("BUMBLEBEE_SOCKET")) {7084print_verbose("Optirun/primusrun detected. Skipping GPU detection");7085use_prime = 0;7086}70877088// Some tools use fake libGL libraries and have them override the real one using7089// LD_LIBRARY_PATH, so we skip them. *But* Steam also sets LD_LIBRARY_PATH for its7090// runtime and includes system `/lib` and `/lib64`... so ignore Steam.7091if (use_prime == -1 && getenv("LD_LIBRARY_PATH") && !getenv("STEAM_RUNTIME_LIBRARY_PATH")) {7092String ld_library_path(getenv("LD_LIBRARY_PATH"));7093Vector<String> libraries = ld_library_path.split(":");70947095for (int i = 0; i < libraries.size(); ++i) {7096if (FileAccess::exists(libraries[i] + "/libGL.so.1") ||7097FileAccess::exists(libraries[i] + "/libGL.so")) {7098print_verbose("Custom libGL override detected. Skipping GPU detection");7099use_prime = 0;7100}7101}7102}71037104if (use_prime == -1) {7105print_verbose("Detecting GPUs, set DRI_PRIME in the environment to override GPU detection logic.");7106use_prime = DetectPrimeX11::detect_prime();7107}71087109if (use_prime) {7110print_line("Found discrete GPU, setting DRI_PRIME=1 to use it.");7111print_line("Note: Set DRI_PRIME=0 in the environment to disable Godot from using the discrete GPU.");7112setenv("DRI_PRIME", "1", 1);7113}7114}7115}7116if (rendering_driver == "opengl3") {7117gl_manager = memnew(GLManager_X11(p_resolution, GLManager_X11::GLES_3_0_COMPATIBLE));7118if (gl_manager->initialize(x11_display) != OK || gl_manager->open_display(x11_display) != OK) {7119memdelete(gl_manager);7120gl_manager = nullptr;7121bool fallback = GLOBAL_GET("rendering/gl_compatibility/fallback_to_gles");7122if (fallback) {7123WARN_PRINT("Your video card drivers seem not to support the required OpenGL version, switching to OpenGLES.");7124rendering_driver = "opengl3_es";7125OS::get_singleton()->set_current_rendering_driver_name(rendering_driver, OS::RENDERING_SOURCE_FALLBACK);7126} else {7127r_error = ERR_UNAVAILABLE;71287129OS::get_singleton()->alert(7130vformat("Your video card drivers seem not to support the required OpenGL 3.3 version.\n\n"7131"If possible, consider updating your video card drivers or using the Vulkan driver.\n\n"7132"You can enable the Vulkan driver by starting the engine from the\n"7133"command line with the command:\n\n \"%s\" --rendering-driver vulkan\n\n"7134"If you recently updated your video card drivers, try rebooting.",7135executable_name),7136"Unable to initialize OpenGL video driver");71377138ERR_FAIL_MSG("Could not initialize OpenGL.");7139}7140} else {7141driver_found = true;7142RasterizerGLES3::make_current(true);7143}7144}71457146if (rendering_driver == "opengl3_es") {7147gl_manager_egl = memnew(GLManagerEGL_X11);7148if (gl_manager_egl->initialize() != OK || gl_manager_egl->open_display(x11_display) != OK) {7149memdelete(gl_manager_egl);7150gl_manager_egl = nullptr;7151r_error = ERR_UNAVAILABLE;71527153OS::get_singleton()->alert(7154"Your video card drivers seem not to support the required OpenGL ES 3.0 version.\n\n"7155"If possible, consider updating your video card drivers.\n\n"7156"If you recently updated your video card drivers, try rebooting.",7157"Unable to initialize OpenGL ES video driver");71587159ERR_FAIL_MSG("Could not initialize OpenGL ES.");7160}7161driver_found = true;7162RasterizerGLES3::make_current(false);7163}71647165#endif // GLES3_ENABLED71667167if (!driver_found) {7168r_error = ERR_UNAVAILABLE;7169ERR_FAIL_MSG("Video driver not found.");7170}71717172Point2i window_position;7173if (p_position != nullptr) {7174window_position = *p_position;7175} else {7176if (p_screen == SCREEN_OF_MAIN_WINDOW) {7177p_screen = SCREEN_PRIMARY;7178}7179Rect2i scr_rect = screen_get_usable_rect(p_screen);7180window_position = scr_rect.position + (scr_rect.size - p_resolution) / 2;7181}71827183WindowID main_window = _create_window(p_mode, p_vsync_mode, p_flags, Rect2i(window_position, p_resolution), p_parent_window);7184if (main_window == INVALID_WINDOW_ID) {7185r_error = ERR_CANT_CREATE;7186return;7187}7188for (int i = 0; i < WINDOW_FLAG_MAX; i++) {7189if (p_flags & (1 << i)) {7190window_set_flag(WindowFlags(i), true, main_window);7191}7192}71937194#if defined(RD_ENABLED)7195if (rendering_context) {7196rendering_device = memnew(RenderingDevice);7197if (rendering_device->initialize(rendering_context, MAIN_WINDOW_ID) != OK) {7198memdelete(rendering_device);7199rendering_device = nullptr;7200memdelete(rendering_context);7201rendering_context = nullptr;7202r_error = ERR_UNAVAILABLE;7203return;7204}7205rendering_device->screen_create(MAIN_WINDOW_ID);72067207RendererCompositorRD::make_current();7208}7209#endif // RD_ENABLED72107211{7212//set all event master mask7213XIEventMask all_master_event_mask;7214static unsigned char all_master_mask_data[XIMaskLen(XI_LASTEVENT)] = {};7215all_master_event_mask.deviceid = XIAllMasterDevices;7216all_master_event_mask.mask_len = sizeof(all_master_mask_data);7217all_master_event_mask.mask = all_master_mask_data;7218XISetMask(all_master_event_mask.mask, XI_DeviceChanged);7219XISetMask(all_master_event_mask.mask, XI_RawMotion);7220XISelectEvents(x11_display, DefaultRootWindow(x11_display), &all_master_event_mask, 1);7221}72227223cursor_size = XcursorGetDefaultSize(x11_display);7224cursor_theme = XcursorGetTheme(x11_display);72257226if (!cursor_theme) {7227print_verbose("XcursorGetTheme could not get cursor theme");7228cursor_theme = "default";7229}72307231for (int i = 0; i < CURSOR_MAX; i++) {7232static const char *cursor_file[] = {7233"left_ptr",7234"xterm",7235"hand2",7236"cross",7237"watch",7238"left_ptr_watch",7239"fleur",7240"dnd-move",7241"crossed_circle",7242"v_double_arrow",7243"h_double_arrow",7244"size_bdiag",7245"size_fdiag",7246"move",7247"row_resize",7248"col_resize",7249"question_arrow"7250};72517252cursor_img[i] = XcursorLibraryLoadImage(cursor_file[i], cursor_theme, cursor_size);7253if (!cursor_img[i]) {7254const char *fallback = nullptr;72557256switch (i) {7257case CURSOR_POINTING_HAND:7258fallback = "pointer";7259break;7260case CURSOR_CROSS:7261fallback = "crosshair";7262break;7263case CURSOR_WAIT:7264fallback = "wait";7265break;7266case CURSOR_BUSY:7267fallback = "progress";7268break;7269case CURSOR_DRAG:7270fallback = "grabbing";7271break;7272case CURSOR_CAN_DROP:7273fallback = "hand1";7274break;7275case CURSOR_FORBIDDEN:7276fallback = "forbidden";7277break;7278case CURSOR_VSIZE:7279fallback = "ns-resize";7280break;7281case CURSOR_HSIZE:7282fallback = "ew-resize";7283break;7284case CURSOR_BDIAGSIZE:7285fallback = "fd_double_arrow";7286break;7287case CURSOR_FDIAGSIZE:7288fallback = "bd_double_arrow";7289break;7290case CURSOR_MOVE:7291cursor_img[i] = cursor_img[CURSOR_DRAG];7292break;7293case CURSOR_VSPLIT:7294fallback = "sb_v_double_arrow";7295break;7296case CURSOR_HSPLIT:7297fallback = "sb_h_double_arrow";7298break;7299case CURSOR_HELP:7300fallback = "help";7301break;7302}7303if (fallback != nullptr) {7304cursor_img[i] = XcursorLibraryLoadImage(fallback, cursor_theme, cursor_size);7305}7306}7307if (cursor_img[i]) {7308cursors[i] = XcursorImageLoadCursor(x11_display, cursor_img[i]);7309} else {7310print_verbose("Failed loading custom cursor: " + String(cursor_file[i]));7311}7312}73137314{7315// Creating an empty/transparent cursor73167317// Create 1x1 bitmap7318Pixmap cursormask = XCreatePixmap(x11_display,7319RootWindow(x11_display, DefaultScreen(x11_display)), 1, 1, 1);73207321// Fill with zero7322XGCValues xgc;7323xgc.function = GXclear;7324GC gc = XCreateGC(x11_display, cursormask, GCFunction, &xgc);7325XFillRectangle(x11_display, cursormask, gc, 0, 0, 1, 1);73267327// Color value doesn't matter. Mask zero means no foreground or background will be drawn7328XColor col = {};73297330Cursor cursor = XCreatePixmapCursor(x11_display,7331cursormask, // source (using cursor mask as placeholder, since it'll all be ignored)7332cursormask, // mask7333&col, &col, 0, 0);73347335XFreePixmap(x11_display, cursormask);7336XFreeGC(x11_display, gc);73377338if (cursor == None) {7339ERR_PRINT("FAILED CREATING CURSOR");7340}73417342null_cursor = cursor;7343}7344cursor_set_shape(CURSOR_BUSY);73457346// Search the X11 event queue for ConfigureNotify events and process all7347// that are currently queued early, so we can get the final window size7348// for correctly drawing of the bootsplash.7349XEvent config_event;7350while (XCheckTypedEvent(x11_display, ConfigureNotify, &config_event)) {7351_window_changed(&config_event);7352}7353events_thread.start(_poll_events_thread, this);73547355_update_real_mouse_position(windows[MAIN_WINDOW_ID]);73567357#ifdef DBUS_ENABLED7358bool dbus_ok = true;7359#ifdef SOWRAP_ENABLED7360if (initialize_dbus(dylibloader_verbose) != 0) {7361print_verbose("Failed to load DBus library!");7362dbus_ok = false;7363}7364#endif7365if (dbus_ok) {7366bool ver_ok = false;7367int version_major = 0;7368int version_minor = 0;7369int version_rev = 0;7370dbus_get_version(&version_major, &version_minor, &version_rev);7371ver_ok = (version_major == 1 && version_minor >= 10) || (version_major > 1); // 1.10.07372print_verbose(vformat("DBus %d.%d.%d detected.", version_major, version_minor, version_rev));7373if (!ver_ok) {7374print_verbose("Unsupported DBus library version!");7375dbus_ok = false;7376}7377}7378if (dbus_ok) {7379screensaver = memnew(FreeDesktopScreenSaver);7380portal_desktop = memnew(FreeDesktopPortalDesktop);7381atspi_monitor = memnew(FreeDesktopAtSPIMonitor);7382}7383#endif // DBUS_ENABLED73847385screen_set_keep_on(GLOBAL_GET("display/window/energy_saving/keep_screen_on"));73867387XSetErrorHandler(&default_window_error_handler);73887389r_error = OK;7390}73917392DisplayServerX11::~DisplayServerX11() {7393// Send owned clipboard data to clipboard manager before exit.7394Window x11_main_window = windows[MAIN_WINDOW_ID].x11_window;7395_clipboard_transfer_ownership(XA_PRIMARY, x11_main_window);7396_clipboard_transfer_ownership(XInternAtom(x11_display, "CLIPBOARD", 0), x11_main_window);73977398events_thread_done.set();7399events_thread.wait_to_finish();74007401if (native_menu) {7402memdelete(native_menu);7403native_menu = nullptr;7404}74057406//destroy all windows7407for (KeyValue<WindowID, WindowData> &E : windows) {7408#if defined(RD_ENABLED)7409if (rendering_device) {7410rendering_device->screen_free(E.key);7411}74127413if (rendering_context) {7414rendering_context->window_destroy(E.key);7415}7416#endif7417#ifdef GLES3_ENABLED7418if (gl_manager) {7419gl_manager->window_destroy(E.key);7420}7421if (gl_manager_egl) {7422gl_manager_egl->window_destroy(E.key);7423}7424#endif74257426#ifdef ACCESSKIT_ENABLED7427if (accessibility_driver) {7428accessibility_driver->window_destroy(E.key);7429}7430#endif74317432WindowData &wd = E.value;7433if (wd.xic) {7434XDestroyIC(wd.xic);7435wd.xic = nullptr;7436}7437XDestroyWindow(x11_display, wd.x11_xim_window);7438#ifdef XKB_ENABLED7439if (xkb_loaded_v05p) {7440if (wd.xkb_state) {7441xkb_compose_state_unref(wd.xkb_state);7442wd.xkb_state = nullptr;7443}7444}7445#endif7446XUnmapWindow(x11_display, wd.x11_window);7447XDestroyWindow(x11_display, wd.x11_window);7448}74497450#ifdef XKB_ENABLED7451if (xkb_loaded_v05p) {7452if (dead_tbl) {7453xkb_compose_table_unref(dead_tbl);7454}7455if (xkb_ctx) {7456xkb_context_unref(xkb_ctx);7457}7458}7459#endif74607461//destroy drivers7462#if defined(RD_ENABLED)7463if (rendering_device) {7464memdelete(rendering_device);7465rendering_device = nullptr;7466}74677468if (rendering_context) {7469memdelete(rendering_context);7470rendering_context = nullptr;7471}7472#endif74737474#ifdef GLES3_ENABLED7475if (gl_manager) {7476memdelete(gl_manager);7477gl_manager = nullptr;7478}7479if (gl_manager_egl) {7480memdelete(gl_manager_egl);7481gl_manager_egl = nullptr;7482}7483#endif74847485if (xrandr_handle) {7486dlclose(xrandr_handle);7487}74887489for (int i = 0; i < CURSOR_MAX; i++) {7490if (cursors[i] != None) {7491XFreeCursor(x11_display, cursors[i]);7492}7493if (cursor_img[i] != nullptr) {7494XcursorImageDestroy(cursor_img[i]);7495}7496}74977498if (xim) {7499XCloseIM(xim);7500}75017502XCloseDisplay(x11_display);7503if (xmbstring) {7504memfree(xmbstring);7505}7506#ifdef ACCESSKIT_ENABLED7507if (accessibility_driver) {7508memdelete(accessibility_driver);7509}7510#endif7511#ifdef SPEECHD_ENABLED7512if (tts) {7513memdelete(tts);7514}7515#endif75167517#ifdef DBUS_ENABLED7518if (screensaver) {7519memdelete(screensaver);7520}7521if (portal_desktop) {7522memdelete(portal_desktop);7523}7524if (atspi_monitor) {7525memdelete(atspi_monitor);7526}7527#endif7528}75297530void DisplayServerX11::register_x11_driver() {7531register_create_function("x11", create_func, get_rendering_drivers_func);7532}75337534#endif // X11 enabled753575367537