/*
 * Copyright 2001-2003 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 implements the console input devices.
 */

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

#define DMX_CONSOLE_DEBUG 0
#define DMX_WINDOW_DEBUG  0

#include "dmxinputinit.h"
#include "dmxevents.h"
#include "dmxconsole.h"
#include "dmxcommon.h"
#include "dmxscrinit.h"
#include "dmxcb.h"
#include "dmxsync.h"

#include "inputstr.h"
#include "input.h"
#include "mipointer.h"
#include "windowstr.h"

#define CONSOLE_NUM 3
#define CONSOLE_DEN 4
#define DMX_CONSOLE_NAME "DMX Console"
#define DMX_RES_NAME     "Xdmx"
#define DMX_RES_CLASS    "XDmx"
#define CONSOLE_BG_COLOR "gray75"
#define CONSOLE_FG_COLOR "black"
#define CONSOLE_SCREEN_BG_COLOR "white"
#define CONSOLE_SCREEN_FG_COLOR "black"
#define CONSOLE_SCREEN_DET_COLOR "gray75"
#define CONSOLE_SCREEN_CUR_COLOR "red"

#if DMX_CONSOLE_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

/* Private area for consoles. */
typedef struct _myPrivate {
    DMX_COMMON_PRIVATE;
    int lastX;
    int lastY;
    int globalX;
    int globalY;
    int curX;
    int curY;
    int width;
    int height;
    int consWidth;
    int consHeight;
    double xScale;
    double yScale;
    XlibGC gc, gcDet, gcRev, gcCur;
    int grabbed, fine, captured;
    Cursor cursorNormal, cursorGrabbed, cursorEmpty;
    Pixmap pixmap;

    CloseScreenProcPtr CloseScreen;
    struct _myPrivate *next;    /* for closing multiple consoles */
    int initialized;
    DevicePtr mou, kbd;
} myPrivate;

static int
scalex(myPrivate * priv, int x)
{
    return (int) ((x * priv->xScale) + .5);
}

static int
scaley(myPrivate * priv, int y)
{
    return (int) ((y * priv->yScale) + .5);
}

static int
unscalex(myPrivate * priv, int x)
{
    return (int) ((x / priv->xScale) + .5);
}

static int
unscaley(myPrivate * priv, int y)
{
    return (int) ((y / priv->yScale) + .5);
}

/** Create the private area for \a pDevice. */
pointer
dmxConsoleCreatePrivate(DeviceIntPtr pDevice)
{
    GETDMXLOCALFROMPDEVICE;
    myPrivate *priv = calloc(1, sizeof(*priv));

    priv->dmxLocal = dmxLocal;
    return priv;
}

/** If \a private is non-NULL, free its associated memory. */
void
dmxConsoleDestroyPrivate(pointer private)
{
    free(private);
}

static void
dmxConsoleDrawFineCursor(myPrivate * priv, XRectangle * rect)
{
    int size = 6;
    int x, y;

    XDrawLine(priv->display, priv->pixmap, priv->gcCur,
              x = scalex(priv, priv->globalX) - size,
              scaley(priv, priv->globalY),
              scalex(priv, priv->globalX) + size, scaley(priv, priv->globalY));
    XDrawLine(priv->display, priv->pixmap, priv->gcCur,
              scalex(priv, priv->globalX),
              y = scaley(priv, priv->globalY) - size,
              scalex(priv, priv->globalX), scaley(priv, priv->globalY) + size);
    if (priv->grabbed) {
        XDrawLine(priv->display, priv->pixmap, priv->gcCur,
                  scalex(priv, priv->globalX) - (int) (size / 1.4),
                  scaley(priv, priv->globalY) - (int) (size / 1.4),
                  scalex(priv, priv->globalX) + (int) (size / 1.4),
                  scaley(priv, priv->globalY) + (int) (size / 1.4));
        XDrawLine(priv->display, priv->pixmap, priv->gcCur,
                  scalex(priv, priv->globalX) - (int) (size / 1.4),
                  scaley(priv, priv->globalY) + (int) (size / 1.4),
                  scalex(priv, priv->globalX) + (int) (size / 1.4),
                  scaley(priv, priv->globalY) - (int) (size / 1.4));
    }
    if (rect) {
        rect->x = x;
        rect->y = y;
        rect->width = 2 * size;
        rect->height = 2 * size;
    }
}

