xref: /third_party/glfw/src/x11_monitor.c (revision b877906b)
1//========================================================================
2// GLFW 3.5 X11 - www.glfw.org
3//------------------------------------------------------------------------
4// Copyright (c) 2002-2006 Marcus Geelnard
5// Copyright (c) 2006-2019 Camilla Löwy <elmindreda@glfw.org>
6//
7// This software is provided 'as-is', without any express or implied
8// warranty. In no event will the authors be held liable for any damages
9// arising from the use of this software.
10//
11// Permission is granted to anyone to use this software for any purpose,
12// including commercial applications, and to alter it and redistribute it
13// freely, subject to the following restrictions:
14//
15// 1. The origin of this software must not be misrepresented; you must not
16//    claim that you wrote the original software. If you use this software
17//    in a product, an acknowledgment in the product documentation would
18//    be appreciated but is not required.
19//
20// 2. Altered source versions must be plainly marked as such, and must not
21//    be misrepresented as being the original software.
22//
23// 3. This notice may not be removed or altered from any source
24//    distribution.
25//
26//========================================================================
27
28#include "internal.h"
29
30#if defined(_GLFW_X11)
31
32#include <limits.h>
33#include <stdlib.h>
34#include <string.h>
35#include <math.h>
36#include <assert.h>
37
38
39// Check whether the display mode should be included in enumeration
40//
41static GLFWbool modeIsGood(const XRRModeInfo* mi)
42{
43    return (mi->modeFlags & RR_Interlace) == 0;
44}
45
46// Calculates the refresh rate, in Hz, from the specified RandR mode info
47//
48static int calculateRefreshRate(const XRRModeInfo* mi)
49{
50    if (mi->hTotal && mi->vTotal)
51        return (int) round((double) mi->dotClock / ((double) mi->hTotal * (double) mi->vTotal));
52    else
53        return 0;
54}
55
56// Returns the mode info for a RandR mode XID
57//
58static const XRRModeInfo* getModeInfo(const XRRScreenResources* sr, RRMode id)
59{
60    for (int i = 0;  i < sr->nmode;  i++)
61    {
62        if (sr->modes[i].id == id)
63            return sr->modes + i;
64    }
65
66    return NULL;
67}
68
69// Convert RandR mode info to GLFW video mode
70//
71static GLFWvidmode vidmodeFromModeInfo(const XRRModeInfo* mi,
72                                       const XRRCrtcInfo* ci)
73{
74    GLFWvidmode mode;
75
76    if (ci->rotation == RR_Rotate_90 || ci->rotation == RR_Rotate_270)
77    {
78        mode.width  = mi->height;
79        mode.height = mi->width;
80    }
81    else
82    {
83        mode.width  = mi->width;
84        mode.height = mi->height;
85    }
86
87    mode.refreshRate = calculateRefreshRate(mi);
88
89    _glfwSplitBPP(DefaultDepth(_glfw.x11.display, _glfw.x11.screen),
90                  &mode.redBits, &mode.greenBits, &mode.blueBits);
91
92    return mode;
93}
94
95
96//////////////////////////////////////////////////////////////////////////
97//////                       GLFW internal API                      //////
98//////////////////////////////////////////////////////////////////////////
99
100// Poll for changes in the set of connected monitors
101//
102void _glfwPollMonitorsX11(void)
103{
104    if (_glfw.x11.randr.available && !_glfw.x11.randr.monitorBroken)
105    {
106        int disconnectedCount, screenCount = 0;
107        _GLFWmonitor** disconnected = NULL;
108        XineramaScreenInfo* screens = NULL;
109        XRRScreenResources* sr = XRRGetScreenResourcesCurrent(_glfw.x11.display,
110                                                              _glfw.x11.root);
111        RROutput primary = XRRGetOutputPrimary(_glfw.x11.display,
112                                               _glfw.x11.root);
113
114        if (_glfw.x11.xinerama.available)
115            screens = XineramaQueryScreens(_glfw.x11.display, &screenCount);
116
117        disconnectedCount = _glfw.monitorCount;
118        if (disconnectedCount)
119        {
120            disconnected = _glfw_calloc(_glfw.monitorCount, sizeof(_GLFWmonitor*));
121            memcpy(disconnected,
122                   _glfw.monitors,
123                   _glfw.monitorCount * sizeof(_GLFWmonitor*));
124        }
125
126        for (int i = 0;  i < sr->noutput;  i++)
127        {
128            int j, type, widthMM, heightMM;
129
130            XRROutputInfo* oi = XRRGetOutputInfo(_glfw.x11.display, sr, sr->outputs[i]);
131            if (oi->connection != RR_Connected || oi->crtc == None)
132            {
133                XRRFreeOutputInfo(oi);
134                continue;
135            }
136
137            for (j = 0;  j < disconnectedCount;  j++)
138            {
139                if (disconnected[j] &&
140                    disconnected[j]->x11.output == sr->outputs[i])
141                {
142                    disconnected[j] = NULL;
143                    break;
144                }
145            }
146
147            if (j < disconnectedCount)
148            {
149                XRRFreeOutputInfo(oi);
150                continue;
151            }
152
153            XRRCrtcInfo* ci = XRRGetCrtcInfo(_glfw.x11.display, sr, oi->crtc);
154            if (ci->rotation == RR_Rotate_90 || ci->rotation == RR_Rotate_270)
155            {
156                widthMM  = oi->mm_height;
157                heightMM = oi->mm_width;
158            }
159            else
160            {
161                widthMM  = oi->mm_width;
162                heightMM = oi->mm_height;
163            }
164
165            if (widthMM <= 0 || heightMM <= 0)
166            {
167                // HACK: If RandR does not provide a physical size, assume the
168                //       X11 default 96 DPI and calculate from the CRTC viewport
169                // NOTE: These members are affected by rotation, unlike the mode
170                //       info and output info members
171                widthMM  = (int) (ci->width * 25.4f / 96.f);
172                heightMM = (int) (ci->height * 25.4f / 96.f);
173            }
174
175            _GLFWmonitor* monitor = _glfwAllocMonitor(oi->name, widthMM, heightMM);
176            monitor->x11.output = sr->outputs[i];
177            monitor->x11.crtc   = oi->crtc;
178
179            for (j = 0;  j < screenCount;  j++)
180            {
181                if (screens[j].x_org == ci->x &&
182                    screens[j].y_org == ci->y &&
183                    screens[j].width == ci->width &&
184                    screens[j].height == ci->height)
185                {
186                    monitor->x11.index = j;
187                    break;
188                }
189            }
190
191            if (monitor->x11.output == primary)
192                type = _GLFW_INSERT_FIRST;
193            else
194                type = _GLFW_INSERT_LAST;
195
196            _glfwInputMonitor(monitor, GLFW_CONNECTED, type);
197
198            XRRFreeOutputInfo(oi);
199            XRRFreeCrtcInfo(ci);
200        }
201
202        XRRFreeScreenResources(sr);
203
204        if (screens)
205            XFree(screens);
206
207        for (int i = 0;  i < disconnectedCount;  i++)
208        {
209            if (disconnected[i])
210                _glfwInputMonitor(disconnected[i], GLFW_DISCONNECTED, 0);
211        }
212
213        _glfw_free(disconnected);
214    }
215    else
216    {
217        const int widthMM = DisplayWidthMM(_glfw.x11.display, _glfw.x11.screen);
218        const int heightMM = DisplayHeightMM(_glfw.x11.display, _glfw.x11.screen);
219
220        _glfwInputMonitor(_glfwAllocMonitor("Display", widthMM, heightMM),
221                          GLFW_CONNECTED,
222                          _GLFW_INSERT_FIRST);
223    }
224}
225
226// Set the current video mode for the specified monitor
227//
228void _glfwSetVideoModeX11(_GLFWmonitor* monitor, const GLFWvidmode* desired)
229{
230    if (_glfw.x11.randr.available && !_glfw.x11.randr.monitorBroken)
231    {
232        GLFWvidmode current;
233        RRMode native = None;
234
235        const GLFWvidmode* best = _glfwChooseVideoMode(monitor, desired);
236        _glfwGetVideoModeX11(monitor, &current);
237        if (_glfwCompareVideoModes(&current, best) == 0)
238            return;
239
240        XRRScreenResources* sr =
241            XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
242        XRRCrtcInfo* ci = XRRGetCrtcInfo(_glfw.x11.display, sr, monitor->x11.crtc);
243        XRROutputInfo* oi = XRRGetOutputInfo(_glfw.x11.display, sr, monitor->x11.output);
244
245        for (int i = 0;  i < oi->nmode;  i++)
246        {
247            const XRRModeInfo* mi = getModeInfo(sr, oi->modes[i]);
248            if (!modeIsGood(mi))
249                continue;
250
251            const GLFWvidmode mode = vidmodeFromModeInfo(mi, ci);
252            if (_glfwCompareVideoModes(best, &mode) == 0)
253            {
254                native = mi->id;
255                break;
256            }
257        }
258
259        if (native)
260        {
261            if (monitor->x11.oldMode == None)
262                monitor->x11.oldMode = ci->mode;
263
264            XRRSetCrtcConfig(_glfw.x11.display,
265                             sr, monitor->x11.crtc,
266                             CurrentTime,
267                             ci->x, ci->y,
268                             native,
269                             ci->rotation,
270                             ci->outputs,
271                             ci->noutput);
272        }
273
274        XRRFreeOutputInfo(oi);
275        XRRFreeCrtcInfo(ci);
276        XRRFreeScreenResources(sr);
277    }
278}
279
280// Restore the saved (original) video mode for the specified monitor
281//
282void _glfwRestoreVideoModeX11(_GLFWmonitor* monitor)
283{
284    if (_glfw.x11.randr.available && !_glfw.x11.randr.monitorBroken)
285    {
286        if (monitor->x11.oldMode == None)
287            return;
288
289        XRRScreenResources* sr =
290            XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
291        XRRCrtcInfo* ci = XRRGetCrtcInfo(_glfw.x11.display, sr, monitor->x11.crtc);
292
293        XRRSetCrtcConfig(_glfw.x11.display,
294                         sr, monitor->x11.crtc,
295                         CurrentTime,
296                         ci->x, ci->y,
297                         monitor->x11.oldMode,
298                         ci->rotation,
299                         ci->outputs,
300                         ci->noutput);
301
302        XRRFreeCrtcInfo(ci);
303        XRRFreeScreenResources(sr);
304
305        monitor->x11.oldMode = None;
306    }
307}
308
309
310//////////////////////////////////////////////////////////////////////////
311//////                       GLFW platform API                      //////
312//////////////////////////////////////////////////////////////////////////
313
314void _glfwFreeMonitorX11(_GLFWmonitor* monitor)
315{
316}
317
318void _glfwGetMonitorPosX11(_GLFWmonitor* monitor, int* xpos, int* ypos)
319{
320    if (_glfw.x11.randr.available && !_glfw.x11.randr.monitorBroken)
321    {
322        XRRScreenResources* sr =
323            XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
324        XRRCrtcInfo* ci = XRRGetCrtcInfo(_glfw.x11.display, sr, monitor->x11.crtc);
325
326        if (ci)
327        {
328            if (xpos)
329                *xpos = ci->x;
330            if (ypos)
331                *ypos = ci->y;
332
333            XRRFreeCrtcInfo(ci);
334        }
335
336        XRRFreeScreenResources(sr);
337    }
338}
339
340void _glfwGetMonitorContentScaleX11(_GLFWmonitor* monitor,
341                                    float* xscale, float* yscale)
342{
343    if (xscale)
344        *xscale = _glfw.x11.contentScaleX;
345    if (yscale)
346        *yscale = _glfw.x11.contentScaleY;
347}
348
349void _glfwGetMonitorWorkareaX11(_GLFWmonitor* monitor,
350                                int* xpos, int* ypos,
351                                int* width, int* height)
352{
353    int areaX = 0, areaY = 0, areaWidth = 0, areaHeight = 0;
354
355    if (_glfw.x11.randr.available && !_glfw.x11.randr.monitorBroken)
356    {
357        XRRScreenResources* sr =
358            XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
359        XRRCrtcInfo* ci = XRRGetCrtcInfo(_glfw.x11.display, sr, monitor->x11.crtc);
360
361        areaX = ci->x;
362        areaY = ci->y;
363
364        const XRRModeInfo* mi = getModeInfo(sr, ci->mode);
365
366        if (ci->rotation == RR_Rotate_90 || ci->rotation == RR_Rotate_270)
367        {
368            areaWidth  = mi->height;
369            areaHeight = mi->width;
370        }
371        else
372        {
373            areaWidth  = mi->width;
374            areaHeight = mi->height;
375        }
376
377        XRRFreeCrtcInfo(ci);
378        XRRFreeScreenResources(sr);
379    }
380    else
381    {
382        areaWidth  = DisplayWidth(_glfw.x11.display, _glfw.x11.screen);
383        areaHeight = DisplayHeight(_glfw.x11.display, _glfw.x11.screen);
384    }
385
386    if (_glfw.x11.NET_WORKAREA && _glfw.x11.NET_CURRENT_DESKTOP)
387    {
388        Atom* extents = NULL;
389        Atom* desktop = NULL;
390        const unsigned long extentCount =
391            _glfwGetWindowPropertyX11(_glfw.x11.root,
392                                      _glfw.x11.NET_WORKAREA,
393                                      XA_CARDINAL,
394                                      (unsigned char**) &extents);
395
396        if (_glfwGetWindowPropertyX11(_glfw.x11.root,
397                                      _glfw.x11.NET_CURRENT_DESKTOP,
398                                      XA_CARDINAL,
399                                      (unsigned char**) &desktop) > 0)
400        {
401            if (extentCount >= 4 && *desktop < extentCount / 4)
402            {
403                const int globalX = extents[*desktop * 4 + 0];
404                const int globalY = extents[*desktop * 4 + 1];
405                const int globalWidth  = extents[*desktop * 4 + 2];
406                const int globalHeight = extents[*desktop * 4 + 3];
407
408                if (areaX < globalX)
409                {
410                    areaWidth -= globalX - areaX;
411                    areaX = globalX;
412                }
413
414                if (areaY < globalY)
415                {
416                    areaHeight -= globalY - areaY;
417                    areaY = globalY;
418                }
419
420                if (areaX + areaWidth > globalX + globalWidth)
421                    areaWidth = globalX - areaX + globalWidth;
422                if (areaY + areaHeight > globalY + globalHeight)
423                    areaHeight = globalY - areaY + globalHeight;
424            }
425        }
426
427        if (extents)
428            XFree(extents);
429        if (desktop)
430            XFree(desktop);
431    }
432
433    if (xpos)
434        *xpos = areaX;
435    if (ypos)
436        *ypos = areaY;
437    if (width)
438        *width = areaWidth;
439    if (height)
440        *height = areaHeight;
441}
442
443GLFWvidmode* _glfwGetVideoModesX11(_GLFWmonitor* monitor, int* count)
444{
445    GLFWvidmode* result;
446
447    *count = 0;
448
449    if (_glfw.x11.randr.available && !_glfw.x11.randr.monitorBroken)
450    {
451        XRRScreenResources* sr =
452            XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
453        XRRCrtcInfo* ci = XRRGetCrtcInfo(_glfw.x11.display, sr, monitor->x11.crtc);
454        XRROutputInfo* oi = XRRGetOutputInfo(_glfw.x11.display, sr, monitor->x11.output);
455
456        result = _glfw_calloc(oi->nmode, sizeof(GLFWvidmode));
457
458        for (int i = 0;  i < oi->nmode;  i++)
459        {
460            const XRRModeInfo* mi = getModeInfo(sr, oi->modes[i]);
461            if (!modeIsGood(mi))
462                continue;
463
464            const GLFWvidmode mode = vidmodeFromModeInfo(mi, ci);
465            int j;
466
467            for (j = 0;  j < *count;  j++)
468            {
469                if (_glfwCompareVideoModes(result + j, &mode) == 0)
470                    break;
471            }
472
473            // Skip duplicate modes
474            if (j < *count)
475                continue;
476
477            (*count)++;
478            result[*count - 1] = mode;
479        }
480
481        XRRFreeOutputInfo(oi);
482        XRRFreeCrtcInfo(ci);
483        XRRFreeScreenResources(sr);
484    }
485    else
486    {
487        *count = 1;
488        result = _glfw_calloc(1, sizeof(GLFWvidmode));
489        _glfwGetVideoModeX11(monitor, result);
490    }
491
492    return result;
493}
494
495GLFWbool _glfwGetVideoModeX11(_GLFWmonitor* monitor, GLFWvidmode* mode)
496{
497    if (_glfw.x11.randr.available && !_glfw.x11.randr.monitorBroken)
498    {
499        XRRScreenResources* sr =
500            XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
501        const XRRModeInfo* mi = NULL;
502
503        XRRCrtcInfo* ci = XRRGetCrtcInfo(_glfw.x11.display, sr, monitor->x11.crtc);
504        if (ci)
505        {
506            mi = getModeInfo(sr, ci->mode);
507            if (mi)
508                *mode = vidmodeFromModeInfo(mi, ci);
509
510            XRRFreeCrtcInfo(ci);
511        }
512
513        XRRFreeScreenResources(sr);
514
515        if (!mi)
516        {
517            _glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to query video mode");
518            return GLFW_FALSE;
519        }
520    }
521    else
522    {
523        mode->width = DisplayWidth(_glfw.x11.display, _glfw.x11.screen);
524        mode->height = DisplayHeight(_glfw.x11.display, _glfw.x11.screen);
525        mode->refreshRate = 0;
526
527        _glfwSplitBPP(DefaultDepth(_glfw.x11.display, _glfw.x11.screen),
528                      &mode->redBits, &mode->greenBits, &mode->blueBits);
529    }
530
531    return GLFW_TRUE;
532}
533
534GLFWbool _glfwGetGammaRampX11(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
535{
536    if (_glfw.x11.randr.available && !_glfw.x11.randr.gammaBroken)
537    {
538        const size_t size = XRRGetCrtcGammaSize(_glfw.x11.display,
539                                                monitor->x11.crtc);
540        XRRCrtcGamma* gamma = XRRGetCrtcGamma(_glfw.x11.display,
541                                              monitor->x11.crtc);
542
543        _glfwAllocGammaArrays(ramp, size);
544
545        memcpy(ramp->red,   gamma->red,   size * sizeof(unsigned short));
546        memcpy(ramp->green, gamma->green, size * sizeof(unsigned short));
547        memcpy(ramp->blue,  gamma->blue,  size * sizeof(unsigned short));
548
549        XRRFreeGamma(gamma);
550        return GLFW_TRUE;
551    }
552    else if (_glfw.x11.vidmode.available)
553    {
554        int size;
555        XF86VidModeGetGammaRampSize(_glfw.x11.display, _glfw.x11.screen, &size);
556
557        _glfwAllocGammaArrays(ramp, size);
558
559        XF86VidModeGetGammaRamp(_glfw.x11.display,
560                                _glfw.x11.screen,
561                                ramp->size, ramp->red, ramp->green, ramp->blue);
562        return GLFW_TRUE;
563    }
564    else
565    {
566        _glfwInputError(GLFW_PLATFORM_ERROR,
567                        "X11: Gamma ramp access not supported by server");
568        return GLFW_FALSE;
569    }
570}
571
572void _glfwSetGammaRampX11(_GLFWmonitor* monitor, const GLFWgammaramp* ramp)
573{
574    if (_glfw.x11.randr.available && !_glfw.x11.randr.gammaBroken)
575    {
576        if (XRRGetCrtcGammaSize(_glfw.x11.display, monitor->x11.crtc) != ramp->size)
577        {
578            _glfwInputError(GLFW_PLATFORM_ERROR,
579                            "X11: Gamma ramp size must match current ramp size");
580            return;
581        }
582
583        XRRCrtcGamma* gamma = XRRAllocGamma(ramp->size);
584
585        memcpy(gamma->red,   ramp->red,   ramp->size * sizeof(unsigned short));
586        memcpy(gamma->green, ramp->green, ramp->size * sizeof(unsigned short));
587        memcpy(gamma->blue,  ramp->blue,  ramp->size * sizeof(unsigned short));
588
589        XRRSetCrtcGamma(_glfw.x11.display, monitor->x11.crtc, gamma);
590        XRRFreeGamma(gamma);
591    }
592    else if (_glfw.x11.vidmode.available)
593    {
594        XF86VidModeSetGammaRamp(_glfw.x11.display,
595                                _glfw.x11.screen,
596                                ramp->size,
597                                (unsigned short*) ramp->red,
598                                (unsigned short*) ramp->green,
599                                (unsigned short*) ramp->blue);
600    }
601    else
602    {
603        _glfwInputError(GLFW_PLATFORM_ERROR,
604                        "X11: Gamma ramp access not supported by server");
605    }
606}
607
608
609//////////////////////////////////////////////////////////////////////////
610//////                        GLFW native API                       //////
611//////////////////////////////////////////////////////////////////////////
612
613GLFWAPI RRCrtc glfwGetX11Adapter(GLFWmonitor* handle)
614{
615    _GLFW_REQUIRE_INIT_OR_RETURN(None);
616
617    if (_glfw.platform.platformID != GLFW_PLATFORM_X11)
618    {
619        _glfwInputError(GLFW_PLATFORM_UNAVAILABLE, "X11: Platform not initialized");
620        return None;
621    }
622
623    _GLFWmonitor* monitor = (_GLFWmonitor*) handle;
624    assert(monitor != NULL);
625
626    return monitor->x11.crtc;
627}
628
629GLFWAPI RROutput glfwGetX11Monitor(GLFWmonitor* handle)
630{
631    _GLFW_REQUIRE_INIT_OR_RETURN(None);
632
633    if (_glfw.platform.platformID != GLFW_PLATFORM_X11)
634    {
635        _glfwInputError(GLFW_PLATFORM_UNAVAILABLE, "X11: Platform not initialized");
636        return None;
637    }
638
639    _GLFWmonitor* monitor = (_GLFWmonitor*) handle;
640    assert(monitor != NULL);
641
642    return monitor->x11.output;
643}
644
645#endif // _GLFW_X11
646
647