/*
** $VER: doubler.c 37.1 (17 Aug 1995)
**
** DoubleR - Simple mouse-input enhancement for ShapeShifter
**
** (C) Copyright 1995 Marius Gröger
**     All Rights Reserved
*/

/*F*/ /* AutoDoc */
/*
AUTODOC DoubleR
**
** (C) Copyright 1995 Marius Gröger
**     All Rights Reserved
**
** NAME
**    DoubleR - simple mouse-handling enhancement for ShapeShifter
**
** REQUIREMENTS
**    * an Amiga Computer
**    * the ShapeShifter Apple Macintosh emulator to be installed
**      ShapeShifter is (C) Copyright 1995 Christian Bauer
**    * ShapeShifter must be cuonfigured that it doesn't read the
**      mouse directly
**
** DESCRIPTION
**    The function of DoubleR is quite simple: it hooks into the Amiga
**    operating system's event handling and watches out for any click with
**    the right mouse-button which occures under the context of a certain
**    task, normally the "ShapeShifter Window Int". After having detected
**    the right button single-click DoubleR generates a double click with
**    the left mouse butten.
**
**    For ShapeShifter, this is fully transparent. The complete code has
**    been implemented extremely system conform.
**
** INSTALLATION
**    The installation of DoubleR is quite simple. Just drag the icon
**    where you want it to have.
**
** STARTING
**    You may start DoubleR from either Shell or Workbench. The following
**    AmigaDOS command template is applied:
**
**       TASK=TASKNAME/A,S=SPEED/K/N,DEBUG/S,QUIET/S
**
**    TASKNAME
**          DoubleR must make a distiction whether an input event came from
**          ShapeShifter or any other task. As each mouse event is bound to
**          a specific intuition window, DoubleR determines the name of the
**          owner task of this window. This parameter lets you define the
**          name by which the above retrieved name is compared. Comparison
**          is case-sensitive. For release 3 of ShapeShifter you should set
**          this parameter to "ShapeShifter Window Int".
**
**    SPEED The delay time between the two clicks which will be generated
**          as a double click.
**          Measured in DOSTICKS (1 tick is 1/50 sec)
**          Defaults to 15
**
**    DEBUG If this option is set, DoubleR will print all right button
**          clicks it gets along with the tasknames to the console. This is
**          useful to determine the correct TASKNAME argument.
**
**    QUIET When set, DoubleR doesn't create any output.
**
**    For Workbench usage, all keywords are equivalent tooltypes. The icon
**    of DoubleR as coming shipped is already configured correctly to
**    use it with the current ShapeShifter release 3.
**
** SOURCE
**   The source code of DoubleR has been written with the GoldED
**   text editor (© Dietmar Eilert). Set the fold marks accordingly.
**
**   The documentation is to be extracted with makedoc. makedoc is
**   © Stefan Ruppert.
**
**   The extrdargs system is ® Stefan Ruppert, the complete package and
**   source code is available on Aminet.
**
** $HISTORY:
**
** 17 Aug 1995 : 037.001 :  initial
**
ENDDOC
*/
/*E*/

#define DEBUG 0

/*F*/ /* includes */
#include <clib/dos_protos.h>
#include <pragmas/dos_pragmas.h>
#include <clib/exec_protos.h>
#include <pragmas/exec_sysbase_pragmas.h>

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

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

#include <intuition/intuitionbase.h>
#include <intuition/intuition.h>

#include <string.h>

#include "extrdargs.h"

#include "compiler.h"
#include "debug.h"

#include "DoubleR_rev.h"

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

#define TEMPLATE "TASK=TASKNAME/A,S=SPEED/K/N,DEBUG/S,QUIET/S"
enum { ARG_TASK, ARG_SPEED, ARG_DEBUG, ARG_QUIET, ARG_COUNT };

#define DEF_SPEED 15

typedef struct GlobalData
{
   struct Library          *gd_SysBase,
                           *gd_UtilityBase,
                           *gd_IconBase,
                           *gd_DOSBase;
   struct IntuitionBase    *gd_IntuitionBase;
   ULONG                    gd_argc;
   UBYTE                  **gd_argv;
   struct Process          *gd_We;
   struct MsgPort          *gd_IPort;
   struct IOStdReq         *gd_IReq;
   struct Interrupt         gd_InputHandler;
   UBYTE                    gd_Taskname[100];
   LONG                     gd_Speed;
   UBYTE                    gd_ProgramName[100];
   UBYTE                   *gd_Debugname;
   struct InputEvent        gd_Event;
   int                      gd_Quiet : 1;
   int                      gd_HandlerInstalled : 1;
   int                      gd_FromWB : 1;
   int                      gd_Debug : 1;
} *GD;

const char AppWindow[] = "CON:30/30/600/50/DoubleR/AUTO";

