
#define DEBUG
/*
 *  DMOUSE-HANDLER.C
 *
 *  (c)Copyright 1989 by Matthew Dillon, All Rights Reserved
 *
 *  V1.20, last revision 3 August 1989
 *
 *  Note on upping the handler process priority.  This is done to cause the
 *  handler task to get CPU before the current input event completes its
 *  processing so intuition calls made by the process are executed before
 *  the event is propogated.  If said intuition calls block, it's ok
 *  because they are not blocking the input handler process.
 */

#include "dmouse.h"
#include <hardware/custom.h>
#include <hardware/dmabits.h>
#include <graphics/gfxmacros.h>


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/*typedef struct Layer	      LAYER;*/
typedef IE  *IEP;
typedef struct IORequest    IORequest;

__far extern struct Custom custom;

DMS	*Dms		       = NULL;
IBASE	*IntuitionBase	       = NULL;
GFXBASE *GfxBase	       = NULL;
struct LayersBase  *LayersBase = NULL;
/*
struct ExecBase    *SysBase    = NULL;
*/
struct DosLibrary  *DOSBase    = NULL;

static PORT	*IOPort = NULL;     /*	For IPC messages		  */
static LIST	BlankList;	    /*	list of external blanker programs */

static char	STimedout = 0;
static char	MTimedout = 0;
static long	STime = 0, MTime = 0;
#ifdef DEBUG
static long	DBFh = NULL;
#endif
static void	*ReqCache = NULL;   /*	to prevent massive AllocMem()s    */


NS	Ns = {	0, 0, 64, -1, 1, -1, -1, 0, CUSTOMSCREEN|SCREENQUIET };


IE DummyIE = { 0 };

short	NRMe	= 0;	/*  Don't Repeat Mouse Events   */

IE *handler();

int doipcmsg(short);
void sendrequest(long, IE *);
void DeleteBlanker(IORequest *);
LAYER *WhichMouseLayer(void);
LAYER *WhichMouseLayer(void);

