/* $XFree86$ */
/*
 * Copyright 2002-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:
 *   Rickard E. (Rik) Faith <faith@redhat.com>
 *
 */

/** \file
 * Provide support and helper functions for enqueing events received by
 * the low-level input drivers. */

#define DMX_EVENTS_DEBUG 0

#include "dmxinputinit.h"
#include "dmxevents.h"
#include "dmxcb.h"
#include "dmxcommon.h"
#include "dmxcursor.h"
#include "dmxmotion.h"
#include "dmxeq.h"
#include "dmxsigio.h"
#include "dmxmap.h"

#include "keysym.h"
#include "opaque.h"
#include "inputstr.h"
#include "mipointer.h"

#ifdef XINPUT
#include "XIstubs.h"
#endif

static int  dmxGlobalX, dmxGlobalY; /* Global cursor position */
static int  dmxGlobalInvalid;       /* Flag indicating dmxCoreMotion
                                     * should move the mouse anyway. */

#if DMX_EVENTS_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 dmxApplyFunctions(DMXInputInfo *dmxInput, DMXFunctionType f)
{
    int i;
    int rc = 0;

    for (i = 0; i < dmxInput->numDevs; i+= dmxInput->devs[i]->binding)
        if (dmxInput->devs[i]->functions)
            rc += dmxInput->devs[i]->functions(dmxInput->devs[i]->private, f);
    return rc;
}

static int dmxCheckFunctionKeys(DMXLocalInputInfoPtr dmxLocal,
                                int type,
                                KeySym keySym)
{
    DMXInputInfo   *dmxInput = &dmxInputs[dmxLocal->inputIdx];
    unsigned short state = 0;
    
    if (dmxLocal->sendsCore)
        state = dmxLocalCoreKeyboard->pDevice->key->state;
    else if (dmxLocal->pDevice->key)
        state = dmxLocal->pDevice->key->state;
    
    DMXDBG3("dmxCheckFunctionKeys: keySym=0x%04x %s state=0x%04x\n",
            keySym, type == KeyPress ? "press" : "release", state);

    if ((state & (ControlMask|Mod1Mask)) != (ControlMask|Mod1Mask)) return 0;

    switch (keySym) {
    case XK_g:
        if (type == KeyPress)
            dmxApplyFunctions(dmxInput, DMX_FUNCTION_GRAB);
        return 1;
    case XK_f:
        if (type == KeyPress)
            dmxApplyFunctions(dmxInput, DMX_FUNCTION_FINE);
        return 1;
    case XK_q:
        if (type == KeyPress && dmxLocal->sendsCore)
            if (dmxApplyFunctions(dmxInput, DMX_FUNCTION_TERMINATE)) {
                dmxLog(dmxInfo, "User request for termination\n");
                dispatchException |= DE_TERMINATE;
            }
        return 1;
    }
    
    return 0;
}

#ifdef XINPUT
static void dmxEnqueueExtEvent(DMXLocalInputInfoPtr dmxLocal, xEvent *e,
                               DMXBlockType block)
{
    xEvent                 xE[2];
    deviceKeyButtonPointer *xev      = (deviceKeyButtonPointer *)xE;
    deviceValuator         *xv       = (deviceValuator *)xev+1;
    DeviceIntPtr           pDevice   = dmxLocal->pDevice;
    DMXInputInfo           *dmxInput = &dmxInputs[dmxLocal->inputIdx];
    int                    type      = e->u.u.type;

    switch (e->u.u.type) {
    case KeyPress:      type = DeviceKeyPress;      break;
    case KeyRelease:    type = DeviceKeyRelease;    break;
    case ButtonPress:   type = DeviceButtonPress;   break;
    case ButtonRelease: type = DeviceButtonRelease; break;
    case MotionNotify:
        dmxLog(dmxError,
               "dmxEnqueueExtEvent: MotionNotify not allowed here\n");
        return;
    default:
        if (e->u.u.type == ProximityIn || e->u.u.type == ProximityOut) break;
        dmxLogInput(dmxInput,
                    "dmxEnqueueExtEvent: Unhandled %s event (%d)\n",
                    e->u.u.type >= LASTEvent ? "extension" : "non-extension",
                    e->u.u.type);
        return;
    }

    xev->type          = type;
    xev->detail        = e->u.u.detail;
    xev->deviceid      = pDevice->id | MORE_EVENTS;
    xev->time          = e->u.keyButtonPointer.time;

    xv->type           = DeviceValuator;
    xv->deviceid       = pDevice->id;
    xv->num_valuators  = 0;
    xv->first_valuator = 0;

    if (block) dmxSigioBlock();
    dmxeqEnqueue(xE);
    if (block) dmxSigioUnblock();
}
#endif

