/**************************************************************************
                          xmgr.cpp  -  description
                             -------------------
    begin                : Tue Feb 18 2003
    copyright            : (C) 2003 by Sheldon Lee Wen
    email                : leewsb@hotmail.com
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#include <lineak/xmgr.h>
#include <lineak/lkbd.h>
#include <lineak/lkey.h>
#include <lineak/lbutton.h>

extern "C" {
   #include <signal.h>
   #include <X11/Xlib.h>
   #include <X11/keysym.h>
}

#include <lineakd_core_functions.h>
#include <lineak/lineak_core_functions.h>

using namespace lineak_core_functions;
extern bool verbose;

bool Xmgr::caughtXerror = false;
bool Xmgr::xkbremapped = false;
unsigned int Xmgr::numlock_mask = 0;
unsigned int Xmgr::scrolllock_mask = 0;
unsigned int Xmgr::capslock_mask = 0;


Xmgr::Xmgr(string display) : displayname(display) {
  mask = 0L;

  screen = DefaultScreen (display.c_str());
  mask |= KeyPressMask | KeyReleaseMask | PointerMotionMask;

  if (! (ourdisplay = XOpenDisplay (displayname.c_str())) ) {
        error("Could not open the display. You should exit!");
  }
  win = DefaultRootWindow (ourdisplay);
  XSetErrorHandler(&Xhandler);
  getModifiers();
  XDisplayKeycodes (ourdisplay, &min, &max);
}

Xmgr::~Xmgr(){
}
/** Did we sucessfully initialize the display and xkbd? */
bool Xmgr::initialized() {
	if (!ourdisplay)
		return false;
	return true;
}
/* X error handler. When a BadValue error occurs during XkbSetMap, we want to retry */
/* Right now we are just ignoring errors */
int Xmgr::Xhandler(Display* theDisplay, XErrorEvent* xev) {
        //if (( (int)(xev->request_code) ==150 || (int)(xev->request_code) ==149) && (int)(xev->minor_code) ==9) {
    	   caughtXerror = true;
           cerr << "*** Xlib error caught ***" << endl;
	   cerr << "Major opcode of failed request: " << (int)(xev->request_code) << " (XKEYBOARD)" << endl;
	   cerr << "Minor opcode of failed request: " << (int)(xev->minor_code) << " (XkbSetMap)" << endl;
	   cerr << "Resource ID of failed request: " << xev->resourceid << endl;
           cerr << "Serial number of failed request: " << xev->serial << endl;
           cerr << "Error code: " << (int)(xev->error_code) << endl;
           cerr << "Type: " << xev->type << endl;
	   cerr << "Maybe we should exit now?" << endl;
        //}
  	return 0;
}
/* Get the modifier masks from the system */
bool Xmgr::getModifiers ()
{
   if (ourdisplay) {
        msg("Getting modifiers for display.");
  	XModifierKeymap *modmap;
  	KeyCode nlock, slock;
	static int mask_table[8] = {
	    ShiftMask, LockMask, ControlMask, Mod1Mask,
	    Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask
	};

	nlock = XKeysymToKeycode (ourdisplay, XK_Num_Lock);
	slock = XKeysymToKeycode (ourdisplay, XK_Scroll_Lock);

	/*
	 *    * Find out the masks for the NumLock and ScrollLock modifiers,
	 *       * so that we can bind the grabs for when they are enabled too.
	 *          */
    	modmap = XGetModifierMapping (ourdisplay);

        if (modmap != NULL && modmap->max_keypermod > 0)
	{
	   for (int i = 0; i < 8 * modmap->max_keypermod; i++)
	   {
	      if (modmap->modifiermap[i] == nlock && nlock != 0)
	          numlock_mask = mask_table[i / modmap->max_keypermod];
	      else if (modmap->modifiermap[i] == slock && slock != 0)
	          scrolllock_mask = mask_table[i / modmap->max_keypermod];
	   }
	}

	capslock_mask = LockMask;
        msg(string("numlock_mask = " + numlock_mask));
	msg(string("scrolllock_mask = " + scrolllock_mask));
	msg(string("capslock_mask = " + capslock_mask));
	
        if (modmap)
	   XFreeModifiermap (modmap);

	return true;
   }
   return false;
}

