summaryrefslogtreecommitdiff
path: root/contrib/SDL-3.2.8/src/video/x11/SDL_x11modes.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_x11modes.c
Initial commit
Diffstat (limited to 'contrib/SDL-3.2.8/src/video/x11/SDL_x11modes.c')
-rw-r--r--contrib/SDL-3.2.8/src/video/x11/SDL_x11modes.c1051
1 files changed, 1051 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/src/video/x11/SDL_x11modes.c b/contrib/SDL-3.2.8/src/video/x11/SDL_x11modes.c
new file mode 100644
index 0000000..e17a2d1
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/x11/SDL_x11modes.c
@@ -0,0 +1,1051 @@
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_x11video.h"
26#include "SDL_x11settings.h"
27#include "edid.h"
28#include "../../events/SDL_displayevents_c.h"
29
30// #define X11MODES_DEBUG
31
32/* Timeout and revert mode switches if the timespan has elapsed without the window becoming fullscreen.
33 * 5 seconds seems good from testing.
34 */
35#define MODE_SWITCH_TIMEOUT_NS SDL_NS_PER_SECOND * 5
36
37/* I'm becoming more and more convinced that the application should never
38 * use XRandR, and it's the window manager's responsibility to track and
39 * manage display modes for fullscreen windows. Right now XRandR is completely
40 * broken with respect to window manager behavior on every window manager that
41 * I can find. For example, on Unity 3D if you show a fullscreen window while
42 * the resolution is changing (within ~250 ms) your window will retain the
43 * fullscreen state hint but be decorated and windowed.
44 *
45 * However, many people swear by it, so let them swear at it. :)
46 */
47// #define XRANDR_DISABLED_BY_DEFAULT
48
49static float GetGlobalContentScale(SDL_VideoDevice *_this)
50{
51 static double scale_factor = 0.0;
52
53 if (scale_factor <= 0.0) {
54
55 // First use the forced scaling factor specified by the app/user
56 const char *hint = SDL_GetHint(SDL_HINT_VIDEO_X11_SCALING_FACTOR);
57 if (hint && *hint) {
58 double value = SDL_atof(hint);
59 if (value >= 1.0f && value <= 10.0f) {
60 scale_factor = value;
61 }
62 }
63
64 // If that failed, try "Xft.dpi" from the XResourcesDatabase...
65 if (scale_factor <= 0.0)
66 {
67 SDL_VideoData *data = _this->internal;
68 Display *display = data->display;
69 char *resource_manager;
70 XrmDatabase db;
71 XrmValue value;
72 char *type;
73
74 X11_XrmInitialize();
75
76 resource_manager = X11_XResourceManagerString(display);
77 if (resource_manager) {
78 db = X11_XrmGetStringDatabase(resource_manager);
79
80 // Get the value of Xft.dpi from the Database
81 if (X11_XrmGetResource(db, "Xft.dpi", "String", &type, &value)) {
82 if (value.addr && type && SDL_strcmp(type, "String") == 0) {
83 int dpi = SDL_atoi(value.addr);
84 scale_factor = dpi / 96.0;
85 }
86 }
87 X11_XrmDestroyDatabase(db);
88 }
89 }
90
91 // If that failed, try the XSETTINGS keys...
92 if (scale_factor <= 0.0) {
93 scale_factor = X11_GetXsettingsIntKey(_this, "Gdk/WindowScalingFactor", -1);
94
95 // The Xft/DPI key is stored in increments of 1024th
96 if (scale_factor <= 0.0) {
97 int dpi = X11_GetXsettingsIntKey(_this, "Xft/DPI", -1);
98 if (dpi > 0) {
99 scale_factor = (double) dpi / 1024.0;
100 scale_factor /= 96.0;
101 }
102 }
103 }
104
105 // If that failed, try the GDK_SCALE envvar...
106 if (scale_factor <= 0.0) {
107 const char *scale_str = SDL_getenv("GDK_SCALE");
108 if (scale_str) {
109 scale_factor = SDL_atoi(scale_str);
110 }
111 }
112
113 // Nothing or a bad value, just fall back to 1.0
114 if (scale_factor <= 0.0) {
115 scale_factor = 1.0;
116 }
117 }
118
119 return (float)scale_factor;
120}
121
122static bool get_visualinfo(Display *display, int screen, XVisualInfo *vinfo)
123{
124 const char *visual_id = SDL_GetHint(SDL_HINT_VIDEO_X11_VISUALID);
125 int depth;
126
127 // Look for an exact visual, if requested
128 if (visual_id && *visual_id) {
129 XVisualInfo *vi, template;
130 int nvis;
131
132 SDL_zero(template);
133 template.visualid = SDL_strtol(visual_id, NULL, 0);
134 vi = X11_XGetVisualInfo(display, VisualIDMask, &template, &nvis);
135 if (vi) {
136 *vinfo = *vi;
137 X11_XFree(vi);
138 return true;
139 }
140 }
141
142 depth = DefaultDepth(display, screen);
143 if ((X11_UseDirectColorVisuals() &&
144 X11_XMatchVisualInfo(display, screen, depth, DirectColor, vinfo)) ||
145 X11_XMatchVisualInfo(display, screen, depth, TrueColor, vinfo) ||
146 X11_XMatchVisualInfo(display, screen, depth, PseudoColor, vinfo) ||
147 X11_XMatchVisualInfo(display, screen, depth, StaticColor, vinfo)) {
148 return true;
149 }
150 return false;
151}
152
153bool X11_GetVisualInfoFromVisual(Display *display, Visual *visual, XVisualInfo *vinfo)
154{
155 XVisualInfo *vi;
156 int nvis;
157
158 vinfo->visualid = X11_XVisualIDFromVisual(visual);
159 vi = X11_XGetVisualInfo(display, VisualIDMask, vinfo, &nvis);
160 if (vi) {
161 *vinfo = *vi;
162 X11_XFree(vi);
163 return true;
164 }
165 return false;
166}
167
168SDL_PixelFormat X11_GetPixelFormatFromVisualInfo(Display *display, XVisualInfo *vinfo)
169{
170 if (vinfo->class == DirectColor || vinfo->class == TrueColor) {
171 int bpp;
172 Uint32 Rmask, Gmask, Bmask, Amask;
173
174 Rmask = vinfo->visual->red_mask;
175 Gmask = vinfo->visual->green_mask;
176 Bmask = vinfo->visual->blue_mask;
177 if (vinfo->depth == 32) {
178 Amask = (0xFFFFFFFF & ~(Rmask | Gmask | Bmask));
179 } else {
180 Amask = 0;
181 }
182
183 bpp = vinfo->depth;
184 if (bpp == 24) {
185 int i, n;
186 XPixmapFormatValues *p = X11_XListPixmapFormats(display, &n);
187 if (p) {
188 for (i = 0; i < n; ++i) {
189 if (p[i].depth == 24) {
190 bpp = p[i].bits_per_pixel;
191 break;
192 }
193 }
194 X11_XFree(p);
195 }
196 }
197
198 return SDL_GetPixelFormatForMasks(bpp, Rmask, Gmask, Bmask, Amask);
199 }
200
201 if (vinfo->class == PseudoColor || vinfo->class == StaticColor) {
202 switch (vinfo->depth) {
203 case 8:
204 return SDL_PIXELFORMAT_INDEX8;
205 case 4:
206 if (BitmapBitOrder(display) == LSBFirst) {
207 return SDL_PIXELFORMAT_INDEX4LSB;
208 } else {
209 return SDL_PIXELFORMAT_INDEX4MSB;
210 }
211 // break; -Wunreachable-code-break
212 case 1:
213 if (BitmapBitOrder(display) == LSBFirst) {
214 return SDL_PIXELFORMAT_INDEX1LSB;
215 } else {
216 return SDL_PIXELFORMAT_INDEX1MSB;
217 }
218 // break; -Wunreachable-code-break
219 }
220 }
221
222 return SDL_PIXELFORMAT_UNKNOWN;
223}
224
225#ifdef SDL_VIDEO_DRIVER_X11_XRANDR
226static bool CheckXRandR(Display *display, int *major, int *minor)
227{
228 // Default the extension not available
229 *major = *minor = 0;
230
231 // Allow environment override
232#ifdef XRANDR_DISABLED_BY_DEFAULT
233 if (!SDL_GetHintBoolean(SDL_HINT_VIDEO_X11_XRANDR, false)) {
234#ifdef X11MODES_DEBUG
235 printf("XRandR disabled by default due to window manager issues\n");
236#endif
237 return false;
238 }
239#else
240 if (!SDL_GetHintBoolean(SDL_HINT_VIDEO_X11_XRANDR, true)) {
241#ifdef X11MODES_DEBUG
242 printf("XRandR disabled due to hint\n");
243#endif
244 return false;
245 }
246#endif // XRANDR_DISABLED_BY_DEFAULT
247
248 if (!SDL_X11_HAVE_XRANDR) {
249#ifdef X11MODES_DEBUG
250 printf("XRandR support not available\n");
251#endif
252 return false;
253 }
254
255 // Query the extension version
256 *major = 1;
257 *minor = 3; // we want 1.3
258 if (!X11_XRRQueryVersion(display, major, minor)) {
259#ifdef X11MODES_DEBUG
260 printf("XRandR not active on the display\n");
261#endif
262 *major = *minor = 0;
263 return false;
264 }
265#ifdef X11MODES_DEBUG
266 printf("XRandR available at version %d.%d!\n", *major, *minor);
267#endif
268 return true;
269}
270
271#define XRANDR_ROTATION_LEFT (1 << 1)
272#define XRANDR_ROTATION_RIGHT (1 << 3)
273
274static void CalculateXRandRRefreshRate(const XRRModeInfo *info, int *numerator, int *denominator)
275{
276 unsigned int vTotal = info->vTotal;
277
278 if (info->modeFlags & RR_DoubleScan) {
279 // doublescan doubles the number of lines
280 vTotal *= 2;
281 }
282
283 if (info->modeFlags & RR_Interlace) {
284 // interlace splits the frame into two fields
285 // the field rate is what is typically reported by monitors
286 vTotal /= 2;
287 }
288
289 if (info->hTotal && vTotal) {
290 *numerator = info->dotClock;
291 *denominator = (info->hTotal * vTotal);
292 } else {
293 *numerator = 0;
294 *denominator = 0;
295 }
296}
297
298static bool SetXRandRModeInfo(Display *display, XRRScreenResources *res, RRCrtc crtc,
299 RRMode modeID, SDL_DisplayMode *mode)
300{
301 int i;
302 for (i = 0; i < res->nmode; ++i) {
303 const XRRModeInfo *info = &res->modes[i];
304 if (info->id == modeID) {
305 XRRCrtcInfo *crtcinfo;
306 Rotation rotation = 0;
307 XFixed scale_w = 0x10000, scale_h = 0x10000;
308 XRRCrtcTransformAttributes *attr;
309
310 crtcinfo = X11_XRRGetCrtcInfo(display, res, crtc);
311 if (crtcinfo) {
312 rotation = crtcinfo->rotation;
313 X11_XRRFreeCrtcInfo(crtcinfo);
314 }
315 if (X11_XRRGetCrtcTransform(display, crtc, &attr) && attr) {
316 scale_w = attr->currentTransform.matrix[0][0];
317 scale_h = attr->currentTransform.matrix[1][1];
318 X11_XFree(attr);
319 }
320
321 if (rotation & (XRANDR_ROTATION_LEFT | XRANDR_ROTATION_RIGHT)) {
322 mode->w = (info->height * scale_w + 0xffff) >> 16;
323 mode->h = (info->width * scale_h + 0xffff) >> 16;
324 } else {
325 mode->w = (info->width * scale_w + 0xffff) >> 16;
326 mode->h = (info->height * scale_h + 0xffff) >> 16;
327 }
328 CalculateXRandRRefreshRate(info, &mode->refresh_rate_numerator, &mode->refresh_rate_denominator);
329 mode->internal->xrandr_mode = modeID;
330#ifdef X11MODES_DEBUG
331 printf("XRandR mode %d: %dx%d@%d/%dHz\n", (int)modeID,
332 mode->screen_w, mode->screen_h, mode->refresh_rate_numerator, mode->refresh_rate_denominator);
333#endif
334 return true;
335 }
336 }
337 return false;
338}
339
340static void SetXRandRDisplayName(Display *dpy, Atom EDID, char *name, const size_t namelen, RROutput output, const unsigned long widthmm, const unsigned long heightmm)
341{
342 // See if we can get the EDID data for the real monitor name
343 int inches;
344 int nprop;
345 Atom *props = X11_XRRListOutputProperties(dpy, output, &nprop);
346 int i;
347
348 for (i = 0; i < nprop; ++i) {
349 unsigned char *prop;
350 int actual_format;
351 unsigned long nitems, bytes_after;
352 Atom actual_type;
353
354 if (props[i] == EDID) {
355 if (X11_XRRGetOutputProperty(dpy, output, props[i], 0, 100, False,
356 False, AnyPropertyType, &actual_type,
357 &actual_format, &nitems, &bytes_after,
358 &prop) == Success) {
359 MonitorInfo *info = decode_edid(prop);
360 if (info) {
361#ifdef X11MODES_DEBUG
362 printf("Found EDID data for %s\n", name);
363 dump_monitor_info(info);
364#endif
365 SDL_strlcpy(name, info->dsc_product_name, namelen);
366 SDL_free(info);
367 }
368 X11_XFree(prop);
369 }
370 break;
371 }
372 }
373
374 if (props) {
375 X11_XFree(props);
376 }
377
378 inches = (int)((SDL_sqrtf(widthmm * widthmm + heightmm * heightmm) / 25.4f) + 0.5f);
379 if (*name && inches) {
380 const size_t len = SDL_strlen(name);
381 (void)SDL_snprintf(&name[len], namelen - len, " %d\"", inches);
382 }
383
384#ifdef X11MODES_DEBUG
385 printf("Display name: %s\n", name);
386#endif
387}
388
389static bool X11_FillXRandRDisplayInfo(SDL_VideoDevice *_this, Display *dpy, int screen, RROutput outputid, XRRScreenResources *res, SDL_VideoDisplay *display, char *display_name, size_t display_name_size)
390{
391 Atom EDID = X11_XInternAtom(dpy, "EDID", False);
392 XRROutputInfo *output_info;
393 int display_x, display_y;
394 unsigned long display_mm_width, display_mm_height;
395 SDL_DisplayData *displaydata;
396 SDL_DisplayMode mode;
397 SDL_DisplayModeData *modedata;
398 RRMode modeID;
399 RRCrtc output_crtc;
400 XRRCrtcInfo *crtc;
401 XVisualInfo vinfo;
402 Uint32 pixelformat;
403 XPixmapFormatValues *pixmapformats;
404 int scanline_pad;
405 int i, n;
406
407 if (!display || !display_name) {
408 return false; // invalid parameters
409 }
410
411 if (!get_visualinfo(dpy, screen, &vinfo)) {
412 return false; // uh, skip this screen?
413 }
414
415 pixelformat = X11_GetPixelFormatFromVisualInfo(dpy, &vinfo);
416 if (SDL_ISPIXELFORMAT_INDEXED(pixelformat)) {
417 return false; // Palettized video modes are no longer supported, ignore this one.
418 }
419
420 scanline_pad = SDL_BYTESPERPIXEL(pixelformat) * 8;
421 pixmapformats = X11_XListPixmapFormats(dpy, &n);
422 if (pixmapformats) {
423 for (i = 0; i < n; i++) {
424 if (pixmapformats[i].depth == vinfo.depth) {
425 scanline_pad = pixmapformats[i].scanline_pad;
426 break;
427 }
428 }
429 X11_XFree(pixmapformats);
430 }
431
432 output_info = X11_XRRGetOutputInfo(dpy, res, outputid);
433 if (!output_info || !output_info->crtc || output_info->connection == RR_Disconnected) {
434 X11_XRRFreeOutputInfo(output_info);
435 return false; // ignore this one.
436 }
437
438 SDL_strlcpy(display_name, output_info->name, display_name_size);
439 display_mm_width = output_info->mm_width;
440 display_mm_height = output_info->mm_height;
441 output_crtc = output_info->crtc;
442 X11_XRRFreeOutputInfo(output_info);
443
444 crtc = X11_XRRGetCrtcInfo(dpy, res, output_crtc);
445 if (!crtc) {
446 return false; // oh well, ignore it.
447 }
448
449 SDL_zero(mode);
450 modeID = crtc->mode;
451 mode.w = crtc->width;
452 mode.h = crtc->height;
453 mode.format = pixelformat;
454
455 display_x = crtc->x;
456 display_y = crtc->y;
457
458 X11_XRRFreeCrtcInfo(crtc);
459
460 displaydata = (SDL_DisplayData *)SDL_calloc(1, sizeof(*displaydata));
461 if (!displaydata) {
462 return false;
463 }
464
465 modedata = (SDL_DisplayModeData *)SDL_calloc(1, sizeof(SDL_DisplayModeData));
466 if (!modedata) {
467 SDL_free(displaydata);
468 return false;
469 }
470
471 modedata->xrandr_mode = modeID;
472 mode.internal = modedata;
473
474 displaydata->screen = screen;
475 displaydata->visual = vinfo.visual;
476 displaydata->depth = vinfo.depth;
477 displaydata->scanline_pad = scanline_pad;
478 displaydata->x = display_x;
479 displaydata->y = display_y;
480 displaydata->use_xrandr = true;
481 displaydata->xrandr_output = outputid;
482 SDL_strlcpy(displaydata->connector_name, display_name, sizeof(displaydata->connector_name));
483
484 SetXRandRModeInfo(dpy, res, output_crtc, modeID, &mode);
485 SetXRandRDisplayName(dpy, EDID, display_name, display_name_size, outputid, display_mm_width, display_mm_height);
486
487 SDL_zero(*display);
488 if (*display_name) {
489 display->name = display_name;
490 }
491 display->desktop_mode = mode;
492 display->content_scale = GetGlobalContentScale(_this);
493 display->internal = displaydata;
494
495 return true;
496}
497
498static bool X11_AddXRandRDisplay(SDL_VideoDevice *_this, Display *dpy, int screen, RROutput outputid, XRRScreenResources *res, bool send_event)
499{
500 SDL_VideoDisplay display;
501 char display_name[128];
502
503 if (!X11_FillXRandRDisplayInfo(_this, dpy, screen, outputid, res, &display, display_name, sizeof(display_name))) {
504 return true; // failed to query data, skip this display
505 }
506
507 if (SDL_AddVideoDisplay(&display, send_event) == 0) {
508 return false;
509 }
510
511 return true;
512}
513
514
515static bool X11_UpdateXRandRDisplay(SDL_VideoDevice *_this, Display *dpy, int screen, RROutput outputid, XRRScreenResources *res, SDL_VideoDisplay *existing_display)
516{
517 SDL_VideoDisplay display;
518 char display_name[128];
519
520 if (!X11_FillXRandRDisplayInfo(_this, dpy, screen, outputid, res, &display, display_name, sizeof(display_name))) {
521 return false; // failed to query current display state
522 }
523
524 // update mode - this call takes ownership of display.desktop_mode.internal
525 SDL_SetDesktopDisplayMode(existing_display, &display.desktop_mode);
526
527 // update bounds
528 if (existing_display->internal->x != display.internal->x ||
529 existing_display->internal->y != display.internal->y) {
530 existing_display->internal->x = display.internal->x;
531 existing_display->internal->y = display.internal->y;
532 SDL_SendDisplayEvent(existing_display, SDL_EVENT_DISPLAY_MOVED, 0, 0);
533 }
534
535 // update scale
536 SDL_SetDisplayContentScale(existing_display, display.content_scale);
537
538 // SDL_DisplayData is updated piece-meal above, free our local copy of this data
539 SDL_free( display.internal );
540
541 return true;
542}
543
544static XRRScreenResources *X11_GetScreenResources(Display *dpy, int screen)
545{
546 XRRScreenResources *res = X11_XRRGetScreenResourcesCurrent(dpy, RootWindow(dpy, screen));
547 if (!res || res->noutput == 0) {
548 if (res) {
549 X11_XRRFreeScreenResources(res);
550 }
551 res = X11_XRRGetScreenResources(dpy, RootWindow(dpy, screen));
552 }
553 return res;
554}
555
556static void X11_CheckDisplaysMoved(SDL_VideoDevice *_this, Display *dpy)
557{
558 const int screen = DefaultScreen(dpy);
559 XRRScreenResources *res = X11_GetScreenResources(dpy, screen);
560 if (!res) {
561 return;
562 }
563
564 SDL_DisplayID *displays = SDL_GetDisplays(NULL);
565 if (displays) {
566 for (int i = 0; displays[i]; ++i) {
567 SDL_VideoDisplay *display = SDL_GetVideoDisplay(displays[i]);
568 const SDL_DisplayData *displaydata = display->internal;
569 X11_UpdateXRandRDisplay(_this, dpy, screen, displaydata->xrandr_output, res, display);
570 }
571 SDL_free(displays);
572 }
573 X11_XRRFreeScreenResources(res);
574}
575
576static void X11_HandleXRandROutputChange(SDL_VideoDevice *_this, const XRROutputChangeNotifyEvent *ev)
577{
578 SDL_DisplayID *displays;
579 SDL_VideoDisplay *display = NULL;
580 int i;
581
582#if 0
583 printf("XRROutputChangeNotifyEvent! [output=%u, crtc=%u, mode=%u, rotation=%u, connection=%u]", (unsigned int) ev->output, (unsigned int) ev->crtc, (unsigned int) ev->mode, (unsigned int) ev->rotation, (unsigned int) ev->connection);
584#endif
585
586 displays = SDL_GetDisplays(NULL);
587 if (displays) {
588 for (i = 0; displays[i]; ++i) {
589 SDL_VideoDisplay *thisdisplay = SDL_GetVideoDisplay(displays[i]);
590 const SDL_DisplayData *displaydata = thisdisplay->internal;
591 if (displaydata->xrandr_output == ev->output) {
592 display = thisdisplay;
593 break;
594 }
595 }
596 SDL_free(displays);
597 }
598
599 if (ev->connection == RR_Disconnected) { // output is going away
600 if (display) {
601 SDL_DelVideoDisplay(display->id, true);
602 }
603 X11_CheckDisplaysMoved(_this, ev->display);
604
605 } else if (ev->connection == RR_Connected) { // output is coming online
606 if (!display) {
607 Display *dpy = ev->display;
608 const int screen = DefaultScreen(dpy);
609 XRRScreenResources *res = X11_GetScreenResources(dpy, screen);
610 if (res) {
611 X11_AddXRandRDisplay(_this, dpy, screen, ev->output, res, true);
612 X11_XRRFreeScreenResources(res);
613 }
614 }
615 X11_CheckDisplaysMoved(_this, ev->display);
616 }
617}
618
619void X11_HandleXRandREvent(SDL_VideoDevice *_this, const XEvent *xevent)
620{
621 SDL_VideoData *videodata = _this->internal;
622 SDL_assert(xevent->type == (videodata->xrandr_event_base + RRNotify));
623
624 switch (((const XRRNotifyEvent *)xevent)->subtype) {
625 case RRNotify_OutputChange:
626 X11_HandleXRandROutputChange(_this, (const XRROutputChangeNotifyEvent *)xevent);
627 break;
628 default:
629 break;
630 }
631}
632
633static void X11_SortOutputsByPriorityHint(SDL_VideoDevice *_this)
634{
635 const char *name_hint = SDL_GetHint(SDL_HINT_VIDEO_DISPLAY_PRIORITY);
636
637 if (name_hint) {
638 char *saveptr;
639 char *str = SDL_strdup(name_hint);
640 SDL_VideoDisplay **sorted_list = SDL_malloc(sizeof(SDL_VideoDisplay *) * _this->num_displays);
641
642 if (str && sorted_list) {
643 int sorted_index = 0;
644
645 // Sort the requested displays to the front of the list.
646 const char *token = SDL_strtok_r(str, ",", &saveptr);
647 while (token) {
648 for (int i = 0; i < _this->num_displays; ++i) {
649 SDL_VideoDisplay *d = _this->displays[i];
650 if (d) {
651 SDL_DisplayData *data = d->internal;
652 if (SDL_strcmp(token, data->connector_name) == 0) {
653 sorted_list[sorted_index++] = d;
654 _this->displays[i] = NULL;
655 break;
656 }
657 }
658 }
659
660 token = SDL_strtok_r(NULL, ",", &saveptr);
661 }
662
663 // Append the remaining displays to the end of the list.
664 for (int i = 0; i < _this->num_displays; ++i) {
665 if (_this->displays[i]) {
666 sorted_list[sorted_index++] = _this->displays[i];
667 }
668 }
669
670 // Copy the sorted list back to the display list.
671 SDL_memcpy(_this->displays, sorted_list, sizeof(SDL_VideoDisplay *) * _this->num_displays);
672 }
673
674 SDL_free(str);
675 SDL_free(sorted_list);
676 }
677}
678
679static bool X11_InitModes_XRandR(SDL_VideoDevice *_this)
680{
681 SDL_VideoData *data = _this->internal;
682 Display *dpy = data->display;
683 const int screencount = ScreenCount(dpy);
684 const int default_screen = DefaultScreen(dpy);
685 RROutput primary = X11_XRRGetOutputPrimary(dpy, RootWindow(dpy, default_screen));
686 int xrandr_error_base = 0;
687 int looking_for_primary;
688 int output;
689 int screen;
690
691 if (!X11_XRRQueryExtension(dpy, &data->xrandr_event_base, &xrandr_error_base)) {
692 return SDL_SetError("XRRQueryExtension failed");
693 }
694
695 for (looking_for_primary = 1; looking_for_primary >= 0; looking_for_primary--) {
696 for (screen = 0; screen < screencount; screen++) {
697
698 // we want the primary output first, and then skipped later.
699 if (looking_for_primary && (screen != default_screen)) {
700 continue;
701 }
702
703 XRRScreenResources *res = X11_GetScreenResources(dpy, screen);
704 if (!res) {
705 continue;
706 }
707
708 for (output = 0; output < res->noutput; output++) {
709 // The primary output _should_ always be sorted first, but just in case...
710 if ((looking_for_primary && (res->outputs[output] != primary)) ||
711 (!looking_for_primary && (screen == default_screen) && (res->outputs[output] == primary))) {
712 continue;
713 }
714 if (!X11_AddXRandRDisplay(_this, dpy, screen, res->outputs[output], res, false)) {
715 break;
716 }
717 }
718
719 X11_XRRFreeScreenResources(res);
720
721 // This will generate events for displays that come and go at runtime.
722 X11_XRRSelectInput(dpy, RootWindow(dpy, screen), RROutputChangeNotifyMask);
723 }
724 }
725
726 if (_this->num_displays == 0) {
727 return SDL_SetError("No available displays");
728 }
729
730 X11_SortOutputsByPriorityHint(_this);
731
732 return true;
733}
734#endif // SDL_VIDEO_DRIVER_X11_XRANDR
735
736/* This is used if there's no better functionality--like XRandR--to use.
737 It won't attempt to supply different display modes at all, but it can
738 enumerate the current displays and their current sizes. */
739static bool X11_InitModes_StdXlib(SDL_VideoDevice *_this)
740{
741 // !!! FIXME: a lot of copy/paste from X11_InitModes_XRandR in this function.
742 SDL_VideoData *data = _this->internal;
743 Display *dpy = data->display;
744 const int default_screen = DefaultScreen(dpy);
745 Screen *screen = ScreenOfDisplay(dpy, default_screen);
746 int scanline_pad, n, i;
747 SDL_DisplayModeData *modedata;
748 SDL_DisplayData *displaydata;
749 SDL_DisplayMode mode;
750 XPixmapFormatValues *pixmapformats;
751 Uint32 pixelformat;
752 XVisualInfo vinfo;
753 SDL_VideoDisplay display;
754
755 // note that generally even if you have a multiple physical monitors, ScreenCount(dpy) still only reports ONE screen.
756
757 if (!get_visualinfo(dpy, default_screen, &vinfo)) {
758 return SDL_SetError("Failed to find an X11 visual for the primary display");
759 }
760
761 pixelformat = X11_GetPixelFormatFromVisualInfo(dpy, &vinfo);
762 if (SDL_ISPIXELFORMAT_INDEXED(pixelformat)) {
763 return SDL_SetError("Palettized video modes are no longer supported");
764 }
765
766 SDL_zero(mode);
767 mode.w = WidthOfScreen(screen);
768 mode.h = HeightOfScreen(screen);
769 mode.format = pixelformat;
770
771 displaydata = (SDL_DisplayData *)SDL_calloc(1, sizeof(*displaydata));
772 if (!displaydata) {
773 return false;
774 }
775
776 modedata = (SDL_DisplayModeData *)SDL_calloc(1, sizeof(SDL_DisplayModeData));
777 if (!modedata) {
778 SDL_free(displaydata);
779 return false;
780 }
781 mode.internal = modedata;
782
783 displaydata->screen = default_screen;
784 displaydata->visual = vinfo.visual;
785 displaydata->depth = vinfo.depth;
786
787 scanline_pad = SDL_BYTESPERPIXEL(pixelformat) * 8;
788 pixmapformats = X11_XListPixmapFormats(dpy, &n);
789 if (pixmapformats) {
790 for (i = 0; i < n; ++i) {
791 if (pixmapformats[i].depth == vinfo.depth) {
792 scanline_pad = pixmapformats[i].scanline_pad;
793 break;
794 }
795 }
796 X11_XFree(pixmapformats);
797 }
798
799 displaydata->scanline_pad = scanline_pad;
800 displaydata->x = 0;
801 displaydata->y = 0;
802 displaydata->use_xrandr = false;
803
804 SDL_zero(display);
805 display.name = (char *)"Generic X11 Display"; /* this is just copied and thrown away, it's safe to cast to char* here. */
806 display.desktop_mode = mode;
807 display.internal = displaydata;
808 display.content_scale = GetGlobalContentScale(_this);
809 if (SDL_AddVideoDisplay(&display, true) == 0) {
810 return false;
811 }
812 return true;
813}
814
815bool X11_InitModes(SDL_VideoDevice *_this)
816{
817 /* XRandR is the One True Modern Way to do this on X11. If this
818 fails, we just won't report any display modes except the current
819 desktop size. */
820#ifdef SDL_VIDEO_DRIVER_X11_XRANDR
821 {
822 SDL_VideoData *data = _this->internal;
823 int xrandr_major, xrandr_minor;
824 // require at least XRandR v1.3
825 if (CheckXRandR(data->display, &xrandr_major, &xrandr_minor) &&
826 (xrandr_major >= 2 || (xrandr_major == 1 && xrandr_minor >= 3)) &&
827 X11_InitModes_XRandR(_this)) {
828 return true;
829 }
830 }
831#endif // SDL_VIDEO_DRIVER_X11_XRANDR
832
833 // still here? Just set up an extremely basic display.
834 return X11_InitModes_StdXlib(_this);
835}
836
837bool X11_GetDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *sdl_display)
838{
839#ifdef SDL_VIDEO_DRIVER_X11_XRANDR
840 SDL_DisplayData *data = sdl_display->internal;
841 SDL_DisplayMode mode;
842
843 /* Unfortunately X11 requires the window to be created with the correct
844 * visual and depth ahead of time, but the SDL API allows you to create
845 * a window before setting the fullscreen display mode. This means that
846 * we have to use the same format for all windows and all display modes.
847 * (or support recreating the window with a new visual behind the scenes)
848 */
849 SDL_zero(mode);
850 mode.format = sdl_display->desktop_mode.format;
851
852 if (data->use_xrandr) {
853 Display *display = _this->internal->display;
854 XRRScreenResources *res;
855
856 res = X11_XRRGetScreenResources(display, RootWindow(display, data->screen));
857 if (res) {
858 SDL_DisplayModeData *modedata;
859 XRROutputInfo *output_info;
860 int i;
861
862 output_info = X11_XRRGetOutputInfo(display, res, data->xrandr_output);
863 if (output_info && output_info->connection != RR_Disconnected) {
864 for (i = 0; i < output_info->nmode; ++i) {
865 modedata = (SDL_DisplayModeData *)SDL_calloc(1, sizeof(SDL_DisplayModeData));
866 if (!modedata) {
867 continue;
868 }
869 mode.internal = modedata;
870
871 if (!SetXRandRModeInfo(display, res, output_info->crtc, output_info->modes[i], &mode) ||
872 !SDL_AddFullscreenDisplayMode(sdl_display, &mode)) {
873 SDL_free(modedata);
874 }
875 }
876 }
877 X11_XRRFreeOutputInfo(output_info);
878 X11_XRRFreeScreenResources(res);
879 }
880 }
881#endif // SDL_VIDEO_DRIVER_X11_XRANDR
882 return true;
883}
884
885#ifdef SDL_VIDEO_DRIVER_X11_XRANDR
886// This catches an error from XRRSetScreenSize, as a workaround for now.
887// !!! FIXME: remove this later when we have a better solution.
888static int (*PreXRRSetScreenSizeErrorHandler)(Display *, XErrorEvent *) = NULL;
889static int SDL_XRRSetScreenSizeErrHandler(Display *d, XErrorEvent *e)
890{
891 // BadMatch: https://github.com/libsdl-org/SDL/issues/4561
892 // BadValue: https://github.com/libsdl-org/SDL/issues/4840
893 if ((e->error_code == BadMatch) || (e->error_code == BadValue)) {
894 return 0;
895 }
896
897 return PreXRRSetScreenSizeErrorHandler(d, e);
898}
899#endif
900
901bool X11_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *sdl_display, SDL_DisplayMode *mode)
902{
903 SDL_VideoData *viddata = _this->internal;
904 SDL_DisplayData *data = sdl_display->internal;
905
906 viddata->last_mode_change_deadline = SDL_GetTicks() + (PENDING_FOCUS_TIME * 2);
907
908 // XWayland mode switches are emulated with viewports and thus instantaneous.
909 if (!viddata->is_xwayland) {
910 if (sdl_display->current_mode != mode) {
911 data->mode_switch_deadline_ns = SDL_GetTicksNS() + MODE_SWITCH_TIMEOUT_NS;
912 } else {
913 data->mode_switch_deadline_ns = 0;
914 }
915 }
916
917#ifdef SDL_VIDEO_DRIVER_X11_XRANDR
918 if (data->use_xrandr) {
919 Display *display = viddata->display;
920 SDL_DisplayModeData *modedata = mode->internal;
921 int mm_width, mm_height;
922 XRRScreenResources *res;
923 XRROutputInfo *output_info;
924 XRRCrtcInfo *crtc;
925 Status status;
926
927 res = X11_XRRGetScreenResources(display, RootWindow(display, data->screen));
928 if (!res) {
929 return SDL_SetError("Couldn't get XRandR screen resources");
930 }
931
932 output_info = X11_XRRGetOutputInfo(display, res, data->xrandr_output);
933 if (!output_info || output_info->connection == RR_Disconnected) {
934 X11_XRRFreeScreenResources(res);
935 return SDL_SetError("Couldn't get XRandR output info");
936 }
937
938 crtc = X11_XRRGetCrtcInfo(display, res, output_info->crtc);
939 if (!crtc) {
940 X11_XRRFreeOutputInfo(output_info);
941 X11_XRRFreeScreenResources(res);
942 return SDL_SetError("Couldn't get XRandR crtc info");
943 }
944
945 if (crtc->mode == modedata->xrandr_mode) {
946#ifdef X11MODES_DEBUG
947 printf("already in desired mode 0x%lx (%ux%u), nothing to do\n",
948 crtc->mode, crtc->width, crtc->height);
949#endif
950 status = Success;
951 goto freeInfo;
952 }
953
954 X11_XGrabServer(display);
955 status = X11_XRRSetCrtcConfig(display, res, output_info->crtc, CurrentTime,
956 0, 0, None, crtc->rotation, NULL, 0);
957 if (status != Success) {
958 goto ungrabServer;
959 }
960
961 mm_width = mode->w * DisplayWidthMM(display, data->screen) / DisplayWidth(display, data->screen);
962 mm_height = mode->h * DisplayHeightMM(display, data->screen) / DisplayHeight(display, data->screen);
963
964 /* !!! FIXME: this can get into a problem scenario when a window is
965 bigger than a physical monitor in a configuration where one screen
966 spans multiple physical monitors. A detailed reproduction case is
967 discussed at https://github.com/libsdl-org/SDL/issues/4561 ...
968 for now we cheat and just catch the X11 error and carry on, which
969 is likely to cause subtle issues but is better than outright
970 crashing */
971 X11_XSync(display, False);
972 PreXRRSetScreenSizeErrorHandler = X11_XSetErrorHandler(SDL_XRRSetScreenSizeErrHandler);
973 X11_XRRSetScreenSize(display, RootWindow(display, data->screen),
974 mode->w, mode->h, mm_width, mm_height);
975 X11_XSync(display, False);
976 X11_XSetErrorHandler(PreXRRSetScreenSizeErrorHandler);
977
978 status = X11_XRRSetCrtcConfig(display, res, output_info->crtc, CurrentTime,
979 crtc->x, crtc->y, modedata->xrandr_mode, crtc->rotation,
980 &data->xrandr_output, 1);
981
982 ungrabServer:
983 X11_XUngrabServer(display);
984 freeInfo:
985 X11_XRRFreeCrtcInfo(crtc);
986 X11_XRRFreeOutputInfo(output_info);
987 X11_XRRFreeScreenResources(res);
988
989 if (status != Success) {
990 return SDL_SetError("X11_XRRSetCrtcConfig failed");
991 }
992 }
993#else
994 (void)data;
995#endif // SDL_VIDEO_DRIVER_X11_XRANDR
996
997 return true;
998}
999
1000void X11_QuitModes(SDL_VideoDevice *_this)
1001{
1002}
1003
1004bool X11_GetDisplayBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *sdl_display, SDL_Rect *rect)
1005{
1006 SDL_DisplayData *data = sdl_display->internal;
1007
1008 rect->x = data->x;
1009 rect->y = data->y;
1010 rect->w = sdl_display->current_mode->w;
1011 rect->h = sdl_display->current_mode->h;
1012 return true;
1013}
1014
1015bool X11_GetDisplayUsableBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *sdl_display, SDL_Rect *rect)
1016{
1017 SDL_VideoData *data = _this->internal;
1018 Display *display = data->display;
1019 Atom _NET_WORKAREA;
1020 int real_format;
1021 Atom real_type;
1022 unsigned long items_read = 0, items_left = 0;
1023 unsigned char *propdata = NULL;
1024 bool result = false;
1025
1026 if (!X11_GetDisplayBounds(_this, sdl_display, rect)) {
1027 return false;
1028 }
1029
1030 _NET_WORKAREA = X11_XInternAtom(display, "_NET_WORKAREA", False);
1031 int status = X11_XGetWindowProperty(display, DefaultRootWindow(display),
1032 _NET_WORKAREA, 0L, 4L, False, XA_CARDINAL,
1033 &real_type, &real_format, &items_read,
1034 &items_left, &propdata);
1035 if ((status == Success) && (items_read >= 4)) {
1036 const long *p = (long *)propdata;
1037 const SDL_Rect usable = { (int)p[0], (int)p[1], (int)p[2], (int)p[3] };
1038 result = true;
1039 if (!SDL_GetRectIntersection(rect, &usable, rect)) {
1040 SDL_zerop(rect);
1041 }
1042 }
1043
1044 if (propdata) {
1045 X11_XFree(propdata);
1046 }
1047
1048 return result;
1049}
1050
1051#endif // SDL_VIDEO_DRIVER_X11