/**
 * @file key.c
 * @author Joe Wingbermuehle
 * @date 2004-2006
 *
 * @brief Key binding functions.
 *
 */

#include "jwm.h"
#include "key.h"

#include "client.h"
#include "clientlist.h"
#include "command.h"
#include "error.h"
#include "main.h"
#include "misc.h"
#include "root.h"
#include "tray.h"

typedef enum {
   MASK_NONE  = 0,
   MASK_ALT   = 1,
   MASK_CTRL  = 2,
   MASK_SHIFT = 4,
   MASK_HYPER = 8,
   MASK_META  = 16,
   MASK_SUPER = 32
} MaskType;

typedef struct ModifierNode {
   char name;
   MaskType mask;
   KeySym symbol1;
   KeySym symbol2;
} ModifierNode;

static ModifierNode modifiers[] = {

   { 'A',   MASK_ALT,   XK_Alt_L,      XK_Alt_R       },
   { 'C',   MASK_CTRL,  XK_Control_L,  XK_Control_R   },
   { 'S',   MASK_SHIFT, XK_Shift_L,    XK_Shift_R     },
   { 'H',   MASK_HYPER, XK_Hyper_L,    XK_Hyper_R     },
   { 'M',   MASK_META,  XK_Meta_L,     XK_Meta_R      },
   { 'P',   MASK_SUPER, XK_Super_L,    XK_Super_R     },
   { 0,     MASK_NONE,  XK_Shift_L,    XK_Shift_R     }

};

typedef struct KeyNode {

   /* These are filled in when the configuration file is parsed */
   int key;
   unsigned int mask;
   KeySym symbol;
   char *command;
   struct KeyNode *next;

   /* This is filled in by StartupKeys if it isn't already set. */
   KeyCode code;

   /* This is filled in by StartupKeys. */
   unsigned int state;

} KeyNode;

typedef struct LockNode {
   KeySym symbol;
   unsigned int mask;
} LockNode;

static XModifierKeymap *modmap;

static LockNode mods[] = {
   { XK_Caps_Lock,   0 },
   { XK_Num_Lock,    0 }
};

static KeyNode *bindings;
static unsigned int modifierMask;

static unsigned int GetModifierMask(KeySym key);
static unsigned int ParseModifierString(const char *str);
static ModifierNode *GetKeyFromModifier(const char *str);
static KeySym ParseKeyString(const char *str);
static int ShouldGrab(KeyType key);
static void GrabKey(KeyNode *np);

/** Initialize key data. */
void InitializeKeys() {

   bindings = NULL;
   modifierMask = 0;

}

/** Startup key bindings. */
void StartupKeys() {

   KeyNode *np;
   int x;

   modmap = JXGetModifierMapping(display);

   for(x = 0; x < sizeof(mods) / sizeof(mods[0]); x++) {
      mods[x].mask = GetModifierMask(mods[x].symbol);
      modifierMask |= mods[x].mask;
   }

   for(np = bindings; np; np = np->next) {

      np->state = 0;
      if(np->mask & MASK_ALT) {
         np->state |= GetModifierMask(XK_Alt_L);
      }
      if(np->mask & MASK_CTRL) {
         np->state |= GetModifierMask(XK_Control_L);
      }
      if(np->mask & MASK_SHIFT) {
         np->state |= GetModifierMask(XK_Shift_L);
      }
      if(np->mask & MASK_HYPER) {
         np->state |= GetModifierMask(XK_Hyper_L);
      }
      if(np->mask & MASK_META) {
         np->state |= GetModifierMask(XK_Meta_L);
      }
      if(np->mask & MASK_SUPER) {
         np->state |= GetModifierMask(XK_Super_L);
      }

      if(!np->code) {
         np->code = JXKeysymToKeycode(display, np->symbol);
      }

      if(ShouldGrab(np->key)) {
         GrabKey(np);
      }

   }

   JXFreeModifiermap(modmap);

}

/** Shutdown key bindings. */
void ShutdownKeys() {

   ClientNode *np;
   int layer;

   for(layer = 0; layer < LAYER_COUNT; layer++) {
      for(np = nodes[layer]; np; np = np->next) {
         JXUngrabKey(display, AnyKey, AnyModifier, np->window);
      }
   }

   JXUngrabKey(display, AnyKey, AnyModifier, rootWindow);

}