DMXScreenInfo *dmxFindFirstScreen(int x, int y)
{
    int i;

    for (i = 0; i < dmxNumScreens; i++) {
        DMXScreenInfo *dmxScreen = &dmxScreens[i];
        if (dmxOnScreen(x, y, dmxScreen)) return dmxScreen;
    }
    return NULL;
}

void dmxCoreMotion(int x, int y, int delta, DMXBlockType block)
{
    DMXScreenInfo *dmxScreen;
    DMXInputInfo  *dmxInput;
    ScreenPtr     pScreen;
    int           localX;
    int           localY;
    int           i;

    if (!dmxGlobalInvalid && dmxGlobalX == x && dmxGlobalY == y) return;
    
    DMXDBG5("dmxCoreMotion(%d,%d,%d) dmxGlobalX=%d dmxGlobalY=%d\n",
            x, y, delta, dmxGlobalX, dmxGlobalY);

    dmxGlobalInvalid = 0;
    dmxGlobalX       = x;
    dmxGlobalY       = y;

    if (dmxGlobalX < 0)                dmxGlobalX = 0;
    if (dmxGlobalY < 0)                dmxGlobalY = 0;
    if (dmxGlobalX >= dmxGlobalWidth)  dmxGlobalX = dmxGlobalWidth  + delta -1;
    if (dmxGlobalY >= dmxGlobalHeight) dmxGlobalY = dmxGlobalHeight + delta -1;
    
    if ((dmxScreen = dmxFindFirstScreen(dmxGlobalX, dmxGlobalY))) {
        localX = dmxGlobalX - dmxScreen->rootXOrigin;
        localY = dmxGlobalY - dmxScreen->rootYOrigin;
        if ((pScreen = miPointerCurrentScreen())
            && pScreen->myNum == dmxScreen->index) {
                                /* Screen is old screen */
            if (block) dmxSigioBlock();
            miPointerAbsoluteCursor(localX, localY, GetTimeInMillis());
            if (block) dmxSigioUnblock();
        } else {
                                /* Screen is new */
            DMXDBG4("   New screen: old=%d new=%d localX=%d localY=%d\n",
                    pScreen->myNum, dmxScreen->index, localX, localY);
            if (block) dmxSigioBlock();
            dmxeqProcessInputEvents();
            miPointerSetNewScreen(dmxScreen->index, localX, localY);
            miPointerAbsoluteCursor(localX, localY, GetTimeInMillis());
            if (block) dmxSigioUnblock();
        }
        miPointerPosition(&localX, &localY);

        if ((pScreen = miPointerCurrentScreen())) {
            dmxGlobalX = localX + dmxScreens[pScreen->myNum].rootXOrigin;
            dmxGlobalY = localY + dmxScreens[pScreen->myNum].rootYOrigin;
            DMXDBG6("   Moved to dmxGlobalX=%d dmxGlobalY=%d"
                    " on screen index=%d/%d localX=%d localY=%d\n",
                    dmxGlobalX, dmxGlobalY,
                    dmxScreen ? dmxScreen->index : -1, pScreen->myNum,
                    localX, localY);
        }
    }
                                /* Send updates down to all core input
                                 * drivers */
    for (i = 0, dmxInput = &dmxInputs[0]; i < dmxNumInputs; i++, dmxInput++) {
        int j;

        for (j = 0; j < dmxInput->numDevs; j += dmxInput->devs[j]->binding)
            if (!dmxInput->detached
                && dmxInput->devs[j]->sendsCore
                && dmxInput->devs[j]->update_position)
                dmxInput->devs[j]->update_position(dmxInput->devs[j]->private,
                                                   dmxGlobalX, dmxGlobalY);
    }
    if (!dmxScreen) ProcessInputEvents();
}

