/*
 * Copyright 2001-2004 Red Hat Inc., Durham, North Carolina.
 *
 * 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 on 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 (including the
 * next paragraph) 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
 * NON-INFRINGEMENT.  IN NO EVENT SHALL RED HAT AND/OR THEIR SUPPLIERS
 * 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.
 */

/*
 * Authors:
 *   David H. Dawes <dawes@xfree86.org>
 *   Kevin E. Martin <kem@redhat.com>
 *   Rickard E. (Rik) Faith <faith@redhat.com>
 *
 */

/** \file
 * This file contains code than supports cursor movement, including the
 * code that initializes and reinitializes the screen positions and
 * computes screen overlap.
 *
 * "This code is based very closely on the XFree86 equivalent
 * (xfree86/common/xf86Cursor.c)."  --David Dawes.
 *
 * "This code was then extensively re-written, as explained here."
 * --Rik Faith
 *
 * The code in xf86Cursor.c used edge lists to implement the
 * CursorOffScreen function.  The edge list computation was complex
 * (especially in the face of arbitrarily overlapping screens) compared
 * with the speed savings in the CursorOffScreen function.  The new
 * implementation has erred on the side of correctness, readability, and
 * maintainability over efficiency.  For the common (non-edge) case, the
 * dmxCursorOffScreen function does avoid a loop over all the screens.
 * When the cursor has left the screen, all the screens are searched,
 * and the first screen (in dmxScreens order) containing the cursor will
 * be returned.  If run-time profiling shows that this routing is a
 * performance bottle-neck, then an edge list may have to be
 * reimplemented.  An edge list algorithm is O(edges) whereas the new
 * algorithm is O(dmxNumScreens).  Since edges is usually 1-3 and
 * dmxNumScreens may be 30-60 for large backend walls, this trade off
 * may be compelling.
 *
 * The xf86InitOrigins routine uses bit masks during the computation and
 * is therefore limited to the length of a word (e.g., 32 or 64 bits)
 * screens.  Because Xdmx is expected to be used with a large number of
 * backend displays, this limitation was removed.  The new
 * implementation has erred on the side of readability over efficiency,
 * using the dmxSL* routines to manage a screen list instead of a
 * bitmap, and a function call to decrease the length of the main
 * routine.  Both algorithms are of the same order, and both are called
 * only at server generation time, so trading clarity and long-term
 * maintainability for efficiency does not seem justified in this case.
 */

#ifdef HAVE_DMX_CONFIG_H
#include <dmx-config.h>
#endif

#define DMX_CURSOR_DEBUG 0

#include "dmx.h"
#include "dmxsync.h"
#include "dmxcursor.h"
#include "dmxlog.h"
#include "dmxprop.h"
#include "dmxinput.h"

#include "mipointer.h"
#include "windowstr.h"
#include "globals.h"
#include "cursorstr.h"
#include "dixevents.h"          /* For GetSpriteCursor() */
#include "inputstr.h"           /* for inputInfo.pointer */

#if DMX_CURSOR_DEBUG
#define DMXDBG0(f)               dmxLog(dmxDebug,f)
#define DMXDBG1(f,a)             dmxLog(dmxDebug,f,a)
#define DMXDBG2(f,a,b)           dmxLog(dmxDebug,f,a,b)
#define DMXDBG3(f,a,b,c)         dmxLog(dmxDebug,f,a,b,c)
#define DMXDBG4(f,a,b,c,d)       dmxLog(dmxDebug,f,a,b,c,d)
#define DMXDBG5(f,a,b,c,d,e)     dmxLog(dmxDebug,f,a,b,c,d,e)
#define DMXDBG6(f,a,b,c,d,e,g)   dmxLog(dmxDebug,f,a,b,c,d,e,g)
#define DMXDBG7(f,a,b,c,d,e,g,h) dmxLog(dmxDebug,f,a,b,c,d,e,g,h)
#else
#define DMXDBG0(f)
#define DMXDBG1(f,a)
#define DMXDBG2(f,a,b)
#define DMXDBG3(f,a,b,c)
#define DMXDBG4(f,a,b,c,d)
#define DMXDBG5(f,a,b,c,d,e)
#define DMXDBG6(f,a,b,c,d,e,g)
#define DMXDBG7(f,a,b,c,d,e,g,h)
#endif

static int dmxCursorDoMultiCursors = 1;

/** Turn off support for displaying multiple cursors on overlapped
    back-end displays.  See #dmxCursorDoMultiCursors. */
void dmxCursorNoMulti(void)
{
    dmxCursorDoMultiCursors = 0;
}

