1 /*************************************************************************/
2 /* os_x11.cpp */
3 /*************************************************************************/
4 /* This file is part of: */
5 /* GODOT ENGINE */
6 /* https://godotengine.org */
7 /*************************************************************************/
8 /* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
9 /* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
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 "os_x11.h"
32
33 #include "core/os/dir_access.h"
34 #include "core/print_string.h"
35 #include "detect_prime.h"
36 #include "drivers/gles2/rasterizer_gles2.h"
37 #include "drivers/gles3/rasterizer_gles3.h"
38 #include "key_mapping_x11.h"
39 #include "main/main.h"
40 #include "servers/visual/visual_server_raster.h"
41 #include "servers/visual/visual_server_wrap_mt.h"
42
43 #ifdef HAVE_MNTENT
44 #include <mntent.h>
45 #endif
46
47 #include <errno.h>
48 #include <stdio.h>
49 #include <stdlib.h>
50 #include <string.h>
51
52 #include <X11/Xatom.h>
53 #include <X11/Xutil.h>
54 #include <X11/extensions/Xinerama.h>
55
56 // ICCCM
57 #define WM_NormalState 1L // window normal state
58 #define WM_IconicState 3L // window minimized
59 // EWMH
60 #define _NET_WM_STATE_REMOVE 0L // remove/unset property
61 #define _NET_WM_STATE_ADD 1L // add/set property
62 #define _NET_WM_STATE_TOGGLE 2L // toggle property
63
64 #include <dlfcn.h>
65 #include <fcntl.h>
66 #include <sys/stat.h>
67 #include <sys/types.h>
68 #include <unistd.h>
69
70 //stupid linux.h
71 #ifdef KEY_TAB
72 #undef KEY_TAB
73 #endif
74
75 #undef CursorShape
76
77 #include <X11/XKBlib.h>
78
79 // 2.2 is the first release with multitouch
80 #define XINPUT_CLIENT_VERSION_MAJOR 2
81 #define XINPUT_CLIENT_VERSION_MINOR 2
82
83 #define VALUATOR_ABSX 0
84 #define VALUATOR_ABSY 1
85 #define VALUATOR_PRESSURE 2
86 #define VALUATOR_TILTX 3
87 #define VALUATOR_TILTY 4
88
89 static const double abs_resolution_mult = 10000.0;
90 static const double abs_resolution_range_mult = 10.0;
91
initialize_core()92 void OS_X11::initialize_core() {
93
94 crash_handler.initialize();
95
96 OS_Unix::initialize_core();
97 }
98
get_current_video_driver() const99 int OS_X11::get_current_video_driver() const {
100 return video_driver_index;
101 }
102
initialize(const VideoMode & p_desired,int p_video_driver,int p_audio_driver)103 Error OS_X11::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) {
104
105 long im_event_mask = 0;
106 last_button_state = 0;
107
108 xmbstring = NULL;
109 x11_window = 0;
110 last_click_ms = 0;
111 last_click_button_index = -1;
112 last_click_pos = Point2(-100, -100);
113 args = OS::get_singleton()->get_cmdline_args();
114 current_videomode = p_desired;
115 main_loop = NULL;
116 last_timestamp = 0;
117 last_mouse_pos_valid = false;
118 last_keyrelease_time = 0;
119 xdnd_version = 0;
120
121 if (get_render_thread_mode() == RENDER_SEPARATE_THREAD) {
122 XInitThreads();
123 }
124
125 /** XLIB INITIALIZATION **/
126 x11_display = XOpenDisplay(NULL);
127
128 if (!x11_display) {
129 ERR_PRINT("X11 Display is not available");
130 return ERR_UNAVAILABLE;
131 }
132
133 char *modifiers = NULL;
134 Bool xkb_dar = False;
135 XAutoRepeatOn(x11_display);
136 xkb_dar = XkbSetDetectableAutoRepeat(x11_display, True, NULL);
137
138 // Try to support IME if detectable auto-repeat is supported
139 if (xkb_dar == True) {
140
141 #ifdef X_HAVE_UTF8_STRING
142 // Xutf8LookupString will be used later instead of XmbLookupString before
143 // the multibyte sequences can be converted to unicode string.
144 modifiers = XSetLocaleModifiers("");
145 #endif
146 }
147
148 if (modifiers == NULL) {
149 if (is_stdout_verbose()) {
150 WARN_PRINT("IME is disabled");
151 }
152 XSetLocaleModifiers("@im=none");
153 WARN_PRINT("Error setting locale modifiers");
154 }
155
156 const char *err;
157 xrr_get_monitors = NULL;
158 xrr_free_monitors = NULL;
159 int xrandr_major = 0;
160 int xrandr_minor = 0;
161 int event_base, error_base;
162 xrandr_ext_ok = XRRQueryExtension(x11_display, &event_base, &error_base);
163 xrandr_handle = dlopen("libXrandr.so.2", RTLD_LAZY);
164 if (!xrandr_handle) {
165 err = dlerror();
166 fprintf(stderr, "could not load libXrandr.so.2, Error: %s\n", err);
167 } else {
168 XRRQueryVersion(x11_display, &xrandr_major, &xrandr_minor);
169 if (((xrandr_major << 8) | xrandr_minor) >= 0x0105) {
170 xrr_get_monitors = (xrr_get_monitors_t)dlsym(xrandr_handle, "XRRGetMonitors");
171 if (!xrr_get_monitors) {
172 err = dlerror();
173 fprintf(stderr, "could not find symbol XRRGetMonitors\nError: %s\n", err);
174 } else {
175 xrr_free_monitors = (xrr_free_monitors_t)dlsym(xrandr_handle, "XRRFreeMonitors");
176 if (!xrr_free_monitors) {
177 err = dlerror();
178 fprintf(stderr, "could not find XRRFreeMonitors\nError: %s\n", err);
179 xrr_get_monitors = NULL;
180 }
181 }
182 }
183 }
184
185 if (!refresh_device_info()) {
186 OS::get_singleton()->alert("Your system does not support XInput 2.\n"
187 "Please upgrade your distribution.",
188 "Unable to initialize XInput");
189 return ERR_UNAVAILABLE;
190 }
191
192 xim = XOpenIM(x11_display, NULL, NULL, NULL);
193
194 if (xim == NULL) {
195 WARN_PRINT("XOpenIM failed");
196 xim_style = 0L;
197 } else {
198 ::XIMCallback im_destroy_callback;
199 im_destroy_callback.client_data = (::XPointer)(this);
200 im_destroy_callback.callback = (::XIMProc)(xim_destroy_callback);
201 if (XSetIMValues(xim, XNDestroyCallback, &im_destroy_callback,
202 NULL) != NULL) {
203 WARN_PRINT("Error setting XIM destroy callback");
204 }
205
206 ::XIMStyles *xim_styles = NULL;
207 xim_style = 0L;
208 char *imvalret = XGetIMValues(xim, XNQueryInputStyle, &xim_styles, NULL);
209 if (imvalret != NULL || xim_styles == NULL) {
210 fprintf(stderr, "Input method doesn't support any styles\n");
211 }
212
213 if (xim_styles) {
214 xim_style = 0L;
215 for (int i = 0; i < xim_styles->count_styles; i++) {
216
217 if (xim_styles->supported_styles[i] ==
218 (XIMPreeditNothing | XIMStatusNothing)) {
219
220 xim_style = xim_styles->supported_styles[i];
221 break;
222 }
223 }
224
225 XFree(xim_styles);
226 }
227 XFree(imvalret);
228 }
229
230 /*
231 char* windowid = getenv("GODOT_WINDOWID");
232 if (windowid) {
233
234 //freopen("/home/punto/stdout", "w", stdout);
235 //reopen("/home/punto/stderr", "w", stderr);
236 x11_window = atol(windowid);
237
238 XWindowAttributes xwa;
239 XGetWindowAttributes(x11_display,x11_window,&xwa);
240
241 current_videomode.width = xwa.width;
242 current_videomode.height = xwa.height;
243 };
244 */
245
246 // maybe contextgl wants to be in charge of creating the window
247 #if defined(OPENGL_ENABLED)
248 if (getenv("DRI_PRIME") == NULL) {
249 int use_prime = -1;
250
251 if (getenv("PRIMUS_DISPLAY") ||
252 getenv("PRIMUS_libGLd") ||
253 getenv("PRIMUS_libGLa") ||
254 getenv("PRIMUS_libGL") ||
255 getenv("PRIMUS_LOAD_GLOBAL") ||
256 getenv("BUMBLEBEE_SOCKET")) {
257
258 print_verbose("Optirun/primusrun detected. Skipping GPU detection");
259 use_prime = 0;
260 }
261
262 if (getenv("LD_LIBRARY_PATH")) {
263 String ld_library_path(getenv("LD_LIBRARY_PATH"));
264 Vector<String> libraries = ld_library_path.split(":");
265
266 for (int i = 0; i < libraries.size(); ++i) {
267 if (FileAccess::exists(libraries[i] + "/libGL.so.1") ||
268 FileAccess::exists(libraries[i] + "/libGL.so")) {
269
270 print_verbose("Custom libGL override detected. Skipping GPU detection");
271 use_prime = 0;
272 }
273 }
274 }
275
276 if (use_prime == -1) {
277 print_verbose("Detecting GPUs, set DRI_PRIME in the environment to override GPU detection logic.");
278 use_prime = detect_prime();
279 }
280
281 if (use_prime) {
282 print_line("Found discrete GPU, setting DRI_PRIME=1 to use it.");
283 print_line("Note: Set DRI_PRIME=0 in the environment to disable Godot from using the discrete GPU.");
284 setenv("DRI_PRIME", "1", 1);
285 }
286 }
287
288 ContextGL_X11::ContextType opengl_api_type = ContextGL_X11::GLES_3_0_COMPATIBLE;
289
290 if (p_video_driver == VIDEO_DRIVER_GLES2) {
291 opengl_api_type = ContextGL_X11::GLES_2_0_COMPATIBLE;
292 }
293
294 bool editor = Engine::get_singleton()->is_editor_hint();
295 bool gl_initialization_error = false;
296
297 context_gl = NULL;
298 while (!context_gl) {
299 context_gl = memnew(ContextGL_X11(x11_display, x11_window, current_videomode, opengl_api_type));
300
301 if (context_gl->initialize() != OK) {
302 memdelete(context_gl);
303 context_gl = NULL;
304
305 if (GLOBAL_GET("rendering/quality/driver/fallback_to_gles2") || editor) {
306 if (p_video_driver == VIDEO_DRIVER_GLES2) {
307 gl_initialization_error = true;
308 break;
309 }
310
311 p_video_driver = VIDEO_DRIVER_GLES2;
312 opengl_api_type = ContextGL_X11::GLES_2_0_COMPATIBLE;
313 } else {
314 gl_initialization_error = true;
315 break;
316 }
317 }
318 }
319
320 while (true) {
321 if (opengl_api_type == ContextGL_X11::GLES_3_0_COMPATIBLE) {
322 if (RasterizerGLES3::is_viable() == OK) {
323 RasterizerGLES3::register_config();
324 RasterizerGLES3::make_current();
325 break;
326 } else {
327 if (GLOBAL_GET("rendering/quality/driver/fallback_to_gles2") || editor) {
328 p_video_driver = VIDEO_DRIVER_GLES2;
329 opengl_api_type = ContextGL_X11::GLES_2_0_COMPATIBLE;
330 continue;
331 } else {
332 gl_initialization_error = true;
333 break;
334 }
335 }
336 }
337
338 if (opengl_api_type == ContextGL_X11::GLES_2_0_COMPATIBLE) {
339 if (RasterizerGLES2::is_viable() == OK) {
340 RasterizerGLES2::register_config();
341 RasterizerGLES2::make_current();
342 break;
343 } else {
344 gl_initialization_error = true;
345 break;
346 }
347 }
348 }
349
350 if (gl_initialization_error) {
351 OS::get_singleton()->alert("Your video card driver does not support any of the supported OpenGL versions.\n"
352 "Please update your drivers or if you have a very old or integrated GPU upgrade it.",
353 "Unable to initialize Video driver");
354 return ERR_UNAVAILABLE;
355 }
356
357 video_driver_index = p_video_driver;
358
359 context_gl->set_use_vsync(current_videomode.use_vsync);
360
361 #endif
362
363 visual_server = memnew(VisualServerRaster);
364 if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) {
365 visual_server = memnew(VisualServerWrapMT(visual_server, get_render_thread_mode() == RENDER_SEPARATE_THREAD));
366 }
367
368 if (current_videomode.maximized) {
369 current_videomode.maximized = false;
370 set_window_maximized(true);
371 // borderless fullscreen window mode
372 } else if (current_videomode.fullscreen) {
373 current_videomode.fullscreen = false;
374 set_window_fullscreen(true);
375 } else if (current_videomode.borderless_window) {
376 Hints hints;
377 Atom property;
378 hints.flags = 2;
379 hints.decorations = 0;
380 property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True);
381 XChangeProperty(x11_display, x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5);
382 }
383
384 // make PID known to X11
385 {
386 const long pid = this->get_process_id();
387 Atom net_wm_pid = XInternAtom(x11_display, "_NET_WM_PID", False);
388 XChangeProperty(x11_display, x11_window, net_wm_pid, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&pid, 1);
389 }
390
391 // disable resizable window
392 if (!current_videomode.resizable && !current_videomode.fullscreen) {
393 XSizeHints *xsh;
394 xsh = XAllocSizeHints();
395 xsh->flags = PMinSize | PMaxSize;
396 XWindowAttributes xwa;
397 if (current_videomode.fullscreen) {
398 XGetWindowAttributes(x11_display, DefaultRootWindow(x11_display), &xwa);
399 } else {
400 XGetWindowAttributes(x11_display, x11_window, &xwa);
401 }
402 xsh->min_width = xwa.width;
403 xsh->max_width = xwa.width;
404 xsh->min_height = xwa.height;
405 xsh->max_height = xwa.height;
406 XSetWMNormalHints(x11_display, x11_window, xsh);
407 XFree(xsh);
408 }
409
410 if (current_videomode.always_on_top) {
411 current_videomode.always_on_top = false;
412 set_window_always_on_top(true);
413 }
414
415 ERR_FAIL_COND_V(!visual_server, ERR_UNAVAILABLE);
416 ERR_FAIL_COND_V(x11_window == 0, ERR_UNAVAILABLE);
417
418 XSetWindowAttributes new_attr;
419
420 new_attr.event_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask |
421 ButtonReleaseMask | EnterWindowMask |
422 LeaveWindowMask | PointerMotionMask |
423 Button1MotionMask |
424 Button2MotionMask | Button3MotionMask |
425 Button4MotionMask | Button5MotionMask |
426 ButtonMotionMask | KeymapStateMask |
427 ExposureMask | VisibilityChangeMask |
428 StructureNotifyMask |
429 SubstructureNotifyMask | SubstructureRedirectMask |
430 FocusChangeMask | PropertyChangeMask |
431 ColormapChangeMask | OwnerGrabButtonMask |
432 im_event_mask;
433
434 XChangeWindowAttributes(x11_display, x11_window, CWEventMask, &new_attr);
435
436 static unsigned char all_mask_data[XIMaskLen(XI_LASTEVENT)] = {};
437 static unsigned char all_master_mask_data[XIMaskLen(XI_LASTEVENT)] = {};
438
439 xi.all_event_mask.deviceid = XIAllDevices;
440 xi.all_event_mask.mask_len = sizeof(all_mask_data);
441 xi.all_event_mask.mask = all_mask_data;
442
443 xi.all_master_event_mask.deviceid = XIAllMasterDevices;
444 xi.all_master_event_mask.mask_len = sizeof(all_master_mask_data);
445 xi.all_master_event_mask.mask = all_master_mask_data;
446
447 XISetMask(xi.all_event_mask.mask, XI_HierarchyChanged);
448 XISetMask(xi.all_master_event_mask.mask, XI_DeviceChanged);
449 XISetMask(xi.all_master_event_mask.mask, XI_RawMotion);
450
451 #ifdef TOUCH_ENABLED
452 if (xi.touch_devices.size()) {
453 XISetMask(xi.all_event_mask.mask, XI_TouchBegin);
454 XISetMask(xi.all_event_mask.mask, XI_TouchUpdate);
455 XISetMask(xi.all_event_mask.mask, XI_TouchEnd);
456 XISetMask(xi.all_event_mask.mask, XI_TouchOwnership);
457 }
458 #endif
459
460 XISelectEvents(x11_display, x11_window, &xi.all_event_mask, 1);
461 XISelectEvents(x11_display, DefaultRootWindow(x11_display), &xi.all_master_event_mask, 1);
462
463 // Disabled by now since grabbing also blocks mouse events
464 // (they are received as extended events instead of standard events)
465 /*XIClearMask(xi.touch_event_mask.mask, XI_TouchOwnership);
466
467 // Grab touch devices to avoid OS gesture interference
468 for (int i = 0; i < xi.touch_devices.size(); ++i) {
469 XIGrabDevice(x11_display, xi.touch_devices[i], x11_window, CurrentTime, None, XIGrabModeAsync, XIGrabModeAsync, False, &xi.touch_event_mask);
470 }*/
471
472 /* set the titlebar name */
473 XStoreName(x11_display, x11_window, "Godot");
474
475 wm_delete = XInternAtom(x11_display, "WM_DELETE_WINDOW", true);
476 XSetWMProtocols(x11_display, x11_window, &wm_delete, 1);
477
478 im_active = false;
479 im_position = Vector2();
480
481 if (xim && xim_style) {
482
483 xic = XCreateIC(xim, XNInputStyle, xim_style, XNClientWindow, x11_window, XNFocusWindow, x11_window, (char *)NULL);
484 if (XGetICValues(xic, XNFilterEvents, &im_event_mask, NULL) != NULL) {
485 WARN_PRINT("XGetICValues couldn't obtain XNFilterEvents value");
486 XDestroyIC(xic);
487 xic = NULL;
488 }
489 if (xic) {
490 XUnsetICFocus(xic);
491 } else {
492 WARN_PRINT("XCreateIC couldn't create xic");
493 }
494 } else {
495
496 xic = NULL;
497 WARN_PRINT("XCreateIC couldn't create xic");
498 }
499
500 cursor_size = XcursorGetDefaultSize(x11_display);
501 cursor_theme = XcursorGetTheme(x11_display);
502
503 if (!cursor_theme) {
504 print_verbose("XcursorGetTheme could not get cursor theme");
505 cursor_theme = "default";
506 }
507
508 for (int i = 0; i < CURSOR_MAX; i++) {
509
510 cursors[i] = None;
511 img[i] = NULL;
512 }
513
514 current_cursor = CURSOR_ARROW;
515
516 for (int i = 0; i < CURSOR_MAX; i++) {
517
518 static const char *cursor_file[] = {
519 "left_ptr",
520 "xterm",
521 "hand2",
522 "cross",
523 "watch",
524 "left_ptr_watch",
525 "fleur",
526 "hand1",
527 "X_cursor",
528 "sb_v_double_arrow",
529 "sb_h_double_arrow",
530 "size_bdiag",
531 "size_fdiag",
532 "hand1",
533 "sb_v_double_arrow",
534 "sb_h_double_arrow",
535 "question_arrow"
536 };
537
538 img[i] = XcursorLibraryLoadImage(cursor_file[i], cursor_theme, cursor_size);
539 if (img[i]) {
540 cursors[i] = XcursorImageLoadCursor(x11_display, img[i]);
541 } else {
542 print_verbose("Failed loading custom cursor: " + String(cursor_file[i]));
543 }
544 }
545
546 {
547 // Creating an empty/transparent cursor
548
549 // Create 1x1 bitmap
550 Pixmap cursormask = XCreatePixmap(x11_display,
551 RootWindow(x11_display, DefaultScreen(x11_display)), 1, 1, 1);
552
553 // Fill with zero
554 XGCValues xgc;
555 xgc.function = GXclear;
556 GC gc = XCreateGC(x11_display, cursormask, GCFunction, &xgc);
557 XFillRectangle(x11_display, cursormask, gc, 0, 0, 1, 1);
558
559 // Color value doesn't matter. Mask zero means no foreground or background will be drawn
560 XColor col = {};
561
562 Cursor cursor = XCreatePixmapCursor(x11_display,
563 cursormask, // source (using cursor mask as placeholder, since it'll all be ignored)
564 cursormask, // mask
565 &col, &col, 0, 0);
566
567 XFreePixmap(x11_display, cursormask);
568 XFreeGC(x11_display, gc);
569
570 if (cursor == None) {
571 ERR_PRINT("FAILED CREATING CURSOR");
572 }
573
574 null_cursor = cursor;
575 }
576 set_cursor_shape(CURSOR_BUSY);
577
578 //Set Xdnd (drag & drop) support
579 Atom XdndAware = XInternAtom(x11_display, "XdndAware", False);
580 Atom version = 5;
581 XChangeProperty(x11_display, x11_window, XdndAware, XA_ATOM, 32, PropModeReplace, (unsigned char *)&version, 1);
582
583 xdnd_enter = XInternAtom(x11_display, "XdndEnter", False);
584 xdnd_position = XInternAtom(x11_display, "XdndPosition", False);
585 xdnd_status = XInternAtom(x11_display, "XdndStatus", False);
586 xdnd_action_copy = XInternAtom(x11_display, "XdndActionCopy", False);
587 xdnd_drop = XInternAtom(x11_display, "XdndDrop", False);
588 xdnd_finished = XInternAtom(x11_display, "XdndFinished", False);
589 xdnd_selection = XInternAtom(x11_display, "XdndSelection", False);
590 requested = None;
591
592 visual_server->init();
593
594 AudioDriverManager::initialize(p_audio_driver);
595
596 input = memnew(InputDefault);
597
598 window_has_focus = true; // Set focus to true at init
599 #ifdef JOYDEV_ENABLED
600 joypad = memnew(JoypadLinux(input));
601 #endif
602 _ensure_user_data_dir();
603
604 power_manager = memnew(PowerX11);
605
606 if (p_desired.layered) {
607 set_window_per_pixel_transparency_enabled(true);
608 }
609
610 XEvent xevent;
611 while (XPending(x11_display) > 0) {
612 XNextEvent(x11_display, &xevent);
613 if (xevent.type == ConfigureNotify) {
614 _window_changed(&xevent);
615 }
616 }
617
618 update_real_mouse_position();
619
620 return OK;
621 }
622
refresh_device_info()623 bool OS_X11::refresh_device_info() {
624 int event_base, error_base;
625
626 print_verbose("XInput: Refreshing devices.");
627
628 if (!XQueryExtension(x11_display, "XInputExtension", &xi.opcode, &event_base, &error_base)) {
629 print_verbose("XInput extension not available. Please upgrade your distribution.");
630 return false;
631 }
632
633 int xi_major_query = XINPUT_CLIENT_VERSION_MAJOR;
634 int xi_minor_query = XINPUT_CLIENT_VERSION_MINOR;
635
636 if (XIQueryVersion(x11_display, &xi_major_query, &xi_minor_query) != Success) {
637 print_verbose(vformat("XInput 2 not available (server supports %d.%d).", xi_major_query, xi_minor_query));
638 xi.opcode = 0;
639 return false;
640 }
641
642 if (xi_major_query < XINPUT_CLIENT_VERSION_MAJOR || (xi_major_query == XINPUT_CLIENT_VERSION_MAJOR && xi_minor_query < XINPUT_CLIENT_VERSION_MINOR)) {
643 print_verbose(vformat("XInput %d.%d not available (server supports %d.%d). Touch input unavailable.",
644 XINPUT_CLIENT_VERSION_MAJOR, XINPUT_CLIENT_VERSION_MINOR, xi_major_query, xi_minor_query));
645 }
646
647 xi.absolute_devices.clear();
648 xi.touch_devices.clear();
649
650 int dev_count;
651 XIDeviceInfo *info = XIQueryDevice(x11_display, XIAllDevices, &dev_count);
652
653 for (int i = 0; i < dev_count; i++) {
654 XIDeviceInfo *dev = &info[i];
655 if (!dev->enabled)
656 continue;
657 if (!(dev->use == XIMasterPointer || dev->use == XIFloatingSlave))
658 continue;
659
660 bool direct_touch = false;
661 bool absolute_mode = false;
662 int resolution_x = 0;
663 int resolution_y = 0;
664 double abs_x_min = 0;
665 double abs_x_max = 0;
666 double abs_y_min = 0;
667 double abs_y_max = 0;
668 double pressure_min = 0;
669 double pressure_max = 0;
670 double tilt_x_min = 0;
671 double tilt_x_max = 0;
672 double tilt_y_min = 0;
673 double tilt_y_max = 0;
674 for (int j = 0; j < dev->num_classes; j++) {
675 #ifdef TOUCH_ENABLED
676 if (dev->classes[j]->type == XITouchClass && ((XITouchClassInfo *)dev->classes[j])->mode == XIDirectTouch) {
677 direct_touch = true;
678 }
679 #endif
680 if (dev->classes[j]->type == XIValuatorClass) {
681 XIValuatorClassInfo *class_info = (XIValuatorClassInfo *)dev->classes[j];
682
683 if (class_info->number == VALUATOR_ABSX && class_info->mode == XIModeAbsolute) {
684 resolution_x = class_info->resolution;
685 abs_x_min = class_info->min;
686 abs_y_max = class_info->max;
687 absolute_mode = true;
688 } else if (class_info->number == VALUATOR_ABSY && class_info->mode == XIModeAbsolute) {
689 resolution_y = class_info->resolution;
690 abs_y_min = class_info->min;
691 abs_y_max = class_info->max;
692 absolute_mode = true;
693 } else if (class_info->number == VALUATOR_PRESSURE && class_info->mode == XIModeAbsolute) {
694 pressure_min = class_info->min;
695 pressure_max = class_info->max;
696 } else if (class_info->number == VALUATOR_TILTX && class_info->mode == XIModeAbsolute) {
697 tilt_x_min = class_info->min;
698 tilt_x_max = class_info->max;
699 } else if (class_info->number == VALUATOR_TILTY && class_info->mode == XIModeAbsolute) {
700 tilt_x_min = class_info->min;
701 tilt_x_max = class_info->max;
702 }
703 }
704 }
705 if (direct_touch) {
706 xi.touch_devices.push_back(dev->deviceid);
707 print_verbose("XInput: Using touch device: " + String(dev->name));
708 }
709 if (absolute_mode) {
710 // If no resolution was reported, use the min/max ranges.
711 if (resolution_x <= 0) {
712 resolution_x = (abs_x_max - abs_x_min) * abs_resolution_range_mult;
713 }
714 if (resolution_y <= 0) {
715 resolution_y = (abs_y_max - abs_y_min) * abs_resolution_range_mult;
716 }
717 xi.absolute_devices[dev->deviceid] = Vector2(abs_resolution_mult / resolution_x, abs_resolution_mult / resolution_y);
718 print_verbose("XInput: Absolute pointing device: " + String(dev->name));
719 }
720
721 xi.pressure = 0;
722 xi.pen_pressure_range[dev->deviceid] = Vector2(pressure_min, pressure_max);
723 xi.pen_tilt_x_range[dev->deviceid] = Vector2(tilt_x_min, tilt_x_max);
724 xi.pen_tilt_y_range[dev->deviceid] = Vector2(tilt_y_min, tilt_y_max);
725 }
726
727 XIFreeDeviceInfo(info);
728 #ifdef TOUCH_ENABLED
729 if (!xi.touch_devices.size()) {
730 print_verbose("XInput: No touch devices found.");
731 }
732 #endif
733
734 return true;
735 }
736
xim_destroy_callback(::XIM im,::XPointer client_data,::XPointer call_data)737 void OS_X11::xim_destroy_callback(::XIM im, ::XPointer client_data,
738 ::XPointer call_data) {
739
740 WARN_PRINT("Input method stopped");
741 OS_X11 *os = reinterpret_cast<OS_X11 *>(client_data);
742 os->xim = NULL;
743 os->xic = NULL;
744 }
745
set_ime_active(const bool p_active)746 void OS_X11::set_ime_active(const bool p_active) {
747
748 im_active = p_active;
749
750 if (!xic)
751 return;
752
753 if (p_active) {
754 XSetICFocus(xic);
755 set_ime_position(im_position);
756 } else {
757 XUnsetICFocus(xic);
758 }
759 }
760
set_ime_position(const Point2 & p_pos)761 void OS_X11::set_ime_position(const Point2 &p_pos) {
762
763 im_position = p_pos;
764
765 if (!xic)
766 return;
767
768 ::XPoint spot;
769 spot.x = short(p_pos.x);
770 spot.y = short(p_pos.y);
771 XVaNestedList preedit_attr = XVaCreateNestedList(0, XNSpotLocation, &spot, NULL);
772 XSetICValues(xic, XNPreeditAttributes, preedit_attr, NULL);
773 XFree(preedit_attr);
774 }
775
get_unique_id() const776 String OS_X11::get_unique_id() const {
777
778 static String machine_id;
779 if (machine_id.empty()) {
780 if (FileAccess *f = FileAccess::open("/etc/machine-id", FileAccess::READ)) {
781 while (machine_id.empty() && !f->eof_reached()) {
782 machine_id = f->get_line().strip_edges();
783 }
784 f->close();
785 memdelete(f);
786 }
787 }
788 return machine_id;
789 }
790
finalize()791 void OS_X11::finalize() {
792
793 if (main_loop)
794 memdelete(main_loop);
795 main_loop = NULL;
796
797 /*
798 if (debugger_connection_console) {
799 memdelete(debugger_connection_console);
800 }
801 */
802 #ifdef ALSAMIDI_ENABLED
803 driver_alsamidi.close();
804 #endif
805
806 #ifdef JOYDEV_ENABLED
807 memdelete(joypad);
808 #endif
809
810 xi.touch_devices.clear();
811 xi.state.clear();
812
813 memdelete(input);
814
815 cursors_cache.clear();
816 visual_server->finish();
817 memdelete(visual_server);
818 //memdelete(rasterizer);
819
820 memdelete(power_manager);
821
822 if (xrandr_handle)
823 dlclose(xrandr_handle);
824
825 XUnmapWindow(x11_display, x11_window);
826 XDestroyWindow(x11_display, x11_window);
827
828 #if defined(OPENGL_ENABLED)
829 memdelete(context_gl);
830 #endif
831 for (int i = 0; i < CURSOR_MAX; i++) {
832 if (cursors[i] != None)
833 XFreeCursor(x11_display, cursors[i]);
834 if (img[i] != NULL)
835 XcursorImageDestroy(img[i]);
836 };
837
838 if (xic) {
839 XDestroyIC(xic);
840 }
841 if (xim) {
842 XCloseIM(xim);
843 }
844
845 XCloseDisplay(x11_display);
846 if (xmbstring)
847 memfree(xmbstring);
848
849 args.clear();
850 }
851
set_mouse_mode(MouseMode p_mode)852 void OS_X11::set_mouse_mode(MouseMode p_mode) {
853
854 if (p_mode == mouse_mode)
855 return;
856
857 if (mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED)
858 XUngrabPointer(x11_display, CurrentTime);
859
860 // The only modes that show a cursor are VISIBLE and CONFINED
861 bool showCursor = (p_mode == MOUSE_MODE_VISIBLE || p_mode == MOUSE_MODE_CONFINED);
862
863 if (showCursor) {
864 XDefineCursor(x11_display, x11_window, cursors[current_cursor]); // show cursor
865 } else {
866 XDefineCursor(x11_display, x11_window, null_cursor); // hide cursor
867 }
868
869 mouse_mode = p_mode;
870
871 if (mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED) {
872
873 //flush pending motion events
874 flush_mouse_motion();
875
876 if (XGrabPointer(
877 x11_display, x11_window, True,
878 ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
879 GrabModeAsync, GrabModeAsync, x11_window, None, CurrentTime) != GrabSuccess) {
880 ERR_PRINT("NO GRAB");
881 }
882
883 if (mouse_mode == MOUSE_MODE_CAPTURED) {
884 center.x = current_videomode.width / 2;
885 center.y = current_videomode.height / 2;
886
887 XWarpPointer(x11_display, None, x11_window,
888 0, 0, 0, 0, (int)center.x, (int)center.y);
889
890 input->set_mouse_position(center);
891 }
892 } else {
893 do_mouse_warp = false;
894 }
895
896 XFlush(x11_display);
897 }
898
warp_mouse_position(const Point2 & p_to)899 void OS_X11::warp_mouse_position(const Point2 &p_to) {
900
901 if (mouse_mode == MOUSE_MODE_CAPTURED) {
902
903 last_mouse_pos = p_to;
904 } else {
905
906 /*XWindowAttributes xwa;
907 XGetWindowAttributes(x11_display, x11_window, &xwa);
908 printf("%d %d\n", xwa.x, xwa.y); needed? */
909
910 XWarpPointer(x11_display, None, x11_window,
911 0, 0, 0, 0, (int)p_to.x, (int)p_to.y);
912 }
913 }
914
flush_mouse_motion()915 void OS_X11::flush_mouse_motion() {
916 while (true) {
917 if (XPending(x11_display) > 0) {
918 XEvent event;
919 XPeekEvent(x11_display, &event);
920
921 if (XGetEventData(x11_display, &event.xcookie) && event.xcookie.type == GenericEvent && event.xcookie.extension == xi.opcode) {
922 XIDeviceEvent *event_data = (XIDeviceEvent *)event.xcookie.data;
923
924 if (event_data->evtype == XI_RawMotion) {
925 XNextEvent(x11_display, &event);
926 } else {
927 break;
928 }
929 } else {
930 break;
931 }
932 } else {
933 break;
934 }
935 }
936
937 xi.relative_motion.x = 0;
938 xi.relative_motion.y = 0;
939 }
940
get_mouse_mode() const941 OS::MouseMode OS_X11::get_mouse_mode() const {
942 return mouse_mode;
943 }
944
get_mouse_button_state() const945 int OS_X11::get_mouse_button_state() const {
946 return last_button_state;
947 }
948
get_mouse_position() const949 Point2 OS_X11::get_mouse_position() const {
950 return last_mouse_pos;
951 }
952
get_window_per_pixel_transparency_enabled() const953 bool OS_X11::get_window_per_pixel_transparency_enabled() const {
954
955 if (!is_layered_allowed()) return false;
956 return layered_window;
957 }
958
set_window_per_pixel_transparency_enabled(bool p_enabled)959 void OS_X11::set_window_per_pixel_transparency_enabled(bool p_enabled) {
960
961 if (!is_layered_allowed()) return;
962 if (layered_window != p_enabled) {
963 if (p_enabled) {
964 layered_window = true;
965 } else {
966 layered_window = false;
967 }
968 }
969 }
970
set_window_title(const String & p_title)971 void OS_X11::set_window_title(const String &p_title) {
972 XStoreName(x11_display, x11_window, p_title.utf8().get_data());
973
974 Atom _net_wm_name = XInternAtom(x11_display, "_NET_WM_NAME", false);
975 Atom utf8_string = XInternAtom(x11_display, "UTF8_STRING", false);
976 XChangeProperty(x11_display, x11_window, _net_wm_name, utf8_string, 8, PropModeReplace, (unsigned char *)p_title.utf8().get_data(), p_title.utf8().length());
977 }
978
set_video_mode(const VideoMode & p_video_mode,int p_screen)979 void OS_X11::set_video_mode(const VideoMode &p_video_mode, int p_screen) {
980 }
981
get_video_mode(int p_screen) const982 OS::VideoMode OS_X11::get_video_mode(int p_screen) const {
983 return current_videomode;
984 }
985
get_fullscreen_mode_list(List<VideoMode> * p_list,int p_screen) const986 void OS_X11::get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen) const {
987 }
988
set_wm_fullscreen(bool p_enabled)989 void OS_X11::set_wm_fullscreen(bool p_enabled) {
990 if (p_enabled && !get_borderless_window()) {
991 // remove decorations if the window is not already borderless
992 Hints hints;
993 Atom property;
994 hints.flags = 2;
995 hints.decorations = 0;
996 property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True);
997 XChangeProperty(x11_display, x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5);
998 }
999
1000 if (p_enabled && !is_window_resizable()) {
1001 // Set the window as resizable to prevent window managers to ignore the fullscreen state flag.
1002 XSizeHints *xsh;
1003
1004 xsh = XAllocSizeHints();
1005 xsh->flags = 0L;
1006 XSetWMNormalHints(x11_display, x11_window, xsh);
1007 XFree(xsh);
1008 }
1009
1010 // Using EWMH -- Extended Window Manager Hints
1011 XEvent xev;
1012 Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);
1013 Atom wm_fullscreen = XInternAtom(x11_display, "_NET_WM_STATE_FULLSCREEN", False);
1014
1015 memset(&xev, 0, sizeof(xev));
1016 xev.type = ClientMessage;
1017 xev.xclient.window = x11_window;
1018 xev.xclient.message_type = wm_state;
1019 xev.xclient.format = 32;
1020 xev.xclient.data.l[0] = p_enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
1021 xev.xclient.data.l[1] = wm_fullscreen;
1022 xev.xclient.data.l[2] = 0;
1023
1024 XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
1025
1026 // set bypass compositor hint
1027 Atom bypass_compositor = XInternAtom(x11_display, "_NET_WM_BYPASS_COMPOSITOR", False);
1028 unsigned long compositing_disable_on = p_enabled ? 1 : 0;
1029 XChangeProperty(x11_display, x11_window, bypass_compositor, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&compositing_disable_on, 1);
1030
1031 XFlush(x11_display);
1032
1033 if (!p_enabled) {
1034 // Reset the non-resizable flags if we un-set these before.
1035 Size2 size = get_window_size();
1036 XSizeHints *xsh;
1037 xsh = XAllocSizeHints();
1038 if (!is_window_resizable()) {
1039 xsh->flags = PMinSize | PMaxSize;
1040 xsh->min_width = size.x;
1041 xsh->max_width = size.x;
1042 xsh->min_height = size.y;
1043 xsh->max_height = size.y;
1044 } else {
1045 xsh->flags = 0L;
1046 if (min_size != Size2()) {
1047 xsh->flags |= PMinSize;
1048 xsh->min_width = min_size.x;
1049 xsh->min_height = min_size.y;
1050 }
1051 if (max_size != Size2()) {
1052 xsh->flags |= PMaxSize;
1053 xsh->max_width = max_size.x;
1054 xsh->max_height = max_size.y;
1055 }
1056 }
1057 XSetWMNormalHints(x11_display, x11_window, xsh);
1058 XFree(xsh);
1059
1060 // put back or remove decorations according to the last set borderless state
1061 Hints hints;
1062 Atom property;
1063 hints.flags = 2;
1064 hints.decorations = current_videomode.borderless_window ? 0 : 1;
1065 property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True);
1066 XChangeProperty(x11_display, x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5);
1067 }
1068 }
1069
set_wm_above(bool p_enabled)1070 void OS_X11::set_wm_above(bool p_enabled) {
1071 Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);
1072 Atom wm_above = XInternAtom(x11_display, "_NET_WM_STATE_ABOVE", False);
1073
1074 XClientMessageEvent xev;
1075 memset(&xev, 0, sizeof(xev));
1076 xev.type = ClientMessage;
1077 xev.window = x11_window;
1078 xev.message_type = wm_state;
1079 xev.format = 32;
1080 xev.data.l[0] = p_enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
1081 xev.data.l[1] = wm_above;
1082 xev.data.l[3] = 1;
1083 XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent *)&xev);
1084 }
1085
get_screen_count() const1086 int OS_X11::get_screen_count() const {
1087 // Using Xinerama Extension
1088 int event_base, error_base;
1089 const Bool ext_okay = XineramaQueryExtension(x11_display, &event_base, &error_base);
1090 if (!ext_okay) return 0;
1091
1092 int count;
1093 XineramaScreenInfo *xsi = XineramaQueryScreens(x11_display, &count);
1094 XFree(xsi);
1095 return count;
1096 }
1097
get_current_screen() const1098 int OS_X11::get_current_screen() const {
1099 int x, y;
1100 Window child;
1101 XTranslateCoordinates(x11_display, x11_window, DefaultRootWindow(x11_display), 0, 0, &x, &y, &child);
1102
1103 int count = get_screen_count();
1104 for (int i = 0; i < count; i++) {
1105 Point2i pos = get_screen_position(i);
1106 Size2i size = get_screen_size(i);
1107 if ((x >= pos.x && x < pos.x + size.width) && (y >= pos.y && y < pos.y + size.height))
1108 return i;
1109 }
1110 return 0;
1111 }
1112
set_current_screen(int p_screen)1113 void OS_X11::set_current_screen(int p_screen) {
1114 int count = get_screen_count();
1115 if (p_screen >= count) return;
1116
1117 if (current_videomode.fullscreen) {
1118 Point2i position = get_screen_position(p_screen);
1119 Size2i size = get_screen_size(p_screen);
1120
1121 XMoveResizeWindow(x11_display, x11_window, position.x, position.y, size.x, size.y);
1122 } else {
1123 if (p_screen != get_current_screen()) {
1124 Point2i position = get_screen_position(p_screen);
1125 XMoveWindow(x11_display, x11_window, position.x, position.y);
1126 }
1127 }
1128 }
1129
get_screen_position(int p_screen) const1130 Point2 OS_X11::get_screen_position(int p_screen) const {
1131 if (p_screen == -1) {
1132 p_screen = get_current_screen();
1133 }
1134
1135 // Using Xinerama Extension
1136 int event_base, error_base;
1137 const Bool ext_okay = XineramaQueryExtension(x11_display, &event_base, &error_base);
1138 if (!ext_okay) {
1139 return Point2i(0, 0);
1140 }
1141
1142 int count;
1143 XineramaScreenInfo *xsi = XineramaQueryScreens(x11_display, &count);
1144 if (p_screen >= count) {
1145 return Point2i(0, 0);
1146 }
1147
1148 Point2i position = Point2i(xsi[p_screen].x_org, xsi[p_screen].y_org);
1149
1150 XFree(xsi);
1151
1152 return position;
1153 }
1154
get_screen_size(int p_screen) const1155 Size2 OS_X11::get_screen_size(int p_screen) const {
1156 if (p_screen == -1) {
1157 p_screen = get_current_screen();
1158 }
1159
1160 // Using Xinerama Extension
1161 int event_base, error_base;
1162 const Bool ext_okay = XineramaQueryExtension(x11_display, &event_base, &error_base);
1163 if (!ext_okay) return Size2i(0, 0);
1164
1165 int count;
1166 XineramaScreenInfo *xsi = XineramaQueryScreens(x11_display, &count);
1167 if (p_screen >= count) return Size2i(0, 0);
1168
1169 Size2i size = Point2i(xsi[p_screen].width, xsi[p_screen].height);
1170 XFree(xsi);
1171 return size;
1172 }
1173
get_screen_dpi(int p_screen) const1174 int OS_X11::get_screen_dpi(int p_screen) const {
1175 if (p_screen == -1) {
1176 p_screen = get_current_screen();
1177 }
1178
1179 //invalid screen?
1180 ERR_FAIL_INDEX_V(p_screen, get_screen_count(), 0);
1181
1182 //Get physical monitor Dimensions through XRandR and calculate dpi
1183 Size2 sc = get_screen_size(p_screen);
1184 if (xrandr_ext_ok) {
1185 int count = 0;
1186 if (xrr_get_monitors) {
1187 xrr_monitor_info *monitors = xrr_get_monitors(x11_display, x11_window, true, &count);
1188 if (p_screen < count) {
1189 double xdpi = sc.width / (double)monitors[p_screen].mwidth * 25.4;
1190 double ydpi = sc.height / (double)monitors[p_screen].mheight * 25.4;
1191 xrr_free_monitors(monitors);
1192 return (xdpi + ydpi) / 2;
1193 }
1194 xrr_free_monitors(monitors);
1195 } else if (p_screen == 0) {
1196 XRRScreenSize *sizes = XRRSizes(x11_display, 0, &count);
1197 if (sizes) {
1198 double xdpi = sc.width / (double)sizes[0].mwidth * 25.4;
1199 double ydpi = sc.height / (double)sizes[0].mheight * 25.4;
1200 return (xdpi + ydpi) / 2;
1201 }
1202 }
1203 }
1204
1205 int width_mm = DisplayWidthMM(x11_display, p_screen);
1206 int height_mm = DisplayHeightMM(x11_display, p_screen);
1207 double xdpi = (width_mm ? sc.width / (double)width_mm * 25.4 : 0);
1208 double ydpi = (height_mm ? sc.height / (double)height_mm * 25.4 : 0);
1209 if (xdpi || ydpi)
1210 return (xdpi + ydpi) / (xdpi && ydpi ? 2 : 1);
1211
1212 //could not get dpi
1213 return 96;
1214 }
1215
get_window_position() const1216 Point2 OS_X11::get_window_position() const {
1217 int x, y;
1218 Window child;
1219 XTranslateCoordinates(x11_display, x11_window, DefaultRootWindow(x11_display), 0, 0, &x, &y, &child);
1220 return Point2i(x, y);
1221 }
1222
set_window_position(const Point2 & p_position)1223 void OS_X11::set_window_position(const Point2 &p_position) {
1224 int x = 0;
1225 int y = 0;
1226 if (!get_borderless_window()) {
1227 //exclude window decorations
1228 XSync(x11_display, False);
1229 Atom prop = XInternAtom(x11_display, "_NET_FRAME_EXTENTS", True);
1230 if (prop != None) {
1231 Atom type;
1232 int format;
1233 unsigned long len;
1234 unsigned long remaining;
1235 unsigned char *data = NULL;
1236 if (XGetWindowProperty(x11_display, x11_window, prop, 0, 4, False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) {
1237 if (format == 32 && len == 4) {
1238 long *extents = (long *)data;
1239 x = extents[0];
1240 y = extents[2];
1241 }
1242 XFree(data);
1243 }
1244 }
1245 }
1246 XMoveWindow(x11_display, x11_window, p_position.x - x, p_position.y - y);
1247 update_real_mouse_position();
1248 }
1249
get_window_size() const1250 Size2 OS_X11::get_window_size() const {
1251 // Use current_videomode width and height instead of XGetWindowAttributes
1252 // since right after a XResizeWindow the attributes may not be updated yet
1253 return Size2i(current_videomode.width, current_videomode.height);
1254 }
1255
get_real_window_size() const1256 Size2 OS_X11::get_real_window_size() const {
1257 XWindowAttributes xwa;
1258 XSync(x11_display, False);
1259 XGetWindowAttributes(x11_display, x11_window, &xwa);
1260 int w = xwa.width;
1261 int h = xwa.height;
1262 Atom prop = XInternAtom(x11_display, "_NET_FRAME_EXTENTS", True);
1263 if (prop != None) {
1264 Atom type;
1265 int format;
1266 unsigned long len;
1267 unsigned long remaining;
1268 unsigned char *data = NULL;
1269 if (XGetWindowProperty(x11_display, x11_window, prop, 0, 4, False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) {
1270 if (format == 32 && len == 4) {
1271 long *extents = (long *)data;
1272 w += extents[0] + extents[1]; // left, right
1273 h += extents[2] + extents[3]; // top, bottom
1274 }
1275 XFree(data);
1276 }
1277 }
1278 return Size2(w, h);
1279 }
1280
get_max_window_size() const1281 Size2 OS_X11::get_max_window_size() const {
1282 return max_size;
1283 }
1284
get_min_window_size() const1285 Size2 OS_X11::get_min_window_size() const {
1286 return min_size;
1287 }
1288
set_min_window_size(const Size2 p_size)1289 void OS_X11::set_min_window_size(const Size2 p_size) {
1290
1291 if ((p_size != Size2()) && (max_size != Size2()) && ((p_size.x > max_size.x) || (p_size.y > max_size.y))) {
1292 ERR_PRINT("Minimum window size can't be larger than maximum window size!");
1293 return;
1294 }
1295 min_size = p_size;
1296
1297 if (is_window_resizable()) {
1298 XSizeHints *xsh;
1299 xsh = XAllocSizeHints();
1300 xsh->flags = 0L;
1301 if (min_size != Size2()) {
1302 xsh->flags |= PMinSize;
1303 xsh->min_width = min_size.x;
1304 xsh->min_height = min_size.y;
1305 }
1306 if (max_size != Size2()) {
1307 xsh->flags |= PMaxSize;
1308 xsh->max_width = max_size.x;
1309 xsh->max_height = max_size.y;
1310 }
1311 XSetWMNormalHints(x11_display, x11_window, xsh);
1312 XFree(xsh);
1313
1314 XFlush(x11_display);
1315 }
1316 }
1317
set_max_window_size(const Size2 p_size)1318 void OS_X11::set_max_window_size(const Size2 p_size) {
1319
1320 if ((p_size != Size2()) && ((p_size.x < min_size.x) || (p_size.y < min_size.y))) {
1321 ERR_PRINT("Maximum window size can't be smaller than minimum window size!");
1322 return;
1323 }
1324 max_size = p_size;
1325
1326 if (is_window_resizable()) {
1327 XSizeHints *xsh;
1328 xsh = XAllocSizeHints();
1329 xsh->flags = 0L;
1330 if (min_size != Size2()) {
1331 xsh->flags |= PMinSize;
1332 xsh->min_width = min_size.x;
1333 xsh->min_height = min_size.y;
1334 }
1335 if (max_size != Size2()) {
1336 xsh->flags |= PMaxSize;
1337 xsh->max_width = max_size.x;
1338 xsh->max_height = max_size.y;
1339 }
1340 XSetWMNormalHints(x11_display, x11_window, xsh);
1341 XFree(xsh);
1342
1343 XFlush(x11_display);
1344 }
1345 }
1346
set_window_size(const Size2 p_size)1347 void OS_X11::set_window_size(const Size2 p_size) {
1348
1349 if (current_videomode.width == p_size.width && current_videomode.height == p_size.height)
1350 return;
1351
1352 XWindowAttributes xwa;
1353 XSync(x11_display, False);
1354 XGetWindowAttributes(x11_display, x11_window, &xwa);
1355 int old_w = xwa.width;
1356 int old_h = xwa.height;
1357
1358 // If window resizable is disabled we need to update the attributes first
1359 XSizeHints *xsh;
1360 xsh = XAllocSizeHints();
1361 if (!is_window_resizable()) {
1362 xsh->flags = PMinSize | PMaxSize;
1363 xsh->min_width = p_size.x;
1364 xsh->max_width = p_size.x;
1365 xsh->min_height = p_size.y;
1366 xsh->max_height = p_size.y;
1367 } else {
1368 xsh->flags = 0L;
1369 if (min_size != Size2()) {
1370 xsh->flags |= PMinSize;
1371 xsh->min_width = min_size.x;
1372 xsh->min_height = min_size.y;
1373 }
1374 if (max_size != Size2()) {
1375 xsh->flags |= PMaxSize;
1376 xsh->max_width = max_size.x;
1377 xsh->max_height = max_size.y;
1378 }
1379 }
1380 XSetWMNormalHints(x11_display, x11_window, xsh);
1381 XFree(xsh);
1382
1383 // Resize the window
1384 XResizeWindow(x11_display, x11_window, p_size.x, p_size.y);
1385
1386 // Update our videomode width and height
1387 current_videomode.width = p_size.x;
1388 current_videomode.height = p_size.y;
1389
1390 for (int timeout = 0; timeout < 50; ++timeout) {
1391 XSync(x11_display, False);
1392 XGetWindowAttributes(x11_display, x11_window, &xwa);
1393
1394 if (old_w != xwa.width || old_h != xwa.height)
1395 break;
1396
1397 usleep(10000);
1398 }
1399 }
1400
set_window_fullscreen(bool p_enabled)1401 void OS_X11::set_window_fullscreen(bool p_enabled) {
1402
1403 if (current_videomode.fullscreen == p_enabled)
1404 return;
1405
1406 if (layered_window)
1407 set_window_per_pixel_transparency_enabled(false);
1408
1409 if (p_enabled && current_videomode.always_on_top) {
1410 // Fullscreen + Always-on-top requires a maximized window on some window managers (Metacity)
1411 set_window_maximized(true);
1412 }
1413 set_wm_fullscreen(p_enabled);
1414 if (!p_enabled && current_videomode.always_on_top) {
1415 // Restore
1416 set_window_maximized(false);
1417 }
1418 if (!p_enabled) {
1419 set_window_position(last_position_before_fs);
1420 } else {
1421 last_position_before_fs = get_window_position();
1422 }
1423 current_videomode.fullscreen = p_enabled;
1424 }
1425
is_window_fullscreen() const1426 bool OS_X11::is_window_fullscreen() const {
1427 return current_videomode.fullscreen;
1428 }
1429
set_window_resizable(bool p_enabled)1430 void OS_X11::set_window_resizable(bool p_enabled) {
1431
1432 XSizeHints *xsh;
1433 xsh = XAllocSizeHints();
1434 if (!p_enabled) {
1435 Size2 size = get_window_size();
1436
1437 xsh->flags = PMinSize | PMaxSize;
1438 xsh->min_width = size.x;
1439 xsh->max_width = size.x;
1440 xsh->min_height = size.y;
1441 xsh->max_height = size.y;
1442 } else {
1443 xsh->flags = 0L;
1444 if (min_size != Size2()) {
1445 xsh->flags |= PMinSize;
1446 xsh->min_width = min_size.x;
1447 xsh->min_height = min_size.y;
1448 }
1449 if (max_size != Size2()) {
1450 xsh->flags |= PMaxSize;
1451 xsh->max_width = max_size.x;
1452 xsh->max_height = max_size.y;
1453 }
1454 }
1455
1456 XSetWMNormalHints(x11_display, x11_window, xsh);
1457 XFree(xsh);
1458
1459 current_videomode.resizable = p_enabled;
1460
1461 XFlush(x11_display);
1462 }
1463
is_window_resizable() const1464 bool OS_X11::is_window_resizable() const {
1465 return current_videomode.resizable;
1466 }
1467
set_window_minimized(bool p_enabled)1468 void OS_X11::set_window_minimized(bool p_enabled) {
1469 // Using ICCCM -- Inter-Client Communication Conventions Manual
1470 XEvent xev;
1471 Atom wm_change = XInternAtom(x11_display, "WM_CHANGE_STATE", False);
1472
1473 memset(&xev, 0, sizeof(xev));
1474 xev.type = ClientMessage;
1475 xev.xclient.window = x11_window;
1476 xev.xclient.message_type = wm_change;
1477 xev.xclient.format = 32;
1478 xev.xclient.data.l[0] = p_enabled ? WM_IconicState : WM_NormalState;
1479
1480 XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
1481
1482 Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);
1483 Atom wm_hidden = XInternAtom(x11_display, "_NET_WM_STATE_HIDDEN", False);
1484
1485 memset(&xev, 0, sizeof(xev));
1486 xev.type = ClientMessage;
1487 xev.xclient.window = x11_window;
1488 xev.xclient.message_type = wm_state;
1489 xev.xclient.format = 32;
1490 xev.xclient.data.l[0] = _NET_WM_STATE_ADD;
1491 xev.xclient.data.l[1] = wm_hidden;
1492
1493 XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
1494 }
1495
is_window_minimized() const1496 bool OS_X11::is_window_minimized() const {
1497 // Using ICCCM -- Inter-Client Communication Conventions Manual
1498 Atom property = XInternAtom(x11_display, "WM_STATE", True);
1499 Atom type;
1500 int format;
1501 unsigned long len;
1502 unsigned long remaining;
1503 unsigned char *data = NULL;
1504 bool retval = false;
1505
1506 int result = XGetWindowProperty(
1507 x11_display,
1508 x11_window,
1509 property,
1510 0,
1511 32,
1512 False,
1513 AnyPropertyType,
1514 &type,
1515 &format,
1516 &len,
1517 &remaining,
1518 &data);
1519
1520 if (result == Success) {
1521 long *state = (long *)data;
1522 if (state[0] == WM_IconicState) {
1523 retval = true;
1524 }
1525 XFree(data);
1526 }
1527
1528 return retval;
1529 }
1530
set_window_maximized(bool p_enabled)1531 void OS_X11::set_window_maximized(bool p_enabled) {
1532 if (is_window_maximized() == p_enabled)
1533 return;
1534
1535 // Using EWMH -- Extended Window Manager Hints
1536 XEvent xev;
1537 Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);
1538 Atom wm_max_horz = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_HORZ", False);
1539 Atom wm_max_vert = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_VERT", False);
1540
1541 memset(&xev, 0, sizeof(xev));
1542 xev.type = ClientMessage;
1543 xev.xclient.window = x11_window;
1544 xev.xclient.message_type = wm_state;
1545 xev.xclient.format = 32;
1546 xev.xclient.data.l[0] = p_enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
1547 xev.xclient.data.l[1] = wm_max_horz;
1548 xev.xclient.data.l[2] = wm_max_vert;
1549
1550 XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
1551
1552 if (p_enabled && is_window_maximize_allowed()) {
1553 // Wait for effective resizing (so the GLX context is too).
1554 // Give up after 0.5s, it's not going to happen on this WM.
1555 // https://github.com/godotengine/godot/issues/19978
1556 for (int attempt = 0; !is_window_maximized() && attempt < 50; attempt++) {
1557 usleep(10000);
1558 }
1559 }
1560
1561 maximized = p_enabled;
1562 }
1563
1564 // Just a helper to reduce code duplication in `is_window_maximize_allowed`
1565 // and `is_window_maximized`.
window_maximize_check(const char * p_atom_name) const1566 bool OS_X11::window_maximize_check(const char *p_atom_name) const {
1567 Atom property = XInternAtom(x11_display, p_atom_name, False);
1568 Atom type;
1569 int format;
1570 unsigned long len;
1571 unsigned long remaining;
1572 unsigned char *data = NULL;
1573 bool retval = false;
1574
1575 int result = XGetWindowProperty(
1576 x11_display,
1577 x11_window,
1578 property,
1579 0,
1580 1024,
1581 False,
1582 XA_ATOM,
1583 &type,
1584 &format,
1585 &len,
1586 &remaining,
1587 &data);
1588
1589 if (result == Success) {
1590 Atom *atoms = (Atom *)data;
1591 Atom wm_act_max_horz = XInternAtom(x11_display, "_NET_WM_ACTION_MAXIMIZE_HORZ", False);
1592 Atom wm_act_max_vert = XInternAtom(x11_display, "_NET_WM_ACTION_MAXIMIZE_VERT", False);
1593 bool found_wm_act_max_horz = false;
1594 bool found_wm_act_max_vert = false;
1595
1596 for (uint64_t i = 0; i < len; i++) {
1597 if (atoms[i] == wm_act_max_horz)
1598 found_wm_act_max_horz = true;
1599 if (atoms[i] == wm_act_max_vert)
1600 found_wm_act_max_vert = true;
1601
1602 if (found_wm_act_max_horz || found_wm_act_max_vert) {
1603 retval = true;
1604 break;
1605 }
1606 }
1607
1608 XFree(data);
1609 }
1610
1611 return retval;
1612 }
1613
is_window_maximize_allowed() const1614 bool OS_X11::is_window_maximize_allowed() const {
1615 return window_maximize_check("_NET_WM_ALLOWED_ACTIONS");
1616 }
1617
is_window_maximized() const1618 bool OS_X11::is_window_maximized() const {
1619 // Using EWMH -- Extended Window Manager Hints
1620 return window_maximize_check("_NET_WM_STATE");
1621 }
1622
set_window_always_on_top(bool p_enabled)1623 void OS_X11::set_window_always_on_top(bool p_enabled) {
1624 if (is_window_always_on_top() == p_enabled)
1625 return;
1626
1627 if (p_enabled && current_videomode.fullscreen) {
1628 // Fullscreen + Always-on-top requires a maximized window on some window managers (Metacity)
1629 set_window_maximized(true);
1630 }
1631 set_wm_above(p_enabled);
1632 if (!p_enabled && !current_videomode.fullscreen) {
1633 // Restore
1634 set_window_maximized(false);
1635 }
1636
1637 current_videomode.always_on_top = p_enabled;
1638 }
1639
is_window_always_on_top() const1640 bool OS_X11::is_window_always_on_top() const {
1641 return current_videomode.always_on_top;
1642 }
1643
is_window_focused() const1644 bool OS_X11::is_window_focused() const {
1645 return window_focused;
1646 }
1647
set_borderless_window(bool p_borderless)1648 void OS_X11::set_borderless_window(bool p_borderless) {
1649
1650 if (get_borderless_window() == p_borderless)
1651 return;
1652
1653 current_videomode.borderless_window = p_borderless;
1654
1655 Hints hints;
1656 Atom property;
1657 hints.flags = 2;
1658 hints.decorations = current_videomode.borderless_window ? 0 : 1;
1659 property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True);
1660 XChangeProperty(x11_display, x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5);
1661
1662 // Preserve window size
1663 set_window_size(Size2(current_videomode.width, current_videomode.height));
1664 }
1665
get_borderless_window()1666 bool OS_X11::get_borderless_window() {
1667
1668 bool borderless = current_videomode.borderless_window;
1669 Atom prop = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True);
1670 if (prop != None) {
1671
1672 Atom type;
1673 int format;
1674 unsigned long len;
1675 unsigned long remaining;
1676 unsigned char *data = NULL;
1677 if (XGetWindowProperty(x11_display, x11_window, prop, 0, sizeof(Hints), False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) {
1678 if (data && (format == 32) && (len >= 5)) {
1679 borderless = !((Hints *)data)->decorations;
1680 }
1681 XFree(data);
1682 }
1683 }
1684 return borderless;
1685 }
1686
request_attention()1687 void OS_X11::request_attention() {
1688 // Using EWMH -- Extended Window Manager Hints
1689 //
1690 // Sets the _NET_WM_STATE_DEMANDS_ATTENTION atom for WM_STATE
1691 // Will be unset by the window manager after user react on the request for attention
1692
1693 XEvent xev;
1694 Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);
1695 Atom wm_attention = XInternAtom(x11_display, "_NET_WM_STATE_DEMANDS_ATTENTION", False);
1696
1697 memset(&xev, 0, sizeof(xev));
1698 xev.type = ClientMessage;
1699 xev.xclient.window = x11_window;
1700 xev.xclient.message_type = wm_state;
1701 xev.xclient.format = 32;
1702 xev.xclient.data.l[0] = _NET_WM_STATE_ADD;
1703 xev.xclient.data.l[1] = wm_attention;
1704
1705 XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
1706 XFlush(x11_display);
1707 }
1708
get_key_modifier_state(unsigned int p_x11_state,Ref<InputEventWithModifiers> state)1709 void OS_X11::get_key_modifier_state(unsigned int p_x11_state, Ref<InputEventWithModifiers> state) {
1710
1711 state->set_shift((p_x11_state & ShiftMask));
1712 state->set_control((p_x11_state & ControlMask));
1713 state->set_alt((p_x11_state & Mod1Mask /*|| p_x11_state&Mod5Mask*/)); //altgr should not count as alt
1714 state->set_metakey((p_x11_state & Mod4Mask));
1715 }
1716
get_mouse_button_state(unsigned int p_x11_button,int p_x11_type)1717 unsigned int OS_X11::get_mouse_button_state(unsigned int p_x11_button, int p_x11_type) {
1718
1719 unsigned int mask = 1 << (p_x11_button - 1);
1720
1721 if (p_x11_type == ButtonPress) {
1722 last_button_state |= mask;
1723 } else {
1724 last_button_state &= ~mask;
1725 }
1726
1727 return last_button_state;
1728 }
1729
handle_key_event(XKeyEvent * p_event,bool p_echo)1730 void OS_X11::handle_key_event(XKeyEvent *p_event, bool p_echo) {
1731
1732 // X11 functions don't know what const is
1733 XKeyEvent *xkeyevent = p_event;
1734
1735 // This code was pretty difficult to write.
1736 // The docs stink and every toolkit seems to
1737 // do it in a different way.
1738
1739 /* Phase 1, obtain a proper keysym */
1740
1741 // This was also very difficult to figure out.
1742 // You'd expect you could just use Keysym provided by
1743 // XKeycodeToKeysym to obtain internationalized
1744 // input.. WRONG!!
1745 // you must use XLookupString (???) which not only wastes
1746 // cycles generating an unnecessary string, but also
1747 // still works in half the cases. (won't handle deadkeys)
1748 // For more complex input methods (deadkeys and more advanced)
1749 // you have to use XmbLookupString (??).
1750 // So.. then you have to chosse which of both results
1751 // you want to keep.
1752 // This is a real bizarreness and cpu waster.
1753
1754 KeySym keysym_keycode = 0; // keysym used to find a keycode
1755 KeySym keysym_unicode = 0; // keysym used to find unicode
1756
1757 // XLookupString returns keysyms usable as nice scancodes/
1758 char str[256 + 1];
1759 XKeyEvent xkeyevent_no_mod = *xkeyevent;
1760 xkeyevent_no_mod.state &= ~ShiftMask;
1761 xkeyevent_no_mod.state &= ~ControlMask;
1762 XLookupString(xkeyevent, str, 256, &keysym_unicode, NULL);
1763 XLookupString(&xkeyevent_no_mod, NULL, 0, &keysym_keycode, NULL);
1764
1765 // Meanwhile, XLookupString returns keysyms useful for unicode.
1766
1767 if (!xmbstring) {
1768 // keep a temporary buffer for the string
1769 xmbstring = (char *)memalloc(sizeof(char) * 8);
1770 xmblen = 8;
1771 }
1772
1773 if (xkeyevent->type == KeyPress && xic) {
1774
1775 Status status;
1776 #ifdef X_HAVE_UTF8_STRING
1777 int utf8len = 8;
1778 char *utf8string = (char *)memalloc(sizeof(char) * utf8len);
1779 int utf8bytes = Xutf8LookupString(xic, xkeyevent, utf8string,
1780 utf8len - 1, &keysym_unicode, &status);
1781 if (status == XBufferOverflow) {
1782 utf8len = utf8bytes + 1;
1783 utf8string = (char *)memrealloc(utf8string, utf8len);
1784 utf8bytes = Xutf8LookupString(xic, xkeyevent, utf8string,
1785 utf8len - 1, &keysym_unicode, &status);
1786 }
1787 utf8string[utf8bytes] = '\0';
1788
1789 if (status == XLookupChars) {
1790 bool keypress = xkeyevent->type == KeyPress;
1791 unsigned int keycode = KeyMappingX11::get_keycode(keysym_keycode);
1792 if (keycode >= 'a' && keycode <= 'z')
1793 keycode -= 'a' - 'A';
1794
1795 String tmp;
1796 tmp.parse_utf8(utf8string, utf8bytes);
1797 for (int i = 0; i < tmp.length(); i++) {
1798 Ref<InputEventKey> k;
1799 k.instance();
1800 if (keycode == 0 && tmp[i] == 0) {
1801 continue;
1802 }
1803
1804 get_key_modifier_state(xkeyevent->state, k);
1805
1806 k->set_unicode(tmp[i]);
1807
1808 k->set_pressed(keypress);
1809
1810 k->set_scancode(keycode);
1811
1812 k->set_echo(false);
1813
1814 if (k->get_scancode() == KEY_BACKTAB) {
1815 //make it consistent across platforms.
1816 k->set_scancode(KEY_TAB);
1817 k->set_shift(true);
1818 }
1819
1820 input->accumulate_input_event(k);
1821 }
1822 memfree(utf8string);
1823 return;
1824 }
1825 memfree(utf8string);
1826 #else
1827 do {
1828
1829 int mnbytes = XmbLookupString(xic, xkeyevent, xmbstring, xmblen - 1, &keysym_unicode, &status);
1830 xmbstring[mnbytes] = '\0';
1831
1832 if (status == XBufferOverflow) {
1833 xmblen = mnbytes + 1;
1834 xmbstring = (char *)memrealloc(xmbstring, xmblen);
1835 }
1836 } while (status == XBufferOverflow);
1837 #endif
1838 }
1839
1840 /* Phase 2, obtain a pigui keycode from the keysym */
1841
1842 // KeyMappingX11 just translated the X11 keysym to a PIGUI
1843 // keysym, so it works in all platforms the same.
1844
1845 unsigned int keycode = KeyMappingX11::get_keycode(keysym_keycode);
1846
1847 /* Phase 3, obtain a unicode character from the keysym */
1848
1849 // KeyMappingX11 also translates keysym to unicode.
1850 // It does a binary search on a table to translate
1851 // most properly.
1852 unsigned int unicode = keysym_unicode > 0 ? KeyMappingX11::get_unicode_from_keysym(keysym_unicode) : 0;
1853
1854 /* Phase 4, determine if event must be filtered */
1855
1856 // This seems to be a side-effect of using XIM.
1857 // XEventFilter looks like a core X11 function,
1858 // but it's actually just used to see if we must
1859 // ignore a deadkey, or events XIM determines
1860 // must not reach the actual gui.
1861 // Guess it was a design problem of the extension
1862
1863 bool keypress = xkeyevent->type == KeyPress;
1864
1865 if (keycode == 0 && unicode == 0)
1866 return;
1867
1868 /* Phase 5, determine modifier mask */
1869
1870 // No problems here, except I had no way to
1871 // know Mod1 was ALT and Mod4 was META (applekey/winkey)
1872 // just tried Mods until i found them.
1873
1874 //print_verbose("mod1: "+itos(xkeyevent->state&Mod1Mask)+" mod 5: "+itos(xkeyevent->state&Mod5Mask));
1875
1876 Ref<InputEventKey> k;
1877 k.instance();
1878
1879 get_key_modifier_state(xkeyevent->state, k);
1880
1881 /* Phase 6, determine echo character */
1882
1883 // Echo characters in X11 are a keyrelease and a keypress
1884 // one after the other with the (almot) same timestamp.
1885 // To detect them, i use XPeekEvent and check that their
1886 // difference in time is below a threshold.
1887
1888 if (xkeyevent->type != KeyPress) {
1889
1890 p_echo = false;
1891
1892 // make sure there are events pending,
1893 // so this call won't block.
1894 if (XPending(x11_display) > 0) {
1895 XEvent peek_event;
1896 XPeekEvent(x11_display, &peek_event);
1897
1898 // I'm using a threshold of 5 msecs,
1899 // since sometimes there seems to be a little
1900 // jitter. I'm still not convinced that all this approach
1901 // is correct, but the xorg developers are
1902 // not very helpful today.
1903
1904 ::Time tresh = ABSDIFF(peek_event.xkey.time, xkeyevent->time);
1905 if (peek_event.type == KeyPress && tresh < 5) {
1906 KeySym rk;
1907 XLookupString((XKeyEvent *)&peek_event, str, 256, &rk, NULL);
1908 if (rk == keysym_keycode) {
1909 XEvent event;
1910 XNextEvent(x11_display, &event); //erase next event
1911 handle_key_event((XKeyEvent *)&event, true);
1912 return; //ignore current, echo next
1913 }
1914 }
1915
1916 // use the time from peek_event so it always works
1917 }
1918
1919 // save the time to check for echo when keypress happens
1920 }
1921
1922 /* Phase 7, send event to Window */
1923
1924 k->set_pressed(keypress);
1925
1926 if (keycode >= 'a' && keycode <= 'z')
1927 keycode -= 'a' - 'A';
1928
1929 k->set_scancode(keycode);
1930 k->set_unicode(unicode);
1931 k->set_echo(p_echo);
1932
1933 if (k->get_scancode() == KEY_BACKTAB) {
1934 //make it consistent across platforms.
1935 k->set_scancode(KEY_TAB);
1936 k->set_shift(true);
1937 }
1938
1939 //don't set mod state if modifier keys are released by themselves
1940 //else event.is_action() will not work correctly here
1941 if (!k->is_pressed()) {
1942 if (k->get_scancode() == KEY_SHIFT)
1943 k->set_shift(false);
1944 else if (k->get_scancode() == KEY_CONTROL)
1945 k->set_control(false);
1946 else if (k->get_scancode() == KEY_ALT)
1947 k->set_alt(false);
1948 else if (k->get_scancode() == KEY_META)
1949 k->set_metakey(false);
1950 }
1951
1952 bool last_is_pressed = Input::get_singleton()->is_key_pressed(k->get_scancode());
1953 if (k->is_pressed()) {
1954 if (last_is_pressed) {
1955 k->set_echo(true);
1956 }
1957 }
1958
1959 //printf("key: %x\n",k->get_scancode());
1960 input->accumulate_input_event(k);
1961 }
1962
1963 struct Property {
1964 unsigned char *data;
1965 int format, nitems;
1966 Atom type;
1967 };
1968
read_property(Display * p_display,Window p_window,Atom p_property)1969 static Property read_property(Display *p_display, Window p_window, Atom p_property) {
1970
1971 Atom actual_type;
1972 int actual_format;
1973 unsigned long nitems;
1974 unsigned long bytes_after;
1975 unsigned char *ret = 0;
1976
1977 int read_bytes = 1024;
1978
1979 //Keep trying to read the property until there are no
1980 //bytes unread.
1981 do {
1982 if (ret != 0)
1983 XFree(ret);
1984
1985 XGetWindowProperty(p_display, p_window, p_property, 0, read_bytes, False, AnyPropertyType,
1986 &actual_type, &actual_format, &nitems, &bytes_after,
1987 &ret);
1988
1989 read_bytes *= 2;
1990
1991 } while (bytes_after != 0);
1992
1993 Property p = { ret, actual_format, (int)nitems, actual_type };
1994
1995 return p;
1996 }
1997
pick_target_from_list(Display * p_display,Atom * p_list,int p_count)1998 static Atom pick_target_from_list(Display *p_display, Atom *p_list, int p_count) {
1999
2000 static const char *target_type = "text/uri-list";
2001
2002 for (int i = 0; i < p_count; i++) {
2003
2004 Atom atom = p_list[i];
2005
2006 if (atom != None && String(XGetAtomName(p_display, atom)) == target_type)
2007 return atom;
2008 }
2009 return None;
2010 }
2011
pick_target_from_atoms(Display * p_disp,Atom p_t1,Atom p_t2,Atom p_t3)2012 static Atom pick_target_from_atoms(Display *p_disp, Atom p_t1, Atom p_t2, Atom p_t3) {
2013
2014 static const char *target_type = "text/uri-list";
2015 if (p_t1 != None && String(XGetAtomName(p_disp, p_t1)) == target_type)
2016 return p_t1;
2017
2018 if (p_t2 != None && String(XGetAtomName(p_disp, p_t2)) == target_type)
2019 return p_t2;
2020
2021 if (p_t3 != None && String(XGetAtomName(p_disp, p_t3)) == target_type)
2022 return p_t3;
2023
2024 return None;
2025 }
2026
_window_changed(XEvent * event)2027 void OS_X11::_window_changed(XEvent *event) {
2028
2029 if (xic) {
2030 // Not portable.
2031 set_ime_position(Point2(0, 1));
2032 }
2033 if ((event->xconfigure.width == current_videomode.width) &&
2034 (event->xconfigure.height == current_videomode.height))
2035 return;
2036
2037 current_videomode.width = event->xconfigure.width;
2038 current_videomode.height = event->xconfigure.height;
2039 }
2040
process_xevents()2041 void OS_X11::process_xevents() {
2042
2043 //printf("checking events %i\n", XPending(x11_display));
2044
2045 do_mouse_warp = false;
2046
2047 // Is the current mouse mode one where it needs to be grabbed.
2048 bool mouse_mode_grab = mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED;
2049
2050 xi.pressure = 0;
2051 xi.tilt = Vector2();
2052 xi.pressure_supported = false;
2053
2054 while (XPending(x11_display) > 0) {
2055 XEvent event;
2056 XNextEvent(x11_display, &event);
2057
2058 if (XFilterEvent(&event, None)) {
2059 continue;
2060 }
2061
2062 if (XGetEventData(x11_display, &event.xcookie)) {
2063
2064 if (event.xcookie.type == GenericEvent && event.xcookie.extension == xi.opcode) {
2065
2066 XIDeviceEvent *event_data = (XIDeviceEvent *)event.xcookie.data;
2067 int index = event_data->detail;
2068 Vector2 pos = Vector2(event_data->event_x, event_data->event_y);
2069
2070 switch (event_data->evtype) {
2071 case XI_HierarchyChanged:
2072 case XI_DeviceChanged: {
2073 refresh_device_info();
2074 } break;
2075 case XI_RawMotion: {
2076 XIRawEvent *raw_event = (XIRawEvent *)event_data;
2077 int device_id = raw_event->deviceid;
2078
2079 // Determine the axis used (called valuators in XInput for some forsaken reason)
2080 // Mask is a bitmask indicating which axes are involved.
2081 // We are interested in the values of axes 0 and 1.
2082 if (raw_event->valuators.mask_len <= 0) {
2083 break;
2084 }
2085
2086 const double *values = raw_event->raw_values;
2087
2088 double rel_x = 0.0;
2089 double rel_y = 0.0;
2090
2091 if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_ABSX)) {
2092 rel_x = *values;
2093 values++;
2094 }
2095
2096 if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_ABSY)) {
2097 rel_y = *values;
2098 values++;
2099 }
2100
2101 if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_PRESSURE)) {
2102 Map<int, Vector2>::Element *pen_pressure = xi.pen_pressure_range.find(device_id);
2103 if (pen_pressure) {
2104 Vector2 pen_pressure_range = pen_pressure->value();
2105 if (pen_pressure_range != Vector2()) {
2106 xi.pressure_supported = true;
2107 xi.pressure = (*values - pen_pressure_range[0]) /
2108 (pen_pressure_range[1] - pen_pressure_range[0]);
2109 }
2110 }
2111
2112 values++;
2113 }
2114
2115 if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_TILTX)) {
2116 Map<int, Vector2>::Element *pen_tilt_x = xi.pen_tilt_x_range.find(device_id);
2117 if (pen_tilt_x) {
2118 Vector2 pen_tilt_x_range = pen_tilt_x->value();
2119 if (pen_tilt_x_range != Vector2()) {
2120 xi.tilt.x = ((*values - pen_tilt_x_range[0]) / (pen_tilt_x_range[1] - pen_tilt_x_range[0])) * 2 - 1;
2121 }
2122 }
2123
2124 values++;
2125 }
2126
2127 if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_TILTY)) {
2128 Map<int, Vector2>::Element *pen_tilt_y = xi.pen_tilt_y_range.find(device_id);
2129 if (pen_tilt_y) {
2130 Vector2 pen_tilt_y_range = pen_tilt_y->value();
2131 if (pen_tilt_y_range != Vector2()) {
2132 xi.tilt.y = ((*values - pen_tilt_y_range[0]) / (pen_tilt_y_range[1] - pen_tilt_y_range[0])) * 2 - 1;
2133 }
2134 }
2135
2136 values++;
2137 }
2138
2139 // https://bugs.freedesktop.org/show_bug.cgi?id=71609
2140 // http://lists.libsdl.org/pipermail/commits-libsdl.org/2015-June/000282.html
2141 if (raw_event->time == xi.last_relative_time && rel_x == xi.relative_motion.x && rel_y == xi.relative_motion.y) {
2142 break; // Flush duplicate to avoid overly fast motion
2143 }
2144
2145 xi.old_raw_pos.x = xi.raw_pos.x;
2146 xi.old_raw_pos.y = xi.raw_pos.y;
2147 xi.raw_pos.x = rel_x;
2148 xi.raw_pos.y = rel_y;
2149
2150 Map<int, Vector2>::Element *abs_info = xi.absolute_devices.find(device_id);
2151
2152 if (abs_info) {
2153 // Absolute mode device
2154 Vector2 mult = abs_info->value();
2155
2156 xi.relative_motion.x += (xi.raw_pos.x - xi.old_raw_pos.x) * mult.x;
2157 xi.relative_motion.y += (xi.raw_pos.y - xi.old_raw_pos.y) * mult.y;
2158 } else {
2159 // Relative mode device
2160 xi.relative_motion.x = xi.raw_pos.x;
2161 xi.relative_motion.y = xi.raw_pos.y;
2162 }
2163
2164 xi.last_relative_time = raw_event->time;
2165 } break;
2166 #ifdef TOUCH_ENABLED
2167 case XI_TouchBegin: // Fall-through
2168 // Disabled hand-in-hand with the grabbing
2169 //XIAllowTouchEvents(x11_display, event_data->deviceid, event_data->detail, x11_window, XIAcceptTouch);
2170
2171 case XI_TouchEnd: {
2172
2173 bool is_begin = event_data->evtype == XI_TouchBegin;
2174
2175 Ref<InputEventScreenTouch> st;
2176 st.instance();
2177 st->set_index(index);
2178 st->set_position(pos);
2179 st->set_pressed(is_begin);
2180
2181 if (is_begin) {
2182 if (xi.state.has(index)) // Defensive
2183 break;
2184 xi.state[index] = pos;
2185 if (xi.state.size() == 1) {
2186 // X11 may send a motion event when a touch gesture begins, that would result
2187 // in a spurious mouse motion event being sent to Godot; remember it to be able to filter it out
2188 xi.mouse_pos_to_filter = pos;
2189 }
2190 input->accumulate_input_event(st);
2191 } else {
2192 if (!xi.state.has(index)) // Defensive
2193 break;
2194 xi.state.erase(index);
2195 input->accumulate_input_event(st);
2196 }
2197 } break;
2198
2199 case XI_TouchUpdate: {
2200
2201 Map<int, Vector2>::Element *curr_pos_elem = xi.state.find(index);
2202 if (!curr_pos_elem) { // Defensive
2203 break;
2204 }
2205
2206 if (curr_pos_elem->value() != pos) {
2207
2208 Ref<InputEventScreenDrag> sd;
2209 sd.instance();
2210 sd->set_index(index);
2211 sd->set_position(pos);
2212 sd->set_relative(pos - curr_pos_elem->value());
2213 input->accumulate_input_event(sd);
2214
2215 curr_pos_elem->value() = pos;
2216 }
2217 } break;
2218 #endif
2219 }
2220 }
2221 }
2222 XFreeEventData(x11_display, &event.xcookie);
2223
2224 switch (event.type) {
2225 case Expose:
2226 Main::force_redraw();
2227 break;
2228
2229 case NoExpose:
2230 minimized = true;
2231 break;
2232
2233 case VisibilityNotify: {
2234 XVisibilityEvent *visibility = (XVisibilityEvent *)&event;
2235 minimized = (visibility->state == VisibilityFullyObscured);
2236 } break;
2237 case LeaveNotify: {
2238 if (main_loop && !mouse_mode_grab)
2239 main_loop->notification(MainLoop::NOTIFICATION_WM_MOUSE_EXIT);
2240
2241 } break;
2242 case EnterNotify: {
2243 if (main_loop && !mouse_mode_grab)
2244 main_loop->notification(MainLoop::NOTIFICATION_WM_MOUSE_ENTER);
2245 } break;
2246 case FocusIn:
2247 minimized = false;
2248 window_has_focus = true;
2249 main_loop->notification(MainLoop::NOTIFICATION_WM_FOCUS_IN);
2250 window_focused = true;
2251
2252 if (mouse_mode_grab) {
2253 // Show and update the cursor if confined and the window regained focus.
2254 if (mouse_mode == MOUSE_MODE_CONFINED)
2255 XUndefineCursor(x11_display, x11_window);
2256 else if (mouse_mode == MOUSE_MODE_CAPTURED) // or re-hide it in captured mode
2257 XDefineCursor(x11_display, x11_window, null_cursor);
2258
2259 XGrabPointer(
2260 x11_display, x11_window, True,
2261 ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
2262 GrabModeAsync, GrabModeAsync, x11_window, None, CurrentTime);
2263 }
2264 #ifdef TOUCH_ENABLED
2265 // Grab touch devices to avoid OS gesture interference
2266 /*for (int i = 0; i < xi.touch_devices.size(); ++i) {
2267 XIGrabDevice(x11_display, xi.touch_devices[i], x11_window, CurrentTime, None, XIGrabModeAsync, XIGrabModeAsync, False, &xi.touch_event_mask);
2268 }*/
2269 #endif
2270 if (xic) {
2271 XSetICFocus(xic);
2272 }
2273 break;
2274
2275 case FocusOut:
2276 window_has_focus = false;
2277 input->release_pressed_events();
2278 main_loop->notification(MainLoop::NOTIFICATION_WM_FOCUS_OUT);
2279 window_focused = false;
2280
2281 if (mouse_mode_grab) {
2282 //dear X11, I try, I really try, but you never work, you do whathever you want.
2283 if (mouse_mode == MOUSE_MODE_CAPTURED) {
2284 // Show the cursor if we're in captured mode so it doesn't look weird.
2285 XUndefineCursor(x11_display, x11_window);
2286 }
2287 XUngrabPointer(x11_display, CurrentTime);
2288 }
2289 #ifdef TOUCH_ENABLED
2290 // Ungrab touch devices so input works as usual while we are unfocused
2291 /*for (int i = 0; i < xi.touch_devices.size(); ++i) {
2292 XIUngrabDevice(x11_display, xi.touch_devices[i], CurrentTime);
2293 }*/
2294
2295 // Release every pointer to avoid sticky points
2296 for (Map<int, Vector2>::Element *E = xi.state.front(); E; E = E->next()) {
2297
2298 Ref<InputEventScreenTouch> st;
2299 st.instance();
2300 st->set_index(E->key());
2301 st->set_position(E->get());
2302 input->accumulate_input_event(st);
2303 }
2304 xi.state.clear();
2305 #endif
2306 if (xic) {
2307 XUnsetICFocus(xic);
2308 }
2309 break;
2310
2311 case ConfigureNotify:
2312 _window_changed(&event);
2313 break;
2314 case ButtonPress:
2315 case ButtonRelease: {
2316
2317 /* exit in case of a mouse button press */
2318 last_timestamp = event.xbutton.time;
2319 if (mouse_mode == MOUSE_MODE_CAPTURED) {
2320 event.xbutton.x = last_mouse_pos.x;
2321 event.xbutton.y = last_mouse_pos.y;
2322 }
2323
2324 Ref<InputEventMouseButton> mb;
2325 mb.instance();
2326
2327 get_key_modifier_state(event.xbutton.state, mb);
2328 mb->set_button_index(event.xbutton.button);
2329 if (mb->get_button_index() == 2)
2330 mb->set_button_index(3);
2331 else if (mb->get_button_index() == 3)
2332 mb->set_button_index(2);
2333 mb->set_button_mask(get_mouse_button_state(mb->get_button_index(), event.xbutton.type));
2334 mb->set_position(Vector2(event.xbutton.x, event.xbutton.y));
2335 mb->set_global_position(mb->get_position());
2336
2337 mb->set_pressed((event.type == ButtonPress));
2338
2339 if (event.type == ButtonPress) {
2340
2341 uint64_t diff = get_ticks_usec() / 1000 - last_click_ms;
2342
2343 if (mb->get_button_index() == last_click_button_index) {
2344
2345 if (diff < 400 && Point2(last_click_pos).distance_to(Point2(event.xbutton.x, event.xbutton.y)) < 5) {
2346
2347 last_click_ms = 0;
2348 last_click_pos = Point2(-100, -100);
2349 last_click_button_index = -1;
2350 mb->set_doubleclick(true);
2351 }
2352
2353 } else if (mb->get_button_index() < 4 || mb->get_button_index() > 7) {
2354 last_click_button_index = mb->get_button_index();
2355 }
2356
2357 if (!mb->is_doubleclick()) {
2358 last_click_ms += diff;
2359 last_click_pos = Point2(event.xbutton.x, event.xbutton.y);
2360 }
2361 }
2362
2363 input->accumulate_input_event(mb);
2364
2365 } break;
2366 case MotionNotify: {
2367
2368 // The X11 API requires filtering one-by-one through the motion
2369 // notify events, in order to figure out which event is the one
2370 // generated by warping the mouse pointer.
2371
2372 while (true) {
2373 if (mouse_mode == MOUSE_MODE_CAPTURED && event.xmotion.x == current_videomode.width / 2 && event.xmotion.y == current_videomode.height / 2) {
2374 //this is likely the warp event since it was warped here
2375 center = Vector2(event.xmotion.x, event.xmotion.y);
2376 break;
2377 }
2378
2379 if (XPending(x11_display) > 0) {
2380 XEvent tevent;
2381 XPeekEvent(x11_display, &tevent);
2382 if (tevent.type == MotionNotify) {
2383 XNextEvent(x11_display, &event);
2384 } else {
2385 break;
2386 }
2387 } else {
2388 break;
2389 }
2390 }
2391
2392 last_timestamp = event.xmotion.time;
2393
2394 // Motion is also simple.
2395 // A little hack is in order
2396 // to be able to send relative motion events.
2397 Point2 pos(event.xmotion.x, event.xmotion.y);
2398
2399 // Avoidance of spurious mouse motion (see handling of touch)
2400 bool filter = false;
2401 // Adding some tolerance to match better Point2i to Vector2
2402 if (xi.state.size() && Vector2(pos).distance_squared_to(xi.mouse_pos_to_filter) < 2) {
2403 filter = true;
2404 }
2405 // Invalidate to avoid filtering a possible legitimate similar event coming later
2406 xi.mouse_pos_to_filter = Vector2(1e10, 1e10);
2407 if (filter) {
2408 break;
2409 }
2410
2411 if (mouse_mode == MOUSE_MODE_CAPTURED) {
2412 if (xi.relative_motion.x == 0 && xi.relative_motion.y == 0) {
2413 break;
2414 }
2415
2416 Point2i new_center = pos;
2417 pos = last_mouse_pos + xi.relative_motion;
2418 center = new_center;
2419 do_mouse_warp = window_has_focus; // warp the cursor if we're focused in
2420 }
2421
2422 if (!last_mouse_pos_valid) {
2423
2424 last_mouse_pos = pos;
2425 last_mouse_pos_valid = true;
2426 }
2427
2428 // Hackish but relative mouse motion is already handled in the RawMotion event.
2429 // RawMotion does not provide the absolute mouse position (whereas MotionNotify does).
2430 // Therefore, RawMotion cannot be the authority on absolute mouse position.
2431 // RawMotion provides more precision than MotionNotify, which doesn't sense subpixel motion.
2432 // Therefore, MotionNotify cannot be the authority on relative mouse motion.
2433 // This means we need to take a combined approach...
2434 Point2 rel;
2435
2436 // Only use raw input if in capture mode. Otherwise use the classic behavior.
2437 if (mouse_mode == MOUSE_MODE_CAPTURED) {
2438 rel = xi.relative_motion;
2439 } else {
2440 rel = pos - last_mouse_pos;
2441 }
2442
2443 // Reset to prevent lingering motion
2444 xi.relative_motion.x = 0;
2445 xi.relative_motion.y = 0;
2446
2447 if (mouse_mode == MOUSE_MODE_CAPTURED) {
2448 pos = Point2i(current_videomode.width / 2, current_videomode.height / 2);
2449 }
2450
2451 Ref<InputEventMouseMotion> mm;
2452 mm.instance();
2453
2454 if (xi.pressure_supported) {
2455 mm->set_pressure(xi.pressure);
2456 } else {
2457 mm->set_pressure((get_mouse_button_state() & (1 << (BUTTON_LEFT - 1))) ? 1.0f : 0.0f);
2458 }
2459 mm->set_tilt(xi.tilt);
2460
2461 // Make the absolute position integral so it doesn't look _too_ weird :)
2462 Point2i posi(pos);
2463
2464 get_key_modifier_state(event.xmotion.state, mm);
2465 mm->set_button_mask(get_mouse_button_state());
2466 mm->set_position(posi);
2467 mm->set_global_position(posi);
2468 input->set_mouse_position(posi);
2469 mm->set_speed(input->get_last_mouse_speed());
2470
2471 mm->set_relative(rel);
2472
2473 last_mouse_pos = pos;
2474
2475 // printf("rel: %d,%d\n", rel.x, rel.y );
2476 // Don't propagate the motion event unless we have focus
2477 // this is so that the relative motion doesn't get messed up
2478 // after we regain focus.
2479 if (window_has_focus || !mouse_mode_grab)
2480 input->accumulate_input_event(mm);
2481
2482 } break;
2483 case KeyPress:
2484 case KeyRelease: {
2485
2486 last_timestamp = event.xkey.time;
2487
2488 // key event is a little complex, so
2489 // it will be handled in its own function.
2490 handle_key_event((XKeyEvent *)&event);
2491 } break;
2492 case SelectionRequest: {
2493
2494 XSelectionRequestEvent *req;
2495 XEvent e, respond;
2496 e = event;
2497
2498 req = &(e.xselectionrequest);
2499 if (req->target == XInternAtom(x11_display, "UTF8_STRING", 0) ||
2500 req->target == XInternAtom(x11_display, "COMPOUND_TEXT", 0) ||
2501 req->target == XInternAtom(x11_display, "TEXT", 0) ||
2502 req->target == XA_STRING ||
2503 req->target == XInternAtom(x11_display, "text/plain;charset=utf-8", 0) ||
2504 req->target == XInternAtom(x11_display, "text/plain", 0)) {
2505 CharString clip = OS::get_clipboard().utf8();
2506 XChangeProperty(x11_display,
2507 req->requestor,
2508 req->property,
2509 req->target,
2510 8,
2511 PropModeReplace,
2512 (unsigned char *)clip.get_data(),
2513 clip.length());
2514 respond.xselection.property = req->property;
2515 } else if (req->target == XInternAtom(x11_display, "TARGETS", 0)) {
2516
2517 Atom data[7];
2518 data[0] = XInternAtom(x11_display, "TARGETS", 0);
2519 data[1] = XInternAtom(x11_display, "UTF8_STRING", 0);
2520 data[2] = XInternAtom(x11_display, "COMPOUND_TEXT", 0);
2521 data[3] = XInternAtom(x11_display, "TEXT", 0);
2522 data[4] = XA_STRING;
2523 data[5] = XInternAtom(x11_display, "text/plain;charset=utf-8", 0);
2524 data[6] = XInternAtom(x11_display, "text/plain", 0);
2525
2526 XChangeProperty(x11_display,
2527 req->requestor,
2528 req->property,
2529 XA_ATOM,
2530 32,
2531 PropModeReplace,
2532 (unsigned char *)&data,
2533 sizeof(data) / sizeof(data[0]));
2534 respond.xselection.property = req->property;
2535
2536 } else {
2537 char *targetname = XGetAtomName(x11_display, req->target);
2538 printf("No Target '%s'\n", targetname);
2539 if (targetname)
2540 XFree(targetname);
2541 respond.xselection.property = None;
2542 }
2543
2544 respond.xselection.type = SelectionNotify;
2545 respond.xselection.display = req->display;
2546 respond.xselection.requestor = req->requestor;
2547 respond.xselection.selection = req->selection;
2548 respond.xselection.target = req->target;
2549 respond.xselection.time = req->time;
2550 XSendEvent(x11_display, req->requestor, True, NoEventMask, &respond);
2551 XFlush(x11_display);
2552 } break;
2553
2554 case SelectionNotify:
2555
2556 if (event.xselection.target == requested) {
2557
2558 Property p = read_property(x11_display, x11_window, XInternAtom(x11_display, "PRIMARY", 0));
2559
2560 Vector<String> files = String((char *)p.data).split("\n", false);
2561 for (int i = 0; i < files.size(); i++) {
2562 files.write[i] = files[i].replace("file://", "").http_unescape().strip_edges();
2563 }
2564 main_loop->drop_files(files);
2565
2566 //Reply that all is well.
2567 XClientMessageEvent m;
2568 memset(&m, 0, sizeof(m));
2569 m.type = ClientMessage;
2570 m.display = x11_display;
2571 m.window = xdnd_source_window;
2572 m.message_type = xdnd_finished;
2573 m.format = 32;
2574 m.data.l[0] = x11_window;
2575 m.data.l[1] = 1;
2576 m.data.l[2] = xdnd_action_copy; //We only ever copy.
2577
2578 XSendEvent(x11_display, xdnd_source_window, False, NoEventMask, (XEvent *)&m);
2579 }
2580 break;
2581
2582 case ClientMessage:
2583
2584 if ((unsigned int)event.xclient.data.l[0] == (unsigned int)wm_delete)
2585 main_loop->notification(MainLoop::NOTIFICATION_WM_QUIT_REQUEST);
2586
2587 else if ((unsigned int)event.xclient.message_type == (unsigned int)xdnd_enter) {
2588
2589 //File(s) have been dragged over the window, check for supported target (text/uri-list)
2590 xdnd_version = (event.xclient.data.l[1] >> 24);
2591 Window source = event.xclient.data.l[0];
2592 bool more_than_3 = event.xclient.data.l[1] & 1;
2593 if (more_than_3) {
2594 Property p = read_property(x11_display, source, XInternAtom(x11_display, "XdndTypeList", False));
2595 requested = pick_target_from_list(x11_display, (Atom *)p.data, p.nitems);
2596 } else
2597 requested = pick_target_from_atoms(x11_display, event.xclient.data.l[2], event.xclient.data.l[3], event.xclient.data.l[4]);
2598 } else if ((unsigned int)event.xclient.message_type == (unsigned int)xdnd_position) {
2599
2600 //xdnd position event, reply with an XDND status message
2601 //just depending on type of data for now
2602 XClientMessageEvent m;
2603 memset(&m, 0, sizeof(m));
2604 m.type = ClientMessage;
2605 m.display = event.xclient.display;
2606 m.window = event.xclient.data.l[0];
2607 m.message_type = xdnd_status;
2608 m.format = 32;
2609 m.data.l[0] = x11_window;
2610 m.data.l[1] = (requested != None);
2611 m.data.l[2] = 0; //empty rectangle
2612 m.data.l[3] = 0;
2613 m.data.l[4] = xdnd_action_copy;
2614
2615 XSendEvent(x11_display, event.xclient.data.l[0], False, NoEventMask, (XEvent *)&m);
2616 XFlush(x11_display);
2617 } else if ((unsigned int)event.xclient.message_type == (unsigned int)xdnd_drop) {
2618
2619 if (requested != None) {
2620 xdnd_source_window = event.xclient.data.l[0];
2621 if (xdnd_version >= 1)
2622 XConvertSelection(x11_display, xdnd_selection, requested, XInternAtom(x11_display, "PRIMARY", 0), x11_window, event.xclient.data.l[2]);
2623 else
2624 XConvertSelection(x11_display, xdnd_selection, requested, XInternAtom(x11_display, "PRIMARY", 0), x11_window, CurrentTime);
2625 } else {
2626 //Reply that we're not interested.
2627 XClientMessageEvent m;
2628 memset(&m, 0, sizeof(m));
2629 m.type = ClientMessage;
2630 m.display = event.xclient.display;
2631 m.window = event.xclient.data.l[0];
2632 m.message_type = xdnd_finished;
2633 m.format = 32;
2634 m.data.l[0] = x11_window;
2635 m.data.l[1] = 0;
2636 m.data.l[2] = None; //Failed.
2637 XSendEvent(x11_display, event.xclient.data.l[0], False, NoEventMask, (XEvent *)&m);
2638 }
2639 }
2640 break;
2641 default:
2642 break;
2643 }
2644 }
2645
2646 XFlush(x11_display);
2647
2648 if (do_mouse_warp) {
2649
2650 XWarpPointer(x11_display, None, x11_window,
2651 0, 0, 0, 0, (int)current_videomode.width / 2, (int)current_videomode.height / 2);
2652
2653 /*
2654 Window root, child;
2655 int root_x, root_y;
2656 int win_x, win_y;
2657 unsigned int mask;
2658 XQueryPointer( x11_display, x11_window, &root, &child, &root_x, &root_y, &win_x, &win_y, &mask );
2659
2660 printf("Root: %d,%d\n", root_x, root_y);
2661 printf("Win: %d,%d\n", win_x, win_y);
2662 */
2663 }
2664
2665 input->flush_accumulated_events();
2666 }
2667
get_main_loop() const2668 MainLoop *OS_X11::get_main_loop() const {
2669
2670 return main_loop;
2671 }
2672
delete_main_loop()2673 void OS_X11::delete_main_loop() {
2674
2675 if (main_loop)
2676 memdelete(main_loop);
2677 main_loop = NULL;
2678 }
2679
set_main_loop(MainLoop * p_main_loop)2680 void OS_X11::set_main_loop(MainLoop *p_main_loop) {
2681
2682 main_loop = p_main_loop;
2683 input->set_main_loop(p_main_loop);
2684 }
2685
can_draw() const2686 bool OS_X11::can_draw() const {
2687
2688 return !minimized;
2689 };
2690
set_clipboard(const String & p_text)2691 void OS_X11::set_clipboard(const String &p_text) {
2692
2693 OS::set_clipboard(p_text);
2694
2695 XSetSelectionOwner(x11_display, XA_PRIMARY, x11_window, CurrentTime);
2696 XSetSelectionOwner(x11_display, XInternAtom(x11_display, "CLIPBOARD", 0), x11_window, CurrentTime);
2697 };
2698
_get_clipboard_impl(Atom p_source,Window x11_window,::Display * x11_display,String p_internal_clipboard,Atom target)2699 static String _get_clipboard_impl(Atom p_source, Window x11_window, ::Display *x11_display, String p_internal_clipboard, Atom target) {
2700
2701 String ret;
2702
2703 Atom type;
2704 Atom selection = XA_PRIMARY;
2705 int format, result;
2706 unsigned long len, bytes_left, dummy;
2707 unsigned char *data;
2708 Window Sown = XGetSelectionOwner(x11_display, p_source);
2709
2710 if (Sown == x11_window) {
2711
2712 return p_internal_clipboard;
2713 };
2714
2715 if (Sown != None) {
2716 XConvertSelection(x11_display, p_source, target, selection,
2717 x11_window, CurrentTime);
2718 XFlush(x11_display);
2719 while (true) {
2720 XEvent event;
2721 XNextEvent(x11_display, &event);
2722 if (event.type == SelectionNotify && event.xselection.requestor == x11_window) {
2723 break;
2724 };
2725 };
2726
2727 //
2728 // Do not get any data, see how much data is there
2729 //
2730 XGetWindowProperty(x11_display, x11_window,
2731 selection, // Tricky..
2732 0, 0, // offset - len
2733 0, // Delete 0==FALSE
2734 AnyPropertyType, //flag
2735 &type, // return type
2736 &format, // return format
2737 &len, &bytes_left, //that
2738 &data);
2739 // DATA is There
2740 if (bytes_left > 0) {
2741 result = XGetWindowProperty(x11_display, x11_window,
2742 selection, 0, bytes_left, 0,
2743 AnyPropertyType, &type, &format,
2744 &len, &dummy, &data);
2745 if (result == Success) {
2746 ret.parse_utf8((const char *)data);
2747 } else
2748 printf("FAIL\n");
2749 XFree(data);
2750 }
2751 }
2752
2753 return ret;
2754 }
2755
_get_clipboard(Atom p_source,Window x11_window,::Display * x11_display,String p_internal_clipboard)2756 static String _get_clipboard(Atom p_source, Window x11_window, ::Display *x11_display, String p_internal_clipboard) {
2757 String ret;
2758 Atom utf8_atom = XInternAtom(x11_display, "UTF8_STRING", True);
2759 if (utf8_atom != None) {
2760 ret = _get_clipboard_impl(p_source, x11_window, x11_display, p_internal_clipboard, utf8_atom);
2761 }
2762 if (ret == "") {
2763 ret = _get_clipboard_impl(p_source, x11_window, x11_display, p_internal_clipboard, XA_STRING);
2764 }
2765 return ret;
2766 }
2767
get_clipboard() const2768 String OS_X11::get_clipboard() const {
2769
2770 String ret;
2771 ret = _get_clipboard(XInternAtom(x11_display, "CLIPBOARD", 0), x11_window, x11_display, OS::get_clipboard());
2772
2773 if (ret == "") {
2774 ret = _get_clipboard(XA_PRIMARY, x11_window, x11_display, OS::get_clipboard());
2775 };
2776
2777 return ret;
2778 }
2779
get_name() const2780 String OS_X11::get_name() const {
2781
2782 return "X11";
2783 }
2784
shell_open(String p_uri)2785 Error OS_X11::shell_open(String p_uri) {
2786
2787 Error ok;
2788 List<String> args;
2789 args.push_back(p_uri);
2790 ok = execute("xdg-open", args, false);
2791 if (ok == OK)
2792 return OK;
2793 ok = execute("gnome-open", args, false);
2794 if (ok == OK)
2795 return OK;
2796 ok = execute("kde-open", args, false);
2797 return ok;
2798 }
2799
_check_internal_feature_support(const String & p_feature)2800 bool OS_X11::_check_internal_feature_support(const String &p_feature) {
2801
2802 return p_feature == "pc";
2803 }
2804
get_config_path() const2805 String OS_X11::get_config_path() const {
2806
2807 if (has_environment("XDG_CONFIG_HOME")) {
2808 return get_environment("XDG_CONFIG_HOME");
2809 } else if (has_environment("HOME")) {
2810 return get_environment("HOME").plus_file(".config");
2811 } else {
2812 return ".";
2813 }
2814 }
2815
get_data_path() const2816 String OS_X11::get_data_path() const {
2817
2818 if (has_environment("XDG_DATA_HOME")) {
2819 return get_environment("XDG_DATA_HOME");
2820 } else if (has_environment("HOME")) {
2821 return get_environment("HOME").plus_file(".local/share");
2822 } else {
2823 return get_config_path();
2824 }
2825 }
2826
get_cache_path() const2827 String OS_X11::get_cache_path() const {
2828
2829 if (has_environment("XDG_CACHE_HOME")) {
2830 return get_environment("XDG_CACHE_HOME");
2831 } else if (has_environment("HOME")) {
2832 return get_environment("HOME").plus_file(".cache");
2833 } else {
2834 return get_config_path();
2835 }
2836 }
2837
get_system_dir(SystemDir p_dir) const2838 String OS_X11::get_system_dir(SystemDir p_dir) const {
2839
2840 String xdgparam;
2841
2842 switch (p_dir) {
2843 case SYSTEM_DIR_DESKTOP: {
2844
2845 xdgparam = "DESKTOP";
2846 } break;
2847 case SYSTEM_DIR_DCIM: {
2848
2849 xdgparam = "PICTURES";
2850
2851 } break;
2852 case SYSTEM_DIR_DOCUMENTS: {
2853
2854 xdgparam = "DOCUMENTS";
2855
2856 } break;
2857 case SYSTEM_DIR_DOWNLOADS: {
2858
2859 xdgparam = "DOWNLOAD";
2860
2861 } break;
2862 case SYSTEM_DIR_MOVIES: {
2863
2864 xdgparam = "VIDEOS";
2865
2866 } break;
2867 case SYSTEM_DIR_MUSIC: {
2868
2869 xdgparam = "MUSIC";
2870
2871 } break;
2872 case SYSTEM_DIR_PICTURES: {
2873
2874 xdgparam = "PICTURES";
2875
2876 } break;
2877 case SYSTEM_DIR_RINGTONES: {
2878
2879 xdgparam = "MUSIC";
2880
2881 } break;
2882 }
2883
2884 String pipe;
2885 List<String> arg;
2886 arg.push_back(xdgparam);
2887 Error err = const_cast<OS_X11 *>(this)->execute("xdg-user-dir", arg, true, NULL, &pipe);
2888 if (err != OK)
2889 return ".";
2890 return pipe.strip_edges();
2891 }
2892
move_window_to_foreground()2893 void OS_X11::move_window_to_foreground() {
2894
2895 XEvent xev;
2896 Atom net_active_window = XInternAtom(x11_display, "_NET_ACTIVE_WINDOW", False);
2897
2898 memset(&xev, 0, sizeof(xev));
2899 xev.type = ClientMessage;
2900 xev.xclient.window = x11_window;
2901 xev.xclient.message_type = net_active_window;
2902 xev.xclient.format = 32;
2903 xev.xclient.data.l[0] = 1;
2904 xev.xclient.data.l[1] = CurrentTime;
2905
2906 XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
2907 XFlush(x11_display);
2908 }
2909
set_cursor_shape(CursorShape p_shape)2910 void OS_X11::set_cursor_shape(CursorShape p_shape) {
2911
2912 ERR_FAIL_INDEX(p_shape, CURSOR_MAX);
2913
2914 if (p_shape == current_cursor) {
2915 return;
2916 }
2917
2918 if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) {
2919 if (cursors[p_shape] != None) {
2920 XDefineCursor(x11_display, x11_window, cursors[p_shape]);
2921 } else if (cursors[CURSOR_ARROW] != None) {
2922 XDefineCursor(x11_display, x11_window, cursors[CURSOR_ARROW]);
2923 }
2924 }
2925
2926 current_cursor = p_shape;
2927 }
2928
get_cursor_shape() const2929 OS::CursorShape OS_X11::get_cursor_shape() const {
2930
2931 return current_cursor;
2932 }
2933
set_custom_mouse_cursor(const RES & p_cursor,CursorShape p_shape,const Vector2 & p_hotspot)2934 void OS_X11::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) {
2935
2936 if (p_cursor.is_valid()) {
2937
2938 Map<CursorShape, Vector<Variant> >::Element *cursor_c = cursors_cache.find(p_shape);
2939
2940 if (cursor_c) {
2941 if (cursor_c->get()[0] == p_cursor && cursor_c->get()[1] == p_hotspot) {
2942 set_cursor_shape(p_shape);
2943 return;
2944 }
2945
2946 cursors_cache.erase(p_shape);
2947 }
2948
2949 Ref<Texture> texture = p_cursor;
2950 Ref<AtlasTexture> atlas_texture = p_cursor;
2951 Ref<Image> image;
2952 Size2 texture_size;
2953 Rect2 atlas_rect;
2954
2955 if (texture.is_valid()) {
2956 image = texture->get_data();
2957 }
2958
2959 if (!image.is_valid() && atlas_texture.is_valid()) {
2960 texture = atlas_texture->get_atlas();
2961
2962 atlas_rect.size.width = texture->get_width();
2963 atlas_rect.size.height = texture->get_height();
2964 atlas_rect.position.x = atlas_texture->get_region().position.x;
2965 atlas_rect.position.y = atlas_texture->get_region().position.y;
2966
2967 texture_size.width = atlas_texture->get_region().size.x;
2968 texture_size.height = atlas_texture->get_region().size.y;
2969 } else if (image.is_valid()) {
2970 texture_size.width = texture->get_width();
2971 texture_size.height = texture->get_height();
2972 }
2973
2974 ERR_FAIL_COND(!texture.is_valid());
2975 ERR_FAIL_COND(p_hotspot.x < 0 || p_hotspot.y < 0);
2976 ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256);
2977 ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height);
2978
2979 image = texture->get_data();
2980
2981 ERR_FAIL_COND(!image.is_valid());
2982
2983 // Create the cursor structure
2984 XcursorImage *cursor_image = XcursorImageCreate(texture_size.width, texture_size.height);
2985 XcursorUInt image_size = texture_size.width * texture_size.height;
2986 XcursorDim size = sizeof(XcursorPixel) * image_size;
2987
2988 cursor_image->version = 1;
2989 cursor_image->size = size;
2990 cursor_image->xhot = p_hotspot.x;
2991 cursor_image->yhot = p_hotspot.y;
2992
2993 // allocate memory to contain the whole file
2994 cursor_image->pixels = (XcursorPixel *)memalloc(size);
2995
2996 image->lock();
2997
2998 for (XcursorPixel index = 0; index < image_size; index++) {
2999 int row_index = floor(index / texture_size.width) + atlas_rect.position.y;
3000 int column_index = (index % int(texture_size.width)) + atlas_rect.position.x;
3001
3002 if (atlas_texture.is_valid()) {
3003 column_index = MIN(column_index, atlas_rect.size.width - 1);
3004 row_index = MIN(row_index, atlas_rect.size.height - 1);
3005 }
3006
3007 *(cursor_image->pixels + index) = image->get_pixel(column_index, row_index).to_argb32();
3008 }
3009
3010 image->unlock();
3011
3012 ERR_FAIL_COND(cursor_image->pixels == NULL);
3013
3014 // Save it for a further usage
3015 cursors[p_shape] = XcursorImageLoadCursor(x11_display, cursor_image);
3016
3017 Vector<Variant> params;
3018 params.push_back(p_cursor);
3019 params.push_back(p_hotspot);
3020 cursors_cache.insert(p_shape, params);
3021
3022 if (p_shape == current_cursor) {
3023 if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) {
3024 XDefineCursor(x11_display, x11_window, cursors[p_shape]);
3025 }
3026 }
3027
3028 memfree(cursor_image->pixels);
3029 XcursorImageDestroy(cursor_image);
3030 } else {
3031 // Reset to default system cursor
3032 if (img[p_shape]) {
3033 cursors[p_shape] = XcursorImageLoadCursor(x11_display, img[p_shape]);
3034 }
3035
3036 CursorShape c = current_cursor;
3037 current_cursor = CURSOR_MAX;
3038 set_cursor_shape(c);
3039
3040 cursors_cache.erase(p_shape);
3041 }
3042 }
3043
release_rendering_thread()3044 void OS_X11::release_rendering_thread() {
3045
3046 #if defined(OPENGL_ENABLED)
3047 context_gl->release_current();
3048 #endif
3049 }
3050
make_rendering_thread()3051 void OS_X11::make_rendering_thread() {
3052
3053 #if defined(OPENGL_ENABLED)
3054 context_gl->make_current();
3055 #endif
3056 }
3057
swap_buffers()3058 void OS_X11::swap_buffers() {
3059
3060 #if defined(OPENGL_ENABLED)
3061 context_gl->swap_buffers();
3062 #endif
3063 }
3064
alert(const String & p_alert,const String & p_title)3065 void OS_X11::alert(const String &p_alert, const String &p_title) {
3066 const char *message_programs[] = { "zenity", "kdialog", "Xdialog", "xmessage" };
3067
3068 String path = get_environment("PATH");
3069 Vector<String> path_elems = path.split(":", false);
3070 String program;
3071
3072 for (int i = 0; i < path_elems.size(); i++) {
3073 for (uint64_t k = 0; k < sizeof(message_programs) / sizeof(char *); k++) {
3074 String tested_path = path_elems[i].plus_file(message_programs[k]);
3075
3076 if (FileAccess::exists(tested_path)) {
3077 program = tested_path;
3078 break;
3079 }
3080 }
3081
3082 if (program.length())
3083 break;
3084 }
3085
3086 List<String> args;
3087
3088 if (program.ends_with("zenity")) {
3089 args.push_back("--error");
3090 args.push_back("--width");
3091 args.push_back("500");
3092 args.push_back("--title");
3093 args.push_back(p_title);
3094 args.push_back("--text");
3095 args.push_back(p_alert);
3096 }
3097
3098 if (program.ends_with("kdialog")) {
3099 args.push_back("--error");
3100 args.push_back(p_alert);
3101 args.push_back("--title");
3102 args.push_back(p_title);
3103 }
3104
3105 if (program.ends_with("Xdialog")) {
3106 args.push_back("--title");
3107 args.push_back(p_title);
3108 args.push_back("--msgbox");
3109 args.push_back(p_alert);
3110 args.push_back("0");
3111 args.push_back("0");
3112 }
3113
3114 if (program.ends_with("xmessage")) {
3115 args.push_back("-center");
3116 args.push_back("-title");
3117 args.push_back(p_title);
3118 args.push_back(p_alert);
3119 }
3120
3121 if (program.length()) {
3122 execute(program, args, true);
3123 } else {
3124 print_line(p_alert);
3125 }
3126 }
3127
3128 bool g_set_icon_error = false;
set_icon_errorhandler(Display * dpy,XErrorEvent * ev)3129 int set_icon_errorhandler(Display *dpy, XErrorEvent *ev) {
3130 g_set_icon_error = true;
3131 return 0;
3132 }
3133
set_icon(const Ref<Image> & p_icon)3134 void OS_X11::set_icon(const Ref<Image> &p_icon) {
3135 int (*oldHandler)(Display *, XErrorEvent *) = XSetErrorHandler(&set_icon_errorhandler);
3136
3137 Atom net_wm_icon = XInternAtom(x11_display, "_NET_WM_ICON", False);
3138
3139 if (p_icon.is_valid()) {
3140 Ref<Image> img = p_icon->duplicate();
3141 img->convert(Image::FORMAT_RGBA8);
3142
3143 while (true) {
3144 int w = img->get_width();
3145 int h = img->get_height();
3146
3147 if (g_set_icon_error) {
3148 g_set_icon_error = false;
3149
3150 WARN_PRINT("Icon too large, attempting to resize icon.");
3151
3152 int new_width, new_height;
3153 if (w > h) {
3154 new_width = w / 2;
3155 new_height = h * new_width / w;
3156 } else {
3157 new_height = h / 2;
3158 new_width = w * new_height / h;
3159 }
3160
3161 w = new_width;
3162 h = new_height;
3163
3164 if (!w || !h) {
3165 WARN_PRINT("Unable to set icon.");
3166 break;
3167 }
3168
3169 img->resize(w, h, Image::INTERPOLATE_CUBIC);
3170 }
3171
3172 // We're using long to have wordsize (32Bit build -> 32 Bits, 64 Bit build -> 64 Bits
3173 Vector<long> pd;
3174
3175 pd.resize(2 + w * h);
3176
3177 pd.write[0] = w;
3178 pd.write[1] = h;
3179
3180 PoolVector<uint8_t>::Read r = img->get_data().read();
3181
3182 long *wr = &pd.write[2];
3183 uint8_t const *pr = r.ptr();
3184
3185 for (int i = 0; i < w * h; i++) {
3186 long v = 0;
3187 // A R G B
3188 v |= pr[3] << 24 | pr[0] << 16 | pr[1] << 8 | pr[2];
3189 *wr++ = v;
3190 pr += 4;
3191 }
3192
3193 XChangeProperty(x11_display, x11_window, net_wm_icon, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)pd.ptr(), pd.size());
3194
3195 if (!g_set_icon_error)
3196 break;
3197 }
3198 } else {
3199 XDeleteProperty(x11_display, x11_window, net_wm_icon);
3200 }
3201
3202 XFlush(x11_display);
3203 XSetErrorHandler(oldHandler);
3204 }
3205
force_process_input()3206 void OS_X11::force_process_input() {
3207 process_xevents(); // get rid of pending events
3208 #ifdef JOYDEV_ENABLED
3209 joypad->process_joypads();
3210 #endif
3211 }
3212
run()3213 void OS_X11::run() {
3214
3215 force_quit = false;
3216
3217 if (!main_loop)
3218 return;
3219
3220 main_loop->init();
3221
3222 //uint64_t last_ticks=get_ticks_usec();
3223
3224 //int frames=0;
3225 //uint64_t frame=0;
3226
3227 while (!force_quit) {
3228
3229 process_xevents(); // get rid of pending events
3230 #ifdef JOYDEV_ENABLED
3231 joypad->process_joypads();
3232 #endif
3233 if (Main::iteration())
3234 break;
3235 };
3236
3237 main_loop->finish();
3238 }
3239
is_joy_known(int p_device)3240 bool OS_X11::is_joy_known(int p_device) {
3241 return input->is_joy_mapped(p_device);
3242 }
3243
get_joy_guid(int p_device) const3244 String OS_X11::get_joy_guid(int p_device) const {
3245 return input->get_joy_guid_remapped(p_device);
3246 }
3247
_set_use_vsync(bool p_enable)3248 void OS_X11::_set_use_vsync(bool p_enable) {
3249 #if defined(OPENGL_ENABLED)
3250 if (context_gl)
3251 context_gl->set_use_vsync(p_enable);
3252 #endif
3253 }
3254 /*
3255 bool OS_X11::is_vsync_enabled() const {
3256
3257 if (context_gl)
3258 return context_gl->is_using_vsync();
3259
3260 return true;
3261 }
3262 */
set_context(int p_context)3263 void OS_X11::set_context(int p_context) {
3264
3265 XClassHint *classHint = XAllocClassHint();
3266
3267 if (classHint) {
3268
3269 CharString name_str;
3270 switch (p_context) {
3271 case CONTEXT_EDITOR:
3272 name_str = "Godot_Editor";
3273 break;
3274 case CONTEXT_PROJECTMAN:
3275 name_str = "Godot_ProjectList";
3276 break;
3277 case CONTEXT_ENGINE:
3278 name_str = "Godot_Engine";
3279 break;
3280 }
3281
3282 CharString class_str;
3283 if (p_context == CONTEXT_ENGINE) {
3284 String config_name = GLOBAL_GET("application/config/name");
3285 if (config_name.length() == 0) {
3286 class_str = "Godot_Engine";
3287 } else {
3288 class_str = config_name.utf8();
3289 }
3290 } else {
3291 class_str = "Godot";
3292 }
3293
3294 classHint->res_class = class_str.ptrw();
3295 classHint->res_name = name_str.ptrw();
3296
3297 XSetClassHint(x11_display, x11_window, classHint);
3298 XFree(classHint);
3299 }
3300 }
3301
get_power_state()3302 OS::PowerState OS_X11::get_power_state() {
3303 return power_manager->get_power_state();
3304 }
3305
get_power_seconds_left()3306 int OS_X11::get_power_seconds_left() {
3307 return power_manager->get_power_seconds_left();
3308 }
3309
get_power_percent_left()3310 int OS_X11::get_power_percent_left() {
3311 return power_manager->get_power_percent_left();
3312 }
3313
disable_crash_handler()3314 void OS_X11::disable_crash_handler() {
3315 crash_handler.disable();
3316 }
3317
is_disable_crash_handler() const3318 bool OS_X11::is_disable_crash_handler() const {
3319 return crash_handler.is_disabled();
3320 }
3321
get_mountpoint(const String & p_path)3322 static String get_mountpoint(const String &p_path) {
3323 struct stat s;
3324 if (stat(p_path.utf8().get_data(), &s)) {
3325 return "";
3326 }
3327
3328 #ifdef HAVE_MNTENT
3329 dev_t dev = s.st_dev;
3330 FILE *fd = setmntent("/proc/mounts", "r");
3331 if (!fd) {
3332 return "";
3333 }
3334
3335 struct mntent mnt;
3336 char buf[1024];
3337 size_t buflen = 1024;
3338 while (getmntent_r(fd, &mnt, buf, buflen)) {
3339 if (!stat(mnt.mnt_dir, &s) && s.st_dev == dev) {
3340 endmntent(fd);
3341 return String(mnt.mnt_dir);
3342 }
3343 }
3344
3345 endmntent(fd);
3346 #endif
3347 return "";
3348 }
3349
move_to_trash(const String & p_path)3350 Error OS_X11::move_to_trash(const String &p_path) {
3351 String trash_can = "";
3352 String mnt = get_mountpoint(p_path);
3353
3354 // If there is a directory "[Mountpoint]/.Trash-[UID]/files", use it as the trash can.
3355 if (mnt != "") {
3356 String path(mnt + "/.Trash-" + itos(getuid()) + "/files");
3357 struct stat s;
3358 if (!stat(path.utf8().get_data(), &s)) {
3359 trash_can = path;
3360 }
3361 }
3362
3363 // Otherwise, if ${XDG_DATA_HOME} is defined, use "${XDG_DATA_HOME}/Trash/files" as the trash can.
3364 if (trash_can == "") {
3365 char *dhome = getenv("XDG_DATA_HOME");
3366 if (dhome) {
3367 trash_can = String(dhome) + "/Trash/files";
3368 }
3369 }
3370
3371 // Otherwise, if ${HOME} is defined, use "${HOME}/.local/share/Trash/files" as the trash can.
3372 if (trash_can == "") {
3373 char *home = getenv("HOME");
3374 if (home) {
3375 trash_can = String(home) + "/.local/share/Trash/files";
3376 }
3377 }
3378
3379 // Issue an error if none of the previous locations is appropriate for the trash can.
3380 if (trash_can == "") {
3381 ERR_PRINTS("move_to_trash: Could not determine the trash can location");
3382 return FAILED;
3383 }
3384
3385 // Create needed directories for decided trash can location.
3386 DirAccess *dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
3387 Error err = dir_access->make_dir_recursive(trash_can);
3388 memdelete(dir_access);
3389
3390 // Issue an error if trash can is not created proprely.
3391 if (err != OK) {
3392 ERR_PRINTS("move_to_trash: Could not create the trash can \"" + trash_can + "\"");
3393 return err;
3394 }
3395
3396 // The trash can is successfully created, now move the given resource to it.
3397 // Do not use DirAccess:rename() because it can't move files across multiple mountpoints.
3398 List<String> mv_args;
3399 mv_args.push_back(p_path);
3400 mv_args.push_back(trash_can);
3401 int retval;
3402 err = execute("mv", mv_args, true, NULL, NULL, &retval);
3403
3404 // Issue an error if "mv" failed to move the given resource to the trash can.
3405 if (err != OK || retval != 0) {
3406 ERR_PRINTS("move_to_trash: Could not move the resource \"" + p_path + "\" to the trash can \"" + trash_can + "\"");
3407 return FAILED;
3408 }
3409
3410 return OK;
3411 }
3412
get_latin_keyboard_variant() const3413 OS::LatinKeyboardVariant OS_X11::get_latin_keyboard_variant() const {
3414
3415 XkbDescRec *xkbdesc = XkbAllocKeyboard();
3416 ERR_FAIL_COND_V(!xkbdesc, LATIN_KEYBOARD_QWERTY);
3417
3418 XkbGetNames(x11_display, XkbSymbolsNameMask, xkbdesc);
3419 ERR_FAIL_COND_V(!xkbdesc->names, LATIN_KEYBOARD_QWERTY);
3420 ERR_FAIL_COND_V(!xkbdesc->names->symbols, LATIN_KEYBOARD_QWERTY);
3421
3422 char *layout = XGetAtomName(x11_display, xkbdesc->names->symbols);
3423 ERR_FAIL_COND_V(!layout, LATIN_KEYBOARD_QWERTY);
3424
3425 Vector<String> info = String(layout).split("+");
3426 ERR_FAIL_INDEX_V(1, info.size(), LATIN_KEYBOARD_QWERTY);
3427
3428 if (info[1].find("colemak") != -1) {
3429 return LATIN_KEYBOARD_COLEMAK;
3430 } else if (info[1].find("qwertz") != -1) {
3431 return LATIN_KEYBOARD_QWERTZ;
3432 } else if (info[1].find("azerty") != -1) {
3433 return LATIN_KEYBOARD_AZERTY;
3434 } else if (info[1].find("qzerty") != -1) {
3435 return LATIN_KEYBOARD_QZERTY;
3436 } else if (info[1].find("dvorak") != -1) {
3437 return LATIN_KEYBOARD_DVORAK;
3438 } else if (info[1].find("neo") != -1) {
3439 return LATIN_KEYBOARD_NEO;
3440 }
3441
3442 return LATIN_KEYBOARD_QWERTY;
3443 }
3444
keyboard_get_layout_count() const3445 int OS_X11::keyboard_get_layout_count() const {
3446 int _group_count = 0;
3447 XkbDescRec *kbd = XkbAllocKeyboard();
3448 if (kbd) {
3449 kbd->dpy = x11_display;
3450 XkbGetControls(x11_display, XkbAllControlsMask, kbd);
3451 XkbGetNames(x11_display, XkbSymbolsNameMask, kbd);
3452
3453 const Atom *groups = kbd->names->groups;
3454 if (kbd->ctrls != NULL) {
3455 _group_count = kbd->ctrls->num_groups;
3456 } else {
3457 while (_group_count < XkbNumKbdGroups && groups[_group_count] != None) {
3458 _group_count++;
3459 }
3460 }
3461 XkbFreeKeyboard(kbd, 0, true);
3462 }
3463 return _group_count;
3464 }
3465
keyboard_get_current_layout() const3466 int OS_X11::keyboard_get_current_layout() const {
3467 XkbStateRec state;
3468 XkbGetState(x11_display, XkbUseCoreKbd, &state);
3469 return state.group;
3470 }
3471
keyboard_set_current_layout(int p_index)3472 void OS_X11::keyboard_set_current_layout(int p_index) {
3473 ERR_FAIL_INDEX(p_index, keyboard_get_layout_count());
3474 XkbLockGroup(x11_display, XkbUseCoreKbd, p_index);
3475 }
3476
keyboard_get_layout_language(int p_index) const3477 String OS_X11::keyboard_get_layout_language(int p_index) const {
3478 String ret;
3479 XkbDescRec *kbd = XkbAllocKeyboard();
3480 if (kbd) {
3481 kbd->dpy = x11_display;
3482 XkbGetControls(x11_display, XkbAllControlsMask, kbd);
3483 XkbGetNames(x11_display, XkbSymbolsNameMask, kbd);
3484 XkbGetNames(x11_display, XkbGroupNamesMask, kbd);
3485
3486 int _group_count = 0;
3487 const Atom *groups = kbd->names->groups;
3488 if (kbd->ctrls != NULL) {
3489 _group_count = kbd->ctrls->num_groups;
3490 } else {
3491 while (_group_count < XkbNumKbdGroups && groups[_group_count] != None) {
3492 _group_count++;
3493 }
3494 }
3495
3496 Atom names = kbd->names->symbols;
3497 if (names != None) {
3498 char *name = XGetAtomName(x11_display, names);
3499 Vector<String> info = String(name).split("+");
3500 if (p_index >= 0 && p_index < _group_count) {
3501 if (p_index + 1 < info.size()) {
3502 ret = info[p_index + 1]; // Skip "pc" at the start and "inet"/"group" at the end of symbols.
3503 } else {
3504 ret = "en"; // No symbol for layout fallback to "en".
3505 }
3506 } else {
3507 ERR_PRINT("Index " + itos(p_index) + "is out of bounds (" + itos(_group_count) + ").");
3508 }
3509 XFree(name);
3510 }
3511 XkbFreeKeyboard(kbd, 0, true);
3512 }
3513 return ret.substr(0, 2);
3514 }
3515
keyboard_get_layout_name(int p_index) const3516 String OS_X11::keyboard_get_layout_name(int p_index) const {
3517 String ret;
3518 XkbDescRec *kbd = XkbAllocKeyboard();
3519 if (kbd) {
3520 kbd->dpy = x11_display;
3521 XkbGetControls(x11_display, XkbAllControlsMask, kbd);
3522 XkbGetNames(x11_display, XkbSymbolsNameMask, kbd);
3523 XkbGetNames(x11_display, XkbGroupNamesMask, kbd);
3524
3525 int _group_count = 0;
3526 const Atom *groups = kbd->names->groups;
3527 if (kbd->ctrls != NULL) {
3528 _group_count = kbd->ctrls->num_groups;
3529 } else {
3530 while (_group_count < XkbNumKbdGroups && groups[_group_count] != None) {
3531 _group_count++;
3532 }
3533 }
3534
3535 if (p_index >= 0 && p_index < _group_count) {
3536 char *full_name = XGetAtomName(x11_display, groups[p_index]);
3537 ret.parse_utf8(full_name);
3538 XFree(full_name);
3539 } else {
3540 ERR_PRINT("Index " + itos(p_index) + "is out of bounds (" + itos(_group_count) + ").");
3541 }
3542 XkbFreeKeyboard(kbd, 0, true);
3543 }
3544 return ret;
3545 }
3546
update_real_mouse_position()3547 void OS_X11::update_real_mouse_position() {
3548 Window root_return, child_return;
3549 int root_x, root_y, win_x, win_y;
3550 unsigned int mask_return;
3551
3552 Bool xquerypointer_result = XQueryPointer(x11_display, x11_window, &root_return, &child_return, &root_x, &root_y,
3553 &win_x, &win_y, &mask_return);
3554
3555 if (xquerypointer_result) {
3556 if (win_x > 0 && win_y > 0 && win_x <= current_videomode.width && win_y <= current_videomode.height) {
3557
3558 last_mouse_pos.x = win_x;
3559 last_mouse_pos.y = win_y;
3560 last_mouse_pos_valid = true;
3561 input->set_mouse_position(last_mouse_pos);
3562 }
3563 }
3564 }
3565
OS_X11()3566 OS_X11::OS_X11() {
3567
3568 #ifdef PULSEAUDIO_ENABLED
3569 AudioDriverManager::add_driver(&driver_pulseaudio);
3570 #endif
3571
3572 #ifdef ALSA_ENABLED
3573 AudioDriverManager::add_driver(&driver_alsa);
3574 #endif
3575
3576 xi.opcode = 0;
3577 xi.last_relative_time = 0;
3578 layered_window = false;
3579 minimized = false;
3580 window_focused = true;
3581 xim_style = 0L;
3582 mouse_mode = MOUSE_MODE_VISIBLE;
3583 last_position_before_fs = Vector2();
3584 }
3585