/**************************************************************
 *
 * Support for using the Quartz Window Manager cursor
 *
 **************************************************************/
/*
 * Copyright (c) 2001-2003 Torrey T. Lyons and Greg Parker.
 *                 All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 * Except as contained in this notice, the name(s) of the above copyright
 * holders shall not be used in advertising or otherwise to promote the sale,
 * use or other dealings in this Software without prior written authorization.
 */
/* $XFree86: xc/programs/Xserver/hw/darwin/quartz/quartzCursor.c,v 1.5 2003/01/23 00:34:26 torrey Exp $ */

#include "quartzCommon.h"
#include "quartzCursor.h"
#include "darwin.h"

#include <pthread.h>

#include "mi.h"
#include "scrnintstr.h"
#include "cursorstr.h"
#include "mipointrst.h"
#include "globals.h"

// Size of the QuickDraw cursor
#define CURSORWIDTH 16
#define CURSORHEIGHT 16

typedef struct {
    int                     qdCursorMode;
    int                     qdCursorVisible;
    int                     useQDCursor;
    QueryBestSizeProcPtr    QueryBestSize;
    miPointerSpriteFuncPtr  spriteFuncs;
} QuartzCursorScreenRec, *QuartzCursorScreenPtr;

static int darwinCursorScreenIndex = -1;
static unsigned long darwinCursorGeneration = 0;
static CursorPtr quartzLatentCursor = NULL;
static QD_Cursor gQDArrow; // QuickDraw arrow cursor

// Cursor for the main thread to set (NULL = arrow cursor).
static CCrsrHandle currentCursor = NULL;
static pthread_mutex_t cursorMutex;
static pthread_cond_t cursorCondition;

#define CURSOR_PRIV(pScreen) \
    ((QuartzCursorScreenPtr)pScreen->devPrivates[darwinCursorScreenIndex].ptr)

#define HIDE_QD_CURSOR(pScreen, visible)                                \
    if (visible) {                                                      \
        int ix;                                                         \
        for (ix = 0; ix < QUARTZ_PRIV(pScreen)->displayCount; ix++) {   \
            CGDisplayHideCursor(QUARTZ_PRIV(pScreen)->displayIDs[ix]);  \
        }                                                               \
        visible = FALSE;                                                \
    } ((void)0)

#define SHOW_QD_CURSOR(pScreen, visible)                                \
    {                                                                   \
        int ix;                                                         \
        for (ix = 0; ix < QUARTZ_PRIV(pScreen)->displayCount; ix++) {   \
            CGDisplayShowCursor(QUARTZ_PRIV(pScreen)->displayIDs[ix]);  \
        }                                                               \
        visible = TRUE;                                                 \
    } ((void)0)

#define CHANGE_QD_CURSOR(cursorH)                                       \
    if (!quartzServerQuitting) {                                        \
        /* Acquire lock and tell the main thread to change cursor */    \
        pthread_mutex_lock(&cursorMutex);                               \
        currentCursor = (CCrsrHandle) (cursorH);                        \
        QuartzMessageMainThread(kQuartzCursorUpdate, NULL, 0);          \
                                                                        \
        /* Wait for the main thread to change the cursor */             \
        pthread_cond_wait(&cursorCondition, &cursorMutex);              \
        pthread_mutex_unlock(&cursorMutex);                             \
    } ((void)0)


/*
 * MakeQDCursor helpers: CTAB_ENTER, interleave
 */

// Add a color entry to a ctab
#define CTAB_ENTER(ctab, index, r, g, b)                                \
    ctab->ctTable[index].value = index;                                 \
    ctab->ctTable[index].rgb.red = r;                                   \
    ctab->ctTable[index].rgb.green = g;                                 \
    ctab->ctTable[index].rgb.blue = b

// Make an unsigned short by interleaving the bits of bytes c1 and c2.
// High bit of c1 is first; low bit of c2 is last.
// Interleave is a built-in INTERCAL operator.
static unsigned short
interleave(
    unsigned char c1,
    unsigned char c2 )
{
    return
        ((c1 & 0x80) << 8) | ((c2 & 0x80) << 7) |
        ((c1 & 0x40) << 7) | ((c2 & 0x40) << 6) |
        ((c1 & 0x20) << 6) | ((c2 & 0x20) << 5) |
        ((c1 & 0x10) << 5) | ((c2 & 0x10) << 4) |
        ((c1 & 0x08) << 4) | ((c2 & 0x08) << 3) |
        ((c1 & 0x04) << 3) | ((c2 & 0x04) << 2) |
        ((c1 & 0x02) << 2) | ((c2 & 0x02) << 1) |
        ((c1 & 0x01) << 1) | ((c2 & 0x01) << 0) ;
}