/** Destroy key data. */
void DestroyKeys() {

   KeyNode *np;

   while(bindings) {
      np = bindings->next;
      if(bindings->command) {
         Release(bindings->command);
      }
      Release(bindings);
      bindings = np;
   }

}

/** Grab a key. */
void GrabKey(KeyNode *np) {

   TrayType *tp;
   int x;
   int index, maxIndex;
   unsigned int mask;

   /* Don't attempt to grab if there is nothing to grab. */
   if(!np->code) {
      return;
   }

   maxIndex = 1 << (sizeof(mods) / sizeof(mods[0]));
   for(index = 0; index < maxIndex; index++) {

      mask = 0;
      for(x = 0; x < sizeof(mods) / sizeof(mods[0]); x++) {
         if(index & (1 << x)) {
            mask |= mods[x].mask;
         }
      }

      mask |= np->state;
      JXGrabKey(display, np->code, mask,
         rootWindow, True, GrabModeAsync, GrabModeAsync);
      for(tp = GetTrays(); tp; tp = tp->next) {
         JXGrabKey(display, np->code, mask,
            tp->window, True, GrabModeAsync, GrabModeAsync);
      }

   }

}

/** Get the key action from an event. */
KeyType GetKey(const XKeyEvent *event) {

   KeyNode *np;
   unsigned int state;

   /* Remove modifiers we don't care about from the state. */
   state = event->state & ~modifierMask;

   /* Loop looking for a matching key binding. */
   for(np = bindings; np; np = np->next) {

      /* We need to treat KeyRelease events on modifiers with a
       * special case since the state and keycode members will have
       * the key set despite the fact we grabbed with no state. */
      if(np->key == KEY_NEXTSTACK_END) {
         if(np->code == event->keycode) {
            return np->key;
         }
      } else {
         if(np->state == state && np->code == event->keycode) {
            return np->key;
         }
      }

   }

   return KEY_NONE;

}

/** Run a command invoked from a key binding. */
void RunKeyCommand(const XKeyEvent *event) {

   KeyNode *np;

   for(np = bindings; np; np = np->next) {
      if(np->state == event->state && np->code == event->keycode) {
         RunCommand(np->command);
         return;
      }
   }

}

/** Show a root menu caused by a key binding. */
void ShowKeyMenu(const XKeyEvent *event) {

   KeyNode *np;
   int button;

   for(np = bindings; np; np = np->next) {
      if(np->state == event->state && np->code == event->keycode) {
         button = atoi(np->command);
         if(button >= 0 && button <= 9) {
            ShowRootMenu(button, 0, 0);
         }
         return;
      }
   }

}

/** Determine if a key should be grabbed on client windows. */
int ShouldGrab(KeyType key) {
   switch(key & 0xFF) {
   case KEY_NEXT:
   case KEY_NEXTSTACK:
   case KEY_NEXTSTACK_END:
   case KEY_CLOSE:
   case KEY_MIN:
   case KEY_MAX:
   case KEY_SHADE:
   case KEY_MOVE:
   case KEY_RESIZE:
   case KEY_ROOT:
   case KEY_WIN:
   case KEY_DESKTOP:
   case KEY_EXEC:
   case KEY_RESTART:
   case KEY_EXIT:
      return 1;
   default:
      return 0;
   }
}

/** Grab keys on a client window. */
void GrabKeys(ClientNode *np) {

   KeyNode *kp;

   for(kp = bindings; kp; kp = kp->next) {
      if(ShouldGrab(kp->key) && kp->code) {
         JXGrabKey(display, kp->code, kp->state,
            np->window, True, GrabModeAsync, GrabModeAsync);
      }
   }

}

/** Get the modifier mask for a key. */
unsigned int GetModifierMask(KeySym key) {

   KeyCode temp;
   int x;

   temp = JXKeysymToKeycode(display, key);
   for(x = 0; x < 8 * modmap->max_keypermod; x++) {
      if(modmap->modifiermap[x] == temp) {
         return 1 << (x / modmap->max_keypermod);
      }
   }

   Warning("modifier not found for keysym 0x%0x", key);

   return 0;

}

/** Parse a modifier mask string. */
unsigned int ParseModifierString(const char *str) {

   unsigned int mask;
   int x, y;
   int found;

   if(!str) {
      return MASK_NONE;
   }

   mask = MASK_NONE;
   for(x = 0; str[x]; x++) {

      found = 0;
      for(y = 0; modifiers[y].name; y++) {
         if(modifiers[y].name == str[x]) {
            mask |= modifiers[y].mask;
            found = 1;
            break;
         }
      }

      if(!found) {
         Warning("invalid modifier: \"%c\"", str[x]);
      }

   }

   return mask;

}

