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 "../SDL_sysvideo.h"
26#include "../SDL_pixels_c.h"
27#include "../../events/SDL_keyboard_c.h"
28#include "../../events/SDL_mouse_c.h"
29#include "../../events/SDL_events_c.h"
30#include "../../core/unix/SDL_appid.h"
31
32#include "SDL_x11video.h"
33#include "SDL_x11mouse.h"
34#include "SDL_x11xinput2.h"
35#include "SDL_x11xfixes.h"
36
37#ifdef SDL_VIDEO_OPENGL_EGL
38#include "SDL_x11opengles.h"
39#endif
40
41#include "SDL_x11xsync.h"
42
43#define _NET_WM_STATE_REMOVE 0l
44#define _NET_WM_STATE_ADD 1l
45
46#define CHECK_WINDOW_DATA(window) \
47 if (!window) { \
48 return SDL_SetError("Invalid window"); \
49 } \
50 if (!window->internal) { \
51 return SDL_SetError("Invalid window driver data"); \
52 }
53
54#define CHECK_DISPLAY_DATA(display) \
55 if (!_display) { \
56 return SDL_SetError("Invalid display"); \
57 } \
58 if (!_display->internal) { \
59 return SDL_SetError("Invalid display driver data"); \
60 }
61
62static Bool isMapNotify(Display *dpy, XEvent *ev, XPointer win) // NOLINT(readability-non-const-parameter): cannot make XPointer a const pointer due to typedef
63{
64 return ev->type == MapNotify && ev->xmap.window == *((Window *)win);
65}
66static Bool isUnmapNotify(Display *dpy, XEvent *ev, XPointer win) // NOLINT(readability-non-const-parameter): cannot make XPointer a const pointer due to typedef
67{
68 return ev->type == UnmapNotify && ev->xunmap.window == *((Window *)win);
69}
70
71/*
72static Bool isConfigureNotify(Display *dpy, XEvent *ev, XPointer win)
73{
74 return ev->type == ConfigureNotify && ev->xconfigure.window == *((Window*)win);
75}
76static Bool X11_XIfEventTimeout(Display *display, XEvent *event_return, Bool (*predicate)(), XPointer arg, int timeoutMS)
77{
78 Uint64 start = SDL_GetTicks();
79
80 while (!X11_XCheckIfEvent(display, event_return, predicate, arg)) {
81 if (SDL_GetTicks() >= (start + timeoutMS)) {
82 return False;
83 }
84 }
85 return True;
86}
87*/
88
89static bool X11_IsWindowMapped(SDL_VideoDevice *_this, SDL_Window *window)
90{
91 SDL_WindowData *data = window->internal;
92 SDL_VideoData *videodata = _this->internal;
93 XWindowAttributes attr;
94
95 X11_XGetWindowAttributes(videodata->display, data->xwindow, &attr);
96 if (attr.map_state != IsUnmapped) {
97 return true;
98 } else {
99 return false;
100 }
101}
102
103static bool X11_IsDisplayOk(Display *display)
104{
105 if (display->flags & XlibDisplayIOError) {
106 return false;
107 }
108 return true;
109}
110
111#if 0
112static bool X11_IsActionAllowed(SDL_Window *window, Atom action)
113{
114 SDL_WindowData *data = window->internal;
115 Atom _NET_WM_ALLOWED_ACTIONS = data->videodata->_NET_WM_ALLOWED_ACTIONS;
116 Atom type;
117 Display *display = data->videodata->display;
118 int form;
119 unsigned long remain;
120 unsigned long len, i;
121 Atom *list;
122 bool ret = false;
123
124 if (X11_XGetWindowProperty(display, data->xwindow, _NET_WM_ALLOWED_ACTIONS, 0, 1024, False, XA_ATOM, &type, &form, &len, &remain, (unsigned char **)&list) == Success) {
125 for (i=0; i<len; ++i) {
126 if (list[i] == action) {
127 ret = true;
128 break;
129 }
130 }
131 X11_XFree(list);
132 }
133 return ret;
134}
135#endif // 0
136
137static void X11_FlushPendingEvents(SDL_VideoDevice *_this, SDL_Window *window)
138{
139 // Serialize and restore the pending flags, as they may be overwritten while flushing.
140 const bool last_position_pending = window->last_position_pending;
141 const bool last_size_pending = window->last_size_pending;
142
143 X11_SyncWindow(_this, window);
144
145 window->last_position_pending = last_position_pending;
146 window->last_size_pending = last_size_pending;
147}
148
149void X11_SetNetWMState(SDL_VideoDevice *_this, Window xwindow, SDL_WindowFlags flags)
150{
151 SDL_VideoData *videodata = _this->internal;
152 Display *display = videodata->display;
153 // !!! FIXME: just dereference videodata below instead of copying to locals.
154 Atom _NET_WM_STATE = videodata->atoms._NET_WM_STATE;
155 // Atom _NET_WM_STATE_HIDDEN = videodata->atoms._NET_WM_STATE_HIDDEN;
156 Atom _NET_WM_STATE_FOCUSED = videodata->atoms._NET_WM_STATE_FOCUSED;
157 Atom _NET_WM_STATE_MAXIMIZED_VERT = videodata->atoms._NET_WM_STATE_MAXIMIZED_VERT;
158 Atom _NET_WM_STATE_MAXIMIZED_HORZ = videodata->atoms._NET_WM_STATE_MAXIMIZED_HORZ;
159 Atom _NET_WM_STATE_FULLSCREEN = videodata->atoms._NET_WM_STATE_FULLSCREEN;
160 Atom _NET_WM_STATE_ABOVE = videodata->atoms._NET_WM_STATE_ABOVE;
161 Atom _NET_WM_STATE_SKIP_TASKBAR = videodata->atoms._NET_WM_STATE_SKIP_TASKBAR;
162 Atom _NET_WM_STATE_SKIP_PAGER = videodata->atoms._NET_WM_STATE_SKIP_PAGER;
163 Atom _NET_WM_STATE_MODAL = videodata->atoms._NET_WM_STATE_MODAL;
164 Atom atoms[16];
165 int count = 0;
166
167 /* The window manager sets this property, we shouldn't set it.
168 If we did, this would indicate to the window manager that we don't
169 actually want to be mapped during X11_XMapRaised(), which would be bad.
170 *
171 if ((flags & SDL_WINDOW_HIDDEN) != 0) {
172 atoms[count++] = _NET_WM_STATE_HIDDEN;
173 }
174 */
175
176 if (flags & SDL_WINDOW_ALWAYS_ON_TOP) {
177 atoms[count++] = _NET_WM_STATE_ABOVE;
178 }
179 if (flags & SDL_WINDOW_UTILITY) {
180 atoms[count++] = _NET_WM_STATE_SKIP_TASKBAR;
181 atoms[count++] = _NET_WM_STATE_SKIP_PAGER;
182 }
183 if (flags & SDL_WINDOW_INPUT_FOCUS) {
184 atoms[count++] = _NET_WM_STATE_FOCUSED;
185 }
186 if (flags & SDL_WINDOW_MAXIMIZED) {
187 atoms[count++] = _NET_WM_STATE_MAXIMIZED_VERT;
188 atoms[count++] = _NET_WM_STATE_MAXIMIZED_HORZ;
189 }
190 if (flags & SDL_WINDOW_FULLSCREEN) {
191 atoms[count++] = _NET_WM_STATE_FULLSCREEN;
192 }
193 if (flags & SDL_WINDOW_MODAL) {
194 atoms[count++] = _NET_WM_STATE_MODAL;
195 }
196
197 SDL_assert(count <= SDL_arraysize(atoms));
198
199 if (count > 0) {
200 X11_XChangeProperty(display, xwindow, _NET_WM_STATE, XA_ATOM, 32,
201 PropModeReplace, (unsigned char *)atoms, count);
202 } else {
203 X11_XDeleteProperty(display, xwindow, _NET_WM_STATE);
204 }
205}
206
207static void X11_ConstrainPopup(SDL_Window *window, bool output_to_pending)
208{
209 // Clamp popup windows to the output borders
210 if (SDL_WINDOW_IS_POPUP(window)) {
211 SDL_Window *w;
212 SDL_DisplayID displayID;
213 SDL_Rect rect;
214 int abs_x = window->last_position_pending ? window->pending.x : window->floating.x;
215 int abs_y = window->last_position_pending ? window->pending.y : window->floating.y;
216 int offset_x = 0, offset_y = 0;
217
218 // Calculate the total offset from the parents
219 for (w = window->parent; SDL_WINDOW_IS_POPUP(w); w = w->parent) {
220 offset_x += w->x;
221 offset_y += w->y;
222 }
223
224 offset_x += w->x;
225 offset_y += w->y;
226 abs_x += offset_x;
227 abs_y += offset_y;
228
229 displayID = SDL_GetDisplayForWindow(w);
230
231 SDL_GetDisplayBounds(displayID, &rect);
232 if (abs_x + window->w > rect.x + rect.w) {
233 abs_x -= (abs_x + window->w) - (rect.x + rect.w);
234 }
235 if (abs_y + window->h > rect.y + rect.h) {
236 abs_y -= (abs_y + window->h) - (rect.y + rect.h);
237 }
238 abs_x = SDL_max(abs_x, rect.x);
239 abs_y = SDL_max(abs_y, rect.y);
240
241 if (output_to_pending) {
242 window->pending.x = abs_x - offset_x;
243 window->pending.y = abs_y - offset_y;
244 } else {
245 window->floating.x = window->windowed.x = abs_x - offset_x;
246 window->floating.y = window->windowed.y = abs_y - offset_y;
247 }
248 }
249}
250
251static void X11_SetKeyboardFocus(SDL_Window *window, bool set_active_focus)
252{
253 SDL_Window *toplevel = window;
254
255 // Find the toplevel parent
256 while (SDL_WINDOW_IS_POPUP(toplevel)) {
257 toplevel = toplevel->parent;
258 }
259
260 toplevel->internal->keyboard_focus = window;
261
262 if (set_active_focus && !window->is_hiding && !window->is_destroying) {
263 SDL_SetKeyboardFocus(window);
264 }
265}
266
267Uint32 X11_GetNetWMState(SDL_VideoDevice *_this, SDL_Window *window, Window xwindow)
268{
269 SDL_VideoData *videodata = _this->internal;
270 Display *display = videodata->display;
271 Atom _NET_WM_STATE = videodata->atoms._NET_WM_STATE;
272 Atom _NET_WM_STATE_HIDDEN = videodata->atoms._NET_WM_STATE_HIDDEN;
273 Atom _NET_WM_STATE_FOCUSED = videodata->atoms._NET_WM_STATE_FOCUSED;
274 Atom _NET_WM_STATE_MAXIMIZED_VERT = videodata->atoms._NET_WM_STATE_MAXIMIZED_VERT;
275 Atom _NET_WM_STATE_MAXIMIZED_HORZ = videodata->atoms._NET_WM_STATE_MAXIMIZED_HORZ;
276 Atom _NET_WM_STATE_FULLSCREEN = videodata->atoms._NET_WM_STATE_FULLSCREEN;
277 Atom actualType;
278 int actualFormat;
279 unsigned long i, numItems, bytesAfter;
280 unsigned char *propertyValue = NULL;
281 long maxLength = 1024;
282 SDL_WindowFlags flags = 0;
283
284 if (X11_XGetWindowProperty(display, xwindow, _NET_WM_STATE,
285 0l, maxLength, False, XA_ATOM, &actualType,
286 &actualFormat, &numItems, &bytesAfter,
287 &propertyValue) == Success) {
288 Atom *atoms = (Atom *)propertyValue;
289 int maximized = 0;
290 int fullscreen = 0;
291
292 for (i = 0; i < numItems; ++i) {
293 if (atoms[i] == _NET_WM_STATE_HIDDEN) {
294 flags |= SDL_WINDOW_MINIMIZED | SDL_WINDOW_OCCLUDED;
295 } else if (atoms[i] == _NET_WM_STATE_FOCUSED) {
296 flags |= SDL_WINDOW_INPUT_FOCUS;
297 } else if (atoms[i] == _NET_WM_STATE_MAXIMIZED_VERT) {
298 maximized |= 1;
299 } else if (atoms[i] == _NET_WM_STATE_MAXIMIZED_HORZ) {
300 maximized |= 2;
301 } else if (atoms[i] == _NET_WM_STATE_FULLSCREEN) {
302 fullscreen = 1;
303 }
304 }
305
306 if (fullscreen == 1) {
307 if (window->flags & SDL_WINDOW_FULLSCREEN) {
308 // Pick whatever state the window expects
309 flags |= (window->flags & SDL_WINDOW_FULLSCREEN);
310 } else {
311 // Assume we're fullscreen desktop
312 flags |= SDL_WINDOW_FULLSCREEN;
313 }
314 }
315
316 if (maximized == 3) {
317 /* Fullscreen windows are maximized on some window managers,
318 and this is functional behavior - if maximized is removed,
319 the windows remain floating centered and not covering the
320 rest of the desktop. So we just won't change the maximize
321 state for fullscreen windows here, otherwise SDL would think
322 we're always maximized when fullscreen and not restore the
323 correct state when leaving fullscreen.
324 */
325 if (fullscreen) {
326 flags |= (window->flags & SDL_WINDOW_MAXIMIZED);
327 } else {
328 flags |= SDL_WINDOW_MAXIMIZED;
329 }
330 }
331
332 /* If the window is unmapped, numItems will be zero and _NET_WM_STATE_HIDDEN
333 * will not be set. Do an additional check to see if the window is unmapped
334 * and mark it as SDL_WINDOW_HIDDEN if it is.
335 */
336 {
337 XWindowAttributes attr;
338 SDL_memset(&attr, 0, sizeof(attr));
339 X11_XGetWindowAttributes(videodata->display, xwindow, &attr);
340 if (attr.map_state == IsUnmapped) {
341 flags |= SDL_WINDOW_HIDDEN;
342 }
343 }
344 X11_XFree(propertyValue);
345 }
346
347 // FIXME, check the size hints for resizable
348 // flags |= SDL_WINDOW_RESIZABLE;
349
350 return flags;
351}
352
353static bool SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, Window w)
354{
355 SDL_VideoData *videodata = _this->internal;
356 SDL_DisplayData *displaydata = SDL_GetDisplayDriverDataForWindow(window);
357 SDL_WindowData *data;
358 int numwindows = videodata->numwindows;
359 int windowlistlength = videodata->windowlistlength;
360 SDL_WindowData **windowlist = videodata->windowlist;
361
362 // Allocate the window data
363 data = (SDL_WindowData *)SDL_calloc(1, sizeof(*data));
364 if (!data) {
365 return false;
366 }
367 data->videodata = videodata;
368 data->window = window;
369 data->xwindow = w;
370 data->hit_test_result = SDL_HITTEST_NORMAL;
371
372 X11_CreateInputContext(data);
373
374 // Associate the data with the window
375
376 if (numwindows < windowlistlength) {
377 windowlist[numwindows] = data;
378 videodata->numwindows++;
379 } else {
380 SDL_WindowData ** new_windowlist = (SDL_WindowData **)SDL_realloc(windowlist, (numwindows + 1) * sizeof(*windowlist));
381 if (!new_windowlist) {
382 SDL_free(data);
383 return false;
384 }
385 windowlist = new_windowlist;
386 windowlist[numwindows] = data;
387 videodata->numwindows++;
388 videodata->windowlistlength++;
389 videodata->windowlist = windowlist;
390 }
391
392 // Fill in the SDL window with the window data
393 {
394 XWindowAttributes attrib;
395
396 X11_XGetWindowAttributes(data->videodata->display, w, &attrib);
397 if (!SDL_WINDOW_IS_POPUP(window)) {
398 window->x = window->windowed.x = window->floating.x = attrib.x;
399 window->y = window->windowed.y = window->floating.y = attrib.y - data->border_top;
400 }
401 window->w = window->windowed.w = window->floating.w = attrib.width;
402 window->h = window->windowed.h = window->floating.h = attrib.height;
403 if (attrib.map_state != IsUnmapped) {
404 window->flags &= ~SDL_WINDOW_HIDDEN;
405 } else {
406 window->flags |= SDL_WINDOW_HIDDEN;
407 }
408 data->visual = attrib.visual;
409 data->colormap = attrib.colormap;
410 }
411
412 window->flags |= X11_GetNetWMState(_this, window, w);
413
414 {
415 Window FocalWindow;
416 int RevertTo = 0;
417 X11_XGetInputFocus(data->videodata->display, &FocalWindow, &RevertTo);
418 if (FocalWindow == w) {
419 window->flags |= SDL_WINDOW_INPUT_FOCUS;
420 }
421
422 if (window->flags & SDL_WINDOW_INPUT_FOCUS) {
423 SDL_SetKeyboardFocus(data->window);
424 }
425
426 if (window->flags & SDL_WINDOW_MOUSE_GRABBED) {
427 // Tell x11 to clip mouse
428 }
429 }
430
431 if (window->flags & SDL_WINDOW_EXTERNAL) {
432 // Query the title from the existing window
433 window->title = X11_GetWindowTitle(_this, w);
434 }
435
436 SDL_PropertiesID props = SDL_GetWindowProperties(window);
437 int screen = (displaydata ? displaydata->screen : 0);
438 SDL_SetPointerProperty(props, SDL_PROP_WINDOW_X11_DISPLAY_POINTER, data->videodata->display);
439 SDL_SetNumberProperty(props, SDL_PROP_WINDOW_X11_SCREEN_NUMBER, screen);
440 SDL_SetNumberProperty(props, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, data->xwindow);
441
442 // All done!
443 window->internal = data;
444 return true;
445}
446
447static void SetupWindowInput(SDL_VideoDevice *_this, SDL_Window *window)
448{
449 long fevent = 0;
450 SDL_WindowData *data = window->internal;
451 Window xwindow = data->xwindow;
452
453#ifdef X_HAVE_UTF8_STRING
454 if (SDL_X11_HAVE_UTF8 && data->ic) {
455 X11_XGetICValues(data->ic, XNFilterEvents, &fevent, NULL);
456 }
457#endif
458
459 X11_Xinput2SelectTouch(_this, window);
460
461 {
462 unsigned int x11_keyboard_events = KeyPressMask | KeyReleaseMask;
463 unsigned int x11_pointer_events = ButtonPressMask | ButtonReleaseMask | PointerMotionMask;
464
465 X11_Xinput2SelectMouseAndKeyboard(_this, window);
466
467 // If XInput2 can handle pointer and keyboard events, we don't track them here
468 if (data->xinput2_keyboard_enabled) {
469 x11_keyboard_events = 0;
470 }
471 if (data->xinput2_mouse_enabled) {
472 x11_pointer_events = 0;
473 }
474
475 X11_XSelectInput(data->videodata->display, xwindow,
476 (FocusChangeMask | EnterWindowMask | LeaveWindowMask | ExposureMask |
477 x11_keyboard_events | x11_pointer_events |
478 PropertyChangeMask | StructureNotifyMask |
479 KeymapStateMask | fevent));
480 }
481}
482
483static void SetWindowBordered(Display *display, int screen, Window window, bool border)
484{
485 /*
486 * this code used to check for KWM_WIN_DECORATION, but KDE hasn't
487 * supported it for years and years. It now respects _MOTIF_WM_HINTS.
488 * Gnome is similar: just use the Motif atom.
489 */
490
491 Atom WM_HINTS = X11_XInternAtom(display, "_MOTIF_WM_HINTS", True);
492 if (WM_HINTS != None) {
493 // Hints used by Motif compliant window managers
494 struct
495 {
496 unsigned long flags;
497 unsigned long functions;
498 unsigned long decorations;
499 long input_mode;
500 unsigned long status;
501 } MWMHints = {
502 (1L << 1), 0, border ? 1 : 0, 0, 0
503 };
504
505 X11_XChangeProperty(display, window, WM_HINTS, WM_HINTS, 32,
506 PropModeReplace, (unsigned char *)&MWMHints,
507 sizeof(MWMHints) / sizeof(long));
508 } else { // set the transient hints instead, if necessary
509 X11_XSetTransientForHint(display, window, RootWindow(display, screen));
510 }
511}
512
513bool X11_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props)
514{
515 Window w = (Window)SDL_GetNumberProperty(create_props, SDL_PROP_WINDOW_CREATE_X11_WINDOW_NUMBER,
516 (Window)SDL_GetPointerProperty(create_props, "sdl2-compat.external_window", NULL));
517 if (w) {
518 window->flags |= SDL_WINDOW_EXTERNAL;
519
520 if (!SetupWindowData(_this, window, w)) {
521 return false;
522 }
523
524 if (SDL_GetHintBoolean(SDL_HINT_VIDEO_X11_EXTERNAL_WINDOW_INPUT, true)) {
525 SetupWindowInput(_this, window);
526 }
527 return true;
528 }
529
530 SDL_VideoData *data = _this->internal;
531 SDL_DisplayData *displaydata = SDL_GetDisplayDriverDataForWindow(window);
532 if (!displaydata) {
533 return SDL_SetError("Could not find display info");
534 }
535
536 const bool force_override_redirect = SDL_GetHintBoolean(SDL_HINT_X11_FORCE_OVERRIDE_REDIRECT, false);
537 const bool use_resize_sync = (window->flags & SDL_WINDOW_VULKAN); /* doesn't work well with Vulkan */
538 SDL_WindowData *windowdata;
539 Display *display = data->display;
540 int screen = displaydata->screen;
541 Visual *visual;
542 int depth;
543 XSetWindowAttributes xattr;
544 XSizeHints *sizehints;
545 XWMHints *wmhints;
546 XClassHint *classhints;
547 Atom _NET_WM_BYPASS_COMPOSITOR;
548 Atom _NET_WM_WINDOW_TYPE;
549 Atom wintype;
550 const char *wintype_name = NULL;
551 long compositor = 1;
552 Atom _NET_WM_PID;
553 const char *hint = NULL;
554 int win_x, win_y;
555 bool undefined_position = false;
556
557#if defined(SDL_VIDEO_OPENGL_GLX) || defined(SDL_VIDEO_OPENGL_EGL)
558 const int transparent = (window->flags & SDL_WINDOW_TRANSPARENT) ? true : false;
559 const char *forced_visual_id = SDL_GetHint(SDL_HINT_VIDEO_X11_WINDOW_VISUALID);
560 const char *display_visual_id = SDL_GetHint(SDL_HINT_VIDEO_X11_VISUALID);
561
562 if (forced_visual_id && *forced_visual_id) {
563 XVisualInfo *vi, template;
564 int nvis;
565
566 SDL_zero(template);
567 template.visualid = SDL_strtol(forced_visual_id, NULL, 0);
568 vi = X11_XGetVisualInfo(display, VisualIDMask, &template, &nvis);
569 if (vi) {
570 visual = vi->visual;
571 depth = vi->depth;
572 X11_XFree(vi);
573 } else {
574 return false;
575 }
576 } else if ((window->flags & SDL_WINDOW_OPENGL) &&
577 (!display_visual_id || !*display_visual_id)) {
578 XVisualInfo *vinfo = NULL;
579
580#ifdef SDL_VIDEO_OPENGL_EGL
581 if (((_this->gl_config.profile_mask == SDL_GL_CONTEXT_PROFILE_ES) ||
582 SDL_GetHintBoolean(SDL_HINT_VIDEO_FORCE_EGL, false))
583#ifdef SDL_VIDEO_OPENGL_GLX
584 && (!_this->gl_data || X11_GL_UseEGL(_this))
585#endif
586 ) {
587 vinfo = X11_GLES_GetVisual(_this, display, screen, transparent);
588 } else
589#endif
590 {
591#ifdef SDL_VIDEO_OPENGL_GLX
592 vinfo = X11_GL_GetVisual(_this, display, screen, transparent);
593#endif
594 }
595
596 if (!vinfo) {
597 return false;
598 }
599 visual = vinfo->visual;
600 depth = vinfo->depth;
601 X11_XFree(vinfo);
602 } else
603#endif
604 {
605 visual = displaydata->visual;
606 depth = displaydata->depth;
607 }
608
609 xattr.override_redirect = ((window->flags & SDL_WINDOW_TOOLTIP) || (window->flags & SDL_WINDOW_POPUP_MENU) || force_override_redirect) ? True : False;
610 xattr.backing_store = NotUseful;
611 xattr.background_pixmap = None;
612 xattr.border_pixel = 0;
613
614 if (visual->class == DirectColor) {
615 XColor *colorcells;
616 int i;
617 int ncolors;
618 int rmax, gmax, bmax;
619 int rmask, gmask, bmask;
620 int rshift, gshift, bshift;
621
622 xattr.colormap =
623 X11_XCreateColormap(display, RootWindow(display, screen),
624 visual, AllocAll);
625
626 // If we can't create a colormap, then we must die
627 if (!xattr.colormap) {
628 return SDL_SetError("Could not create writable colormap");
629 }
630
631 // OK, we got a colormap, now fill it in as best as we can
632 colorcells = SDL_malloc(visual->map_entries * sizeof(XColor));
633 if (!colorcells) {
634 return false;
635 }
636 ncolors = visual->map_entries;
637 rmax = 0xffff;
638 gmax = 0xffff;
639 bmax = 0xffff;
640
641 rshift = 0;
642 rmask = visual->red_mask;
643 while (0 == (rmask & 1)) {
644 rshift++;
645 rmask >>= 1;
646 }
647
648 gshift = 0;
649 gmask = visual->green_mask;
650 while (0 == (gmask & 1)) {
651 gshift++;
652 gmask >>= 1;
653 }
654
655 bshift = 0;
656 bmask = visual->blue_mask;
657 while (0 == (bmask & 1)) {
658 bshift++;
659 bmask >>= 1;
660 }
661
662 // build the color table pixel values
663 for (i = 0; i < ncolors; i++) {
664 Uint32 red = (rmax * i) / (ncolors - 1);
665 Uint32 green = (gmax * i) / (ncolors - 1);
666 Uint32 blue = (bmax * i) / (ncolors - 1);
667
668 Uint32 rbits = (rmask * i) / (ncolors - 1);
669 Uint32 gbits = (gmask * i) / (ncolors - 1);
670 Uint32 bbits = (bmask * i) / (ncolors - 1);
671
672 Uint32 pix =
673 (rbits << rshift) | (gbits << gshift) | (bbits << bshift);
674
675 colorcells[i].pixel = pix;
676
677 colorcells[i].red = red;
678 colorcells[i].green = green;
679 colorcells[i].blue = blue;
680
681 colorcells[i].flags = DoRed | DoGreen | DoBlue;
682 }
683
684 X11_XStoreColors(display, xattr.colormap, colorcells, ncolors);
685
686 SDL_free(colorcells);
687 } else {
688 xattr.colormap =
689 X11_XCreateColormap(display, RootWindow(display, screen),
690 visual, AllocNone);
691 }
692
693 if (window->undefined_x && window->undefined_y &&
694 window->last_displayID == SDL_GetPrimaryDisplay()) {
695 undefined_position = true;
696 }
697
698 if (SDL_WINDOW_IS_POPUP(window)) {
699 X11_ConstrainPopup(window, false);
700 }
701 SDL_RelativeToGlobalForWindow(window,
702 window->floating.x, window->floating.y,
703 &win_x, &win_y);
704
705 /* Always create this with the window->floating.* fields; if we're creating a windowed mode window,
706 * that's fine. If we're creating a maximized or fullscreen window, the window manager will want to
707 * know these values so it can use them if we go _back_ to the base floating windowed mode. SDL manages
708 * migration to fullscreen after CreateSDLWindow returns, which will put all the SDL_Window fields and
709 * system state as expected.
710 */
711 w = X11_XCreateWindow(display, RootWindow(display, screen),
712 win_x, win_y, window->floating.w, window->floating.h,
713 0, depth, InputOutput, visual,
714 (CWOverrideRedirect | CWBackPixmap | CWBorderPixel |
715 CWBackingStore | CWColormap),
716 &xattr);
717 if (!w) {
718 return SDL_SetError("Couldn't create window");
719 }
720
721 /* Don't set the borderless flag if we're about to go fullscreen.
722 * This prevents the window manager from moving a full-screen borderless
723 * window to a different display before we actually go fullscreen.
724 */
725 if (!(window->pending_flags & SDL_WINDOW_FULLSCREEN)) {
726 SetWindowBordered(display, screen, w, !(window->flags & SDL_WINDOW_BORDERLESS));
727 }
728
729 sizehints = X11_XAllocSizeHints();
730 // Setup the normal size hints
731 sizehints->flags = 0;
732 if (!(window->flags & SDL_WINDOW_RESIZABLE)) {
733 sizehints->min_width = sizehints->max_width = window->floating.w;
734 sizehints->min_height = sizehints->max_height = window->floating.h;
735 sizehints->flags |= (PMaxSize | PMinSize);
736 }
737 if (!undefined_position) {
738 sizehints->x = win_x;
739 sizehints->y = win_y;
740 sizehints->flags |= USPosition;
741 }
742
743 // Setup the input hints so we get keyboard input
744 wmhints = X11_XAllocWMHints();
745 wmhints->input = !(window->flags & SDL_WINDOW_NOT_FOCUSABLE) ? True : False;
746 wmhints->window_group = data->window_group;
747 wmhints->flags = InputHint | WindowGroupHint;
748
749 // Setup the class hints so we can get an icon (AfterStep)
750 classhints = X11_XAllocClassHint();
751 classhints->res_name = (char *)SDL_GetExeName();
752 classhints->res_class = (char *)SDL_GetAppID();
753
754 // Set the size, input and class hints, and define WM_CLIENT_MACHINE and WM_LOCALE_NAME
755 X11_XSetWMProperties(display, w, NULL, NULL, NULL, 0, sizehints, wmhints, classhints);
756
757 X11_XFree(sizehints);
758 X11_XFree(wmhints);
759 X11_XFree(classhints);
760 // Set the PID related to the window for the given hostname, if possible
761 if (data->pid > 0) {
762 long pid = (long)data->pid;
763 _NET_WM_PID = X11_XInternAtom(display, "_NET_WM_PID", False);
764 X11_XChangeProperty(display, w, _NET_WM_PID, XA_CARDINAL, 32, PropModeReplace,
765 (unsigned char *)&pid, 1);
766 }
767
768 // Set the window manager state
769 X11_SetNetWMState(_this, w, window->flags);
770
771 compositor = 2; // don't disable compositing except for "normal" windows
772 hint = SDL_GetHint(SDL_HINT_X11_WINDOW_TYPE);
773 if (window->flags & SDL_WINDOW_UTILITY) {
774 wintype_name = "_NET_WM_WINDOW_TYPE_UTILITY";
775 } else if (window->flags & SDL_WINDOW_TOOLTIP) {
776 wintype_name = "_NET_WM_WINDOW_TYPE_TOOLTIP";
777 } else if (window->flags & SDL_WINDOW_POPUP_MENU) {
778 wintype_name = "_NET_WM_WINDOW_TYPE_POPUP_MENU";
779 } else if (hint && *hint) {
780 wintype_name = hint;
781 } else {
782 wintype_name = "_NET_WM_WINDOW_TYPE_NORMAL";
783 compositor = 1; // disable compositing for "normal" windows
784 }
785
786 // Let the window manager know what type of window we are.
787 _NET_WM_WINDOW_TYPE = X11_XInternAtom(display, "_NET_WM_WINDOW_TYPE", False);
788 wintype = X11_XInternAtom(display, wintype_name, False);
789 X11_XChangeProperty(display, w, _NET_WM_WINDOW_TYPE, XA_ATOM, 32,
790 PropModeReplace, (unsigned char *)&wintype, 1);
791 if (SDL_GetHintBoolean(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, true)) {
792 _NET_WM_BYPASS_COMPOSITOR = X11_XInternAtom(display, "_NET_WM_BYPASS_COMPOSITOR", False);
793 X11_XChangeProperty(display, w, _NET_WM_BYPASS_COMPOSITOR, XA_CARDINAL, 32,
794 PropModeReplace,
795 (unsigned char *)&compositor, 1);
796 }
797
798 {
799 Atom protocols[4];
800 int proto_count = 0;
801
802 protocols[proto_count++] = data->atoms.WM_DELETE_WINDOW; // Allow window to be deleted by the WM
803 protocols[proto_count++] = data->atoms.WM_TAKE_FOCUS; // Since we will want to set input focus explicitly
804
805 // Default to using ping if there is no hint
806 if (SDL_GetHintBoolean(SDL_HINT_VIDEO_X11_NET_WM_PING, true)) {
807 protocols[proto_count++] = data->atoms._NET_WM_PING; // Respond so WM knows we're alive
808 }
809
810#ifdef SDL_VIDEO_DRIVER_X11_XSYNC
811 if (use_resize_sync) {
812 protocols[proto_count++] = data->atoms._NET_WM_SYNC_REQUEST; /* Respond after completing resize */
813 }
814#endif /* SDL_VIDEO_DRIVER_X11_XSYNC */
815
816 SDL_assert(proto_count <= sizeof(protocols) / sizeof(protocols[0]));
817
818 X11_XSetWMProtocols(display, w, protocols, proto_count);
819 }
820
821 if (!SetupWindowData(_this, window, w)) {
822 X11_XDestroyWindow(display, w);
823 return false;
824 }
825 windowdata = window->internal;
826
827 // Set the parent if this is a non-popup window.
828 if (!SDL_WINDOW_IS_POPUP(window) && window->parent) {
829 X11_XSetTransientForHint(display, w, window->parent->internal->xwindow);
830 }
831
832 // Set the flag if the borders were forced on when creating a fullscreen window for later removal.
833 windowdata->fullscreen_borders_forced_on = !!(window->pending_flags & SDL_WINDOW_FULLSCREEN) &&
834 !!(window->flags & SDL_WINDOW_BORDERLESS);
835
836#ifdef SDL_VIDEO_DRIVER_X11_XSYNC
837 if (use_resize_sync) {
838 X11_InitResizeSync(window);
839 }
840#endif /* SDL_VIDEO_DRIVER_X11_XSYNC */
841
842#if defined(SDL_VIDEO_OPENGL_ES) || defined(SDL_VIDEO_OPENGL_ES2) || defined(SDL_VIDEO_OPENGL_EGL)
843 if ((window->flags & SDL_WINDOW_OPENGL) &&
844 ((_this->gl_config.profile_mask == SDL_GL_CONTEXT_PROFILE_ES) ||
845 SDL_GetHintBoolean(SDL_HINT_VIDEO_FORCE_EGL, false))
846#ifdef SDL_VIDEO_OPENGL_GLX
847 && (!_this->gl_data || X11_GL_UseEGL(_this))
848#endif
849 ) {
850#ifdef SDL_VIDEO_OPENGL_EGL
851 if (!_this->egl_data) {
852 return false;
853 }
854
855 // Create the GLES window surface
856 windowdata->egl_surface = SDL_EGL_CreateSurface(_this, window, (NativeWindowType)w);
857
858 if (windowdata->egl_surface == EGL_NO_SURFACE) {
859 return SDL_SetError("Could not create GLES window surface");
860 }
861#else
862 return SDL_SetError("Could not create GLES window surface (EGL support not configured)");
863#endif // SDL_VIDEO_OPENGL_EGL
864 }
865#endif
866
867#ifdef SDL_VIDEO_DRIVER_X11_XSHAPE
868 // Tooltips do not receive input
869 if (window->flags & SDL_WINDOW_TOOLTIP) {
870 Region region = X11_XCreateRegion();
871 X11_XShapeCombineRegion(display, w, ShapeInput, 0, 0, region, ShapeSet);
872 X11_XDestroyRegion(region);
873 }
874#endif
875
876 SetupWindowInput(_this, window);
877
878 // For _ICC_PROFILE.
879 X11_XSelectInput(display, RootWindow(display, screen), PropertyChangeMask);
880
881 X11_XFlush(display);
882
883 return true;
884}
885
886char *X11_GetWindowTitle(SDL_VideoDevice *_this, Window xwindow)
887{
888 SDL_VideoData *data = _this->internal;
889 Display *display = data->display;
890 int status, real_format;
891 Atom real_type;
892 unsigned long items_read, items_left;
893 unsigned char *propdata;
894 char *title = NULL;
895
896 status = X11_XGetWindowProperty(display, xwindow, data->atoms._NET_WM_NAME,
897 0L, 8192L, False, data->atoms.UTF8_STRING, &real_type, &real_format,
898 &items_read, &items_left, &propdata);
899 if (status == Success && propdata) {
900 title = SDL_strdup(SDL_static_cast(char *, propdata));
901 X11_XFree(propdata);
902 } else {
903 status = X11_XGetWindowProperty(display, xwindow, XA_WM_NAME,
904 0L, 8192L, False, XA_STRING, &real_type, &real_format,
905 &items_read, &items_left, &propdata);
906 if (status == Success && propdata) {
907 title = SDL_iconv_string("UTF-8", "", SDL_static_cast(char *, propdata), items_read + 1);
908 SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Failed to convert WM_NAME title expecting UTF8! Title: %s", title);
909 X11_XFree(propdata);
910 } else {
911 SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Could not get any window title response from Xorg, returning empty string!");
912 title = SDL_strdup("");
913 }
914 }
915 return title;
916}
917
918void X11_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window)
919{
920 SDL_WindowData *data = window->internal;
921 Window xwindow = data->xwindow;
922 Display *display = data->videodata->display;
923 char *title = window->title ? window->title : "";
924
925 SDL_X11_SetWindowTitle(display, xwindow, title);
926}
927
928static bool caught_x11_error = false;
929static int X11_CatchAnyError(Display *d, XErrorEvent *e)
930{
931 /* this may happen during tumultuous times when we are polling anyhow,
932 so just note we had an error and return control. */
933 caught_x11_error = true;
934 return 0;
935}
936
937static void X11_ExternalResizeMoveSync(SDL_Window *window)
938{
939 SDL_WindowData *data = window->internal;
940 Display *display = data->videodata->display;
941 int (*prev_handler)(Display *, XErrorEvent *);
942 unsigned int childCount;
943 Window childReturn, root, parent;
944 Window *children;
945 XWindowAttributes attrs;
946 Uint64 timeout = 0;
947 int x, y;
948 const bool send_move = !!(data->pending_operation & X11_PENDING_OP_MOVE);
949 const bool send_resize = !!(data->pending_operation & X11_PENDING_OP_RESIZE);
950
951 X11_XSync(display, False);
952 X11_XQueryTree(display, data->xwindow, &root, &parent, &children, &childCount);
953 prev_handler = X11_XSetErrorHandler(X11_CatchAnyError);
954
955 /* Wait a brief time to see if the window manager decided to let the move or resize happen.
956 * If the window changes at all, even to an unexpected value, we break out.
957 */
958 timeout = SDL_GetTicksNS() + SDL_MS_TO_NS(100);
959 while (true) {
960 caught_x11_error = false;
961 X11_XSync(display, False);
962 X11_XGetWindowAttributes(display, data->xwindow, &attrs);
963 X11_XTranslateCoordinates(display, parent, DefaultRootWindow(display),
964 attrs.x, attrs.y, &x, &y, &childReturn);
965 SDL_GlobalToRelativeForWindow(window, x, y, &x, &y);
966
967 if (!caught_x11_error) {
968 if ((data->pending_operation & X11_PENDING_OP_MOVE) && (x == data->expected.x + data->border_left && y == data->expected.y + data->border_top)) {
969 data->pending_operation &= ~X11_PENDING_OP_MOVE;
970 }
971 if ((data->pending_operation & X11_PENDING_OP_RESIZE) && (attrs.width == data->expected.w && attrs.height == data->expected.h)) {
972 data->pending_operation &= ~X11_PENDING_OP_RESIZE;
973 }
974
975 if (data->pending_operation == X11_PENDING_OP_NONE) {
976 break;
977 }
978 }
979
980 if (SDL_GetTicksNS() >= timeout) {
981 // Timed out without the expected values. Update the requested data so future sync calls won't block.
982 data->pending_operation &= ~(X11_PENDING_OP_MOVE | X11_PENDING_OP_RESIZE);
983 data->expected.x = x;
984 data->expected.y = y;
985 data->expected.w = attrs.width;
986 data->expected.h = attrs.height;
987 break;
988 }
989
990 SDL_Delay(10);
991 }
992
993 if (!caught_x11_error) {
994 if (send_move) {
995 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MOVED, x, y);
996 }
997 if (send_resize) {
998 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, attrs.width, attrs.height);
999 }
1000 }
1001
1002 X11_XSetErrorHandler(prev_handler);
1003 caught_x11_error = false;
1004}
1005
1006/* Wait a brief time, or not, to see if the window manager decided to move/resize the window.
1007 * Send MOVED and RESIZED window events */
1008static bool X11_SyncWindowTimeout(SDL_VideoDevice *_this, SDL_Window *window, Uint64 param_timeout)
1009{
1010 SDL_WindowData *data = window->internal;
1011 Display *display = data->videodata->display;
1012 int (*prev_handler)(Display *, XErrorEvent *);
1013 Uint64 timeout = 0;
1014 bool force_exit = false;
1015 bool result = true;
1016
1017 X11_XSync(display, False);
1018 prev_handler = X11_XSetErrorHandler(X11_CatchAnyError);
1019
1020 if (param_timeout) {
1021 timeout = SDL_GetTicksNS() + param_timeout;
1022 }
1023
1024 while (true) {
1025 X11_XSync(display, False);
1026 X11_PumpEvents(_this);
1027
1028 if ((data->pending_operation & X11_PENDING_OP_MOVE) && (window->x == data->expected.x + data->border_left && window->y == data->expected.y + data->border_top)) {
1029 data->pending_operation &= ~X11_PENDING_OP_MOVE;
1030 }
1031 if ((data->pending_operation & X11_PENDING_OP_RESIZE) && (window->w == data->expected.w && window->h == data->expected.h)) {
1032 data->pending_operation &= ~X11_PENDING_OP_RESIZE;
1033 }
1034
1035 if (data->pending_operation == X11_PENDING_OP_NONE) {
1036 if (force_exit ||
1037 (window->x == data->expected.x + data->border_left && window->y == data->expected.y + data->border_top &&
1038 window->w == data->expected.w && window->h == data->expected.h)) {
1039 // The window is in the expected state and nothing is pending. Done.
1040 break;
1041 }
1042
1043 /* No operations are pending, but the window still isn't in the expected state.
1044 * Try one more time before exiting.
1045 */
1046 force_exit = true;
1047 }
1048
1049 if (SDL_GetTicksNS() >= timeout) {
1050 // Timed out without the expected values. Update the requested data so future sync calls won't block.
1051 data->expected.x = window->x;
1052 data->expected.y = window->y;
1053 data->expected.w = window->w;
1054 data->expected.h = window->h;
1055
1056 result = false;
1057 break;
1058 }
1059
1060 SDL_Delay(10);
1061 }
1062
1063 data->pending_operation = X11_PENDING_OP_NONE;
1064
1065 if (!caught_x11_error) {
1066 X11_PumpEvents(_this);
1067 } else {
1068 result = false;
1069 }
1070
1071 X11_XSetErrorHandler(prev_handler);
1072 caught_x11_error = false;
1073
1074 return result;
1075}
1076
1077bool X11_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *icon)
1078{
1079 SDL_WindowData *data = window->internal;
1080 Display *display = data->videodata->display;
1081 Atom _NET_WM_ICON = data->videodata->atoms._NET_WM_ICON;
1082 int (*prevHandler)(Display *, XErrorEvent *) = NULL;
1083 bool result = true;
1084
1085 if (icon) {
1086 int x, y;
1087 int propsize;
1088 long *propdata;
1089 Uint32 *src;
1090 long *dst;
1091
1092 // Set the _NET_WM_ICON property
1093 SDL_assert(icon->format == SDL_PIXELFORMAT_ARGB8888);
1094 propsize = 2 + (icon->w * icon->h);
1095 propdata = SDL_malloc(propsize * sizeof(long));
1096
1097 if (!propdata) {
1098 return false;
1099 }
1100
1101 X11_XSync(display, False);
1102 prevHandler = X11_XSetErrorHandler(X11_CatchAnyError);
1103
1104 propdata[0] = icon->w;
1105 propdata[1] = icon->h;
1106 dst = &propdata[2];
1107
1108 for (y = 0; y < icon->h; ++y) {
1109 src = (Uint32 *)((Uint8 *)icon->pixels + y * icon->pitch);
1110 for (x = 0; x < icon->w; ++x) {
1111 *dst++ = *src++;
1112 }
1113 }
1114
1115 X11_XChangeProperty(display, data->xwindow, _NET_WM_ICON, XA_CARDINAL,
1116 32, PropModeReplace, (unsigned char *)propdata,
1117 propsize);
1118 SDL_free(propdata);
1119
1120 if (caught_x11_error) {
1121 result = SDL_SetError("An error occurred while trying to set the window's icon");
1122 }
1123 }
1124
1125 X11_XFlush(display);
1126
1127 if (prevHandler) {
1128 X11_XSetErrorHandler(prevHandler);
1129 caught_x11_error = false;
1130 }
1131
1132 return result;
1133}
1134
1135void X11_UpdateWindowPosition(SDL_Window *window, bool use_current_position)
1136{
1137 SDL_WindowData *data = window->internal;
1138 Display *display = data->videodata->display;
1139 const int rel_x = use_current_position ? window->x : window->pending.x;
1140 const int rel_y = use_current_position ? window->y : window->pending.y;
1141
1142 SDL_RelativeToGlobalForWindow(window,
1143 rel_x - data->border_left, rel_y - data->border_top,
1144 &data->expected.x, &data->expected.y);
1145
1146 // Attempt to move the window
1147 if (window->flags & SDL_WINDOW_HIDDEN) {
1148 window->internal->pending_position = true;
1149 } else {
1150 data->pending_operation |= X11_PENDING_OP_MOVE;
1151 X11_XMoveWindow(display, data->xwindow, data->expected.x, data->expected.y);
1152 }
1153}
1154
1155bool X11_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window *window)
1156{
1157 // Sync any pending fullscreen or maximize events.
1158 if (window->internal->pending_operation & (X11_PENDING_OP_FULLSCREEN | X11_PENDING_OP_MAXIMIZE)) {
1159 X11_FlushPendingEvents(_this, window);
1160 }
1161
1162 // Set the position as pending if the window is maximized with a restore pending.
1163 if (window->flags & SDL_WINDOW_MAXIMIZED) {
1164 if (window->internal->pending_operation & X11_PENDING_OP_RESTORE) {
1165 window->internal->pending_position = true;
1166 }
1167 return true;
1168 }
1169
1170 if (!(window->flags & SDL_WINDOW_FULLSCREEN)) {
1171 if (SDL_WINDOW_IS_POPUP(window)) {
1172 X11_ConstrainPopup(window, true);
1173 }
1174 X11_UpdateWindowPosition(window, false);
1175 } else {
1176 SDL_UpdateFullscreenMode(window, SDL_FULLSCREEN_OP_UPDATE, true);
1177 }
1178 return true;
1179}
1180
1181void X11_SetWindowMinMax(SDL_Window *window, bool use_current)
1182{
1183 SDL_WindowData *data = window->internal;
1184 Display *display = data->videodata->display;
1185 XSizeHints *sizehints = X11_XAllocSizeHints();
1186 long hint_flags = 0;
1187
1188 X11_XGetWMNormalHints(display, data->xwindow, sizehints, &hint_flags);
1189 sizehints->flags &= ~(PMinSize | PMaxSize | PAspect);
1190
1191 if (data->window->flags & SDL_WINDOW_RESIZABLE) {
1192 if (data->window->min_w || data->window->min_h) {
1193 sizehints->flags |= PMinSize;
1194 sizehints->min_width = data->window->min_w;
1195 sizehints->min_height = data->window->min_h;
1196 }
1197 if (data->window->max_w || data->window->max_h) {
1198 sizehints->flags |= PMaxSize;
1199 sizehints->max_width = data->window->max_w;
1200 sizehints->max_height = data->window->max_h;
1201 }
1202 if (data->window->min_aspect > 0.0f || data->window->max_aspect > 0.0f) {
1203 sizehints->flags |= PAspect;
1204 SDL_CalculateFraction(data->window->min_aspect, &sizehints->min_aspect.x, &sizehints->min_aspect.y);
1205 SDL_CalculateFraction(data->window->max_aspect, &sizehints->max_aspect.x, &sizehints->max_aspect.y);
1206 }
1207 } else {
1208 // Set the min/max to the same values to make the window non-resizable
1209 sizehints->flags |= PMinSize | PMaxSize;
1210 sizehints->min_width = sizehints->max_width = use_current ? data->window->floating.w : window->windowed.w;
1211 sizehints->min_height = sizehints->max_height = use_current ? data->window->floating.h : window->windowed.h;
1212 }
1213
1214 X11_XSetWMNormalHints(display, data->xwindow, sizehints);
1215 X11_XFree(sizehints);
1216}
1217
1218void X11_SetWindowMinimumSize(SDL_VideoDevice *_this, SDL_Window *window)
1219{
1220 if (window->internal->pending_operation & X11_PENDING_OP_FULLSCREEN) {
1221 X11_SyncWindow(_this, window);
1222 }
1223
1224 if (!(window->flags & SDL_WINDOW_FULLSCREEN)) {
1225 X11_SetWindowMinMax(window, true);
1226 }
1227}
1228
1229void X11_SetWindowMaximumSize(SDL_VideoDevice *_this, SDL_Window *window)
1230{
1231 if (window->internal->pending_operation & X11_PENDING_OP_FULLSCREEN) {
1232 X11_SyncWindow(_this, window);
1233 }
1234
1235 if (!(window->flags & SDL_WINDOW_FULLSCREEN)) {
1236 X11_SetWindowMinMax(window, true);
1237 }
1238}
1239
1240void X11_SetWindowAspectRatio(SDL_VideoDevice *_this, SDL_Window *window)
1241{
1242 if (window->internal->pending_operation & X11_PENDING_OP_FULLSCREEN) {
1243 X11_SyncWindow(_this, window);
1244 }
1245
1246 if (!(window->flags & SDL_WINDOW_FULLSCREEN)) {
1247 X11_SetWindowMinMax(window, true);
1248 }
1249}
1250
1251void X11_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window)
1252{
1253 SDL_WindowData *data = window->internal;
1254 Display *display = data->videodata->display;
1255
1256 /* Wait for pending maximize and fullscreen operations to complete, as these windows
1257 * don't get size changes.
1258 */
1259 if (data->pending_operation & (X11_PENDING_OP_MAXIMIZE | X11_PENDING_OP_FULLSCREEN)) {
1260 X11_FlushPendingEvents(_this, window);
1261 }
1262
1263 // Set the size as pending if the window is being restored.
1264 if (window->flags & (SDL_WINDOW_MAXIMIZED | SDL_WINDOW_FULLSCREEN)) {
1265 // New size will be set when the window is restored.
1266 if (data->pending_operation & X11_PENDING_OP_RESTORE) {
1267 data->pending_size = true;
1268 } else {
1269 // Can't resize the window.
1270 window->last_size_pending = false;
1271 }
1272 return;
1273 }
1274
1275 if (!(window->flags & SDL_WINDOW_RESIZABLE)) {
1276 if (!(window->flags & SDL_WINDOW_FULLSCREEN)) {
1277 /* Apparently, if the X11 Window is set to a 'non-resizable' window, you cannot resize it using the X11_XResizeWindow, thus
1278 * we must set the size hints to adjust the window size.
1279 */
1280 XSizeHints *sizehints = X11_XAllocSizeHints();
1281 long userhints;
1282 int dest_x, dest_y;
1283
1284 X11_XGetWMNormalHints(display, data->xwindow, sizehints, &userhints);
1285
1286 data->expected.w = sizehints->min_width = sizehints->max_width = window->pending.w;
1287 data->expected.h = sizehints->min_height = sizehints->max_height = window->pending.h;
1288 sizehints->flags |= PMinSize | PMaxSize;
1289 data->pending_operation |= X11_PENDING_OP_RESIZE;
1290
1291 X11_XSetWMNormalHints(display, data->xwindow, sizehints);
1292
1293 /* From Pierre-Loup:
1294 WMs each have their little quirks with that. When you change the
1295 size hints, they get a ConfigureNotify event with the
1296 WM_NORMAL_SIZE_HINTS Atom. They all save the hints then, but they
1297 don't all resize the window right away to enforce the new hints.
1298
1299 Some of them resize only after:
1300 - A user-initiated move or resize
1301 - A code-initiated move or resize
1302 - Hiding & showing window (Unmap & map)
1303
1304 The following move & resize seems to help a lot of WMs that didn't
1305 properly update after the hints were changed. We don't do a
1306 hide/show, because there are supposedly subtle problems with doing so
1307 and transitioning from windowed to fullscreen in Unity.
1308 */
1309 X11_XResizeWindow(display, data->xwindow, window->pending.w, window->pending.h);
1310 const int x = window->last_position_pending ? window->pending.x : window->x;
1311 const int y = window->last_position_pending ? window->pending.y : window->y;
1312 SDL_RelativeToGlobalForWindow(window,
1313 x - data->border_left,
1314 y - data->border_top,
1315 &dest_x, &dest_y);
1316 X11_XMoveWindow(display, data->xwindow, dest_x, dest_y);
1317 X11_XRaiseWindow(display, data->xwindow);
1318
1319 X11_XFree(sizehints);
1320 }
1321 } else {
1322 data->expected.w = window->pending.w;
1323 data->expected.h = window->pending.h;
1324 data->pending_operation |= X11_PENDING_OP_RESIZE;
1325 X11_XResizeWindow(display, data->xwindow, data->expected.w, data->expected.h);
1326 }
1327
1328 /* External windows may call this to update the renderer size, but not pump SDL events,
1329 * so the size event needs to be synthesized for external windows. If it is wrong, the
1330 * true size will be sent if/when events are processed.
1331 */
1332 if (window->flags & SDL_WINDOW_EXTERNAL) {
1333 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, window->pending.w, window->pending.h);
1334 }
1335}
1336
1337bool X11_GetWindowBordersSize(SDL_VideoDevice *_this, SDL_Window *window, int *top, int *left, int *bottom, int *right)
1338{
1339 SDL_WindowData *data = window->internal;
1340
1341 *left = data->border_left;
1342 *right = data->border_right;
1343 *top = data->border_top;
1344 *bottom = data->border_bottom;
1345
1346 return true;
1347}
1348
1349bool X11_SetWindowOpacity(SDL_VideoDevice *_this, SDL_Window *window, float opacity)
1350{
1351 SDL_WindowData *data = window->internal;
1352 Display *display = data->videodata->display;
1353 Atom _NET_WM_WINDOW_OPACITY = data->videodata->atoms._NET_WM_WINDOW_OPACITY;
1354
1355 if (opacity == 1.0f) {
1356 X11_XDeleteProperty(display, data->xwindow, _NET_WM_WINDOW_OPACITY);
1357 } else {
1358 const Uint32 FullyOpaque = 0xFFFFFFFF;
1359 const long alpha = (long)((double)opacity * (double)FullyOpaque);
1360 X11_XChangeProperty(display, data->xwindow, _NET_WM_WINDOW_OPACITY, XA_CARDINAL, 32,
1361 PropModeReplace, (unsigned char *)&alpha, 1);
1362 }
1363
1364 return true;
1365}
1366
1367bool X11_SetWindowParent(SDL_VideoDevice *_this, SDL_Window *window, SDL_Window *parent)
1368{
1369 SDL_WindowData *data = window->internal;
1370 SDL_WindowData *parent_data = parent ? parent->internal : NULL;
1371 SDL_VideoData *video_data = _this->internal;
1372 Display *display = video_data->display;
1373
1374 if (parent_data) {
1375 X11_XSetTransientForHint(display, data->xwindow, parent_data->xwindow);
1376 } else {
1377 X11_XDeleteProperty(display, data->xwindow, video_data->atoms.WM_TRANSIENT_FOR);
1378 }
1379
1380 return true;
1381}
1382
1383bool X11_SetWindowModal(SDL_VideoDevice *_this, SDL_Window *window, bool modal)
1384{
1385 SDL_WindowData *data = window->internal;
1386 SDL_VideoData *video_data = _this->internal;
1387 SDL_DisplayData *displaydata = SDL_GetDisplayDriverDataForWindow(window);
1388 Display *display = video_data->display;
1389 Uint32 flags = window->flags;
1390 Atom _NET_WM_STATE = data->videodata->atoms._NET_WM_STATE;
1391 Atom _NET_WM_STATE_MODAL = data->videodata->atoms._NET_WM_STATE_MODAL;
1392
1393 if (modal) {
1394 flags |= SDL_WINDOW_MODAL;
1395 } else {
1396 flags &= ~SDL_WINDOW_MODAL;
1397 X11_XDeleteProperty(display, data->xwindow, video_data->atoms.WM_TRANSIENT_FOR);
1398 }
1399
1400 if (X11_IsWindowMapped(_this, window)) {
1401 XEvent e;
1402
1403 SDL_zero(e);
1404 e.xany.type = ClientMessage;
1405 e.xclient.message_type = _NET_WM_STATE;
1406 e.xclient.format = 32;
1407 e.xclient.window = data->xwindow;
1408 e.xclient.data.l[0] = modal ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
1409 e.xclient.data.l[1] = _NET_WM_STATE_MODAL;
1410 e.xclient.data.l[3] = 0l;
1411
1412 X11_XSendEvent(display, RootWindow(display, displaydata->screen), 0,
1413 SubstructureNotifyMask | SubstructureRedirectMask, &e);
1414 } else {
1415 X11_SetNetWMState(_this, data->xwindow, flags);
1416 }
1417
1418 X11_XFlush(display);
1419
1420 return true;
1421}
1422
1423void X11_SetWindowBordered(SDL_VideoDevice *_this, SDL_Window *window, bool bordered)
1424{
1425 const bool focused = (window->flags & SDL_WINDOW_INPUT_FOCUS) ? true : false;
1426 const bool visible = (!(window->flags & SDL_WINDOW_HIDDEN)) ? true : false;
1427 SDL_WindowData *data = window->internal;
1428 SDL_DisplayData *displaydata = SDL_GetDisplayDriverDataForWindow(window);
1429 Display *display = data->videodata->display;
1430 XEvent event;
1431
1432 if (data->pending_operation & X11_PENDING_OP_FULLSCREEN) {
1433 X11_SyncWindow(_this, window);
1434 }
1435
1436 // If the window is fullscreen, the resize capability will be set/cleared when it is returned to windowed mode.
1437 if (!(window->flags & SDL_WINDOW_FULLSCREEN)) {
1438 SetWindowBordered(display, displaydata->screen, data->xwindow, bordered);
1439 X11_XFlush(display);
1440
1441 if (visible) {
1442 XWindowAttributes attr;
1443 do {
1444 X11_XSync(display, False);
1445 X11_XGetWindowAttributes(display, data->xwindow, &attr);
1446 } while (attr.map_state != IsViewable);
1447
1448 if (focused) {
1449 X11_XSetInputFocus(display, data->xwindow, RevertToParent, CurrentTime);
1450 }
1451 }
1452
1453 // make sure these don't make it to the real event queue if they fired here.
1454 X11_XSync(display, False);
1455 X11_XCheckIfEvent(display, &event, &isUnmapNotify, (XPointer)&data->xwindow);
1456 X11_XCheckIfEvent(display, &event, &isMapNotify, (XPointer)&data->xwindow);
1457
1458 // Turning the borders off doesn't send an extent event, so they must be cleared here.
1459 X11_GetBorderValues(data);
1460
1461 // Make sure the window manager didn't resize our window for the difference.
1462 X11_XResizeWindow(display, data->xwindow, window->floating.w, window->floating.h);
1463 X11_XSync(display, False);
1464 } else {
1465 // If fullscreen, set a flag to toggle the borders when returning to windowed mode.
1466 data->toggle_borders = true;
1467 data->fullscreen_borders_forced_on = false;
1468 }
1469}
1470
1471void X11_SetWindowResizable(SDL_VideoDevice *_this, SDL_Window *window, bool resizable)
1472{
1473 SDL_WindowData *data = window->internal;
1474
1475 if (data->pending_operation & X11_PENDING_OP_FULLSCREEN) {
1476 X11_SyncWindow(_this, window);
1477 }
1478
1479 // If the window is fullscreen, the resize capability will be set/cleared when it is returned to windowed mode.
1480 if (!(window->flags & SDL_WINDOW_FULLSCREEN)) {
1481 X11_SetWindowMinMax(window, true);
1482 }
1483}
1484
1485void X11_SetWindowAlwaysOnTop(SDL_VideoDevice *_this, SDL_Window *window, bool on_top)
1486{
1487 SDL_WindowData *data = window->internal;
1488 SDL_DisplayData *displaydata = SDL_GetDisplayDriverDataForWindow(window);
1489 Display *display = data->videodata->display;
1490 Atom _NET_WM_STATE = data->videodata->atoms._NET_WM_STATE;
1491 Atom _NET_WM_STATE_ABOVE = data->videodata->atoms._NET_WM_STATE_ABOVE;
1492
1493 if (X11_IsWindowMapped(_this, window)) {
1494 XEvent e;
1495
1496 SDL_zero(e);
1497 e.xany.type = ClientMessage;
1498 e.xclient.message_type = _NET_WM_STATE;
1499 e.xclient.format = 32;
1500 e.xclient.window = data->xwindow;
1501 e.xclient.data.l[0] =
1502 on_top ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
1503 e.xclient.data.l[1] = _NET_WM_STATE_ABOVE;
1504 e.xclient.data.l[3] = 0l;
1505
1506 X11_XSendEvent(display, RootWindow(display, displaydata->screen), 0,
1507 SubstructureNotifyMask | SubstructureRedirectMask, &e);
1508 } else {
1509 X11_SetNetWMState(_this, data->xwindow, window->flags);
1510 }
1511 X11_XFlush(display);
1512}
1513
1514void X11_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window)
1515{
1516 SDL_WindowData *data = window->internal;
1517 Display *display = data->videodata->display;
1518 bool bActivate = SDL_GetHintBoolean(SDL_HINT_WINDOW_ACTIVATE_WHEN_SHOWN, true);
1519 bool position_is_absolute = false;
1520 bool set_position = false;
1521 XEvent event;
1522
1523 if (SDL_WINDOW_IS_POPUP(window)) {
1524 // Update the position in case the parent moved while we were hidden
1525 X11_ConstrainPopup(window, true);
1526 data->pending_position = true;
1527
1528 // Coordinates after X11_ConstrainPopup() are already in the global space.
1529 position_is_absolute = true;
1530 set_position = true;
1531 }
1532
1533 /* Whether XMapRaised focuses the window is based on the window type and it is
1534 * wm specific. There isn't much we can do here */
1535 (void)bActivate;
1536
1537 if (!X11_IsWindowMapped(_this, window)) {
1538 X11_XMapRaised(display, data->xwindow);
1539 /* Blocking wait for "MapNotify" event.
1540 * We use X11_XIfEvent because pXWindowEvent takes a mask rather than a type,
1541 * and XCheckTypedWindowEvent doesn't block */
1542 if (!(window->flags & SDL_WINDOW_EXTERNAL) && X11_IsDisplayOk(display)) {
1543 X11_XIfEvent(display, &event, &isMapNotify, (XPointer)&data->xwindow);
1544 }
1545 X11_XFlush(display);
1546 set_position = data->pending_position ||
1547 (!(window->flags & SDL_WINDOW_BORDERLESS) && !window->undefined_x && !window->undefined_y);
1548 }
1549
1550 if (!data->videodata->net_wm) {
1551 // no WM means no FocusIn event, which confuses us. Force it.
1552 X11_XSync(display, False);
1553 X11_XSetInputFocus(display, data->xwindow, RevertToNone, CurrentTime);
1554 X11_XFlush(display);
1555 }
1556
1557 // Popup menus grab the keyboard
1558 if (window->flags & SDL_WINDOW_POPUP_MENU) {
1559 X11_SetKeyboardFocus(window, window->parent == SDL_GetKeyboardFocus());
1560 }
1561
1562 // Get some valid border values, if we haven't received them yet
1563 if (data->border_left == 0 && data->border_right == 0 && data->border_top == 0 && data->border_bottom == 0) {
1564 X11_GetBorderValues(data);
1565 }
1566
1567 if (set_position) {
1568 // Apply the window position, accounting for offsets due to the borders appearing.
1569 const int tx = data->pending_position ? window->pending.x : window->x;
1570 const int ty = data->pending_position ? window->pending.y : window->y;
1571 int x, y;
1572 if (position_is_absolute) {
1573 x = tx;
1574 y = ty;
1575 } else {
1576 SDL_RelativeToGlobalForWindow(window,
1577 tx - data->border_left, ty - data->border_top,
1578 &x, &y);
1579 }
1580 data->pending_position = false;
1581 X11_XMoveWindow(display, data->xwindow, x, y);
1582 }
1583
1584 /* Some window managers can send garbage coordinates while mapping the window, so don't emit size and position
1585 * events during the initial configure events.
1586 */
1587 data->size_move_event_flags = X11_SIZE_MOVE_EVENTS_DISABLE;
1588 X11_XSync(display, False);
1589 X11_PumpEvents(_this);
1590 data->size_move_event_flags = 0;
1591
1592 // If a configure event was received (type is non-zero), send the final window size and coordinates.
1593 if (data->last_xconfigure.type) {
1594 int x, y;
1595 SDL_GlobalToRelativeForWindow(data->window, data->last_xconfigure.x, data->last_xconfigure.y, &x, &y);
1596 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, data->last_xconfigure.width, data->last_xconfigure.height);
1597 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MOVED, x, y);
1598 }
1599}
1600
1601void X11_HideWindow(SDL_VideoDevice *_this, SDL_Window *window)
1602{
1603 SDL_WindowData *data = window->internal;
1604 SDL_DisplayData *displaydata = SDL_GetDisplayDriverDataForWindow(window);
1605 int screen = (displaydata ? displaydata->screen : 0);
1606 Display *display = data->videodata->display;
1607 XEvent event;
1608
1609 if (X11_IsWindowMapped(_this, window)) {
1610 X11_XWithdrawWindow(display, data->xwindow, screen);
1611 // Blocking wait for "UnmapNotify" event
1612 if (!(window->flags & SDL_WINDOW_EXTERNAL) && X11_IsDisplayOk(display)) {
1613 X11_XIfEvent(display, &event, &isUnmapNotify, (XPointer)&data->xwindow);
1614 }
1615 X11_XFlush(display);
1616 }
1617
1618 // Transfer keyboard focus back to the parent
1619 if (window->flags & SDL_WINDOW_POPUP_MENU) {
1620 SDL_Window *new_focus = window->parent;
1621 bool set_focus = window == SDL_GetKeyboardFocus();
1622
1623 // Find the highest level window, up to the toplevel parent, that isn't being hidden or destroyed.
1624 while (SDL_WINDOW_IS_POPUP(new_focus) && (new_focus->is_hiding || new_focus->is_destroying)) {
1625 new_focus = new_focus->parent;
1626
1627 // If some window in the chain currently had focus, set it to the new lowest-level window.
1628 if (!set_focus) {
1629 set_focus = new_focus == SDL_GetKeyboardFocus();
1630 }
1631 }
1632
1633 X11_SetKeyboardFocus(new_focus, set_focus);
1634 }
1635
1636 X11_XSync(display, False);
1637 X11_PumpEvents(_this);
1638}
1639
1640static bool X11_SetWindowActive(SDL_VideoDevice *_this, SDL_Window *window)
1641{
1642 CHECK_WINDOW_DATA(window);
1643
1644 SDL_WindowData *data = window->internal;
1645 SDL_DisplayData *displaydata = SDL_GetDisplayDriverDataForWindow(window);
1646 Display *display = data->videodata->display;
1647 Atom _NET_ACTIVE_WINDOW = data->videodata->atoms._NET_ACTIVE_WINDOW;
1648
1649 if (X11_IsWindowMapped(_this, window)) {
1650 XEvent e;
1651
1652 // printf("SDL Window %p: sending _NET_ACTIVE_WINDOW with timestamp %lu\n", window, data->user_time);
1653
1654 SDL_zero(e);
1655 e.xany.type = ClientMessage;
1656 e.xclient.message_type = _NET_ACTIVE_WINDOW;
1657 e.xclient.format = 32;
1658 e.xclient.window = data->xwindow;
1659 e.xclient.data.l[0] = 1; // source indication. 1 = application
1660 e.xclient.data.l[1] = data->user_time;
1661 e.xclient.data.l[2] = 0;
1662
1663 X11_XSendEvent(display, RootWindow(display, displaydata->screen), 0,
1664 SubstructureNotifyMask | SubstructureRedirectMask, &e);
1665
1666 X11_XFlush(display);
1667 }
1668 return true;
1669}
1670
1671void X11_RaiseWindow(SDL_VideoDevice *_this, SDL_Window *window)
1672{
1673 SDL_WindowData *data = window->internal;
1674 Display *display = data->videodata->display;
1675 bool bActivate = SDL_GetHintBoolean(SDL_HINT_WINDOW_ACTIVATE_WHEN_RAISED, true);
1676
1677 X11_XRaiseWindow(display, data->xwindow);
1678 if (bActivate) {
1679 X11_SetWindowActive(_this, window);
1680 }
1681 X11_XFlush(display);
1682}
1683
1684static bool X11_SetWindowMaximized(SDL_VideoDevice *_this, SDL_Window *window, bool maximized)
1685{
1686 CHECK_WINDOW_DATA(window);
1687
1688 SDL_WindowData *data = window->internal;
1689 SDL_DisplayData *displaydata = SDL_GetDisplayDriverDataForWindow(window);
1690 Display *display = data->videodata->display;
1691 Atom _NET_WM_STATE = data->videodata->atoms._NET_WM_STATE;
1692 Atom _NET_WM_STATE_MAXIMIZED_VERT = data->videodata->atoms._NET_WM_STATE_MAXIMIZED_VERT;
1693 Atom _NET_WM_STATE_MAXIMIZED_HORZ = data->videodata->atoms._NET_WM_STATE_MAXIMIZED_HORZ;
1694
1695 if (window->flags & SDL_WINDOW_FULLSCREEN) {
1696 /* Fullscreen windows are maximized on some window managers,
1697 and this is functional behavior, so don't remove that state
1698 now, we'll take care of it when we leave fullscreen mode.
1699 */
1700 return true;
1701 }
1702
1703 if (X11_IsWindowMapped(_this, window)) {
1704 XEvent e;
1705
1706 SDL_zero(e);
1707 e.xany.type = ClientMessage;
1708 e.xclient.message_type = _NET_WM_STATE;
1709 e.xclient.format = 32;
1710 e.xclient.window = data->xwindow;
1711 e.xclient.data.l[0] =
1712 maximized ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
1713 e.xclient.data.l[1] = _NET_WM_STATE_MAXIMIZED_VERT;
1714 e.xclient.data.l[2] = _NET_WM_STATE_MAXIMIZED_HORZ;
1715 e.xclient.data.l[3] = 0l;
1716
1717 if (maximized) {
1718 SDL_DisplayID displayID = SDL_GetDisplayForWindow(window);
1719 SDL_Rect bounds;
1720
1721 SDL_zero(bounds);
1722 SDL_GetDisplayUsableBounds(displayID, &bounds);
1723
1724 data->expected.x = bounds.x + data->border_left;
1725 data->expected.y = bounds.y + data->border_top;
1726 data->expected.w = bounds.w - (data->border_left + data->border_right);
1727 data->expected.h = bounds.h - (data->border_top + data->border_bottom);
1728 } else {
1729 data->expected.x = window->floating.x;
1730 data->expected.y = window->floating.y;
1731 data->expected.w = window->floating.w;
1732 data->expected.h = window->floating.h;
1733 }
1734
1735 X11_XSendEvent(display, RootWindow(display, displaydata->screen), 0,
1736 SubstructureNotifyMask | SubstructureRedirectMask, &e);
1737 } else {
1738 X11_SetNetWMState(_this, data->xwindow, window->flags);
1739 }
1740 X11_XFlush(display);
1741
1742 return true;
1743}
1744
1745void X11_MaximizeWindow(SDL_VideoDevice *_this, SDL_Window *window)
1746{
1747 if (window->internal->pending_operation & (X11_PENDING_OP_FULLSCREEN | X11_PENDING_OP_MINIMIZE)) {
1748 SDL_SyncWindow(window);
1749 }
1750
1751 if (window->flags & SDL_WINDOW_FULLSCREEN) {
1752 // If fullscreen, just toggle the restored state.
1753 window->internal->window_was_maximized = true;
1754 return;
1755 }
1756
1757 if (!(window->flags & SDL_WINDOW_MINIMIZED)) {
1758 window->internal->pending_operation |= X11_PENDING_OP_MAXIMIZE;
1759 X11_SetWindowMaximized(_this, window, true);
1760 }
1761}
1762
1763void X11_MinimizeWindow(SDL_VideoDevice *_this, SDL_Window *window)
1764{
1765 SDL_WindowData *data = window->internal;
1766 SDL_DisplayData *displaydata = SDL_GetDisplayDriverDataForWindow(window);
1767 Display *display = data->videodata->display;
1768
1769 if (data->pending_operation & SDL_WINDOW_FULLSCREEN) {
1770 SDL_SyncWindow(window);
1771 }
1772
1773 data->pending_operation |= X11_PENDING_OP_MINIMIZE;
1774 if (!(window->flags & SDL_WINDOW_FULLSCREEN)) {
1775 data->window_was_maximized = !!(window->flags & SDL_WINDOW_MAXIMIZED);
1776 }
1777 X11_XIconifyWindow(display, data->xwindow, displaydata->screen);
1778 X11_XFlush(display);
1779}
1780
1781void X11_RestoreWindow(SDL_VideoDevice *_this, SDL_Window *window)
1782{
1783 if (window->internal->pending_operation & (X11_PENDING_OP_FULLSCREEN | X11_PENDING_OP_MAXIMIZE | X11_PENDING_OP_MINIMIZE)) {
1784 SDL_SyncWindow(window);
1785 }
1786
1787 if ((window->flags & SDL_WINDOW_FULLSCREEN) && !(window->flags & SDL_WINDOW_MINIMIZED)) {
1788 // If fullscreen and not minimized, just toggle the restored state.
1789 window->internal->window_was_maximized = false;
1790 return;
1791 }
1792
1793 if (window->flags & (SDL_WINDOW_MINIMIZED | SDL_WINDOW_MAXIMIZED) ||
1794 (window->internal->pending_operation & X11_PENDING_OP_MINIMIZE)) {
1795 window->internal->pending_operation |= X11_PENDING_OP_RESTORE;
1796 }
1797
1798 // If the window was minimized while maximized, restore as maximized.
1799 const bool maximize = !!(window->flags & SDL_WINDOW_MINIMIZED) && window->internal->window_was_maximized;
1800 X11_SetWindowMaximized(_this, window, maximize);
1801 X11_ShowWindow(_this, window);
1802 X11_SetWindowActive(_this, window);
1803}
1804
1805// This asks the Window Manager to handle fullscreen for us. This is the modern way.
1806static SDL_FullscreenResult X11_SetWindowFullscreenViaWM(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *_display, SDL_FullscreenOp fullscreen)
1807{
1808 CHECK_WINDOW_DATA(window);
1809 CHECK_DISPLAY_DATA(_display);
1810
1811 SDL_WindowData *data = window->internal;
1812 SDL_DisplayData *displaydata = _display->internal;
1813 Display *display = data->videodata->display;
1814 Atom _NET_WM_STATE = data->videodata->atoms._NET_WM_STATE;
1815 Atom _NET_WM_STATE_FULLSCREEN = data->videodata->atoms._NET_WM_STATE_FULLSCREEN;
1816
1817 if (X11_IsWindowMapped(_this, window)) {
1818 XEvent e;
1819
1820 // Flush any pending fullscreen events.
1821 if (data->pending_operation & (X11_PENDING_OP_FULLSCREEN | X11_PENDING_OP_MAXIMIZE | X11_PENDING_OP_MOVE)) {
1822 X11_SyncWindow(_this, window);
1823 }
1824
1825 if (!(window->flags & SDL_WINDOW_FULLSCREEN)) {
1826 if (fullscreen == SDL_FULLSCREEN_OP_UPDATE) {
1827 // Request was out of date; set -1 to signal the video core to undo a mode switch.
1828 return SDL_FULLSCREEN_FAILED;
1829 } else if (fullscreen == SDL_FULLSCREEN_OP_LEAVE) {
1830 // Nothing to do.
1831 return SDL_FULLSCREEN_SUCCEEDED;
1832 }
1833 }
1834
1835 if (fullscreen && !(window->flags & SDL_WINDOW_RESIZABLE)) {
1836 /* Compiz refuses fullscreen toggle if we're not resizable, so update the hints so we
1837 can be resized to the fullscreen resolution (or reset so we're not resizable again) */
1838 XSizeHints *sizehints = X11_XAllocSizeHints();
1839 long flags = 0;
1840 X11_XGetWMNormalHints(display, data->xwindow, sizehints, &flags);
1841 // we are going fullscreen so turn the flags off
1842 sizehints->flags &= ~(PMinSize | PMaxSize | PAspect);
1843 X11_XSetWMNormalHints(display, data->xwindow, sizehints);
1844 X11_XFree(sizehints);
1845 }
1846
1847 SDL_zero(e);
1848 e.xany.type = ClientMessage;
1849 e.xclient.message_type = _NET_WM_STATE;
1850 e.xclient.format = 32;
1851 e.xclient.window = data->xwindow;
1852 e.xclient.data.l[0] =
1853 fullscreen ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
1854 e.xclient.data.l[1] = _NET_WM_STATE_FULLSCREEN;
1855 e.xclient.data.l[3] = 0l;
1856
1857 X11_XSendEvent(display, RootWindow(display, displaydata->screen), 0,
1858 SubstructureNotifyMask | SubstructureRedirectMask, &e);
1859
1860 if (!!(window->flags & SDL_WINDOW_FULLSCREEN) != fullscreen) {
1861 data->pending_operation |= X11_PENDING_OP_FULLSCREEN;
1862 }
1863
1864 // Set the position so the window will be on the target display
1865 if (fullscreen) {
1866 SDL_DisplayID current = SDL_GetDisplayForWindowPosition(window);
1867 SDL_copyp(&data->requested_fullscreen_mode, &window->current_fullscreen_mode);
1868 if (fullscreen != !!(window->flags & SDL_WINDOW_FULLSCREEN)) {
1869 data->window_was_maximized = !!(window->flags & SDL_WINDOW_MAXIMIZED);
1870 }
1871 data->expected.x = displaydata->x;
1872 data->expected.y = displaydata->y;
1873 data->expected.w = _display->current_mode->w;
1874 data->expected.h = _display->current_mode->h;
1875
1876 // Only move the window if it isn't fullscreen or already on the target display.
1877 if (!(window->flags & SDL_WINDOW_FULLSCREEN) || (!current || current != _display->id)) {
1878 X11_XMoveWindow(display, data->xwindow, displaydata->x, displaydata->y);
1879 data->pending_operation |= X11_PENDING_OP_MOVE;
1880 }
1881 } else {
1882 SDL_zero(data->requested_fullscreen_mode);
1883
1884 /* Fullscreen windows sometimes end up being marked maximized by
1885 * window managers. Force it back to how we expect it to be.
1886 */
1887 SDL_zero(e);
1888 e.xany.type = ClientMessage;
1889 e.xclient.message_type = _NET_WM_STATE;
1890 e.xclient.format = 32;
1891 e.xclient.window = data->xwindow;
1892 if (data->window_was_maximized) {
1893 e.xclient.data.l[0] = _NET_WM_STATE_ADD;
1894 } else {
1895 e.xclient.data.l[0] = _NET_WM_STATE_REMOVE;
1896 }
1897 e.xclient.data.l[1] = data->videodata->atoms._NET_WM_STATE_MAXIMIZED_VERT;
1898 e.xclient.data.l[2] = data->videodata->atoms._NET_WM_STATE_MAXIMIZED_HORZ;
1899 e.xclient.data.l[3] = 0l;
1900 X11_XSendEvent(display, RootWindow(display, displaydata->screen), 0,
1901 SubstructureNotifyMask | SubstructureRedirectMask, &e);
1902 }
1903 } else {
1904 SDL_WindowFlags flags;
1905
1906 flags = window->flags;
1907 if (fullscreen) {
1908 flags |= SDL_WINDOW_FULLSCREEN;
1909 } else {
1910 flags &= ~SDL_WINDOW_FULLSCREEN;
1911 }
1912 X11_SetNetWMState(_this, data->xwindow, flags);
1913 }
1914
1915 if (data->visual->class == DirectColor) {
1916 if (fullscreen) {
1917 X11_XInstallColormap(display, data->colormap);
1918 } else {
1919 X11_XUninstallColormap(display, data->colormap);
1920 }
1921 }
1922
1923 return SDL_FULLSCREEN_PENDING;
1924}
1925
1926SDL_FullscreenResult X11_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *_display, SDL_FullscreenOp fullscreen)
1927{
1928 return X11_SetWindowFullscreenViaWM(_this, window, _display, fullscreen);
1929}
1930
1931typedef struct
1932{
1933 unsigned char *data;
1934 int format, count;
1935 Atom type;
1936} SDL_x11Prop;
1937
1938/* Reads property
1939 Must call X11_XFree on results
1940 */
1941static void X11_ReadProperty(SDL_x11Prop *p, Display *disp, Window w, Atom prop)
1942{
1943 unsigned char *ret = NULL;
1944 Atom type;
1945 int fmt;
1946 unsigned long count;
1947 unsigned long bytes_left;
1948 int bytes_fetch = 0;
1949
1950 do {
1951 if (ret) {
1952 X11_XFree(ret);
1953 }
1954 X11_XGetWindowProperty(disp, w, prop, 0, bytes_fetch, False, AnyPropertyType, &type, &fmt, &count, &bytes_left, &ret);
1955 bytes_fetch += bytes_left;
1956 } while (bytes_left != 0);
1957
1958 p->data = ret;
1959 p->format = fmt;
1960 p->count = count;
1961 p->type = type;
1962}
1963
1964void *X11_GetWindowICCProfile(SDL_VideoDevice *_this, SDL_Window *window, size_t *size)
1965{
1966 SDL_WindowData *data = window->internal;
1967 Display *display = data->videodata->display;
1968 XWindowAttributes attributes;
1969 Atom icc_profile_atom;
1970 char icc_atom_string[sizeof("_ICC_PROFILE_") + 12];
1971 void *ret_icc_profile_data = NULL;
1972 CARD8 *icc_profile_data;
1973 int real_format;
1974 unsigned long real_nitems;
1975 SDL_x11Prop atomProp;
1976
1977 X11_XGetWindowAttributes(display, data->xwindow, &attributes);
1978 if (X11_XScreenNumberOfScreen(attributes.screen) > 0) {
1979 (void)SDL_snprintf(icc_atom_string, sizeof("_ICC_PROFILE_") + 12, "%s%d", "_ICC_PROFILE_", X11_XScreenNumberOfScreen(attributes.screen));
1980 } else {
1981 SDL_strlcpy(icc_atom_string, "_ICC_PROFILE", sizeof("_ICC_PROFILE"));
1982 }
1983 X11_XGetWindowAttributes(display, RootWindowOfScreen(attributes.screen), &attributes);
1984
1985 icc_profile_atom = X11_XInternAtom(display, icc_atom_string, True);
1986 if (icc_profile_atom == None) {
1987 SDL_SetError("Screen is not calibrated.");
1988 return NULL;
1989 }
1990
1991 X11_ReadProperty(&atomProp, display, RootWindowOfScreen(attributes.screen), icc_profile_atom);
1992 real_format = atomProp.format;
1993 real_nitems = atomProp.count;
1994 icc_profile_data = atomProp.data;
1995 if (real_format == None) {
1996 SDL_SetError("Screen is not calibrated.");
1997 return NULL;
1998 }
1999
2000 ret_icc_profile_data = SDL_malloc(real_nitems);
2001 if (!ret_icc_profile_data) {
2002 return NULL;
2003 }
2004
2005 SDL_memcpy(ret_icc_profile_data, icc_profile_data, real_nitems);
2006 *size = real_nitems;
2007 X11_XFree(icc_profile_data);
2008
2009 return ret_icc_profile_data;
2010}
2011
2012bool X11_SetWindowMouseGrab(SDL_VideoDevice *_this, SDL_Window *window, bool grabbed)
2013{
2014 SDL_WindowData *data = window->internal;
2015 Display *display;
2016
2017 if (!data) {
2018 return SDL_SetError("Invalid window data");
2019 }
2020 data->mouse_grabbed = false;
2021
2022 display = data->videodata->display;
2023
2024 if (grabbed) {
2025 /* If the window is unmapped, XGrab calls return GrabNotViewable,
2026 so when we get a MapNotify later, we'll try to update the grab as
2027 appropriate. */
2028 if (window->flags & SDL_WINDOW_HIDDEN) {
2029 return true;
2030 }
2031
2032 /* If XInput2 is enabled, it will grab the pointer on button presses,
2033 * which results in XGrabPointer returning AlreadyGrabbed. If buttons
2034 * are currently pressed, clear any existing grabs before attempting
2035 * the confinement grab.
2036 */
2037 if (data->xinput2_mouse_enabled && SDL_GetMouseState(NULL, NULL)) {
2038 X11_XUngrabPointer(display, CurrentTime);
2039 }
2040
2041 // Try to grab the mouse
2042 if (!data->videodata->broken_pointer_grab) {
2043 const unsigned int mask = ButtonPressMask | ButtonReleaseMask | PointerMotionMask | FocusChangeMask;
2044 int attempts;
2045 int result = 0;
2046
2047 // Try for up to 5000ms (5s) to grab. If it still fails, stop trying.
2048 for (attempts = 0; attempts < 100; attempts++) {
2049 result = X11_XGrabPointer(display, data->xwindow, False, mask, GrabModeAsync,
2050 GrabModeAsync, data->xwindow, None, CurrentTime);
2051 if (result == GrabSuccess) {
2052 data->mouse_grabbed = true;
2053 break;
2054 }
2055 SDL_Delay(50);
2056 }
2057
2058 if (result != GrabSuccess) {
2059 data->videodata->broken_pointer_grab = true; // don't try again.
2060 }
2061 }
2062
2063 X11_Xinput2GrabTouch(_this, window);
2064
2065 // Raise the window if we grab the mouse
2066 X11_XRaiseWindow(display, data->xwindow);
2067 } else {
2068 X11_XUngrabPointer(display, CurrentTime);
2069
2070 X11_Xinput2UngrabTouch(_this, window);
2071 }
2072 X11_XSync(display, False);
2073
2074 if (!data->videodata->broken_pointer_grab) {
2075 return true;
2076 } else {
2077 return SDL_SetError("The X server refused to let us grab the mouse. You might experience input bugs.");
2078 }
2079}
2080
2081bool X11_SetWindowKeyboardGrab(SDL_VideoDevice *_this, SDL_Window *window, bool grabbed)
2082{
2083 SDL_WindowData *data = window->internal;
2084 Display *display;
2085
2086 if (!data) {
2087 return SDL_SetError("Invalid window data");
2088 }
2089
2090 display = data->videodata->display;
2091
2092 if (grabbed) {
2093 /* If the window is unmapped, XGrab calls return GrabNotViewable,
2094 so when we get a MapNotify later, we'll try to update the grab as
2095 appropriate. */
2096 if (window->flags & SDL_WINDOW_HIDDEN) {
2097 return true;
2098 }
2099
2100 X11_XGrabKeyboard(display, data->xwindow, True, GrabModeAsync,
2101 GrabModeAsync, CurrentTime);
2102 } else {
2103 X11_XUngrabKeyboard(display, CurrentTime);
2104 }
2105 X11_XSync(display, False);
2106
2107 return true;
2108}
2109
2110void X11_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window)
2111{
2112 SDL_WindowData *data = window->internal;
2113
2114 if (data) {
2115 SDL_VideoData *videodata = data->videodata;
2116 Display *display = videodata->display;
2117 int numwindows = videodata->numwindows;
2118 SDL_WindowData **windowlist = videodata->windowlist;
2119 int i;
2120
2121 if (windowlist) {
2122 for (i = 0; i < numwindows; ++i) {
2123 if (windowlist[i] && (windowlist[i]->window == window)) {
2124 windowlist[i] = windowlist[numwindows - 1];
2125 windowlist[numwindows - 1] = NULL;
2126 videodata->numwindows--;
2127 break;
2128 }
2129 }
2130 }
2131#ifdef X_HAVE_UTF8_STRING
2132 if (data->ic) {
2133 X11_XDestroyIC(data->ic);
2134 SDL_free(data->preedit_text);
2135 SDL_free(data->preedit_feedback);
2136 }
2137#endif
2138
2139#ifdef SDL_VIDEO_DRIVER_X11_XSYNC
2140 X11_TermResizeSync(window);
2141#endif /* SDL_VIDEO_DRIVER_X11_XSYNC */
2142
2143 if (!(window->flags & SDL_WINDOW_EXTERNAL)) {
2144 X11_XDestroyWindow(display, data->xwindow);
2145 X11_XFlush(display);
2146 }
2147 SDL_free(data);
2148
2149#ifdef SDL_VIDEO_DRIVER_X11_XFIXES
2150 // If the pointer barriers are active for this, deactivate it.
2151 if (videodata->active_cursor_confined_window == window) {
2152 X11_DestroyPointerBarrier(_this, window);
2153 }
2154#endif // SDL_VIDEO_DRIVER_X11_XFIXES
2155 }
2156 window->internal = NULL;
2157}
2158
2159bool X11_SetWindowHitTest(SDL_Window *window, bool enabled)
2160{
2161 return true; // just succeed, the real work is done elsewhere.
2162}
2163
2164void X11_AcceptDragAndDrop(SDL_Window *window, bool accept)
2165{
2166 SDL_WindowData *data = window->internal;
2167 Display *display = data->videodata->display;
2168 Atom XdndAware = data->videodata->atoms.XdndAware;
2169
2170 if (accept) {
2171 Atom xdnd_version = 5;
2172 X11_XChangeProperty(display, data->xwindow, XdndAware, XA_ATOM, 32,
2173 PropModeReplace, (unsigned char *)&xdnd_version, 1);
2174 } else {
2175 X11_XDeleteProperty(display, data->xwindow, XdndAware);
2176 }
2177}
2178
2179bool X11_FlashWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOperation operation)
2180{
2181 SDL_WindowData *data = window->internal;
2182 Display *display = data->videodata->display;
2183 XWMHints *wmhints;
2184
2185 wmhints = X11_XGetWMHints(display, data->xwindow);
2186 if (!wmhints) {
2187 return SDL_SetError("Couldn't get WM hints");
2188 }
2189
2190 wmhints->flags &= ~XUrgencyHint;
2191 data->flashing_window = false;
2192 data->flash_cancel_time = 0;
2193
2194 switch (operation) {
2195 case SDL_FLASH_CANCEL:
2196 // Taken care of above
2197 break;
2198 case SDL_FLASH_BRIEFLY:
2199 if (!(window->flags & SDL_WINDOW_INPUT_FOCUS)) {
2200 wmhints->flags |= XUrgencyHint;
2201 data->flashing_window = true;
2202 // On Ubuntu 21.04 this causes a dialog to pop up, so leave it up for a full second so users can see it
2203 data->flash_cancel_time = SDL_GetTicks() + 1000;
2204 }
2205 break;
2206 case SDL_FLASH_UNTIL_FOCUSED:
2207 if (!(window->flags & SDL_WINDOW_INPUT_FOCUS)) {
2208 wmhints->flags |= XUrgencyHint;
2209 data->flashing_window = true;
2210 }
2211 break;
2212 default:
2213 break;
2214 }
2215
2216 X11_XSetWMHints(display, data->xwindow, wmhints);
2217 X11_XFree(wmhints);
2218 return true;
2219}
2220
2221bool SDL_X11_SetWindowTitle(Display *display, Window xwindow, char *title)
2222{
2223 Atom _NET_WM_NAME = X11_XInternAtom(display, "_NET_WM_NAME", False);
2224 XTextProperty titleprop;
2225 int conv = X11_XmbTextListToTextProperty(display, &title, 1, XTextStyle, &titleprop);
2226 Status status;
2227
2228 if (X11_XSupportsLocale() != True) {
2229 return SDL_SetError("Current locale not supported by X server, cannot continue.");
2230 }
2231
2232 if (conv == 0) {
2233 X11_XSetTextProperty(display, xwindow, &titleprop, XA_WM_NAME);
2234 X11_XFree(titleprop.value);
2235 // we know this can't be a locale error as we checked X locale validity
2236 } else if (conv < 0) {
2237 return SDL_OutOfMemory();
2238 } else { // conv > 0
2239 SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "%d characters were not convertible to the current locale!", conv);
2240 return true;
2241 }
2242
2243#ifdef X_HAVE_UTF8_STRING
2244 status = X11_Xutf8TextListToTextProperty(display, &title, 1, XUTF8StringStyle, &titleprop);
2245 if (status == Success) {
2246 X11_XSetTextProperty(display, xwindow, &titleprop, _NET_WM_NAME);
2247 X11_XFree(titleprop.value);
2248 } else {
2249 return SDL_SetError("Failed to convert title to UTF8! Bad encoding, or bad Xorg encoding? Window title: «%s»", title);
2250 }
2251#endif
2252
2253 X11_XFlush(display);
2254 return true;
2255}
2256
2257void X11_ShowWindowSystemMenu(SDL_Window *window, int x, int y)
2258{
2259 SDL_WindowData *data = window->internal;
2260 SDL_DisplayData *displaydata = SDL_GetDisplayDriverDataForWindow(window);
2261 Display *display = data->videodata->display;
2262 Window root = RootWindow(display, displaydata->screen);
2263 XClientMessageEvent e;
2264 Window childReturn;
2265 int wx, wy;
2266
2267 SDL_zero(e);
2268 X11_XTranslateCoordinates(display, data->xwindow, root, x, y, &wx, &wy, &childReturn);
2269
2270 e.type = ClientMessage;
2271 e.window = data->xwindow;
2272 e.message_type = X11_XInternAtom(display, "_GTK_SHOW_WINDOW_MENU", 0);
2273 e.data.l[0] = 0; // GTK device ID (unused)
2274 e.data.l[1] = wx; // X coordinate relative to root
2275 e.data.l[2] = wy; // Y coordinate relative to root
2276 e.format = 32;
2277
2278 X11_XSendEvent(display, root, False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent *)&e);
2279 X11_XFlush(display);
2280}
2281
2282bool X11_SyncWindow(SDL_VideoDevice *_this, SDL_Window *window)
2283{
2284 SDL_WindowData *data = window->internal;
2285
2286 // If the window is external and has only a pending resize or move event, use the special external sync path to avoid processing events.
2287 if ((window->flags & SDL_WINDOW_EXTERNAL) &&
2288 (data->pending_operation & ~(X11_PENDING_OP_RESIZE | X11_PENDING_OP_MOVE)) == X11_PENDING_OP_NONE) {
2289 X11_ExternalResizeMoveSync(window);
2290 return true;
2291 }
2292
2293 const Uint64 current_time = SDL_GetTicksNS();
2294 Uint64 timeout = 0;
2295
2296 // Allow time for any pending mode switches to complete.
2297 for (int i = 0; i < _this->num_displays; ++i) {
2298 if (_this->displays[i]->internal->mode_switch_deadline_ns &&
2299 current_time < _this->displays[i]->internal->mode_switch_deadline_ns) {
2300 timeout = SDL_max(_this->displays[i]->internal->mode_switch_deadline_ns - current_time, timeout);
2301 }
2302 }
2303
2304 /* 100ms is fine for most cases, but, for some reason, maximizing
2305 * a window can take a very long time.
2306 */
2307 timeout += window->internal->pending_operation & X11_PENDING_OP_MAXIMIZE ? SDL_MS_TO_NS(1000) : SDL_MS_TO_NS(100);
2308
2309 return X11_SyncWindowTimeout(_this, window, timeout);
2310}
2311
2312bool X11_SetWindowFocusable(SDL_VideoDevice *_this, SDL_Window *window, bool focusable)
2313{
2314 SDL_WindowData *data = window->internal;
2315 Display *display = data->videodata->display;
2316 XWMHints *wmhints;
2317
2318 wmhints = X11_XGetWMHints(display, data->xwindow);
2319 if (!wmhints) {
2320 return SDL_SetError("Couldn't get WM hints");
2321 }
2322
2323 wmhints->input = focusable ? True : False;
2324 wmhints->flags |= InputHint;
2325
2326 X11_XSetWMHints(display, data->xwindow, wmhints);
2327 X11_XFree(wmhints);
2328
2329 return true;
2330}
2331
2332#endif // SDL_VIDEO_DRIVER_X11
2333