/* -*- mode:C; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
 * libFakeKey
 *
 * A simple library for converting utf8 chars into 'fake' keypresses.
 *
 * Uses ideas from Fontconfig, libvirtkeys.c, keysym2ucs.c and dasher. 
 *
 * Authored By Matthew Allum  <amllum@openedhand.com>
 *
 * Copyright (C) 2004 OpenedHand
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser 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.
 */


#include <fakekey/fakekey.h>

#if HAVE_CONFIG_H
#include "config.h"
#endif

#if (WANT_DEBUG)
#define DBG(x, a...) \
 fprintf (stderr,  __FILE__ ":%d,%s() " x "\n", __LINE__, __func__, ##a)
#else
#define DBG(x, a...) do {} while (0)
#endif

#define MARK() DBG("mark")

#define N_MODIFIER_INDEXES (Mod5MapIndex + 1)

typedef unsigned int FkChar32;

struct FakeKey
{
  Display *xdpy;
  int      min_keycode, max_keycode;
  int      n_keysyms_per_keycode;
  KeySym  *keysyms;
  int      held_keycode;
  int      held_state_flags;
  KeyCode  modifier_table[N_MODIFIER_INDEXES];
  int      shift_mod_index, alt_mod_index, meta_mod_index;
};


/* utf8_to_ucs4() Borrowed from fontconfig 
 *
 * Converts the next Unicode char from src into dst and returns the 
 * number of bytes containing the char. src nust be at least len bytes 
 * long.
 */
static int 
utf8_to_ucs4 (const unsigned char *src_orig,
	      FkChar32	          *dst,
	      int	           len)
{
    const unsigned char *src = src_orig;
    unsigned char 	 s;
    int		         extra;
    FkChar32	         result;

    if (len == 0)
	return 0;
    
    s = *src++;
    len--;
    
    if (!(s & 0x80))
    {
	result = s;
	extra = 0;
    } 
    else if (!(s & 0x40))
    {
	return -1;
    }
    else if (!(s & 0x20))
    {
	result = s & 0x1f;
	extra = 1;
    }
    else if (!(s & 0x10))
    {
	result = s & 0xf;
	extra = 2;
    }
    else if (!(s & 0x08))
    {
	result = s & 0x07;
	extra = 3;
    }
    else if (!(s & 0x04))
    {
	result = s & 0x03;
	extra = 4;
    }
    else if ( ! (s & 0x02))
    {
	result = s & 0x01;
	extra = 5;
    }
    else
    {
	return -1;
    }
    if (extra > len)
	return -1;
    
    while (extra--)
    {
	result <<= 6;
	s = *src++;
	
	if ((s & 0xc0) != 0x80)
	    return -1;
	
	result |= s & 0x3f;
    }
    *dst = result;
    return src - src_orig;
}

FakeKey*
fakekey_init(Display *xdpy)
{
  FakeKey         *fk = NULL;
  int              event, error, major, minor;
  XModifierKeymap *modifiers;
  int              mod_index;
  int              mod_key;
  KeyCode         *kp;
  
  if (xdpy == NULL) return NULL;
  
  if (!XTestQueryExtension(xdpy, &event, &error, &major, &minor))
    {
      return NULL;
    }

  fk = malloc(sizeof(FakeKey));
  memset(fk,0,sizeof(FakeKey));

  fk->xdpy = xdpy;

  /* Find keycode limits */

  XDisplayKeycodes(fk->xdpy, &fk->min_keycode, &fk->max_keycode);

  /* Get the mapping  */

  /* TODO: Below needs to be kept in sync with anything else  	 
   *       that may change the keyboard mapping. 
   *
   *   case MappingNotify:
   *    XRefreshKeyboardMapping(&ev.xmapping);
   *
   */

  fk->keysyms = XGetKeyboardMapping(fk->xdpy, 
				    fk->min_keycode, 
				    fk->max_keycode - fk->min_keycode + 1, 
				    &fk->n_keysyms_per_keycode);


  modifiers = XGetModifierMapping(fk->xdpy);

  kp = modifiers->modifiermap;    

  for (mod_index = 0; mod_index < 8; mod_index++)
    {
      fk->modifier_table[mod_index] = 0;
      
      for (mod_key = 0; mod_key < modifiers->max_keypermod; mod_key++)
	{
	  int keycode = kp[mod_index * modifiers->max_keypermod + mod_key]; 
	  
	  if (keycode != 0)
	    {
	      fk->modifier_table[mod_index] = keycode;
	      break;
	    }
	}
    }
  
  for (mod_index = Mod1MapIndex; mod_index <= Mod5MapIndex; mod_index++)
    {
      if (fk->modifier_table[mod_index])
	{
	  KeySym ks = XKeycodeToKeysym(fk->xdpy, 
				       fk->modifier_table[mod_index], 0);
	  
	  /* 
	   *  Note: ControlMapIndex is already defined by xlib
	   *        ShiftMapIndex
	   */
	  
	  switch (ks)
	    {
	    case XK_Meta_R:
	    case XK_Meta_L:
	      fk->meta_mod_index = mod_index;
	      break;
	      
	    case XK_Alt_R:
	    case XK_Alt_L:
	      fk->alt_mod_index = mod_index;
	      break;
	      
	    case XK_Shift_R:
	    case XK_Shift_L:
	      fk->shift_mod_index = mod_index;
	      break;
	    }
	}
    }
  
  if (modifiers)
    XFreeModifiermap(modifiers);

  return fk;
}

int
fakekey_reload_keysyms(FakeKey *fk)
{
  if (fk->keysyms) 
    XFree(fk->keysyms);

  fk->keysyms = XGetKeyboardMapping(fk->xdpy, 
				    fk->min_keycode, 
				    fk->max_keycode - fk->min_keycode + 1, 
				    &fk->n_keysyms_per_keycode);
  return 1;
}

int 
fakekey_send_keyevent(FakeKey *fk, 
		      KeyCode  keycode,
		      Bool     is_press,
		      int      flags)
{
  if (flags)
    {
      if (flags & FAKEKEYMOD_SHIFT)
	XTestFakeKeyEvent(fk->xdpy, fk->modifier_table[ShiftMapIndex], 
			  is_press, CurrentTime);

      if (flags & FAKEKEYMOD_CONTROL)
	XTestFakeKeyEvent(fk->xdpy, fk->modifier_table[ControlMapIndex], 
			  is_press, CurrentTime);

      if (flags & FAKEKEYMOD_ALT)
	XTestFakeKeyEvent(fk->xdpy, fk->modifier_table[fk->alt_mod_index], 
			  is_press, CurrentTime);

      XSync(fk->xdpy, False);
    }

  XTestFakeKeyEvent(fk->xdpy, keycode, is_press, CurrentTime);

  XSync(fk->xdpy, False);

  return 1;
}

int
fakekey_press_keysym(FakeKey *fk, 
		     KeySym   keysym,
		     int      flags)
{
  static int modifiedkey;
  KeyCode    code = 0;

  if ((code = XKeysymToKeycode(fk->xdpy, keysym)) != 0)
    {
      DBG("got keycode, no remap\n");

      /* we already have a keycode for this keysym */
      /* Does it need a shift key though ? */
      if (XKeycodeToKeysym(fk->xdpy, code, 0) != keysym)
	{
	  DBG("does not equal code for index o, needs shift?\n");
	  /* TODO: Assumes 1st modifier is shifted  */
	  if (XKeycodeToKeysym(fk->xdpy, code, 1) == keysym)
	    flags |= FAKEKEYMOD_SHIFT; 	/* can get at it via shift */
	  else
	    code = 0; /* urg, some other modifier do it the heavy way */
	}
      else
        {
          /* the keysym is unshifted; clear the shift flag if it is set */
          flags &= ~FAKEKEYMOD_SHIFT;
	}
    }

  if (!code)
    {
      int index;

      DBG("remapping kbd to get code\n");

      /* Change one of the last 10 keysyms to our converted utf8,
       * remapping the x keyboard on the fly. 
       *
       * This make assumption the last 10 arn't already used.
       * TODO: probably safer to check for this. 
       */

      modifiedkey = (modifiedkey+1) % 10;

      /* Point at the end of keysyms, modifier 0 */

      index = (fk->max_keycode - fk->min_keycode - modifiedkey - 1) * fk->n_keysyms_per_keycode;

      fk->keysyms[index] = keysym;
      
      XChangeKeyboardMapping(fk->xdpy, 
			     fk->min_keycode, 
			     fk->n_keysyms_per_keycode, 
			     fk->keysyms, 
			     (fk->max_keycode-fk->min_keycode));

      XSync(fk->xdpy, False);
	
      /* From dasher src;
       * There's no way whatsoever that this could ever possibly
       * be guaranteed to work (ever), but it does.
       *    
       */

      code = fk->max_keycode - modifiedkey - 1;

      /* The below is lightly safer;
       *
       *  code = XKeysymToKeycode(fk->xdpy, keysym);
       *
       * but this appears to break in that the new mapping is not immediatly 
       * put to work. It would seem a MappingNotify event is needed so
       * Xlib can do some changes internally ? ( xlib is doing something 
       * related to above ? )
       * 
       * Probably better to try and grab the mapping notify *here* ?
       */

      if (XKeycodeToKeysym(fk->xdpy, code, 0) != keysym)
        {
          DBG("does not equal code for index 0, needs shift?\n");
          /* TODO: Assumes 1st modifier is shifted  */
          if (XKeycodeToKeysym(fk->xdpy, code, 1) == keysym)
            flags |= FAKEKEYMOD_SHIFT; 	/* can get at it via shift */
          else
            DBG("attempted to add keycode to keymap but seem to have failed");
        }
    }

  if (code != 0) 
    {
      fakekey_send_keyevent(fk, code, True, flags);

      fk->held_state_flags = flags;
      fk->held_keycode     = code;

      return 1;
    }

  fk->held_state_flags = 0;
  fk->held_keycode     = 0;

  return 0; 			/* failed */
}

int
fakekey_press(FakeKey             *fk, 
	      const unsigned char *utf8_char_in,
	      int                  len_bytes,
	      int                  flags)
{
  FkChar32   ucs4_out;

  if (fk->held_keycode) 	/* key is already held down */
    return 0;

  /* TODO: check for Return key here and other chars */

  if (len_bytes < 0)
    {
      /*
      unsigned char *p = utf8_char_in;
      while (*p != '\0') { len_bytes++; p++; } 
      */
      len_bytes = strlen(utf8_char_in); 	/* OK ? */
    }

  if (utf8_to_ucs4 (utf8_char_in, &ucs4_out, len_bytes) < 1)
    {
      DBG("failed with %i. len is %i\n", 
	     utf8_to_ucs4 (utf8_char_in, &ucs4_out, len_bytes), len_bytes);
      return 0;
    }

    /* first check for Latin-1 characters (1:1 mapping) 
    if ((keysym >= 0x0020 && keysym <= 0x007e) ||
        (keysym >= 0x00a0 && keysym <= 0x00ff))
        return keysym;
    */

  if (ucs4_out > 0x00ff)	       /* < 0xff assume Latin-1 1:1 mapping */
    ucs4_out = ucs4_out | 0x01000000;  /* This gives us the magic X keysym */

  return fakekey_press_keysym(fk, (KeySym)ucs4_out, flags);
}

void
fakekey_repeat(FakeKey *fk)
{
  if (!fk->held_keycode)
    return;

  fakekey_send_keyevent(fk, fk->held_keycode, True, fk->held_state_flags);
}

void
fakekey_release(FakeKey *fk)
{
  if (!fk->held_keycode)
    return;

  fakekey_send_keyevent(fk, fk->held_keycode, False, fk->held_state_flags);

  fk->held_state_flags = 0;
  fk->held_keycode     = 0;
}

