1/* $Id: getmode.c,v 1.2 2020/10/22 20:47:23 thorpej Exp $ */
2/** @file
3 * VirtualBox X11 Additions graphics driver dynamic video mode functions.
4 */
5
6/*
7 * Copyright (C) 2006-2017 Oracle Corporation
8 *
9 * Permission is hereby granted, free of charge, to any person obtaining a
10 * copy of this software and associated documentation files (the "Software"),
11 * to deal in the Software without restriction, including without limitation
12 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
13 * and/or sell copies of the Software, and to permit persons to whom the
14 * Software is furnished to do so, subject to the following conditions:
15 *
16 * The above copyright notice and this permission notice shall be included in
17 * all copies or substantial portions of the Software.
18 *
19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
22 * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,
23 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
24 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
25 * USE OR OTHER DEALINGS IN THE SOFTWARE.
26 */
27
28#include "vboxvideo_drv.h"
29
30#define NEED_XF86_TYPES
31#include "xf86.h"
32
33#ifdef XORG_7X
34# include <stdio.h>
35# include <stdlib.h>
36# include <string.h>
37#endif
38
39#ifdef VBOXVIDEO_13
40# ifdef RT_OS_LINUX
41#  include <linux/input.h>
42#  ifndef EVIOCGRAB
43#   define EVIOCGRAB _IOW('E', 0x90, int)
44#  endif
45#  ifndef KEY_SWITCHVIDEOMODE
46#   define KEY_SWITCHVIDEOMODE 227
47#  endif
48#  include <dirent.h>
49#  include <errno.h>
50#  include <fcntl.h>
51#  include <unistd.h>
52# endif /* RT_OS_LINUX */
53#endif /* VBOXVIDEO_13 */
54
55/**************************************************************************
56* Main functions                                                          *
57**************************************************************************/
58
59/**
60 * Fills a display mode M with a built-in mode of name pszName and dimensions
61 * cx and cy.
62 */
63static void vboxFillDisplayMode(ScrnInfoPtr pScrn, DisplayModePtr m,
64                                const char *pszName, unsigned cx, unsigned cy)
65{
66    VBOXPtr pVBox = pScrn->driverPrivate;
67    char szName[256];
68    DisplayModePtr pPrev = m->prev;
69    DisplayModePtr pNext = m->next;
70
71    if (!pszName)
72    {
73        sprintf(szName, "%ux%u", cx, cy);
74        pszName = szName;
75    }
76    TRACE_LOG("pszName=%s, cx=%u, cy=%u\n", pszName, cx, cy);
77    if (m->name)
78        free((void*)m->name);
79    memset(m, '\0', sizeof(*m));
80    m->prev          = pPrev;
81    m->next          = pNext;
82    m->status        = MODE_OK;
83    m->type          = M_T_BUILTIN;
84    /* Older versions of VBox only support screen widths which are a multiple
85     * of 8 */
86    if (pVBox->fAnyX)
87        m->HDisplay  = cx;
88    else
89        m->HDisplay  = cx & ~7;
90    m->HSyncStart    = m->HDisplay + 2;
91    m->HSyncEnd      = m->HDisplay + 4;
92    m->HTotal        = m->HDisplay + 6;
93    m->VDisplay      = cy;
94    m->VSyncStart    = m->VDisplay + 2;
95    m->VSyncEnd      = m->VDisplay + 4;
96    m->VTotal        = m->VDisplay + 6;
97    m->Clock         = m->HTotal * m->VTotal * 60 / 1000; /* kHz */
98    m->name      = xnfstrdup(pszName);
99}
100
101/**
102 * Allocates an empty display mode and links it into the doubly linked list of
103 * modes pointed to by pScrn->modes.  Returns a pointer to the newly allocated
104 * memory.
105 */
106static DisplayModePtr vboxAddEmptyScreenMode(ScrnInfoPtr pScrn)
107{
108    DisplayModePtr pMode = xnfcalloc(sizeof(DisplayModeRec), 1);
109
110    TRACE_ENTRY();
111    if (!pScrn->modes)
112    {
113        pScrn->modes = pMode;
114        pMode->next = pMode;
115        pMode->prev = pMode;
116    }
117    else
118    {
119        pMode->next = pScrn->modes;
120        pMode->prev = pScrn->modes->prev;
121        pMode->next->prev = pMode;
122        pMode->prev->next = pMode;
123    }
124    return pMode;
125}
126
127/**
128 * Create display mode entries in the screen information structure for each
129 * of the graphics modes that we wish to support, that is:
130 *  - A dynamic mode in first place which will be updated by the RandR code.
131 *  - Any modes that the user requested in xorg.conf/XFree86Config.
132 */
133void vboxAddModes(ScrnInfoPtr pScrn)
134{
135    unsigned cx = 0;
136    unsigned cy = 0;
137    unsigned i;
138    DisplayModePtr pMode;
139
140    /* Add two dynamic mode entries.  When we receive a new size hint we will
141     * update whichever of these is not current. */
142    pMode = vboxAddEmptyScreenMode(pScrn);
143    vboxFillDisplayMode(pScrn, pMode, NULL, 800, 600);
144    pMode = vboxAddEmptyScreenMode(pScrn);
145    vboxFillDisplayMode(pScrn, pMode, NULL, 800, 600);
146    /* Add any modes specified by the user.  We assume here that the mode names
147     * reflect the mode sizes. */
148    for (i = 0; pScrn->display->modes && pScrn->display->modes[i]; i++)
149    {
150        if (sscanf(pScrn->display->modes[i], "%ux%u", &cx, &cy) == 2)
151        {
152            pMode = vboxAddEmptyScreenMode(pScrn);
153            vboxFillDisplayMode(pScrn, pMode, pScrn->display->modes[i], cx, cy);
154        }
155    }
156}
157
158/** Set the initial values for the guest screen size hints to standard values
159 * in case nothing else is available. */
160void VBoxInitialiseSizeHints(ScrnInfoPtr pScrn)
161{
162    VBOXPtr pVBox = VBOXGetRec(pScrn);
163    unsigned i;
164
165    for (i = 0; i < pVBox->cScreens; ++i)
166    {
167        pVBox->pScreens[i].aPreferredSize.cx = 800;
168        pVBox->pScreens[i].aPreferredSize.cy = 600;
169        pVBox->pScreens[i].afConnected       = true;
170    }
171    /* Set up the first mode correctly to match the requested initial mode. */
172    pScrn->modes->HDisplay = pVBox->pScreens[0].aPreferredSize.cx;
173    pScrn->modes->VDisplay = pVBox->pScreens[0].aPreferredSize.cy;
174}
175
176static Bool useHardwareCursor(uint32_t fCursorCapabilities)
177{
178    if (fCursorCapabilities & VBOX_VBVA_CURSOR_CAPABILITY_HARDWARE)
179        return true;
180    return false;
181}
182
183static void compareAndMaybeSetUseHardwareCursor(VBOXPtr pVBox, uint32_t fCursorCapabilities, Bool *pfChanged, Bool fSet)
184{
185    if (pVBox->fUseHardwareCursor != useHardwareCursor(fCursorCapabilities))
186        *pfChanged = true;
187    if (fSet)
188        pVBox->fUseHardwareCursor = useHardwareCursor(fCursorCapabilities);
189}
190
191#define COMPARE_AND_MAYBE_SET(pDest, src, pfChanged, fSet) \
192do { \
193    if (*(pDest) != (src)) \
194    { \
195        if (fSet) \
196            *(pDest) = (src); \
197        *(pfChanged) = true; \
198    } \
199} while(0)
200
201/** Read in information about the most recent size hints and cursor
202 * capabilities requested for the guest screens from HGSMI. */
203void vbvxReadSizesAndCursorIntegrationFromHGSMI(ScrnInfoPtr pScrn, Bool *pfNeedUpdate)
204{
205    VBOXPtr pVBox = VBOXGetRec(pScrn);
206    int rc;
207    unsigned i;
208    Bool fChanged = false;
209    uint32_t fCursorCapabilities;
210
211    if (!pVBox->fHaveHGSMIModeHints)
212        return;
213    rc = VBoxHGSMIGetModeHints(&pVBox->guestCtx, pVBox->cScreens, pVBox->paVBVAModeHints);
214    AssertMsg(rc == VINF_SUCCESS, ("VBoxHGSMIGetModeHints failed, rc=%d.\n", rc));
215    for (i = 0; i < pVBox->cScreens; ++i)
216        if (pVBox->paVBVAModeHints[i].magic == VBVAMODEHINT_MAGIC)
217        {
218            COMPARE_AND_MAYBE_SET(&pVBox->pScreens[i].aPreferredSize.cx, pVBox->paVBVAModeHints[i].cx & 0x8fff, &fChanged, true);
219            COMPARE_AND_MAYBE_SET(&pVBox->pScreens[i].aPreferredSize.cy, pVBox->paVBVAModeHints[i].cy & 0x8fff, &fChanged, true);
220            COMPARE_AND_MAYBE_SET(&pVBox->pScreens[i].afConnected, RT_BOOL(pVBox->paVBVAModeHints[i].fEnabled), &fChanged, true);
221            COMPARE_AND_MAYBE_SET(&pVBox->pScreens[i].aPreferredLocation.x, (int32_t)pVBox->paVBVAModeHints[i].dx & 0x8fff, &fChanged,
222                                  true);
223            COMPARE_AND_MAYBE_SET(&pVBox->pScreens[i].aPreferredLocation.y, (int32_t)pVBox->paVBVAModeHints[i].dy & 0x8fff, &fChanged,
224                                  true);
225            if (pVBox->paVBVAModeHints[i].dx != ~(uint32_t)0 && pVBox->paVBVAModeHints[i].dy != ~(uint32_t)0)
226                COMPARE_AND_MAYBE_SET(&pVBox->pScreens[i].afHaveLocation, true, &fChanged, true);
227            else
228                COMPARE_AND_MAYBE_SET(&pVBox->pScreens[i].afHaveLocation, false, &fChanged, true);
229        }
230    rc = VBoxQueryConfHGSMI(&pVBox->guestCtx, VBOX_VBVA_CONF32_CURSOR_CAPABILITIES, &fCursorCapabilities);
231    AssertMsg(rc == VINF_SUCCESS, ("Getting VBOX_VBVA_CONF32_CURSOR_CAPABILITIES failed, rc=%d.\n", rc));
232    compareAndMaybeSetUseHardwareCursor(pVBox, fCursorCapabilities, &fChanged, true);
233    if (pfNeedUpdate != NULL && fChanged)
234        *pfNeedUpdate = true;
235}
236
237#undef COMPARE_AND_MAYBE_SET
238
239#ifdef VBOXVIDEO_13
240# ifdef RT_OS_LINUX
241/** We have this for two purposes: one is to ensure that the X server is woken
242 * up when we get a video ACPI event.  Two is to grab ACPI video events to
243 * prevent gnome-settings-daemon from seeing them, as older versions ignored
244 * the time stamp and handled them at the wrong time. */
245static void acpiEventHandler(int fd, void *pvData)
246{
247    struct input_event event;
248    ssize_t rc;
249    RT_NOREF(pvData);
250
251    do
252        rc = read(fd, &event, sizeof(event));
253    while (rc > 0 || (rc == -1 && errno == EINTR));
254    /* Why do they return EAGAIN instead of zero bytes read like everyone else does? */
255    AssertMsg(rc != -1 || errno == EAGAIN, ("Reading ACPI input event failed.\n"));
256}
257
258void vbvxSetUpLinuxACPI(ScreenPtr pScreen)
259{
260    VBOXPtr pVBox = VBOXGetRec(xf86Screens[pScreen->myNum]);
261    struct dirent *pDirent;
262    DIR *pDir;
263    int fd = -1;
264
265    if (pVBox->fdACPIDevices != -1 || pVBox->hACPIEventHandler != NULL)
266        FatalError("ACPI input file descriptor not initialised correctly.\n");
267    pDir = opendir("/dev/input");
268    if (pDir == NULL)
269        return;
270    for (pDirent = readdir(pDir); pDirent != NULL; pDirent = readdir(pDir))
271    {
272        if (strncmp(pDirent->d_name, "event", sizeof("event") - 1) == 0)
273        {
274#define BITS_PER_BLOCK (sizeof(unsigned long) * 8)
275            char szFile[64] = "/dev/input/";
276            char szDevice[64] = "";
277            unsigned long afKeys[KEY_MAX / BITS_PER_BLOCK];
278
279            strncat(szFile, pDirent->d_name, sizeof(szFile) - sizeof("/dev/input/"));
280            if (fd != -1)
281                close(fd);
282            fd = open(szFile, O_RDONLY | O_NONBLOCK);
283            if (   fd == -1
284                || ioctl(fd, EVIOCGNAME(sizeof(szDevice)), szDevice) == -1
285                || strcmp(szDevice, "Video Bus") != 0)
286                continue;
287            if (   ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(afKeys)), afKeys) == -1
288                || ((   afKeys[KEY_SWITCHVIDEOMODE / BITS_PER_BLOCK]
289                     >> KEY_SWITCHVIDEOMODE % BITS_PER_BLOCK) & 1) == 0)
290                break;
291            if (ioctl(fd, EVIOCGRAB, (void *)1) != 0)
292                break;
293            pVBox->hACPIEventHandler
294                = xf86AddGeneralHandler(fd, acpiEventHandler, pScreen);
295            if (pVBox->hACPIEventHandler == NULL)
296                break;
297            pVBox->fdACPIDevices = fd;
298            fd = -1;
299            break;
300#undef BITS_PER_BLOCK
301        }
302    }
303    if (fd != -1)
304        close(fd);
305    closedir(pDir);
306}
307
308void vbvxCleanUpLinuxACPI(ScreenPtr pScreen)
309{
310    VBOXPtr pVBox = VBOXGetRec(xf86Screens[pScreen->myNum]);
311    if (pVBox->fdACPIDevices != -1)
312        close(pVBox->fdACPIDevices);
313    pVBox->fdACPIDevices = -1;
314    xf86RemoveGeneralHandler(pVBox->hACPIEventHandler);
315    pVBox->hACPIEventHandler = NULL;
316}
317# endif /* RT_OS_LINUX */
318#endif /* VBOXVIDEO_13 */
319