static Bool dmxCursorOffScreen(ScreenPtr *ppScreen, int *x, int *y)
{
    DMXScreenInfo *dmxScreen;
    int           i;
    int           localX = *x;
    int           localY = *y;
    int           globalX;
    int           globalY;
    
    if (screenInfo.numScreens == 1)
        return FALSE;

                                /* On current screen? */
    dmxScreen = &dmxScreens[(*ppScreen)->myNum];
    if (localX >= 0
        && localX < dmxScreen->rootWidth
        && localY >= 0
        && localY < dmxScreen->rootHeight)
        return FALSE;

                                /* Convert to global coordinate space */
    globalX = dmxScreen->rootXOrigin + localX;
    globalY = dmxScreen->rootYOrigin + localY;

                                /* Is cursor on the current screen?
                                 * This efficiently exits this routine
                                 * for the most common case. */
    if (ppScreen && *ppScreen) {
        dmxScreen = &dmxScreens[(*ppScreen)->myNum];
        if (globalX >= dmxScreen->rootXOrigin
            && globalX < dmxScreen->rootXOrigin + dmxScreen->rootWidth
            && globalY >= dmxScreen->rootYOrigin
            && globalY < dmxScreen->rootYOrigin + dmxScreen->rootHeight)
            return FALSE;
    }

                                /* Find first screen cursor is on */
    for (i = 0; i < dmxNumScreens; i++) {
        dmxScreen = &dmxScreens[i];
        if (globalX >= dmxScreen->rootXOrigin
            && globalX < dmxScreen->rootXOrigin + dmxScreen->rootWidth
            && globalY >= dmxScreen->rootYOrigin
            && globalY < dmxScreen->rootYOrigin + dmxScreen->rootHeight) {
            if (dmxScreen->index == (*ppScreen)->myNum)
                return FALSE;
            *ppScreen = screenInfo.screens[dmxScreen->index];
            *x        = globalX - dmxScreen->rootXOrigin;
            *y        = globalY - dmxScreen->rootYOrigin;
            return TRUE;
        }
    }
    return FALSE;
}

static void dmxCrossScreen(ScreenPtr pScreen, Bool entering)
{
}

static void dmxWarpCursor(DeviceIntPtr pDev, ScreenPtr pScreen, int x, int y)
{
    DMXDBG3("dmxWarpCursor(%d,%d,%d)\n", pScreen->myNum, x, y);
#if 11 /*BP*/
    /* This call is depracated.  Replace with???? */
    miPointerWarpCursor(pDev, pScreen, x, y);
#else
    pScreen->SetCursorPosition(pDev, pScreen, x, y, FALSE);
#endif
}

miPointerScreenFuncRec dmxPointerCursorFuncs =
{
    dmxCursorOffScreen,
    dmxCrossScreen,
    dmxWarpCursor,
    NULL,
    NULL,
};


/** Create a list of screens that we'll manipulate. */
static int *dmxSLCreate(void)
{
    int *list = malloc(dmxNumScreens * sizeof(*list));
    int i;
    
    for (i = 0; i < dmxNumScreens; i++)
        list[i] = 1;
    return list;
}

/** Free list. */
static void dmxSLFree(int *list)
{
    free(list);
}

/** Find next uninitialized entry in list. */
static int dmxSLFindNext(int *list)
{
    int i;
    for (i = 0; i < dmxNumScreens; i++)
        if (list[i])
            return i;
    return -1;
}

/** Make one pass over all the screens and return the number updated. */
static int dmxTryComputeScreenOrigins(int *screensLeft)
{
    ScreenPtr       pScreen, refScreen;
    DMXScreenInfo   *screen;
    int             i, ref;
    int             changed = 0;

    for (i = 0; i < dmxNumScreens; i++) {
        if (!screensLeft[i])
            continue;
        screen  = &dmxScreens[i];
        pScreen = screenInfo.screens[i];
        switch (screen->where) {
        case PosAbsolute:
            pScreen->x = screen->whereX;
            pScreen->y = screen->whereY;
            ++changed, screensLeft[i] = 0;
            break;
        case PosRelative:
            ref = screen->whereRefScreen;
            if (screensLeft[ref])
                break;
            refScreen = screenInfo.screens[ref];
            pScreen->x = refScreen->x + screen->whereX;
            pScreen->y = refScreen->y + screen->whereY;
            ++changed, screensLeft[i] = 0;
            break;
        case PosRightOf:
            ref = screen->whereRefScreen;
            if (screensLeft[ref])
                break;
            refScreen = screenInfo.screens[ref];
            pScreen->x = refScreen->x + refScreen->width;
            pScreen->y = refScreen->y;
            ++changed, screensLeft[i] = 0;
            break;
        case PosLeftOf:
            ref = screen->whereRefScreen;
            if (screensLeft[ref])
                break;
            refScreen = screenInfo.screens[ref];
            pScreen->x = refScreen->x - pScreen->width;
            pScreen->y = refScreen->y;
            ++changed, screensLeft[i] = 0;
            break;
        case PosBelow:
            ref = screen->whereRefScreen;
            if (screensLeft[ref])
                break;
            refScreen = screenInfo.screens[ref];
            pScreen->x = refScreen->x;
            pScreen->y = refScreen->y + refScreen->height;
            ++changed, screensLeft[i] = 0;
            break;
        case PosAbove:
            ref = screen->whereRefScreen;
            if (screensLeft[ref])
                break;
            refScreen = screenInfo.screens[ref];
            pScreen->x = refScreen->x;
            pScreen->y = refScreen->y - pScreen->height;
            ++changed, screensLeft[i] = 0;
            break;
        case PosNone:
            dmxLog(dmxFatal, "No position information for screen %d\n", i);
        }
    }
    return changed;
}

