/*
 * mev.c - simple client to print mouse events (gpm-Linux)
 *
 * Copyright 1994,1995   rubini@linux.it (Alessandro Rubini)
 * Copyright (C) 1998 Ian Zimmerman <itz@rahul.net>
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
 ********/

/*
 * This client is meant to be used both interactively to check
 * that gpm is working, and as a background process to convert gpm events
 * to textual strings. I'm using it to handle Linux mouse
 * events to emacs
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>
#include <termios.h>
#include <errno.h>

#include <linux/keyboard.h> /* to use KG_SHIFT and so on */

#define ALL_KEY_MODS ((1<<KG_SHIFT)|(1<<KG_ALT)|(1<<KG_ALTGR)|(1<<KG_CTRL))

#include "message.h"  /* messages */
#include "gpm.h"

#define GPM_NAME "gpm"
#define GPM_DATE "X-Mas 2002"

char *prgname;
struct node {char *name; int flag;};
struct node  tableEv[]= {
   {"move",    GPM_MOVE},
   {"drag",    GPM_DRAG},
   {"down",    GPM_DOWN},
   {"up",      GPM_UP},
   {"press",   GPM_DOWN},
   {"release", GPM_UP},
   {"motion",  GPM_MOVE | GPM_DRAG},
   {"hard",    GPM_HARD},
   {"any",     ~0},
   {"all",     ~0},
   {"none",     0},
   {NULL,0}
};
struct node  tableMod[]= {
   {"shift",    1<<KG_SHIFT},
   {"anyAlt",   1<<KG_ALT | 1<<KG_ALTGR},
   {"leftAlt",  1<<KG_ALT},
   {"rightAlt", 1<<KG_ALTGR},
   {"control",  1<<KG_CTRL},
   {"any",     ~0},
   {"all",     ~0},
   {"none",     0},
   {NULL,0}
};


/* provide defaults */
int opt_mask    = ~0;           /* get everything */
int opt_default = ~GPM_HARD;    /* pass everithing unused */
int opt_minMod  =  0;           /* run always */
int opt_maxMod  = ~0;           /* with any modifier */
int opt_intrct  =  0;
int opt_vc      =  0;      /* by default get the current vc */
int opt_emacs   =  0;
int opt_fit     =  0;
int opt_pointer =  0;

/*===================================================================*/
int user_handler(Gpm_Event *event, void *data)
{
   data = NULL; /* FIXME: unused */

   if (opt_fit) Gpm_FitEvent(event);

   printf("mouse: event 0x%02X, at %2i,%2i (delta %2i,%2i), "
          "buttons %i, modifiers 0x%02X\r\n",
	 event->type,
	 event->x, event->y,
	 event->dx, event->dy,
	 event->buttons, event->modifiers);

   if (event->type & (GPM_DRAG|GPM_DOWN)) {
      if (0 != opt_pointer) {
         GPM_DRAWPOINTER(event);
      } /*if*/
   } /*if*/
   return 0;
}