static void
dmxConsoleDrawWindows(pointer private)
{
    GETONLYPRIVFROMPRIVATE;
    Display *dpy = priv->display;
    int i;
    Region whole, used, avail;
    XRectangle rect;

    whole = XCreateRegion();
    used = XCreateRegion();
    avail = XCreateRegion();
    rect.x = 0;
    rect.y = 0;
    rect.width = priv->consWidth;
    rect.height = priv->consHeight;
    XUnionRectWithRegion(&rect, whole, whole);

    for (i = 0; i < dmxNumScreens; i++) {
        ScreenPtr pScreen = screenInfo.screens[i];
        WindowPtr pRoot = pScreen->root;
        WindowPtr pChild;

#if DMX_WINDOW_DEBUG
        dmxLog(dmxDebug, "%lu %p %p %p 2\n",
               pRoot->drawable.id,
               pRoot->parent, pRoot->firstChild, pRoot->lastChild);
#endif

        for (pChild = pRoot->firstChild; pChild; pChild = pChild->nextSib) {
            if (pChild->mapped && pChild->realized) {
#if DMX_WINDOW_DEBUG
                dmxLog(dmxDebug, "  %p %d,%d %dx%d %d %d  %d RECTS\n",
                       pChild,
                       pChild->drawable.x,
                       pChild->drawable.y,
                       pChild->drawable.width,
                       pChild->drawable.height,
                       pChild->visibility,
                       pChild->overrideRedirect,
                       RegionNumRects(&pChild->clipList));
#endif
                rect.x = scalex(priv, pChild->drawable.x + pScreen->x);
                rect.y = scaley(priv, pChild->drawable.y + pScreen->y);
                rect.width = scalex(priv, pChild->drawable.width);
                rect.height = scaley(priv, pChild->drawable.height);
                XDrawRectangle(dpy, priv->pixmap, priv->gc,
                               rect.x, rect.y, rect.width, rect.height);
                XUnionRectWithRegion(&rect, used, used);
                XSubtractRegion(whole, used, avail);
                XSetRegion(dpy, priv->gc, avail);
            }
        }
#ifdef PANORAMIX
        if (!noPanoramiXExtension)
            break;              /* Screen 0 valid with Xinerama */
#endif
    }
    XDestroyRegion(avail);
    XDestroyRegion(used);
    XDestroyRegion(whole);
    XSetClipMask(dpy, priv->gc, None);
}

static void
dmxConsoleDraw(myPrivate * priv, int updateCursor, int update)
{
    GETDMXINPUTFROMPRIV;
    Display *dpy = priv->display;
    int i;

    XFillRectangle(dpy, priv->pixmap, priv->gc, 0, 0,
                   priv->consWidth, priv->consHeight);

    for (i = 0; i < dmxNumScreens; i++) {
        DMXScreenInfo *dmxScreen = &dmxScreens[i];

        XFillRectangle(dpy, priv->pixmap,
                       dmxScreen->beDisplay ? priv->gcRev : priv->gcDet,
                       scalex(priv, screenInfo.screens[i]->x),
                       scaley(priv, screenInfo.screens[i]->y),
                       scalex(priv, screenInfo.screens[i]->width),
                       scaley(priv, screenInfo.screens[i]->height));
    }
    for (i = 0; i < dmxNumScreens; i++) {
        XDrawRectangle(dpy, priv->pixmap, priv->gc,
                       scalex(priv, screenInfo.screens[i]->x),
                       scaley(priv, screenInfo.screens[i]->y),
                       scalex(priv, screenInfo.screens[i]->width),
                       scaley(priv, screenInfo.screens[i]->height));
    }
    if (dmxInput->windows)
        dmxConsoleDrawWindows(priv);
    if (priv->fine && updateCursor)
        dmxConsoleDrawFineCursor(priv, 0);
    if (update) {
        XCopyArea(priv->display, priv->pixmap, priv->window, priv->gc,
                  0, 0, priv->consWidth, priv->consHeight, 0, 0);
        XSync(priv->display, False);    /* Not a backend display */
    }
}

static void
dmxConsoleClearCursor(myPrivate * priv, int x, int y, XRectangle * rect)
{
    int cw = 14, ch = 14;       /* Clear width and height */

    rect->x = scalex(priv, x) - cw / 2;
    rect->y = scaley(priv, y) - ch / 2;
    rect->width = cw;
    rect->height = ch;
    XSetClipRectangles(priv->display, priv->gc, 0, 0, rect, 1, Unsorted);
    XSetClipRectangles(priv->display, priv->gcDet, 0, 0, rect, 1, Unsorted);
    XSetClipRectangles(priv->display, priv->gcRev, 0, 0, rect, 1, Unsorted);
    dmxConsoleDraw(priv, 0, 0);
    XSetClipMask(priv->display, priv->gc, None);
    XSetClipMask(priv->display, priv->gcDet, None);
    XSetClipMask(priv->display, priv->gcRev, None);
}

