/*
** $VER: netinput.c 1.3 (22 Jul 1995)
**
** AMIGA netinput client code
**
** (C (14 Jul 1995) Copyright 1995 Marius Gröger
**     All Rights Reserved
**
** $HISTORY:
**
** 22 Jul 1995 : 001.003 :  QUIET option
** 14 Jul 1995 : 001.002 :  some code cleanup
** 06 Mar 1995 : 001.001 :  version id added
** 05 Mar 1995 : 001.000 :  created
*/

#define DEBUG 0

/*F*/ /* includes */
#include <NETINCLUDE:sys/types.h>
#include <NETINCLUDE:sys/stat.h>
#include <NETINCLUDE:stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <clib/netlib_protos.h>
#include <netdb.h>

#include <clib/dos_protos.h>
#include <pragmas/dos_pragmas.h>

#include <clib/exec_protos.h>
#include <pragmas/exec_sysbase_pragmas.h>

#include <exec/memory.h>
#include <exec/io.h>
#include <exec/interrupts.h>

#include <devices/input.h>
#include <devices/inputevent.h>

#include "netinput.h"
#include "unixsem.h"
#include "compiler.h"
#include "debug.h"

/*E*/
/*F*/ /* global data */
extern struct Library *SocketBase, *DOSBase;

#define TEMPLATE "HOST/A,IMM=IGNOREMOUSEMOVES/K/N,QUIET/S"
enum { ARG_HOST, ARG_IMM, ARG_QUIET, ARG_COUNT };

#define MAX_IMM 900000
#define MIN_IMM 0
#define DEF_IMM 0

   /*
   ** we maintain an InputEvent-FIFO of FIFO_COUNT elements
   ** this value MUST be power of 2!
   */
#define FIFO_COUNT 64

typedef struct GlobalData
{
   struct Library   *gd_SysBase,
                    *gd_UtilityBase,
                    *gd_DOSBase;
   struct MsgPort   *gd_IPort;
   struct IOStdReq  *gd_IReq;
   struct Interrupt  gd_InputHandler;
   UBYTE             gd_ProgramName[100];
   UBYTE             gd_HostName[64];
   ULONG             gd_Flags;
   int               gd_Socket;
   CountSemaphore    gd_Full,
                     gd_Empty;
   struct InputEvent gd_Event[FIFO_COUNT],
                     gd_Intermediate;
   UWORD             gd_Write, gd_Read;
   struct Task      *gd_We;
   LONG              gd_IgnoreMouseMoves;
   int               gd_Quiet : 1;
} *GD;

#define FOGF_HANDLER_INSTALLED   (1<<0)

#ifdef __SASC
     /*
     ** redirect all shared library bases to our device base.
     */
#  define SysBase      gd->gd_SysBase
#  define DOSBase      gd->gd_DOSBase
#  define UtilityBase  gd->gd_UtilityBase
     /*
     ** This macro declares a local variable which temporary gets
     ** SysBase directly from AbsExecBase.
     */
#  define LOCALSYSBASE struct { void *gd_SysBase; } *gd = (void*)0x4
#else
#  error Please define library bases for your compiler
#endif


/*E*/

/*F*/ /* revision */
#define VERSION 37
#define REVISION 1
#define DATE "22.07.95"
#define VERS "netinputd 37.1"
#define VSTRING "netinputd 37.1 (22.07.95)"
#define VERSTAG "\0$VER: netinputd 37.1 (22.07.95)"
const STATIC UBYTE version[]=VERSTAG;
/*E*/

/*F*/ /* private symbols */
static int init_client(char *hostname, char *servername, char *protocolname, char *yourname);
static BOOL parseargs(GD gd);
static VOID freeall(GD gd);
static BOOL openall(GD gd);
static GD creategd(VOID);
static BOOL request(GD gd, ULONG type, ULONG flags);
static ASM SAVEDS struct InputEvent *inputhandler(REG(a0) struct InputEvent *event, REG(a1) GD gd);
static VOID removehandler(GD gd);
static BOOL installhandler(GD gd);
/*E*/
/*F*/ /* exported symbols */
extern VOID main(VOID);
/*E*/

/*F*/ static int init_client(char *hostname, char *servername, char *protocolname, char *yourname)
{
   int sock = -1;
   struct servent *sp;
   struct hostent *hp;
   struct sockaddr_in server;

   sp = getservbyname(servername, protocolname);
   if (sp != NULL)
   {
      hp = gethostbyname(hostname);
      if (hp != NULL)
      {
         bzero((char *)&server, sizeof(server));
         server.sin_port = sp->s_port;
         bcopy(hp->h_addr, (char *)&server.sin_addr, hp->h_length);
         server.sin_family = hp->h_addrtype;

         sock = socket(AF_INET, (LONG)(strcmp(protocolname,"tcp") ? SOCK_DGRAM : SOCK_STREAM), 0);
         if (sock >= 0)
         {
            if (connect((LONG)sock, (struct sockaddr *)&server, sizeof (server)) < 0)
            {
               close(sock);
               sock = -1;
            }
         }
      }
   }

   return sock;
}
/*E*/

