
#ifdef HAVE_XORG_CONFIG_H
#include <xorg-config.h>
#endif

#include <string.h>

#include "misc.h"
#include "xf86.h"
#include "xf86_OSproc.h"

#include <X11/X.h>
#include "scrnintstr.h"
#include "pixmapstr.h"
#include "windowstr.h"
#include "xf86str.h"
#include "cursorstr.h"
#include "mi.h"
#include "mipointer.h"
#include "randrstr.h"
#include "xf86CursorPriv.h"

#include "servermd.h"

static void
xf86RecolorCursor_locked(xf86CursorScreenPtr ScreenPriv, CursorPtr pCurs);

static CARD32
xf86ReverseBitOrder(CARD32 v)
{
    return (((0x01010101 & v) << 7) | ((0x02020202 & v) << 5) |
            ((0x04040404 & v) << 3) | ((0x08080808 & v) << 1) |
            ((0x10101010 & v) >> 1) | ((0x20202020 & v) >> 3) |
            ((0x40404040 & v) >> 5) | ((0x80808080 & v) >> 7));
}

#if BITMAP_SCANLINE_PAD == 64

#if 1
/* Cursors might be only 32 wide. Give'em a chance */
#define SCANLINE CARD32
#define CUR_BITMAP_SCANLINE_PAD 32
#define CUR_LOG2_BITMAP_PAD 5
#define REVERSE_BIT_ORDER(w) xf86ReverseBitOrder(w)
#else
#define SCANLINE CARD64
#define CUR_BITMAP_SCANLINE_PAD BITMAP_SCANLINE_PAD
#define CUR_LOG2_BITMAP_PAD LOG2_BITMAP_PAD
#define REVERSE_BIT_ORDER(w) xf86CARD64ReverseBits(w)
static CARD64 xf86CARD64ReverseBits(CARD64 w);

static CARD64
xf86CARD64ReverseBits(CARD64 w)
{
    unsigned char *p = (unsigned char *) &w;

    p[0] = byte_reversed[p[0]];
    p[1] = byte_reversed[p[1]];
    p[2] = byte_reversed[p[2]];
    p[3] = byte_reversed[p[3]];
    p[4] = byte_reversed[p[4]];
    p[5] = byte_reversed[p[5]];
    p[6] = byte_reversed[p[6]];
    p[7] = byte_reversed[p[7]];

    return w;
}
#endif

#else

#define SCANLINE CARD32
#define CUR_BITMAP_SCANLINE_PAD BITMAP_SCANLINE_PAD
#define CUR_LOG2_BITMAP_PAD LOG2_BITMAP_PAD
#define REVERSE_BIT_ORDER(w) xf86ReverseBitOrder(w)

#endif                          /* BITMAP_SCANLINE_PAD == 64 */

static unsigned char *RealizeCursorInterleave0(xf86CursorInfoPtr, CursorPtr);
static unsigned char *RealizeCursorInterleave1(xf86CursorInfoPtr, CursorPtr);
static unsigned char *RealizeCursorInterleave8(xf86CursorInfoPtr, CursorPtr);
static unsigned char *RealizeCursorInterleave16(xf86CursorInfoPtr, CursorPtr);
static unsigned char *RealizeCursorInterleave32(xf86CursorInfoPtr, CursorPtr);
static unsigned char *RealizeCursorInterleave64(xf86CursorInfoPtr, CursorPtr);