/*
 * MakeQDCursor
 * Make a QuickDraw color cursor from the given X11 cursor.
 * Warning: This code is nasty. Color cursors were meant to be read
 * from resources; constructing the structures programmatically is messy.
 */
/*
    QuickDraw cursor representation:
    Our color cursor is a 2 bit per pixel pixmap.
    Each pixel's bits are (source<<1 | mask) from the original X cursor pixel.
    The cursor's color table maps the colors like this:
    (2-bit value | X result    | colortable | Mac result)
             00  | transparent | white      | transparent (white outside mask)
             01  | back color  | back color | back color
             10  | undefined   | black      | invert background (just for fun)
             11  | fore color  | fore color | fore color
*/
static CCrsrHandle
MakeQDCursor(
    CursorPtr pCursor )
{
    CCrsrHandle result;
    CCrsrPtr curs;
    int i, w, h;
    unsigned short rowMask;
    PixMap *pix;
    ColorTable *ctab;
    unsigned short *image;

    result = (CCrsrHandle) NewHandleClear(sizeof(CCrsr));
    if (!result) return NULL;
    HLock((Handle)result);
    curs = *result;

    // Initialize CCrsr
    curs->crsrType = 0x8001;     // 0x8000 = b&w, 0x8001 = color
    curs->crsrMap = (PixMapHandle) NewHandleClear(sizeof(PixMap));
    if (!curs->crsrMap) goto pixAllocFailed;
    HLock((Handle)curs->crsrMap);
    pix = *curs->crsrMap;
    curs->crsrData = NULL;       // raw cursor image data (set below)
    curs->crsrXData = NULL;      // QD's processed data
    curs->crsrXValid = 0;        // zero means QD must re-process cursor data
    curs->crsrXHandle = NULL;    // reserved
    memset(curs->crsr1Data, 0, CURSORWIDTH*CURSORHEIGHT/8); // b&w data
    memset(curs->crsrMask,  0, CURSORWIDTH*CURSORHEIGHT/8); // b&w & color mask
    curs->crsrHotSpot.h = min(CURSORWIDTH,  pCursor->bits->xhot); // hot spot
    curs->crsrHotSpot.v = min(CURSORHEIGHT, pCursor->bits->yhot); // hot spot
    curs->crsrXTable = 0;        // reserved
    curs->crsrID = GetCTSeed();  // unique ID from Color Manager

    // Set the b&w data and mask
    w = min(pCursor->bits->width,  CURSORWIDTH);
    h = min(pCursor->bits->height, CURSORHEIGHT);
    rowMask = ~((1 << (CURSORWIDTH - w)) - 1);
    for (i = 0; i < h; i++) {
        curs->crsr1Data[i] = rowMask &
        ((pCursor->bits->source[i*4]<<8) | pCursor->bits->source[i*4+1]);
        curs->crsrMask[i] = rowMask &
        ((pCursor->bits->mask[i*4]<<8)   | pCursor->bits->mask[i*4+1]);
    }

    // Set the color data and mask
    // crsrMap: defines bit depth and size and colortable only
    pix->rowBytes = (CURSORWIDTH * 2 / 8) | 0x8000; // last bit on means PixMap
    SetRect(&pix->bounds, 0, 0, CURSORWIDTH, CURSORHEIGHT); // see TN 1020
    pix->pixelSize = 2;
    pix->cmpCount = 1;
    pix->cmpSize = 2;
    // pix->pmTable set below

    // crsrData is the pixel data. crsrMap's baseAddr is not used.
    curs->crsrData = NewHandleClear(CURSORWIDTH*CURSORHEIGHT * 2 / 8);
    if (!curs->crsrData) goto imageAllocFailed;
    HLock((Handle)curs->crsrData);
    image = (unsigned short *) *curs->crsrData;
    // Pixel data is just 1-bit data and mask interleaved (see above)
    for (i = 0; i < h; i++) {
        unsigned char s, m;
        s = pCursor->bits->source[i*4] & (rowMask >> 8);
        m = pCursor->bits->mask[i*4] & (rowMask >> 8);
        image[2*i] = interleave(s, m);
        s = pCursor->bits->source[i*4+1] & (rowMask & 0x00ff);
        m = pCursor->bits->mask[i*4+1] & (rowMask & 0x00ff);
        image[2*i+1] = interleave(s, m);
    }

    // Build the color table (entries described above)
    // NewPixMap allocates a color table handle.
    pix->pmTable = (CTabHandle) NewHandleClear(sizeof(ColorTable) + 3
                    * sizeof(ColorSpec));
    if (!pix->pmTable) goto ctabAllocFailed;
    HLock((Handle)pix->pmTable);
    ctab = *pix->pmTable;
    ctab->ctSeed = GetCTSeed();
    ctab->ctFlags = 0;
    ctab->ctSize = 3; // color count - 1
    CTAB_ENTER(ctab, 0, 0xffff, 0xffff, 0xffff);
    CTAB_ENTER(ctab, 1, pCursor->backRed, pCursor->backGreen,
               pCursor->backBlue);
    CTAB_ENTER(ctab, 2, 0x0000, 0x0000, 0x0000);
    CTAB_ENTER(ctab, 3, pCursor->foreRed, pCursor->foreGreen,
               pCursor->foreBlue);

    HUnlock((Handle)pix->pmTable); // ctab
    HUnlock((Handle)curs->crsrData); // image data
    HUnlock((Handle)curs->crsrMap); // pix
    HUnlock((Handle)result); // cursor

    return result;

    // "What we have here is a failure to allocate"
ctabAllocFailed:
    HUnlock((Handle)curs->crsrData);
    DisposeHandle((Handle)curs->crsrData);
imageAllocFailed:
    HUnlock((Handle)curs->crsrMap);
    DisposeHandle((Handle)curs->crsrMap);
pixAllocFailed:
    HUnlock((Handle)result);
    DisposeHandle((Handle)result);
    return NULL;
}