#ifdef XINPUT
#define DMX_MAX_AXES 32         /* Max axes reported by this routine */
static void dmxExtMotion(DMXLocalInputInfoPtr dmxLocal,
                         int *v, int firstAxis, int axesCount,
                         DMXMotionType type, DMXBlockType block)
{
    DeviceIntPtr           pDevice = dmxLocal->pDevice;
    xEvent                 xE[2 * DMX_MAX_AXES/6];
    deviceKeyButtonPointer *xev    = (deviceKeyButtonPointer *)xE;
    deviceValuator         *xv     = (deviceValuator *)xev+1;
    int                    thisX   = 0;
    int                    thisY   = 0;
    int                    i;
    int                    count;

    memset(xE, 0, sizeof(xE));

    if (axesCount > DMX_MAX_AXES) axesCount = DMX_MAX_AXES;

    if (!pDevice->valuator->mode && axesCount == 2) {
                                /* The dmx console is a relative mode
                                 * device that sometimes reports
                                 * absolute motion.  It only has two
                                 * axes. */
        if (type == DMX_RELATIVE) {
            thisX = -v[0];
            thisY = -v[1];
            dmxLocal->lastX += thisX;
            dmxLocal->lastY += thisY;
            if (dmxLocal->update_position)
                dmxLocal->update_position(dmxLocal->private,
                                          dmxLocal->lastX, dmxLocal->lastY);
        } else {                    /* Convert to relative */
            if (dmxLocal->lastX || dmxLocal->lastY) {
                thisX = v[0] - dmxLocal->lastX;
                thisY = v[1] - dmxLocal->lastY;
            }
            dmxLocal->lastX = v[0];
            dmxLocal->lastY = v[1];
        }
        v[0] = thisX;
        v[1] = thisY;
    }

    if (axesCount <= 6) {
                                /* Optimize for the common case when
                                 * only 1 or 2 axes change. */
            xev->time          = GetTimeInMillis();
            xev->type          = DeviceMotionNotify;
            xev->detail        = 0;
            xev->deviceid      = pDevice->id | MORE_EVENTS;
            
            xv->type           = DeviceValuator;
            xv->deviceid       = pDevice->id;
            xv->num_valuators  = axesCount;
            xv->first_valuator = firstAxis;
            switch (xv->num_valuators) {
            case 6: xv->valuator5 = v[5];
            case 5: xv->valuator4 = v[4];
            case 4: xv->valuator3 = v[3];
            case 3: xv->valuator2 = v[2];
            case 2: xv->valuator1 = v[1];
            case 1: xv->valuator0 = v[0];
            }
            count              = 2;
    } else {
        for (i = 0, count = 0; i < axesCount; i += 6) {
            xev->time          = GetTimeInMillis();
            xev->type          = DeviceMotionNotify;
            xev->detail        = 0;
            xev->deviceid      = pDevice->id | MORE_EVENTS;
            xev               += 2;
            
            xv->type           = DeviceValuator;
            xv->deviceid       = pDevice->id;
            xv->num_valuators  = (i+6 >= axesCount ? axesCount - i : 6);
            xv->first_valuator = firstAxis + i;
            switch (xv->num_valuators) {
            case 6: xv->valuator5 = v[i+5];
            case 5: xv->valuator4 = v[i+4];
            case 4: xv->valuator3 = v[i+3];
            case 3: xv->valuator2 = v[i+2];
            case 2: xv->valuator1 = v[i+1];
            case 1: xv->valuator0 = v[i+0];
            }
            xv                += 2;
            count             += 2;
        }
    }

    if (block) dmxSigioBlock();
    dmxPointerPutMotionEvent(pDevice, firstAxis, axesCount, v, xev->time);
    dmxeqEnqueue(xE);
    if (block) dmxSigioUnblock();
}