/* initialize X (xkb) so it understands the extra keys */
void Xmgr::cleanup(LKbd& myKbd) {
  if (ourdisplay) {

	vector<int> keycodes = myKbd.getKeyCodes();

  	/* Go through all the keycodes and ungrab them. */
   	for (int i=0; i != (int)keycodes.size(); ) {
		int keycode = keycodes[i];
    	   /* Ungrab the key */
           XUngrabKey(ourdisplay, keycode, AnyModifier, DefaultRootWindow(ourdisplay));
           i++;
  	}
	vector<unsigned int> buttoncodes = myKbd.getButtonCodes();

  	/* Go through all the buttons and ungrab them. */
   	for (int i=0; i != (int)buttoncodes.size(); ) {
		unsigned int buttoncode = buttoncodes[i];

    	   /* Ungrab the button */
           XUngrabButton(ourdisplay, buttoncode, AnyModifier, DefaultRootWindow(ourdisplay));
           i++;
  	}
  }
}
/* initialize X (xkb) so it understands the extra keys */
bool Xmgr::initialize(LKbd& myKbd) {
   if (ourdisplay) {
   	//getModifiers();
	
        XAllowEvents(ourdisplay, AsyncKeyboard, CurrentTime);
        XSelectInput(ourdisplay,DefaultRootWindow(ourdisplay),mask);
//        vector<int> keycodes = myKbd.getKeyCodes();
	vector<string> keys = myKbd.getNames();
        for (vector<string>::iterator it = keys.begin(); it != keys.end(); it++) {
           LObject *obj = myKbd.getObject(*it); 
           if (obj->getType() == SYM)
           {
	      LKey *key = static_cast<LKey*>(obj);
              grabKey (key);
           }
           else if (obj->getType() == BUTTON)
           {
	      LButton *btn = static_cast<LButton*>(obj);
              grabButton (btn);
           }
           else
           {  // It is either a key of type CODE or nothing we know.
	      LKey *key = static_cast<LKey*>(obj);
              if ((key->getKeyCode()) >= min && (key->getKeyCode()) <= max)
              {
                 grabKey (key);
              }
              else
              {
                 cerr << "--- X manager initialization error ---" << endl;

                 if (verbose)
                 {
                    cout << *key;
                 }

                 fprintf (stderr,
                       "  The keycode %d cannot be used, as it's not between the\n"
                       "  min(%d) and max(%d) keycode of your keyboard.\n"
                       "  Please increase the 'maximum' value in\n"
                       "    /usr/X11R6/lib/X11/xkb/keycodes/xfree86,\n"
                       "  then restart X.\n", key->getKeyCode(), min, max);
                 exit (false);
              }
           }
        }
    //*/	
  	/* add our new keysyms to the key codes of our display */
/*
      for (vector<int>::iterator it = keycodes.begin() ; it != keycodes.end();it++) {
         int keycode = *it;
         if (verbose) cout << "Mapping keycode: " << keycode << endl;
         XGrabKey(ourdisplay,keycode,AnyModifier,DefaultRootWindow(ourdisplay),0,GrabModeAsync,GrabModeAsync);
	 // TODO: Grab the modifier combinations too.
         int i = 0;
         while (caughtXerror && i != 10) {
            caughtXerror = false;
            XGrabKey(ourdisplay,keycode,AnyModifier,DefaultRootWindow(ourdisplay),0,GrabModeAsync,GrabModeAsync);
            i++;
         }
      }
*/
   }
   else
        return false;

 return true;
}

