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