/* $XFree86$ */
/*
 * $Xorg: mieq.c,v 1.4 2001/02/09 02:05:20 xorgcvs Exp $
 *
 * Copyright 1990, 1998  The Open Group
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation.
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
 * OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
 * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * Except as contained in this notice, the name of The Open Group shall not be
 * used in advertising or otherwise to promote the sale, use or other dealings
 * in this Software without prior written authorization from The Open Group.
 *
 * Author:  Keith Packard, MIT X Consortium
 */

/*
 * dmxeq.c is derived from mi/mieq.c so that XInput events can be handled
 *
 * Modified by: Rickard E. (Rik) Faith <faith@redhat.com>
 *
 * 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.
 */

/** \file
 * This file provides an event queue that knows about XInput events.
 * All of the code is based on mi/mieq.c and was modified as little as
 * possible to provide XInput event support (the copyright and some of
 * the comments are from The Open Group, Keith Packard, MIT X
 * Consortium).  (Another example of similar code is provided in
 * hw/xfree86/common/xf86Xinput.c.) */

#define DMX_EQ_DEBUG 0

#include "dmx.h"
#include "dmxeq.h"
#include "dmxinput.h"
#include "dmxlog.h"
#include "dmxdpms.h"

#include "inputstr.h"
#include "scrnintstr.h"         /* For screenInfo */

#ifdef XINPUT
#include "XIproto.h"
#define EXTENSION_PROC_ARGS void *
#include "extinit.h"            /* For LookupDeviceIntRec */
#endif

#if DMX_EQ_DEBUG
#define DMXDBG2(f,a,b)           dmxLog(dmxDebug,f,a,b)
#define DMXDBG5(f,a,b,c,d,e)     dmxLog(dmxDebug,f,a,b,c,d,e)
#else
#define DMXDBG2(f,a,b)
#define DMXDBG5(f,a,b,c,d,e)
#endif

/** The size of our queue.  (The queue provided by mi/mieq.c has a size
 * of 256.) */
#define QUEUE_SIZE  256

/** Information about the event. */
typedef struct _Event {
    xEvent	   event;    /**< Event. */
    ScreenPtr	   pScreen;  /**< Screen on which event occurred. */
#ifdef XINPUT
    deviceValuator valuator; /**< XInput device valuator information. */
#endif
} EventRec, *EventPtr;

/** Event queue. */
typedef struct _EventQueue {
    HWEventQueueType head; /**< Queue head; must be long for SetInputCheck. */ 
    HWEventQueueType tail; /**< Queue tail; must be long for SetInputCheck. */ 
    CARD32           lastEventTime; /**< To avoid time running backwards. */
    Bool	     lastMotion;    /**< True if last event was motion.  */
    EventRec         events[QUEUE_SIZE]; /**< Static allocation for signals. */
    DevicePtr	     pKbd, pPtr;    /**< Device pointers (to get funcs) */
    ScreenPtr	     pEnqueueScreen;/**< Screen events are delivered to. */
    ScreenPtr	     pDequeueScreen;/**< Screen events are dispatched to. */
} EventQueueRec, *EventQueuePtr;

static EventQueueRec dmxEventQueue;
static Bool          dmxeqInitializedFlag = FALSE;

Bool dmxeqInitialized(void)
{
    return dmxeqInitializedFlag;
}

Bool dmxeqInit(DevicePtr pKbd, DevicePtr pPtr)
{
    static unsigned long dmxGeneration = 0;
    
    if (dmxGeneration == serverGeneration && dmxeqInitializedFlag)
        return FALSE;
    dmxGeneration                = serverGeneration;
    dmxeqInitializedFlag         = TRUE;
    dmxEventQueue.head           = 0;
    dmxEventQueue.tail           = 0;
    dmxEventQueue.lastEventTime  = GetTimeInMillis();
    dmxEventQueue.pKbd           = pKbd;
    dmxEventQueue.pPtr           = pPtr;
    dmxEventQueue.lastMotion     = FALSE;
    dmxEventQueue.pEnqueueScreen = screenInfo.screens[0];
    dmxEventQueue.pDequeueScreen = dmxEventQueue.pEnqueueScreen;
    SetInputCheck(&dmxEventQueue.head, &dmxEventQueue.tail);
    return TRUE;
}

/**
 * This function adds an event to the end of the queue.  If the event is
 * an XInput event, then the next event (the valuator event) is also
 * stored in the queue.  If the new event has a time before the time of
 * the last event currently on the queue, then the time is updated for
 * the new event.
 *
 * Must be reentrant with ProcessInputEvents.  Assumption: dmxeqEnqueue
 * will never be interrupted.  If this is called from both signal
 * handlers and regular code, make sure the signal is suspended when
 * called from regular code.
 */