void Xmgr::grabKey (LKey* key)
{
  KeyCode keycode = key->getKeyCode();
  unsigned int modifier = 0; // = key->getModifier();

  if (verbose)
     cout << "grabKey for " << key->getName() << endl;

  /** Experimental */
  vector<unsigned int> mods = key->getModifiers();
  for (unsigned int i=0; i < mods.size(); i++) {
     if (verbose) cout << "mods[" << i << "] = " << mods[i] << endl;
     modifier = mods[i];
     /** End experimental */
     modifier &= ~(numlock_mask | capslock_mask | scrolllock_mask);
     if (verbose) cout << "modifier = " << modifier << endl;

     XGrabKey (ourdisplay, keycode, modifier, (win ? win : DefaultRootWindow (ourdisplay)),
            False, GrabModeAsync, GrabModeAsync);

     if (modifier == AnyModifier)
       return;

     if (numlock_mask)
        XGrabKey (ourdisplay, keycode, modifier | numlock_mask,
              (win ? win : DefaultRootWindow (ourdisplay)),
              False, GrabModeAsync, GrabModeAsync);

     if (capslock_mask)
        XGrabKey (ourdisplay, keycode, modifier | capslock_mask,
              (win ? win : DefaultRootWindow (ourdisplay)),
              False, GrabModeAsync, GrabModeAsync);

     if (scrolllock_mask)
        XGrabKey (ourdisplay, keycode, modifier | scrolllock_mask,
              (win ? win : DefaultRootWindow (ourdisplay)),
              False, GrabModeAsync, GrabModeAsync);

     if (numlock_mask && capslock_mask)
        XGrabKey (ourdisplay, keycode, modifier | numlock_mask | capslock_mask,
              (win ? win : DefaultRootWindow (ourdisplay)),
              False, GrabModeAsync, GrabModeAsync);

     if (numlock_mask && scrolllock_mask)
        XGrabKey (ourdisplay, keycode, modifier | numlock_mask | scrolllock_mask,
              (win ? win : DefaultRootWindow (ourdisplay)),
              False, GrabModeAsync, GrabModeAsync);

     if (capslock_mask && scrolllock_mask)
        XGrabKey (ourdisplay, keycode, modifier | capslock_mask | scrolllock_mask,
              (win ? win : DefaultRootWindow (ourdisplay)),
              False, GrabModeAsync, GrabModeAsync);

     if (numlock_mask && capslock_mask && scrolllock_mask)
        XGrabKey (ourdisplay, keycode,
              modifier | numlock_mask | capslock_mask | scrolllock_mask,
              (win ? win : DefaultRootWindow (ourdisplay)), False, GrabModeAsync,
              GrabModeAsync);
   }
}
void Xmgr::grabButton (LButton* key)
{
  unsigned int button = key->getButton();
  unsigned int modifier = 0; // = key->getModifier();
  vector<unsigned int> mods = key->getModifiers();
  
  if (verbose)
     cout << "grabButton for " << key->getName() << endl;

  for (unsigned int i=0; i < mods.size(); i++) {
        if (verbose) cout << "mods[" << i << "] = " << mods[i] << endl;
        modifier &= mods[i];
	modifier &= ~(numlock_mask | capslock_mask | scrolllock_mask);
        if (verbose) cout << "modifier = " << modifier << endl;
	
	XGrabButton (ourdisplay, button, modifier, (win ? win : DefaultRootWindow (ourdisplay)),
		False, ButtonPressMask | ButtonReleaseMask,
		GrabModeAsync, GrabModeAsync, None, None);
	
	if (modifier == AnyModifier)
	return;
	
	if (numlock_mask)
	XGrabButton (ourdisplay, button, modifier | numlock_mask,
			(win ? win : DefaultRootWindow (ourdisplay)),
			False, ButtonPressMask | ButtonReleaseMask,
			GrabModeAsync, GrabModeAsync, None, None);
	
	
	if (capslock_mask)
	XGrabButton (ourdisplay, button, modifier | capslock_mask,
			(win ? win : DefaultRootWindow (ourdisplay)),
			False, ButtonPressMask | ButtonReleaseMask,
			GrabModeAsync, GrabModeAsync, None, None);
	
	if (scrolllock_mask)
	XGrabButton (ourdisplay, button, modifier | scrolllock_mask,
			(win ? win : DefaultRootWindow (ourdisplay)),
			False, ButtonPressMask | ButtonReleaseMask,
			GrabModeAsync, GrabModeAsync, None, None);
	
	if (numlock_mask && capslock_mask)
	XGrabButton (ourdisplay, button, modifier | numlock_mask | capslock_mask,
			(win ? win : DefaultRootWindow (ourdisplay)),
			False, ButtonPressMask | ButtonReleaseMask,
			GrabModeAsync, GrabModeAsync, None, None);
	
	if (numlock_mask && scrolllock_mask)
	XGrabButton (ourdisplay, button, modifier | numlock_mask | scrolllock_mask,
			(win ? win : DefaultRootWindow (ourdisplay)),
			False, ButtonPressMask | ButtonReleaseMask,
			GrabModeAsync, GrabModeAsync, None, None);
	
	if (capslock_mask && scrolllock_mask)
	XGrabButton (ourdisplay, button, modifier | capslock_mask | scrolllock_mask,
			(win ? win : DefaultRootWindow (ourdisplay)),
			False, ButtonPressMask | ButtonReleaseMask,
			GrabModeAsync, GrabModeAsync, None, None);
	
	if (numlock_mask && capslock_mask && scrolllock_mask)
	XGrabButton (ourdisplay, button,
			modifier | numlock_mask | capslock_mask | scrolllock_mask,
			(win ? win : DefaultRootWindow (ourdisplay)), False,
			ButtonPressMask | ButtonReleaseMask, GrabModeAsync,
			GrabModeAsync, None, None);
   }
}