const char VersionId[] = VERSTAG;

#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
#  define IntuitionBase gd->gd_IntuitionBase
#  define IconBase      gd->gd_IconBase
     /*
     ** 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*/ /* private symbols */
static BOOL parseargs(GD gd);
static VOID freeall(GD gd);
static BOOL openall(GD gd);
static GD creategd(VOID);
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);
static VOID writemouseevent(GD gd, UWORD code, UWORD qualifier, WORD x, WORD y);
static VOID doubleclick(GD gd);
/*E*/
/*F*/ /* exported symbols */
extern LONG SAVEDS STDARGS main(ULONG argc, UBYTE **argv);
/*E*/

  /*
  ** entry point
  */
/*F*/ extern LONG SAVEDS STDARGS main(ULONG argc, UBYTE **argv)
{
   int rc = RETURN_FAIL;
   GD gd;
   ULONG sigrec;

   if (gd = creategd())
   {
      d(("gd at %lx\n",gd));
      gd->gd_argc = argc;
      gd->gd_argv = argv;

      if (openall(gd))
      {
         if (gd->gd_FromWB)
            strcpy(gd->gd_ProgramName, "DoubleR");
         else
            GetProgramName(gd->gd_ProgramName, sizeof(gd->gd_ProgramName));

         if (parseargs(gd))
         {
            if (installhandler(gd))
            {
               if (!gd->gd_Quiet)
               {
                  Printf("%s: DoubleR working, tracking task \"%s\".\n",gd->gd_ProgramName, gd->gd_Taskname);
                  Printf("All right-button clicks are mapped to left-button double clicks.\n");
                  if (gd->gd_Debug) Printf("Debug mode is on.\n");
                  Printf("Press Control-C to exit.\n");
               }

               for(;;)
               {
                  sigrec = Wait(SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_F | SIGBREAKF_CTRL_D);

                  d(("waked up\n"));

                  if (sigrec & SIGBREAKF_CTRL_C)
                  {
                     d(("received break signal\n"));
                     break;
                  }

                  if (sigrec & SIGBREAKF_CTRL_F)
                  {
                     d(("received signal for double-click\n"));
                     doubleclick(gd);
                  }

                  if ((sigrec & SIGBREAKF_CTRL_D) && gd->gd_Debugname)
                  {
                     d(("received debug signal\n"));
                     Forbid();
                     if (!gd->gd_Quiet)
                        Printf("%s: TASKNAME=\"%s\"\n",
                           gd->gd_ProgramName, gd->gd_Debugname);
                     gd->gd_Debugname = NULL;
                     Permit();
                  }
               }

               if (!gd->gd_Quiet)
                  Printf("%s: Exiting.\n", gd->gd_ProgramName);

               removehandler(gd);
               rc = RETURN_OK;
            }
         }
      }

      freeall(gd);
   }

   return rc;
}
/*E*/