/*-------------------------------------------------------------------*/
int emacs_handler(Gpm_Event *event, void *data)
{
   int i,j;
   static int dragX, dragY;
   static char buffer[64];

   data = NULL; /* FIXME: unused */

   /* itz Mon Mar 23 20:54:54 PST 1998 emacs likes the modifier bits in
   alphabetical order, so I'll use a lookup table instead of a loop; it
   is faster anyways */
   /* static char *s_mod[]={"S-","M-","C-","M-",NULL}; */
   static char *s_mod[] = {
      "",                         /* 000 */
      "S-",                       /* 001 */
      "M-",                       /* 002 */
      "M-S-",                     /* 003 */
      "C-",                       /* 004 */
      "C-S-",                     /* 005 */
      "C-M-",                     /* 006 */
      "C-M-S-",                   /* 007 */
      /* idea: maybe we should map AltGr to Emacs Alt instead of Meta? */
      "M-",                       /* 010 */
      "M-S-",                     /* 011 */
      "M-",                       /* 012 */
      "M-S-",                     /* 013 */
      "C-M-",                     /* 014 */
      "C-M-S-",                   /* 015 */
      "C-M-",                     /* 016 */
      "C-M-S-",                   /* 017 */
   };
   /* itz Mon Mar 23 08:23:14 PST 1998 what emacs calls a `drag' event
     is our `up' event with coordinates different from the `down'
     event.  What gpm calls `drag' is just `mouse-movement' to emacs. */

   static char *s_type[]={"mouse-movement", "mouse-movement","down-mouse-","mouse-",NULL};
   static char *s_button[]={"3","2","1",NULL};
   static char *s_multi[]={"double-", "triple-", 0};
   static char s_count[]="23";
   char count = '1';
   struct timeval tv_cur;
   long timestamp;
   static long dragTime;

   /* itz Mon Mar 23 08:27:53 PST 1998 this flag is needed because even
   if the final coordinates of a drag are identical to the initial
   ones, it is still a drag if there was any movement in between.  Sigh. */
   static int dragFlag = 0;

   gettimeofday(&tv_cur, 0);
   timestamp = ((short)tv_cur.tv_sec) * 1000 + (tv_cur.tv_usec / 1000);
   if (opt_fit) Gpm_FitEvent(event);
   buffer[0]=0;

   /* itz Sun Mar 22 19:09:04 PST 1998 Emacs doesn't understand
   modifiers on motion events. */
   if (!(event->type & (GPM_MOVE|GPM_DRAG))) {
      /* modifiers */
      strcpy(buffer, s_mod[event->modifiers & ALL_KEY_MODS]);
      /* multiple */
      for (i=0, j=GPM_DOUBLE; s_multi[i]; i++, j<<=1)
         if (event->type & j) {
            count = s_count[i];
            strcat(buffer,s_multi[i]);
         } /*if*/
   } /*if*/

   if (event->type & GPM_DRAG) {
      dragFlag = 1;
   } /*if*/

   /* itz Mon Mar 23 08:26:33 PST 1998 up-event after movement is a drag. */
   if ((event->type & GPM_UP) && dragFlag) {
      strcat(buffer, "drag-");
   } /*if*/

   /* type */
   for (i=0, j=GPM_MOVE; s_type[i]; i++, j<<=1)
      if (event->type & j)
         strcat(buffer,s_type[i]);

   /* itz Sun Mar 22 19:09:04 PST 1998 Emacs doesn't understand
   modifiers on motion events. */
   if (!(event->type & (GPM_MOVE|GPM_DRAG)))
      /* button */
      for (i=0, j=GPM_B_RIGHT; s_button[i]; i++, j<<=1)
         if (event->buttons & j)
            strcat(buffer,s_button[i]);

   if ((event->type & GPM_UP) && dragFlag) {
      printf("(%s ((%i . %i) %ld) %c ((%i . %i) %ld))\n",
           buffer,
           event->x, event->y, timestamp,
           count,
           dragX, dragY, dragTime);
   } else if (event->type & (GPM_DOWN|GPM_UP)) {
      printf("(%s ((%i . %i) %ld) %c)\n",
           buffer, event->x, event->y, timestamp, count);
   } else if (event->type & (GPM_MOVE|GPM_DRAG)) {
      printf("(%s ((%i . %i) %ld))\n",
           buffer, event->x, event->y, timestamp);
   } /*if*/

   if (event->type & GPM_DOWN) {
      dragX=event->x;
      dragY=event->y;
      dragTime=timestamp;
      dragFlag = 0;
   } /*if*/

   if (event->type & (GPM_DRAG|GPM_DOWN)) {
      if (0 == opt_pointer) {
         GPM_DRAWPOINTER(event);
      } /*if*/
   } /*if*/

   return 0;
}

/*===================================================================*/
int usage(void)
{
   //printf( "(" GPM_NAME ") " GPM_RELEASE ", " GPM_DATE "\n"
   printf( "(" GPM_NAME ") , " GPM_DATE "\n"
          "Usage: %s [options]\n",prgname);
   printf("  Valid options are\n"
         "    -C <number>   choose virtual console (beware of it)\n"
         "    -d <number>   choose the default mask\n"
         "    -e <number>   choose the eventMask\n"
         "    -E            emacs-mode\n"
         "    -i            accept commands from stdin\n"
         "    -f            fit drag events inside the screen\n"
         "    -m <number>   minimum modifier mask\n"
         "    -M <number>   maximum modifier mask\n"
         "    -p            show pointer while dragging\n"
         "    -u            user-mode (default)\n"
         );

   return 1;
}