/*F*/ static BOOL parseargs(GD gd)
{
   struct RDArgs *rda;
   BOOL rc = FALSE;
   LONG param[ARG_COUNT];

   memset(param, 0, sizeof(param));

   if (rda = ReadArgs(TEMPLATE, param, NULL))
   {
      strncpy(gd->gd_HostName, (char*)param[ARG_HOST], sizeof(gd->gd_HostName)-1);

      gd->gd_IgnoreMouseMoves = DEF_IMM;
      if (param[ARG_IMM])
      {
         gd->gd_IgnoreMouseMoves = *((LONG*)param[ARG_IMM]);
         if (gd->gd_IgnoreMouseMoves > MAX_IMM) gd->gd_IgnoreMouseMoves = MAX_IMM;
         else if (gd->gd_IgnoreMouseMoves < MIN_IMM) gd->gd_IgnoreMouseMoves = MIN_IMM;
      }

      d(("hostname %s, imm %ld\n",gd->gd_HostName, gd->gd_IgnoreMouseMoves));

      gd->gd_Quiet = (param[ARG_QUIET] != 0);

      rc = TRUE;

      FreeArgs(rda);
   }
   else PrintFault(IoErr(), gd->gd_ProgramName);

   return(rc);
}
/*E*/

/*F*/ static VOID removehandler(GD gd)
{
   if (gd->gd_Flags & FOGF_HANDLER_INSTALLED)
   {
      gd->gd_IReq->io_Command = IND_REMHANDLER;
      gd->gd_IReq->io_Data = (APTR)&gd->gd_InputHandler;
      gd->gd_IReq->io_Length = sizeof(struct Interrupt);
      if (!DoIO((struct IORequest*)gd->gd_IReq))
         gd->gd_Flags &= ~FOGF_HANDLER_INSTALLED;
   }
}
/*E*/
/*F*/ static BOOL installhandler(GD gd)
{
   BOOL rv = FALSE;

   gd->gd_InputHandler.is_Code = (void (*)())&inputhandler;
   gd->gd_InputHandler.is_Data = (APTR)gd;
   gd->gd_InputHandler.is_Node.ln_Pri = 127;
   gd->gd_InputHandler.is_Node.ln_Type = NT_UNKNOWN;
   gd->gd_InputHandler.is_Node.ln_Name = "« netinput »";
   gd->gd_IReq->io_Command = IND_ADDHANDLER;
   gd->gd_IReq->io_Data = (APTR)&gd->gd_InputHandler;
   gd->gd_IReq->io_Length = sizeof(struct Interrupt);
   if (!DoIO((struct IORequest*)gd->gd_IReq))
   {
      gd->gd_Flags |= FOGF_HANDLER_INSTALLED;
      rv = TRUE;
   }

   return rv;
}
/*E*/

/*F*/ static VOID freeall(GD gd)
{
   if (gd->gd_IReq)
   {
      if (gd->gd_IReq->io_Device) CloseDevice((struct IORequest*)gd->gd_IReq);
      DeleteIORequest(gd->gd_IReq);
   }
   if (gd->gd_IPort) DeleteMsgPort(gd->gd_IPort);
   if (DOSBase) CloseLibrary(DOSBase);
   if (UtilityBase) CloseLibrary(UtilityBase);
   FreeVec(gd);
}
/*E*/
/*F*/ static BOOL openall(GD gd)
{
   BOOL rv = FALSE;

   sinit(&gd->gd_Full,-1);
   sinit(&gd->gd_Empty,-1);

   sset(&gd->gd_Full, 0);
   sset(&gd->gd_Empty, FIFO_COUNT);

   gd->gd_Read = 0;
   gd->gd_Write = 0;

   gd->gd_We = FindTask(NULL);

   d(("opening dos.library\n"))
   if (DOSBase = OpenLibrary("dos.library", 37))
   {
      d(("opening utility.library\n"))
      if (UtilityBase = OpenLibrary("utility.library", 37))
      {
         d(("creating msg port\n"))
         if (gd->gd_IPort = CreateMsgPort())
         {
            d(("creating io request\n"))
            if (gd->gd_IReq = CreateIORequest(gd->gd_IPort, sizeof(struct IOStdReq)))
            {
               d(("opening input.device\n"))
               if (!OpenDevice("input.device",0,(struct IORequest*)gd->gd_IReq, 0))
               {
                  rv = TRUE;
               }
            }
         }
      }
   }

   return rv;
}
/*E*/
/*F*/ static GD creategd(VOID)
{
   GD new;
   LOCALSYSBASE;

   new = AllocVec(sizeof(struct GlobalData), MEMF_ANY | MEMF_CLEAR);
   new->gd_SysBase = SysBase;

   return new;
}
/*E*/