static void
dmxConsoleUpdateFineCursor(myPrivate * priv)
{
    int leave = 0;
    XRectangle rects[2];

    dmxConsoleClearCursor(priv, priv->globalX, priv->globalY, &rects[0]);
    if (priv->dmxLocal->sendsCore) {
        dmxGetGlobalPosition(&priv->globalX, &priv->globalY);
    }
    else {
        priv->globalX = priv->dmxLocal->lastX;
        priv->globalY = priv->dmxLocal->lastY;
    }

    priv->lastX = scalex(priv, priv->width / 2);
    priv->lastY = scaley(priv, priv->height / 2);

    /* Compute new warp position, which may be
       outside the window */
    if (priv->globalX < 1 || priv->globalX >= priv->width) {
        if (priv->globalX < 1)
            priv->lastX = 0;
        else
            priv->lastX = scalex(priv, priv->width);
        priv->lastY = scaley(priv, priv->globalY);
        ++leave;
    }
    if (priv->globalY < 1 || priv->globalY >= priv->height) {
        if (priv->globalY < 1)
            priv->lastY = 0;
        else
            priv->lastY = scaley(priv, priv->height);
        priv->lastX = scalex(priv, priv->globalX);
        ++leave;
    }

    /* Draw pseudo cursor in window */
    dmxConsoleDrawFineCursor(priv, &rects[1]);

    XSetClipRectangles(priv->display, priv->gc, 0, 0, rects, 2, Unsorted);
    XCopyArea(priv->display, priv->pixmap, priv->window, priv->gc,
              0, 0, priv->consWidth, priv->consHeight, 0, 0);
    XSetClipMask(priv->display, priv->gc, None);

    DMXDBG2("dmxConsoleUpdateFineCursor: WARP %d %d\n",
            priv->lastX, priv->lastY);
    XWarpPointer(priv->display, priv->window, priv->window,
                 0, 0, 0, 0, priv->lastX, priv->lastY);
    XSync(priv->display, False);        /* Not a backend display */

    if (leave) {
        XEvent X;

        while (XCheckMaskEvent(priv->display, PointerMotionMask, &X)) {
            if (X.type == MotionNotify) {
                if (X.xmotion.x != priv->lastX || X.xmotion.y != priv->lastY) {
                    DMXDBG4("Ignoring motion to %d %d after leave frm %d %d\n",
                            X.xmotion.x, X.xmotion.y, priv->lastX, priv->lastY);
                }
            }
            else {
                dmxLog(dmxInfo, "Ignoring event (%d): %s ****************\n",
                       X.type, dmxEventName(X.type));
            }
        }
    }
    DMXDBG6("dmxConsoleUpdateFineCursor: Warp %d %d on %d %d [%d %d]\n",
            priv->lastX, priv->lastY,
            scalex(priv, priv->width),
            scaley(priv, priv->height), priv->globalX, priv->globalY);
}

/** Whenever the window layout (size, position, stacking order) might be
 * changed, this routine is called with the \a pWindow that changed and
 * the \a type of change.  This routine is called in a conservative
 * fashion: the actual layout of the windows of the screen might not
 * have had any human-visible changes. */
void
dmxConsoleUpdateInfo(pointer private, DMXUpdateType type, WindowPtr pWindow)
{
    GETONLYPRIVFROMPRIVATE;
    dmxConsoleDraw(priv, 1, 1);
}

static void
dmxConsoleMoveAbsolute(myPrivate * priv, int x, int y,
                       DevicePtr pDev, dmxMotionProcPtr motion,
                       DMXBlockType block)
{
    int tmpX, tmpY, v[2];

    tmpX = unscalex(priv, x);
    tmpY = unscalex(priv, y);
    DMXDBG6("dmxConsoleMoveAbsolute(,%d,%d) %d %d =? %d %d\n",
            x, y, tmpX, tmpY, priv->curX, priv->curY);
    if (tmpX == priv->curX && tmpY == priv->curY)
        return;
    v[0] = unscalex(priv, x);
    v[1] = unscaley(priv, y);
    motion(pDev, v, 0, 2, DMX_ABSOLUTE_CONFINED, block);
    /* dmxConsoleUpdatePosition gets called here by dmxCoreMotion */
}

static void
dmxConsoleMoveRelative(myPrivate * priv, int x, int y,
                       DevicePtr pDev, dmxMotionProcPtr motion,
                       DMXBlockType block)
{
    int v[2];

    /* Ignore the event generated from * warping back to middle */
    if (x == priv->lastX && y == priv->lastY)
        return;
    v[0] = priv->lastX - x;
    v[1] = priv->lastY - y;
    motion(pDev, v, 0, 2, DMX_RELATIVE, block);
    /* dmxConsoleUpdatePosition gets called here by dmxCoreMotion */
}

/** This routine gets called from #dmxCoreMotion for each motion.  This
 * allows the console's notion of the cursor postion to change when
 * another input device actually caused the change. */
