/* irxevent.c
 * Written by Heinrich Langos <heinrich@null.net> and heavily modified
 * by David Allen <s2mdalle@titan.vcu.edu>
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
/* GTKeyboard - A Graphical Keyboard For X
 * Copyright (C) 1999, 2000
 *
 * 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 Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA  02111-1307, USA.
 */

#define IRXEVENT_C
#include <errno.h>
#include <unistd.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/stat.h>
#include <sys/types.h>
#include "config.h"

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <sys/time.h>

#include "master.h"

#ifndef KMANIP_H
#  include "include/kmanip.h"
#endif /* KMANIP_H */

/* This is the ugly character -> keysym mapping.  For the most part, the
 * KeySym expressed as an int is the right character, but not always.
 *
 * Also, some compilers hate casting a char into a long, so we do some of
 * this.
 */
KeySym character_to_keysym(char i)
{
     KeySym x;
     char subbuf[15];
     
     if(isalpha(i))  /* Hopefully most common event */
     {
	  sprintf(subbuf,"%c",i);
	  x = XStringToKeysym(subbuf);
	  return(x);
     } /* End if */
     else if(isdigit(i))
     {
	  switch(i){
	  case '0': return(XK_0); case '1': return(XK_1); 
	  case '2': return(XK_2); case '3': return(XK_3);
	  case '4': return(XK_4); case '5': return(XK_5);
	  case '6': return(XK_6); case '7': return(XK_7);
	  case '8': return(XK_8); case '9': return(XK_9);
	  default:  return(XK_0); /* Shouldn't happen */
	  } /* End switch */
     } /* End if */
     
     switch(i){
     case '>':  return(XK_greater);   /* Cover likely HTML tag elements 1st */
     case '<':  return(XK_less);
     case '/':  return(XK_slash);
     case ' ':  return(XK_space);
     case '=':  return(XK_equal);
     case '+':  return(XK_plus);
     case '!':  return(XK_exclam);
     case '&':  return(XK_ampersand);
     case '(':  return(XK_parenleft);
     case ')':  return(XK_parenright);
     case '"':  return(XK_quotedbl);
     case '-':  return(XK_minus);
     case '*':  return(XK_asterisk);
     case '.':  return(XK_period);
     case '\b': return(XK_BackSpace);
     case '\t': return(XK_Tab);
     case '\n': return(XK_Return);
     default:   return((KeySym)i);
     } /* End switch */

     /* I have no idea, so we'll just cast it and hope we're right.  :) */
     return((KeySym)i);
} /* End character_to_keysym() */

/* seems that xfree86 computes the timestamps for events like this 
 * strange but it relies on the *1000-32bit-wrap-around     
 * if anybody knows exactly how to do it, please contact me 
 * returns: a timestamp for the 
 * constructed event 
 */
static Time fake_timestamp()
{
     int  tint;
     struct timeval  tv;
     struct timezone tz; /* is not used since ages */
     gettimeofday(&tv, &tz);
     tint = (int)tv.tv_sec * 1000;
     tint = tint/1000 * 1000;
     tint = tint + tv.tv_usec/1000;
     return((Time)tint);
} /* End fake_timestamp() */

/* in: id of the root window name of the target window 
 * returns: id of the target window 
 * called by: main 
 */
Window find_window(Window top,char *name)
{
     char *wname, *iname;
     XClassHint xch;
     Window *children,foo;
     int revert_to_return;
     unsigned int nc;

     if (!strcmp(active_window_name, name))
     {
	  XGetInputFocus(GDK_DISPLAY(), &foo, &revert_to_return);
	  return(foo);
     } /* End if */

     /* First the base case */
     if (XFetchName(GDK_DISPLAY(),top,&wname))
     {
	  if (!strncmp(wname, name, strlen(name)))  
	  {
	       XFree(wname);
	       return(top);  /* found it! */
	  } /* End if */
	  
	  XFree(wname);
     } /* End if */
     
     if(XGetIconName(GDK_DISPLAY(),top,&iname))
     {
	  if (!strncmp(iname,name,strlen(name)))  
	  {
	       XFree(iname);
	       return(top);  /* found it! */
	  } /* End if */
	  XFree(iname);
     } /* End if */

     if(XGetClassHint(GDK_DISPLAY(),top,&xch))  
     {
	  if(!strcmp(xch.res_class,name))  
	  {
	       XFree(xch.res_name); XFree(xch.res_class);
	       return(top);  /* found it! */
	  } /* End if */
	  XFree(xch.res_name); XFree(xch.res_class);
     } /* End if */
     
     if(!XQueryTree(GDK_DISPLAY(), top, &foo, &foo, &children, &nc) || 
	children == NULL) 
	  return(0);  /* no more windows here */
     
     /* check all the sub windows */
     for(;nc>0; nc--)  
     {
	  top = find_window(children[nc-1],name);
	  if(top) break;  /* we found it somewhere */
     } /* End for */

     /* Free that mem!  Yeehaw!!! */
     if(children) 
	  XFree(children);

     return(top);
} /* End find_window() */

/* This just assigns a bunch of things to certain elements of the pointer 
 * that are shared no matter what type of signal GTKeyboard is sending. 
 * Prevents code duplication in ugly places
 */
void gtkeyboard_XEvent_common_setup(XKeyEvent *xev)
{
     xev->type         = KeyPress;
     xev->display      = GDK_DISPLAY();
     xev->root         = root;
     xev->subwindow    = None;
     xev->time         = fake_timestamp();
     xev->same_screen  = True;
     xev->state        = 0;
     xev->x = xev->y = xev->x_root = xev->y_root = 1;
} /* End gtkeyboard_XEvent_common_setup */