/*F*/ static BOOL parseargs(GD gd)
{
   struct ExtRDArgs eargs = {NULL};
   BOOL rc = FALSE;
   LONG param[ARG_COUNT];

   memset(param, 0, sizeof(param));
   eargs.erda_Template      = TEMPLATE;
   eargs.erda_Parameter     = param;
   eargs.erda_FileParameter = 0;
   eargs.erda_Window        = "CON:////DoubleR/WAIT/CLOSE/AUTO";
   eargs.erda_SysBase       = SysBase;
   eargs.erda_DOSBase       = DOSBase;
   eargs.erda_IconBase      = IconBase;

   d(("calling ExtReadArgs()\n"));
   if (ExtReadArgs(gd->gd_argc, (STRPTR *)gd->gd_argv, &eargs) == 0)
   {
      d(("ok\n"));

      strcpy(gd->gd_Taskname, (char*)param[ARG_TASK]);

      if (param[ARG_SPEED])
         gd->gd_Speed = *((LONG*)param[ARG_SPEED]);
      else
         gd->gd_Speed = DEF_SPEED;

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

      gd->gd_Debug = (param[ARG_DEBUG] != 0);

      rc = TRUE;
   }
   else
   {
      PrintFault(IoErr(), gd->gd_ProgramName);
   }
   ExtFreeArgs(&eargs);

   return(rc);
}
/*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 (IconBase) CloseLibrary(IconBase);
   if (DOSBase) CloseLibrary(DOSBase);
   if (IntuitionBase) CloseLibrary((struct Library *)IntuitionBase);
   if (UtilityBase) CloseLibrary(UtilityBase);
   FreeVec(gd);
}
/*E*/
/*F*/ static BOOL openall(GD gd)
{
   BOOL rv = FALSE;

   if (gd->gd_argc == 0)
   {
      gd->gd_FromWB = 1;
      d(("opening utility.library\n"));
      IconBase = OpenLibrary("icon.library", 37);
   }

   if (!gd->gd_FromWB || (IconBase != NULL))
   {
      d(("opening dos.library\n"));
      if (DOSBase = OpenLibrary("dos.library", 37))
      {
         d(("opening intuition.library\n"));
         if (IntuitionBase = (void*)OpenLibrary("intuition.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;

   if (new = AllocVec(sizeof(struct GlobalData), MEMF_ANY | MEMF_CLEAR))
   {
      new->gd_SysBase = SysBase;
      new->gd_We = (struct Process *)FindTask(NULL);
   }

   return new;
}
/*E*/

/*F*/ static VOID removehandler(GD gd)
{
   if (gd->gd_HandlerInstalled)
   {
      gd->gd_IReq->io_Command = IND_REMHANDLER;
      gd->gd_IReq->io_Data = (APTR)&gd->gd_InputHandler;
      gd->gd_IReq->io_Length = sizeof(struct Interrupt);
      gd->gd_IReq->io_Flags = IOF_QUICK;
      if (!DoIO((struct IORequest*)gd->gd_IReq))
         gd->gd_HandlerInstalled = 0;
   }
}
/*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 = "« DoubleR »";
   gd->gd_IReq->io_Command = IND_ADDHANDLER;
   gd->gd_IReq->io_Data = (APTR)&gd->gd_InputHandler;
   gd->gd_IReq->io_Length = sizeof(struct Interrupt);
   gd->gd_IReq->io_Flags = IOF_QUICK;
   if (!DoIO((struct IORequest*)gd->gd_IReq))
   {
      gd->gd_HandlerInstalled = 1;
      rv = TRUE;
   }

   return rv;
}
/*E*/
/*F*/ static ASM SAVEDS struct InputEvent *inputhandler(REG(a0) struct InputEvent *event, REG(a1) GD gd)
{
   struct Window *window;

   if ((event->ie_Class == IECLASS_RAWMOUSE) &&
       (event->ie_Code == (IECODE_UP_PREFIX|IECODE_RBUTTON)) &&
       (window = IntuitionBase->ActiveWindow))
   {
      if (gd->gd_Debug)
      {
         d(("debug\n"));
         gd->gd_Debugname = (UBYTE*)(((struct Task*)window->UserPort->mp_SigTask)->tc_Node.ln_Name);
         Signal((struct Task *)gd->gd_We, SIGBREAKF_CTRL_D);
      }

      if (!strcmp(gd->gd_Taskname, ((struct Task*)window->UserPort->mp_SigTask)->tc_Node.ln_Name))
      {
         d(("right mouse button pressed\n"));
         gd->gd_Event.ie_X = event->ie_X;
         gd->gd_Event.ie_Y = event->ie_Y;
         gd->gd_Event.ie_Qualifier = event->ie_Qualifier;
         Signal((struct Task *)gd->gd_We, SIGBREAKF_CTRL_F);
      }
   }
   return event;
}
/*E*/
/*F*/static VOID writemouseevent(GD gd, UWORD code, UWORD qualifier, WORD x, WORD y)
{
   /* setup event structure */
   gd->gd_Event.ie_NextEvent = NULL;
   gd->gd_Event.ie_Class = IECLASS_RAWMOUSE;
   gd->gd_Event.ie_SubClass = IESUBCLASS_COMPATIBLE;
   gd->gd_Event.ie_Code = code;
   gd->gd_Event.ie_Qualifier = qualifier;
   gd->gd_Event.ie_X = x;
   gd->gd_Event.ie_Y = y;

   /* setup io request structure */
   gd->gd_IReq->io_Command = IND_WRITEEVENT;
   gd->gd_IReq->io_Flags = IOF_QUICK;
   gd->gd_IReq->io_Data = (APTR)&gd->gd_Event;
   gd->gd_IReq->io_Length = sizeof(struct InputEvent);

   /* initiate request */
   DoIO((struct IORequest*)gd->gd_IReq);
}
/*E*/
/*F*/static VOID doubleclick(GD gd)
{
   UWORD qualifier;
   WORD x, y;

   /* get values from input-handler under protection */
   Forbid();
   qualifier = gd->gd_Event.ie_Qualifier;
   x = gd->gd_Event.ie_X;
   y = gd->gd_Event.ie_Y;
   Permit();

   /* the first click */
   writemouseevent(gd, IECODE_LBUTTON, qualifier, x, y);
   writemouseevent(gd, IECODE_LBUTTON | IECODE_UP_PREFIX, qualifier, x, y);
   /* wait some time */
   Delay(gd->gd_Speed);
   /* and the first click */
   writemouseevent(gd, IECODE_LBUTTON, qualifier, x, y);
   writemouseevent(gd, IECODE_LBUTTON | IECODE_UP_PREFIX, qualifier, x, y);
}
/*E*/

