/*------- 
 *  Kurta "Series One" driver
 *
 *  Please do not look too closely at this code, as it could
 * cause health problems for those who cannot tolerate a lot
 * of laughing.  In other words, this codes is pretty ugly
 * and probably wont be of much use to anyone.
 * If you make any changes to this driver, please let me know
 * what you did to improve/change it.  The driver does need a
 * lot of work, but is does it's minimal job of sending mouse
 * events.
 */
#include <DOS/DOS.h>
#include <Devices/Serial.h>
#include <Devices/Input.h>
#include <Devices/InputEvent.h>
#include <Exec/IO.h>
#include <Exec/Lists.h>
#include <Exec/Memory.h>
#include <Exec/Devices.h>
#include <Exec/Libraries.h>
#include <Libraries/Commodities.h>
#include <Libraries/DOSExtens.h>
#include <WorkBench/Startup.h>
#include <CLib/ALib_protos.h>
#include <Proto/Commodities.h>
#include <Proto/DOS.h>
#include <Proto/Exec.h>
#include <Proto/Intuition.h>

#define	ABSEXECBASE ((struct ExecBase **)4L)

#define	READ_BUFFER	 5	// Each command is 5 bytes long.
/*------------------  FORMAT3 Settings -----------------------------*/
#define	BUTTON_GRN	 1 // m 0001	m = Menu
#define	BUTTON_RED	 2 // m 0010	bit 7 always = 1
#define	BUTTON_BLU	 4 // m 0100	bit 6 always = 0
#define	BUTTON_YLW	 8 // m 1000
#define	MENUSTRIP		32 // M xxxx	bit 4 = menu Y/N
#define	CMD_SIZE		 7 // Hi bits shifted Left 6 (128).
#define	MAXVALUE	    127 // = 1<<CMD_SIZE-1
/*------------------------------------------------------------------*/
#define	BUTTONS		(BUTTON_GRN|BUTTON_RED|BUTTON_BLU|BUTTON_YLW)
#define	BUTTON_MID	BUTTON_RED
#define	BUTTON_LEFT	BUTTON_GRN	// Its not the left, but it is always Button 1
#define	BUTTON_DOWN	BUTTON_BLU
#define	BUTTON_RIGHT	BUTTON_YLW
#define	MY_MOUSEMOVE	1	//  If any values change from
#define	MY_BUTTONEVENT 2	// the previous, then I must send
#define	MENU_FUNCTION	4	// an InputEvent to handler.
#define	TABLET_LORES  1200	// 100ppi for 12inches
#define	TABLET_HIRES	2400 // 200ppi for 12inches

struct Command
{
	UBYTE Buttons; //	Value = 1, 2, 4, 8, or 0-15 combo.
	UBYTE pButtons; //	Previous buttons.  Used for MOUSEUP/DOWN changes.
	UBYTE Menu;	//	Menu F0-F11
	UBYTE MButtons; //	Keep menu button presses separate.
	WORD X, Y;	//	Coordinates
	WORD pX, pY;	//	Previous coordinates. Used for DELTA values.
};

struct DosLibrary	 *DOSBase;
struct IntuitionBase  *IntuitionBase;
struct Library	 *CxBase, *IconBase;
extern struct WBStartup *WBenchMsg;

UWORD  GetKurta(UBYTE *, struct Command *);
ULONG  DoKMenu(struct Command *);

BOOL ExitFlag = 0;
struct MsgPort *broker_mp;
CxObj *broker, *filter, *sender, *translate;
struct NewBroker newbroker =
{
	NB_VERSION,
	"Kurta_Driver",
	"Kurta Drawing Pad Driver",
	"Commodity for Kurta Drawing Pad",
	NBU_UNIQUE | NBU_NOTIFY,
	0, 0, 0, 0
};
struct InputEvent *ie;
ULONG cxsigflag;