static void dmxComputeScreenOrigins(void)
{
    ScreenPtr       pScreen;
    int             *screensLeft;
    int             i, ref;
    int             minX, minY;

                                /* Compute origins based on
                                 * configuration information. */
    screensLeft = dmxSLCreate();
    while ((i = dmxSLFindNext(screensLeft)) >= 0) {
        while (dmxTryComputeScreenOrigins(screensLeft));
        if ((i = dmxSLFindNext(screensLeft)) >= 0) {
	    /* All of the remaining screens are referencing each other.
	     * Assign a value to one of them and go through again.  This
	     * guarantees that we will eventually terminate.
	     */
	    ref                     = dmxScreens[i].whereRefScreen;
	    pScreen                 = screenInfo.screens[ref];
	    pScreen->x = pScreen->y = 0;
	    screensLeft[ref]        = 0;
	}
    }
    dmxSLFree(screensLeft);


                                /* Justify the topmost and leftmost to
                                 * (0,0). */
    minX = screenInfo.screens[0]->x;
    minY = screenInfo.screens[0]->y;
    for (i = 1; i < dmxNumScreens; i++) { /* Compute minX, minY */
	if (screenInfo.screens[i]->x < minX)
            minX = screenInfo.screens[i]->x;
	if (screenInfo.screens[i]->y < minY)
            minY = screenInfo.screens[i]->y;
    }
    if (minX || minY) {
	for (i = 0; i < dmxNumScreens; i++) {
	    screenInfo.screens[i]->x -= minX;
	    screenInfo.screens[i]->y -= minY;
	}
    }
}

/** Recompute origin information in the #dmxScreens list.  This is
 * called from #dmxInitOrigins. */
void dmxReInitOrigins(void)
{
    int        i;

    if (dmxNumScreens > MAXSCREENS)
        dmxLog(dmxFatal, "dmxNumScreens = %d > MAXSCREENS = %d\n",
               dmxNumScreens, MAXSCREENS);

    for (i = 0; i < dmxNumScreens; i++) {
        DMXScreenInfo    *dmxScreen  = &dmxScreens[i];
        dmxLogOutput(dmxScreen,
                     "s=%dx%d%+d%+d r=%dx%d%+d%+d @%d,%d"
                     " (be=%dx%d depth=%d bpp=%d)\n",
                     dmxScreen->scrnWidth, dmxScreen->scrnHeight,
                     dmxScreen->scrnX, dmxScreen->scrnY,

                     dmxScreen->rootWidth, dmxScreen->rootHeight,
                     dmxScreen->rootX, dmxScreen->rootY,
                     
                     dmxScreen->rootXOrigin, dmxScreen->rootYOrigin,
                     dmxScreen->beWidth, dmxScreen->beHeight,
                     dmxScreen->beDepth, dmxScreen->beBPP);
    }
}

/** Initialize screen origins (and relative position).  This is called
 * for each server generation.  For dynamic reconfiguration, use
 * #dmxReInitOrigins() instead. */
