1/* $Id: pointer.c,v 1.3 2024/07/04 06:40:40 mrg Exp $ */
2/** @file
3 * VirtualBox X11 Additions graphics driver utility 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#ifdef HAVE_CONFIG_H
29#include "config.h"
30#endif
31
32#ifndef PCIACCESS
33# include "xf86Pci.h"
34# include <Pci.h>
35#endif
36
37#include "xf86.h"
38#define NEED_XF86_TYPES
39#include "compiler.h"
40#include "cursorstr.h"
41#include "servermd.h"
42
43#include "vboxvideo_drv.h"
44
45#ifdef XORG_7X
46# include <stdlib.h>
47# include <string.h>
48#endif
49
50#define VBOX_MAX_CURSOR_WIDTH 64
51#define VBOX_MAX_CURSOR_HEIGHT 64
52
53/**************************************************************************
54* Debugging functions and macros                                          *
55**************************************************************************/
56
57/* #define DEBUG_POINTER */
58
59#ifdef DEBUG
60# define PUT_PIXEL(c) ErrorF ("%c", c)
61#else /* DEBUG_VIDEO not defined */
62# define PUT_PIXEL(c) do { } while(0)
63#endif /* DEBUG_VIDEO not defined */
64
65/** Macro to printf an error message and return from a function */
66#define RETERROR(scrnIndex, RetVal, ...) \
67    do \
68    { \
69        xf86DrvMsg(scrnIndex, X_ERROR, __VA_ARGS__); \
70        return RetVal; \
71    } \
72    while (0)
73
74/** Structure to pass cursor image data between realise_cursor() and
75 * load_cursor_image().  The members match the parameters to
76 * @a VBoxHGSMIUpdatePointerShape(). */
77struct vboxCursorImage
78{
79    uint32_t fFlags;
80    uint32_t cHotX;
81    uint32_t cHotY;
82    uint32_t cWidth;
83    uint32_t cHeight;
84    uint8_t *pPixels;
85    uint32_t cbLength;
86};
87
88#ifdef DEBUG_POINTER
89static void
90vbox_show_shape(unsigned short w, unsigned short h, CARD32 bg, unsigned char *image)
91{
92    size_t x, y;
93    unsigned short pitch;
94    CARD32 *color;
95    unsigned char *mask;
96    size_t sizeMask;
97
98    image    += sizeof(struct vboxCursorImage);
99    mask      = image;
100    pitch     = (w + 7) / 8;
101    sizeMask  = (pitch * h + 3) & ~3;
102    color     = (CARD32 *)(image + sizeMask);
103
104    TRACE_ENTRY();
105    for (y = 0; y < h; ++y, mask += pitch, color += w)
106    {
107        for (x = 0; x < w; ++x)
108        {
109            if (mask[x / 8] & (1 << (7 - (x % 8))))
110                ErrorF (" ");
111            else
112            {
113                CARD32 c = color[x];
114                if (c == bg)
115                    ErrorF("Y");
116                else
117                    ErrorF("X");
118            }
119        }
120        ErrorF("\n");
121    }
122}
123#endif
124
125/**************************************************************************
126* Main functions                                                          *
127**************************************************************************/
128
129void vbvxCursorTerm(VBOXPtr pVBox)
130{
131    TRACE_ENTRY();
132
133    xf86DestroyCursorInfoRec(pVBox->pCurs);
134    pVBox->pCurs = NULL;
135    TRACE_EXIT();
136}
137
138static void
139vbox_vmm_hide_cursor(ScrnInfoPtr pScrn, VBOXPtr pVBox)
140{
141    int rc;
142    RT_NOREF(pScrn);
143
144    rc = VBoxHGSMIUpdatePointerShape(&pVBox->guestCtx, 0, 0, 0, 0, 0, NULL, 0);
145    AssertMsg(rc == VINF_SUCCESS, ("Could not hide the virtual mouse pointer, VBox error %d.\n", rc));
146}
147
148static void
149vbox_vmm_show_cursor(ScrnInfoPtr pScrn, VBOXPtr pVBox)
150{
151    int rc;
152    RT_NOREF(pScrn);
153
154    if (!pVBox->fUseHardwareCursor)
155        return;
156    rc = VBoxHGSMIUpdatePointerShape(&pVBox->guestCtx, VBOX_MOUSE_POINTER_VISIBLE,
157                                     0, 0, 0, 0, NULL, 0);
158    AssertMsg(rc == VINF_SUCCESS, ("Could not unhide the virtual mouse pointer.\n"));
159}
160
161static void
162vbox_vmm_load_cursor_image(ScrnInfoPtr pScrn, VBOXPtr pVBox,
163                           unsigned char *pvImage)
164{
165    int rc;
166    struct vboxCursorImage *pImage;
167    pImage = (struct vboxCursorImage *)pvImage;
168    RT_NOREF(pScrn);
169
170#ifdef DEBUG_POINTER
171    vbox_show_shape(pImage->cWidth, pImage->cHeight, 0, pvImage);
172#endif
173
174    rc = VBoxHGSMIUpdatePointerShape(&pVBox->guestCtx, pImage->fFlags,
175             pImage->cHotX, pImage->cHotY, pImage->cWidth, pImage->cHeight,
176             pImage->pPixels, pImage->cbLength);
177    AssertMsg(rc == VINF_SUCCESS, ("Unable to set the virtual mouse pointer image.\n"));
178}
179
180static void
181vbox_set_cursor_colors(ScrnInfoPtr pScrn, int bg, int fg)
182{
183    RT_NOREF(pScrn);
184    RT_NOREF(bg);
185    RT_NOREF(fg);
186    /* ErrorF("vbox_set_cursor_colors NOT IMPLEMENTED\n"); */
187}
188
189
190static void
191vbox_set_cursor_position(ScrnInfoPtr pScrn, int x, int y)
192{
193    VBOXPtr pVBox = pScrn->driverPrivate;
194
195    /* This currently does nothing. */
196    VBoxHGSMICursorPosition(&pVBox->guestCtx, true, x, y, NULL, NULL);
197}
198
199static void
200vbox_hide_cursor(ScrnInfoPtr pScrn)
201{
202    VBOXPtr pVBox = pScrn->driverPrivate;
203
204    vbox_vmm_hide_cursor(pScrn, pVBox);
205}
206
207static void
208vbox_show_cursor(ScrnInfoPtr pScrn)
209{
210    VBOXPtr pVBox = pScrn->driverPrivate;
211
212    vbox_vmm_show_cursor(pScrn, pVBox);
213}
214
215static void
216vbox_load_cursor_image(ScrnInfoPtr pScrn, unsigned char *image)
217{
218    VBOXPtr pVBox = pScrn->driverPrivate;
219
220    vbox_vmm_load_cursor_image(pScrn, pVBox, image);
221}
222
223static Bool
224vbox_use_hw_cursor(ScreenPtr pScreen, CursorPtr pCurs)
225{
226    ScrnInfoPtr pScrn = xf86Screens[pScreen->myNum];
227    VBOXPtr pVBox = pScrn->driverPrivate;
228    RT_NOREF(pCurs);
229    return pVBox->fUseHardwareCursor;
230}
231
232static unsigned char
233color_to_byte(unsigned c)
234{
235    return (c >> 8) & 0xff;
236}
237
238static unsigned char *
239vbox_realize_cursor(xf86CursorInfoPtr infoPtr, CursorPtr pCurs)
240{
241    VBOXPtr pVBox;
242    CursorBitsPtr bitsp;
243    unsigned short w, h, x, y;
244    unsigned char *c, *p, *pm, *ps, *m;
245    size_t sizeRequest, sizeRgba, sizeMask, srcPitch, dstPitch;
246    CARD32 fc, bc, *cp;
247    int scrnIndex = infoPtr->pScrn->scrnIndex;
248    struct vboxCursorImage *pImage;
249
250    pVBox = infoPtr->pScrn->driverPrivate;
251    bitsp = pCurs->bits;
252    w = bitsp->width;
253    h = bitsp->height;
254
255    if (!w || !h || w > VBOX_MAX_CURSOR_WIDTH || h > VBOX_MAX_CURSOR_HEIGHT)
256        RETERROR(scrnIndex, NULL,
257            "Error invalid cursor dimensions %dx%d\n", w, h);
258
259    if ((bitsp->xhot > w) || (bitsp->yhot > h))
260        RETERROR(scrnIndex, NULL,
261            "Error invalid cursor hotspot location %dx%d (max %dx%d)\n",
262            bitsp->xhot, bitsp->yhot, w, h);
263
264    srcPitch = PixmapBytePad (bitsp->width, 1);
265    dstPitch = (w + 7) / 8;
266    sizeMask = ((dstPitch * h) + 3) & (size_t) ~3;
267    sizeRgba = w * h * 4;
268    sizeRequest = sizeMask + sizeRgba + sizeof(*pImage);
269
270    p = c = calloc (1, sizeRequest);
271    if (!c)
272        RETERROR(scrnIndex, NULL,
273                 "Error failed to alloc %lu bytes for cursor\n",
274                 (unsigned long) sizeRequest);
275
276    pImage = (struct vboxCursorImage *)p;
277    pImage->pPixels = m = p + sizeof(*pImage);
278    cp = (CARD32 *)(m + sizeMask);
279
280    TRACE_LOG ("w=%d h=%d sm=%d sr=%d p=%d\n",
281           w, h, (int) sizeMask, (int) sizeRgba, (int) dstPitch);
282    TRACE_LOG ("m=%p c=%p cp=%p\n", m, c, (void *)cp);
283
284    fc = color_to_byte (pCurs->foreBlue)
285      | (color_to_byte (pCurs->foreGreen) << 8)
286      | (color_to_byte (pCurs->foreRed)   << 16);
287
288    bc = color_to_byte (pCurs->backBlue)
289      | (color_to_byte (pCurs->backGreen) << 8)
290      | (color_to_byte (pCurs->backRed)   << 16);
291
292    /*
293     * Convert the Xorg source/mask bits to the and/xor bits VBox needs.
294     * Xorg:
295     *   The mask is a bitmap indicating which parts of the cursor are
296     *   transparent and which parts are drawn. The source is a bitmap
297     *   indicating which parts of the non-transparent portion of the
298     *   the cursor should be painted in the foreground color and which
299     *   should be painted in the background color. By default, set bits
300     *   indicate the opaque part of the mask bitmap and clear bits
301     *   indicate the transparent part.
302     * VBox:
303     *   The color data is the XOR mask. The AND mask bits determine
304     *   which pixels of the color data (XOR mask) will replace (overwrite)
305     *   the screen pixels (AND mask bit = 0) and which ones will be XORed
306     *   with existing screen pixels (AND mask bit = 1).
307     *   For example when you have the AND mask all 0, then you see the
308     *   correct mouse pointer image surrounded by black square.
309     */
310    for (pm = bitsp->mask, ps = bitsp->source, y = 0;
311         y < h;
312         ++y, pm += srcPitch, ps += srcPitch, m += dstPitch)
313    {
314        for (x = 0; x < w; ++x)
315        {
316            if (pm[x / 8] & (1 << (x % 8)))
317            {
318                /* opaque, leave AND mask bit at 0 */
319                if (ps[x / 8] & (1 << (x % 8)))
320                {
321                    *cp++ = fc;
322                    PUT_PIXEL('X');
323                }
324                else
325                {
326                    *cp++ = bc;
327                    PUT_PIXEL('*');
328                }
329            }
330            else
331            {
332                /* transparent, set AND mask bit */
333                m[x / 8] |= 1 << (7 - (x % 8));
334                /* don't change the screen pixel */
335                *cp++ = 0;
336                PUT_PIXEL(' ');
337            }
338        }
339        PUT_PIXEL('\n');
340    }
341
342    pImage->cWidth   = w;
343    pImage->cHeight  = h;
344    pImage->cHotX    = bitsp->xhot;
345    pImage->cHotY    = bitsp->yhot;
346    pImage->fFlags   = VBOX_MOUSE_POINTER_VISIBLE | VBOX_MOUSE_POINTER_SHAPE;
347    pImage->cbLength = sizeRequest - sizeof(*pImage);
348
349#ifdef DEBUG_POINTER
350    ErrorF("shape = %p\n", p);
351    vbox_show_shape(w, h, bc, c);
352#endif
353
354    return p;
355}
356
357#ifdef ARGB_CURSOR
358static Bool
359vbox_use_hw_cursor_argb(ScreenPtr pScreen, CursorPtr pCurs)
360{
361    ScrnInfoPtr pScrn = xf86Screens[pScreen->myNum];
362    VBOXPtr pVBox = pScrn->driverPrivate;
363
364    if (!pVBox->fUseHardwareCursor)
365        return FALSE;
366    if (   (pCurs->bits->height > VBOX_MAX_CURSOR_HEIGHT)
367        || (pCurs->bits->width > VBOX_MAX_CURSOR_WIDTH)
368        || (pScrn->bitsPerPixel <= 8))
369        return FALSE;
370    return TRUE;
371}
372
373
374static void
375vbox_load_cursor_argb(ScrnInfoPtr pScrn, CursorPtr pCurs)
376{
377    VBOXPtr pVBox;
378    CursorBitsPtr bitsp;
379    unsigned short w, h;
380    unsigned short cx, cy;
381    unsigned char *pm;
382    CARD32 *pc;
383    size_t sizeData, sizeMask;
384    CARD8 *p;
385    int scrnIndex;
386    uint32_t fFlags =   VBOX_MOUSE_POINTER_VISIBLE | VBOX_MOUSE_POINTER_SHAPE
387                      | VBOX_MOUSE_POINTER_ALPHA;
388
389    pVBox = pScrn->driverPrivate;
390    bitsp = pCurs->bits;
391    w     = bitsp->width;
392    h     = bitsp->height;
393    scrnIndex = pScrn->scrnIndex;
394
395    /* Mask must be generated for alpha cursors, that is required by VBox. */
396    /* note: (michael) the next struct must be 32bit aligned. */
397    sizeMask  = ((w + 7) / 8 * h + 3) & ~3;
398
399    if (!w || !h || w > VBOX_MAX_CURSOR_WIDTH || h > VBOX_MAX_CURSOR_HEIGHT)
400        RETERROR(scrnIndex, ,
401                 "Error invalid cursor dimensions %dx%d\n", w, h);
402
403    if ((bitsp->xhot > w) || (bitsp->yhot > h))
404        RETERROR(scrnIndex, ,
405                 "Error invalid cursor hotspot location %dx%d (max %dx%d)\n",
406                 bitsp->xhot, bitsp->yhot, w, h);
407
408    sizeData = w * h * 4 + sizeMask;
409    p = calloc(1, sizeData);
410    if (!p)
411        RETERROR(scrnIndex, ,
412                 "Error failed to alloc %lu bytes for cursor\n",
413                 (unsigned long)sizeData);
414
415    memcpy(p + sizeMask, bitsp->argb, w * h * 4);
416
417    /* Emulate the AND mask. */
418    pm = p;
419    pc = bitsp->argb;
420
421    /* Init AND mask to 1 */
422    memset(pm, 0xFF, sizeMask);
423
424    /*
425     * The additions driver must provide the AND mask for alpha cursors. The host frontend
426     * which can handle alpha channel, will ignore the AND mask and draw an alpha cursor.
427     * But if the host does not support ARGB, then it simply uses the AND mask and the color
428     * data to draw a normal color cursor.
429     */
430    for (cy = 0; cy < h; cy++)
431    {
432        unsigned char bitmask = 0x80;
433
434        for (cx = 0; cx < w; cx++, bitmask >>= 1)
435        {
436            if (bitmask == 0)
437                bitmask = 0x80;
438
439            if (pc[cx] >= 0xF0000000)
440                pm[cx / 8] &= ~bitmask;
441        }
442
443        /* Point to next source and dest scans */
444        pc += w;
445        pm += (w + 7) / 8;
446    }
447
448    VBoxHGSMIUpdatePointerShape(&pVBox->guestCtx, fFlags, bitsp->xhot,
449                                bitsp->yhot, w, h, p, sizeData);
450    free(p);
451}
452#endif
453
454Bool vbvxCursorInit(ScreenPtr pScreen)
455{
456    ScrnInfoPtr pScrn = xf86Screens[pScreen->myNum];
457    VBOXPtr pVBox = pScrn->driverPrivate;
458    xf86CursorInfoPtr pCurs = NULL;
459    Bool rc = TRUE;
460
461    TRACE_ENTRY();
462    pVBox->pCurs = pCurs = xf86CreateCursorInfoRec();
463    if (!pCurs) {
464        xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
465                   "Failed to create X Window cursor information structures for virtual mouse.\n");
466        rc = FALSE;
467    }
468    if (rc) {
469        pCurs->MaxWidth = VBOX_MAX_CURSOR_WIDTH;
470        pCurs->MaxHeight = VBOX_MAX_CURSOR_HEIGHT;
471        pCurs->Flags =   HARDWARE_CURSOR_TRUECOLOR_AT_8BPP
472                       | HARDWARE_CURSOR_SOURCE_MASK_INTERLEAVE_1
473                       | HARDWARE_CURSOR_BIT_ORDER_MSBFIRST
474                       | HARDWARE_CURSOR_UPDATE_UNHIDDEN;
475
476        pCurs->SetCursorColors   = vbox_set_cursor_colors;
477        pCurs->SetCursorPosition = vbox_set_cursor_position;
478        pCurs->LoadCursorImage   = vbox_load_cursor_image;
479        pCurs->HideCursor        = vbox_hide_cursor;
480        pCurs->ShowCursor        = vbox_show_cursor;
481        pCurs->UseHWCursor       = vbox_use_hw_cursor;
482        pCurs->RealizeCursor     = vbox_realize_cursor;
483
484#ifdef ARGB_CURSOR
485        pCurs->UseHWCursorARGB   = vbox_use_hw_cursor_argb;
486        pCurs->LoadCursorARGB    = vbox_load_cursor_argb;
487#endif
488
489        rc = xf86InitCursor(pScreen, pCurs);
490    }
491    if (!rc)
492        xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
493                   "Failed to enable mouse pointer integration.\n");
494    if (!rc && (pCurs != NULL))
495        xf86DestroyCursorInfoRec(pCurs);
496    return rc;
497}
498