__saveds Kurta(void)
{
	struct IOExtSer   *sio;	//  Serial IO request.
	struct MsgPort	   *smp;	//  Serial MsgPort.
	struct IOStdReq   *iio;	//  Input IO request.
	struct MsgPort	   *imp;	//  Input MsgPort.
	struct InputEvent *myevent;	// My fake input event.
	struct IEPointerTablet *iep;	// My new pointer pixel positions.
	struct Command	  c; // What happened on the tablet.
	UBYTE  readbuffer[READ_BUFFER];	// Serial data from tablet.
	ULONG  result;
	ULONG  waitmask;
	UWORD  kevent;	 // Has there been any events from Kurta()?
	UWORD  bmask;		// Holds which buttons have changed since last message.
	ULONG  msgid, msgtype;
	CxMsg  *msg;

	struct Process *proc;
	struct Message *WBMsg = NULL;

	proc = (struct Process *)FindTask(NULL); // Who/Where am I?
	if( !proc->pr_CLI )		// Am I started from a CLI?
	{
		WaitPort(&proc->pr_MsgPort);	// NO? Well then get WBStartup Msg.
		WBMsg = GetMsg(&proc->pr_MsgPort);
	}

	DOSBase=(struct DosLibrary *)OpenLibrary("dos.library", 37);
	if( !DOSBase ) // No startup code, get DOSBase my self
		return(-1);

	if( smp = (struct MsgPort *)CreatePort("Kurta_Serial", 0) )
	{
		if( imp = (struct MsgPort *)CreatePort("Kurta_Input", 0) )
		{
			if( sio=(struct IOExtSer *)CreateExtIO(smp, sizeof(struct IOExtSer)) )
			{
				if( myevent= (struct InputEvent *)AllocVec(sizeof(struct InputEvent), MEMF_PUBLIC) )
				{
					if( iep = (struct IEPointerTablet *)AllocVec(sizeof(struct IEPointerTablet), MEMF_PUBLIC) )
					{
		  if (iio = (struct IOStdReq *)CreateIORequest(imp,sizeof(struct IOStdReq)))
		  {
			if( !OpenDevice("input.device", NULL,
								(struct IORequest *)iio, NULL) )
			{
				if( IntuitionBase = (struct IntuitionBase *)
							OpenLibrary("intuition.library", 36L) )
				{
									sio->io_SerFlags = 0;
									if( !OpenDevice(SERIALNAME, NULL, sio, NULL) )
									{
										sio->IOSer.io_Command  = SDCMD_SETPARAMS;
										sio->io_SerFlags	  &= ~SERF_PARTY_ON;
										sio->io_SerFlags	  |= SERF_XDISABLED | SERF_SHARED;
										sio->io_Baud		   = 9600;
										if( !(result=DoIO(sio)) )
										{
									/*** Put commodity startup here  ***/
	    if (CxBase = OpenLibrary("commodities.library", 37L))
   	 {
      	  if (broker_mp = CreateMsgPort())
	        {
   	         newbroker.nb_Port = broker_mp;

         	   if (broker = CxBroker(&newbroker, NULL))
            	{
						ActivateCxObj(broker, 1L);

											waitmask = SIGBREAKF_CTRL_C | 1L<<smp->mp_SigBit | (1L << broker_mp->mp_SigBit);
/*---------------Initialization complete------------------------------------*/
											/*  Now just wait until data comes in, or user breaks. */
											while( !ExitFlag )
											{
												sio->IOSer.io_Command  = CMD_READ;
												sio->IOSer.io_Length   = READ_BUFFER;
												sio->IOSer.io_Data	   = (APTR)readbuffer;
												SendIO(sio);
												result = Wait(waitmask); // Sit and wait...
												if( ExitFlag||(result & SIGBREAKF_CTRL_C) )	// Quit
													break;
			if( result & (1L<<broker_mp->mp_SigBit) )
			{
	        while(msg = (CxMsg *)GetMsg(broker_mp))
	        {

            msgid = CxMsgID(msg);
            msgtype = CxMsgType(msg);
            ReplyMsg((struct Message *)msg);

            switch(msgtype)
            {
                case CXM_IEVENT:
                    /* Shouldn't get any of these in this example */
                    break;
                case CXM_COMMAND:
                    /* Commodities has sent a command */
                    switch(msgid)
                    {
                        case CXCMD_DISABLE:
                            /* The user clicked Commodities Exchange disable
                             * gadget better disable
                             */
                            ActivateCxObj(broker, 0L);
                            break;
                        case CXCMD_ENABLE:
                            /* user clicked enable gadget */
                            ActivateCxObj(broker, 1L);
                            break;
                        case CXCMD_KILL:
                            /* user clicked kill gadget, better quit */
                            ExitFlag=TRUE;
                            break;
                    }
                    break;
                default:
//                    kprintf("Unknown msgtype\n");
                    break;
            }
            break;
        }
        }

												if( CheckIO(sio) )
												{
												WaitIO(sio);
												if( sio->IOSer.io_Actual = 5 )
												{
													kevent = GetKurta(readbuffer, &c);
												  if( kevent&MENU_FUNCTION )
													{
														if( c.Menu==11 )
														{
															ExitFlag = TRUE;
															break;
														}
														myevent->ie_Class = IECLASS_RAWKEY; /* new mouse pos */
														myevent->ie_SubClass = NULL;
														myevent->ie_Code = c.Menu+0x4F;
														myevent->ie_Qualifier = NULL;
														myevent->ie_EventAddress = (APTR)iep; /* IEPointerTablet */
														myevent->ie_NextEvent = NULL;
														iio->io_Data = (APTR)myevent;	  /* InputEvent */
														iio->io_Length = sizeof(struct InputEvent);
														iio->io_Command = IND_WRITEEVENT;

														DoIO((struct IORequest *)iio);
													}
																//result = DoKMenu(&c);
													else
													{
														if( kevent & MY_MOUSEMOVE )
														{
															myevent->ie_Class = IECLASS_NEWPOINTERPOS; /* new mouse pos */
															myevent->ie_SubClass = IESUBCLASS_TABLET;   /* on pixel */
															myevent->ie_Code = IECODE_NOBUTTON;
															myevent->ie_Qualifier = NULL;
															myevent->ie_EventAddress = (APTR)iep; /* IEPointerTablet */
															myevent->ie_NextEvent = NULL;
															iio->io_Data = (APTR)myevent;	  /* InputEvent */
															iio->io_Length = sizeof(struct InputEvent);
															iio->io_Command = IND_WRITEEVENT;
															iep->iept_Range.X = TABLET_LORES;
															iep->iept_Range.Y = TABLET_LORES;
															iep->iept_Value.X = c.X;
															iep->iept_Value.Y = c.Y;
															iep->iept_Pressure	= NULL;	// Not implemented.

															DoIO((struct IORequest *)iio);
														}
														if( kevent & MY_BUTTONEVENT ) // TABLET cannot do Buttons.
														{
															bmask = c.Buttons ^ c.pButtons;
															myevent->ie_Class = IECLASS_RAWMOUSE;
															myevent->ie_SubClass = NULL;
															myevent->ie_Code = IECODE_NOBUTTON;
															myevent->ie_Qualifier = NULL;
															myevent->ie_EventAddress = NULL;
															myevent->ie_NextEvent = NULL;
															iio->io_Data = (APTR)myevent;	  /* InputEvent */
															iio->io_Length = sizeof(struct InputEvent);
															iio->io_Command = IND_WRITEEVENT;
															if( bmask & BUTTON_LEFT )
															{
																myevent->ie_Code = IECODE_LBUTTON;
																if( !(c.Buttons & BUTTON_LEFT) )
																	myevent->ie_Code |= IECODE_UP_PREFIX;
															}
															else
															if( bmask & BUTTON_RIGHT )
															{
																myevent->ie_Code = IECODE_RBUTTON;
																if( !(c.Buttons & BUTTON_RIGHT) )
																	myevent->ie_Code |= IECODE_UP_PREFIX;
															}
															else
															if( bmask & BUTTON_MID )
															{
																myevent->ie_Code = IECODE_MBUTTON;
																if( !(c.Buttons & BUTTON_MID) )
																	myevent->ie_Code |= IECODE_UP_PREFIX;
															}

															DoIO((struct IORequest *)iio);
														}
													}
												}
												else return(2);	//	printf("Bad Command.\n");
												}

											}
											DisplayBeep(NULL);
											AbortIO(sio);	// Clean up port and quit.
											WaitIO(sio);
											DisplayBeep(NULL);

/*-----------------Cleanup starts here--------------------------------------*/
									/*** Put commodity teardown here  ***/
                DeleteCxObj(broker);

                while(msg = (CxMsg *)GetMsg(broker_mp))
                        ReplyMsg((struct Message *)msg);
            }
            DeletePort(broker_mp);
        }
        CloseLibrary(CxBase);
    }

										}
										else
											return(3);	//printf("Could not set Serial Params.\n");
										CloseDevice(sio);
									}
									else
										return(4);	//printf("Could not open serial.device.\n");
									CloseLibrary(IntuitionBase);
								}
								else
									return(5);	//printf("Could not open IntuitionBase.\n");
							CloseDevice((struct IORequest *)iio);
							}
							else
								return(6);	//printf("Could no OpenDevice for iio.\n");
							DeleteIORequest(iio);
						}
						else
							return(7);	//printf("Could not CreateIORequest(Input).\n");
						FreeVec(iep);
					}
					else
						return(8);	//printf("Could not AllocMem 'iep'.\n");
					FreeVec(myevent);
				}
				else
					return(9);	//printf("Could not AllocMem for 'myevent'.\n");
				DeleteExtIO(sio);
			}
			else
				return(10);	//printf("Could not Create[serial]ExtIO.\n");
			DeletePort(imp);
		}
		else
			return(11);	//printf("Could not CreatePort(Kurta_Input)\.\n");
		DeletePort(smp);
	}
	else
		return(12);	//printf("Could not CreatePort(Kurta_Serial)\n");
	if( DOSBase )	CloseLibrary(DOSBase);
  if( WBMsg )	// Hey! We were started from WB, clean up.
	{
		Forbid();
		ReplyMsg(WBMsg);
		Permit();
  }

	return(0);	// Remember to exit from Main or program will continue...
}

