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 screencount = ScreenCount(dpy);
559
560 SDL_DisplayID *displays = SDL_GetDisplays(NULL);
561 if (!displays) {
562 return;
563 }
564
565 for (int screen = 0; screen < screencount; ++screen) {
566 XRRScreenResources *res = X11_GetScreenResources(dpy, screen);
567 if (!res) {
568 continue;
569 }
570
571 for (int i = 0; displays[i]; ++i) {
572 SDL_VideoDisplay *display = SDL_GetVideoDisplay(displays[i]);
573 const SDL_DisplayData *displaydata = display->internal;
574 if (displaydata->screen == screen) {
575 X11_UpdateXRandRDisplay(_this, dpy, screen, displaydata->xrandr_output, res, display);
576 }
577 }
578 X11_XRRFreeScreenResources(res);
579 }
580 SDL_free(displays);
581}
582
583static void X11_CheckDisplaysRemoved(SDL_VideoDevice *_this, Display *dpy)
584{
585 const int screencount = ScreenCount(dpy);
586 int num_displays = 0;
587
588 SDL_DisplayID *displays = SDL_GetDisplays(&num_displays);
589 if (!displays) {
590 return;
591 }
592
593 for (int screen = 0; screen < screencount; ++screen) {
594 XRRScreenResources *res = X11_GetScreenResources(dpy, screen);
595 if (!res) {
596 continue;
597 }
598
599 for (int output = 0; output < res->noutput; output++) {
600 for (int i = 0; i < num_displays; ++i) {
601 if (!displays[i]) {
602 // We already removed this display from the list
603 continue;
604 }
605
606 SDL_VideoDisplay *display = SDL_GetVideoDisplay(displays[i]);
607 const SDL_DisplayData *displaydata = display->internal;
608 if (displaydata->xrandr_output == res->outputs[output]) {
609 // This display is active, remove it from the list
610 displays[i] = 0;
611 break;
612 }
613 }
614 }
615 X11_XRRFreeScreenResources(res);
616 }
617
618 for (int i = 0; i < num_displays; ++i) {
619 if (displays[i]) {
620 // This display wasn't in the XRandR list
621 SDL_DelVideoDisplay(displays[i], true);
622 }
623 }
624 SDL_free(displays);
625}
626
627static void X11_HandleXRandROutputChange(SDL_VideoDevice *_this, const XRROutputChangeNotifyEvent *ev)
628{
629 SDL_DisplayID *displays;
630 SDL_VideoDisplay *display = NULL;
631 int i;
632
633#if 0
634 printf("XRROutputChangeNotifyEvent! [output=%u, crtc=%u, mode=%u, rotation=%u, connection=%u]\n", (unsigned int) ev->output, (unsigned int) ev->crtc, (unsigned int) ev->mode, (unsigned int) ev->rotation, (unsigned int) ev->connection);
635#endif
636
637 // XWayland doesn't always send output disconnected events
638 X11_CheckDisplaysRemoved(_this, ev->display);
639
640 displays = SDL_GetDisplays(NULL);
641 if (displays) {
642 for (i = 0; displays[i]; ++i) {
643 SDL_VideoDisplay *thisdisplay = SDL_GetVideoDisplay(displays[i]);
644 const SDL_DisplayData *displaydata = thisdisplay->internal;
645 if (displaydata->xrandr_output == ev->output) {
646 display = thisdisplay;
647 break;
648 }
649 }
650 SDL_free(displays);
651 }
652
653 if (ev->connection == RR_Disconnected) { // output is going away
654 if (display) {
655 SDL_DelVideoDisplay(display->id, true);
656 }
657 X11_CheckDisplaysMoved(_this, ev->display);
658
659 } else if (ev->connection == RR_Connected) { // output is coming online
660 if (!display) {
661 Display *dpy = ev->display;
662 const int screen = DefaultScreen(dpy);
663 XRRScreenResources *res = X11_GetScreenResources(dpy, screen);
664 if (res) {
665 X11_AddXRandRDisplay(_this, dpy, screen, ev->output, res, true);
666 X11_XRRFreeScreenResources(res);
667 }
668 }
669 X11_CheckDisplaysMoved(_this, ev->display);
670 }
671}
672
673void X11_HandleXRandREvent(SDL_VideoDevice *_this, const XEvent *xevent)
674{
675 SDL_VideoData *videodata = _this->internal;
676 SDL_assert(xevent->type == (videodata->xrandr_event_base + RRNotify));
677
678 switch (((const XRRNotifyEvent *)xevent)->subtype) {
679 case RRNotify_OutputChange:
680 X11_HandleXRandROutputChange(_this, (const XRROutputChangeNotifyEvent *)xevent);
681 break;
682 default:
683 break;
684 }
685}
686
687static void X11_SortOutputsByPriorityHint(SDL_VideoDevice *_this)
688{
689 const char *name_hint = SDL_GetHint(SDL_HINT_VIDEO_DISPLAY_PRIORITY);
690
691 if (name_hint) {
692 char *saveptr;
693 char *str = SDL_strdup(name_hint);
694 SDL_VideoDisplay **sorted_list = SDL_malloc(sizeof(SDL_VideoDisplay *) * _this->num_displays);
695
696 if (str && sorted_list) {
697 int sorted_index = 0;
698
699 // Sort the requested displays to the front of the list.
700 const char *token = SDL_strtok_r(str, ",", &saveptr);
701 while (token) {
702 for (int i = 0; i < _this->num_displays; ++i) {
703 SDL_VideoDisplay *d = _this->displays[i];
704 if (d) {
705 SDL_DisplayData *data = d->internal;
706 if (SDL_strcmp(token, data->connector_name) == 0) {
707 sorted_list[sorted_index++] = d;
708 _this->displays[i] = NULL;
709 break;
710 }
711 }
712 }
713
714 token = SDL_strtok_r(NULL, ",", &saveptr);
715 }
716
717 // Append the remaining displays to the end of the list.
718 for (int i = 0; i < _this->num_displays; ++i) {
719 if (_this->displays[i]) {
720 sorted_list[sorted_index++] = _this->displays[i];
721 }
722 }
723
724 // Copy the sorted list back to the display list.
725 SDL_memcpy(_this->displays, sorted_list, sizeof(SDL_VideoDisplay *) * _this->num_displays);
726 }
727
728 SDL_free(str);
729 SDL_free(sorted_list);
730 }
731}
732
733static bool X11_InitModes_XRandR(SDL_VideoDevice *_this)
734{
735 SDL_VideoData *data = _this->internal;
736 Display *dpy = data->display;
737 const int screencount = ScreenCount(dpy);
738 const int default_screen = DefaultScreen(dpy);
739 RROutput primary = X11_XRRGetOutputPrimary(dpy, RootWindow(dpy, default_screen));
740 int xrandr_error_base = 0;
741 int looking_for_primary;
742 int output;
743 int screen;
744
745 if (!X11_XRRQueryExtension(dpy, &data->xrandr_event_base, &xrandr_error_base)) {
746 return SDL_SetError("XRRQueryExtension failed");
747 }
748
749 for (looking_for_primary = 1; looking_for_primary >= 0; looking_for_primary--) {
750 for (screen = 0; screen < screencount; screen++) {
751
752 // we want the primary output first, and then skipped later.
753 if (looking_for_primary && (screen != default_screen)) {
754 continue;
755 }
756
757 XRRScreenResources *res = X11_GetScreenResources(dpy, screen);
758 if (!res) {
759 continue;
760 }
761
762 for (output = 0; output < res->noutput; output++) {
763 // The primary output _should_ always be sorted first, but just in case...
764 if ((looking_for_primary && (res->outputs[output] != primary)) ||
765 (!looking_for_primary && (screen == default_screen) && (res->outputs[output] == primary))) {
766 continue;
767 }
768 if (!X11_AddXRandRDisplay(_this, dpy, screen, res->outputs[output], res, false)) {
769 break;
770 }
771 }
772
773 X11_XRRFreeScreenResources(res);
774
775 // This will generate events for displays that come and go at runtime.
776 X11_XRRSelectInput(dpy, RootWindow(dpy, screen), RROutputChangeNotifyMask);
777 }
778 }
779
780 if (_this->num_displays == 0) {
781 return SDL_SetError("No available displays");
782 }
783
784 X11_SortOutputsByPriorityHint(_this);
785
786 return true;
787}
788#endif // SDL_VIDEO_DRIVER_X11_XRANDR
789
790/* This is used if there's no better functionality--like XRandR--to use.
791 It won't attempt to supply different display modes at all, but it can
792 enumerate the current displays and their current sizes. */
793static bool X11_InitModes_StdXlib(SDL_VideoDevice *_this)
794{
795 // !!! FIXME: a lot of copy/paste from X11_InitModes_XRandR in this function.
796 SDL_VideoData *data = _this->internal;
797 Display *dpy = data->display;
798 const int default_screen = DefaultScreen(dpy);
799 Screen *screen = ScreenOfDisplay(dpy, default_screen);
800 int scanline_pad, n, i;
801 SDL_DisplayModeData *modedata;
802 SDL_DisplayData *displaydata;
803 SDL_DisplayMode mode;
804 XPixmapFormatValues *pixmapformats;
805 Uint32 pixelformat;
806 XVisualInfo vinfo;
807 SDL_VideoDisplay display;
808
809 // note that generally even if you have a multiple physical monitors, ScreenCount(dpy) still only reports ONE screen.
810
811 if (!get_visualinfo(dpy, default_screen, &vinfo)) {
812 return SDL_SetError("Failed to find an X11 visual for the primary display");
813 }
814
815 pixelformat = X11_GetPixelFormatFromVisualInfo(dpy, &vinfo);
816 if (SDL_ISPIXELFORMAT_INDEXED(pixelformat)) {
817 return SDL_SetError("Palettized video modes are no longer supported");
818 }
819
820 SDL_zero(mode);
821 mode.w = WidthOfScreen(screen);
822 mode.h = HeightOfScreen(screen);
823 mode.format = pixelformat;
824
825 displaydata = (SDL_DisplayData *)SDL_calloc(1, sizeof(*displaydata));
826 if (!displaydata) {
827 return false;
828 }
829
830 modedata = (SDL_DisplayModeData *)SDL_calloc(1, sizeof(SDL_DisplayModeData));
831 if (!modedata) {
832 SDL_free(displaydata);
833 return false;
834 }
835 mode.internal = modedata;
836
837 displaydata->screen = default_screen;
838 displaydata->visual = vinfo.visual;
839 displaydata->depth = vinfo.depth;
840
841 scanline_pad = SDL_BYTESPERPIXEL(pixelformat) * 8;
842 pixmapformats = X11_XListPixmapFormats(dpy, &n);
843 if (pixmapformats) {
844 for (i = 0; i < n; ++i) {
845 if (pixmapformats[i].depth == vinfo.depth) {
846 scanline_pad = pixmapformats[i].scanline_pad;
847 break;
848 }
849 }
850 X11_XFree(pixmapformats);
851 }
852
853 displaydata->scanline_pad = scanline_pad;
854 displaydata->x = 0;
855 displaydata->y = 0;
856 displaydata->use_xrandr = false;
857
858 SDL_zero(display);
859 display.name = (char *)"Generic X11 Display"; /* this is just copied and thrown away, it's safe to cast to char* here. */
860 display.desktop_mode = mode;
861 display.internal = displaydata;
862 display.content_scale = GetGlobalContentScale(_this);
863 if (SDL_AddVideoDisplay(&display, true) == 0) {
864 return false;
865 }
866 return true;
867}
868
869bool X11_InitModes(SDL_VideoDevice *_this)
870{
871 /* XRandR is the One True Modern Way to do this on X11. If this
872 fails, we just won't report any display modes except the current
873 desktop size. */
874#ifdef SDL_VIDEO_DRIVER_X11_XRANDR
875 {
876 SDL_VideoData *data = _this->internal;
877 int xrandr_major, xrandr_minor;
878 // require at least XRandR v1.3
879 if (CheckXRandR(data->display, &xrandr_major, &xrandr_minor) &&
880 (xrandr_major >= 2 || (xrandr_major == 1 && xrandr_minor >= 3)) &&
881 X11_InitModes_XRandR(_this)) {
882 return true;
883 }
884 }
885#endif // SDL_VIDEO_DRIVER_X11_XRANDR
886
887 // still here? Just set up an extremely basic display.
888 return X11_InitModes_StdXlib(_this);
889}
890
891bool X11_GetDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *sdl_display)
892{
893#ifdef SDL_VIDEO_DRIVER_X11_XRANDR
894 SDL_DisplayData *data = sdl_display->internal;
895 SDL_DisplayMode mode;
896
897 /* Unfortunately X11 requires the window to be created with the correct
898 * visual and depth ahead of time, but the SDL API allows you to create
899 * a window before setting the fullscreen display mode. This means that
900 * we have to use the same format for all windows and all display modes.
901 * (or support recreating the window with a new visual behind the scenes)
902 */
903 SDL_zero(mode);
904 mode.format = sdl_display->desktop_mode.format;
905
906 if (data->use_xrandr) {
907 Display *display = _this->internal->display;
908 XRRScreenResources *res;
909
910 res = X11_XRRGetScreenResources(display, RootWindow(display, data->screen));
911 if (res) {
912 SDL_DisplayModeData *modedata;
913 XRROutputInfo *output_info;
914 int i;
915
916 output_info = X11_XRRGetOutputInfo(display, res, data->xrandr_output);
917 if (output_info && output_info->connection != RR_Disconnected) {
918 for (i = 0; i < output_info->nmode; ++i) {
919 modedata = (SDL_DisplayModeData *)SDL_calloc(1, sizeof(SDL_DisplayModeData));
920 if (!modedata) {
921 continue;
922 }
923 mode.internal = modedata;
924
925 if (!SetXRandRModeInfo(display, res, output_info->crtc, output_info->modes[i], &mode) ||
926 !SDL_AddFullscreenDisplayMode(sdl_display, &mode)) {
927 SDL_free(modedata);
928 }
929 }
930 }
931 X11_XRRFreeOutputInfo(output_info);
932 X11_XRRFreeScreenResources(res);
933 }
934 }
935#endif // SDL_VIDEO_DRIVER_X11_XRANDR
936 return true;
937}
938
939#ifdef SDL_VIDEO_DRIVER_X11_XRANDR
940// This catches an error from XRRSetScreenSize, as a workaround for now.
941// !!! FIXME: remove this later when we have a better solution.
942static int (*PreXRRSetScreenSizeErrorHandler)(Display *, XErrorEvent *) = NULL;
943static int SDL_XRRSetScreenSizeErrHandler(Display *d, XErrorEvent *e)
944{
945 // BadMatch: https://github.com/libsdl-org/SDL/issues/4561
946 // BadValue: https://github.com/libsdl-org/SDL/issues/4840
947 if ((e->error_code == BadMatch) || (e->error_code == BadValue)) {
948 return 0;
949 }
950
951 return PreXRRSetScreenSizeErrorHandler(d, e);
952}
953#endif
954
955bool X11_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *sdl_display, SDL_DisplayMode *mode)
956{
957 SDL_VideoData *viddata = _this->internal;
958 SDL_DisplayData *data = sdl_display->internal;
959
960 viddata->last_mode_change_deadline = SDL_GetTicks() + (PENDING_FOCUS_TIME * 2);
961
962 // XWayland mode switches are emulated with viewports and thus instantaneous.
963 if (!viddata->is_xwayland) {
964 if (sdl_display->current_mode != mode) {
965 data->mode_switch_deadline_ns = SDL_GetTicksNS() + MODE_SWITCH_TIMEOUT_NS;
966 } else {
967 data->mode_switch_deadline_ns = 0;
968 }
969 }
970
971#ifdef SDL_VIDEO_DRIVER_X11_XRANDR
972 if (data->use_xrandr) {
973 Display *display = viddata->display;
974 SDL_DisplayModeData *modedata = mode->internal;
975 int mm_width, mm_height;
976 XRRScreenResources *res;
977 XRROutputInfo *output_info;
978 XRRCrtcInfo *crtc;
979 Status status;
980
981 res = X11_XRRGetScreenResources(display, RootWindow(display, data->screen));
982 if (!res) {
983 return SDL_SetError("Couldn't get XRandR screen resources");
984 }
985
986 output_info = X11_XRRGetOutputInfo(display, res, data->xrandr_output);
987 if (!output_info || output_info->connection == RR_Disconnected) {
988 X11_XRRFreeScreenResources(res);
989 return SDL_SetError("Couldn't get XRandR output info");
990 }
991
992 crtc = X11_XRRGetCrtcInfo(display, res, output_info->crtc);
993 if (!crtc) {
994 X11_XRRFreeOutputInfo(output_info);
995 X11_XRRFreeScreenResources(res);
996 return SDL_SetError("Couldn't get XRandR crtc info");
997 }
998
999 if (crtc->mode == modedata->xrandr_mode) {
1000#ifdef X11MODES_DEBUG
1001 printf("already in desired mode 0x%lx (%ux%u), nothing to do\n",
1002 crtc->mode, crtc->width, crtc->height);
1003#endif
1004 status = Success;
1005 goto freeInfo;
1006 }
1007
1008 X11_XGrabServer(display);
1009 status = X11_XRRSetCrtcConfig(display, res, output_info->crtc, CurrentTime,
1010 0, 0, None, crtc->rotation, NULL, 0);
1011 if (status != Success) {
1012 goto ungrabServer;
1013 }
1014
1015 mm_width = mode->w * DisplayWidthMM(display, data->screen) / DisplayWidth(display, data->screen);
1016 mm_height = mode->h * DisplayHeightMM(display, data->screen) / DisplayHeight(display, data->screen);
1017
1018 /* !!! FIXME: this can get into a problem scenario when a window is
1019 bigger than a physical monitor in a configuration where one screen
1020 spans multiple physical monitors. A detailed reproduction case is
1021 discussed at https://github.com/libsdl-org/SDL/issues/4561 ...
1022 for now we cheat and just catch the X11 error and carry on, which
1023 is likely to cause subtle issues but is better than outright
1024 crashing */
1025 X11_XSync(display, False);
1026 PreXRRSetScreenSizeErrorHandler = X11_XSetErrorHandler(SDL_XRRSetScreenSizeErrHandler);
1027 X11_XRRSetScreenSize(display, RootWindow(display, data->screen),
1028 mode->w, mode->h, mm_width, mm_height);
1029 X11_XSync(display, False);
1030 X11_XSetErrorHandler(PreXRRSetScreenSizeErrorHandler);
1031
1032 status = X11_XRRSetCrtcConfig(display, res, output_info->crtc, CurrentTime,
1033 crtc->x, crtc->y, modedata->xrandr_mode, crtc->rotation,
1034 &data->xrandr_output, 1);
1035
1036 ungrabServer:
1037 X11_XUngrabServer(display);
1038 freeInfo:
1039 X11_XRRFreeCrtcInfo(crtc);
1040 X11_XRRFreeOutputInfo(output_info);
1041 X11_XRRFreeScreenResources(res);
1042
1043 if (status != Success) {
1044 return SDL_SetError("X11_XRRSetCrtcConfig failed");
1045 }
1046 }
1047#else
1048 (void)data;
1049#endif // SDL_VIDEO_DRIVER_X11_XRANDR
1050
1051 return true;
1052}
1053
1054void X11_QuitModes(SDL_VideoDevice *_this)
1055{
1056}
1057
1058bool X11_GetDisplayBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *sdl_display, SDL_Rect *rect)
1059{
1060 SDL_DisplayData *data = sdl_display->internal;
1061
1062 rect->x = data->x;
1063 rect->y = data->y;
1064 rect->w = sdl_display->current_mode->w;
1065 rect->h = sdl_display->current_mode->h;
1066 return true;
1067}
1068
1069bool X11_GetDisplayUsableBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *sdl_display, SDL_Rect *rect)
1070{
1071 SDL_VideoData *data = _this->internal;
1072 Display *display = data->display;
1073 Atom _NET_WORKAREA;
1074 int real_format;
1075 Atom real_type;
1076 unsigned long items_read = 0, items_left = 0;
1077 unsigned char *propdata = NULL;
1078 bool result = false;
1079
1080 if (!X11_GetDisplayBounds(_this, sdl_display, rect)) {
1081 return false;
1082 }
1083
1084 _NET_WORKAREA = X11_XInternAtom(display, "_NET_WORKAREA", False);
1085 int status = X11_XGetWindowProperty(display, DefaultRootWindow(display),
1086 _NET_WORKAREA, 0L, 4L, False, XA_CARDINAL,
1087 &real_type, &real_format, &items_read,
1088 &items_left, &propdata);
1089 if ((status == Success) && (items_read >= 4)) {
1090 const long *p = (long *)propdata;
1091 const SDL_Rect usable = { (int)p[0], (int)p[1], (int)p[2], (int)p[3] };
1092 result = true;
1093 if (!SDL_GetRectIntersection(rect, &usable, rect)) {
1094 SDL_zerop(rect);
1095 }
1096 }
1097
1098 if (propdata) {
1099 X11_XFree(propdata);
1100 }
1101
1102 return result;
1103}
1104
1105#endif // SDL_VIDEO_DRIVER_X11
1106