void dmxInitOrigins(void)
{
    int  i;

    if (dmxNumScreens > MAXSCREENS)
        dmxLog(dmxFatal, "dmxNumScreens = %d > MAXSCREENS = %d\n",
               dmxNumScreens, MAXSCREENS);

    for (i = 0; i < dmxNumScreens; i++) {
        DMXScreenInfo    *dmxScreen  = &dmxScreens[i];
        dmxLogOutput(dmxScreen,
                     "(request) s=%dx%d%+d%+d r=%dx%d%+d%+d @%d,%d (%d)"
                     " (be=%dx%d depth=%d bpp=%d)\n",
                     dmxScreen->scrnWidth, dmxScreen->scrnHeight,
                     dmxScreen->scrnX, dmxScreen->scrnY,

                     dmxScreen->rootWidth, dmxScreen->rootHeight,
                     dmxScreen->rootX, dmxScreen->rootY,

                     dmxScreen->whereX, dmxScreen->whereY,
                     dmxScreen->where,

                     dmxScreen->beWidth, dmxScreen->beHeight,
                     dmxScreen->beDepth, dmxScreen->beBPP);
    }

    dmxComputeScreenOrigins();

    for (i = 0; i < dmxNumScreens; i++) {
        DMXScreenInfo  *dmxScreen = &dmxScreens[i];
        dmxScreen->rootXOrigin = screenInfo.screens[i]->x;
        dmxScreen->rootYOrigin = screenInfo.screens[i]->y;
    }

    dmxReInitOrigins();
}

/** Returns non-zero if the global \a x, \a y coordinate is on the
 * screen window of the \a dmxScreen. */
int dmxOnScreen(int x, int y, DMXScreenInfo *dmxScreen)
{
#if DMX_CURSOR_DEBUG > 1
    dmxLog(dmxDebug,
           "dmxOnScreen %d %d,%d (r=%dx%d%+d%+d@%d,%d s=%dx%d%+d%+d)\n",
           dmxScreen->index, x, y,
           dmxScreen->rootWidth, dmxScreen->rootHeight,
           dmxScreen->rootX, dmxScreen->rootY,
           dmxScreen->rootXOrigin, dmxScreen->rootYOrigin,
           dmxScreen->scrnWidth, dmxScreen->scrnHeight,
           dmxScreen->scrnX, dmxScreen->scrnY);
#endif
    if (x >= dmxScreen->rootXOrigin
        && x < dmxScreen->rootXOrigin + dmxScreen->rootWidth
        && y >= dmxScreen->rootYOrigin
        && y < dmxScreen->rootYOrigin + dmxScreen->rootHeight) return 1;
    return 0;
}

/** Returns non-zero if \a a overlaps \a b. */
static int dmxDoesOverlap(DMXScreenInfo *a, DMXScreenInfo *b)
{
    if (dmxOnScreen(a->rootXOrigin,
                    a->rootYOrigin,                 b))
        return 1;

    if (dmxOnScreen(a->rootXOrigin,
                    a->rootYOrigin + a->scrnWidth,  b))
        return 1;

    if (dmxOnScreen(a->rootXOrigin + a->scrnHeight,
                    a->rootYOrigin,                 b))
        return 1;

    if (dmxOnScreen(a->rootXOrigin + a->scrnHeight,
                    a->rootYOrigin + a->scrnWidth,  b))
        return 1;

    if (dmxOnScreen(b->rootXOrigin,
                    b->rootYOrigin,                 a))
        return 1;

    if (dmxOnScreen(b->rootXOrigin,
                    b->rootYOrigin + b->scrnWidth,  a))
        return 1;

    if (dmxOnScreen(b->rootXOrigin + b->scrnHeight,
                    b->rootYOrigin,                 a))
        return 1;

    if (dmxOnScreen(b->rootXOrigin + b->scrnHeight,
                    b->rootYOrigin + b->scrnWidth,  a))
        return 1;

    return 0;
}

/** Used with \a dmxInterateOverlap to print out a list of screens which
 * overlap each other. */
static void *dmxPrintOverlap(DMXScreenInfo *dmxScreen, void *closure)
{
    DMXScreenInfo *a = closure;
    if (dmxScreen != a) {
        if (dmxScreen->cursorNotShared)
            dmxLogOutputCont(a, " [%d/%s]", dmxScreen->index, dmxScreen->name);
        else
            dmxLogOutputCont(a, " %d/%s", dmxScreen->index, dmxScreen->name);
    }
    return NULL;
}

/** Iterate over the screens which overlap with the \a start screen,
 * calling \a f with the \a closure for each argument.  Often used with
 * #dmxPrintOverlap. */
static void *dmxIterateOverlap(DMXScreenInfo *start,
                               void *(*f)(DMXScreenInfo *dmxScreen, void *),
                               void *closure)
{
    DMXScreenInfo *pt;

    if (!start->over) return f(start, closure);

    for (pt = start->over; /* condition at end of loop */; pt = pt->over) {
        void *retval;
        if ((retval = f(pt, closure))) return retval;
        if (pt == start) break;
    }
    return NULL;
}

/** Used with #dmxPropertyIterate to determine if screen \a a is the
 * same as the screen \a closure. */
static void *dmxTestSameDisplay(DMXScreenInfo *a, void *closure)
{
    DMXScreenInfo *b = closure;

    if (a == b)
        return a;
    return NULL;
}