void
_main()
{
    DMS *dms;
    IOR *ior;
    INT addhand;
    IBASE *ib;

    /*
    SysBase = *(struct ExecBase **)4;
    */
    DOSBase = (struct DosLibrary *)OpenLibrary("dos.library", 0);
    {
	PROC *proc = (PROC *)FindTask(NULL);
	proc->pr_ConsoleTask = NULL;
    }
    NRMe = 0;
    dms = Dms = (DMS *)FindPort(PORTNAME);
    if (!dms)
	return;
    dms->Port.mp_Flags = PA_SIGNAL;
    dms->Port.mp_SigBit = AllocSignal(-1);
    dms->Port.mp_SigTask = FindTask(NULL);
    dms->HandTask = dms->Port.mp_SigTask;
    ior = CreateStdIO(&dms->Port);
    IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library", 0);
    GfxBase = (struct GfxBase *)OpenLibrary("graphics.library", 0);
    LayersBase = (struct LayersBase *)OpenLibrary("layers.library", 0);
    IOPort = CreatePort("DMouse.ipc", 0);
    NewList(&BlankList);

    if (!IntuitionBase || !GfxBase || !LayersBase)
	goto startupfail;

    ib = IntuitionBase;

    clrmem(&addhand, sizeof(addhand));
    addhand.is_Node.ln_Name = "DMouse";
    addhand.is_Node.ln_Pri = dms->IPri;
    addhand.is_Code = (FPTR)handler;
    addhand.is_Data = NULL;

    if (OpenDevice("input.device", 0, ior, 0)) {
	goto startupfail;
    } else {
	SCR *scr = NULL;
	short sproff = 0;
	long ipc_mask;

	Signal(dms->ShakeTask, 1 << dms->ShakeSig);
	ior->io_Command = IND_ADDHANDLER;
	ior->io_Data = (APTR)&addhand;
	ior->io_Message.mn_Node.ln_Type = NT_MESSAGE;
	DoIO(ior);

	ipc_mask = 1 << IOPort->mp_SigBit;

	for (;;) {
	    long sigs = Wait(SBF_C|(1<<dms->Port.mp_SigBit)|ipc_mask);
	    if (sigs & (1 << dms->Port.mp_SigBit)) {
		REQ *msg;
		while (msg = (REQ *)GetMsg(&dms->Port)) {
		    switch((long)msg->Msg.mn_Node.ln_Name) {
		    case REQ_SCREENON:
			if (scr)
			    CloseScreen(scr);
			scr = NULL;
			doipcmsg(0x82);
			break;
		    case REQ_SCREENOFF:
			if (scr)
			    ScreenToFront(scr);
			if (doipcmsg(0x83) == 0 && scr == NULL) {
			    if (scr = OpenScreen(&Ns))
				SetRGB4(&scr->ViewPort, 0, 0, 0, 0);
			}
			break;
		    case REQ_MOUSEON:
			if (sproff) {
			    ON_SPRITE;
			    sproff = 0;
			}
			doipcmsg(0x80);
			break;
		    case REQ_MOUSEOFF:
			/*
			 *  note, sometimes the sprite gets turned on again, so
			 *  we re-off it every mouse-ptr-timeout
			 */
			if (doipcmsg(0x81) == 0) {
			    WaitTOF();
			    OFF_SPRITE;
			    sproff = 1;
			}
			break;
		    case REQ_DOCMD:
			{
			    long fh = (long)Open("nil:", 1006);
			    Execute(dms->Cmd, NULL, fh);
			    if (fh)
				Close(fh);
			}
			break;
		    case REQ_RAWMOUSE:
			{
			    LAYER *layer;

			    NRMe = 0;
			    Forbid();
			    layer = WhichMouseLayer();
			    if (msg->ie_Code == IECODE_RBUTTON && dms->LMBEnable && (msg->ie_Qualifier & dms->RQual)) {
				WIN *win;
				if (layer && (win = (WIN *)layer->Window) && !(win->Flags & BACKDROP) && (win->NextWindow || win->WScreen->FirstWindow != win)) {
				    if (dms->FBEnable || (win->Flags & WINDOWDEPTH)) {
					if (dms->Workbench)
					    WindowToBack(win);
					else
					    BehindLayer(0, layer);
				    }
				} else if (ib->FirstScreen)
				    ScreenToBack(ib->FirstScreen);
			    }
			    if (layer && layer->Window) {
				if (msg->ie_Code == IECODE_LBUTTON && !(((WIN *)layer->Window)->Flags & BACKDROP) && dms->LMBEnable && layer->ClipRect && layer->ClipRect->Next) {
				    /*
				     *	Note: Case where it is the 'first' click in a series, where dms->CTime is
				     *	      garbage, works properly no matter what DoubleClick returns.
				     */
				    if (dms->LQual == 0 || (msg->ie_Qualifier & dms->LQual)) {
					if ((APTR)dms->CWin == layer->Window && DoubleClick(dms->CTime.tv_secs, dms->CTime.tv_micro, msg->ie_TimeStamp.tv_secs, msg->ie_TimeStamp.tv_micro))
					    --dms->CLeft;
					else
					    dms->CLeft = dms->Clicks - 1;
					dms->CTime = msg->ie_TimeStamp;
					dms->CWin = (WIN *)layer->Window;
					if (dms->CLeft == 0) {
					    dms->CLeft = dms->Clicks;
					    if (dms->FBEnable || (((WIN *)layer->Window)->Flags & WINDOWDEPTH)) {
						if (dms->Workbench)
						    WindowToFront((WIN *)layer->Window);
						else
						    UpfrontLayer(0, layer);
					    }
					}
				    }
				}
				if ((dms->AAEnable & 1) && (void *)layer->Window != (void *)ib->ActiveWindow && msg->ie_Code == IECODE_NOBUTTON && !(msg->ie_Qualifier & 0x7000)) {
				    if (!ib->ActiveWindow || !ib->ActiveWindow->FirstRequest)
					ActivateWindow((WIN *)layer->Window);
				}
			    }
			    Permit();
			}
			break;
		    case REQ_RAWKEY:
			{
			    LAYER *layer;

			    Forbid();
			    layer = WhichMouseLayer();
			    if (layer && layer->Window && (void *)layer->Window != (void *)ib->ActiveWindow) {
				if (!ib->ActiveWindow || !ib->ActiveWindow->FirstRequest)
				    ActivateWindow((WIN *)layer->Window);
			    }
			    Permit();
			}
			break;
#ifdef DEBUG
		    case REQ_DEBUG:
			{
			    char buf[128];
			    if (!DBFh) {
				DBFh = Open("con:0/0/400/100/dmouse-debug", 1006);
				if (!DBFh)
				    break;
			    }
			    sprintf(buf, "%02lx %04lx %04lx (%d,%d)\n",
				msg->ie_Class,
				msg->ie_Code,
				msg->ie_Qualifier,
				msg->rq_X,
				msg->rq_Y
			    );
			    Write(DBFh, buf, strlen(buf));
			}
			break;
		    case REQ_DEBUGOFF:
			if (DBFh) {
			    Close(DBFh);
			    DBFh = NULL;
			}
			break;
#endif
		    }
		    if (ReqCache == NULL && msg->Msg.mn_Length == sizeof(REQ))
			ReqCache = (void *)msg;
		    else
			FreeMem(msg, msg->Msg.mn_Length);
		}
	    }
	    if (sigs & SBF_C)
		break;

	    /*
	     *	IPC request.
	     */

	    if (sigs & ipc_mask) {
		IORequest *ior;
		while (ior = (IORequest *)GetMsg(IOPort)) {
		    long req = 0;

		    if (ior->io_Message.mn_Node.ln_Type == NT_REPLYMSG) {
			FreeMem(ior, ior->io_Message.mn_Length);
			continue;
		    }

		    ior->io_Error = 0;
		    switch(ior->io_Command) {
		    case 0x80:	/* mouse on  */
			req = REQ_MOUSEON;
			break;
		    case 0x81:	/* mouse off */
			req = REQ_MOUSEOFF;
			MTimedout = 1;
			break;
		    case 0x82:	/* screen on */
			req = REQ_SCREENON;
			break;
		    case 0x83:	/* screen off*/
			req = REQ_SCREENOFF;
			STimedout = 1;
			break;
		    case 0x84:	/* add hand  */
			AddHead(&BlankList, ior);
			ior = NULL;
			break;
		    case 0x85:	/* rem hand  */
			DeleteBlanker(ior);
			ior = NULL;
			break;
		    }
		    if (req)
			sendrequest(req, NULL);
		    if (ior)
			ReplyMsg(&ior->io_Message);
		}
	    }
	}
#ifdef DEBUG
	if (DBFh) {
	    Close(DBFh);
	    DBFh = NULL;
	}
#endif
	ior->io_Command = IND_REMHANDLER;
	ior->io_Data = (APTR)&addhand;
	ior->io_Message.mn_Node.ln_Type = NT_MESSAGE;
	DoIO(ior);

	ior->io_Command = IND_WRITEEVENT;	/*  NULL EVENT	*/
	ior->io_Length = sizeof(IE);
	ior->io_Data = (APTR)&DummyIE;
	ior->io_Message.mn_Node.ln_Type = NT_MESSAGE;
	DoIO(ior);
	CloseDevice(ior);
	{
	    MSG *msg;
	    while (msg = GetMsg(&dms->Port))
		FreeMem(msg, msg->mn_Length);
	}
	if (scr)
	    CloseScreen(scr);

	if (sproff) {
	    ON_SPRITE;
	    sproff = 0;
	}
    }
    goto closedown;
startupfail:
    dms->StartupError = 1;
    Signal(dms->ShakeTask, 1 << dms->ShakeSig);
    Wait(SBF_C);
closedown:
    DeleteStdIO(ior);
fail:
    if (IOPort) {
	IORequest *ior;     /*	wait for RemReq messages */

	doipcmsg(0x86);     /*  send closedown requests  */
	Forbid();
	while (GetHead(&BlankList)) {
	    WaitPort(IOPort);
	    while (ior = (IORequest *)GetMsg(IOPort)) {
		if (ior->io_Message.mn_Node.ln_Type == NT_REPLYMSG) {
		    FreeMem(ior, ior->io_Message.mn_Length);
		    continue;
		}
		if (ior->io_Command == 0x85)    /*  receive remove req  */
		    DeleteBlanker(ior);
		else
		    ReplyMsg(&ior->io_Message);              /*  ignore other reqs   */
	    }
	}
	DeletePort(IOPort);
	Permit();
    }
    if (IntuitionBase)
	CloseLibrary((LIB *)IntuitionBase);
    if (GfxBase)
	CloseLibrary((LIB *)GfxBase);
    if (LayersBase)
	CloseLibrary((LIB *)LayersBase);
    CloseLibrary((LIB *)DOSBase);
    if (ReqCache)
	FreeMem(ReqCache, sizeof(REQ));
    Forbid();
    Signal(dms->ShakeTask, 1 << dms->ShakeSig);
}