/*
 * FreeQDCursor
 * Destroy a QuickDraw color cursor created with MakeQDCursor().
 * The cursor must not currently be on screen.
 */
static void FreeQDCursor(CCrsrHandle cursHandle)
{
    CCrsrPtr curs;
    PixMap *pix;

    HLock((Handle)cursHandle);
    curs = *cursHandle;
    HLock((Handle)curs->crsrMap);
    pix = *curs->crsrMap;
    DisposeHandle((Handle)pix->pmTable);
    HUnlock((Handle)curs->crsrMap);
    DisposeHandle((Handle)curs->crsrMap);
    DisposeHandle((Handle)curs->crsrData);
    HUnlock((Handle)cursHandle);
    DisposeHandle((Handle)cursHandle);
}


/*
===========================================================================

 Pointer sprite functions

===========================================================================
*/

/*
 * QuartzRealizeCursor
 * Convert the X cursor representation to QuickDraw format if possible.
 */
Bool
QuartzRealizeCursor(
    ScreenPtr pScreen,
    CursorPtr pCursor )
{
    CCrsrHandle qdCursor;
    QuartzCursorScreenPtr ScreenPriv = CURSOR_PRIV(pScreen);

    if(!pCursor || !pCursor->bits)
        return FALSE;

    // if the cursor is too big we use a software cursor
    if ((pCursor->bits->height > CURSORHEIGHT) ||
        (pCursor->bits->width > CURSORWIDTH) || !ScreenPriv->useQDCursor)
    {
        if (quartzRootless) {
            // rootless can't use a software cursor
            return TRUE;
        } else {
            return (*ScreenPriv->spriteFuncs->RealizeCursor)
                        (pScreen, pCursor);
        }
    }

    // make new cursor image
    qdCursor = MakeQDCursor(pCursor);
    if (!qdCursor) return FALSE;

    // save the result
    pCursor->devPriv[pScreen->myNum] = (pointer) qdCursor;

    return TRUE;
}


/*
 * QuartzUnrealizeCursor
 * Free the storage space associated with a realized cursor.
 */
Bool
QuartzUnrealizeCursor(
    ScreenPtr pScreen,
    CursorPtr pCursor )
{
    QuartzCursorScreenPtr ScreenPriv = CURSOR_PRIV(pScreen);

    if ((pCursor->bits->height > CURSORHEIGHT) ||
        (pCursor->bits->width > CURSORWIDTH) || !ScreenPriv->useQDCursor)
    {
        if (quartzRootless) {
            return TRUE;
        } else {
            return (*ScreenPriv->spriteFuncs->UnrealizeCursor)
                        (pScreen, pCursor);
        }
    } else {
        CCrsrHandle oldCursor = (CCrsrHandle) pCursor->devPriv[pScreen->myNum];

        if (currentCursor != oldCursor) {
            // This should only fail when quitting, in which case we just leak.
            FreeQDCursor(oldCursor);
        }
        pCursor->devPriv[pScreen->myNum] = NULL;
        return TRUE;
    }
}


/*
 * QuartzSetCursor
 * Set the cursor sprite and position.
 * Use QuickDraw cursor if possible.
 */
