Path: blob/master/platform/linuxbsd/x11/display_server_x11.cpp
11353 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/io/file_access.h"39#include "core/math/math_funcs.h"40#include "core/os/main_loop.h"41#include "core/string/print_string.h"42#include "core/string/ustring.h"43#include "core/version.h"44#include "drivers/png/png_driver_common.h"45#include "main/main.h"4647#include "servers/rendering/dummy/rasterizer_dummy.h"4849#if defined(VULKAN_ENABLED)50#include "servers/rendering/renderer_rd/renderer_compositor_rd.h"51#endif5253#if defined(GLES3_ENABLED)54#include "drivers/gles3/rasterizer_gles3.h"55#endif5657#ifdef ACCESSKIT_ENABLED58#include "drivers/accesskit/accessibility_driver_accesskit.h"59#endif6061#ifdef DBUS_ENABLED62#ifdef SOWRAP_ENABLED63#include "dbus-so_wrap.h"64#else65#include <dbus/dbus.h>66#endif67#endif6869#include <dlfcn.h>70#include <sys/stat.h>71#include <sys/types.h>72#include <unistd.h>73#include <climits>74#include <cstdio>75#include <cstdlib>7677#undef CursorShape78#include <X11/XKBlib.h>7980// ICCCM81#define WM_NormalState 1L // window normal state82#define WM_IconicState 3L // window minimized83// EWMH84#define _NET_WM_STATE_REMOVE 0L // remove/unset property85#define _NET_WM_STATE_ADD 1L // add/set property8687#define _NET_WM_MOVERESIZE_SIZE_TOPLEFT 0L88#define _NET_WM_MOVERESIZE_SIZE_TOP 1L89#define _NET_WM_MOVERESIZE_SIZE_TOPRIGHT 2L90#define _NET_WM_MOVERESIZE_SIZE_RIGHT 3L91#define _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT 4L92#define _NET_WM_MOVERESIZE_SIZE_BOTTOM 5L93#define _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT 6L94#define _NET_WM_MOVERESIZE_SIZE_LEFT 7L95#define _NET_WM_MOVERESIZE_MOVE 8L9697// 2.2 is the first release with multitouch98#define XINPUT_CLIENT_VERSION_MAJOR 299#define XINPUT_CLIENT_VERSION_MINOR 2100101#define VALUATOR_ABSX 0102#define VALUATOR_ABSY 1103#define VALUATOR_PRESSURE 2104#define VALUATOR_TILTX 3105#define VALUATOR_TILTY 4106107//#define DISPLAY_SERVER_X11_DEBUG_LOGS_ENABLED108#ifdef DISPLAY_SERVER_X11_DEBUG_LOGS_ENABLED109#define DEBUG_LOG_X11(...) printf(__VA_ARGS__)110#else111#define DEBUG_LOG_X11(...)112#endif113114static const double abs_resolution_mult = 10000.0;115static const double abs_resolution_range_mult = 10.0;116117// Hints for X11 fullscreen118struct Hints {119unsigned long flags = 0;120unsigned long functions = 0;121unsigned long decorations = 0;122long inputMode = 0;123unsigned long status = 0;124};125126static String get_atom_name(Display *p_disp, Atom p_atom) {127char *name = XGetAtomName(p_disp, p_atom);128ERR_FAIL_NULL_V_MSG(name, String(), "Atom is invalid.");129String ret = String::utf8(name);130XFree(name);131return ret;132}133134bool DisplayServerX11::has_feature(Feature p_feature) const {135switch (p_feature) {136#ifndef DISABLE_DEPRECATED137case FEATURE_GLOBAL_MENU: {138return (native_menu && native_menu->has_feature(NativeMenu::FEATURE_GLOBAL_MENU));139} break;140#endif141case FEATURE_SUBWINDOWS:142#ifdef TOUCH_ENABLED143case FEATURE_TOUCHSCREEN:144#endif145case FEATURE_MOUSE:146case FEATURE_MOUSE_WARP:147case FEATURE_CLIPBOARD:148case FEATURE_CURSOR_SHAPE:149case FEATURE_CUSTOM_CURSOR_SHAPE:150case FEATURE_IME:151case FEATURE_WINDOW_TRANSPARENCY:152//case FEATURE_HIDPI:153case FEATURE_ICON:154//case FEATURE_NATIVE_ICON:155case FEATURE_SWAP_BUFFERS:156#ifdef DBUS_ENABLED157case FEATURE_KEEP_SCREEN_ON:158#endif159case FEATURE_CLIPBOARD_PRIMARY:160case FEATURE_WINDOW_EMBEDDING:161case FEATURE_WINDOW_DRAG: {162return true;163} break;164165//case FEATURE_NATIVE_DIALOG:166//case FEATURE_NATIVE_DIALOG_INPUT:167#ifdef DBUS_ENABLED168case FEATURE_NATIVE_DIALOG_FILE:169case FEATURE_NATIVE_DIALOG_FILE_EXTRA:170case FEATURE_NATIVE_DIALOG_FILE_MIME: {171return (portal_desktop && portal_desktop->is_supported() && portal_desktop->is_file_chooser_supported());172} break;173case FEATURE_NATIVE_COLOR_PICKER: {174return (portal_desktop && portal_desktop->is_supported() && portal_desktop->is_screenshot_supported());175} break;176#endif177case FEATURE_SCREEN_CAPTURE: {178return !xwayland;179} break;180181#ifdef SPEECHD_ENABLED182case FEATURE_TEXT_TO_SPEECH: {183return true;184} break;185#endif186187#ifdef ACCESSKIT_ENABLED188case FEATURE_ACCESSIBILITY_SCREEN_READER: {189return (accessibility_driver != nullptr);190} break;191#endif192193default: {194return false;195}196}197}198199String DisplayServerX11::get_name() const {200return "X11";201}202203void DisplayServerX11::_update_real_mouse_position(const WindowData &wd) {204Window root_return, child_return;205int root_x, root_y, win_x, win_y;206unsigned int mask_return;207208Bool xquerypointer_result = XQueryPointer(x11_display, wd.x11_window, &root_return, &child_return, &root_x, &root_y,209&win_x, &win_y, &mask_return);210211if (xquerypointer_result) {212if (win_x > 0 && win_y > 0 && win_x <= wd.size.width && win_y <= wd.size.height) {213last_mouse_pos.x = win_x;214last_mouse_pos.y = win_y;215last_mouse_pos_valid = true;216Input::get_singleton()->set_mouse_position(last_mouse_pos);217}218}219}220221bool DisplayServerX11::_refresh_device_info() {222int event_base, error_base;223224print_verbose("XInput: Refreshing devices.");225226if (!XQueryExtension(x11_display, "XInputExtension", &xi.opcode, &event_base, &error_base)) {227print_verbose("XInput extension not available. Please upgrade your distribution.");228return false;229}230231int xi_major_query = XINPUT_CLIENT_VERSION_MAJOR;232int xi_minor_query = XINPUT_CLIENT_VERSION_MINOR;233234if (XIQueryVersion(x11_display, &xi_major_query, &xi_minor_query) != Success) {235print_verbose(vformat("XInput 2 not available (server supports %d.%d).", xi_major_query, xi_minor_query));236xi.opcode = 0;237return false;238}239240if (xi_major_query < XINPUT_CLIENT_VERSION_MAJOR || (xi_major_query == XINPUT_CLIENT_VERSION_MAJOR && xi_minor_query < XINPUT_CLIENT_VERSION_MINOR)) {241print_verbose(vformat("XInput %d.%d not available (server supports %d.%d). Touch input unavailable.",242XINPUT_CLIENT_VERSION_MAJOR, XINPUT_CLIENT_VERSION_MINOR, xi_major_query, xi_minor_query));243}244245xi.absolute_devices.clear();246xi.touch_devices.clear();247xi.pen_inverted_devices.clear();248xi.last_relative_time = 0;249250int dev_count;251XIDeviceInfo *info = XIQueryDevice(x11_display, XIAllDevices, &dev_count);252253for (int i = 0; i < dev_count; i++) {254XIDeviceInfo *dev = &info[i];255if (!dev->enabled) {256continue;257}258if (!(dev->use == XISlavePointer || dev->use == XIFloatingSlave)) {259continue;260}261262bool direct_touch = false;263bool absolute_mode = false;264int resolution_x = 0;265int resolution_y = 0;266double abs_x_min = 0;267double abs_x_max = 0;268double abs_y_min = 0;269double abs_y_max = 0;270double pressure_min = 0;271double pressure_max = 0;272double tilt_x_min = 0;273double tilt_x_max = 0;274double tilt_y_min = 0;275double tilt_y_max = 0;276for (int j = 0; j < dev->num_classes; j++) {277#ifdef TOUCH_ENABLED278if (dev->classes[j]->type == XITouchClass && ((XITouchClassInfo *)dev->classes[j])->mode == XIDirectTouch) {279direct_touch = true;280}281#endif282if (dev->classes[j]->type == XIValuatorClass) {283XIValuatorClassInfo *class_info = (XIValuatorClassInfo *)dev->classes[j];284285if (class_info->number == VALUATOR_ABSX && class_info->mode == XIModeAbsolute) {286resolution_x = class_info->resolution;287abs_x_min = class_info->min;288abs_x_max = class_info->max;289absolute_mode = true;290} else if (class_info->number == VALUATOR_ABSY && class_info->mode == XIModeAbsolute) {291resolution_y = class_info->resolution;292abs_y_min = class_info->min;293abs_y_max = class_info->max;294absolute_mode = true;295} else if (class_info->number == VALUATOR_PRESSURE && class_info->mode == XIModeAbsolute) {296pressure_min = class_info->min;297pressure_max = class_info->max;298} else if (class_info->number == VALUATOR_TILTX && class_info->mode == XIModeAbsolute) {299tilt_x_min = class_info->min;300tilt_x_max = class_info->max;301} else if (class_info->number == VALUATOR_TILTY && class_info->mode == XIModeAbsolute) {302tilt_y_min = class_info->min;303tilt_y_max = class_info->max;304}305}306}307if (direct_touch) {308xi.touch_devices.push_back(dev->deviceid);309print_verbose("XInput: Using touch device: " + String(dev->name));310}311if (absolute_mode) {312// If no resolution was reported, use the min/max ranges.313if (resolution_x <= 0) {314resolution_x = (abs_x_max - abs_x_min) * abs_resolution_range_mult;315}316if (resolution_y <= 0) {317resolution_y = (abs_y_max - abs_y_min) * abs_resolution_range_mult;318}319xi.absolute_devices[dev->deviceid] = Vector2(abs_resolution_mult / resolution_x, abs_resolution_mult / resolution_y);320print_verbose("XInput: Absolute pointing device: " + String(dev->name));321}322323xi.pressure = 0;324xi.pen_pressure_range[dev->deviceid] = Vector2(pressure_min, pressure_max);325xi.pen_tilt_x_range[dev->deviceid] = Vector2(tilt_x_min, tilt_x_max);326xi.pen_tilt_y_range[dev->deviceid] = Vector2(tilt_y_min, tilt_y_max);327xi.pen_inverted_devices[dev->deviceid] = String(dev->name).findn("eraser") > 0;328}329330XIFreeDeviceInfo(info);331#ifdef TOUCH_ENABLED332if (!xi.touch_devices.size()) {333print_verbose("XInput: No touch devices found.");334}335#endif336337return true;338}339340void DisplayServerX11::_flush_mouse_motion() {341// Block events polling while flushing motion events.342MutexLock mutex_lock(events_mutex);343344for (uint32_t event_index = 0; event_index < polled_events.size(); ++event_index) {345XEvent &event = polled_events[event_index];346if (XGetEventData(x11_display, &event.xcookie) && event.xcookie.type == GenericEvent && event.xcookie.extension == xi.opcode) {347XIDeviceEvent *event_data = (XIDeviceEvent *)event.xcookie.data;348if (event_data->evtype == XI_RawMotion) {349XFreeEventData(x11_display, &event.xcookie);350polled_events.remove_at(event_index--);351continue;352}353XFreeEventData(x11_display, &event.xcookie);354break;355}356}357358xi.relative_motion.x = 0;359xi.relative_motion.y = 0;360}361362#ifdef SPEECHD_ENABLED363364void DisplayServerX11::initialize_tts() const {365const_cast<DisplayServerX11 *>(this)->tts = memnew(TTS_Linux);366}367368bool DisplayServerX11::tts_is_speaking() const {369if (unlikely(!tts)) {370initialize_tts();371}372ERR_FAIL_NULL_V(tts, false);373return tts->is_speaking();374}375376bool DisplayServerX11::tts_is_paused() const {377if (unlikely(!tts)) {378initialize_tts();379}380ERR_FAIL_NULL_V(tts, false);381return tts->is_paused();382}383384TypedArray<Dictionary> DisplayServerX11::tts_get_voices() const {385if (unlikely(!tts)) {386initialize_tts();387}388ERR_FAIL_NULL_V(tts, TypedArray<Dictionary>());389return tts->get_voices();390}391392void DisplayServerX11::tts_speak(const String &p_text, const String &p_voice, int p_volume, float p_pitch, float p_rate, int p_utterance_id, bool p_interrupt) {393if (unlikely(!tts)) {394initialize_tts();395}396ERR_FAIL_NULL(tts);397tts->speak(p_text, p_voice, p_volume, p_pitch, p_rate, p_utterance_id, p_interrupt);398}399400void DisplayServerX11::tts_pause() {401if (unlikely(!tts)) {402initialize_tts();403}404ERR_FAIL_NULL(tts);405tts->pause();406}407408void DisplayServerX11::tts_resume() {409if (unlikely(!tts)) {410initialize_tts();411}412ERR_FAIL_NULL(tts);413tts->resume();414}415416void DisplayServerX11::tts_stop() {417if (unlikely(!tts)) {418initialize_tts();419}420ERR_FAIL_NULL(tts);421tts->stop();422}423424#endif425426#ifdef DBUS_ENABLED427428bool DisplayServerX11::is_dark_mode_supported() const {429return portal_desktop && portal_desktop->is_supported() && portal_desktop->is_settings_supported();430}431432bool DisplayServerX11::is_dark_mode() const {433if (!is_dark_mode_supported()) {434return false;435}436switch (portal_desktop->get_appearance_color_scheme()) {437case 1:438// Prefers dark theme.439return true;440case 2:441// Prefers light theme.442return false;443default:444// Preference unknown.445return false;446}447}448449Color DisplayServerX11::get_accent_color() const {450if (!portal_desktop) {451return Color();452}453return portal_desktop->get_appearance_accent_color();454}455456void DisplayServerX11::set_system_theme_change_callback(const Callable &p_callable) {457ERR_FAIL_COND(!portal_desktop);458portal_desktop->set_system_theme_change_callback(p_callable);459}460461Error 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) {462ERR_FAIL_COND_V(!portal_desktop, ERR_UNAVAILABLE);463WindowID window_id = p_window_id;464465if (!windows.has(window_id) || windows[window_id].is_popup) {466window_id = MAIN_WINDOW_ID;467}468469String xid = vformat("x11:%x", (uint64_t)windows[window_id].x11_window);470return 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);471}472473Error 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) {474ERR_FAIL_COND_V(!portal_desktop, ERR_UNAVAILABLE);475WindowID window_id = p_window_id;476477if (!windows.has(window_id) || windows[window_id].is_popup) {478window_id = MAIN_WINDOW_ID;479}480481String xid = vformat("x11:%x", (uint64_t)windows[window_id].x11_window);482return 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);483}484485#endif486487void DisplayServerX11::beep() const {488XBell(x11_display, 0);489}490491void DisplayServerX11::_mouse_update_mode() {492_THREAD_SAFE_METHOD_493494MouseMode wanted_mouse_mode = mouse_mode_override_enabled495? mouse_mode_override496: mouse_mode_base;497498if (wanted_mouse_mode == mouse_mode) {499return;500}501502if (mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN) {503XUngrabPointer(x11_display, CurrentTime);504}505506// The only modes that show a cursor are VISIBLE and CONFINED507bool show_cursor = (wanted_mouse_mode == MOUSE_MODE_VISIBLE || wanted_mouse_mode == MOUSE_MODE_CONFINED);508bool previously_shown = (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED);509510if (show_cursor && !previously_shown) {511WindowID window_id = get_window_at_screen_position(mouse_get_position());512if (window_id != INVALID_WINDOW_ID && window_mouseover_id != window_id) {513if (window_mouseover_id != INVALID_WINDOW_ID) {514_send_window_event(windows[window_mouseover_id], WINDOW_EVENT_MOUSE_EXIT);515}516window_mouseover_id = window_id;517_send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_ENTER);518}519}520521for (const KeyValue<WindowID, WindowData> &E : windows) {522if (show_cursor) {523XDefineCursor(x11_display, E.value.x11_window, cursors[current_cursor]); // show cursor524} else {525XDefineCursor(x11_display, E.value.x11_window, null_cursor); // hide cursor526}527}528mouse_mode = wanted_mouse_mode;529530if (mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN) {531//flush pending motion events532_flush_mouse_motion();533WindowID window_id = _get_focused_window_or_popup();534if (!windows.has(window_id)) {535window_id = MAIN_WINDOW_ID;536}537WindowData &window = windows[window_id];538539if (XGrabPointer(540x11_display, window.x11_window, True,541ButtonPressMask | ButtonReleaseMask | PointerMotionMask,542GrabModeAsync, GrabModeAsync, window.x11_window, None, CurrentTime) != GrabSuccess) {543ERR_PRINT("NO GRAB");544}545546if (mouse_mode == MOUSE_MODE_CAPTURED) {547center.x = window.size.width / 2;548center.y = window.size.height / 2;549550XWarpPointer(x11_display, None, window.x11_window,5510, 0, 0, 0, (int)center.x, (int)center.y);552553Input::get_singleton()->set_mouse_position(center);554}555} else {556do_mouse_warp = false;557}558559XFlush(x11_display);560}561562void DisplayServerX11::mouse_set_mode(MouseMode p_mode) {563ERR_FAIL_INDEX(p_mode, MouseMode::MOUSE_MODE_MAX);564if (p_mode == mouse_mode_base) {565return;566}567mouse_mode_base = p_mode;568_mouse_update_mode();569}570571DisplayServerX11::MouseMode DisplayServerX11::mouse_get_mode() const {572return mouse_mode;573}574575void DisplayServerX11::mouse_set_mode_override(MouseMode p_mode) {576ERR_FAIL_INDEX(p_mode, MouseMode::MOUSE_MODE_MAX);577if (p_mode == mouse_mode_override) {578return;579}580mouse_mode_override = p_mode;581_mouse_update_mode();582}583584DisplayServerX11::MouseMode DisplayServerX11::mouse_get_mode_override() const {585return mouse_mode_override;586}587588void DisplayServerX11::mouse_set_mode_override_enabled(bool p_override_enabled) {589if (p_override_enabled == mouse_mode_override_enabled) {590return;591}592mouse_mode_override_enabled = p_override_enabled;593_mouse_update_mode();594}595596bool DisplayServerX11::mouse_is_mode_override_enabled() const {597return mouse_mode_override_enabled;598}599600void DisplayServerX11::warp_mouse(const Point2i &p_position) {601_THREAD_SAFE_METHOD_602603if (mouse_mode == MOUSE_MODE_CAPTURED) {604last_mouse_pos = p_position;605} else {606WindowID window_id = _get_focused_window_or_popup();607if (!windows.has(window_id)) {608window_id = MAIN_WINDOW_ID;609}610611XWarpPointer(x11_display, None, windows[window_id].x11_window,6120, 0, 0, 0, (int)p_position.x, (int)p_position.y);613}614}615616Point2i DisplayServerX11::mouse_get_position() const {617int number_of_screens = XScreenCount(x11_display);618for (int i = 0; i < number_of_screens; i++) {619Window root, child;620int root_x, root_y, win_x, win_y;621unsigned int mask;622if (XQueryPointer(x11_display, XRootWindow(x11_display, i), &root, &child, &root_x, &root_y, &win_x, &win_y, &mask)) {623XWindowAttributes root_attrs;624XGetWindowAttributes(x11_display, root, &root_attrs);625626return Vector2i(root_attrs.x + root_x, root_attrs.y + root_y);627}628}629return Vector2i();630}631632BitField<MouseButtonMask> DisplayServerX11::mouse_get_button_state() const {633int number_of_screens = XScreenCount(x11_display);634for (int i = 0; i < number_of_screens; i++) {635Window root, child;636int root_x, root_y, win_x, win_y;637unsigned int mask;638if (XQueryPointer(x11_display, XRootWindow(x11_display, i), &root, &child, &root_x, &root_y, &win_x, &win_y, &mask)) {639BitField<MouseButtonMask> last_button_state = MouseButtonMask::NONE;640641if (mask & Button1Mask) {642last_button_state.set_flag(MouseButtonMask::LEFT);643}644if (mask & Button2Mask) {645last_button_state.set_flag(MouseButtonMask::MIDDLE);646}647if (mask & Button3Mask) {648last_button_state.set_flag(MouseButtonMask::RIGHT);649}650if (mask & Button4Mask) {651last_button_state.set_flag(MouseButtonMask::MB_XBUTTON1);652}653if (mask & Button5Mask) {654last_button_state.set_flag(MouseButtonMask::MB_XBUTTON2);655}656657return last_button_state;658}659}660return MouseButtonMask::NONE;661}662663void DisplayServerX11::clipboard_set(const String &p_text) {664_THREAD_SAFE_METHOD_665666{667// The clipboard content can be accessed while polling for events.668MutexLock mutex_lock(events_mutex);669internal_clipboard = p_text;670}671672XSetSelectionOwner(x11_display, XA_PRIMARY, windows[MAIN_WINDOW_ID].x11_window, CurrentTime);673XSetSelectionOwner(x11_display, XInternAtom(x11_display, "CLIPBOARD", 0), windows[MAIN_WINDOW_ID].x11_window, CurrentTime);674}675676void DisplayServerX11::clipboard_set_primary(const String &p_text) {677_THREAD_SAFE_METHOD_678if (!p_text.is_empty()) {679{680// The clipboard content can be accessed while polling for events.681MutexLock mutex_lock(events_mutex);682internal_clipboard_primary = p_text;683}684685XSetSelectionOwner(x11_display, XA_PRIMARY, windows[MAIN_WINDOW_ID].x11_window, CurrentTime);686XSetSelectionOwner(x11_display, XInternAtom(x11_display, "PRIMARY", 0), windows[MAIN_WINDOW_ID].x11_window, CurrentTime);687}688}689690Bool DisplayServerX11::_predicate_clipboard_selection(Display *display, XEvent *event, XPointer arg) {691if (event->type == SelectionNotify && event->xselection.requestor == *(Window *)arg) {692return True;693} else {694return False;695}696}697698Bool DisplayServerX11::_predicate_clipboard_incr(Display *display, XEvent *event, XPointer arg) {699if (event->type == PropertyNotify && event->xproperty.state == PropertyNewValue && event->xproperty.atom == *(Atom *)arg) {700return True;701} else {702return False;703}704}705706String DisplayServerX11::_clipboard_get_impl(Atom p_source, Window x11_window, Atom target) const {707String ret;708709Window selection_owner = XGetSelectionOwner(x11_display, p_source);710if (selection_owner == x11_window) {711static const char *target_type = "PRIMARY";712if (p_source != None && get_atom_name(x11_display, p_source) == target_type) {713return internal_clipboard_primary;714} else {715return internal_clipboard;716}717}718719if (selection_owner != None) {720// Block events polling while processing selection events.721MutexLock mutex_lock(events_mutex);722723Atom selection = XA_PRIMARY;724XConvertSelection(x11_display, p_source, target, selection,725x11_window, CurrentTime);726727XFlush(x11_display);728729// Blocking wait for predicate to be True and remove the event from the queue.730XEvent event;731XIfEvent(x11_display, &event, _predicate_clipboard_selection, (XPointer)&x11_window);732733// Do not get any data, see how much data is there.734Atom type;735int format, result;736unsigned long len, bytes_left, dummy;737unsigned char *data;738XGetWindowProperty(x11_display, x11_window,739selection, // Tricky..7400, 0, // offset - len7410, // Delete 0==FALSE742AnyPropertyType, // flag743&type, // return type744&format, // return format745&len, &bytes_left, // data length746&data);747748if (data) {749XFree(data);750}751752if (type == XInternAtom(x11_display, "INCR", 0)) {753// Data is going to be received incrementally.754DEBUG_LOG_X11("INCR selection started.\n");755756LocalVector<uint8_t> incr_data;757uint32_t data_size = 0;758bool success = false;759760// Delete INCR property to notify the owner.761XDeleteProperty(x11_display, x11_window, type);762763// Process events from the queue.764bool done = false;765while (!done) {766if (!_wait_for_events()) {767// Error or timeout, abort.768break;769}770771// Non-blocking wait for next event and remove it from the queue.772XEvent ev;773while (XCheckIfEvent(x11_display, &ev, _predicate_clipboard_incr, (XPointer)&selection)) {774result = XGetWindowProperty(x11_display, x11_window,775selection, // selection type7760, LONG_MAX, // offset - len777True, // delete property to notify the owner778AnyPropertyType, // flag779&type, // return type780&format, // return format781&len, &bytes_left, // data length782&data);783784DEBUG_LOG_X11("PropertyNotify: len=%lu, format=%i\n", len, format);785786if (result == Success) {787if (data && (len > 0)) {788uint32_t prev_size = incr_data.size();789if (prev_size == 0) {790// First property contains initial data size.791unsigned long initial_size = *(unsigned long *)data;792incr_data.resize(initial_size);793} else {794// New chunk, resize to be safe and append data.795incr_data.resize(MAX(data_size + len, prev_size));796memcpy(incr_data.ptr() + data_size, data, len);797data_size += len;798}799} else {800// Last chunk, process finished.801done = true;802success = true;803}804} else {805print_verbose("Failed to get selection data chunk.");806done = true;807}808809if (data) {810XFree(data);811}812813if (done) {814break;815}816}817}818819if (success && (data_size > 0)) {820ret.append_utf8((const char *)incr_data.ptr(), data_size);821}822} else if (bytes_left > 0) {823// Data is ready and can be processed all at once.824result = XGetWindowProperty(x11_display, x11_window,825selection, 0, bytes_left, 0,826AnyPropertyType, &type, &format,827&len, &dummy, &data);828829if (result == Success) {830ret.append_utf8((const char *)data);831} else {832print_verbose("Failed to get selection data.");833}834835if (data) {836XFree(data);837}838}839}840841return ret;842}843844Atom DisplayServerX11::_clipboard_get_image_target(Atom p_source, Window x11_window) const {845Atom target = XInternAtom(x11_display, "TARGETS", 0);846Atom png = XInternAtom(x11_display, "image/png", 0);847Atom *valid_targets = nullptr;848unsigned long atom_count = 0;849850Window selection_owner = XGetSelectionOwner(x11_display, p_source);851if (selection_owner != None && selection_owner != x11_window) {852// Block events polling while processing selection events.853MutexLock mutex_lock(events_mutex);854855Atom selection = XA_PRIMARY;856XConvertSelection(x11_display, p_source, target, selection, x11_window, CurrentTime);857858XFlush(x11_display);859860// Blocking wait for predicate to be True and remove the event from the queue.861XEvent event;862XIfEvent(x11_display, &event, _predicate_clipboard_selection, (XPointer)&x11_window);863// Do not get any data, see how much data is there.864Atom type;865int format, result;866unsigned long len, bytes_left, dummy;867XGetWindowProperty(x11_display, x11_window,868selection, // Tricky..8690, 0, // offset - len8700, // Delete 0==FALSE871XA_ATOM, // flag872&type, // return type873&format, // return format874&len, &bytes_left, // data length875(unsigned char **)&valid_targets);876877if (valid_targets) {878XFree(valid_targets);879valid_targets = nullptr;880}881882if (type == XA_ATOM && bytes_left > 0) {883// Data is ready and can be processed all at once.884result = XGetWindowProperty(x11_display, x11_window,885selection, 0, bytes_left / 4, 0,886XA_ATOM, &type, &format,887&len, &dummy, (unsigned char **)&valid_targets);888if (result == Success) {889atom_count = len;890} else {891print_verbose("Failed to get selection data.");892return None;893}894} else {895return None;896}897} else {898return None;899}900for (unsigned long i = 0; i < atom_count; i++) {901Atom atom = valid_targets[i];902if (atom == png) {903XFree(valid_targets);904return png;905}906}907908XFree(valid_targets);909return None;910}911912String DisplayServerX11::_clipboard_get(Atom p_source, Window x11_window) const {913String ret;914Atom utf8_atom = XInternAtom(x11_display, "UTF8_STRING", True);915if (utf8_atom != None) {916ret = _clipboard_get_impl(p_source, x11_window, utf8_atom);917}918if (ret.is_empty()) {919ret = _clipboard_get_impl(p_source, x11_window, XA_STRING);920}921return ret;922}923924String DisplayServerX11::clipboard_get() const {925_THREAD_SAFE_METHOD_926927String ret;928ret = _clipboard_get(XInternAtom(x11_display, "CLIPBOARD", 0), windows[MAIN_WINDOW_ID].x11_window);929930if (ret.is_empty()) {931ret = _clipboard_get(XA_PRIMARY, windows[MAIN_WINDOW_ID].x11_window);932}933934return ret;935}936937String DisplayServerX11::clipboard_get_primary() const {938_THREAD_SAFE_METHOD_939940String ret;941ret = _clipboard_get(XInternAtom(x11_display, "PRIMARY", 0), windows[MAIN_WINDOW_ID].x11_window);942943if (ret.is_empty()) {944ret = _clipboard_get(XA_PRIMARY, windows[MAIN_WINDOW_ID].x11_window);945}946947return ret;948}949950Ref<Image> DisplayServerX11::clipboard_get_image() const {951_THREAD_SAFE_METHOD_952Atom clipboard = XInternAtom(x11_display, "CLIPBOARD", 0);953Window x11_window = windows[MAIN_WINDOW_ID].x11_window;954Ref<Image> ret;955Atom target = _clipboard_get_image_target(clipboard, x11_window);956if (target == None) {957return ret;958}959960Window selection_owner = XGetSelectionOwner(x11_display, clipboard);961962if (selection_owner != None && selection_owner != x11_window) {963// Block events polling while processing selection events.964MutexLock mutex_lock(events_mutex);965966// Identifier for the property the other window967// will send the converted data to.968Atom transfer_prop = XA_PRIMARY;969XConvertSelection(x11_display,970clipboard, // source selection971target, // format to convert to972transfer_prop, // output property973x11_window, CurrentTime);974975XFlush(x11_display);976977// Blocking wait for predicate to be True and remove the event from the queue.978XEvent event;979XIfEvent(x11_display, &event, _predicate_clipboard_selection, (XPointer)&x11_window);980981// Do not get any data, see how much data is there.982Atom type;983int format, result;984unsigned long len, bytes_left, dummy;985unsigned char *data;986XGetWindowProperty(x11_display, x11_window,987transfer_prop, // Property data is transferred through9880, 1, // offset, len (4 so we can get the size if INCR is used)9890, // Delete 0==FALSE990AnyPropertyType, // flag991&type, // return type992&format, // return format993&len, &bytes_left, // data length994&data);995996if (type == XInternAtom(x11_display, "INCR", 0)) {997ERR_FAIL_COND_V_MSG(len != 1, ret, "Incremental transfer initial value was not length.");998999// Data is going to be received incrementally.1000DEBUG_LOG_X11("INCR selection started.\n");10011002LocalVector<uint8_t> incr_data;1003uint32_t data_size = 0;1004bool success = false;10051006// Initial response is the lower bound of the length of the transferred data.1007incr_data.resize(*(unsigned long *)data);1008XFree(data);1009data = nullptr;10101011// Delete INCR property to notify the owner.1012XDeleteProperty(x11_display, x11_window, transfer_prop);10131014// Process events from the queue.1015bool done = false;1016while (!done) {1017if (!_wait_for_events()) {1018// Error or timeout, abort.1019break;1020}1021// Non-blocking wait for next event and remove it from the queue.1022XEvent ev;1023while (XCheckIfEvent(x11_display, &ev, _predicate_clipboard_incr, (XPointer)&transfer_prop)) {1024result = XGetWindowProperty(x11_display, x11_window,1025transfer_prop, // output property10260, LONG_MAX, // offset - len1027True, // delete property to notify the owner1028AnyPropertyType, // flag1029&type, // return type1030&format, // return format1031&len, &bytes_left, // data length1032&data);10331034DEBUG_LOG_X11("PropertyNotify: len=%lu, format=%i\n", len, format);10351036if (result == Success) {1037if (data && (len > 0)) {1038uint32_t prev_size = incr_data.size();1039// New chunk, resize to be safe and append data.1040incr_data.resize(MAX(data_size + len, prev_size));1041memcpy(incr_data.ptr() + data_size, data, len);1042data_size += len;1043} else if (!(format == 0 && len == 0)) {1044// For unclear reasons the first GetWindowProperty always returns a length and format of 0.1045// Otherwise, last chunk, process finished.1046done = true;1047success = true;1048}1049} else {1050print_verbose("Failed to get selection data chunk.");1051done = true;1052}10531054if (data) {1055XFree(data);1056data = nullptr;1057}10581059if (done) {1060break;1061}1062}1063}10641065if (success && (data_size > 0)) {1066ret.instantiate();1067PNGDriverCommon::png_to_image(incr_data.ptr(), incr_data.size(), false, ret);1068}1069} else if (bytes_left > 0) {1070if (data) {1071XFree(data);1072data = nullptr;1073}1074// Data is ready and can be processed all at once.1075result = XGetWindowProperty(x11_display, x11_window,1076transfer_prop, 0, bytes_left + 4, 0,1077AnyPropertyType, &type, &format,1078&len, &dummy, &data);1079if (result == Success) {1080ret.instantiate();1081PNGDriverCommon::png_to_image((uint8_t *)data, bytes_left, false, ret);1082} else {1083print_verbose("Failed to get selection data.");1084}10851086if (data) {1087XFree(data);1088}1089}1090}10911092return ret;1093}10941095bool DisplayServerX11::clipboard_has_image() const {1096Atom target = _clipboard_get_image_target(1097XInternAtom(x11_display, "CLIPBOARD", 0),1098windows[MAIN_WINDOW_ID].x11_window);1099return target != None;1100}11011102Bool DisplayServerX11::_predicate_clipboard_save_targets(Display *display, XEvent *event, XPointer arg) {1103if (event->xany.window == *(Window *)arg) {1104return (event->type == SelectionRequest) ||1105(event->type == SelectionNotify);1106} else {1107return False;1108}1109}11101111void DisplayServerX11::_clipboard_transfer_ownership(Atom p_source, Window x11_window) const {1112_THREAD_SAFE_METHOD_11131114Window selection_owner = XGetSelectionOwner(x11_display, p_source);11151116if (selection_owner != x11_window) {1117return;1118}11191120// Block events polling while processing selection events.1121MutexLock mutex_lock(events_mutex);11221123Atom clipboard_manager = XInternAtom(x11_display, "CLIPBOARD_MANAGER", False);1124Atom save_targets = XInternAtom(x11_display, "SAVE_TARGETS", False);1125XConvertSelection(x11_display, clipboard_manager, save_targets, None,1126x11_window, CurrentTime);11271128// Process events from the queue.1129while (true) {1130if (!_wait_for_events()) {1131// Error or timeout, abort.1132break;1133}11341135// Non-blocking wait for next event and remove it from the queue.1136XEvent ev;1137while (XCheckIfEvent(x11_display, &ev, _predicate_clipboard_save_targets, (XPointer)&x11_window)) {1138switch (ev.type) {1139case SelectionRequest:1140_handle_selection_request_event(&(ev.xselectionrequest));1141break;11421143case SelectionNotify: {1144if (ev.xselection.target == save_targets) {1145// Once SelectionNotify is received, we're done whether it succeeded or not.1146return;1147}11481149break;1150}1151}1152}1153}1154}11551156int DisplayServerX11::get_screen_count() const {1157_THREAD_SAFE_METHOD_1158int count = 0;11591160// Using Xinerama Extension1161int event_base, error_base;1162if (xinerama_ext_ok && XineramaQueryExtension(x11_display, &event_base, &error_base)) {1163XineramaScreenInfo *xsi = XineramaQueryScreens(x11_display, &count);1164XFree(xsi);1165}1166if (count == 0) {1167count = XScreenCount(x11_display);1168}11691170return count;1171}11721173int DisplayServerX11::get_primary_screen() const {1174int event_base, error_base;1175if (xinerama_ext_ok && XineramaQueryExtension(x11_display, &event_base, &error_base)) {1176return 0;1177} else {1178return XDefaultScreen(x11_display);1179}1180}11811182int DisplayServerX11::get_keyboard_focus_screen() const {1183int count = get_screen_count();1184if (count < 2) {1185// Early exit with single monitor.1186return 0;1187}11881189Window focus = 0;1190int revert_to = 0;11911192XGetInputFocus(x11_display, &focus, &revert_to);1193if (focus) {1194Window focus_child = 0;1195int x = 0, y = 0;1196XTranslateCoordinates(x11_display, focus, DefaultRootWindow(x11_display), 0, 0, &x, &y, &focus_child);11971198XWindowAttributes xwa;1199XGetWindowAttributes(x11_display, focus, &xwa);1200Rect2i window_rect = Rect2i(x, y, xwa.width, xwa.height);12011202// Find which monitor has the largest overlap with the given window.1203int screen_index = 0;1204int max_area = 0;1205for (int i = 0; i < count; i++) {1206Rect2i screen_rect = _screen_get_rect(i);1207Rect2i intersection = screen_rect.intersection(window_rect);1208int area = intersection.get_area();1209if (area > max_area) {1210max_area = area;1211screen_index = i;1212}1213}1214return screen_index;1215}12161217return get_primary_screen();1218}12191220Rect2i DisplayServerX11::_screen_get_rect(int p_screen) const {1221Rect2i rect(0, 0, 0, 0);12221223p_screen = _get_screen_index(p_screen);1224int screen_count = get_screen_count();1225ERR_FAIL_INDEX_V(p_screen, screen_count, Rect2i());12261227// Using Xinerama Extension.1228bool found = false;1229int event_base, error_base;1230if (xinerama_ext_ok && XineramaQueryExtension(x11_display, &event_base, &error_base)) {1231int count;1232XineramaScreenInfo *xsi = XineramaQueryScreens(x11_display, &count);1233if (xsi) {1234if (count > 0) {1235// Check if screen is valid.1236if (p_screen < count) {1237rect.position.x = xsi[p_screen].x_org;1238rect.position.y = xsi[p_screen].y_org;1239rect.size.width = xsi[p_screen].width;1240rect.size.height = xsi[p_screen].height;1241found = true;1242} else {1243ERR_PRINT(vformat("Invalid screen index: %d (count: %d).", p_screen, count));1244}1245}1246XFree(xsi);1247}1248}12491250if (!found) {1251int count = XScreenCount(x11_display);1252if (p_screen < count) {1253Window root = XRootWindow(x11_display, p_screen);1254XWindowAttributes xwa;1255XGetWindowAttributes(x11_display, root, &xwa);1256rect.position.x = xwa.x;1257rect.position.y = xwa.y;1258rect.size.width = xwa.width;1259rect.size.height = xwa.height;1260} else {1261ERR_PRINT(vformat("Invalid screen index: %d (count: %d).", p_screen, count));1262}1263}12641265return rect;1266}12671268Point2i DisplayServerX11::screen_get_position(int p_screen) const {1269_THREAD_SAFE_METHOD_12701271return _screen_get_rect(p_screen).position;1272}12731274Size2i DisplayServerX11::screen_get_size(int p_screen) const {1275_THREAD_SAFE_METHOD_12761277return _screen_get_rect(p_screen).size;1278}12791280// A Handler to avoid crashing on non-fatal X errors by default.1281//1282// The original X11 error formatter `_XPrintDefaultError` is defined here:1283// https://gitlab.freedesktop.org/xorg/lib/libx11/-/blob/e45ca7b41dcd3ace7681d6897505f85d374640f2/src/XlibInt.c#L13221284// It is not exposed through the API, accesses X11 internals,1285// and is much more complex, so this is a less complete simplified error X11 printer.1286int default_window_error_handler(Display *display, XErrorEvent *error) {1287static char message[1024];1288XGetErrorText(display, error->error_code, message, sizeof(message));12891290ERR_PRINT(vformat("Unhandled XServer error: %s"1291"\n Major opcode of failed request: %d"1292"\n Serial number of failed request: %d"1293"\n Current serial number in output stream: %d",1294String::utf8(message), (uint64_t)error->request_code, (uint64_t)error->minor_code, (uint64_t)error->serial));1295return 0;1296}12971298bool g_bad_window = false;1299int bad_window_error_handler(Display *display, XErrorEvent *error) {1300if (error->error_code == BadWindow) {1301g_bad_window = true;1302} else {1303return default_window_error_handler(display, error);1304}1305return 0;1306}13071308Rect2i DisplayServerX11::screen_get_usable_rect(int p_screen) const {1309_THREAD_SAFE_METHOD_13101311p_screen = _get_screen_index(p_screen);1312int screen_count = get_screen_count();1313ERR_FAIL_INDEX_V(p_screen, screen_count, Rect2i());13141315bool is_multiscreen = screen_count > 1;13161317// Use full monitor size as fallback.1318Rect2i rect = _screen_get_rect(p_screen);13191320// There's generally only one screen reported by xlib even in multi-screen setup,1321// in this case it's just one virtual screen composed of all physical monitors.1322int x11_screen_count = ScreenCount(x11_display);1323Window x11_window = RootWindow(x11_display, p_screen < x11_screen_count ? p_screen : 0);13241325Atom type;1326int format = 0;1327unsigned long remaining = 0;13281329// Find active desktop for the root window.1330unsigned int desktop_index = 0;1331Atom desktop_prop = XInternAtom(x11_display, "_NET_CURRENT_DESKTOP", True);1332if (desktop_prop != None) {1333unsigned long desktop_len = 0;1334unsigned char *desktop_data = nullptr;1335if (XGetWindowProperty(x11_display, x11_window, desktop_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &desktop_len, &remaining, &desktop_data) == Success) {1336if ((format == 32) && (desktop_len > 0) && desktop_data) {1337desktop_index = (unsigned int)desktop_data[0];1338}1339if (desktop_data) {1340XFree(desktop_data);1341}1342}1343}13441345bool use_simple_method = true;13461347// First check for GTK work area, which is more accurate for multi-screen setup.1348if (is_multiscreen) {1349// Use already calculated work area when available.1350Atom gtk_workareas_prop = XInternAtom(x11_display, "_GTK_WORKAREAS", False);1351if (gtk_workareas_prop != None) {1352char gtk_workarea_prop_name[32];1353snprintf(gtk_workarea_prop_name, 32, "_GTK_WORKAREAS_D%d", desktop_index);1354Atom gtk_workarea_prop = XInternAtom(x11_display, gtk_workarea_prop_name, True);1355if (gtk_workarea_prop != None) {1356unsigned long workarea_len = 0;1357unsigned char *workarea_data = nullptr;1358if (XGetWindowProperty(x11_display, x11_window, gtk_workarea_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &workarea_len, &remaining, &workarea_data) == Success) {1359if ((format == 32) && (workarea_len % 4 == 0) && workarea_data) {1360long *rect_data = (long *)workarea_data;1361for (uint32_t data_offset = 0; data_offset < workarea_len; data_offset += 4) {1362Rect2i workarea_rect;1363workarea_rect.position.x = rect_data[data_offset];1364workarea_rect.position.y = rect_data[data_offset + 1];1365workarea_rect.size.x = rect_data[data_offset + 2];1366workarea_rect.size.y = rect_data[data_offset + 3];13671368// Intersect with actual monitor size to find the correct area,1369// because areas are not in the same order as screens from Xinerama.1370if (rect.grow(-1).intersects(workarea_rect)) {1371rect = rect.intersection(workarea_rect);1372XFree(workarea_data);1373return rect;1374}1375}1376}1377}1378if (workarea_data) {1379XFree(workarea_data);1380}1381}1382}13831384// Fallback to calculating work area by hand from struts.1385Atom client_list_prop = XInternAtom(x11_display, "_NET_CLIENT_LIST", True);1386if (client_list_prop != None) {1387unsigned long clients_len = 0;1388unsigned char *clients_data = nullptr;1389if (XGetWindowProperty(x11_display, x11_window, client_list_prop, 0, LONG_MAX, False, XA_WINDOW, &type, &format, &clients_len, &remaining, &clients_data) == Success) {1390if ((format == 32) && (clients_len > 0) && clients_data) {1391Window *windows_data = (Window *)clients_data;13921393Rect2i desktop_rect;1394bool desktop_valid = false;13951396// Get full desktop size.1397{1398Atom desktop_geometry_prop = XInternAtom(x11_display, "_NET_DESKTOP_GEOMETRY", True);1399if (desktop_geometry_prop != None) {1400unsigned long geom_len = 0;1401unsigned char *geom_data = nullptr;1402if (XGetWindowProperty(x11_display, x11_window, desktop_geometry_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &geom_len, &remaining, &geom_data) == Success) {1403if ((format == 32) && (geom_len >= 2) && geom_data) {1404desktop_valid = true;1405long *size_data = (long *)geom_data;1406desktop_rect.size.x = size_data[0];1407desktop_rect.size.y = size_data[1];1408}1409}1410if (geom_data) {1411XFree(geom_data);1412}1413}1414}14151416// Get full desktop position.1417if (desktop_valid) {1418Atom desktop_viewport_prop = XInternAtom(x11_display, "_NET_DESKTOP_VIEWPORT", True);1419if (desktop_viewport_prop != None) {1420unsigned long viewport_len = 0;1421unsigned char *viewport_data = nullptr;1422if (XGetWindowProperty(x11_display, x11_window, desktop_viewport_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &viewport_len, &remaining, &viewport_data) == Success) {1423if ((format == 32) && (viewport_len >= 2) && viewport_data) {1424desktop_valid = true;1425long *pos_data = (long *)viewport_data;1426desktop_rect.position.x = pos_data[0];1427desktop_rect.position.y = pos_data[1];1428}1429}1430if (viewport_data) {1431XFree(viewport_data);1432}1433}1434}14351436if (desktop_valid) {1437// Handle bad window errors silently because there's no other way to check1438// that one of the windows has been destroyed in the meantime.1439int (*oldHandler)(Display *, XErrorEvent *) = XSetErrorHandler(&bad_window_error_handler);14401441for (unsigned long win_index = 0; win_index < clients_len; ++win_index) {1442g_bad_window = false;14431444// Remove strut size from desktop size to get a more accurate result.1445bool strut_found = false;1446unsigned long strut_len = 0;1447unsigned char *strut_data = nullptr;1448Atom strut_partial_prop = XInternAtom(x11_display, "_NET_WM_STRUT_PARTIAL", True);1449if (strut_partial_prop != None) {1450if (XGetWindowProperty(x11_display, windows_data[win_index], strut_partial_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &strut_len, &remaining, &strut_data) == Success) {1451strut_found = true;1452}1453}1454// Fallback to older strut property.1455if (!g_bad_window && !strut_found) {1456Atom strut_prop = XInternAtom(x11_display, "_NET_WM_STRUT", True);1457if (strut_prop != None) {1458if (XGetWindowProperty(x11_display, windows_data[win_index], strut_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &strut_len, &remaining, &strut_data) == Success) {1459strut_found = true;1460}1461}1462}1463if (!g_bad_window && strut_found && (format == 32) && (strut_len >= 4) && strut_data) {1464use_simple_method = false;14651466long *struts = (long *)strut_data;14671468long left = struts[0];1469long right = struts[1];1470long top = struts[2];1471long bottom = struts[3];14721473long left_start_y, left_end_y, right_start_y, right_end_y;1474long top_start_x, top_end_x, bottom_start_x, bottom_end_x;14751476if (strut_len >= 12) {1477left_start_y = struts[4];1478left_end_y = struts[5];1479right_start_y = struts[6];1480right_end_y = struts[7];1481top_start_x = struts[8];1482top_end_x = struts[9];1483bottom_start_x = struts[10];1484bottom_end_x = struts[11];1485} else {1486left_start_y = 0;1487left_end_y = desktop_rect.size.y;1488right_start_y = 0;1489right_end_y = desktop_rect.size.y;1490top_start_x = 0;1491top_end_x = desktop_rect.size.x;1492bottom_start_x = 0;1493bottom_end_x = desktop_rect.size.x;1494}14951496const Point2i &pos = desktop_rect.position;1497const Size2i &size = desktop_rect.size;14981499Rect2i left_rect(pos.x, pos.y + left_start_y, left, left_end_y - left_start_y);1500if (left_rect.size.x > 0) {1501Rect2i intersection = rect.intersection(left_rect);1502if (intersection.has_area() && intersection.size.x < rect.size.x) {1503rect.position.x = left_rect.size.x;1504rect.size.x = rect.size.x - intersection.size.x;1505}1506}15071508Rect2i right_rect(pos.x + size.x - right, pos.y + right_start_y, right, right_end_y - right_start_y);1509if (right_rect.size.x > 0) {1510Rect2i intersection = rect.intersection(right_rect);1511if (intersection.has_area() && right_rect.size.x < rect.size.x) {1512rect.size.x = intersection.position.x - rect.position.x;1513}1514}15151516Rect2i top_rect(pos.x + top_start_x, pos.y, top_end_x - top_start_x, top);1517if (top_rect.size.y > 0) {1518Rect2i intersection = rect.intersection(top_rect);1519if (intersection.has_area() && intersection.size.y < rect.size.y) {1520rect.position.y = top_rect.size.y;1521rect.size.y = rect.size.y - intersection.size.y;1522}1523}15241525Rect2i bottom_rect(pos.x + bottom_start_x, pos.y + size.y - bottom, bottom_end_x - bottom_start_x, bottom);1526if (bottom_rect.size.y > 0) {1527Rect2i intersection = rect.intersection(bottom_rect);1528if (intersection.has_area() && right_rect.size.y < rect.size.y) {1529rect.size.y = intersection.position.y - rect.position.y;1530}1531}1532}1533if (strut_data) {1534XFree(strut_data);1535}1536}15371538// Restore default error handler.1539XSetErrorHandler(oldHandler);1540}1541}1542}1543if (clients_data) {1544XFree(clients_data);1545}1546}1547}15481549// Single screen or fallback for multi screen.1550if (use_simple_method) {1551// Get desktop available size from the global work area.1552Atom workarea_prop = XInternAtom(x11_display, "_NET_WORKAREA", True);1553if (workarea_prop != None) {1554unsigned long workarea_len = 0;1555unsigned char *workarea_data = nullptr;1556if (XGetWindowProperty(x11_display, x11_window, workarea_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &workarea_len, &remaining, &workarea_data) == Success) {1557if ((format == 32) && (workarea_len >= ((desktop_index + 1) * 4)) && workarea_data) {1558long *rect_data = (long *)workarea_data;1559int data_offset = desktop_index * 4;1560Rect2i workarea_rect;1561workarea_rect.position.x = rect_data[data_offset];1562workarea_rect.position.y = rect_data[data_offset + 1];1563workarea_rect.size.x = rect_data[data_offset + 2];1564workarea_rect.size.y = rect_data[data_offset + 3];15651566// Intersect with actual monitor size to get a proper approximation in multi-screen setup.1567if (!is_multiscreen) {1568rect = workarea_rect;1569} else if (rect.intersects(workarea_rect)) {1570rect = rect.intersection(workarea_rect);1571}1572}1573}1574if (workarea_data) {1575XFree(workarea_data);1576}1577}1578}15791580return rect;1581}15821583Rect2i DisplayServerX11::_screens_get_full_rect() const {1584Rect2i full_rect;15851586int count = get_screen_count();1587for (int i = 0; i < count; i++) {1588if (i == 0) {1589full_rect = _screen_get_rect(i);1590continue;1591}15921593Rect2i screen_rect = _screen_get_rect(i);1594if (full_rect.position.x > screen_rect.position.x) {1595full_rect.size.x += full_rect.position.x - screen_rect.position.x;1596full_rect.position.x = screen_rect.position.x;1597}1598if (full_rect.position.y > screen_rect.position.y) {1599full_rect.size.y += full_rect.position.y - screen_rect.position.y;1600full_rect.position.y = screen_rect.position.y;1601}1602if (full_rect.position.x + full_rect.size.x < screen_rect.position.x + screen_rect.size.x) {1603full_rect.size.x = screen_rect.position.x + screen_rect.size.x - full_rect.position.x;1604}1605if (full_rect.position.y + full_rect.size.y < screen_rect.position.y + screen_rect.size.y) {1606full_rect.size.y = screen_rect.position.y + screen_rect.size.y - full_rect.position.y;1607}1608}16091610return full_rect;1611}16121613int DisplayServerX11::screen_get_dpi(int p_screen) const {1614_THREAD_SAFE_METHOD_16151616p_screen = _get_screen_index(p_screen);1617int screen_count = get_screen_count();1618ERR_FAIL_INDEX_V(p_screen, screen_count, 96);16191620//Get physical monitor Dimensions through XRandR and calculate dpi1621Size2i sc = screen_get_size(p_screen);1622if (xrandr_ext_ok) {1623int count = 0;1624if (xrr_get_monitors) {1625xrr_monitor_info *monitors = xrr_get_monitors(x11_display, windows[MAIN_WINDOW_ID].x11_window, true, &count);1626if (p_screen < count) {1627double xdpi = sc.width / (double)monitors[p_screen].mwidth * 25.4;1628double ydpi = sc.height / (double)monitors[p_screen].mheight * 25.4;1629xrr_free_monitors(monitors);1630return (xdpi + ydpi) / 2;1631}1632xrr_free_monitors(monitors);1633} else if (p_screen == 0) {1634XRRScreenSize *sizes = XRRSizes(x11_display, 0, &count);1635if (sizes) {1636double xdpi = sc.width / (double)sizes[0].mwidth * 25.4;1637double ydpi = sc.height / (double)sizes[0].mheight * 25.4;1638return (xdpi + ydpi) / 2;1639}1640}1641}16421643int width_mm = DisplayWidthMM(x11_display, p_screen);1644int height_mm = DisplayHeightMM(x11_display, p_screen);1645double xdpi = (width_mm ? sc.width / (double)width_mm * 25.4 : 0);1646double ydpi = (height_mm ? sc.height / (double)height_mm * 25.4 : 0);1647if (xdpi || ydpi) {1648return (xdpi + ydpi) / (xdpi && ydpi ? 2 : 1);1649}16501651//could not get dpi1652return 96;1653}16541655int get_image_errorhandler(Display *dpy, XErrorEvent *ev) {1656return 0;1657}16581659Color DisplayServerX11::screen_get_pixel(const Point2i &p_position) const {1660Point2i pos = p_position;16611662if (xwayland) {1663return Color();1664}16651666int (*old_handler)(Display *, XErrorEvent *) = XSetErrorHandler(&get_image_errorhandler);16671668Color color;1669int number_of_screens = XScreenCount(x11_display);1670for (int i = 0; i < number_of_screens; i++) {1671Window root = XRootWindow(x11_display, i);1672XWindowAttributes root_attrs;1673XGetWindowAttributes(x11_display, root, &root_attrs);1674if ((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)) {1675XImage *image = XGetImage(x11_display, root, pos.x, pos.y, 1, 1, AllPlanes, XYPixmap);1676if (image) {1677XColor c;1678c.pixel = XGetPixel(image, 0, 0);1679XDestroyImage(image);1680XQueryColor(x11_display, XDefaultColormap(x11_display, i), &c);1681color = Color(float(c.red) / 65535.0, float(c.green) / 65535.0, float(c.blue) / 65535.0, 1.0);1682break;1683}1684}1685}16861687XSetErrorHandler(old_handler);16881689return color;1690}16911692Ref<Image> DisplayServerX11::screen_get_image(int p_screen) const {1693ERR_FAIL_INDEX_V(p_screen, get_screen_count(), Ref<Image>());16941695p_screen = _get_screen_index(p_screen);1696int screen_count = get_screen_count();1697ERR_FAIL_INDEX_V(p_screen, screen_count, Ref<Image>());16981699if (xwayland) {1700return Ref<Image>();1701}17021703int (*old_handler)(Display *, XErrorEvent *) = XSetErrorHandler(&get_image_errorhandler);17041705XImage *image = nullptr;17061707bool found = false;1708int event_base, error_base;1709if (xinerama_ext_ok && XineramaQueryExtension(x11_display, &event_base, &error_base)) {1710int xin_count;1711XineramaScreenInfo *xsi = XineramaQueryScreens(x11_display, &xin_count);1712if (xsi) {1713if (xin_count > 0) {1714if (p_screen < xin_count) {1715int x_count = XScreenCount(x11_display);1716for (int i = 0; i < x_count; i++) {1717Window root = XRootWindow(x11_display, i);1718XWindowAttributes root_attrs;1719XGetWindowAttributes(x11_display, root, &root_attrs);1720if ((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)) {1721found = true;1722image = XGetImage(x11_display, root, xsi[p_screen].x_org, xsi[p_screen].y_org, xsi[p_screen].width, xsi[p_screen].height, AllPlanes, ZPixmap);1723break;1724}1725}1726} else {1727ERR_PRINT(vformat("Invalid screen index: %d (count: %d).", p_screen, xin_count));1728}1729}1730XFree(xsi);1731}1732}1733if (!found) {1734int x_count = XScreenCount(x11_display);1735if (p_screen < x_count) {1736Window root = XRootWindow(x11_display, p_screen);17371738XWindowAttributes root_attrs;1739XGetWindowAttributes(x11_display, root, &root_attrs);17401741image = XGetImage(x11_display, root, root_attrs.x, root_attrs.y, root_attrs.width, root_attrs.height, AllPlanes, ZPixmap);1742} else {1743ERR_PRINT(vformat("Invalid screen index: %d (count: %d).", p_screen, x_count));1744}1745}17461747XSetErrorHandler(old_handler);17481749Ref<Image> img;1750if (image) {1751int width = image->width;1752int height = image->height;17531754Vector<uint8_t> img_data;1755img_data.resize(height * width * 4);17561757uint8_t *sr = (uint8_t *)image->data;1758uint8_t *wr = (uint8_t *)img_data.ptrw();17591760if (image->bits_per_pixel == 24 && image->red_mask == 0xff0000 && image->green_mask == 0x00ff00 && image->blue_mask == 0x0000ff) {1761for (int y = 0; y < height; y++) {1762for (int x = 0; x < width; x++) {1763wr[(y * width + x) * 4 + 0] = sr[(y * width + x) * 3 + 2];1764wr[(y * width + x) * 4 + 1] = sr[(y * width + x) * 3 + 1];1765wr[(y * width + x) * 4 + 2] = sr[(y * width + x) * 3 + 0];1766wr[(y * width + x) * 4 + 3] = 255;1767}1768}1769} else if (image->bits_per_pixel == 24 && image->red_mask == 0x0000ff && image->green_mask == 0x00ff00 && image->blue_mask == 0xff0000) {1770for (int y = 0; y < height; y++) {1771for (int x = 0; x < width; x++) {1772wr[(y * width + x) * 4 + 0] = sr[(y * width + x) * 3 + 2];1773wr[(y * width + x) * 4 + 1] = sr[(y * width + x) * 3 + 1];1774wr[(y * width + x) * 4 + 2] = sr[(y * width + x) * 3 + 0];1775wr[(y * width + x) * 4 + 3] = 255;1776}1777}1778} else if (image->bits_per_pixel == 32 && image->red_mask == 0xff0000 && image->green_mask == 0x00ff00 && image->blue_mask == 0x0000ff) {1779for (int y = 0; y < height; y++) {1780for (int x = 0; x < width; x++) {1781wr[(y * width + x) * 4 + 0] = sr[(y * width + x) * 4 + 2];1782wr[(y * width + x) * 4 + 1] = sr[(y * width + x) * 4 + 1];1783wr[(y * width + x) * 4 + 2] = sr[(y * width + x) * 4 + 0];1784wr[(y * width + x) * 4 + 3] = 255;1785}1786}1787} else {1788String 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);1789XDestroyImage(image);1790ERR_FAIL_V_MSG(Ref<Image>(), msg);1791}1792img = Image::create_from_data(width, height, false, Image::FORMAT_RGBA8, img_data);1793XDestroyImage(image);1794}17951796return img;1797}17981799float DisplayServerX11::screen_get_refresh_rate(int p_screen) const {1800_THREAD_SAFE_METHOD_18011802p_screen = _get_screen_index(p_screen);1803int screen_count = get_screen_count();1804ERR_FAIL_INDEX_V(p_screen, screen_count, SCREEN_REFRESH_RATE_FALLBACK);18051806//Use xrandr to get screen refresh rate.1807if (xrandr_ext_ok) {1808XRRScreenResources *screen_info = XRRGetScreenResourcesCurrent(x11_display, windows[MAIN_WINDOW_ID].x11_window);1809if (screen_info) {1810RRMode current_mode = 0;1811xrr_monitor_info *monitors = nullptr;18121813if (xrr_get_monitors) {1814int count = 0;1815monitors = xrr_get_monitors(x11_display, windows[MAIN_WINDOW_ID].x11_window, true, &count);1816ERR_FAIL_INDEX_V(p_screen, count, SCREEN_REFRESH_RATE_FALLBACK);1817} else {1818ERR_PRINT("An error occurred while trying to get the screen refresh rate.");1819return SCREEN_REFRESH_RATE_FALLBACK;1820}18211822bool found_active_mode = false;1823for (int crtc = 0; crtc < screen_info->ncrtc; crtc++) { // Loop through outputs to find which one is currently outputting.1824XRRCrtcInfo *monitor_info = XRRGetCrtcInfo(x11_display, screen_info, screen_info->crtcs[crtc]);1825if (monitor_info->x != monitors[p_screen].x || monitor_info->y != monitors[p_screen].y) { // If X and Y aren't the same as the monitor we're looking for, this isn't the right monitor. Continue.1826continue;1827}18281829if (monitor_info->mode != None) {1830current_mode = monitor_info->mode;1831found_active_mode = true;1832break;1833}1834}18351836if (found_active_mode) {1837for (int mode = 0; mode < screen_info->nmode; mode++) {1838XRRModeInfo m_info = screen_info->modes[mode];1839if (m_info.id == current_mode) {1840// Snap to nearest 0.01 to stay consistent with other platforms.1841return Math::snapped((float)m_info.dotClock / ((float)m_info.hTotal * (float)m_info.vTotal), 0.01);1842}1843}1844}18451846ERR_PRINT("An error occurred while trying to get the screen refresh rate."); // We should have returned the refresh rate by now. An error must have occurred.1847return SCREEN_REFRESH_RATE_FALLBACK;1848} else {1849ERR_PRINT("An error occurred while trying to get the screen refresh rate.");1850return SCREEN_REFRESH_RATE_FALLBACK;1851}1852}1853ERR_PRINT("An error occurred while trying to get the screen refresh rate.");1854return SCREEN_REFRESH_RATE_FALLBACK;1855}18561857#ifdef DBUS_ENABLED1858void DisplayServerX11::screen_set_keep_on(bool p_enable) {1859if (screen_is_kept_on() == p_enable) {1860return;1861}18621863if (screensaver) {1864if (p_enable) {1865screensaver->inhibit();1866} else {1867screensaver->uninhibit();1868}18691870keep_screen_on = p_enable;1871}1872}18731874bool DisplayServerX11::screen_is_kept_on() const {1875return keep_screen_on;1876}1877#endif18781879Vector<DisplayServer::WindowID> DisplayServerX11::get_window_list() const {1880_THREAD_SAFE_METHOD_18811882Vector<int> ret;1883for (const KeyValue<WindowID, WindowData> &E : windows) {1884ret.push_back(E.key);1885}1886return ret;1887}18881889DisplayServer::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) {1890_THREAD_SAFE_METHOD_18911892WindowID id = _create_window(p_mode, p_vsync_mode, p_flags, p_rect, 0);1893for (int i = 0; i < WINDOW_FLAG_MAX; i++) {1894if (p_flags & (1 << i)) {1895window_set_flag(WindowFlags(i), true, id);1896}1897}1898#ifdef RD_ENABLED1899if (rendering_device) {1900rendering_device->screen_create(id);1901}1902#endif19031904if (p_transient_parent != INVALID_WINDOW_ID) {1905window_set_transient(id, p_transient_parent);1906}19071908return id;1909}19101911void DisplayServerX11::show_window(WindowID p_id) {1912_THREAD_SAFE_METHOD_19131914WindowData &wd = windows[p_id];1915popup_open(p_id);19161917DEBUG_LOG_X11("show_window: %lu (%u) \n", wd.x11_window, p_id);19181919XMapWindow(x11_display, wd.x11_window);1920XSync(x11_display, False);1921_validate_mode_on_map(p_id);19221923if (p_id == MAIN_WINDOW_ID) {1924// Get main window size for boot splash drawing.1925bool get_config_event = false;19261927// Search the X11 event queue for ConfigureNotify events and process all that are currently queued early.1928{1929MutexLock mutex_lock(events_mutex);19301931for (uint32_t event_index = 0; event_index < polled_events.size(); ++event_index) {1932XEvent &config_event = polled_events[event_index];1933if (config_event.type == ConfigureNotify) {1934_window_changed(&config_event);1935get_config_event = true;1936}1937}1938XEvent config_event;1939while (XCheckTypedEvent(x11_display, ConfigureNotify, &config_event)) {1940_window_changed(&config_event);1941get_config_event = true;1942}1943}19441945// Estimate maximize/full screen window size, ConfigureNotify may arrive only after maximize animation is finished.1946if (!get_config_event && (wd.maximized || wd.fullscreen)) {1947int screen = window_get_current_screen(p_id);1948Size2i sz;1949if (wd.maximized) {1950sz = screen_get_usable_rect(screen).size;1951if (!wd.borderless) {1952Atom prop = XInternAtom(x11_display, "_NET_FRAME_EXTENTS", True);1953if (prop != None) {1954Atom type;1955int format;1956unsigned long len;1957unsigned long remaining;1958unsigned char *data = nullptr;1959if (XGetWindowProperty(x11_display, wd.x11_window, prop, 0, 4, False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) {1960if (format == 32 && len == 4 && data) {1961long *extents = (long *)data;1962sz.width -= extents[0] + extents[1]; // left, right1963sz.height -= extents[2] + extents[3]; // top, bottom1964}1965XFree(data);1966}1967}1968}1969} else {1970sz = screen_get_size(screen);1971}1972if (sz == Size2i()) {1973return;1974}19751976wd.size = sz;1977#if defined(RD_ENABLED)1978if (rendering_context) {1979rendering_context->window_set_size(p_id, sz.width, sz.height);1980}1981#endif1982#if defined(GLES3_ENABLED)1983if (gl_manager) {1984gl_manager->window_resize(p_id, sz.width, sz.height);1985}1986if (gl_manager_egl) {1987gl_manager_egl->window_resize(p_id, sz.width, sz.height);1988}1989#endif1990}1991}1992}19931994void DisplayServerX11::delete_sub_window(WindowID p_id) {1995_THREAD_SAFE_METHOD_19961997ERR_FAIL_COND(!windows.has(p_id));1998ERR_FAIL_COND_MSG(p_id == MAIN_WINDOW_ID, "Main window can't be deleted");19992000popup_close(p_id);20012002WindowData &wd = windows[p_id];20032004DEBUG_LOG_X11("delete_sub_window: %lu (%u) \n", wd.x11_window, p_id);20052006if (window_mouseover_id == p_id) {2007window_mouseover_id = INVALID_WINDOW_ID;2008_send_window_event(windows[p_id], WINDOW_EVENT_MOUSE_EXIT);2009}20102011while (wd.transient_children.size()) {2012window_set_transient(*wd.transient_children.begin(), INVALID_WINDOW_ID);2013}20142015if (wd.transient_parent != INVALID_WINDOW_ID) {2016window_set_transient(p_id, INVALID_WINDOW_ID);2017}20182019#if defined(RD_ENABLED)2020if (rendering_device) {2021rendering_device->screen_free(p_id);2022}20232024if (rendering_context) {2025rendering_context->window_destroy(p_id);2026}2027#endif2028#ifdef GLES3_ENABLED2029if (gl_manager) {2030gl_manager->window_destroy(p_id);2031}2032if (gl_manager_egl) {2033gl_manager_egl->window_destroy(p_id);2034}2035#endif20362037#ifdef ACCESSKIT_ENABLED2038if (accessibility_driver) {2039accessibility_driver->window_destroy(p_id);2040}2041#endif20422043if (wd.xic) {2044XDestroyIC(wd.xic);2045wd.xic = nullptr;2046}2047XDestroyWindow(x11_display, wd.x11_xim_window);2048#ifdef XKB_ENABLED2049if (xkb_loaded_v05p) {2050if (wd.xkb_state) {2051xkb_compose_state_unref(wd.xkb_state);2052wd.xkb_state = nullptr;2053}2054}2055#endif20562057XUnmapWindow(x11_display, wd.x11_window);2058XDestroyWindow(x11_display, wd.x11_window);20592060window_set_rect_changed_callback(Callable(), p_id);2061window_set_window_event_callback(Callable(), p_id);2062window_set_input_event_callback(Callable(), p_id);2063window_set_input_text_callback(Callable(), p_id);2064window_set_drop_files_callback(Callable(), p_id);20652066windows.erase(p_id);20672068if (last_focused_window == p_id) {2069last_focused_window = INVALID_WINDOW_ID;2070}2071}20722073int64_t DisplayServerX11::window_get_native_handle(HandleType p_handle_type, WindowID p_window) const {2074ERR_FAIL_COND_V(!windows.has(p_window), 0);2075switch (p_handle_type) {2076case DISPLAY_HANDLE: {2077return (int64_t)x11_display;2078}2079case WINDOW_HANDLE: {2080return (int64_t)windows[p_window].x11_window;2081}2082case WINDOW_VIEW: {2083return 0; // Not supported.2084}2085#ifdef GLES3_ENABLED2086case OPENGL_CONTEXT: {2087if (gl_manager) {2088return (int64_t)gl_manager->get_glx_context(p_window);2089}2090if (gl_manager_egl) {2091return (int64_t)gl_manager_egl->get_context(p_window);2092}2093return 0;2094}2095case EGL_DISPLAY: {2096if (gl_manager_egl) {2097return (int64_t)gl_manager_egl->get_display(p_window);2098}2099return 0;2100}2101case EGL_CONFIG: {2102if (gl_manager_egl) {2103return (int64_t)gl_manager_egl->get_config(p_window);2104}2105return 0;2106}2107#endif2108default: {2109return 0;2110}2111}2112}21132114void DisplayServerX11::window_attach_instance_id(ObjectID p_instance, WindowID p_window) {2115ERR_FAIL_COND(!windows.has(p_window));2116WindowData &wd = windows[p_window];21172118wd.instance_id = p_instance;2119}21202121ObjectID DisplayServerX11::window_get_attached_instance_id(WindowID p_window) const {2122ERR_FAIL_COND_V(!windows.has(p_window), ObjectID());2123const WindowData &wd = windows[p_window];2124return wd.instance_id;2125}21262127DisplayServerX11::WindowID DisplayServerX11::get_window_at_screen_position(const Point2i &p_position) const {2128WindowID found_window = INVALID_WINDOW_ID;2129WindowID parent_window = INVALID_WINDOW_ID;2130unsigned int focus_order = 0;2131for (const KeyValue<WindowID, WindowData> &E : windows) {2132const WindowData &wd = E.value;21332134// Discard windows with no focus.2135if (wd.focus_order == 0) {2136continue;2137}21382139// Find topmost window which contains the given position.2140WindowID window_id = E.key;2141Rect2i win_rect = Rect2i(window_get_position(window_id), window_get_size(window_id));2142if (win_rect.has_point(p_position)) {2143// For siblings, pick the window which was focused last.2144if ((parent_window != wd.transient_parent) || (wd.focus_order > focus_order)) {2145found_window = window_id;2146parent_window = wd.transient_parent;2147focus_order = wd.focus_order;2148}2149}2150}21512152return found_window;2153}21542155void DisplayServerX11::window_set_title(const String &p_title, WindowID p_window) {2156_THREAD_SAFE_METHOD_21572158ERR_FAIL_COND(!windows.has(p_window));2159WindowData &wd = windows[p_window];21602161XStoreName(x11_display, wd.x11_window, p_title.utf8().get_data());21622163Atom _net_wm_name = XInternAtom(x11_display, "_NET_WM_NAME", false);2164Atom utf8_string = XInternAtom(x11_display, "UTF8_STRING", false);2165if (_net_wm_name != None && utf8_string != None) {2166CharString utf8_title = p_title.utf8();2167XChangeProperty(x11_display, wd.x11_window, _net_wm_name, utf8_string, 8, PropModeReplace, (unsigned char *)utf8_title.get_data(), utf8_title.length());2168}2169}21702171void DisplayServerX11::window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window) {2172_THREAD_SAFE_METHOD_21732174ERR_FAIL_COND(!windows.has(p_window));2175windows[p_window].mpath = p_region;2176_update_window_mouse_passthrough(p_window);2177}21782179void DisplayServerX11::_update_window_mouse_passthrough(WindowID p_window) {2180ERR_FAIL_COND(!windows.has(p_window));2181ERR_FAIL_COND(!xshaped_ext_ok);21822183const Vector<Vector2> region_path = windows[p_window].mpath;21842185int event_base, error_base;2186const Bool ext_okay = XShapeQueryExtension(x11_display, &event_base, &error_base);2187if (ext_okay) {2188if (windows[p_window].mpass) {2189Region region = XCreateRegion();2190XShapeCombineRegion(x11_display, windows[p_window].x11_window, ShapeInput, 0, 0, region, ShapeSet);2191XDestroyRegion(region);2192} else if (region_path.is_empty()) {2193XShapeCombineMask(x11_display, windows[p_window].x11_window, ShapeInput, 0, 0, None, ShapeSet);2194} else {2195XPoint *points = (XPoint *)memalloc(sizeof(XPoint) * region_path.size());2196for (int i = 0; i < region_path.size(); i++) {2197points[i].x = region_path[i].x;2198points[i].y = region_path[i].y;2199}2200Region region = XPolygonRegion(points, region_path.size(), EvenOddRule);2201memfree(points);2202XShapeCombineRegion(x11_display, windows[p_window].x11_window, ShapeInput, 0, 0, region, ShapeSet);2203XDestroyRegion(region);2204}2205}2206}22072208void DisplayServerX11::window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window) {2209_THREAD_SAFE_METHOD_22102211ERR_FAIL_COND(!windows.has(p_window));2212WindowData &wd = windows[p_window];2213wd.rect_changed_callback = p_callable;2214}22152216void DisplayServerX11::window_set_window_event_callback(const Callable &p_callable, WindowID p_window) {2217_THREAD_SAFE_METHOD_22182219ERR_FAIL_COND(!windows.has(p_window));2220WindowData &wd = windows[p_window];2221wd.event_callback = p_callable;2222}22232224void DisplayServerX11::window_set_input_event_callback(const Callable &p_callable, WindowID p_window) {2225_THREAD_SAFE_METHOD_22262227ERR_FAIL_COND(!windows.has(p_window));2228WindowData &wd = windows[p_window];2229wd.input_event_callback = p_callable;2230}22312232void DisplayServerX11::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) {2233_THREAD_SAFE_METHOD_22342235ERR_FAIL_COND(!windows.has(p_window));2236WindowData &wd = windows[p_window];2237wd.input_text_callback = p_callable;2238}22392240void DisplayServerX11::window_set_drop_files_callback(const Callable &p_callable, WindowID p_window) {2241_THREAD_SAFE_METHOD_22422243ERR_FAIL_COND(!windows.has(p_window));2244WindowData &wd = windows[p_window];2245wd.drop_files_callback = p_callable;2246}22472248int DisplayServerX11::window_get_current_screen(WindowID p_window) const {2249_THREAD_SAFE_METHOD_22502251int count = get_screen_count();2252if (count < 2) {2253// Early exit with single monitor.2254return 0;2255}22562257ERR_FAIL_COND_V(!windows.has(p_window), INVALID_SCREEN);2258const WindowData &wd = windows[p_window];22592260const Rect2i window_rect(wd.position, wd.size);22612262// Find which monitor has the largest overlap with the given window.2263int screen_index = 0;2264int max_area = 0;2265for (int i = 0; i < count; i++) {2266Rect2i screen_rect = _screen_get_rect(i);2267Rect2i intersection = screen_rect.intersection(window_rect);2268int area = intersection.get_area();2269if (area > max_area) {2270max_area = area;2271screen_index = i;2272}2273}22742275return screen_index;2276}22772278void DisplayServerX11::gl_window_make_current(DisplayServer::WindowID p_window_id) {2279#if defined(GLES3_ENABLED)2280if (gl_manager) {2281gl_manager->window_make_current(p_window_id);2282}2283if (gl_manager_egl) {2284gl_manager_egl->window_make_current(p_window_id);2285}2286#endif2287}22882289void DisplayServerX11::window_set_current_screen(int p_screen, WindowID p_window) {2290_THREAD_SAFE_METHOD_22912292ERR_FAIL_COND(!windows.has(p_window));22932294p_screen = _get_screen_index(p_screen);2295int screen_count = get_screen_count();2296ERR_FAIL_INDEX(p_screen, screen_count);22972298if (window_get_current_screen(p_window) == p_screen) {2299return;2300}2301WindowData &wd = windows[p_window];23022303if (wd.embed_parent) {2304print_line("Embedded window can't be moved to another screen.");2305return;2306}23072308if (window_get_mode(p_window) == WINDOW_MODE_FULLSCREEN || window_get_mode(p_window) == WINDOW_MODE_MAXIMIZED) {2309Point2i position = screen_get_position(p_screen);2310Size2i size = screen_get_size(p_screen);23112312XMoveResizeWindow(x11_display, wd.x11_window, position.x, position.y, size.x, size.y);2313} else {2314Rect2i srect = screen_get_usable_rect(p_screen);2315Point2i wpos = window_get_position(p_window) - screen_get_position(window_get_current_screen(p_window));2316Size2i wsize = window_get_size(p_window);2317wpos += srect.position;2318if (srect != Rect2i()) {2319wpos = wpos.clamp(srect.position, srect.position + srect.size - wsize / 3);2320}2321window_set_position(wpos, p_window);2322}2323}23242325void DisplayServerX11::window_set_transient(WindowID p_window, WindowID p_parent) {2326_THREAD_SAFE_METHOD_23272328ERR_FAIL_COND(p_window == p_parent);23292330ERR_FAIL_COND(!windows.has(p_window));2331WindowData &wd_window = windows[p_window];23322333WindowID prev_parent = wd_window.transient_parent;2334ERR_FAIL_COND(prev_parent == p_parent);23352336DEBUG_LOG_X11("window_set_transient: %lu (%u), prev_parent=%u, parent=%u\n", wd_window.x11_window, p_window, prev_parent, p_parent);23372338ERR_FAIL_COND_MSG(wd_window.on_top, "Windows with the 'on top' can't become transient.");2339if (p_parent == INVALID_WINDOW_ID) {2340//remove transient23412342ERR_FAIL_COND(prev_parent == INVALID_WINDOW_ID);2343ERR_FAIL_COND(!windows.has(prev_parent));23442345WindowData &wd_parent = windows[prev_parent];23462347wd_window.transient_parent = INVALID_WINDOW_ID;2348wd_parent.transient_children.erase(p_window);23492350XSetTransientForHint(x11_display, wd_window.x11_window, None);23512352XWindowAttributes xwa;2353XSync(x11_display, False);2354XGetWindowAttributes(x11_display, wd_parent.x11_window, &xwa);23552356// Set focus to parent sub window to avoid losing all focus when closing a nested sub-menu.2357// RevertToPointerRoot is used to make sure we don't lose all focus in case2358// a subwindow and its parent are both destroyed.2359if (!wd_window.no_focus && !wd_window.is_popup && wd_window.focused) {2360if ((xwa.map_state == IsViewable) && !wd_parent.no_focus && !wd_window.is_popup && _window_focus_check()) {2361_set_input_focus(wd_parent.x11_window, RevertToPointerRoot);2362}2363}2364} else {2365ERR_FAIL_COND(!windows.has(p_parent));2366ERR_FAIL_COND_MSG(prev_parent != INVALID_WINDOW_ID, "Window already has a transient parent");2367WindowData &wd_parent = windows[p_parent];23682369wd_window.transient_parent = p_parent;2370wd_parent.transient_children.insert(p_window);23712372XSetTransientForHint(x11_display, wd_window.x11_window, wd_parent.x11_window);2373}2374}23752376// Helper method. Assumes that the window id has already been checked and exists.2377void DisplayServerX11::_update_size_hints(WindowID p_window) {2378WindowData &wd = windows[p_window];2379WindowMode window_mode = window_get_mode(p_window);2380XSizeHints *xsh = XAllocSizeHints();23812382// Always set the position and size hints - they should be synchronized with the actual values after the window is mapped anyway2383xsh->flags |= PPosition | PSize;2384xsh->x = wd.position.x;2385xsh->y = wd.position.y;2386xsh->width = wd.size.width;2387xsh->height = wd.size.height;23882389if (window_mode == WINDOW_MODE_FULLSCREEN || window_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) {2390// Do not set any other hints to prevent the window manager from ignoring the fullscreen flags2391} else if (window_get_flag(WINDOW_FLAG_RESIZE_DISABLED, p_window)) {2392// If resizing is disabled, use the forced size2393xsh->flags |= PMinSize | PMaxSize;2394xsh->min_width = wd.size.x;2395xsh->max_width = wd.size.x;2396xsh->min_height = wd.size.y;2397xsh->max_height = wd.size.y;2398} else {2399// Otherwise, just respect min_size and max_size2400if (wd.min_size != Size2i()) {2401xsh->flags |= PMinSize;2402xsh->min_width = wd.min_size.x;2403xsh->min_height = wd.min_size.y;2404}2405if (wd.max_size != Size2i()) {2406xsh->flags |= PMaxSize;2407xsh->max_width = wd.max_size.x;2408xsh->max_height = wd.max_size.y;2409}2410}24112412XSetWMNormalHints(x11_display, wd.x11_window, xsh);2413XFree(xsh);2414}24152416void DisplayServerX11::_update_actions_hints(WindowID p_window) {2417WindowData &wd = windows[p_window];24182419Atom prop = XInternAtom(x11_display, "_NET_WM_ALLOWED_ACTIONS", False);2420if (prop != None) {2421Atom wm_act_max_horz = XInternAtom(x11_display, "_NET_WM_ACTION_MAXIMIZE_HORZ", False);2422Atom wm_act_max_vert = XInternAtom(x11_display, "_NET_WM_ACTION_MAXIMIZE_VERT", False);2423Atom wm_act_min = XInternAtom(x11_display, "_NET_WM_ACTION_MINIMIZE", False);2424Atom type;2425int format;2426unsigned long len;2427unsigned long remaining;2428unsigned char *data = nullptr;2429if (XGetWindowProperty(x11_display, wd.x11_window, prop, 0, 1024, False, XA_ATOM, &type, &format, &len, &remaining, &data) == Success) {2430Atom *atoms = (Atom *)data;2431Vector<Atom> new_atoms;2432for (uint64_t i = 0; i < len; i++) {2433if (atoms[i] != wm_act_max_horz && atoms[i] != wm_act_max_vert && atoms[i] != wm_act_min) {2434new_atoms.push_back(atoms[i]);2435}2436}2437if (!wd.no_max_btn) {2438new_atoms.push_back(wm_act_max_horz);2439new_atoms.push_back(wm_act_max_vert);2440}2441if (!wd.no_min_btn) {2442new_atoms.push_back(wm_act_min);2443}2444XChangeProperty(x11_display, wd.x11_window, prop, XA_ATOM, 32, PropModeReplace, (unsigned char *)new_atoms.ptrw(), new_atoms.size());2445XFree(data);2446}2447}2448}24492450Point2i DisplayServerX11::window_get_position(WindowID p_window) const {2451_THREAD_SAFE_METHOD_24522453ERR_FAIL_COND_V(!windows.has(p_window), Point2i());2454const WindowData &wd = windows[p_window];24552456return wd.position;2457}24582459Point2i DisplayServerX11::window_get_position_with_decorations(WindowID p_window) const {2460_THREAD_SAFE_METHOD_24612462ERR_FAIL_COND_V(!windows.has(p_window), Size2i());2463const WindowData &wd = windows[p_window];24642465if (wd.fullscreen) {2466return wd.position;2467}24682469XWindowAttributes xwa;2470XSync(x11_display, False);2471XGetWindowAttributes(x11_display, wd.x11_window, &xwa);2472int x = wd.position.x;2473int y = wd.position.y;2474Atom prop = XInternAtom(x11_display, "_NET_FRAME_EXTENTS", True);2475if (prop != None) {2476Atom type;2477int format;2478unsigned long len;2479unsigned long remaining;2480unsigned char *data = nullptr;2481if (XGetWindowProperty(x11_display, wd.x11_window, prop, 0, 4, False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) {2482if (format == 32 && len == 4 && data) {2483long *extents = (long *)data;2484x -= extents[0]; // left2485y -= extents[2]; // top2486}2487XFree(data);2488}2489}2490return Size2i(x, y);2491}24922493void DisplayServerX11::window_set_position(const Point2i &p_position, WindowID p_window) {2494_THREAD_SAFE_METHOD_24952496ERR_FAIL_COND(!windows.has(p_window));2497WindowData &wd = windows[p_window];24982499if (wd.embed_parent) {2500print_line("Embedded window can't be moved.");2501return;2502}25032504wd.position = p_position;2505int x = 0;2506int y = 0;2507if (!window_get_flag(WINDOW_FLAG_BORDERLESS, p_window)) {2508//exclude window decorations2509XSync(x11_display, False);2510Atom prop = XInternAtom(x11_display, "_NET_FRAME_EXTENTS", True);2511if (prop != None) {2512Atom type;2513int format;2514unsigned long len;2515unsigned long remaining;2516unsigned char *data = nullptr;2517if (XGetWindowProperty(x11_display, wd.x11_window, prop, 0, 4, False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) {2518if (format == 32 && len == 4 && data) {2519long *extents = (long *)data;2520x = extents[0];2521y = extents[2];2522}2523XFree(data);2524}2525}2526}2527XMoveWindow(x11_display, wd.x11_window, p_position.x - x, p_position.y - y);2528_update_real_mouse_position(wd);2529}25302531void DisplayServerX11::window_set_max_size(const Size2i p_size, WindowID p_window) {2532_THREAD_SAFE_METHOD_25332534ERR_FAIL_COND(!windows.has(p_window));2535WindowData &wd = windows[p_window];25362537if (wd.embed_parent) {2538print_line("Embedded windows can't have a maximum size.");2539return;2540}25412542if ((p_size != Size2i()) && ((p_size.x < wd.min_size.x) || (p_size.y < wd.min_size.y))) {2543ERR_PRINT("Maximum window size can't be smaller than minimum window size!");2544return;2545}2546wd.max_size = p_size;25472548_update_size_hints(p_window);2549XFlush(x11_display);2550}25512552Size2i DisplayServerX11::window_get_max_size(WindowID p_window) const {2553_THREAD_SAFE_METHOD_25542555ERR_FAIL_COND_V(!windows.has(p_window), Size2i());2556const WindowData &wd = windows[p_window];25572558return wd.max_size;2559}25602561void DisplayServerX11::window_set_min_size(const Size2i p_size, WindowID p_window) {2562_THREAD_SAFE_METHOD_25632564ERR_FAIL_COND(!windows.has(p_window));2565WindowData &wd = windows[p_window];25662567if (wd.embed_parent) {2568print_line("Embedded windows can't have a minimum size.");2569return;2570}25712572if ((p_size != Size2i()) && (wd.max_size != Size2i()) && ((p_size.x > wd.max_size.x) || (p_size.y > wd.max_size.y))) {2573ERR_PRINT("Minimum window size can't be larger than maximum window size!");2574return;2575}2576wd.min_size = p_size;25772578_update_size_hints(p_window);2579XFlush(x11_display);2580}25812582Size2i DisplayServerX11::window_get_min_size(WindowID p_window) const {2583_THREAD_SAFE_METHOD_25842585ERR_FAIL_COND_V(!windows.has(p_window), Size2i());2586const WindowData &wd = windows[p_window];25872588return wd.min_size;2589}25902591void DisplayServerX11::window_set_size(const Size2i p_size, WindowID p_window) {2592_THREAD_SAFE_METHOD_25932594ERR_FAIL_COND(!windows.has(p_window));25952596Size2i size = p_size;2597size = size.maxi(1);25982599WindowData &wd = windows[p_window];26002601if (wd.embed_parent) {2602print_line("Embedded window can't be resized.");2603return;2604}26052606if (wd.size.width == size.width && wd.size.height == size.height) {2607return;2608}26092610XWindowAttributes xwa;2611XSync(x11_display, False);2612XGetWindowAttributes(x11_display, wd.x11_window, &xwa);2613int old_w = xwa.width;2614int old_h = xwa.height;26152616// Update our videomode width and height2617wd.size = size;26182619// Update the size hints first to make sure the window size can be set2620_update_size_hints(p_window);26212622// Resize the window2623XResizeWindow(x11_display, wd.x11_window, size.x, size.y);26242625for (int timeout = 0; timeout < 50; ++timeout) {2626XSync(x11_display, False);2627XGetWindowAttributes(x11_display, wd.x11_window, &xwa);26282629if (old_w != xwa.width || old_h != xwa.height) {2630break;2631}26322633OS::get_singleton()->delay_usec(10'000);2634}26352636// Keep rendering context window size in sync2637#if defined(RD_ENABLED)2638if (rendering_context) {2639rendering_context->window_set_size(p_window, xwa.width, xwa.height);2640}2641#endif2642#if defined(GLES3_ENABLED)2643if (gl_manager) {2644gl_manager->window_resize(p_window, xwa.width, xwa.height);2645}2646if (gl_manager_egl) {2647gl_manager_egl->window_resize(p_window, xwa.width, xwa.height);2648}2649#endif2650}26512652Size2i DisplayServerX11::window_get_size(WindowID p_window) const {2653_THREAD_SAFE_METHOD_26542655ERR_FAIL_COND_V(!windows.has(p_window), Size2i());2656const WindowData &wd = windows[p_window];2657return wd.size;2658}26592660Size2i DisplayServerX11::window_get_size_with_decorations(WindowID p_window) const {2661_THREAD_SAFE_METHOD_26622663ERR_FAIL_COND_V(!windows.has(p_window), Size2i());2664const WindowData &wd = windows[p_window];26652666if (wd.fullscreen) {2667return wd.size;2668}26692670XWindowAttributes xwa;2671XSync(x11_display, False);2672XGetWindowAttributes(x11_display, wd.x11_window, &xwa);2673int w = xwa.width;2674int h = xwa.height;2675Atom prop = XInternAtom(x11_display, "_NET_FRAME_EXTENTS", True);2676if (prop != None) {2677Atom type;2678int format;2679unsigned long len;2680unsigned long remaining;2681unsigned char *data = nullptr;2682if (XGetWindowProperty(x11_display, wd.x11_window, prop, 0, 4, False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) {2683if (format == 32 && len == 4 && data) {2684long *extents = (long *)data;2685w += extents[0] + extents[1]; // left, right2686h += extents[2] + extents[3]; // top, bottom2687}2688XFree(data);2689}2690}2691return Size2i(w, h);2692}26932694// Just a helper to reduce code duplication in `window_is_maximize_allowed`2695// and `_set_wm_maximized`.2696bool DisplayServerX11::_window_maximize_check(WindowID p_window, const char *p_atom_name) const {2697ERR_FAIL_COND_V(!windows.has(p_window), false);2698const WindowData &wd = windows[p_window];26992700Atom property = XInternAtom(x11_display, p_atom_name, False);2701Atom type;2702int format;2703unsigned long len;2704unsigned long remaining;2705unsigned char *data = nullptr;2706bool retval = false;27072708if (property == None) {2709return false;2710}27112712int result = XGetWindowProperty(2713x11_display,2714wd.x11_window,2715property,27160,27171024,2718False,2719XA_ATOM,2720&type,2721&format,2722&len,2723&remaining,2724&data);27252726if (result == Success && data) {2727Atom *atoms = (Atom *)data;2728Atom wm_act_max_horz;2729Atom wm_act_max_vert;2730bool checking_state = strcmp(p_atom_name, "_NET_WM_STATE") == 0;2731if (checking_state) {2732wm_act_max_horz = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_HORZ", False);2733wm_act_max_vert = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_VERT", False);2734} else {2735wm_act_max_horz = XInternAtom(x11_display, "_NET_WM_ACTION_MAXIMIZE_HORZ", False);2736wm_act_max_vert = XInternAtom(x11_display, "_NET_WM_ACTION_MAXIMIZE_VERT", False);2737}2738bool found_wm_act_max_horz = false;2739bool found_wm_act_max_vert = false;27402741for (uint64_t i = 0; i < len; i++) {2742if (atoms[i] == wm_act_max_horz) {2743found_wm_act_max_horz = true;2744}2745if (atoms[i] == wm_act_max_vert) {2746found_wm_act_max_vert = true;2747}27482749if (checking_state) {2750if (found_wm_act_max_horz && found_wm_act_max_vert) {2751retval = true;2752break;2753}2754} else {2755if (found_wm_act_max_horz || found_wm_act_max_vert) {2756retval = true;2757break;2758}2759}2760}27612762XFree(data);2763}27642765return retval;2766}27672768bool DisplayServerX11::_window_minimize_check(WindowID p_window) const {2769const WindowData &wd = windows[p_window];27702771// Using EWMH instead of ICCCM, might work better for Wayland users.2772Atom property = XInternAtom(x11_display, "_NET_WM_STATE", True);2773Atom hidden = XInternAtom(x11_display, "_NET_WM_STATE_HIDDEN", True);2774if (property == None || hidden == None) {2775return false;2776}27772778Atom type;2779int format;2780unsigned long len;2781unsigned long remaining;2782Atom *atoms = nullptr;27832784int result = XGetWindowProperty(2785x11_display,2786wd.x11_window,2787property,27880,278932,2790False,2791XA_ATOM,2792&type,2793&format,2794&len,2795&remaining,2796(unsigned char **)&atoms);27972798if (result == Success && atoms) {2799for (unsigned int i = 0; i < len; i++) {2800if (atoms[i] == hidden) {2801XFree(atoms);2802return true;2803}2804}2805XFree(atoms);2806}28072808return false;2809}28102811bool DisplayServerX11::_window_fullscreen_check(WindowID p_window) const {2812ERR_FAIL_COND_V(!windows.has(p_window), false);2813const WindowData &wd = windows[p_window];28142815// Using EWMH -- Extended Window Manager Hints2816Atom property = XInternAtom(x11_display, "_NET_WM_STATE", False);2817Atom type;2818int format;2819unsigned long len;2820unsigned long remaining;2821unsigned char *data = nullptr;2822bool retval = false;28232824if (property == None) {2825return retval;2826}28272828int result = XGetWindowProperty(2829x11_display,2830wd.x11_window,2831property,28320,28331024,2834False,2835XA_ATOM,2836&type,2837&format,2838&len,2839&remaining,2840&data);28412842if (result == Success) {2843Atom *atoms = (Atom *)data;2844Atom wm_fullscreen = XInternAtom(x11_display, "_NET_WM_STATE_FULLSCREEN", False);2845for (uint64_t i = 0; i < len; i++) {2846if (atoms[i] == wm_fullscreen) {2847retval = true;2848break;2849}2850}2851XFree(data);2852}28532854return retval;2855}28562857void DisplayServerX11::_validate_mode_on_map(WindowID p_window) {2858// Check if we applied any window modes that didn't take effect while unmapped2859const WindowData &wd = windows[p_window];2860if (wd.fullscreen && !_window_fullscreen_check(p_window)) {2861_set_wm_fullscreen(p_window, true, wd.exclusive_fullscreen);2862} else if (wd.maximized && !_window_maximize_check(p_window, "_NET_WM_STATE")) {2863_set_wm_maximized(p_window, true);2864} else if (wd.minimized && !_window_minimize_check(p_window)) {2865_set_wm_minimized(p_window, true);2866}28672868if (wd.on_top) {2869Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);2870Atom wm_above = XInternAtom(x11_display, "_NET_WM_STATE_ABOVE", False);28712872XClientMessageEvent xev;2873memset(&xev, 0, sizeof(xev));2874xev.type = ClientMessage;2875xev.window = wd.x11_window;2876xev.message_type = wm_state;2877xev.format = 32;2878xev.data.l[0] = _NET_WM_STATE_ADD;2879xev.data.l[1] = wm_above;2880xev.data.l[3] = 1;2881XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent *)&xev);2882}2883}28842885bool DisplayServerX11::window_is_maximize_allowed(WindowID p_window) const {2886_THREAD_SAFE_METHOD_2887return _window_maximize_check(p_window, "_NET_WM_ALLOWED_ACTIONS");2888}28892890void DisplayServerX11::_set_wm_maximized(WindowID p_window, bool p_enabled) {2891ERR_FAIL_COND(!windows.has(p_window));2892WindowData &wd = windows[p_window];28932894// Using EWMH -- Extended Window Manager Hints2895XEvent xev;2896Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);2897Atom wm_max_horz = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_HORZ", False);2898Atom wm_max_vert = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_VERT", False);28992900memset(&xev, 0, sizeof(xev));2901xev.type = ClientMessage;2902xev.xclient.window = wd.x11_window;2903xev.xclient.message_type = wm_state;2904xev.xclient.format = 32;2905xev.xclient.data.l[0] = p_enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;2906xev.xclient.data.l[1] = wm_max_horz;2907xev.xclient.data.l[2] = wm_max_vert;29082909XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);29102911if (p_enabled && window_is_maximize_allowed(p_window)) {2912// Wait for effective resizing (so the GLX context is too).2913// Give up after 0.5s, it's not going to happen on this WM.2914// https://github.com/godotengine/godot/issues/199782915for (int attempt = 0; window_get_mode(p_window) != WINDOW_MODE_MAXIMIZED && attempt < 50; attempt++) {2916OS::get_singleton()->delay_usec(10'000);2917}2918}2919wd.maximized = p_enabled;2920}29212922void DisplayServerX11::_set_wm_minimized(WindowID p_window, bool p_enabled) {2923WindowData &wd = windows[p_window];2924// Using ICCCM -- Inter-Client Communication Conventions Manual2925XEvent xev;2926Atom wm_change = XInternAtom(x11_display, "WM_CHANGE_STATE", False);29272928memset(&xev, 0, sizeof(xev));2929xev.type = ClientMessage;2930xev.xclient.window = wd.x11_window;2931xev.xclient.message_type = wm_change;2932xev.xclient.format = 32;2933xev.xclient.data.l[0] = p_enabled ? WM_IconicState : WM_NormalState;29342935XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);29362937Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);2938Atom wm_hidden = XInternAtom(x11_display, "_NET_WM_STATE_HIDDEN", False);29392940memset(&xev, 0, sizeof(xev));2941xev.type = ClientMessage;2942xev.xclient.window = wd.x11_window;2943xev.xclient.message_type = wm_state;2944xev.xclient.format = 32;2945xev.xclient.data.l[0] = p_enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;2946xev.xclient.data.l[1] = wm_hidden;29472948XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);2949wd.minimized = p_enabled;2950}29512952void DisplayServerX11::_set_wm_fullscreen(WindowID p_window, bool p_enabled, bool p_exclusive) {2953ERR_FAIL_COND(!windows.has(p_window));2954WindowData &wd = windows[p_window];29552956if (p_enabled && !window_get_flag(WINDOW_FLAG_BORDERLESS, p_window)) {2957// remove decorations if the window is not already borderless2958Hints hints;2959Atom property;2960hints.flags = 2;2961hints.decorations = 0;2962property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True);2963if (property != None) {2964XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5);2965}2966}29672968if (p_enabled) {2969// Set the window as resizable to prevent window managers to ignore the fullscreen state flag.2970_update_size_hints(p_window);2971}29722973// Using EWMH -- Extended Window Manager Hints2974XEvent xev;2975Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);2976Atom wm_fullscreen = XInternAtom(x11_display, "_NET_WM_STATE_FULLSCREEN", False);29772978memset(&xev, 0, sizeof(xev));2979xev.type = ClientMessage;2980xev.xclient.window = wd.x11_window;2981xev.xclient.message_type = wm_state;2982xev.xclient.format = 32;2983xev.xclient.data.l[0] = p_enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;2984xev.xclient.data.l[1] = wm_fullscreen;2985xev.xclient.data.l[2] = 0;29862987XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);29882989// set bypass compositor hint2990Atom bypass_compositor = XInternAtom(x11_display, "_NET_WM_BYPASS_COMPOSITOR", False);2991unsigned long compositing_disable_on = 0; // Use default.2992if (p_enabled) {2993if (p_exclusive) {2994compositing_disable_on = 1; // Force composition OFF to reduce overhead.2995} else {2996compositing_disable_on = 2; // Force composition ON to allow popup windows.2997}2998}2999if (bypass_compositor != None) {3000XChangeProperty(x11_display, wd.x11_window, bypass_compositor, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&compositing_disable_on, 1);3001}30023003XFlush(x11_display);30043005if (!p_enabled) {3006// Reset the non-resizable flags if we un-set these before.3007_update_size_hints(p_window);30083009// put back or remove decorations according to the last set borderless state3010Hints hints;3011Atom property;3012hints.flags = 2;3013hints.decorations = wd.borderless ? 0 : 1;3014property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True);3015if (property != None) {3016XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5);3017}3018}3019}30203021void DisplayServerX11::window_set_mode(WindowMode p_mode, WindowID p_window) {3022_THREAD_SAFE_METHOD_30233024ERR_FAIL_COND(!windows.has(p_window));3025WindowData &wd = windows[p_window];30263027WindowMode old_mode = window_get_mode(p_window);3028if (old_mode == p_mode) {3029return; // do nothing3030}30313032if (p_mode != WINDOW_MODE_WINDOWED && wd.embed_parent) {3033print_line("Embedded window only supports Windowed mode.");3034return;3035}30363037// Remove all "extra" modes.3038switch (old_mode) {3039case WINDOW_MODE_WINDOWED: {3040//do nothing3041} break;3042case WINDOW_MODE_MINIMIZED: {3043_set_wm_minimized(p_window, false);3044} break;3045case WINDOW_MODE_EXCLUSIVE_FULLSCREEN:3046case WINDOW_MODE_FULLSCREEN: {3047//Remove full-screen3048wd.fullscreen = false;3049wd.exclusive_fullscreen = false;30503051_set_wm_fullscreen(p_window, false, false);30523053//un-maximize required for always on top3054bool on_top = window_get_flag(WINDOW_FLAG_ALWAYS_ON_TOP, p_window);30553056window_set_position(wd.last_position_before_fs, p_window);30573058if (on_top) {3059_set_wm_maximized(p_window, false);3060}30613062} break;3063case WINDOW_MODE_MAXIMIZED: {3064_set_wm_maximized(p_window, false);3065} break;3066}30673068switch (p_mode) {3069case WINDOW_MODE_WINDOWED: {3070//do nothing3071} break;3072case WINDOW_MODE_MINIMIZED: {3073_set_wm_minimized(p_window, true);3074} break;3075case WINDOW_MODE_EXCLUSIVE_FULLSCREEN:3076case WINDOW_MODE_FULLSCREEN: {3077wd.last_position_before_fs = wd.position;30783079if (window_get_flag(WINDOW_FLAG_ALWAYS_ON_TOP, p_window)) {3080_set_wm_maximized(p_window, true);3081}30823083wd.fullscreen = true;3084if (p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) {3085wd.exclusive_fullscreen = true;3086_set_wm_fullscreen(p_window, true, true);3087} else {3088wd.exclusive_fullscreen = false;3089_set_wm_fullscreen(p_window, true, false);3090}3091} break;3092case WINDOW_MODE_MAXIMIZED: {3093_set_wm_maximized(p_window, true);3094} break;3095}3096}30973098DisplayServer::WindowMode DisplayServerX11::window_get_mode(WindowID p_window) const {3099_THREAD_SAFE_METHOD_31003101ERR_FAIL_COND_V(!windows.has(p_window), WINDOW_MODE_WINDOWED);3102const WindowData &wd = windows[p_window];31033104if (wd.fullscreen) { //if fullscreen, it's not in another mode3105if (wd.exclusive_fullscreen) {3106return WINDOW_MODE_EXCLUSIVE_FULLSCREEN;3107} else {3108return WINDOW_MODE_FULLSCREEN;3109}3110}31113112// Test maximized.3113// Using EWMH -- Extended Window Manager Hints3114if (_window_maximize_check(p_window, "_NET_WM_STATE")) {3115return WINDOW_MODE_MAXIMIZED;3116}31173118{3119if (_window_minimize_check(p_window)) {3120return WINDOW_MODE_MINIMIZED;3121}3122}31233124// All other discarded, return windowed.31253126return WINDOW_MODE_WINDOWED;3127}31283129void DisplayServerX11::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) {3130_THREAD_SAFE_METHOD_31313132ERR_FAIL_COND(!windows.has(p_window));3133WindowData &wd = windows[p_window];31343135switch (p_flag) {3136case WINDOW_FLAG_MAXIMIZE_DISABLED: {3137wd.no_max_btn = p_enabled;3138_update_actions_hints(p_window);31393140XFlush(x11_display);3141} break;3142case WINDOW_FLAG_MINIMIZE_DISABLED: {3143wd.no_min_btn = p_enabled;3144_update_actions_hints(p_window);31453146XFlush(x11_display);3147} break;3148case WINDOW_FLAG_RESIZE_DISABLED: {3149if (p_enabled && wd.embed_parent) {3150print_line("Embedded window resize can't be disabled.");3151return;3152}31533154wd.resize_disabled = p_enabled;3155_update_size_hints(p_window);3156_update_actions_hints(p_window);31573158XFlush(x11_display);3159} break;3160case WINDOW_FLAG_BORDERLESS: {3161Hints hints;3162Atom property;3163hints.flags = 2;3164hints.decorations = p_enabled ? 0 : 1;3165property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True);3166if (property != None) {3167XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5);3168}31693170// Preserve window size3171if (!wd.embed_parent) {3172window_set_size(window_get_size(p_window), p_window);3173}31743175wd.borderless = p_enabled;3176_update_window_mouse_passthrough(p_window);3177} break;3178case WINDOW_FLAG_ALWAYS_ON_TOP: {3179ERR_FAIL_COND_MSG(wd.transient_parent != INVALID_WINDOW_ID, "Can't make a window transient if the 'on top' flag is active.");3180if (p_enabled && wd.embed_parent) {3181print_line("Embedded window can't become on top.");3182return;3183}3184if (p_enabled && wd.fullscreen) {3185_set_wm_maximized(p_window, true);3186}31873188Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);3189Atom wm_above = XInternAtom(x11_display, "_NET_WM_STATE_ABOVE", False);31903191XClientMessageEvent xev;3192memset(&xev, 0, sizeof(xev));3193xev.type = ClientMessage;3194xev.window = wd.x11_window;3195xev.message_type = wm_state;3196xev.format = 32;3197xev.data.l[0] = p_enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;3198xev.data.l[1] = wm_above;3199xev.data.l[3] = 1;3200XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent *)&xev);32013202if (!p_enabled && !wd.fullscreen) {3203_set_wm_maximized(p_window, false);3204}3205wd.on_top = p_enabled;32063207} break;3208case WINDOW_FLAG_TRANSPARENT: {3209wd.layered_window = p_enabled;3210} break;3211case WINDOW_FLAG_NO_FOCUS: {3212wd.no_focus = p_enabled;3213} break;3214case WINDOW_FLAG_MOUSE_PASSTHROUGH: {3215wd.mpass = p_enabled;3216_update_window_mouse_passthrough(p_window);3217} break;3218case WINDOW_FLAG_POPUP: {3219XWindowAttributes xwa;3220XSync(x11_display, False);3221XGetWindowAttributes(x11_display, wd.x11_window, &xwa);32223223ERR_FAIL_COND_MSG(p_window == MAIN_WINDOW_ID, "Main window can't be popup.");3224ERR_FAIL_COND_MSG((xwa.map_state == IsViewable) && (wd.is_popup != p_enabled), "Popup flag can't changed while window is opened.");3225if (p_enabled && wd.embed_parent) {3226print_line("Embedded window can't be popup.");3227return;3228}3229wd.is_popup = p_enabled;3230} break;3231default: {3232}3233}3234}32353236bool DisplayServerX11::window_get_flag(WindowFlags p_flag, WindowID p_window) const {3237_THREAD_SAFE_METHOD_32383239ERR_FAIL_COND_V(!windows.has(p_window), false);3240const WindowData &wd = windows[p_window];32413242switch (p_flag) {3243case WINDOW_FLAG_MAXIMIZE_DISABLED: {3244return wd.no_max_btn;3245} break;3246case WINDOW_FLAG_MINIMIZE_DISABLED: {3247return wd.no_min_btn;3248} break;3249case WINDOW_FLAG_RESIZE_DISABLED: {3250return wd.resize_disabled;3251} break;3252case WINDOW_FLAG_BORDERLESS: {3253bool borderless = wd.borderless;3254Atom prop = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True);3255if (prop != None) {3256Atom type;3257int format;3258unsigned long len;3259unsigned long remaining;3260unsigned char *data = nullptr;3261if (XGetWindowProperty(x11_display, wd.x11_window, prop, 0, sizeof(Hints), False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) {3262if (data && (format == 32) && (len >= 5)) {3263borderless = !(reinterpret_cast<Hints *>(data)->decorations);3264}3265if (data) {3266XFree(data);3267}3268}3269}3270return borderless;3271} break;3272case WINDOW_FLAG_ALWAYS_ON_TOP: {3273return wd.on_top;3274} break;3275case WINDOW_FLAG_TRANSPARENT: {3276return wd.layered_window;3277} break;3278case WINDOW_FLAG_NO_FOCUS: {3279return wd.no_focus;3280} break;3281case WINDOW_FLAG_MOUSE_PASSTHROUGH: {3282return wd.mpass;3283} break;3284case WINDOW_FLAG_POPUP: {3285return wd.is_popup;3286} break;3287default: {3288}3289}32903291return false;3292}32933294void DisplayServerX11::window_request_attention(WindowID p_window) {3295_THREAD_SAFE_METHOD_32963297ERR_FAIL_COND(!windows.has(p_window));3298const WindowData &wd = windows[p_window];3299// Using EWMH -- Extended Window Manager Hints3300//3301// Sets the _NET_WM_STATE_DEMANDS_ATTENTION atom for WM_STATE3302// Will be unset by the window manager after user react on the request for attention33033304XEvent xev;3305Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);3306Atom wm_attention = XInternAtom(x11_display, "_NET_WM_STATE_DEMANDS_ATTENTION", False);33073308memset(&xev, 0, sizeof(xev));3309xev.type = ClientMessage;3310xev.xclient.window = wd.x11_window;3311xev.xclient.message_type = wm_state;3312xev.xclient.format = 32;3313xev.xclient.data.l[0] = _NET_WM_STATE_ADD;3314xev.xclient.data.l[1] = wm_attention;33153316XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);3317XFlush(x11_display);3318}33193320void DisplayServerX11::window_move_to_foreground(WindowID p_window) {3321_THREAD_SAFE_METHOD_33223323ERR_FAIL_COND(!windows.has(p_window));3324const WindowData &wd = windows[p_window];33253326XEvent xev;3327Atom net_active_window = XInternAtom(x11_display, "_NET_ACTIVE_WINDOW", False);33283329memset(&xev, 0, sizeof(xev));3330xev.type = ClientMessage;3331xev.xclient.window = wd.x11_window;3332xev.xclient.message_type = net_active_window;3333xev.xclient.format = 32;3334xev.xclient.data.l[0] = 1;3335xev.xclient.data.l[1] = CurrentTime;33363337XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);3338XFlush(x11_display);3339}33403341DisplayServerX11::WindowID DisplayServerX11::get_focused_window() const {3342return last_focused_window;3343}33443345bool DisplayServerX11::window_is_focused(WindowID p_window) const {3346_THREAD_SAFE_METHOD_33473348ERR_FAIL_COND_V(!windows.has(p_window), false);33493350const WindowData &wd = windows[p_window];33513352return wd.focused;3353}33543355bool DisplayServerX11::window_can_draw(WindowID p_window) const {3356//this seems to be all that is provided by X113357return window_get_mode(p_window) != WINDOW_MODE_MINIMIZED;3358}33593360bool DisplayServerX11::can_any_window_draw() const {3361_THREAD_SAFE_METHOD_33623363for (const KeyValue<WindowID, WindowData> &E : windows) {3364if (window_get_mode(E.key) != WINDOW_MODE_MINIMIZED) {3365return true;3366}3367}33683369return false;3370}33713372void DisplayServerX11::window_set_ime_active(const bool p_active, WindowID p_window) {3373_THREAD_SAFE_METHOD_33743375ERR_FAIL_COND(!windows.has(p_window));3376WindowData &wd = windows[p_window];33773378if (!wd.xic) {3379return;3380}3381if (!wd.focused) {3382wd.ime_active = false;3383im_text = String();3384im_selection = Vector2i();3385return;3386}33873388// Block events polling while changing input focus3389// because it triggers some event polling internally.3390if (p_active) {3391MutexLock mutex_lock(events_mutex);33923393wd.ime_active = true;33943395XMapWindow(x11_display, wd.x11_xim_window);33963397XWindowAttributes xwa;3398XSync(x11_display, False);3399XGetWindowAttributes(x11_display, wd.x11_xim_window, &xwa);3400if (xwa.map_state == IsViewable && _window_focus_check()) {3401_set_input_focus(wd.x11_xim_window, RevertToParent);3402}3403XSetICFocus(wd.xic);3404} else {3405MutexLock mutex_lock(events_mutex);3406XUnsetICFocus(wd.xic);3407XUnmapWindow(x11_display, wd.x11_xim_window);3408wd.ime_active = false;34093410im_text = String();3411im_selection = Vector2i();3412}3413}34143415void DisplayServerX11::window_set_ime_position(const Point2i &p_pos, WindowID p_window) {3416_THREAD_SAFE_METHOD_34173418ERR_FAIL_COND(!windows.has(p_window));3419WindowData &wd = windows[p_window];34203421if (!wd.xic) {3422return;3423}3424if (!wd.focused) {3425return;3426}34273428if (wd.ime_active) {3429XWindowAttributes xwa;3430XSync(x11_display, False);3431XGetWindowAttributes(x11_display, wd.x11_xim_window, &xwa);3432if (xwa.map_state == IsViewable) {3433XMoveWindow(x11_display, wd.x11_xim_window, p_pos.x, p_pos.y);3434}3435}3436}34373438int DisplayServerX11::accessibility_should_increase_contrast() const {3439#ifdef DBUS_ENABLED3440if (!portal_desktop) {3441return -1;3442}3443return portal_desktop->get_high_contrast();3444#endif3445return -1;3446}34473448int DisplayServerX11::accessibility_screen_reader_active() const {3449#ifdef DBUS_ENABLED3450if (atspi_monitor && atspi_monitor->is_supported()) {3451return atspi_monitor->is_active();3452}3453#endif3454return -1;3455}34563457Point2i DisplayServerX11::ime_get_selection() const {3458return im_selection;3459}34603461String DisplayServerX11::ime_get_text() const {3462return im_text;3463}34643465void DisplayServerX11::cursor_set_shape(CursorShape p_shape) {3466_THREAD_SAFE_METHOD_34673468ERR_FAIL_INDEX(p_shape, CURSOR_MAX);34693470if (p_shape == current_cursor) {3471return;3472}34733474if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) {3475if (cursors[p_shape] != None) {3476for (const KeyValue<WindowID, WindowData> &E : windows) {3477XDefineCursor(x11_display, E.value.x11_window, cursors[p_shape]);3478}3479} else if (cursors[CURSOR_ARROW] != None) {3480for (const KeyValue<WindowID, WindowData> &E : windows) {3481XDefineCursor(x11_display, E.value.x11_window, cursors[CURSOR_ARROW]);3482}3483}3484}34853486current_cursor = p_shape;3487}34883489DisplayServerX11::CursorShape DisplayServerX11::cursor_get_shape() const {3490return current_cursor;3491}34923493void DisplayServerX11::cursor_set_custom_image(const Ref<Resource> &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) {3494_THREAD_SAFE_METHOD_34953496ERR_FAIL_INDEX(p_shape, CURSOR_MAX);34973498if (p_cursor.is_valid()) {3499HashMap<CursorShape, Vector<Variant>>::Iterator cursor_c = cursors_cache.find(p_shape);35003501if (cursor_c) {3502if (cursor_c->value[0] == p_cursor && cursor_c->value[1] == p_hotspot) {3503cursor_set_shape(p_shape);3504return;3505}35063507cursors_cache.erase(p_shape);3508}35093510Ref<Image> image = _get_cursor_image_from_resource(p_cursor, p_hotspot);3511ERR_FAIL_COND(image.is_null());3512Vector2i texture_size = image->get_size();35133514// Create the cursor structure3515XcursorImage *cursor_image = XcursorImageCreate(texture_size.width, texture_size.height);3516XcursorUInt image_size = texture_size.width * texture_size.height;3517XcursorDim size = sizeof(XcursorPixel) * image_size;35183519cursor_image->version = 1;3520cursor_image->size = size;3521cursor_image->xhot = p_hotspot.x;3522cursor_image->yhot = p_hotspot.y;35233524// allocate memory to contain the whole file3525cursor_image->pixels = (XcursorPixel *)memalloc(size);35263527for (XcursorPixel index = 0; index < image_size; index++) {3528int row_index = std::floor(index / texture_size.width);3529int column_index = index % int(texture_size.width);35303531*(cursor_image->pixels + index) = image->get_pixel(column_index, row_index).to_argb32();3532}35333534ERR_FAIL_NULL(cursor_image->pixels);35353536// Save it for a further usage3537cursors[p_shape] = XcursorImageLoadCursor(x11_display, cursor_image);35383539Vector<Variant> params;3540params.push_back(p_cursor);3541params.push_back(p_hotspot);3542cursors_cache.insert(p_shape, params);35433544if (p_shape == current_cursor) {3545if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) {3546for (const KeyValue<WindowID, WindowData> &E : windows) {3547XDefineCursor(x11_display, E.value.x11_window, cursors[p_shape]);3548}3549}3550}35513552memfree(cursor_image->pixels);3553XcursorImageDestroy(cursor_image);3554} else {3555// Reset to default system cursor3556if (cursor_img[p_shape]) {3557cursors[p_shape] = XcursorImageLoadCursor(x11_display, cursor_img[p_shape]);3558}35593560cursors_cache.erase(p_shape);35613562CursorShape c = current_cursor;3563current_cursor = CURSOR_MAX;3564cursor_set_shape(c);3565}3566}35673568bool DisplayServerX11::get_swap_cancel_ok() {3569return swap_cancel_ok;3570}35713572int DisplayServerX11::keyboard_get_layout_count() const {3573int _group_count = 0;3574XkbDescRec *kbd = XkbAllocKeyboard();3575if (kbd) {3576kbd->dpy = x11_display;3577XkbGetControls(x11_display, XkbAllControlsMask, kbd);3578XkbGetNames(x11_display, XkbSymbolsNameMask, kbd);35793580const Atom *groups = kbd->names->groups;3581if (kbd->ctrls != nullptr) {3582_group_count = kbd->ctrls->num_groups;3583} else {3584while (_group_count < XkbNumKbdGroups && groups[_group_count] != None) {3585_group_count++;3586}3587}3588XkbFreeKeyboard(kbd, 0, true);3589}3590return _group_count;3591}35923593int DisplayServerX11::keyboard_get_current_layout() const {3594XkbStateRec state;3595XkbGetState(x11_display, XkbUseCoreKbd, &state);3596return state.group;3597}35983599void DisplayServerX11::keyboard_set_current_layout(int p_index) {3600ERR_FAIL_INDEX(p_index, keyboard_get_layout_count());3601XkbLockGroup(x11_display, XkbUseCoreKbd, p_index);3602}36033604String DisplayServerX11::keyboard_get_layout_language(int p_index) const {3605String ret;3606XkbDescRec *kbd = XkbAllocKeyboard();3607if (kbd) {3608kbd->dpy = x11_display;3609XkbGetControls(x11_display, XkbAllControlsMask, kbd);3610XkbGetNames(x11_display, XkbSymbolsNameMask, kbd);3611XkbGetNames(x11_display, XkbGroupNamesMask, kbd);36123613int _group_count = 0;3614const Atom *groups = kbd->names->groups;3615if (kbd->ctrls != nullptr) {3616_group_count = kbd->ctrls->num_groups;3617} else {3618while (_group_count < XkbNumKbdGroups && groups[_group_count] != None) {3619_group_count++;3620}3621}36223623Atom names = kbd->names->symbols;3624if (names != None) {3625Vector<String> info = get_atom_name(x11_display, names).split("+");3626if (p_index >= 0 && p_index < _group_count) {3627if (p_index + 1 < info.size()) {3628ret = info[p_index + 1]; // Skip "pc" at the start and "inet"/"group" at the end of symbols.3629} else {3630ret = "en"; // No symbol for layout fallback to "en".3631}3632} else {3633ERR_PRINT("Index " + itos(p_index) + "is out of bounds (" + itos(_group_count) + ").");3634}3635}3636XkbFreeKeyboard(kbd, 0, true);3637}3638return ret.substr(0, 2);3639}36403641String DisplayServerX11::keyboard_get_layout_name(int p_index) const {3642String ret;3643XkbDescRec *kbd = XkbAllocKeyboard();3644if (kbd) {3645kbd->dpy = x11_display;3646XkbGetControls(x11_display, XkbAllControlsMask, kbd);3647XkbGetNames(x11_display, XkbSymbolsNameMask, kbd);3648XkbGetNames(x11_display, XkbGroupNamesMask, kbd);36493650int _group_count = 0;3651const Atom *groups = kbd->names->groups;3652if (kbd->ctrls != nullptr) {3653_group_count = kbd->ctrls->num_groups;3654} else {3655while (_group_count < XkbNumKbdGroups && groups[_group_count] != None) {3656_group_count++;3657}3658}36593660if (p_index >= 0 && p_index < _group_count) {3661ret = get_atom_name(x11_display, groups[p_index]);3662} else {3663ERR_PRINT("Index " + itos(p_index) + "is out of bounds (" + itos(_group_count) + ").");3664}3665XkbFreeKeyboard(kbd, 0, true);3666}3667return ret;3668}36693670Key DisplayServerX11::keyboard_get_keycode_from_physical(Key p_keycode) const {3671Key modifiers = p_keycode & KeyModifierMask::MODIFIER_MASK;3672Key keycode_no_mod = p_keycode & KeyModifierMask::CODE_MASK;3673unsigned int xkeycode = KeyMappingX11::get_xlibcode(keycode_no_mod);3674KeySym xkeysym = XkbKeycodeToKeysym(x11_display, xkeycode, keyboard_get_current_layout(), 0);3675if (is_ascii_lower_case(xkeysym)) {3676xkeysym -= ('a' - 'A');3677}36783679Key key = KeyMappingX11::get_keycode(xkeysym);3680// If not found, fallback to QWERTY.3681// This should match the behavior of the event pump3682if (key == Key::NONE) {3683return p_keycode;3684}3685return (Key)(key | modifiers);3686}36873688Key DisplayServerX11::keyboard_get_label_from_physical(Key p_keycode) const {3689Key modifiers = p_keycode & KeyModifierMask::MODIFIER_MASK;3690Key keycode_no_mod = p_keycode & KeyModifierMask::CODE_MASK;3691unsigned int xkeycode = KeyMappingX11::get_xlibcode(keycode_no_mod);3692KeySym xkeysym = XkbKeycodeToKeysym(x11_display, xkeycode, keyboard_get_current_layout(), 0);3693if (is_ascii_lower_case(xkeysym)) {3694xkeysym -= ('a' - 'A');3695}36963697Key key = KeyMappingX11::get_keycode(xkeysym);3698#ifdef XKB_ENABLED3699if (xkb_loaded_v08p) {3700String keysym = String::chr(xkb_keysym_to_utf32(xkb_keysym_to_upper(xkeysym)));3701key = fix_key_label(keysym[0], KeyMappingX11::get_keycode(xkeysym));3702}3703#endif37043705// If not found, fallback to QWERTY.3706// This should match the behavior of the event pump3707if (key == Key::NONE) {3708return p_keycode;3709}3710return (Key)(key | modifiers);3711}37123713bool DisplayServerX11::color_picker(const Callable &p_callback) {3714#ifdef DBUS_ENABLED3715if (!portal_desktop) {3716return false;3717}3718WindowID window_id = last_focused_window;37193720if (!windows.has(window_id)) {3721window_id = MAIN_WINDOW_ID;3722}37233724String xid = vformat("x11:%x", (uint64_t)windows[window_id].x11_window);3725return portal_desktop->color_picker(xid, p_callback);3726#else3727return false;3728#endif3729}37303731DisplayServerX11::Property DisplayServerX11::_read_property(Display *p_display, Window p_window, Atom p_property) {3732Atom actual_type = None;3733int actual_format = 0;3734unsigned long nitems = 0;3735unsigned long bytes_after = 0;3736unsigned char *ret = nullptr;37373738// Keep trying to read the property until there are no bytes unread.3739if (p_property != None) {3740int read_bytes = 1024;3741do {3742if (ret != nullptr) {3743XFree(ret);3744}37453746XGetWindowProperty(p_display, p_window, p_property, 0, read_bytes, False, AnyPropertyType,3747&actual_type, &actual_format, &nitems, &bytes_after,3748&ret);37493750read_bytes *= 2;37513752} while (bytes_after != 0);3753}37543755Property p = { ret, actual_format, (int)nitems, actual_type };37563757return p;3758}37593760static Atom pick_target_from_list(Display *p_display, const Atom *p_list, int p_count) {3761static const char *target_type = "text/uri-list";37623763for (int i = 0; i < p_count; i++) {3764Atom atom = p_list[i];37653766if (atom != None && get_atom_name(p_display, atom) == target_type) {3767return atom;3768}3769}3770return None;3771}37723773static Atom pick_target_from_atoms(Display *p_disp, Atom p_t1, Atom p_t2, Atom p_t3) {3774static const char *target_type = "text/uri-list";3775if (p_t1 != None && get_atom_name(p_disp, p_t1) == target_type) {3776return p_t1;3777}37783779if (p_t2 != None && get_atom_name(p_disp, p_t2) == target_type) {3780return p_t2;3781}37823783if (p_t3 != None && get_atom_name(p_disp, p_t3) == target_type) {3784return p_t3;3785}37863787return None;3788}37893790void DisplayServerX11::_get_key_modifier_state(unsigned int p_x11_state, Ref<InputEventWithModifiers> state) {3791state->set_shift_pressed((p_x11_state & ShiftMask));3792state->set_ctrl_pressed((p_x11_state & ControlMask));3793state->set_alt_pressed((p_x11_state & Mod1Mask /*|| p_x11_state&Mod5Mask*/)); //altgr should not count as alt3794state->set_meta_pressed((p_x11_state & Mod4Mask));3795}37963797void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, LocalVector<XEvent> &p_events, uint32_t &p_event_index, bool p_echo) {3798WindowData &wd = windows[p_window];3799// X11 functions don't know what const is3800XKeyEvent *xkeyevent = p_event;38013802if (wd.ime_in_progress) {3803return;3804}3805if (wd.ime_suppress_next_keyup) {3806wd.ime_suppress_next_keyup = false;3807if (xkeyevent->type != KeyPress) {3808return;3809}3810}38113812// This code was pretty difficult to write.3813// The docs stink and every toolkit seems to3814// do it in a different way.38153816/* Phase 1, obtain a proper keysym */38173818// This was also very difficult to figure out.3819// You'd expect you could just use Keysym provided by3820// XKeycodeToKeysym to obtain internationalized3821// input.. WRONG!!3822// you must use XLookupString (???) which not only wastes3823// cycles generating an unnecessary string, but also3824// still works in half the cases. (won't handle deadkeys)3825// For more complex input methods (deadkeys and more advanced)3826// you have to use XmbLookupString (??).3827// So then you have to choose which of both results3828// you want to keep.3829// This is a real bizarreness and cpu waster.38303831KeySym keysym_keycode = 0; // keysym used to find a keycode3832KeySym keysym_unicode = 0; // keysym used to find unicode38333834// XLookupString returns keysyms usable as nice keycodes.3835char str[256] = {};3836XKeyEvent xkeyevent_no_mod = *xkeyevent;3837xkeyevent_no_mod.state &= 0xFF00;3838XLookupString(xkeyevent, str, 255, &keysym_unicode, nullptr);3839XLookupString(&xkeyevent_no_mod, nullptr, 0, &keysym_keycode, nullptr);38403841// Get a normalized keysym (ignoring modifiers like Shift/Ctrl).3842String keysym;3843#ifdef XKB_ENABLED3844if (xkb_loaded_v08p) {3845KeySym keysym_unicode_nm = 0; // keysym used to find unicode3846XLookupString(&xkeyevent_no_mod, nullptr, 0, &keysym_unicode_nm, nullptr);38473848// Unicode codepoint corresponding to the pressed key.3849// Printable keys (letters, numbers, symbols) return a valid codepoint.3850u_int32_t unicode_cp = xkb_keysym_to_utf32(xkb_keysym_to_upper(keysym_unicode_nm));38513852// Non-printable keys (Ctrl, Home, CapsLock, F1, etc.) return 0, so we skip them.3853if (unicode_cp != 0) {3854keysym = String::chr(unicode_cp);3855}3856}3857#endif38583859// Meanwhile, XLookupString returns keysyms useful for unicode.38603861if (!xmbstring) {3862// keep a temporary buffer for the string3863xmbstring = (char *)memalloc(sizeof(char) * 8);3864xmblen = 8;3865}38663867if (xkeyevent->type == KeyPress && wd.xic) {3868Status status;3869#ifdef X_HAVE_UTF8_STRING3870int utf8len = 8;3871char *utf8string = (char *)memalloc(sizeof(char) * utf8len);3872int utf8bytes = Xutf8LookupString(wd.xic, xkeyevent, utf8string,3873utf8len - 1, &keysym_unicode, &status);3874if (status == XBufferOverflow) {3875utf8len = utf8bytes + 1;3876utf8string = (char *)memrealloc(utf8string, utf8len);3877utf8bytes = Xutf8LookupString(wd.xic, xkeyevent, utf8string,3878utf8len - 1, &keysym_unicode, &status);3879}3880utf8string[utf8bytes] = '\0';38813882if (status == XLookupChars) {3883bool keypress = xkeyevent->type == KeyPress;38843885Key keycode = Key::NONE;3886if (KeyMappingX11::is_sym_numpad(keysym_unicode)) {3887// Special case for numpad keys.3888keycode = KeyMappingX11::get_keycode(keysym_unicode);3889}38903891if (keycode == Key::NONE) {3892keycode = KeyMappingX11::get_keycode(keysym_keycode);3893}38943895Key physical_keycode = KeyMappingX11::get_scancode(xkeyevent->keycode);38963897if (keycode >= Key::A + 32 && keycode <= Key::Z + 32) {3898keycode -= 'a' - 'A';3899}39003901String tmp = String::utf8(utf8string, utf8bytes);3902for (int i = 0; i < tmp.length(); i++) {3903Ref<InputEventKey> k;3904k.instantiate();3905if (physical_keycode == Key::NONE && keycode == Key::NONE && tmp[i] == 0) {3906continue;3907}39083909if (keycode == Key::NONE) {3910keycode = (Key)physical_keycode;3911}39123913_get_key_modifier_state(xkeyevent->state, k);39143915k->set_window_id(p_window);3916k->set_pressed(keypress);39173918k->set_keycode(keycode);3919k->set_physical_keycode(physical_keycode);3920if (!keysym.is_empty()) {3921k->set_key_label(fix_key_label(keysym[0], keycode));3922} else {3923k->set_key_label(keycode);3924}3925if (keypress) {3926k->set_unicode(fix_unicode(tmp[i]));3927}39283929k->set_echo(false);39303931if (k->get_keycode() == Key::BACKTAB) {3932//make it consistent across platforms.3933k->set_keycode(Key::TAB);3934k->set_physical_keycode(Key::TAB);3935k->set_shift_pressed(true);3936}39373938Input::get_singleton()->parse_input_event(k);3939}3940memfree(utf8string);3941return;3942}3943memfree(utf8string);3944#else3945do {3946int mnbytes = XmbLookupString(xic, xkeyevent, xmbstring, xmblen - 1, &keysym_unicode, &status);3947xmbstring[mnbytes] = '\0';39483949if (status == XBufferOverflow) {3950xmblen = mnbytes + 1;3951xmbstring = (char *)memrealloc(xmbstring, xmblen);3952}3953} while (status == XBufferOverflow);3954#endif3955#ifdef XKB_ENABLED3956} else if (xkeyevent->type == KeyPress && wd.xkb_state && xkb_loaded_v05p) {3957xkb_compose_feed_result res = xkb_compose_state_feed(wd.xkb_state, keysym_unicode);3958if (res == XKB_COMPOSE_FEED_ACCEPTED) {3959if (xkb_compose_state_get_status(wd.xkb_state) == XKB_COMPOSE_COMPOSED) {3960bool keypress = xkeyevent->type == KeyPress;3961Key physical_keycode = KeyMappingX11::get_scancode(xkeyevent->keycode);3962KeyLocation key_location = KeyMappingX11::get_location(xkeyevent->keycode);39633964Key keycode = Key::NONE;3965if (KeyMappingX11::is_sym_numpad(keysym_unicode)) {3966// Special case for numpad keys.3967keycode = KeyMappingX11::get_keycode(keysym_unicode);3968}39693970if (keycode == Key::NONE) {3971keycode = KeyMappingX11::get_keycode(keysym_keycode);3972}39733974if (keycode >= Key::A + 32 && keycode <= Key::Z + 32) {3975keycode -= 'a' - 'A';3976}39773978char str_xkb[256] = {};3979int str_xkb_size = xkb_compose_state_get_utf8(wd.xkb_state, str_xkb, 255);39803981String tmp = String::utf8(str_xkb, str_xkb_size);3982for (int i = 0; i < tmp.length(); i++) {3983Ref<InputEventKey> k;3984k.instantiate();3985if (physical_keycode == Key::NONE && keycode == Key::NONE && tmp[i] == 0) {3986continue;3987}39883989if (keycode == Key::NONE) {3990keycode = (Key)physical_keycode;3991}39923993_get_key_modifier_state(xkeyevent->state, k);39943995k->set_window_id(p_window);3996k->set_pressed(keypress);39973998k->set_keycode(keycode);3999k->set_physical_keycode(physical_keycode);4000if (!keysym.is_empty()) {4001k->set_key_label(fix_key_label(keysym[0], keycode));4002} else {4003k->set_key_label(keycode);4004}4005if (keypress) {4006k->set_unicode(fix_unicode(tmp[i]));4007}40084009k->set_location(key_location);40104011k->set_echo(false);40124013if (k->get_keycode() == Key::BACKTAB) {4014//make it consistent across platforms.4015k->set_keycode(Key::TAB);4016k->set_physical_keycode(Key::TAB);4017k->set_shift_pressed(true);4018}40194020Input::get_singleton()->parse_input_event(k);4021}4022return;4023}4024}4025#endif4026}40274028/* Phase 2, obtain a Godot keycode from the keysym */40294030// KeyMappingX11 just translated the X11 keysym to a PIGUI4031// keysym, so it works in all platforms the same.40324033Key keycode = Key::NONE;4034if (KeyMappingX11::is_sym_numpad(keysym_unicode) || KeyMappingX11::is_sym_numpad(keysym_keycode)) {4035// Special case for numpad keys.4036keycode = KeyMappingX11::get_keycode(keysym_unicode);4037}40384039if (keycode == Key::NONE) {4040keycode = KeyMappingX11::get_keycode(keysym_keycode);4041}40424043Key physical_keycode = KeyMappingX11::get_scancode(xkeyevent->keycode);40444045KeyLocation key_location = KeyMappingX11::get_location(xkeyevent->keycode);40464047/* Phase 3, obtain a unicode character from the keysym */40484049// KeyMappingX11 also translates keysym to unicode.4050// It does a binary search on a table to translate4051// most properly.4052char32_t unicode = keysym_unicode > 0 ? KeyMappingX11::get_unicode_from_keysym(keysym_unicode) : 0;40534054/* Phase 4, determine if event must be filtered */40554056// This seems to be a side-effect of using XIM.4057// XFilterEvent looks like a core X11 function,4058// but it's actually just used to see if we must4059// ignore a deadkey, or events XIM determines4060// must not reach the actual gui.4061// Guess it was a design problem of the extension40624063bool keypress = xkeyevent->type == KeyPress;40644065if (physical_keycode == Key::NONE && keycode == Key::NONE && unicode == 0) {4066return;4067}40684069if (keycode == Key::NONE) {4070keycode = (Key)physical_keycode;4071}40724073/* Phase 5, determine modifier mask */40744075// No problems here, except I had no way to4076// know Mod1 was ALT and Mod4 was META (applekey/winkey)4077// just tried Mods until i found them.40784079//print_verbose("mod1: "+itos(xkeyevent->state&Mod1Mask)+" mod 5: "+itos(xkeyevent->state&Mod5Mask));40804081Ref<InputEventKey> k;4082k.instantiate();4083k->set_window_id(p_window);40844085_get_key_modifier_state(xkeyevent->state, k);40864087/* Phase 6, determine echo character */40884089// Echo characters in X11 are a keyrelease and a keypress4090// one after the other with the (almot) same timestamp.4091// To detect them, i compare to the next event in list and4092// check that their difference in time is below a threshold.40934094if (xkeyevent->type != KeyPress) {4095p_echo = false;40964097// make sure there are events pending,4098// so this call won't block.4099if (p_event_index + 1 < p_events.size()) {4100XEvent &peek_event = p_events[p_event_index + 1];41014102// I'm using a threshold of 5 msecs,4103// since sometimes there seems to be a little4104// jitter. I'm still not convinced that all this approach4105// is correct, but the xorg developers are4106// not very helpful today.41074108#define ABSDIFF(x, y) (((x) < (y)) ? ((y) - (x)) : ((x) - (y)))4109::Time threshold = ABSDIFF(peek_event.xkey.time, xkeyevent->time);4110#undef ABSDIFF4111if (peek_event.type == KeyPress && threshold < 5) {4112KeySym rk;4113XLookupString((XKeyEvent *)&peek_event, str, 256, &rk, nullptr);4114if (rk == keysym_keycode) {4115// Consume to next event.4116++p_event_index;4117_handle_key_event(p_window, (XKeyEvent *)&peek_event, p_events, p_event_index, true);4118return; //ignore current, echo next4119}4120}41214122// use the time from peek_event so it always works4123}41244125// save the time to check for echo when keypress happens4126}41274128/* Phase 7, send event to Window */41294130k->set_pressed(keypress);41314132if (keycode >= Key::A + 32 && keycode <= Key::Z + 32) {4133keycode -= int('a' - 'A');4134}41354136k->set_keycode(keycode);4137k->set_physical_keycode((Key)physical_keycode);4138if (!keysym.is_empty()) {4139k->set_key_label(fix_key_label(keysym[0], keycode));4140} else {4141k->set_key_label(keycode);4142}4143if (keypress) {4144k->set_unicode(fix_unicode(unicode));4145}41464147k->set_location(key_location);41484149k->set_echo(p_echo);41504151if (k->get_keycode() == Key::BACKTAB) {4152//make it consistent across platforms.4153k->set_keycode(Key::TAB);4154k->set_physical_keycode(Key::TAB);4155k->set_shift_pressed(true);4156}41574158//don't set mod state if modifier keys are released by themselves4159//else event.is_action() will not work correctly here4160if (!k->is_pressed()) {4161if (k->get_keycode() == Key::SHIFT) {4162k->set_shift_pressed(false);4163} else if (k->get_keycode() == Key::CTRL) {4164k->set_ctrl_pressed(false);4165} else if (k->get_keycode() == Key::ALT) {4166k->set_alt_pressed(false);4167} else if (k->get_keycode() == Key::META) {4168k->set_meta_pressed(false);4169}4170}41714172bool last_is_pressed = Input::get_singleton()->is_key_pressed(k->get_keycode());4173if (k->is_pressed()) {4174if (last_is_pressed) {4175k->set_echo(true);4176}4177}41784179Input::get_singleton()->parse_input_event(k);4180}41814182Atom DisplayServerX11::_process_selection_request_target(Atom p_target, Window p_requestor, Atom p_property, Atom p_selection) const {4183if (p_target == XInternAtom(x11_display, "TARGETS", 0)) {4184// Request to list all supported targets.4185Atom data[9];4186data[0] = XInternAtom(x11_display, "TARGETS", 0);4187data[1] = XInternAtom(x11_display, "SAVE_TARGETS", 0);4188data[2] = XInternAtom(x11_display, "MULTIPLE", 0);4189data[3] = XInternAtom(x11_display, "UTF8_STRING", 0);4190data[4] = XInternAtom(x11_display, "COMPOUND_TEXT", 0);4191data[5] = XInternAtom(x11_display, "TEXT", 0);4192data[6] = XA_STRING;4193data[7] = XInternAtom(x11_display, "text/plain;charset=utf-8", 0);4194data[8] = XInternAtom(x11_display, "text/plain", 0);41954196XChangeProperty(x11_display,4197p_requestor,4198p_property,4199XA_ATOM,420032,4201PropModeReplace,4202(unsigned char *)&data,4203std_size(data));4204return p_property;4205} else if (p_target == XInternAtom(x11_display, "SAVE_TARGETS", 0)) {4206// Request to check if SAVE_TARGETS is supported, nothing special to do.4207XChangeProperty(x11_display,4208p_requestor,4209p_property,4210XInternAtom(x11_display, "NULL", False),421132,4212PropModeReplace,4213nullptr,42140);4215return p_property;4216} else if (p_target == XInternAtom(x11_display, "UTF8_STRING", 0) ||4217p_target == XInternAtom(x11_display, "COMPOUND_TEXT", 0) ||4218p_target == XInternAtom(x11_display, "TEXT", 0) ||4219p_target == XA_STRING ||4220p_target == XInternAtom(x11_display, "text/plain;charset=utf-8", 0) ||4221p_target == XInternAtom(x11_display, "text/plain", 0)) {4222// Directly using internal clipboard because we know our window4223// is the owner during a selection request.4224CharString clip;4225static const char *target_type = "PRIMARY";4226if (p_selection != None && get_atom_name(x11_display, p_selection) == target_type) {4227clip = internal_clipboard_primary.utf8();4228} else {4229clip = internal_clipboard.utf8();4230}4231XChangeProperty(x11_display,4232p_requestor,4233p_property,4234p_target,42358,4236PropModeReplace,4237(unsigned char *)clip.get_data(),4238clip.length());4239return p_property;4240} else {4241char *target_name = XGetAtomName(x11_display, p_target);4242print_verbose(vformat("Target '%s' not supported.", target_name));4243if (target_name) {4244XFree(target_name);4245}4246return None;4247}4248}42494250void DisplayServerX11::_handle_selection_request_event(XSelectionRequestEvent *p_event) const {4251XEvent respond;4252if (p_event->target == XInternAtom(x11_display, "MULTIPLE", 0)) {4253// Request for multiple target conversions at once.4254Atom atom_pair = XInternAtom(x11_display, "ATOM_PAIR", False);4255respond.xselection.property = None;42564257Atom type;4258int format;4259unsigned long len;4260unsigned long remaining;4261unsigned char *data = nullptr;4262if (XGetWindowProperty(x11_display, p_event->requestor, p_event->property, 0, LONG_MAX, False, atom_pair, &type, &format, &len, &remaining, &data) == Success) {4263if ((len >= 2) && data) {4264Atom *targets = (Atom *)data;4265for (uint64_t i = 0; i < len; i += 2) {4266Atom target = targets[i];4267Atom &property = targets[i + 1];4268property = _process_selection_request_target(target, p_event->requestor, property, p_event->selection);4269}42704271XChangeProperty(x11_display,4272p_event->requestor,4273p_event->property,4274atom_pair,427532,4276PropModeReplace,4277(unsigned char *)targets,4278len);42794280respond.xselection.property = p_event->property;4281}4282XFree(data);4283}4284} else {4285// Request for target conversion.4286respond.xselection.property = _process_selection_request_target(p_event->target, p_event->requestor, p_event->property, p_event->selection);4287}42884289respond.xselection.type = SelectionNotify;4290respond.xselection.display = p_event->display;4291respond.xselection.requestor = p_event->requestor;4292respond.xselection.selection = p_event->selection;4293respond.xselection.target = p_event->target;4294respond.xselection.time = p_event->time;42954296XSendEvent(x11_display, p_event->requestor, True, NoEventMask, &respond);4297XFlush(x11_display);4298}42994300int DisplayServerX11::_xim_preedit_start_callback(::XIM xim, ::XPointer client_data,4301::XPointer call_data) {4302DisplayServerX11 *ds = reinterpret_cast<DisplayServerX11 *>(client_data);4303WindowID window_id = ds->_get_focused_window_or_popup();4304WindowData &wd = ds->windows[window_id];4305if (wd.ime_active) {4306wd.ime_in_progress = true;4307}43084309return -1; // Allow preedit strings of any length (no limit).4310}43114312void DisplayServerX11::_xim_preedit_done_callback(::XIM xim, ::XPointer client_data,4313::XPointer call_data) {4314DisplayServerX11 *ds = reinterpret_cast<DisplayServerX11 *>(client_data);4315WindowID window_id = ds->_get_focused_window_or_popup();4316WindowData &wd = ds->windows[window_id];4317if (wd.ime_active) {4318wd.ime_in_progress = false;4319wd.ime_suppress_next_keyup = true;4320}4321}43224323void DisplayServerX11::_xim_preedit_draw_callback(::XIM xim, ::XPointer client_data,4324::XIMPreeditDrawCallbackStruct *call_data) {4325DisplayServerX11 *ds = reinterpret_cast<DisplayServerX11 *>(client_data);4326WindowID window_id = ds->_get_focused_window_or_popup();4327WindowData &wd = ds->windows[window_id];43284329XIMText *xim_text = call_data->text;4330if (wd.ime_active) {4331if (xim_text != nullptr) {4332String changed_text;4333if (xim_text->encoding_is_wchar) {4334changed_text = String(xim_text->string.wide_char);4335} else {4336changed_text.append_utf8(xim_text->string.multi_byte);4337}43384339if (call_data->chg_length < 0) {4340ds->im_text = ds->im_text.substr(0, call_data->chg_first) + changed_text;4341} else {4342ds->im_text = ds->im_text.substr(0, call_data->chg_first) + changed_text + ds->im_text.substr(call_data->chg_length);4343}43444345// Find the start and end of the selection.4346int start = 0, count = 0;4347for (int i = 0; i < xim_text->length; i++) {4348if (xim_text->feedback[i] & XIMReverse) {4349if (count == 0) {4350start = i;4351count = 1;4352} else {4353count++;4354}4355}4356}4357if (count > 0) {4358ds->im_selection = Point2i(start + call_data->chg_first, count);4359} else {4360ds->im_selection = Point2i(call_data->caret, 0);4361}4362} else {4363ds->im_text = String();4364ds->im_selection = Point2i();4365}43664367callable_mp((Object *)OS_Unix::get_singleton()->get_main_loop(), &Object::notification).call_deferred(MainLoop::NOTIFICATION_OS_IME_UPDATE, false);4368}4369}43704371void DisplayServerX11::_xim_preedit_caret_callback(::XIM xim, ::XPointer client_data,4372::XIMPreeditCaretCallbackStruct *call_data) {4373}43744375void DisplayServerX11::_xim_destroy_callback(::XIM im, ::XPointer client_data,4376::XPointer call_data) {4377WARN_PRINT("Input method stopped");4378DisplayServerX11 *ds = reinterpret_cast<DisplayServerX11 *>(client_data);4379ds->xim = nullptr;43804381for (KeyValue<WindowID, WindowData> &E : ds->windows) {4382E.value.xic = nullptr;4383}4384}43854386void DisplayServerX11::_window_changed(XEvent *event) {4387WindowID window_id = MAIN_WINDOW_ID;43884389// Assign the event to the relevant window4390for (const KeyValue<WindowID, WindowData> &E : windows) {4391if (event->xany.window == E.value.x11_window) {4392window_id = E.key;4393break;4394}4395}43964397Rect2i new_rect;43984399WindowData &wd = windows[window_id];4400if (wd.x11_window != event->xany.window) { // Check if the correct window, in case it was not main window or anything else4401return;4402}44034404// Query display server about a possible new window state.4405wd.fullscreen = _window_fullscreen_check(window_id);4406wd.maximized = _window_maximize_check(window_id, "_NET_WM_STATE") && !wd.fullscreen;4407wd.minimized = _window_minimize_check(window_id) && !wd.fullscreen && !wd.maximized;44084409// Readjusting the window position if the window is being reparented by the window manager for decoration4410Window root, parent, *children;4411unsigned int nchildren;4412if (XQueryTree(x11_display, wd.x11_window, &root, &parent, &children, &nchildren) && wd.parent != parent) {4413wd.parent = parent;4414if (!wd.embed_parent) {4415window_set_position(wd.position, window_id);4416}4417}4418XFree(children);44194420{4421//the position in xconfigure is not useful here, obtain it manually4422int x = 0, y = 0;4423Window child;4424XTranslateCoordinates(x11_display, wd.x11_window, DefaultRootWindow(x11_display), 0, 0, &x, &y, &child);4425new_rect.position.x = x;4426new_rect.position.y = y;44274428new_rect.size.width = event->xconfigure.width;4429new_rect.size.height = event->xconfigure.height;4430}44314432if (new_rect == Rect2i(wd.position, wd.size)) {4433return;4434}44354436wd.position = new_rect.position;4437wd.size = new_rect.size;44384439#if defined(RD_ENABLED)4440if (rendering_context) {4441rendering_context->window_set_size(window_id, wd.size.width, wd.size.height);4442}4443#endif4444#if defined(GLES3_ENABLED)4445if (gl_manager) {4446gl_manager->window_resize(window_id, wd.size.width, wd.size.height);4447}4448if (gl_manager_egl) {4449gl_manager_egl->window_resize(window_id, wd.size.width, wd.size.height);4450}4451#endif44524453if (wd.rect_changed_callback.is_valid()) {4454wd.rect_changed_callback.call(new_rect);4455}4456}44574458DisplayServer::WindowID DisplayServerX11::_get_focused_window_or_popup() const {4459const List<WindowID>::Element *E = popup_list.back();4460if (E) {4461return E->get();4462}44634464return last_focused_window;4465}44664467void DisplayServerX11::_dispatch_input_events(const Ref<InputEvent> &p_event) {4468static_cast<DisplayServerX11 *>(get_singleton())->_dispatch_input_event(p_event);4469}44704471void DisplayServerX11::_dispatch_input_event(const Ref<InputEvent> &p_event) {4472{4473List<WindowID>::Element *E = popup_list.back();4474if (E && Object::cast_to<InputEventKey>(*p_event)) {4475// Redirect keyboard input to active popup.4476if (windows.has(E->get())) {4477Callable callable = windows[E->get()].input_event_callback;4478if (callable.is_valid()) {4479callable.call(p_event);4480}4481}4482return;4483}4484}44854486Ref<InputEventFromWindow> event_from_window = p_event;4487if (event_from_window.is_valid() && event_from_window->get_window_id() != INVALID_WINDOW_ID) {4488// Send to a single window.4489if (windows.has(event_from_window->get_window_id())) {4490Callable callable = windows[event_from_window->get_window_id()].input_event_callback;4491if (callable.is_valid()) {4492callable.call(p_event);4493}4494}4495} else {4496// Send to all windows. Copy all pending callbacks, since callback can erase window.4497Vector<Callable> cbs;4498for (KeyValue<WindowID, WindowData> &E : windows) {4499Callable callable = E.value.input_event_callback;4500if (callable.is_valid()) {4501cbs.push_back(callable);4502}4503}4504for (const Callable &cb : cbs) {4505cb.call(p_event);4506}4507}4508}45094510void DisplayServerX11::_send_window_event(const WindowData &wd, WindowEvent p_event) {4511if (wd.event_callback.is_valid()) {4512Variant event = int(p_event);4513wd.event_callback.call(event);4514}4515}45164517void DisplayServerX11::_set_input_focus(Window p_window, int p_revert_to) {4518Window focused_window;4519int focus_ret_state;4520XGetInputFocus(x11_display, &focused_window, &focus_ret_state);45214522// Only attempt to change focus if the window isn't already focused, in order to4523// prevent issues with Godot stealing input focus with alternative window managers.4524if (p_window != focused_window) {4525XSetInputFocus(x11_display, p_window, p_revert_to, CurrentTime);4526}4527}45284529void DisplayServerX11::_poll_events_thread(void *ud) {4530DisplayServerX11 *display_server = static_cast<DisplayServerX11 *>(ud);4531display_server->_poll_events();4532}45334534Bool DisplayServerX11::_predicate_all_events(Display *display, XEvent *event, XPointer arg) {4535// Just accept all events.4536return True;4537}45384539bool DisplayServerX11::_wait_for_events() const {4540int x11_fd = ConnectionNumber(x11_display);4541fd_set in_fds;45424543XFlush(x11_display);45444545FD_ZERO(&in_fds);4546FD_SET(x11_fd, &in_fds);45474548struct timeval tv;4549tv.tv_usec = 0;4550tv.tv_sec = 1;45514552// Wait for next event or timeout.4553int num_ready_fds = select(x11_fd + 1, &in_fds, nullptr, nullptr, &tv);45544555if (num_ready_fds > 0) {4556// Event received.4557return true;4558} else {4559// Error or timeout.4560if (num_ready_fds < 0) {4561ERR_PRINT("_wait_for_events: select error: " + itos(errno));4562}4563return false;4564}4565}45664567void DisplayServerX11::_poll_events() {4568while (!events_thread_done.is_set()) {4569_wait_for_events();45704571// Process events from the queue.4572{4573MutexLock mutex_lock(events_mutex);45744575_check_pending_events(polled_events);4576}4577}4578}45794580void DisplayServerX11::_check_pending_events(LocalVector<XEvent> &r_events) {4581// Flush to make sure to gather all pending events.4582XFlush(x11_display);45834584// Non-blocking wait for next event and remove it from the queue.4585XEvent ev = {};4586while (XCheckIfEvent(x11_display, &ev, _predicate_all_events, nullptr)) {4587// Check if the input manager wants to process the event.4588if (XFilterEvent(&ev, None)) {4589// Event has been filtered by the Input Manager,4590// it has to be ignored and a new one will be received.4591continue;4592}45934594// Handle selection request events directly in the event thread, because4595// communication through the x server takes several events sent back and forth4596// and we don't want to block other programs while processing only one each frame.4597if (ev.type == SelectionRequest) {4598_handle_selection_request_event(&(ev.xselectionrequest));4599continue;4600}46014602r_events.push_back(ev);4603}4604}46054606DisplayServer::WindowID DisplayServerX11::window_get_active_popup() const {4607const List<WindowID>::Element *E = popup_list.back();4608if (E) {4609return E->get();4610} else {4611return INVALID_WINDOW_ID;4612}4613}46144615void DisplayServerX11::window_set_popup_safe_rect(WindowID p_window, const Rect2i &p_rect) {4616_THREAD_SAFE_METHOD_46174618ERR_FAIL_COND(!windows.has(p_window));4619WindowData &wd = windows[p_window];4620wd.parent_safe_rect = p_rect;4621}46224623Rect2i DisplayServerX11::window_get_popup_safe_rect(WindowID p_window) const {4624_THREAD_SAFE_METHOD_46254626ERR_FAIL_COND_V(!windows.has(p_window), Rect2i());4627const WindowData &wd = windows[p_window];4628return wd.parent_safe_rect;4629}46304631void DisplayServerX11::popup_open(WindowID p_window) {4632_THREAD_SAFE_METHOD_46334634bool has_popup_ancestor = false;4635WindowID transient_root = p_window;4636while (true) {4637WindowID parent = windows[transient_root].transient_parent;4638if (parent == INVALID_WINDOW_ID) {4639break;4640} else {4641transient_root = parent;4642if (windows[parent].is_popup) {4643has_popup_ancestor = true;4644break;4645}4646}4647}46484649// Detect tooltips and other similar popups that shouldn't block input to their parent.4650bool ignores_input = window_get_flag(WINDOW_FLAG_NO_FOCUS, p_window) && window_get_flag(WINDOW_FLAG_MOUSE_PASSTHROUGH, p_window);46514652WindowData &wd = windows[p_window];4653if (wd.is_popup || (has_popup_ancestor && !ignores_input)) {4654// Find current popup parent, or root popup if new window is not transient.4655List<WindowID>::Element *C = nullptr;4656List<WindowID>::Element *E = popup_list.back();4657while (E) {4658if (wd.transient_parent != E->get() || wd.transient_parent == INVALID_WINDOW_ID) {4659C = E;4660E = E->prev();4661} else {4662break;4663}4664}4665if (C) {4666_send_window_event(windows[C->get()], DisplayServerX11::WINDOW_EVENT_CLOSE_REQUEST);4667}46684669time_since_popup = OS::get_singleton()->get_ticks_msec();4670popup_list.push_back(p_window);4671}4672}46734674void DisplayServerX11::popup_close(WindowID p_window) {4675_THREAD_SAFE_METHOD_46764677List<WindowID>::Element *E = popup_list.find(p_window);4678while (E) {4679List<WindowID>::Element *F = E->next();4680WindowID win_id = E->get();4681popup_list.erase(E);46824683if (win_id != p_window) {4684// Only request close on related windows, not this window. We are already processing it.4685_send_window_event(windows[win_id], DisplayServerX11::WINDOW_EVENT_CLOSE_REQUEST);4686}4687E = F;4688}4689}46904691bool DisplayServerX11::mouse_process_popups() {4692_THREAD_SAFE_METHOD_46934694if (popup_list.is_empty()) {4695return false;4696}46974698uint64_t delta = OS::get_singleton()->get_ticks_msec() - time_since_popup;4699if (delta < 250) {4700return false;4701}47024703int number_of_screens = XScreenCount(x11_display);4704bool closed = false;4705for (int i = 0; i < number_of_screens; i++) {4706Window root, child;4707int root_x, root_y, win_x, win_y;4708unsigned int mask;4709if (XQueryPointer(x11_display, XRootWindow(x11_display, i), &root, &child, &root_x, &root_y, &win_x, &win_y, &mask)) {4710XWindowAttributes root_attrs;4711XGetWindowAttributes(x11_display, root, &root_attrs);4712Vector2i pos = Vector2i(root_attrs.x + root_x, root_attrs.y + root_y);4713if (mask != last_mouse_monitor_mask) {4714if (((mask & Button1Mask) || (mask & Button2Mask) || (mask & Button3Mask) || (mask & Button4Mask) || (mask & Button5Mask))) {4715List<WindowID>::Element *C = nullptr;4716List<WindowID>::Element *E = popup_list.back();4717// Find top popup to close.4718while (E) {4719// Popup window area.4720Rect2i win_rect = Rect2i(window_get_position_with_decorations(E->get()), window_get_size_with_decorations(E->get()));4721// Area of the parent window, which responsible for opening sub-menu.4722Rect2i safe_rect = window_get_popup_safe_rect(E->get());4723if (win_rect.has_point(pos)) {4724break;4725} else if (safe_rect != Rect2i() && safe_rect.has_point(pos)) {4726break;4727} else {4728C = E;4729E = E->prev();4730}4731}4732if (C) {4733_send_window_event(windows[C->get()], DisplayServerX11::WINDOW_EVENT_CLOSE_REQUEST);4734closed = true;4735}4736}4737}4738last_mouse_monitor_mask = mask;4739}4740}4741return closed;4742}47434744bool DisplayServerX11::_window_focus_check() {4745Window focused_window;4746int focus_ret_state;4747XGetInputFocus(x11_display, &focused_window, &focus_ret_state);47484749bool has_focus = false;4750for (const KeyValue<int, DisplayServerX11::WindowData> &wid : windows) {4751if (wid.value.x11_window == focused_window || (wid.value.xic && wid.value.ime_active && wid.value.x11_xim_window == focused_window)) {4752has_focus = true;4753break;4754}4755}47564757return has_focus;4758}47594760void DisplayServerX11::process_events() {4761ERR_FAIL_COND(!Thread::is_main_thread());47624763_THREAD_SAFE_LOCK_47644765#ifdef DISPLAY_SERVER_X11_DEBUG_LOGS_ENABLED4766static int frame = 0;4767++frame;4768#endif47694770bool ignore_events = mouse_process_popups();47714772if (app_focused) {4773//verify that one of the windows has focus, else send focus out notification4774bool focus_found = false;4775for (const KeyValue<WindowID, WindowData> &E : windows) {4776if (E.value.focused) {4777focus_found = true;4778break;4779}4780}47814782if (!focus_found) {4783uint64_t delta = OS::get_singleton()->get_ticks_msec() - time_since_no_focus;47844785if (delta > 250) {4786//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.4787if (OS::get_singleton()->get_main_loop()) {4788DEBUG_LOG_X11("All focus lost, triggering NOTIFICATION_APPLICATION_FOCUS_OUT\n");4789OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT);4790}4791app_focused = false;4792}4793} else {4794time_since_no_focus = OS::get_singleton()->get_ticks_msec();4795}4796}47974798do_mouse_warp = false;47994800// Is the current mouse mode one where it needs to be grabbed.4801bool mouse_mode_grab = mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN;48024803xi.pressure = 0;4804xi.tilt = Vector2();4805xi.pressure_supported = false;48064807LocalVector<XEvent> events;4808{4809// Block events polling while flushing events.4810MutexLock mutex_lock(events_mutex);4811events = polled_events;4812polled_events.clear();4813}48144815for (uint32_t event_index = 0; event_index < events.size(); ++event_index) {4816XEvent &event = events[event_index];48174818bool ime_window_event = false;4819WindowID window_id = MAIN_WINDOW_ID;48204821// Assign the event to the relevant window4822for (const KeyValue<WindowID, WindowData> &E : windows) {4823if (event.xany.window == E.value.x11_window) {4824window_id = E.key;4825break;4826}4827if (event.xany.window == E.value.x11_xim_window) {4828window_id = E.key;4829ime_window_event = true;4830break;4831}4832}48334834if (XGetEventData(x11_display, &event.xcookie)) {4835if (event.xcookie.type == GenericEvent && event.xcookie.extension == xi.opcode) {4836XIDeviceEvent *event_data = (XIDeviceEvent *)event.xcookie.data;4837switch (event_data->evtype) {4838case XI_HierarchyChanged:4839case XI_DeviceChanged: {4840_refresh_device_info();4841} break;4842case XI_RawMotion: {4843if (ime_window_event || ignore_events) {4844break;4845}4846XIRawEvent *raw_event = (XIRawEvent *)event_data;4847int device_id = raw_event->sourceid;48484849// Determine the axis used (called valuators in XInput for some forsaken reason)4850// Mask is a bitmask indicating which axes are involved.4851// We are interested in the values of axes 0 and 1.4852if (raw_event->valuators.mask_len <= 0) {4853break;4854}48554856const double *values = raw_event->raw_values;48574858double rel_x = 0.0;4859double rel_y = 0.0;48604861if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_ABSX)) {4862rel_x = *values;4863values++;4864}48654866if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_ABSY)) {4867rel_y = *values;4868values++;4869}48704871if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_PRESSURE)) {4872HashMap<int, Vector2>::Iterator pen_pressure = xi.pen_pressure_range.find(device_id);4873if (pen_pressure) {4874Vector2 pen_pressure_range = pen_pressure->value;4875if (pen_pressure_range != Vector2()) {4876xi.pressure_supported = true;4877xi.pressure = (*values - pen_pressure_range[0]) /4878(pen_pressure_range[1] - pen_pressure_range[0]);4879}4880}48814882values++;4883}48844885if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_TILTX)) {4886HashMap<int, Vector2>::Iterator pen_tilt_x = xi.pen_tilt_x_range.find(device_id);4887if (pen_tilt_x) {4888Vector2 pen_tilt_x_range = pen_tilt_x->value;4889if (pen_tilt_x_range[0] != 0 && *values < 0) {4890xi.tilt.x = *values / -pen_tilt_x_range[0];4891} else if (pen_tilt_x_range[1] != 0) {4892xi.tilt.x = *values / pen_tilt_x_range[1];4893}4894}48954896values++;4897}48984899if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_TILTY)) {4900HashMap<int, Vector2>::Iterator pen_tilt_y = xi.pen_tilt_y_range.find(device_id);4901if (pen_tilt_y) {4902Vector2 pen_tilt_y_range = pen_tilt_y->value;4903if (pen_tilt_y_range[0] != 0 && *values < 0) {4904xi.tilt.y = *values / -pen_tilt_y_range[0];4905} else if (pen_tilt_y_range[1] != 0) {4906xi.tilt.y = *values / pen_tilt_y_range[1];4907}4908}49094910values++;4911}49124913HashMap<int, bool>::Iterator pen_inverted = xi.pen_inverted_devices.find(device_id);4914if (pen_inverted) {4915xi.pen_inverted = pen_inverted->value;4916}49174918// https://bugs.freedesktop.org/show_bug.cgi?id=716094919// http://lists.libsdl.org/pipermail/commits-libsdl.org/2015-June/000282.html4920if (raw_event->time == xi.last_relative_time && rel_x == xi.relative_motion.x && rel_y == xi.relative_motion.y) {4921break; // Flush duplicate to avoid overly fast motion4922}49234924xi.old_raw_pos.x = xi.raw_pos.x;4925xi.old_raw_pos.y = xi.raw_pos.y;4926xi.raw_pos.x = rel_x;4927xi.raw_pos.y = rel_y;49284929HashMap<int, Vector2>::Iterator abs_info = xi.absolute_devices.find(device_id);49304931if (abs_info) {4932// Absolute mode device4933Vector2 mult = abs_info->value;49344935xi.relative_motion.x += (xi.raw_pos.x - xi.old_raw_pos.x) * mult.x;4936xi.relative_motion.y += (xi.raw_pos.y - xi.old_raw_pos.y) * mult.y;4937} else {4938// Relative mode device4939xi.relative_motion.x = xi.raw_pos.x;4940xi.relative_motion.y = xi.raw_pos.y;4941}49424943xi.last_relative_time = raw_event->time;4944} break;4945#ifdef TOUCH_ENABLED4946case XI_TouchBegin:4947case XI_TouchEnd: {4948if (ime_window_event || ignore_events) {4949break;4950}4951bool is_begin = event_data->evtype == XI_TouchBegin;49524953int index = event_data->detail;4954Vector2 pos = Vector2(event_data->event_x, event_data->event_y);49554956Ref<InputEventScreenTouch> st;4957st.instantiate();4958st->set_window_id(window_id);4959st->set_index(index);4960st->set_position(pos);4961st->set_pressed(is_begin);49624963if (is_begin) {4964if (xi.state.has(index)) { // Defensive4965break;4966}4967xi.state[index] = pos;4968if (xi.state.size() == 1) {4969// X11 may send a motion event when a touch gesture begins, that would result4970// in a spurious mouse motion event being sent to Godot; remember it to be able to filter it out4971xi.mouse_pos_to_filter = pos;4972}4973Input::get_singleton()->parse_input_event(st);4974} else {4975if (!xi.state.has(index)) { // Defensive4976break;4977}4978xi.state.erase(index);4979Input::get_singleton()->parse_input_event(st);4980}4981} break;49824983case XI_TouchUpdate: {4984if (ime_window_event || ignore_events) {4985break;4986}49874988int index = event_data->detail;4989Vector2 pos = Vector2(event_data->event_x, event_data->event_y);49904991HashMap<int, Vector2>::Iterator curr_pos_elem = xi.state.find(index);4992if (!curr_pos_elem) { // Defensive4993break;4994}49954996if (curr_pos_elem->value != pos) {4997Ref<InputEventScreenDrag> sd;4998sd.instantiate();4999sd->set_window_id(window_id);5000sd->set_index(index);5001sd->set_position(pos);5002sd->set_relative(pos - curr_pos_elem->value);5003sd->set_relative_screen_position(sd->get_relative());5004Input::get_singleton()->parse_input_event(sd);50055006curr_pos_elem->value = pos;5007}5008} break;5009#endif5010}5011}5012}5013XFreeEventData(x11_display, &event.xcookie);50145015switch (event.type) {5016case MapNotify: {5017DEBUG_LOG_X11("[%u] MapNotify window=%lu (%u) \n", frame, event.xmap.window, window_id);5018if (ime_window_event) {5019break;5020}50215022const WindowData &wd = windows[window_id];50235024XWindowAttributes xwa;5025XSync(x11_display, False);5026XGetWindowAttributes(x11_display, wd.x11_window, &xwa);50275028_update_actions_hints(window_id);5029XFlush(x11_display);50305031// Set focus when menu window is started.5032// RevertToPointerRoot is used to make sure we don't lose all focus in case5033// a subwindow and its parent are both destroyed.5034if ((xwa.map_state == IsViewable) && !wd.no_focus && !wd.is_popup && _window_focus_check()) {5035_set_input_focus(wd.x11_window, RevertToPointerRoot);5036}50375038// Have we failed to set fullscreen while the window was unmapped?5039_validate_mode_on_map(window_id);50405041// On KDE Plasma, when the parent window of an embedded process is restored after being minimized,5042// only the embedded window receives the Map notification, causing it to5043// appear without its parent.5044if (wd.embed_parent) {5045XMapWindow(x11_display, wd.embed_parent);5046}5047} break;50485049case Expose: {5050DEBUG_LOG_X11("[%u] Expose window=%lu (%u), count='%u' \n", frame, event.xexpose.window, window_id, event.xexpose.count);5051if (ime_window_event) {5052break;5053}50545055windows[window_id].fullscreen = _window_fullscreen_check(window_id);50565057Main::force_redraw();5058} break;50595060case NoExpose: {5061DEBUG_LOG_X11("[%u] NoExpose drawable=%lu (%u) \n", frame, event.xnoexpose.drawable, window_id);5062if (ime_window_event) {5063break;5064}50655066windows[window_id].minimized = true;5067} break;50685069case VisibilityNotify: {5070DEBUG_LOG_X11("[%u] VisibilityNotify window=%lu (%u), state=%u \n", frame, event.xvisibility.window, window_id, event.xvisibility.state);5071if (ime_window_event) {5072break;5073}50745075windows[window_id].minimized = _window_minimize_check(window_id);5076} break;50775078case LeaveNotify: {5079DEBUG_LOG_X11("[%u] LeaveNotify window=%lu (%u), mode='%u' \n", frame, event.xcrossing.window, window_id, event.xcrossing.mode);5080if (ime_window_event) {5081break;5082}50835084if (!mouse_mode_grab && window_mouseover_id == window_id) {5085window_mouseover_id = INVALID_WINDOW_ID;5086_send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_EXIT);5087}50885089} break;50905091case EnterNotify: {5092DEBUG_LOG_X11("[%u] EnterNotify window=%lu (%u), mode='%u' \n", frame, event.xcrossing.window, window_id, event.xcrossing.mode);5093if (ime_window_event) {5094break;5095}50965097if (!mouse_mode_grab && window_mouseover_id != window_id) {5098if (window_mouseover_id != INVALID_WINDOW_ID) {5099_send_window_event(windows[window_mouseover_id], WINDOW_EVENT_MOUSE_EXIT);5100}5101window_mouseover_id = window_id;5102_send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_ENTER);5103}5104} break;51055106case FocusIn: {5107DEBUG_LOG_X11("[%u] FocusIn window=%lu (%u), mode='%u' \n", frame, event.xfocus.window, window_id, event.xfocus.mode);5108if (ime_window_event || (event.xfocus.detail == NotifyInferior)) {5109break;5110}51115112WindowData &wd = windows[window_id];5113last_focused_window = window_id;5114wd.focused = true;51155116// Keep track of focus order for overlapping windows.5117static unsigned int focus_order = 0;5118wd.focus_order = ++focus_order;51195120#ifdef ACCESSKIT_ENABLED5121if (accessibility_driver) {5122accessibility_driver->accessibility_set_window_focused(window_id, true);5123}5124#endif5125_send_window_event(wd, WINDOW_EVENT_FOCUS_IN);51265127if (mouse_mode_grab) {5128// Show and update the cursor if confined and the window regained focus.51295130for (const KeyValue<WindowID, WindowData> &E : windows) {5131if (mouse_mode == MOUSE_MODE_CONFINED) {5132XUndefineCursor(x11_display, E.value.x11_window);5133} else if (mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN) { // Or re-hide it.5134XDefineCursor(x11_display, E.value.x11_window, null_cursor);5135}51365137XGrabPointer(5138x11_display, E.value.x11_window, True,5139ButtonPressMask | ButtonReleaseMask | PointerMotionMask,5140GrabModeAsync, GrabModeAsync, E.value.x11_window, None, CurrentTime);5141}5142}5143#ifdef TOUCH_ENABLED5144// Grab touch devices to avoid OS gesture interference5145/*for (int i = 0; i < xi.touch_devices.size(); ++i) {5146XIGrabDevice(x11_display, xi.touch_devices[i], x11_window, CurrentTime, None, XIGrabModeAsync, XIGrabModeAsync, False, &xi.touch_event_mask);5147}*/5148#endif51495150if (!app_focused) {5151if (OS::get_singleton()->get_main_loop()) {5152OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN);5153}5154app_focused = true;5155}5156} break;51575158case FocusOut: {5159DEBUG_LOG_X11("[%u] FocusOut window=%lu (%u), mode='%u' \n", frame, event.xfocus.window, window_id, event.xfocus.mode);5160WindowData &wd = windows[window_id];5161if (ime_window_event || (event.xfocus.detail == NotifyInferior)) {5162break;5163}5164if (wd.ime_active) {5165MutexLock mutex_lock(events_mutex);5166XUnsetICFocus(wd.xic);5167XUnmapWindow(x11_display, wd.x11_xim_window);5168wd.ime_active = false;5169im_text = String();5170im_selection = Vector2i();5171OS_Unix::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE);5172}5173wd.focused = false;51745175Input::get_singleton()->release_pressed_events();5176#ifdef ACCESSKIT_ENABLED5177if (accessibility_driver) {5178accessibility_driver->accessibility_set_window_focused(window_id, false);5179}5180#endif5181_send_window_event(wd, WINDOW_EVENT_FOCUS_OUT);51825183if (mouse_mode_grab) {5184for (const KeyValue<WindowID, WindowData> &E : windows) {5185//dear X11, I try, I really try, but you never work, you do whatever you want.5186if (mouse_mode == MOUSE_MODE_CAPTURED) {5187// Show the cursor if we're in captured mode so it doesn't look weird.5188XUndefineCursor(x11_display, E.value.x11_window);5189}5190}5191XUngrabPointer(x11_display, CurrentTime);5192}5193#ifdef TOUCH_ENABLED5194// Ungrab touch devices so input works as usual while we are unfocused5195/*for (int i = 0; i < xi.touch_devices.size(); ++i) {5196XIUngrabDevice(x11_display, xi.touch_devices[i], CurrentTime);5197}*/51985199// Release every pointer to avoid sticky points5200for (const KeyValue<int, Vector2> &E : xi.state) {5201Ref<InputEventScreenTouch> st;5202st.instantiate();5203st->set_index(E.key);5204st->set_window_id(window_id);5205st->set_position(E.value);5206Input::get_singleton()->parse_input_event(st);5207}5208xi.state.clear();5209#endif5210} break;52115212case ConfigureNotify: {5213DEBUG_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);5214if (event.xconfigure.window == windows[window_id].x11_xim_window) {5215break;5216}52175218_window_changed(&event);5219} break;52205221case ButtonPress:5222case ButtonRelease: {5223if (ime_window_event || ignore_events) {5224break;5225}5226/* exit in case of a mouse button press */5227last_timestamp = event.xbutton.time;5228if (mouse_mode == MOUSE_MODE_CAPTURED) {5229event.xbutton.x = last_mouse_pos.x;5230event.xbutton.y = last_mouse_pos.y;5231}52325233Ref<InputEventMouseButton> mb;5234mb.instantiate();52355236mb->set_window_id(window_id);5237_get_key_modifier_state(event.xbutton.state, mb);5238mb->set_button_index((MouseButton)event.xbutton.button);5239if (mb->get_button_index() == MouseButton::RIGHT) {5240mb->set_button_index(MouseButton::MIDDLE);5241} else if (mb->get_button_index() == MouseButton::MIDDLE) {5242mb->set_button_index(MouseButton::RIGHT);5243}5244mb->set_position(Vector2(event.xbutton.x, event.xbutton.y));5245mb->set_global_position(mb->get_position());52465247mb->set_pressed((event.type == ButtonPress));52485249if (mb->is_pressed() && mb->get_button_index() >= MouseButton::WHEEL_UP && mb->get_button_index() <= MouseButton::WHEEL_RIGHT) {5250MouseButtonMask mask = mouse_button_to_mask(mb->get_button_index());5251BitField<MouseButtonMask> scroll_mask = mouse_get_button_state();5252scroll_mask.set_flag(mask);5253mb->set_button_mask(scroll_mask);5254} else {5255mb->set_button_mask(mouse_get_button_state());5256}52575258const WindowData &wd = windows[window_id];52595260if (event.type == ButtonPress) {5261DEBUG_LOG_X11("[%u] ButtonPress window=%lu (%u), button_index=%u \n", frame, event.xbutton.window, window_id, mb->get_button_index());52625263// Ensure window focus on click.5264// RevertToPointerRoot is used to make sure we don't lose all focus in case5265// a subwindow and its parent are both destroyed.5266if (!wd.no_focus && !wd.is_popup) {5267_set_input_focus(wd.x11_window, RevertToPointerRoot);5268}52695270uint64_t diff = OS::get_singleton()->get_ticks_usec() / 1000 - last_click_ms;52715272if (mb->get_button_index() == last_click_button_index) {5273if (diff < 400 && Vector2(last_click_pos).distance_to(Vector2(event.xbutton.x, event.xbutton.y)) < 5) {5274last_click_ms = 0;5275last_click_pos = Point2i(-100, -100);5276last_click_button_index = MouseButton::NONE;5277mb->set_double_click(true);5278}52795280} else if (mb->get_button_index() < MouseButton::WHEEL_UP || mb->get_button_index() > MouseButton::WHEEL_RIGHT) {5281last_click_button_index = mb->get_button_index();5282}52835284if (!mb->is_double_click()) {5285last_click_ms += diff;5286last_click_pos = Point2i(event.xbutton.x, event.xbutton.y);5287}5288} else {5289DEBUG_LOG_X11("[%u] ButtonRelease window=%lu (%u), button_index=%u \n", frame, event.xbutton.window, window_id, mb->get_button_index());52905291WindowID window_id_other = INVALID_WINDOW_ID;5292Window wd_other_x11_window;5293if (!wd.focused) {5294// Propagate the event to the focused window,5295// because it's received only on the topmost window.5296// Note: This is needed for drag & drop to work between windows,5297// because the engine expects events to keep being processed5298// on the same window dragging started.5299for (const KeyValue<WindowID, WindowData> &E : windows) {5300if (E.value.focused) {5301if (E.key != window_id) {5302window_id_other = E.key;5303wd_other_x11_window = E.value.x11_window;5304}5305break;5306}5307}5308}53095310if (window_id_other != INVALID_WINDOW_ID) {5311int x, y;5312Window child;5313XTranslateCoordinates(x11_display, wd.x11_window, wd_other_x11_window, event.xbutton.x, event.xbutton.y, &x, &y, &child);53145315mb->set_window_id(window_id_other);5316mb->set_position(Vector2(x, y));5317mb->set_global_position(mb->get_position());5318}5319}53205321Input::get_singleton()->parse_input_event(mb);53225323} break;5324case MotionNotify: {5325if (ime_window_event || ignore_events) {5326break;5327}5328// The X11 API requires filtering one-by-one through the motion5329// notify events, in order to figure out which event is the one5330// generated by warping the mouse pointer.5331WindowID focused_window_id = _get_focused_window_or_popup();5332if (!windows.has(focused_window_id)) {5333focused_window_id = MAIN_WINDOW_ID;5334}53355336while (true) {5337if (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) {5338//this is likely the warp event since it was warped here5339center = Vector2(event.xmotion.x, event.xmotion.y);5340break;5341}53425343if (event_index + 1 < events.size()) {5344const XEvent &next_event = events[event_index + 1];5345if (next_event.type == MotionNotify) {5346++event_index;5347event = next_event;5348} else {5349break;5350}5351} else {5352break;5353}5354}53555356last_timestamp = event.xmotion.time;53575358// Motion is also simple.5359// A little hack is in order5360// to be able to send relative motion events.5361Point2i pos(event.xmotion.x, event.xmotion.y);53625363// Avoidance of spurious mouse motion (see handling of touch)5364bool filter = false;5365// Adding some tolerance to match better Point2i to Vector25366if (xi.state.size() && Vector2(pos).distance_squared_to(xi.mouse_pos_to_filter) < 2) {5367filter = true;5368}5369// Invalidate to avoid filtering a possible legitimate similar event coming later5370xi.mouse_pos_to_filter = Vector2(1e10, 1e10);5371if (filter) {5372break;5373}53745375const WindowData &wd = windows[window_id];5376bool focused = wd.focused;53775378if (mouse_mode == MOUSE_MODE_CAPTURED) {5379if (xi.relative_motion.x == 0 && xi.relative_motion.y == 0) {5380break;5381}53825383Point2i new_center = pos;5384pos = last_mouse_pos + xi.relative_motion;5385center = new_center;5386do_mouse_warp = focused; // warp the cursor if we're focused in5387}53885389if (!last_mouse_pos_valid) {5390last_mouse_pos = pos;5391last_mouse_pos_valid = true;5392}53935394// Hackish but relative mouse motion is already handled in the RawMotion event.5395// RawMotion does not provide the absolute mouse position (whereas MotionNotify does).5396// Therefore, RawMotion cannot be the authority on absolute mouse position.5397// RawMotion provides more precision than MotionNotify, which doesn't sense subpixel motion.5398// Therefore, MotionNotify cannot be the authority on relative mouse motion.5399// This means we need to take a combined approach...5400Point2i rel;54015402// Only use raw input if in capture mode. Otherwise use the classic behavior.5403if (mouse_mode == MOUSE_MODE_CAPTURED) {5404rel = xi.relative_motion;5405} else {5406rel = pos - last_mouse_pos;5407}54085409// Reset to prevent lingering motion5410xi.relative_motion.x = 0;5411xi.relative_motion.y = 0;5412if (mouse_mode == MOUSE_MODE_CAPTURED) {5413pos = Point2i(windows[focused_window_id].size.width / 2, windows[focused_window_id].size.height / 2);5414}54155416BitField<MouseButtonMask> last_button_state = MouseButtonMask::NONE;5417if (event.xmotion.state & Button1Mask) {5418last_button_state.set_flag(MouseButtonMask::LEFT);5419}5420if (event.xmotion.state & Button2Mask) {5421last_button_state.set_flag(MouseButtonMask::MIDDLE);5422}5423if (event.xmotion.state & Button3Mask) {5424last_button_state.set_flag(MouseButtonMask::RIGHT);5425}5426if (event.xmotion.state & Button4Mask) {5427last_button_state.set_flag(MouseButtonMask::MB_XBUTTON1);5428}5429if (event.xmotion.state & Button5Mask) {5430last_button_state.set_flag(MouseButtonMask::MB_XBUTTON2);5431}54325433Ref<InputEventMouseMotion> mm;5434mm.instantiate();54355436mm->set_window_id(window_id);5437if (xi.pressure_supported) {5438mm->set_pressure(xi.pressure);5439} else {5440mm->set_pressure(bool(last_button_state.has_flag(MouseButtonMask::LEFT)) ? 1.0f : 0.0f);5441}5442mm->set_tilt(xi.tilt);5443mm->set_pen_inverted(xi.pen_inverted);54445445_get_key_modifier_state(event.xmotion.state, mm);5446mm->set_button_mask(last_button_state);5447mm->set_position(pos);5448mm->set_global_position(pos);5449mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity());5450mm->set_screen_velocity(mm->get_velocity());54515452mm->set_relative(rel);5453mm->set_relative_screen_position(rel);54545455last_mouse_pos = pos;54565457// printf("rel: %d,%d\n", rel.x, rel.y );5458// Don't propagate the motion event unless we have focus5459// this is so that the relative motion doesn't get messed up5460// after we regain focus.5461// Adjusted to parse the input event if the window is not focused allowing mouse hovering on the editor5462// the embedding process has focus.5463if (!focused) {5464// Propagate the event to the focused window,5465// because it's received only on the topmost window.5466// Note: This is needed for drag & drop to work between windows,5467// because the engine expects events to keep being processed5468// on the same window dragging started.5469for (const KeyValue<WindowID, WindowData> &E : windows) {5470const WindowData &wd_other = E.value;5471if (wd_other.focused) {5472int x, y;5473Window child;5474XTranslateCoordinates(x11_display, wd.x11_window, wd_other.x11_window, event.xmotion.x, event.xmotion.y, &x, &y, &child);54755476Point2i pos_focused(x, y);54775478mm->set_window_id(E.key);5479mm->set_position(pos_focused);5480mm->set_global_position(pos_focused);5481mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity());54825483break;5484}5485}5486}54875488Input::get_singleton()->parse_input_event(mm);54895490} break;5491case KeyPress:5492case KeyRelease: {5493if (ignore_events) {5494break;5495}5496#ifdef DISPLAY_SERVER_X11_DEBUG_LOGS_ENABLED5497if (event.type == KeyPress) {5498DEBUG_LOG_X11("[%u] KeyPress window=%lu (%u), keycode=%u, time=%lu \n", frame, event.xkey.window, window_id, event.xkey.keycode, event.xkey.time);5499} else {5500DEBUG_LOG_X11("[%u] KeyRelease window=%lu (%u), keycode=%u, time=%lu \n", frame, event.xkey.window, window_id, event.xkey.keycode, event.xkey.time);5501}5502#endif5503last_timestamp = event.xkey.time;55045505// key event is a little complex, so5506// it will be handled in its own function.5507_handle_key_event(window_id, &event.xkey, events, event_index);5508} break;55095510case SelectionNotify:5511if (ime_window_event) {5512break;5513}5514if (event.xselection.target == requested) {5515Property p = _read_property(x11_display, windows[window_id].x11_window, XInternAtom(x11_display, "PRIMARY", 0));55165517Vector<String> files = String((char *)p.data).split("\r\n", false);5518XFree(p.data);5519for (int i = 0; i < files.size(); i++) {5520files.write[i] = files[i].replace("file://", "").uri_file_decode();5521}55225523if (windows[window_id].drop_files_callback.is_valid()) {5524Variant v_files = files;5525const Variant *v_args[1] = { &v_files };5526Variant ret;5527Callable::CallError ce;5528windows[window_id].drop_files_callback.callp((const Variant **)&v_args, 1, ret, ce);5529if (ce.error != Callable::CallError::CALL_OK) {5530ERR_PRINT(vformat("Failed to execute drop files callback: %s.", Variant::get_callable_error_text(windows[window_id].drop_files_callback, v_args, 1, ce)));5531}5532}55335534//Reply that all is well.5535XClientMessageEvent m;5536memset(&m, 0, sizeof(m));5537m.type = ClientMessage;5538m.display = x11_display;5539m.window = xdnd_source_window;5540m.message_type = xdnd_finished;5541m.format = 32;5542m.data.l[0] = windows[window_id].x11_window;5543m.data.l[1] = 1;5544m.data.l[2] = xdnd_action_copy; //We only ever copy.55455546XSendEvent(x11_display, xdnd_source_window, False, NoEventMask, (XEvent *)&m);5547}5548break;55495550case ClientMessage:5551if (ime_window_event) {5552break;5553}5554if ((unsigned int)event.xclient.data.l[0] == (unsigned int)wm_delete) {5555_send_window_event(windows[window_id], WINDOW_EVENT_CLOSE_REQUEST);5556}55575558else if ((unsigned int)event.xclient.message_type == (unsigned int)xdnd_enter) {5559//File(s) have been dragged over the window, check for supported target (text/uri-list)5560xdnd_version = (event.xclient.data.l[1] >> 24);5561Window source = event.xclient.data.l[0];5562bool more_than_3 = event.xclient.data.l[1] & 1;5563if (more_than_3) {5564Property p = _read_property(x11_display, source, XInternAtom(x11_display, "XdndTypeList", False));5565requested = pick_target_from_list(x11_display, (Atom *)p.data, p.nitems);5566XFree(p.data);5567} else {5568requested = pick_target_from_atoms(x11_display, event.xclient.data.l[2], event.xclient.data.l[3], event.xclient.data.l[4]);5569}5570} else if ((unsigned int)event.xclient.message_type == (unsigned int)xdnd_position) {5571//xdnd position event, reply with an XDND status message5572//just depending on type of data for now5573XClientMessageEvent m;5574memset(&m, 0, sizeof(m));5575m.type = ClientMessage;5576m.display = event.xclient.display;5577m.window = event.xclient.data.l[0];5578m.message_type = xdnd_status;5579m.format = 32;5580m.data.l[0] = windows[window_id].x11_window;5581m.data.l[1] = (requested != None);5582m.data.l[2] = 0; //empty rectangle5583m.data.l[3] = 0;5584m.data.l[4] = xdnd_action_copy;55855586XSendEvent(x11_display, event.xclient.data.l[0], False, NoEventMask, (XEvent *)&m);5587XFlush(x11_display);5588} else if ((unsigned int)event.xclient.message_type == (unsigned int)xdnd_drop) {5589if (requested != None) {5590xdnd_source_window = event.xclient.data.l[0];5591if (xdnd_version >= 1) {5592XConvertSelection(x11_display, xdnd_selection, requested, XInternAtom(x11_display, "PRIMARY", 0), windows[window_id].x11_window, event.xclient.data.l[2]);5593} else {5594XConvertSelection(x11_display, xdnd_selection, requested, XInternAtom(x11_display, "PRIMARY", 0), windows[window_id].x11_window, CurrentTime);5595}5596} else {5597//Reply that we're not interested.5598XClientMessageEvent m;5599memset(&m, 0, sizeof(m));5600m.type = ClientMessage;5601m.display = event.xclient.display;5602m.window = event.xclient.data.l[0];5603m.message_type = xdnd_finished;5604m.format = 32;5605m.data.l[0] = windows[window_id].x11_window;5606m.data.l[1] = 0;5607m.data.l[2] = None; //Failed.5608XSendEvent(x11_display, event.xclient.data.l[0], False, NoEventMask, (XEvent *)&m);5609}5610}5611break;5612default:5613break;5614}5615}56165617XFlush(x11_display);56185619if (do_mouse_warp) {5620XWarpPointer(x11_display, None, windows[MAIN_WINDOW_ID].x11_window,56210, 0, 0, 0, (int)windows[MAIN_WINDOW_ID].size.width / 2, (int)windows[MAIN_WINDOW_ID].size.height / 2);56225623/*5624Window root, child;5625int root_x, root_y;5626int win_x, win_y;5627unsigned int mask;5628XQueryPointer( x11_display, x11_window, &root, &child, &root_x, &root_y, &win_x, &win_y, &mask );56295630printf("Root: %d,%d\n", root_x, root_y);5631printf("Win: %d,%d\n", win_x, win_y);5632*/5633}56345635#ifdef DBUS_ENABLED5636if (portal_desktop) {5637portal_desktop->process_callbacks();5638}5639#endif56405641_THREAD_SAFE_UNLOCK_56425643Input::get_singleton()->flush_buffered_events();5644}56455646void DisplayServerX11::release_rendering_thread() {5647#if defined(GLES3_ENABLED)5648if (gl_manager) {5649gl_manager->release_current();5650}5651if (gl_manager_egl) {5652gl_manager_egl->release_current();5653}5654#endif5655}56565657void DisplayServerX11::swap_buffers() {5658#if defined(GLES3_ENABLED)5659if (gl_manager) {5660gl_manager->swap_buffers();5661}5662if (gl_manager_egl) {5663gl_manager_egl->swap_buffers();5664}5665#endif5666}56675668void DisplayServerX11::_update_context(WindowData &wd) {5669XClassHint *classHint = XAllocClassHint();56705671if (classHint) {5672CharString name_str;5673switch (context) {5674case CONTEXT_EDITOR:5675name_str = "Godot_Editor";5676break;5677case CONTEXT_PROJECTMAN:5678name_str = "Godot_ProjectList";5679break;5680case CONTEXT_ENGINE:5681name_str = "Godot_Engine";5682break;5683}56845685CharString class_str;5686if (context == CONTEXT_ENGINE) {5687String config_name = GLOBAL_GET("application/config/name");5688if (config_name.length() == 0) {5689class_str = "Godot_Engine";5690} else {5691class_str = config_name.utf8();5692}5693} else {5694class_str = "Godot";5695}56965697classHint->res_class = class_str.ptrw();5698classHint->res_name = name_str.ptrw();56995700XSetClassHint(x11_display, wd.x11_window, classHint);5701XFree(classHint);5702}5703}57045705void DisplayServerX11::set_context(Context p_context) {5706_THREAD_SAFE_METHOD_57075708context = p_context;57095710for (KeyValue<WindowID, WindowData> &E : windows) {5711_update_context(E.value);5712}5713}57145715bool DisplayServerX11::is_window_transparency_available() const {5716CharString net_wm_cm_name = vformat("_NET_WM_CM_S%d", XDefaultScreen(x11_display)).ascii();5717Atom net_wm_cm = XInternAtom(x11_display, net_wm_cm_name.get_data(), False);5718if (net_wm_cm == None) {5719return false;5720}5721if (XGetSelectionOwner(x11_display, net_wm_cm) == None) {5722return false;5723}5724#if defined(RD_ENABLED)5725if (rendering_device && !rendering_device->is_composite_alpha_supported()) {5726return false;5727}5728#endif5729return OS::get_singleton()->is_layered_allowed();5730}57315732void DisplayServerX11::set_native_icon(const String &p_filename) {5733WARN_PRINT("Native icon not supported by this display server.");5734}57355736bool g_set_icon_error = false;5737int set_icon_errorhandler(Display *dpy, XErrorEvent *ev) {5738g_set_icon_error = true;5739return 0;5740}57415742void DisplayServerX11::set_icon(const Ref<Image> &p_icon) {5743_THREAD_SAFE_METHOD_57445745WindowData &wd = windows[MAIN_WINDOW_ID];57465747int (*oldHandler)(Display *, XErrorEvent *) = XSetErrorHandler(&set_icon_errorhandler);57485749Atom net_wm_icon = XInternAtom(x11_display, "_NET_WM_ICON", False);57505751if (p_icon.is_valid()) {5752ERR_FAIL_COND(p_icon->get_width() <= 0 || p_icon->get_height() <= 0);57535754Ref<Image> img = p_icon->duplicate();5755img->convert(Image::FORMAT_RGBA8);57565757while (true) {5758int w = img->get_width();5759int h = img->get_height();57605761if (g_set_icon_error) {5762g_set_icon_error = false;57635764WARN_PRINT(vformat("Icon too large (%dx%d), attempting to downscale icon.", w, h));57655766int new_width, new_height;5767if (w > h) {5768new_width = w / 2;5769new_height = h * new_width / w;5770} else {5771new_height = h / 2;5772new_width = w * new_height / h;5773}57745775w = new_width;5776h = new_height;57775778if (!w || !h) {5779WARN_PRINT("Unable to set icon.");5780break;5781}57825783img->resize(w, h, Image::INTERPOLATE_CUBIC);5784}57855786// We're using long to have wordsize (32Bit build -> 32 Bits, 64 Bit build -> 64 Bits5787Vector<long> pd;57885789pd.resize(2 + w * h);57905791pd.write[0] = w;5792pd.write[1] = h;57935794const uint8_t *r = img->get_data().ptr();57955796long *wr = &pd.write[2];5797uint8_t const *pr = r;57985799for (int i = 0; i < w * h; i++) {5800long v = 0;5801// A R G B5802v |= pr[3] << 24 | pr[0] << 16 | pr[1] << 8 | pr[2];5803*wr++ = v;5804pr += 4;5805}58065807if (net_wm_icon != None) {5808XChangeProperty(x11_display, wd.x11_window, net_wm_icon, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)pd.ptr(), pd.size());5809}58105811if (!g_set_icon_error) {5812break;5813}5814}5815} else {5816XDeleteProperty(x11_display, wd.x11_window, net_wm_icon);5817}58185819XFlush(x11_display);5820XSetErrorHandler(oldHandler);5821}58225823void DisplayServerX11::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window) {5824_THREAD_SAFE_METHOD_5825#if defined(RD_ENABLED)5826if (rendering_context) {5827rendering_context->window_set_vsync_mode(p_window, p_vsync_mode);5828}5829#endif58305831#if defined(GLES3_ENABLED)5832if (gl_manager) {5833gl_manager->set_use_vsync(p_vsync_mode != DisplayServer::VSYNC_DISABLED);5834}5835if (gl_manager_egl) {5836gl_manager_egl->set_use_vsync(p_vsync_mode != DisplayServer::VSYNC_DISABLED);5837}5838#endif5839}58405841DisplayServer::VSyncMode DisplayServerX11::window_get_vsync_mode(WindowID p_window) const {5842_THREAD_SAFE_METHOD_5843#if defined(RD_ENABLED)5844if (rendering_context) {5845return rendering_context->window_get_vsync_mode(p_window);5846}5847#endif5848#if defined(GLES3_ENABLED)5849if (gl_manager) {5850return gl_manager->is_using_vsync() ? DisplayServer::VSYNC_ENABLED : DisplayServer::VSYNC_DISABLED;5851}5852if (gl_manager_egl) {5853return gl_manager_egl->is_using_vsync() ? DisplayServer::VSYNC_ENABLED : DisplayServer::VSYNC_DISABLED;5854}5855#endif5856return DisplayServer::VSYNC_ENABLED;5857}58585859void DisplayServerX11::window_start_drag(WindowID p_window) {5860_THREAD_SAFE_METHOD_58615862ERR_FAIL_COND(!windows.has(p_window));5863WindowData &wd = windows[p_window];58645865if (wd.embed_parent) {5866return; // Embedded window.5867}58685869XClientMessageEvent m;5870memset(&m, 0, sizeof(m));58715872XUngrabPointer(x11_display, CurrentTime);58735874Window root_return, child_return;5875int root_x, root_y, win_x, win_y;5876unsigned int mask_return;58775878Bool xquerypointer_result = XQueryPointer(x11_display, wd.x11_window, &root_return, &child_return, &root_x, &root_y, &win_x, &win_y, &mask_return);58795880m.type = ClientMessage;5881m.window = wd.x11_window;5882m.message_type = XInternAtom(x11_display, "_NET_WM_MOVERESIZE", True);5883m.format = 32;5884if (xquerypointer_result) {5885m.data.l[0] = root_x;5886m.data.l[1] = root_y;5887m.data.l[3] = Button1;5888}5889m.data.l[2] = _NET_WM_MOVERESIZE_MOVE;5890m.data.l[4] = 1; // Source - normal application.58915892XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent *)&m);58935894XSync(x11_display, 0);5895}58965897void DisplayServerX11::window_start_resize(WindowResizeEdge p_edge, WindowID p_window) {5898_THREAD_SAFE_METHOD_58995900ERR_FAIL_INDEX(int(p_edge), WINDOW_EDGE_MAX);59015902ERR_FAIL_COND(!windows.has(p_window));5903WindowData &wd = windows[p_window];59045905if (wd.embed_parent) {5906return; // Embedded window.5907}59085909XClientMessageEvent m;5910memset(&m, 0, sizeof(m));59115912XUngrabPointer(x11_display, CurrentTime);59135914Window root_return, child_return;5915int root_x, root_y, win_x, win_y;5916unsigned int mask_return;59175918Bool xquerypointer_result = XQueryPointer(x11_display, wd.x11_window, &root_return, &child_return, &root_x, &root_y, &win_x, &win_y, &mask_return);59195920m.type = ClientMessage;5921m.window = wd.x11_window;5922m.message_type = XInternAtom(x11_display, "_NET_WM_MOVERESIZE", True);5923m.format = 32;5924if (xquerypointer_result) {5925m.data.l[0] = root_x;5926m.data.l[1] = root_y;5927m.data.l[3] = Button1;5928}59295930switch (p_edge) {5931case DisplayServer::WINDOW_EDGE_TOP_LEFT: {5932m.data.l[2] = _NET_WM_MOVERESIZE_SIZE_TOPLEFT;5933} break;5934case DisplayServer::WINDOW_EDGE_TOP: {5935m.data.l[2] = _NET_WM_MOVERESIZE_SIZE_TOP;5936} break;5937case DisplayServer::WINDOW_EDGE_TOP_RIGHT: {5938m.data.l[2] = _NET_WM_MOVERESIZE_SIZE_TOPRIGHT;5939} break;5940case DisplayServer::WINDOW_EDGE_LEFT: {5941m.data.l[2] = _NET_WM_MOVERESIZE_SIZE_LEFT;5942} break;5943case DisplayServer::WINDOW_EDGE_RIGHT: {5944m.data.l[2] = _NET_WM_MOVERESIZE_SIZE_RIGHT;5945} break;5946case DisplayServer::WINDOW_EDGE_BOTTOM_LEFT: {5947m.data.l[2] = _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT;5948} break;5949case DisplayServer::WINDOW_EDGE_BOTTOM: {5950m.data.l[2] = _NET_WM_MOVERESIZE_SIZE_BOTTOM;5951} break;5952case DisplayServer::WINDOW_EDGE_BOTTOM_RIGHT: {5953m.data.l[2] = _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT;5954} break;5955default:5956break;5957}5958m.data.l[4] = 1; // Source - normal application.59595960XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent *)&m);59615962XSync(x11_display, 0);5963}59645965pid_t get_window_pid(Display *p_display, Window p_window) {5966Atom atom = XInternAtom(p_display, "_NET_WM_PID", False);5967Atom actualType;5968int actualFormat;5969unsigned long nItems, bytesAfter;5970unsigned char *prop = nullptr;5971if (XGetWindowProperty(p_display, p_window, atom, 0, sizeof(pid_t), False, AnyPropertyType,5972&actualType, &actualFormat, &nItems, &bytesAfter, &prop) == Success) {5973if (nItems > 0) {5974pid_t pid = *(pid_t *)prop;5975XFree(prop);5976return pid;5977}5978}59795980return 0; // PID not found.5981}59825983Window find_window_from_process_id_internal(Display *p_display, pid_t p_process_id, Window p_window) {5984Window dummy;5985Window *children;5986unsigned int num_children;59875988if (!XQueryTree(p_display, p_window, &dummy, &dummy, &children, &num_children)) {5989return 0;5990}59915992for (unsigned int i = 0; i < num_children; i++) {5993const Window child = children[i];5994if (get_window_pid(p_display, child) == p_process_id) {5995XFree(children);5996return child;5997}5998}59996000// Then check children of children.6001for (unsigned int i = 0; i < num_children; i++) {6002Window wnd = find_window_from_process_id_internal(p_display, p_process_id, children[i]);6003if (wnd != 0) {6004XFree(children);6005return wnd;6006}6007}60086009if (children) {6010XFree(children);6011}60126013return 0;6014}60156016Window find_window_from_process_id(Display *p_display, pid_t p_process_id) {6017// Handle bad window errors silently because while looping6018// windows can be destroyed, resulting in BadWindow errors.6019int (*oldHandler)(Display *, XErrorEvent *) = XSetErrorHandler(&bad_window_error_handler);60206021const int screencount = XScreenCount(p_display);6022Window process_window = 0;60236024for (int screen_index = 0; screen_index < screencount; screen_index++) {6025Window root = RootWindow(p_display, screen_index);60266027Window wnd = find_window_from_process_id_internal(p_display, p_process_id, root);60286029if (wnd != 0) {6030process_window = wnd;6031break;6032}6033}60346035// Restore default error handler.6036XSetErrorHandler(oldHandler);60376038return process_window;6039}60406041Point2i DisplayServerX11::_get_window_position(Window p_window) const {6042int x = 0, y = 0;6043Window child;6044XTranslateCoordinates(x11_display, p_window, DefaultRootWindow(x11_display), 0, 0, &x, &y, &child);6045return Point2i(x, y);6046}60476048Rect2i DisplayServerX11::_get_window_rect(Window p_window) const {6049XWindowAttributes xwa;6050XGetWindowAttributes(x11_display, p_window, &xwa);6051return Rect2i(xwa.x, xwa.y, xwa.width, xwa.height);6052}60536054void DisplayServerX11::_set_window_taskbar_pager_enabled(Window p_window, bool p_enabled) {6055Atom wmState = XInternAtom(x11_display, "_NET_WM_STATE", False);6056Atom skipTaskbar = XInternAtom(x11_display, "_NET_WM_STATE_SKIP_TASKBAR", False);6057Atom skipPager = XInternAtom(x11_display, "_NET_WM_STATE_SKIP_PAGER", False);60586059XClientMessageEvent xev;6060memset(&xev, 0, sizeof(xev));6061xev.type = ClientMessage;6062xev.window = p_window;6063xev.message_type = wmState;6064xev.format = 32;6065xev.data.l[0] = p_enabled ? _NET_WM_STATE_REMOVE : _NET_WM_STATE_ADD; // When enabled, we must remove the skip.6066xev.data.l[1] = skipTaskbar;6067xev.data.l[2] = skipPager;6068xev.data.l[3] = 0;6069xev.data.l[4] = 0;60706071// Send the client message to the root window.6072XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent *)&xev);6073}60746075Error DisplayServerX11::embed_process(WindowID p_window, OS::ProcessID p_pid, const Rect2i &p_rect, bool p_visible, bool p_grab_focus) {6076_THREAD_SAFE_METHOD_60776078ERR_FAIL_COND_V(!windows.has(p_window), FAILED);60796080const WindowData &wd = windows[p_window];60816082DEBUG_LOG_X11("Starting embedding %ld to window %lu \n", p_pid, wd.x11_window);60836084EmbeddedProcessData *ep = nullptr;6085if (embedded_processes.has(p_pid)) {6086ep = embedded_processes.get(p_pid);6087} else {6088// New process, trying to find the window.6089Window process_window = find_window_from_process_id(x11_display, p_pid);6090if (!process_window) {6091return ERR_DOES_NOT_EXIST;6092}6093DEBUG_LOG_X11("Process %ld window found: %lu \n", p_pid, process_window);6094ep = memnew(EmbeddedProcessData);6095ep->process_window = process_window;6096ep->visible = true;6097XSetTransientForHint(x11_display, process_window, wd.x11_window);6098_set_window_taskbar_pager_enabled(process_window, false);6099embedded_processes.insert(p_pid, ep);6100}61016102// Handle bad window errors silently because just in case the embedded window was closed.6103int (*oldHandler)(Display *, XErrorEvent *) = XSetErrorHandler(&bad_window_error_handler);61046105if (p_visible) {6106// Resize and move the window to match the desired rectangle.6107// X11 does not allow moving the window entirely outside the screen boundaries.6108// To ensure the window remains visible, we will resize it to fit within both the screen and the specified rectangle.6109Rect2i desired_rect = p_rect;61106111// First resize the desired rect to fit inside all the screens without considering the6112// working area.6113Rect2i screens_full_rect = _screens_get_full_rect();6114Vector2i screens_full_end = screens_full_rect.get_end();6115if (desired_rect.position.x < screens_full_rect.position.x) {6116desired_rect.size.x = MAX(desired_rect.size.x - (screens_full_rect.position.x - desired_rect.position.x), 0);6117desired_rect.position.x = screens_full_rect.position.x;6118}6119if (desired_rect.position.x + desired_rect.size.x > screens_full_end.x) {6120desired_rect.size.x = MAX(screens_full_end.x - desired_rect.position.x, 0);6121}6122if (desired_rect.position.y < screens_full_rect.position.y) {6123desired_rect.size.y = MAX(desired_rect.size.y - (screens_full_rect.position.y - desired_rect.position.y), 0);6124desired_rect.position.y = screens_full_rect.position.y;6125}6126if (desired_rect.position.y + desired_rect.size.y > screens_full_end.y) {6127desired_rect.size.y = MAX(screens_full_end.y - desired_rect.position.y, 0);6128}61296130// Second, for each screen, check if the desired rectangle is within a portion of the screen6131// that is outside the working area. Each screen can have a different working area6132// depending on top, bottom, or side panels.6133int desired_area = desired_rect.get_area();6134int count = get_screen_count();6135for (int i = 0; i < count; i++) {6136Rect2i screen_rect = _screen_get_rect(i);6137if (screen_rect.intersection(desired_rect).get_area() == 0) {6138continue;6139}61406141// The desired rect is inside this screen.6142Rect2i screen_usable_rect = screen_get_usable_rect(i);6143int screen_usable_area = screen_usable_rect.intersection(desired_rect).get_area();6144if (screen_usable_area == desired_area) {6145// The desired rect is fulling inside the usable rect of the screen. No need to resize.6146continue;6147}61486149if (desired_rect.position.x >= screen_rect.position.x && desired_rect.position.x < screen_usable_rect.position.x) {6150int offset = screen_usable_rect.position.x - desired_rect.position.x;6151desired_rect.size.x = MAX(desired_rect.size.x - offset, 0);6152desired_rect.position.x += offset;6153}6154if (desired_rect.position.y >= screen_rect.position.y && desired_rect.position.y < screen_usable_rect.position.y) {6155int offset = screen_usable_rect.position.y - desired_rect.position.y;6156desired_rect.size.y = MAX(desired_rect.size.y - offset, 0);6157desired_rect.position.y += offset;6158}61596160Vector2i desired_end = desired_rect.get_end();6161Vector2i screen_end = screen_rect.get_end();6162Vector2i screen_usable_end = screen_usable_rect.get_end();6163if (desired_end.x > screen_usable_end.x && desired_end.x <= screen_end.x) {6164desired_rect.size.x = MAX(desired_rect.size.x - (desired_end.x - screen_usable_end.x), 0);6165}6166if (desired_end.y > screen_usable_end.y && desired_end.y <= screen_end.y) {6167desired_rect.size.y = MAX(desired_rect.size.y - (desired_end.y - screen_usable_end.y), 0);6168}6169}61706171if (desired_rect.size.x <= 100 || desired_rect.size.y <= 100) {6172p_visible = false;6173}61746175if (p_visible) {6176Rect2i current_process_window_rect = _get_window_rect(ep->process_window);6177if (current_process_window_rect != desired_rect) {6178DEBUG_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);6179XMoveResizeWindow(x11_display, ep->process_window, desired_rect.position.x, desired_rect.position.y, desired_rect.size.x, desired_rect.size.y);6180}6181}6182}61836184if (ep->visible != p_visible) {6185if (p_visible) {6186XMapWindow(x11_display, ep->process_window);6187} else {6188XUnmapWindow(x11_display, ep->process_window);6189}6190ep->visible = p_visible;6191}61926193if (p_grab_focus && p_visible) {6194Window focused_window = 0;6195int revert_to = 0;6196XGetInputFocus(x11_display, &focused_window, &revert_to);6197if (focused_window != ep->process_window) {6198// Be sure that the window is visible to prevent BadMatch error when calling XSetInputFocus on a not viewable window.6199XWindowAttributes attr;6200if (XGetWindowAttributes(x11_display, ep->process_window, &attr) && attr.map_state == IsViewable) {6201XSetInputFocus(x11_display, ep->process_window, RevertToParent, CurrentTime);6202}6203}6204}62056206// Restore default error handler.6207XSetErrorHandler(oldHandler);6208return OK;6209}62106211Error DisplayServerX11::request_close_embedded_process(OS::ProcessID p_pid) {6212_THREAD_SAFE_METHOD_62136214if (!embedded_processes.has(p_pid)) {6215return ERR_DOES_NOT_EXIST;6216}62176218EmbeddedProcessData *ep = embedded_processes.get(p_pid);62196220// Handle bad window errors silently because just in case the embedded window was closed.6221int (*oldHandler)(Display *, XErrorEvent *) = XSetErrorHandler(&bad_window_error_handler);62226223// Check if the window is still valid.6224XWindowAttributes attr;6225if (XGetWindowAttributes(x11_display, ep->process_window, &attr)) {6226// Send the message to gracefully close the window.6227XEvent ev;6228memset(&ev, 0, sizeof(ev));6229ev.xclient.type = ClientMessage;6230ev.xclient.window = ep->process_window;6231ev.xclient.message_type = XInternAtom(x11_display, "WM_PROTOCOLS", True);6232ev.xclient.format = 32;6233ev.xclient.data.l[0] = XInternAtom(x11_display, "WM_DELETE_WINDOW", False);6234ev.xclient.data.l[1] = CurrentTime;6235XSendEvent(x11_display, ep->process_window, False, NoEventMask, &ev);6236}62376238// Restore default error handler.6239XSetErrorHandler(oldHandler);62406241return OK;6242}62436244Error DisplayServerX11::remove_embedded_process(OS::ProcessID p_pid) {6245_THREAD_SAFE_METHOD_62466247if (!embedded_processes.has(p_pid)) {6248return ERR_DOES_NOT_EXIST;6249}62506251EmbeddedProcessData *ep = embedded_processes.get(p_pid);62526253request_close_embedded_process(p_pid);62546255embedded_processes.erase(p_pid);6256memdelete(ep);62576258return OK;6259}62606261OS::ProcessID DisplayServerX11::get_focused_process_id() {6262Window focused_window = 0;6263int revert_to = 0;62646265XGetInputFocus(x11_display, &focused_window, &revert_to);62666267if (focused_window == None) {6268return 0;6269}62706271return get_window_pid(x11_display, focused_window);6272}62736274Vector<String> DisplayServerX11::get_rendering_drivers_func() {6275Vector<String> drivers;62766277#ifdef VULKAN_ENABLED6278drivers.push_back("vulkan");6279#endif6280#ifdef GLES3_ENABLED6281drivers.push_back("opengl3");6282drivers.push_back("opengl3_es");6283#endif6284drivers.push_back("dummy");62856286return drivers;6287}62886289DisplayServer *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) {6290DisplayServer *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));6291return ds;6292}62936294DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect, Window p_parent_window) {6295//Create window62966297XVisualInfo visualInfo;6298bool vi_selected = false;62996300#ifdef GLES3_ENABLED6301if (gl_manager) {6302Error err;6303visualInfo = gl_manager->get_vi(x11_display, err);6304ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't acquire visual info from display.");6305vi_selected = true;6306}6307if (gl_manager_egl) {6308XVisualInfo visual_info_template;6309int visual_id = gl_manager_egl->display_get_native_visual_id(x11_display);6310ERR_FAIL_COND_V_MSG(visual_id < 0, INVALID_WINDOW_ID, "Unable to get a visual id.");63116312visual_info_template.visualid = (VisualID)visual_id;63136314int number_of_visuals = 0;6315XVisualInfo *vi_list = XGetVisualInfo(x11_display, VisualIDMask, &visual_info_template, &number_of_visuals);6316ERR_FAIL_COND_V(number_of_visuals <= 0, INVALID_WINDOW_ID);63176318visualInfo = vi_list[0];63196320XFree(vi_list);6321}6322#endif63236324if (!vi_selected) {6325long visualMask = VisualScreenMask;6326int numberOfVisuals;6327XVisualInfo vInfoTemplate = {};6328vInfoTemplate.screen = DefaultScreen(x11_display);6329XVisualInfo *vi_list = XGetVisualInfo(x11_display, visualMask, &vInfoTemplate, &numberOfVisuals);6330ERR_FAIL_NULL_V(vi_list, INVALID_WINDOW_ID);63316332visualInfo = vi_list[0];6333if (OS::get_singleton()->is_layered_allowed()) {6334for (int i = 0; i < numberOfVisuals; i++) {6335XRenderPictFormat *pict_format = XRenderFindVisualFormat(x11_display, vi_list[i].visual);6336if (!pict_format) {6337continue;6338}6339visualInfo = vi_list[i];6340if (pict_format->direct.alphaMask > 0) {6341break;6342}6343}6344}6345XFree(vi_list);6346}63476348Colormap colormap = XCreateColormap(x11_display, RootWindow(x11_display, visualInfo.screen), visualInfo.visual, AllocNone);63496350XSetWindowAttributes windowAttributes = {};6351windowAttributes.colormap = colormap;6352windowAttributes.background_pixel = 0xFFFFFFFF;6353windowAttributes.border_pixel = 0;6354windowAttributes.event_mask = KeyPressMask | KeyReleaseMask | StructureNotifyMask | ExposureMask;63556356unsigned long valuemask = CWBorderPixel | CWColormap | CWEventMask;63576358if (OS::get_singleton()->is_layered_allowed()) {6359windowAttributes.background_pixmap = None;6360windowAttributes.background_pixel = 0;6361windowAttributes.border_pixmap = None;6362valuemask |= CWBackPixel;6363}63646365WindowID id = window_id_counter++;6366WindowData &wd = windows[id];63676368if (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) {6369wd.no_focus = true;6370}63716372if (p_flags & WINDOW_FLAG_POPUP_BIT) {6373wd.is_popup = true;6374}63756376// Setup for menu subwindows:6377// - override_redirect forces the WM not to interfere with the window, to avoid delays due to6378// handling decorations and placement.6379// On the other hand, focus changes need to be handled manually when this is set.6380// - save_under is a hint for the WM to keep the content of windows behind to avoid repaint.6381if (wd.no_focus) {6382windowAttributes.override_redirect = True;6383windowAttributes.save_under = True;6384valuemask |= CWOverrideRedirect | CWSaveUnder;6385}63866387int rq_screen = get_screen_from_rect(p_rect);6388if (rq_screen < 0) {6389rq_screen = get_primary_screen(); // Requested window rect is outside any screen bounds.6390}63916392Rect2i win_rect = p_rect;6393if (!p_parent_window) {6394// No parent.6395if (p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) {6396Rect2i screen_rect = Rect2i(screen_get_position(rq_screen), screen_get_size(rq_screen));63976398win_rect = screen_rect;6399} else {6400Rect2i srect = screen_get_usable_rect(rq_screen);6401Point2i wpos = p_rect.position;6402wpos = wpos.clamp(srect.position, srect.position + srect.size - p_rect.size / 3);64036404win_rect.position = wpos;6405}6406}64076408// Position and size hints are set from these values before they are updated to the actual6409// window size, so we need to initialize them here.6410wd.position = win_rect.position;6411wd.size = win_rect.size;64126413{6414wd.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);64156416wd.parent = RootWindow(x11_display, visualInfo.screen);64176418DEBUG_LOG_X11("CreateWindow window=%lu, parent: %lu \n", wd.x11_window, wd.parent);64196420if (p_parent_window) {6421wd.embed_parent = p_parent_window;6422XSetTransientForHint(x11_display, wd.x11_window, p_parent_window);6423}64246425XSetWindowAttributes window_attributes_ime = {};6426window_attributes_ime.event_mask = KeyPressMask | KeyReleaseMask | StructureNotifyMask | ExposureMask;64276428wd.x11_xim_window = XCreateWindow(x11_display, wd.x11_window, 0, 0, 1, 1, 0, CopyFromParent, InputOnly, CopyFromParent, CWEventMask, &window_attributes_ime);6429#ifdef XKB_ENABLED6430if (dead_tbl && xkb_loaded_v05p) {6431wd.xkb_state = xkb_compose_state_new(dead_tbl, XKB_COMPOSE_STATE_NO_FLAGS);6432}6433#endif6434#ifdef ACCESSKIT_ENABLED6435if (accessibility_driver && !accessibility_driver->window_create(id, nullptr)) {6436if (OS::get_singleton()->is_stdout_verbose()) {6437ERR_PRINT("Can't create an accessibility adapter for window, accessibility support disabled!");6438}6439memdelete(accessibility_driver);6440accessibility_driver = nullptr;6441}6442#endif6443// Enable receiving notification when the window is initialized (MapNotify)6444// so the focus can be set at the right time.6445if (!wd.no_focus && !wd.is_popup) {6446XSelectInput(x11_display, wd.x11_window, StructureNotifyMask);6447}64486449//associate PID6450// make PID known to X116451{6452const long pid = OS::get_singleton()->get_process_id();6453Atom net_wm_pid = XInternAtom(x11_display, "_NET_WM_PID", False);6454if (net_wm_pid != None) {6455XChangeProperty(x11_display, wd.x11_window, net_wm_pid, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&pid, 1);6456}6457}64586459long im_event_mask = 0;64606461{6462XIEventMask all_event_mask;6463XSetWindowAttributes new_attr;64646465new_attr.event_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask |6466ButtonReleaseMask | EnterWindowMask |6467LeaveWindowMask | PointerMotionMask |6468Button1MotionMask |6469Button2MotionMask | Button3MotionMask |6470Button4MotionMask | Button5MotionMask |6471ButtonMotionMask | KeymapStateMask |6472ExposureMask | VisibilityChangeMask |6473StructureNotifyMask |6474SubstructureNotifyMask | SubstructureRedirectMask |6475FocusChangeMask | PropertyChangeMask |6476ColormapChangeMask | OwnerGrabButtonMask |6477im_event_mask;64786479XChangeWindowAttributes(x11_display, wd.x11_window, CWEventMask, &new_attr);64806481static unsigned char all_mask_data[XIMaskLen(XI_LASTEVENT)] = {};64826483all_event_mask.deviceid = XIAllDevices;6484all_event_mask.mask_len = sizeof(all_mask_data);6485all_event_mask.mask = all_mask_data;64866487XISetMask(all_event_mask.mask, XI_HierarchyChanged);64886489#ifdef TOUCH_ENABLED6490if (xi.touch_devices.size()) {6491XISetMask(all_event_mask.mask, XI_TouchBegin);6492XISetMask(all_event_mask.mask, XI_TouchUpdate);6493XISetMask(all_event_mask.mask, XI_TouchEnd);6494XISetMask(all_event_mask.mask, XI_TouchOwnership);6495}6496#endif64976498XISelectEvents(x11_display, wd.x11_window, &all_event_mask, 1);6499}65006501/* set the titlebar name */6502XStoreName(x11_display, wd.x11_window, "Godot");6503XSetWMProtocols(x11_display, wd.x11_window, &wm_delete, 1);6504if (xdnd_aware != None) {6505XChangeProperty(x11_display, wd.x11_window, xdnd_aware, XA_ATOM, 32, PropModeReplace, (unsigned char *)&xdnd_version, 1);6506}65076508if (xim && xim_style) {6509// Block events polling while changing input focus6510// because it triggers some event polling internally.6511MutexLock mutex_lock(events_mutex);65126513// Force on-the-spot for the over-the-spot style.6514if ((xim_style & XIMPreeditPosition) != 0) {6515xim_style &= ~XIMPreeditPosition;6516xim_style |= XIMPreeditCallbacks;6517}6518if ((xim_style & XIMPreeditCallbacks) != 0) {6519::XIMCallback preedit_start_callback;6520preedit_start_callback.client_data = (::XPointer)(this);6521preedit_start_callback.callback = (::XIMProc)(void *)(_xim_preedit_start_callback);65226523::XIMCallback preedit_done_callback;6524preedit_done_callback.client_data = (::XPointer)(this);6525preedit_done_callback.callback = (::XIMProc)(_xim_preedit_done_callback);65266527::XIMCallback preedit_draw_callback;6528preedit_draw_callback.client_data = (::XPointer)(this);6529preedit_draw_callback.callback = (::XIMProc)(_xim_preedit_draw_callback);65306531::XIMCallback preedit_caret_callback;6532preedit_caret_callback.client_data = (::XPointer)(this);6533preedit_caret_callback.callback = (::XIMProc)(_xim_preedit_caret_callback);65346535::XVaNestedList preedit_attributes = XVaCreateNestedList(0,6536XNPreeditStartCallback, &preedit_start_callback,6537XNPreeditDoneCallback, &preedit_done_callback,6538XNPreeditDrawCallback, &preedit_draw_callback,6539XNPreeditCaretCallback, &preedit_caret_callback,6540(char *)nullptr);65416542wd.xic = XCreateIC(xim,6543XNInputStyle, xim_style,6544XNClientWindow, wd.x11_xim_window,6545XNFocusWindow, wd.x11_xim_window,6546XNPreeditAttributes, preedit_attributes,6547(char *)nullptr);6548XFree(preedit_attributes);6549} else {6550wd.xic = XCreateIC(xim,6551XNInputStyle, xim_style,6552XNClientWindow, wd.x11_xim_window,6553XNFocusWindow, wd.x11_xim_window,6554(char *)nullptr);6555}65566557if (XGetICValues(wd.xic, XNFilterEvents, &im_event_mask, nullptr) != nullptr) {6558WARN_PRINT("XGetICValues couldn't obtain XNFilterEvents value");6559XDestroyIC(wd.xic);6560wd.xic = nullptr;6561}6562if (wd.xic) {6563XUnsetICFocus(wd.xic);6564} else {6565WARN_PRINT("XCreateIC couldn't create wd.xic");6566}6567} else {6568wd.xic = nullptr;6569WARN_PRINT("XCreateIC couldn't create wd.xic");6570}65716572_update_context(wd);65736574if (p_flags & WINDOW_FLAG_BORDERLESS_BIT) {6575Hints hints;6576Atom property;6577hints.flags = 2;6578hints.decorations = 0;6579property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True);6580if (property != None) {6581XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5);6582}6583}65846585if (wd.is_popup || wd.no_focus || (wd.embed_parent && !kde5_embed_workaround)) {6586// Set Utility type to disable fade animations.6587Atom type_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE_UTILITY", False);6588Atom wt_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE", False);6589if (wt_atom != None && type_atom != None) {6590XChangeProperty(x11_display, wd.x11_window, wt_atom, XA_ATOM, 32, PropModeReplace, (unsigned char *)&type_atom, 1);6591}6592} else {6593Atom type_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE_NORMAL", False);6594Atom wt_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE", False);65956596if (wt_atom != None && type_atom != None) {6597XChangeProperty(x11_display, wd.x11_window, wt_atom, XA_ATOM, 32, PropModeReplace, (unsigned char *)&type_atom, 1);6598}6599}66006601if (p_parent_window) {6602// Disable the window in the taskbar and alt-tab.6603_set_window_taskbar_pager_enabled(wd.x11_window, false);6604}66056606_update_size_hints(id);66076608#if defined(RD_ENABLED)6609if (rendering_context) {6610union {6611#ifdef VULKAN_ENABLED6612RenderingContextDriverVulkanX11::WindowPlatformData vulkan;6613#endif6614} wpd;6615#ifdef VULKAN_ENABLED6616if (rendering_driver == "vulkan") {6617wpd.vulkan.window = wd.x11_window;6618wpd.vulkan.display = x11_display;6619}6620#endif6621Error err = rendering_context->window_create(id, &wpd);6622ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, vformat("Can't create a %s window", rendering_driver));66236624rendering_context->window_set_size(id, win_rect.size.width, win_rect.size.height);6625rendering_context->window_set_vsync_mode(id, p_vsync_mode);6626}6627#endif6628#ifdef GLES3_ENABLED6629if (gl_manager) {6630Error err = gl_manager->window_create(id, wd.x11_window, x11_display, win_rect.size.width, win_rect.size.height);6631ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't create an OpenGL window");6632}6633if (gl_manager_egl) {6634Error err = gl_manager_egl->window_create(id, x11_display, &wd.x11_window, win_rect.size.width, win_rect.size.height);6635ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Failed to create an OpenGLES window.");6636}6637window_set_vsync_mode(p_vsync_mode, id);6638#endif66396640//set_class_hint(x11_display, wd.x11_window);6641XFlush(x11_display);66426643XSync(x11_display, False);6644//XSetErrorHandler(oldHandler);6645}66466647window_set_mode(p_mode, id);66486649//sync size6650{6651XWindowAttributes xwa;66526653XSync(x11_display, False);6654XGetWindowAttributes(x11_display, wd.x11_window, &xwa);66556656wd.position.x = xwa.x;6657wd.position.y = xwa.y;6658wd.size.width = xwa.width;6659wd.size.height = xwa.height;6660}66616662//set cursor6663if (cursors[current_cursor] != None) {6664XDefineCursor(x11_display, wd.x11_window, cursors[current_cursor]);6665}66666667return id;6668}66696670static bool _is_xim_style_supported(const ::XIMStyle &p_style) {6671const ::XIMStyle supported_preedit = XIMPreeditCallbacks | XIMPreeditPosition | XIMPreeditNothing | XIMPreeditNone;6672const ::XIMStyle supported_status = XIMStatusNothing | XIMStatusNone;66736674// Check preedit style is supported6675if ((p_style & supported_preedit) == 0) {6676return false;6677}66786679// Check status style is supported6680if ((p_style & supported_status) == 0) {6681return false;6682}66836684return true;6685}66866687static ::XIMStyle _get_best_xim_style(const ::XIMStyle &p_style_a, const ::XIMStyle &p_style_b) {6688if (p_style_a == 0) {6689return p_style_b;6690}6691if (p_style_b == 0) {6692return p_style_a;6693}66946695const ::XIMStyle preedit = XIMPreeditArea | XIMPreeditCallbacks | XIMPreeditPosition | XIMPreeditNothing | XIMPreeditNone;6696const ::XIMStyle status = XIMStatusArea | XIMStatusCallbacks | XIMStatusNothing | XIMStatusNone;66976698::XIMStyle a = p_style_a & preedit;6699::XIMStyle b = p_style_b & preedit;6700if (a != b) {6701// Compare preedit styles.6702if ((a | b) & XIMPreeditCallbacks) {6703return a == XIMPreeditCallbacks ? p_style_a : p_style_b;6704} else if ((a | b) & XIMPreeditPosition) {6705return a == XIMPreeditPosition ? p_style_a : p_style_b;6706} else if ((a | b) & XIMPreeditArea) {6707return a == XIMPreeditArea ? p_style_a : p_style_b;6708} else if ((a | b) & XIMPreeditNothing) {6709return a == XIMPreeditNothing ? p_style_a : p_style_b;6710}6711} else {6712// Preedit styles are the same, compare status styles.6713a = p_style_a & status;6714b = p_style_b & status;67156716if ((a | b) & XIMStatusCallbacks) {6717return a == XIMStatusCallbacks ? p_style_a : p_style_b;6718} else if ((a | b) & XIMStatusArea) {6719return a == XIMStatusArea ? p_style_a : p_style_b;6720} else if ((a | b) & XIMStatusNothing) {6721return a == XIMStatusNothing ? p_style_a : p_style_b;6722}6723}6724return p_style_a;6725}67266727DisplayServerX11::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) {6728KeyMappingX11::initialize();67296730String current_desk = OS::get_singleton()->get_environment("XDG_CURRENT_DESKTOP").to_lower();6731String session_desk = OS::get_singleton()->get_environment("XDG_SESSION_DESKTOP").to_lower();6732swap_cancel_ok = (current_desk.contains("kde") || session_desk.contains("kde") || current_desk.contains("lxqt") || session_desk.contains("lxqt"));67336734xwayland = OS::get_singleton()->get_environment("XDG_SESSION_TYPE").to_lower() == "wayland";6735kde5_embed_workaround = current_desk == "kde" && OS::get_singleton()->get_environment("KDE_SESSION_VERSION") == "5";67366737native_menu = memnew(NativeMenu);6738context = p_context;67396740#ifdef SOWRAP_ENABLED6741#ifdef DEBUG_ENABLED6742int dylibloader_verbose = 1;6743#else6744int dylibloader_verbose = 0;6745#endif6746if (initialize_xlib(dylibloader_verbose) != 0) {6747r_error = ERR_UNAVAILABLE;6748ERR_FAIL_MSG("Can't load Xlib dynamically.");6749}67506751if (initialize_xcursor(dylibloader_verbose) != 0) {6752r_error = ERR_UNAVAILABLE;6753ERR_FAIL_MSG("Can't load XCursor dynamically.");6754}6755#ifdef XKB_ENABLED6756bool xkb_loaded = (initialize_xkbcommon(dylibloader_verbose) == 0);6757xkb_loaded_v05p = xkb_loaded;6758if (!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) {6759xkb_loaded_v05p = false;6760print_verbose("Detected XKBcommon library version older than 0.5, dead key composition and Unicode key labels disabled.");6761}6762xkb_loaded_v08p = xkb_loaded;6763if (!xkb_keysym_to_utf32 || !xkb_keysym_to_upper) {6764xkb_loaded_v08p = false;6765print_verbose("Detected XKBcommon library version older than 0.8, Unicode key labels disabled.");6766}6767#endif6768if (initialize_xext(dylibloader_verbose) != 0) {6769r_error = ERR_UNAVAILABLE;6770ERR_FAIL_MSG("Can't load Xext dynamically.");6771}67726773if (initialize_xinerama(dylibloader_verbose) != 0) {6774xinerama_ext_ok = false;6775}67766777if (initialize_xrandr(dylibloader_verbose) != 0) {6778xrandr_ext_ok = false;6779}67806781if (initialize_xrender(dylibloader_verbose) != 0) {6782r_error = ERR_UNAVAILABLE;6783ERR_FAIL_MSG("Can't load Xrender dynamically.");6784}67856786if (initialize_xinput2(dylibloader_verbose) != 0) {6787r_error = ERR_UNAVAILABLE;6788ERR_FAIL_MSG("Can't load Xinput2 dynamically.");6789}6790#else6791#ifdef XKB_ENABLED6792bool xkb_loaded = true;6793xkb_loaded_v05p = true;6794xkb_loaded_v08p = true;6795#endif6796#endif67976798#ifdef XKB_ENABLED6799if (xkb_loaded) {6800xkb_ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS);6801if (xkb_ctx) {6802const char *locale = getenv("LC_ALL");6803if (!locale || !*locale) {6804locale = getenv("LC_CTYPE");6805}6806if (!locale || !*locale) {6807locale = getenv("LANG");6808}6809if (!locale || !*locale) {6810locale = "C";6811}6812dead_tbl = xkb_compose_table_new_from_locale(xkb_ctx, locale, XKB_COMPOSE_COMPILE_NO_FLAGS);6813}6814}6815#endif68166817Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events);68186819r_error = OK;68206821#ifdef SOWRAP_ENABLED6822{6823if (!XcursorImageCreate || !XcursorImageLoadCursor || !XcursorImageDestroy || !XcursorGetDefaultSize || !XcursorGetTheme || !XcursorLibraryLoadImage) {6824// There's no API to check version, check if functions are available instead.6825ERR_PRINT("Unsupported Xcursor library version.");6826r_error = ERR_UNAVAILABLE;6827return;6828}6829}6830#endif68316832for (int i = 0; i < CURSOR_MAX; i++) {6833cursors[i] = None;6834cursor_img[i] = nullptr;6835}68366837XInitThreads(); //always use threads68386839/** XLIB INITIALIZATION **/6840x11_display = XOpenDisplay(nullptr);68416842if (!x11_display) {6843ERR_PRINT("X11 Display is not available");6844r_error = ERR_UNAVAILABLE;6845return;6846}68476848if (xshaped_ext_ok) {6849int version_major = 0;6850int version_minor = 0;6851int rc = XShapeQueryVersion(x11_display, &version_major, &version_minor);6852print_verbose(vformat("Xshape %d.%d detected.", version_major, version_minor));6853if (rc != 1 || version_major < 1) {6854xshaped_ext_ok = false;6855print_verbose("Unsupported Xshape library version.");6856}6857}68586859if (xinerama_ext_ok) {6860int version_major = 0;6861int version_minor = 0;6862int rc = XineramaQueryVersion(x11_display, &version_major, &version_minor);6863print_verbose(vformat("Xinerama %d.%d detected.", version_major, version_minor));6864if (rc != 1 || version_major < 1) {6865xinerama_ext_ok = false;6866print_verbose("Unsupported Xinerama library version.");6867}6868}68696870if (xrandr_ext_ok) {6871int version_major = 0;6872int version_minor = 0;6873int rc = XRRQueryVersion(x11_display, &version_major, &version_minor);6874print_verbose(vformat("Xrandr %d.%d detected.", version_major, version_minor));6875if (rc != 1 || (version_major == 1 && version_minor < 3) || (version_major < 1)) {6876xrandr_ext_ok = false;6877print_verbose("Unsupported Xrandr library version.");6878}6879}68806881{6882int version_major = 0;6883int version_minor = 0;6884int rc = XRenderQueryVersion(x11_display, &version_major, &version_minor);6885print_verbose(vformat("Xrender %d.%d detected.", version_major, version_minor));6886if (rc != 1 || (version_major == 0 && version_minor < 11)) {6887ERR_PRINT("Unsupported Xrender library version.");6888r_error = ERR_UNAVAILABLE;6889XCloseDisplay(x11_display);6890return;6891}6892}68936894{6895int version_major = 2; // Report 2.2 as supported by engine, but should work with 2.1 or 2.0 library as well.6896int version_minor = 2;6897int rc = XIQueryVersion(x11_display, &version_major, &version_minor);6898print_verbose(vformat("Xinput %d.%d detected.", version_major, version_minor));6899if (rc != Success || (version_major < 2)) {6900ERR_PRINT("Unsupported Xinput2 library version.");6901r_error = ERR_UNAVAILABLE;6902XCloseDisplay(x11_display);6903return;6904}6905}69066907char *modifiers = nullptr;6908Bool xkb_dar = False;6909XAutoRepeatOn(x11_display);6910xkb_dar = XkbSetDetectableAutoRepeat(x11_display, True, nullptr);69116912// Try to support IME if detectable auto-repeat is supported6913if (xkb_dar == True) {6914#ifdef X_HAVE_UTF8_STRING6915// Xutf8LookupString will be used later instead of XmbLookupString before6916// the multibyte sequences can be converted to unicode string.6917modifiers = XSetLocaleModifiers("");6918#endif6919}69206921if (modifiers == nullptr) {6922if (OS::get_singleton()->is_stdout_verbose()) {6923WARN_PRINT("IME is disabled");6924}6925XSetLocaleModifiers("@im=none");6926WARN_PRINT("Error setting locale modifiers");6927}69286929const char *err;6930int xrandr_major = 0;6931int xrandr_minor = 0;6932int event_base, error_base;6933xrandr_ext_ok = XRRQueryExtension(x11_display, &event_base, &error_base);6934xrandr_handle = dlopen("libXrandr.so.2", RTLD_LAZY);6935if (!xrandr_handle) {6936err = dlerror();6937// For some arcane reason, NetBSD now ships libXrandr.so.3 while the rest of the world has libXrandr.so.2...6938// In case this happens for other X11 platforms in the future, let's give it a try too before failing.6939xrandr_handle = dlopen("libXrandr.so.3", RTLD_LAZY);6940if (!xrandr_handle) {6941fprintf(stderr, "could not load libXrandr.so.2, Error: %s\n", err);6942}6943}69446945if (xrandr_handle) {6946XRRQueryVersion(x11_display, &xrandr_major, &xrandr_minor);6947if (((xrandr_major << 8) | xrandr_minor) >= 0x0105) {6948xrr_get_monitors = (xrr_get_monitors_t)dlsym(xrandr_handle, "XRRGetMonitors");6949if (!xrr_get_monitors) {6950err = dlerror();6951fprintf(stderr, "could not find symbol XRRGetMonitors\nError: %s\n", err);6952} else {6953xrr_free_monitors = (xrr_free_monitors_t)dlsym(xrandr_handle, "XRRFreeMonitors");6954if (!xrr_free_monitors) {6955err = dlerror();6956fprintf(stderr, "could not find XRRFreeMonitors\nError: %s\n", err);6957xrr_get_monitors = nullptr;6958}6959}6960}6961}69626963if (!_refresh_device_info()) {6964OS::get_singleton()->alert("Your system does not support XInput 2.\n"6965"Please upgrade your distribution.",6966"Unable to initialize XInput");6967r_error = ERR_UNAVAILABLE;6968return;6969}69706971xim = XOpenIM(x11_display, nullptr, nullptr, nullptr);69726973if (xim == nullptr) {6974WARN_PRINT("XOpenIM failed");6975xim_style = 0L;6976} else {6977::XIMCallback im_destroy_callback;6978im_destroy_callback.client_data = (::XPointer)(this);6979im_destroy_callback.callback = (::XIMProc)(_xim_destroy_callback);6980if (XSetIMValues(xim, XNDestroyCallback, &im_destroy_callback,6981nullptr) != nullptr) {6982WARN_PRINT("Error setting XIM destroy callback");6983}69846985::XIMStyles *xim_styles = nullptr;6986xim_style = 0L;6987char *imvalret = XGetIMValues(xim, XNQueryInputStyle, &xim_styles, nullptr);6988if (imvalret != nullptr || xim_styles == nullptr) {6989fprintf(stderr, "Input method doesn't support any styles\n");6990}69916992if (xim_styles) {6993xim_style = 0L;6994for (int i = 0; i < xim_styles->count_styles; i++) {6995const ::XIMStyle &style = xim_styles->supported_styles[i];69966997if (!_is_xim_style_supported(style)) {6998continue;6999}70007001xim_style = _get_best_xim_style(xim_style, style);7002}70037004XFree(xim_styles);7005}7006XFree(imvalret);7007}70087009/* Atom internment */7010wm_delete = XInternAtom(x11_display, "WM_DELETE_WINDOW", true);7011// Set Xdnd (drag & drop) support.7012xdnd_aware = XInternAtom(x11_display, "XdndAware", False);7013xdnd_enter = XInternAtom(x11_display, "XdndEnter", False);7014xdnd_position = XInternAtom(x11_display, "XdndPosition", False);7015xdnd_status = XInternAtom(x11_display, "XdndStatus", False);7016xdnd_action_copy = XInternAtom(x11_display, "XdndActionCopy", False);7017xdnd_drop = XInternAtom(x11_display, "XdndDrop", False);7018xdnd_finished = XInternAtom(x11_display, "XdndFinished", False);7019xdnd_selection = XInternAtom(x11_display, "XdndSelection", False);70207021#ifdef SPEECHD_ENABLED7022// Init TTS7023bool tts_enabled = GLOBAL_GET("audio/general/text_to_speech");7024if (tts_enabled) {7025initialize_tts();7026}7027#endif70287029#ifdef ACCESSKIT_ENABLED7030if (accessibility_get_mode() != DisplayServer::AccessibilityMode::ACCESSIBILITY_DISABLED) {7031accessibility_driver = memnew(AccessibilityDriverAccessKit);7032if (accessibility_driver->init() != OK) {7033memdelete(accessibility_driver);7034accessibility_driver = nullptr;7035}7036}7037#endif70387039//!!!!!!!!!!!!!!!!!!!!!!!!!!7040//TODO - do Vulkan and OpenGL support checks, driver selection and fallback7041rendering_driver = p_rendering_driver;70427043bool driver_found = false;7044String executable_name = OS::get_singleton()->get_executable_path().get_file();70457046// Initialize context and rendering device.70477048if (rendering_driver == "dummy") {7049RasterizerDummy::make_current();7050driver_found = true;7051}70527053#if defined(RD_ENABLED)7054#if defined(VULKAN_ENABLED)7055if (rendering_driver == "vulkan") {7056rendering_context = memnew(RenderingContextDriverVulkanX11);7057}7058#endif // VULKAN_ENABLED70597060if (rendering_context) {7061if (rendering_context->initialize() != OK) {7062memdelete(rendering_context);7063rendering_context = nullptr;7064#if defined(GLES3_ENABLED)7065bool fallback_to_opengl3 = GLOBAL_GET("rendering/rendering_device/fallback_to_opengl3");7066if (fallback_to_opengl3 && rendering_driver != "opengl3") {7067WARN_PRINT("Your video card drivers seem not to support the required Vulkan version, switching to OpenGL 3.");7068rendering_driver = "opengl3";7069OS::get_singleton()->set_current_rendering_method("gl_compatibility");7070OS::get_singleton()->set_current_rendering_driver_name(rendering_driver);7071} else7072#endif // GLES3_ENABLED7073{7074r_error = ERR_CANT_CREATE;70757076if (p_rendering_driver == "vulkan") {7077OS::get_singleton()->alert(7078vformat("Your video card drivers seem not to support the required Vulkan version.\n\n"7079"If possible, consider updating your video card drivers or using the OpenGL 3 driver.\n\n"7080"You can enable the OpenGL 3 driver by starting the engine from the\n"7081"command line with the command:\n\n \"%s\" --rendering-driver opengl3\n\n"7082"If you recently updated your video card drivers, try rebooting.",7083executable_name),7084"Unable to initialize Vulkan video driver");7085}70867087ERR_FAIL_MSG(vformat("Could not initialize %s", rendering_driver));7088}7089}7090driver_found = true;7091}7092#endif // RD_ENABLED70937094#if defined(GLES3_ENABLED)7095if (rendering_driver == "opengl3" || rendering_driver == "opengl3_es") {7096if (getenv("DRI_PRIME") == nullptr) {7097int use_prime = -1;70987099if (getenv("PRIMUS_DISPLAY") ||7100getenv("PRIMUS_libGLd") ||7101getenv("PRIMUS_libGLa") ||7102getenv("PRIMUS_libGL") ||7103getenv("PRIMUS_LOAD_GLOBAL") ||7104getenv("BUMBLEBEE_SOCKET")) {7105print_verbose("Optirun/primusrun detected. Skipping GPU detection");7106use_prime = 0;7107}71087109// Some tools use fake libGL libraries and have them override the real one using7110// LD_LIBRARY_PATH, so we skip them. *But* Steam also sets LD_LIBRARY_PATH for its7111// runtime and includes system `/lib` and `/lib64`... so ignore Steam.7112if (use_prime == -1 && getenv("LD_LIBRARY_PATH") && !getenv("STEAM_RUNTIME_LIBRARY_PATH")) {7113String ld_library_path(getenv("LD_LIBRARY_PATH"));7114Vector<String> libraries = ld_library_path.split(":");71157116for (int i = 0; i < libraries.size(); ++i) {7117if (FileAccess::exists(libraries[i] + "/libGL.so.1") ||7118FileAccess::exists(libraries[i] + "/libGL.so")) {7119print_verbose("Custom libGL override detected. Skipping GPU detection");7120use_prime = 0;7121}7122}7123}71247125if (use_prime == -1) {7126print_verbose("Detecting GPUs, set DRI_PRIME in the environment to override GPU detection logic.");7127use_prime = DetectPrimeX11::detect_prime();7128}71297130if (use_prime) {7131print_line("Found discrete GPU, setting DRI_PRIME=1 to use it.");7132print_line("Note: Set DRI_PRIME=0 in the environment to disable Godot from using the discrete GPU.");7133setenv("DRI_PRIME", "1", 1);7134}7135}7136}7137if (rendering_driver == "opengl3") {7138gl_manager = memnew(GLManager_X11(p_resolution, GLManager_X11::GLES_3_0_COMPATIBLE));7139if (gl_manager->initialize(x11_display) != OK || gl_manager->open_display(x11_display) != OK) {7140memdelete(gl_manager);7141gl_manager = nullptr;7142bool fallback = GLOBAL_GET("rendering/gl_compatibility/fallback_to_gles");7143if (fallback) {7144WARN_PRINT("Your video card drivers seem not to support the required OpenGL version, switching to OpenGLES.");7145rendering_driver = "opengl3_es";7146OS::get_singleton()->set_current_rendering_driver_name(rendering_driver);7147} else {7148r_error = ERR_UNAVAILABLE;71497150OS::get_singleton()->alert(7151vformat("Your video card drivers seem not to support the required OpenGL 3.3 version.\n\n"7152"If possible, consider updating your video card drivers or using the Vulkan driver.\n\n"7153"You can enable the Vulkan driver by starting the engine from the\n"7154"command line with the command:\n\n \"%s\" --rendering-driver vulkan\n\n"7155"If you recently updated your video card drivers, try rebooting.",7156executable_name),7157"Unable to initialize OpenGL video driver");71587159ERR_FAIL_MSG("Could not initialize OpenGL.");7160}7161} else {7162driver_found = true;7163RasterizerGLES3::make_current(true);7164}7165}71667167if (rendering_driver == "opengl3_es") {7168gl_manager_egl = memnew(GLManagerEGL_X11);7169if (gl_manager_egl->initialize() != OK || gl_manager_egl->open_display(x11_display) != OK) {7170memdelete(gl_manager_egl);7171gl_manager_egl = nullptr;7172r_error = ERR_UNAVAILABLE;71737174OS::get_singleton()->alert(7175"Your video card drivers seem not to support the required OpenGL ES 3.0 version.\n\n"7176"If possible, consider updating your video card drivers.\n\n"7177"If you recently updated your video card drivers, try rebooting.",7178"Unable to initialize OpenGL ES video driver");71797180ERR_FAIL_MSG("Could not initialize OpenGL ES.");7181}7182driver_found = true;7183RasterizerGLES3::make_current(false);7184}71857186#endif // GLES3_ENABLED71877188if (!driver_found) {7189r_error = ERR_UNAVAILABLE;7190ERR_FAIL_MSG("Video driver not found.");7191}71927193Point2i window_position;7194if (p_position != nullptr) {7195window_position = *p_position;7196} else {7197if (p_screen == SCREEN_OF_MAIN_WINDOW) {7198p_screen = SCREEN_PRIMARY;7199}7200Rect2i scr_rect = screen_get_usable_rect(p_screen);7201window_position = scr_rect.position + (scr_rect.size - p_resolution) / 2;7202}72037204WindowID main_window = _create_window(p_mode, p_vsync_mode, p_flags, Rect2i(window_position, p_resolution), p_parent_window);7205if (main_window == INVALID_WINDOW_ID) {7206r_error = ERR_CANT_CREATE;7207return;7208}7209for (int i = 0; i < WINDOW_FLAG_MAX; i++) {7210if (p_flags & (1 << i)) {7211window_set_flag(WindowFlags(i), true, main_window);7212}7213}72147215#if defined(RD_ENABLED)7216if (rendering_context) {7217rendering_device = memnew(RenderingDevice);7218if (rendering_device->initialize(rendering_context, MAIN_WINDOW_ID) != OK) {7219memdelete(rendering_device);7220rendering_device = nullptr;7221memdelete(rendering_context);7222rendering_context = nullptr;7223r_error = ERR_UNAVAILABLE;7224return;7225}7226rendering_device->screen_create(MAIN_WINDOW_ID);72277228RendererCompositorRD::make_current();7229}7230#endif // RD_ENABLED72317232{7233//set all event master mask7234XIEventMask all_master_event_mask;7235static unsigned char all_master_mask_data[XIMaskLen(XI_LASTEVENT)] = {};7236all_master_event_mask.deviceid = XIAllMasterDevices;7237all_master_event_mask.mask_len = sizeof(all_master_mask_data);7238all_master_event_mask.mask = all_master_mask_data;7239XISetMask(all_master_event_mask.mask, XI_DeviceChanged);7240XISetMask(all_master_event_mask.mask, XI_RawMotion);7241XISelectEvents(x11_display, DefaultRootWindow(x11_display), &all_master_event_mask, 1);7242}72437244cursor_size = XcursorGetDefaultSize(x11_display);7245cursor_theme = XcursorGetTheme(x11_display);72467247if (!cursor_theme) {7248print_verbose("XcursorGetTheme could not get cursor theme");7249cursor_theme = "default";7250}72517252for (int i = 0; i < CURSOR_MAX; i++) {7253static const char *cursor_file[] = {7254"left_ptr",7255"xterm",7256"hand2",7257"cross",7258"watch",7259"left_ptr_watch",7260"fleur",7261"dnd-move",7262"crossed_circle",7263"v_double_arrow",7264"h_double_arrow",7265"size_bdiag",7266"size_fdiag",7267"move",7268"row_resize",7269"col_resize",7270"question_arrow"7271};72727273cursor_img[i] = XcursorLibraryLoadImage(cursor_file[i], cursor_theme, cursor_size);7274if (!cursor_img[i]) {7275const char *fallback = nullptr;72767277switch (i) {7278case CURSOR_POINTING_HAND:7279fallback = "pointer";7280break;7281case CURSOR_CROSS:7282fallback = "crosshair";7283break;7284case CURSOR_WAIT:7285fallback = "wait";7286break;7287case CURSOR_BUSY:7288fallback = "progress";7289break;7290case CURSOR_DRAG:7291fallback = "grabbing";7292break;7293case CURSOR_CAN_DROP:7294fallback = "hand1";7295break;7296case CURSOR_FORBIDDEN:7297fallback = "forbidden";7298break;7299case CURSOR_VSIZE:7300fallback = "ns-resize";7301break;7302case CURSOR_HSIZE:7303fallback = "ew-resize";7304break;7305case CURSOR_BDIAGSIZE:7306fallback = "fd_double_arrow";7307break;7308case CURSOR_FDIAGSIZE:7309fallback = "bd_double_arrow";7310break;7311case CURSOR_MOVE:7312cursor_img[i] = cursor_img[CURSOR_DRAG];7313break;7314case CURSOR_VSPLIT:7315fallback = "sb_v_double_arrow";7316break;7317case CURSOR_HSPLIT:7318fallback = "sb_h_double_arrow";7319break;7320case CURSOR_HELP:7321fallback = "help";7322break;7323}7324if (fallback != nullptr) {7325cursor_img[i] = XcursorLibraryLoadImage(fallback, cursor_theme, cursor_size);7326}7327}7328if (cursor_img[i]) {7329cursors[i] = XcursorImageLoadCursor(x11_display, cursor_img[i]);7330} else {7331print_verbose("Failed loading custom cursor: " + String(cursor_file[i]));7332}7333}73347335{7336// Creating an empty/transparent cursor73377338// Create 1x1 bitmap7339Pixmap cursormask = XCreatePixmap(x11_display,7340RootWindow(x11_display, DefaultScreen(x11_display)), 1, 1, 1);73417342// Fill with zero7343XGCValues xgc;7344xgc.function = GXclear;7345GC gc = XCreateGC(x11_display, cursormask, GCFunction, &xgc);7346XFillRectangle(x11_display, cursormask, gc, 0, 0, 1, 1);73477348// Color value doesn't matter. Mask zero means no foreground or background will be drawn7349XColor col = {};73507351Cursor cursor = XCreatePixmapCursor(x11_display,7352cursormask, // source (using cursor mask as placeholder, since it'll all be ignored)7353cursormask, // mask7354&col, &col, 0, 0);73557356XFreePixmap(x11_display, cursormask);7357XFreeGC(x11_display, gc);73587359if (cursor == None) {7360ERR_PRINT("FAILED CREATING CURSOR");7361}73627363null_cursor = cursor;7364}7365cursor_set_shape(CURSOR_BUSY);73667367// Search the X11 event queue for ConfigureNotify events and process all7368// that are currently queued early, so we can get the final window size7369// for correctly drawing of the bootsplash.7370XEvent config_event;7371while (XCheckTypedEvent(x11_display, ConfigureNotify, &config_event)) {7372_window_changed(&config_event);7373}7374events_thread.start(_poll_events_thread, this);73757376_update_real_mouse_position(windows[MAIN_WINDOW_ID]);73777378#ifdef DBUS_ENABLED7379bool dbus_ok = true;7380#ifdef SOWRAP_ENABLED7381if (initialize_dbus(dylibloader_verbose) != 0) {7382print_verbose("Failed to load DBus library!");7383dbus_ok = false;7384}7385#endif7386if (dbus_ok) {7387bool ver_ok = false;7388int version_major = 0;7389int version_minor = 0;7390int version_rev = 0;7391dbus_get_version(&version_major, &version_minor, &version_rev);7392ver_ok = (version_major == 1 && version_minor >= 10) || (version_major > 1); // 1.10.07393print_verbose(vformat("DBus %d.%d.%d detected.", version_major, version_minor, version_rev));7394if (!ver_ok) {7395print_verbose("Unsupported DBus library version!");7396dbus_ok = false;7397}7398}7399if (dbus_ok) {7400screensaver = memnew(FreeDesktopScreenSaver);7401portal_desktop = memnew(FreeDesktopPortalDesktop);7402atspi_monitor = memnew(FreeDesktopAtSPIMonitor);7403}7404#endif // DBUS_ENABLED74057406screen_set_keep_on(GLOBAL_GET("display/window/energy_saving/keep_screen_on"));74077408XSetErrorHandler(&default_window_error_handler);74097410r_error = OK;7411}74127413DisplayServerX11::~DisplayServerX11() {7414// Send owned clipboard data to clipboard manager before exit.7415Window x11_main_window = windows[MAIN_WINDOW_ID].x11_window;7416_clipboard_transfer_ownership(XA_PRIMARY, x11_main_window);7417_clipboard_transfer_ownership(XInternAtom(x11_display, "CLIPBOARD", 0), x11_main_window);74187419events_thread_done.set();7420events_thread.wait_to_finish();74217422if (native_menu) {7423memdelete(native_menu);7424native_menu = nullptr;7425}74267427//destroy all windows7428for (KeyValue<WindowID, WindowData> &E : windows) {7429#if defined(RD_ENABLED)7430if (rendering_device) {7431rendering_device->screen_free(E.key);7432}74337434if (rendering_context) {7435rendering_context->window_destroy(E.key);7436}7437#endif7438#ifdef GLES3_ENABLED7439if (gl_manager) {7440gl_manager->window_destroy(E.key);7441}7442if (gl_manager_egl) {7443gl_manager_egl->window_destroy(E.key);7444}7445#endif74467447#ifdef ACCESSKIT_ENABLED7448if (accessibility_driver) {7449accessibility_driver->window_destroy(E.key);7450}7451#endif74527453WindowData &wd = E.value;7454if (wd.xic) {7455XDestroyIC(wd.xic);7456wd.xic = nullptr;7457}7458XDestroyWindow(x11_display, wd.x11_xim_window);7459#ifdef XKB_ENABLED7460if (xkb_loaded_v05p) {7461if (wd.xkb_state) {7462xkb_compose_state_unref(wd.xkb_state);7463wd.xkb_state = nullptr;7464}7465}7466#endif7467XUnmapWindow(x11_display, wd.x11_window);7468XDestroyWindow(x11_display, wd.x11_window);7469}74707471#ifdef XKB_ENABLED7472if (xkb_loaded_v05p) {7473if (dead_tbl) {7474xkb_compose_table_unref(dead_tbl);7475}7476if (xkb_ctx) {7477xkb_context_unref(xkb_ctx);7478}7479}7480#endif74817482//destroy drivers7483#if defined(RD_ENABLED)7484if (rendering_device) {7485memdelete(rendering_device);7486rendering_device = nullptr;7487}74887489if (rendering_context) {7490memdelete(rendering_context);7491rendering_context = nullptr;7492}7493#endif74947495#ifdef GLES3_ENABLED7496if (gl_manager) {7497memdelete(gl_manager);7498gl_manager = nullptr;7499}7500if (gl_manager_egl) {7501memdelete(gl_manager_egl);7502gl_manager_egl = nullptr;7503}7504#endif75057506if (xrandr_handle) {7507dlclose(xrandr_handle);7508}75097510for (int i = 0; i < CURSOR_MAX; i++) {7511if (cursors[i] != None) {7512XFreeCursor(x11_display, cursors[i]);7513}7514if (cursor_img[i] != nullptr) {7515XcursorImageDestroy(cursor_img[i]);7516}7517}75187519if (xim) {7520XCloseIM(xim);7521}75227523XCloseDisplay(x11_display);7524if (xmbstring) {7525memfree(xmbstring);7526}7527#ifdef ACCESSKIT_ENABLED7528if (accessibility_driver) {7529memdelete(accessibility_driver);7530}7531#endif7532#ifdef SPEECHD_ENABLED7533if (tts) {7534memdelete(tts);7535}7536#endif75377538#ifdef DBUS_ENABLED7539if (screensaver) {7540memdelete(screensaver);7541}7542if (portal_desktop) {7543memdelete(portal_desktop);7544}7545if (atspi_monitor) {7546memdelete(atspi_monitor);7547}7548#endif7549}75507551void DisplayServerX11::register_x11_driver() {7552register_create_function("x11", create_func, get_rendering_drivers_func);7553}75547555#endif // X11 enabled755675577558