/** Detects overlapping dmxScreens and creates circular lists.  This
 * uses an O(dmxNumScreens^2) algorithm, but dmxNumScreens is < 100 and
 * the computation only needs to be performed for every server
 * generation or dynamic reconfiguration . */
void dmxInitOverlap(void)
{
    int           i, j;
    DMXScreenInfo *a, *b, *pt;

    for (i = 0; i < dmxNumScreens; i++)
        dmxScreens[i].over = NULL;

    for (i = 0; i < dmxNumScreens; i++) {
        a = &dmxScreens[i];
        
        for (j = i+1; j < dmxNumScreens; j++) {
            b = &dmxScreens[j];
            if (b->over)
                continue;
            
            if (dmxDoesOverlap(a, b)) {
                DMXDBG6("%d overlaps %d: a=%p %p b=%p %p\n",
                        a->index, b->index, a, a->over, b, b->over);
                b->over = (a->over ? a->over : a);
                a->over = b;
            }
        }
    }

    for (i = 0; i < dmxNumScreens; i++) {
        a = &dmxScreens[i];
        
        if (!a->over)
            continue;
        
                                /* Flag all pairs that are on same display */
        for (pt = a->over; pt != a; pt = pt->over) {
            if (dmxPropertyIterate(a, dmxTestSameDisplay, pt)) {
                /* The ->over sets contain the transitive set of screens
                 * that overlap.  For screens that are on the same
                 * backend display, we only want to exclude pairs of
                 * screens that mutually overlap on the backend display,
                 * so we call dmxDoesOverlap, which is stricter than the
                 * ->over set. */
                if (!dmxDoesOverlap(a, pt))
                    continue;
                a->cursorNotShared  = 1;
                pt->cursorNotShared = 1;
                dmxLog(dmxInfo,
                       "Screen %d and %d overlap on %s\n",
                       a->index, pt->index, a->name);
            }
        }
    }

    for (i = 0; i < dmxNumScreens; i++) {
        a = &dmxScreens[i];
        
        if (a->over) {
            dmxLogOutput(a, "Overlaps");
            dmxIterateOverlap(a, dmxPrintOverlap, a);
            dmxLogOutputCont(a, "\n");
        }
    }
}

/** Create \a pCursor on the back-end associated with \a pScreen. */
void dmxBECreateCursor(ScreenPtr pScreen, CursorPtr pCursor)
{
    DMXScreenInfo    *dmxScreen = &dmxScreens[pScreen->myNum];
    dmxCursorPrivPtr  pCursorPriv = DMX_GET_CURSOR_PRIV(pCursor, pScreen);
    CursorBitsPtr     pBits = pCursor->bits;
    Pixmap            src, msk;
    XColor            fg, bg;
    XImage           *img;
    XlibGC            gc = NULL;
    XGCValues         v;
    unsigned long     m;
    int               i;

    if (!pCursorPriv)
	return;

    m = GCFunction | GCPlaneMask | GCForeground | GCBackground | GCClipMask;
    v.function = GXcopy;
    v.plane_mask = AllPlanes;
    v.foreground = 1L;
    v.background = 0L;
    v.clip_mask = None;

    for (i = 0; i < dmxScreen->beNumPixmapFormats; i++) {
	if (dmxScreen->bePixmapFormats[i].depth == 1) {
	    /* Create GC in the back-end servers */
	    gc = XCreateGC(dmxScreen->beDisplay, dmxScreen->scrnDefDrawables[i],
			   m, &v);
	    break;
	}
    }
    if (!gc)
        dmxLog(dmxFatal, "dmxRealizeCursor: gc not initialized\n");

    src = XCreatePixmap(dmxScreen->beDisplay, dmxScreen->scrnWin,
			pBits->width, pBits->height, 1);
    msk = XCreatePixmap(dmxScreen->beDisplay, dmxScreen->scrnWin,
			pBits->width, pBits->height, 1);

    img = XCreateImage(dmxScreen->beDisplay,
		       dmxScreen->beVisuals[dmxScreen->beDefVisualIndex].visual,
		       1, XYBitmap, 0, (char *)pBits->source,
		       pBits->width, pBits->height,
		       BitmapPad(dmxScreen->beDisplay), 0);

    XPutImage(dmxScreen->beDisplay, src, gc, img, 0, 0, 0, 0,
	      pBits->width, pBits->height);

    XFree(img);
  
    img = XCreateImage(dmxScreen->beDisplay,
		       dmxScreen->beVisuals[dmxScreen->beDefVisualIndex].visual,
		       1, XYBitmap, 0, (char *)pBits->mask,
		       pBits->width, pBits->height,
		       BitmapPad(dmxScreen->beDisplay), 0);

    XPutImage(dmxScreen->beDisplay, msk, gc, img, 0, 0, 0, 0,
	      pBits->width, pBits->height);

    XFree(img);

    fg.red   = pCursor->foreRed;
    fg.green = pCursor->foreGreen;
    fg.blue  = pCursor->foreBlue;

    bg.red   = pCursor->backRed;
    bg.green = pCursor->backGreen;
    bg.blue  = pCursor->backBlue;

    pCursorPriv->cursor = XCreatePixmapCursor(dmxScreen->beDisplay,
					      src, msk,
					      &fg, &bg,
					      pBits->xhot, pBits->yhot);

    XFreePixmap(dmxScreen->beDisplay, src);
    XFreePixmap(dmxScreen->beDisplay, msk);
    XFreeGC(dmxScreen->beDisplay, gc);

    dmxSync(dmxScreen, FALSE);
}