static void
QuartzSetCursor(
    ScreenPtr       pScreen,
    CursorPtr       pCursor,
    int             x,
    int             y)
{
    QuartzCursorScreenPtr ScreenPriv = CURSOR_PRIV(pScreen);

    quartzLatentCursor = pCursor;

    // Don't touch Mac OS cursor if X is hidden!
    if (!quartzServerVisible)
        return;

    if (!pCursor) {
        // Remove the cursor completely.
        HIDE_QD_CURSOR(pScreen, ScreenPriv->qdCursorVisible);
        if (! ScreenPriv->qdCursorMode)
            (*ScreenPriv->spriteFuncs->SetCursor)(pScreen, 0, x, y);
    }
    else if ((pCursor->bits->height <= CURSORHEIGHT) &&
             (pCursor->bits->width <= CURSORWIDTH) && ScreenPriv->useQDCursor)
    {
        // Cursor is small enough to use QuickDraw directly.
        if (! ScreenPriv->qdCursorMode)    // remove the X cursor
            (*ScreenPriv->spriteFuncs->SetCursor)(pScreen, 0, x, y);
        ScreenPriv->qdCursorMode = TRUE;

        CHANGE_QD_CURSOR(pCursor->devPriv[pScreen->myNum]);
        SHOW_QD_CURSOR(pScreen, ScreenPriv->qdCursorVisible);
    }
    else if (quartzRootless) {
        // Rootless can't use a software cursor, so we just use Mac OS arrow.
        CHANGE_QD_CURSOR(NULL);
        SHOW_QD_CURSOR(pScreen, ScreenPriv->qdCursorVisible);
    }
    else {
        // Cursor is too big for QuickDraw. Use X software cursor.
        HIDE_QD_CURSOR(pScreen, ScreenPriv->qdCursorVisible);
        ScreenPriv->qdCursorMode = FALSE;
        (*ScreenPriv->spriteFuncs->SetCursor)(pScreen, pCursor, x, y);
    }
}


/*
 * QuartzReallySetCursor
 * Set the QuickDraw cursor. Called from the main thread since changing the
 * cursor with QuickDraw is not thread safe on dual processor machines.
 */
void
QuartzReallySetCursor()
{
    pthread_mutex_lock(&cursorMutex);

    if (currentCursor) {
        SetCCursor(currentCursor);
    } else {
        SetCursor(&gQDArrow);
    }

    pthread_cond_signal(&cursorCondition);
    pthread_mutex_unlock(&cursorMutex);
}


/*
 * QuartzMoveCursor
 * Move the cursor. This is a noop for QuickDraw.
 */
static void
QuartzMoveCursor(
    ScreenPtr   pScreen,
    int         x,
    int         y)
{
    QuartzCursorScreenPtr ScreenPriv = CURSOR_PRIV(pScreen);

    // only the X cursor needs to be explicitly moved
    if (!ScreenPriv->qdCursorMode)
        (*ScreenPriv->spriteFuncs->MoveCursor)(pScreen, x, y);
}


static miPointerSpriteFuncRec quartzSpriteFuncsRec = {
    QuartzRealizeCursor,
    QuartzUnrealizeCursor,
    QuartzSetCursor,
    QuartzMoveCursor
};


/*
===========================================================================

 Pointer screen functions

===========================================================================
*/

/*
 * QuartzCursorOffScreen
 */
static Bool QuartzCursorOffScreen(ScreenPtr *pScreen, int *x, int *y)
{
    return FALSE;
}


/*
 * QuartzCrossScreen
 */
static void QuartzCrossScreen(ScreenPtr pScreen, Bool entering)
{
    return;
}


/*
 * QuartzWarpCursor
 *  Change the cursor position without generating an event or motion history.
 *  The input coordinates (x,y) are in pScreen-local X11 coordinates.
 *
 */