static int dmxTranslateAndEnqueueExtEvent(DMXLocalInputInfoPtr dmxLocal,
                                          XEvent *e, DMXBlockType block)
{
    xEvent                 xE[2];
    deviceKeyButtonPointer *xev    = (deviceKeyButtonPointer *)xE;
    deviceValuator         *xv     = (deviceValuator *)xev+1;
    int                    type;
    int                    event   = -1;
    XDeviceKeyEvent        *ke     = (XDeviceKeyEvent *)e;
    XDeviceMotionEvent     *me     = (XDeviceMotionEvent *)e;

    if (!e) return -1;          /* No extended event passed, cannot handle */
    
    if ((XID)dmxLocal->deviceId != ke->deviceid) {
                                /* Search for the correct dmxLocal,
                                 * since backend and console events are
                                 * picked up for the first device on
                                 * that X server. */
        int i;
        DMXInputInfo *dmxInput = &dmxInputs[dmxLocal->inputIdx];
        for (i = 0; i < dmxInput->numDevs; i++) {
            dmxLocal = dmxInput->devs[i];
            if ((XID)dmxLocal->deviceId == ke->deviceid) break;
        }
    }

    if ((XID)dmxLocal->deviceId != ke->deviceid
        || (type = dmxMapLookup(dmxLocal, e->type)) < 0)
        return -1;    /* No mapping, so this event is unhandled */

    switch (type) {
    case XI_DeviceValuator:          event = DeviceValuator;          break;
    case XI_DeviceKeyPress:          event = DeviceKeyPress;          break;
    case XI_DeviceKeyRelease:        event = DeviceKeyRelease;        break;
    case XI_DeviceButtonPress:       event = DeviceButtonPress;       break;
    case XI_DeviceButtonRelease:     event = DeviceButtonRelease;     break;
    case XI_DeviceMotionNotify:      event = DeviceMotionNotify;      break;
    case XI_DeviceFocusIn:           event = DeviceFocusIn;           break;
    case XI_DeviceFocusOut:          event = DeviceFocusOut;          break;
    case XI_ProximityIn:             event = ProximityIn;             break;
    case XI_ProximityOut:            event = ProximityOut;            break;
    case XI_DeviceStateNotify:       event = DeviceStateNotify;       break;
    case XI_DeviceMappingNotify:     event = DeviceMappingNotify;     break;
    case XI_ChangeDeviceNotify:      event = ChangeDeviceNotify;      break;
    case XI_DeviceKeystateNotify:    event = DeviceStateNotify;       break;
    case XI_DeviceButtonstateNotify: event = DeviceStateNotify;       break;
    }

    switch (type) {
    case XI_DeviceKeyPress: 
    case XI_DeviceKeyRelease:
    case XI_DeviceButtonPress:
    case XI_DeviceButtonRelease:
    case XI_ProximityIn:
    case XI_ProximityOut:
        xev->type          = event;
        xev->detail        = ke->keycode; /* same as ->button */
        xev->deviceid      = dmxLocal->pDevice->id | MORE_EVENTS;
        xev->time          = GetTimeInMillis();

        xv->type           = DeviceValuator;
        xv->deviceid       = dmxLocal->pDevice->id;
        xv->num_valuators  = ke->axes_count;
        xv->first_valuator = ke->first_axis;
        xv->valuator0      = ke->axis_data[0];
        xv->valuator1      = ke->axis_data[1];
        xv->valuator2      = ke->axis_data[2];
        xv->valuator3      = ke->axis_data[3];
        xv->valuator4      = ke->axis_data[4];
        xv->valuator5      = ke->axis_data[5];

        if (block) dmxSigioBlock();
        dmxeqEnqueue(xE);
        if (block) dmxSigioUnblock();
        break;

    case XI_DeviceMotionNotify:
        dmxExtMotion(dmxLocal, me->axis_data, me->first_axis, me->axes_count,
                     DMX_ABSOLUTE, block);
        break;
    case XI_DeviceFocusIn:
    case XI_DeviceFocusOut:
    case XI_DeviceStateNotify:
    case XI_DeviceMappingNotify:
    case XI_ChangeDeviceNotify:
    case XI_DeviceKeystateNotify:
    case XI_DeviceButtonstateNotify:
                                /* These are ignored, since DMX will
                                 * generate its own events of these
                                 * types, as necessary.

                                 * Perhaps ChangeDeviceNotify should
                                 * generate an error, because it is
                                 * unexpected? */
        break;
    case XI_DeviceValuator:
    default:
        dmxLog(dmxWarning,
               "XInput extension event (remote=%d -> zero-based=%d)"
               " not supported yet\n", e->type, type);
        return -1;
    }
    return 0;
}
#endif

static int dmxGetButtonMapping(DMXLocalInputInfoPtr dmxLocal, int button)
{
    ButtonClassPtr b = dmxLocal->pDevice->button;

    if (button > b->numButtons) { /* This shouldn't happen. */
        dmxLog(dmxWarning, "Button %d pressed, but only %d buttons?!?\n",
               button, b->numButtons);
        return button;
    }
    return b->map[button];
}

/** Return DMX's notion of the pointer position in the global coordinate
 * space. */
void dmxGetGlobalPosition(int *x, int *y)
{
    *x = dmxGlobalX;
    *y = dmxGlobalY;
}

/** Invalidate the global position for #dmxCoreMotion. */
void dmxInvalidateGlobalPosition(void)
{
    dmxGlobalInvalid = 1;
}