void
dmxConsoleUpdatePosition(pointer private, int x, int y)
{
    GETONLYPRIVFROMPRIVATE;
    int tmpX, tmpY;
    Display *dpy = priv->display;
    static unsigned long dmxGeneration = 0;

    tmpX = scalex(priv, x);
    tmpY = scaley(priv, y);
    DMXDBG6("dmxConsoleUpdatePosition(,%d,%d) new=%d,%d dims=%d,%d\n",
            x, y, tmpX, tmpY, priv->consWidth, priv->consHeight);

    if (priv->fine)
        dmxConsoleUpdateFineCursor(priv);
    if (tmpX != priv->curX || tmpY != priv->curY) {
        if (tmpX < 0)
            tmpX = 0;
        if (tmpY < 0)
            tmpY = 0;
        if (tmpX >= priv->consWidth)
            tmpX = priv->consWidth - 1;
        if (tmpY >= priv->consHeight)
            tmpY = priv->consHeight - 1;
        priv->curX = tmpX;
        priv->curY = tmpY;
        if (!priv->fine) {
            DMXDBG2("   WARP B %d %d\n", priv->curX, priv->curY);
            XWarpPointer(dpy, priv->window,
                         priv->window, 0, 0, 0, 0, tmpX, tmpY);
            XSync(dpy, False);  /* Not a backend display */
        }
    }

    if (dmxGeneration != serverGeneration) {
        dmxGeneration = serverGeneration;
        dmxConsoleDraw(priv, 1, 1);
    }
}

/** Collect all pending events from the console's display.  Plase these
 * events on the server event queue using the \a motion and \a enqueue
 * routines.  The \a checkspecial routine is used to check for special
 * keys that need handling.  \a block tells if signals should be blocked
 * when updating the event queue. */
void
dmxConsoleCollectEvents(DevicePtr pDev,
                        dmxMotionProcPtr motion,
                        dmxEnqueueProcPtr enqueue,
                        dmxCheckSpecialProcPtr checkspecial, DMXBlockType block)
{
    GETPRIVFROMPDEV;
    GETDMXINPUTFROMPRIV;
    Display *dpy = priv->display;
    Window win = priv->window;
    int width = priv->width;
    int height = priv->height;
    XEvent X, N;
    XSetWindowAttributes attribs;
    static int rInitialized = 0;
    static Region r;
    XRectangle rect;
    static int raising = 0, raiseX, raiseY;     /* FIXME */

    while (XPending(dpy)) {
        XNextEvent(dpy, &X);
        switch (X.type) {
        case VisibilityNotify:
            break;
        case Expose:
            DMXDBG5("dmxConsoleCollectEvents: Expose #%d %d %d %d %d\n",
                    X.xexpose.count,
                    X.xexpose.x, X.xexpose.y,
                    X.xexpose.width, X.xexpose.height);
            if (!rInitialized++)
                r = XCreateRegion();
            rect.x = X.xexpose.x;
            rect.y = X.xexpose.y;
            rect.width = X.xexpose.width;
            rect.height = X.xexpose.height;
            XUnionRectWithRegion(&rect, r, r);
            if (X.xexpose.count == 0) {
                XSetRegion(dpy, priv->gc, r);
                XSetRegion(dpy, priv->gcDet, r);
                XSetRegion(dpy, priv->gcRev, r);
                dmxConsoleDraw(priv, 1, 1);
                XSetClipMask(dpy, priv->gc, None);
                XSetClipMask(dpy, priv->gcDet, None);
                XSetClipMask(dpy, priv->gcRev, None);
                XDestroyRegion(r);
                rInitialized = 0;
            }
            break;
        case ResizeRequest:
            DMXDBG2("dmxConsoleCollectEvents: Resize %d %d\n",
                    X.xresizerequest.width, X.xresizerequest.height);
            priv->consWidth = X.xresizerequest.width;
            priv->consHeight = X.xresizerequest.height;
            priv->xScale = (double) priv->consWidth / width;
            priv->yScale = (double) priv->consHeight / height;
            attribs.override_redirect = True;
            XChangeWindowAttributes(dpy, win, CWOverrideRedirect, &attribs);
            XResizeWindow(dpy, win, priv->consWidth, priv->consHeight);
            XFreePixmap(dpy, priv->pixmap);
            priv->pixmap = XCreatePixmap(dpy,
                                         RootWindow(dpy, DefaultScreen(dpy)),
                                         priv->consWidth,
                                         priv->consHeight,
                                         DefaultDepth(dpy, DefaultScreen(dpy)));
            dmxConsoleDraw(priv, 1, 1);
            attribs.override_redirect = False;
            XChangeWindowAttributes(dpy, win, CWOverrideRedirect, &attribs);
            break;
        case LeaveNotify:
            DMXDBG4("dmxConsoleCollectEvents: Leave @ %d,%d; r=%d f=%d\n",
                    X.xcrossing.x, X.xcrossing.y, raising, priv->fine);
            if (!priv->captured)
                dmxCommonRestoreState(priv);
            else {
                dmxConsoleUncapture(dmxInput);
                dmxCommonRestoreState(priv);
            }
            break;
        case EnterNotify:
            DMXDBG6("dmxConsoleCollectEvents: Enter %d,%d r=%d f=%d (%d,%d)\n",
                    X.xcrossing.x, X.xcrossing.y, raising, priv->fine,
                    priv->curX, priv->curY);
            dmxCommonSaveState(priv);
            if (raising) {
                raising = 0;
                dmxConsoleMoveAbsolute(priv, raiseX, raiseY,
                                       priv->mou, motion, block);
            }
            else {
                if (priv->fine) {
                    /* The raise will generate an event near the center,
                     * which is not where the cursor should be.  So we
                     * save the real position, do the raise, and move
                     * the cursor here again after the raise generates
                     * the event. */
                    raising = 1;
                    raiseX = X.xcrossing.x;
                    raiseY = X.xcrossing.y;
                    XRaiseWindow(dpy, priv->window);
                }
                XSync(dpy, False);      /* Not a backend display */
                if (!X.xcrossing.x && !X.xcrossing.y)
                    dmxConsoleMoveAbsolute(priv, priv->curX, priv->curY,
                                           priv->mou, motion, block);
            }
            break;
        case MotionNotify:
            if (priv->curX == X.xmotion.x && priv->curY == X.xmotion.y)
                continue;
            if (XPending(dpy)) {        /* do motion compression */
                XPeekEvent(dpy, &N);
                if (N.type == MotionNotify)
                    continue;
            }
            DMXDBG2("dmxConsoleCollectEvents: Motion %d %d\n",
                    X.xmotion.x, X.xmotion.y);
            if (raising) {
                raising = 0;
                dmxConsoleMoveAbsolute(priv, raiseX, raiseY,
                                       priv->mou, motion, block);
            }
            else {
                if (priv->fine)
                    dmxConsoleMoveRelative(priv, X.xmotion.x, X.xmotion.y,
                                           priv->mou, motion, block);
                else
                    dmxConsoleMoveAbsolute(priv, X.xmotion.x, X.xmotion.y,
                                           priv->mou, motion, block);
            }
            break;
        case KeyPress:
        case KeyRelease:
            enqueue(priv->kbd, X.type, X.xkey.keycode, 0, NULL, block);
            break;
        default:
            /* Pass the whole event here, because
             * this may be an extension event. */
            enqueue(priv->mou, X.type, X.xbutton.button, 0, &X, block);
            break;
        }
    }
}

