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