/** Enqueue a motion event for \a pDev.  The \a v vector has length \a
 * axesCount, and contains values for each of the axes, starting at \a
 * firstAxes.
 *
 * The \a type of the motion may be \a DMX_RELATIVE, \a DMX_ABSOLUTE, or
 * \a DMX_ABSOLUTE_CONFINED (in the latter case, the pointer will not be
 * allowed to move outside the global boundaires).
 *
 * If \a block is set to \a DMX_BLOCK, then the SIGIO handler will be
 * blocked around calls to #dmxeqEnqueue(). */
void dmxMotion(DevicePtr pDev, int *v, int firstAxes, int axesCount,
               DMXMotionType type, DMXBlockType block)
{
#ifdef XINPUT
    GETDMXLOCALFROMPDEV;

    if (!dmxLocal->sendsCore) {
        dmxExtMotion(dmxLocal, v, firstAxes, axesCount, type, block);
        return;
    }
#endif
    if (axesCount == 2) switch (type) {
      case DMX_RELATIVE:          dmxCoreMotion(dmxGlobalX - v[0],
                                                dmxGlobalY - v[1],
                                                0, block);              break;
      case DMX_ABSOLUTE:          dmxCoreMotion(v[0], v[1], 0, block);  break;
      case DMX_ABSOLUTE_CONFINED: dmxCoreMotion(v[0], v[1], -1, block); break;
    }
}

static KeySym dmxKeyCodeToKeySym(DMXLocalInputInfoPtr dmxLocal,
                                 KeyCode keyCode)
{
    KeySymsPtr pKeySyms = NULL;

    if (!dmxLocal || !dmxLocal->pDevice || !dmxLocal->pDevice->key)
        return NoSymbol;
    pKeySyms = &dmxLocal->pDevice->key->curKeySyms;
    if (!pKeySyms) return NoSymbol;
    
    if (keyCode > pKeySyms->minKeyCode && keyCode <= pKeySyms->maxKeyCode) {
        DMXDBG2("dmxKeyCodeToKeySym: Translated keyCode=%d to keySym=0x%04x\n",
                keyCode,
                pKeySyms->map[(keyCode - pKeySyms->minKeyCode)
                              * pKeySyms->mapWidth]);
               
        return pKeySyms->map[(keyCode - pKeySyms->minKeyCode)
                             * pKeySyms->mapWidth];
    }
    return NoSymbol;
}

static KeyCode dmxKeySymToKeyCode(DMXLocalInputInfoPtr dmxLocal, KeySym keySym,
                                  int tryFirst)
{
    KeySymsPtr pKeySyms = &dmxLocal->pDevice->key->curKeySyms;
    int        i;

                                /* Optimize for similar maps */
    if (tryFirst >= pKeySyms->minKeyCode
        && tryFirst <= pKeySyms->maxKeyCode
        && pKeySyms->map[(tryFirst - pKeySyms->minKeyCode)
                         * pKeySyms->mapWidth] == keySym)
        return tryFirst;

    for (i = pKeySyms->minKeyCode; i <= pKeySyms->maxKeyCode; i++) {
        if (pKeySyms->map[(i - pKeySyms->minKeyCode)
                          * pKeySyms->mapWidth] == keySym) {
            DMXDBG3("dmxKeySymToKeyCode: Translated keySym=0x%04x to"
                    " keyCode=%d (reverses to core keySym=0x%04x)\n",
                    keySym, i, dmxKeyCodeToKeySym(dmxLocalCoreKeyboard,i));
            return i;
        }
    }
    return 0;
}

static int dmxFixup(DevicePtr pDev, int detail, KeySym keySym)
{
    GETDMXLOCALFROMPDEV;
    int keyCode;
    
    if (!dmxLocal->pDevice->key) {
        dmxLog(dmxWarning, "dmxFixup: not a keyboard device (%s)\n",
               dmxLocal->pDevice->name);
        return NoSymbol;
    }
    if (!keySym) keySym = dmxKeyCodeToKeySym(dmxLocal, detail);
    if (keySym == NoSymbol) return detail;
    keyCode = dmxKeySymToKeyCode(dmxLocalCoreKeyboard, keySym, detail);

    return keyCode ? keyCode : detail;
}

/** Enqueue a non-motion event from the \a pDev device with the
 * specified \a type and \a detail.  If the event is a KeyPress or
 * KeyRelease event, then the \a keySym is also specified.
 *
 * If \a block is set to \a DMX_BLOCK, then the SIGIO handler will be
 * blocked around calls to #dmxeqEnqueue(). */
    