Bool
xf86InitHardwareCursor(ScreenPtr pScreen, xf86CursorInfoPtr infoPtr)
{
    if ((infoPtr->MaxWidth <= 0) || (infoPtr->MaxHeight <= 0))
        return FALSE;

    /* These are required for now */
    if (!infoPtr->SetCursorPosition ||
        !xf86DriverHasLoadCursorImage(infoPtr) ||
        !infoPtr->HideCursor ||
        !xf86DriverHasShowCursor(infoPtr) ||
        !infoPtr->SetCursorColors)
        return FALSE;

    if (infoPtr->RealizeCursor) {
        /* Don't overwrite a driver provided Realize Cursor function */
    }
    else if (HARDWARE_CURSOR_SOURCE_MASK_INTERLEAVE_1 & infoPtr->Flags) {
        infoPtr->RealizeCursor = RealizeCursorInterleave1;
    }
    else if (HARDWARE_CURSOR_SOURCE_MASK_INTERLEAVE_8 & infoPtr->Flags) {
        infoPtr->RealizeCursor = RealizeCursorInterleave8;
    }
    else if (HARDWARE_CURSOR_SOURCE_MASK_INTERLEAVE_16 & infoPtr->Flags) {
        infoPtr->RealizeCursor = RealizeCursorInterleave16;
    }
    else if (HARDWARE_CURSOR_SOURCE_MASK_INTERLEAVE_32 & infoPtr->Flags) {
        infoPtr->RealizeCursor = RealizeCursorInterleave32;
    }
    else if (HARDWARE_CURSOR_SOURCE_MASK_INTERLEAVE_64 & infoPtr->Flags) {
        infoPtr->RealizeCursor = RealizeCursorInterleave64;
    }
    else {                      /* not interleaved */
        infoPtr->RealizeCursor = RealizeCursorInterleave0;
    }

    infoPtr->pScrn = xf86ScreenToScrn(pScreen);

    return TRUE;
}

static Bool
xf86ScreenCheckHWCursor(ScreenPtr pScreen, CursorPtr cursor, xf86CursorInfoPtr infoPtr)
{
    return
        (cursor->bits->argb && infoPtr->UseHWCursorARGB &&
         infoPtr->UseHWCursorARGB(pScreen, cursor)) ||
        (cursor->bits->argb == 0 &&
         cursor->bits->height <= infoPtr->MaxHeight &&
         cursor->bits->width <= infoPtr->MaxWidth &&
         (!infoPtr->UseHWCursor || infoPtr->UseHWCursor(pScreen, cursor)));
}

Bool
xf86CheckHWCursor(ScreenPtr pScreen, CursorPtr cursor, xf86CursorInfoPtr infoPtr)
{
    ScreenPtr pSlave;
    Bool use_hw_cursor = TRUE;

    input_lock();

    if (!xf86ScreenCheckHWCursor(pScreen, cursor, infoPtr)) {
        use_hw_cursor = FALSE;
	goto unlock;
    }

    /* ask each driver consuming a pixmap if it can support HW cursor */
    xorg_list_for_each_entry(pSlave, &pScreen->slave_list, slave_head) {
        xf86CursorScreenPtr sPriv;

        if (!RRHasScanoutPixmap(pSlave))
            continue;

        sPriv = dixLookupPrivate(&pSlave->devPrivates, xf86CursorScreenKey);
        if (!sPriv) { /* NULL if Option "SWCursor", possibly other conditions */
            use_hw_cursor = FALSE;
	    break;
	}

        /* FALSE if HWCursor not supported by slave */
        if (!xf86ScreenCheckHWCursor(pSlave, cursor, sPriv->CursorInfoPtr)) {
            use_hw_cursor = FALSE;
	    break;
	}
    }

unlock:
    input_unlock();

    return use_hw_cursor;
}

static Bool
xf86ScreenSetCursor(ScreenPtr pScreen, CursorPtr pCurs, int x, int y)
{
    xf86CursorScreenPtr ScreenPriv =
        (xf86CursorScreenPtr) dixLookupPrivate(&pScreen->devPrivates,
                                               xf86CursorScreenKey);

    xf86CursorInfoPtr infoPtr;
    unsigned char *bits;

    if (!ScreenPriv) { /* NULL if Option "SWCursor" */
        return (pCurs == NullCursor);
    }

    infoPtr = ScreenPriv->CursorInfoPtr;

    if (pCurs == NullCursor) {
        (*infoPtr->HideCursor) (infoPtr->pScrn);
        return TRUE;
    }

    /*
     * Hot plugged GPU's do not have a CursorScreenKey, force sw cursor.
     * This check can be removed once dix/privates.c gets relocation code for
     * PRIVATE_CURSOR. Also see the related comment in AddGPUScreen().
     */
    if (!_dixGetScreenPrivateKey(CursorScreenKey, pScreen))
        return FALSE;

    bits =
        dixLookupScreenPrivate(&pCurs->devPrivates, CursorScreenKey, pScreen);

    x -= infoPtr->pScrn->frameX0;
    y -= infoPtr->pScrn->frameY0;

    if (!pCurs->bits->argb || !xf86DriverHasLoadCursorARGB(infoPtr))
        if (!bits) {
            bits = (*infoPtr->RealizeCursor) (infoPtr, pCurs);
            dixSetScreenPrivate(&pCurs->devPrivates, CursorScreenKey, pScreen,
                                bits);
        }

    if (!(infoPtr->Flags & HARDWARE_CURSOR_UPDATE_UNHIDDEN))
        (*infoPtr->HideCursor) (infoPtr->pScrn);

    if (pCurs->bits->argb && xf86DriverHasLoadCursorARGB(infoPtr)) {
        if (!xf86DriverLoadCursorARGB (infoPtr, pCurs))
            return FALSE;
    } else
    if (bits)
        if (!xf86DriverLoadCursorImage (infoPtr, bits))
            return FALSE;

    xf86RecolorCursor_locked (ScreenPriv, pCurs);

    (*infoPtr->SetCursorPosition) (infoPtr->pScrn, x, y);

    return xf86DriverShowCursor(infoPtr);
}

