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