summaryrefslogtreecommitdiff
path: root/contrib/SDL-3.2.8/src/video/x11/SDL_x11window.c
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2025-12-27 12:03:39 -0800
committer3gg <3gg@shellblade.net>2025-12-27 12:03:39 -0800
commit5a079a2d114f96d4847d1ee305d5b7c16eeec50e (patch)
tree8926ab44f168acf787d8e19608857b3af0f82758 /contrib/SDL-3.2.8/src/video/x11/SDL_x11window.c
Initial commit
Diffstat (limited to 'contrib/SDL-3.2.8/src/video/x11/SDL_x11window.c')
-rw-r--r--contrib/SDL-3.2.8/src/video/x11/SDL_x11window.c2243
1 files changed, 2243 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/src/video/x11/SDL_x11window.c b/contrib/SDL-3.2.8/src/video/x11/SDL_x11window.c
new file mode 100644
index 0000000..5562053
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/x11/SDL_x11window.c
@@ -0,0 +1,2243 @@
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 SetupWindowInput(_this, window);
525 return true;
526 }
527
528 SDL_VideoData *data = _this->internal;
529 SDL_DisplayData *displaydata = SDL_GetDisplayDriverDataForWindow(window);
530 if (!displaydata) {
531 return SDL_SetError("Could not find display info");
532 }
533
534 const bool force_override_redirect = SDL_GetHintBoolean(SDL_HINT_X11_FORCE_OVERRIDE_REDIRECT, false);
535 const bool use_resize_sync = (window->flags & SDL_WINDOW_VULKAN); /* doesn't work well with Vulkan */
536 SDL_WindowData *windowdata;
537 Display *display = data->display;
538 int screen = displaydata->screen;
539 Visual *visual;
540 int depth;
541 XSetWindowAttributes xattr;
542 XSizeHints *sizehints;
543 XWMHints *wmhints;
544 XClassHint *classhints;
545 Atom _NET_WM_BYPASS_COMPOSITOR;
546 Atom _NET_WM_WINDOW_TYPE;
547 Atom wintype;
548 const char *wintype_name = NULL;
549 long compositor = 1;
550 Atom _NET_WM_PID;
551 const char *hint = NULL;
552 int win_x, win_y;
553 bool undefined_position = false;
554
555#if defined(SDL_VIDEO_OPENGL_GLX) || defined(SDL_VIDEO_OPENGL_EGL)
556 const int transparent = (window->flags & SDL_WINDOW_TRANSPARENT) ? true : false;
557 const char *forced_visual_id = SDL_GetHint(SDL_HINT_VIDEO_X11_WINDOW_VISUALID);
558 const char *display_visual_id = SDL_GetHint(SDL_HINT_VIDEO_X11_VISUALID);
559
560 if (forced_visual_id && *forced_visual_id) {
561 XVisualInfo *vi, template;
562 int nvis;
563
564 SDL_zero(template);
565 template.visualid = SDL_strtol(forced_visual_id, NULL, 0);
566 vi = X11_XGetVisualInfo(display, VisualIDMask, &template, &nvis);
567 if (vi) {
568 visual = vi->visual;
569 depth = vi->depth;
570 X11_XFree(vi);
571 } else {
572 return false;
573 }
574 } else if ((window->flags & SDL_WINDOW_OPENGL) &&
575 (!display_visual_id || !*display_visual_id)) {
576 XVisualInfo *vinfo = NULL;
577
578#ifdef SDL_VIDEO_OPENGL_EGL
579 if (((_this->gl_config.profile_mask == SDL_GL_CONTEXT_PROFILE_ES) ||
580 SDL_GetHintBoolean(SDL_HINT_VIDEO_FORCE_EGL, false))
581#ifdef SDL_VIDEO_OPENGL_GLX
582 && (!_this->gl_data || X11_GL_UseEGL(_this))
583#endif
584 ) {
585 vinfo = X11_GLES_GetVisual(_this, display, screen, transparent);
586 } else
587#endif
588 {
589#ifdef SDL_VIDEO_OPENGL_GLX
590 vinfo = X11_GL_GetVisual(_this, display, screen, transparent);
591#endif
592 }
593
594 if (!vinfo) {
595 return false;
596 }
597 visual = vinfo->visual;
598 depth = vinfo->depth;
599 X11_XFree(vinfo);
600 } else
601#endif
602 {
603 visual = displaydata->visual;
604 depth = displaydata->depth;
605 }
606
607 xattr.override_redirect = ((window->flags & SDL_WINDOW_TOOLTIP) || (window->flags & SDL_WINDOW_POPUP_MENU) || force_override_redirect) ? True : False;
608 xattr.backing_store = NotUseful;
609 xattr.background_pixmap = None;
610 xattr.border_pixel = 0;
611
612 if (visual->class == DirectColor) {
613 XColor *colorcells;
614 int i;
615 int ncolors;
616 int rmax, gmax, bmax;
617 int rmask, gmask, bmask;
618 int rshift, gshift, bshift;
619
620 xattr.colormap =
621 X11_XCreateColormap(display, RootWindow(display, screen),
622 visual, AllocAll);
623
624 // If we can't create a colormap, then we must die
625 if (!xattr.colormap) {
626 return SDL_SetError("Could not create writable colormap");
627 }
628
629 // OK, we got a colormap, now fill it in as best as we can
630 colorcells = SDL_malloc(visual->map_entries * sizeof(XColor));
631 if (!colorcells) {
632 return false;
633 }
634 ncolors = visual->map_entries;
635 rmax = 0xffff;
636 gmax = 0xffff;
637 bmax = 0xffff;
638
639 rshift = 0;
640 rmask = visual->red_mask;
641 while (0 == (rmask & 1)) {
642 rshift++;
643 rmask >>= 1;
644 }
645
646 gshift = 0;
647 gmask = visual->green_mask;
648 while (0 == (gmask & 1)) {
649 gshift++;
650 gmask >>= 1;
651 }
652
653 bshift = 0;
654 bmask = visual->blue_mask;
655 while (0 == (bmask & 1)) {
656 bshift++;
657 bmask >>= 1;
658 }
659
660 // build the color table pixel values
661 for (i = 0; i < ncolors; i++) {
662 Uint32 red = (rmax * i) / (ncolors - 1);
663 Uint32 green = (gmax * i) / (ncolors - 1);
664 Uint32 blue = (bmax * i) / (ncolors - 1);
665
666 Uint32 rbits = (rmask * i) / (ncolors - 1);
667 Uint32 gbits = (gmask * i) / (ncolors - 1);
668 Uint32 bbits = (bmask * i) / (ncolors - 1);
669
670 Uint32 pix =
671 (rbits << rshift) | (gbits << gshift) | (bbits << bshift);
672
673 colorcells[i].pixel = pix;
674
675 colorcells[i].red = red;
676 colorcells[i].green = green;
677 colorcells[i].blue = blue;
678
679 colorcells[i].flags = DoRed | DoGreen | DoBlue;
680 }
681
682 X11_XStoreColors(display, xattr.colormap, colorcells, ncolors);
683
684 SDL_free(colorcells);
685 } else {
686 xattr.colormap =
687 X11_XCreateColormap(display, RootWindow(display, screen),
688 visual, AllocNone);
689 }
690
691 if (window->undefined_x && window->undefined_y &&
692 window->last_displayID == SDL_GetPrimaryDisplay()) {
693 undefined_position = true;
694 }
695
696 if (SDL_WINDOW_IS_POPUP(window)) {
697 X11_ConstrainPopup(window, false);
698 }
699 SDL_RelativeToGlobalForWindow(window,
700 window->floating.x, window->floating.y,
701 &win_x, &win_y);
702
703 /* Always create this with the window->floating.* fields; if we're creating a windowed mode window,
704 * that's fine. If we're creating a maximized or fullscreen window, the window manager will want to
705 * know these values so it can use them if we go _back_ to the base floating windowed mode. SDL manages
706 * migration to fullscreen after CreateSDLWindow returns, which will put all the SDL_Window fields and
707 * system state as expected.
708 */
709 w = X11_XCreateWindow(display, RootWindow(display, screen),
710 win_x, win_y, window->floating.w, window->floating.h,
711 0, depth, InputOutput, visual,
712 (CWOverrideRedirect | CWBackPixmap | CWBorderPixel |
713 CWBackingStore | CWColormap),
714 &xattr);
715 if (!w) {
716 return SDL_SetError("Couldn't create window");
717 }
718
719 /* Don't set the borderless flag if we're about to go fullscreen.
720 * This prevents the window manager from moving a full-screen borderless
721 * window to a different display before we actually go fullscreen.
722 */
723 if (!(window->pending_flags & SDL_WINDOW_FULLSCREEN)) {
724 SetWindowBordered(display, screen, w, !(window->flags & SDL_WINDOW_BORDERLESS));
725 }
726
727 sizehints = X11_XAllocSizeHints();
728 // Setup the normal size hints
729 sizehints->flags = 0;
730 if (!(window->flags & SDL_WINDOW_RESIZABLE)) {
731 sizehints->min_width = sizehints->max_width = window->floating.w;
732 sizehints->min_height = sizehints->max_height = window->floating.h;
733 sizehints->flags |= (PMaxSize | PMinSize);
734 }
735 if (!undefined_position) {
736 sizehints->x = win_x;
737 sizehints->y = win_y;
738 sizehints->flags |= USPosition;
739 }
740
741 // Setup the input hints so we get keyboard input
742 wmhints = X11_XAllocWMHints();
743 wmhints->input = !(window->flags & SDL_WINDOW_NOT_FOCUSABLE) ? True : False;
744 wmhints->window_group = data->window_group;
745 wmhints->flags = InputHint | WindowGroupHint;
746
747 // Setup the class hints so we can get an icon (AfterStep)
748 classhints = X11_XAllocClassHint();
749 classhints->res_name = (char *)SDL_GetExeName();
750 classhints->res_class = (char *)SDL_GetAppID();
751
752 // Set the size, input and class hints, and define WM_CLIENT_MACHINE and WM_LOCALE_NAME
753 X11_XSetWMProperties(display, w, NULL, NULL, NULL, 0, sizehints, wmhints, classhints);
754
755 X11_XFree(sizehints);
756 X11_XFree(wmhints);
757 X11_XFree(classhints);
758 // Set the PID related to the window for the given hostname, if possible
759 if (data->pid > 0) {
760 long pid = (long)data->pid;
761 _NET_WM_PID = X11_XInternAtom(display, "_NET_WM_PID", False);
762 X11_XChangeProperty(display, w, _NET_WM_PID, XA_CARDINAL, 32, PropModeReplace,
763 (unsigned char *)&pid, 1);
764 }
765
766 // Set the window manager state
767 X11_SetNetWMState(_this, w, window->flags);
768
769 compositor = 2; // don't disable compositing except for "normal" windows
770 hint = SDL_GetHint(SDL_HINT_X11_WINDOW_TYPE);
771 if (window->flags & SDL_WINDOW_UTILITY) {
772 wintype_name = "_NET_WM_WINDOW_TYPE_UTILITY";
773 } else if (window->flags & SDL_WINDOW_TOOLTIP) {
774 wintype_name = "_NET_WM_WINDOW_TYPE_TOOLTIP";
775 } else if (window->flags & SDL_WINDOW_POPUP_MENU) {
776 wintype_name = "_NET_WM_WINDOW_TYPE_POPUP_MENU";
777 } else if (hint && *hint) {
778 wintype_name = hint;
779 } else {
780 wintype_name = "_NET_WM_WINDOW_TYPE_NORMAL";
781 compositor = 1; // disable compositing for "normal" windows
782 }
783
784 // Let the window manager know what type of window we are.
785 _NET_WM_WINDOW_TYPE = X11_XInternAtom(display, "_NET_WM_WINDOW_TYPE", False);
786 wintype = X11_XInternAtom(display, wintype_name, False);
787 X11_XChangeProperty(display, w, _NET_WM_WINDOW_TYPE, XA_ATOM, 32,
788 PropModeReplace, (unsigned char *)&wintype, 1);
789 if (SDL_GetHintBoolean(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, true)) {
790 _NET_WM_BYPASS_COMPOSITOR = X11_XInternAtom(display, "_NET_WM_BYPASS_COMPOSITOR", False);
791 X11_XChangeProperty(display, w, _NET_WM_BYPASS_COMPOSITOR, XA_CARDINAL, 32,
792 PropModeReplace,
793 (unsigned char *)&compositor, 1);
794 }
795
796 {
797 Atom protocols[4];
798 int proto_count = 0;
799
800 protocols[proto_count++] = data->atoms.WM_DELETE_WINDOW; // Allow window to be deleted by the WM
801 protocols[proto_count++] = data->atoms.WM_TAKE_FOCUS; // Since we will want to set input focus explicitly
802
803 // Default to using ping if there is no hint
804 if (SDL_GetHintBoolean(SDL_HINT_VIDEO_X11_NET_WM_PING, true)) {
805 protocols[proto_count++] = data->atoms._NET_WM_PING; // Respond so WM knows we're alive
806 }
807
808#ifdef SDL_VIDEO_DRIVER_X11_XSYNC
809 if (use_resize_sync) {
810 protocols[proto_count++] = data->atoms._NET_WM_SYNC_REQUEST; /* Respond after completing resize */
811 }
812#endif /* SDL_VIDEO_DRIVER_X11_XSYNC */
813
814 SDL_assert(proto_count <= sizeof(protocols) / sizeof(protocols[0]));
815
816 X11_XSetWMProtocols(display, w, protocols, proto_count);
817 }
818
819 if (!SetupWindowData(_this, window, w)) {
820 X11_XDestroyWindow(display, w);
821 return false;
822 }
823 windowdata = window->internal;
824
825 // Set the parent if this is a non-popup window.
826 if (!SDL_WINDOW_IS_POPUP(window) && window->parent) {
827 X11_XSetTransientForHint(display, w, window->parent->internal->xwindow);
828 }
829
830 // Set the flag if the borders were forced on when creating a fullscreen window for later removal.
831 windowdata->fullscreen_borders_forced_on = !!(window->pending_flags & SDL_WINDOW_FULLSCREEN) &&
832 !!(window->flags & SDL_WINDOW_BORDERLESS);
833
834#ifdef SDL_VIDEO_DRIVER_X11_XSYNC
835 if (use_resize_sync) {
836 X11_InitResizeSync(window);
837 }
838#endif /* SDL_VIDEO_DRIVER_X11_XSYNC */
839
840#if defined(SDL_VIDEO_OPENGL_ES) || defined(SDL_VIDEO_OPENGL_ES2) || defined(SDL_VIDEO_OPENGL_EGL)
841 if ((window->flags & SDL_WINDOW_OPENGL) &&
842 ((_this->gl_config.profile_mask == SDL_GL_CONTEXT_PROFILE_ES) ||
843 SDL_GetHintBoolean(SDL_HINT_VIDEO_FORCE_EGL, false))
844#ifdef SDL_VIDEO_OPENGL_GLX
845 && (!_this->gl_data || X11_GL_UseEGL(_this))
846#endif
847 ) {
848#ifdef SDL_VIDEO_OPENGL_EGL
849 if (!_this->egl_data) {
850 return false;
851 }
852
853 // Create the GLES window surface
854 windowdata->egl_surface = SDL_EGL_CreateSurface(_this, window, (NativeWindowType)w);
855
856 if (windowdata->egl_surface == EGL_NO_SURFACE) {
857 return SDL_SetError("Could not create GLES window surface");
858 }
859#else
860 return SDL_SetError("Could not create GLES window surface (EGL support not configured)");
861#endif // SDL_VIDEO_OPENGL_EGL
862 }
863#endif
864
865#ifdef SDL_VIDEO_DRIVER_X11_XSHAPE
866 // Tooltips do not receive input
867 if (window->flags & SDL_WINDOW_TOOLTIP) {
868 Region region = X11_XCreateRegion();
869 X11_XShapeCombineRegion(display, w, ShapeInput, 0, 0, region, ShapeSet);
870 X11_XDestroyRegion(region);
871 }
872#endif
873
874 SetupWindowInput(_this, window);
875
876 // For _ICC_PROFILE.
877 X11_XSelectInput(display, RootWindow(display, screen), PropertyChangeMask);
878
879 X11_XFlush(display);
880
881 return true;
882}
883
884char *X11_GetWindowTitle(SDL_VideoDevice *_this, Window xwindow)
885{
886 SDL_VideoData *data = _this->internal;
887 Display *display = data->display;
888 int status, real_format;
889 Atom real_type;
890 unsigned long items_read, items_left;
891 unsigned char *propdata;
892 char *title = NULL;
893
894 status = X11_XGetWindowProperty(display, xwindow, data->atoms._NET_WM_NAME,
895 0L, 8192L, False, data->atoms.UTF8_STRING, &real_type, &real_format,
896 &items_read, &items_left, &propdata);
897 if (status == Success && propdata) {
898 title = SDL_strdup(SDL_static_cast(char *, propdata));
899 X11_XFree(propdata);
900 } else {
901 status = X11_XGetWindowProperty(display, xwindow, XA_WM_NAME,
902 0L, 8192L, False, XA_STRING, &real_type, &real_format,
903 &items_read, &items_left, &propdata);
904 if (status == Success && propdata) {
905 title = SDL_iconv_string("UTF-8", "", SDL_static_cast(char *, propdata), items_read + 1);
906 SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Failed to convert WM_NAME title expecting UTF8! Title: %s", title);
907 X11_XFree(propdata);
908 } else {
909 SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Could not get any window title response from Xorg, returning empty string!");
910 title = SDL_strdup("");
911 }
912 }
913 return title;
914}
915
916void X11_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window)
917{
918 SDL_WindowData *data = window->internal;
919 Window xwindow = data->xwindow;
920 Display *display = data->videodata->display;
921 char *title = window->title ? window->title : "";
922
923 SDL_X11_SetWindowTitle(display, xwindow, title);
924}
925
926static bool caught_x11_error = false;
927static int X11_CatchAnyError(Display *d, XErrorEvent *e)
928{
929 /* this may happen during tumultuous times when we are polling anyhow,
930 so just note we had an error and return control. */
931 caught_x11_error = true;
932 return 0;
933}
934
935/* Wait a brief time, or not, to see if the window manager decided to move/resize the window.
936 * Send MOVED and RESIZED window events */
937static bool X11_SyncWindowTimeout(SDL_VideoDevice *_this, SDL_Window *window, Uint64 param_timeout)
938{
939 SDL_WindowData *data = window->internal;
940 Display *display = data->videodata->display;
941 int (*prev_handler)(Display *, XErrorEvent *);
942 Uint64 timeout = 0;
943 bool force_exit = false;
944 bool result = true;
945
946 X11_XSync(display, False);
947 prev_handler = X11_XSetErrorHandler(X11_CatchAnyError);
948
949 if (param_timeout) {
950 timeout = SDL_GetTicksNS() + param_timeout;
951 }
952
953 while (true) {
954 X11_XSync(display, False);
955 X11_PumpEvents(_this);
956
957 if ((data->pending_operation & X11_PENDING_OP_MOVE) && (window->x == data->expected.x + data->border_left && window->y == data->expected.y + data->border_top)) {
958 data->pending_operation &= ~X11_PENDING_OP_MOVE;
959 }
960 if ((data->pending_operation & X11_PENDING_OP_RESIZE) && (window->w == data->expected.w && window->h == data->expected.h)) {
961 data->pending_operation &= ~X11_PENDING_OP_RESIZE;
962 }
963
964 if (data->pending_operation == X11_PENDING_OP_NONE) {
965 if (force_exit ||
966 (window->x == data->expected.x + data->border_left && window->y == data->expected.y + data->border_top &&
967 window->w == data->expected.w && window->h == data->expected.h)) {
968 // The window is in the expected state and nothing is pending. Done.
969 break;
970 }
971
972 /* No operations are pending, but the window still isn't in the expected state.
973 * Try one more time before exiting.
974 */
975 force_exit = true;
976 }
977
978 if (SDL_GetTicksNS() >= timeout) {
979 // Timed out without the expected values. Update the requested data so future sync calls won't block.
980 data->expected.x = window->x;
981 data->expected.y = window->y;
982 data->expected.w = window->w;
983 data->expected.h = window->h;
984
985 result = false;
986 break;
987 }
988
989 SDL_Delay(10);
990 }
991
992 data->pending_operation = X11_PENDING_OP_NONE;
993
994 if (!caught_x11_error) {
995 X11_PumpEvents(_this);
996 } else {
997 result = false;
998 }
999
1000 X11_XSetErrorHandler(prev_handler);
1001 caught_x11_error = false;
1002
1003 return result;
1004}
1005
1006bool X11_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *icon)
1007{
1008 SDL_WindowData *data = window->internal;
1009 Display *display = data->videodata->display;
1010 Atom _NET_WM_ICON = data->videodata->atoms._NET_WM_ICON;
1011 int (*prevHandler)(Display *, XErrorEvent *) = NULL;
1012 bool result = true;
1013
1014 if (icon) {
1015 int x, y;
1016 int propsize;
1017 long *propdata;
1018 Uint32 *src;
1019 long *dst;
1020
1021 // Set the _NET_WM_ICON property
1022 SDL_assert(icon->format == SDL_PIXELFORMAT_ARGB8888);
1023 propsize = 2 + (icon->w * icon->h);
1024 propdata = SDL_malloc(propsize * sizeof(long));
1025
1026 if (!propdata) {
1027 return false;
1028 }
1029
1030 X11_XSync(display, False);
1031 prevHandler = X11_XSetErrorHandler(X11_CatchAnyError);
1032
1033 propdata[0] = icon->w;
1034 propdata[1] = icon->h;
1035 dst = &propdata[2];
1036
1037 for (y = 0; y < icon->h; ++y) {
1038 src = (Uint32 *)((Uint8 *)icon->pixels + y * icon->pitch);
1039 for (x = 0; x < icon->w; ++x) {
1040 *dst++ = *src++;
1041 }
1042 }
1043
1044 X11_XChangeProperty(display, data->xwindow, _NET_WM_ICON, XA_CARDINAL,
1045 32, PropModeReplace, (unsigned char *)propdata,
1046 propsize);
1047 SDL_free(propdata);
1048
1049 if (caught_x11_error) {
1050 result = SDL_SetError("An error occurred while trying to set the window's icon");
1051 }
1052 }
1053
1054 X11_XFlush(display);
1055
1056 if (prevHandler) {
1057 X11_XSetErrorHandler(prevHandler);
1058 caught_x11_error = false;
1059 }
1060
1061 return result;
1062}
1063
1064void X11_UpdateWindowPosition(SDL_Window *window, bool use_current_position)
1065{
1066 SDL_WindowData *data = window->internal;
1067 Display *display = data->videodata->display;
1068 const int rel_x = use_current_position ? window->x : window->pending.x;
1069 const int rel_y = use_current_position ? window->y : window->pending.y;
1070
1071 SDL_RelativeToGlobalForWindow(window,
1072 rel_x - data->border_left, rel_y - data->border_top,
1073 &data->expected.x, &data->expected.y);
1074
1075 // Attempt to move the window
1076 if (window->flags & SDL_WINDOW_HIDDEN) {
1077 window->internal->pending_position = true;
1078 } else {
1079 data->pending_operation |= X11_PENDING_OP_MOVE;
1080 X11_XMoveWindow(display, data->xwindow, data->expected.x, data->expected.y);
1081 }
1082}
1083
1084bool X11_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window *window)
1085{
1086 // Sync any pending fullscreen or maximize events.
1087 if (window->internal->pending_operation & (X11_PENDING_OP_FULLSCREEN | X11_PENDING_OP_MAXIMIZE)) {
1088 X11_FlushPendingEvents(_this, window);
1089 }
1090
1091 // Set the position as pending if the window is maximized with a restore pending.
1092 if (window->flags & SDL_WINDOW_MAXIMIZED) {
1093 if (window->internal->pending_operation & X11_PENDING_OP_RESTORE) {
1094 window->internal->pending_position = true;
1095 }
1096 return true;
1097 }
1098
1099 if (!(window->flags & SDL_WINDOW_FULLSCREEN)) {
1100 if (SDL_WINDOW_IS_POPUP(window)) {
1101 X11_ConstrainPopup(window, true);
1102 }
1103 X11_UpdateWindowPosition(window, false);
1104 } else {
1105 SDL_UpdateFullscreenMode(window, SDL_FULLSCREEN_OP_UPDATE, true);
1106 }
1107 return true;
1108}
1109
1110void X11_SetWindowMinMax(SDL_Window *window, bool use_current)
1111{
1112 SDL_WindowData *data = window->internal;
1113 Display *display = data->videodata->display;
1114 XSizeHints *sizehints = X11_XAllocSizeHints();
1115 long hint_flags = 0;
1116
1117 X11_XGetWMNormalHints(display, data->xwindow, sizehints, &hint_flags);
1118 sizehints->flags &= ~(PMinSize | PMaxSize | PAspect);
1119
1120 if (data->window->flags & SDL_WINDOW_RESIZABLE) {
1121 if (data->window->min_w || data->window->min_h) {
1122 sizehints->flags |= PMinSize;
1123 sizehints->min_width = data->window->min_w;
1124 sizehints->min_height = data->window->min_h;
1125 }
1126 if (data->window->max_w || data->window->max_h) {
1127 sizehints->flags |= PMaxSize;
1128 sizehints->max_width = data->window->max_w;
1129 sizehints->max_height = data->window->max_h;
1130 }
1131 if (data->window->min_aspect > 0.0f || data->window->max_aspect > 0.0f) {
1132 sizehints->flags |= PAspect;
1133 SDL_CalculateFraction(data->window->min_aspect, &sizehints->min_aspect.x, &sizehints->min_aspect.y);
1134 SDL_CalculateFraction(data->window->max_aspect, &sizehints->max_aspect.x, &sizehints->max_aspect.y);
1135 }
1136 } else {
1137 // Set the min/max to the same values to make the window non-resizable
1138 sizehints->flags |= PMinSize | PMaxSize;
1139 sizehints->min_width = sizehints->max_width = use_current ? data->window->floating.w : window->windowed.w;
1140 sizehints->min_height = sizehints->max_height = use_current ? data->window->floating.h : window->windowed.h;
1141 }
1142
1143 X11_XSetWMNormalHints(display, data->xwindow, sizehints);
1144 X11_XFree(sizehints);
1145}
1146
1147void X11_SetWindowMinimumSize(SDL_VideoDevice *_this, SDL_Window *window)
1148{
1149 if (window->internal->pending_operation & X11_PENDING_OP_FULLSCREEN) {
1150 X11_SyncWindow(_this, window);
1151 }
1152
1153 if (!(window->flags & SDL_WINDOW_FULLSCREEN)) {
1154 X11_SetWindowMinMax(window, true);
1155 }
1156}
1157
1158void X11_SetWindowMaximumSize(SDL_VideoDevice *_this, SDL_Window *window)
1159{
1160 if (window->internal->pending_operation & X11_PENDING_OP_FULLSCREEN) {
1161 X11_SyncWindow(_this, window);
1162 }
1163
1164 if (!(window->flags & SDL_WINDOW_FULLSCREEN)) {
1165 X11_SetWindowMinMax(window, true);
1166 }
1167}
1168
1169void X11_SetWindowAspectRatio(SDL_VideoDevice *_this, SDL_Window *window)
1170{
1171 if (window->internal->pending_operation & X11_PENDING_OP_FULLSCREEN) {
1172 X11_SyncWindow(_this, window);
1173 }
1174
1175 if (!(window->flags & SDL_WINDOW_FULLSCREEN)) {
1176 X11_SetWindowMinMax(window, true);
1177 }
1178}
1179
1180void X11_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window)
1181{
1182 SDL_WindowData *data = window->internal;
1183 Display *display = data->videodata->display;
1184
1185 /* Wait for pending maximize and fullscreen operations to complete, as these windows
1186 * don't get size changes.
1187 */
1188 if (data->pending_operation & (X11_PENDING_OP_MAXIMIZE | X11_PENDING_OP_FULLSCREEN)) {
1189 X11_FlushPendingEvents(_this, window);
1190 }
1191
1192 // Set the size as pending if the window is being restored.
1193 if (window->flags & (SDL_WINDOW_MAXIMIZED | SDL_WINDOW_FULLSCREEN)) {
1194 // New size will be set when the window is restored.
1195 if (data->pending_operation & X11_PENDING_OP_RESTORE) {
1196 data->pending_size = true;
1197 } else {
1198 // Can't resize the window.
1199 window->last_size_pending = false;
1200 }
1201 return;
1202 }
1203
1204 if (!(window->flags & SDL_WINDOW_RESIZABLE)) {
1205 if (!(window->flags & SDL_WINDOW_FULLSCREEN)) {
1206 /* Apparently, if the X11 Window is set to a 'non-resizable' window, you cannot resize it using the X11_XResizeWindow, thus
1207 * we must set the size hints to adjust the window size.
1208 */
1209 XSizeHints *sizehints = X11_XAllocSizeHints();
1210 long userhints;
1211 int dest_x, dest_y;
1212
1213 X11_XGetWMNormalHints(display, data->xwindow, sizehints, &userhints);
1214
1215 sizehints->min_width = sizehints->max_width = window->pending.w;
1216 sizehints->min_height = sizehints->max_height = window->pending.h;
1217 sizehints->flags |= PMinSize | PMaxSize;
1218
1219 X11_XSetWMNormalHints(display, data->xwindow, sizehints);
1220
1221 /* From Pierre-Loup:
1222 WMs each have their little quirks with that. When you change the
1223 size hints, they get a ConfigureNotify event with the
1224 WM_NORMAL_SIZE_HINTS Atom. They all save the hints then, but they
1225 don't all resize the window right away to enforce the new hints.
1226
1227 Some of them resize only after:
1228 - A user-initiated move or resize
1229 - A code-initiated move or resize
1230 - Hiding & showing window (Unmap & map)
1231
1232 The following move & resize seems to help a lot of WMs that didn't
1233 properly update after the hints were changed. We don't do a
1234 hide/show, because there are supposedly subtle problems with doing so
1235 and transitioning from windowed to fullscreen in Unity.
1236 */
1237 X11_XResizeWindow(display, data->xwindow, window->pending.w, window->pending.h);
1238 const int x = window->last_position_pending ? window->pending.x : window->x;
1239 const int y = window->last_position_pending ? window->pending.y : window->y;
1240 SDL_RelativeToGlobalForWindow(window,
1241 x - data->border_left,
1242 y - data->border_top,
1243 &dest_x, &dest_y);
1244 X11_XMoveWindow(display, data->xwindow, dest_x, dest_y);
1245 X11_XRaiseWindow(display, data->xwindow);
1246
1247 X11_XFree(sizehints);
1248 }
1249 } else {
1250 data->expected.w = window->pending.w;
1251 data->expected.h = window->pending.h;
1252 data->pending_operation |= X11_PENDING_OP_RESIZE;
1253 X11_XResizeWindow(display, data->xwindow, data->expected.w, data->expected.h);
1254 }
1255}
1256
1257bool X11_GetWindowBordersSize(SDL_VideoDevice *_this, SDL_Window *window, int *top, int *left, int *bottom, int *right)
1258{
1259 SDL_WindowData *data = window->internal;
1260
1261 *left = data->border_left;
1262 *right = data->border_right;
1263 *top = data->border_top;
1264 *bottom = data->border_bottom;
1265
1266 return true;
1267}
1268
1269bool X11_SetWindowOpacity(SDL_VideoDevice *_this, SDL_Window *window, float opacity)
1270{
1271 SDL_WindowData *data = window->internal;
1272 Display *display = data->videodata->display;
1273 Atom _NET_WM_WINDOW_OPACITY = data->videodata->atoms._NET_WM_WINDOW_OPACITY;
1274
1275 if (opacity == 1.0f) {
1276 X11_XDeleteProperty(display, data->xwindow, _NET_WM_WINDOW_OPACITY);
1277 } else {
1278 const Uint32 FullyOpaque = 0xFFFFFFFF;
1279 const long alpha = (long)((double)opacity * (double)FullyOpaque);
1280 X11_XChangeProperty(display, data->xwindow, _NET_WM_WINDOW_OPACITY, XA_CARDINAL, 32,
1281 PropModeReplace, (unsigned char *)&alpha, 1);
1282 }
1283
1284 return true;
1285}
1286
1287bool X11_SetWindowParent(SDL_VideoDevice *_this, SDL_Window *window, SDL_Window *parent)
1288{
1289 SDL_WindowData *data = window->internal;
1290 SDL_WindowData *parent_data = parent ? parent->internal : NULL;
1291 SDL_VideoData *video_data = _this->internal;
1292 Display *display = video_data->display;
1293
1294 if (parent_data) {
1295 X11_XSetTransientForHint(display, data->xwindow, parent_data->xwindow);
1296 } else {
1297 X11_XDeleteProperty(display, data->xwindow, video_data->atoms.WM_TRANSIENT_FOR);
1298 }
1299
1300 return true;
1301}
1302
1303bool X11_SetWindowModal(SDL_VideoDevice *_this, SDL_Window *window, bool modal)
1304{
1305 SDL_WindowData *data = window->internal;
1306 SDL_VideoData *video_data = _this->internal;
1307 SDL_DisplayData *displaydata = SDL_GetDisplayDriverDataForWindow(window);
1308 Display *display = video_data->display;
1309 Uint32 flags = window->flags;
1310 Atom _NET_WM_STATE = data->videodata->atoms._NET_WM_STATE;
1311 Atom _NET_WM_STATE_MODAL = data->videodata->atoms._NET_WM_STATE_MODAL;
1312
1313 if (modal) {
1314 flags |= SDL_WINDOW_MODAL;
1315 } else {
1316 flags &= ~SDL_WINDOW_MODAL;
1317 X11_XDeleteProperty(display, data->xwindow, video_data->atoms.WM_TRANSIENT_FOR);
1318 }
1319
1320 if (X11_IsWindowMapped(_this, window)) {
1321 XEvent e;
1322
1323 SDL_zero(e);
1324 e.xany.type = ClientMessage;
1325 e.xclient.message_type = _NET_WM_STATE;
1326 e.xclient.format = 32;
1327 e.xclient.window = data->xwindow;
1328 e.xclient.data.l[0] = modal ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
1329 e.xclient.data.l[1] = _NET_WM_STATE_MODAL;
1330 e.xclient.data.l[3] = 0l;
1331
1332 X11_XSendEvent(display, RootWindow(display, displaydata->screen), 0,
1333 SubstructureNotifyMask | SubstructureRedirectMask, &e);
1334 } else {
1335 X11_SetNetWMState(_this, data->xwindow, flags);
1336 }
1337
1338 X11_XFlush(display);
1339
1340 return true;
1341}
1342
1343void X11_SetWindowBordered(SDL_VideoDevice *_this, SDL_Window *window, bool bordered)
1344{
1345 const bool focused = (window->flags & SDL_WINDOW_INPUT_FOCUS) ? true : false;
1346 const bool visible = (!(window->flags & SDL_WINDOW_HIDDEN)) ? true : false;
1347 SDL_WindowData *data = window->internal;
1348 SDL_DisplayData *displaydata = SDL_GetDisplayDriverDataForWindow(window);
1349 Display *display = data->videodata->display;
1350 XEvent event;
1351
1352 if (data->pending_operation & X11_PENDING_OP_FULLSCREEN) {
1353 X11_SyncWindow(_this, window);
1354 }
1355
1356 // If the window is fullscreen, the resize capability will be set/cleared when it is returned to windowed mode.
1357 if (!(window->flags & SDL_WINDOW_FULLSCREEN)) {
1358 SetWindowBordered(display, displaydata->screen, data->xwindow, bordered);
1359 X11_XFlush(display);
1360
1361 if (visible) {
1362 XWindowAttributes attr;
1363 do {
1364 X11_XSync(display, False);
1365 X11_XGetWindowAttributes(display, data->xwindow, &attr);
1366 } while (attr.map_state != IsViewable);
1367
1368 if (focused) {
1369 X11_XSetInputFocus(display, data->xwindow, RevertToParent, CurrentTime);
1370 }
1371 }
1372
1373 // make sure these don't make it to the real event queue if they fired here.
1374 X11_XSync(display, False);
1375 X11_XCheckIfEvent(display, &event, &isUnmapNotify, (XPointer)&data->xwindow);
1376 X11_XCheckIfEvent(display, &event, &isMapNotify, (XPointer)&data->xwindow);
1377
1378 // Turning the borders off doesn't send an extent event, so they must be cleared here.
1379 X11_GetBorderValues(data);
1380
1381 // Make sure the window manager didn't resize our window for the difference.
1382 X11_XResizeWindow(display, data->xwindow, window->floating.w, window->floating.h);
1383 X11_XSync(display, False);
1384 } else {
1385 // If fullscreen, set a flag to toggle the borders when returning to windowed mode.
1386 data->toggle_borders = true;
1387 data->fullscreen_borders_forced_on = false;
1388 }
1389}
1390
1391void X11_SetWindowResizable(SDL_VideoDevice *_this, SDL_Window *window, bool resizable)
1392{
1393 SDL_WindowData *data = window->internal;
1394
1395 if (data->pending_operation & X11_PENDING_OP_FULLSCREEN) {
1396 X11_SyncWindow(_this, window);
1397 }
1398
1399 // If the window is fullscreen, the resize capability will be set/cleared when it is returned to windowed mode.
1400 if (!(window->flags & SDL_WINDOW_FULLSCREEN)) {
1401 X11_SetWindowMinMax(window, true);
1402 }
1403}
1404
1405void X11_SetWindowAlwaysOnTop(SDL_VideoDevice *_this, SDL_Window *window, bool on_top)
1406{
1407 SDL_WindowData *data = window->internal;
1408 SDL_DisplayData *displaydata = SDL_GetDisplayDriverDataForWindow(window);
1409 Display *display = data->videodata->display;
1410 Atom _NET_WM_STATE = data->videodata->atoms._NET_WM_STATE;
1411 Atom _NET_WM_STATE_ABOVE = data->videodata->atoms._NET_WM_STATE_ABOVE;
1412
1413 if (X11_IsWindowMapped(_this, window)) {
1414 XEvent e;
1415
1416 SDL_zero(e);
1417 e.xany.type = ClientMessage;
1418 e.xclient.message_type = _NET_WM_STATE;
1419 e.xclient.format = 32;
1420 e.xclient.window = data->xwindow;
1421 e.xclient.data.l[0] =
1422 on_top ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
1423 e.xclient.data.l[1] = _NET_WM_STATE_ABOVE;
1424 e.xclient.data.l[3] = 0l;
1425
1426 X11_XSendEvent(display, RootWindow(display, displaydata->screen), 0,
1427 SubstructureNotifyMask | SubstructureRedirectMask, &e);
1428 } else {
1429 X11_SetNetWMState(_this, data->xwindow, window->flags);
1430 }
1431 X11_XFlush(display);
1432}
1433
1434void X11_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window)
1435{
1436 SDL_WindowData *data = window->internal;
1437 Display *display = data->videodata->display;
1438 bool bActivate = SDL_GetHintBoolean(SDL_HINT_WINDOW_ACTIVATE_WHEN_SHOWN, true);
1439 bool position_is_absolute = false;
1440 bool set_position = false;
1441 XEvent event;
1442
1443 if (SDL_WINDOW_IS_POPUP(window)) {
1444 // Update the position in case the parent moved while we were hidden
1445 X11_ConstrainPopup(window, true);
1446 data->pending_position = true;
1447
1448 // Coordinates after X11_ConstrainPopup() are already in the global space.
1449 position_is_absolute = true;
1450 set_position = true;
1451 }
1452
1453 /* Whether XMapRaised focuses the window is based on the window type and it is
1454 * wm specific. There isn't much we can do here */
1455 (void)bActivate;
1456
1457 if (!X11_IsWindowMapped(_this, window)) {
1458 X11_XMapRaised(display, data->xwindow);
1459 /* Blocking wait for "MapNotify" event.
1460 * We use X11_XIfEvent because pXWindowEvent takes a mask rather than a type,
1461 * and XCheckTypedWindowEvent doesn't block */
1462 if (!(window->flags & SDL_WINDOW_EXTERNAL) && X11_IsDisplayOk(display)) {
1463 X11_XIfEvent(display, &event, &isMapNotify, (XPointer)&data->xwindow);
1464 }
1465 X11_XFlush(display);
1466 set_position = data->pending_position ||
1467 (!(window->flags & SDL_WINDOW_BORDERLESS) && !window->undefined_x && !window->undefined_y);
1468 }
1469
1470 if (!data->videodata->net_wm) {
1471 // no WM means no FocusIn event, which confuses us. Force it.
1472 X11_XSync(display, False);
1473 X11_XSetInputFocus(display, data->xwindow, RevertToNone, CurrentTime);
1474 X11_XFlush(display);
1475 }
1476
1477 // Popup menus grab the keyboard
1478 if (window->flags & SDL_WINDOW_POPUP_MENU) {
1479 X11_SetKeyboardFocus(window, window->parent == SDL_GetKeyboardFocus());
1480 }
1481
1482 // Get some valid border values, if we haven't received them yet
1483 if (data->border_left == 0 && data->border_right == 0 && data->border_top == 0 && data->border_bottom == 0) {
1484 X11_GetBorderValues(data);
1485 }
1486
1487 if (set_position) {
1488 // Apply the window position, accounting for offsets due to the borders appearing.
1489 const int tx = data->pending_position ? window->pending.x : window->x;
1490 const int ty = data->pending_position ? window->pending.y : window->y;
1491 int x, y;
1492 if (position_is_absolute) {
1493 x = tx;
1494 y = ty;
1495 } else {
1496 SDL_RelativeToGlobalForWindow(window,
1497 tx - data->border_left, ty - data->border_top,
1498 &x, &y);
1499 }
1500 data->pending_position = false;
1501 X11_XMoveWindow(display, data->xwindow, x, y);
1502 }
1503
1504 /* Some window managers can send garbage coordinates while mapping the window, so don't emit size and position
1505 * events during the initial configure events.
1506 */
1507 data->size_move_event_flags = X11_SIZE_MOVE_EVENTS_DISABLE;
1508 X11_XSync(display, False);
1509 X11_PumpEvents(_this);
1510 data->size_move_event_flags = 0;
1511
1512 // If a configure event was received (type is non-zero), send the final window size and coordinates.
1513 if (data->last_xconfigure.type) {
1514 int x, y;
1515 SDL_GlobalToRelativeForWindow(data->window, data->last_xconfigure.x, data->last_xconfigure.y, &x, &y);
1516 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, data->last_xconfigure.width, data->last_xconfigure.height);
1517 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MOVED, x, y);
1518 }
1519}
1520
1521void X11_HideWindow(SDL_VideoDevice *_this, SDL_Window *window)
1522{
1523 SDL_WindowData *data = window->internal;
1524 SDL_DisplayData *displaydata = SDL_GetDisplayDriverDataForWindow(window);
1525 int screen = (displaydata ? displaydata->screen : 0);
1526 Display *display = data->videodata->display;
1527 XEvent event;
1528
1529 if (X11_IsWindowMapped(_this, window)) {
1530 X11_XWithdrawWindow(display, data->xwindow, screen);
1531 // Blocking wait for "UnmapNotify" event
1532 if (!(window->flags & SDL_WINDOW_EXTERNAL) && X11_IsDisplayOk(display)) {
1533 X11_XIfEvent(display, &event, &isUnmapNotify, (XPointer)&data->xwindow);
1534 }
1535 X11_XFlush(display);
1536 }
1537
1538 // Transfer keyboard focus back to the parent
1539 if (window->flags & SDL_WINDOW_POPUP_MENU) {
1540 SDL_Window *new_focus = window->parent;
1541 bool set_focus = window == SDL_GetKeyboardFocus();
1542
1543 // Find the highest level window, up to the toplevel parent, that isn't being hidden or destroyed.
1544 while (SDL_WINDOW_IS_POPUP(new_focus) && (new_focus->is_hiding || new_focus->is_destroying)) {
1545 new_focus = new_focus->parent;
1546
1547 // If some window in the chain currently had focus, set it to the new lowest-level window.
1548 if (!set_focus) {
1549 set_focus = new_focus == SDL_GetKeyboardFocus();
1550 }
1551 }
1552
1553 X11_SetKeyboardFocus(new_focus, set_focus);
1554 }
1555
1556 X11_XSync(display, False);
1557 X11_PumpEvents(_this);
1558}
1559
1560static bool X11_SetWindowActive(SDL_VideoDevice *_this, SDL_Window *window)
1561{
1562 CHECK_WINDOW_DATA(window);
1563
1564 SDL_WindowData *data = window->internal;
1565 SDL_DisplayData *displaydata = SDL_GetDisplayDriverDataForWindow(window);
1566 Display *display = data->videodata->display;
1567 Atom _NET_ACTIVE_WINDOW = data->videodata->atoms._NET_ACTIVE_WINDOW;
1568
1569 if (X11_IsWindowMapped(_this, window)) {
1570 XEvent e;
1571
1572 // printf("SDL Window %p: sending _NET_ACTIVE_WINDOW with timestamp %lu\n", window, data->user_time);
1573
1574 SDL_zero(e);
1575 e.xany.type = ClientMessage;
1576 e.xclient.message_type = _NET_ACTIVE_WINDOW;
1577 e.xclient.format = 32;
1578 e.xclient.window = data->xwindow;
1579 e.xclient.data.l[0] = 1; // source indication. 1 = application
1580 e.xclient.data.l[1] = data->user_time;
1581 e.xclient.data.l[2] = 0;
1582
1583 X11_XSendEvent(display, RootWindow(display, displaydata->screen), 0,
1584 SubstructureNotifyMask | SubstructureRedirectMask, &e);
1585
1586 X11_XFlush(display);
1587 }
1588 return true;
1589}
1590
1591void X11_RaiseWindow(SDL_VideoDevice *_this, SDL_Window *window)
1592{
1593 SDL_WindowData *data = window->internal;
1594 Display *display = data->videodata->display;
1595 bool bActivate = SDL_GetHintBoolean(SDL_HINT_WINDOW_ACTIVATE_WHEN_RAISED, true);
1596
1597 X11_XRaiseWindow(display, data->xwindow);
1598 if (bActivate) {
1599 X11_SetWindowActive(_this, window);
1600 }
1601 X11_XFlush(display);
1602}
1603
1604static bool X11_SetWindowMaximized(SDL_VideoDevice *_this, SDL_Window *window, bool maximized)
1605{
1606 CHECK_WINDOW_DATA(window);
1607
1608 SDL_WindowData *data = window->internal;
1609 SDL_DisplayData *displaydata = SDL_GetDisplayDriverDataForWindow(window);
1610 Display *display = data->videodata->display;
1611 Atom _NET_WM_STATE = data->videodata->atoms._NET_WM_STATE;
1612 Atom _NET_WM_STATE_MAXIMIZED_VERT = data->videodata->atoms._NET_WM_STATE_MAXIMIZED_VERT;
1613 Atom _NET_WM_STATE_MAXIMIZED_HORZ = data->videodata->atoms._NET_WM_STATE_MAXIMIZED_HORZ;
1614
1615 if (window->flags & SDL_WINDOW_FULLSCREEN) {
1616 /* Fullscreen windows are maximized on some window managers,
1617 and this is functional behavior, so don't remove that state
1618 now, we'll take care of it when we leave fullscreen mode.
1619 */
1620 return true;
1621 }
1622
1623 if (X11_IsWindowMapped(_this, window)) {
1624 XEvent e;
1625
1626 SDL_zero(e);
1627 e.xany.type = ClientMessage;
1628 e.xclient.message_type = _NET_WM_STATE;
1629 e.xclient.format = 32;
1630 e.xclient.window = data->xwindow;
1631 e.xclient.data.l[0] =
1632 maximized ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
1633 e.xclient.data.l[1] = _NET_WM_STATE_MAXIMIZED_VERT;
1634 e.xclient.data.l[2] = _NET_WM_STATE_MAXIMIZED_HORZ;
1635 e.xclient.data.l[3] = 0l;
1636
1637 if (maximized) {
1638 SDL_DisplayID displayID = SDL_GetDisplayForWindow(window);
1639 SDL_Rect bounds;
1640
1641 SDL_zero(bounds);
1642 SDL_GetDisplayUsableBounds(displayID, &bounds);
1643
1644 data->expected.x = bounds.x + data->border_left;
1645 data->expected.y = bounds.y + data->border_top;
1646 data->expected.w = bounds.w - (data->border_left + data->border_right);
1647 data->expected.h = bounds.h - (data->border_top + data->border_bottom);
1648 } else {
1649 data->expected.x = window->floating.x;
1650 data->expected.y = window->floating.y;
1651 data->expected.w = window->floating.w;
1652 data->expected.h = window->floating.h;
1653 }
1654
1655 X11_XSendEvent(display, RootWindow(display, displaydata->screen), 0,
1656 SubstructureNotifyMask | SubstructureRedirectMask, &e);
1657 } else {
1658 X11_SetNetWMState(_this, data->xwindow, window->flags);
1659 }
1660 X11_XFlush(display);
1661
1662 return true;
1663}
1664
1665void X11_MaximizeWindow(SDL_VideoDevice *_this, SDL_Window *window)
1666{
1667 if (window->internal->pending_operation & (X11_PENDING_OP_FULLSCREEN | X11_PENDING_OP_MINIMIZE)) {
1668 SDL_SyncWindow(window);
1669 }
1670
1671 if (window->flags & SDL_WINDOW_FULLSCREEN) {
1672 // If fullscreen, just toggle the restored state.
1673 window->internal->window_was_maximized = true;
1674 return;
1675 }
1676
1677 if (!(window->flags & SDL_WINDOW_MINIMIZED)) {
1678 window->internal->pending_operation |= X11_PENDING_OP_MAXIMIZE;
1679 X11_SetWindowMaximized(_this, window, true);
1680 }
1681}
1682
1683void X11_MinimizeWindow(SDL_VideoDevice *_this, SDL_Window *window)
1684{
1685 SDL_WindowData *data = window->internal;
1686 SDL_DisplayData *displaydata = SDL_GetDisplayDriverDataForWindow(window);
1687 Display *display = data->videodata->display;
1688
1689 if (data->pending_operation & SDL_WINDOW_FULLSCREEN) {
1690 SDL_SyncWindow(window);
1691 }
1692
1693 data->pending_operation |= X11_PENDING_OP_MINIMIZE;
1694 if (!(window->flags & SDL_WINDOW_FULLSCREEN)) {
1695 data->window_was_maximized = !!(window->flags & SDL_WINDOW_MAXIMIZED);
1696 }
1697 X11_XIconifyWindow(display, data->xwindow, displaydata->screen);
1698 X11_XFlush(display);
1699}
1700
1701void X11_RestoreWindow(SDL_VideoDevice *_this, SDL_Window *window)
1702{
1703 if (window->internal->pending_operation & (X11_PENDING_OP_FULLSCREEN | X11_PENDING_OP_MAXIMIZE | X11_PENDING_OP_MINIMIZE)) {
1704 SDL_SyncWindow(window);
1705 }
1706
1707 if ((window->flags & SDL_WINDOW_FULLSCREEN) && !(window->flags & SDL_WINDOW_MINIMIZED)) {
1708 // If fullscreen and not minimized, just toggle the restored state.
1709 window->internal->window_was_maximized = false;
1710 return;
1711 }
1712
1713 if (window->flags & (SDL_WINDOW_MINIMIZED | SDL_WINDOW_MAXIMIZED) ||
1714 (window->internal->pending_operation & X11_PENDING_OP_MINIMIZE)) {
1715 window->internal->pending_operation |= X11_PENDING_OP_RESTORE;
1716 }
1717
1718 // If the window was minimized while maximized, restore as maximized.
1719 const bool maximize = !!(window->flags & SDL_WINDOW_MINIMIZED) && window->internal->window_was_maximized;
1720 X11_SetWindowMaximized(_this, window, maximize);
1721 X11_ShowWindow(_this, window);
1722 X11_SetWindowActive(_this, window);
1723}
1724
1725// This asks the Window Manager to handle fullscreen for us. This is the modern way.
1726static SDL_FullscreenResult X11_SetWindowFullscreenViaWM(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *_display, SDL_FullscreenOp fullscreen)
1727{
1728 CHECK_WINDOW_DATA(window);
1729 CHECK_DISPLAY_DATA(_display);
1730
1731 SDL_WindowData *data = window->internal;
1732 SDL_DisplayData *displaydata = _display->internal;
1733 Display *display = data->videodata->display;
1734 Atom _NET_WM_STATE = data->videodata->atoms._NET_WM_STATE;
1735 Atom _NET_WM_STATE_FULLSCREEN = data->videodata->atoms._NET_WM_STATE_FULLSCREEN;
1736
1737 if (X11_IsWindowMapped(_this, window)) {
1738 XEvent e;
1739
1740 // Flush any pending fullscreen events.
1741 if (data->pending_operation & (X11_PENDING_OP_FULLSCREEN | X11_PENDING_OP_MAXIMIZE | X11_PENDING_OP_MOVE)) {
1742 X11_SyncWindow(_this, window);
1743 }
1744
1745 if (!(window->flags & SDL_WINDOW_FULLSCREEN)) {
1746 if (fullscreen == SDL_FULLSCREEN_OP_UPDATE) {
1747 // Request was out of date; set -1 to signal the video core to undo a mode switch.
1748 return SDL_FULLSCREEN_FAILED;
1749 } else if (fullscreen == SDL_FULLSCREEN_OP_LEAVE) {
1750 // Nothing to do.
1751 return SDL_FULLSCREEN_SUCCEEDED;
1752 }
1753 }
1754
1755 if (fullscreen && !(window->flags & SDL_WINDOW_RESIZABLE)) {
1756 /* Compiz refuses fullscreen toggle if we're not resizable, so update the hints so we
1757 can be resized to the fullscreen resolution (or reset so we're not resizable again) */
1758 XSizeHints *sizehints = X11_XAllocSizeHints();
1759 long flags = 0;
1760 X11_XGetWMNormalHints(display, data->xwindow, sizehints, &flags);
1761 // we are going fullscreen so turn the flags off
1762 sizehints->flags &= ~(PMinSize | PMaxSize | PAspect);
1763 X11_XSetWMNormalHints(display, data->xwindow, sizehints);
1764 X11_XFree(sizehints);
1765 }
1766
1767 SDL_zero(e);
1768 e.xany.type = ClientMessage;
1769 e.xclient.message_type = _NET_WM_STATE;
1770 e.xclient.format = 32;
1771 e.xclient.window = data->xwindow;
1772 e.xclient.data.l[0] =
1773 fullscreen ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
1774 e.xclient.data.l[1] = _NET_WM_STATE_FULLSCREEN;
1775 e.xclient.data.l[3] = 0l;
1776
1777 X11_XSendEvent(display, RootWindow(display, displaydata->screen), 0,
1778 SubstructureNotifyMask | SubstructureRedirectMask, &e);
1779
1780 if (!!(window->flags & SDL_WINDOW_FULLSCREEN) != fullscreen) {
1781 data->pending_operation |= X11_PENDING_OP_FULLSCREEN;
1782 }
1783
1784 // Set the position so the window will be on the target display
1785 if (fullscreen) {
1786 SDL_DisplayID current = SDL_GetDisplayForWindowPosition(window);
1787 SDL_copyp(&data->requested_fullscreen_mode, &window->current_fullscreen_mode);
1788 if (fullscreen != !!(window->flags & SDL_WINDOW_FULLSCREEN)) {
1789 data->window_was_maximized = !!(window->flags & SDL_WINDOW_MAXIMIZED);
1790 }
1791 data->expected.x = displaydata->x;
1792 data->expected.y = displaydata->y;
1793 data->expected.w = _display->current_mode->w;
1794 data->expected.h = _display->current_mode->h;
1795
1796 // Only move the window if it isn't fullscreen or already on the target display.
1797 if (!(window->flags & SDL_WINDOW_FULLSCREEN) || (!current || current != _display->id)) {
1798 X11_XMoveWindow(display, data->xwindow, displaydata->x, displaydata->y);
1799 data->pending_operation |= X11_PENDING_OP_MOVE;
1800 }
1801 } else {
1802 SDL_zero(data->requested_fullscreen_mode);
1803
1804 /* Fullscreen windows sometimes end up being marked maximized by
1805 * window managers. Force it back to how we expect it to be.
1806 */
1807 SDL_zero(e);
1808 e.xany.type = ClientMessage;
1809 e.xclient.message_type = _NET_WM_STATE;
1810 e.xclient.format = 32;
1811 e.xclient.window = data->xwindow;
1812 if (data->window_was_maximized) {
1813 e.xclient.data.l[0] = _NET_WM_STATE_ADD;
1814 } else {
1815 e.xclient.data.l[0] = _NET_WM_STATE_REMOVE;
1816 }
1817 e.xclient.data.l[1] = data->videodata->atoms._NET_WM_STATE_MAXIMIZED_VERT;
1818 e.xclient.data.l[2] = data->videodata->atoms._NET_WM_STATE_MAXIMIZED_HORZ;
1819 e.xclient.data.l[3] = 0l;
1820 X11_XSendEvent(display, RootWindow(display, displaydata->screen), 0,
1821 SubstructureNotifyMask | SubstructureRedirectMask, &e);
1822 }
1823 } else {
1824 SDL_WindowFlags flags;
1825
1826 flags = window->flags;
1827 if (fullscreen) {
1828 flags |= SDL_WINDOW_FULLSCREEN;
1829 } else {
1830 flags &= ~SDL_WINDOW_FULLSCREEN;
1831 }
1832 X11_SetNetWMState(_this, data->xwindow, flags);
1833 }
1834
1835 if (data->visual->class == DirectColor) {
1836 if (fullscreen) {
1837 X11_XInstallColormap(display, data->colormap);
1838 } else {
1839 X11_XUninstallColormap(display, data->colormap);
1840 }
1841 }
1842
1843 return SDL_FULLSCREEN_PENDING;
1844}
1845
1846SDL_FullscreenResult X11_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *_display, SDL_FullscreenOp fullscreen)
1847{
1848 return X11_SetWindowFullscreenViaWM(_this, window, _display, fullscreen);
1849}
1850
1851typedef struct
1852{
1853 unsigned char *data;
1854 int format, count;
1855 Atom type;
1856} SDL_x11Prop;
1857
1858/* Reads property
1859 Must call X11_XFree on results
1860 */
1861static void X11_ReadProperty(SDL_x11Prop *p, Display *disp, Window w, Atom prop)
1862{
1863 unsigned char *ret = NULL;
1864 Atom type;
1865 int fmt;
1866 unsigned long count;
1867 unsigned long bytes_left;
1868 int bytes_fetch = 0;
1869
1870 do {
1871 if (ret) {
1872 X11_XFree(ret);
1873 }
1874 X11_XGetWindowProperty(disp, w, prop, 0, bytes_fetch, False, AnyPropertyType, &type, &fmt, &count, &bytes_left, &ret);
1875 bytes_fetch += bytes_left;
1876 } while (bytes_left != 0);
1877
1878 p->data = ret;
1879 p->format = fmt;
1880 p->count = count;
1881 p->type = type;
1882}
1883
1884void *X11_GetWindowICCProfile(SDL_VideoDevice *_this, SDL_Window *window, size_t *size)
1885{
1886 SDL_WindowData *data = window->internal;
1887 Display *display = data->videodata->display;
1888 XWindowAttributes attributes;
1889 Atom icc_profile_atom;
1890 char icc_atom_string[sizeof("_ICC_PROFILE_") + 12];
1891 void *ret_icc_profile_data = NULL;
1892 CARD8 *icc_profile_data;
1893 int real_format;
1894 unsigned long real_nitems;
1895 SDL_x11Prop atomProp;
1896
1897 X11_XGetWindowAttributes(display, data->xwindow, &attributes);
1898 if (X11_XScreenNumberOfScreen(attributes.screen) > 0) {
1899 (void)SDL_snprintf(icc_atom_string, sizeof("_ICC_PROFILE_") + 12, "%s%d", "_ICC_PROFILE_", X11_XScreenNumberOfScreen(attributes.screen));
1900 } else {
1901 SDL_strlcpy(icc_atom_string, "_ICC_PROFILE", sizeof("_ICC_PROFILE"));
1902 }
1903 X11_XGetWindowAttributes(display, RootWindowOfScreen(attributes.screen), &attributes);
1904
1905 icc_profile_atom = X11_XInternAtom(display, icc_atom_string, True);
1906 if (icc_profile_atom == None) {
1907 SDL_SetError("Screen is not calibrated.");
1908 return NULL;
1909 }
1910
1911 X11_ReadProperty(&atomProp, display, RootWindowOfScreen(attributes.screen), icc_profile_atom);
1912 real_format = atomProp.format;
1913 real_nitems = atomProp.count;
1914 icc_profile_data = atomProp.data;
1915 if (real_format == None) {
1916 SDL_SetError("Screen is not calibrated.");
1917 return NULL;
1918 }
1919
1920 ret_icc_profile_data = SDL_malloc(real_nitems);
1921 if (!ret_icc_profile_data) {
1922 return NULL;
1923 }
1924
1925 SDL_memcpy(ret_icc_profile_data, icc_profile_data, real_nitems);
1926 *size = real_nitems;
1927 X11_XFree(icc_profile_data);
1928
1929 return ret_icc_profile_data;
1930}
1931
1932bool X11_SetWindowMouseGrab(SDL_VideoDevice *_this, SDL_Window *window, bool grabbed)
1933{
1934 SDL_WindowData *data = window->internal;
1935 Display *display;
1936
1937 if (!data) {
1938 return SDL_SetError("Invalid window data");
1939 }
1940 data->mouse_grabbed = false;
1941
1942 display = data->videodata->display;
1943
1944 if (grabbed) {
1945 /* If the window is unmapped, XGrab calls return GrabNotViewable,
1946 so when we get a MapNotify later, we'll try to update the grab as
1947 appropriate. */
1948 if (window->flags & SDL_WINDOW_HIDDEN) {
1949 return true;
1950 }
1951
1952 /* If XInput2 is enabled, it will grab the pointer on button presses,
1953 * which results in XGrabPointer returning AlreadyGrabbed. If buttons
1954 * are currently pressed, clear any existing grabs before attempting
1955 * the confinement grab.
1956 */
1957 if (data->xinput2_mouse_enabled && SDL_GetMouseState(NULL, NULL)) {
1958 X11_XUngrabPointer(display, CurrentTime);
1959 }
1960
1961 // Try to grab the mouse
1962 if (!data->videodata->broken_pointer_grab) {
1963 const unsigned int mask = ButtonPressMask | ButtonReleaseMask | PointerMotionMask | FocusChangeMask;
1964 int attempts;
1965 int result = 0;
1966
1967 // Try for up to 5000ms (5s) to grab. If it still fails, stop trying.
1968 for (attempts = 0; attempts < 100; attempts++) {
1969 result = X11_XGrabPointer(display, data->xwindow, False, mask, GrabModeAsync,
1970 GrabModeAsync, data->xwindow, None, CurrentTime);
1971 if (result == GrabSuccess) {
1972 data->mouse_grabbed = true;
1973 break;
1974 }
1975 SDL_Delay(50);
1976 }
1977
1978 if (result != GrabSuccess) {
1979 data->videodata->broken_pointer_grab = true; // don't try again.
1980 }
1981 }
1982
1983 X11_Xinput2GrabTouch(_this, window);
1984
1985 // Raise the window if we grab the mouse
1986 X11_XRaiseWindow(display, data->xwindow);
1987 } else {
1988 X11_XUngrabPointer(display, CurrentTime);
1989
1990 X11_Xinput2UngrabTouch(_this, window);
1991 }
1992 X11_XSync(display, False);
1993
1994 if (!data->videodata->broken_pointer_grab) {
1995 return true;
1996 } else {
1997 return SDL_SetError("The X server refused to let us grab the mouse. You might experience input bugs.");
1998 }
1999}
2000
2001bool X11_SetWindowKeyboardGrab(SDL_VideoDevice *_this, SDL_Window *window, bool grabbed)
2002{
2003 SDL_WindowData *data = window->internal;
2004 Display *display;
2005
2006 if (!data) {
2007 return SDL_SetError("Invalid window data");
2008 }
2009
2010 display = data->videodata->display;
2011
2012 if (grabbed) {
2013 /* If the window is unmapped, XGrab calls return GrabNotViewable,
2014 so when we get a MapNotify later, we'll try to update the grab as
2015 appropriate. */
2016 if (window->flags & SDL_WINDOW_HIDDEN) {
2017 return true;
2018 }
2019
2020 X11_XGrabKeyboard(display, data->xwindow, True, GrabModeAsync,
2021 GrabModeAsync, CurrentTime);
2022 } else {
2023 X11_XUngrabKeyboard(display, CurrentTime);
2024 }
2025 X11_XSync(display, False);
2026
2027 return true;
2028}
2029
2030void X11_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window)
2031{
2032 SDL_WindowData *data = window->internal;
2033
2034 if (data) {
2035 SDL_VideoData *videodata = data->videodata;
2036 Display *display = videodata->display;
2037 int numwindows = videodata->numwindows;
2038 SDL_WindowData **windowlist = videodata->windowlist;
2039 int i;
2040
2041 if (windowlist) {
2042 for (i = 0; i < numwindows; ++i) {
2043 if (windowlist[i] && (windowlist[i]->window == window)) {
2044 windowlist[i] = windowlist[numwindows - 1];
2045 windowlist[numwindows - 1] = NULL;
2046 videodata->numwindows--;
2047 break;
2048 }
2049 }
2050 }
2051#ifdef X_HAVE_UTF8_STRING
2052 if (data->ic) {
2053 X11_XDestroyIC(data->ic);
2054 SDL_free(data->preedit_text);
2055 SDL_free(data->preedit_feedback);
2056 }
2057#endif
2058
2059#ifdef SDL_VIDEO_DRIVER_X11_XSYNC
2060 X11_TermResizeSync(window);
2061#endif /* SDL_VIDEO_DRIVER_X11_XSYNC */
2062
2063 if (!(window->flags & SDL_WINDOW_EXTERNAL)) {
2064 X11_XDestroyWindow(display, data->xwindow);
2065 X11_XFlush(display);
2066 }
2067 SDL_free(data);
2068
2069#ifdef SDL_VIDEO_DRIVER_X11_XFIXES
2070 // If the pointer barriers are active for this, deactivate it.
2071 if (videodata->active_cursor_confined_window == window) {
2072 X11_DestroyPointerBarrier(_this, window);
2073 }
2074#endif // SDL_VIDEO_DRIVER_X11_XFIXES
2075 }
2076 window->internal = NULL;
2077}
2078
2079bool X11_SetWindowHitTest(SDL_Window *window, bool enabled)
2080{
2081 return true; // just succeed, the real work is done elsewhere.
2082}
2083
2084void X11_AcceptDragAndDrop(SDL_Window *window, bool accept)
2085{
2086 SDL_WindowData *data = window->internal;
2087 Display *display = data->videodata->display;
2088 Atom XdndAware = data->videodata->atoms.XdndAware;
2089
2090 if (accept) {
2091 Atom xdnd_version = 5;
2092 X11_XChangeProperty(display, data->xwindow, XdndAware, XA_ATOM, 32,
2093 PropModeReplace, (unsigned char *)&xdnd_version, 1);
2094 } else {
2095 X11_XDeleteProperty(display, data->xwindow, XdndAware);
2096 }
2097}
2098
2099bool X11_FlashWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOperation operation)
2100{
2101 SDL_WindowData *data = window->internal;
2102 Display *display = data->videodata->display;
2103 XWMHints *wmhints;
2104
2105 wmhints = X11_XGetWMHints(display, data->xwindow);
2106 if (!wmhints) {
2107 return SDL_SetError("Couldn't get WM hints");
2108 }
2109
2110 wmhints->flags &= ~XUrgencyHint;
2111 data->flashing_window = false;
2112 data->flash_cancel_time = 0;
2113
2114 switch (operation) {
2115 case SDL_FLASH_CANCEL:
2116 // Taken care of above
2117 break;
2118 case SDL_FLASH_BRIEFLY:
2119 if (!(window->flags & SDL_WINDOW_INPUT_FOCUS)) {
2120 wmhints->flags |= XUrgencyHint;
2121 data->flashing_window = true;
2122 // On Ubuntu 21.04 this causes a dialog to pop up, so leave it up for a full second so users can see it
2123 data->flash_cancel_time = SDL_GetTicks() + 1000;
2124 }
2125 break;
2126 case SDL_FLASH_UNTIL_FOCUSED:
2127 if (!(window->flags & SDL_WINDOW_INPUT_FOCUS)) {
2128 wmhints->flags |= XUrgencyHint;
2129 data->flashing_window = true;
2130 }
2131 break;
2132 default:
2133 break;
2134 }
2135
2136 X11_XSetWMHints(display, data->xwindow, wmhints);
2137 X11_XFree(wmhints);
2138 return true;
2139}
2140
2141bool SDL_X11_SetWindowTitle(Display *display, Window xwindow, char *title)
2142{
2143 Atom _NET_WM_NAME = X11_XInternAtom(display, "_NET_WM_NAME", False);
2144 XTextProperty titleprop;
2145 int conv = X11_XmbTextListToTextProperty(display, &title, 1, XTextStyle, &titleprop);
2146 Status status;
2147
2148 if (X11_XSupportsLocale() != True) {
2149 return SDL_SetError("Current locale not supported by X server, cannot continue.");
2150 }
2151
2152 if (conv == 0) {
2153 X11_XSetTextProperty(display, xwindow, &titleprop, XA_WM_NAME);
2154 X11_XFree(titleprop.value);
2155 // we know this can't be a locale error as we checked X locale validity
2156 } else if (conv < 0) {
2157 return SDL_OutOfMemory();
2158 } else { // conv > 0
2159 SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "%d characters were not convertible to the current locale!", conv);
2160 return true;
2161 }
2162
2163#ifdef X_HAVE_UTF8_STRING
2164 status = X11_Xutf8TextListToTextProperty(display, &title, 1, XUTF8StringStyle, &titleprop);
2165 if (status == Success) {
2166 X11_XSetTextProperty(display, xwindow, &titleprop, _NET_WM_NAME);
2167 X11_XFree(titleprop.value);
2168 } else {
2169 return SDL_SetError("Failed to convert title to UTF8! Bad encoding, or bad Xorg encoding? Window title: «%s»", title);
2170 }
2171#endif
2172
2173 X11_XFlush(display);
2174 return true;
2175}
2176
2177void X11_ShowWindowSystemMenu(SDL_Window *window, int x, int y)
2178{
2179 SDL_WindowData *data = window->internal;
2180 SDL_DisplayData *displaydata = SDL_GetDisplayDriverDataForWindow(window);
2181 Display *display = data->videodata->display;
2182 Window root = RootWindow(display, displaydata->screen);
2183 XClientMessageEvent e;
2184 Window childReturn;
2185 int wx, wy;
2186
2187 SDL_zero(e);
2188 X11_XTranslateCoordinates(display, data->xwindow, root, x, y, &wx, &wy, &childReturn);
2189
2190 e.type = ClientMessage;
2191 e.window = data->xwindow;
2192 e.message_type = X11_XInternAtom(display, "_GTK_SHOW_WINDOW_MENU", 0);
2193 e.data.l[0] = 0; // GTK device ID (unused)
2194 e.data.l[1] = wx; // X coordinate relative to root
2195 e.data.l[2] = wy; // Y coordinate relative to root
2196 e.format = 32;
2197
2198 X11_XSendEvent(display, root, False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent *)&e);
2199 X11_XFlush(display);
2200}
2201
2202bool X11_SyncWindow(SDL_VideoDevice *_this, SDL_Window *window)
2203{
2204 const Uint64 current_time = SDL_GetTicksNS();
2205 Uint64 timeout = 0;
2206
2207 // Allow time for any pending mode switches to complete.
2208 for (int i = 0; i < _this->num_displays; ++i) {
2209 if (_this->displays[i]->internal->mode_switch_deadline_ns &&
2210 current_time < _this->displays[i]->internal->mode_switch_deadline_ns) {
2211 timeout = SDL_max(_this->displays[i]->internal->mode_switch_deadline_ns - current_time, timeout);
2212 }
2213 }
2214
2215 /* 100ms is fine for most cases, but, for some reason, maximizing
2216 * a window can take a very long time.
2217 */
2218 timeout += window->internal->pending_operation & X11_PENDING_OP_MAXIMIZE ? SDL_MS_TO_NS(1000) : SDL_MS_TO_NS(100);
2219
2220 return X11_SyncWindowTimeout(_this, window, timeout);
2221}
2222
2223bool X11_SetWindowFocusable(SDL_VideoDevice *_this, SDL_Window *window, bool focusable)
2224{
2225 SDL_WindowData *data = window->internal;
2226 Display *display = data->videodata->display;
2227 XWMHints *wmhints;
2228
2229 wmhints = X11_XGetWMHints(display, data->xwindow);
2230 if (!wmhints) {
2231 return SDL_SetError("Couldn't get WM hints");
2232 }
2233
2234 wmhints->input = focusable ? True : False;
2235 wmhints->flags |= InputHint;
2236
2237 X11_XSetWMHints(display, data->xwindow, wmhints);
2238 X11_XFree(wmhints);
2239
2240 return true;
2241}
2242
2243#endif // SDL_VIDEO_DRIVER_X11