static Bool _dmxRealizeCursor(ScreenPtr pScreen, CursorPtr pCursor)
{
    DMXScreenInfo    *dmxScreen = &dmxScreens[pScreen->myNum];
    dmxCursorPrivPtr  pCursorPriv;

    DMXDBG2("_dmxRealizeCursor(%d,%p)\n", pScreen->myNum, pCursor);

    DMX_SET_CURSOR_PRIV(pCursor, pScreen, malloc(sizeof(*pCursorPriv)));
    if (!DMX_GET_CURSOR_PRIV(pCursor, pScreen))
	return FALSE;

    pCursorPriv = DMX_GET_CURSOR_PRIV(pCursor, pScreen);
    pCursorPriv->cursor = (Cursor)0;

    if (!dmxScreen->beDisplay)
	return TRUE;

    dmxBECreateCursor(pScreen, pCursor);
    return TRUE;
}

/** Free \a pCursor on the back-end associated with \a pScreen. */
Bool dmxBEFreeCursor(ScreenPtr pScreen, CursorPtr pCursor)
{
    DMXScreenInfo    *dmxScreen = &dmxScreens[pScreen->myNum];
    dmxCursorPrivPtr  pCursorPriv = DMX_GET_CURSOR_PRIV(pCursor, pScreen);

    if (pCursorPriv) {
	XFreeCursor(dmxScreen->beDisplay, pCursorPriv->cursor);
	pCursorPriv->cursor = (Cursor)0;
	return TRUE;
    }

    return FALSE;
}

static Bool _dmxUnrealizeCursor(ScreenPtr pScreen, CursorPtr pCursor)
{
    DMXScreenInfo    *dmxScreen = &dmxScreens[pScreen->myNum];

    DMXDBG2("_dmxUnrealizeCursor(%d,%p)\n",
            pScreen->myNum, pCursor);

    if (dmxScreen->beDisplay) {
	if (dmxBEFreeCursor(pScreen, pCursor))
	    free(DMX_GET_CURSOR_PRIV(pCursor, pScreen));
    }
    DMX_SET_CURSOR_PRIV(pCursor, pScreen, NULL);

    return TRUE;
}

static void _dmxMoveCursor(ScreenPtr pScreen, int x, int y)
{
    DMXScreenInfo *dmxScreen = &dmxScreens[pScreen->myNum];
    int           newX       = x + dmxScreen->rootX;
    int           newY       = y + dmxScreen->rootY;

    if (newX < 0) newX = 0;
    if (newY < 0) newY = 0;

    DMXDBG5("_dmxMoveCursor(%d,%d,%d) -> %d,%d\n",
            pScreen->myNum, x, y, newX, newY);
    if (dmxScreen->beDisplay) {
	XWarpPointer(dmxScreen->beDisplay, None, dmxScreen->scrnWin,
		     0, 0, 0, 0, newX, newY);
	dmxSync(dmxScreen, TRUE);
    }
}

static void _dmxSetCursor(ScreenPtr pScreen, CursorPtr pCursor, int x, int y)
{
    DMXScreenInfo    *dmxScreen = &dmxScreens[pScreen->myNum];

    DMXDBG4("_dmxSetCursor(%d,%p,%d,%d)\n", pScreen->myNum, pCursor, x, y);

    if (pCursor) {
	dmxCursorPrivPtr  pCursorPriv = DMX_GET_CURSOR_PRIV(pCursor, pScreen);
	if (pCursorPriv && dmxScreen->curCursor != pCursorPriv->cursor) {
	    if (dmxScreen->beDisplay)
		XDefineCursor(dmxScreen->beDisplay, dmxScreen->scrnWin,
			      pCursorPriv->cursor);
            dmxScreen->cursor        = pCursor;
	    dmxScreen->curCursor     = pCursorPriv->cursor;
            dmxScreen->cursorVisible = 1;
	}
	_dmxMoveCursor(pScreen, x, y);
    } else {
	if (dmxScreen->beDisplay)
	    XDefineCursor(dmxScreen->beDisplay, dmxScreen->scrnWin,
			  dmxScreen->noCursor);
        dmxScreen->cursor        = NULL;
	dmxScreen->curCursor     = (Cursor)0;
        dmxScreen->cursorVisible = 0;
    }
    if (dmxScreen->beDisplay) dmxSync(dmxScreen, TRUE);
}