/** Get the key from a modifier character. */
ModifierNode *GetKeyFromModifier(const char *str) {

   int x, y;

   if(str == NULL) {
      return NULL;
   }

   for(x = 0; str[x]; x++) {

      for(y = 0; modifiers[y].name; y++) {
         if(modifiers[y].name == str[x]) {
            return &modifiers[y];
         }
      }

   }

   return NULL;

}

/** Parse a key string. */
KeySym ParseKeyString(const char *str) {

   KeySym symbol;

   symbol = JXStringToKeysym(str);
   if(symbol == NoSymbol) {
      Warning("invalid key symbol: \"%s\"", str);
   }

   return symbol;

}

/** Insert a key binding. */
void InsertBinding(KeyType key, const char *modifiers,
   const char *stroke, const char *code, const char *command) {

   KeyNode *np;
   ModifierNode *mp;
   unsigned int mask;
   char *temp;
   int offset;
   KeySym sym;

   mask = ParseModifierString(modifiers);

   if(stroke && strlen(stroke) > 0) {

      for(offset = 0; stroke[offset]; offset++) {
         if(stroke[offset] == '#') {

            temp = CopyString(stroke);

            for(temp[offset] = '1'; temp[offset] <= '9'; temp[offset]++) {

               sym = ParseKeyString(temp);
               if(sym == NoSymbol) {
                  Release(temp);
                  return;
               }

               np = Allocate(sizeof(KeyNode));
               np->next = bindings;
               bindings = np;

               np->key = key | ((temp[offset] - '1' + 1) << 8);
               np->mask = mask;
               np->symbol = sym;
               np->command = NULL;
               np->code = 0;

            }

            Release(temp);

            return;
         }
      }

      sym = ParseKeyString(stroke);
      if(sym == NoSymbol) {
         return;
      }

      np = Allocate(sizeof(KeyNode));
      np->next = bindings;
      bindings = np;

      np->key = key;
      np->mask = mask;
      np->symbol = sym;
      np->command = CopyString(command);
      np->code = 0;

   } else if(code && strlen(code) > 0) {

      np = Allocate(sizeof(KeyNode));
      np->next = bindings;
      bindings = np;

      np->key = key;
      np->mask = mask;
      np->symbol = NoSymbol;
      np->command = CopyString(command);
      np->code = atoi(code);

   } else {

      Warning("neither key nor keycode specified for Key");
      np = NULL;

   }

   /* KEY_NEXTSTACK is special in that we need to know when the user
    * is finished walking the window stack. To do this we watch for
    * the release of a modifier key used with the KEY_NEXTSTACK binding
    * and report it as KEY_NEXTSTACK_END. If no modifier is specified
    * for KEY_NEXTSTACK we use KEY_NEXT instead and warn the user.
    */
   if(np != NULL && key == KEY_NEXTSTACK) {

      mp = GetKeyFromModifier(modifiers);
      if(!mp) {

         Warning("no valid modifier specified for \"nextstacked\"");
         np->key = KEY_NEXT;

      } else {

         /* Create a node for the first modifier. */
         np = Allocate(sizeof(KeyNode));
         np->next = bindings;
         bindings = np;

         np->key = KEY_NEXTSTACK_END;
         np->mask = 0;
         np->symbol = mp->symbol1;
         np->command = NULL;
         np->code = 0;

         /* Create a node for the second modifier. */
         np = Allocate(sizeof(KeyNode));
         np->next = bindings;
         bindings = np;

         np->key = KEY_NEXTSTACK_END;
         np->mask = 0;
         np->symbol = mp->symbol2;
         np->command = NULL;
         np->code = 0;


      }

   }

}

/** Validate key bindings. */
void ValidateKeys() {

   KeyNode *kp;
   int bindex;

   for(kp = bindings; kp; kp = kp->next) {
      if((kp->key & 0xFF) == KEY_ROOT && kp->command) {
         bindex = atoi(kp->command);
         if(!IsRootMenuDefined(bindex)) {
            Warning("key binding: root menu %d not defined", bindex);
         }
      }
   }

}



syntax highlighted by Code2HTML, v. 0.9.1