static void
dmxCloseConsole(myPrivate * priv)
{
    GETDMXINPUTFROMPRIV;
    dmxCommonRestoreState(priv);
    if (priv->display) {
        XFreeGC(priv->display, priv->gc);
        XFreeGC(priv->display, priv->gcDet);
        XFreeGC(priv->display, priv->gcRev);
        XFreeGC(priv->display, priv->gcCur);
        if (!dmxInput->console)
            XCloseDisplay(priv->display);
    }
    priv->display = NULL;
}

static Bool
dmxCloseConsoleScreen(int idx, ScreenPtr pScreen)
{
    myPrivate *priv, *last;

    for (last = priv = (myPrivate *) dixLookupPrivate(&pScreen->devPrivates,
                                                      dmxScreenPrivateKey);
         priv; priv = priv->next)
        dmxCloseConsole(last = priv);

    DMX_UNWRAP(CloseScreen, last, pScreen);
    return pScreen->CloseScreen(idx, pScreen);
}

static Cursor
dmxConsoleCreateEmptyCursor(myPrivate * priv)
{
    char noCursorData[] = { 0, 0, 0, 0, 0, 0, 0, 0 };
    Pixmap pixmap;
    Cursor cursor;
    XColor color, tmpColor;
    Display *dpy = priv->display;

    /* Create empty cursor for window */
    pixmap = XCreateBitmapFromData(priv->display, priv->window,
                                   noCursorData, 8, 8);
    if (!XAllocNamedColor(dpy, DefaultColormap(dpy, DefaultScreen(dpy)),
                          "black", &color, &tmpColor))
        dmxLog(dmxFatal, "Cannot allocate color for cursor\n");
    cursor = XCreatePixmapCursor(dpy, pixmap, pixmap, &color, &color, 0, 0);
    XFreePixmap(dpy, pixmap);
    return cursor;
}

static void
dmxConsoleComputeWidthHeight(myPrivate * priv,
                             int *width, int *height,
                             double *xScale, double *yScale,
                             int *consWidth, int *consHeight)
{
    int screen;
    Display *dpy = priv->display;

    *width = 0;
    *height = 0;
    *xScale = 1.0;
    *yScale = 1.0;

    screen = DefaultScreen(dpy);
    *consWidth = DisplayWidth(dpy, screen) * CONSOLE_NUM / CONSOLE_DEN;
    *consHeight = DisplayHeight(dpy, screen) * CONSOLE_NUM / CONSOLE_DEN;

    if (*consWidth < 1)
        *consWidth = 1;
    if (*consHeight < 1)
        *consHeight = 1;

#if 1
    /* Always keep the console size similar
     * to the global bounding box. */
    *width = dmxGlobalWidth;
    *height = dmxGlobalHeight;
#else
    /* Make the console window as big as
     * possible by computing the visible
     * bounding box. */
    for (i = 0; i < dmxNumScreens; i++) {
        if (screenInfo.screens[i]->x + screenInfo.screens[i]->width > *width)
            *width = screenInfo.screens[i]->x + screenInfo.screens[i]->width;

        if (screenInfo.screens[i]->y + screenInfo.screens[i]->height > *height)
            *height = screenInfo.screens[i]->y + screenInfo.screens[i]->height;
    }
#endif

    if ((double) *consWidth / *width < (double) *consHeight / *height)
        *xScale = *yScale = (double) *consWidth / *width;
    else
        *xScale = *yScale = (double) *consHeight / *height;

    *consWidth = scalex(priv, *width);
    *consHeight = scaley(priv, *height);
    if (*consWidth < 1)
        *consWidth = 1;
    if (*consHeight < 1)
        *consHeight = 1;
}