/*F*/ static VOID INLINE forwardevent(GD gd, struct InputEvent *event)
{
   swait(&gd->gd_Empty);
   gd->gd_Event[gd->gd_Write] = *event;
   ssignal(&gd->gd_Full);

   gd->gd_Write = (gd->gd_Write+1) & (FIFO_COUNT-1);
}
/*E*/
/*F*/ static ASM SAVEDS struct InputEvent *inputhandler(REG(a0) struct InputEvent *event, REG(a1) GD gd)
{
   switch(event->ie_Class)
   {
      case IECLASS_RAWMOUSE:
         if ((event->ie_Code & 0xFF) != IECODE_NOBUTTON)
         {
            if (gd->gd_Intermediate.ie_Class != IECLASS_NULL)
            {
               d(("writing event 3\n"));
               forwardevent(gd, &gd->gd_Intermediate);
               gd->gd_Intermediate.ie_Class = IECLASS_NULL;
            }
            forwardevent(gd, event);
         }
         else if (gd->gd_IgnoreMouseMoves)
         {
            if (gd->gd_Intermediate.ie_Class != IECLASS_NULL)
            {
               d (("%ld/%ld - %ld/%ld\n",event->ie_TimeStamp.tv_secs,
                                         event->ie_TimeStamp.tv_micro,
                                         gd->gd_Intermediate.ie_TimeStamp.tv_secs,
                                         gd->gd_Intermediate.ie_TimeStamp.tv_micro));

               if ((event->ie_TimeStamp.tv_secs == gd->gd_Intermediate.ie_TimeStamp.tv_secs) &&
                   ((event->ie_TimeStamp.tv_micro - gd->gd_Intermediate.ie_TimeStamp.tv_micro) <= gd->gd_IgnoreMouseMoves))
               {
                  d(("storing event 1\n"));
                  gd->gd_Intermediate = *event;
               }
               else
               {
                  d(("writing event 1\n"));
                  gd->gd_Intermediate.ie_Class = IECLASS_NULL;
                  forwardevent(gd, event);
               }
            }
            else
            {
               d(("storing event 2\n"));
               gd->gd_Intermediate = *event;
            }
         }
         else
         {
            d(("writing event 2\n"));
            forwardevent(gd, event);
         }
         event = NULL;
      break;

      case IECLASS_RAWKEY:
         if (gd->gd_Intermediate.ie_Class != IECLASS_NULL)
         {
            forwardevent(gd, &gd->gd_Intermediate);
            gd->gd_Intermediate.ie_Class = IECLASS_NULL;
         }
         if (((event->ie_Code & ~IECODE_UP_PREFIX) == 0x45) && (event->ie_Qualifier & IEQUALIFIER_CONTROL))
         {
            if (event->ie_Code & IECODE_UP_PREFIX)
               Signal(gd->gd_We, SIGBREAKF_CTRL_C);

            event = NULL;
            break;
         }
         forwardevent(gd,event);
         event = NULL;
      break;

      default:
         if (gd->gd_Intermediate.ie_Class != IECLASS_NULL)
         {
            forwardevent(gd, &gd->gd_Intermediate);
            gd->gd_Intermediate.ie_Class = IECLASS_NULL;
         }
      break;
   }
   return event;
}
/*E*/

/*F*/ static BOOL request(GD gd, ULONG type, ULONG flags)
{
   int l;
   struct foPacket foc;

   foc.Type = type;
   foc.Flags = flags;

   l = send((LONG)gd->gd_Socket, (UBYTE*)&foc, sizeof(foc), 0);

   return (BOOL)(l == sizeof(foc));
}
/*E*/

/*F*/ extern VOID main(VOID)
{
   int rc = RETURN_FAIL;
   GD gd;

   if (gd = creategd())
   {
      d(("gd at %lx\n",gd))

      if (openall(gd))
      {
         GetProgramName(gd->gd_ProgramName, sizeof(gd->gd_ProgramName));

         if (parseargs(gd))
         {
            gd->gd_Socket = init_client(gd->gd_HostName, FO_SERVERNAME, FO_PROTOCOL, gd->gd_ProgramName);
            if (gd->gd_Socket >= 0)
            {
               if (installhandler(gd))
               {
                  if (!gd->gd_Quiet)
                     Printf( "Transparent mode on.\nEscape key is <Ctrl-Escape>.\n");

                  while(!(SetSignal(0,0) & SIGBREAKF_CTRL_C))
                  {
                     swait(&gd->gd_Full);
                     if (request(gd, FOPT_Event, 0))
                     {
                        send((LONG)gd->gd_Socket, (UBYTE*)&gd->gd_Event[gd->gd_Read], sizeof(struct InputEvent), 0);
                     }
                     ssignal(&gd->gd_Empty);

                     gd->gd_Read = (gd->gd_Read+1) & (FIFO_COUNT-1);

                  }
                  if (!gd->gd_Quiet)
                     Printf( "Transparent mode left.\n");

                  removehandler(gd);
                  rc = TRUE;
               }

               if (!request(gd, FOPT_Finish, 0))
                  Printf( "%s: could not finish connection\n", gd->gd_ProgramName);
               else
                  rc = RETURN_OK;
            }
            shutdown(gd->gd_Socket,2);
            close(gd->gd_Socket);
         }
      }

      freeall(gd);
   }

   exit(rc);
}
/*E*/