static Bool dmxRealizeCursor(DeviceIntPtr pDev, ScreenPtr pScreen, CursorPtr pCursor)
{
    DMXScreenInfo *start = &dmxScreens[pScreen->myNum];
    DMXScreenInfo *pt;

    if (!start->over || !dmxCursorDoMultiCursors || start->cursorNotShared)
        return _dmxRealizeCursor(pScreen, pCursor);

    for (pt = start->over; /* condition at end of loop */; pt = pt->over) {
        if (pt->cursorNotShared)
            continue;
        _dmxRealizeCursor(screenInfo.screens[pt->index], pCursor);
        if (pt == start)
            break;
    }
    return TRUE;
}

static Bool dmxUnrealizeCursor(DeviceIntPtr pDev, ScreenPtr pScreen, CursorPtr pCursor)
{
    DMXScreenInfo *start = &dmxScreens[pScreen->myNum];
    DMXScreenInfo *pt;

    if (!start->over || !dmxCursorDoMultiCursors || start->cursorNotShared)
        return _dmxUnrealizeCursor(pScreen, pCursor);

    for (pt = start->over; /* condition at end of loop */; pt = pt->over) {
        if (pt->cursorNotShared)
            continue;
        _dmxUnrealizeCursor(screenInfo.screens[pt->index], pCursor);
        if (pt == start)
            break;
    }
    return TRUE;
}

static CursorPtr dmxFindCursor(DMXScreenInfo *start)
{
    DMXScreenInfo *pt;

    if (!start || !start->over)
        return GetSpriteCursor(inputInfo.pointer);
    for (pt = start->over; /* condition at end of loop */; pt = pt->over) {
        if (pt->cursor)
            return pt->cursor;
        if (pt == start)
            break;
    }
    return GetSpriteCursor(inputInfo.pointer);
}

/** Move the cursor to coordinates (\a x, \a y)on \a pScreen.  This
 * function is usually called via #dmxPointerSpriteFuncs, except during
 * reconfiguration when the cursor is repositioned to force an update on
 * newley overlapping screens and on screens that no longer overlap.
 *
 * The coords (x,y) are in global coord space.  We'll loop over the
 * back-end screens and see if they contain the global coord.  If so, call
 * _dmxMoveCursor() (XWarpPointer) to position the pointer on that screen.
 */
void dmxMoveCursor(DeviceIntPtr pDev, ScreenPtr pScreen, int x, int y)
{
    DMXScreenInfo *start = &dmxScreens[pScreen->myNum];
    DMXScreenInfo *pt;

    DMXDBG3("dmxMoveCursor(%d,%d,%d)\n", pScreen->myNum, x, y);

    if (!start->over || !dmxCursorDoMultiCursors || start->cursorNotShared) {
        _dmxMoveCursor(pScreen, x, y);
        return;
    }

    for (pt = start->over; /* condition at end of loop */; pt = pt->over) {
        if (pt->cursorNotShared)
            continue;
        if (dmxOnScreen(x + start->rootXOrigin, y + start->rootYOrigin, pt)) {
            if (/* pt != start && */ !pt->cursorVisible) {
                if (!pt->cursor) {
                                /* This only happens during
                                 * reconfiguration when a new overlap
                                 * occurs. */
                    CursorPtr pCursor;
                    
                    if ((pCursor = dmxFindCursor(start)))
                        _dmxRealizeCursor(screenInfo.screens[pt->index],
                                          pt->cursor = pCursor);
                    
                }
                _dmxSetCursor(screenInfo.screens[pt->index],
                              pt->cursor,
                              x + start->rootXOrigin - pt->rootXOrigin,
                              y + start->rootYOrigin - pt->rootYOrigin);
            }
            _dmxMoveCursor(screenInfo.screens[pt->index],
                           x + start->rootXOrigin - pt->rootXOrigin,
                           y + start->rootYOrigin - pt->rootYOrigin);
        } else if (/* pt != start && */ pt->cursorVisible) {
            _dmxSetCursor(screenInfo.screens[pt->index],
                          NULL,
                          x + start->rootXOrigin - pt->rootXOrigin,
                          y + start->rootYOrigin - pt->rootYOrigin);
        }
        if (pt == start)
            break;
    }
}