void
DeleteBlanker(ior)
IORequest *ior;
{
    IORequest *io2;

    ior->io_Error = 0;
    for (io2 = (IORequest *)GetHead(&BlankList); io2; io2 = (IORequest *)GetSucc(io2)) {
	if (io2->io_Unit == ior->io_Unit) {
	    Remove(io2);
	    if (ior)
		ReplyMsg(&ior->io_Message);
	    ReplyMsg(&io2->io_Message);
	    ior = NULL;
	}
    }
    if (ior) {
	ior->io_Error = -1;
	ReplyMsg(&ior->io_Message);
    }
}

doipcmsg(cmd)
short cmd;
{
    short count = 0;
    short flags = 1 << (cmd & 0x7F);    /*  enable flags */
    IORequest *iob, *io;

    for (iob = (IORequest *)GetHead(&BlankList); iob; iob = (IORequest *)GetSucc(iob)) {
	if (cmd == 0x86 || (iob->io_Flags & flags)) {
	    io = AllocMem(sizeof(IORequest), MEMF_PUBLIC|MEMF_CLEAR);
	    if (io) {
		io->io_Command = cmd;
		io->io_Unit = iob->io_Unit;
		io->io_Message.mn_ReplyPort = IOPort;
		io->io_Message.mn_Length = sizeof(IORequest);
		PutMsg(iob->io_Message.mn_ReplyPort, &io->io_Message);
		++count;
	    }
	}
    }
    return((int)count);
}