/*===================================================================*/
#define PARSE_EVENTS 0
#define PARSE_MODIFIERS 1
void getmask(char *arg, int which, int* where)
{
   int last=0, value=0;
   char *cur;
   struct node *table, *n;
   int mode = 0;                 /* 0 = set, 1 = add, 2 = subtract */

   if ('+' == arg[0]) {
      mode = 1;
      ++arg;
   } else if ('-' == arg[0]) {
      mode = 2;
      ++arg;
   }

   if (isdigit(arg[0])) {
      switch(mode) {
      case 0: *where = atoi(arg); break;
      case 1: *where |= atoi(arg); break;
      case 2: *where &= ~atoi(arg); break;
      } /*switch*/
      return;
   } /*if*/

   table= (PARSE_MODIFIERS == which) ? tableMod : tableEv;
   while (1)
      {
         while (*arg && !isalnum(*arg)) arg++; /* skip delimiters */
         cur=arg;
         while(isalnum(*cur)) cur++; /* scan the word */
         if (!*cur) last++;
         *cur=0;

         for (n=table;n->name;n++)
            if (!strcmp(n->name,arg))
          {
            value |= n->flag;
            break;
          }
         if (!n->name) fprintf(stderr,"%s: Incorrect flag \"%s\"\n",prgname,arg);
         if (last) break;

         cur++; arg=cur;
      }

   switch(mode) {
   case 0: *where = value; break;
   case 1: *where |= value; break;
   case 2: *where &= ~value; break;
   } /*switch*/
}

/*===================================================================*/
int cmdline(int argc, char **argv, char *options)
{
   int opt;

   while ((opt = getopt(argc, argv, options)) != -1)
      {
         switch (opt)
            {
          /* itz Tue Mar 24 17:11:52 PST 1998 i hate options that do
             too much.  Made them orthogonal. */

            case 'C':  sscanf(optarg,"%x",&opt_vc); break;
            case 'd':  getmask(optarg, PARSE_EVENTS, &opt_default); break;
            case 'e':  getmask(optarg, PARSE_EVENTS, &opt_mask); break;
            case 'E':  opt_emacs = 1; break;
            case 'i':  opt_intrct=1; break;
            case 'f':  opt_fit=1; break;
            case 'm':  getmask(optarg, PARSE_MODIFIERS, &opt_minMod); break;
            case 'M':  getmask(optarg, PARSE_MODIFIERS, &opt_maxMod); break;
            case 'p':  opt_pointer =1; break;
            case 'u':  opt_emacs=0; break;
            default:   return 1;
            }
      }
   return 0;
}

void
do_snapshot()
{
   Gpm_Event event;
   int i=Gpm_GetSnapshot(&event);
   char *s;

   if (-1 == i) {
      fprintf(stderr,"Warning: cannot get snapshot!\n");
      fprintf(stderr,"Have you run \"configure\" and \"make install\"?\n");
      return;
   } /*if*/

   fprintf(stderr,"Mouse has %d buttons\n",i);
   fprintf(stderr,"Currently sits at (%d,%d)\n",event.x,event.y);
   fprintf(stderr,"The window is %d columns by %d rows\n",event.dx,event.dy);
   s=Gpm_GetLibVersion(&i);
   fprintf(stderr,"The library is version \"%s\" (%i)\n",s,i);
   s=Gpm_GetServerVersion(&i);
   fprintf(stderr,"The daemon is version \"%s\" (%i)\n",s,i);
   fprintf(stderr,"The current console is %d, with modifiers 0x%02x\n",
          event.vc,event.modifiers);
   fprintf(stderr,"The button mask is 0x%02X\n",event.buttons);
}