static void dmxSetCursor(DeviceIntPtr pDev, ScreenPtr pScreen, CursorPtr pCursor, int x, int y)
{
    DMXScreenInfo *start = &dmxScreens[pScreen->myNum];
    DMXScreenInfo *pt;
    int           GX, GY, gx, gy;

    DMXDBG5("dmxSetCursor(%d %p, %p,%d,%d)\n",
            pScreen->myNum, start, pCursor, x, y);

                                /* We do this check here because of two cases:
                                 *
                                 * 1) if a client calls XWarpPointer()
                                 * and Xinerama is not running, we can
                                 * have mi's notion of the pointer
                                 * position out of phase with DMX's
                                 * notion.
                                 *
                                 * 2) if a down button is held while the
                                 * cursor moves outside the root window,
                                 * mi's notion of the pointer position
                                 * is out of phase with DMX's notion and
                                 * the cursor can remain visible when it
                                 * shouldn't be. */

    dmxGetGlobalPosition(&GX, &GY);
    gx = start->rootXOrigin + x;
    gy = start->rootYOrigin + y;
    if (x && y && (GX != gx || GY != gy))
        dmxCoreMotion(NULL, gx, gy, 0, DMX_NO_BLOCK);
    
    if (!start->over || !dmxCursorDoMultiCursors || start->cursorNotShared) {
        _dmxSetCursor(pScreen, pCursor, x, y);
        return;
    }

    for (pt = start->over; /* condition at end of loop */; pt = pt->over) {
        if (pt->cursorNotShared)
            continue;
        if (dmxOnScreen(x + start->rootXOrigin, y + start->rootYOrigin, pt)) {
            _dmxSetCursor(screenInfo.screens[pt->index], pCursor,
                          x + start->rootXOrigin - pt->rootXOrigin,
                          y + start->rootYOrigin - pt->rootYOrigin);
        } else {
            _dmxSetCursor(screenInfo.screens[pt->index], NULL,
                          x + start->rootXOrigin - pt->rootXOrigin,
                          y + start->rootYOrigin - pt->rootYOrigin);
        }
        if (pt == start)
            break;
    }
}


/** This routine is used by the backend input routines to hide the
 * cursor on a screen that is being used for relative input.  \see
 * dmxbackend.c */
void dmxHideCursor(DMXScreenInfo *dmxScreen)
{
    int       x, y;
    ScreenPtr pScreen = screenInfo.screens[dmxScreen->index];

    dmxGetGlobalPosition(&x, &y);
    _dmxSetCursor(pScreen, NULL, x, y);
}

/** This routine is called during reconfiguration to make sure the
 * cursor is visible. */
void dmxCheckCursor(void)
{
    int           i;
    int           x, y;
    ScreenPtr     pScreen;
    DMXScreenInfo *firstScreen;

    dmxGetGlobalPosition(&x, &y);
    firstScreen = dmxFindFirstScreen(x, y);

    DMXDBG2("dmxCheckCursor %d %d\n", x, y);
    for (i = 0; i < dmxNumScreens; i++) {
        DMXScreenInfo *dmxScreen = &dmxScreens[i];
        pScreen                  = screenInfo.screens[dmxScreen->index];

        if (!dmxOnScreen(x, y, dmxScreen)) {
            if (firstScreen && i == miPointerGetScreen(inputInfo.pointer)->myNum)
                miPointerSetScreen(inputInfo.pointer, firstScreen->index, x, y);
            _dmxSetCursor(pScreen, NULL,
                          x - dmxScreen->rootXOrigin,
                          y - dmxScreen->rootYOrigin);
        } else {
            if (!dmxScreen->cursor) {
                CursorPtr pCursor;
                
                if ((pCursor = dmxFindCursor(dmxScreen))) {
                    _dmxRealizeCursor(pScreen, dmxScreen->cursor = pCursor);
                }
            }
            _dmxSetCursor(pScreen, dmxScreen->cursor,
                          x - dmxScreen->rootXOrigin,
                          y - dmxScreen->rootYOrigin);
        }
    }
    DMXDBG2("   leave dmxCheckCursor %d %d\n", x, y);
}

static Bool dmxDeviceCursorInitialize(DeviceIntPtr pDev, ScreenPtr pScr)
{
    return TRUE;
}

static void dmxDeviceCursorCleanup(DeviceIntPtr pDev, ScreenPtr pScr)
{
}

miPointerSpriteFuncRec dmxPointerSpriteFuncs =
{
    dmxRealizeCursor,
    dmxUnrealizeCursor,
    dmxSetCursor,
    dmxMoveCursor,
    dmxDeviceCursorInitialize,
    dmxDeviceCursorCleanup
};