Bool
xf86SetCursor(ScreenPtr pScreen, CursorPtr pCurs, int x, int y)
{
    xf86CursorScreenPtr ScreenPriv =
        (xf86CursorScreenPtr) dixLookupPrivate(&pScreen->devPrivates,
                                               xf86CursorScreenKey);
    ScreenPtr pSlave;
    Bool ret = FALSE;

    input_lock();

    x -= ScreenPriv->HotX;
    y -= ScreenPriv->HotY;

    if (!xf86ScreenSetCursor(pScreen, pCurs, x, y))
        goto out;

    /* ask each slave driver to set the cursor. */
    xorg_list_for_each_entry(pSlave, &pScreen->slave_list, slave_head) {
        if (!RRHasScanoutPixmap(pSlave))
            continue;

        if (!xf86ScreenSetCursor(pSlave, pCurs, x, y)) {
            /*
             * hide the master (and successfully set slave) cursors,
             * otherwise both the hw and sw cursor will show.
             */
            xf86SetCursor(pScreen, NullCursor, x, y);
            goto out;
        }
    }
    ret = TRUE;

 out:
    input_unlock();
    return ret;
}

void
xf86SetTransparentCursor(ScreenPtr pScreen)
{
    xf86CursorScreenPtr ScreenPriv =
        (xf86CursorScreenPtr) dixLookupPrivate(&pScreen->devPrivates,
                                               xf86CursorScreenKey);
    xf86CursorInfoPtr infoPtr = ScreenPriv->CursorInfoPtr;

    input_lock();

    if (!ScreenPriv->transparentData)
        ScreenPriv->transparentData =
            (*infoPtr->RealizeCursor) (infoPtr, NullCursor);

    if (!(infoPtr->Flags & HARDWARE_CURSOR_UPDATE_UNHIDDEN))
        (*infoPtr->HideCursor) (infoPtr->pScrn);

    if (ScreenPriv->transparentData)
        xf86DriverLoadCursorImage (infoPtr,
                                   ScreenPriv->transparentData);

    xf86DriverShowCursor(infoPtr);

    input_unlock();
}

static void
xf86ScreenMoveCursor(ScreenPtr pScreen, int x, int y)
{
    xf86CursorScreenPtr ScreenPriv =
        (xf86CursorScreenPtr) dixLookupPrivate(&pScreen->devPrivates,
                                               xf86CursorScreenKey);
    xf86CursorInfoPtr infoPtr = ScreenPriv->CursorInfoPtr;

    x -= infoPtr->pScrn->frameX0;
    y -= infoPtr->pScrn->frameY0;

    (*infoPtr->SetCursorPosition) (infoPtr->pScrn, x, y);
}

void
xf86MoveCursor(ScreenPtr pScreen, int x, int y)
{
    xf86CursorScreenPtr ScreenPriv =
        (xf86CursorScreenPtr) dixLookupPrivate(&pScreen->devPrivates,
                                               xf86CursorScreenKey);
    ScreenPtr pSlave;

    input_lock();

    x -= ScreenPriv->HotX;
    y -= ScreenPriv->HotY;

    xf86ScreenMoveCursor(pScreen, x, y);

    /* ask each slave driver to move the cursor */
    xorg_list_for_each_entry(pSlave, &pScreen->slave_list, slave_head) {
        if (!RRHasScanoutPixmap(pSlave))
            continue;

        xf86ScreenMoveCursor(pSlave, x, y);
    }

    input_unlock();
}