void dmxEnqueue(DevicePtr pDev, int type, int detail, KeySym keySym,
                XEvent *e, DMXBlockType block)
{
    GETDMXINPUTFROMPDEV;
    xEvent xE;

    DMXDBG2("dmxEnqueue: Enqueuing type=%d detail=0x%0x\n", type, detail);

    switch (type) {
    case KeyPress:
    case KeyRelease:
        if (!keySym) keySym = dmxKeyCodeToKeySym(dmxLocal, detail);
        if (dmxCheckFunctionKeys(dmxLocal, type, keySym))
            return;
        if (dmxLocal->sendsCore && dmxLocal != dmxLocalCoreKeyboard)
            xE.u.u.detail = dmxFixup(pDev, detail, keySym);
        break;
    case ButtonPress:
    case ButtonRelease:
        detail = dmxGetButtonMapping(dmxLocal, detail);
        break;
    case MotionNotify:
        /* All MotionNotify events should be sent via dmxCoreMotion and
         * dmxExtMotion -- no input driver should build motion events by
         * hand. */
        dmxLog(dmxError, "dmxEnqueueXEvent: MotionNotify not allowed here\n");
        break;
                                /* Always ignore these events */
    case EnterNotify:
    case LeaveNotify:
    case KeymapNotify:
    case MappingNotify:         /* This is sent because we change the
                                 * modifier map on the backend/console
                                 * input device so that we have complete
                                 * control of the input device LEDs. */
        return;
    default:
#ifdef XINPUT
        if (type == ProximityIn || type == ProximityOut) {
            if (dmxLocal->sendsCore) return; /* Not a core event */
            break;
        }
#endif
        if (type >= LASTEvent) {
#ifdef XINPUT
            if (dmxTranslateAndEnqueueExtEvent(dmxLocal, e, block))
#endif
                dmxLogInput(dmxInput, "Unhandled extension event: %d\n", type);
        } else {
            dmxLogInput(dmxInput, "Unhandled event: %d (%s)\n",
                        type, dmxEventName(type));
        }
        return;
    }

    memset(&xE, 0, sizeof(xE));
    xE.u.u.type                = type;
    xE.u.u.detail              = detail;
    xE.u.keyButtonPointer.time = GetTimeInMillis();

#ifdef XINPUT
    if (!dmxLocal->sendsCore) dmxEnqueueExtEvent(dmxLocal, &xE, block);
    else
#endif
        dmxeqEnqueue(&xE);
}

/** A pointer to this routine is passed to low-level input drivers so
 * that all special keychecking is unified to this file.  This function
 * returns 0 if no special keys have been pressed.  If the user has
 * requested termination of the DMX server, -1 is returned.  If the user
 * has requested a switch to a VT, then the (1-based) number of that VT
 * is returned. */
int dmxCheckSpecialKeys(DevicePtr pDev, KeySym keySym)
{
    GETDMXINPUTFROMPDEV;
    int            vt    = 0;
    unsigned short state = 0;

    if (dmxLocal->sendsCore)
        state = dmxLocalCoreKeyboard->pDevice->key->state;
    else if (dmxLocal->pDevice->key)
        state = dmxLocal->pDevice->key->state;

    if (!dmxLocal->sendsCore) return 0; /* Only for core devices */

    DMXDBG2("dmxCheckSpecialKeys: keySym=0x%04x state=0x%04x\n", keySym,state);
    
    if ((state & (ControlMask|Mod1Mask)) != (ControlMask|Mod1Mask)) return 0;
    
    switch (keySym) {
    case XK_F1:
    case XK_F2:
    case XK_F3:
    case XK_F4:
    case XK_F5:
    case XK_F6:
    case XK_F7:
    case XK_F8:
    case XK_F9:
    case XK_F10:
        vt = keySym - XK_F1 + 1;
        break;

    case XK_F11:
    case XK_F12:
        vt = keySym - XK_F11 + 11;
        break;

    case XK_q:                  /* To avoid confusion  */
    case XK_BackSpace:
    case XK_Delete:
    case XK_KP_Delete:
        dmxLog(dmxInfo, "User request for termination\n");
        dispatchException |= DE_TERMINATE;
        return -1;              /* Terminate */
    }

    if (vt) {
        dmxLog(dmxInfo, "Request to switch to VT %d\n", vt);
        dmxInput->vt_switch_pending = vt;
        return vt;
    }

    return 0;                   /* Do nothing */
}