/** Re-initialized the console device described by \a pDev (after a
 * reconfig). */
void
dmxConsoleReInit(DevicePtr pDev)
{
    GETPRIVFROMPDEV;
    Display *dpy;

    if (!priv || !priv->initialized)
        return;
    dpy = priv->display;

    dmxConsoleComputeWidthHeight(priv,
                                 &priv->width, &priv->height,
                                 &priv->xScale, &priv->yScale,
                                 &priv->consWidth, &priv->consHeight);
    XResizeWindow(dpy, priv->window, priv->consWidth, priv->consHeight);
    XFreePixmap(dpy, priv->pixmap);
    priv->pixmap = XCreatePixmap(dpy,
                                 RootWindow(dpy, DefaultScreen(dpy)),
                                 priv->consWidth,
                                 priv->consHeight,
                                 DefaultDepth(dpy, DefaultScreen(dpy)));
    dmxConsoleDraw(priv, 1, 1);
}

/** Initialized the console device described by \a pDev. */
void
dmxConsoleInit(DevicePtr pDev)
{
    GETPRIVFROMPDEV;
    DMXInputInfo *dmxInput = &dmxInputs[dmxLocal->inputIdx];
    int screen;
    unsigned long mask;
    XSetWindowAttributes attribs;
    Display *dpy;
    Window win;
    XGCValues gcvals;
    XColor color;
    XClassHint class_hints;
    unsigned long tmp;

    if (dmxLocal->type == DMX_LOCAL_MOUSE)
        priv->mou = pDev;
    if (dmxLocal->type == DMX_LOCAL_KEYBOARD)
        priv->kbd = pDev;
    if (priv->initialized++)
        return;                 /* Only do once for mouse/keyboard pair */

    if (!(dpy = priv->display = XOpenDisplay(dmxInput->name)))
        dmxLog(dmxFatal,
               "dmxOpenConsole: cannot open console display %s\n",
               dmxInput->name);

    /* Set up defaults */
    dmxConsoleComputeWidthHeight(priv,
                                 &priv->width, &priv->height,
                                 &priv->xScale, &priv->yScale,
                                 &priv->consWidth, &priv->consHeight);

    /* Private initialization using computed values or constants. */
    screen = DefaultScreen(dpy);
    priv->initPointerX = scalex(priv, priv->width / 2);
    priv->initPointerY = scaley(priv, priv->height / 2);
    priv->eventMask = (ButtonPressMask
                       | ButtonReleaseMask
                       | PointerMotionMask
                       | EnterWindowMask
                       | LeaveWindowMask
                       | KeyPressMask
                       | KeyReleaseMask | ExposureMask | ResizeRedirectMask);

    mask = CWBackPixel | CWEventMask | CWColormap | CWOverrideRedirect;
    attribs.colormap = DefaultColormap(dpy, screen);
    if (XParseColor(dpy, attribs.colormap, CONSOLE_BG_COLOR, &color)
        && XAllocColor(dpy, attribs.colormap, &color)) {
        attribs.background_pixel = color.pixel;
    }
    else
        attribs.background_pixel = WhitePixel(dpy, screen);

    attribs.event_mask = priv->eventMask;
    attribs.override_redirect = False;

    win = priv->window = XCreateWindow(dpy,
                                       RootWindow(dpy, screen),
                                       0, 0, priv->consWidth, priv->consHeight,
                                       0,
                                       DefaultDepth(dpy, screen),
                                       InputOutput,
                                       DefaultVisual(dpy, screen),
                                       mask, &attribs);
    priv->pixmap = XCreatePixmap(dpy, RootWindow(dpy, screen),
                                 priv->consWidth, priv->consHeight,
                                 DefaultDepth(dpy, screen));

    /* Set up properties */
    XStoreName(dpy, win, DMX_CONSOLE_NAME);
    class_hints.res_name = DMX_RES_NAME;
    class_hints.res_class = DMX_RES_CLASS;
    XSetClassHint(dpy, win, &class_hints);

    /* Map the window */
    XMapWindow(dpy, win);

    /* Create cursors */
    priv->cursorNormal = XCreateFontCursor(dpy, XC_circle);
    priv->cursorGrabbed = XCreateFontCursor(dpy, XC_spider);
    priv->cursorEmpty = dmxConsoleCreateEmptyCursor(priv);
    XDefineCursor(dpy, priv->window, priv->cursorNormal);

    /* Create GC */
    mask = (GCFunction | GCPlaneMask | GCClipMask | GCForeground |
            GCBackground | GCLineWidth | GCLineStyle | GCCapStyle |
            GCFillStyle | GCGraphicsExposures);
    gcvals.function = GXcopy;
    gcvals.plane_mask = AllPlanes;
    gcvals.clip_mask = None;
    if (XParseColor(dpy, attribs.colormap, CONSOLE_SCREEN_FG_COLOR, &color)
        && XAllocColor(dpy, attribs.colormap, &color)) {
        gcvals.foreground = color.pixel;
    }
    else
        gcvals.foreground = BlackPixel(dpy, screen);
    if (XParseColor(dpy, attribs.colormap, CONSOLE_SCREEN_BG_COLOR, &color)
        && XAllocColor(dpy, attribs.colormap, &color)) {
        gcvals.background = color.pixel;
    }
    else
        gcvals.background = WhitePixel(dpy, screen);
    gcvals.line_width = 0;
    gcvals.line_style = LineSolid;
    gcvals.cap_style = CapNotLast;
    gcvals.fill_style = FillSolid;
    gcvals.graphics_exposures = False;

    priv->gc = XCreateGC(dpy, win, mask, &gcvals);

    tmp = gcvals.foreground;
    if (XParseColor(dpy, attribs.colormap, CONSOLE_SCREEN_DET_COLOR, &color)
        && XAllocColor(dpy, attribs.colormap, &color)) {
        gcvals.foreground = color.pixel;
    }
    else
        gcvals.foreground = BlackPixel(dpy, screen);
    priv->gcDet = XCreateGC(dpy, win, mask, &gcvals);
    gcvals.foreground = tmp;

    tmp = gcvals.background;
    gcvals.background = gcvals.foreground;
    gcvals.foreground = tmp;
    priv->gcRev = XCreateGC(dpy, win, mask, &gcvals);

    gcvals.background = gcvals.foreground;
    if (XParseColor(dpy, attribs.colormap, CONSOLE_SCREEN_CUR_COLOR, &color)
        && XAllocColor(dpy, attribs.colormap, &color)) {
        gcvals.foreground = color.pixel;
    }
    else
        gcvals.foreground = BlackPixel(dpy, screen);
    priv->gcCur = XCreateGC(dpy, win, mask, &gcvals);

    dmxConsoleDraw(priv, 1, 1);

    if (dixLookupPrivate(&screenInfo.screens[0]->devPrivates,
                         dmxScreenPrivateKey))
        priv->next = dixLookupPrivate(&screenInfo.screens[0]->devPrivates,
                                      dmxScreenPrivateKey);
    else
        DMX_WRAP(CloseScreen, dmxCloseConsoleScreen,
                 priv, screenInfo.screens[0]);
    dixSetPrivate(&screenInfo.screens[0]->devPrivates, dmxScreenPrivateKey,
                  priv);
}