static void
xf86RecolorCursor_locked(xf86CursorScreenPtr ScreenPriv, CursorPtr pCurs)
{
    xf86CursorInfoPtr infoPtr = ScreenPriv->CursorInfoPtr;

    /* recoloring isn't applicable to ARGB cursors and drivers
       shouldn't have to ignore SetCursorColors requests */
    if (pCurs->bits->argb)
        return;

    if (ScreenPriv->PalettedCursor) {
        xColorItem sourceColor, maskColor;
        ColormapPtr pmap = ScreenPriv->pInstalledMap;

        if (!pmap)
            return;

        sourceColor.red = pCurs->foreRed;
        sourceColor.green = pCurs->foreGreen;
        sourceColor.blue = pCurs->foreBlue;
        FakeAllocColor(pmap, &sourceColor);
        maskColor.red = pCurs->backRed;
        maskColor.green = pCurs->backGreen;
        maskColor.blue = pCurs->backBlue;
        FakeAllocColor(pmap, &maskColor);
        FakeFreeColor(pmap, sourceColor.pixel);
        FakeFreeColor(pmap, maskColor.pixel);
        (*infoPtr->SetCursorColors) (infoPtr->pScrn,
                                     maskColor.pixel, sourceColor.pixel);
    }
    else {                      /* Pass colors in 8-8-8 RGB format */
        (*infoPtr->SetCursorColors) (infoPtr->pScrn,
                                     (pCurs->backBlue >> 8) |
                                     ((pCurs->backGreen >> 8) << 8) |
                                     ((pCurs->backRed >> 8) << 16),
                                     (pCurs->foreBlue >> 8) |
                                     ((pCurs->foreGreen >> 8) << 8) |
                                     ((pCurs->foreRed >> 8) << 16)
            );
    }
}

void
xf86RecolorCursor(ScreenPtr pScreen, CursorPtr pCurs, Bool displayed)
{
    xf86CursorScreenPtr ScreenPriv =
        (xf86CursorScreenPtr) dixLookupPrivate(&pScreen->devPrivates,
                                               xf86CursorScreenKey);

    input_lock();
    xf86RecolorCursor_locked (ScreenPriv, pCurs);
    input_unlock();
}