static int assign_keycode_from_keysym(KeySym foo, XKeyEvent *xev)
{
     unsigned long mask = 0;
     char *keyname = (char *)NULL;

     keyname = XKeysymToString(foo);
     xev->keycode = XKeysymToKeycode(GDK_DISPLAY(), foo);

     /* Check and assign masks. */
     if(options.SHIFT)  /* Need ShiftMask? */
     {
          mask = find_modifier_mask(XKeysymToKeycode(GDK_DISPLAY(),
                                                     XK_Shift_L));
          if(!mask)
          {
               /* WTF?  Shift_L isn't mapped?  OK, try Shift_R */
               mask = find_modifier_mask(XKeysymToKeycode(GDK_DISPLAY(),
                                                          XK_Shift_R));
          } /* End if */

          fprintf(Q,"Shift mask: 0x%lx normal mask 0x%lx\n", mask, 
                  (unsigned long)ShiftMask);

          /* Even if mask is actually 0 because we couldn't find the shift
           * key mapped, this just won't do anything at all.  
           * find_modifier_mask issued a warning about not finding it, too
           */
	  xev->state |= mask;
	  options.SHIFT = 0;
     } /* End if */

     if(options.CAPS_LOCK) /* Need LockMask? */
     {
          mask = find_modifier_mask(XKeysymToKeycode(GDK_DISPLAY(),
                                                     XK_Caps_Lock));
          fprintf(Q,"Capslock mask: 0x%lx normal mask 0x%lx\n",
                  mask, (unsigned long)LockMask);
	  xev->state |= mask;   /* Normally LockMask */
     } /* End if */

     if(options.CONTROL)   /* Need ControlMask? */
     {
          mask = find_modifier_mask(XKeysymToKeycode(GDK_DISPLAY(),
                                                     XK_Control_L));
          if(!mask)
          {
               mask = find_modifier_mask(XKeysymToKeycode(GDK_DISPLAY(),
                                                          XK_Control_R));
          } /* End if */

          fprintf(Q,"Control mask: 0x%lx normal mask 0x%lx\n",
                  mask, (unsigned long)ControlMask);
	  xev->state |= mask;
     } /* End if */

     if(options.ALT)       /* Need Mod1Mask? */
     {
          mask = find_modifier_mask(XKeysymToKeycode(GDK_DISPLAY(),
                                                     XK_Alt_L));
          if(!mask)
          {
               mask = find_modifier_mask(XKeysymToKeycode(GDK_DISPLAY(),
                                                          XK_Alt_R));
          } /* End if */

          fprintf(Q,"Alt mask: 0x%lx normal mask 0x%lx\n",
                  mask, (unsigned long)Mod1Mask);
	  xev->state |= mask;
     } /* End if */

     if(options.NUMLOCK) 
     {
          mask = find_modifier_mask(XKeysymToKeycode(GDK_DISPLAY(), 
                                                     XK_Num_Lock));
          fprintf(Q,"Numlock: Mask 0x%lx\n", mask);
	  xev->state |= mask;
     } /* End if */

     if(options.ALT_GR) 
     {
          mask = find_modifier_mask(XKeysymToKeycode(GDK_DISPLAY(), 
                                                     XK_Mode_switch));
          fprintf(Q,"Alt_GR: Mask 0x%lx\n", mask);
          xev->state |= mask;
     } /* End if */
    
     if(strstr(keyname, "Cyrillic_"))   /* Cyrillic? */
     {
          mask = find_modifier_mask(XKeysymToKeycode(GDK_DISPLAY(),
                                                     XK_ISO_Group_Shift));
          /* FIXME: How do we get this mask and what does it mean?
           * Seems to be XKB_Group.  Default 0x2000
           */
          
          xev->state |= mask;
     } /* End if */

     /* xev->state = xev->state | ButtonMotionMask; */

     fprintf(Q,"Final mask on event: %ld 0x%lx\n",
             (unsigned long)xev->state, (unsigned long)xev->state);

     if(xev->keycode != 0)
	  return 1;

     else return 0;
} /* End assign_keycode_from_keysym() */

void keysym_sendkey(KeySym somesym, Window w)
{
     gtkeyboard_XEvent_common_setup((XKeyEvent *)&xev);

     /* assign_keycode_from_keysym() will also add in the needed
      * masks.  WARNING:  This may change options.SHIFT and other 
      * bitflags in options according to whether or not they should
      * change. 
      */
     if(!assign_keycode_from_keysym(somesym, (XKeyEvent *)&xev))
     {
	  fprintf(stderr,"keysym_sendkey() Error: 0 KeyCode from (%ld,'%s')\n",
		  somesym, XKeysymToString(somesym));
	  fflush(stderr);
	  
	  chocolate(zero_keycode);
	  annoying_popup(zero_keycode);
	  return;
     } /* End if */

     xev.xkey.window = w;

     /* This may produce a BadWindow error with Xlib.  Bummer.
      * This happens most commonly when the window that was selected to
      * redirect to doesn't exist on screen anymore.
      */
     gdk_error_trap_push();      /* Catch errors, hopefully */
     
     XSendEvent(GDK_DISPLAY(), w, True, KeyPressMask, &xev);

     xev.type = KeyRelease;      /* Start the next Event */
     /* usleep(50000);	*/
     XFlush(GDK_DISPLAY());
     xev.xkey.time = fake_timestamp();
     XSendEvent(GDK_DISPLAY(),w,True,KeyReleaseMask,&xev);
     XSync(GDK_DISPLAY(),True);

     gdk_flush();

     if(gdk_error_trap_pop())
     {
	  gtkeyboard_error(2, "Can't send key to foreign window!\n",
			   "Is your redirect window valid?\n");
     } /* End if */

     return;
} /* End keysym_sendkey() */