/*
 *  The INPUT.DEVICE HANDLER
 */

__geta4 IE *
CHandler(Ev)
IE *Ev;
{
    IE *ev;
    DMS *dms;

    dms = Dms;
    for (ev = Ev; ev; ev = ev->ie_NextEvent) {  /*  chgd feb 1990 3/Ev->ev */
#ifdef DEBUG
	if (dms->Debug) {
	    if (ev->ie_Class != IECLASS_TIMER)
		sendrequest(REQ_DEBUG, ev);
	} else if (DBFh) {
	    sendrequest(REQ_DEBUGOFF, ev);
	}
#endif
	switch(ev->ie_Class) {
	case IECLASS_RAWMOUSE:
	    /*
	     *	Mouse events restore both the screen and mouse pointer.
	     */

	    STime = ev->ie_TimeStamp.tv_secs + dms->STo;
	    MTime = ev->ie_TimeStamp.tv_secs + dms->MTo;
	    if (STimedout)
		sendrequest(REQ_SCREENON, ev);
	    if (MTimedout)
		sendrequest(REQ_MOUSEON, ev);
	    STimedout = MTimedout = 0;

	    /*
	     *	Mouse Acceleration
	     */
	    {
		short n;
		short s;

		if (dms->Acc != 1) {
		    n = ev->ie_X;
		    s = 1;
		    if (n < 0) {
			n = -n;
			s = -1;
		    }
		    if (n > dms->AThresh)
			ev->ie_X = s * (short)((n - dms->AThresh - 1) * dms->Acc + dms->AThresh + 1);
		    n = ev->ie_Y;
		    s = 1;
		    if (n < 0) {
			n = -n;
			s = -1;
		    }
		    if (n > dms->AThresh)
			ev->ie_Y = s * (short)((n - dms->AThresh - 1) * dms->Acc + dms->AThresh + 1);
		}
	    }

	    /*
	     *	Auto Activate and LMB (win/scrn front/bak)
	     */

	    if (dms->LMBEnable && ev->ie_Code == IECODE_RBUTTON && (ev->ie_Qualifier & dms->RQual))
		ev->ie_Class = IECLASS_NULL;	/*  remove event    */
	    if (NRMe == 0 && ((dms->AAEnable & 1) || dms->LMBEnable)) {
		short old;
		NRMe = 1;
		if (ev->ie_Code != IECODE_NOBUTTON)
		    old = SetTaskPri(dms->Port.mp_SigTask, 21);
		sendrequest(REQ_RAWMOUSE, ev);
		if (ev->ie_Code != IECODE_NOBUTTON) {
		    SetTaskPri(dms->Port.mp_SigTask, old);
		    WaitTOF();  /*  cause a delay   */
		}
	    }
	    break;
	case IECLASS_RAWKEY:
	    /*
	     *	Keyboard events will kill the screen timeout but not
	     *	the mouse timeout.  Note that the priority of the
	     *	co-process must be upped to ensure it is able to make the
	     *	window active before the keystroke is passed further.
	     *
	     *	key releases are ignored
	     *
	     *	note: ie_Qualifier may or may not have bit 15 set
	     */
	    if (ev->ie_Code & 0x80)
		break;
	    if (dms->AAEnable & 2) {
		short old;
		old = SetTaskPri(dms->Port.mp_SigTask, 21);
		sendrequest(REQ_RAWKEY, ev);
		SetTaskPri(dms->Port.mp_SigTask, old);
		WaitTOF();  /*  cause a delay   */
	    }
	    STime = ev->ie_TimeStamp.tv_secs + dms->STo;
	    if (STimedout) {
		sendrequest(REQ_SCREENON, ev);
		if (dms->MTo == 0)
		    sendrequest(REQ_MOUSEON, ev);
	    }
	    STimedout = 0;

	    if (ev->ie_Code == dms->Code && (ev->ie_Qualifier | 0x8000) == dms->Qual) {
		sendrequest(REQ_DOCMD, ev);
		ev->ie_Class = IECLASS_NULL;	/*  remove event    */
	    }
	    break;
	case IECLASS_TIMER:
	    /*
	     *	On a timer event, if timeout has occured execute the operation
	     *	and reset the timeout.	Note that this will cause continuous
	     *	timeouts every STo and MTo seconds... required because at any
	     *	time Intuition might turn the mouse back on or open a screen or
	     *	something and I want the blanker's to work in the long run.
	     */
	    {
		long old;
		if (dms->Reset) {
		    dms->Reset = 0;
		    STime = ev->ie_TimeStamp.tv_secs + dms->STo;
		    MTime = ev->ie_TimeStamp.tv_secs + dms->MTo;
		}
		if (dms->STo && (old = STime - ev->ie_TimeStamp.tv_secs) < 0) {
		    STime = ev->ie_TimeStamp.tv_secs + dms->STo + 10;
		    STimedout = 1;
		    MTimedout = 1;
		    if (old > -10) {
			sendrequest(REQ_SCREENOFF, ev);
			sendrequest(REQ_MOUSEOFF, ev);
		    }
		}
		if (dms->MTo && (old = MTime - ev->ie_TimeStamp.tv_secs) < 0) {
		    MTime = ev->ie_TimeStamp.tv_secs + dms->MTo + 1;
		    MTimedout = 1;
		    if (old > -10)
			sendrequest(REQ_MOUSEOFF, ev);
		}
	    }
	    break;
	}
    }
    return(Ev);
}