UWORD
GetKurta(UBYTE *cmd, struct Command *c)
{
	UWORD event = 0;
	static	UBYTE  accel=0;	// Immitate a mouse accelerator for the tablet.

  /*	 Save all previous stats	 */
	c->pX = c->X;
	c->pY = c->Y;
	c->pButtons = c->Buttons;
	if( cmd[0]&MENUSTRIP )
	{
		c->Menu = (cmd[1]&MAXVALUE) - 0x0A; //	Which menu (0-11).
		c->MButtons = (cmd[0]&BUTTONS)|(cmd[2]&MAXVALUE)<<CMD_SIZE;
		if( c->Menu == 0 )
		{
			accel++; if( accel >2 ) accel = 0;	 // 0,1,2 (x << accel = 1, 2, 4 )
			DisplayBeep(NULL);
		}
		else
			event |= MENU_FUNCTION;
	}
	else
	{
		c->Buttons = (cmd[0]&BUTTONS); // Buttons currently pressed
		c->X = ((cmd[1]&MAXVALUE) | (cmd[2]&MAXVALUE)<<CMD_SIZE)<<accel;
		c->Y = ((cmd[3]&MAXVALUE) | (cmd[4]&MAXVALUE)<<CMD_SIZE)<<accel;
		if( c->X < 0 )	 c->X = 0;
		if( c->Y < 0 )	 c->Y = 0;
		if( (c->pX != c->X)||(c->pY != c->Y) ) //  Send input events if
			event |= MY_MOUSEMOVE;		// if any event differs
		if( c->pButtons != c->Buttons )		// from the previous.
			event |= MY_BUTTONEVENT;		//
	}

	return(event);
}

ULONG
DoKMenu(struct Command *c)
{
	if( c->Menu == 11 )
		ExitFlag = TRUE;

		DisplayBeep(NULL);

	return(ExitFlag);
}

