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