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