1/**************************************************************************/
2/* display_server_x11.cpp */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#include "display_server_x11.h"
32
33#ifdef X11_ENABLED
34
35#include "x11/detect_prime_x11.h"
36#include "x11/key_mapping_x11.h"
37
38#include "core/config/project_settings.h"
39#include "core/math/math_funcs.h"
40#include "core/string/print_string.h"
41#include "core/string/ustring.h"
42#include "main/main.h"
43#include "scene/resources/atlas_texture.h"
44
45#if defined(VULKAN_ENABLED)
46#include "servers/rendering/renderer_rd/renderer_compositor_rd.h"
47#endif
48
49#if defined(GLES3_ENABLED)
50#include "drivers/gles3/rasterizer_gles3.h"
51#endif
52
53#include <dlfcn.h>
54#include <limits.h>
55#include <stdio.h>
56#include <stdlib.h>
57#include <string.h>
58#include <sys/stat.h>
59#include <sys/types.h>
60#include <unistd.h>
61
62#undef CursorShape
63#include <X11/XKBlib.h>
64
65// ICCCM
66#define WM_NormalState 1L // window normal state
67#define WM_IconicState 3L // window minimized
68// EWMH
69#define _NET_WM_STATE_REMOVE 0L // remove/unset property
70#define _NET_WM_STATE_ADD 1L // add/set property
71
72// 2.2 is the first release with multitouch
73#define XINPUT_CLIENT_VERSION_MAJOR 2
74#define XINPUT_CLIENT_VERSION_MINOR 2
75
76#define VALUATOR_ABSX 0
77#define VALUATOR_ABSY 1
78#define VALUATOR_PRESSURE 2
79#define VALUATOR_TILTX 3
80#define VALUATOR_TILTY 4
81
82//#define DISPLAY_SERVER_X11_DEBUG_LOGS_ENABLED
83#ifdef DISPLAY_SERVER_X11_DEBUG_LOGS_ENABLED
84#define DEBUG_LOG_X11(...) printf(__VA_ARGS__)
85#else
86#define DEBUG_LOG_X11(...)
87#endif
88
89static const double abs_resolution_mult = 10000.0;
90static const double abs_resolution_range_mult = 10.0;
91
92// Hints for X11 fullscreen
93struct Hints {
94 unsigned long flags = 0;
95 unsigned long functions = 0;
96 unsigned long decorations = 0;
97 long inputMode = 0;
98 unsigned long status = 0;
99};
100
101static String get_atom_name(Display *p_disp, Atom p_atom) {
102 char *name = XGetAtomName(p_disp, p_atom);
103 ERR_FAIL_NULL_V_MSG(name, String(), "Atom is invalid.");
104 String ret;
105 ret.parse_utf8(name);
106 XFree(name);
107 return ret;
108}
109
110bool DisplayServerX11::has_feature(Feature p_feature) const {
111 switch (p_feature) {
112 case FEATURE_SUBWINDOWS:
113#ifdef TOUCH_ENABLED
114 case FEATURE_TOUCHSCREEN:
115#endif
116 case FEATURE_MOUSE:
117 case FEATURE_MOUSE_WARP:
118 case FEATURE_CLIPBOARD:
119 case FEATURE_CURSOR_SHAPE:
120 case FEATURE_CUSTOM_CURSOR_SHAPE:
121 case FEATURE_IME:
122 case FEATURE_WINDOW_TRANSPARENCY:
123 //case FEATURE_HIDPI:
124 case FEATURE_ICON:
125#ifdef DBUS_ENABLED
126 case FEATURE_NATIVE_DIALOG:
127#endif
128 //case FEATURE_NATIVE_ICON:
129 case FEATURE_SWAP_BUFFERS:
130#ifdef DBUS_ENABLED
131 case FEATURE_KEEP_SCREEN_ON:
132#endif
133 case FEATURE_CLIPBOARD_PRIMARY:
134 case FEATURE_TEXT_TO_SPEECH:
135 case FEATURE_SCREEN_CAPTURE:
136 return true;
137 default: {
138 }
139 }
140
141 return false;
142}
143
144String DisplayServerX11::get_name() const {
145 return "X11";
146}
147
148void DisplayServerX11::_update_real_mouse_position(const WindowData &wd) {
149 Window root_return, child_return;
150 int root_x, root_y, win_x, win_y;
151 unsigned int mask_return;
152
153 Bool xquerypointer_result = XQueryPointer(x11_display, wd.x11_window, &root_return, &child_return, &root_x, &root_y,
154 &win_x, &win_y, &mask_return);
155
156 if (xquerypointer_result) {
157 if (win_x > 0 && win_y > 0 && win_x <= wd.size.width && win_y <= wd.size.height) {
158 last_mouse_pos.x = win_x;
159 last_mouse_pos.y = win_y;
160 last_mouse_pos_valid = true;
161 Input::get_singleton()->set_mouse_position(last_mouse_pos);
162 }
163 }
164}
165
166bool DisplayServerX11::_refresh_device_info() {
167 int event_base, error_base;
168
169 print_verbose("XInput: Refreshing devices.");
170
171 if (!XQueryExtension(x11_display, "XInputExtension", &xi.opcode, &event_base, &error_base)) {
172 print_verbose("XInput extension not available. Please upgrade your distribution.");
173 return false;
174 }
175
176 int xi_major_query = XINPUT_CLIENT_VERSION_MAJOR;
177 int xi_minor_query = XINPUT_CLIENT_VERSION_MINOR;
178
179 if (XIQueryVersion(x11_display, &xi_major_query, &xi_minor_query) != Success) {
180 print_verbose(vformat("XInput 2 not available (server supports %d.%d).", xi_major_query, xi_minor_query));
181 xi.opcode = 0;
182 return false;
183 }
184
185 if (xi_major_query < XINPUT_CLIENT_VERSION_MAJOR || (xi_major_query == XINPUT_CLIENT_VERSION_MAJOR && xi_minor_query < XINPUT_CLIENT_VERSION_MINOR)) {
186 print_verbose(vformat("XInput %d.%d not available (server supports %d.%d). Touch input unavailable.",
187 XINPUT_CLIENT_VERSION_MAJOR, XINPUT_CLIENT_VERSION_MINOR, xi_major_query, xi_minor_query));
188 }
189
190 xi.absolute_devices.clear();
191 xi.touch_devices.clear();
192 xi.pen_inverted_devices.clear();
193 xi.last_relative_time = 0;
194
195 int dev_count;
196 XIDeviceInfo *info = XIQueryDevice(x11_display, XIAllDevices, &dev_count);
197
198 for (int i = 0; i < dev_count; i++) {
199 XIDeviceInfo *dev = &info[i];
200 if (!dev->enabled) {
201 continue;
202 }
203 if (!(dev->use == XISlavePointer || dev->use == XIFloatingSlave)) {
204 continue;
205 }
206
207 bool direct_touch = false;
208 bool absolute_mode = false;
209 int resolution_x = 0;
210 int resolution_y = 0;
211 double abs_x_min = 0;
212 double abs_x_max = 0;
213 double abs_y_min = 0;
214 double abs_y_max = 0;
215 double pressure_min = 0;
216 double pressure_max = 0;
217 double tilt_x_min = 0;
218 double tilt_x_max = 0;
219 double tilt_y_min = 0;
220 double tilt_y_max = 0;
221 for (int j = 0; j < dev->num_classes; j++) {
222#ifdef TOUCH_ENABLED
223 if (dev->classes[j]->type == XITouchClass && ((XITouchClassInfo *)dev->classes[j])->mode == XIDirectTouch) {
224 direct_touch = true;
225 }
226#endif
227 if (dev->classes[j]->type == XIValuatorClass) {
228 XIValuatorClassInfo *class_info = (XIValuatorClassInfo *)dev->classes[j];
229
230 if (class_info->number == VALUATOR_ABSX && class_info->mode == XIModeAbsolute) {
231 resolution_x = class_info->resolution;
232 abs_x_min = class_info->min;
233 abs_x_max = class_info->max;
234 absolute_mode = true;
235 } else if (class_info->number == VALUATOR_ABSY && class_info->mode == XIModeAbsolute) {
236 resolution_y = class_info->resolution;
237 abs_y_min = class_info->min;
238 abs_y_max = class_info->max;
239 absolute_mode = true;
240 } else if (class_info->number == VALUATOR_PRESSURE && class_info->mode == XIModeAbsolute) {
241 pressure_min = class_info->min;
242 pressure_max = class_info->max;
243 } else if (class_info->number == VALUATOR_TILTX && class_info->mode == XIModeAbsolute) {
244 tilt_x_min = class_info->min;
245 tilt_x_max = class_info->max;
246 } else if (class_info->number == VALUATOR_TILTY && class_info->mode == XIModeAbsolute) {
247 tilt_y_min = class_info->min;
248 tilt_y_max = class_info->max;
249 }
250 }
251 }
252 if (direct_touch) {
253 xi.touch_devices.push_back(dev->deviceid);
254 print_verbose("XInput: Using touch device: " + String(dev->name));
255 }
256 if (absolute_mode) {
257 // If no resolution was reported, use the min/max ranges.
258 if (resolution_x <= 0) {
259 resolution_x = (abs_x_max - abs_x_min) * abs_resolution_range_mult;
260 }
261 if (resolution_y <= 0) {
262 resolution_y = (abs_y_max - abs_y_min) * abs_resolution_range_mult;
263 }
264 xi.absolute_devices[dev->deviceid] = Vector2(abs_resolution_mult / resolution_x, abs_resolution_mult / resolution_y);
265 print_verbose("XInput: Absolute pointing device: " + String(dev->name));
266 }
267
268 xi.pressure = 0;
269 xi.pen_pressure_range[dev->deviceid] = Vector2(pressure_min, pressure_max);
270 xi.pen_tilt_x_range[dev->deviceid] = Vector2(tilt_x_min, tilt_x_max);
271 xi.pen_tilt_y_range[dev->deviceid] = Vector2(tilt_y_min, tilt_y_max);
272 xi.pen_inverted_devices[dev->deviceid] = String(dev->name).findn("eraser") > 0;
273 }
274
275 XIFreeDeviceInfo(info);
276#ifdef TOUCH_ENABLED
277 if (!xi.touch_devices.size()) {
278 print_verbose("XInput: No touch devices found.");
279 }
280#endif
281
282 return true;
283}
284
285void DisplayServerX11::_flush_mouse_motion() {
286 // Block events polling while flushing motion events.
287 MutexLock mutex_lock(events_mutex);
288
289 for (uint32_t event_index = 0; event_index < polled_events.size(); ++event_index) {
290 XEvent &event = polled_events[event_index];
291 if (XGetEventData(x11_display, &event.xcookie) && event.xcookie.type == GenericEvent && event.xcookie.extension == xi.opcode) {
292 XIDeviceEvent *event_data = (XIDeviceEvent *)event.xcookie.data;
293 if (event_data->evtype == XI_RawMotion) {
294 XFreeEventData(x11_display, &event.xcookie);
295 polled_events.remove_at(event_index--);
296 continue;
297 }
298 XFreeEventData(x11_display, &event.xcookie);
299 break;
300 }
301 }
302
303 xi.relative_motion.x = 0;
304 xi.relative_motion.y = 0;
305}
306
307#ifdef SPEECHD_ENABLED
308
309bool DisplayServerX11::tts_is_speaking() const {
310 ERR_FAIL_NULL_V_MSG(tts, false, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
311 return tts->is_speaking();
312}
313
314bool DisplayServerX11::tts_is_paused() const {
315 ERR_FAIL_NULL_V_MSG(tts, false, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
316 return tts->is_paused();
317}
318
319TypedArray<Dictionary> DisplayServerX11::tts_get_voices() const {
320 ERR_FAIL_NULL_V_MSG(tts, TypedArray<Dictionary>(), "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
321 return tts->get_voices();
322}
323
324void 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) {
325 ERR_FAIL_NULL_MSG(tts, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
326 tts->speak(p_text, p_voice, p_volume, p_pitch, p_rate, p_utterance_id, p_interrupt);
327}
328
329void DisplayServerX11::tts_pause() {
330 ERR_FAIL_NULL_MSG(tts, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
331 tts->pause();
332}
333
334void DisplayServerX11::tts_resume() {
335 ERR_FAIL_NULL_MSG(tts, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
336 tts->resume();
337}
338
339void DisplayServerX11::tts_stop() {
340 ERR_FAIL_NULL_MSG(tts, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
341 tts->stop();
342}
343
344#endif
345
346#ifdef DBUS_ENABLED
347
348bool DisplayServerX11::is_dark_mode_supported() const {
349 return portal_desktop->is_supported();
350}
351
352bool DisplayServerX11::is_dark_mode() const {
353 switch (portal_desktop->get_appearance_color_scheme()) {
354 case 1:
355 // Prefers dark theme.
356 return true;
357 case 2:
358 // Prefers light theme.
359 return false;
360 default:
361 // Preference unknown.
362 return false;
363 }
364}
365
366Error 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) {
367 WindowID window_id = _get_focused_window_or_popup();
368
369 if (!windows.has(window_id)) {
370 window_id = MAIN_WINDOW_ID;
371 }
372
373 String xid = vformat("x11:%x", (uint64_t)windows[window_id].x11_window);
374 return portal_desktop->file_dialog_show(xid, p_title, p_current_directory, p_filename, p_mode, p_filters, p_callback);
375}
376
377#endif
378
379void DisplayServerX11::mouse_set_mode(MouseMode p_mode) {
380 _THREAD_SAFE_METHOD_
381
382 if (p_mode == mouse_mode) {
383 return;
384 }
385
386 if (mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN) {
387 XUngrabPointer(x11_display, CurrentTime);
388 }
389
390 // The only modes that show a cursor are VISIBLE and CONFINED
391 bool show_cursor = (p_mode == MOUSE_MODE_VISIBLE || p_mode == MOUSE_MODE_CONFINED);
392 bool previously_shown = (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED);
393
394 if (show_cursor && !previously_shown) {
395 WindowID window_id = get_window_at_screen_position(mouse_get_position());
396 if (window_id != INVALID_WINDOW_ID && window_mouseover_id != window_id) {
397 if (window_mouseover_id != INVALID_WINDOW_ID) {
398 _send_window_event(windows[window_mouseover_id], WINDOW_EVENT_MOUSE_EXIT);
399 }
400 window_mouseover_id = window_id;
401 _send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_ENTER);
402 }
403 }
404
405 for (const KeyValue<WindowID, WindowData> &E : windows) {
406 if (show_cursor) {
407 XDefineCursor(x11_display, E.value.x11_window, cursors[current_cursor]); // show cursor
408 } else {
409 XDefineCursor(x11_display, E.value.x11_window, null_cursor); // hide cursor
410 }
411 }
412 mouse_mode = p_mode;
413
414 if (mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN) {
415 //flush pending motion events
416 _flush_mouse_motion();
417 WindowID window_id = _get_focused_window_or_popup();
418 if (!windows.has(window_id)) {
419 window_id = MAIN_WINDOW_ID;
420 }
421 WindowData &window = windows[window_id];
422
423 if (XGrabPointer(
424 x11_display, window.x11_window, True,
425 ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
426 GrabModeAsync, GrabModeAsync, window.x11_window, None, CurrentTime) != GrabSuccess) {
427 ERR_PRINT("NO GRAB");
428 }
429
430 if (mouse_mode == MOUSE_MODE_CAPTURED) {
431 center.x = window.size.width / 2;
432 center.y = window.size.height / 2;
433
434 XWarpPointer(x11_display, None, window.x11_window,
435 0, 0, 0, 0, (int)center.x, (int)center.y);
436
437 Input::get_singleton()->set_mouse_position(center);
438 }
439 } else {
440 do_mouse_warp = false;
441 }
442
443 XFlush(x11_display);
444}
445
446DisplayServerX11::MouseMode DisplayServerX11::mouse_get_mode() const {
447 return mouse_mode;
448}
449
450void DisplayServerX11::warp_mouse(const Point2i &p_position) {
451 _THREAD_SAFE_METHOD_
452
453 if (mouse_mode == MOUSE_MODE_CAPTURED) {
454 last_mouse_pos = p_position;
455 } else {
456 WindowID window_id = _get_focused_window_or_popup();
457 if (!windows.has(window_id)) {
458 window_id = MAIN_WINDOW_ID;
459 }
460
461 XWarpPointer(x11_display, None, windows[window_id].x11_window,
462 0, 0, 0, 0, (int)p_position.x, (int)p_position.y);
463 }
464}
465
466Point2i DisplayServerX11::mouse_get_position() const {
467 int number_of_screens = XScreenCount(x11_display);
468 for (int i = 0; i < number_of_screens; i++) {
469 Window root, child;
470 int root_x, root_y, win_x, win_y;
471 unsigned int mask;
472 if (XQueryPointer(x11_display, XRootWindow(x11_display, i), &root, &child, &root_x, &root_y, &win_x, &win_y, &mask)) {
473 XWindowAttributes root_attrs;
474 XGetWindowAttributes(x11_display, root, &root_attrs);
475
476 return Vector2i(root_attrs.x + root_x, root_attrs.y + root_y);
477 }
478 }
479 return Vector2i();
480}
481
482BitField<MouseButtonMask> DisplayServerX11::mouse_get_button_state() const {
483 return last_button_state;
484}
485
486void DisplayServerX11::clipboard_set(const String &p_text) {
487 _THREAD_SAFE_METHOD_
488
489 {
490 // The clipboard content can be accessed while polling for events.
491 MutexLock mutex_lock(events_mutex);
492 internal_clipboard = p_text;
493 }
494
495 XSetSelectionOwner(x11_display, XA_PRIMARY, windows[MAIN_WINDOW_ID].x11_window, CurrentTime);
496 XSetSelectionOwner(x11_display, XInternAtom(x11_display, "CLIPBOARD", 0), windows[MAIN_WINDOW_ID].x11_window, CurrentTime);
497}
498
499void DisplayServerX11::clipboard_set_primary(const String &p_text) {
500 _THREAD_SAFE_METHOD_
501 if (!p_text.is_empty()) {
502 {
503 // The clipboard content can be accessed while polling for events.
504 MutexLock mutex_lock(events_mutex);
505 internal_clipboard_primary = p_text;
506 }
507
508 XSetSelectionOwner(x11_display, XA_PRIMARY, windows[MAIN_WINDOW_ID].x11_window, CurrentTime);
509 XSetSelectionOwner(x11_display, XInternAtom(x11_display, "PRIMARY", 0), windows[MAIN_WINDOW_ID].x11_window, CurrentTime);
510 }
511}
512
513Bool DisplayServerX11::_predicate_clipboard_selection(Display *display, XEvent *event, XPointer arg) {
514 if (event->type == SelectionNotify && event->xselection.requestor == *(Window *)arg) {
515 return True;
516 } else {
517 return False;
518 }
519}
520
521Bool DisplayServerX11::_predicate_clipboard_incr(Display *display, XEvent *event, XPointer arg) {
522 if (event->type == PropertyNotify && event->xproperty.state == PropertyNewValue) {
523 return True;
524 } else {
525 return False;
526 }
527}
528
529String DisplayServerX11::_clipboard_get_impl(Atom p_source, Window x11_window, Atom target) const {
530 String ret;
531
532 Window selection_owner = XGetSelectionOwner(x11_display, p_source);
533 if (selection_owner == x11_window) {
534 static const char *target_type = "PRIMARY";
535 if (p_source != None && get_atom_name(x11_display, p_source) == target_type) {
536 return internal_clipboard_primary;
537 } else {
538 return internal_clipboard;
539 }
540 }
541
542 if (selection_owner != None) {
543 // Block events polling while processing selection events.
544 MutexLock mutex_lock(events_mutex);
545
546 Atom selection = XA_PRIMARY;
547 XConvertSelection(x11_display, p_source, target, selection,
548 x11_window, CurrentTime);
549
550 XFlush(x11_display);
551
552 // Blocking wait for predicate to be True and remove the event from the queue.
553 XEvent event;
554 XIfEvent(x11_display, &event, _predicate_clipboard_selection, (XPointer)&x11_window);
555
556 // Do not get any data, see how much data is there.
557 Atom type;
558 int format, result;
559 unsigned long len, bytes_left, dummy;
560 unsigned char *data;
561 XGetWindowProperty(x11_display, x11_window,
562 selection, // Tricky..
563 0, 0, // offset - len
564 0, // Delete 0==FALSE
565 AnyPropertyType, // flag
566 &type, // return type
567 &format, // return format
568 &len, &bytes_left, // data length
569 &data);
570
571 if (data) {
572 XFree(data);
573 }
574
575 if (type == XInternAtom(x11_display, "INCR", 0)) {
576 // Data is going to be received incrementally.
577 DEBUG_LOG_X11("INCR selection started.\n");
578
579 LocalVector<uint8_t> incr_data;
580 uint32_t data_size = 0;
581 bool success = false;
582
583 // Delete INCR property to notify the owner.
584 XDeleteProperty(x11_display, x11_window, type);
585
586 // Process events from the queue.
587 bool done = false;
588 while (!done) {
589 if (!_wait_for_events()) {
590 // Error or timeout, abort.
591 break;
592 }
593
594 // Non-blocking wait for next event and remove it from the queue.
595 XEvent ev;
596 while (XCheckIfEvent(x11_display, &ev, _predicate_clipboard_incr, nullptr)) {
597 result = XGetWindowProperty(x11_display, x11_window,
598 selection, // selection type
599 0, LONG_MAX, // offset - len
600 True, // delete property to notify the owner
601 AnyPropertyType, // flag
602 &type, // return type
603 &format, // return format
604 &len, &bytes_left, // data length
605 &data);
606
607 DEBUG_LOG_X11("PropertyNotify: len=%lu, format=%i\n", len, format);
608
609 if (result == Success) {
610 if (data && (len > 0)) {
611 uint32_t prev_size = incr_data.size();
612 if (prev_size == 0) {
613 // First property contains initial data size.
614 unsigned long initial_size = *(unsigned long *)data;
615 incr_data.resize(initial_size);
616 } else {
617 // New chunk, resize to be safe and append data.
618 incr_data.resize(MAX(data_size + len, prev_size));
619 memcpy(incr_data.ptr() + data_size, data, len);
620 data_size += len;
621 }
622 } else {
623 // Last chunk, process finished.
624 done = true;
625 success = true;
626 }
627 } else {
628 print_verbose("Failed to get selection data chunk.");
629 done = true;
630 }
631
632 if (data) {
633 XFree(data);
634 }
635
636 if (done) {
637 break;
638 }
639 }
640 }
641
642 if (success && (data_size > 0)) {
643 ret.parse_utf8((const char *)incr_data.ptr(), data_size);
644 }
645 } else if (bytes_left > 0) {
646 // Data is ready and can be processed all at once.
647 result = XGetWindowProperty(x11_display, x11_window,
648 selection, 0, bytes_left, 0,
649 AnyPropertyType, &type, &format,
650 &len, &dummy, &data);
651
652 if (result == Success) {
653 ret.parse_utf8((const char *)data);
654 } else {
655 print_verbose("Failed to get selection data.");
656 }
657
658 if (data) {
659 XFree(data);
660 }
661 }
662 }
663
664 return ret;
665}
666
667String DisplayServerX11::_clipboard_get(Atom p_source, Window x11_window) const {
668 String ret;
669 Atom utf8_atom = XInternAtom(x11_display, "UTF8_STRING", True);
670 if (utf8_atom != None) {
671 ret = _clipboard_get_impl(p_source, x11_window, utf8_atom);
672 }
673 if (ret.is_empty()) {
674 ret = _clipboard_get_impl(p_source, x11_window, XA_STRING);
675 }
676 return ret;
677}
678
679String DisplayServerX11::clipboard_get() const {
680 _THREAD_SAFE_METHOD_
681
682 String ret;
683 ret = _clipboard_get(XInternAtom(x11_display, "CLIPBOARD", 0), windows[MAIN_WINDOW_ID].x11_window);
684
685 if (ret.is_empty()) {
686 ret = _clipboard_get(XA_PRIMARY, windows[MAIN_WINDOW_ID].x11_window);
687 }
688
689 return ret;
690}
691
692String DisplayServerX11::clipboard_get_primary() const {
693 _THREAD_SAFE_METHOD_
694
695 String ret;
696 ret = _clipboard_get(XInternAtom(x11_display, "PRIMARY", 0), windows[MAIN_WINDOW_ID].x11_window);
697
698 if (ret.is_empty()) {
699 ret = _clipboard_get(XA_PRIMARY, windows[MAIN_WINDOW_ID].x11_window);
700 }
701
702 return ret;
703}
704
705Bool DisplayServerX11::_predicate_clipboard_save_targets(Display *display, XEvent *event, XPointer arg) {
706 if (event->xany.window == *(Window *)arg) {
707 return (event->type == SelectionRequest) ||
708 (event->type == SelectionNotify);
709 } else {
710 return False;
711 }
712}
713
714void DisplayServerX11::_clipboard_transfer_ownership(Atom p_source, Window x11_window) const {
715 _THREAD_SAFE_METHOD_
716
717 Window selection_owner = XGetSelectionOwner(x11_display, p_source);
718
719 if (selection_owner != x11_window) {
720 return;
721 }
722
723 // Block events polling while processing selection events.
724 MutexLock mutex_lock(events_mutex);
725
726 Atom clipboard_manager = XInternAtom(x11_display, "CLIPBOARD_MANAGER", False);
727 Atom save_targets = XInternAtom(x11_display, "SAVE_TARGETS", False);
728 XConvertSelection(x11_display, clipboard_manager, save_targets, None,
729 x11_window, CurrentTime);
730
731 // Process events from the queue.
732 while (true) {
733 if (!_wait_for_events()) {
734 // Error or timeout, abort.
735 break;
736 }
737
738 // Non-blocking wait for next event and remove it from the queue.
739 XEvent ev;
740 while (XCheckIfEvent(x11_display, &ev, _predicate_clipboard_save_targets, (XPointer)&x11_window)) {
741 switch (ev.type) {
742 case SelectionRequest:
743 _handle_selection_request_event(&(ev.xselectionrequest));
744 break;
745
746 case SelectionNotify: {
747 if (ev.xselection.target == save_targets) {
748 // Once SelectionNotify is received, we're done whether it succeeded or not.
749 return;
750 }
751
752 break;
753 }
754 }
755 }
756 }
757}
758
759int DisplayServerX11::get_screen_count() const {
760 _THREAD_SAFE_METHOD_
761 int count = 0;
762
763 // Using Xinerama Extension
764 int event_base, error_base;
765 if (xinerama_ext_ok && XineramaQueryExtension(x11_display, &event_base, &error_base)) {
766 XineramaScreenInfo *xsi = XineramaQueryScreens(x11_display, &count);
767 XFree(xsi);
768 } else {
769 count = XScreenCount(x11_display);
770 }
771
772 return count;
773}
774
775int DisplayServerX11::get_primary_screen() const {
776 int event_base, error_base;
777 if (xinerama_ext_ok && XineramaQueryExtension(x11_display, &event_base, &error_base)) {
778 return 0;
779 } else {
780 return XDefaultScreen(x11_display);
781 }
782}
783
784int DisplayServerX11::get_keyboard_focus_screen() const {
785 int count = get_screen_count();
786 if (count < 2) {
787 // Early exit with single monitor.
788 return 0;
789 }
790
791 Window focus = 0;
792 int revert_to = 0;
793
794 XGetInputFocus(x11_display, &focus, &revert_to);
795 if (focus) {
796 Window focus_child = 0;
797 int x = 0, y = 0;
798 XTranslateCoordinates(x11_display, focus, DefaultRootWindow(x11_display), 0, 0, &x, &y, &focus_child);
799
800 XWindowAttributes xwa;
801 XGetWindowAttributes(x11_display, focus, &xwa);
802 Rect2i window_rect = Rect2i(x, y, xwa.width, xwa.height);
803
804 // Find which monitor has the largest overlap with the given window.
805 int screen_index = 0;
806 int max_area = 0;
807 for (int i = 0; i < count; i++) {
808 Rect2i screen_rect = _screen_get_rect(i);
809 Rect2i intersection = screen_rect.intersection(window_rect);
810 int area = intersection.get_area();
811 if (area > max_area) {
812 max_area = area;
813 screen_index = i;
814 }
815 }
816 return screen_index;
817 }
818
819 return get_primary_screen();
820}
821
822Rect2i DisplayServerX11::_screen_get_rect(int p_screen) const {
823 Rect2i rect(0, 0, 0, 0);
824
825 p_screen = _get_screen_index(p_screen);
826 ERR_FAIL_COND_V(p_screen < 0, rect);
827
828 // Using Xinerama Extension.
829 int event_base, error_base;
830 if (xinerama_ext_ok && XineramaQueryExtension(x11_display, &event_base, &error_base)) {
831 int count;
832 XineramaScreenInfo *xsi = XineramaQueryScreens(x11_display, &count);
833
834 // Check if screen is valid.
835 if (p_screen < count) {
836 rect.position.x = xsi[p_screen].x_org;
837 rect.position.y = xsi[p_screen].y_org;
838 rect.size.width = xsi[p_screen].width;
839 rect.size.height = xsi[p_screen].height;
840 } else {
841 ERR_PRINT("Invalid screen index: " + itos(p_screen) + "(count: " + itos(count) + ").");
842 }
843
844 if (xsi) {
845 XFree(xsi);
846 }
847 } else {
848 int count = XScreenCount(x11_display);
849 if (p_screen < count) {
850 Window root = XRootWindow(x11_display, p_screen);
851 XWindowAttributes xwa;
852 XGetWindowAttributes(x11_display, root, &xwa);
853 rect.position.x = xwa.x;
854 rect.position.y = xwa.y;
855 rect.size.width = xwa.width;
856 rect.size.height = xwa.height;
857 } else {
858 ERR_PRINT("Invalid screen index: " + itos(p_screen) + "(count: " + itos(count) + ").");
859 }
860 }
861
862 return rect;
863}
864
865Point2i DisplayServerX11::screen_get_position(int p_screen) const {
866 _THREAD_SAFE_METHOD_
867
868 return _screen_get_rect(p_screen).position;
869}
870
871Size2i DisplayServerX11::screen_get_size(int p_screen) const {
872 _THREAD_SAFE_METHOD_
873
874 return _screen_get_rect(p_screen).size;
875}
876
877// A Handler to avoid crashing on non-fatal X errors by default.
878//
879// The original X11 error formatter `_XPrintDefaultError` is defined here:
880// https://gitlab.freedesktop.org/xorg/lib/libx11/-/blob/e45ca7b41dcd3ace7681d6897505f85d374640f2/src/XlibInt.c#L1322
881// It is not exposed through the API, accesses X11 internals,
882// and is much more complex, so this is a less complete simplified error X11 printer.
883int default_window_error_handler(Display *display, XErrorEvent *error) {
884 static char message[1024];
885 XGetErrorText(display, error->error_code, message, sizeof(message));
886
887 ERR_PRINT(vformat("Unhandled XServer error: %s"
888 "\n Major opcode of failed request: %d"
889 "\n Serial number of failed request: %d"
890 "\n Current serial number in output stream: %d",
891 String::utf8(message), (uint64_t)error->request_code, (uint64_t)error->minor_code, (uint64_t)error->serial));
892 return 0;
893}
894
895bool g_bad_window = false;
896int bad_window_error_handler(Display *display, XErrorEvent *error) {
897 if (error->error_code == BadWindow) {
898 g_bad_window = true;
899 } else {
900 return default_window_error_handler(display, error);
901 }
902 return 0;
903}
904
905Rect2i DisplayServerX11::screen_get_usable_rect(int p_screen) const {
906 _THREAD_SAFE_METHOD_
907
908 p_screen = _get_screen_index(p_screen);
909 int screen_count = get_screen_count();
910
911 // Check if screen is valid.
912 ERR_FAIL_INDEX_V(p_screen, screen_count, Rect2i(0, 0, 0, 0));
913
914 bool is_multiscreen = screen_count > 1;
915
916 // Use full monitor size as fallback.
917 Rect2i rect = _screen_get_rect(p_screen);
918
919 // There's generally only one screen reported by xlib even in multi-screen setup,
920 // in this case it's just one virtual screen composed of all physical monitors.
921 int x11_screen_count = ScreenCount(x11_display);
922 Window x11_window = RootWindow(x11_display, p_screen < x11_screen_count ? p_screen : 0);
923
924 Atom type;
925 int format = 0;
926 unsigned long remaining = 0;
927
928 // Find active desktop for the root window.
929 unsigned int desktop_index = 0;
930 Atom desktop_prop = XInternAtom(x11_display, "_NET_CURRENT_DESKTOP", True);
931 if (desktop_prop != None) {
932 unsigned long desktop_len = 0;
933 unsigned char *desktop_data = nullptr;
934 if (XGetWindowProperty(x11_display, x11_window, desktop_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &desktop_len, &remaining, &desktop_data) == Success) {
935 if ((format == 32) && (desktop_len > 0) && desktop_data) {
936 desktop_index = (unsigned int)desktop_data[0];
937 }
938 if (desktop_data) {
939 XFree(desktop_data);
940 }
941 }
942 }
943
944 bool use_simple_method = true;
945
946 // First check for GTK work area, which is more accurate for multi-screen setup.
947 if (is_multiscreen) {
948 // Use already calculated work area when available.
949 Atom gtk_workareas_prop = XInternAtom(x11_display, "_GTK_WORKAREAS", False);
950 if (gtk_workareas_prop != None) {
951 char gtk_workarea_prop_name[32];
952 snprintf(gtk_workarea_prop_name, 32, "_GTK_WORKAREAS_D%d", desktop_index);
953 Atom gtk_workarea_prop = XInternAtom(x11_display, gtk_workarea_prop_name, True);
954 if (gtk_workarea_prop != None) {
955 unsigned long workarea_len = 0;
956 unsigned char *workarea_data = nullptr;
957 if (XGetWindowProperty(x11_display, x11_window, gtk_workarea_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &workarea_len, &remaining, &workarea_data) == Success) {
958 if ((format == 32) && (workarea_len % 4 == 0) && workarea_data) {
959 long *rect_data = (long *)workarea_data;
960 for (uint32_t data_offset = 0; data_offset < workarea_len; data_offset += 4) {
961 Rect2i workarea_rect;
962 workarea_rect.position.x = rect_data[data_offset];
963 workarea_rect.position.y = rect_data[data_offset + 1];
964 workarea_rect.size.x = rect_data[data_offset + 2];
965 workarea_rect.size.y = rect_data[data_offset + 3];
966
967 // Intersect with actual monitor size to find the correct area,
968 // because areas are not in the same order as screens from Xinerama.
969 if (rect.grow(-1).intersects(workarea_rect)) {
970 rect = rect.intersection(workarea_rect);
971 XFree(workarea_data);
972 return rect;
973 }
974 }
975 }
976 }
977 if (workarea_data) {
978 XFree(workarea_data);
979 }
980 }
981 }
982
983 // Fallback to calculating work area by hand from struts.
984 Atom client_list_prop = XInternAtom(x11_display, "_NET_CLIENT_LIST", True);
985 if (client_list_prop != None) {
986 unsigned long clients_len = 0;
987 unsigned char *clients_data = nullptr;
988 if (XGetWindowProperty(x11_display, x11_window, client_list_prop, 0, LONG_MAX, False, XA_WINDOW, &type, &format, &clients_len, &remaining, &clients_data) == Success) {
989 if ((format == 32) && (clients_len > 0) && clients_data) {
990 Window *windows_data = (Window *)clients_data;
991
992 Rect2i desktop_rect;
993 bool desktop_valid = false;
994
995 // Get full desktop size.
996 {
997 Atom desktop_geometry_prop = XInternAtom(x11_display, "_NET_DESKTOP_GEOMETRY", True);
998 if (desktop_geometry_prop != None) {
999 unsigned long geom_len = 0;
1000 unsigned char *geom_data = nullptr;
1001 if (XGetWindowProperty(x11_display, x11_window, desktop_geometry_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &geom_len, &remaining, &geom_data) == Success) {
1002 if ((format == 32) && (geom_len >= 2) && geom_data) {
1003 desktop_valid = true;
1004 long *size_data = (long *)geom_data;
1005 desktop_rect.size.x = size_data[0];
1006 desktop_rect.size.y = size_data[1];
1007 }
1008 }
1009 if (geom_data) {
1010 XFree(geom_data);
1011 }
1012 }
1013 }
1014
1015 // Get full desktop position.
1016 if (desktop_valid) {
1017 Atom desktop_viewport_prop = XInternAtom(x11_display, "_NET_DESKTOP_VIEWPORT", True);
1018 if (desktop_viewport_prop != None) {
1019 unsigned long viewport_len = 0;
1020 unsigned char *viewport_data = nullptr;
1021 if (XGetWindowProperty(x11_display, x11_window, desktop_viewport_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &viewport_len, &remaining, &viewport_data) == Success) {
1022 if ((format == 32) && (viewport_len >= 2) && viewport_data) {
1023 desktop_valid = true;
1024 long *pos_data = (long *)viewport_data;
1025 desktop_rect.position.x = pos_data[0];
1026 desktop_rect.position.y = pos_data[1];
1027 }
1028 }
1029 if (viewport_data) {
1030 XFree(viewport_data);
1031 }
1032 }
1033 }
1034
1035 if (desktop_valid) {
1036 use_simple_method = false;
1037
1038 // Handle bad window errors silently because there's no other way to check
1039 // that one of the windows has been destroyed in the meantime.
1040 int (*oldHandler)(Display *, XErrorEvent *) = XSetErrorHandler(&bad_window_error_handler);
1041
1042 for (unsigned long win_index = 0; win_index < clients_len; ++win_index) {
1043 g_bad_window = false;
1044
1045 // Remove strut size from desktop size to get a more accurate result.
1046 bool strut_found = false;
1047 unsigned long strut_len = 0;
1048 unsigned char *strut_data = nullptr;
1049 Atom strut_partial_prop = XInternAtom(x11_display, "_NET_WM_STRUT_PARTIAL", True);
1050 if (strut_partial_prop != None) {
1051 if (XGetWindowProperty(x11_display, windows_data[win_index], strut_partial_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &strut_len, &remaining, &strut_data) == Success) {
1052 strut_found = true;
1053 }
1054 }
1055 // Fallback to older strut property.
1056 if (!g_bad_window && !strut_found) {
1057 Atom strut_prop = XInternAtom(x11_display, "_NET_WM_STRUT", True);
1058 if (strut_prop != None) {
1059 if (XGetWindowProperty(x11_display, windows_data[win_index], strut_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &strut_len, &remaining, &strut_data) == Success) {
1060 strut_found = true;
1061 }
1062 }
1063 }
1064 if (!g_bad_window && strut_found && (format == 32) && (strut_len >= 4) && strut_data) {
1065 long *struts = (long *)strut_data;
1066
1067 long left = struts[0];
1068 long right = struts[1];
1069 long top = struts[2];
1070 long bottom = struts[3];
1071
1072 long left_start_y, left_end_y, right_start_y, right_end_y;
1073 long top_start_x, top_end_x, bottom_start_x, bottom_end_x;
1074
1075 if (strut_len >= 12) {
1076 left_start_y = struts[4];
1077 left_end_y = struts[5];
1078 right_start_y = struts[6];
1079 right_end_y = struts[7];
1080 top_start_x = struts[8];
1081 top_end_x = struts[9];
1082 bottom_start_x = struts[10];
1083 bottom_end_x = struts[11];
1084 } else {
1085 left_start_y = 0;
1086 left_end_y = desktop_rect.size.y;
1087 right_start_y = 0;
1088 right_end_y = desktop_rect.size.y;
1089 top_start_x = 0;
1090 top_end_x = desktop_rect.size.x;
1091 bottom_start_x = 0;
1092 bottom_end_x = desktop_rect.size.x;
1093 }
1094
1095 const Point2i &pos = desktop_rect.position;
1096 const Size2i &size = desktop_rect.size;
1097
1098 Rect2i left_rect(pos.x, pos.y + left_start_y, left, left_end_y - left_start_y);
1099 if (left_rect.size.x > 0) {
1100 Rect2i intersection = rect.intersection(left_rect);
1101 if (intersection.has_area() && intersection.size.x < rect.size.x) {
1102 rect.position.x = left_rect.size.x;
1103 rect.size.x = rect.size.x - intersection.size.x;
1104 }
1105 }
1106
1107 Rect2i right_rect(pos.x + size.x - right, pos.y + right_start_y, right, right_end_y - right_start_y);
1108 if (right_rect.size.x > 0) {
1109 Rect2i intersection = rect.intersection(right_rect);
1110 if (intersection.has_area() && right_rect.size.x < rect.size.x) {
1111 rect.size.x = intersection.position.x - rect.position.x;
1112 }
1113 }
1114
1115 Rect2i top_rect(pos.x + top_start_x, pos.y, top_end_x - top_start_x, top);
1116 if (top_rect.size.y > 0) {
1117 Rect2i intersection = rect.intersection(top_rect);
1118 if (intersection.has_area() && intersection.size.y < rect.size.y) {
1119 rect.position.y = top_rect.size.y;
1120 rect.size.y = rect.size.y - intersection.size.y;
1121 }
1122 }
1123
1124 Rect2i bottom_rect(pos.x + bottom_start_x, pos.y + size.y - bottom, bottom_end_x - bottom_start_x, bottom);
1125 if (bottom_rect.size.y > 0) {
1126 Rect2i intersection = rect.intersection(bottom_rect);
1127 if (intersection.has_area() && right_rect.size.y < rect.size.y) {
1128 rect.size.y = intersection.position.y - rect.position.y;
1129 }
1130 }
1131 }
1132 if (strut_data) {
1133 XFree(strut_data);
1134 }
1135 }
1136
1137 // Restore default error handler.
1138 XSetErrorHandler(oldHandler);
1139 }
1140 }
1141 }
1142 if (clients_data) {
1143 XFree(clients_data);
1144 }
1145 }
1146 }
1147
1148 // Single screen or fallback for multi screen.
1149 if (use_simple_method) {
1150 // Get desktop available size from the global work area.
1151 Atom workarea_prop = XInternAtom(x11_display, "_NET_WORKAREA", True);
1152 if (workarea_prop != None) {
1153 unsigned long workarea_len = 0;
1154 unsigned char *workarea_data = nullptr;
1155 if (XGetWindowProperty(x11_display, x11_window, workarea_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &workarea_len, &remaining, &workarea_data) == Success) {
1156 if ((format == 32) && (workarea_len >= ((desktop_index + 1) * 4)) && workarea_data) {
1157 long *rect_data = (long *)workarea_data;
1158 int data_offset = desktop_index * 4;
1159 Rect2i workarea_rect;
1160 workarea_rect.position.x = rect_data[data_offset];
1161 workarea_rect.position.y = rect_data[data_offset + 1];
1162 workarea_rect.size.x = rect_data[data_offset + 2];
1163 workarea_rect.size.y = rect_data[data_offset + 3];
1164
1165 // Intersect with actual monitor size to get a proper approximation in multi-screen setup.
1166 if (!is_multiscreen) {
1167 rect = workarea_rect;
1168 } else if (rect.intersects(workarea_rect)) {
1169 rect = rect.intersection(workarea_rect);
1170 }
1171 }
1172 }
1173 if (workarea_data) {
1174 XFree(workarea_data);
1175 }
1176 }
1177 }
1178
1179 return rect;
1180}
1181
1182int DisplayServerX11::screen_get_dpi(int p_screen) const {
1183 _THREAD_SAFE_METHOD_
1184
1185 p_screen = _get_screen_index(p_screen);
1186 ERR_FAIL_INDEX_V(p_screen, get_screen_count(), 0);
1187
1188 //Get physical monitor Dimensions through XRandR and calculate dpi
1189 Size2i sc = screen_get_size(p_screen);
1190 if (xrandr_ext_ok) {
1191 int count = 0;
1192 if (xrr_get_monitors) {
1193 xrr_monitor_info *monitors = xrr_get_monitors(x11_display, windows[MAIN_WINDOW_ID].x11_window, true, &count);
1194 if (p_screen < count) {
1195 double xdpi = sc.width / (double)monitors[p_screen].mwidth * 25.4;
1196 double ydpi = sc.height / (double)monitors[p_screen].mheight * 25.4;
1197 xrr_free_monitors(monitors);
1198 return (xdpi + ydpi) / 2;
1199 }
1200 xrr_free_monitors(monitors);
1201 } else if (p_screen == 0) {
1202 XRRScreenSize *sizes = XRRSizes(x11_display, 0, &count);
1203 if (sizes) {
1204 double xdpi = sc.width / (double)sizes[0].mwidth * 25.4;
1205 double ydpi = sc.height / (double)sizes[0].mheight * 25.4;
1206 return (xdpi + ydpi) / 2;
1207 }
1208 }
1209 }
1210
1211 int width_mm = DisplayWidthMM(x11_display, p_screen);
1212 int height_mm = DisplayHeightMM(x11_display, p_screen);
1213 double xdpi = (width_mm ? sc.width / (double)width_mm * 25.4 : 0);
1214 double ydpi = (height_mm ? sc.height / (double)height_mm * 25.4 : 0);
1215 if (xdpi || ydpi) {
1216 return (xdpi + ydpi) / (xdpi && ydpi ? 2 : 1);
1217 }
1218
1219 //could not get dpi
1220 return 96;
1221}
1222
1223Color DisplayServerX11::screen_get_pixel(const Point2i &p_position) const {
1224 Point2i pos = p_position;
1225
1226 int number_of_screens = XScreenCount(x11_display);
1227 for (int i = 0; i < number_of_screens; i++) {
1228 Window root = XRootWindow(x11_display, i);
1229 XWindowAttributes root_attrs;
1230 XGetWindowAttributes(x11_display, root, &root_attrs);
1231 if ((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)) {
1232 XImage *image = XGetImage(x11_display, root, pos.x, pos.y, 1, 1, AllPlanes, XYPixmap);
1233 if (image) {
1234 XColor c;
1235 c.pixel = XGetPixel(image, 0, 0);
1236 XFree(image);
1237 XQueryColor(x11_display, XDefaultColormap(x11_display, i), &c);
1238 return Color(float(c.red) / 65535.0, float(c.green) / 65535.0, float(c.blue) / 65535.0, 1.0);
1239 }
1240 }
1241 }
1242
1243 return Color();
1244}
1245
1246Ref<Image> DisplayServerX11::screen_get_image(int p_screen) const {
1247 ERR_FAIL_INDEX_V(p_screen, get_screen_count(), Ref<Image>());
1248
1249 switch (p_screen) {
1250 case SCREEN_PRIMARY: {
1251 p_screen = get_primary_screen();
1252 } break;
1253 case SCREEN_OF_MAIN_WINDOW: {
1254 p_screen = window_get_current_screen(MAIN_WINDOW_ID);
1255 } break;
1256 default:
1257 break;
1258 }
1259
1260 ERR_FAIL_COND_V(p_screen < 0, Ref<Image>());
1261
1262 XImage *image = nullptr;
1263
1264 int event_base, error_base;
1265 if (xinerama_ext_ok && XineramaQueryExtension(x11_display, &event_base, &error_base)) {
1266 int xin_count;
1267 XineramaScreenInfo *xsi = XineramaQueryScreens(x11_display, &xin_count);
1268 if (p_screen < xin_count) {
1269 int x_count = XScreenCount(x11_display);
1270 for (int i = 0; i < x_count; i++) {
1271 Window root = XRootWindow(x11_display, i);
1272 XWindowAttributes root_attrs;
1273 XGetWindowAttributes(x11_display, root, &root_attrs);
1274 if ((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)) {
1275 image = XGetImage(x11_display, root, xsi[p_screen].x_org, xsi[p_screen].y_org, xsi[p_screen].width, xsi[p_screen].height, AllPlanes, ZPixmap);
1276 break;
1277 }
1278 }
1279 } else {
1280 ERR_FAIL_V_MSG(Ref<Image>(), "Invalid screen index: " + itos(p_screen) + "(count: " + itos(xin_count) + ").");
1281 }
1282 } else {
1283 int x_count = XScreenCount(x11_display);
1284 if (p_screen < x_count) {
1285 Window root = XRootWindow(x11_display, p_screen);
1286
1287 XWindowAttributes root_attrs;
1288 XGetWindowAttributes(x11_display, root, &root_attrs);
1289
1290 image = XGetImage(x11_display, root, root_attrs.x, root_attrs.y, root_attrs.width, root_attrs.height, AllPlanes, ZPixmap);
1291 } else {
1292 ERR_FAIL_V_MSG(Ref<Image>(), "Invalid screen index: " + itos(p_screen) + "(count: " + itos(x_count) + ").");
1293 }
1294 }
1295
1296 Ref<Image> img;
1297 if (image) {
1298 int width = image->width;
1299 int height = image->height;
1300
1301 Vector<uint8_t> img_data;
1302 img_data.resize(height * width * 4);
1303
1304 uint8_t *sr = (uint8_t *)image->data;
1305 uint8_t *wr = (uint8_t *)img_data.ptrw();
1306
1307 if (image->bits_per_pixel == 24 && image->red_mask == 0xff0000 && image->green_mask == 0x00ff00 && image->blue_mask == 0x0000ff) {
1308 for (int y = 0; y < height; y++) {
1309 for (int x = 0; x < width; x++) {
1310 wr[(y * width + x) * 4 + 0] = sr[(y * width + x) * 3 + 2];
1311 wr[(y * width + x) * 4 + 1] = sr[(y * width + x) * 3 + 1];
1312 wr[(y * width + x) * 4 + 2] = sr[(y * width + x) * 3 + 0];
1313 wr[(y * width + x) * 4 + 3] = 255;
1314 }
1315 }
1316 } else if (image->bits_per_pixel == 24 && image->red_mask == 0x0000ff && image->green_mask == 0x00ff00 && image->blue_mask == 0xff0000) {
1317 for (int y = 0; y < height; y++) {
1318 for (int x = 0; x < width; x++) {
1319 wr[(y * width + x) * 4 + 0] = sr[(y * width + x) * 3 + 2];
1320 wr[(y * width + x) * 4 + 1] = sr[(y * width + x) * 3 + 1];
1321 wr[(y * width + x) * 4 + 2] = sr[(y * width + x) * 3 + 0];
1322 wr[(y * width + x) * 4 + 3] = 255;
1323 }
1324 }
1325 } else if (image->bits_per_pixel == 32 && image->red_mask == 0xff0000 && image->green_mask == 0x00ff00 && image->blue_mask == 0x0000ff) {
1326 for (int y = 0; y < height; y++) {
1327 for (int x = 0; x < width; x++) {
1328 wr[(y * width + x) * 4 + 0] = sr[(y * width + x) * 4 + 2];
1329 wr[(y * width + x) * 4 + 1] = sr[(y * width + x) * 4 + 1];
1330 wr[(y * width + x) * 4 + 2] = sr[(y * width + x) * 4 + 0];
1331 wr[(y * width + x) * 4 + 3] = 255;
1332 }
1333 }
1334 } else {
1335 XFree(image);
1336 ERR_FAIL_V_MSG(Ref<Image>(), 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));
1337 }
1338 img = Image::create_from_data(width, height, false, Image::FORMAT_RGBA8, img_data);
1339 XFree(image);
1340 }
1341
1342 return img;
1343}
1344
1345float DisplayServerX11::screen_get_refresh_rate(int p_screen) const {
1346 _THREAD_SAFE_METHOD_
1347
1348 p_screen = _get_screen_index(p_screen);
1349 ERR_FAIL_INDEX_V(p_screen, get_screen_count(), SCREEN_REFRESH_RATE_FALLBACK);
1350
1351 //Use xrandr to get screen refresh rate.
1352 if (xrandr_ext_ok) {
1353 XRRScreenResources *screen_info = XRRGetScreenResources(x11_display, windows[MAIN_WINDOW_ID].x11_window);
1354 if (screen_info) {
1355 RRMode current_mode = 0;
1356 xrr_monitor_info *monitors = nullptr;
1357
1358 if (xrr_get_monitors) {
1359 int count = 0;
1360 monitors = xrr_get_monitors(x11_display, windows[MAIN_WINDOW_ID].x11_window, true, &count);
1361 ERR_FAIL_INDEX_V(p_screen, count, SCREEN_REFRESH_RATE_FALLBACK);
1362 } else {
1363 ERR_PRINT("An error occurred while trying to get the screen refresh rate.");
1364 return SCREEN_REFRESH_RATE_FALLBACK;
1365 }
1366
1367 bool found_active_mode = false;
1368 for (int crtc = 0; crtc < screen_info->ncrtc; crtc++) { // Loop through outputs to find which one is currently outputting.
1369 XRRCrtcInfo *monitor_info = XRRGetCrtcInfo(x11_display, screen_info, screen_info->crtcs[crtc]);
1370 if (monitor_info->x != monitors[p_screen].x || monitor_info->y != monitors[p_screen].y) { // If X and Y aren't the same as the monitor we're looking for, this isn't the right monitor. Continue.
1371 continue;
1372 }
1373
1374 if (monitor_info->mode != None) {
1375 current_mode = monitor_info->mode;
1376 found_active_mode = true;
1377 break;
1378 }
1379 }
1380
1381 if (found_active_mode) {
1382 for (int mode = 0; mode < screen_info->nmode; mode++) {
1383 XRRModeInfo m_info = screen_info->modes[mode];
1384 if (m_info.id == current_mode) {
1385 // Snap to nearest 0.01 to stay consistent with other platforms.
1386 return Math::snapped((float)m_info.dotClock / ((float)m_info.hTotal * (float)m_info.vTotal), 0.01);
1387 }
1388 }
1389 }
1390
1391 ERR_PRINT("An error occurred while trying to get the screen refresh rate."); // We should have returned the refresh rate by now. An error must have occurred.
1392 return SCREEN_REFRESH_RATE_FALLBACK;
1393 } else {
1394 ERR_PRINT("An error occurred while trying to get the screen refresh rate.");
1395 return SCREEN_REFRESH_RATE_FALLBACK;
1396 }
1397 }
1398 ERR_PRINT("An error occurred while trying to get the screen refresh rate.");
1399 return SCREEN_REFRESH_RATE_FALLBACK;
1400}
1401
1402#ifdef DBUS_ENABLED
1403void DisplayServerX11::screen_set_keep_on(bool p_enable) {
1404 if (screen_is_kept_on() == p_enable) {
1405 return;
1406 }
1407
1408 if (p_enable) {
1409 screensaver->inhibit();
1410 } else {
1411 screensaver->uninhibit();
1412 }
1413
1414 keep_screen_on = p_enable;
1415}
1416
1417bool DisplayServerX11::screen_is_kept_on() const {
1418 return keep_screen_on;
1419}
1420#endif
1421
1422Vector<DisplayServer::WindowID> DisplayServerX11::get_window_list() const {
1423 _THREAD_SAFE_METHOD_
1424
1425 Vector<int> ret;
1426 for (const KeyValue<WindowID, WindowData> &E : windows) {
1427 ret.push_back(E.key);
1428 }
1429 return ret;
1430}
1431
1432DisplayServer::WindowID DisplayServerX11::create_sub_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect) {
1433 _THREAD_SAFE_METHOD_
1434
1435 WindowID id = _create_window(p_mode, p_vsync_mode, p_flags, p_rect);
1436 for (int i = 0; i < WINDOW_FLAG_MAX; i++) {
1437 if (p_flags & (1 << i)) {
1438 window_set_flag(WindowFlags(i), true, id);
1439 }
1440 }
1441
1442 return id;
1443}
1444
1445void DisplayServerX11::show_window(WindowID p_id) {
1446 _THREAD_SAFE_METHOD_
1447
1448 const WindowData &wd = windows[p_id];
1449 popup_open(p_id);
1450
1451 DEBUG_LOG_X11("show_window: %lu (%u) \n", wd.x11_window, p_id);
1452
1453 XMapWindow(x11_display, wd.x11_window);
1454 XSync(x11_display, False);
1455 _validate_mode_on_map(p_id);
1456}
1457
1458void DisplayServerX11::delete_sub_window(WindowID p_id) {
1459 _THREAD_SAFE_METHOD_
1460
1461 ERR_FAIL_COND(!windows.has(p_id));
1462 ERR_FAIL_COND_MSG(p_id == MAIN_WINDOW_ID, "Main window can't be deleted");
1463
1464 popup_close(p_id);
1465
1466 WindowData &wd = windows[p_id];
1467
1468 DEBUG_LOG_X11("delete_sub_window: %lu (%u) \n", wd.x11_window, p_id);
1469
1470 if (window_mouseover_id == p_id) {
1471 window_mouseover_id = INVALID_WINDOW_ID;
1472 _send_window_event(windows[p_id], WINDOW_EVENT_MOUSE_EXIT);
1473 }
1474
1475 window_set_rect_changed_callback(Callable(), p_id);
1476 window_set_window_event_callback(Callable(), p_id);
1477 window_set_input_event_callback(Callable(), p_id);
1478 window_set_input_text_callback(Callable(), p_id);
1479 window_set_drop_files_callback(Callable(), p_id);
1480
1481 while (wd.transient_children.size()) {
1482 window_set_transient(*wd.transient_children.begin(), INVALID_WINDOW_ID);
1483 }
1484
1485 if (wd.transient_parent != INVALID_WINDOW_ID) {
1486 window_set_transient(p_id, INVALID_WINDOW_ID);
1487 }
1488
1489#ifdef VULKAN_ENABLED
1490 if (context_vulkan) {
1491 context_vulkan->window_destroy(p_id);
1492 }
1493#endif
1494#ifdef GLES3_ENABLED
1495 if (gl_manager) {
1496 gl_manager->window_destroy(p_id);
1497 }
1498#endif
1499
1500 if (wd.xic) {
1501 XDestroyIC(wd.xic);
1502 wd.xic = nullptr;
1503 }
1504 XDestroyWindow(x11_display, wd.x11_xim_window);
1505#ifdef XKB_ENABLED
1506 if (xkb_loaded_v05p) {
1507 if (wd.xkb_state) {
1508 xkb_compose_state_unref(wd.xkb_state);
1509 wd.xkb_state = nullptr;
1510 }
1511 }
1512#endif
1513
1514 XUnmapWindow(x11_display, wd.x11_window);
1515 XDestroyWindow(x11_display, wd.x11_window);
1516
1517 windows.erase(p_id);
1518}
1519
1520int64_t DisplayServerX11::window_get_native_handle(HandleType p_handle_type, WindowID p_window) const {
1521 ERR_FAIL_COND_V(!windows.has(p_window), 0);
1522 switch (p_handle_type) {
1523 case DISPLAY_HANDLE: {
1524 return (int64_t)x11_display;
1525 }
1526 case WINDOW_HANDLE: {
1527 return (int64_t)windows[p_window].x11_window;
1528 }
1529 case WINDOW_VIEW: {
1530 return 0; // Not supported.
1531 }
1532#ifdef GLES3_ENABLED
1533 case OPENGL_CONTEXT: {
1534 if (gl_manager) {
1535 return (int64_t)gl_manager->get_glx_context(p_window);
1536 }
1537 return 0;
1538 }
1539#endif
1540 default: {
1541 return 0;
1542 }
1543 }
1544}
1545
1546void DisplayServerX11::window_attach_instance_id(ObjectID p_instance, WindowID p_window) {
1547 ERR_FAIL_COND(!windows.has(p_window));
1548 WindowData &wd = windows[p_window];
1549
1550 wd.instance_id = p_instance;
1551}
1552
1553ObjectID DisplayServerX11::window_get_attached_instance_id(WindowID p_window) const {
1554 ERR_FAIL_COND_V(!windows.has(p_window), ObjectID());
1555 const WindowData &wd = windows[p_window];
1556 return wd.instance_id;
1557}
1558
1559DisplayServerX11::WindowID DisplayServerX11::get_window_at_screen_position(const Point2i &p_position) const {
1560 WindowID found_window = INVALID_WINDOW_ID;
1561 WindowID parent_window = INVALID_WINDOW_ID;
1562 unsigned int focus_order = 0;
1563 for (const KeyValue<WindowID, WindowData> &E : windows) {
1564 const WindowData &wd = E.value;
1565
1566 // Discard windows with no focus.
1567 if (wd.focus_order == 0) {
1568 continue;
1569 }
1570
1571 // Find topmost window which contains the given position.
1572 WindowID window_id = E.key;
1573 Rect2i win_rect = Rect2i(window_get_position(window_id), window_get_size(window_id));
1574 if (win_rect.has_point(p_position)) {
1575 // For siblings, pick the window which was focused last.
1576 if ((parent_window != wd.transient_parent) || (wd.focus_order > focus_order)) {
1577 found_window = window_id;
1578 parent_window = wd.transient_parent;
1579 focus_order = wd.focus_order;
1580 }
1581 }
1582 }
1583
1584 return found_window;
1585}
1586
1587void DisplayServerX11::window_set_title(const String &p_title, WindowID p_window) {
1588 _THREAD_SAFE_METHOD_
1589
1590 ERR_FAIL_COND(!windows.has(p_window));
1591 WindowData &wd = windows[p_window];
1592
1593 XStoreName(x11_display, wd.x11_window, p_title.utf8().get_data());
1594
1595 Atom _net_wm_name = XInternAtom(x11_display, "_NET_WM_NAME", false);
1596 Atom utf8_string = XInternAtom(x11_display, "UTF8_STRING", false);
1597 if (_net_wm_name != None && utf8_string != None) {
1598 XChangeProperty(x11_display, wd.x11_window, _net_wm_name, utf8_string, 8, PropModeReplace, (unsigned char *)p_title.utf8().get_data(), p_title.utf8().length());
1599 }
1600}
1601
1602void DisplayServerX11::window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window) {
1603 _THREAD_SAFE_METHOD_
1604
1605 ERR_FAIL_COND(!windows.has(p_window));
1606 windows[p_window].mpath = p_region;
1607 _update_window_mouse_passthrough(p_window);
1608}
1609
1610void DisplayServerX11::_update_window_mouse_passthrough(WindowID p_window) {
1611 ERR_FAIL_COND(!windows.has(p_window));
1612 ERR_FAIL_COND(!xshaped_ext_ok);
1613
1614 const Vector<Vector2> region_path = windows[p_window].mpath;
1615
1616 int event_base, error_base;
1617 const Bool ext_okay = XShapeQueryExtension(x11_display, &event_base, &error_base);
1618 if (ext_okay) {
1619 if (windows[p_window].mpass) {
1620 Region region = XCreateRegion();
1621 XShapeCombineRegion(x11_display, windows[p_window].x11_window, ShapeInput, 0, 0, region, ShapeSet);
1622 XDestroyRegion(region);
1623 } else if (region_path.size() == 0) {
1624 XShapeCombineMask(x11_display, windows[p_window].x11_window, ShapeInput, 0, 0, None, ShapeSet);
1625 } else {
1626 XPoint *points = (XPoint *)memalloc(sizeof(XPoint) * region_path.size());
1627 for (int i = 0; i < region_path.size(); i++) {
1628 points[i].x = region_path[i].x;
1629 points[i].y = region_path[i].y;
1630 }
1631 Region region = XPolygonRegion(points, region_path.size(), EvenOddRule);
1632 memfree(points);
1633 XShapeCombineRegion(x11_display, windows[p_window].x11_window, ShapeInput, 0, 0, region, ShapeSet);
1634 XDestroyRegion(region);
1635 }
1636 }
1637}
1638
1639void DisplayServerX11::window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window) {
1640 _THREAD_SAFE_METHOD_
1641
1642 ERR_FAIL_COND(!windows.has(p_window));
1643 WindowData &wd = windows[p_window];
1644 wd.rect_changed_callback = p_callable;
1645}
1646
1647void DisplayServerX11::window_set_window_event_callback(const Callable &p_callable, WindowID p_window) {
1648 _THREAD_SAFE_METHOD_
1649
1650 ERR_FAIL_COND(!windows.has(p_window));
1651 WindowData &wd = windows[p_window];
1652 wd.event_callback = p_callable;
1653}
1654
1655void DisplayServerX11::window_set_input_event_callback(const Callable &p_callable, WindowID p_window) {
1656 _THREAD_SAFE_METHOD_
1657
1658 ERR_FAIL_COND(!windows.has(p_window));
1659 WindowData &wd = windows[p_window];
1660 wd.input_event_callback = p_callable;
1661}
1662
1663void DisplayServerX11::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) {
1664 _THREAD_SAFE_METHOD_
1665
1666 ERR_FAIL_COND(!windows.has(p_window));
1667 WindowData &wd = windows[p_window];
1668 wd.input_text_callback = p_callable;
1669}
1670
1671void DisplayServerX11::window_set_drop_files_callback(const Callable &p_callable, WindowID p_window) {
1672 _THREAD_SAFE_METHOD_
1673
1674 ERR_FAIL_COND(!windows.has(p_window));
1675 WindowData &wd = windows[p_window];
1676 wd.drop_files_callback = p_callable;
1677}
1678
1679int DisplayServerX11::window_get_current_screen(WindowID p_window) const {
1680 _THREAD_SAFE_METHOD_
1681
1682 int count = get_screen_count();
1683 if (count < 2) {
1684 // Early exit with single monitor.
1685 return 0;
1686 }
1687
1688 ERR_FAIL_COND_V(!windows.has(p_window), 0);
1689 const WindowData &wd = windows[p_window];
1690
1691 const Rect2i window_rect(wd.position, wd.size);
1692
1693 // Find which monitor has the largest overlap with the given window.
1694 int screen_index = 0;
1695 int max_area = 0;
1696 for (int i = 0; i < count; i++) {
1697 Rect2i screen_rect = _screen_get_rect(i);
1698 Rect2i intersection = screen_rect.intersection(window_rect);
1699 int area = intersection.get_area();
1700 if (area > max_area) {
1701 max_area = area;
1702 screen_index = i;
1703 }
1704 }
1705
1706 return screen_index;
1707}
1708
1709void DisplayServerX11::gl_window_make_current(DisplayServer::WindowID p_window_id) {
1710#if defined(GLES3_ENABLED)
1711 if (gl_manager) {
1712 gl_manager->window_make_current(p_window_id);
1713 }
1714#endif
1715}
1716
1717void DisplayServerX11::window_set_current_screen(int p_screen, WindowID p_window) {
1718 _THREAD_SAFE_METHOD_
1719
1720 ERR_FAIL_COND(!windows.has(p_window));
1721 WindowData &wd = windows[p_window];
1722
1723 p_screen = _get_screen_index(p_screen);
1724 ERR_FAIL_INDEX(p_screen, get_screen_count());
1725
1726 if (window_get_current_screen(p_window) == p_screen) {
1727 return;
1728 }
1729
1730 if (window_get_mode(p_window) == WINDOW_MODE_FULLSCREEN) {
1731 Point2i position = screen_get_position(p_screen);
1732 Size2i size = screen_get_size(p_screen);
1733
1734 XMoveResizeWindow(x11_display, wd.x11_window, position.x, position.y, size.x, size.y);
1735 } else {
1736 Rect2i srect = screen_get_usable_rect(p_screen);
1737 Point2i wpos = window_get_position(p_window) - screen_get_position(window_get_current_screen(p_window));
1738 Size2i wsize = window_get_size(p_window);
1739 wpos += srect.position;
1740 if (srect != Rect2i()) {
1741 wpos.x = CLAMP(wpos.x, srect.position.x, srect.position.x + srect.size.width - wsize.width / 3);
1742 wpos.y = CLAMP(wpos.y, srect.position.y, srect.position.y + srect.size.height - wsize.height / 3);
1743 }
1744 window_set_position(wpos, p_window);
1745 }
1746}
1747
1748void DisplayServerX11::window_set_transient(WindowID p_window, WindowID p_parent) {
1749 _THREAD_SAFE_METHOD_
1750
1751 ERR_FAIL_COND(p_window == p_parent);
1752
1753 ERR_FAIL_COND(!windows.has(p_window));
1754 WindowData &wd_window = windows[p_window];
1755
1756 WindowID prev_parent = wd_window.transient_parent;
1757 ERR_FAIL_COND(prev_parent == p_parent);
1758
1759 DEBUG_LOG_X11("window_set_transient: %lu (%u), prev_parent=%u, parent=%u\n", wd_window.x11_window, p_window, prev_parent, p_parent);
1760
1761 ERR_FAIL_COND_MSG(wd_window.on_top, "Windows with the 'on top' can't become transient.");
1762 if (p_parent == INVALID_WINDOW_ID) {
1763 //remove transient
1764
1765 ERR_FAIL_COND(prev_parent == INVALID_WINDOW_ID);
1766 ERR_FAIL_COND(!windows.has(prev_parent));
1767
1768 WindowData &wd_parent = windows[prev_parent];
1769
1770 wd_window.transient_parent = INVALID_WINDOW_ID;
1771 wd_parent.transient_children.erase(p_window);
1772
1773 XSetTransientForHint(x11_display, wd_window.x11_window, None);
1774
1775 XWindowAttributes xwa;
1776 XSync(x11_display, False);
1777 XGetWindowAttributes(x11_display, wd_parent.x11_window, &xwa);
1778
1779 // Set focus to parent sub window to avoid losing all focus when closing a nested sub-menu.
1780 // RevertToPointerRoot is used to make sure we don't lose all focus in case
1781 // a subwindow and its parent are both destroyed.
1782 if (!wd_window.no_focus && !wd_window.is_popup && wd_window.focused) {
1783 if ((xwa.map_state == IsViewable) && !wd_parent.no_focus && !wd_window.is_popup) {
1784 XSetInputFocus(x11_display, wd_parent.x11_window, RevertToPointerRoot, CurrentTime);
1785 }
1786 }
1787 } else {
1788 ERR_FAIL_COND(!windows.has(p_parent));
1789 ERR_FAIL_COND_MSG(prev_parent != INVALID_WINDOW_ID, "Window already has a transient parent");
1790 WindowData &wd_parent = windows[p_parent];
1791
1792 wd_window.transient_parent = p_parent;
1793 wd_parent.transient_children.insert(p_window);
1794
1795 XSetTransientForHint(x11_display, wd_window.x11_window, wd_parent.x11_window);
1796 }
1797}
1798
1799// Helper method. Assumes that the window id has already been checked and exists.
1800void DisplayServerX11::_update_size_hints(WindowID p_window) {
1801 WindowData &wd = windows[p_window];
1802 WindowMode window_mode = window_get_mode(p_window);
1803 XSizeHints *xsh = XAllocSizeHints();
1804
1805 // Always set the position and size hints - they should be synchronized with the actual values after the window is mapped anyway
1806 xsh->flags |= PPosition | PSize;
1807 xsh->x = wd.position.x;
1808 xsh->y = wd.position.y;
1809 xsh->width = wd.size.width;
1810 xsh->height = wd.size.height;
1811
1812 if (window_mode == WINDOW_MODE_FULLSCREEN || window_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) {
1813 // Do not set any other hints to prevent the window manager from ignoring the fullscreen flags
1814 } else if (window_get_flag(WINDOW_FLAG_RESIZE_DISABLED, p_window)) {
1815 // If resizing is disabled, use the forced size
1816 xsh->flags |= PMinSize | PMaxSize;
1817 xsh->min_width = wd.size.x;
1818 xsh->max_width = wd.size.x;
1819 xsh->min_height = wd.size.y;
1820 xsh->max_height = wd.size.y;
1821 } else {
1822 // Otherwise, just respect min_size and max_size
1823 if (wd.min_size != Size2i()) {
1824 xsh->flags |= PMinSize;
1825 xsh->min_width = wd.min_size.x;
1826 xsh->min_height = wd.min_size.y;
1827 }
1828 if (wd.max_size != Size2i()) {
1829 xsh->flags |= PMaxSize;
1830 xsh->max_width = wd.max_size.x;
1831 xsh->max_height = wd.max_size.y;
1832 }
1833 }
1834
1835 XSetWMNormalHints(x11_display, wd.x11_window, xsh);
1836 XFree(xsh);
1837}
1838
1839Point2i DisplayServerX11::window_get_position(WindowID p_window) const {
1840 _THREAD_SAFE_METHOD_
1841
1842 ERR_FAIL_COND_V(!windows.has(p_window), Point2i());
1843 const WindowData &wd = windows[p_window];
1844
1845 return wd.position;
1846}
1847
1848Point2i DisplayServerX11::window_get_position_with_decorations(WindowID p_window) const {
1849 _THREAD_SAFE_METHOD_
1850
1851 ERR_FAIL_COND_V(!windows.has(p_window), Size2i());
1852 const WindowData &wd = windows[p_window];
1853
1854 if (wd.fullscreen) {
1855 return wd.position;
1856 }
1857
1858 XWindowAttributes xwa;
1859 XSync(x11_display, False);
1860 XGetWindowAttributes(x11_display, wd.x11_window, &xwa);
1861 int x = wd.position.x;
1862 int y = wd.position.y;
1863 Atom prop = XInternAtom(x11_display, "_NET_FRAME_EXTENTS", True);
1864 if (prop != None) {
1865 Atom type;
1866 int format;
1867 unsigned long len;
1868 unsigned long remaining;
1869 unsigned char *data = nullptr;
1870 if (XGetWindowProperty(x11_display, wd.x11_window, prop, 0, 4, False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) {
1871 if (format == 32 && len == 4 && data) {
1872 long *extents = (long *)data;
1873 x -= extents[0]; // left
1874 y -= extents[2]; // top
1875 }
1876 XFree(data);
1877 }
1878 }
1879 return Size2i(x, y);
1880}
1881
1882void DisplayServerX11::window_set_position(const Point2i &p_position, WindowID p_window) {
1883 _THREAD_SAFE_METHOD_
1884
1885 ERR_FAIL_COND(!windows.has(p_window));
1886 WindowData &wd = windows[p_window];
1887
1888 int x = 0;
1889 int y = 0;
1890 if (!window_get_flag(WINDOW_FLAG_BORDERLESS, p_window)) {
1891 //exclude window decorations
1892 XSync(x11_display, False);
1893 Atom prop = XInternAtom(x11_display, "_NET_FRAME_EXTENTS", True);
1894 if (prop != None) {
1895 Atom type;
1896 int format;
1897 unsigned long len;
1898 unsigned long remaining;
1899 unsigned char *data = nullptr;
1900 if (XGetWindowProperty(x11_display, wd.x11_window, prop, 0, 4, False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) {
1901 if (format == 32 && len == 4 && data) {
1902 long *extents = (long *)data;
1903 x = extents[0];
1904 y = extents[2];
1905 }
1906 XFree(data);
1907 }
1908 }
1909 }
1910 XMoveWindow(x11_display, wd.x11_window, p_position.x - x, p_position.y - y);
1911 _update_real_mouse_position(wd);
1912}
1913
1914void DisplayServerX11::window_set_max_size(const Size2i p_size, WindowID p_window) {
1915 _THREAD_SAFE_METHOD_
1916
1917 ERR_FAIL_COND(!windows.has(p_window));
1918 WindowData &wd = windows[p_window];
1919
1920 if ((p_size != Size2i()) && ((p_size.x < wd.min_size.x) || (p_size.y < wd.min_size.y))) {
1921 ERR_PRINT("Maximum window size can't be smaller than minimum window size!");
1922 return;
1923 }
1924 wd.max_size = p_size;
1925
1926 _update_size_hints(p_window);
1927 XFlush(x11_display);
1928}
1929
1930Size2i DisplayServerX11::window_get_max_size(WindowID p_window) const {
1931 _THREAD_SAFE_METHOD_
1932
1933 ERR_FAIL_COND_V(!windows.has(p_window), Size2i());
1934 const WindowData &wd = windows[p_window];
1935
1936 return wd.max_size;
1937}
1938
1939void DisplayServerX11::window_set_min_size(const Size2i p_size, WindowID p_window) {
1940 _THREAD_SAFE_METHOD_
1941
1942 ERR_FAIL_COND(!windows.has(p_window));
1943 WindowData &wd = windows[p_window];
1944
1945 if ((p_size != Size2i()) && (wd.max_size != Size2i()) && ((p_size.x > wd.max_size.x) || (p_size.y > wd.max_size.y))) {
1946 ERR_PRINT("Minimum window size can't be larger than maximum window size!");
1947 return;
1948 }
1949 wd.min_size = p_size;
1950
1951 _update_size_hints(p_window);
1952 XFlush(x11_display);
1953}
1954
1955Size2i DisplayServerX11::window_get_min_size(WindowID p_window) const {
1956 _THREAD_SAFE_METHOD_
1957
1958 ERR_FAIL_COND_V(!windows.has(p_window), Size2i());
1959 const WindowData &wd = windows[p_window];
1960
1961 return wd.min_size;
1962}
1963
1964void DisplayServerX11::window_set_size(const Size2i p_size, WindowID p_window) {
1965 _THREAD_SAFE_METHOD_
1966
1967 ERR_FAIL_COND(!windows.has(p_window));
1968
1969 Size2i size = p_size;
1970 size.x = MAX(1, size.x);
1971 size.y = MAX(1, size.y);
1972
1973 WindowData &wd = windows[p_window];
1974
1975 if (wd.size.width == size.width && wd.size.height == size.height) {
1976 return;
1977 }
1978
1979 XWindowAttributes xwa;
1980 XSync(x11_display, False);
1981 XGetWindowAttributes(x11_display, wd.x11_window, &xwa);
1982 int old_w = xwa.width;
1983 int old_h = xwa.height;
1984
1985 // Update our videomode width and height
1986 wd.size = size;
1987
1988 // Update the size hints first to make sure the window size can be set
1989 _update_size_hints(p_window);
1990
1991 // Resize the window
1992 XResizeWindow(x11_display, wd.x11_window, size.x, size.y);
1993
1994 for (int timeout = 0; timeout < 50; ++timeout) {
1995 XSync(x11_display, False);
1996 XGetWindowAttributes(x11_display, wd.x11_window, &xwa);
1997
1998 if (old_w != xwa.width || old_h != xwa.height) {
1999 break;
2000 }
2001
2002 usleep(10000);
2003 }
2004
2005 // Keep rendering context window size in sync
2006#if defined(VULKAN_ENABLED)
2007 if (context_vulkan) {
2008 context_vulkan->window_resize(p_window, xwa.width, xwa.height);
2009 }
2010#endif
2011#if defined(GLES3_ENABLED)
2012 if (gl_manager) {
2013 gl_manager->window_resize(p_window, xwa.width, xwa.height);
2014 }
2015#endif
2016}
2017
2018Size2i DisplayServerX11::window_get_size(WindowID p_window) const {
2019 _THREAD_SAFE_METHOD_
2020
2021 ERR_FAIL_COND_V(!windows.has(p_window), Size2i());
2022 const WindowData &wd = windows[p_window];
2023 return wd.size;
2024}
2025
2026Size2i DisplayServerX11::window_get_size_with_decorations(WindowID p_window) const {
2027 _THREAD_SAFE_METHOD_
2028
2029 ERR_FAIL_COND_V(!windows.has(p_window), Size2i());
2030 const WindowData &wd = windows[p_window];
2031
2032 if (wd.fullscreen) {
2033 return wd.size;
2034 }
2035
2036 XWindowAttributes xwa;
2037 XSync(x11_display, False);
2038 XGetWindowAttributes(x11_display, wd.x11_window, &xwa);
2039 int w = xwa.width;
2040 int h = xwa.height;
2041 Atom prop = XInternAtom(x11_display, "_NET_FRAME_EXTENTS", True);
2042 if (prop != None) {
2043 Atom type;
2044 int format;
2045 unsigned long len;
2046 unsigned long remaining;
2047 unsigned char *data = nullptr;
2048 if (XGetWindowProperty(x11_display, wd.x11_window, prop, 0, 4, False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) {
2049 if (format == 32 && len == 4 && data) {
2050 long *extents = (long *)data;
2051 w += extents[0] + extents[1]; // left, right
2052 h += extents[2] + extents[3]; // top, bottom
2053 }
2054 XFree(data);
2055 }
2056 }
2057 return Size2i(w, h);
2058}
2059
2060// Just a helper to reduce code duplication in `window_is_maximize_allowed`
2061// and `_set_wm_maximized`.
2062bool DisplayServerX11::_window_maximize_check(WindowID p_window, const char *p_atom_name) const {
2063 ERR_FAIL_COND_V(!windows.has(p_window), false);
2064 const WindowData &wd = windows[p_window];
2065
2066 Atom property = XInternAtom(x11_display, p_atom_name, False);
2067 Atom type;
2068 int format;
2069 unsigned long len;
2070 unsigned long remaining;
2071 unsigned char *data = nullptr;
2072 bool retval = false;
2073
2074 if (property == None) {
2075 return false;
2076 }
2077
2078 int result = XGetWindowProperty(
2079 x11_display,
2080 wd.x11_window,
2081 property,
2082 0,
2083 1024,
2084 False,
2085 XA_ATOM,
2086 &type,
2087 &format,
2088 &len,
2089 &remaining,
2090 &data);
2091
2092 if (result == Success && data) {
2093 Atom *atoms = (Atom *)data;
2094 Atom wm_act_max_horz;
2095 Atom wm_act_max_vert;
2096 if (strcmp(p_atom_name, "_NET_WM_STATE") == 0) {
2097 wm_act_max_horz = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_HORZ", False);
2098 wm_act_max_vert = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_VERT", False);
2099 } else {
2100 wm_act_max_horz = XInternAtom(x11_display, "_NET_WM_ACTION_MAXIMIZE_HORZ", False);
2101 wm_act_max_vert = XInternAtom(x11_display, "_NET_WM_ACTION_MAXIMIZE_VERT", False);
2102 }
2103 bool found_wm_act_max_horz = false;
2104 bool found_wm_act_max_vert = false;
2105
2106 for (uint64_t i = 0; i < len; i++) {
2107 if (atoms[i] == wm_act_max_horz) {
2108 found_wm_act_max_horz = true;
2109 }
2110 if (atoms[i] == wm_act_max_vert) {
2111 found_wm_act_max_vert = true;
2112 }
2113
2114 if (found_wm_act_max_horz || found_wm_act_max_vert) {
2115 retval = true;
2116 break;
2117 }
2118 }
2119
2120 XFree(data);
2121 }
2122
2123 return retval;
2124}
2125
2126bool DisplayServerX11::_window_minimize_check(WindowID p_window) const {
2127 const WindowData &wd = windows[p_window];
2128
2129 // Using EWMH instead of ICCCM, might work better for Wayland users.
2130 Atom property = XInternAtom(x11_display, "_NET_WM_STATE", True);
2131 Atom hidden = XInternAtom(x11_display, "_NET_WM_STATE_HIDDEN", True);
2132 if (property == None || hidden == None) {
2133 return false;
2134 }
2135
2136 Atom type;
2137 int format;
2138 unsigned long len;
2139 unsigned long remaining;
2140 Atom *atoms = nullptr;
2141
2142 int result = XGetWindowProperty(
2143 x11_display,
2144 wd.x11_window,
2145 property,
2146 0,
2147 32,
2148 False,
2149 XA_ATOM,
2150 &type,
2151 &format,
2152 &len,
2153 &remaining,
2154 (unsigned char **)&atoms);
2155
2156 if (result == Success && atoms) {
2157 for (unsigned int i = 0; i < len; i++) {
2158 if (atoms[i] == hidden) {
2159 XFree(atoms);
2160 return true;
2161 }
2162 }
2163 XFree(atoms);
2164 }
2165
2166 return false;
2167}
2168
2169bool DisplayServerX11::_window_fullscreen_check(WindowID p_window) const {
2170 ERR_FAIL_COND_V(!windows.has(p_window), false);
2171 const WindowData &wd = windows[p_window];
2172
2173 // Using EWMH -- Extended Window Manager Hints
2174 Atom property = XInternAtom(x11_display, "_NET_WM_STATE", False);
2175 Atom type;
2176 int format;
2177 unsigned long len;
2178 unsigned long remaining;
2179 unsigned char *data = nullptr;
2180 bool retval = false;
2181
2182 if (property == None) {
2183 return retval;
2184 }
2185
2186 int result = XGetWindowProperty(
2187 x11_display,
2188 wd.x11_window,
2189 property,
2190 0,
2191 1024,
2192 False,
2193 XA_ATOM,
2194 &type,
2195 &format,
2196 &len,
2197 &remaining,
2198 &data);
2199
2200 if (result == Success) {
2201 Atom *atoms = (Atom *)data;
2202 Atom wm_fullscreen = XInternAtom(x11_display, "_NET_WM_STATE_FULLSCREEN", False);
2203 for (uint64_t i = 0; i < len; i++) {
2204 if (atoms[i] == wm_fullscreen) {
2205 retval = true;
2206 break;
2207 }
2208 }
2209 XFree(data);
2210 }
2211
2212 return retval;
2213}
2214
2215void DisplayServerX11::_validate_mode_on_map(WindowID p_window) {
2216 // Check if we applied any window modes that didn't take effect while unmapped
2217 const WindowData &wd = windows[p_window];
2218 if (wd.fullscreen && !_window_fullscreen_check(p_window)) {
2219 _set_wm_fullscreen(p_window, true, wd.exclusive_fullscreen);
2220 } else if (wd.maximized && !_window_maximize_check(p_window, "_NET_WM_STATE")) {
2221 _set_wm_maximized(p_window, true);
2222 } else if (wd.minimized && !_window_minimize_check(p_window)) {
2223 _set_wm_minimized(p_window, true);
2224 }
2225
2226 if (wd.on_top) {
2227 Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);
2228 Atom wm_above = XInternAtom(x11_display, "_NET_WM_STATE_ABOVE", False);
2229
2230 XClientMessageEvent xev;
2231 memset(&xev, 0, sizeof(xev));
2232 xev.type = ClientMessage;
2233 xev.window = wd.x11_window;
2234 xev.message_type = wm_state;
2235 xev.format = 32;
2236 xev.data.l[0] = _NET_WM_STATE_ADD;
2237 xev.data.l[1] = wm_above;
2238 xev.data.l[3] = 1;
2239 XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent *)&xev);
2240 }
2241}
2242
2243bool DisplayServerX11::window_is_maximize_allowed(WindowID p_window) const {
2244 _THREAD_SAFE_METHOD_
2245 return _window_maximize_check(p_window, "_NET_WM_ALLOWED_ACTIONS");
2246}
2247
2248void DisplayServerX11::_set_wm_maximized(WindowID p_window, bool p_enabled) {
2249 ERR_FAIL_COND(!windows.has(p_window));
2250 WindowData &wd = windows[p_window];
2251
2252 // Using EWMH -- Extended Window Manager Hints
2253 XEvent xev;
2254 Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);
2255 Atom wm_max_horz = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_HORZ", False);
2256 Atom wm_max_vert = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_VERT", False);
2257
2258 memset(&xev, 0, sizeof(xev));
2259 xev.type = ClientMessage;
2260 xev.xclient.window = wd.x11_window;
2261 xev.xclient.message_type = wm_state;
2262 xev.xclient.format = 32;
2263 xev.xclient.data.l[0] = p_enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
2264 xev.xclient.data.l[1] = wm_max_horz;
2265 xev.xclient.data.l[2] = wm_max_vert;
2266
2267 XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
2268
2269 if (p_enabled && window_is_maximize_allowed(p_window)) {
2270 // Wait for effective resizing (so the GLX context is too).
2271 // Give up after 0.5s, it's not going to happen on this WM.
2272 // https://github.com/godotengine/godot/issues/19978
2273 for (int attempt = 0; window_get_mode(p_window) != WINDOW_MODE_MAXIMIZED && attempt < 50; attempt++) {
2274 usleep(10000);
2275 }
2276 }
2277 wd.maximized = p_enabled;
2278}
2279
2280void DisplayServerX11::_set_wm_minimized(WindowID p_window, bool p_enabled) {
2281 WindowData &wd = windows[p_window];
2282 // Using ICCCM -- Inter-Client Communication Conventions Manual
2283 XEvent xev;
2284 Atom wm_change = XInternAtom(x11_display, "WM_CHANGE_STATE", False);
2285
2286 memset(&xev, 0, sizeof(xev));
2287 xev.type = ClientMessage;
2288 xev.xclient.window = wd.x11_window;
2289 xev.xclient.message_type = wm_change;
2290 xev.xclient.format = 32;
2291 xev.xclient.data.l[0] = p_enabled ? WM_IconicState : WM_NormalState;
2292
2293 XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
2294
2295 Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);
2296 Atom wm_hidden = XInternAtom(x11_display, "_NET_WM_STATE_HIDDEN", False);
2297
2298 memset(&xev, 0, sizeof(xev));
2299 xev.type = ClientMessage;
2300 xev.xclient.window = wd.x11_window;
2301 xev.xclient.message_type = wm_state;
2302 xev.xclient.format = 32;
2303 xev.xclient.data.l[0] = p_enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
2304 xev.xclient.data.l[1] = wm_hidden;
2305
2306 XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
2307 wd.minimized = p_enabled;
2308}
2309
2310void DisplayServerX11::_set_wm_fullscreen(WindowID p_window, bool p_enabled, bool p_exclusive) {
2311 ERR_FAIL_COND(!windows.has(p_window));
2312 WindowData &wd = windows[p_window];
2313
2314 if (p_enabled && !window_get_flag(WINDOW_FLAG_BORDERLESS, p_window)) {
2315 // remove decorations if the window is not already borderless
2316 Hints hints;
2317 Atom property;
2318 hints.flags = 2;
2319 hints.decorations = 0;
2320 property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True);
2321 if (property != None) {
2322 XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5);
2323 }
2324 }
2325
2326 if (p_enabled) {
2327 // Set the window as resizable to prevent window managers to ignore the fullscreen state flag.
2328 _update_size_hints(p_window);
2329 }
2330
2331 // Using EWMH -- Extended Window Manager Hints
2332 XEvent xev;
2333 Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);
2334 Atom wm_fullscreen = XInternAtom(x11_display, "_NET_WM_STATE_FULLSCREEN", False);
2335
2336 memset(&xev, 0, sizeof(xev));
2337 xev.type = ClientMessage;
2338 xev.xclient.window = wd.x11_window;
2339 xev.xclient.message_type = wm_state;
2340 xev.xclient.format = 32;
2341 xev.xclient.data.l[0] = p_enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
2342 xev.xclient.data.l[1] = wm_fullscreen;
2343 xev.xclient.data.l[2] = 0;
2344
2345 XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
2346
2347 // set bypass compositor hint
2348 Atom bypass_compositor = XInternAtom(x11_display, "_NET_WM_BYPASS_COMPOSITOR", False);
2349 unsigned long compositing_disable_on = 0; // Use default.
2350 if (p_enabled) {
2351 if (p_exclusive) {
2352 compositing_disable_on = 1; // Force composition OFF to reduce overhead.
2353 } else {
2354 compositing_disable_on = 2; // Force composition ON to allow popup windows.
2355 }
2356 }
2357 if (bypass_compositor != None) {
2358 XChangeProperty(x11_display, wd.x11_window, bypass_compositor, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&compositing_disable_on, 1);
2359 }
2360
2361 XFlush(x11_display);
2362
2363 if (!p_enabled) {
2364 // Reset the non-resizable flags if we un-set these before.
2365 _update_size_hints(p_window);
2366
2367 // put back or remove decorations according to the last set borderless state
2368 Hints hints;
2369 Atom property;
2370 hints.flags = 2;
2371 hints.decorations = wd.borderless ? 0 : 1;
2372 property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True);
2373 if (property != None) {
2374 XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5);
2375 }
2376 }
2377}
2378
2379void DisplayServerX11::window_set_mode(WindowMode p_mode, WindowID p_window) {
2380 _THREAD_SAFE_METHOD_
2381
2382 ERR_FAIL_COND(!windows.has(p_window));
2383 WindowData &wd = windows[p_window];
2384
2385 WindowMode old_mode = window_get_mode(p_window);
2386 if (old_mode == p_mode) {
2387 return; // do nothing
2388 }
2389 //remove all "extra" modes
2390
2391 switch (old_mode) {
2392 case WINDOW_MODE_WINDOWED: {
2393 //do nothing
2394 } break;
2395 case WINDOW_MODE_MINIMIZED: {
2396 _set_wm_minimized(p_window, false);
2397 } break;
2398 case WINDOW_MODE_EXCLUSIVE_FULLSCREEN:
2399 case WINDOW_MODE_FULLSCREEN: {
2400 //Remove full-screen
2401 wd.fullscreen = false;
2402 wd.exclusive_fullscreen = false;
2403
2404 _set_wm_fullscreen(p_window, false, false);
2405
2406 //un-maximize required for always on top
2407 bool on_top = window_get_flag(WINDOW_FLAG_ALWAYS_ON_TOP, p_window);
2408
2409 window_set_position(wd.last_position_before_fs, p_window);
2410
2411 if (on_top) {
2412 _set_wm_maximized(p_window, false);
2413 }
2414
2415 } break;
2416 case WINDOW_MODE_MAXIMIZED: {
2417 _set_wm_maximized(p_window, false);
2418 } break;
2419 }
2420
2421 switch (p_mode) {
2422 case WINDOW_MODE_WINDOWED: {
2423 //do nothing
2424 } break;
2425 case WINDOW_MODE_MINIMIZED: {
2426 _set_wm_minimized(p_window, true);
2427 } break;
2428 case WINDOW_MODE_EXCLUSIVE_FULLSCREEN:
2429 case WINDOW_MODE_FULLSCREEN: {
2430 wd.last_position_before_fs = wd.position;
2431
2432 if (window_get_flag(WINDOW_FLAG_ALWAYS_ON_TOP, p_window)) {
2433 _set_wm_maximized(p_window, true);
2434 }
2435
2436 wd.fullscreen = true;
2437 if (p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) {
2438 wd.exclusive_fullscreen = true;
2439 _set_wm_fullscreen(p_window, true, true);
2440 } else {
2441 wd.exclusive_fullscreen = false;
2442 _set_wm_fullscreen(p_window, true, false);
2443 }
2444 } break;
2445 case WINDOW_MODE_MAXIMIZED: {
2446 _set_wm_maximized(p_window, true);
2447 } break;
2448 }
2449}
2450
2451DisplayServer::WindowMode DisplayServerX11::window_get_mode(WindowID p_window) const {
2452 _THREAD_SAFE_METHOD_
2453
2454 ERR_FAIL_COND_V(!windows.has(p_window), WINDOW_MODE_WINDOWED);
2455 const WindowData &wd = windows[p_window];
2456
2457 if (wd.fullscreen) { //if fullscreen, it's not in another mode
2458 if (wd.exclusive_fullscreen) {
2459 return WINDOW_MODE_EXCLUSIVE_FULLSCREEN;
2460 } else {
2461 return WINDOW_MODE_FULLSCREEN;
2462 }
2463 }
2464
2465 // Test maximized.
2466 // Using EWMH -- Extended Window Manager Hints
2467 if (_window_maximize_check(p_window, "_NET_WM_STATE")) {
2468 return WINDOW_MODE_MAXIMIZED;
2469 }
2470
2471 {
2472 if (_window_minimize_check(p_window)) {
2473 return WINDOW_MODE_MINIMIZED;
2474 }
2475 }
2476
2477 // All other discarded, return windowed.
2478
2479 return WINDOW_MODE_WINDOWED;
2480}
2481
2482void DisplayServerX11::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) {
2483 _THREAD_SAFE_METHOD_
2484
2485 ERR_FAIL_COND(!windows.has(p_window));
2486 WindowData &wd = windows[p_window];
2487
2488 switch (p_flag) {
2489 case WINDOW_FLAG_RESIZE_DISABLED: {
2490 wd.resize_disabled = p_enabled;
2491
2492 _update_size_hints(p_window);
2493
2494 XFlush(x11_display);
2495 } break;
2496 case WINDOW_FLAG_BORDERLESS: {
2497 Hints hints;
2498 Atom property;
2499 hints.flags = 2;
2500 hints.decorations = p_enabled ? 0 : 1;
2501 property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True);
2502 if (property != None) {
2503 XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5);
2504 }
2505
2506 // Preserve window size
2507 window_set_size(window_get_size(p_window), p_window);
2508
2509 wd.borderless = p_enabled;
2510 _update_window_mouse_passthrough(p_window);
2511 } break;
2512 case WINDOW_FLAG_ALWAYS_ON_TOP: {
2513 ERR_FAIL_COND_MSG(wd.transient_parent != INVALID_WINDOW_ID, "Can't make a window transient if the 'on top' flag is active.");
2514 if (p_enabled && wd.fullscreen) {
2515 _set_wm_maximized(p_window, true);
2516 }
2517
2518 Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);
2519 Atom wm_above = XInternAtom(x11_display, "_NET_WM_STATE_ABOVE", False);
2520
2521 XClientMessageEvent xev;
2522 memset(&xev, 0, sizeof(xev));
2523 xev.type = ClientMessage;
2524 xev.window = wd.x11_window;
2525 xev.message_type = wm_state;
2526 xev.format = 32;
2527 xev.data.l[0] = p_enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
2528 xev.data.l[1] = wm_above;
2529 xev.data.l[3] = 1;
2530 XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent *)&xev);
2531
2532 if (!p_enabled && !wd.fullscreen) {
2533 _set_wm_maximized(p_window, false);
2534 }
2535 wd.on_top = p_enabled;
2536
2537 } break;
2538 case WINDOW_FLAG_TRANSPARENT: {
2539 wd.layered_window = p_enabled;
2540 } break;
2541 case WINDOW_FLAG_NO_FOCUS: {
2542 wd.no_focus = p_enabled;
2543 } break;
2544 case WINDOW_FLAG_MOUSE_PASSTHROUGH: {
2545 wd.mpass = p_enabled;
2546 _update_window_mouse_passthrough(p_window);
2547 } break;
2548 case WINDOW_FLAG_POPUP: {
2549 XWindowAttributes xwa;
2550 XSync(x11_display, False);
2551 XGetWindowAttributes(x11_display, wd.x11_window, &xwa);
2552
2553 ERR_FAIL_COND_MSG(p_window == MAIN_WINDOW_ID, "Main window can't be popup.");
2554 ERR_FAIL_COND_MSG((xwa.map_state == IsViewable) && (wd.is_popup != p_enabled), "Popup flag can't changed while window is opened.");
2555 wd.is_popup = p_enabled;
2556 } break;
2557 default: {
2558 }
2559 }
2560}
2561
2562bool DisplayServerX11::window_get_flag(WindowFlags p_flag, WindowID p_window) const {
2563 _THREAD_SAFE_METHOD_
2564
2565 ERR_FAIL_COND_V(!windows.has(p_window), false);
2566 const WindowData &wd = windows[p_window];
2567
2568 switch (p_flag) {
2569 case WINDOW_FLAG_RESIZE_DISABLED: {
2570 return wd.resize_disabled;
2571 } break;
2572 case WINDOW_FLAG_BORDERLESS: {
2573 bool borderless = wd.borderless;
2574 Atom prop = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True);
2575 if (prop != None) {
2576 Atom type;
2577 int format;
2578 unsigned long len;
2579 unsigned long remaining;
2580 unsigned char *data = nullptr;
2581 if (XGetWindowProperty(x11_display, wd.x11_window, prop, 0, sizeof(Hints), False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) {
2582 if (data && (format == 32) && (len >= 5)) {
2583 borderless = !(reinterpret_cast<Hints *>(data)->decorations);
2584 }
2585 if (data) {
2586 XFree(data);
2587 }
2588 }
2589 }
2590 return borderless;
2591 } break;
2592 case WINDOW_FLAG_ALWAYS_ON_TOP: {
2593 return wd.on_top;
2594 } break;
2595 case WINDOW_FLAG_TRANSPARENT: {
2596 return wd.layered_window;
2597 } break;
2598 case WINDOW_FLAG_NO_FOCUS: {
2599 return wd.no_focus;
2600 } break;
2601 case WINDOW_FLAG_MOUSE_PASSTHROUGH: {
2602 return wd.mpass;
2603 } break;
2604 case WINDOW_FLAG_POPUP: {
2605 return wd.is_popup;
2606 } break;
2607 default: {
2608 }
2609 }
2610
2611 return false;
2612}
2613
2614void DisplayServerX11::window_request_attention(WindowID p_window) {
2615 _THREAD_SAFE_METHOD_
2616
2617 ERR_FAIL_COND(!windows.has(p_window));
2618 const WindowData &wd = windows[p_window];
2619 // Using EWMH -- Extended Window Manager Hints
2620 //
2621 // Sets the _NET_WM_STATE_DEMANDS_ATTENTION atom for WM_STATE
2622 // Will be unset by the window manager after user react on the request for attention
2623
2624 XEvent xev;
2625 Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);
2626 Atom wm_attention = XInternAtom(x11_display, "_NET_WM_STATE_DEMANDS_ATTENTION", False);
2627
2628 memset(&xev, 0, sizeof(xev));
2629 xev.type = ClientMessage;
2630 xev.xclient.window = wd.x11_window;
2631 xev.xclient.message_type = wm_state;
2632 xev.xclient.format = 32;
2633 xev.xclient.data.l[0] = _NET_WM_STATE_ADD;
2634 xev.xclient.data.l[1] = wm_attention;
2635
2636 XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
2637 XFlush(x11_display);
2638}
2639
2640void DisplayServerX11::window_move_to_foreground(WindowID p_window) {
2641 _THREAD_SAFE_METHOD_
2642
2643 ERR_FAIL_COND(!windows.has(p_window));
2644 const WindowData &wd = windows[p_window];
2645
2646 XEvent xev;
2647 Atom net_active_window = XInternAtom(x11_display, "_NET_ACTIVE_WINDOW", False);
2648
2649 memset(&xev, 0, sizeof(xev));
2650 xev.type = ClientMessage;
2651 xev.xclient.window = wd.x11_window;
2652 xev.xclient.message_type = net_active_window;
2653 xev.xclient.format = 32;
2654 xev.xclient.data.l[0] = 1;
2655 xev.xclient.data.l[1] = CurrentTime;
2656
2657 XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
2658 XFlush(x11_display);
2659}
2660
2661bool DisplayServerX11::window_is_focused(WindowID p_window) const {
2662 _THREAD_SAFE_METHOD_
2663
2664 ERR_FAIL_COND_V(!windows.has(p_window), false);
2665 const WindowData &wd = windows[p_window];
2666
2667 return wd.focused;
2668}
2669
2670bool DisplayServerX11::window_can_draw(WindowID p_window) const {
2671 //this seems to be all that is provided by X11
2672 return window_get_mode(p_window) != WINDOW_MODE_MINIMIZED;
2673}
2674
2675bool DisplayServerX11::can_any_window_draw() const {
2676 _THREAD_SAFE_METHOD_
2677
2678 for (const KeyValue<WindowID, WindowData> &E : windows) {
2679 if (window_get_mode(E.key) != WINDOW_MODE_MINIMIZED) {
2680 return true;
2681 }
2682 }
2683
2684 return false;
2685}
2686
2687void DisplayServerX11::window_set_ime_active(const bool p_active, WindowID p_window) {
2688 _THREAD_SAFE_METHOD_
2689
2690 ERR_FAIL_COND(!windows.has(p_window));
2691 WindowData &wd = windows[p_window];
2692
2693 if (!wd.xic) {
2694 return;
2695 }
2696 if (!wd.focused) {
2697 wd.ime_active = false;
2698 im_text = String();
2699 im_selection = Vector2i();
2700 return;
2701 }
2702
2703 // Block events polling while changing input focus
2704 // because it triggers some event polling internally.
2705 if (p_active) {
2706 MutexLock mutex_lock(events_mutex);
2707
2708 wd.ime_active = true;
2709
2710 XMapWindow(x11_display, wd.x11_xim_window);
2711
2712 XWindowAttributes xwa;
2713 XSync(x11_display, False);
2714 XGetWindowAttributes(x11_display, wd.x11_xim_window, &xwa);
2715 if (xwa.map_state == IsViewable) {
2716 XSetInputFocus(x11_display, wd.x11_xim_window, RevertToParent, CurrentTime);
2717 }
2718 XSetICFocus(wd.xic);
2719 } else {
2720 MutexLock mutex_lock(events_mutex);
2721 XUnsetICFocus(wd.xic);
2722 XUnmapWindow(x11_display, wd.x11_xim_window);
2723 wd.ime_active = false;
2724
2725 im_text = String();
2726 im_selection = Vector2i();
2727 }
2728}
2729
2730void DisplayServerX11::window_set_ime_position(const Point2i &p_pos, WindowID p_window) {
2731 _THREAD_SAFE_METHOD_
2732
2733 ERR_FAIL_COND(!windows.has(p_window));
2734 WindowData &wd = windows[p_window];
2735
2736 if (!wd.xic) {
2737 return;
2738 }
2739 if (!wd.focused) {
2740 return;
2741 }
2742
2743 if (wd.ime_active) {
2744 XWindowAttributes xwa;
2745 XSync(x11_display, False);
2746 XGetWindowAttributes(x11_display, wd.x11_xim_window, &xwa);
2747 if (xwa.map_state == IsViewable) {
2748 XMoveWindow(x11_display, wd.x11_xim_window, p_pos.x, p_pos.y);
2749 }
2750 }
2751}
2752
2753Point2i DisplayServerX11::ime_get_selection() const {
2754 return im_selection;
2755}
2756
2757String DisplayServerX11::ime_get_text() const {
2758 return im_text;
2759}
2760
2761void DisplayServerX11::cursor_set_shape(CursorShape p_shape) {
2762 _THREAD_SAFE_METHOD_
2763
2764 ERR_FAIL_INDEX(p_shape, CURSOR_MAX);
2765
2766 if (p_shape == current_cursor) {
2767 return;
2768 }
2769
2770 if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) {
2771 if (cursors[p_shape] != None) {
2772 for (const KeyValue<WindowID, WindowData> &E : windows) {
2773 XDefineCursor(x11_display, E.value.x11_window, cursors[p_shape]);
2774 }
2775 } else if (cursors[CURSOR_ARROW] != None) {
2776 for (const KeyValue<WindowID, WindowData> &E : windows) {
2777 XDefineCursor(x11_display, E.value.x11_window, cursors[CURSOR_ARROW]);
2778 }
2779 }
2780 }
2781
2782 current_cursor = p_shape;
2783}
2784
2785DisplayServerX11::CursorShape DisplayServerX11::cursor_get_shape() const {
2786 return current_cursor;
2787}
2788
2789void DisplayServerX11::cursor_set_custom_image(const Ref<Resource> &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) {
2790 _THREAD_SAFE_METHOD_
2791
2792 ERR_FAIL_INDEX(p_shape, CURSOR_MAX);
2793
2794 if (p_cursor.is_valid()) {
2795 HashMap<CursorShape, Vector<Variant>>::Iterator cursor_c = cursors_cache.find(p_shape);
2796
2797 if (cursor_c) {
2798 if (cursor_c->value[0] == p_cursor && cursor_c->value[1] == p_hotspot) {
2799 cursor_set_shape(p_shape);
2800 return;
2801 }
2802
2803 cursors_cache.erase(p_shape);
2804 }
2805
2806 Ref<Texture2D> texture = p_cursor;
2807 ERR_FAIL_COND(!texture.is_valid());
2808 Ref<AtlasTexture> atlas_texture = p_cursor;
2809 Size2i texture_size;
2810 Rect2i atlas_rect;
2811
2812 if (atlas_texture.is_valid()) {
2813 texture = atlas_texture->get_atlas();
2814
2815 atlas_rect.size.width = texture->get_width();
2816 atlas_rect.size.height = texture->get_height();
2817 atlas_rect.position.x = atlas_texture->get_region().position.x;
2818 atlas_rect.position.y = atlas_texture->get_region().position.y;
2819
2820 texture_size.width = atlas_texture->get_region().size.x;
2821 texture_size.height = atlas_texture->get_region().size.y;
2822 } else {
2823 texture_size.width = texture->get_width();
2824 texture_size.height = texture->get_height();
2825 }
2826
2827 ERR_FAIL_COND(p_hotspot.x < 0 || p_hotspot.y < 0);
2828 ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256);
2829 ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height);
2830
2831 Ref<Image> image = texture->get_image();
2832
2833 ERR_FAIL_COND(!image.is_valid());
2834 if (image->is_compressed()) {
2835 image = image->duplicate(true);
2836 Error err = image->decompress();
2837 ERR_FAIL_COND_MSG(err != OK, "Couldn't decompress VRAM-compressed custom mouse cursor image. Switch to a lossless compression mode in the Import dock.");
2838 }
2839
2840 // Create the cursor structure
2841 XcursorImage *cursor_image = XcursorImageCreate(texture_size.width, texture_size.height);
2842 XcursorUInt image_size = texture_size.width * texture_size.height;
2843 XcursorDim size = sizeof(XcursorPixel) * image_size;
2844
2845 cursor_image->version = 1;
2846 cursor_image->size = size;
2847 cursor_image->xhot = p_hotspot.x;
2848 cursor_image->yhot = p_hotspot.y;
2849
2850 // allocate memory to contain the whole file
2851 cursor_image->pixels = (XcursorPixel *)memalloc(size);
2852
2853 for (XcursorPixel index = 0; index < image_size; index++) {
2854 int row_index = floor(index / texture_size.width) + atlas_rect.position.y;
2855 int column_index = (index % int(texture_size.width)) + atlas_rect.position.x;
2856
2857 if (atlas_texture.is_valid()) {
2858 column_index = MIN(column_index, atlas_rect.size.width - 1);
2859 row_index = MIN(row_index, atlas_rect.size.height - 1);
2860 }
2861
2862 *(cursor_image->pixels + index) = image->get_pixel(column_index, row_index).to_argb32();
2863 }
2864
2865 ERR_FAIL_COND(cursor_image->pixels == nullptr);
2866
2867 // Save it for a further usage
2868 cursors[p_shape] = XcursorImageLoadCursor(x11_display, cursor_image);
2869
2870 Vector<Variant> params;
2871 params.push_back(p_cursor);
2872 params.push_back(p_hotspot);
2873 cursors_cache.insert(p_shape, params);
2874
2875 if (p_shape == current_cursor) {
2876 if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) {
2877 for (const KeyValue<WindowID, WindowData> &E : windows) {
2878 XDefineCursor(x11_display, E.value.x11_window, cursors[p_shape]);
2879 }
2880 }
2881 }
2882
2883 memfree(cursor_image->pixels);
2884 XcursorImageDestroy(cursor_image);
2885 } else {
2886 // Reset to default system cursor
2887 if (cursor_img[p_shape]) {
2888 cursors[p_shape] = XcursorImageLoadCursor(x11_display, cursor_img[p_shape]);
2889 }
2890
2891 cursors_cache.erase(p_shape);
2892
2893 CursorShape c = current_cursor;
2894 current_cursor = CURSOR_MAX;
2895 cursor_set_shape(c);
2896 }
2897}
2898
2899int DisplayServerX11::keyboard_get_layout_count() const {
2900 int _group_count = 0;
2901 XkbDescRec *kbd = XkbAllocKeyboard();
2902 if (kbd) {
2903 kbd->dpy = x11_display;
2904 XkbGetControls(x11_display, XkbAllControlsMask, kbd);
2905 XkbGetNames(x11_display, XkbSymbolsNameMask, kbd);
2906
2907 const Atom *groups = kbd->names->groups;
2908 if (kbd->ctrls != nullptr) {
2909 _group_count = kbd->ctrls->num_groups;
2910 } else {
2911 while (_group_count < XkbNumKbdGroups && groups[_group_count] != None) {
2912 _group_count++;
2913 }
2914 }
2915 XkbFreeKeyboard(kbd, 0, true);
2916 }
2917 return _group_count;
2918}
2919
2920int DisplayServerX11::keyboard_get_current_layout() const {
2921 XkbStateRec state;
2922 XkbGetState(x11_display, XkbUseCoreKbd, &state);
2923 return state.group;
2924}
2925
2926void DisplayServerX11::keyboard_set_current_layout(int p_index) {
2927 ERR_FAIL_INDEX(p_index, keyboard_get_layout_count());
2928 XkbLockGroup(x11_display, XkbUseCoreKbd, p_index);
2929}
2930
2931String DisplayServerX11::keyboard_get_layout_language(int p_index) const {
2932 String ret;
2933 XkbDescRec *kbd = XkbAllocKeyboard();
2934 if (kbd) {
2935 kbd->dpy = x11_display;
2936 XkbGetControls(x11_display, XkbAllControlsMask, kbd);
2937 XkbGetNames(x11_display, XkbSymbolsNameMask, kbd);
2938 XkbGetNames(x11_display, XkbGroupNamesMask, kbd);
2939
2940 int _group_count = 0;
2941 const Atom *groups = kbd->names->groups;
2942 if (kbd->ctrls != nullptr) {
2943 _group_count = kbd->ctrls->num_groups;
2944 } else {
2945 while (_group_count < XkbNumKbdGroups && groups[_group_count] != None) {
2946 _group_count++;
2947 }
2948 }
2949
2950 Atom names = kbd->names->symbols;
2951 if (names != None) {
2952 Vector<String> info = get_atom_name(x11_display, names).split("+");
2953 if (p_index >= 0 && p_index < _group_count) {
2954 if (p_index + 1 < info.size()) {
2955 ret = info[p_index + 1]; // Skip "pc" at the start and "inet"/"group" at the end of symbols.
2956 } else {
2957 ret = "en"; // No symbol for layout fallback to "en".
2958 }
2959 } else {
2960 ERR_PRINT("Index " + itos(p_index) + "is out of bounds (" + itos(_group_count) + ").");
2961 }
2962 }
2963 XkbFreeKeyboard(kbd, 0, true);
2964 }
2965 return ret.substr(0, 2);
2966}
2967
2968String DisplayServerX11::keyboard_get_layout_name(int p_index) const {
2969 String ret;
2970 XkbDescRec *kbd = XkbAllocKeyboard();
2971 if (kbd) {
2972 kbd->dpy = x11_display;
2973 XkbGetControls(x11_display, XkbAllControlsMask, kbd);
2974 XkbGetNames(x11_display, XkbSymbolsNameMask, kbd);
2975 XkbGetNames(x11_display, XkbGroupNamesMask, kbd);
2976
2977 int _group_count = 0;
2978 const Atom *groups = kbd->names->groups;
2979 if (kbd->ctrls != nullptr) {
2980 _group_count = kbd->ctrls->num_groups;
2981 } else {
2982 while (_group_count < XkbNumKbdGroups && groups[_group_count] != None) {
2983 _group_count++;
2984 }
2985 }
2986
2987 if (p_index >= 0 && p_index < _group_count) {
2988 ret = get_atom_name(x11_display, groups[p_index]);
2989 } else {
2990 ERR_PRINT("Index " + itos(p_index) + "is out of bounds (" + itos(_group_count) + ").");
2991 }
2992 XkbFreeKeyboard(kbd, 0, true);
2993 }
2994 return ret;
2995}
2996
2997Key DisplayServerX11::keyboard_get_keycode_from_physical(Key p_keycode) const {
2998 Key modifiers = p_keycode & KeyModifierMask::MODIFIER_MASK;
2999 Key keycode_no_mod = p_keycode & KeyModifierMask::CODE_MASK;
3000 unsigned int xkeycode = KeyMappingX11::get_xlibcode(keycode_no_mod);
3001 KeySym xkeysym = XkbKeycodeToKeysym(x11_display, xkeycode, keyboard_get_current_layout(), 0);
3002 if (is_ascii_lower_case(xkeysym)) {
3003 xkeysym -= ('a' - 'A');
3004 }
3005
3006 Key key = KeyMappingX11::get_keycode(xkeysym);
3007 // If not found, fallback to QWERTY.
3008 // This should match the behavior of the event pump
3009 if (key == Key::NONE) {
3010 return p_keycode;
3011 }
3012 return (Key)(key | modifiers);
3013}
3014
3015Key DisplayServerX11::keyboard_get_label_from_physical(Key p_keycode) const {
3016 Key modifiers = p_keycode & KeyModifierMask::MODIFIER_MASK;
3017 Key keycode_no_mod = p_keycode & KeyModifierMask::CODE_MASK;
3018 unsigned int xkeycode = KeyMappingX11::get_xlibcode(keycode_no_mod);
3019 KeySym xkeysym = XkbKeycodeToKeysym(x11_display, xkeycode, keyboard_get_current_layout(), 0);
3020 if (is_ascii_lower_case(xkeysym)) {
3021 xkeysym -= ('a' - 'A');
3022 }
3023
3024 Key key = KeyMappingX11::get_keycode(xkeysym);
3025#ifdef XKB_ENABLED
3026 if (xkb_loaded_v08p) {
3027 String keysym = String::chr(xkb_keysym_to_utf32(xkb_keysym_to_upper(xkeysym)));
3028 key = fix_key_label(keysym[0], KeyMappingX11::get_keycode(xkeysym));
3029 }
3030#endif
3031
3032 // If not found, fallback to QWERTY.
3033 // This should match the behavior of the event pump
3034 if (key == Key::NONE) {
3035 return p_keycode;
3036 }
3037 return (Key)(key | modifiers);
3038}
3039DisplayServerX11::Property DisplayServerX11::_read_property(Display *p_display, Window p_window, Atom p_property) {
3040 Atom actual_type = None;
3041 int actual_format = 0;
3042 unsigned long nitems = 0;
3043 unsigned long bytes_after = 0;
3044 unsigned char *ret = nullptr;
3045
3046 // Keep trying to read the property until there are no bytes unread.
3047 if (p_property != None) {
3048 int read_bytes = 1024;
3049 do {
3050 if (ret != nullptr) {
3051 XFree(ret);
3052 }
3053
3054 XGetWindowProperty(p_display, p_window, p_property, 0, read_bytes, False, AnyPropertyType,
3055 &actual_type, &actual_format, &nitems, &bytes_after,
3056 &ret);
3057
3058 read_bytes *= 2;
3059
3060 } while (bytes_after != 0);
3061 }
3062
3063 Property p = { ret, actual_format, (int)nitems, actual_type };
3064
3065 return p;
3066}
3067
3068static Atom pick_target_from_list(Display *p_display, const Atom *p_list, int p_count) {
3069 static const char *target_type = "text/uri-list";
3070
3071 for (int i = 0; i < p_count; i++) {
3072 Atom atom = p_list[i];
3073
3074 if (atom != None && get_atom_name(p_display, atom) == target_type) {
3075 return atom;
3076 }
3077 }
3078 return None;
3079}
3080
3081static Atom pick_target_from_atoms(Display *p_disp, Atom p_t1, Atom p_t2, Atom p_t3) {
3082 static const char *target_type = "text/uri-list";
3083 if (p_t1 != None && get_atom_name(p_disp, p_t1) == target_type) {
3084 return p_t1;
3085 }
3086
3087 if (p_t2 != None && get_atom_name(p_disp, p_t2) == target_type) {
3088 return p_t2;
3089 }
3090
3091 if (p_t3 != None && get_atom_name(p_disp, p_t3) == target_type) {
3092 return p_t3;
3093 }
3094
3095 return None;
3096}
3097
3098void DisplayServerX11::_get_key_modifier_state(unsigned int p_x11_state, Ref<InputEventWithModifiers> state) {
3099 state->set_shift_pressed((p_x11_state & ShiftMask));
3100 state->set_ctrl_pressed((p_x11_state & ControlMask));
3101 state->set_alt_pressed((p_x11_state & Mod1Mask /*|| p_x11_state&Mod5Mask*/)); //altgr should not count as alt
3102 state->set_meta_pressed((p_x11_state & Mod4Mask));
3103}
3104
3105BitField<MouseButtonMask> DisplayServerX11::_get_mouse_button_state(MouseButton p_x11_button, int p_x11_type) {
3106 MouseButtonMask mask = mouse_button_to_mask(p_x11_button);
3107
3108 if (p_x11_type == ButtonPress) {
3109 last_button_state.set_flag(mask);
3110 } else {
3111 last_button_state.clear_flag(mask);
3112 }
3113
3114 return last_button_state;
3115}
3116
3117void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, LocalVector<XEvent> &p_events, uint32_t &p_event_index, bool p_echo) {
3118 WindowData &wd = windows[p_window];
3119 // X11 functions don't know what const is
3120 XKeyEvent *xkeyevent = p_event;
3121
3122 if (wd.ime_in_progress) {
3123 return;
3124 }
3125 if (wd.ime_suppress_next_keyup) {
3126 wd.ime_suppress_next_keyup = false;
3127 if (xkeyevent->type != KeyPress) {
3128 return;
3129 }
3130 }
3131
3132 // This code was pretty difficult to write.
3133 // The docs stink and every toolkit seems to
3134 // do it in a different way.
3135
3136 /* Phase 1, obtain a proper keysym */
3137
3138 // This was also very difficult to figure out.
3139 // You'd expect you could just use Keysym provided by
3140 // XKeycodeToKeysym to obtain internationalized
3141 // input.. WRONG!!
3142 // you must use XLookupString (???) which not only wastes
3143 // cycles generating an unnecessary string, but also
3144 // still works in half the cases. (won't handle deadkeys)
3145 // For more complex input methods (deadkeys and more advanced)
3146 // you have to use XmbLookupString (??).
3147 // So then you have to choose which of both results
3148 // you want to keep.
3149 // This is a real bizarreness and cpu waster.
3150
3151 KeySym keysym_keycode = 0; // keysym used to find a keycode
3152 KeySym keysym_unicode = 0; // keysym used to find unicode
3153
3154 // XLookupString returns keysyms usable as nice keycodes.
3155 char str[256] = {};
3156 XKeyEvent xkeyevent_no_mod = *xkeyevent;
3157 xkeyevent_no_mod.state &= ~ShiftMask;
3158 xkeyevent_no_mod.state &= ~ControlMask;
3159 XLookupString(xkeyevent, str, 255, &keysym_unicode, nullptr);
3160 XLookupString(&xkeyevent_no_mod, nullptr, 0, &keysym_keycode, nullptr);
3161
3162 String keysym;
3163#ifdef XKB_ENABLED
3164 if (xkb_loaded_v08p) {
3165 KeySym keysym_unicode_nm = 0; // keysym used to find unicode
3166 XLookupString(&xkeyevent_no_mod, nullptr, 0, &keysym_unicode_nm, nullptr);
3167 keysym = String::chr(xkb_keysym_to_utf32(xkb_keysym_to_upper(keysym_unicode_nm)));
3168 }
3169#endif
3170
3171 // Meanwhile, XLookupString returns keysyms useful for unicode.
3172
3173 if (!xmbstring) {
3174 // keep a temporary buffer for the string
3175 xmbstring = (char *)memalloc(sizeof(char) * 8);
3176 xmblen = 8;
3177 }
3178
3179 if (xkeyevent->type == KeyPress && wd.xic) {
3180 Status status;
3181#ifdef X_HAVE_UTF8_STRING
3182 int utf8len = 8;
3183 char *utf8string = (char *)memalloc(sizeof(char) * utf8len);
3184 int utf8bytes = Xutf8LookupString(wd.xic, xkeyevent, utf8string,
3185 utf8len - 1, &keysym_unicode, &status);
3186 if (status == XBufferOverflow) {
3187 utf8len = utf8bytes + 1;
3188 utf8string = (char *)memrealloc(utf8string, utf8len);
3189 utf8bytes = Xutf8LookupString(wd.xic, xkeyevent, utf8string,
3190 utf8len - 1, &keysym_unicode, &status);
3191 }
3192 utf8string[utf8bytes] = '\0';
3193
3194 if (status == XLookupChars) {
3195 bool keypress = xkeyevent->type == KeyPress;
3196 Key keycode = KeyMappingX11::get_keycode(keysym_keycode);
3197 Key physical_keycode = KeyMappingX11::get_scancode(xkeyevent->keycode);
3198
3199 if (keycode >= Key::A + 32 && keycode <= Key::Z + 32) {
3200 keycode -= 'a' - 'A';
3201 }
3202
3203 String tmp;
3204 tmp.parse_utf8(utf8string, utf8bytes);
3205 for (int i = 0; i < tmp.length(); i++) {
3206 Ref<InputEventKey> k;
3207 k.instantiate();
3208 if (physical_keycode == Key::NONE && keycode == Key::NONE && tmp[i] == 0) {
3209 continue;
3210 }
3211
3212 if (keycode == Key::NONE) {
3213 keycode = (Key)physical_keycode;
3214 }
3215
3216 _get_key_modifier_state(xkeyevent->state, k);
3217
3218 k->set_window_id(p_window);
3219 k->set_pressed(keypress);
3220
3221 k->set_keycode(keycode);
3222 k->set_physical_keycode(physical_keycode);
3223 if (!keysym.is_empty()) {
3224 k->set_key_label(fix_key_label(keysym[0], keycode));
3225 } else {
3226 k->set_key_label(keycode);
3227 }
3228 if (keypress) {
3229 k->set_unicode(fix_unicode(tmp[i]));
3230 }
3231
3232 k->set_echo(false);
3233
3234 if (k->get_keycode() == Key::BACKTAB) {
3235 //make it consistent across platforms.
3236 k->set_keycode(Key::TAB);
3237 k->set_physical_keycode(Key::TAB);
3238 k->set_shift_pressed(true);
3239 }
3240
3241 Input::get_singleton()->parse_input_event(k);
3242 }
3243 memfree(utf8string);
3244 return;
3245 }
3246 memfree(utf8string);
3247#else
3248 do {
3249 int mnbytes = XmbLookupString(xic, xkeyevent, xmbstring, xmblen - 1, &keysym_unicode, &status);
3250 xmbstring[mnbytes] = '\0';
3251
3252 if (status == XBufferOverflow) {
3253 xmblen = mnbytes + 1;
3254 xmbstring = (char *)memrealloc(xmbstring, xmblen);
3255 }
3256 } while (status == XBufferOverflow);
3257#endif
3258#ifdef XKB_ENABLED
3259 } else if (xkeyevent->type == KeyPress && wd.xkb_state && xkb_loaded_v05p) {
3260 xkb_compose_feed_result res = xkb_compose_state_feed(wd.xkb_state, keysym_unicode);
3261 if (res == XKB_COMPOSE_FEED_ACCEPTED) {
3262 if (xkb_compose_state_get_status(wd.xkb_state) == XKB_COMPOSE_COMPOSED) {
3263 bool keypress = xkeyevent->type == KeyPress;
3264 Key keycode = KeyMappingX11::get_keycode(keysym_keycode);
3265 Key physical_keycode = KeyMappingX11::get_scancode(xkeyevent->keycode);
3266
3267 if (keycode >= Key::A + 32 && keycode <= Key::Z + 32) {
3268 keycode -= 'a' - 'A';
3269 }
3270
3271 char str_xkb[256] = {};
3272 int str_xkb_size = xkb_compose_state_get_utf8(wd.xkb_state, str_xkb, 255);
3273
3274 String tmp;
3275 tmp.parse_utf8(str_xkb, str_xkb_size);
3276 for (int i = 0; i < tmp.length(); i++) {
3277 Ref<InputEventKey> k;
3278 k.instantiate();
3279 if (physical_keycode == Key::NONE && keycode == Key::NONE && tmp[i] == 0) {
3280 continue;
3281 }
3282
3283 if (keycode == Key::NONE) {
3284 keycode = (Key)physical_keycode;
3285 }
3286
3287 _get_key_modifier_state(xkeyevent->state, k);
3288
3289 k->set_window_id(p_window);
3290 k->set_pressed(keypress);
3291
3292 k->set_keycode(keycode);
3293 k->set_physical_keycode(physical_keycode);
3294 if (!keysym.is_empty()) {
3295 k->set_key_label(fix_key_label(keysym[0], keycode));
3296 } else {
3297 k->set_key_label(keycode);
3298 }
3299 if (keypress) {
3300 k->set_unicode(fix_unicode(tmp[i]));
3301 }
3302
3303 k->set_echo(false);
3304
3305 if (k->get_keycode() == Key::BACKTAB) {
3306 //make it consistent across platforms.
3307 k->set_keycode(Key::TAB);
3308 k->set_physical_keycode(Key::TAB);
3309 k->set_shift_pressed(true);
3310 }
3311
3312 Input::get_singleton()->parse_input_event(k);
3313 }
3314 return;
3315 }
3316 }
3317#endif
3318 }
3319
3320 /* Phase 2, obtain a Godot keycode from the keysym */
3321
3322 // KeyMappingX11 just translated the X11 keysym to a PIGUI
3323 // keysym, so it works in all platforms the same.
3324
3325 Key keycode = KeyMappingX11::get_keycode(keysym_keycode);
3326 Key physical_keycode = KeyMappingX11::get_scancode(xkeyevent->keycode);
3327
3328 /* Phase 3, obtain a unicode character from the keysym */
3329
3330 // KeyMappingX11 also translates keysym to unicode.
3331 // It does a binary search on a table to translate
3332 // most properly.
3333 char32_t unicode = keysym_unicode > 0 ? KeyMappingX11::get_unicode_from_keysym(keysym_unicode) : 0;
3334
3335 /* Phase 4, determine if event must be filtered */
3336
3337 // This seems to be a side-effect of using XIM.
3338 // XFilterEvent looks like a core X11 function,
3339 // but it's actually just used to see if we must
3340 // ignore a deadkey, or events XIM determines
3341 // must not reach the actual gui.
3342 // Guess it was a design problem of the extension
3343
3344 bool keypress = xkeyevent->type == KeyPress;
3345
3346 if (physical_keycode == Key::NONE && keycode == Key::NONE && unicode == 0) {
3347 return;
3348 }
3349
3350 if (keycode == Key::NONE) {
3351 keycode = (Key)physical_keycode;
3352 }
3353
3354 /* Phase 5, determine modifier mask */
3355
3356 // No problems here, except I had no way to
3357 // know Mod1 was ALT and Mod4 was META (applekey/winkey)
3358 // just tried Mods until i found them.
3359
3360 //print_verbose("mod1: "+itos(xkeyevent->state&Mod1Mask)+" mod 5: "+itos(xkeyevent->state&Mod5Mask));
3361
3362 Ref<InputEventKey> k;
3363 k.instantiate();
3364 k->set_window_id(p_window);
3365
3366 _get_key_modifier_state(xkeyevent->state, k);
3367
3368 /* Phase 6, determine echo character */
3369
3370 // Echo characters in X11 are a keyrelease and a keypress
3371 // one after the other with the (almot) same timestamp.
3372 // To detect them, i compare to the next event in list and
3373 // check that their difference in time is below a threshold.
3374
3375 if (xkeyevent->type != KeyPress) {
3376 p_echo = false;
3377
3378 // make sure there are events pending,
3379 // so this call won't block.
3380 if (p_event_index + 1 < p_events.size()) {
3381 XEvent &peek_event = p_events[p_event_index + 1];
3382
3383 // I'm using a threshold of 5 msecs,
3384 // since sometimes there seems to be a little
3385 // jitter. I'm still not convinced that all this approach
3386 // is correct, but the xorg developers are
3387 // not very helpful today.
3388
3389#define ABSDIFF(x, y) (((x) < (y)) ? ((y) - (x)) : ((x) - (y)))
3390 ::Time threshold = ABSDIFF(peek_event.xkey.time, xkeyevent->time);
3391#undef ABSDIFF
3392 if (peek_event.type == KeyPress && threshold < 5) {
3393 KeySym rk;
3394 XLookupString((XKeyEvent *)&peek_event, str, 256, &rk, nullptr);
3395 if (rk == keysym_keycode) {
3396 // Consume to next event.
3397 ++p_event_index;
3398 _handle_key_event(p_window, (XKeyEvent *)&peek_event, p_events, p_event_index, true);
3399 return; //ignore current, echo next
3400 }
3401 }
3402
3403 // use the time from peek_event so it always works
3404 }
3405
3406 // save the time to check for echo when keypress happens
3407 }
3408
3409 /* Phase 7, send event to Window */
3410
3411 k->set_pressed(keypress);
3412
3413 if (keycode >= Key::A + 32 && keycode <= Key::Z + 32) {
3414 keycode -= int('a' - 'A');
3415 }
3416
3417 k->set_keycode(keycode);
3418 k->set_physical_keycode((Key)physical_keycode);
3419 if (!keysym.is_empty()) {
3420 k->set_key_label(fix_key_label(keysym[0], keycode));
3421 } else {
3422 k->set_key_label(keycode);
3423 }
3424 if (keypress) {
3425 k->set_unicode(fix_unicode(unicode));
3426 }
3427 k->set_echo(p_echo);
3428
3429 if (k->get_keycode() == Key::BACKTAB) {
3430 //make it consistent across platforms.
3431 k->set_keycode(Key::TAB);
3432 k->set_physical_keycode(Key::TAB);
3433 k->set_shift_pressed(true);
3434 }
3435
3436 //don't set mod state if modifier keys are released by themselves
3437 //else event.is_action() will not work correctly here
3438 if (!k->is_pressed()) {
3439 if (k->get_keycode() == Key::SHIFT) {
3440 k->set_shift_pressed(false);
3441 } else if (k->get_keycode() == Key::CTRL) {
3442 k->set_ctrl_pressed(false);
3443 } else if (k->get_keycode() == Key::ALT) {
3444 k->set_alt_pressed(false);
3445 } else if (k->get_keycode() == Key::META) {
3446 k->set_meta_pressed(false);
3447 }
3448 }
3449
3450 bool last_is_pressed = Input::get_singleton()->is_key_pressed(k->get_keycode());
3451 if (k->is_pressed()) {
3452 if (last_is_pressed) {
3453 k->set_echo(true);
3454 }
3455 }
3456
3457 Input::get_singleton()->parse_input_event(k);
3458}
3459
3460Atom DisplayServerX11::_process_selection_request_target(Atom p_target, Window p_requestor, Atom p_property, Atom p_selection) const {
3461 if (p_target == XInternAtom(x11_display, "TARGETS", 0)) {
3462 // Request to list all supported targets.
3463 Atom data[9];
3464 data[0] = XInternAtom(x11_display, "TARGETS", 0);
3465 data[1] = XInternAtom(x11_display, "SAVE_TARGETS", 0);
3466 data[2] = XInternAtom(x11_display, "MULTIPLE", 0);
3467 data[3] = XInternAtom(x11_display, "UTF8_STRING", 0);
3468 data[4] = XInternAtom(x11_display, "COMPOUND_TEXT", 0);
3469 data[5] = XInternAtom(x11_display, "TEXT", 0);
3470 data[6] = XA_STRING;
3471 data[7] = XInternAtom(x11_display, "text/plain;charset=utf-8", 0);
3472 data[8] = XInternAtom(x11_display, "text/plain", 0);
3473
3474 XChangeProperty(x11_display,
3475 p_requestor,
3476 p_property,
3477 XA_ATOM,
3478 32,
3479 PropModeReplace,
3480 (unsigned char *)&data,
3481 sizeof(data) / sizeof(data[0]));
3482 return p_property;
3483 } else if (p_target == XInternAtom(x11_display, "SAVE_TARGETS", 0)) {
3484 // Request to check if SAVE_TARGETS is supported, nothing special to do.
3485 XChangeProperty(x11_display,
3486 p_requestor,
3487 p_property,
3488 XInternAtom(x11_display, "NULL", False),
3489 32,
3490 PropModeReplace,
3491 nullptr,
3492 0);
3493 return p_property;
3494 } else if (p_target == XInternAtom(x11_display, "UTF8_STRING", 0) ||
3495 p_target == XInternAtom(x11_display, "COMPOUND_TEXT", 0) ||
3496 p_target == XInternAtom(x11_display, "TEXT", 0) ||
3497 p_target == XA_STRING ||
3498 p_target == XInternAtom(x11_display, "text/plain;charset=utf-8", 0) ||
3499 p_target == XInternAtom(x11_display, "text/plain", 0)) {
3500 // Directly using internal clipboard because we know our window
3501 // is the owner during a selection request.
3502 CharString clip;
3503 static const char *target_type = "PRIMARY";
3504 if (p_selection != None && get_atom_name(x11_display, p_selection) == target_type) {
3505 clip = internal_clipboard_primary.utf8();
3506 } else {
3507 clip = internal_clipboard.utf8();
3508 }
3509 XChangeProperty(x11_display,
3510 p_requestor,
3511 p_property,
3512 p_target,
3513 8,
3514 PropModeReplace,
3515 (unsigned char *)clip.get_data(),
3516 clip.length());
3517 return p_property;
3518 } else {
3519 char *target_name = XGetAtomName(x11_display, p_target);
3520 print_verbose(vformat("Target '%s' not supported.", target_name));
3521 if (target_name) {
3522 XFree(target_name);
3523 }
3524 return None;
3525 }
3526}
3527
3528void DisplayServerX11::_handle_selection_request_event(XSelectionRequestEvent *p_event) const {
3529 XEvent respond;
3530 if (p_event->target == XInternAtom(x11_display, "MULTIPLE", 0)) {
3531 // Request for multiple target conversions at once.
3532 Atom atom_pair = XInternAtom(x11_display, "ATOM_PAIR", False);
3533 respond.xselection.property = None;
3534
3535 Atom type;
3536 int format;
3537 unsigned long len;
3538 unsigned long remaining;
3539 unsigned char *data = nullptr;
3540 if (XGetWindowProperty(x11_display, p_event->requestor, p_event->property, 0, LONG_MAX, False, atom_pair, &type, &format, &len, &remaining, &data) == Success) {
3541 if ((len >= 2) && data) {
3542 Atom *targets = (Atom *)data;
3543 for (uint64_t i = 0; i < len; i += 2) {
3544 Atom target = targets[i];
3545 Atom &property = targets[i + 1];
3546 property = _process_selection_request_target(target, p_event->requestor, property, p_event->selection);
3547 }
3548
3549 XChangeProperty(x11_display,
3550 p_event->requestor,
3551 p_event->property,
3552 atom_pair,
3553 32,
3554 PropModeReplace,
3555 (unsigned char *)targets,
3556 len);
3557
3558 respond.xselection.property = p_event->property;
3559 }
3560 XFree(data);
3561 }
3562 } else {
3563 // Request for target conversion.
3564 respond.xselection.property = _process_selection_request_target(p_event->target, p_event->requestor, p_event->property, p_event->selection);
3565 }
3566
3567 respond.xselection.type = SelectionNotify;
3568 respond.xselection.display = p_event->display;
3569 respond.xselection.requestor = p_event->requestor;
3570 respond.xselection.selection = p_event->selection;
3571 respond.xselection.target = p_event->target;
3572 respond.xselection.time = p_event->time;
3573
3574 XSendEvent(x11_display, p_event->requestor, True, NoEventMask, &respond);
3575 XFlush(x11_display);
3576}
3577
3578int DisplayServerX11::_xim_preedit_start_callback(::XIM xim, ::XPointer client_data,
3579 ::XPointer call_data) {
3580 DisplayServerX11 *ds = reinterpret_cast<DisplayServerX11 *>(client_data);
3581 WindowID window_id = ds->_get_focused_window_or_popup();
3582 WindowData &wd = ds->windows[window_id];
3583 if (wd.ime_active) {
3584 wd.ime_in_progress = true;
3585 }
3586
3587 return -1; // Allow preedit strings of any length (no limit).
3588}
3589
3590void DisplayServerX11::_xim_preedit_done_callback(::XIM xim, ::XPointer client_data,
3591 ::XPointer call_data) {
3592 DisplayServerX11 *ds = reinterpret_cast<DisplayServerX11 *>(client_data);
3593 WindowID window_id = ds->_get_focused_window_or_popup();
3594 WindowData &wd = ds->windows[window_id];
3595 if (wd.ime_active) {
3596 wd.ime_in_progress = false;
3597 wd.ime_suppress_next_keyup = true;
3598 }
3599}
3600
3601void DisplayServerX11::_xim_preedit_draw_callback(::XIM xim, ::XPointer client_data,
3602 ::XIMPreeditDrawCallbackStruct *call_data) {
3603 DisplayServerX11 *ds = reinterpret_cast<DisplayServerX11 *>(client_data);
3604 WindowID window_id = ds->_get_focused_window_or_popup();
3605 WindowData &wd = ds->windows[window_id];
3606
3607 XIMText *xim_text = call_data->text;
3608 if (wd.ime_active) {
3609 if (xim_text != nullptr) {
3610 String changed_text;
3611 if (xim_text->encoding_is_wchar) {
3612 changed_text = String(xim_text->string.wide_char);
3613 } else {
3614 changed_text.parse_utf8(xim_text->string.multi_byte);
3615 }
3616
3617 if (call_data->chg_length < 0) {
3618 ds->im_text = ds->im_text.substr(0, call_data->chg_first) + changed_text;
3619 } else {
3620 ds->im_text = ds->im_text.substr(0, call_data->chg_first) + changed_text + ds->im_text.substr(call_data->chg_length);
3621 }
3622
3623 // Find the start and end of the selection.
3624 int start = 0, count = 0;
3625 for (int i = 0; i < xim_text->length; i++) {
3626 if (xim_text->feedback[i] & XIMReverse) {
3627 if (count == 0) {
3628 start = i;
3629 count = 1;
3630 } else {
3631 count++;
3632 }
3633 }
3634 }
3635 if (count > 0) {
3636 ds->im_selection = Point2i(start + call_data->chg_first, count);
3637 } else {
3638 ds->im_selection = Point2i(call_data->caret, 0);
3639 }
3640 } else {
3641 ds->im_text = String();
3642 ds->im_selection = Point2i();
3643 }
3644
3645 OS_Unix::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE);
3646 }
3647}
3648
3649void DisplayServerX11::_xim_preedit_caret_callback(::XIM xim, ::XPointer client_data,
3650 ::XIMPreeditCaretCallbackStruct *call_data) {
3651}
3652
3653void DisplayServerX11::_xim_destroy_callback(::XIM im, ::XPointer client_data,
3654 ::XPointer call_data) {
3655 WARN_PRINT("Input method stopped");
3656 DisplayServerX11 *ds = reinterpret_cast<DisplayServerX11 *>(client_data);
3657 ds->xim = nullptr;
3658
3659 for (KeyValue<WindowID, WindowData> &E : ds->windows) {
3660 E.value.xic = nullptr;
3661 }
3662}
3663
3664void DisplayServerX11::_window_changed(XEvent *event) {
3665 WindowID window_id = MAIN_WINDOW_ID;
3666
3667 // Assign the event to the relevant window
3668 for (const KeyValue<WindowID, WindowData> &E : windows) {
3669 if (event->xany.window == E.value.x11_window) {
3670 window_id = E.key;
3671 break;
3672 }
3673 }
3674
3675 Rect2i new_rect;
3676
3677 WindowData &wd = windows[window_id];
3678 if (wd.x11_window != event->xany.window) { // Check if the correct window, in case it was not main window or anything else
3679 return;
3680 }
3681
3682 // Query display server about a possible new window state.
3683 wd.fullscreen = _window_fullscreen_check(window_id);
3684 wd.maximized = _window_maximize_check(window_id, "_NET_WM_STATE") && !wd.fullscreen;
3685 wd.minimized = _window_minimize_check(window_id) && !wd.fullscreen && !wd.maximized;
3686
3687 // Readjusting the window position if the window is being reparented by the window manager for decoration
3688 Window root, parent, *children;
3689 unsigned int nchildren;
3690 if (XQueryTree(x11_display, wd.x11_window, &root, &parent, &children, &nchildren) && wd.parent != parent) {
3691 wd.parent = parent;
3692 window_set_position(wd.position, window_id);
3693 }
3694 XFree(children);
3695
3696 {
3697 //the position in xconfigure is not useful here, obtain it manually
3698 int x = 0, y = 0;
3699 Window child;
3700 XTranslateCoordinates(x11_display, wd.x11_window, DefaultRootWindow(x11_display), 0, 0, &x, &y, &child);
3701 new_rect.position.x = x;
3702 new_rect.position.y = y;
3703
3704 new_rect.size.width = event->xconfigure.width;
3705 new_rect.size.height = event->xconfigure.height;
3706 }
3707
3708 if (new_rect == Rect2i(wd.position, wd.size)) {
3709 return;
3710 }
3711
3712 wd.position = new_rect.position;
3713 wd.size = new_rect.size;
3714
3715#if defined(VULKAN_ENABLED)
3716 if (context_vulkan) {
3717 context_vulkan->window_resize(window_id, wd.size.width, wd.size.height);
3718 }
3719#endif
3720#if defined(GLES3_ENABLED)
3721 if (gl_manager) {
3722 gl_manager->window_resize(window_id, wd.size.width, wd.size.height);
3723 }
3724#endif
3725
3726 if (!wd.rect_changed_callback.is_null()) {
3727 Rect2i r = new_rect;
3728
3729 Variant rect = r;
3730
3731 Variant *rectp = &rect;
3732 Variant ret;
3733 Callable::CallError ce;
3734 wd.rect_changed_callback.callp((const Variant **)&rectp, 1, ret, ce);
3735 }
3736}
3737
3738DisplayServer::WindowID DisplayServerX11::_get_focused_window_or_popup() const {
3739 const List<WindowID>::Element *E = popup_list.back();
3740 if (E) {
3741 return E->get();
3742 }
3743
3744 return last_focused_window;
3745}
3746
3747void DisplayServerX11::_dispatch_input_events(const Ref<InputEvent> &p_event) {
3748 static_cast<DisplayServerX11 *>(get_singleton())->_dispatch_input_event(p_event);
3749}
3750
3751void DisplayServerX11::_dispatch_input_event(const Ref<InputEvent> &p_event) {
3752 Variant ev = p_event;
3753 Variant *evp = &ev;
3754 Variant ret;
3755 Callable::CallError ce;
3756
3757 {
3758 List<WindowID>::Element *E = popup_list.back();
3759 if (E && Object::cast_to<InputEventKey>(*p_event)) {
3760 // Redirect keyboard input to active popup.
3761 if (windows.has(E->get())) {
3762 Callable callable = windows[E->get()].input_event_callback;
3763 if (callable.is_valid()) {
3764 callable.callp((const Variant **)&evp, 1, ret, ce);
3765 }
3766 }
3767 return;
3768 }
3769 }
3770
3771 Ref<InputEventFromWindow> event_from_window = p_event;
3772 if (event_from_window.is_valid() && event_from_window->get_window_id() != INVALID_WINDOW_ID) {
3773 // Send to a single window.
3774 if (windows.has(event_from_window->get_window_id())) {
3775 Callable callable = windows[event_from_window->get_window_id()].input_event_callback;
3776 if (callable.is_valid()) {
3777 callable.callp((const Variant **)&evp, 1, ret, ce);
3778 }
3779 }
3780 } else {
3781 // Send to all windows.
3782 for (KeyValue<WindowID, WindowData> &E : windows) {
3783 Callable callable = E.value.input_event_callback;
3784 if (callable.is_valid()) {
3785 callable.callp((const Variant **)&evp, 1, ret, ce);
3786 }
3787 }
3788 }
3789}
3790
3791void DisplayServerX11::_send_window_event(const WindowData &wd, WindowEvent p_event) {
3792 if (!wd.event_callback.is_null()) {
3793 Variant event = int(p_event);
3794 Variant *eventp = &event;
3795 Variant ret;
3796 Callable::CallError ce;
3797 wd.event_callback.callp((const Variant **)&eventp, 1, ret, ce);
3798 }
3799}
3800
3801void DisplayServerX11::_poll_events_thread(void *ud) {
3802 DisplayServerX11 *display_server = static_cast<DisplayServerX11 *>(ud);
3803 display_server->_poll_events();
3804}
3805
3806Bool DisplayServerX11::_predicate_all_events(Display *display, XEvent *event, XPointer arg) {
3807 // Just accept all events.
3808 return True;
3809}
3810
3811bool DisplayServerX11::_wait_for_events() const {
3812 int x11_fd = ConnectionNumber(x11_display);
3813 fd_set in_fds;
3814
3815 XFlush(x11_display);
3816
3817 FD_ZERO(&in_fds);
3818 FD_SET(x11_fd, &in_fds);
3819
3820 struct timeval tv;
3821 tv.tv_usec = 0;
3822 tv.tv_sec = 1;
3823
3824 // Wait for next event or timeout.
3825 int num_ready_fds = select(x11_fd + 1, &in_fds, nullptr, nullptr, &tv);
3826
3827 if (num_ready_fds > 0) {
3828 // Event received.
3829 return true;
3830 } else {
3831 // Error or timeout.
3832 if (num_ready_fds < 0) {
3833 ERR_PRINT("_wait_for_events: select error: " + itos(errno));
3834 }
3835 return false;
3836 }
3837}
3838
3839void DisplayServerX11::_poll_events() {
3840 while (!events_thread_done.is_set()) {
3841 _wait_for_events();
3842
3843 // Process events from the queue.
3844 {
3845 MutexLock mutex_lock(events_mutex);
3846
3847 _check_pending_events(polled_events);
3848 }
3849 }
3850}
3851
3852void DisplayServerX11::_check_pending_events(LocalVector<XEvent> &r_events) {
3853 // Flush to make sure to gather all pending events.
3854 XFlush(x11_display);
3855
3856 // Non-blocking wait for next event and remove it from the queue.
3857 XEvent ev = {};
3858 while (XCheckIfEvent(x11_display, &ev, _predicate_all_events, nullptr)) {
3859 // Check if the input manager wants to process the event.
3860 if (XFilterEvent(&ev, None)) {
3861 // Event has been filtered by the Input Manager,
3862 // it has to be ignored and a new one will be received.
3863 continue;
3864 }
3865
3866 // Handle selection request events directly in the event thread, because
3867 // communication through the x server takes several events sent back and forth
3868 // and we don't want to block other programs while processing only one each frame.
3869 if (ev.type == SelectionRequest) {
3870 _handle_selection_request_event(&(ev.xselectionrequest));
3871 continue;
3872 }
3873
3874 r_events.push_back(ev);
3875 }
3876}
3877
3878DisplayServer::WindowID DisplayServerX11::window_get_active_popup() const {
3879 const List<WindowID>::Element *E = popup_list.back();
3880 if (E) {
3881 return E->get();
3882 } else {
3883 return INVALID_WINDOW_ID;
3884 }
3885}
3886
3887void DisplayServerX11::window_set_popup_safe_rect(WindowID p_window, const Rect2i &p_rect) {
3888 _THREAD_SAFE_METHOD_
3889
3890 ERR_FAIL_COND(!windows.has(p_window));
3891 WindowData &wd = windows[p_window];
3892 wd.parent_safe_rect = p_rect;
3893}
3894
3895Rect2i DisplayServerX11::window_get_popup_safe_rect(WindowID p_window) const {
3896 _THREAD_SAFE_METHOD_
3897
3898 ERR_FAIL_COND_V(!windows.has(p_window), Rect2i());
3899 const WindowData &wd = windows[p_window];
3900 return wd.parent_safe_rect;
3901}
3902
3903void DisplayServerX11::popup_open(WindowID p_window) {
3904 _THREAD_SAFE_METHOD_
3905
3906 bool has_popup_ancestor = false;
3907 WindowID transient_root = p_window;
3908 while (true) {
3909 WindowID parent = windows[transient_root].transient_parent;
3910 if (parent == INVALID_WINDOW_ID) {
3911 break;
3912 } else {
3913 transient_root = parent;
3914 if (windows[parent].is_popup) {
3915 has_popup_ancestor = true;
3916 break;
3917 }
3918 }
3919 }
3920
3921 WindowData &wd = windows[p_window];
3922 if (wd.is_popup || has_popup_ancestor) {
3923 // Find current popup parent, or root popup if new window is not transient.
3924 List<WindowID>::Element *C = nullptr;
3925 List<WindowID>::Element *E = popup_list.back();
3926 while (E) {
3927 if (wd.transient_parent != E->get() || wd.transient_parent == INVALID_WINDOW_ID) {
3928 C = E;
3929 E = E->prev();
3930 } else {
3931 break;
3932 }
3933 }
3934 if (C) {
3935 _send_window_event(windows[C->get()], DisplayServerX11::WINDOW_EVENT_CLOSE_REQUEST);
3936 }
3937
3938 time_since_popup = OS::get_singleton()->get_ticks_msec();
3939 popup_list.push_back(p_window);
3940 }
3941}
3942
3943void DisplayServerX11::popup_close(WindowID p_window) {
3944 _THREAD_SAFE_METHOD_
3945
3946 List<WindowID>::Element *E = popup_list.find(p_window);
3947 while (E) {
3948 List<WindowID>::Element *F = E->next();
3949 WindowID win_id = E->get();
3950 popup_list.erase(E);
3951
3952 _send_window_event(windows[win_id], DisplayServerX11::WINDOW_EVENT_CLOSE_REQUEST);
3953 E = F;
3954 }
3955}
3956
3957bool DisplayServerX11::mouse_process_popups() {
3958 _THREAD_SAFE_METHOD_
3959
3960 if (popup_list.is_empty()) {
3961 return false;
3962 }
3963
3964 uint64_t delta = OS::get_singleton()->get_ticks_msec() - time_since_popup;
3965 if (delta < 250) {
3966 return false;
3967 }
3968
3969 int number_of_screens = XScreenCount(x11_display);
3970 bool closed = false;
3971 for (int i = 0; i < number_of_screens; i++) {
3972 Window root, child;
3973 int root_x, root_y, win_x, win_y;
3974 unsigned int mask;
3975 if (XQueryPointer(x11_display, XRootWindow(x11_display, i), &root, &child, &root_x, &root_y, &win_x, &win_y, &mask)) {
3976 XWindowAttributes root_attrs;
3977 XGetWindowAttributes(x11_display, root, &root_attrs);
3978 Vector2i pos = Vector2i(root_attrs.x + root_x, root_attrs.y + root_y);
3979 if (mask != last_mouse_monitor_mask) {
3980 if (((mask & Button1Mask) || (mask & Button2Mask) || (mask & Button3Mask) || (mask & Button4Mask) || (mask & Button5Mask))) {
3981 List<WindowID>::Element *C = nullptr;
3982 List<WindowID>::Element *E = popup_list.back();
3983 // Find top popup to close.
3984 while (E) {
3985 // Popup window area.
3986 Rect2i win_rect = Rect2i(window_get_position_with_decorations(E->get()), window_get_size_with_decorations(E->get()));
3987 // Area of the parent window, which responsible for opening sub-menu.
3988 Rect2i safe_rect = window_get_popup_safe_rect(E->get());
3989 if (win_rect.has_point(pos)) {
3990 break;
3991 } else if (safe_rect != Rect2i() && safe_rect.has_point(pos)) {
3992 break;
3993 } else {
3994 C = E;
3995 E = E->prev();
3996 }
3997 }
3998 if (C) {
3999 _send_window_event(windows[C->get()], DisplayServerX11::WINDOW_EVENT_CLOSE_REQUEST);
4000 closed = true;
4001 }
4002 }
4003 }
4004 last_mouse_monitor_mask = mask;
4005 }
4006 }
4007 return closed;
4008}
4009
4010void DisplayServerX11::process_events() {
4011 _THREAD_SAFE_METHOD_
4012
4013#ifdef DISPLAY_SERVER_X11_DEBUG_LOGS_ENABLED
4014 static int frame = 0;
4015 ++frame;
4016#endif
4017
4018 bool ignore_events = mouse_process_popups();
4019
4020 if (app_focused) {
4021 //verify that one of the windows has focus, else send focus out notification
4022 bool focus_found = false;
4023 for (const KeyValue<WindowID, WindowData> &E : windows) {
4024 if (E.value.focused) {
4025 focus_found = true;
4026 break;
4027 }
4028 }
4029
4030 if (!focus_found) {
4031 uint64_t delta = OS::get_singleton()->get_ticks_msec() - time_since_no_focus;
4032
4033 if (delta > 250) {
4034 //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.
4035 if (OS::get_singleton()->get_main_loop()) {
4036 DEBUG_LOG_X11("All focus lost, triggering NOTIFICATION_APPLICATION_FOCUS_OUT\n");
4037 OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT);
4038 }
4039 app_focused = false;
4040 }
4041 } else {
4042 time_since_no_focus = OS::get_singleton()->get_ticks_msec();
4043 }
4044 }
4045
4046 do_mouse_warp = false;
4047
4048 // Is the current mouse mode one where it needs to be grabbed.
4049 bool mouse_mode_grab = mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN;
4050
4051 xi.pressure = 0;
4052 xi.tilt = Vector2();
4053 xi.pressure_supported = false;
4054
4055 LocalVector<XEvent> events;
4056 {
4057 // Block events polling while flushing events.
4058 MutexLock mutex_lock(events_mutex);
4059 events = polled_events;
4060 polled_events.clear();
4061
4062 // Check for more pending events to avoid an extra frame delay.
4063 _check_pending_events(events);
4064 }
4065
4066 for (uint32_t event_index = 0; event_index < events.size(); ++event_index) {
4067 XEvent &event = events[event_index];
4068
4069 bool ime_window_event = false;
4070 WindowID window_id = MAIN_WINDOW_ID;
4071
4072 // Assign the event to the relevant window
4073 for (const KeyValue<WindowID, WindowData> &E : windows) {
4074 if (event.xany.window == E.value.x11_window) {
4075 window_id = E.key;
4076 break;
4077 }
4078 if (event.xany.window == E.value.x11_xim_window) {
4079 window_id = E.key;
4080 ime_window_event = true;
4081 break;
4082 }
4083 }
4084
4085 if (XGetEventData(x11_display, &event.xcookie)) {
4086 if (event.xcookie.type == GenericEvent && event.xcookie.extension == xi.opcode) {
4087 XIDeviceEvent *event_data = (XIDeviceEvent *)event.xcookie.data;
4088 int index = event_data->detail;
4089 Vector2 pos = Vector2(event_data->event_x, event_data->event_y);
4090
4091 switch (event_data->evtype) {
4092 case XI_HierarchyChanged:
4093 case XI_DeviceChanged: {
4094 _refresh_device_info();
4095 } break;
4096 case XI_RawMotion: {
4097 if (ime_window_event || ignore_events) {
4098 break;
4099 }
4100 XIRawEvent *raw_event = (XIRawEvent *)event_data;
4101 int device_id = raw_event->sourceid;
4102
4103 // Determine the axis used (called valuators in XInput for some forsaken reason)
4104 // Mask is a bitmask indicating which axes are involved.
4105 // We are interested in the values of axes 0 and 1.
4106 if (raw_event->valuators.mask_len <= 0) {
4107 break;
4108 }
4109
4110 const double *values = raw_event->raw_values;
4111
4112 double rel_x = 0.0;
4113 double rel_y = 0.0;
4114
4115 if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_ABSX)) {
4116 rel_x = *values;
4117 values++;
4118 }
4119
4120 if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_ABSY)) {
4121 rel_y = *values;
4122 values++;
4123 }
4124
4125 if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_PRESSURE)) {
4126 HashMap<int, Vector2>::Iterator pen_pressure = xi.pen_pressure_range.find(device_id);
4127 if (pen_pressure) {
4128 Vector2 pen_pressure_range = pen_pressure->value;
4129 if (pen_pressure_range != Vector2()) {
4130 xi.pressure_supported = true;
4131 xi.pressure = (*values - pen_pressure_range[0]) /
4132 (pen_pressure_range[1] - pen_pressure_range[0]);
4133 }
4134 }
4135
4136 values++;
4137 }
4138
4139 if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_TILTX)) {
4140 HashMap<int, Vector2>::Iterator pen_tilt_x = xi.pen_tilt_x_range.find(device_id);
4141 if (pen_tilt_x) {
4142 Vector2 pen_tilt_x_range = pen_tilt_x->value;
4143 if (pen_tilt_x_range[0] != 0 && *values < 0) {
4144 xi.tilt.x = *values / -pen_tilt_x_range[0];
4145 } else if (pen_tilt_x_range[1] != 0) {
4146 xi.tilt.x = *values / pen_tilt_x_range[1];
4147 }
4148 }
4149
4150 values++;
4151 }
4152
4153 if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_TILTY)) {
4154 HashMap<int, Vector2>::Iterator pen_tilt_y = xi.pen_tilt_y_range.find(device_id);
4155 if (pen_tilt_y) {
4156 Vector2 pen_tilt_y_range = pen_tilt_y->value;
4157 if (pen_tilt_y_range[0] != 0 && *values < 0) {
4158 xi.tilt.y = *values / -pen_tilt_y_range[0];
4159 } else if (pen_tilt_y_range[1] != 0) {
4160 xi.tilt.y = *values / pen_tilt_y_range[1];
4161 }
4162 }
4163
4164 values++;
4165 }
4166
4167 HashMap<int, bool>::Iterator pen_inverted = xi.pen_inverted_devices.find(device_id);
4168 if (pen_inverted) {
4169 xi.pen_inverted = pen_inverted->value;
4170 }
4171
4172 // https://bugs.freedesktop.org/show_bug.cgi?id=71609
4173 // http://lists.libsdl.org/pipermail/commits-libsdl.org/2015-June/000282.html
4174 if (raw_event->time == xi.last_relative_time && rel_x == xi.relative_motion.x && rel_y == xi.relative_motion.y) {
4175 break; // Flush duplicate to avoid overly fast motion
4176 }
4177
4178 xi.old_raw_pos.x = xi.raw_pos.x;
4179 xi.old_raw_pos.y = xi.raw_pos.y;
4180 xi.raw_pos.x = rel_x;
4181 xi.raw_pos.y = rel_y;
4182
4183 HashMap<int, Vector2>::Iterator abs_info = xi.absolute_devices.find(device_id);
4184
4185 if (abs_info) {
4186 // Absolute mode device
4187 Vector2 mult = abs_info->value;
4188
4189 xi.relative_motion.x += (xi.raw_pos.x - xi.old_raw_pos.x) * mult.x;
4190 xi.relative_motion.y += (xi.raw_pos.y - xi.old_raw_pos.y) * mult.y;
4191 } else {
4192 // Relative mode device
4193 xi.relative_motion.x = xi.raw_pos.x;
4194 xi.relative_motion.y = xi.raw_pos.y;
4195 }
4196
4197 xi.last_relative_time = raw_event->time;
4198 } break;
4199#ifdef TOUCH_ENABLED
4200 case XI_TouchBegin:
4201 case XI_TouchEnd: {
4202 if (ime_window_event || ignore_events) {
4203 break;
4204 }
4205 bool is_begin = event_data->evtype == XI_TouchBegin;
4206
4207 Ref<InputEventScreenTouch> st;
4208 st.instantiate();
4209 st->set_window_id(window_id);
4210 st->set_index(index);
4211 st->set_position(pos);
4212 st->set_pressed(is_begin);
4213
4214 if (is_begin) {
4215 if (xi.state.has(index)) { // Defensive
4216 break;
4217 }
4218 xi.state[index] = pos;
4219 if (xi.state.size() == 1) {
4220 // X11 may send a motion event when a touch gesture begins, that would result
4221 // in a spurious mouse motion event being sent to Godot; remember it to be able to filter it out
4222 xi.mouse_pos_to_filter = pos;
4223 }
4224 Input::get_singleton()->parse_input_event(st);
4225 } else {
4226 if (!xi.state.has(index)) { // Defensive
4227 break;
4228 }
4229 xi.state.erase(index);
4230 Input::get_singleton()->parse_input_event(st);
4231 }
4232 } break;
4233
4234 case XI_TouchUpdate: {
4235 if (ime_window_event || ignore_events) {
4236 break;
4237 }
4238 HashMap<int, Vector2>::Iterator curr_pos_elem = xi.state.find(index);
4239 if (!curr_pos_elem) { // Defensive
4240 break;
4241 }
4242
4243 if (curr_pos_elem->value != pos) {
4244 Ref<InputEventScreenDrag> sd;
4245 sd.instantiate();
4246 sd->set_window_id(window_id);
4247 sd->set_index(index);
4248 sd->set_position(pos);
4249 sd->set_relative(pos - curr_pos_elem->value);
4250 Input::get_singleton()->parse_input_event(sd);
4251
4252 curr_pos_elem->value = pos;
4253 }
4254 } break;
4255#endif
4256 }
4257 }
4258 }
4259 XFreeEventData(x11_display, &event.xcookie);
4260
4261 switch (event.type) {
4262 case MapNotify: {
4263 DEBUG_LOG_X11("[%u] MapNotify window=%lu (%u) \n", frame, event.xmap.window, window_id);
4264 if (ime_window_event) {
4265 break;
4266 }
4267
4268 const WindowData &wd = windows[window_id];
4269
4270 XWindowAttributes xwa;
4271 XSync(x11_display, False);
4272 XGetWindowAttributes(x11_display, wd.x11_window, &xwa);
4273
4274 // Set focus when menu window is started.
4275 // RevertToPointerRoot is used to make sure we don't lose all focus in case
4276 // a subwindow and its parent are both destroyed.
4277 if ((xwa.map_state == IsViewable) && !wd.no_focus && !wd.is_popup) {
4278 XSetInputFocus(x11_display, wd.x11_window, RevertToPointerRoot, CurrentTime);
4279 }
4280
4281 // Have we failed to set fullscreen while the window was unmapped?
4282 _validate_mode_on_map(window_id);
4283 } break;
4284
4285 case Expose: {
4286 DEBUG_LOG_X11("[%u] Expose window=%lu (%u), count='%u' \n", frame, event.xexpose.window, window_id, event.xexpose.count);
4287 if (ime_window_event) {
4288 break;
4289 }
4290
4291 windows[window_id].fullscreen = _window_fullscreen_check(window_id);
4292
4293 Main::force_redraw();
4294 } break;
4295
4296 case NoExpose: {
4297 DEBUG_LOG_X11("[%u] NoExpose drawable=%lu (%u) \n", frame, event.xnoexpose.drawable, window_id);
4298 if (ime_window_event) {
4299 break;
4300 }
4301
4302 windows[window_id].minimized = true;
4303 } break;
4304
4305 case VisibilityNotify: {
4306 DEBUG_LOG_X11("[%u] VisibilityNotify window=%lu (%u), state=%u \n", frame, event.xvisibility.window, window_id, event.xvisibility.state);
4307 if (ime_window_event) {
4308 break;
4309 }
4310
4311 windows[window_id].minimized = _window_minimize_check(window_id);
4312 } break;
4313
4314 case LeaveNotify: {
4315 DEBUG_LOG_X11("[%u] LeaveNotify window=%lu (%u), mode='%u' \n", frame, event.xcrossing.window, window_id, event.xcrossing.mode);
4316 if (ime_window_event) {
4317 break;
4318 }
4319
4320 if (!mouse_mode_grab && window_mouseover_id == window_id) {
4321 window_mouseover_id = INVALID_WINDOW_ID;
4322 _send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_EXIT);
4323 }
4324
4325 } break;
4326
4327 case EnterNotify: {
4328 DEBUG_LOG_X11("[%u] EnterNotify window=%lu (%u), mode='%u' \n", frame, event.xcrossing.window, window_id, event.xcrossing.mode);
4329 if (ime_window_event) {
4330 break;
4331 }
4332
4333 if (!mouse_mode_grab && window_mouseover_id != window_id) {
4334 if (window_mouseover_id != INVALID_WINDOW_ID) {
4335 _send_window_event(windows[window_mouseover_id], WINDOW_EVENT_MOUSE_EXIT);
4336 }
4337 window_mouseover_id = window_id;
4338 _send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_ENTER);
4339 }
4340 } break;
4341
4342 case FocusIn: {
4343 DEBUG_LOG_X11("[%u] FocusIn window=%lu (%u), mode='%u' \n", frame, event.xfocus.window, window_id, event.xfocus.mode);
4344 if (ime_window_event || (event.xfocus.detail == NotifyInferior)) {
4345 break;
4346 }
4347
4348 WindowData &wd = windows[window_id];
4349 last_focused_window = window_id;
4350 wd.focused = true;
4351
4352 // Keep track of focus order for overlapping windows.
4353 static unsigned int focus_order = 0;
4354 wd.focus_order = ++focus_order;
4355
4356 _send_window_event(wd, WINDOW_EVENT_FOCUS_IN);
4357
4358 if (mouse_mode_grab) {
4359 // Show and update the cursor if confined and the window regained focus.
4360
4361 for (const KeyValue<WindowID, WindowData> &E : windows) {
4362 if (mouse_mode == MOUSE_MODE_CONFINED) {
4363 XUndefineCursor(x11_display, E.value.x11_window);
4364 } else if (mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN) { // Or re-hide it.
4365 XDefineCursor(x11_display, E.value.x11_window, null_cursor);
4366 }
4367
4368 XGrabPointer(
4369 x11_display, E.value.x11_window, True,
4370 ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
4371 GrabModeAsync, GrabModeAsync, E.value.x11_window, None, CurrentTime);
4372 }
4373 }
4374#ifdef TOUCH_ENABLED
4375 // Grab touch devices to avoid OS gesture interference
4376 /*for (int i = 0; i < xi.touch_devices.size(); ++i) {
4377 XIGrabDevice(x11_display, xi.touch_devices[i], x11_window, CurrentTime, None, XIGrabModeAsync, XIGrabModeAsync, False, &xi.touch_event_mask);
4378 }*/
4379#endif
4380
4381 if (!app_focused) {
4382 if (OS::get_singleton()->get_main_loop()) {
4383 OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN);
4384 }
4385 app_focused = true;
4386 }
4387 } break;
4388
4389 case FocusOut: {
4390 DEBUG_LOG_X11("[%u] FocusOut window=%lu (%u), mode='%u' \n", frame, event.xfocus.window, window_id, event.xfocus.mode);
4391 WindowData &wd = windows[window_id];
4392 if (ime_window_event || (event.xfocus.detail == NotifyInferior)) {
4393 break;
4394 }
4395 if (wd.ime_active) {
4396 MutexLock mutex_lock(events_mutex);
4397 XUnsetICFocus(wd.xic);
4398 XUnmapWindow(x11_display, wd.x11_xim_window);
4399 wd.ime_active = false;
4400 im_text = String();
4401 im_selection = Vector2i();
4402 OS_Unix::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE);
4403 }
4404 wd.focused = false;
4405
4406 Input::get_singleton()->release_pressed_events();
4407 _send_window_event(wd, WINDOW_EVENT_FOCUS_OUT);
4408
4409 if (mouse_mode_grab) {
4410 for (const KeyValue<WindowID, WindowData> &E : windows) {
4411 //dear X11, I try, I really try, but you never work, you do whatever you want.
4412 if (mouse_mode == MOUSE_MODE_CAPTURED) {
4413 // Show the cursor if we're in captured mode so it doesn't look weird.
4414 XUndefineCursor(x11_display, E.value.x11_window);
4415 }
4416 }
4417 XUngrabPointer(x11_display, CurrentTime);
4418 }
4419#ifdef TOUCH_ENABLED
4420 // Ungrab touch devices so input works as usual while we are unfocused
4421 /*for (int i = 0; i < xi.touch_devices.size(); ++i) {
4422 XIUngrabDevice(x11_display, xi.touch_devices[i], CurrentTime);
4423 }*/
4424
4425 // Release every pointer to avoid sticky points
4426 for (const KeyValue<int, Vector2> &E : xi.state) {
4427 Ref<InputEventScreenTouch> st;
4428 st.instantiate();
4429 st->set_index(E.key);
4430 st->set_window_id(window_id);
4431 st->set_position(E.value);
4432 Input::get_singleton()->parse_input_event(st);
4433 }
4434 xi.state.clear();
4435#endif
4436 } break;
4437
4438 case ConfigureNotify: {
4439 DEBUG_LOG_X11("[%u] ConfigureNotify window=%lu (%u), event=%lu, above=%lu, override_redirect=%u \n", frame, event.xconfigure.window, window_id, event.xconfigure.event, event.xconfigure.above, event.xconfigure.override_redirect);
4440 if (event.xconfigure.window == windows[window_id].x11_xim_window) {
4441 break;
4442 }
4443
4444 const WindowData &wd = windows[window_id];
4445
4446 XWindowAttributes xwa;
4447 XSync(x11_display, False);
4448 XGetWindowAttributes(x11_display, wd.x11_window, &xwa);
4449
4450 // Set focus when menu window is re-used.
4451 // RevertToPointerRoot is used to make sure we don't lose all focus in case
4452 // a subwindow and its parent are both destroyed.
4453 if ((xwa.map_state == IsViewable) && !wd.no_focus && !wd.is_popup) {
4454 XSetInputFocus(x11_display, wd.x11_window, RevertToPointerRoot, CurrentTime);
4455 }
4456
4457 _window_changed(&event);
4458 } break;
4459
4460 case ButtonPress:
4461 case ButtonRelease: {
4462 if (ime_window_event || ignore_events) {
4463 break;
4464 }
4465 /* exit in case of a mouse button press */
4466 last_timestamp = event.xbutton.time;
4467 if (mouse_mode == MOUSE_MODE_CAPTURED) {
4468 event.xbutton.x = last_mouse_pos.x;
4469 event.xbutton.y = last_mouse_pos.y;
4470 }
4471
4472 Ref<InputEventMouseButton> mb;
4473 mb.instantiate();
4474
4475 mb->set_window_id(window_id);
4476 _get_key_modifier_state(event.xbutton.state, mb);
4477 mb->set_button_index((MouseButton)event.xbutton.button);
4478 if (mb->get_button_index() == MouseButton::RIGHT) {
4479 mb->set_button_index(MouseButton::MIDDLE);
4480 } else if (mb->get_button_index() == MouseButton::MIDDLE) {
4481 mb->set_button_index(MouseButton::RIGHT);
4482 }
4483 mb->set_button_mask(_get_mouse_button_state(mb->get_button_index(), event.xbutton.type));
4484 mb->set_position(Vector2(event.xbutton.x, event.xbutton.y));
4485 mb->set_global_position(mb->get_position());
4486
4487 mb->set_pressed((event.type == ButtonPress));
4488
4489 const WindowData &wd = windows[window_id];
4490
4491 if (event.type == ButtonPress) {
4492 DEBUG_LOG_X11("[%u] ButtonPress window=%lu (%u), button_index=%u \n", frame, event.xbutton.window, window_id, mb->get_button_index());
4493
4494 // Ensure window focus on click.
4495 // RevertToPointerRoot is used to make sure we don't lose all focus in case
4496 // a subwindow and its parent are both destroyed.
4497 if (!wd.no_focus && !wd.is_popup) {
4498 XSetInputFocus(x11_display, wd.x11_window, RevertToPointerRoot, CurrentTime);
4499 }
4500
4501 uint64_t diff = OS::get_singleton()->get_ticks_usec() / 1000 - last_click_ms;
4502
4503 if (mb->get_button_index() == last_click_button_index) {
4504 if (diff < 400 && Vector2(last_click_pos).distance_to(Vector2(event.xbutton.x, event.xbutton.y)) < 5) {
4505 last_click_ms = 0;
4506 last_click_pos = Point2i(-100, -100);
4507 last_click_button_index = MouseButton::NONE;
4508 mb->set_double_click(true);
4509 }
4510
4511 } else if (mb->get_button_index() < MouseButton::WHEEL_UP || mb->get_button_index() > MouseButton::WHEEL_RIGHT) {
4512 last_click_button_index = mb->get_button_index();
4513 }
4514
4515 if (!mb->is_double_click()) {
4516 last_click_ms += diff;
4517 last_click_pos = Point2i(event.xbutton.x, event.xbutton.y);
4518 }
4519 } else {
4520 DEBUG_LOG_X11("[%u] ButtonRelease window=%lu (%u), button_index=%u \n", frame, event.xbutton.window, window_id, mb->get_button_index());
4521
4522 WindowID window_id_other = INVALID_WINDOW_ID;
4523 Window wd_other_x11_window;
4524 if (wd.focused) {
4525 // Handle cases where an unfocused popup is open that needs to receive button-up events.
4526 WindowID popup_id = _get_focused_window_or_popup();
4527 if (popup_id != INVALID_WINDOW_ID && popup_id != window_id) {
4528 window_id_other = popup_id;
4529 wd_other_x11_window = windows[popup_id].x11_window;
4530 }
4531 } else {
4532 // Propagate the event to the focused window,
4533 // because it's received only on the topmost window.
4534 // Note: This is needed for drag & drop to work between windows,
4535 // because the engine expects events to keep being processed
4536 // on the same window dragging started.
4537 for (const KeyValue<WindowID, WindowData> &E : windows) {
4538 if (E.value.focused) {
4539 if (E.key != window_id) {
4540 window_id_other = E.key;
4541 wd_other_x11_window = E.value.x11_window;
4542 }
4543 break;
4544 }
4545 }
4546 }
4547
4548 if (window_id_other != INVALID_WINDOW_ID) {
4549 int x, y;
4550 Window child;
4551 XTranslateCoordinates(x11_display, wd.x11_window, wd_other_x11_window, event.xbutton.x, event.xbutton.y, &x, &y, &child);
4552
4553 mb->set_window_id(window_id_other);
4554 mb->set_position(Vector2(x, y));
4555 mb->set_global_position(mb->get_position());
4556 }
4557 }
4558
4559 Input::get_singleton()->parse_input_event(mb);
4560
4561 } break;
4562 case MotionNotify: {
4563 if (ime_window_event || ignore_events) {
4564 break;
4565 }
4566 // The X11 API requires filtering one-by-one through the motion
4567 // notify events, in order to figure out which event is the one
4568 // generated by warping the mouse pointer.
4569 WindowID focused_window_id = _get_focused_window_or_popup();
4570 if (!windows.has(focused_window_id)) {
4571 focused_window_id = MAIN_WINDOW_ID;
4572 }
4573
4574 while (true) {
4575 if (mouse_mode == MOUSE_MODE_CAPTURED && event.xmotion.x == windows[focused_window_id].size.width / 2 && event.xmotion.y == windows[focused_window_id].size.height / 2) {
4576 //this is likely the warp event since it was warped here
4577 center = Vector2(event.xmotion.x, event.xmotion.y);
4578 break;
4579 }
4580
4581 if (event_index + 1 < events.size()) {
4582 const XEvent &next_event = events[event_index + 1];
4583 if (next_event.type == MotionNotify) {
4584 ++event_index;
4585 event = next_event;
4586 } else {
4587 break;
4588 }
4589 } else {
4590 break;
4591 }
4592 }
4593
4594 last_timestamp = event.xmotion.time;
4595
4596 // Motion is also simple.
4597 // A little hack is in order
4598 // to be able to send relative motion events.
4599 Point2i pos(event.xmotion.x, event.xmotion.y);
4600
4601 // Avoidance of spurious mouse motion (see handling of touch)
4602 bool filter = false;
4603 // Adding some tolerance to match better Point2i to Vector2
4604 if (xi.state.size() && Vector2(pos).distance_squared_to(xi.mouse_pos_to_filter) < 2) {
4605 filter = true;
4606 }
4607 // Invalidate to avoid filtering a possible legitimate similar event coming later
4608 xi.mouse_pos_to_filter = Vector2(1e10, 1e10);
4609 if (filter) {
4610 break;
4611 }
4612
4613 const WindowData &wd = windows[window_id];
4614 bool focused = wd.focused;
4615
4616 if (mouse_mode == MOUSE_MODE_CAPTURED) {
4617 if (xi.relative_motion.x == 0 && xi.relative_motion.y == 0) {
4618 break;
4619 }
4620
4621 Point2i new_center = pos;
4622 pos = last_mouse_pos + xi.relative_motion;
4623 center = new_center;
4624 do_mouse_warp = focused; // warp the cursor if we're focused in
4625 }
4626
4627 if (!last_mouse_pos_valid) {
4628 last_mouse_pos = pos;
4629 last_mouse_pos_valid = true;
4630 }
4631
4632 // Hackish but relative mouse motion is already handled in the RawMotion event.
4633 // RawMotion does not provide the absolute mouse position (whereas MotionNotify does).
4634 // Therefore, RawMotion cannot be the authority on absolute mouse position.
4635 // RawMotion provides more precision than MotionNotify, which doesn't sense subpixel motion.
4636 // Therefore, MotionNotify cannot be the authority on relative mouse motion.
4637 // This means we need to take a combined approach...
4638 Point2i rel;
4639
4640 // Only use raw input if in capture mode. Otherwise use the classic behavior.
4641 if (mouse_mode == MOUSE_MODE_CAPTURED) {
4642 rel = xi.relative_motion;
4643 } else {
4644 rel = pos - last_mouse_pos;
4645 }
4646
4647 // Reset to prevent lingering motion
4648 xi.relative_motion.x = 0;
4649 xi.relative_motion.y = 0;
4650 if (mouse_mode == MOUSE_MODE_CAPTURED) {
4651 pos = Point2i(windows[focused_window_id].size.width / 2, windows[focused_window_id].size.height / 2);
4652 }
4653
4654 Ref<InputEventMouseMotion> mm;
4655 mm.instantiate();
4656
4657 mm->set_window_id(window_id);
4658 if (xi.pressure_supported) {
4659 mm->set_pressure(xi.pressure);
4660 } else {
4661 mm->set_pressure(bool(mouse_get_button_state().has_flag(MouseButtonMask::LEFT)) ? 1.0f : 0.0f);
4662 }
4663 mm->set_tilt(xi.tilt);
4664 mm->set_pen_inverted(xi.pen_inverted);
4665
4666 _get_key_modifier_state(event.xmotion.state, mm);
4667 mm->set_button_mask(mouse_get_button_state());
4668 mm->set_position(pos);
4669 mm->set_global_position(pos);
4670 mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity());
4671
4672 mm->set_relative(rel);
4673
4674 last_mouse_pos = pos;
4675
4676 // printf("rel: %d,%d\n", rel.x, rel.y );
4677 // Don't propagate the motion event unless we have focus
4678 // this is so that the relative motion doesn't get messed up
4679 // after we regain focus.
4680 if (focused) {
4681 Input::get_singleton()->parse_input_event(mm);
4682 } else {
4683 // Propagate the event to the focused window,
4684 // because it's received only on the topmost window.
4685 // Note: This is needed for drag & drop to work between windows,
4686 // because the engine expects events to keep being processed
4687 // on the same window dragging started.
4688 for (const KeyValue<WindowID, WindowData> &E : windows) {
4689 const WindowData &wd_other = E.value;
4690 if (wd_other.focused) {
4691 int x, y;
4692 Window child;
4693 XTranslateCoordinates(x11_display, wd.x11_window, wd_other.x11_window, event.xmotion.x, event.xmotion.y, &x, &y, &child);
4694
4695 Point2i pos_focused(x, y);
4696
4697 mm->set_window_id(E.key);
4698 mm->set_position(pos_focused);
4699 mm->set_global_position(pos_focused);
4700 mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity());
4701 Input::get_singleton()->parse_input_event(mm);
4702
4703 break;
4704 }
4705 }
4706 }
4707
4708 } break;
4709 case KeyPress:
4710 case KeyRelease: {
4711 if (ignore_events) {
4712 break;
4713 }
4714#ifdef DISPLAY_SERVER_X11_DEBUG_LOGS_ENABLED
4715 if (event.type == KeyPress) {
4716 DEBUG_LOG_X11("[%u] KeyPress window=%lu (%u), keycode=%u, time=%lu \n", frame, event.xkey.window, window_id, event.xkey.keycode, event.xkey.time);
4717 } else {
4718 DEBUG_LOG_X11("[%u] KeyRelease window=%lu (%u), keycode=%u, time=%lu \n", frame, event.xkey.window, window_id, event.xkey.keycode, event.xkey.time);
4719 }
4720#endif
4721 last_timestamp = event.xkey.time;
4722
4723 // key event is a little complex, so
4724 // it will be handled in its own function.
4725 _handle_key_event(window_id, &event.xkey, events, event_index);
4726 } break;
4727
4728 case SelectionNotify:
4729 if (ime_window_event) {
4730 break;
4731 }
4732 if (event.xselection.target == requested) {
4733 Property p = _read_property(x11_display, windows[window_id].x11_window, XInternAtom(x11_display, "PRIMARY", 0));
4734
4735 Vector<String> files = String((char *)p.data).split("\r\n", false);
4736 XFree(p.data);
4737 for (int i = 0; i < files.size(); i++) {
4738 files.write[i] = files[i].replace("file://", "").uri_decode();
4739 }
4740
4741 if (!windows[window_id].drop_files_callback.is_null()) {
4742 Variant v = files;
4743 Variant *vp = &v;
4744 Variant ret;
4745 Callable::CallError ce;
4746 windows[window_id].drop_files_callback.callp((const Variant **)&vp, 1, ret, ce);
4747 }
4748
4749 //Reply that all is well.
4750 XClientMessageEvent m;
4751 memset(&m, 0, sizeof(m));
4752 m.type = ClientMessage;
4753 m.display = x11_display;
4754 m.window = xdnd_source_window;
4755 m.message_type = xdnd_finished;
4756 m.format = 32;
4757 m.data.l[0] = windows[window_id].x11_window;
4758 m.data.l[1] = 1;
4759 m.data.l[2] = xdnd_action_copy; //We only ever copy.
4760
4761 XSendEvent(x11_display, xdnd_source_window, False, NoEventMask, (XEvent *)&m);
4762 }
4763 break;
4764
4765 case ClientMessage:
4766 if (ime_window_event) {
4767 break;
4768 }
4769 if ((unsigned int)event.xclient.data.l[0] == (unsigned int)wm_delete) {
4770 _send_window_event(windows[window_id], WINDOW_EVENT_CLOSE_REQUEST);
4771 }
4772
4773 else if ((unsigned int)event.xclient.message_type == (unsigned int)xdnd_enter) {
4774 //File(s) have been dragged over the window, check for supported target (text/uri-list)
4775 xdnd_version = (event.xclient.data.l[1] >> 24);
4776 Window source = event.xclient.data.l[0];
4777 bool more_than_3 = event.xclient.data.l[1] & 1;
4778 if (more_than_3) {
4779 Property p = _read_property(x11_display, source, XInternAtom(x11_display, "XdndTypeList", False));
4780 requested = pick_target_from_list(x11_display, (Atom *)p.data, p.nitems);
4781 XFree(p.data);
4782 } else {
4783 requested = pick_target_from_atoms(x11_display, event.xclient.data.l[2], event.xclient.data.l[3], event.xclient.data.l[4]);
4784 }
4785 } else if ((unsigned int)event.xclient.message_type == (unsigned int)xdnd_position) {
4786 //xdnd position event, reply with an XDND status message
4787 //just depending on type of data for now
4788 XClientMessageEvent m;
4789 memset(&m, 0, sizeof(m));
4790 m.type = ClientMessage;
4791 m.display = event.xclient.display;
4792 m.window = event.xclient.data.l[0];
4793 m.message_type = xdnd_status;
4794 m.format = 32;
4795 m.data.l[0] = windows[window_id].x11_window;
4796 m.data.l[1] = (requested != None);
4797 m.data.l[2] = 0; //empty rectangle
4798 m.data.l[3] = 0;
4799 m.data.l[4] = xdnd_action_copy;
4800
4801 XSendEvent(x11_display, event.xclient.data.l[0], False, NoEventMask, (XEvent *)&m);
4802 XFlush(x11_display);
4803 } else if ((unsigned int)event.xclient.message_type == (unsigned int)xdnd_drop) {
4804 if (requested != None) {
4805 xdnd_source_window = event.xclient.data.l[0];
4806 if (xdnd_version >= 1) {
4807 XConvertSelection(x11_display, xdnd_selection, requested, XInternAtom(x11_display, "PRIMARY", 0), windows[window_id].x11_window, event.xclient.data.l[2]);
4808 } else {
4809 XConvertSelection(x11_display, xdnd_selection, requested, XInternAtom(x11_display, "PRIMARY", 0), windows[window_id].x11_window, CurrentTime);
4810 }
4811 } else {
4812 //Reply that we're not interested.
4813 XClientMessageEvent m;
4814 memset(&m, 0, sizeof(m));
4815 m.type = ClientMessage;
4816 m.display = event.xclient.display;
4817 m.window = event.xclient.data.l[0];
4818 m.message_type = xdnd_finished;
4819 m.format = 32;
4820 m.data.l[0] = windows[window_id].x11_window;
4821 m.data.l[1] = 0;
4822 m.data.l[2] = None; //Failed.
4823 XSendEvent(x11_display, event.xclient.data.l[0], False, NoEventMask, (XEvent *)&m);
4824 }
4825 }
4826 break;
4827 default:
4828 break;
4829 }
4830 }
4831
4832 XFlush(x11_display);
4833
4834 if (do_mouse_warp) {
4835 XWarpPointer(x11_display, None, windows[MAIN_WINDOW_ID].x11_window,
4836 0, 0, 0, 0, (int)windows[MAIN_WINDOW_ID].size.width / 2, (int)windows[MAIN_WINDOW_ID].size.height / 2);
4837
4838 /*
4839 Window root, child;
4840 int root_x, root_y;
4841 int win_x, win_y;
4842 unsigned int mask;
4843 XQueryPointer( x11_display, x11_window, &root, &child, &root_x, &root_y, &win_x, &win_y, &mask );
4844
4845 printf("Root: %d,%d\n", root_x, root_y);
4846 printf("Win: %d,%d\n", win_x, win_y);
4847 */
4848 }
4849
4850 Input::get_singleton()->flush_buffered_events();
4851}
4852
4853void DisplayServerX11::release_rendering_thread() {
4854#if defined(GLES3_ENABLED)
4855 if (gl_manager) {
4856 gl_manager->release_current();
4857 }
4858#endif
4859}
4860
4861void DisplayServerX11::make_rendering_thread() {
4862#if defined(GLES3_ENABLED)
4863 if (gl_manager) {
4864 gl_manager->make_current();
4865 }
4866#endif
4867}
4868
4869void DisplayServerX11::swap_buffers() {
4870#if defined(GLES3_ENABLED)
4871 if (gl_manager) {
4872 gl_manager->swap_buffers();
4873 }
4874#endif
4875}
4876
4877void DisplayServerX11::_update_context(WindowData &wd) {
4878 XClassHint *classHint = XAllocClassHint();
4879
4880 if (classHint) {
4881 CharString name_str;
4882 switch (context) {
4883 case CONTEXT_EDITOR:
4884 name_str = "Godot_Editor";
4885 break;
4886 case CONTEXT_PROJECTMAN:
4887 name_str = "Godot_ProjectList";
4888 break;
4889 case CONTEXT_ENGINE:
4890 name_str = "Godot_Engine";
4891 break;
4892 }
4893
4894 CharString class_str;
4895 if (context == CONTEXT_ENGINE) {
4896 String config_name = GLOBAL_GET("application/config/name");
4897 if (config_name.length() == 0) {
4898 class_str = "Godot_Engine";
4899 } else {
4900 class_str = config_name.utf8();
4901 }
4902 } else {
4903 class_str = "Godot";
4904 }
4905
4906 classHint->res_class = class_str.ptrw();
4907 classHint->res_name = name_str.ptrw();
4908
4909 XSetClassHint(x11_display, wd.x11_window, classHint);
4910 XFree(classHint);
4911 }
4912}
4913
4914void DisplayServerX11::set_context(Context p_context) {
4915 _THREAD_SAFE_METHOD_
4916
4917 context = p_context;
4918
4919 for (KeyValue<WindowID, WindowData> &E : windows) {
4920 _update_context(E.value);
4921 }
4922}
4923
4924void DisplayServerX11::set_native_icon(const String &p_filename) {
4925 WARN_PRINT("Native icon not supported by this display server.");
4926}
4927
4928bool g_set_icon_error = false;
4929int set_icon_errorhandler(Display *dpy, XErrorEvent *ev) {
4930 g_set_icon_error = true;
4931 return 0;
4932}
4933
4934void DisplayServerX11::set_icon(const Ref<Image> &p_icon) {
4935 _THREAD_SAFE_METHOD_
4936
4937 WindowData &wd = windows[MAIN_WINDOW_ID];
4938
4939 int (*oldHandler)(Display *, XErrorEvent *) = XSetErrorHandler(&set_icon_errorhandler);
4940
4941 Atom net_wm_icon = XInternAtom(x11_display, "_NET_WM_ICON", False);
4942
4943 if (p_icon.is_valid()) {
4944 ERR_FAIL_COND(p_icon->get_width() <= 0 || p_icon->get_height() <= 0);
4945
4946 Ref<Image> img = p_icon->duplicate();
4947 img->convert(Image::FORMAT_RGBA8);
4948
4949 while (true) {
4950 int w = img->get_width();
4951 int h = img->get_height();
4952
4953 if (g_set_icon_error) {
4954 g_set_icon_error = false;
4955
4956 WARN_PRINT("Icon too large, attempting to resize icon.");
4957
4958 int new_width, new_height;
4959 if (w > h) {
4960 new_width = w / 2;
4961 new_height = h * new_width / w;
4962 } else {
4963 new_height = h / 2;
4964 new_width = w * new_height / h;
4965 }
4966
4967 w = new_width;
4968 h = new_height;
4969
4970 if (!w || !h) {
4971 WARN_PRINT("Unable to set icon.");
4972 break;
4973 }
4974
4975 img->resize(w, h, Image::INTERPOLATE_CUBIC);
4976 }
4977
4978 // We're using long to have wordsize (32Bit build -> 32 Bits, 64 Bit build -> 64 Bits
4979 Vector<long> pd;
4980
4981 pd.resize(2 + w * h);
4982
4983 pd.write[0] = w;
4984 pd.write[1] = h;
4985
4986 const uint8_t *r = img->get_data().ptr();
4987
4988 long *wr = &pd.write[2];
4989 uint8_t const *pr = r;
4990
4991 for (int i = 0; i < w * h; i++) {
4992 long v = 0;
4993 // A R G B
4994 v |= pr[3] << 24 | pr[0] << 16 | pr[1] << 8 | pr[2];
4995 *wr++ = v;
4996 pr += 4;
4997 }
4998
4999 if (net_wm_icon != None) {
5000 XChangeProperty(x11_display, wd.x11_window, net_wm_icon, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)pd.ptr(), pd.size());
5001 }
5002
5003 if (!g_set_icon_error) {
5004 break;
5005 }
5006 }
5007 } else {
5008 XDeleteProperty(x11_display, wd.x11_window, net_wm_icon);
5009 }
5010
5011 XFlush(x11_display);
5012 XSetErrorHandler(oldHandler);
5013}
5014
5015void DisplayServerX11::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window) {
5016 _THREAD_SAFE_METHOD_
5017#if defined(VULKAN_ENABLED)
5018 if (context_vulkan) {
5019 context_vulkan->set_vsync_mode(p_window, p_vsync_mode);
5020 }
5021#endif
5022
5023#if defined(GLES3_ENABLED)
5024 if (gl_manager) {
5025 gl_manager->set_use_vsync(p_vsync_mode != DisplayServer::VSYNC_DISABLED);
5026 }
5027#endif
5028}
5029
5030DisplayServer::VSyncMode DisplayServerX11::window_get_vsync_mode(WindowID p_window) const {
5031 _THREAD_SAFE_METHOD_
5032#if defined(VULKAN_ENABLED)
5033 if (context_vulkan) {
5034 return context_vulkan->get_vsync_mode(p_window);
5035 }
5036#endif
5037#if defined(GLES3_ENABLED)
5038 if (gl_manager) {
5039 return gl_manager->is_using_vsync() ? DisplayServer::VSYNC_ENABLED : DisplayServer::VSYNC_DISABLED;
5040 }
5041#endif
5042 return DisplayServer::VSYNC_ENABLED;
5043}
5044
5045Vector<String> DisplayServerX11::get_rendering_drivers_func() {
5046 Vector<String> drivers;
5047
5048#ifdef VULKAN_ENABLED
5049 drivers.push_back("vulkan");
5050#endif
5051#ifdef GLES3_ENABLED
5052 drivers.push_back("opengl3");
5053#endif
5054
5055 return drivers;
5056}
5057
5058DisplayServer *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, Error &r_error) {
5059 DisplayServer *ds = memnew(DisplayServerX11(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_position, p_resolution, p_screen, r_error));
5060 if (r_error != OK) {
5061 if (p_rendering_driver == "vulkan") {
5062 String executable_name = OS::get_singleton()->get_executable_path().get_file();
5063 OS::get_singleton()->alert(
5064 vformat("Your video card drivers seem not to support the required Vulkan version.\n\n"
5065 "If possible, consider updating your video card drivers or using the OpenGL 3 driver.\n\n"
5066 "You can enable the OpenGL 3 driver by starting the engine from the\n"
5067 "command line with the command:\n\n \"%s\" --rendering-driver opengl3\n\n"
5068 "If you recently updated your video card drivers, try rebooting.",
5069 executable_name),
5070 "Unable to initialize Vulkan video driver");
5071 } else {
5072 OS::get_singleton()->alert(
5073 "Your video card drivers seem not to support the required OpenGL 3.3 version.\n\n"
5074 "If possible, consider updating your video card drivers.\n\n"
5075 "If you recently updated your video card drivers, try rebooting.",
5076 "Unable to initialize OpenGL video driver");
5077 }
5078 }
5079 return ds;
5080}
5081
5082DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect) {
5083 //Create window
5084
5085 XVisualInfo visualInfo;
5086 bool vi_selected = false;
5087
5088#ifdef GLES3_ENABLED
5089 if (gl_manager) {
5090 Error err;
5091 visualInfo = gl_manager->get_vi(x11_display, err);
5092 ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't acquire visual info from display.");
5093 vi_selected = true;
5094 }
5095#endif
5096
5097 if (!vi_selected) {
5098 long visualMask = VisualScreenMask;
5099 int numberOfVisuals;
5100 XVisualInfo vInfoTemplate = {};
5101 vInfoTemplate.screen = DefaultScreen(x11_display);
5102 XVisualInfo *vi_list = XGetVisualInfo(x11_display, visualMask, &vInfoTemplate, &numberOfVisuals);
5103 ERR_FAIL_NULL_V(vi_list, INVALID_WINDOW_ID);
5104
5105 visualInfo = vi_list[0];
5106 if (OS::get_singleton()->is_layered_allowed()) {
5107 for (int i = 0; i < numberOfVisuals; i++) {
5108 XRenderPictFormat *pict_format = XRenderFindVisualFormat(x11_display, vi_list[i].visual);
5109 if (!pict_format) {
5110 continue;
5111 }
5112 visualInfo = vi_list[i];
5113 if (pict_format->direct.alphaMask > 0) {
5114 break;
5115 }
5116 }
5117 }
5118 XFree(vi_list);
5119 }
5120
5121 Colormap colormap = XCreateColormap(x11_display, RootWindow(x11_display, visualInfo.screen), visualInfo.visual, AllocNone);
5122
5123 XSetWindowAttributes windowAttributes = {};
5124 windowAttributes.colormap = colormap;
5125 windowAttributes.background_pixel = 0xFFFFFFFF;
5126 windowAttributes.border_pixel = 0;
5127 windowAttributes.event_mask = KeyPressMask | KeyReleaseMask | StructureNotifyMask | ExposureMask;
5128
5129 unsigned long valuemask = CWBorderPixel | CWColormap | CWEventMask;
5130
5131 if (OS::get_singleton()->is_layered_allowed()) {
5132 windowAttributes.background_pixmap = None;
5133 windowAttributes.background_pixel = 0;
5134 windowAttributes.border_pixmap = None;
5135 valuemask |= CWBackPixel;
5136 }
5137
5138 WindowID id = window_id_counter++;
5139 WindowData &wd = windows[id];
5140
5141 if (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) {
5142 wd.no_focus = true;
5143 }
5144
5145 if (p_flags & WINDOW_FLAG_POPUP_BIT) {
5146 wd.is_popup = true;
5147 }
5148
5149 // Setup for menu subwindows:
5150 // - override_redirect forces the WM not to interfere with the window, to avoid delays due to
5151 // handling decorations and placement.
5152 // On the other hand, focus changes need to be handled manually when this is set.
5153 // - save_under is a hint for the WM to keep the content of windows behind to avoid repaint.
5154 if (wd.no_focus) {
5155 windowAttributes.override_redirect = True;
5156 windowAttributes.save_under = True;
5157 valuemask |= CWOverrideRedirect | CWSaveUnder;
5158 }
5159
5160 int rq_screen = get_screen_from_rect(p_rect);
5161 if (rq_screen < 0) {
5162 rq_screen = get_primary_screen(); // Requested window rect is outside any screen bounds.
5163 }
5164
5165 Rect2i win_rect = p_rect;
5166 if (p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) {
5167 Rect2i screen_rect = Rect2i(screen_get_position(rq_screen), screen_get_size(rq_screen));
5168
5169 win_rect = screen_rect;
5170 } else {
5171 Rect2i srect = screen_get_usable_rect(rq_screen);
5172 Point2i wpos = p_rect.position;
5173 wpos.x = CLAMP(wpos.x, srect.position.x, srect.position.x + srect.size.width - p_rect.size.width / 3);
5174 wpos.y = CLAMP(wpos.y, srect.position.y, srect.position.y + srect.size.height - p_rect.size.height / 3);
5175
5176 win_rect.position = wpos;
5177 }
5178
5179 // Position and size hints are set from these values before they are updated to the actual
5180 // window size, so we need to initialize them here.
5181 wd.position = win_rect.position;
5182 wd.size = win_rect.size;
5183
5184 {
5185 wd.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);
5186
5187 wd.parent = RootWindow(x11_display, visualInfo.screen);
5188 XSetWindowAttributes window_attributes_ime = {};
5189 window_attributes_ime.event_mask = KeyPressMask | KeyReleaseMask | StructureNotifyMask | ExposureMask;
5190
5191 wd.x11_xim_window = XCreateWindow(x11_display, wd.x11_window, 0, 0, 1, 1, 0, CopyFromParent, InputOnly, CopyFromParent, CWEventMask, &window_attributes_ime);
5192#ifdef XKB_ENABLED
5193 if (dead_tbl && xkb_loaded_v05p) {
5194 wd.xkb_state = xkb_compose_state_new(dead_tbl, XKB_COMPOSE_STATE_NO_FLAGS);
5195 }
5196#endif
5197 // Enable receiving notification when the window is initialized (MapNotify)
5198 // so the focus can be set at the right time.
5199 if (!wd.no_focus && !wd.is_popup) {
5200 XSelectInput(x11_display, wd.x11_window, StructureNotifyMask);
5201 }
5202
5203 //associate PID
5204 // make PID known to X11
5205 {
5206 const long pid = OS::get_singleton()->get_process_id();
5207 Atom net_wm_pid = XInternAtom(x11_display, "_NET_WM_PID", False);
5208 if (net_wm_pid != None) {
5209 XChangeProperty(x11_display, wd.x11_window, net_wm_pid, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&pid, 1);
5210 }
5211 }
5212
5213 long im_event_mask = 0;
5214
5215 {
5216 XIEventMask all_event_mask;
5217 XSetWindowAttributes new_attr;
5218
5219 new_attr.event_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask |
5220 ButtonReleaseMask | EnterWindowMask |
5221 LeaveWindowMask | PointerMotionMask |
5222 Button1MotionMask |
5223 Button2MotionMask | Button3MotionMask |
5224 Button4MotionMask | Button5MotionMask |
5225 ButtonMotionMask | KeymapStateMask |
5226 ExposureMask | VisibilityChangeMask |
5227 StructureNotifyMask |
5228 SubstructureNotifyMask | SubstructureRedirectMask |
5229 FocusChangeMask | PropertyChangeMask |
5230 ColormapChangeMask | OwnerGrabButtonMask |
5231 im_event_mask;
5232
5233 XChangeWindowAttributes(x11_display, wd.x11_window, CWEventMask, &new_attr);
5234
5235 static unsigned char all_mask_data[XIMaskLen(XI_LASTEVENT)] = {};
5236
5237 all_event_mask.deviceid = XIAllDevices;
5238 all_event_mask.mask_len = sizeof(all_mask_data);
5239 all_event_mask.mask = all_mask_data;
5240
5241 XISetMask(all_event_mask.mask, XI_HierarchyChanged);
5242
5243#ifdef TOUCH_ENABLED
5244 if (xi.touch_devices.size()) {
5245 XISetMask(all_event_mask.mask, XI_TouchBegin);
5246 XISetMask(all_event_mask.mask, XI_TouchUpdate);
5247 XISetMask(all_event_mask.mask, XI_TouchEnd);
5248 XISetMask(all_event_mask.mask, XI_TouchOwnership);
5249 }
5250#endif
5251
5252 XISelectEvents(x11_display, wd.x11_window, &all_event_mask, 1);
5253 }
5254
5255 /* set the titlebar name */
5256 XStoreName(x11_display, wd.x11_window, "Godot");
5257 XSetWMProtocols(x11_display, wd.x11_window, &wm_delete, 1);
5258 if (xdnd_aware != None) {
5259 XChangeProperty(x11_display, wd.x11_window, xdnd_aware, XA_ATOM, 32, PropModeReplace, (unsigned char *)&xdnd_version, 1);
5260 }
5261
5262 if (xim && xim_style) {
5263 // Block events polling while changing input focus
5264 // because it triggers some event polling internally.
5265 MutexLock mutex_lock(events_mutex);
5266
5267 // Force on-the-spot for the over-the-spot style.
5268 if ((xim_style & XIMPreeditPosition) != 0) {
5269 xim_style &= ~XIMPreeditPosition;
5270 xim_style |= XIMPreeditCallbacks;
5271 }
5272 if ((xim_style & XIMPreeditCallbacks) != 0) {
5273 ::XIMCallback preedit_start_callback;
5274 preedit_start_callback.client_data = (::XPointer)(this);
5275 preedit_start_callback.callback = (::XIMProc)(void *)(_xim_preedit_start_callback);
5276
5277 ::XIMCallback preedit_done_callback;
5278 preedit_done_callback.client_data = (::XPointer)(this);
5279 preedit_done_callback.callback = (::XIMProc)(_xim_preedit_done_callback);
5280
5281 ::XIMCallback preedit_draw_callback;
5282 preedit_draw_callback.client_data = (::XPointer)(this);
5283 preedit_draw_callback.callback = (::XIMProc)(_xim_preedit_draw_callback);
5284
5285 ::XIMCallback preedit_caret_callback;
5286 preedit_caret_callback.client_data = (::XPointer)(this);
5287 preedit_caret_callback.callback = (::XIMProc)(_xim_preedit_caret_callback);
5288
5289 ::XVaNestedList preedit_attributes = XVaCreateNestedList(0,
5290 XNPreeditStartCallback, &preedit_start_callback,
5291 XNPreeditDoneCallback, &preedit_done_callback,
5292 XNPreeditDrawCallback, &preedit_draw_callback,
5293 XNPreeditCaretCallback, &preedit_caret_callback,
5294 (char *)nullptr);
5295
5296 wd.xic = XCreateIC(xim,
5297 XNInputStyle, xim_style,
5298 XNClientWindow, wd.x11_xim_window,
5299 XNFocusWindow, wd.x11_xim_window,
5300 XNPreeditAttributes, preedit_attributes,
5301 (char *)nullptr);
5302 XFree(preedit_attributes);
5303 } else {
5304 wd.xic = XCreateIC(xim,
5305 XNInputStyle, xim_style,
5306 XNClientWindow, wd.x11_xim_window,
5307 XNFocusWindow, wd.x11_xim_window,
5308 (char *)nullptr);
5309 }
5310
5311 if (XGetICValues(wd.xic, XNFilterEvents, &im_event_mask, nullptr) != nullptr) {
5312 WARN_PRINT("XGetICValues couldn't obtain XNFilterEvents value");
5313 XDestroyIC(wd.xic);
5314 wd.xic = nullptr;
5315 }
5316 if (wd.xic) {
5317 XUnsetICFocus(wd.xic);
5318 } else {
5319 WARN_PRINT("XCreateIC couldn't create wd.xic");
5320 }
5321 } else {
5322 wd.xic = nullptr;
5323 WARN_PRINT("XCreateIC couldn't create wd.xic");
5324 }
5325
5326 _update_context(wd);
5327
5328 if (p_flags & WINDOW_FLAG_BORDERLESS_BIT) {
5329 Hints hints;
5330 Atom property;
5331 hints.flags = 2;
5332 hints.decorations = 0;
5333 property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True);
5334 if (property != None) {
5335 XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5);
5336 }
5337 }
5338
5339 if (wd.is_popup || wd.no_focus) {
5340 // Set Utility type to disable fade animations.
5341 Atom type_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE_UTILITY", False);
5342 Atom wt_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE", False);
5343 if (wt_atom != None && type_atom != None) {
5344 XChangeProperty(x11_display, wd.x11_window, wt_atom, XA_ATOM, 32, PropModeReplace, (unsigned char *)&type_atom, 1);
5345 }
5346 } else {
5347 Atom type_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE_NORMAL", False);
5348 Atom wt_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE", False);
5349
5350 if (wt_atom != None && type_atom != None) {
5351 XChangeProperty(x11_display, wd.x11_window, wt_atom, XA_ATOM, 32, PropModeReplace, (unsigned char *)&type_atom, 1);
5352 }
5353 }
5354
5355 _update_size_hints(id);
5356
5357#if defined(VULKAN_ENABLED)
5358 if (context_vulkan) {
5359 Error err = context_vulkan->window_create(id, p_vsync_mode, wd.x11_window, x11_display, win_rect.size.width, win_rect.size.height);
5360 ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't create a Vulkan window");
5361 }
5362#endif
5363#ifdef GLES3_ENABLED
5364 if (gl_manager) {
5365 Error err = gl_manager->window_create(id, wd.x11_window, x11_display, win_rect.size.width, win_rect.size.height);
5366 ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't create an OpenGL window");
5367 window_set_vsync_mode(p_vsync_mode, id);
5368 }
5369#endif
5370
5371 //set_class_hint(x11_display, wd.x11_window);
5372 XFlush(x11_display);
5373
5374 XSync(x11_display, False);
5375 //XSetErrorHandler(oldHandler);
5376 }
5377
5378 window_set_mode(p_mode, id);
5379
5380 //sync size
5381 {
5382 XWindowAttributes xwa;
5383
5384 XSync(x11_display, False);
5385 XGetWindowAttributes(x11_display, wd.x11_window, &xwa);
5386
5387 wd.position.x = xwa.x;
5388 wd.position.y = xwa.y;
5389 wd.size.width = xwa.width;
5390 wd.size.height = xwa.height;
5391 }
5392
5393 //set cursor
5394 if (cursors[current_cursor] != None) {
5395 XDefineCursor(x11_display, wd.x11_window, cursors[current_cursor]);
5396 }
5397
5398 return id;
5399}
5400
5401static bool _is_xim_style_supported(const ::XIMStyle &p_style) {
5402 const ::XIMStyle supported_preedit = XIMPreeditCallbacks | XIMPreeditPosition | XIMPreeditNothing | XIMPreeditNone;
5403 const ::XIMStyle supported_status = XIMStatusNothing | XIMStatusNone;
5404
5405 // Check preedit style is supported
5406 if ((p_style & supported_preedit) == 0) {
5407 return false;
5408 }
5409
5410 // Check status style is supported
5411 if ((p_style & supported_status) == 0) {
5412 return false;
5413 }
5414
5415 return true;
5416}
5417
5418static ::XIMStyle _get_best_xim_style(const ::XIMStyle &p_style_a, const ::XIMStyle &p_style_b) {
5419 if (p_style_a == 0) {
5420 return p_style_b;
5421 }
5422 if (p_style_b == 0) {
5423 return p_style_a;
5424 }
5425
5426 const ::XIMStyle preedit = XIMPreeditArea | XIMPreeditCallbacks | XIMPreeditPosition | XIMPreeditNothing | XIMPreeditNone;
5427 const ::XIMStyle status = XIMStatusArea | XIMStatusCallbacks | XIMStatusNothing | XIMStatusNone;
5428
5429 ::XIMStyle a = p_style_a & preedit;
5430 ::XIMStyle b = p_style_b & preedit;
5431 if (a != b) {
5432 // Compare preedit styles.
5433 if ((a | b) & XIMPreeditCallbacks) {
5434 return a == XIMPreeditCallbacks ? p_style_a : p_style_b;
5435 } else if ((a | b) & XIMPreeditPosition) {
5436 return a == XIMPreeditPosition ? p_style_a : p_style_b;
5437 } else if ((a | b) & XIMPreeditArea) {
5438 return a == XIMPreeditArea ? p_style_a : p_style_b;
5439 } else if ((a | b) & XIMPreeditNothing) {
5440 return a == XIMPreeditNothing ? p_style_a : p_style_b;
5441 }
5442 } else {
5443 // Preedit styles are the same, compare status styles.
5444 a = p_style_a & status;
5445 b = p_style_b & status;
5446
5447 if ((a | b) & XIMStatusCallbacks) {
5448 return a == XIMStatusCallbacks ? p_style_a : p_style_b;
5449 } else if ((a | b) & XIMStatusArea) {
5450 return a == XIMStatusArea ? p_style_a : p_style_b;
5451 } else if ((a | b) & XIMStatusNothing) {
5452 return a == XIMStatusNothing ? p_style_a : p_style_b;
5453 }
5454 }
5455 return p_style_a;
5456}
5457
5458DisplayServerX11::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, Error &r_error) {
5459 KeyMappingX11::initialize();
5460
5461#ifdef SOWRAP_ENABLED
5462#ifdef DEBUG_ENABLED
5463 int dylibloader_verbose = 1;
5464#else
5465 int dylibloader_verbose = 0;
5466#endif
5467 if (initialize_xlib(dylibloader_verbose) != 0) {
5468 r_error = ERR_UNAVAILABLE;
5469 ERR_FAIL_MSG("Can't load Xlib dynamically.");
5470 }
5471
5472 if (initialize_xcursor(dylibloader_verbose) != 0) {
5473 r_error = ERR_UNAVAILABLE;
5474 ERR_FAIL_MSG("Can't load XCursor dynamically.");
5475 }
5476#ifdef XKB_ENABLED
5477 bool xkb_loaded = (initialize_xkbcommon(dylibloader_verbose) == 0);
5478 xkb_loaded_v05p = xkb_loaded;
5479 if (!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) {
5480 xkb_loaded_v05p = false;
5481 print_verbose("Detected XKBcommon library version older than 0.5, dead key composition and Unicode key labels disabled.");
5482 }
5483 xkb_loaded_v08p = xkb_loaded;
5484 if (!xkb_keysym_to_utf32 || !xkb_keysym_to_upper) {
5485 xkb_loaded_v08p = false;
5486 print_verbose("Detected XKBcommon library version older than 0.8, Unicode key labels disabled.");
5487 }
5488#endif
5489 if (initialize_xext(dylibloader_verbose) != 0) {
5490 r_error = ERR_UNAVAILABLE;
5491 ERR_FAIL_MSG("Can't load Xext dynamically.");
5492 }
5493
5494 if (initialize_xinerama(dylibloader_verbose) != 0) {
5495 xinerama_ext_ok = false;
5496 }
5497
5498 if (initialize_xrandr(dylibloader_verbose) != 0) {
5499 xrandr_ext_ok = false;
5500 }
5501
5502 if (initialize_xrender(dylibloader_verbose) != 0) {
5503 r_error = ERR_UNAVAILABLE;
5504 ERR_FAIL_MSG("Can't load Xrender dynamically.");
5505 }
5506
5507 if (initialize_xinput2(dylibloader_verbose) != 0) {
5508 r_error = ERR_UNAVAILABLE;
5509 ERR_FAIL_MSG("Can't load Xinput2 dynamically.");
5510 }
5511#else
5512#ifdef XKB_ENABLED
5513 bool xkb_loaded = true;
5514 xkb_loaded_v05p = true;
5515 xkb_loaded_v08p = true;
5516#endif
5517#endif
5518
5519#ifdef XKB_ENABLED
5520 if (xkb_loaded) {
5521 xkb_ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
5522 if (xkb_ctx) {
5523 const char *locale = getenv("LC_ALL");
5524 if (!locale || !*locale) {
5525 locale = getenv("LC_CTYPE");
5526 }
5527 if (!locale || !*locale) {
5528 locale = getenv("LANG");
5529 }
5530 if (!locale || !*locale) {
5531 locale = "C";
5532 }
5533 dead_tbl = xkb_compose_table_new_from_locale(xkb_ctx, locale, XKB_COMPOSE_COMPILE_NO_FLAGS);
5534 }
5535 }
5536#endif
5537
5538 Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events);
5539
5540 r_error = OK;
5541
5542#ifdef SOWRAP_ENABLED
5543 {
5544 if (!XcursorImageCreate || !XcursorImageLoadCursor || !XcursorImageDestroy || !XcursorGetDefaultSize || !XcursorGetTheme || !XcursorLibraryLoadImage) {
5545 // There's no API to check version, check if functions are available instead.
5546 ERR_PRINT("Unsupported Xcursor library version.");
5547 r_error = ERR_UNAVAILABLE;
5548 return;
5549 }
5550 }
5551#endif
5552
5553 for (int i = 0; i < CURSOR_MAX; i++) {
5554 cursors[i] = None;
5555 cursor_img[i] = nullptr;
5556 }
5557
5558 XInitThreads(); //always use threads
5559
5560 /** XLIB INITIALIZATION **/
5561 x11_display = XOpenDisplay(nullptr);
5562
5563 if (!x11_display) {
5564 ERR_PRINT("X11 Display is not available");
5565 r_error = ERR_UNAVAILABLE;
5566 return;
5567 }
5568
5569 if (xshaped_ext_ok) {
5570 int version_major = 0;
5571 int version_minor = 0;
5572 int rc = XShapeQueryVersion(x11_display, &version_major, &version_minor);
5573 print_verbose(vformat("Xshape %d.%d detected.", version_major, version_minor));
5574 if (rc != 1 || version_major < 1) {
5575 xshaped_ext_ok = false;
5576 print_verbose("Unsupported Xshape library version.");
5577 }
5578 }
5579
5580 if (xinerama_ext_ok) {
5581 int version_major = 0;
5582 int version_minor = 0;
5583 int rc = XineramaQueryVersion(x11_display, &version_major, &version_minor);
5584 print_verbose(vformat("Xinerama %d.%d detected.", version_major, version_minor));
5585 if (rc != 1 || version_major < 1) {
5586 xinerama_ext_ok = false;
5587 print_verbose("Unsupported Xinerama library version.");
5588 }
5589 }
5590
5591 if (xrandr_ext_ok) {
5592 int version_major = 0;
5593 int version_minor = 0;
5594 int rc = XRRQueryVersion(x11_display, &version_major, &version_minor);
5595 print_verbose(vformat("Xrandr %d.%d detected.", version_major, version_minor));
5596 if (rc != 1 || (version_major == 1 && version_minor < 3) || (version_major < 1)) {
5597 xrandr_ext_ok = false;
5598 print_verbose("Unsupported Xrandr library version.");
5599 }
5600 }
5601
5602 {
5603 int version_major = 0;
5604 int version_minor = 0;
5605 int rc = XRenderQueryVersion(x11_display, &version_major, &version_minor);
5606 print_verbose(vformat("Xrender %d.%d detected.", version_major, version_minor));
5607 if (rc != 1 || (version_major == 0 && version_minor < 11)) {
5608 ERR_PRINT("Unsupported Xrender library version.");
5609 r_error = ERR_UNAVAILABLE;
5610 XCloseDisplay(x11_display);
5611 return;
5612 }
5613 }
5614
5615 {
5616 int version_major = 2; // Report 2.2 as supported by engine, but should work with 2.1 or 2.0 library as well.
5617 int version_minor = 2;
5618 int rc = XIQueryVersion(x11_display, &version_major, &version_minor);
5619 print_verbose(vformat("Xinput %d.%d detected.", version_major, version_minor));
5620 if (rc != Success || (version_major < 2)) {
5621 ERR_PRINT("Unsupported Xinput2 library version.");
5622 r_error = ERR_UNAVAILABLE;
5623 XCloseDisplay(x11_display);
5624 return;
5625 }
5626 }
5627
5628 char *modifiers = nullptr;
5629 Bool xkb_dar = False;
5630 XAutoRepeatOn(x11_display);
5631 xkb_dar = XkbSetDetectableAutoRepeat(x11_display, True, nullptr);
5632
5633 // Try to support IME if detectable auto-repeat is supported
5634 if (xkb_dar == True) {
5635#ifdef X_HAVE_UTF8_STRING
5636 // Xutf8LookupString will be used later instead of XmbLookupString before
5637 // the multibyte sequences can be converted to unicode string.
5638 modifiers = XSetLocaleModifiers("");
5639#endif
5640 }
5641
5642 if (modifiers == nullptr) {
5643 if (OS::get_singleton()->is_stdout_verbose()) {
5644 WARN_PRINT("IME is disabled");
5645 }
5646 XSetLocaleModifiers("@im=none");
5647 WARN_PRINT("Error setting locale modifiers");
5648 }
5649
5650 const char *err;
5651 int xrandr_major = 0;
5652 int xrandr_minor = 0;
5653 int event_base, error_base;
5654 xrandr_ext_ok = XRRQueryExtension(x11_display, &event_base, &error_base);
5655 xrandr_handle = dlopen("libXrandr.so.2", RTLD_LAZY);
5656 if (!xrandr_handle) {
5657 err = dlerror();
5658 // For some arcane reason, NetBSD now ships libXrandr.so.3 while the rest of the world has libXrandr.so.2...
5659 // In case this happens for other X11 platforms in the future, let's give it a try too before failing.
5660 xrandr_handle = dlopen("libXrandr.so.3", RTLD_LAZY);
5661 if (!xrandr_handle) {
5662 fprintf(stderr, "could not load libXrandr.so.2, Error: %s\n", err);
5663 }
5664 }
5665
5666 if (xrandr_handle) {
5667 XRRQueryVersion(x11_display, &xrandr_major, &xrandr_minor);
5668 if (((xrandr_major << 8) | xrandr_minor) >= 0x0105) {
5669 xrr_get_monitors = (xrr_get_monitors_t)dlsym(xrandr_handle, "XRRGetMonitors");
5670 if (!xrr_get_monitors) {
5671 err = dlerror();
5672 fprintf(stderr, "could not find symbol XRRGetMonitors\nError: %s\n", err);
5673 } else {
5674 xrr_free_monitors = (xrr_free_monitors_t)dlsym(xrandr_handle, "XRRFreeMonitors");
5675 if (!xrr_free_monitors) {
5676 err = dlerror();
5677 fprintf(stderr, "could not find XRRFreeMonitors\nError: %s\n", err);
5678 xrr_get_monitors = nullptr;
5679 }
5680 }
5681 }
5682 }
5683
5684 if (!_refresh_device_info()) {
5685 OS::get_singleton()->alert("Your system does not support XInput 2.\n"
5686 "Please upgrade your distribution.",
5687 "Unable to initialize XInput");
5688 r_error = ERR_UNAVAILABLE;
5689 return;
5690 }
5691
5692 xim = XOpenIM(x11_display, nullptr, nullptr, nullptr);
5693
5694 if (xim == nullptr) {
5695 WARN_PRINT("XOpenIM failed");
5696 xim_style = 0L;
5697 } else {
5698 ::XIMCallback im_destroy_callback;
5699 im_destroy_callback.client_data = (::XPointer)(this);
5700 im_destroy_callback.callback = (::XIMProc)(_xim_destroy_callback);
5701 if (XSetIMValues(xim, XNDestroyCallback, &im_destroy_callback,
5702 nullptr) != nullptr) {
5703 WARN_PRINT("Error setting XIM destroy callback");
5704 }
5705
5706 ::XIMStyles *xim_styles = nullptr;
5707 xim_style = 0L;
5708 char *imvalret = XGetIMValues(xim, XNQueryInputStyle, &xim_styles, nullptr);
5709 if (imvalret != nullptr || xim_styles == nullptr) {
5710 fprintf(stderr, "Input method doesn't support any styles\n");
5711 }
5712
5713 if (xim_styles) {
5714 xim_style = 0L;
5715 for (int i = 0; i < xim_styles->count_styles; i++) {
5716 const ::XIMStyle &style = xim_styles->supported_styles[i];
5717
5718 if (!_is_xim_style_supported(style)) {
5719 continue;
5720 }
5721
5722 xim_style = _get_best_xim_style(xim_style, style);
5723 }
5724
5725 XFree(xim_styles);
5726 }
5727 XFree(imvalret);
5728 }
5729
5730 /* Atom internment */
5731 wm_delete = XInternAtom(x11_display, "WM_DELETE_WINDOW", true);
5732 // Set Xdnd (drag & drop) support.
5733 xdnd_aware = XInternAtom(x11_display, "XdndAware", False);
5734 xdnd_enter = XInternAtom(x11_display, "XdndEnter", False);
5735 xdnd_position = XInternAtom(x11_display, "XdndPosition", False);
5736 xdnd_status = XInternAtom(x11_display, "XdndStatus", False);
5737 xdnd_action_copy = XInternAtom(x11_display, "XdndActionCopy", False);
5738 xdnd_drop = XInternAtom(x11_display, "XdndDrop", False);
5739 xdnd_finished = XInternAtom(x11_display, "XdndFinished", False);
5740 xdnd_selection = XInternAtom(x11_display, "XdndSelection", False);
5741
5742#ifdef SPEECHD_ENABLED
5743 // Init TTS
5744 bool tts_enabled = GLOBAL_GET("audio/general/text_to_speech");
5745 if (tts_enabled) {
5746 tts = memnew(TTS_Linux);
5747 }
5748#endif
5749
5750 //!!!!!!!!!!!!!!!!!!!!!!!!!!
5751 //TODO - do Vulkan and OpenGL support checks, driver selection and fallback
5752 rendering_driver = p_rendering_driver;
5753
5754 bool driver_found = false;
5755#if defined(VULKAN_ENABLED)
5756 if (rendering_driver == "vulkan") {
5757 context_vulkan = memnew(VulkanContextX11);
5758 if (context_vulkan->initialize() != OK) {
5759 memdelete(context_vulkan);
5760 context_vulkan = nullptr;
5761 r_error = ERR_CANT_CREATE;
5762 ERR_FAIL_MSG("Could not initialize Vulkan");
5763 }
5764 driver_found = true;
5765 }
5766#endif
5767 // Initialize context and rendering device.
5768#if defined(GLES3_ENABLED)
5769 if (rendering_driver == "opengl3") {
5770 if (getenv("DRI_PRIME") == nullptr) {
5771 int use_prime = -1;
5772
5773 if (getenv("PRIMUS_DISPLAY") ||
5774 getenv("PRIMUS_libGLd") ||
5775 getenv("PRIMUS_libGLa") ||
5776 getenv("PRIMUS_libGL") ||
5777 getenv("PRIMUS_LOAD_GLOBAL") ||
5778 getenv("BUMBLEBEE_SOCKET")) {
5779 print_verbose("Optirun/primusrun detected. Skipping GPU detection");
5780 use_prime = 0;
5781 }
5782
5783 // Some tools use fake libGL libraries and have them override the real one using
5784 // LD_LIBRARY_PATH, so we skip them. *But* Steam also sets LD_LIBRARY_PATH for its
5785 // runtime and includes system `/lib` and `/lib64`... so ignore Steam.
5786 if (use_prime == -1 && getenv("LD_LIBRARY_PATH") && !getenv("STEAM_RUNTIME_LIBRARY_PATH")) {
5787 String ld_library_path(getenv("LD_LIBRARY_PATH"));
5788 Vector<String> libraries = ld_library_path.split(":");
5789
5790 for (int i = 0; i < libraries.size(); ++i) {
5791 if (FileAccess::exists(libraries[i] + "/libGL.so.1") ||
5792 FileAccess::exists(libraries[i] + "/libGL.so")) {
5793 print_verbose("Custom libGL override detected. Skipping GPU detection");
5794 use_prime = 0;
5795 }
5796 }
5797 }
5798
5799 if (use_prime == -1) {
5800 print_verbose("Detecting GPUs, set DRI_PRIME in the environment to override GPU detection logic.");
5801 use_prime = detect_prime();
5802 }
5803
5804 if (use_prime) {
5805 print_line("Found discrete GPU, setting DRI_PRIME=1 to use it.");
5806 print_line("Note: Set DRI_PRIME=0 in the environment to disable Godot from using the discrete GPU.");
5807 setenv("DRI_PRIME", "1", 1);
5808 }
5809 }
5810
5811 GLManager_X11::ContextType opengl_api_type = GLManager_X11::GLES_3_0_COMPATIBLE;
5812
5813 gl_manager = memnew(GLManager_X11(p_resolution, opengl_api_type));
5814
5815 if (gl_manager->initialize(x11_display) != OK) {
5816 memdelete(gl_manager);
5817 gl_manager = nullptr;
5818 r_error = ERR_UNAVAILABLE;
5819 return;
5820 }
5821 driver_found = true;
5822
5823 if (true) {
5824 RasterizerGLES3::make_current();
5825 } else {
5826 memdelete(gl_manager);
5827 gl_manager = nullptr;
5828 r_error = ERR_UNAVAILABLE;
5829 return;
5830 }
5831 }
5832#endif
5833 if (!driver_found) {
5834 r_error = ERR_UNAVAILABLE;
5835 ERR_FAIL_MSG("Video driver not found");
5836 }
5837
5838 Point2i window_position;
5839 if (p_position != nullptr) {
5840 window_position = *p_position;
5841 } else {
5842 if (p_screen == SCREEN_OF_MAIN_WINDOW) {
5843 p_screen = SCREEN_PRIMARY;
5844 }
5845 window_position = screen_get_position(p_screen) + (screen_get_size(p_screen) - p_resolution) / 2;
5846 }
5847
5848 WindowID main_window = _create_window(p_mode, p_vsync_mode, p_flags, Rect2i(window_position, p_resolution));
5849 if (main_window == INVALID_WINDOW_ID) {
5850 r_error = ERR_CANT_CREATE;
5851 return;
5852 }
5853 for (int i = 0; i < WINDOW_FLAG_MAX; i++) {
5854 if (p_flags & (1 << i)) {
5855 window_set_flag(WindowFlags(i), true, main_window);
5856 }
5857 }
5858 show_window(main_window);
5859
5860#if defined(VULKAN_ENABLED)
5861 if (rendering_driver == "vulkan") {
5862 //temporary
5863 rendering_device_vulkan = memnew(RenderingDeviceVulkan);
5864 rendering_device_vulkan->initialize(context_vulkan);
5865
5866 RendererCompositorRD::make_current();
5867 }
5868#endif
5869
5870 {
5871 //set all event master mask
5872 XIEventMask all_master_event_mask;
5873 static unsigned char all_master_mask_data[XIMaskLen(XI_LASTEVENT)] = {};
5874 all_master_event_mask.deviceid = XIAllMasterDevices;
5875 all_master_event_mask.mask_len = sizeof(all_master_mask_data);
5876 all_master_event_mask.mask = all_master_mask_data;
5877 XISetMask(all_master_event_mask.mask, XI_DeviceChanged);
5878 XISetMask(all_master_event_mask.mask, XI_RawMotion);
5879 XISelectEvents(x11_display, DefaultRootWindow(x11_display), &all_master_event_mask, 1);
5880 }
5881
5882 cursor_size = XcursorGetDefaultSize(x11_display);
5883 cursor_theme = XcursorGetTheme(x11_display);
5884
5885 if (!cursor_theme) {
5886 print_verbose("XcursorGetTheme could not get cursor theme");
5887 cursor_theme = "default";
5888 }
5889
5890 for (int i = 0; i < CURSOR_MAX; i++) {
5891 static const char *cursor_file[] = {
5892 "left_ptr",
5893 "xterm",
5894 "hand2",
5895 "cross",
5896 "watch",
5897 "left_ptr_watch",
5898 "fleur",
5899 "dnd-move",
5900 "crossed_circle",
5901 "v_double_arrow",
5902 "h_double_arrow",
5903 "size_bdiag",
5904 "size_fdiag",
5905 "move",
5906 "row_resize",
5907 "col_resize",
5908 "question_arrow"
5909 };
5910
5911 cursor_img[i] = XcursorLibraryLoadImage(cursor_file[i], cursor_theme, cursor_size);
5912 if (!cursor_img[i]) {
5913 const char *fallback = nullptr;
5914
5915 switch (i) {
5916 case CURSOR_POINTING_HAND:
5917 fallback = "pointer";
5918 break;
5919 case CURSOR_CROSS:
5920 fallback = "crosshair";
5921 break;
5922 case CURSOR_WAIT:
5923 fallback = "wait";
5924 break;
5925 case CURSOR_BUSY:
5926 fallback = "progress";
5927 break;
5928 case CURSOR_DRAG:
5929 fallback = "grabbing";
5930 break;
5931 case CURSOR_CAN_DROP:
5932 fallback = "hand1";
5933 break;
5934 case CURSOR_FORBIDDEN:
5935 fallback = "forbidden";
5936 break;
5937 case CURSOR_VSIZE:
5938 fallback = "ns-resize";
5939 break;
5940 case CURSOR_HSIZE:
5941 fallback = "ew-resize";
5942 break;
5943 case CURSOR_BDIAGSIZE:
5944 fallback = "fd_double_arrow";
5945 break;
5946 case CURSOR_FDIAGSIZE:
5947 fallback = "bd_double_arrow";
5948 break;
5949 case CURSOR_MOVE:
5950 cursor_img[i] = cursor_img[CURSOR_DRAG];
5951 break;
5952 case CURSOR_VSPLIT:
5953 fallback = "sb_v_double_arrow";
5954 break;
5955 case CURSOR_HSPLIT:
5956 fallback = "sb_h_double_arrow";
5957 break;
5958 case CURSOR_HELP:
5959 fallback = "help";
5960 break;
5961 }
5962 if (fallback != nullptr) {
5963 cursor_img[i] = XcursorLibraryLoadImage(fallback, cursor_theme, cursor_size);
5964 }
5965 }
5966 if (cursor_img[i]) {
5967 cursors[i] = XcursorImageLoadCursor(x11_display, cursor_img[i]);
5968 } else {
5969 print_verbose("Failed loading custom cursor: " + String(cursor_file[i]));
5970 }
5971 }
5972
5973 {
5974 // Creating an empty/transparent cursor
5975
5976 // Create 1x1 bitmap
5977 Pixmap cursormask = XCreatePixmap(x11_display,
5978 RootWindow(x11_display, DefaultScreen(x11_display)), 1, 1, 1);
5979
5980 // Fill with zero
5981 XGCValues xgc;
5982 xgc.function = GXclear;
5983 GC gc = XCreateGC(x11_display, cursormask, GCFunction, &xgc);
5984 XFillRectangle(x11_display, cursormask, gc, 0, 0, 1, 1);
5985
5986 // Color value doesn't matter. Mask zero means no foreground or background will be drawn
5987 XColor col = {};
5988
5989 Cursor cursor = XCreatePixmapCursor(x11_display,
5990 cursormask, // source (using cursor mask as placeholder, since it'll all be ignored)
5991 cursormask, // mask
5992 &col, &col, 0, 0);
5993
5994 XFreePixmap(x11_display, cursormask);
5995 XFreeGC(x11_display, gc);
5996
5997 if (cursor == None) {
5998 ERR_PRINT("FAILED CREATING CURSOR");
5999 }
6000
6001 null_cursor = cursor;
6002 }
6003 cursor_set_shape(CURSOR_BUSY);
6004
6005 // Search the X11 event queue for ConfigureNotify events and process all
6006 // that are currently queued early, so we can get the final window size
6007 // for correctly drawing of the bootsplash.
6008 XEvent config_event;
6009 while (XCheckTypedEvent(x11_display, ConfigureNotify, &config_event)) {
6010 _window_changed(&config_event);
6011 }
6012 events_thread.start(_poll_events_thread, this);
6013
6014 _update_real_mouse_position(windows[MAIN_WINDOW_ID]);
6015
6016#ifdef DBUS_ENABLED
6017 screensaver = memnew(FreeDesktopScreenSaver);
6018 screen_set_keep_on(GLOBAL_GET("display/window/energy_saving/keep_screen_on"));
6019
6020 portal_desktop = memnew(FreeDesktopPortalDesktop);
6021#endif
6022 XSetErrorHandler(&default_window_error_handler);
6023
6024 r_error = OK;
6025}
6026
6027DisplayServerX11::~DisplayServerX11() {
6028 // Send owned clipboard data to clipboard manager before exit.
6029 Window x11_main_window = windows[MAIN_WINDOW_ID].x11_window;
6030 _clipboard_transfer_ownership(XA_PRIMARY, x11_main_window);
6031 _clipboard_transfer_ownership(XInternAtom(x11_display, "CLIPBOARD", 0), x11_main_window);
6032
6033 events_thread_done.set();
6034 events_thread.wait_to_finish();
6035
6036 //destroy all windows
6037 for (KeyValue<WindowID, WindowData> &E : windows) {
6038#ifdef VULKAN_ENABLED
6039 if (context_vulkan) {
6040 context_vulkan->window_destroy(E.key);
6041 }
6042#endif
6043#ifdef GLES3_ENABLED
6044 if (gl_manager) {
6045 gl_manager->window_destroy(E.key);
6046 }
6047#endif
6048
6049 WindowData &wd = E.value;
6050 if (wd.xic) {
6051 XDestroyIC(wd.xic);
6052 wd.xic = nullptr;
6053 }
6054 XDestroyWindow(x11_display, wd.x11_xim_window);
6055#ifdef XKB_ENABLED
6056 if (xkb_loaded_v05p) {
6057 if (wd.xkb_state) {
6058 xkb_compose_state_unref(wd.xkb_state);
6059 wd.xkb_state = nullptr;
6060 }
6061 }
6062#endif
6063 XUnmapWindow(x11_display, wd.x11_window);
6064 XDestroyWindow(x11_display, wd.x11_window);
6065 }
6066
6067#ifdef XKB_ENABLED
6068 if (xkb_loaded_v05p) {
6069 if (dead_tbl) {
6070 xkb_compose_table_unref(dead_tbl);
6071 }
6072 if (xkb_ctx) {
6073 xkb_context_unref(xkb_ctx);
6074 }
6075 }
6076#endif
6077
6078 //destroy drivers
6079#if defined(VULKAN_ENABLED)
6080 if (rendering_device_vulkan) {
6081 rendering_device_vulkan->finalize();
6082 memdelete(rendering_device_vulkan);
6083 rendering_device_vulkan = nullptr;
6084 }
6085
6086 if (context_vulkan) {
6087 memdelete(context_vulkan);
6088 context_vulkan = nullptr;
6089 }
6090#endif
6091
6092#ifdef GLES3_ENABLED
6093 if (gl_manager) {
6094 memdelete(gl_manager);
6095 gl_manager = nullptr;
6096 }
6097#endif
6098
6099 if (xrandr_handle) {
6100 dlclose(xrandr_handle);
6101 }
6102
6103 for (int i = 0; i < CURSOR_MAX; i++) {
6104 if (cursors[i] != None) {
6105 XFreeCursor(x11_display, cursors[i]);
6106 }
6107 if (cursor_img[i] != nullptr) {
6108 XcursorImageDestroy(cursor_img[i]);
6109 }
6110 }
6111
6112 if (xim) {
6113 XCloseIM(xim);
6114 }
6115
6116 XCloseDisplay(x11_display);
6117 if (xmbstring) {
6118 memfree(xmbstring);
6119 }
6120
6121#ifdef SPEECHD_ENABLED
6122 if (tts) {
6123 memdelete(tts);
6124 }
6125#endif
6126
6127#ifdef DBUS_ENABLED
6128 memdelete(screensaver);
6129 memdelete(portal_desktop);
6130#endif
6131}
6132
6133void DisplayServerX11::register_x11_driver() {
6134 register_create_function("x11", create_func, get_rendering_drivers_func);
6135}
6136
6137#endif // X11 enabled
6138