/** Fill in the \a info structure for the specified \a pDev.  Only used
 * for pointers. */
void
dmxConsoleMouGetInfo(DevicePtr pDev, DMXLocalInitInfoPtr info)
{
    GETPRIVFROMPDEV;

    info->buttonClass = 1;
    dmxCommonMouGetMap(pDev, info->map, &info->numButtons);
    info->valuatorClass = 1;
    info->numRelAxes = 2;
    info->minval[0] = 0;
    info->minval[1] = 0;
    /* max possible console window size: */
    info->maxval[0] = DisplayWidth(priv->display, DefaultScreen(priv->display));
    info->maxval[1] =
        DisplayHeight(priv->display, DefaultScreen(priv->display));
    info->res[0] = 1;
    info->minres[0] = 0;
    info->maxres[0] = 1;
    info->ptrFeedbackClass = 1;
}

/** Fill in the \a info structure for the specified \a pDev.  Only used
 * for keyboard. */
void
dmxConsoleKbdGetInfo(DevicePtr pDev, DMXLocalInitInfoPtr info)
{
    dmxCommonKbdGetInfo(pDev, info);
    info->keyboard = 1;
    info->keyClass = 1;
    dmxCommonKbdGetMap(pDev, &info->keySyms, info->modMap);
    info->freemap = 1;
    info->focusClass = 1;
    info->kbdFeedbackClass = 1;
}

