1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_VIDEO_DRIVER_X11
24
25#include <sys/types.h>
26#include <sys/time.h>
27#include <signal.h>
28#include <unistd.h>
29#include <limits.h> // For INT_MAX
30
31#include "SDL_x11video.h"
32#include "SDL_x11pen.h"
33#include "SDL_x11touch.h"
34#include "SDL_x11xinput2.h"
35#include "SDL_x11xfixes.h"
36#include "SDL_x11settings.h"
37#include "../SDL_clipboard_c.h"
38#include "SDL_x11xsync.h"
39#include "../../core/unix/SDL_poll.h"
40#include "../../events/SDL_events_c.h"
41#include "../../events/SDL_mouse_c.h"
42#include "../../events/SDL_touch_c.h"
43#include "../../core/linux/SDL_system_theme.h"
44#include "../SDL_sysvideo.h"
45
46#include <stdio.h>
47
48#if 0
49#define DEBUG_XEVENTS
50#endif
51
52#ifndef _NET_WM_MOVERESIZE_SIZE_TOPLEFT
53#define _NET_WM_MOVERESIZE_SIZE_TOPLEFT 0
54#endif
55
56#ifndef _NET_WM_MOVERESIZE_SIZE_TOP
57#define _NET_WM_MOVERESIZE_SIZE_TOP 1
58#endif
59
60#ifndef _NET_WM_MOVERESIZE_SIZE_TOPRIGHT
61#define _NET_WM_MOVERESIZE_SIZE_TOPRIGHT 2
62#endif
63
64#ifndef _NET_WM_MOVERESIZE_SIZE_RIGHT
65#define _NET_WM_MOVERESIZE_SIZE_RIGHT 3
66#endif
67
68#ifndef _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT
69#define _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT 4
70#endif
71
72#ifndef _NET_WM_MOVERESIZE_SIZE_BOTTOM
73#define _NET_WM_MOVERESIZE_SIZE_BOTTOM 5
74#endif
75
76#ifndef _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT
77#define _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT 6
78#endif
79
80#ifndef _NET_WM_MOVERESIZE_SIZE_LEFT
81#define _NET_WM_MOVERESIZE_SIZE_LEFT 7
82#endif
83
84#ifndef _NET_WM_MOVERESIZE_MOVE
85#define _NET_WM_MOVERESIZE_MOVE 8
86#endif
87
88typedef struct
89{
90 unsigned char *data;
91 int format, count;
92 Atom type;
93} SDL_x11Prop;
94
95/* Reads property
96 Must call X11_XFree on results
97 */
98static void X11_ReadProperty(SDL_x11Prop *p, Display *disp, Window w, Atom prop)
99{
100 unsigned char *ret = NULL;
101 Atom type;
102 int fmt;
103 unsigned long count;
104 unsigned long bytes_left;
105 int bytes_fetch = 0;
106
107 do {
108 if (ret) {
109 X11_XFree(ret);
110 }
111 X11_XGetWindowProperty(disp, w, prop, 0, bytes_fetch, False, AnyPropertyType, &type, &fmt, &count, &bytes_left, &ret);
112 bytes_fetch += bytes_left;
113 } while (bytes_left != 0);
114
115 p->data = ret;
116 p->format = fmt;
117 p->count = count;
118 p->type = type;
119}
120
121/* Find text-uri-list in a list of targets and return it's atom
122 if available, else return None */
123static Atom X11_PickTarget(Display *disp, Atom list[], int list_count)
124{
125 Atom request = None;
126 char *name;
127 int i;
128 for (i = 0; i < list_count && request == None; i++) {
129 name = X11_XGetAtomName(disp, list[i]);
130 // Preferred MIME targets
131 if ((SDL_strcmp("text/uri-list", name) == 0) ||
132 (SDL_strcmp("text/plain;charset=utf-8", name) == 0) ||
133 (SDL_strcmp("UTF8_STRING", name) == 0)) {
134 request = list[i];
135 }
136 // Fallback MIME targets
137 if ((SDL_strcmp("text/plain", name) == 0) ||
138 (SDL_strcmp("TEXT", name) == 0)) {
139 if (request == None) {
140 request = list[i];
141 }
142 }
143 X11_XFree(name);
144 }
145 return request;
146}
147
148/* Wrapper for X11_PickTarget for a maximum of three targets, a special
149 case in the Xdnd protocol */
150static Atom X11_PickTargetFromAtoms(Display *disp, Atom a0, Atom a1, Atom a2)
151{
152 int count = 0;
153 Atom atom[3];
154 if (a0 != None) {
155 atom[count++] = a0;
156 }
157 if (a1 != None) {
158 atom[count++] = a1;
159 }
160 if (a2 != None) {
161 atom[count++] = a2;
162 }
163 return X11_PickTarget(disp, atom, count);
164}
165
166struct KeyRepeatCheckData
167{
168 XEvent *event;
169 bool found;
170};
171
172static Bool X11_KeyRepeatCheckIfEvent(Display *display, XEvent *chkev,
173 XPointer arg)
174{
175 struct KeyRepeatCheckData *d = (struct KeyRepeatCheckData *)arg;
176 if (chkev->type == KeyPress && chkev->xkey.keycode == d->event->xkey.keycode && chkev->xkey.time - d->event->xkey.time < 2) {
177 d->found = true;
178 }
179 return False;
180}
181
182/* Check to see if this is a repeated key.
183 (idea shamelessly lifted from GII -- thanks guys! :)
184 */
185static bool X11_KeyRepeat(Display *display, XEvent *event)
186{
187 XEvent dummyev;
188 struct KeyRepeatCheckData d;
189 d.event = event;
190 d.found = false;
191 if (X11_XPending(display)) {
192 X11_XCheckIfEvent(display, &dummyev, X11_KeyRepeatCheckIfEvent, (XPointer)&d);
193 }
194 return d.found;
195}
196
197static bool X11_IsWheelEvent(Display *display, int button, int *xticks, int *yticks)
198{
199 /* according to the xlib docs, no specific mouse wheel events exist.
200 However, the defacto standard is that the vertical wheel is X buttons
201 4 (up) and 5 (down) and a horizontal wheel is 6 (left) and 7 (right). */
202
203 // Xlib defines "Button1" through 5, so we just use literals here.
204 switch (button) {
205 case 4:
206 *yticks = 1;
207 return true;
208 case 5:
209 *yticks = -1;
210 return true;
211 case 6:
212 *xticks = 1;
213 return true;
214 case 7:
215 *xticks = -1;
216 return true;
217 default:
218 break;
219 }
220 return false;
221}
222
223// An X11 event hook
224static SDL_X11EventHook g_X11EventHook = NULL;
225static void *g_X11EventHookData = NULL;
226
227void SDL_SetX11EventHook(SDL_X11EventHook callback, void *userdata)
228{
229 g_X11EventHook = callback;
230 g_X11EventHookData = userdata;
231}
232
233#ifdef SDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS
234static void X11_HandleGenericEvent(SDL_VideoDevice *_this, XEvent *xev)
235{
236 SDL_VideoData *videodata = _this->internal;
237
238 // event is a union, so cookie == &event, but this is type safe.
239 XGenericEventCookie *cookie = &xev->xcookie;
240 if (X11_XGetEventData(videodata->display, cookie)) {
241 if (!g_X11EventHook || g_X11EventHook(g_X11EventHookData, xev)) {
242 X11_HandleXinput2Event(_this, cookie);
243 }
244 X11_XFreeEventData(videodata->display, cookie);
245 }
246}
247#endif // SDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS
248
249static void X11_UpdateSystemKeyModifiers(SDL_VideoData *viddata)
250{
251 Window junk_window;
252 int x, y;
253
254 X11_XQueryPointer(viddata->display, DefaultRootWindow(viddata->display), &junk_window, &junk_window, &x, &y, &x, &y, &viddata->xkb.xkb_modifiers);
255}
256
257static void X11_ReconcileModifiers(SDL_VideoData *viddata)
258{
259 const Uint32 xk_modifiers = viddata->xkb.xkb_modifiers;
260
261 /* If a modifier was activated by a keypress, it will be tied to the
262 * specific left/right key that initiated it. Otherwise, the ambiguous
263 * left/right combo is used.
264 */
265 if (xk_modifiers & ShiftMask) {
266 if (!(viddata->xkb.sdl_modifiers & SDL_KMOD_SHIFT)) {
267 viddata->xkb.sdl_modifiers |= SDL_KMOD_SHIFT;
268 }
269 } else {
270 viddata->xkb.sdl_modifiers &= ~SDL_KMOD_SHIFT;
271 }
272
273 if (xk_modifiers & ControlMask) {
274 if (!(viddata->xkb.sdl_modifiers & SDL_KMOD_CTRL)) {
275 viddata->xkb.sdl_modifiers |= SDL_KMOD_CTRL;
276 }
277 } else {
278 viddata->xkb.sdl_modifiers &= ~SDL_KMOD_CTRL;
279 }
280
281 // Mod1 is used for the Alt keys
282 if (xk_modifiers & Mod1Mask) {
283 if (!(viddata->xkb.sdl_modifiers & SDL_KMOD_ALT)) {
284 viddata->xkb.sdl_modifiers |= SDL_KMOD_ALT;
285 }
286 } else {
287 viddata->xkb.sdl_modifiers &= ~SDL_KMOD_ALT;
288 }
289
290 // Mod4 is used for the Super (aka GUI/Logo) keys.
291 if (xk_modifiers & Mod4Mask) {
292 if (!(viddata->xkb.sdl_modifiers & SDL_KMOD_GUI)) {
293 viddata->xkb.sdl_modifiers |= SDL_KMOD_GUI;
294 }
295 } else {
296 viddata->xkb.sdl_modifiers &= ~SDL_KMOD_GUI;
297 }
298
299 // Mod3 is typically Level 5 shift.
300 if (xk_modifiers & Mod3Mask) {
301 viddata->xkb.sdl_modifiers |= SDL_KMOD_LEVEL5;
302 } else {
303 viddata->xkb.sdl_modifiers &= ~SDL_KMOD_LEVEL5;
304 }
305
306 // Mod5 is typically Level 3 shift (aka AltGr).
307 if (xk_modifiers & Mod5Mask) {
308 viddata->xkb.sdl_modifiers |= SDL_KMOD_MODE;
309 } else {
310 viddata->xkb.sdl_modifiers &= ~SDL_KMOD_MODE;
311 }
312
313 if (xk_modifiers & LockMask) {
314 viddata->xkb.sdl_modifiers |= SDL_KMOD_CAPS;
315 } else {
316 viddata->xkb.sdl_modifiers &= ~SDL_KMOD_CAPS;
317 }
318
319 if (xk_modifiers & viddata->xkb.numlock_mask) {
320 viddata->xkb.sdl_modifiers |= SDL_KMOD_NUM;
321 } else {
322 viddata->xkb.sdl_modifiers &= ~SDL_KMOD_NUM;
323 }
324
325 if (xk_modifiers & viddata->xkb.scrolllock_mask) {
326 viddata->xkb.sdl_modifiers |= SDL_KMOD_SCROLL;
327 } else {
328 viddata->xkb.sdl_modifiers &= ~SDL_KMOD_SCROLL;
329 }
330
331 SDL_SetModState(viddata->xkb.sdl_modifiers);
332}
333
334static void X11_HandleModifierKeys(SDL_VideoData *viddata, SDL_Scancode scancode, bool pressed, bool allow_reconciliation)
335{
336 const SDL_Keycode keycode = SDL_GetKeyFromScancode(scancode, SDL_KMOD_NONE, false);
337 SDL_Keymod mod = SDL_KMOD_NONE;
338 bool reconcile = false;
339
340 /* SDL clients expect modifier state to be activated at the same time as the
341 * source keypress, so we set pressed modifier state with the usual modifier
342 * keys here, as the explicit modifier event won't arrive until after the
343 * keypress event. If this is wrong, it will be corrected when the explicit
344 * modifier state is checked.
345 */
346 switch (keycode) {
347 case SDLK_LSHIFT:
348 mod = SDL_KMOD_LSHIFT;
349 break;
350 case SDLK_RSHIFT:
351 mod = SDL_KMOD_RSHIFT;
352 break;
353 case SDLK_LCTRL:
354 mod = SDL_KMOD_LCTRL;
355 break;
356 case SDLK_RCTRL:
357 mod = SDL_KMOD_RCTRL;
358 break;
359 case SDLK_LALT:
360 mod = SDL_KMOD_LALT;
361 break;
362 case SDLK_RALT:
363 mod = SDL_KMOD_RALT;
364 break;
365 case SDLK_LGUI:
366 mod = SDL_KMOD_LGUI;
367 break;
368 case SDLK_RGUI:
369 mod = SDL_KMOD_RGUI;
370 break;
371 case SDLK_MODE:
372 mod = SDL_KMOD_MODE;
373 break;
374 case SDLK_LEVEL5_SHIFT:
375 mod = SDL_KMOD_LEVEL5;
376 break;
377 case SDLK_CAPSLOCK:
378 case SDLK_NUMLOCKCLEAR:
379 case SDLK_SCROLLLOCK:
380 {
381 /* For locking modifier keys, query the lock state directly, or we may have to wait until the next
382 * key press event to know if a lock was actually activated from the key event.
383 */
384 unsigned int cur_mask = viddata->xkb.xkb_modifiers;
385 X11_UpdateSystemKeyModifiers(viddata);
386
387 if (viddata->xkb.xkb_modifiers & LockMask) {
388 cur_mask |= LockMask;
389 } else {
390 cur_mask &= ~LockMask;
391 }
392 if (viddata->xkb.xkb_modifiers & viddata->xkb.numlock_mask) {
393 cur_mask |= viddata->xkb.numlock_mask;
394 } else {
395 cur_mask &= ~viddata->xkb.numlock_mask;
396 }
397 if (viddata->xkb.xkb_modifiers & viddata->xkb.scrolllock_mask) {
398 cur_mask |= viddata->xkb.scrolllock_mask;
399 } else {
400 cur_mask &= ~viddata->xkb.scrolllock_mask;
401 }
402
403 viddata->xkb.xkb_modifiers = cur_mask;
404 } SDL_FALLTHROUGH;
405 default:
406 reconcile = true;
407 break;
408 }
409
410 if (pressed) {
411 viddata->xkb.sdl_modifiers |= mod;
412 } else {
413 viddata->xkb.sdl_modifiers &= ~mod;
414 }
415
416 if (allow_reconciliation) {
417 if (reconcile) {
418 X11_ReconcileModifiers(viddata);
419 } else {
420 SDL_SetModState(viddata->xkb.sdl_modifiers);
421 }
422 }
423}
424
425void X11_ReconcileKeyboardState(SDL_VideoDevice *_this)
426{
427 SDL_VideoData *videodata = _this->internal;
428 Display *display = videodata->display;
429 char keys[32];
430 int keycode;
431 const bool *keyboardState;
432
433 X11_XQueryKeymap(display, keys);
434
435 keyboardState = SDL_GetKeyboardState(0);
436 for (keycode = 0; keycode < SDL_arraysize(videodata->key_layout); ++keycode) {
437 SDL_Scancode scancode = videodata->key_layout[keycode];
438 bool x11KeyPressed = (keys[keycode / 8] & (1 << (keycode % 8))) != 0;
439 bool sdlKeyPressed = keyboardState[scancode];
440
441 if (x11KeyPressed && !sdlKeyPressed) {
442 // Only update modifier state for keys that are pressed in another application
443 switch (SDL_GetKeyFromScancode(scancode, SDL_KMOD_NONE, false)) {
444 case SDLK_LCTRL:
445 case SDLK_RCTRL:
446 case SDLK_LSHIFT:
447 case SDLK_RSHIFT:
448 case SDLK_LALT:
449 case SDLK_RALT:
450 case SDLK_LGUI:
451 case SDLK_RGUI:
452 case SDLK_MODE:
453 case SDLK_LEVEL5_SHIFT:
454 X11_HandleModifierKeys(videodata, scancode, true, false);
455 SDL_SendKeyboardKeyIgnoreModifiers(0, SDL_GLOBAL_KEYBOARD_ID, keycode, scancode, true);
456 break;
457 default:
458 break;
459 }
460 } else if (!x11KeyPressed && sdlKeyPressed) {
461 X11_HandleModifierKeys(videodata, scancode, false, false);
462 SDL_SendKeyboardKeyIgnoreModifiers(0, SDL_GLOBAL_KEYBOARD_ID, keycode, scancode, false);
463 }
464 }
465
466 X11_UpdateSystemKeyModifiers(videodata);
467 X11_ReconcileModifiers(videodata);
468}
469
470static void X11_DispatchFocusIn(SDL_VideoDevice *_this, SDL_WindowData *data)
471{
472#ifdef DEBUG_XEVENTS
473 SDL_Log("window 0x%lx: Dispatching FocusIn", data->xwindow);
474#endif
475 SDL_SetKeyboardFocus(data->window);
476 X11_ReconcileKeyboardState(_this);
477#ifdef X_HAVE_UTF8_STRING
478 if (data->ic) {
479 X11_XSetICFocus(data->ic);
480 }
481#endif
482 if (data->flashing_window) {
483 X11_FlashWindow(_this, data->window, SDL_FLASH_CANCEL);
484 }
485}
486
487static void X11_DispatchFocusOut(SDL_VideoDevice *_this, SDL_WindowData *data)
488{
489#ifdef DEBUG_XEVENTS
490 SDL_Log("window 0x%lx: Dispatching FocusOut", data->xwindow);
491#endif
492 /* If another window has already processed a focus in, then don't try to
493 * remove focus here. Doing so will incorrectly remove focus from that
494 * window, and the focus lost event for this window will have already
495 * been dispatched anyway. */
496 if (data->window == SDL_GetKeyboardFocus()) {
497 SDL_SetKeyboardFocus(NULL);
498 }
499#ifdef X_HAVE_UTF8_STRING
500 if (data->ic) {
501 X11_XUnsetICFocus(data->ic);
502 }
503#endif
504}
505
506static void X11_DispatchMapNotify(SDL_WindowData *data)
507{
508 SDL_Window *window = data->window;
509 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESTORED, 0, 0);
510 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_SHOWN, 0, 0);
511 if (!(window->flags & SDL_WINDOW_HIDDEN) && (window->flags & SDL_WINDOW_INPUT_FOCUS)) {
512 SDL_UpdateWindowGrab(window);
513 }
514}
515
516static void X11_DispatchUnmapNotify(SDL_WindowData *data)
517{
518 SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_HIDDEN, 0, 0);
519 SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MINIMIZED, 0, 0);
520}
521
522static void DispatchWindowMove(SDL_VideoDevice *_this, const SDL_WindowData *data, const SDL_Point *point)
523{
524 SDL_VideoData *videodata = _this->internal;
525 SDL_Window *window = data->window;
526 Display *display = videodata->display;
527 XEvent evt;
528
529 // !!! FIXME: we need to regrab this if necessary when the drag is done.
530 X11_XUngrabPointer(display, 0L);
531 X11_XFlush(display);
532
533 evt.xclient.type = ClientMessage;
534 evt.xclient.window = data->xwindow;
535 evt.xclient.message_type = videodata->atoms._NET_WM_MOVERESIZE;
536 evt.xclient.format = 32;
537 evt.xclient.data.l[0] = (size_t)window->x + point->x;
538 evt.xclient.data.l[1] = (size_t)window->y + point->y;
539 evt.xclient.data.l[2] = _NET_WM_MOVERESIZE_MOVE;
540 evt.xclient.data.l[3] = Button1;
541 evt.xclient.data.l[4] = 0;
542 X11_XSendEvent(display, DefaultRootWindow(display), False, SubstructureRedirectMask | SubstructureNotifyMask, &evt);
543
544 X11_XSync(display, 0);
545}
546
547static void ScheduleWindowMove(SDL_VideoDevice *_this, SDL_WindowData *data, const SDL_Point *point)
548{
549 data->pending_move = true;
550 data->pending_move_point = *point;
551}
552
553static void InitiateWindowResize(SDL_VideoDevice *_this, const SDL_WindowData *data, const SDL_Point *point, int direction)
554{
555 SDL_VideoData *videodata = _this->internal;
556 SDL_Window *window = data->window;
557 Display *display = videodata->display;
558 XEvent evt;
559
560 if (direction < _NET_WM_MOVERESIZE_SIZE_TOPLEFT || direction > _NET_WM_MOVERESIZE_SIZE_LEFT) {
561 return;
562 }
563
564 // !!! FIXME: we need to regrab this if necessary when the drag is done.
565 X11_XUngrabPointer(display, 0L);
566 X11_XFlush(display);
567
568 evt.xclient.type = ClientMessage;
569 evt.xclient.window = data->xwindow;
570 evt.xclient.message_type = videodata->atoms._NET_WM_MOVERESIZE;
571 evt.xclient.format = 32;
572 evt.xclient.data.l[0] = (size_t)window->x + point->x;
573 evt.xclient.data.l[1] = (size_t)window->y + point->y;
574 evt.xclient.data.l[2] = direction;
575 evt.xclient.data.l[3] = Button1;
576 evt.xclient.data.l[4] = 0;
577 X11_XSendEvent(display, DefaultRootWindow(display), False, SubstructureRedirectMask | SubstructureNotifyMask, &evt);
578
579 X11_XSync(display, 0);
580}
581
582bool X11_ProcessHitTest(SDL_VideoDevice *_this, SDL_WindowData *data, const float x, const float y, bool force_new_result)
583{
584 SDL_Window *window = data->window;
585 if (!window->hit_test) return false;
586 const SDL_Point point = { (int)x, (int)y };
587 SDL_HitTestResult rc = window->hit_test(window, &point, window->hit_test_data);
588 if (!force_new_result && rc == data->hit_test_result) {
589 return true;
590 }
591 X11_SetHitTestCursor(rc);
592 data->hit_test_result = rc;
593 return true;
594}
595
596bool X11_TriggerHitTestAction(SDL_VideoDevice *_this, SDL_WindowData *data, const float x, const float y)
597{
598 SDL_Window *window = data->window;
599
600 if (window->hit_test) {
601 const SDL_Point point = { (int)x, (int)y };
602 static const int directions[] = {
603 _NET_WM_MOVERESIZE_SIZE_TOPLEFT, _NET_WM_MOVERESIZE_SIZE_TOP,
604 _NET_WM_MOVERESIZE_SIZE_TOPRIGHT, _NET_WM_MOVERESIZE_SIZE_RIGHT,
605 _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT, _NET_WM_MOVERESIZE_SIZE_BOTTOM,
606 _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT, _NET_WM_MOVERESIZE_SIZE_LEFT
607 };
608
609 switch (data->hit_test_result) {
610 case SDL_HITTEST_DRAGGABLE:
611 /* Some window managers get in a bad state when a move event starts while input is transitioning
612 to the SDL window. This can happen when clicking on a drag region of an unfocused window
613 where the same mouse down event will trigger a drag event and a window activate. */
614 if (data->window->flags & SDL_WINDOW_INPUT_FOCUS) {
615 DispatchWindowMove(_this, data, &point);
616 } else {
617 ScheduleWindowMove(_this, data, &point);
618 }
619 return true;
620
621 case SDL_HITTEST_RESIZE_TOPLEFT:
622 case SDL_HITTEST_RESIZE_TOP:
623 case SDL_HITTEST_RESIZE_TOPRIGHT:
624 case SDL_HITTEST_RESIZE_RIGHT:
625 case SDL_HITTEST_RESIZE_BOTTOMRIGHT:
626 case SDL_HITTEST_RESIZE_BOTTOM:
627 case SDL_HITTEST_RESIZE_BOTTOMLEFT:
628 case SDL_HITTEST_RESIZE_LEFT:
629 InitiateWindowResize(_this, data, &point, directions[data->hit_test_result - SDL_HITTEST_RESIZE_TOPLEFT]);
630 return true;
631
632 default:
633 return false;
634 }
635 }
636
637 return false;
638}
639
640static void X11_UpdateUserTime(SDL_WindowData *data, const unsigned long latest)
641{
642 if (latest && (latest != data->user_time)) {
643 SDL_VideoData *videodata = data->videodata;
644 Display *display = videodata->display;
645 X11_XChangeProperty(display, data->xwindow, videodata->atoms._NET_WM_USER_TIME,
646 XA_CARDINAL, 32, PropModeReplace,
647 (const unsigned char *)&latest, 1);
648#ifdef DEBUG_XEVENTS
649 SDL_Log("window 0x%lx: updating _NET_WM_USER_TIME to %lu", data->xwindow, latest);
650#endif
651 data->user_time = latest;
652 }
653}
654
655static void X11_HandleClipboardEvent(SDL_VideoDevice *_this, const XEvent *xevent)
656{
657 int i;
658 SDL_VideoData *videodata = _this->internal;
659 Display *display = videodata->display;
660
661 SDL_assert(videodata->clipboard_window != None);
662 SDL_assert(xevent->xany.window == videodata->clipboard_window);
663
664 switch (xevent->type) {
665 // Copy the selection from our own CUTBUFFER to the requested property
666 case SelectionRequest:
667 {
668 const XSelectionRequestEvent *req = &xevent->xselectionrequest;
669 XEvent sevent;
670 int mime_formats;
671 unsigned char *seln_data;
672 size_t seln_length = 0;
673 Atom XA_TARGETS = videodata->atoms.TARGETS;
674 SDLX11_ClipboardData *clipboard;
675
676#ifdef DEBUG_XEVENTS
677 char *atom_name;
678 atom_name = X11_XGetAtomName(display, req->target);
679 SDL_Log("window CLIPBOARD: SelectionRequest (requestor = 0x%lx, target = 0x%lx, mime_type = %s)",
680 req->requestor, req->target, atom_name);
681 if (atom_name) {
682 X11_XFree(atom_name);
683 }
684#endif
685
686 if (req->selection == XA_PRIMARY) {
687 clipboard = &videodata->primary_selection;
688 } else {
689 clipboard = &videodata->clipboard;
690 }
691
692 SDL_zero(sevent);
693 sevent.xany.type = SelectionNotify;
694 sevent.xselection.selection = req->selection;
695 sevent.xselection.target = None;
696 sevent.xselection.property = None; // tell them no by default
697 sevent.xselection.requestor = req->requestor;
698 sevent.xselection.time = req->time;
699
700 /* !!! FIXME: We were probably storing this on the root window
701 because an SDL window might go away...? but we don't have to do
702 this now (or ever, really). */
703
704 if (req->target == XA_TARGETS) {
705 Atom *supportedFormats;
706 supportedFormats = SDL_malloc((clipboard->mime_count + 1) * sizeof(Atom));
707 supportedFormats[0] = XA_TARGETS;
708 mime_formats = 1;
709 for (i = 0; i < clipboard->mime_count; ++i) {
710 supportedFormats[mime_formats++] = X11_XInternAtom(display, clipboard->mime_types[i], False);
711 }
712 X11_XChangeProperty(display, req->requestor, req->property,
713 XA_ATOM, 32, PropModeReplace,
714 (unsigned char *)supportedFormats,
715 mime_formats);
716 sevent.xselection.property = req->property;
717 sevent.xselection.target = XA_TARGETS;
718 SDL_free(supportedFormats);
719 } else {
720 if (clipboard->callback) {
721 for (i = 0; i < clipboard->mime_count; ++i) {
722 const char *mime_type = clipboard->mime_types[i];
723 if (X11_XInternAtom(display, mime_type, False) != req->target) {
724 continue;
725 }
726
727 // FIXME: We don't support the X11 INCR protocol for large clipboards. Do we want that? - Yes, yes we do.
728 // This is a safe cast, XChangeProperty() doesn't take a const value, but it doesn't modify the data
729 seln_data = (unsigned char *)clipboard->callback(clipboard->userdata, mime_type, &seln_length);
730 if (seln_data) {
731 X11_XChangeProperty(display, req->requestor, req->property,
732 req->target, 8, PropModeReplace,
733 seln_data, seln_length);
734 sevent.xselection.property = req->property;
735 sevent.xselection.target = req->target;
736 }
737 break;
738 }
739 }
740 }
741 X11_XSendEvent(display, req->requestor, False, 0, &sevent);
742 X11_XSync(display, False);
743 } break;
744
745 case SelectionNotify:
746 {
747 const XSelectionEvent *xsel = &xevent->xselection;
748#ifdef DEBUG_XEVENTS
749 const char *propName = xsel->property ? X11_XGetAtomName(display, xsel->property) : "None";
750 const char *targetName = xsel->target ? X11_XGetAtomName(display, xsel->target) : "None";
751
752 SDL_Log("window CLIPBOARD: SelectionNotify (requestor = 0x%lx, target = %s, property = %s)",
753 xsel->requestor, targetName, propName);
754#endif
755 if (xsel->target == videodata->atoms.TARGETS && xsel->property == videodata->atoms.SDL_FORMATS) {
756 /* the new mime formats are the SDL_FORMATS property as an array of Atoms */
757 Atom atom = None;
758 Atom *patom;
759 unsigned char* data = NULL;
760 int format_property = 0;
761 unsigned long length = 0;
762 unsigned long bytes_left = 0;
763 int j;
764
765 X11_XGetWindowProperty(display, GetWindow(_this), videodata->atoms.SDL_FORMATS, 0, 200,
766 0, XA_ATOM, &atom, &format_property, &length, &bytes_left, &data);
767
768 int allocationsize = (length + 1) * sizeof(char*);
769 for (j = 0, patom = (Atom*)data; j < length; j++, patom++) {
770 char *atomStr = X11_XGetAtomName(display, *patom);
771 allocationsize += SDL_strlen(atomStr) + 1;
772 X11_XFree(atomStr);
773 }
774
775 char **new_mime_types = SDL_AllocateTemporaryMemory(allocationsize);
776 if (new_mime_types) {
777 char *strPtr = (char *)(new_mime_types + length + 1);
778
779 for (j = 0, patom = (Atom*)data; j < length; j++, patom++) {
780 char *atomStr = X11_XGetAtomName(display, *patom);
781 new_mime_types[j] = strPtr;
782 strPtr = stpcpy(strPtr, atomStr) + 1;
783 X11_XFree(atomStr);
784 }
785 new_mime_types[length] = NULL;
786
787 SDL_SendClipboardUpdate(false, new_mime_types, length);
788 }
789
790 if (data) {
791 X11_XFree(data);
792 }
793 }
794
795 videodata->selection_waiting = false;
796 } break;
797
798 case SelectionClear:
799 {
800 Atom XA_CLIPBOARD = videodata->atoms.CLIPBOARD;
801 SDLX11_ClipboardData *clipboard = NULL;
802
803#ifdef DEBUG_XEVENTS
804 SDL_Log("window CLIPBOARD: SelectionClear (requestor = 0x%lx, target = 0x%lx)",
805 xevent->xselection.requestor, xevent->xselection.target);
806#endif
807
808 if (xevent->xselectionclear.selection == XA_PRIMARY) {
809 clipboard = &videodata->primary_selection;
810 } else if (XA_CLIPBOARD != None && xevent->xselectionclear.selection == XA_CLIPBOARD) {
811 clipboard = &videodata->clipboard;
812 }
813 if (clipboard && clipboard->callback) {
814 if (clipboard->sequence) {
815 SDL_CancelClipboardData(clipboard->sequence);
816 } else {
817 SDL_free(clipboard->userdata);
818 }
819 SDL_zerop(clipboard);
820 }
821 } break;
822
823 case PropertyNotify:
824 {
825 char *name_of_atom = X11_XGetAtomName(display, xevent->xproperty.atom);
826
827 if (SDL_strncmp(name_of_atom, "SDL_SELECTION", sizeof("SDL_SELECTION") - 1) == 0 && xevent->xproperty.state == PropertyNewValue) {
828 videodata->selection_incr_waiting = false;
829 }
830
831 if (name_of_atom) {
832 X11_XFree(name_of_atom);
833 }
834 } break;
835 }
836}
837
838static void X11_HandleSettingsEvent(SDL_VideoDevice *_this, const XEvent *xevent)
839{
840 SDL_VideoData *videodata = _this->internal;
841
842 SDL_assert(videodata->xsettings_window != None);
843 SDL_assert(xevent->xany.window == videodata->xsettings_window);
844
845 X11_HandleXsettings(_this, xevent);
846}
847
848static Bool isMapNotify(Display *display, XEvent *ev, XPointer arg)
849{
850 XUnmapEvent *unmap;
851
852 unmap = (XUnmapEvent *)arg;
853
854 return ev->type == MapNotify &&
855 ev->xmap.window == unmap->window &&
856 ev->xmap.serial == unmap->serial;
857}
858
859static Bool isReparentNotify(Display *display, XEvent *ev, XPointer arg)
860{
861 XUnmapEvent *unmap;
862
863 unmap = (XUnmapEvent *)arg;
864
865 return ev->type == ReparentNotify &&
866 ev->xreparent.window == unmap->window &&
867 ev->xreparent.serial == unmap->serial;
868}
869
870static bool IsHighLatin1(const char *string, int length)
871{
872 while (length-- > 0) {
873 Uint8 ch = (Uint8)*string;
874 if (ch >= 0x80) {
875 return true;
876 }
877 ++string;
878 }
879 return false;
880}
881
882static int XLookupStringAsUTF8(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, XComposeStatus *status_in_out)
883{
884 int result = X11_XLookupString(event_struct, buffer_return, bytes_buffer, keysym_return, status_in_out);
885 if (IsHighLatin1(buffer_return, result)) {
886 char *utf8_text = SDL_iconv_string("UTF-8", "ISO-8859-1", buffer_return, result + 1);
887 if (utf8_text) {
888 SDL_strlcpy(buffer_return, utf8_text, bytes_buffer);
889 SDL_free(utf8_text);
890 return SDL_strlen(buffer_return);
891 } else {
892 return 0;
893 }
894 }
895 return result;
896}
897
898SDL_WindowData *X11_FindWindow(SDL_VideoDevice *_this, Window window)
899{
900 const SDL_VideoData *videodata = _this->internal;
901 int i;
902
903 if (videodata && videodata->windowlist) {
904 for (i = 0; i < videodata->numwindows; ++i) {
905 if ((videodata->windowlist[i] != NULL) &&
906 (videodata->windowlist[i]->xwindow == window)) {
907 return videodata->windowlist[i];
908 }
909 }
910 }
911 return NULL;
912}
913
914Uint64 X11_GetEventTimestamp(unsigned long time)
915{
916 // FIXME: Get the event time in the SDL tick time base
917 return SDL_GetTicksNS();
918}
919
920void X11_HandleKeyEvent(SDL_VideoDevice *_this, SDL_WindowData *windowdata, SDL_KeyboardID keyboardID, XEvent *xevent)
921{
922 SDL_VideoData *videodata = _this->internal;
923 Display *display = videodata->display;
924 KeyCode keycode = xevent->xkey.keycode;
925 KeySym keysym = NoSymbol;
926 int text_length = 0;
927 char text[64];
928 Status status = 0;
929 bool handled_by_ime = false;
930 bool pressed = (xevent->type == KeyPress);
931 SDL_Scancode scancode = videodata->key_layout[keycode];
932 Uint64 timestamp = X11_GetEventTimestamp(xevent->xkey.time);
933
934#ifdef DEBUG_XEVENTS
935 SDL_Log("window 0x%lx %s (X11 keycode = 0x%X)", xevent->xany.window, (xevent->type == KeyPress ? "KeyPress" : "KeyRelease"), xevent->xkey.keycode);
936#endif
937#ifdef DEBUG_SCANCODES
938 if (scancode == SDL_SCANCODE_UNKNOWN && keycode) {
939 int min_keycode, max_keycode;
940 X11_XDisplayKeycodes(display, &min_keycode, &max_keycode);
941 keysym = X11_KeyCodeToSym(_this, keycode, xevent->xkey.state >> 13);
942 SDL_Log("The key you just pressed is not recognized by SDL. To help get this fixed, please report this to the SDL forums/mailing list <https://discourse.libsdl.org/> X11 KeyCode %d (%d), X11 KeySym 0x%lX (%s).",
943 keycode, keycode - min_keycode, keysym,
944 X11_XKeysymToString(keysym));
945 }
946#endif // DEBUG SCANCODES
947
948 text[0] = '\0';
949 videodata->xkb.xkb_modifiers = xevent->xkey.state;
950
951 if (SDL_TextInputActive(windowdata->window)) {
952 // filter events catches XIM events and sends them to the correct handler
953 if (X11_XFilterEvent(xevent, None)) {
954#ifdef DEBUG_XEVENTS
955 SDL_Log("Filtered event type = %d display = %p window = 0x%lx",
956 xevent->type, xevent->xany.display, xevent->xany.window);
957#endif
958 handled_by_ime = true;
959 }
960
961 if (!handled_by_ime) {
962#ifdef X_HAVE_UTF8_STRING
963 if (windowdata->ic && xevent->type == KeyPress) {
964 text_length = X11_Xutf8LookupString(windowdata->ic, &xevent->xkey, text, sizeof(text) - 1,
965 &keysym, &status);
966 } else {
967 text_length = XLookupStringAsUTF8(&xevent->xkey, text, sizeof(text) - 1, &keysym, NULL);
968 }
969#else
970 text_length = XLookupStringAsUTF8(&xevent->xkey, text, sizeof(text) - 1, &keysym, NULL);
971#endif
972 }
973 }
974
975 if (!handled_by_ime) {
976 if (pressed) {
977 X11_HandleModifierKeys(videodata, scancode, true, true);
978 SDL_SendKeyboardKeyIgnoreModifiers(timestamp, keyboardID, keycode, scancode, true);
979
980 if (*text) {
981 text[text_length] = '\0';
982 X11_ClearComposition(windowdata);
983 SDL_SendKeyboardText(text);
984 }
985 } else {
986 if (X11_KeyRepeat(display, xevent)) {
987 // We're about to get a repeated key down, ignore the key up
988 return;
989 }
990
991 X11_HandleModifierKeys(videodata, scancode, false, true);
992 SDL_SendKeyboardKeyIgnoreModifiers(timestamp, keyboardID, keycode, scancode, false);
993 }
994 }
995
996 if (pressed) {
997 X11_UpdateUserTime(windowdata, xevent->xkey.time);
998 }
999}
1000
1001void X11_HandleButtonPress(SDL_VideoDevice *_this, SDL_WindowData *windowdata, SDL_MouseID mouseID, int button, float x, float y, unsigned long time)
1002{
1003 SDL_Window *window = windowdata->window;
1004 const SDL_VideoData *videodata = _this->internal;
1005 Display *display = videodata->display;
1006 int xticks = 0, yticks = 0;
1007 Uint64 timestamp = X11_GetEventTimestamp(time);
1008
1009#ifdef DEBUG_XEVENTS
1010 SDL_Log("window 0x%lx: ButtonPress (X11 button = %d)", windowdata->xwindow, button);
1011#endif
1012
1013 SDL_Mouse *mouse = SDL_GetMouse();
1014 if (!mouse->relative_mode && (x != mouse->x || y != mouse->y)) {
1015 X11_ProcessHitTest(_this, windowdata, x, y, false);
1016 SDL_SendMouseMotion(timestamp, window, mouseID, false, x, y);
1017 }
1018
1019 if (X11_IsWheelEvent(display, button, &xticks, &yticks)) {
1020 SDL_SendMouseWheel(timestamp, window, mouseID, (float)-xticks, (float)yticks, SDL_MOUSEWHEEL_NORMAL);
1021 } else {
1022 bool ignore_click = false;
1023 if (button > 7) {
1024 /* X button values 4-7 are used for scrolling, so X1 is 8, X2 is 9, ...
1025 => subtract (8-SDL_BUTTON_X1) to get value SDL expects */
1026 button -= (8 - SDL_BUTTON_X1);
1027 }
1028 if (button == Button1) {
1029 if (X11_TriggerHitTestAction(_this, windowdata, x, y)) {
1030 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_HIT_TEST, 0, 0);
1031 return; // don't pass this event on to app.
1032 }
1033 }
1034 if (windowdata->last_focus_event_time) {
1035 const int X11_FOCUS_CLICK_TIMEOUT = 10;
1036 if (SDL_GetTicks() < (windowdata->last_focus_event_time + X11_FOCUS_CLICK_TIMEOUT)) {
1037 ignore_click = !SDL_GetHintBoolean(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, false);
1038 }
1039 windowdata->last_focus_event_time = 0;
1040 }
1041 if (!ignore_click) {
1042 SDL_SendMouseButton(timestamp, window, mouseID, button, true);
1043 }
1044 }
1045 X11_UpdateUserTime(windowdata, time);
1046}
1047
1048void X11_HandleButtonRelease(SDL_VideoDevice *_this, SDL_WindowData *windowdata, SDL_MouseID mouseID, int button, unsigned long time)
1049{
1050 SDL_Window *window = windowdata->window;
1051 const SDL_VideoData *videodata = _this->internal;
1052 Display *display = videodata->display;
1053 // The X server sends a Release event for each Press for wheels. Ignore them.
1054 int xticks = 0, yticks = 0;
1055 Uint64 timestamp = X11_GetEventTimestamp(time);
1056
1057#ifdef DEBUG_XEVENTS
1058 SDL_Log("window 0x%lx: ButtonRelease (X11 button = %d)", windowdata->xwindow, button);
1059#endif
1060 if (!X11_IsWheelEvent(display, button, &xticks, &yticks)) {
1061 if (button > 7) {
1062 // see explanation at case ButtonPress
1063 button -= (8 - SDL_BUTTON_X1);
1064 }
1065 SDL_SendMouseButton(timestamp, window, mouseID, button, false);
1066 }
1067}
1068
1069void X11_GetBorderValues(SDL_WindowData *data)
1070{
1071 SDL_VideoData *videodata = data->videodata;
1072 Display *display = videodata->display;
1073
1074 Atom type;
1075 int format;
1076 unsigned long nitems, bytes_after;
1077 unsigned char *property;
1078
1079 // Some compositors will send extents even when the border hint is turned off. Ignore them in this case.
1080 if (!(data->window->flags & SDL_WINDOW_BORDERLESS)) {
1081 if (X11_XGetWindowProperty(display, data->xwindow, videodata->atoms._NET_FRAME_EXTENTS, 0, 16, 0, XA_CARDINAL, &type, &format, &nitems, &bytes_after, &property) == Success) {
1082 if (type != None && nitems == 4) {
1083 data->border_left = (int)((long *)property)[0];
1084 data->border_right = (int)((long *)property)[1];
1085 data->border_top = (int)((long *)property)[2];
1086 data->border_bottom = (int)((long *)property)[3];
1087 }
1088 X11_XFree(property);
1089
1090#ifdef DEBUG_XEVENTS
1091 SDL_Log("New _NET_FRAME_EXTENTS: left=%d right=%d, top=%d, bottom=%d", data->border_left, data->border_right, data->border_top, data->border_bottom);
1092#endif
1093 }
1094 } else {
1095 data->border_left = data->border_top = data->border_right = data->border_bottom = 0;
1096 }
1097}
1098
1099static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent)
1100{
1101 SDL_VideoData *videodata = _this->internal;
1102 Display *display;
1103 SDL_WindowData *data;
1104 XClientMessageEvent m;
1105 int i;
1106
1107 SDL_assert(videodata != NULL);
1108 display = videodata->display;
1109
1110 // filter events catches XIM events and sends them to the correct handler
1111 // Key press/release events are filtered in X11_HandleKeyEvent()
1112 if (xevent->type != KeyPress && xevent->type != KeyRelease) {
1113 if (X11_XFilterEvent(xevent, None)) {
1114#ifdef DEBUG_XEVENTS
1115 SDL_Log("Filtered event type = %d display = %p window = 0x%lx",
1116 xevent->type, xevent->xany.display, xevent->xany.window);
1117#endif
1118 return;
1119 }
1120 }
1121
1122#ifdef SDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS
1123 if (xevent->type == GenericEvent) {
1124 X11_HandleGenericEvent(_this, xevent);
1125 return;
1126 }
1127#endif
1128
1129 // Calling the event hook for generic events happens in X11_HandleGenericEvent(), where the event data is available
1130 if (g_X11EventHook) {
1131 if (!g_X11EventHook(g_X11EventHookData, xevent)) {
1132 return;
1133 }
1134 }
1135
1136#ifdef SDL_VIDEO_DRIVER_X11_XRANDR
1137 if (videodata->xrandr_event_base && (xevent->type == (videodata->xrandr_event_base + RRNotify))) {
1138 X11_HandleXRandREvent(_this, xevent);
1139 }
1140#endif
1141
1142#ifdef DEBUG_XEVENTS
1143 SDL_Log("X11 event type = %d display = %p window = 0x%lx",
1144 xevent->type, xevent->xany.display, xevent->xany.window);
1145#endif
1146
1147#ifdef SDL_VIDEO_DRIVER_X11_XFIXES
1148 if (SDL_X11_HAVE_XFIXES &&
1149 xevent->type == X11_GetXFixesSelectionNotifyEvent()) {
1150 XFixesSelectionNotifyEvent *ev = (XFixesSelectionNotifyEvent *)xevent;
1151
1152#ifdef DEBUG_XEVENTS
1153 SDL_Log("window CLIPBOARD: XFixesSelectionNotify (selection = %s)",
1154 X11_XGetAtomName(display, ev->selection));
1155#endif
1156
1157 if (ev->subtype == XFixesSetSelectionOwnerNotify)
1158 {
1159 if (ev->selection != videodata->atoms.CLIPBOARD)
1160 return;
1161
1162 if (X11_XGetSelectionOwner(display, ev->selection) == videodata->clipboard_window)
1163 return;
1164
1165 /* when here we're notified that the clipboard had an external change, we request the
1166 * available mime types by asking for a conversion to the TARGETS format. We should get a
1167 * SelectionNotify event later, and when treating these results, we will push a ClipboardUpdated
1168 * event
1169 */
1170
1171 X11_XConvertSelection(display, videodata->atoms.CLIPBOARD, videodata->atoms.TARGETS,
1172 videodata->atoms.SDL_FORMATS, GetWindow(_this), CurrentTime);
1173 }
1174
1175 return;
1176 }
1177#endif // SDL_VIDEO_DRIVER_X11_XFIXES
1178
1179 if ((videodata->clipboard_window != None) &&
1180 (videodata->clipboard_window == xevent->xany.window)) {
1181 X11_HandleClipboardEvent(_this, xevent);
1182 return;
1183 }
1184
1185 if ((videodata->xsettings_window != None) &&
1186 (videodata->xsettings_window == xevent->xany.window)) {
1187 X11_HandleSettingsEvent(_this, xevent);
1188 return;
1189 }
1190
1191 data = X11_FindWindow(_this, xevent->xany.window);
1192
1193 if (!data) {
1194 // The window for KeymapNotify, etc events is 0
1195 if (xevent->type == KeymapNotify) {
1196#ifdef DEBUG_XEVENTS
1197 SDL_Log("window 0x%lx: KeymapNotify!", xevent->xany.window);
1198#endif
1199 if (SDL_GetKeyboardFocus() != NULL) {
1200#ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLOOKUPKEYSYM
1201 if (videodata->xkb.desc_ptr) {
1202 XkbStateRec state;
1203 if (X11_XkbGetState(videodata->display, XkbUseCoreKbd, &state) == Success) {
1204 if (state.group != videodata->xkb.current_group) {
1205 // Only rebuild the keymap if the layout has changed.
1206 videodata->xkb.current_group = state.group;
1207 X11_UpdateKeymap(_this, true);
1208 }
1209 }
1210 }
1211#endif
1212 X11_ReconcileKeyboardState(_this);
1213 }
1214 } else if (xevent->type == MappingNotify) {
1215 // Has the keyboard layout changed?
1216 const int request = xevent->xmapping.request;
1217
1218#ifdef DEBUG_XEVENTS
1219 SDL_Log("window 0x%lx: MappingNotify!", xevent->xany.window);
1220#endif
1221 if ((request == MappingKeyboard) || (request == MappingModifier)) {
1222 X11_XRefreshKeyboardMapping(&xevent->xmapping);
1223 }
1224
1225 X11_UpdateKeymap(_this, true);
1226 } else if (xevent->type == PropertyNotify && videodata && videodata->windowlist) {
1227 char *name_of_atom = X11_XGetAtomName(display, xevent->xproperty.atom);
1228
1229 if (SDL_strncmp(name_of_atom, "_ICC_PROFILE", sizeof("_ICC_PROFILE") - 1) == 0) {
1230 XWindowAttributes attrib;
1231 int screennum;
1232 for (i = 0; i < videodata->numwindows; ++i) {
1233 if (videodata->windowlist[i] != NULL) {
1234 data = videodata->windowlist[i];
1235 X11_XGetWindowAttributes(display, data->xwindow, &attrib);
1236 screennum = X11_XScreenNumberOfScreen(attrib.screen);
1237 if (screennum == 0 && SDL_strcmp(name_of_atom, "_ICC_PROFILE") == 0) {
1238 SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_ICCPROF_CHANGED, 0, 0);
1239 } else if (SDL_strncmp(name_of_atom, "_ICC_PROFILE_", sizeof("_ICC_PROFILE_") - 1) == 0 && SDL_strlen(name_of_atom) > sizeof("_ICC_PROFILE_") - 1) {
1240 int iccscreennum = SDL_atoi(&name_of_atom[sizeof("_ICC_PROFILE_") - 1]);
1241
1242 if (screennum == iccscreennum) {
1243 SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_ICCPROF_CHANGED, 0, 0);
1244 }
1245 }
1246 }
1247 }
1248 }
1249
1250 if (name_of_atom) {
1251 X11_XFree(name_of_atom);
1252 }
1253 }
1254 return;
1255 }
1256
1257 switch (xevent->type) {
1258
1259 // Gaining mouse coverage?
1260 case EnterNotify:
1261 {
1262 SDL_Mouse *mouse = SDL_GetMouse();
1263#ifdef DEBUG_XEVENTS
1264 SDL_Log("window 0x%lx: EnterNotify! (%d,%d,%d)", xevent->xany.window,
1265 xevent->xcrossing.x,
1266 xevent->xcrossing.y,
1267 xevent->xcrossing.mode);
1268 if (xevent->xcrossing.mode == NotifyGrab) {
1269 SDL_Log("Mode: NotifyGrab");
1270 }
1271 if (xevent->xcrossing.mode == NotifyUngrab) {
1272 SDL_Log("Mode: NotifyUngrab");
1273 }
1274#endif
1275 SDL_SetMouseFocus(data->window);
1276
1277 mouse->last_x = xevent->xcrossing.x;
1278 mouse->last_y = xevent->xcrossing.y;
1279
1280#ifdef SDL_VIDEO_DRIVER_X11_XFIXES
1281 {
1282 // Only create the barriers if we have input focus
1283 SDL_WindowData *windowdata = data->window->internal;
1284 if ((data->pointer_barrier_active == true) && windowdata->window->flags & SDL_WINDOW_INPUT_FOCUS) {
1285 X11_ConfineCursorWithFlags(_this, windowdata->window, &windowdata->barrier_rect, X11_BARRIER_HANDLED_BY_EVENT);
1286 }
1287 }
1288#endif
1289
1290 if (!mouse->relative_mode) {
1291 SDL_SendMouseMotion(0, data->window, SDL_GLOBAL_MOUSE_ID, false, (float)xevent->xcrossing.x, (float)xevent->xcrossing.y);
1292 }
1293
1294 // We ungrab in LeaveNotify, so we may need to grab again here
1295 SDL_UpdateWindowGrab(data->window);
1296
1297 X11_ProcessHitTest(_this, data, mouse->last_x, mouse->last_y, true);
1298 } break;
1299 // Losing mouse coverage?
1300 case LeaveNotify:
1301 {
1302#ifdef DEBUG_XEVENTS
1303 SDL_Log("window 0x%lx: LeaveNotify! (%d,%d,%d)", xevent->xany.window,
1304 xevent->xcrossing.x,
1305 xevent->xcrossing.y,
1306 xevent->xcrossing.mode);
1307 if (xevent->xcrossing.mode == NotifyGrab) {
1308 SDL_Log("Mode: NotifyGrab");
1309 }
1310 if (xevent->xcrossing.mode == NotifyUngrab) {
1311 SDL_Log("Mode: NotifyUngrab");
1312 }
1313#endif
1314 if (!SDL_GetMouse()->relative_mode) {
1315 SDL_SendMouseMotion(0, data->window, SDL_GLOBAL_MOUSE_ID, false, (float)xevent->xcrossing.x, (float)xevent->xcrossing.y);
1316 }
1317
1318 if (xevent->xcrossing.mode != NotifyGrab &&
1319 xevent->xcrossing.mode != NotifyUngrab &&
1320 xevent->xcrossing.detail != NotifyInferior) {
1321
1322 /* In order for interaction with the window decorations and menu to work properly
1323 on Mutter, we need to ungrab the keyboard when the the mouse leaves. */
1324 if (!(data->window->flags & SDL_WINDOW_FULLSCREEN)) {
1325 X11_SetWindowKeyboardGrab(_this, data->window, false);
1326 }
1327
1328 SDL_SetMouseFocus(NULL);
1329 }
1330 } break;
1331
1332 // Gaining input focus?
1333 case FocusIn:
1334 {
1335 if (xevent->xfocus.mode == NotifyGrab || xevent->xfocus.mode == NotifyUngrab) {
1336 // Someone is handling a global hotkey, ignore it
1337#ifdef DEBUG_XEVENTS
1338 SDL_Log("window 0x%lx: FocusIn (NotifyGrab/NotifyUngrab, ignoring)", xevent->xany.window);
1339#endif
1340 break;
1341 }
1342
1343 if (xevent->xfocus.detail == NotifyInferior || xevent->xfocus.detail == NotifyPointer) {
1344#ifdef DEBUG_XEVENTS
1345 SDL_Log("window 0x%lx: FocusIn (NotifyInferior/NotifyPointer, ignoring)", xevent->xany.window);
1346#endif
1347 break;
1348 }
1349#ifdef DEBUG_XEVENTS
1350 SDL_Log("window 0x%lx: FocusIn!", xevent->xany.window);
1351#endif
1352 if (!videodata->last_mode_change_deadline) /* no recent mode changes */ {
1353 data->pending_focus = PENDING_FOCUS_NONE;
1354 data->pending_focus_time = 0;
1355 X11_DispatchFocusIn(_this, data);
1356 } else {
1357 data->pending_focus = PENDING_FOCUS_IN;
1358 data->pending_focus_time = SDL_GetTicks() + PENDING_FOCUS_TIME;
1359 }
1360 data->last_focus_event_time = SDL_GetTicks();
1361 } break;
1362
1363 // Losing input focus?
1364 case FocusOut:
1365 {
1366 if (xevent->xfocus.mode == NotifyGrab || xevent->xfocus.mode == NotifyUngrab) {
1367 // Someone is handling a global hotkey, ignore it
1368#ifdef DEBUG_XEVENTS
1369 SDL_Log("window 0x%lx: FocusOut (NotifyGrab/NotifyUngrab, ignoring)", xevent->xany.window);
1370#endif
1371 break;
1372 }
1373 if (xevent->xfocus.detail == NotifyInferior || xevent->xfocus.detail == NotifyPointer) {
1374 /* We still have focus if a child gets focus. We also don't
1375 care about the position of the pointer when the keyboard
1376 focus changed. */
1377#ifdef DEBUG_XEVENTS
1378 SDL_Log("window 0x%lx: FocusOut (NotifyInferior/NotifyPointer, ignoring)", xevent->xany.window);
1379#endif
1380 break;
1381 }
1382#ifdef DEBUG_XEVENTS
1383 SDL_Log("window 0x%lx: FocusOut!", xevent->xany.window);
1384#endif
1385 if (!videodata->last_mode_change_deadline) /* no recent mode changes */ {
1386 data->pending_focus = PENDING_FOCUS_NONE;
1387 data->pending_focus_time = 0;
1388 X11_DispatchFocusOut(_this, data);
1389 } else {
1390 data->pending_focus = PENDING_FOCUS_OUT;
1391 data->pending_focus_time = SDL_GetTicks() + PENDING_FOCUS_TIME;
1392 }
1393
1394#ifdef SDL_VIDEO_DRIVER_X11_XFIXES
1395 // Disable confinement if it is activated.
1396 if (data->pointer_barrier_active == true) {
1397 X11_ConfineCursorWithFlags(_this, data->window, NULL, X11_BARRIER_HANDLED_BY_EVENT);
1398 }
1399#endif // SDL_VIDEO_DRIVER_X11_XFIXES
1400 } break;
1401
1402
1403 // Have we been iconified?
1404 case UnmapNotify:
1405 {
1406 XEvent ev;
1407
1408#ifdef DEBUG_XEVENTS
1409 SDL_Log("window 0x%lx: UnmapNotify!", xevent->xany.window);
1410#endif
1411
1412 if (X11_XCheckIfEvent(display, &ev, &isReparentNotify, (XPointer)&xevent->xunmap)) {
1413 X11_XCheckIfEvent(display, &ev, &isMapNotify, (XPointer)&xevent->xunmap);
1414 } else {
1415 X11_DispatchUnmapNotify(data);
1416 }
1417
1418#ifdef SDL_VIDEO_DRIVER_X11_XFIXES
1419 // Disable confinement if the window gets hidden.
1420 if (data->pointer_barrier_active == true) {
1421 X11_ConfineCursorWithFlags(_this, data->window, NULL, X11_BARRIER_HANDLED_BY_EVENT);
1422 }
1423#endif // SDL_VIDEO_DRIVER_X11_XFIXES
1424 } break;
1425
1426 // Have we been restored?
1427 case MapNotify:
1428 {
1429#ifdef DEBUG_XEVENTS
1430 SDL_Log("window 0x%lx: MapNotify!", xevent->xany.window);
1431#endif
1432 X11_DispatchMapNotify(data);
1433
1434#ifdef SDL_VIDEO_DRIVER_X11_XFIXES
1435 // Enable confinement if it was activated.
1436 if (data->pointer_barrier_active == true) {
1437 X11_ConfineCursorWithFlags(_this, data->window, &data->barrier_rect, X11_BARRIER_HANDLED_BY_EVENT);
1438 }
1439#endif // SDL_VIDEO_DRIVER_X11_XFIXES
1440 } break;
1441
1442 // Have we been resized or moved?
1443 case ConfigureNotify:
1444 {
1445#ifdef DEBUG_XEVENTS
1446 SDL_Log("window 0x%lx: ConfigureNotify! (position: %d,%d, size: %dx%d)", xevent->xany.window,
1447 xevent->xconfigure.x, xevent->xconfigure.y,
1448 xevent->xconfigure.width, xevent->xconfigure.height);
1449#endif
1450 // Real configure notify events are relative to the parent, synthetic events are absolute.
1451 if (!xevent->xconfigure.send_event)
1452 {
1453 unsigned int NumChildren;
1454 Window ChildReturn, Root, Parent;
1455 Window *Children;
1456 // Translate these coordinates back to relative to root
1457 X11_XQueryTree(data->videodata->display, xevent->xconfigure.window, &Root, &Parent, &Children, &NumChildren);
1458 X11_XTranslateCoordinates(xevent->xconfigure.display,
1459 Parent, DefaultRootWindow(xevent->xconfigure.display),
1460 xevent->xconfigure.x, xevent->xconfigure.y,
1461 &xevent->xconfigure.x, &xevent->xconfigure.y,
1462 &ChildReturn);
1463 }
1464
1465 if (xevent->xconfigure.x != data->last_xconfigure.x ||
1466 xevent->xconfigure.y != data->last_xconfigure.y) {
1467 if (!data->size_move_event_flags) {
1468 SDL_Window *w;
1469 int x = xevent->xconfigure.x;
1470 int y = xevent->xconfigure.y;
1471
1472 data->pending_operation &= ~X11_PENDING_OP_MOVE;
1473 SDL_GlobalToRelativeForWindow(data->window, x, y, &x, &y);
1474 SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MOVED, x, y);
1475
1476 for (w = data->window->first_child; w; w = w->next_sibling) {
1477 // Don't update hidden child popup windows, their relative position doesn't change
1478 if (SDL_WINDOW_IS_POPUP(w) && !(w->flags & SDL_WINDOW_HIDDEN)) {
1479 X11_UpdateWindowPosition(w, true);
1480 }
1481 }
1482 }
1483 }
1484
1485#ifdef SDL_VIDEO_DRIVER_X11_XSYNC
1486 X11_HandleConfigure(data->window, &xevent->xconfigure);
1487#endif /* SDL_VIDEO_DRIVER_X11_XSYNC */
1488
1489 if (xevent->xconfigure.width != data->last_xconfigure.width ||
1490 xevent->xconfigure.height != data->last_xconfigure.height) {
1491 if (!data->size_move_event_flags) {
1492 data->pending_operation &= ~X11_PENDING_OP_RESIZE;
1493 SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_RESIZED,
1494 xevent->xconfigure.width,
1495 xevent->xconfigure.height);
1496 }
1497 }
1498
1499 data->last_xconfigure = xevent->xconfigure;
1500 } break;
1501
1502 // Have we been requested to quit (or another client message?)
1503 case ClientMessage:
1504 {
1505 static int xdnd_version = 0;
1506
1507 if (xevent->xclient.message_type == videodata->atoms.XdndEnter) {
1508
1509 bool use_list = xevent->xclient.data.l[1] & 1;
1510 data->xdnd_source = xevent->xclient.data.l[0];
1511 xdnd_version = (xevent->xclient.data.l[1] >> 24);
1512#ifdef DEBUG_XEVENTS
1513 SDL_Log("XID of source window : 0x%lx", data->xdnd_source);
1514 SDL_Log("Protocol version to use : %d", xdnd_version);
1515 SDL_Log("More then 3 data types : %d", (int)use_list);
1516#endif
1517
1518 if (use_list) {
1519 // fetch conversion targets
1520 SDL_x11Prop p;
1521 X11_ReadProperty(&p, display, data->xdnd_source, videodata->atoms.XdndTypeList);
1522 // pick one
1523 data->xdnd_req = X11_PickTarget(display, (Atom *)p.data, p.count);
1524 X11_XFree(p.data);
1525 } else {
1526 // pick from list of three
1527 data->xdnd_req = X11_PickTargetFromAtoms(display, xevent->xclient.data.l[2], xevent->xclient.data.l[3], xevent->xclient.data.l[4]);
1528 }
1529 } else if (xevent->xclient.message_type == videodata->atoms.XdndLeave) {
1530#ifdef DEBUG_XEVENTS
1531 SDL_Log("XID of source window : 0x%lx", xevent->xclient.data.l[0]);
1532#endif
1533 SDL_SendDropComplete(data->window);
1534 } else if (xevent->xclient.message_type == videodata->atoms.XdndPosition) {
1535
1536#ifdef DEBUG_XEVENTS
1537 Atom act = videodata->atoms.XdndActionCopy;
1538 if (xdnd_version >= 2) {
1539 act = xevent->xclient.data.l[4];
1540 }
1541 SDL_Log("Action requested by user is : %s", X11_XGetAtomName(display, act));
1542#endif
1543 {
1544 // Drag and Drop position
1545 int root_x, root_y, window_x, window_y;
1546 Window ChildReturn;
1547 root_x = xevent->xclient.data.l[2] >> 16;
1548 root_y = xevent->xclient.data.l[2] & 0xffff;
1549 // Translate from root to current window position
1550 X11_XTranslateCoordinates(display, DefaultRootWindow(display), data->xwindow,
1551 root_x, root_y, &window_x, &window_y, &ChildReturn);
1552
1553 SDL_SendDropPosition(data->window, (float)window_x, (float)window_y);
1554 }
1555
1556 // reply with status
1557 SDL_memset(&m, 0, sizeof(XClientMessageEvent));
1558 m.type = ClientMessage;
1559 m.display = xevent->xclient.display;
1560 m.window = xevent->xclient.data.l[0];
1561 m.message_type = videodata->atoms.XdndStatus;
1562 m.format = 32;
1563 m.data.l[0] = data->xwindow;
1564 m.data.l[1] = (data->xdnd_req != None);
1565 m.data.l[2] = 0; // specify an empty rectangle
1566 m.data.l[3] = 0;
1567 m.data.l[4] = videodata->atoms.XdndActionCopy; // we only accept copying anyway
1568
1569 X11_XSendEvent(display, xevent->xclient.data.l[0], False, NoEventMask, (XEvent *)&m);
1570 X11_XFlush(display);
1571 } else if (xevent->xclient.message_type == videodata->atoms.XdndDrop) {
1572 if (data->xdnd_req == None) {
1573 // say again - not interested!
1574 SDL_memset(&m, 0, sizeof(XClientMessageEvent));
1575 m.type = ClientMessage;
1576 m.display = xevent->xclient.display;
1577 m.window = xevent->xclient.data.l[0];
1578 m.message_type = videodata->atoms.XdndFinished;
1579 m.format = 32;
1580 m.data.l[0] = data->xwindow;
1581 m.data.l[1] = 0;
1582 m.data.l[2] = None; // fail!
1583 X11_XSendEvent(display, xevent->xclient.data.l[0], False, NoEventMask, (XEvent *)&m);
1584 } else {
1585 // convert
1586 if (xdnd_version >= 1) {
1587 X11_XConvertSelection(display, videodata->atoms.XdndSelection, data->xdnd_req, videodata->atoms.PRIMARY, data->xwindow, xevent->xclient.data.l[2]);
1588 } else {
1589 X11_XConvertSelection(display, videodata->atoms.XdndSelection, data->xdnd_req, videodata->atoms.PRIMARY, data->xwindow, CurrentTime);
1590 }
1591 }
1592 } else if ((xevent->xclient.message_type == videodata->atoms.WM_PROTOCOLS) &&
1593 (xevent->xclient.format == 32) &&
1594 (xevent->xclient.data.l[0] == videodata->atoms._NET_WM_PING)) {
1595 Window root = DefaultRootWindow(display);
1596
1597#ifdef DEBUG_XEVENTS
1598 SDL_Log("window 0x%lx: _NET_WM_PING", xevent->xany.window);
1599#endif
1600 xevent->xclient.window = root;
1601 X11_XSendEvent(display, root, False, SubstructureRedirectMask | SubstructureNotifyMask, xevent);
1602 break;
1603 }
1604
1605 else if ((xevent->xclient.message_type == videodata->atoms.WM_PROTOCOLS) &&
1606 (xevent->xclient.format == 32) &&
1607 (xevent->xclient.data.l[0] == videodata->atoms.WM_DELETE_WINDOW)) {
1608
1609#ifdef DEBUG_XEVENTS
1610 SDL_Log("window 0x%lx: WM_DELETE_WINDOW", xevent->xany.window);
1611#endif
1612 SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_CLOSE_REQUESTED, 0, 0);
1613 break;
1614 } else if ((xevent->xclient.message_type == videodata->atoms.WM_PROTOCOLS) &&
1615 (xevent->xclient.format == 32) &&
1616 (xevent->xclient.data.l[0] == videodata->atoms._NET_WM_SYNC_REQUEST)) {
1617
1618#ifdef DEBUG_XEVENTS
1619 printf("window %p: _NET_WM_SYNC_REQUEST\n", data);
1620#endif
1621#ifdef SDL_VIDEO_DRIVER_X11_XSYNC
1622 X11_HandleSyncRequest(data->window, &xevent->xclient);
1623#endif /* SDL_VIDEO_DRIVER_X11_XSYNC */
1624 break;
1625 }
1626 } break;
1627
1628 // Do we need to refresh ourselves?
1629 case Expose:
1630 {
1631#ifdef DEBUG_XEVENTS
1632 SDL_Log("window 0x%lx: Expose (count = %d)", xevent->xany.window, xevent->xexpose.count);
1633#endif
1634 SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_EXPOSED, 0, 0);
1635 } break;
1636
1637 /* Use XInput2 instead of the xevents API if possible, for:
1638 - KeyPress
1639 - KeyRelease
1640 - MotionNotify
1641 - ButtonPress
1642 - ButtonRelease
1643 XInput2 has more precise information, e.g., to distinguish different input devices. */
1644 case KeyPress:
1645 case KeyRelease:
1646 {
1647 if (data->xinput2_keyboard_enabled) {
1648 // This input is being handled by XInput2
1649 break;
1650 }
1651
1652 X11_HandleKeyEvent(_this, data, SDL_GLOBAL_KEYBOARD_ID, xevent);
1653 } break;
1654
1655 case MotionNotify:
1656 {
1657 if (data->xinput2_mouse_enabled && !data->mouse_grabbed) {
1658 // This input is being handled by XInput2
1659 break;
1660 }
1661
1662 SDL_Mouse *mouse = SDL_GetMouse();
1663 if (!mouse->relative_mode) {
1664#ifdef DEBUG_MOTION
1665 SDL_Log("window 0x%lx: X11 motion: %d,%d", xevent->xany.window, xevent->xmotion.x, xevent->xmotion.y);
1666#endif
1667
1668 X11_ProcessHitTest(_this, data, (float)xevent->xmotion.x, (float)xevent->xmotion.y, false);
1669 SDL_SendMouseMotion(0, data->window, SDL_GLOBAL_MOUSE_ID, false, (float)xevent->xmotion.x, (float)xevent->xmotion.y);
1670 }
1671 } break;
1672
1673 case ButtonPress:
1674 {
1675 if (data->xinput2_mouse_enabled) {
1676 // This input is being handled by XInput2
1677 break;
1678 }
1679
1680 X11_HandleButtonPress(_this, data, SDL_GLOBAL_MOUSE_ID, xevent->xbutton.button,
1681 xevent->xbutton.x, xevent->xbutton.y, xevent->xbutton.time);
1682 } break;
1683
1684 case ButtonRelease:
1685 {
1686 if (data->xinput2_mouse_enabled) {
1687 // This input is being handled by XInput2
1688 break;
1689 }
1690
1691 X11_HandleButtonRelease(_this, data, SDL_GLOBAL_MOUSE_ID, xevent->xbutton.button, xevent->xbutton.time);
1692 } break;
1693
1694 case PropertyNotify:
1695 {
1696#ifdef DEBUG_XEVENTS
1697 unsigned char *propdata;
1698 int status, real_format;
1699 Atom real_type;
1700 unsigned long items_read, items_left;
1701
1702 char *name = X11_XGetAtomName(display, xevent->xproperty.atom);
1703 if (name) {
1704 SDL_Log("window 0x%lx: PropertyNotify: %s %s time=%lu", xevent->xany.window, name, (xevent->xproperty.state == PropertyDelete) ? "deleted" : "changed", xevent->xproperty.time);
1705 X11_XFree(name);
1706 }
1707
1708 status = X11_XGetWindowProperty(display, data->xwindow, xevent->xproperty.atom, 0L, 8192L, False, AnyPropertyType, &real_type, &real_format, &items_read, &items_left, &propdata);
1709 if (status == Success && items_read > 0) {
1710 if (real_type == XA_INTEGER) {
1711 int *values = (int *)propdata;
1712
1713 SDL_Log("{");
1714 for (i = 0; i < items_read; i++) {
1715 SDL_Log(" %d", values[i]);
1716 }
1717 SDL_Log(" }");
1718 } else if (real_type == XA_CARDINAL) {
1719 if (real_format == 32) {
1720 Uint32 *values = (Uint32 *)propdata;
1721
1722 SDL_Log("{");
1723 for (i = 0; i < items_read; i++) {
1724 SDL_Log(" %d", values[i]);
1725 }
1726 SDL_Log(" }");
1727 } else if (real_format == 16) {
1728 Uint16 *values = (Uint16 *)propdata;
1729
1730 SDL_Log("{");
1731 for (i = 0; i < items_read; i++) {
1732 SDL_Log(" %d", values[i]);
1733 }
1734 SDL_Log(" }");
1735 } else if (real_format == 8) {
1736 Uint8 *values = (Uint8 *)propdata;
1737
1738 SDL_Log("{");
1739 for (i = 0; i < items_read; i++) {
1740 SDL_Log(" %d", values[i]);
1741 }
1742 SDL_Log(" }");
1743 }
1744 } else if (real_type == XA_STRING ||
1745 real_type == videodata->atoms.UTF8_STRING) {
1746 SDL_Log("{ \"%s\" }", propdata);
1747 } else if (real_type == XA_ATOM) {
1748 Atom *atoms = (Atom *)propdata;
1749
1750 SDL_Log("{");
1751 for (i = 0; i < items_read; i++) {
1752 char *atomname = X11_XGetAtomName(display, atoms[i]);
1753 if (atomname) {
1754 SDL_Log(" %s", atomname);
1755 X11_XFree(atomname);
1756 }
1757 }
1758 SDL_Log(" }");
1759 } else {
1760 char *atomname = X11_XGetAtomName(display, real_type);
1761 SDL_Log("Unknown type: 0x%lx (%s)", real_type, atomname ? atomname : "UNKNOWN");
1762 if (atomname) {
1763 X11_XFree(atomname);
1764 }
1765 }
1766 }
1767 if (status == Success) {
1768 X11_XFree(propdata);
1769 }
1770#endif // DEBUG_XEVENTS
1771
1772 /* Take advantage of this moment to make sure user_time has a
1773 valid timestamp from the X server, so if we later try to
1774 raise/restore this window, _NET_ACTIVE_WINDOW can have a
1775 non-zero timestamp, even if there's never been a mouse or
1776 key press to this window so far. Note that we don't try to
1777 set _NET_WM_USER_TIME here, though. That's only for legit
1778 user interaction with the window. */
1779 if (!data->user_time) {
1780 data->user_time = xevent->xproperty.time;
1781 }
1782
1783 if (xevent->xproperty.atom == data->videodata->atoms._NET_WM_STATE) {
1784 /* Get the new state from the window manager.
1785 Compositing window managers can alter visibility of windows
1786 without ever mapping / unmapping them, so we handle that here,
1787 because they use the NETWM protocol to notify us of changes.
1788 */
1789 const SDL_WindowFlags flags = X11_GetNetWMState(_this, data->window, xevent->xproperty.window);
1790 const SDL_WindowFlags changed = flags ^ data->window->flags;
1791
1792 if ((changed & (SDL_WINDOW_HIDDEN | SDL_WINDOW_FULLSCREEN)) != 0) {
1793 if (flags & SDL_WINDOW_HIDDEN) {
1794 X11_DispatchUnmapNotify(data);
1795 } else {
1796 X11_DispatchMapNotify(data);
1797 }
1798 }
1799
1800 if (!SDL_WINDOW_IS_POPUP(data->window)) {
1801 if (changed & SDL_WINDOW_FULLSCREEN) {
1802 data->pending_operation &= ~X11_PENDING_OP_FULLSCREEN;
1803
1804 if (flags & SDL_WINDOW_FULLSCREEN) {
1805 if (!(flags & SDL_WINDOW_MINIMIZED)) {
1806 const bool commit = SDL_memcmp(&data->window->current_fullscreen_mode, &data->requested_fullscreen_mode, sizeof(SDL_DisplayMode)) != 0;
1807
1808 SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_ENTER_FULLSCREEN, 0, 0);
1809 if (commit) {
1810 /* This was initiated by the compositor, or the mode was changed between the request and the window
1811 * becoming fullscreen. Switch to the application requested mode if necessary.
1812 */
1813 SDL_copyp(&data->window->current_fullscreen_mode, &data->window->requested_fullscreen_mode);
1814 SDL_UpdateFullscreenMode(data->window, SDL_FULLSCREEN_OP_UPDATE, true);
1815 } else {
1816 SDL_UpdateFullscreenMode(data->window, SDL_FULLSCREEN_OP_ENTER, false);
1817 }
1818 }
1819 } else {
1820 SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_LEAVE_FULLSCREEN, 0, 0);
1821 SDL_UpdateFullscreenMode(data->window, false, false);
1822
1823 SDL_zero(data->requested_fullscreen_mode);
1824
1825 // Need to restore or update any limits changed while the window was fullscreen.
1826 X11_SetWindowMinMax(data->window, !!(flags & SDL_WINDOW_MAXIMIZED));
1827
1828 // Toggle the borders if they were forced on while creating a borderless fullscreen window.
1829 if (data->fullscreen_borders_forced_on) {
1830 data->toggle_borders = true;
1831 data->fullscreen_borders_forced_on = false;
1832 }
1833 }
1834
1835 if ((flags & SDL_WINDOW_FULLSCREEN) &&
1836 (data->border_top || data->border_left || data->border_bottom || data->border_right)) {
1837 /* If the window is entering fullscreen and the borders are
1838 * non-zero sized, turn off size events until the borders are
1839 * shut off to avoid bogus window sizes and positions, and
1840 * note that the old borders were non-zero for restoration.
1841 */
1842 data->size_move_event_flags |= X11_SIZE_MOVE_EVENTS_WAIT_FOR_BORDERS;
1843 data->previous_borders_nonzero = true;
1844 } else if (!(flags & SDL_WINDOW_FULLSCREEN) &&
1845 data->previous_borders_nonzero &&
1846 (!data->border_top && !data->border_left && !data->border_bottom && !data->border_right)) {
1847 /* If the window is leaving fullscreen and the current borders
1848 * are zero sized, but weren't when entering fullscreen, turn
1849 * off size events until the borders come back to avoid bogus
1850 * window sizes and positions.
1851 */
1852 data->size_move_event_flags |= X11_SIZE_MOVE_EVENTS_WAIT_FOR_BORDERS;
1853 data->previous_borders_nonzero = false;
1854 } else {
1855 data->size_move_event_flags = 0;
1856 data->previous_borders_nonzero = false;
1857
1858 if (!(data->window->flags & SDL_WINDOW_FULLSCREEN) && data->toggle_borders) {
1859 data->toggle_borders = false;
1860 X11_SetWindowBordered(_this, data->window, !(data->window->flags & SDL_WINDOW_BORDERLESS));
1861 }
1862 }
1863 }
1864 if ((changed & SDL_WINDOW_MAXIMIZED) && ((flags & SDL_WINDOW_MAXIMIZED) && !(flags & SDL_WINDOW_MINIMIZED))) {
1865 data->pending_operation &= ~X11_PENDING_OP_MAXIMIZE;
1866 if ((changed & SDL_WINDOW_MINIMIZED)) {
1867 data->pending_operation &= ~X11_PENDING_OP_RESTORE;
1868 // If coming out of minimized, send a restore event before sending maximized.
1869 SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_RESTORED, 0, 0);
1870 }
1871 SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MAXIMIZED, 0, 0);
1872 }
1873 if ((changed & SDL_WINDOW_MINIMIZED) && (flags & SDL_WINDOW_MINIMIZED)) {
1874 data->pending_operation &= ~X11_PENDING_OP_MINIMIZE;
1875 SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MINIMIZED, 0, 0);
1876 }
1877 if (!(flags & (SDL_WINDOW_MAXIMIZED | SDL_WINDOW_MINIMIZED))) {
1878 data->pending_operation &= ~X11_PENDING_OP_RESTORE;
1879 SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_RESTORED, 0, 0);
1880
1881 // Apply any pending state if restored.
1882 if (!(flags & SDL_WINDOW_FULLSCREEN)) {
1883 if (data->pending_position) {
1884 data->pending_position = false;
1885 data->pending_operation |= X11_PENDING_OP_MOVE;
1886 data->expected.x = data->window->pending.x - data->border_left;
1887 data->expected.y = data->window->pending.y - data->border_top;
1888 X11_XMoveWindow(display, data->xwindow, data->expected.x, data->expected.y);
1889 }
1890 if (data->pending_size) {
1891 data->pending_size = false;
1892 data->pending_operation |= X11_PENDING_OP_RESIZE;
1893 data->expected.w = data->window->pending.w;
1894 data->expected.h = data->window->pending.h;
1895 X11_XResizeWindow(display, data->xwindow, data->window->pending.w, data->window->pending.h);
1896 }
1897 }
1898 }
1899 if ((flags & SDL_WINDOW_INPUT_FOCUS)) {
1900 if (data->pending_move) {
1901 DispatchWindowMove(_this, data, &data->pending_move_point);
1902 data->pending_move = false;
1903 }
1904 }
1905 }
1906 if (changed & SDL_WINDOW_OCCLUDED) {
1907 SDL_SendWindowEvent(data->window, (flags & SDL_WINDOW_OCCLUDED) ? SDL_EVENT_WINDOW_OCCLUDED : SDL_EVENT_WINDOW_EXPOSED, 0, 0);
1908 }
1909 } else if (xevent->xproperty.atom == videodata->atoms.XKLAVIER_STATE) {
1910 /* Hack for Ubuntu 12.04 (etc) that doesn't send MappingNotify
1911 events when the keyboard layout changes (for example,
1912 changing from English to French on the menubar's keyboard
1913 icon). Since it changes the XKLAVIER_STATE property, we
1914 notice and reinit our keymap here. This might not be the
1915 right approach, but it seems to work. */
1916 X11_UpdateKeymap(_this, true);
1917 } else if (xevent->xproperty.atom == videodata->atoms._NET_FRAME_EXTENTS) {
1918 /* Events are disabled when leaving fullscreen until the borders appear to avoid
1919 * incorrect size/position events.
1920 */
1921 if (data->size_move_event_flags) {
1922 data->size_move_event_flags &= ~X11_SIZE_MOVE_EVENTS_WAIT_FOR_BORDERS;
1923 X11_GetBorderValues(data);
1924
1925 }
1926 if (!(data->window->flags & SDL_WINDOW_FULLSCREEN) && data->toggle_borders) {
1927 data->toggle_borders = false;
1928 X11_SetWindowBordered(_this, data->window, !(data->window->flags & SDL_WINDOW_BORDERLESS));
1929 }
1930 }
1931 } break;
1932
1933 case SelectionNotify:
1934 {
1935 Atom target = xevent->xselection.target;
1936#ifdef DEBUG_XEVENTS
1937 SDL_Log("window 0x%lx: SelectionNotify (requestor = 0x%lx, target = 0x%lx)", xevent->xany.window,
1938 xevent->xselection.requestor, xevent->xselection.target);
1939#endif
1940 if (target == data->xdnd_req) {
1941 // read data
1942 SDL_x11Prop p;
1943 X11_ReadProperty(&p, display, data->xwindow, videodata->atoms.PRIMARY);
1944
1945 if (p.format == 8) {
1946 char *saveptr = NULL;
1947 char *name = X11_XGetAtomName(display, target);
1948 if (name) {
1949 char *token = SDL_strtok_r((char *)p.data, "\r\n", &saveptr);
1950 while (token) {
1951 if ((SDL_strcmp("text/plain;charset=utf-8", name) == 0) ||
1952 (SDL_strcmp("UTF8_STRING", name) == 0) ||
1953 (SDL_strcmp("text/plain", name) == 0) ||
1954 (SDL_strcmp("TEXT", name) == 0)) {
1955 SDL_SendDropText(data->window, token);
1956 } else if (SDL_strcmp("text/uri-list", name) == 0) {
1957 if (SDL_URIToLocal(token, token) >= 0) {
1958 SDL_SendDropFile(data->window, NULL, token);
1959 }
1960 }
1961 token = SDL_strtok_r(NULL, "\r\n", &saveptr);
1962 }
1963 X11_XFree(name);
1964 }
1965 SDL_SendDropComplete(data->window);
1966 }
1967 X11_XFree(p.data);
1968
1969 // send reply
1970 SDL_memset(&m, 0, sizeof(XClientMessageEvent));
1971 m.type = ClientMessage;
1972 m.display = display;
1973 m.window = data->xdnd_source;
1974 m.message_type = videodata->atoms.XdndFinished;
1975 m.format = 32;
1976 m.data.l[0] = data->xwindow;
1977 m.data.l[1] = 1;
1978 m.data.l[2] = videodata->atoms.XdndActionCopy;
1979 X11_XSendEvent(display, data->xdnd_source, False, NoEventMask, (XEvent *)&m);
1980
1981 X11_XSync(display, False);
1982 }
1983 } break;
1984
1985 default:
1986 {
1987#ifdef DEBUG_XEVENTS
1988 SDL_Log("window 0x%lx: Unhandled event %d", xevent->xany.window, xevent->type);
1989#endif
1990 } break;
1991 }
1992}
1993
1994static void X11_HandleFocusChanges(SDL_VideoDevice *_this)
1995{
1996 SDL_VideoData *videodata = _this->internal;
1997 int i;
1998
1999 if (videodata && videodata->windowlist) {
2000 for (i = 0; i < videodata->numwindows; ++i) {
2001 SDL_WindowData *data = videodata->windowlist[i];
2002 if (data && data->pending_focus != PENDING_FOCUS_NONE) {
2003 Uint64 now = SDL_GetTicks();
2004 if (now >= data->pending_focus_time) {
2005 if (data->pending_focus == PENDING_FOCUS_IN) {
2006 X11_DispatchFocusIn(_this, data);
2007 } else {
2008 X11_DispatchFocusOut(_this, data);
2009 }
2010 data->pending_focus = PENDING_FOCUS_NONE;
2011 }
2012 }
2013 }
2014 }
2015}
2016
2017static Bool isAnyEvent(Display *display, XEvent *ev, XPointer arg)
2018{
2019 return True;
2020}
2021
2022static bool X11_PollEvent(Display *display, XEvent *event)
2023{
2024 if (!X11_XCheckIfEvent(display, event, isAnyEvent, NULL)) {
2025 return false;
2026 }
2027
2028 return true;
2029}
2030
2031void X11_SendWakeupEvent(SDL_VideoDevice *_this, SDL_Window *window)
2032{
2033 SDL_VideoData *data = _this->internal;
2034 Display *req_display = data->request_display;
2035 Window xwindow = window->internal->xwindow;
2036 XClientMessageEvent event;
2037
2038 SDL_memset(&event, 0, sizeof(XClientMessageEvent));
2039 event.type = ClientMessage;
2040 event.display = req_display;
2041 event.send_event = True;
2042 event.message_type = data->atoms._SDL_WAKEUP;
2043 event.format = 8;
2044
2045 X11_XSendEvent(req_display, xwindow, False, NoEventMask, (XEvent *)&event);
2046 /* XSendEvent returns a status and it could be BadValue or BadWindow. If an
2047 error happens it is an SDL's internal error and there is nothing we can do here. */
2048 X11_XFlush(req_display);
2049}
2050
2051int X11_WaitEventTimeout(SDL_VideoDevice *_this, Sint64 timeoutNS)
2052{
2053 SDL_VideoData *videodata = _this->internal;
2054 Display *display;
2055 XEvent xevent;
2056 display = videodata->display;
2057
2058 SDL_zero(xevent);
2059
2060 // Flush and poll to grab any events already read and queued
2061 X11_XFlush(display);
2062 if (X11_PollEvent(display, &xevent)) {
2063 // Fall through
2064 } else if (timeoutNS == 0) {
2065 return 0;
2066 } else {
2067 // Use SDL_IOR_NO_RETRY to ensure SIGINT will break us out of our wait
2068 int err = SDL_IOReady(ConnectionNumber(display), SDL_IOR_READ | SDL_IOR_NO_RETRY, timeoutNS);
2069 if (err > 0) {
2070 if (!X11_PollEvent(display, &xevent)) {
2071 /* Someone may have beat us to reading the fd. Return 1 here to
2072 * trigger the normal spurious wakeup logic in the event core. */
2073 return 1;
2074 }
2075 } else if (err == 0) {
2076 // Timeout
2077 return 0;
2078 } else {
2079 // Error returned from poll()/select()
2080
2081 if (errno == EINTR) {
2082 /* If the wait was interrupted by a signal, we may have generated a
2083 * SDL_EVENT_QUIT event. Let the caller know to call SDL_PumpEvents(). */
2084 return 1;
2085 } else {
2086 return err;
2087 }
2088 }
2089 }
2090
2091 X11_DispatchEvent(_this, &xevent);
2092
2093#ifdef SDL_USE_LIBDBUS
2094 SDL_DBus_PumpEvents();
2095#endif
2096 return 1;
2097}
2098
2099void X11_PumpEvents(SDL_VideoDevice *_this)
2100{
2101 SDL_VideoData *data = _this->internal;
2102 XEvent xevent;
2103 int i;
2104
2105 /* Check if a display had the mode changed and is waiting for a window to asynchronously become
2106 * fullscreen. If there is no fullscreen window past the elapsed timeout, revert the mode switch.
2107 */
2108 for (i = 0; i < _this->num_displays; ++i) {
2109 if (_this->displays[i]->internal->mode_switch_deadline_ns) {
2110 if (_this->displays[i]->fullscreen_window) {
2111 _this->displays[i]->internal->mode_switch_deadline_ns = 0;
2112 } else if (SDL_GetTicksNS() >= _this->displays[i]->internal->mode_switch_deadline_ns) {
2113 SDL_LogError(SDL_LOG_CATEGORY_VIDEO,
2114 "Time out elapsed after mode switch on display %" SDL_PRIu32 " with no window becoming fullscreen; reverting", _this->displays[i]->id);
2115 SDL_SetDisplayModeForDisplay(_this->displays[i], NULL);
2116 _this->displays[i]->internal->mode_switch_deadline_ns = 0;
2117 }
2118 }
2119 }
2120
2121 if (data->last_mode_change_deadline) {
2122 if (SDL_GetTicks() >= data->last_mode_change_deadline) {
2123 data->last_mode_change_deadline = 0; // assume we're done.
2124 }
2125 }
2126
2127 // Update activity every 30 seconds to prevent screensaver
2128 if (_this->suspend_screensaver) {
2129 Uint64 now = SDL_GetTicks();
2130 if (!data->screensaver_activity || now >= (data->screensaver_activity + 30000)) {
2131 X11_XResetScreenSaver(data->display);
2132
2133#ifdef SDL_USE_LIBDBUS
2134 SDL_DBus_ScreensaverTickle();
2135#endif
2136
2137 data->screensaver_activity = now;
2138 }
2139 }
2140
2141 SDL_zero(xevent);
2142
2143 // Keep processing pending events
2144 while (X11_PollEvent(data->display, &xevent)) {
2145 X11_DispatchEvent(_this, &xevent);
2146 }
2147
2148#ifdef SDL_USE_LIBDBUS
2149 SDL_DBus_PumpEvents();
2150#endif
2151
2152 // FIXME: Only need to do this when there are pending focus changes
2153 X11_HandleFocusChanges(_this);
2154
2155 // FIXME: Only need to do this when there are flashing windows
2156 for (i = 0; i < data->numwindows; ++i) {
2157 if (data->windowlist[i] != NULL &&
2158 data->windowlist[i]->flash_cancel_time &&
2159 SDL_GetTicks() >= data->windowlist[i]->flash_cancel_time) {
2160 X11_FlashWindow(_this, data->windowlist[i]->window, SDL_FLASH_CANCEL);
2161 }
2162 }
2163
2164 if (data->xinput_hierarchy_changed) {
2165 X11_Xinput2UpdateDevices(_this, false);
2166 data->xinput_hierarchy_changed = false;
2167 }
2168}
2169
2170bool X11_SuspendScreenSaver(SDL_VideoDevice *_this)
2171{
2172#ifdef SDL_VIDEO_DRIVER_X11_XSCRNSAVER
2173 SDL_VideoData *data = _this->internal;
2174 int dummy;
2175 int major_version, minor_version;
2176#endif // SDL_VIDEO_DRIVER_X11_XSCRNSAVER
2177
2178#ifdef SDL_USE_LIBDBUS
2179 if (SDL_DBus_ScreensaverInhibit(_this->suspend_screensaver)) {
2180 return true;
2181 }
2182
2183 if (_this->suspend_screensaver) {
2184 SDL_DBus_ScreensaverTickle();
2185 }
2186#endif
2187
2188#ifdef SDL_VIDEO_DRIVER_X11_XSCRNSAVER
2189 if (SDL_X11_HAVE_XSS) {
2190 // X11_XScreenSaverSuspend was introduced in MIT-SCREEN-SAVER 1.1
2191 if (!X11_XScreenSaverQueryExtension(data->display, &dummy, &dummy) ||
2192 !X11_XScreenSaverQueryVersion(data->display,
2193 &major_version, &minor_version) ||
2194 major_version < 1 || (major_version == 1 && minor_version < 1)) {
2195 return SDL_Unsupported();
2196 }
2197
2198 X11_XScreenSaverSuspend(data->display, _this->suspend_screensaver);
2199 X11_XResetScreenSaver(data->display);
2200 return true;
2201 }
2202#endif
2203 return SDL_Unsupported();
2204}
2205
2206#endif // SDL_VIDEO_DRIVER_X11
2207