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