static void
QuartzWarpCursor(
    ScreenPtr               pScreen,
    int                     x,
    int                     y)
{
    static int              neverMoved = TRUE;

    if (neverMoved) {
        // Don't move the cursor the first time. This is the jump-to-center
        // initialization, and it's annoying because we may still be in MacOS.
        neverMoved = FALSE;
        return;
    }

    if (quartzServerVisible) {
        CGDisplayErr        cgErr;
        CGPoint             cgPoint;
        // Only need to do this for one display. Any display will do.
        CGDirectDisplayID   cgID = QUARTZ_PRIV(pScreen)->displayIDs[0];
        CGRect              cgRect = CGDisplayBounds(cgID);

        // Convert (x,y) to CoreGraphics screen-local CG coordinates.
        // This is necessary because the X11 screen and CG screen may not
        // coincide. (e.g. X11 screen may be moved to dodge the menu bar)

        // Make point in X11 global coordinates
        cgPoint = CGPointMake(x + dixScreenOrigins[pScreen->myNum].x,
                              y + dixScreenOrigins[pScreen->myNum].y);
        // Shift to CoreGraphics global screen coordinates
        cgPoint.x += darwinMainScreenX;
        cgPoint.y += darwinMainScreenY;
        // Shift to CoreGraphics screen-local coordinates
        cgPoint.x -= cgRect.origin.x;
        cgPoint.y -= cgRect.origin.y;

        cgErr = CGDisplayMoveCursorToPoint(cgID, cgPoint);
        if (cgErr != CGDisplayNoErr) {
            ErrorF("Could not set cursor position with error code 0x%x.\n",
                    cgErr);
        }
    }

    miPointerWarpCursor(pScreen, x, y);
    miPointerUpdate();
}


static miPointerScreenFuncRec quartzScreenFuncsRec = {
    QuartzCursorOffScreen,
    QuartzCrossScreen,
    QuartzWarpCursor,
    DarwinEQPointerPost,
    DarwinEQSwitchScreen
};


/*
===========================================================================

 Other screen functions

===========================================================================
*/

/*
 * QuartzCursorQueryBestSize
 * Handle queries for best cursor size
 */
static void
QuartzCursorQueryBestSize(
   int              class,
   unsigned short   *width,
   unsigned short   *height,
   ScreenPtr        pScreen)
{
    QuartzCursorScreenPtr ScreenPriv = CURSOR_PRIV(pScreen);

    if (class == CursorShape) {
        *width = CURSORWIDTH;
        *height = CURSORHEIGHT;
    } else {
        (*ScreenPriv->QueryBestSize)(class, width, height, pScreen);
    }
}


/*
 * QuartzInitCursor
 * Initialize cursor support
 */
Bool
QuartzInitCursor(
    ScreenPtr   pScreen )
{
    QuartzCursorScreenPtr   ScreenPriv;
    miPointerScreenPtr      PointPriv;
    DarwinFramebufferPtr    dfb = SCREEN_PRIV(pScreen);

    // initialize software cursor handling (always needed as backup)
    if (!miDCInitialize(pScreen, &quartzScreenFuncsRec)) {
        return FALSE;
    }

    // allocate private storage for this screen's QuickDraw cursor info
    if (darwinCursorGeneration != serverGeneration) {
        if ((darwinCursorScreenIndex = AllocateScreenPrivateIndex()) < 0)
            return FALSE;
        darwinCursorGeneration = serverGeneration;
    }

    ScreenPriv = xcalloc( 1, sizeof(QuartzCursorScreenRec) );
    if (!ScreenPriv) return FALSE;

    CURSOR_PRIV(pScreen) = ScreenPriv;

    // override some screen procedures
    ScreenPriv->QueryBestSize = pScreen->QueryBestSize;
    pScreen->QueryBestSize = QuartzCursorQueryBestSize;

    // initialize QuickDraw cursor handling
    GetQDGlobalsArrow(&gQDArrow);
    PointPriv = (miPointerScreenPtr)
                    pScreen->devPrivates[miPointerScreenIndex].ptr;

    ScreenPriv->spriteFuncs = PointPriv->spriteFuncs;
    PointPriv->spriteFuncs = &quartzSpriteFuncsRec;

    if (!quartzRootless)
        ScreenPriv->useQDCursor = QuartzFSUseQDCursor(dfb->colorBitsPerPixel);
    else
        ScreenPriv->useQDCursor = TRUE;
    ScreenPriv->qdCursorMode = TRUE;
    ScreenPriv->qdCursorVisible = TRUE;

    // initialize cursor mutex lock
    pthread_mutex_init(&cursorMutex, NULL);

    // initialize condition for waiting
    pthread_cond_init(&cursorCondition, NULL);

    return TRUE;
}


// X server is hiding. Restore the Aqua cursor.
void QuartzSuspendXCursor(
    ScreenPtr pScreen )
{
    QuartzCursorScreenPtr ScreenPriv = CURSOR_PRIV(pScreen);

    CHANGE_QD_CURSOR(NULL);
    SHOW_QD_CURSOR(pScreen, ScreenPriv->qdCursorVisible);
}


// X server is showing. Restore the X cursor.
void QuartzResumeXCursor(
    ScreenPtr pScreen,
    int x,
    int y )
{
    QuartzSetCursor(pScreen, quartzLatentCursor, x, y);
}