/* These functions assume that MaxWidth is a multiple of 32 */
static unsigned char *
RealizeCursorInterleave0(xf86CursorInfoPtr infoPtr, CursorPtr pCurs)
{

    SCANLINE *SrcS, *SrcM, *DstS, *DstM;
    SCANLINE *pSrc, *pMsk;
    unsigned char *mem;
    int size = (infoPtr->MaxWidth * infoPtr->MaxHeight) >> 2;
    int SrcPitch, DstPitch, Pitch, y, x;

    /* how many words are in the source or mask */
    int words = size / (CUR_BITMAP_SCANLINE_PAD / 4);

    if (!(mem = calloc(1, size)))
        return NULL;

    if (pCurs == NullCursor) {
        if (infoPtr->Flags & HARDWARE_CURSOR_INVERT_MASK) {
            DstM = (SCANLINE *) mem;
            if (!(infoPtr->Flags & HARDWARE_CURSOR_SWAP_SOURCE_AND_MASK))
                DstM += words;
            memset(DstM, -1, words * sizeof(SCANLINE));
        }
        return mem;
    }

    /* SrcPitch == the number of scanlines wide the cursor image is */
    SrcPitch = (pCurs->bits->width + (BITMAP_SCANLINE_PAD - 1)) >>
        CUR_LOG2_BITMAP_PAD;

    /* DstPitch is the width of the hw cursor in scanlines */
    DstPitch = infoPtr->MaxWidth >> CUR_LOG2_BITMAP_PAD;
    Pitch = SrcPitch < DstPitch ? SrcPitch : DstPitch;

    SrcS = (SCANLINE *) pCurs->bits->source;
    SrcM = (SCANLINE *) pCurs->bits->mask;
    DstS = (SCANLINE *) mem;
    DstM = DstS + words;

    if (infoPtr->Flags & HARDWARE_CURSOR_SWAP_SOURCE_AND_MASK) {
        SCANLINE *tmp;

        tmp = DstS;
        DstS = DstM;
        DstM = tmp;
    }

    if (infoPtr->Flags & HARDWARE_CURSOR_AND_SOURCE_WITH_MASK) {
        for (y = pCurs->bits->height, pSrc = DstS, pMsk = DstM;
             y--;
             pSrc += DstPitch, pMsk += DstPitch, SrcS += SrcPitch, SrcM +=
             SrcPitch) {
            for (x = 0; x < Pitch; x++) {
                pSrc[x] = SrcS[x] & SrcM[x];
                pMsk[x] = SrcM[x];
            }
        }
    }
    else {
        for (y = pCurs->bits->height, pSrc = DstS, pMsk = DstM;
             y--;
             pSrc += DstPitch, pMsk += DstPitch, SrcS += SrcPitch, SrcM +=
             SrcPitch) {
            for (x = 0; x < Pitch; x++) {
                pSrc[x] = SrcS[x];
                pMsk[x] = SrcM[x];
            }
        }
    }

    if (infoPtr->Flags & HARDWARE_CURSOR_NIBBLE_SWAPPED) {
        int count = size;
        unsigned char *pntr1 = (unsigned char *) DstS;
        unsigned char *pntr2 = (unsigned char *) DstM;
        unsigned char a, b;

        while (count) {

            a = *pntr1;
            b = *pntr2;
            *pntr1 = ((a & 0xF0) >> 4) | ((a & 0x0F) << 4);
            *pntr2 = ((b & 0xF0) >> 4) | ((b & 0x0F) << 4);
            pntr1++;
            pntr2++;
            count -= 2;
        }
    }

    /*
     * Must be _after_ HARDWARE_CURSOR_AND_SOURCE_WITH_MASK to avoid wiping
     * out entire source mask.
     */
    if (infoPtr->Flags & HARDWARE_CURSOR_INVERT_MASK) {
        int count = words;
        SCANLINE *pntr = DstM;

        while (count--) {
            *pntr = ~(*pntr);
            pntr++;
        }
    }

    if (infoPtr->Flags & HARDWARE_CURSOR_BIT_ORDER_MSBFIRST) {
        for (y = pCurs->bits->height, pSrc = DstS, pMsk = DstM;
             y--; pSrc += DstPitch, pMsk += DstPitch) {
            for (x = 0; x < Pitch; x++) {
                pSrc[x] = REVERSE_BIT_ORDER(pSrc[x]);
                pMsk[x] = REVERSE_BIT_ORDER(pMsk[x]);
            }
        }
    }

    return mem;
}

static unsigned char *
RealizeCursorInterleave1(xf86CursorInfoPtr infoPtr, CursorPtr pCurs)
{
    unsigned char *DstS, *DstM;
    unsigned char *pntr;
    unsigned char *mem, *mem2;
    int count;
    int size = (infoPtr->MaxWidth * infoPtr->MaxHeight) >> 2;

    /* Realize the cursor without interleaving */
    if (!(mem2 = RealizeCursorInterleave0(infoPtr, pCurs)))
        return NULL;

    if (!(mem = calloc(1, size))) {
        free(mem2);
        return NULL;
    }

    /* 1 bit interleave */
    DstS = mem2;
    DstM = DstS + (size >> 1);
    pntr = mem;
    count = size;
    while (count) {
        *pntr++ = ((*DstS & 0x01)) | ((*DstM & 0x01) << 1) |
            ((*DstS & 0x02) << 1) | ((*DstM & 0x02) << 2) |
            ((*DstS & 0x04) << 2) | ((*DstM & 0x04) << 3) |
            ((*DstS & 0x08) << 3) | ((*DstM & 0x08) << 4);
        *pntr++ = ((*DstS & 0x10) >> 4) | ((*DstM & 0x10) >> 3) |
            ((*DstS & 0x20) >> 3) | ((*DstM & 0x20) >> 2) |
            ((*DstS & 0x40) >> 2) | ((*DstM & 0x40) >> 1) |
            ((*DstS & 0x80) >> 1) | ((*DstM & 0x80));
        DstS++;
        DstM++;
        count -= 2;
    }

    /* Free the uninterleaved cursor */
    free(mem2);

    return mem;
}

