/********************************************************************
 *                                                                  *
 * mw-cursor-keys.c: a program to generate fake cursor key presses  *
 *                   when the mouse wheel is rotated                *
 *                                                                  *
 * Author: Dave Harris <dah31@cam.ac.uk>                            *
 *                                                                  *
 * Date:   $Date: 1998/05/21 16:03:22 $                             *
 *                                                                  *
 ********************************************************************/

#define VERSION "1.2beta"

#include <exec/types.h>
#include <exec/ports.h>
#include <exec/nodes.h>
#include <devices/input.h>
#include <devices/inputevent.h>
#include <devices/keyboard.h>
#include <libraries/dos.h>
#include <dos/dostags.h>
#include <proto/exec.h>
#include <proto/dos.h>

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

#include "include/mousewheel.h"

#if 0
#define SAY(x) \
{if(_Backstdout) {Write(_Backstdout, (x), strlen(x)); Close(_Backstdout);}}
#else
#define SAY(x) {printf(x);}
#endif

static const char *mousewheel_version = MOUSEWHEEL_VERSION;

/*** prototypes ***/
#ifndef __SASC
int stcd_i(char *s, int *answer);
#endif  /* __SASC */
int _tinymain(char *);
int main(int argc, char **argv);
VOID send_key(UWORD);
void get_wheely_bits(void);

/*** globals ***/
struct MsgPort *our_port = NULL;
struct MsgPort *server_port = NULL;
struct mousewheel_message quit_message;

/*** code ***/
VOID MemCleanup(VOID)
{
}
     
static struct dev {
  struct MsgPort   *port;
  struct IORequest *iob;
  BOOL             open;
} ser = {NULL, NULL, FALSE},
  in = {NULL, NULL, FALSE},
  time = {NULL, NULL, FALSE},
  key = {NULL, NULL, FALSE};
     
/* shorthand */
#define IN_IOB          ((struct IOStdReq *)(in.iob))
#define TIME_IOB        ((struct timerequest *)(time.iob))
#define KEY_IOB         ((struct IOStdReq *)(key.iob))
     
static struct InputEvent event = {
  NULL,                            /* NextEvent */
  IECLASS_RAWKEY,                  /* Class */
  NULL,                            /* SubClass */
  NULL,                            /* Code, filled in later */
  NULL,                            /* Qualifier, filled in later */
  /*       { NULL, NULL },         */      /* Position, filled in later */
  {{ NULL, NULL }},                        /* Position, filled in later */
  { 0L, 0L }                       /* TimeStamp */ 
};
     
/* Matrix for reading key states. All we care about are the 
 * shift/ctrl/alt/amiga keys, which are conveniently qrouped in
 * keymatrix[12] in the same order as required for the qualifier
 *
 * The following number, which is the ONLY read length that works for
 * KBD_READMATRIX, was documented NOWHERE! I had to find it by trial and error.
 */
#define KEY_MATRIX_READ_LENGTH  13
     
static UBYTE keymatrix[16];
     
/*********************************************************************
 * close_dev(): self-contained device closing routine                *
 *********************************************************************/
void close_dev(struct dev *dev)
{
  if(dev->open) {
    AbortIO(dev->iob);
    CloseDevice(dev->iob);
    dev->open = FALSE;
  }
  if(dev->iob) {
    DeleteExtIO(dev->iob);
    dev->iob = NULL;
  }
  if(dev->port) {
    DeletePort(dev->port);
    dev->port = NULL;
  }
}

/*********************************************************************
 * die(): clean everything up and exit                               *
 *********************************************************************/
void die(void)
{
  struct mousewheel_message *reply;

  /* tell the server we're quitting */
  if (server_port != NULL)
  {
    quit_message.type = MOUSEWHEEL_CLIENT_QUITTING;
    quit_message.content.task = FindTask(NULL);
    quit_message.message.mn_Length = sizeof(struct mousewheel_message);
    PutMsg(server_port, (struct Message *)&quit_message);
    WaitPort(our_port);
    reply = (struct mousewheel_message *)GetMsg(our_port);
    D(printf("client: reply received (type %d)\n", reply->type));
  }
  if (our_port != NULL) DeletePort(our_port);

  close_dev(&time);
  close_dev(&in);
  close_dev(&key);
  _exit(0);
}

/*********************************************************************
 * open_dev(): self-contained device opening routine                 *
 *********************************************************************/
void open_dev(struct dev *dev, char *portname,
	      char *devname, int unit,
	      int size)
{ 
  if(!(dev->port = CreatePort(portname, 0)) ||
     !(dev->iob = CreateExtIO(dev->port, size)) ||
     !(dev->open = !OpenDevice(devname, unit, dev->iob, 0L))) {
    SAY("Device error on ");
    SAY(devname);
    SAY("\n");
    die();
  }
}