/*===================================================================*/
int interact(char *cmd) /* returns 0 on success and !=0 on error */
{
   Gpm_Connect conn;
   int argc=0;
   char *argv[20];

   if (*cmd && cmd[strlen(cmd)-1]=='\n')
      cmd[strlen(cmd)-1]='\0';
   if (!*cmd) return 0;

   /*
   * Interaction is accomplished by building an argv and passing it to
   * cmdline(), to use the same syntax used to invoke the program
   */

   while (argc<19)
      {
         while(isspace(*cmd)) cmd++;
         argv[argc++]=cmd;
         while (*cmd && isgraph(*cmd)) cmd++;
         if (!*cmd) break;
         *cmd=0;
         cmd++;
      }
   argv[argc]=NULL;

   if (!strcmp(argv[0],"pop")) {
      return (Gpm_Close()==0 ? 1 : 0); /* a different convention on ret values */
   } /*if*/

   if (!strcmp(argv[0],"info")) {
      fprintf(stderr,"The stack of connection info is %i depth\n",gpm_flag);
      return 0;
   } /*if*/

   if (!strcmp(argv[0],"quit")) {
      exit(0);
   } /*if*/

   if (!strcmp(argv[0],"snapshot")) {
      do_snapshot();
      return 0;
   } /*if*/

   optind=0; /* scan the entire line */
   if (strcmp(argv[0],"push") || cmdline(argc,argv,"d:e:m:M:")) {
      fprintf(stderr,"Syntax error in input line\n");
      return 0;
   } /*if*/

   conn.eventMask=opt_mask;
   conn.defaultMask=opt_default;
   conn.maxMod=opt_maxMod;
   conn.minMod=opt_minMod;

   if (Gpm_Open(&conn,opt_vc)==-1)
      {
         fprintf(stderr,"%s: Can't open mouse connection\r\n",argv[0]);
         return 1;
      }
   return 0;
}

/*===================================================================*/
int main(int argc, char **argv)
{
   Gpm_Connect conn;
   char cmd[128];
   Gpm_Handler* my_handler;       /* not the real gpm handler! */
   fd_set readset;

   prgname=argv[0];

   if (cmdline(argc,argv,"C:d:e:Efim:M:pu")) exit(usage());

   gpm_zerobased = opt_emacs;
   conn.eventMask=opt_mask;
   conn.defaultMask=opt_default;
   conn.maxMod=opt_maxMod;
   conn.minMod=opt_minMod;

   if (Gpm_Open(&conn,opt_vc) == -1) {
      gpm_report(GPM_PR_ERR,"%s: Can't open mouse connection\n",prgname);
      exit(1);
   } else if (gpm_fd == -2) {
      gpm_report(GPM_PR_OOPS,"%s: use rmev to see gpm events in xterm or rxvt\n",prgname);
   }

   gpm_report(GPM_PR_DEBUG,"STILL RUNNING_1");

   my_handler= opt_emacs ? emacs_handler : user_handler;

   /* itz Sun Mar 22 09:51:33 PST 1998 needed in case the output is a pipe */
   setvbuf(stdout, 0, _IOLBF, 0);
   setvbuf(stdin, 0, _IOLBF, 0);

   while(1) {                   /* forever */
      FD_ZERO(&readset);
      FD_SET(gpm_fd, &readset);
      if (opt_intrct) {
         FD_SET(STDIN_FILENO, &readset);
      }

      if (select(gpm_fd+1, &readset, 0, 0, 0) < 0 && errno == EINTR)
         continue;
      if (FD_ISSET(STDIN_FILENO, &readset)) {
         if (0 == fgets(cmd, sizeof(cmd), stdin) || interact(cmd)) {
            exit(0);                /* ^D typed on input */
         } /*if*/
      } /*if*/
      if (FD_ISSET(gpm_fd, &readset)) {
         Gpm_Event evt;
         if (Gpm_GetEvent(&evt) > 0) {
            my_handler(&evt, 0);
         } else {
            fprintf(stderr, "mev says : Oops, Gpm_GetEvent()\n");
         }
      } /*if*/

   } /*while*/

   /*....................................... Done */

   while (Gpm_Close()); /* close all the stack */

   exit(0);
}