void
sendrequest(creq, ev)
long creq;
IE *ev;
{
    REQ *req;

    if (req = ReqCache)
	ReqCache = NULL;
    else
	req = AllocMem(sizeof(REQ), MEMF_PUBLIC);

    if (req) {
	req->Msg.mn_Node.ln_Name = (char *)creq;
	req->Msg.mn_ReplyPort = NULL;
	req->Msg.mn_Length = sizeof(REQ);
	if (ev) {
	    req->ie_Class= ev->ie_Class;
	    req->ie_Code = ev->ie_Code;
	    req->ie_Qualifier = ev->ie_Qualifier;
	    req->ie_TimeStamp = ev->ie_TimeStamp;
	    req->rq_X = ev->ie_X;
	    req->rq_Y = ev->ie_Y;
	}
	PutMsg(&Dms->Port, (MSG *)req);
    }
}

LAYER *
WhichMouseLayer()
{
    IBASE *ib = IntuitionBase;
    LAYER *layer = NULL;
    SCR *scr = ib->FirstScreen;

    for (scr = ib->FirstScreen; scr; scr = scr->NextScreen) {
	short mousey = ib->MouseY;
	short mousex = ib->MouseX;
	if (!(scr->ViewPort.Modes & LACE))
	    mousey >>= 1;
	if (!(scr->ViewPort.Modes & HIRES))
	    mousex >>= 1;
	if (layer = WhichLayer(&scr->LayerInfo, mousex, mousey - scr->ViewPort.DyOffset))
	    break;
	if (mousey >= scr->ViewPort.DyOffset)
	    break;
    }
    return(layer);
}