/*********************************************************************
 * my_DoIO does everything that DoIO does (I hope) except it allows  *
 * for a break signal.                                               *
 *********************************************************************/
void my_DoIO(struct IORequest *iob)
{
  register LONGBITS signals, sigbit;
  
  /* Lattice/SAS doesn't seem to provide a register-parameter entry to BeginIO,
   * so here's a hack that avoids an annoying 3 line assembly language program.
   * Just don't try to call BeginIO on anything but iob->io_Device.
   */
#pragma libcall iob->io_Device BeginIO 1e 901
  
  iob->io_Flags |= IOF_QUICK;
  BeginIO(iob);
  if(!(iob->io_Flags & IOF_QUICK)) {
    sigbit = 1L << iob->io_Message.mn_ReplyPort->mp_SigBit;
    signals = Wait(sigbit | SIGBREAKF_CTRL_C);
    
    if(signals & sigbit)
      GetMsg(iob->io_Message.mn_ReplyPort);
    
    if(signals & SIGBREAKF_CTRL_C)
      die();
  }
}

/********************************************************************
 * send_key(): send InputEvent of class IECLASS_RAWKEY              *
 ********************************************************************/
void send_key(UWORD code)
{
  /* We want to hang on to the qualifiers */
  my_DoIO(key.iob);
  
  /* straightforward parts of qualifier: ctrl/alt/shift keys */
  event.ie_Qualifier = keymatrix[12];

  /* Might as well get a valid time stamp---someone's bound to be
   * relying on it
   */
  my_DoIO(time.iob);
  event.ie_TimeStamp = TIME_IOB->tr_time;

  /* this is preset above */
  /*  event.ie_Class = IECLASS_RAWKEY; */
  event.ie_Code = code;

  my_DoIO(in.iob);
}

/********************************************************************
 * main()                                                           *
 ********************************************************************/
int main(int argc, char **argv)
{
  our_port = CreatePort(0, 0);
  if (our_port == NULL) {
    printf("Couldn't create port!\n");
    exit(1);
  }

  D(bug("Looking for server port..."));
  server_port = FindPort(MOUSEWHEEL_PORTNAME);
  if (server_port == NULL)
  {
    printf("Couldn't open mouse_port!\n");
    if (our_port != NULL) {
      DeletePort(our_port);
      exit(1);
    }
  }
  D(bug(" done.\nOpening devices..."));

  open_dev(&in, NULL, "input.device", 0L, sizeof(struct IOStdReq));
  open_dev(&time, NULL, TIMERNAME, UNIT_MICROHZ, sizeof(struct timerequest));
  open_dev(&key, NULL, "keyboard.device", 0L, sizeof(struct IOStdReq));
  D(bug(" done.\nSetting stuff up..."));
  
  D(bug("DEBUGLEVEL is at least 1\n"));
  D2(bug("DEBUGLEVEL is at least 2\n"));
  SAY("MouseWheel " MW_VERSION " \xA9 Copyright 1998 Dave Harris\n");
  SAY("Derived from OptMouse " VERSION " \xA9 Copyright 1989,1991 J. Edward Hanway\n");
  
  /* Since each IORequest will be used for only one type of command,
   * lots of invariant fields can be filled in here
   */
  KEY_IOB->io_Command = KBD_READMATRIX;
  KEY_IOB->io_Data = (APTR) keymatrix;
  KEY_IOB->io_Length = KEY_MATRIX_READ_LENGTH;
  
  TIME_IOB->tr_node.io_Command = TR_GETSYSTIME;
  
  IN_IOB->io_Command = IND_WRITEEVENT;
  IN_IOB->io_Flags = 0;
  IN_IOB->io_Length = sizeof(struct InputEvent);
  IN_IOB->io_Data = (APTR) &event;

  /* do stuff */
  get_wheely_bits();

  close_dev(&time);
  close_dev(&ser);
  close_dev(&key);
  if (our_port != NULL) DeletePort(our_port);
  return 0;
}  /* main() */

char *message_type_names[] =
{
  "message type 0",
  "MOUSEWHEEL_NEW_CLIENT",
  "MOUSEWHEEL_SIGNALS",
  "MOUSEWHEEL_FUNCTION",
  "MOUSEWHEEL_EVENT",
  "MOUSEWHEEL_CLIENT_QUITTING",
  "MOUSEWHEEL_DRIVER_QUITTING",
  "MOUSEWHEEL_CHILD_TASK_QUITTING"
};