/** Handle special console-only keys. */
int
dmxConsoleFunctions(pointer private, DMXFunctionType function)
{
    GETONLYPRIVFROMPRIVATE;
    XRectangle rect;
    Display *dpy = priv->display;

    switch (function) {
    case DMX_FUNCTION_FINE:
        if (priv->fine) {
            priv->fine = 0;
            dmxConsoleClearCursor(priv, priv->globalX, priv->globalY, &rect);
            XSetClipRectangles(dpy, priv->gc, 0, 0, &rect, 1, Unsorted);
            XCopyArea(dpy, priv->pixmap, priv->window, priv->gc,
                      0, 0, priv->consWidth, priv->consHeight, 0, 0);
            XSetClipMask(dpy, priv->gc, None);

            XDefineCursor(dpy, priv->window,
                          priv->grabbed
                          ? priv->cursorGrabbed : priv->cursorNormal);
            XWarpPointer(dpy, priv->window, priv->window,
                         0, 0, 0, 0,
                         scalex(priv, priv->globalX),
                         scaley(priv, priv->globalY));
            XSync(dpy, False);  /* Not a backend display */
        }
        else {
            priv->fine = 1;
            XRaiseWindow(dpy, priv->window);
            XDefineCursor(dpy, priv->window, priv->cursorEmpty);
            dmxConsoleUpdateFineCursor(priv);
        }
        return 1;
    case DMX_FUNCTION_GRAB:
        if (priv->grabbed) {
            XUngrabKeyboard(dpy, CurrentTime);
            XUngrabPointer(dpy, CurrentTime);
            XDefineCursor(dpy, priv->window,
                          priv->fine ? priv->cursorEmpty : priv->cursorNormal);
        }
        else {
            if (XGrabPointer(dpy, priv->window, True,
                             0, GrabModeAsync, GrabModeAsync, priv->window,
                             None, CurrentTime)) {
                dmxLog(dmxError, "XGrabPointer failed\n");
                return 0;
            }
            if (XGrabKeyboard(dpy, priv->window, True,
                              GrabModeAsync, GrabModeAsync, CurrentTime)) {
                dmxLog(dmxError, "XGrabKeyboard failed\n");
                XUngrabPointer(dpy, CurrentTime);
                return 0;
            }
            XDefineCursor(dpy, priv->window,
                          priv->fine ? priv->cursorEmpty : priv->cursorGrabbed);
        }
        priv->grabbed = !priv->grabbed;
        if (priv->fine)
            dmxConsoleUpdateFineCursor(priv);
        return 1;
    case DMX_FUNCTION_TERMINATE:
        return 1;
    default:
        return 0;
    }
}

static void
dmxDump(void)
{
    int i, j;
    DMXInputInfo *dmxInput;
    XEvent X;

    for (i = 0, dmxInput = &dmxInputs[0]; i < dmxNumInputs; i++, dmxInput++) {
        for (j = 0; j < dmxInput->numDevs; j++) {
            DMXLocalInputInfoPtr dmxLocal = dmxInput->devs[j];
            myPrivate *priv = dmxLocal->private;

            while (priv
                   && priv->display
                   && XCheckTypedEvent(priv->display, MotionNotify, &X)) {
                DMXDBG4("dmxDump: %s/%d threw event away %d %s\n",
                        dmxInput->name, j, X.type, dmxEventName(X.type));
            }
        }
    }
}

/** This routine is used to warp the pointer into the console window
 * from anywhere on the screen.  It is used when backend and console
 * input are both being taken from the same X display. */
void
dmxConsoleCapture(DMXInputInfo * dmxInput)
{
    int i;
    XEvent X;

    DMXDBG0("dmxConsoleCapture\n");
    dmxSync(NULL, TRUE);
    for (i = 0; i < dmxInput->numDevs; i++) {
        DMXLocalInputInfoPtr dmxLocal = dmxInput->devs[i];
        myPrivate *priv = dmxLocal->private;

        if (dmxLocal->extType != DMX_LOCAL_TYPE_CONSOLE)
            continue;
        if (dmxLocal->type != DMX_LOCAL_MOUSE)
            continue;
        if (priv->captured)
            continue;
        priv->captured = 2;     /* Ungrab only after proximal events. */
        XRaiseWindow(priv->display, priv->window);
        XSync(priv->display, False);    /* Not a backend display */
        while (XCheckTypedEvent(priv->display, MotionNotify, &X)) {
            DMXDBG3("   Ignoring motion to %d %d after capture on %s\n",
                    X.xmotion.x, X.xmotion.y, dmxInput->name);
        }
        XWarpPointer(priv->display, None,
                     priv->window, 0, 0, 0, 0, priv->curX, priv->curY);
        XSync(priv->display, False);    /* Not a backend display */
        dmxDump();
        if (priv->fine)
            dmxConsoleUpdateFineCursor(priv);
    }
}

/** Undo the capture that was done by #dmxConsoleCapture. */
void
dmxConsoleUncapture(DMXInputInfo * dmxInput)
{
    int i;

    DMXDBG0("dmxConsoleUncapture\n");
    dmxSync(NULL, TRUE);
    for (i = 0; i < dmxInput->numDevs; i++) {
        DMXLocalInputInfoPtr dmxLocal = dmxInput->devs[i];
        myPrivate *priv = dmxLocal->private;

        if (dmxLocal->extType != DMX_LOCAL_TYPE_CONSOLE)
            continue;
        if (dmxLocal->type != DMX_LOCAL_MOUSE)
            continue;
        if (!priv->captured)
            continue;
        priv->captured = 0;
        XSync(priv->display, False);    /* Not a backend display */
    }
}