void dmxeqEnqueue(xEvent *e)
{
    HWEventQueueType oldtail, newtail;
    Bool             isMotion;

    oldtail                               = dmxEventQueue.tail;
    isMotion                              = e->u.u.type == MotionNotify;
    if (isMotion
        && dmxEventQueue.lastMotion
        && oldtail != dmxEventQueue.head) {
	if (oldtail == 0) oldtail = QUEUE_SIZE;
	oldtail = oldtail - 1;
    } else {
    	newtail = oldtail + 1;
    	if (newtail == QUEUE_SIZE) newtail = 0;
    	/* Toss events which come in late */
    	if (newtail == dmxEventQueue.head) return;
	dmxEventQueue.tail = newtail;
    }
    DMXDBG2("dmxeqEnqueue %d %d\n", dmxEventQueue.head, dmxEventQueue.tail);
    dmxEventQueue.lastMotion              = isMotion;
    dmxEventQueue.events[oldtail].pScreen = dmxEventQueue.pEnqueueScreen;

                                /* Store the event in the queue */
    dmxEventQueue.events[oldtail].event   = *e;
#ifdef XINPUT
    {
                                /* If this is an XInput event, store the
                                 * valuator event, too */
        deviceKeyButtonPointer *ev = (deviceKeyButtonPointer *)e;
        if (e->u.u.type >= LASTEvent && (ev->deviceid & MORE_EVENTS))
            dmxEventQueue.events[oldtail].valuator = *(deviceValuator *)(ev+1);
    }
#endif

                                /* Make sure that event times don't go
                                 * backwards - this is "unnecessary",
                                 * but very useful */
    if (e->u.keyButtonPointer.time < dmxEventQueue.lastEventTime
        && dmxEventQueue.lastEventTime - e->u.keyButtonPointer.time < 10000) {
	dmxEventQueue.events[oldtail].event.u.keyButtonPointer.time =
	    dmxEventQueue.lastEventTime;
    }
}

/** Make \a pScreen the new screen for enqueueing events.  If \a fromDIX
 * is TRUE, also make \a pScreen the new screen for dequeuing events. */
void dmxeqSwitchScreen(ScreenPtr pScreen, Bool fromDIX)
{
    dmxEventQueue.pEnqueueScreen = pScreen;
    if (fromDIX) dmxEventQueue.pDequeueScreen = pScreen;
}

#ifdef XINPUT
static void dmxeqProcessXInputEvent(xEvent *xe, EventRec *e)
{
    deviceKeyButtonPointer *ev     = (deviceKeyButtonPointer *)xe;
    int                    id      = ev->deviceid & DEVICE_BITS;
    DeviceIntPtr           pDevice = LookupDeviceIntRec(id);
    
    if (!pDevice) {
        dmxLog(dmxError, "dmxeqProcessInputEvents: id %d not found\n", id);
        return;
    }

    if (!pDevice->public.processInputProc) {
        dmxLog(dmxError,
               "dmxeqProcessInputEvents: no processInputProc for"
               " device id %d (%s)\n", id, pDevice->name);
        return;
    }
    
    if (ev->deviceid & MORE_EVENTS) {
        xe[1] = *(xEvent *)(&e->valuator);
        pDevice->public.processInputProc(xe, pDevice, 2);
    } else {
        pDevice->public.processInputProc(xe, pDevice, 1);
    }
}
#endif

/**
 * This function is called from #ProcessInputEvents() to remove events
 * from the queue and process them.
 */
void dmxeqProcessInputEvents(void)
{
    EventRec	*e;
    int		x, y;
    xEvent	xe[2];

    while (dmxEventQueue.head != dmxEventQueue.tail) {
        dmxDPMSWakeup();        /* Handles screen saver and DPMS */
	e = &dmxEventQueue.events[dmxEventQueue.head];
        DMXDBG5("dmxeqProcessInputEvents: type=%d screen=%p,%p root=%d,%d\n",
                e->event.u.u.type,
                e->pScreen, dmxEventQueue.pDequeueScreen,
                e->event.u.keyButtonPointer.rootX,
                e->event.u.keyButtonPointer.rootY);
	/*
	 * Assumption - screen switching can only occur on core motion events
	 */
	if (e->event.u.u.type == MotionNotify
            && e->pScreen != dmxEventQueue.pDequeueScreen) {
	    dmxEventQueue.pDequeueScreen = e->pScreen;
	    x = e->event.u.keyButtonPointer.rootX;
	    y = e->event.u.keyButtonPointer.rootY;
	    if (dmxEventQueue.head == QUEUE_SIZE - 1) dmxEventQueue.head = 0;
	    else                                      ++dmxEventQueue.head;
	    NewCurrentScreen(dmxEventQueue.pDequeueScreen, x, y);
	} else {
	    xe[0] = e->event;
	    if (dmxEventQueue.head == QUEUE_SIZE - 1) dmxEventQueue.head = 0;
	    else                                      ++dmxEventQueue.head;
	    switch (xe[0].u.u.type) {
	    case KeyPress:
	    case KeyRelease:
                if (!dmxEventQueue.pKbd) {
                    dmxLog(dmxError, "dmxeqProcessInputEvents: No keyboard\n");
                    return;
                }
                dmxEventQueue.pKbd
                    ->processInputProc(xe,
                                       (DeviceIntPtr)dmxEventQueue.pKbd, 1);
	    	break;
            default:
#ifdef XINPUT
                dmxeqProcessXInputEvent(xe, e);
                break;
#endif
                /* ifndef XINPUT, fall through */
            case ButtonPress:
            case ButtonRelease:
            case MotionNotify:
                if (!dmxEventQueue.pPtr) {
                    dmxLog(dmxError, "dmxeqProcessInputEvents: No mouse\n");
                    return;
                }
                dmxEventQueue.pPtr
                    ->processInputProc(xe,
                                       (DeviceIntPtr)dmxEventQueue.pPtr, 1);
	    	break;
	    }
	}
    }
}
