1 | /* |
2 | Simple DirectMedia Layer |
3 | Copyright (C) 1997-2021 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 | #if SDL_VIDEO_DRIVER_X11 |
24 | |
25 | #include "SDL_hints.h" |
26 | #include "SDL_x11video.h" |
27 | #include "SDL_timer.h" |
28 | #include "edid.h" |
29 | |
30 | /* #define X11MODES_DEBUG */ |
31 | |
32 | /* I'm becoming more and more convinced that the application should never |
33 | * use XRandR, and it's the window manager's responsibility to track and |
34 | * manage display modes for fullscreen windows. Right now XRandR is completely |
35 | * broken with respect to window manager behavior on every window manager that |
36 | * I can find. For example, on Unity 3D if you show a fullscreen window while |
37 | * the resolution is changing (within ~250 ms) your window will retain the |
38 | * fullscreen state hint but be decorated and windowed. |
39 | * |
40 | * However, many people swear by it, so let them swear at it. :) |
41 | */ |
42 | /* #define XRANDR_DISABLED_BY_DEFAULT */ |
43 | |
44 | |
45 | static int |
46 | get_visualinfo(Display * display, int screen, XVisualInfo * vinfo) |
47 | { |
48 | const char *visual_id = SDL_getenv("SDL_VIDEO_X11_VISUALID" ); |
49 | int depth; |
50 | |
51 | /* Look for an exact visual, if requested */ |
52 | if (visual_id) { |
53 | XVisualInfo *vi, template; |
54 | int nvis; |
55 | |
56 | SDL_zero(template); |
57 | template.visualid = SDL_strtol(visual_id, NULL, 0); |
58 | vi = X11_XGetVisualInfo(display, VisualIDMask, &template, &nvis); |
59 | if (vi) { |
60 | *vinfo = *vi; |
61 | X11_XFree(vi); |
62 | return 0; |
63 | } |
64 | } |
65 | |
66 | depth = DefaultDepth(display, screen); |
67 | if ((X11_UseDirectColorVisuals() && |
68 | X11_XMatchVisualInfo(display, screen, depth, DirectColor, vinfo)) || |
69 | X11_XMatchVisualInfo(display, screen, depth, TrueColor, vinfo) || |
70 | X11_XMatchVisualInfo(display, screen, depth, PseudoColor, vinfo) || |
71 | X11_XMatchVisualInfo(display, screen, depth, StaticColor, vinfo)) { |
72 | return 0; |
73 | } |
74 | return -1; |
75 | } |
76 | |
77 | int |
78 | X11_GetVisualInfoFromVisual(Display * display, Visual * visual, XVisualInfo * vinfo) |
79 | { |
80 | XVisualInfo *vi; |
81 | int nvis; |
82 | |
83 | vinfo->visualid = X11_XVisualIDFromVisual(visual); |
84 | vi = X11_XGetVisualInfo(display, VisualIDMask, vinfo, &nvis); |
85 | if (vi) { |
86 | *vinfo = *vi; |
87 | X11_XFree(vi); |
88 | return 0; |
89 | } |
90 | return -1; |
91 | } |
92 | |
93 | Uint32 |
94 | X11_GetPixelFormatFromVisualInfo(Display * display, XVisualInfo * vinfo) |
95 | { |
96 | if (vinfo->class == DirectColor || vinfo->class == TrueColor) { |
97 | int bpp; |
98 | Uint32 Rmask, Gmask, Bmask, Amask; |
99 | |
100 | Rmask = vinfo->visual->red_mask; |
101 | Gmask = vinfo->visual->green_mask; |
102 | Bmask = vinfo->visual->blue_mask; |
103 | if (vinfo->depth == 32) { |
104 | Amask = (0xFFFFFFFF & ~(Rmask | Gmask | Bmask)); |
105 | } else { |
106 | Amask = 0; |
107 | } |
108 | |
109 | bpp = vinfo->depth; |
110 | if (bpp == 24) { |
111 | int i, n; |
112 | XPixmapFormatValues *p = X11_XListPixmapFormats(display, &n); |
113 | if (p) { |
114 | for (i = 0; i < n; ++i) { |
115 | if (p[i].depth == 24) { |
116 | bpp = p[i].bits_per_pixel; |
117 | break; |
118 | } |
119 | } |
120 | X11_XFree(p); |
121 | } |
122 | } |
123 | |
124 | return SDL_MasksToPixelFormatEnum(bpp, Rmask, Gmask, Bmask, Amask); |
125 | } |
126 | |
127 | if (vinfo->class == PseudoColor || vinfo->class == StaticColor) { |
128 | switch (vinfo->depth) { |
129 | case 8: |
130 | return SDL_PIXELTYPE_INDEX8; |
131 | case 4: |
132 | if (BitmapBitOrder(display) == LSBFirst) { |
133 | return SDL_PIXELFORMAT_INDEX4LSB; |
134 | } else { |
135 | return SDL_PIXELFORMAT_INDEX4MSB; |
136 | } |
137 | /* break; -Wunreachable-code-break */ |
138 | case 1: |
139 | if (BitmapBitOrder(display) == LSBFirst) { |
140 | return SDL_PIXELFORMAT_INDEX1LSB; |
141 | } else { |
142 | return SDL_PIXELFORMAT_INDEX1MSB; |
143 | } |
144 | /* break; -Wunreachable-code-break */ |
145 | } |
146 | } |
147 | |
148 | return SDL_PIXELFORMAT_UNKNOWN; |
149 | } |
150 | |
151 | #if SDL_VIDEO_DRIVER_X11_XINERAMA |
152 | static SDL_bool |
153 | CheckXinerama(Display * display, int *major, int *minor) |
154 | { |
155 | int event_base = 0; |
156 | int error_base = 0; |
157 | |
158 | /* Default the extension not available */ |
159 | *major = *minor = 0; |
160 | |
161 | /* Allow environment override */ |
162 | if (!SDL_GetHintBoolean(SDL_HINT_VIDEO_X11_XINERAMA, SDL_TRUE)) { |
163 | #ifdef X11MODES_DEBUG |
164 | printf("Xinerama disabled due to hint\n" ); |
165 | #endif |
166 | return SDL_FALSE; |
167 | } |
168 | |
169 | if (!SDL_X11_HAVE_XINERAMA) { |
170 | #ifdef X11MODES_DEBUG |
171 | printf("Xinerama support not available\n" ); |
172 | #endif |
173 | return SDL_FALSE; |
174 | } |
175 | |
176 | /* Query the extension version */ |
177 | if (!X11_XineramaQueryExtension(display, &event_base, &error_base) || |
178 | !X11_XineramaQueryVersion(display, major, minor) || |
179 | !X11_XineramaIsActive(display)) { |
180 | #ifdef X11MODES_DEBUG |
181 | printf("Xinerama not active on the display\n" ); |
182 | #endif |
183 | return SDL_FALSE; |
184 | } |
185 | #ifdef X11MODES_DEBUG |
186 | printf("Xinerama available at version %d.%d!\n" , *major, *minor); |
187 | #endif |
188 | return SDL_TRUE; |
189 | } |
190 | |
191 | /* !!! FIXME: remove this later. */ |
192 | /* we have a weird bug where XineramaQueryScreens() throws an X error, so this |
193 | is here to help track it down (and not crash, too!). */ |
194 | static SDL_bool xinerama_triggered_error = SDL_FALSE; |
195 | static int |
196 | X11_XineramaFailed(Display * d, XErrorEvent * e) |
197 | { |
198 | xinerama_triggered_error = SDL_TRUE; |
199 | fprintf(stderr, "XINERAMA X ERROR: type=%d serial=%lu err=%u req=%u minor=%u\n" , |
200 | e->type, e->serial, (unsigned int) e->error_code, |
201 | (unsigned int) e->request_code, (unsigned int) e->minor_code); |
202 | fflush(stderr); |
203 | return 0; |
204 | } |
205 | #endif /* SDL_VIDEO_DRIVER_X11_XINERAMA */ |
206 | |
207 | #if SDL_VIDEO_DRIVER_X11_XRANDR |
208 | static SDL_bool |
209 | CheckXRandR(Display * display, int *major, int *minor) |
210 | { |
211 | /* Default the extension not available */ |
212 | *major = *minor = 0; |
213 | |
214 | /* Allow environment override */ |
215 | #ifdef XRANDR_DISABLED_BY_DEFAULT |
216 | if (!SDL_GetHintBoolean(SDL_HINT_VIDEO_X11_XRANDR, SDL_FALSE)) { |
217 | #ifdef X11MODES_DEBUG |
218 | printf("XRandR disabled by default due to window manager issues\n" ); |
219 | #endif |
220 | return SDL_FALSE; |
221 | } |
222 | #else |
223 | if (!SDL_GetHintBoolean(SDL_HINT_VIDEO_X11_XRANDR, SDL_TRUE)) { |
224 | #ifdef X11MODES_DEBUG |
225 | printf("XRandR disabled due to hint\n" ); |
226 | #endif |
227 | return SDL_FALSE; |
228 | } |
229 | #endif /* XRANDR_ENABLED_BY_DEFAULT */ |
230 | |
231 | if (!SDL_X11_HAVE_XRANDR) { |
232 | #ifdef X11MODES_DEBUG |
233 | printf("XRandR support not available\n" ); |
234 | #endif |
235 | return SDL_FALSE; |
236 | } |
237 | |
238 | /* Query the extension version */ |
239 | *major = 1; *minor = 3; /* we want 1.3 */ |
240 | if (!X11_XRRQueryVersion(display, major, minor)) { |
241 | #ifdef X11MODES_DEBUG |
242 | printf("XRandR not active on the display\n" ); |
243 | #endif |
244 | *major = *minor = 0; |
245 | return SDL_FALSE; |
246 | } |
247 | #ifdef X11MODES_DEBUG |
248 | printf("XRandR available at version %d.%d!\n" , *major, *minor); |
249 | #endif |
250 | return SDL_TRUE; |
251 | } |
252 | |
253 | #define XRANDR_ROTATION_LEFT (1 << 1) |
254 | #define XRANDR_ROTATION_RIGHT (1 << 3) |
255 | |
256 | static int |
257 | CalculateXRandRRefreshRate(const XRRModeInfo *info) |
258 | { |
259 | return (info->hTotal && info->vTotal) ? |
260 | SDL_round(((double)info->dotClock / (double)(info->hTotal * info->vTotal))) : 0; |
261 | } |
262 | |
263 | static SDL_bool |
264 | SetXRandRModeInfo(Display *display, XRRScreenResources *res, RRCrtc crtc, |
265 | RRMode modeID, SDL_DisplayMode *mode) |
266 | { |
267 | int i; |
268 | for (i = 0; i < res->nmode; ++i) { |
269 | const XRRModeInfo *info = &res->modes[i]; |
270 | if (info->id == modeID) { |
271 | XRRCrtcInfo *crtcinfo; |
272 | Rotation rotation = 0; |
273 | |
274 | crtcinfo = X11_XRRGetCrtcInfo(display, res, crtc); |
275 | if (crtcinfo) { |
276 | rotation = crtcinfo->rotation; |
277 | X11_XRRFreeCrtcInfo(crtcinfo); |
278 | } |
279 | |
280 | if (rotation & (XRANDR_ROTATION_LEFT|XRANDR_ROTATION_RIGHT)) { |
281 | mode->w = info->height; |
282 | mode->h = info->width; |
283 | } else { |
284 | mode->w = info->width; |
285 | mode->h = info->height; |
286 | } |
287 | mode->refresh_rate = CalculateXRandRRefreshRate(info); |
288 | ((SDL_DisplayModeData*)mode->driverdata)->xrandr_mode = modeID; |
289 | #ifdef X11MODES_DEBUG |
290 | printf("XRandR mode %d: %dx%d@%dHz\n" , (int) modeID, mode->w, mode->h, mode->refresh_rate); |
291 | #endif |
292 | return SDL_TRUE; |
293 | } |
294 | } |
295 | return SDL_FALSE; |
296 | } |
297 | |
298 | static void |
299 | SetXRandRDisplayName(Display *dpy, Atom EDID, char *name, const size_t namelen, RROutput output, const unsigned long widthmm, const unsigned long heightmm) |
300 | { |
301 | /* See if we can get the EDID data for the real monitor name */ |
302 | int inches; |
303 | int nprop; |
304 | Atom *props = X11_XRRListOutputProperties(dpy, output, &nprop); |
305 | int i; |
306 | |
307 | for (i = 0; i < nprop; ++i) { |
308 | unsigned char *prop; |
309 | int actual_format; |
310 | unsigned long nitems, bytes_after; |
311 | Atom actual_type; |
312 | |
313 | if (props[i] == EDID) { |
314 | if (X11_XRRGetOutputProperty(dpy, output, props[i], 0, 100, False, |
315 | False, AnyPropertyType, &actual_type, |
316 | &actual_format, &nitems, &bytes_after, |
317 | &prop) == Success) { |
318 | MonitorInfo *info = decode_edid(prop); |
319 | if (info) { |
320 | #ifdef X11MODES_DEBUG |
321 | printf("Found EDID data for %s\n" , name); |
322 | dump_monitor_info(info); |
323 | #endif |
324 | SDL_strlcpy(name, info->dsc_product_name, namelen); |
325 | free(info); |
326 | } |
327 | X11_XFree(prop); |
328 | } |
329 | break; |
330 | } |
331 | } |
332 | |
333 | if (props) { |
334 | X11_XFree(props); |
335 | } |
336 | |
337 | inches = (int)((SDL_sqrtf(widthmm * widthmm + heightmm * heightmm) / 25.4f) + 0.5f); |
338 | if (*name && inches) { |
339 | const size_t len = SDL_strlen(name); |
340 | SDL_snprintf(&name[len], namelen-len, " %d\"" , inches); |
341 | } |
342 | |
343 | #ifdef X11MODES_DEBUG |
344 | printf("Display name: %s\n" , name); |
345 | #endif |
346 | } |
347 | |
348 | |
349 | static int |
350 | X11_InitModes_XRandR(_THIS) |
351 | { |
352 | SDL_VideoData *data = (SDL_VideoData *) _this->driverdata; |
353 | Display *dpy = data->display; |
354 | const int screencount = ScreenCount(dpy); |
355 | const int default_screen = DefaultScreen(dpy); |
356 | RROutput primary = X11_XRRGetOutputPrimary(dpy, RootWindow(dpy, default_screen)); |
357 | Atom EDID = X11_XInternAtom(dpy, "EDID" , False); |
358 | XRRScreenResources *res = NULL; |
359 | Uint32 pixelformat; |
360 | XVisualInfo vinfo; |
361 | XPixmapFormatValues *pixmapformats; |
362 | int looking_for_primary; |
363 | int scanline_pad; |
364 | int output; |
365 | int screen, i, n; |
366 | |
367 | for (looking_for_primary = 1; looking_for_primary >= 0; looking_for_primary--) { |
368 | for (screen = 0; screen < screencount; screen++) { |
369 | |
370 | /* we want the primary output first, and then skipped later. */ |
371 | if (looking_for_primary && (screen != default_screen)) { |
372 | continue; |
373 | } |
374 | |
375 | if (get_visualinfo(dpy, screen, &vinfo) < 0) { |
376 | continue; /* uh, skip this screen? */ |
377 | } |
378 | |
379 | pixelformat = X11_GetPixelFormatFromVisualInfo(dpy, &vinfo); |
380 | if (SDL_ISPIXELFORMAT_INDEXED(pixelformat)) { |
381 | continue; /* Palettized video modes are no longer supported */ |
382 | } |
383 | |
384 | scanline_pad = SDL_BYTESPERPIXEL(pixelformat) * 8; |
385 | pixmapformats = X11_XListPixmapFormats(dpy, &n); |
386 | if (pixmapformats) { |
387 | for (i = 0; i < n; ++i) { |
388 | if (pixmapformats[i].depth == vinfo.depth) { |
389 | scanline_pad = pixmapformats[i].scanline_pad; |
390 | break; |
391 | } |
392 | } |
393 | X11_XFree(pixmapformats); |
394 | } |
395 | |
396 | res = X11_XRRGetScreenResourcesCurrent(dpy, RootWindow(dpy, screen)); |
397 | if (!res || res->noutput == 0) { |
398 | if (res) { |
399 | X11_XRRFreeScreenResources(res); |
400 | } |
401 | |
402 | res = X11_XRRGetScreenResources(dpy, RootWindow(dpy, screen)); |
403 | if (!res) { |
404 | continue; |
405 | } |
406 | } |
407 | |
408 | for (output = 0; output < res->noutput; output++) { |
409 | XRROutputInfo *output_info; |
410 | int display_x, display_y; |
411 | unsigned long display_mm_width, display_mm_height; |
412 | SDL_DisplayData *displaydata; |
413 | char display_name[128]; |
414 | SDL_DisplayMode mode; |
415 | SDL_DisplayModeData *modedata; |
416 | SDL_VideoDisplay display; |
417 | RRMode modeID; |
418 | RRCrtc output_crtc; |
419 | XRRCrtcInfo *crtc; |
420 | |
421 | /* The primary output _should_ always be sorted first, but just in case... */ |
422 | if ((looking_for_primary && (res->outputs[output] != primary)) || |
423 | (!looking_for_primary && (screen == default_screen) && (res->outputs[output] == primary))) { |
424 | continue; |
425 | } |
426 | |
427 | output_info = X11_XRRGetOutputInfo(dpy, res, res->outputs[output]); |
428 | if (!output_info || !output_info->crtc || output_info->connection == RR_Disconnected) { |
429 | X11_XRRFreeOutputInfo(output_info); |
430 | continue; |
431 | } |
432 | |
433 | SDL_strlcpy(display_name, output_info->name, sizeof(display_name)); |
434 | display_mm_width = output_info->mm_width; |
435 | display_mm_height = output_info->mm_height; |
436 | output_crtc = output_info->crtc; |
437 | X11_XRRFreeOutputInfo(output_info); |
438 | |
439 | crtc = X11_XRRGetCrtcInfo(dpy, res, output_crtc); |
440 | if (!crtc) { |
441 | continue; |
442 | } |
443 | |
444 | SDL_zero(mode); |
445 | modeID = crtc->mode; |
446 | mode.w = crtc->width; |
447 | mode.h = crtc->height; |
448 | mode.format = pixelformat; |
449 | |
450 | display_x = crtc->x; |
451 | display_y = crtc->y; |
452 | |
453 | X11_XRRFreeCrtcInfo(crtc); |
454 | |
455 | displaydata = (SDL_DisplayData *) SDL_calloc(1, sizeof(*displaydata)); |
456 | if (!displaydata) { |
457 | return SDL_OutOfMemory(); |
458 | } |
459 | |
460 | modedata = (SDL_DisplayModeData *) SDL_calloc(1, sizeof(SDL_DisplayModeData)); |
461 | if (!modedata) { |
462 | SDL_free(displaydata); |
463 | return SDL_OutOfMemory(); |
464 | } |
465 | modedata->xrandr_mode = modeID; |
466 | mode.driverdata = modedata; |
467 | |
468 | displaydata->screen = screen; |
469 | displaydata->visual = vinfo.visual; |
470 | displaydata->depth = vinfo.depth; |
471 | displaydata->hdpi = display_mm_width ? (((float) mode.w) * 25.4f / display_mm_width) : 0.0f; |
472 | displaydata->vdpi = display_mm_height ? (((float) mode.h) * 25.4f / display_mm_height) : 0.0f; |
473 | displaydata->ddpi = SDL_ComputeDiagonalDPI(mode.w, mode.h, ((float) display_mm_width) / 25.4f,((float) display_mm_height) / 25.4f); |
474 | displaydata->scanline_pad = scanline_pad; |
475 | displaydata->x = display_x; |
476 | displaydata->y = display_y; |
477 | displaydata->use_xrandr = 1; |
478 | displaydata->xrandr_output = res->outputs[output]; |
479 | |
480 | SetXRandRModeInfo(dpy, res, output_crtc, modeID, &mode); |
481 | SetXRandRDisplayName(dpy, EDID, display_name, sizeof (display_name), res->outputs[output], display_mm_width, display_mm_height); |
482 | |
483 | SDL_zero(display); |
484 | if (*display_name) { |
485 | display.name = display_name; |
486 | } |
487 | display.desktop_mode = mode; |
488 | display.current_mode = mode; |
489 | display.driverdata = displaydata; |
490 | SDL_AddVideoDisplay(&display, SDL_FALSE); |
491 | } |
492 | |
493 | X11_XRRFreeScreenResources(res); |
494 | } |
495 | } |
496 | |
497 | if (_this->num_displays == 0) { |
498 | return SDL_SetError("No available displays" ); |
499 | } |
500 | |
501 | return 0; |
502 | } |
503 | #endif /* SDL_VIDEO_DRIVER_X11_XRANDR */ |
504 | |
505 | #if SDL_VIDEO_DRIVER_X11_XVIDMODE |
506 | static SDL_bool |
507 | CheckVidMode(Display * display, int *major, int *minor) |
508 | { |
509 | int vm_event, vm_error = -1; |
510 | /* Default the extension not available */ |
511 | *major = *minor = 0; |
512 | |
513 | /* Allow environment override */ |
514 | if (!SDL_GetHintBoolean(SDL_HINT_VIDEO_X11_XVIDMODE, SDL_TRUE)) { |
515 | #ifdef X11MODES_DEBUG |
516 | printf("XVidMode disabled due to hint\n" ); |
517 | #endif |
518 | return SDL_FALSE; |
519 | } |
520 | |
521 | if (!SDL_X11_HAVE_XVIDMODE) { |
522 | #ifdef X11MODES_DEBUG |
523 | printf("XVidMode support not available\n" ); |
524 | #endif |
525 | return SDL_FALSE; |
526 | } |
527 | |
528 | /* Query the extension version */ |
529 | if (!X11_XF86VidModeQueryExtension(display, &vm_event, &vm_error) |
530 | || !X11_XF86VidModeQueryVersion(display, major, minor)) { |
531 | #ifdef X11MODES_DEBUG |
532 | printf("XVidMode not active on the display\n" ); |
533 | #endif |
534 | return SDL_FALSE; |
535 | } |
536 | #ifdef X11MODES_DEBUG |
537 | printf("XVidMode available at version %d.%d!\n" , *major, *minor); |
538 | #endif |
539 | return SDL_TRUE; |
540 | } |
541 | |
542 | static |
543 | Bool XF86VidModeGetModeInfo(Display * dpy, int scr, |
544 | XF86VidModeModeInfo* info) |
545 | { |
546 | Bool retval; |
547 | int dotclock; |
548 | XF86VidModeModeLine l; |
549 | SDL_zerop(info); |
550 | SDL_zero(l); |
551 | retval = X11_XF86VidModeGetModeLine(dpy, scr, &dotclock, &l); |
552 | info->dotclock = dotclock; |
553 | info->hdisplay = l.hdisplay; |
554 | info->hsyncstart = l.hsyncstart; |
555 | info->hsyncend = l.hsyncend; |
556 | info->htotal = l.htotal; |
557 | info->hskew = l.hskew; |
558 | info->vdisplay = l.vdisplay; |
559 | info->vsyncstart = l.vsyncstart; |
560 | info->vsyncend = l.vsyncend; |
561 | info->vtotal = l.vtotal; |
562 | info->flags = l.flags; |
563 | info->privsize = l.privsize; |
564 | info->private = l.private; |
565 | return retval; |
566 | } |
567 | |
568 | static int |
569 | CalculateXVidModeRefreshRate(const XF86VidModeModeInfo * info) |
570 | { |
571 | return (info->htotal |
572 | && info->vtotal) ? (1000 * info->dotclock / (info->htotal * |
573 | info->vtotal)) : 0; |
574 | } |
575 | |
576 | static SDL_bool |
577 | SetXVidModeModeInfo(const XF86VidModeModeInfo *info, SDL_DisplayMode *mode) |
578 | { |
579 | mode->w = info->hdisplay; |
580 | mode->h = info->vdisplay; |
581 | mode->refresh_rate = CalculateXVidModeRefreshRate(info); |
582 | ((SDL_DisplayModeData*)mode->driverdata)->vm_mode = *info; |
583 | return SDL_TRUE; |
584 | } |
585 | #endif /* SDL_VIDEO_DRIVER_X11_XVIDMODE */ |
586 | |
587 | int |
588 | X11_InitModes(_THIS) |
589 | { |
590 | SDL_VideoData *data = (SDL_VideoData *) _this->driverdata; |
591 | int snum, screen, screencount = 0; |
592 | #if SDL_VIDEO_DRIVER_X11_XINERAMA |
593 | int xinerama_major, xinerama_minor; |
594 | int use_xinerama = 0; |
595 | XineramaScreenInfo *xinerama = NULL; |
596 | #endif |
597 | #if SDL_VIDEO_DRIVER_X11_XRANDR |
598 | int xrandr_major, xrandr_minor; |
599 | #endif |
600 | #if SDL_VIDEO_DRIVER_X11_XVIDMODE |
601 | int vm_major, vm_minor; |
602 | int use_vidmode = 0; |
603 | #endif |
604 | |
605 | /* XRandR is the One True Modern Way to do this on X11. If it's enabled and |
606 | available, don't even look at other ways of doing things. */ |
607 | #if SDL_VIDEO_DRIVER_X11_XRANDR |
608 | /* require at least XRandR v1.3 */ |
609 | if (CheckXRandR(data->display, &xrandr_major, &xrandr_minor) && |
610 | (xrandr_major >= 2 || (xrandr_major == 1 && xrandr_minor >= 3))) { |
611 | if (X11_InitModes_XRandR(_this) == 0) |
612 | return 0; |
613 | } |
614 | #endif /* SDL_VIDEO_DRIVER_X11_XRANDR */ |
615 | |
616 | /* !!! FIXME: eventually remove support for Xinerama and XVidMode (everything below here). */ |
617 | |
618 | /* This is a workaround for some apps (UnrealEngine4, for example) until |
619 | we sort out the ramifications of removing XVidMode support outright. |
620 | This block should be removed with the XVidMode support. */ |
621 | { |
622 | if (SDL_GetHintBoolean("SDL_VIDEO_X11_REQUIRE_XRANDR" , SDL_FALSE)) { |
623 | #if SDL_VIDEO_DRIVER_X11_XRANDR |
624 | return SDL_SetError("XRandR support is required but not available" ); |
625 | #else |
626 | return SDL_SetError("XRandR support is required but not built into SDL!" ); |
627 | #endif |
628 | } |
629 | } |
630 | |
631 | #if SDL_VIDEO_DRIVER_X11_XINERAMA |
632 | /* Query Xinerama extention |
633 | * NOTE: This works with Nvidia Twinview correctly, but you need version 302.17 (released on June 2012) |
634 | * or newer of the Nvidia binary drivers |
635 | */ |
636 | if (CheckXinerama(data->display, &xinerama_major, &xinerama_minor)) { |
637 | int (*handler) (Display *, XErrorEvent *); |
638 | X11_XSync(data->display, False); |
639 | handler = X11_XSetErrorHandler(X11_XineramaFailed); |
640 | xinerama = X11_XineramaQueryScreens(data->display, &screencount); |
641 | X11_XSync(data->display, False); |
642 | X11_XSetErrorHandler(handler); |
643 | if (xinerama_triggered_error) { |
644 | xinerama = 0; |
645 | } |
646 | if (xinerama) { |
647 | use_xinerama = xinerama_major * 100 + xinerama_minor; |
648 | } |
649 | } |
650 | if (!xinerama) { |
651 | screencount = ScreenCount(data->display); |
652 | } |
653 | #else |
654 | screencount = ScreenCount(data->display); |
655 | #endif /* SDL_VIDEO_DRIVER_X11_XINERAMA */ |
656 | |
657 | #if SDL_VIDEO_DRIVER_X11_XVIDMODE |
658 | if (CheckVidMode(data->display, &vm_major, &vm_minor)) { |
659 | use_vidmode = vm_major * 100 + vm_minor; |
660 | } |
661 | #endif /* SDL_VIDEO_DRIVER_X11_XVIDMODE */ |
662 | |
663 | for (snum = 0; snum < screencount; ++snum) { |
664 | XVisualInfo vinfo; |
665 | SDL_VideoDisplay display; |
666 | SDL_DisplayData *displaydata; |
667 | SDL_DisplayMode mode; |
668 | SDL_DisplayModeData *modedata; |
669 | XPixmapFormatValues *pixmapFormats; |
670 | char display_name[128]; |
671 | int i, n; |
672 | |
673 | /* Re-order screens to always put default screen first */ |
674 | if (snum == 0) { |
675 | screen = DefaultScreen(data->display); |
676 | } else if (snum == DefaultScreen(data->display)) { |
677 | screen = 0; |
678 | } else { |
679 | screen = snum; |
680 | } |
681 | |
682 | #if SDL_VIDEO_DRIVER_X11_XINERAMA |
683 | if (xinerama) { |
684 | if (get_visualinfo(data->display, 0, &vinfo) < 0) { |
685 | continue; |
686 | } |
687 | } else { |
688 | if (get_visualinfo(data->display, screen, &vinfo) < 0) { |
689 | continue; |
690 | } |
691 | } |
692 | #else |
693 | if (get_visualinfo(data->display, screen, &vinfo) < 0) { |
694 | continue; |
695 | } |
696 | #endif |
697 | |
698 | displaydata = (SDL_DisplayData *) SDL_calloc(1, sizeof(*displaydata)); |
699 | if (!displaydata) { |
700 | continue; |
701 | } |
702 | display_name[0] = '\0'; |
703 | |
704 | mode.format = X11_GetPixelFormatFromVisualInfo(data->display, &vinfo); |
705 | if (SDL_ISPIXELFORMAT_INDEXED(mode.format)) { |
706 | /* We don't support palettized modes now */ |
707 | SDL_free(displaydata); |
708 | continue; |
709 | } |
710 | #if SDL_VIDEO_DRIVER_X11_XINERAMA |
711 | if (xinerama) { |
712 | mode.w = xinerama[screen].width; |
713 | mode.h = xinerama[screen].height; |
714 | } else { |
715 | mode.w = DisplayWidth(data->display, screen); |
716 | mode.h = DisplayHeight(data->display, screen); |
717 | } |
718 | #else |
719 | mode.w = DisplayWidth(data->display, screen); |
720 | mode.h = DisplayHeight(data->display, screen); |
721 | #endif |
722 | mode.refresh_rate = 0; |
723 | |
724 | modedata = (SDL_DisplayModeData *) SDL_calloc(1, sizeof(SDL_DisplayModeData)); |
725 | if (!modedata) { |
726 | SDL_free(displaydata); |
727 | continue; |
728 | } |
729 | mode.driverdata = modedata; |
730 | |
731 | #if SDL_VIDEO_DRIVER_X11_XINERAMA |
732 | /* Most of SDL's calls to X11 are unwaware of Xinerama, and to X11 standard calls, when Xinerama is active, |
733 | * there's only one screen available. So we force the screen number to zero and |
734 | * let Xinerama specific code handle specific functionality using displaydata->xinerama_info |
735 | */ |
736 | if (use_xinerama) { |
737 | displaydata->screen = 0; |
738 | displaydata->use_xinerama = use_xinerama; |
739 | displaydata->xinerama_info = xinerama[screen]; |
740 | displaydata->xinerama_screen = screen; |
741 | } |
742 | else displaydata->screen = screen; |
743 | #else |
744 | displaydata->screen = screen; |
745 | #endif |
746 | displaydata->visual = vinfo.visual; |
747 | displaydata->depth = vinfo.depth; |
748 | |
749 | /* We use the displaydata screen index here so that this works |
750 | for both the Xinerama case, where we get the overall DPI, |
751 | and the regular X11 screen info case. */ |
752 | displaydata->hdpi = (float)DisplayWidth(data->display, displaydata->screen) * 25.4f / |
753 | DisplayWidthMM(data->display, displaydata->screen); |
754 | displaydata->vdpi = (float)DisplayHeight(data->display, displaydata->screen) * 25.4f / |
755 | DisplayHeightMM(data->display, displaydata->screen); |
756 | displaydata->ddpi = SDL_ComputeDiagonalDPI(DisplayWidth(data->display, displaydata->screen), |
757 | DisplayHeight(data->display, displaydata->screen), |
758 | (float)DisplayWidthMM(data->display, displaydata->screen) / 25.4f, |
759 | (float)DisplayHeightMM(data->display, displaydata->screen) / 25.4f); |
760 | |
761 | displaydata->scanline_pad = SDL_BYTESPERPIXEL(mode.format) * 8; |
762 | pixmapFormats = X11_XListPixmapFormats(data->display, &n); |
763 | if (pixmapFormats) { |
764 | for (i = 0; i < n; ++i) { |
765 | if (pixmapFormats[i].depth == displaydata->depth) { |
766 | displaydata->scanline_pad = pixmapFormats[i].scanline_pad; |
767 | break; |
768 | } |
769 | } |
770 | X11_XFree(pixmapFormats); |
771 | } |
772 | |
773 | #if SDL_VIDEO_DRIVER_X11_XINERAMA |
774 | if (use_xinerama) { |
775 | displaydata->x = xinerama[screen].x_org; |
776 | displaydata->y = xinerama[screen].y_org; |
777 | } |
778 | else |
779 | #endif |
780 | { |
781 | displaydata->x = 0; |
782 | displaydata->y = 0; |
783 | } |
784 | |
785 | #if SDL_VIDEO_DRIVER_X11_XVIDMODE |
786 | if (!displaydata->use_xrandr && |
787 | #if SDL_VIDEO_DRIVER_X11_XINERAMA |
788 | /* XVidMode only works on the screen at the origin */ |
789 | (!displaydata->use_xinerama || |
790 | (displaydata->x == 0 && displaydata->y == 0)) && |
791 | #endif |
792 | use_vidmode) { |
793 | displaydata->use_vidmode = use_vidmode; |
794 | if (displaydata->use_xinerama) { |
795 | displaydata->vidmode_screen = 0; |
796 | } else { |
797 | displaydata->vidmode_screen = screen; |
798 | } |
799 | XF86VidModeGetModeInfo(data->display, displaydata->vidmode_screen, &modedata->vm_mode); |
800 | } |
801 | #endif /* SDL_VIDEO_DRIVER_X11_XVIDMODE */ |
802 | |
803 | SDL_zero(display); |
804 | if (*display_name) { |
805 | display.name = display_name; |
806 | } |
807 | display.desktop_mode = mode; |
808 | display.current_mode = mode; |
809 | display.driverdata = displaydata; |
810 | SDL_AddVideoDisplay(&display, SDL_FALSE); |
811 | } |
812 | |
813 | #if SDL_VIDEO_DRIVER_X11_XINERAMA |
814 | if (xinerama) X11_XFree(xinerama); |
815 | #endif |
816 | |
817 | if (_this->num_displays == 0) { |
818 | return SDL_SetError("No available displays" ); |
819 | } |
820 | return 0; |
821 | } |
822 | |
823 | void |
824 | X11_GetDisplayModes(_THIS, SDL_VideoDisplay * sdl_display) |
825 | { |
826 | Display *display = ((SDL_VideoData *) _this->driverdata)->display; |
827 | SDL_DisplayData *data = (SDL_DisplayData *) sdl_display->driverdata; |
828 | #if SDL_VIDEO_DRIVER_X11_XVIDMODE |
829 | int nmodes; |
830 | XF86VidModeModeInfo ** modes; |
831 | #endif |
832 | SDL_DisplayMode mode; |
833 | |
834 | /* Unfortunately X11 requires the window to be created with the correct |
835 | * visual and depth ahead of time, but the SDL API allows you to create |
836 | * a window before setting the fullscreen display mode. This means that |
837 | * we have to use the same format for all windows and all display modes. |
838 | * (or support recreating the window with a new visual behind the scenes) |
839 | */ |
840 | mode.format = sdl_display->current_mode.format; |
841 | mode.driverdata = NULL; |
842 | |
843 | #if SDL_VIDEO_DRIVER_X11_XINERAMA |
844 | if (data->use_xinerama) { |
845 | int screen_w; |
846 | int screen_h; |
847 | |
848 | screen_w = DisplayWidth(display, data->screen); |
849 | screen_h = DisplayHeight(display, data->screen); |
850 | |
851 | if (data->use_vidmode && !data->xinerama_info.x_org && !data->xinerama_info.y_org && |
852 | (screen_w > data->xinerama_info.width || screen_h > data->xinerama_info.height)) { |
853 | SDL_DisplayModeData *modedata; |
854 | /* Add the full (both screens combined) xinerama mode only on the display that starts at 0,0 |
855 | * if we're using vidmode. |
856 | */ |
857 | mode.w = screen_w; |
858 | mode.h = screen_h; |
859 | mode.refresh_rate = 0; |
860 | modedata = (SDL_DisplayModeData *) SDL_calloc(1, sizeof(SDL_DisplayModeData)); |
861 | if (modedata) { |
862 | *modedata = *(SDL_DisplayModeData *)sdl_display->desktop_mode.driverdata; |
863 | } |
864 | mode.driverdata = modedata; |
865 | if (!SDL_AddDisplayMode(sdl_display, &mode)) { |
866 | SDL_free(modedata); |
867 | } |
868 | } |
869 | else if (!data->use_xrandr) |
870 | { |
871 | SDL_DisplayModeData *modedata; |
872 | /* Add the current mode of each monitor otherwise if we can't get them from xrandr */ |
873 | mode.w = data->xinerama_info.width; |
874 | mode.h = data->xinerama_info.height; |
875 | mode.refresh_rate = 0; |
876 | modedata = (SDL_DisplayModeData *) SDL_calloc(1, sizeof(SDL_DisplayModeData)); |
877 | if (modedata) { |
878 | *modedata = *(SDL_DisplayModeData *)sdl_display->desktop_mode.driverdata; |
879 | } |
880 | mode.driverdata = modedata; |
881 | if (!SDL_AddDisplayMode(sdl_display, &mode)) { |
882 | SDL_free(modedata); |
883 | } |
884 | } |
885 | |
886 | } |
887 | #endif /* SDL_VIDEO_DRIVER_X11_XINERAMA */ |
888 | |
889 | #if SDL_VIDEO_DRIVER_X11_XRANDR |
890 | if (data->use_xrandr) { |
891 | XRRScreenResources *res; |
892 | |
893 | res = X11_XRRGetScreenResources (display, RootWindow(display, data->screen)); |
894 | if (res) { |
895 | SDL_DisplayModeData *modedata; |
896 | XRROutputInfo *output_info; |
897 | int i; |
898 | |
899 | output_info = X11_XRRGetOutputInfo(display, res, data->xrandr_output); |
900 | if (output_info && output_info->connection != RR_Disconnected) { |
901 | for (i = 0; i < output_info->nmode; ++i) { |
902 | modedata = (SDL_DisplayModeData *) SDL_calloc(1, sizeof(SDL_DisplayModeData)); |
903 | if (!modedata) { |
904 | continue; |
905 | } |
906 | mode.driverdata = modedata; |
907 | |
908 | if (!SetXRandRModeInfo(display, res, output_info->crtc, output_info->modes[i], &mode) || |
909 | !SDL_AddDisplayMode(sdl_display, &mode)) { |
910 | SDL_free(modedata); |
911 | } |
912 | } |
913 | } |
914 | X11_XRRFreeOutputInfo(output_info); |
915 | X11_XRRFreeScreenResources(res); |
916 | } |
917 | return; |
918 | } |
919 | #endif /* SDL_VIDEO_DRIVER_X11_XRANDR */ |
920 | |
921 | #if SDL_VIDEO_DRIVER_X11_XVIDMODE |
922 | if (data->use_vidmode && |
923 | X11_XF86VidModeGetAllModeLines(display, data->vidmode_screen, &nmodes, &modes)) { |
924 | int i; |
925 | SDL_DisplayModeData *modedata; |
926 | |
927 | #ifdef X11MODES_DEBUG |
928 | printf("VidMode modes: (unsorted)\n" ); |
929 | for (i = 0; i < nmodes; ++i) { |
930 | printf("Mode %d: %d x %d @ %d, flags: 0x%x\n" , i, |
931 | modes[i]->hdisplay, modes[i]->vdisplay, |
932 | CalculateXVidModeRefreshRate(modes[i]), modes[i]->flags); |
933 | } |
934 | #endif |
935 | for (i = 0; i < nmodes; ++i) { |
936 | modedata = (SDL_DisplayModeData *) SDL_calloc(1, sizeof(SDL_DisplayModeData)); |
937 | if (!modedata) { |
938 | continue; |
939 | } |
940 | mode.driverdata = modedata; |
941 | |
942 | if (!SetXVidModeModeInfo(modes[i], &mode) || !SDL_AddDisplayMode(sdl_display, &mode)) { |
943 | SDL_free(modedata); |
944 | } |
945 | } |
946 | X11_XFree(modes); |
947 | return; |
948 | } |
949 | #endif /* SDL_VIDEO_DRIVER_X11_XVIDMODE */ |
950 | |
951 | if (!data->use_xrandr && !data->use_vidmode) { |
952 | SDL_DisplayModeData *modedata; |
953 | /* Add the desktop mode */ |
954 | mode = sdl_display->desktop_mode; |
955 | modedata = (SDL_DisplayModeData *) SDL_calloc(1, sizeof(SDL_DisplayModeData)); |
956 | if (modedata) { |
957 | *modedata = *(SDL_DisplayModeData *)sdl_display->desktop_mode.driverdata; |
958 | } |
959 | mode.driverdata = modedata; |
960 | if (!SDL_AddDisplayMode(sdl_display, &mode)) { |
961 | SDL_free(modedata); |
962 | } |
963 | } |
964 | } |
965 | |
966 | int |
967 | X11_SetDisplayMode(_THIS, SDL_VideoDisplay * sdl_display, SDL_DisplayMode * mode) |
968 | { |
969 | SDL_VideoData *viddata = (SDL_VideoData *) _this->driverdata; |
970 | Display *display = viddata->display; |
971 | SDL_DisplayData *data = (SDL_DisplayData *) sdl_display->driverdata; |
972 | SDL_DisplayModeData *modedata = (SDL_DisplayModeData *)mode->driverdata; |
973 | |
974 | viddata->last_mode_change_deadline = SDL_GetTicks() + (PENDING_FOCUS_TIME * 2); |
975 | |
976 | #if SDL_VIDEO_DRIVER_X11_XRANDR |
977 | if (data->use_xrandr) { |
978 | XRRScreenResources *res; |
979 | XRROutputInfo *output_info; |
980 | XRRCrtcInfo *crtc; |
981 | Status status; |
982 | |
983 | res = X11_XRRGetScreenResources (display, RootWindow(display, data->screen)); |
984 | if (!res) { |
985 | return SDL_SetError("Couldn't get XRandR screen resources" ); |
986 | } |
987 | |
988 | output_info = X11_XRRGetOutputInfo(display, res, data->xrandr_output); |
989 | if (!output_info || output_info->connection == RR_Disconnected) { |
990 | X11_XRRFreeScreenResources(res); |
991 | return SDL_SetError("Couldn't get XRandR output info" ); |
992 | } |
993 | |
994 | crtc = X11_XRRGetCrtcInfo(display, res, output_info->crtc); |
995 | if (!crtc) { |
996 | X11_XRRFreeOutputInfo(output_info); |
997 | X11_XRRFreeScreenResources(res); |
998 | return SDL_SetError("Couldn't get XRandR crtc info" ); |
999 | } |
1000 | |
1001 | status = X11_XRRSetCrtcConfig (display, res, output_info->crtc, CurrentTime, |
1002 | crtc->x, crtc->y, modedata->xrandr_mode, crtc->rotation, |
1003 | &data->xrandr_output, 1); |
1004 | |
1005 | X11_XRRFreeCrtcInfo(crtc); |
1006 | X11_XRRFreeOutputInfo(output_info); |
1007 | X11_XRRFreeScreenResources(res); |
1008 | |
1009 | if (status != Success) { |
1010 | return SDL_SetError("X11_XRRSetCrtcConfig failed" ); |
1011 | } |
1012 | } |
1013 | #endif /* SDL_VIDEO_DRIVER_X11_XRANDR */ |
1014 | |
1015 | #if SDL_VIDEO_DRIVER_X11_XVIDMODE |
1016 | if (data->use_vidmode) { |
1017 | X11_XF86VidModeSwitchToMode(display, data->vidmode_screen, &modedata->vm_mode); |
1018 | } |
1019 | #endif /* SDL_VIDEO_DRIVER_X11_XVIDMODE */ |
1020 | |
1021 | return 0; |
1022 | } |
1023 | |
1024 | void |
1025 | X11_QuitModes(_THIS) |
1026 | { |
1027 | } |
1028 | |
1029 | int |
1030 | X11_GetDisplayBounds(_THIS, SDL_VideoDisplay * sdl_display, SDL_Rect * rect) |
1031 | { |
1032 | SDL_DisplayData *data = (SDL_DisplayData *) sdl_display->driverdata; |
1033 | |
1034 | rect->x = data->x; |
1035 | rect->y = data->y; |
1036 | rect->w = sdl_display->current_mode.w; |
1037 | rect->h = sdl_display->current_mode.h; |
1038 | |
1039 | #if SDL_VIDEO_DRIVER_X11_XINERAMA |
1040 | /* Get the real current bounds of the display */ |
1041 | if (data->use_xinerama) { |
1042 | Display *display = ((SDL_VideoData *) _this->driverdata)->display; |
1043 | int screencount; |
1044 | XineramaScreenInfo *xinerama = X11_XineramaQueryScreens(display, &screencount); |
1045 | if (xinerama) { |
1046 | rect->x = xinerama[data->xinerama_screen].x_org; |
1047 | rect->y = xinerama[data->xinerama_screen].y_org; |
1048 | X11_XFree(xinerama); |
1049 | } |
1050 | } |
1051 | #endif /* SDL_VIDEO_DRIVER_X11_XINERAMA */ |
1052 | return 0; |
1053 | } |
1054 | |
1055 | int |
1056 | X11_GetDisplayDPI(_THIS, SDL_VideoDisplay * sdl_display, float * ddpi, float * hdpi, float * vdpi) |
1057 | { |
1058 | SDL_DisplayData *data = (SDL_DisplayData *) sdl_display->driverdata; |
1059 | |
1060 | if (ddpi) { |
1061 | *ddpi = data->ddpi; |
1062 | } |
1063 | if (hdpi) { |
1064 | *hdpi = data->hdpi; |
1065 | } |
1066 | if (vdpi) { |
1067 | *vdpi = data->vdpi; |
1068 | } |
1069 | |
1070 | return data->ddpi != 0.0f ? 0 : SDL_SetError("Couldn't get DPI" ); |
1071 | } |
1072 | |
1073 | int |
1074 | X11_GetDisplayUsableBounds(_THIS, SDL_VideoDisplay * sdl_display, SDL_Rect * rect) |
1075 | { |
1076 | SDL_VideoData *data = (SDL_VideoData *) _this->driverdata; |
1077 | Display *display = data->display; |
1078 | Atom _NET_WORKAREA; |
1079 | int status, real_format; |
1080 | int retval = -1; |
1081 | Atom real_type; |
1082 | unsigned long items_read = 0, items_left = 0; |
1083 | unsigned char *propdata = NULL; |
1084 | |
1085 | if (X11_GetDisplayBounds(_this, sdl_display, rect) < 0) { |
1086 | return -1; |
1087 | } |
1088 | |
1089 | _NET_WORKAREA = X11_XInternAtom(display, "_NET_WORKAREA" , False); |
1090 | status = X11_XGetWindowProperty(display, DefaultRootWindow(display), |
1091 | _NET_WORKAREA, 0L, 4L, False, XA_CARDINAL, |
1092 | &real_type, &real_format, &items_read, |
1093 | &items_left, &propdata); |
1094 | if ((status == Success) && (items_read >= 4)) { |
1095 | const long *p = (long*) propdata; |
1096 | const SDL_Rect usable = { (int)p[0], (int)p[1], (int)p[2], (int)p[3] }; |
1097 | retval = 0; |
1098 | if (!SDL_IntersectRect(rect, &usable, rect)) { |
1099 | SDL_zerop(rect); |
1100 | } |
1101 | } |
1102 | |
1103 | if (propdata) { |
1104 | X11_XFree(propdata); |
1105 | } |
1106 | |
1107 | return retval; |
1108 | } |
1109 | |
1110 | #endif /* SDL_VIDEO_DRIVER_X11 */ |
1111 | |
1112 | /* vi: set ts=4 sw=4 expandtab: */ |
1113 | |