static unsigned char *
RealizeCursorInterleave8(xf86CursorInfoPtr infoPtr, CursorPtr pCurs)
{
    unsigned char *DstS, *DstM;
    unsigned char *pntr;
    unsigned char *mem, *mem2;
    int count;
    int size = (infoPtr->MaxWidth * infoPtr->MaxHeight) >> 2;

    /* Realize the cursor without interleaving */
    if (!(mem2 = RealizeCursorInterleave0(infoPtr, pCurs)))
        return NULL;

    if (!(mem = calloc(1, size))) {
        free(mem2);
        return NULL;
    }

    /* 8 bit interleave */
    DstS = mem2;
    DstM = DstS + (size >> 1);
    pntr = mem;
    count = size;
    while (count) {
        *pntr++ = *DstS++;
        *pntr++ = *DstM++;
        count -= 2;
    }

    /* Free the uninterleaved cursor */
    free(mem2);

    return mem;
}

static unsigned char *
RealizeCursorInterleave16(xf86CursorInfoPtr infoPtr, CursorPtr pCurs)
{
    unsigned short *DstS, *DstM;
    unsigned short *pntr;
    unsigned char *mem, *mem2;
    int count;
    int size = (infoPtr->MaxWidth * infoPtr->MaxHeight) >> 2;

    /* Realize the cursor without interleaving */
    if (!(mem2 = RealizeCursorInterleave0(infoPtr, pCurs)))
        return NULL;

    if (!(mem = calloc(1, size))) {
        free(mem2);
        return NULL;
    }

    /* 16 bit interleave */
    DstS = (void *) mem2;
    DstM = DstS + (size >> 2);
    pntr = (void *) mem;
    count = (size >> 1);
    while (count) {
        *pntr++ = *DstS++;
        *pntr++ = *DstM++;
        count -= 2;
    }

    /* Free the uninterleaved cursor */
    free(mem2);

    return mem;
}

static unsigned char *
RealizeCursorInterleave32(xf86CursorInfoPtr infoPtr, CursorPtr pCurs)
{
    CARD32 *DstS, *DstM;
    CARD32 *pntr;
    unsigned char *mem, *mem2;
    int count;
    int size = (infoPtr->MaxWidth * infoPtr->MaxHeight) >> 2;

    /* Realize the cursor without interleaving */
    if (!(mem2 = RealizeCursorInterleave0(infoPtr, pCurs)))
        return NULL;

    if (!(mem = calloc(1, size))) {
        free(mem2);
        return NULL;
    }

    /* 32 bit interleave */
    DstS = (void *) mem2;
    DstM = DstS + (size >> 3);
    pntr = (void *) mem;
    count = (size >> 2);
    while (count) {
        *pntr++ = *DstS++;
        *pntr++ = *DstM++;
        count -= 2;
    }

    /* Free the uninterleaved cursor */
    free(mem2);

    return mem;
}

static unsigned char *
RealizeCursorInterleave64(xf86CursorInfoPtr infoPtr, CursorPtr pCurs)
{
    CARD32 *DstS, *DstM;
    CARD32 *pntr;
    unsigned char *mem, *mem2;
    int count;
    int size = (infoPtr->MaxWidth * infoPtr->MaxHeight) >> 2;

    /* Realize the cursor without interleaving */
    if (!(mem2 = RealizeCursorInterleave0(infoPtr, pCurs)))
        return NULL;

    if (!(mem = calloc(1, size))) {
        free(mem2);
        return NULL;
    }

    /* 64 bit interleave */
    DstS = (void *) mem2;
    DstM = DstS + (size >> 3);
    pntr = (void *) mem;
    count = (size >> 2);
    while (count) {
        *pntr++ = *DstS++;
        *pntr++ = *DstS++;
        *pntr++ = *DstM++;
        *pntr++ = *DstM++;
        count -= 4;
    }

    /* Free the uninterleaved cursor */
    free(mem2);

    return mem;
}
